Compare commits
45 Commits
fix/update
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76225ee7e3 | ||
|
|
0dea6487c4 | ||
|
|
dde43ea049 | ||
|
|
747ce88d6d | ||
|
|
8f288290e9 | ||
|
|
4c1cee3c7e | ||
|
|
4d1adfbdd7 | ||
|
|
a0e7ac5ef1 | ||
|
|
1732fb7a5c | ||
|
|
08955054f4 | ||
|
|
675f4ba420 | ||
|
|
76bccdda2d | ||
|
|
3bc73a214a | ||
|
|
388231a52e | ||
|
|
38650966bf | ||
|
|
c94e18bc15 | ||
|
|
620dc9666c | ||
|
|
433c7b06af | ||
|
|
de56f637f2 | ||
|
|
3103831ea1 | ||
|
|
1d8fe51b1e | ||
|
|
81cbbedb53 | ||
|
|
26a27195ca | ||
|
|
3584e2b6d2 | ||
|
|
2afbbd55bf | ||
|
|
457023efd4 | ||
|
|
3e8e39bd85 | ||
|
|
9647ce7fcb | ||
|
|
65a58f8367 | ||
|
|
d17f197c47 | ||
|
|
5f677a283d | ||
|
|
cd4c429ff3 | ||
|
|
526067c585 | ||
|
|
9392ba9ac6 | ||
|
|
561592b0b1 | ||
|
|
2b97c49e03 | ||
|
|
50194a9b8e | ||
|
|
eddb00d4c1 | ||
|
|
95ebfa73f4 | ||
|
|
c110209720 | ||
|
|
7cd25c98bb | ||
|
|
d5fc8e2b07 | ||
|
|
b668498d7d | ||
|
|
90516869c1 | ||
|
|
c718196c26 |
8
.github/workflows/sync-skills.yml
vendored
8
.github/workflows/sync-skills.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sync skills from clawdbot
|
||||
- name: Sync skills from OpenClaw
|
||||
run: go run ./cmd/sync-skills
|
||||
|
||||
- name: Commit & push if changed
|
||||
@ -26,8 +26,8 @@ jobs:
|
||||
echo "No changes"
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "clawdbot-ci"
|
||||
git config user.email "ci@clawdbot"
|
||||
git config user.name "openclaw-ci"
|
||||
git config user.email "ci@openclaw.local"
|
||||
git add -A
|
||||
git commit -m "sync skills from clawdbot"
|
||||
git commit -m "sync skills from openclaw"
|
||||
git push
|
||||
|
||||
4
.github/workflows/update-tools.yml
vendored
4
.github/workflows/update-tools.yml
vendored
@ -31,8 +31,8 @@ jobs:
|
||||
echo "No changes"
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "clawdbot-ci"
|
||||
git config user.email "ci@clawdbot"
|
||||
git config user.name "openclaw-ci"
|
||||
git config user.email "ci@openclaw.local"
|
||||
git add -A
|
||||
git commit -m "update tool releases"
|
||||
git push
|
||||
|
||||
31
AGENTS.md
Normal file
31
AGENTS.md
Normal file
@ -0,0 +1,31 @@
|
||||
# AGENTS.md — nix-openclaw-tools
|
||||
|
||||
This repo packages OpenClaw-adjacent CLI tools and plugin metadata. It is part of the public OpenClaw Nix packaging boundary, not Josh's private machine config.
|
||||
|
||||
## Boundary Model
|
||||
|
||||
- OpenClaw owns product/runtime behavior.
|
||||
- nix-openclaw owns the batteries-included Nix module surface, runtime injection, launchd/systemd wiring, and package-contract checks.
|
||||
- nix-openclaw-tools owns reproducible packages for tool CLIs and the plugin metadata/skills that describe them.
|
||||
- nixos-config and other downstream machine repos should only choose hosts, secrets, accounts, and which plugins are enabled. If a downstream repo has to build a tool, copy a tool package, or hand-wire a tool into an agent PATH, fix this repo or nix-openclaw instead.
|
||||
|
||||
## Packaging Rules
|
||||
|
||||
- Packages must be Garnix-cacheable for supported systems. Do not rely on Homebrew, globally installed Node/Python, Xcode state outside Nix, or runtime `npm`/`npx`/`uvx`.
|
||||
- Prefer upstream release assets when they exist. Build from source only when release assets do not exist or are not suitable.
|
||||
- If a tool is a first-party OpenClaw battery, add/update package, plugin metadata, checks, and auto-update logic together.
|
||||
- QMD belongs here when packaged for Nix OpenClaw. The package should provide the `qmd` CLI; nix-openclaw decides how to inject it into the OpenClaw runtime.
|
||||
- Do not expose tools globally by policy from this repo. Export packages and plugin metadata; let nix-openclaw place them on the correct agent/runtime PATH.
|
||||
|
||||
## Automation
|
||||
|
||||
- `cmd/update-tools` is the release-asset updater. Keep it resilient: one upstream tool changing asset names should not silently rot the whole repo.
|
||||
- QMD freshness should be checked by automation too. Josh should not have to remember a separate QMD bump path.
|
||||
- `cmd/sync-skills` tracks OpenClaw skills from upstream main.
|
||||
- Garnix should build package checks for every supported package/system after update commits.
|
||||
|
||||
## Workflow
|
||||
|
||||
- Trunk-based development on `main`.
|
||||
- Keep commits small and logical.
|
||||
- Run targeted package checks before committing; use CI/Garnix for expensive cross-platform proof.
|
||||
66
README.md
66
README.md
@ -1,36 +1,50 @@
|
||||
# nix-steipete-tools
|
||||
# nix-openclaw-tools
|
||||
|
||||
> Core tools for openclaw. Batteries included. Always fresh.
|
||||
> Reproducible OpenClaw tool packaging.
|
||||
|
||||
Nix packaging for [Peter Steinberger's](https://github.com/steipete) tools, with per-tool openclaw plugins. Part of the [nix-openclaw](https://github.com/openclaw/nix-openclaw) ecosystem.
|
||||
Nix packaging for OpenClaw-adjacent tools, with per-tool OpenClaw plugin metadata. Part of the [nix-openclaw](https://github.com/openclaw/nix-openclaw) ecosystem.
|
||||
|
||||
Darwin/aarch64 plus Linux (x86_64/aarch64) for tools that ship Linux builds.
|
||||
On Linux, `summarize` is built from source (Node 22 + pnpm) since upstream only ships a macOS Bun binary.
|
||||
|
||||
## Why this exists
|
||||
|
||||
These tools are essential for a capable openclaw instance - screen capture, camera access, TTS, messaging. Packaging them as Nix flakes with openclaw plugin metadata means:
|
||||
OpenClaw should have a small default toolchain and explicit optional plugins. Packaging these tools as Nix flakes with OpenClaw plugin metadata means:
|
||||
|
||||
- **Reproducible**: Pinned versions, no Homebrew drift
|
||||
- **Declarative**: Add a plugin, `home-manager switch`, done
|
||||
- **Fresh**: CI keeps tools and skills at latest automatically
|
||||
- **Integrated**: Skills teach your bot how to use each tool
|
||||
|
||||
## Pure Nix and Home Manager design
|
||||
|
||||
This repo is meant to be consumable directly from Nix flakes, Darwin, and Home
|
||||
Manager configs. Packages should stay pinned and reproducible, and optional
|
||||
Home Manager modules should expose normal `programs.<name>` options instead of
|
||||
requiring downstream users to hand-write app bundle or launchd glue.
|
||||
|
||||
For macOS app bundles, prefer a Nix package plus Home Manager integration. Home
|
||||
Manager can expose app bundles from `home.packages` through its Darwin app
|
||||
targets, and modules can opt into `launchd.agents` when Home Manager should own
|
||||
startup. Homebrew casks are still useful, but they belong in a nix-darwin
|
||||
Homebrew configuration, not in these pure Nix package/module definitions.
|
||||
|
||||
## What's included
|
||||
|
||||
| Tool | What it does |
|
||||
|------|--------------|
|
||||
| [**summarize**](https://github.com/steipete/summarize) | Link → clean text → summary |
|
||||
| [**gogcli**](https://github.com/steipete/gogcli) | Google CLI for Gmail, Calendar, Drive, and Contacts |
|
||||
| [**goplaces**](https://github.com/steipete/goplaces) | Google Places API (New) CLI |
|
||||
| [**discrawl**](https://github.com/openclaw/discrawl) | Mirror Discord into SQLite and search history locally |
|
||||
| [**wacrawl**](https://github.com/steipete/wacrawl) | Read-only local archive and search for WhatsApp Desktop data |
|
||||
| [**gogcli**](https://github.com/openclaw/gogcli) | Google CLI for Gmail, Calendar, Drive, and Contacts |
|
||||
| [**goplaces**](https://github.com/openclaw/goplaces) | Google Places API (New) CLI |
|
||||
| [**camsnap**](https://github.com/steipete/camsnap) | Capture snapshots/clips from RTSP/ONVIF cameras |
|
||||
| [**sonoscli**](https://github.com/steipete/sonoscli) | Control Sonos speakers |
|
||||
| [**bird**](https://github.com/steipete/bird) | Fast X CLI for tweeting, replying, and reading |
|
||||
| [**peekaboo**](https://github.com/steipete/peekaboo) | Lightning-fast macOS screenshots & AI vision analysis |
|
||||
| [**peekaboo**](https://github.com/openclaw/Peekaboo) | Lightning-fast macOS screenshots & AI vision analysis |
|
||||
| [**poltergeist**](https://github.com/steipete/poltergeist) | Universal file watcher with auto-rebuild |
|
||||
| [**sag**](https://github.com/steipete/sag) | Command-line ElevenLabs TTS with mac-style flags |
|
||||
| [**imsg**](https://github.com/steipete/imsg) | iMessage/SMS CLI |
|
||||
| [**oracle**](https://github.com/steipete/oracle) | Bundle prompts + files for AI queries |
|
||||
| [**imsg**](https://github.com/openclaw/imsg) | iMessage/SMS CLI |
|
||||
| [**qmd**](https://github.com/tobi/qmd) | On-device hybrid search for markdown knowledge bases |
|
||||
|
||||
## Usage (as openclaw plugins)
|
||||
|
||||
@ -38,9 +52,11 @@ Each tool is a subflake under `tools/<tool>/` exporting `openclawPlugin`. Point
|
||||
|
||||
```nix
|
||||
programs.openclaw.plugins = [
|
||||
{ source = "github:openclaw/nix-steipete-tools?dir=tools/camsnap"; }
|
||||
{ source = "github:openclaw/nix-steipete-tools?dir=tools/peekaboo"; }
|
||||
{ source = "github:openclaw/nix-steipete-tools?dir=tools/summarize"; }
|
||||
{ source = "github:openclaw/nix-openclaw-tools?dir=tools/camsnap"; }
|
||||
{ source = "github:openclaw/nix-openclaw-tools?dir=tools/discrawl"; }
|
||||
{ source = "github:openclaw/nix-openclaw-tools?dir=tools/peekaboo"; }
|
||||
{ source = "github:openclaw/nix-openclaw-tools?dir=tools/summarize"; }
|
||||
{ source = "github:openclaw/nix-openclaw-tools?dir=tools/wacrawl"; }
|
||||
];
|
||||
```
|
||||
|
||||
@ -54,17 +70,21 @@ Each plugin bundles:
|
||||
If you just want the binaries without the plugin wrapper:
|
||||
|
||||
```nix
|
||||
inputs.nix-steipete-tools.url = "github:openclaw/nix-steipete-tools";
|
||||
inputs.nix-openclaw-tools.url = "github:openclaw/nix-openclaw-tools";
|
||||
|
||||
# Then use:
|
||||
inputs.nix-steipete-tools.packages.aarch64-darwin.camsnap
|
||||
inputs.nix-steipete-tools.packages.aarch64-darwin.peekaboo
|
||||
inputs.nix-openclaw-tools.packages.aarch64-darwin.camsnap
|
||||
inputs.nix-openclaw-tools.packages.aarch64-darwin.discrawl
|
||||
inputs.nix-openclaw-tools.packages.aarch64-darwin.peekaboo
|
||||
inputs.nix-openclaw-tools.packages.aarch64-darwin.wacrawl
|
||||
# etc.
|
||||
|
||||
# Linux examples:
|
||||
inputs.nix-steipete-tools.packages.x86_64-linux.camsnap
|
||||
inputs.nix-steipete-tools.packages.aarch64-linux.gogcli
|
||||
inputs.nix-steipete-tools.packages.x86_64-linux.summarize
|
||||
inputs.nix-openclaw-tools.packages.x86_64-linux.camsnap
|
||||
inputs.nix-openclaw-tools.packages.x86_64-linux.discrawl
|
||||
inputs.nix-openclaw-tools.packages.aarch64-linux.gogcli
|
||||
inputs.nix-openclaw-tools.packages.x86_64-linux.summarize
|
||||
inputs.nix-openclaw-tools.packages.x86_64-linux.wacrawl
|
||||
```
|
||||
|
||||
## Skills syncing
|
||||
@ -85,7 +105,11 @@ Tools track upstream GitHub releases directly (not Homebrew).
|
||||
go run ./cmd/update-tools
|
||||
```
|
||||
|
||||
Fetches latest release versions/URLs/hashes and updates the Nix expressions. Oracle uses pnpm and auto-derives its hash via build mismatch.
|
||||
Fetches latest release versions/URLs/hashes and updates the Nix expressions.
|
||||
|
||||
QMD is source-packaged because upstream does not publish release assets. Keep it
|
||||
fresh through the same maintainer automation path, but do not make its smoke
|
||||
check pull embedding/reranking models; model prewarming belongs in nix-openclaw.
|
||||
|
||||
## CI
|
||||
|
||||
@ -95,7 +119,7 @@ Fetches latest release versions/URLs/hashes and updates the Nix expressions. Ora
|
||||
| **update-tools** | Every 10 min | Checks for new tool releases |
|
||||
| **Garnix** | On push | Builds all packages via `checks.*` (darwin + linux) |
|
||||
|
||||
Automated PRs keep everything fresh without manual intervention.
|
||||
Automation commits directly when versions or skills change.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
workdir, err := os.MkdirTemp("", "clawdbot-skills-")
|
||||
workdir, err := os.MkdirTemp("", "openclaw-skills-")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -60,18 +60,18 @@ func main() {
|
||||
|
||||
mappings := []Mapping{
|
||||
{"summarize", "skills/summarize"},
|
||||
{"discrawl", "skills/discrawl"},
|
||||
{"wacrawl", "skills/wacrawl"},
|
||||
{"gogcli", "skills/gog"},
|
||||
{"camsnap", "skills/camsnap"},
|
||||
{"sonoscli", "skills/sonoscli"},
|
||||
{"bird", "skills/bird"},
|
||||
{"peekaboo", "skills/peekaboo"},
|
||||
{"sag", "skills/sag"},
|
||||
{"imsg", "skills/imsg"},
|
||||
{"oracle", "skills/oracle"},
|
||||
}
|
||||
|
||||
log.Printf("[sync-skills] cloning clawdbot main")
|
||||
if err := run("", "git", "clone", "--depth", "1", "--filter=blob:none", "--sparse", "https://github.com/clawdbot/clawdbot.git", workdir); err != nil {
|
||||
log.Printf("[sync-skills] cloning openclaw main")
|
||||
if err := run("", "git", "clone", "--depth", "1", "--filter=blob:none", "--sparse", "https://github.com/openclaw/openclaw.git", workdir); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
paths := []string{}
|
||||
|
||||
@ -2,14 +2,16 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/clawdbot/nix-steipete-tools/internal"
|
||||
"github.com/openclaw/nix-openclaw-tools/internal"
|
||||
)
|
||||
|
||||
type Tool struct {
|
||||
@ -68,6 +70,107 @@ func updateSourceBlock(path, system, url, hash string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func readVersion(path string) (string, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
match := regexp.MustCompile(`version = "([^"]+)";`).FindStringSubmatch(string(data))
|
||||
if len(match) < 2 {
|
||||
return "", fmt.Errorf("version not found in %s", path)
|
||||
}
|
||||
return match[1], nil
|
||||
}
|
||||
|
||||
func fetchText(url string) (string, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if token := os.Getenv("GH_TOKEN"); token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("fetch %s: %s: %s", url, resp.Status, string(body))
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func qmdNodeModulesHash(upstreamFlake, system string) (string, error) {
|
||||
re := regexp.MustCompile(fmt.Sprintf(`"?%s"?\s*=\s*"([^"]+)";`, regexp.QuoteMeta(system)))
|
||||
match := re.FindStringSubmatch(upstreamFlake)
|
||||
if len(match) < 2 {
|
||||
return "", fmt.Errorf("qmd nodeModules hash for %s not found upstream", system)
|
||||
}
|
||||
hash := match[1]
|
||||
if strings.Contains(hash, "AAAAAAAA") || strings.Contains(hash, "fake") {
|
||||
return "", fmt.Errorf("qmd nodeModules hash for %s is not populated upstream", system)
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func updateQMD(repoRoot string) error {
|
||||
log.Printf("[update-tools] qmd")
|
||||
qmdFile := filepath.Join(repoRoot, "nix", "pkgs", "qmd.nix")
|
||||
currentVersion, err := readVersion(qmdFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rel, err := internal.LatestRelease("tobi/qmd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := strings.TrimPrefix(rel.TagName, "v")
|
||||
if currentVersion == version {
|
||||
return nil
|
||||
}
|
||||
|
||||
srcHash, err := internal.PrefetchGitHub("tobi", "qmd", "v"+version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upstreamFlake, err := fetchText(fmt.Sprintf("https://raw.githubusercontent.com/tobi/qmd/v%s/flake.nix", version))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeHashes := map[string]string{}
|
||||
for _, system := range []string{"aarch64-darwin", "x86_64-linux"} {
|
||||
hash, err := qmdNodeModulesHash(upstreamFlake, system)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeHashes[system] = hash
|
||||
}
|
||||
|
||||
if err := internal.ReplaceOnce(qmdFile, regexp.MustCompile(`version = "[^"]+";`), fmt.Sprintf(`version = "%s";`, version)); err != nil {
|
||||
return err
|
||||
}
|
||||
srcRe := regexp.MustCompile(`(?s)src = fetchFromGitHub \{.*?hash = "sha256-[^"]+";`)
|
||||
if err := internal.ReplaceOnceFunc(qmdFile, srcRe, func(s string) string {
|
||||
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, fmt.Sprintf(`hash = "%s";`, srcHash))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for system, hash := range nodeHashes {
|
||||
re := regexp.MustCompile(fmt.Sprintf(`"%s" = "sha256-[^"]+";`, regexp.QuoteMeta(system)))
|
||||
if err := internal.ReplaceOnce(qmdFile, re, fmt.Sprintf(`"%s" = "%s";`, system, hash)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateSummarize(repoRoot string) error {
|
||||
log.Printf("[update-tools] summarize")
|
||||
summarizeFile := filepath.Join(repoRoot, "nix", "pkgs", "summarize.nix")
|
||||
@ -140,85 +243,6 @@ func updateSummarize(repoRoot string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateOracle(repoRoot string) error {
|
||||
log.Printf("[update-tools] oracle")
|
||||
oracleFile := filepath.Join(repoRoot, "nix", "pkgs", "oracle.nix")
|
||||
orig, err := os.ReadFile(oracleFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rel, err := internal.LatestRelease("steipete/oracle")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version := strings.TrimPrefix(rel.TagName, "v")
|
||||
var assetURL string
|
||||
for _, a := range rel.Assets {
|
||||
if matched, _ := regexp.MatchString(`oracle-[0-9.]+\.tgz`, a.Name); matched {
|
||||
assetURL = a.BrowserDownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
if assetURL == "" {
|
||||
return fmt.Errorf("no asset matched for oracle")
|
||||
}
|
||||
assetHash, err := internal.PrefetchHash(assetURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lockHash, err := internal.PrefetchGitHub("steipete", "oracle", rel.TagName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`version = "[^"]+";`), fmt.Sprintf(`version = "%s";`, version)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`url = "[^"]+";`), fmt.Sprintf(`url = "%s";`, assetURL)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`hash = "sha256-[^"]+";`), fmt.Sprintf(`hash = "%s";`, assetHash)); err != nil {
|
||||
return err
|
||||
}
|
||||
lockRe := regexp.MustCompile(`(?s)lockSrc = fetchFromGitHub \{[^}]*hash = "sha256-[^"]+";`)
|
||||
if err := internal.ReplaceOnceFunc(oracleFile, lockRe, func(s string) string {
|
||||
out := regexp.MustCompile(`rev = "[^"]+";`).ReplaceAllString(s, fmt.Sprintf(`rev = "%s";`, rel.TagName))
|
||||
out = regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(out, fmt.Sprintf(`hash = "%s";`, lockHash))
|
||||
return out
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
pnpmRe := regexp.MustCompile(`(?s)pnpmDeps.*hash = "sha256-[^"]+";`)
|
||||
if err := internal.ReplaceOnceFunc(oracleFile, pnpmRe, func(s string) string {
|
||||
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, `hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";`)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[update-tools] oracle: deriving pnpm hash")
|
||||
logText, buildErr := internal.NixBuildOracle()
|
||||
pnpmHash := internal.ExtractGotHash(logText)
|
||||
if pnpmHash == "" {
|
||||
// Restore original file so we don't leave a broken placeholder hash behind.
|
||||
_ = os.WriteFile(oracleFile, orig, 0644)
|
||||
// Surface some context in CI logs. This is usually a hash-mismatch line we failed to parse.
|
||||
lines := strings.Split(logText, "\n")
|
||||
start := 0
|
||||
if len(lines) > 200 {
|
||||
start = len(lines) - 200
|
||||
}
|
||||
log.Printf("[update-tools] oracle build output (last %d lines):\n%s", len(lines)-start, strings.Join(lines[start:], "\n"))
|
||||
return fmt.Errorf("oracle pnpm hash not found (build err: %v)", buildErr)
|
||||
}
|
||||
if err := internal.ReplaceOnceFunc(oracleFile, pnpmRe, func(s string) string {
|
||||
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, fmt.Sprintf(`hash = "%s";`, pnpmHash))
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
repoRoot, err := os.Getwd()
|
||||
if err != nil {
|
||||
@ -226,9 +250,29 @@ func main() {
|
||||
}
|
||||
|
||||
tools := []Tool{
|
||||
{
|
||||
Name: "discrawl",
|
||||
Repo: "openclaw/discrawl",
|
||||
Assets: []AssetSpec{
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`discrawl_[0-9.]+_darwin_arm64\.tar\.gz`)},
|
||||
{System: "x86_64-linux", Regex: regexp.MustCompile(`discrawl_[0-9.]+_linux_amd64\.tar\.gz`)},
|
||||
{System: "aarch64-linux", Regex: regexp.MustCompile(`discrawl_[0-9.]+_linux_arm64\.tar\.gz`)},
|
||||
},
|
||||
NixFile: filepath.Join(repoRoot, "nix", "pkgs", "discrawl.nix"),
|
||||
},
|
||||
{
|
||||
Name: "wacrawl",
|
||||
Repo: "steipete/wacrawl",
|
||||
Assets: []AssetSpec{
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`wacrawl_[0-9.]+_darwin_arm64\.tar\.gz`)},
|
||||
{System: "x86_64-linux", Regex: regexp.MustCompile(`wacrawl_[0-9.]+_linux_amd64\.tar\.gz`)},
|
||||
{System: "aarch64-linux", Regex: regexp.MustCompile(`wacrawl_[0-9.]+_linux_arm64\.tar\.gz`)},
|
||||
},
|
||||
NixFile: filepath.Join(repoRoot, "nix", "pkgs", "wacrawl.nix"),
|
||||
},
|
||||
{
|
||||
Name: "gogcli",
|
||||
Repo: "steipete/gogcli",
|
||||
Repo: "openclaw/gogcli",
|
||||
Assets: []AssetSpec{
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`gogcli_[0-9.]+_darwin_arm64\.tar\.gz`)},
|
||||
{System: "x86_64-linux", Regex: regexp.MustCompile(`gogcli_[0-9.]+_linux_amd64\.tar\.gz`)},
|
||||
@ -236,6 +280,17 @@ func main() {
|
||||
},
|
||||
NixFile: filepath.Join(repoRoot, "nix", "pkgs", "gogcli.nix"),
|
||||
},
|
||||
{
|
||||
Name: "goplaces",
|
||||
Repo: "openclaw/goplaces",
|
||||
Assets: []AssetSpec{
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`goplaces_[0-9.]+_darwin_arm64\.tar\.gz`)},
|
||||
{System: "x86_64-darwin", Regex: regexp.MustCompile(`goplaces_[0-9.]+_darwin_amd64\.tar\.gz`)},
|
||||
{System: "x86_64-linux", Regex: regexp.MustCompile(`goplaces_[0-9.]+_linux_amd64\.tar\.gz`)},
|
||||
{System: "aarch64-linux", Regex: regexp.MustCompile(`goplaces_[0-9.]+_linux_arm64\.tar\.gz`)},
|
||||
},
|
||||
NixFile: filepath.Join(repoRoot, "nix", "pkgs", "goplaces.nix"),
|
||||
},
|
||||
{
|
||||
Name: "camsnap",
|
||||
Repo: "steipete/camsnap",
|
||||
@ -250,26 +305,17 @@ func main() {
|
||||
Name: "sonoscli",
|
||||
Repo: "steipete/sonoscli",
|
||||
Assets: []AssetSpec{
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`sonoscli-macos-arm64\.tar\.gz`)},
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`sonoscli_[0-9.]+_darwin_arm64\.tar\.gz`)},
|
||||
{System: "x86_64-linux", Regex: regexp.MustCompile(`sonoscli_[0-9.]+_linux_amd64\.tar\.gz`)},
|
||||
{System: "aarch64-linux", Regex: regexp.MustCompile(`sonoscli_[0-9.]+_linux_arm64\.tar\.gz`)},
|
||||
},
|
||||
NixFile: filepath.Join(repoRoot, "nix", "pkgs", "sonoscli.nix"),
|
||||
},
|
||||
{
|
||||
Name: "bird",
|
||||
Repo: "steipete/bird",
|
||||
Optional: true, // repo got nuked; keep packaging pinned, but don't fail the updater
|
||||
Assets: []AssetSpec{
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`bird-macos-universal-v[0-9.]+\.tar\.gz`)},
|
||||
},
|
||||
NixFile: filepath.Join(repoRoot, "nix", "pkgs", "bird.nix"),
|
||||
},
|
||||
{
|
||||
Name: "peekaboo",
|
||||
Repo: "steipete/peekaboo",
|
||||
Repo: "openclaw/Peekaboo",
|
||||
Assets: []AssetSpec{
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`peekaboo-macos-universal\.tar\.gz`)},
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`peekaboo-macos-(?:arm64|universal)\.tar\.gz`)},
|
||||
},
|
||||
NixFile: filepath.Join(repoRoot, "nix", "pkgs", "peekaboo.nix"),
|
||||
},
|
||||
@ -292,7 +338,7 @@ func main() {
|
||||
},
|
||||
{
|
||||
Name: "imsg",
|
||||
Repo: "steipete/imsg",
|
||||
Repo: "openclaw/imsg",
|
||||
Assets: []AssetSpec{
|
||||
{System: "aarch64-darwin", Regex: regexp.MustCompile(`imsg-macos\.zip`)},
|
||||
},
|
||||
@ -303,6 +349,9 @@ func main() {
|
||||
if err := updateSummarize(repoRoot); err != nil {
|
||||
log.Fatalf("update summarize failed: %v", err)
|
||||
}
|
||||
if err := updateQMD(repoRoot); err != nil {
|
||||
log.Fatalf("update qmd failed: %v", err)
|
||||
}
|
||||
for _, tool := range tools {
|
||||
if err := updateTool(tool); err != nil {
|
||||
if tool.Optional {
|
||||
@ -313,9 +362,4 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateOracle(repoRoot); err != nil {
|
||||
// Oracle releases occasionally ship with an out-of-date pnpm-lock.yaml.
|
||||
// In that case, we keep the previously pinned version and still update other tools.
|
||||
log.Printf("[update-tools] skipping oracle update: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
33
cmd/update-tools/main_test.go
Normal file
33
cmd/update-tools/main_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestQMDNodeModulesHash(t *testing.T) {
|
||||
upstream := `
|
||||
nodeModulesHashes = {
|
||||
x86_64-linux = "sha256-linux";
|
||||
aarch64-darwin = "sha256-darwin";
|
||||
};
|
||||
`
|
||||
|
||||
got, err := qmdNodeModulesHash(upstream, "aarch64-darwin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "sha256-darwin" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQMDNodeModulesHashRejectsFake(t *testing.T) {
|
||||
upstream := `
|
||||
nodeModulesHashes = {
|
||||
aarch64-darwin = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
`
|
||||
|
||||
_, err := qmdNodeModulesHash(upstream, "aarch64-darwin")
|
||||
if err == nil {
|
||||
t.Fatal("expected fake hash to be rejected")
|
||||
}
|
||||
}
|
||||
36
flake.nix
36
flake.nix
@ -1,5 +1,5 @@
|
||||
{
|
||||
description = "Nix packaging for steipete tools (clawdbot)";
|
||||
description = "Nix packaging for OpenClaw tools";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
@ -12,16 +12,17 @@
|
||||
forAllSystems = f: lib.genAttrs systems (system: f system);
|
||||
packageSystems = {
|
||||
summarize = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
discrawl = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
wacrawl = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
gogcli = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
goplaces = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
camsnap = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
sonoscli = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
bird = [ "aarch64-darwin" ];
|
||||
peekaboo = [ "aarch64-darwin" ];
|
||||
poltergeist = [ "aarch64-darwin" ];
|
||||
sag = [ "aarch64-darwin" "x86_64-linux" ];
|
||||
imsg = [ "aarch64-darwin" ];
|
||||
oracle = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
qmd = [ "aarch64-darwin" "x86_64-linux" ];
|
||||
};
|
||||
in {
|
||||
packages = forAllSystems (system:
|
||||
@ -36,6 +37,12 @@
|
||||
nodejs = if pkgs ? nodejs_22 then pkgs.nodejs_22 else pkgs.nodejs;
|
||||
};
|
||||
})
|
||||
// (lib.optionalAttrs (supports "discrawl") {
|
||||
discrawl = pkgs.callPackage ./nix/pkgs/discrawl.nix {};
|
||||
})
|
||||
// (lib.optionalAttrs (supports "wacrawl") {
|
||||
wacrawl = pkgs.callPackage ./nix/pkgs/wacrawl.nix {};
|
||||
})
|
||||
// (lib.optionalAttrs (supports "gogcli") {
|
||||
gogcli = pkgs.callPackage ./nix/pkgs/gogcli.nix {};
|
||||
})
|
||||
@ -48,9 +55,6 @@
|
||||
// (lib.optionalAttrs (supports "sonoscli") {
|
||||
sonoscli = pkgs.callPackage ./nix/pkgs/sonoscli.nix {};
|
||||
})
|
||||
// (lib.optionalAttrs (supports "bird") {
|
||||
bird = pkgs.callPackage ./nix/pkgs/bird.nix {};
|
||||
})
|
||||
// (lib.optionalAttrs (supports "peekaboo") {
|
||||
peekaboo = pkgs.callPackage ./nix/pkgs/peekaboo.nix {};
|
||||
})
|
||||
@ -63,14 +67,22 @@
|
||||
// (lib.optionalAttrs (supports "imsg") {
|
||||
imsg = pkgs.callPackage ./nix/pkgs/imsg.nix {};
|
||||
})
|
||||
// (lib.optionalAttrs (supports "oracle") {
|
||||
oracle = pkgs.callPackage ./nix/pkgs/oracle.nix {
|
||||
pkgs = pkgs;
|
||||
pnpm = if pkgs ? pnpm_10 then pkgs.pnpm_10 else pkgs.pnpm;
|
||||
};
|
||||
// (lib.optionalAttrs (supports "qmd") {
|
||||
qmd = pkgs.callPackage ./nix/pkgs/qmd.nix {};
|
||||
})
|
||||
);
|
||||
|
||||
checks = forAllSystems (system: self.packages.${system});
|
||||
checks = forAllSystems (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
packages = self.packages.${system};
|
||||
in
|
||||
packages
|
||||
// (lib.optionalAttrs (packages ? qmd) {
|
||||
qmd-smoke = pkgs.callPackage ./nix/checks/qmd-smoke.nix {
|
||||
qmd = packages.qmd;
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@ -1,3 +1,3 @@
|
||||
module github.com/clawdbot/nix-steipete-tools
|
||||
module github.com/openclaw/nix-openclaw-tools
|
||||
|
||||
go 1.22
|
||||
|
||||
@ -61,15 +61,6 @@ func PrefetchGitHub(owner, repo, rev string) (string, error) {
|
||||
return res.Hash, nil
|
||||
}
|
||||
|
||||
func NixBuildOracle() (string, error) {
|
||||
cmd := exec.Command("nix", "build", ".#oracle")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
err := cmd.Run()
|
||||
return out.String(), err
|
||||
}
|
||||
|
||||
func NixBuildSummarize() (string, error) {
|
||||
return NixBuildSummarizeSystem("")
|
||||
}
|
||||
|
||||
25
nix/checks/qmd-smoke.nix
Normal file
25
nix/checks/qmd-smoke.nix
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
runCommand,
|
||||
qmd,
|
||||
}:
|
||||
|
||||
runCommand "qmd-smoke" { nativeBuildInputs = [ qmd ]; } ''
|
||||
set -eu
|
||||
|
||||
export HOME="$TMPDIR/home"
|
||||
export XDG_CONFIG_HOME="$TMPDIR/config"
|
||||
export XDG_CACHE_HOME="$TMPDIR/cache"
|
||||
export XDG_DATA_HOME="$TMPDIR/data"
|
||||
mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$XDG_DATA_HOME" "$TMPDIR/notes"
|
||||
|
||||
printf '%s\n\n%s\n' '# Smoke' 'qmd packaging smoke' > "$TMPDIR/notes/smoke.md"
|
||||
|
||||
qmd --help >/dev/null
|
||||
qmd collection list >/dev/null
|
||||
qmd collection add "$TMPDIR/notes" --name smoke
|
||||
qmd update
|
||||
qmd search packaging --json | grep -q packaging
|
||||
qmd status >/dev/null
|
||||
|
||||
touch "$out"
|
||||
''
|
||||
@ -1,39 +0,0 @@
|
||||
{ lib, stdenv, fetchurl }:
|
||||
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/steipete/bird/releases/download/v0.8.0/bird-macos-universal-v0.8.0.tar.gz";
|
||||
hash = "sha256-PYm7QE6LDtTvMx8Nxi2HOFJjTKKoFK56Ssfv/BFDIM8=";
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "bird";
|
||||
version = "0.8.0";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
dontConfigure = true;
|
||||
dontBuild = true;
|
||||
|
||||
unpackPhase = ''
|
||||
tar -xzf "$src"
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/bin"
|
||||
cp bird "$out/bin/bird"
|
||||
chmod 0755 "$out/bin/bird"
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Fast X CLI for tweeting, replying, and reading";
|
||||
homepage = "https://github.com/steipete/bird";
|
||||
license = licenses.mit;
|
||||
platforms = builtins.attrNames sources;
|
||||
mainProgram = "bird";
|
||||
};
|
||||
}
|
||||
53
nix/pkgs/discrawl.nix
Normal file
53
nix/pkgs/discrawl.nix
Normal file
@ -0,0 +1,53 @@
|
||||
{ lib, stdenv, fetchurl }:
|
||||
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/openclaw/discrawl/releases/download/v0.7.0/discrawl_0.7.0_darwin_arm64.tar.gz";
|
||||
hash = "sha256-MdiCWs19GIVKfCtrQQHUrkfnEvcLpmjmwRYwELVfMlA=";
|
||||
};
|
||||
"x86_64-linux" = {
|
||||
url = "https://github.com/openclaw/discrawl/releases/download/v0.7.0/discrawl_0.7.0_linux_amd64.tar.gz";
|
||||
hash = "sha256-wLtFIDJ2gED+149rWlFaQ/Bdv+WnkH6+eoWXXsI8McE=";
|
||||
};
|
||||
"aarch64-linux" = {
|
||||
url = "https://github.com/openclaw/discrawl/releases/download/v0.7.0/discrawl_0.7.0_linux_arm64.tar.gz";
|
||||
hash = "sha256-yoBcWyV6vGqxTreTeE8qsZKZzkyli40PK/2LFj72GHk=";
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "discrawl";
|
||||
version = "0.7.0";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
dontConfigure = true;
|
||||
dontBuild = true;
|
||||
|
||||
unpackPhase = ''
|
||||
tar -xzf "$src"
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/bin" "$out/share/doc/discrawl"
|
||||
cp $(find . -type f -name discrawl | head -1) "$out/bin/discrawl"
|
||||
chmod 0755 "$out/bin/discrawl"
|
||||
if [ -f LICENSE ]; then
|
||||
cp LICENSE "$out/share/doc/discrawl/"
|
||||
fi
|
||||
if [ -f README.md ]; then
|
||||
cp README.md "$out/share/doc/discrawl/"
|
||||
fi
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Mirror Discord into SQLite and search server history locally";
|
||||
homepage = "https://github.com/openclaw/discrawl";
|
||||
license = licenses.mit;
|
||||
platforms = builtins.attrNames sources;
|
||||
mainProgram = "discrawl";
|
||||
};
|
||||
}
|
||||
@ -3,22 +3,22 @@
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/steipete/gogcli/releases/download/v0.10.0/gogcli_0.10.0_darwin_arm64.tar.gz";
|
||||
hash = "sha256-sNPDBSmr40X1Epzm7ZRzJfcDHU7p70kLHl12/5c4/J8=";
|
||||
url = "https://github.com/openclaw/gogcli/releases/download/v0.15.0/gogcli_0.15.0_darwin_arm64.tar.gz";
|
||||
hash = "sha256-WMc9nprZ+2m6Nd+MabD5eGXJ2XSmpRFfG7RuI/Ldl8k=";
|
||||
};
|
||||
"x86_64-linux" = {
|
||||
url = "https://github.com/steipete/gogcli/releases/download/v0.10.0/gogcli_0.10.0_linux_amd64.tar.gz";
|
||||
hash = "sha256-f8DVB7LQprzprE6IyGu3jZo2ckRXEu9FiomMM7KqIV0=";
|
||||
url = "https://github.com/openclaw/gogcli/releases/download/v0.15.0/gogcli_0.15.0_linux_amd64.tar.gz";
|
||||
hash = "sha256-v6KpyAkr0ynbiaoQD4B0o/FCUePbcWuSdid5PkcUjJ0=";
|
||||
};
|
||||
"aarch64-linux" = {
|
||||
url = "https://github.com/steipete/gogcli/releases/download/v0.10.0/gogcli_0.10.0_linux_arm64.tar.gz";
|
||||
hash = "sha256-Q48xgr47bF3Om3kyKm6tnH9nOW9g0Em7i0o5AEhPZRU=";
|
||||
url = "https://github.com/openclaw/gogcli/releases/download/v0.15.0/gogcli_0.15.0_linux_arm64.tar.gz";
|
||||
hash = "sha256-R3+DUVlX9asKDc9B/NSFSDF11PsY4O1hZXZsMQsqGzU=";
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "gogcli";
|
||||
version = "0.10.0";
|
||||
version = "0.15.0";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
@ -39,7 +39,7 @@ stdenv.mkDerivation {
|
||||
|
||||
meta = with lib; {
|
||||
description = "Google CLI for Gmail, Calendar, Drive, and Contacts";
|
||||
homepage = "https://github.com/steipete/gogcli";
|
||||
homepage = "https://github.com/openclaw/gogcli";
|
||||
license = licenses.mit;
|
||||
platforms = builtins.attrNames sources;
|
||||
mainProgram = "gog";
|
||||
|
||||
@ -1,31 +1,60 @@
|
||||
{ lib, buildGoModule, fetchFromGitHub }:
|
||||
{ lib, stdenv, fetchurl }:
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "goplaces";
|
||||
version = "0.2.2-dev";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "joshp123";
|
||||
repo = "goplaces";
|
||||
rev = "a71fe3de986a78607d923f397113d7eb1babc111";
|
||||
hash = "sha256-Lufp9+fwcoluNZR9iDYoIJ1yu3sM/8Q/EOkAlq5VLdk=";
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/openclaw/goplaces/releases/download/v0.4.0/goplaces_0.4.0_darwin_arm64.tar.gz";
|
||||
hash = "sha256-FJ8hVqQhp/Ppk17CrDJk4dmu2pvS2xBX5cAzafpaM8g=";
|
||||
};
|
||||
"x86_64-darwin" = {
|
||||
url = "https://github.com/openclaw/goplaces/releases/download/v0.4.0/goplaces_0.4.0_darwin_amd64.tar.gz";
|
||||
hash = "sha256-/vVK0hqh1q3tliSy4mRT9X3jRayYhWAfZLS+VsFd+NU=";
|
||||
};
|
||||
"x86_64-linux" = {
|
||||
url = "https://github.com/openclaw/goplaces/releases/download/v0.4.0/goplaces_0.4.0_linux_amd64.tar.gz";
|
||||
hash = "sha256-cUHDAZ4Ep50K2ljLn8LyJeWPJlcr/sytP95UsNmSv3w=";
|
||||
};
|
||||
"aarch64-linux" = {
|
||||
url = "https://github.com/openclaw/goplaces/releases/download/v0.4.0/goplaces_0.4.0_linux_arm64.tar.gz";
|
||||
hash = "sha256-YjeLRDLwuQbUOYZYnpNkuYDvsm54PB1jmqTHas2T5VY=";
|
||||
};
|
||||
};
|
||||
|
||||
subPackages = [ "cmd/goplaces" ];
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X github.com/steipete/goplaces/internal/cli.Version=${version}"
|
||||
];
|
||||
|
||||
vendorHash = "sha256-OFTjLtKwYSy4tM+D12mqI28M73YJdG4DyqPkXS7ZKUg=";
|
||||
|
||||
meta = with lib; {
|
||||
description = "Modern Go client + CLI for the Google Places API";
|
||||
homepage = "https://github.com/joshp123/goplaces";
|
||||
description = "Modern Go client + CLI for the Google Places API (New)";
|
||||
homepage = "https://github.com/openclaw/goplaces";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.darwin ++ platforms.linux;
|
||||
platforms = builtins.attrNames sources;
|
||||
mainProgram = "goplaces";
|
||||
};
|
||||
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "goplaces";
|
||||
version = "0.4.0";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
dontConfigure = true;
|
||||
dontBuild = true;
|
||||
|
||||
unpackPhase = ''
|
||||
tar -xzf "$src"
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/bin" "$out/share/doc/goplaces"
|
||||
cp $(find . -type f -name goplaces | head -1) "$out/bin/goplaces"
|
||||
chmod 0755 "$out/bin/goplaces"
|
||||
if [ -f LICENSE ]; then
|
||||
cp LICENSE "$out/share/doc/goplaces/"
|
||||
fi
|
||||
if [ -f README.md ]; then
|
||||
cp README.md "$out/share/doc/goplaces/"
|
||||
fi
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
inherit meta;
|
||||
}
|
||||
|
||||
@ -3,14 +3,14 @@
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/steipete/imsg/releases/download/v0.4.0/imsg-macos.zip";
|
||||
hash = "sha256-0OXjM+6IGS1ZW/7Z7s5g417K0DABRZZtWtJ0WMM+QHs=";
|
||||
url = "https://github.com/openclaw/imsg/releases/download/v0.8.0/imsg-macos.zip";
|
||||
hash = "sha256-CO2pbs85AbN8FcKdai22BCElRiYJLcj9jf5sXUj1lE8=";
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "imsg";
|
||||
version = "0.4.0";
|
||||
version = "0.8.0";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
@ -35,7 +35,7 @@ stdenv.mkDerivation {
|
||||
|
||||
meta = with lib; {
|
||||
description = "Send and read iMessage / SMS from the terminal";
|
||||
homepage = "https://github.com/steipete/imsg";
|
||||
homepage = "https://github.com/openclaw/imsg";
|
||||
license = licenses.mit;
|
||||
platforms = builtins.attrNames sources;
|
||||
mainProgram = "imsg";
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
{ lib
|
||||
, stdenv
|
||||
, fetchurl
|
||||
, fetchFromGitHub
|
||||
, nodejs
|
||||
, pnpm
|
||||
, python3
|
||||
, python3Packages
|
||||
, pkg-config
|
||||
, makeWrapper
|
||||
, pkgs
|
||||
, zstd
|
||||
}:
|
||||
|
||||
let
|
||||
pnpmFetchDepsPkg = pkgs.callPackage "${pkgs.path}/pkgs/build-support/node/fetch-pnpm-deps" {
|
||||
inherit pnpm;
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "oracle";
|
||||
version = "0.8.5";
|
||||
|
||||
srcTarball = fetchurl {
|
||||
url = "https://github.com/steipete/oracle/releases/download/v0.8.5/oracle-0.8.5.tgz";
|
||||
hash = "sha256-MSb1+5wEHK38iq+yob7Tz7xov0Wh9zHcmXGs/l2KdMA=";
|
||||
};
|
||||
|
||||
lockSrc = fetchFromGitHub {
|
||||
owner = "steipete";
|
||||
repo = "oracle";
|
||||
rev = "v0.8.5";
|
||||
hash = "sha256-q1l3IcVAj7Gb8Lp0JzQakLRg2AlVrJniMBHRwxKQVbM=";
|
||||
};
|
||||
|
||||
srcPatched = stdenv.mkDerivation {
|
||||
name = "oracle-src-patched";
|
||||
src = finalAttrs.srcTarball;
|
||||
nativeBuildInputs = [ python3 ];
|
||||
dontConfigure = true;
|
||||
dontBuild = true;
|
||||
unpackPhase = ''
|
||||
tar -xzf "$src"
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p "$out"
|
||||
if [ -d package ]; then
|
||||
shopt -s dotglob
|
||||
mv package/* "$out"/
|
||||
else
|
||||
cp -R . "$out"/
|
||||
fi
|
||||
cp -f "${finalAttrs.lockSrc}/pnpm-lock.yaml" "$out/pnpm-lock.yaml"
|
||||
export OUT_DIR="$out"
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(os.environ["OUT_DIR"]) / "package.json"
|
||||
data = json.loads(path.read_text())
|
||||
data.pop("packageManager", None)
|
||||
path.write_text(json.dumps(data, indent=2) + "\n")
|
||||
PY
|
||||
'';
|
||||
};
|
||||
|
||||
src = finalAttrs.srcPatched;
|
||||
|
||||
pnpmDeps = (pnpmFetchDepsPkg.fetchPnpmDeps {
|
||||
pname = finalAttrs.pname;
|
||||
version = finalAttrs.version;
|
||||
src = finalAttrs.srcPatched;
|
||||
hash = "sha256-Tmwe55l4QMzXsO7F1kUSnGuZjkuMtDORNtqKnWZ/HrA=";
|
||||
fetcherVersion = 3;
|
||||
});
|
||||
|
||||
nativeBuildInputs = [
|
||||
nodejs
|
||||
pnpm
|
||||
python3
|
||||
python3Packages.setuptools
|
||||
pkg-config
|
||||
makeWrapper
|
||||
zstd
|
||||
];
|
||||
|
||||
env = {
|
||||
PNPM_IGNORE_PACKAGE_MANAGER_CHECK = "1";
|
||||
CI = "1";
|
||||
HOME = "/tmp";
|
||||
PNPM_HOME = "/tmp/pnpm-home";
|
||||
PNPM_CONFIG_HOME = "/tmp/pnpm-config";
|
||||
XDG_CACHE_HOME = "/tmp/pnpm-cache";
|
||||
NPM_CONFIG_USERCONFIG = "/tmp/pnpm-config/.npmrc";
|
||||
npm_config_nodedir = "${nodejs.dev}";
|
||||
npm_config_build_from_source = "1";
|
||||
PNPM_CONFIG_IGNORE_SCRIPTS = "1";
|
||||
};
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
mkdir -p "$HOME" "$PNPM_HOME" "$PNPM_CONFIG_HOME" "$XDG_CACHE_HOME"
|
||||
export PNPM_STORE_PATH="$TMPDIR/pnpm-store"
|
||||
mkdir -p "$PNPM_STORE_PATH"
|
||||
tar --zstd -xf ${finalAttrs.pnpmDeps}/pnpm-store.tar.zst -C "$PNPM_STORE_PATH"
|
||||
pnpm install --offline --no-frozen-lockfile --store-dir "$PNPM_STORE_PATH" --ignore-scripts
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/libexec" "$out/bin"
|
||||
cp -r dist package.json vendor assets-oracle-icon.png node_modules "$out/libexec/"
|
||||
chmod 0755 "$out/libexec/dist/bin/oracle-cli.js" "$out/libexec/dist/bin/oracle-mcp.js"
|
||||
ln -s "$out/libexec/dist/bin/oracle-cli.js" "$out/bin/oracle"
|
||||
ln -s "$out/libexec/dist/bin/oracle-mcp.js" "$out/bin/oracle-mcp"
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Bundle prompts + files for second-model review";
|
||||
homepage = "https://github.com/steipete/oracle";
|
||||
license = licenses.mit;
|
||||
platforms = [ "aarch64-darwin" "x86_64-linux" "aarch64-linux" ];
|
||||
mainProgram = "oracle";
|
||||
};
|
||||
})
|
||||
@ -3,14 +3,14 @@
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/steipete/Peekaboo/releases/download/v3.0.0-beta3/peekaboo-macos-universal.tar.gz";
|
||||
hash = "sha256-d+rfb9XFTqxktIRNXMiHiQttb0XUmvYbBcbinqLL0kU=";
|
||||
url = "https://github.com/openclaw/Peekaboo/releases/download/v3.0.0-beta4/peekaboo-macos-arm64.tar.gz";
|
||||
hash = "sha256-74eXVHpRAmcs0mzK3GLh/3So78AEMZzXBvx1Zg7uOkc=";
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "peekaboo";
|
||||
version = "3.0.0-beta3";
|
||||
version = "3.0.0-beta4";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
@ -31,7 +31,7 @@ stdenv.mkDerivation {
|
||||
|
||||
meta = with lib; {
|
||||
description = "Lightning-fast macOS screenshots & AI vision analysis";
|
||||
homepage = "https://github.com/steipete/peekaboo";
|
||||
homepage = "https://github.com/openclaw/Peekaboo";
|
||||
license = licenses.mit;
|
||||
platforms = builtins.attrNames sources;
|
||||
mainProgram = "peekaboo";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{ lib, stdenv, fetchurl, watchman }:
|
||||
{ lib, stdenv, fetchurl }:
|
||||
|
||||
let
|
||||
sources = {
|
||||
@ -30,8 +30,6 @@ stdenv.mkDerivation {
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
propagatedBuildInputs = [ watchman ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Universal file watcher with auto-rebuild for any language or build system";
|
||||
homepage = "https://github.com/steipete/poltergeist";
|
||||
|
||||
123
nix/pkgs/qmd.nix
Normal file
123
nix/pkgs/qmd.nix
Normal file
@ -0,0 +1,123 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
stdenvNoCC,
|
||||
fetchFromGitHub,
|
||||
bun,
|
||||
makeWrapper,
|
||||
nodejs,
|
||||
node-gyp,
|
||||
python3,
|
||||
sqlite,
|
||||
darwin,
|
||||
xcbuild,
|
||||
}:
|
||||
|
||||
let
|
||||
pname = "qmd";
|
||||
version = "2.1.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "tobi";
|
||||
repo = "qmd";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-bqIVaNRTa8H5vrw3RwsD7QdtTa0xNvRuEVzlzE1hIBQ=";
|
||||
};
|
||||
|
||||
nodeModulesHashes = {
|
||||
"aarch64-darwin" = "sha256-qU+9KdR/nTocelyANS09I/4yaQ+7s1LvJNqB27IOK/c=";
|
||||
"x86_64-linux" = "sha256-D0ezO4vqq4iswcAMU2DCql9ZAQvh3me6N9aDB5roq4w=";
|
||||
};
|
||||
|
||||
system = stdenv.hostPlatform.system;
|
||||
|
||||
nodeModules = stdenvNoCC.mkDerivation {
|
||||
pname = "qmd-node-modules";
|
||||
inherit version src;
|
||||
|
||||
impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [
|
||||
"GIT_PROXY_COMMAND"
|
||||
"SOCKS_SERVER"
|
||||
];
|
||||
|
||||
nativeBuildInputs = [ bun ];
|
||||
|
||||
dontConfigure = true;
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
export HOME="$(mktemp -d)"
|
||||
bun install \
|
||||
--backend copyfile \
|
||||
--frozen-lockfile \
|
||||
--ignore-scripts \
|
||||
--no-progress \
|
||||
--production
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out"
|
||||
cp -R node_modules "$out/"
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
dontFixup = true;
|
||||
|
||||
outputHash = nodeModulesHashes.${system};
|
||||
outputHashAlgo = "sha256";
|
||||
outputHashMode = "recursive";
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
inherit pname version src;
|
||||
|
||||
nativeBuildInputs = [
|
||||
bun
|
||||
makeWrapper
|
||||
nodejs
|
||||
node-gyp
|
||||
python3
|
||||
]
|
||||
++ lib.optionals stdenv.hostPlatform.isDarwin [
|
||||
darwin.cctools
|
||||
xcbuild
|
||||
];
|
||||
|
||||
buildInputs = [ sqlite ];
|
||||
|
||||
dontConfigure = true;
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
export HOME="$(mktemp -d)"
|
||||
|
||||
cp -R ${nodeModules}/node_modules ./
|
||||
chmod -R u+w node_modules
|
||||
|
||||
(cd node_modules/better-sqlite3 && node-gyp rebuild --release)
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/bin" "$out/lib/qmd"
|
||||
|
||||
cp -r node_modules src package.json "$out/lib/qmd/"
|
||||
|
||||
makeWrapper ${bun}/bin/bun "$out/bin/qmd" \
|
||||
--add-flags "$out/lib/qmd/src/cli/qmd.ts" \
|
||||
--set DYLD_LIBRARY_PATH "${sqlite.out}/lib" \
|
||||
--set LD_LIBRARY_PATH "${sqlite.out}/lib"
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "On-device hybrid search for markdown knowledge bases";
|
||||
homepage = "https://github.com/tobi/qmd";
|
||||
license = licenses.mit;
|
||||
platforms = builtins.attrNames nodeModulesHashes;
|
||||
mainProgram = "qmd";
|
||||
};
|
||||
}
|
||||
@ -3,18 +3,18 @@
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/steipete/sag/releases/download/v0.2.2/sag_0.2.2_darwin_universal.tar.gz";
|
||||
hash = "sha256-BVS675EiF9nh85iPttdJLUbS9JEFpeuRdeP4YfOc0ok=";
|
||||
url = "https://github.com/steipete/sag/releases/download/v0.3.0/sag_0.3.0_darwin_universal.tar.gz";
|
||||
hash = "sha256-6JqfjA09qlBrdLp72NCbk0VNkZ/6zv5U0rXcYohAnxQ=";
|
||||
};
|
||||
"x86_64-linux" = {
|
||||
url = "https://github.com/steipete/sag/releases/download/v0.2.2/sag_0.2.2_linux_amd64.tar.gz";
|
||||
hash = "sha256-/d/iVTZI/ZzTRGYQxVF22JeFDXn0qpXWC2BdOH/6vcg=";
|
||||
url = "https://github.com/steipete/sag/releases/download/v0.3.0/sag_0.3.0_linux_amd64.tar.gz";
|
||||
hash = "sha256-jUwjqeORPphMX3xhX36zy/W/X10PP0wP5Ryz5ggpKX4=";
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "sag";
|
||||
version = "0.2.2";
|
||||
version = "0.3.0";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
|
||||
@ -3,22 +3,22 @@
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/steipete/sonoscli/releases/download/v0.1.0/sonoscli-macos-arm64.tar.gz";
|
||||
hash = "sha256-t5VUWXPrxgYXopiQEuO7k91Gx70oefyhbOZmF/XDwaw=";
|
||||
url = "https://github.com/steipete/sonoscli/releases/download/v0.3.0/sonoscli_0.3.0_darwin_arm64.tar.gz";
|
||||
hash = "sha256-xAVAldfh4R0xyGsEmNruBfICSFckv44z72fEshjTxJM=";
|
||||
};
|
||||
"x86_64-linux" = {
|
||||
url = "https://github.com/steipete/sonoscli/releases/download/v0.1.0/sonoscli_0.1.0_linux_amd64.tar.gz";
|
||||
hash = "sha256-8g/sTD4P8Ctbpv5N0nZ1SpP+UH6CUuUwNEo4VjW01ZM=";
|
||||
url = "https://github.com/steipete/sonoscli/releases/download/v0.3.0/sonoscli_0.3.0_linux_amd64.tar.gz";
|
||||
hash = "sha256-CQjaSE/eVu6rdExYcIuOhXCQp7842A+qo7dhPw1CDms=";
|
||||
};
|
||||
"aarch64-linux" = {
|
||||
url = "https://github.com/steipete/sonoscli/releases/download/v0.1.0/sonoscli_0.1.0_linux_arm64.tar.gz";
|
||||
hash = "sha256-EtBtsNcvD5OvryUjCQ5oy3H7w4etgfXs7PkdsefWdE0=";
|
||||
url = "https://github.com/steipete/sonoscli/releases/download/v0.3.0/sonoscli_0.3.0_linux_arm64.tar.gz";
|
||||
hash = "sha256-LpbCP/8DbknNeUmfCkm4gSBYPo723/e2r4KPT+1cpcc=";
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "sonoscli";
|
||||
version = "0.1.0";
|
||||
version = "0.3.0";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
|
||||
@ -15,17 +15,17 @@
|
||||
|
||||
let
|
||||
pname = "summarize";
|
||||
version = "0.11.1";
|
||||
version = "0.14.1";
|
||||
binSources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/steipete/summarize/releases/download/v0.11.1/summarize-macos-arm64-v0.11.1.tar.gz";
|
||||
hash = "sha256-RJNeCxWfbMCOrD6ABR4wSALdtl1sImS4Wkjz1TdPmTE=";
|
||||
url = "https://github.com/steipete/summarize/releases/download/v0.14.1/summarize-macos-arm64-v0.14.1.tar.gz";
|
||||
hash = "sha256-X3aBGiAjoOSIINxHyGOzg/b88fpCZQcv0cM2L0KqS8c=";
|
||||
};
|
||||
};
|
||||
|
||||
src = fetchurl {
|
||||
url = "https://github.com/steipete/summarize/archive/refs/tags/v${version}.tar.gz";
|
||||
hash = "sha256-2s3XAy9evHRZQthTWRhIjkv7Z40K5PiUFHEIGo6XxBs=";
|
||||
hash = "sha256-UAf7IXerk5VM3DyLyMD259Dh55ZjcZPK/3T2kPj9RO4=";
|
||||
};
|
||||
|
||||
pnpmFetchDepsPkg = pkgs.callPackage "${pkgs.path}/pkgs/build-support/node/fetch-pnpm-deps" {
|
||||
@ -36,7 +36,7 @@ let
|
||||
pname = pname;
|
||||
version = version;
|
||||
src = src;
|
||||
hash = "sha256-vqTXJ64DpanUwdUlSS3Fa+zy70qIGgsNtKzYF82J1RU=";
|
||||
hash = "sha256-s1I3TTX3uw/iJpbWvoNE5kg9XQVjw0mnA5pRTVs7ypY=";
|
||||
fetcherVersion = 3;
|
||||
});
|
||||
|
||||
@ -72,7 +72,7 @@ if stdenv.isLinux then
|
||||
PNPM_CONFIG_HOME = "/tmp/pnpm-config";
|
||||
XDG_CACHE_HOME = "/tmp/pnpm-cache";
|
||||
NPM_CONFIG_USERCONFIG = "/tmp/pnpm-config/.npmrc";
|
||||
npm_config_nodedir = "${nodejs.dev}";
|
||||
npm_config_nodedir = "${lib.getDev nodejs}";
|
||||
npm_config_build_from_source = "1";
|
||||
PNPM_CONFIG_IGNORE_SCRIPTS = "1";
|
||||
PNPM_CONFIG_MANAGE_PACKAGE_MANAGER_VERSIONS = "false";
|
||||
@ -120,6 +120,7 @@ if stdenv.isLinux then
|
||||
runHook preInstall
|
||||
mkdir -p "$out/libexec" "$out/libexec/packages" "$out/libexec/apps" "$out/bin"
|
||||
cp -r dist node_modules "$out/libexec/"
|
||||
find "$out/libexec/node_modules" -name ".pnpm-workspace-state-v1.json" -delete
|
||||
cp -r packages/core "$out/libexec/packages/"
|
||||
cp -r apps/chrome-extension "$out/libexec/apps/"
|
||||
chmod 0755 "$out/libexec/dist/cli.js"
|
||||
|
||||
53
nix/pkgs/wacrawl.nix
Normal file
53
nix/pkgs/wacrawl.nix
Normal file
@ -0,0 +1,53 @@
|
||||
{ lib, stdenv, fetchurl }:
|
||||
|
||||
let
|
||||
sources = {
|
||||
"aarch64-darwin" = {
|
||||
url = "https://github.com/steipete/wacrawl/releases/download/v0.2.4/wacrawl_0.2.4_darwin_arm64.tar.gz";
|
||||
hash = "sha256-vn6GN8inl06EwYCRW0xlDJMaj3iFP82Dbdt5HpH/NMo=";
|
||||
};
|
||||
"x86_64-linux" = {
|
||||
url = "https://github.com/steipete/wacrawl/releases/download/v0.2.4/wacrawl_0.2.4_linux_amd64.tar.gz";
|
||||
hash = "sha256-kK4E24aJw4ItLK3lw3tsEbfcFe8OeR7/iOJlTInMRiA=";
|
||||
};
|
||||
"aarch64-linux" = {
|
||||
url = "https://github.com/steipete/wacrawl/releases/download/v0.2.4/wacrawl_0.2.4_linux_arm64.tar.gz";
|
||||
hash = "sha256-k033oRaYjGdU9JVdQn3yfjmxWw/UtSPsjRTisVGCsfs=";
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
pname = "wacrawl";
|
||||
version = "0.2.4";
|
||||
|
||||
src = fetchurl sources.${stdenv.hostPlatform.system};
|
||||
|
||||
dontConfigure = true;
|
||||
dontBuild = true;
|
||||
|
||||
unpackPhase = ''
|
||||
tar -xzf "$src"
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p "$out/bin" "$out/share/doc/wacrawl"
|
||||
cp $(find . -type f -name wacrawl | head -1) "$out/bin/wacrawl"
|
||||
chmod 0755 "$out/bin/wacrawl"
|
||||
if [ -f LICENSE ]; then
|
||||
cp LICENSE "$out/share/doc/wacrawl/"
|
||||
fi
|
||||
if [ -f README.md ]; then
|
||||
cp README.md "$out/share/doc/wacrawl/"
|
||||
fi
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Read-only local archive and search for WhatsApp Desktop data";
|
||||
homepage = "https://github.com/steipete/wacrawl";
|
||||
license = licenses.mit;
|
||||
platforms = builtins.attrNames sources;
|
||||
mainProgram = "wacrawl";
|
||||
};
|
||||
}
|
||||
@ -1,224 +0,0 @@
|
||||
---
|
||||
name: bird
|
||||
description: X/Twitter CLI for reading, searching, posting, and engagement via cookies.
|
||||
homepage: https://bird.fast
|
||||
metadata:
|
||||
{
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "🐦",
|
||||
"requires": { "bins": ["bird"] },
|
||||
"install":
|
||||
[
|
||||
{
|
||||
"id": "brew",
|
||||
"kind": "brew",
|
||||
"formula": "steipete/tap/bird",
|
||||
"bins": ["bird"],
|
||||
"label": "Install bird (brew)",
|
||||
"os": ["darwin"],
|
||||
},
|
||||
{
|
||||
"id": "npm",
|
||||
"kind": "node",
|
||||
"package": "@steipete/bird",
|
||||
"bins": ["bird"],
|
||||
"label": "Install bird (npm)",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
---
|
||||
|
||||
# bird 🐦
|
||||
|
||||
Fast X/Twitter CLI using GraphQL + cookie auth.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
# npm/pnpm/bun
|
||||
npm install -g @steipete/bird
|
||||
|
||||
# Homebrew (macOS, prebuilt binary)
|
||||
brew install steipete/tap/bird
|
||||
|
||||
# One-shot (no install)
|
||||
bunx @steipete/bird whoami
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
`bird` uses cookie-based auth.
|
||||
|
||||
Use `--auth-token` / `--ct0` to pass cookies directly, or `--cookie-source` for browser cookies.
|
||||
|
||||
Run `bird check` to see which source is active. For Arc/Brave, use `--chrome-profile-dir <path>`.
|
||||
|
||||
## Commands
|
||||
|
||||
### Account & Auth
|
||||
|
||||
```bash
|
||||
bird whoami # Show logged-in account
|
||||
bird check # Show credential sources
|
||||
bird query-ids --fresh # Refresh GraphQL query ID cache
|
||||
```
|
||||
|
||||
### Reading Tweets
|
||||
|
||||
```bash
|
||||
bird read <url-or-id> # Read a single tweet
|
||||
bird <url-or-id> # Shorthand for read
|
||||
bird thread <url-or-id> # Full conversation thread
|
||||
bird replies <url-or-id> # List replies to a tweet
|
||||
```
|
||||
|
||||
### Timelines
|
||||
|
||||
```bash
|
||||
bird home # Home timeline (For You)
|
||||
bird home --following # Following timeline
|
||||
bird user-tweets @handle -n 20 # User's profile timeline
|
||||
bird mentions # Tweets mentioning you
|
||||
bird mentions --user @handle # Mentions of another user
|
||||
```
|
||||
|
||||
### Search
|
||||
|
||||
```bash
|
||||
bird search "query" -n 10
|
||||
bird search "from:steipete" --all --max-pages 3
|
||||
```
|
||||
|
||||
### News & Trending
|
||||
|
||||
```bash
|
||||
bird news -n 10 # AI-curated from Explore tabs
|
||||
bird news --ai-only # Filter to AI-curated only
|
||||
bird news --sports # Sports tab
|
||||
bird news --with-tweets # Include related tweets
|
||||
bird trending # Alias for news
|
||||
```
|
||||
|
||||
### Lists
|
||||
|
||||
```bash
|
||||
bird lists # Your lists
|
||||
bird lists --member-of # Lists you're a member of
|
||||
bird list-timeline <id> -n 20 # Tweets from a list
|
||||
```
|
||||
|
||||
### Bookmarks & Likes
|
||||
|
||||
```bash
|
||||
bird bookmarks -n 10
|
||||
bird bookmarks --folder-id <id> # Specific folder
|
||||
bird bookmarks --include-parent # Include parent tweet
|
||||
bird bookmarks --author-chain # Author's self-reply chain
|
||||
bird bookmarks --full-chain-only # Full reply chain
|
||||
bird unbookmark <url-or-id>
|
||||
bird likes -n 10
|
||||
```
|
||||
|
||||
### Social Graph
|
||||
|
||||
```bash
|
||||
bird following -n 20 # Users you follow
|
||||
bird followers -n 20 # Users following you
|
||||
bird following --user <id> # Another user's following
|
||||
bird about @handle # Account origin/location info
|
||||
```
|
||||
|
||||
### Engagement Actions
|
||||
|
||||
```bash
|
||||
bird follow @handle # Follow a user
|
||||
bird unfollow @handle # Unfollow a user
|
||||
```
|
||||
|
||||
### Posting
|
||||
|
||||
```bash
|
||||
bird tweet "hello world"
|
||||
bird reply <url-or-id> "nice thread!"
|
||||
bird tweet "check this out" --media image.png --alt "description"
|
||||
```
|
||||
|
||||
**⚠️ Posting risks**: Posting is more likely to be rate limited; if blocked, use the browser tool instead.
|
||||
|
||||
## Media Uploads
|
||||
|
||||
```bash
|
||||
bird tweet "hi" --media img.png --alt "description"
|
||||
bird tweet "pics" --media a.jpg --media b.jpg # Up to 4 images
|
||||
bird tweet "video" --media clip.mp4 # Or 1 video
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
Commands supporting pagination: `replies`, `thread`, `search`, `bookmarks`, `likes`, `list-timeline`, `following`, `followers`, `user-tweets`
|
||||
|
||||
```bash
|
||||
bird bookmarks --all # Fetch all pages
|
||||
bird bookmarks --max-pages 3 # Limit pages
|
||||
bird bookmarks --cursor <cursor> # Resume from cursor
|
||||
bird replies <id> --all --delay 1000 # Delay between pages (ms)
|
||||
```
|
||||
|
||||
## Output Options
|
||||
|
||||
```bash
|
||||
--json # JSON output
|
||||
--json-full # JSON with raw API response
|
||||
--plain # No emoji, no color (script-friendly)
|
||||
--no-emoji # Disable emoji
|
||||
--no-color # Disable ANSI colors (or set NO_COLOR=1)
|
||||
--quote-depth n # Max quoted tweet depth in JSON (default: 1)
|
||||
```
|
||||
|
||||
## Global Options
|
||||
|
||||
```bash
|
||||
--auth-token <token> # Set auth_token cookie
|
||||
--ct0 <token> # Set ct0 cookie
|
||||
--cookie-source <source> # Cookie source for browser cookies (repeatable)
|
||||
--chrome-profile <name> # Chrome profile name
|
||||
--chrome-profile-dir <path> # Chrome/Chromium profile dir or cookie DB path
|
||||
--firefox-profile <name> # Firefox profile
|
||||
--timeout <ms> # Request timeout
|
||||
--cookie-timeout <ms> # Cookie extraction timeout
|
||||
```
|
||||
|
||||
## Config File
|
||||
|
||||
`~/.config/bird/config.json5` (global) or `./.birdrc.json5` (project):
|
||||
|
||||
```json5
|
||||
{
|
||||
cookieSource: ["chrome"],
|
||||
chromeProfileDir: "/path/to/Arc/Profile",
|
||||
timeoutMs: 20000,
|
||||
quoteDepth: 1,
|
||||
}
|
||||
```
|
||||
|
||||
Environment variables: `BIRD_TIMEOUT_MS`, `BIRD_COOKIE_TIMEOUT_MS`, `BIRD_QUOTE_DEPTH`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Query IDs stale (404 errors)
|
||||
|
||||
```bash
|
||||
bird query-ids --fresh
|
||||
```
|
||||
|
||||
### Cookie extraction fails
|
||||
|
||||
- Check browser is logged into X
|
||||
- Try different `--cookie-source`
|
||||
- For Arc/Brave: use `--chrome-profile-dir`
|
||||
|
||||
---
|
||||
|
||||
**TL;DR**: Read/search/engage with CLI. Post carefully or use browser. 🐦
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
description = "openclaw plugin: bird";
|
||||
description = "openclaw plugin: discrawl";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "path:../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
@ -13,24 +13,24 @@
|
||||
pluginFor = system:
|
||||
let
|
||||
packagesForSystem = root.packages.${system} or {};
|
||||
bird = packagesForSystem.bird or null;
|
||||
discrawl = packagesForSystem.discrawl or null;
|
||||
in
|
||||
if bird == null then null else {
|
||||
name = "bird";
|
||||
skills = [ ./skills/bird ];
|
||||
packages = [ bird ];
|
||||
if discrawl == null then null else {
|
||||
name = "discrawl";
|
||||
skills = [ ./skills/discrawl ];
|
||||
packages = [ discrawl ];
|
||||
needs = {
|
||||
stateDirs = [];
|
||||
requiredEnv = [];
|
||||
stateDirs = [ ".discrawl" ];
|
||||
requiredEnv = [ ];
|
||||
};
|
||||
};
|
||||
in {
|
||||
packages = lib.genAttrs systems (system:
|
||||
let
|
||||
bird = (root.packages.${system} or {}).bird or null;
|
||||
discrawl = (root.packages.${system} or {}).discrawl or null;
|
||||
in
|
||||
if bird == null then {}
|
||||
else { bird = bird; }
|
||||
if discrawl == null then {}
|
||||
else { discrawl = discrawl; }
|
||||
);
|
||||
|
||||
openclawPlugin = pluginFor;
|
||||
74
tools/discrawl/skills/discrawl/SKILL.md
Normal file
74
tools/discrawl/skills/discrawl/SKILL.md
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
name: discrawl
|
||||
description: Mirror Discord guild history into local SQLite and query it offline with search, messages, mentions, reports, and DM wiretap import.
|
||||
homepage: https://github.com/openclaw/discrawl
|
||||
metadata:
|
||||
{
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "🛰️",
|
||||
"requires": { "bins": ["discrawl"] },
|
||||
"install":
|
||||
[
|
||||
{
|
||||
"id": "brew",
|
||||
"kind": "brew",
|
||||
"formula": "steipete/tap/discrawl",
|
||||
"bins": ["discrawl"],
|
||||
"label": "Install discrawl (brew)",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
---
|
||||
|
||||
# discrawl
|
||||
|
||||
Use `discrawl` to mirror Discord guild data into local SQLite, then query it offline.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when the user wants to:
|
||||
|
||||
- search Discord history locally without relying on Discord search
|
||||
- archive a guild into SQLite for later queries
|
||||
- inspect recent messages, mentions, channels, or members from a local archive
|
||||
- import local Discord Desktop cache data for DM recovery/search
|
||||
- publish or subscribe to a Git-backed Discord archive snapshot
|
||||
|
||||
## Requirements
|
||||
|
||||
- Discord bot token for guild sync, or an existing OpenClaw Discord config
|
||||
- local Discord Desktop cache files only if using `wiretap`
|
||||
- enough local disk for SQLite archive growth
|
||||
|
||||
## Setup
|
||||
|
||||
- Default config: `~/.discrawl/config.toml`
|
||||
- Default database: `~/.discrawl/discrawl.db`
|
||||
- Fastest setup when OpenClaw already has Discord configured:
|
||||
- `discrawl init --from-openclaw ~/.openclaw/openclaw.json`
|
||||
- Env-only setup:
|
||||
- `export DISCORD_BOT_TOKEN="..."`
|
||||
- `discrawl init`
|
||||
|
||||
## Common Commands
|
||||
|
||||
- Doctor: `discrawl doctor`
|
||||
- Initial history: `discrawl sync --full`
|
||||
- Incremental refresh: `discrawl sync`
|
||||
- Live tail: `discrawl tail`
|
||||
- Search: `discrawl search "panic nil pointer"`
|
||||
- Recent channel messages: `discrawl messages --channel general --hours 24`
|
||||
- Mentions: `discrawl mentions --user <user-id>`
|
||||
- DM cache import: `discrawl wiretap`
|
||||
- Local DM search: `discrawl dms --search "launch checklist"`
|
||||
- Read-only SQL: `discrawl sql "select count(*) from messages"`
|
||||
- Git-backed reader mode: `discrawl subscribe <private-repo-url>`
|
||||
|
||||
## Notes
|
||||
|
||||
- Bot-token sync reads only guilds/channels the bot can access.
|
||||
- `wiretap` uses local Discord Desktop cache files only; it does not use a user token.
|
||||
- Prefer `discrawl doctor` before a first sync.
|
||||
- Use `sync --full` once for backfill, then plain `sync` for routine refreshes.
|
||||
59
tools/gogcli/flake.lock
generated
Normal file
59
tools/gogcli/flake.lock
generated
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1767364772,
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1767364772,
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"root": "root_2"
|
||||
}
|
||||
},
|
||||
"root_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"path": "../..",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"path": "../..",
|
||||
"type": "path"
|
||||
},
|
||||
"parent": []
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
59
tools/goplaces/flake.lock
generated
Normal file
59
tools/goplaces/flake.lock
generated
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1767364772,
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1767364772,
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"root": "root_2"
|
||||
}
|
||||
},
|
||||
"root_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"path": "../..",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"path": "../..",
|
||||
"type": "path"
|
||||
},
|
||||
"parent": []
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=6352c8247b3b889d7f17bce1f09d6c58fd34932c&narHash=sha256-nfCSSyNU97XpKVPgo6mODBwrVeTOuMCl3i18QuGjpN0=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
---
|
||||
name: goplaces
|
||||
description: Query Google Places API (New) via the goplaces CLI for text search, place details, resolve, and reviews. Use for human-friendly place lookup or JSON output for scripts.
|
||||
homepage: https://github.com/steipete/goplaces
|
||||
metadata: {"clawdbot":{"emoji":"📍","requires":{"bins":["goplaces"],"env":["GOOGLE_PLACES_API_KEY"]},"primaryEnv":"GOOGLE_PLACES_API_KEY","install":[{"id":"brew","kind":"brew","formula":"steipete/tap/goplaces","bins":["goplaces"],"label":"Install goplaces (brew)"}]}}
|
||||
homepage: https://github.com/openclaw/goplaces
|
||||
metadata: {"openclaw":{"emoji":"📍","requires":{"bins":["goplaces"],"env":["GOOGLE_PLACES_API_KEY"]},"primaryEnv":"GOOGLE_PLACES_API_KEY","install":[{"id":"brew","kind":"brew","formula":"steipete/tap/goplaces","bins":["goplaces"],"label":"Install goplaces (brew)"}]}}
|
||||
---
|
||||
|
||||
# goplaces
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: imsg
|
||||
description: iMessage/SMS CLI for listing chats, history, watch, and sending.
|
||||
description: iMessage/SMS CLI for listing chats, history, and sending messages via Messages.app.
|
||||
homepage: https://imsg.to
|
||||
metadata:
|
||||
{
|
||||
@ -23,52 +23,100 @@ metadata:
|
||||
}
|
||||
---
|
||||
|
||||
# imsg Actions
|
||||
# imsg
|
||||
|
||||
## Overview
|
||||
Use `imsg` to read and send iMessage/SMS via macOS Messages.app.
|
||||
|
||||
Use `imsg` to read and send Messages.app iMessage/SMS on macOS.
|
||||
## When to Use
|
||||
|
||||
Requirements: Messages.app signed in, Full Disk Access for your terminal, and Automation permission to control Messages.app for sending.
|
||||
✅ **USE this skill when:**
|
||||
|
||||
## Inputs to collect
|
||||
- User explicitly asks to send iMessage or SMS
|
||||
- Reading iMessage conversation history
|
||||
- Checking recent Messages.app chats
|
||||
- Sending to phone numbers or Apple IDs
|
||||
|
||||
- Recipient handle (phone/email) for `send`
|
||||
- `chatId` for history/watch (from `imsg chats --limit 10 --json`)
|
||||
- `text` and optional `file` path for sends
|
||||
## When NOT to Use
|
||||
|
||||
## Actions
|
||||
❌ **DON'T use this skill when:**
|
||||
|
||||
### List chats
|
||||
- Telegram messages → use `message` tool with `channel:telegram`
|
||||
- Signal messages → use Signal channel if configured
|
||||
- WhatsApp messages → use WhatsApp channel if configured
|
||||
- Discord messages → use `message` tool with `channel:discord`
|
||||
- Slack messages → use `slack` skill
|
||||
- Group chat management (adding/removing members) → not supported
|
||||
- Bulk/mass messaging → always confirm with user first
|
||||
- Replying in current conversation → just reply normally (OpenClaw routes automatically)
|
||||
|
||||
## Requirements
|
||||
|
||||
- macOS with Messages.app signed in
|
||||
- Full Disk Access for terminal
|
||||
- Automation permission for Messages.app (for sending)
|
||||
|
||||
## Common Commands
|
||||
|
||||
### List Chats
|
||||
|
||||
```bash
|
||||
imsg chats --limit 10 --json
|
||||
```
|
||||
|
||||
### Fetch chat history
|
||||
### View History
|
||||
|
||||
```bash
|
||||
# By chat ID
|
||||
imsg history --chat-id 1 --limit 20 --json
|
||||
|
||||
# With attachments info
|
||||
imsg history --chat-id 1 --limit 20 --attachments --json
|
||||
```
|
||||
|
||||
### Watch a chat
|
||||
### Watch for New Messages
|
||||
|
||||
```bash
|
||||
imsg watch --chat-id 1 --attachments
|
||||
```
|
||||
|
||||
### Send a message
|
||||
### Send Messages
|
||||
|
||||
```bash
|
||||
imsg send --to "+14155551212" --text "hi" --file /path/pic.jpg
|
||||
# Text only
|
||||
imsg send --to "+14155551212" --text "Hello!"
|
||||
|
||||
# With attachment
|
||||
imsg send --to "+14155551212" --text "Check this out" --file /path/to/image.jpg
|
||||
|
||||
# Specify service
|
||||
imsg send --to "+14155551212" --text "Hi" --service imessage
|
||||
imsg send --to "+14155551212" --text "Hi" --service sms
|
||||
```
|
||||
|
||||
## Notes
|
||||
## Service Options
|
||||
|
||||
- `--service imessage|sms|auto` controls delivery.
|
||||
- Confirm recipient + message before sending.
|
||||
- `--service imessage` — Force iMessage (requires recipient has iMessage)
|
||||
- `--service sms` — Force SMS (green bubble)
|
||||
- `--service auto` — Let Messages.app decide (default)
|
||||
|
||||
## Ideas to try
|
||||
## Safety Rules
|
||||
|
||||
- Use `imsg chats --limit 10 --json` to discover chat ids.
|
||||
- Watch a high-signal chat to stream incoming messages.
|
||||
1. **Always confirm recipient and message content** before sending
|
||||
2. **Never send to unknown numbers** without explicit user approval
|
||||
3. **Be careful with attachments** — confirm file path exists
|
||||
4. **Rate limit yourself** — don't spam
|
||||
|
||||
## Example Workflow
|
||||
|
||||
User: "Text mom that I'll be late"
|
||||
|
||||
```bash
|
||||
# 1. Find mom's chat
|
||||
imsg chats --limit 20 --json | jq '.[] | select(.displayName | contains("Mom"))'
|
||||
|
||||
# 2. Confirm with user
|
||||
# "Found Mom at +1555123456. Send 'I'll be late' via iMessage?"
|
||||
|
||||
# 3. Send after confirmation
|
||||
imsg send --to "+1555123456" --text "I'll be late"
|
||||
```
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
{
|
||||
description = "openclaw plugin: oracle";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
let
|
||||
lib = nixpkgs.lib;
|
||||
systems = builtins.attrNames root.packages;
|
||||
pluginFor = system:
|
||||
let
|
||||
packagesForSystem = root.packages.${system} or {};
|
||||
oracle = packagesForSystem.oracle or null;
|
||||
in
|
||||
if oracle == null then null else {
|
||||
name = "oracle";
|
||||
skills = [ ./skills/oracle ];
|
||||
packages = [ oracle ];
|
||||
needs = {
|
||||
stateDirs = [];
|
||||
requiredEnv = [];
|
||||
};
|
||||
};
|
||||
in {
|
||||
packages = lib.genAttrs systems (system:
|
||||
let
|
||||
oracle = (root.packages.${system} or {}).oracle or null;
|
||||
in
|
||||
if oracle == null then {}
|
||||
else { oracle = oracle; }
|
||||
);
|
||||
|
||||
openclawPlugin = pluginFor;
|
||||
};
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
---
|
||||
name: oracle
|
||||
description: Best practices for using the oracle CLI (prompt + file bundling, engines, sessions, and file attachment patterns).
|
||||
homepage: https://askoracle.dev
|
||||
metadata:
|
||||
{
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "🧿",
|
||||
"requires": { "bins": ["oracle"] },
|
||||
"install":
|
||||
[
|
||||
{
|
||||
"id": "node",
|
||||
"kind": "node",
|
||||
"package": "@steipete/oracle",
|
||||
"bins": ["oracle"],
|
||||
"label": "Install oracle (node)",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
---
|
||||
|
||||
# oracle — best use
|
||||
|
||||
Oracle bundles your prompt + selected files into one “one-shot” request so another model can answer with real repo context (API or browser automation). Treat output as advisory: verify against code + tests.
|
||||
|
||||
## Main use case (browser, GPT‑5.2 Pro)
|
||||
|
||||
Default workflow here: `--engine browser` with GPT‑5.2 Pro in ChatGPT. This is the common “long think” path: ~10 minutes to ~1 hour is normal; expect a stored session you can reattach to.
|
||||
|
||||
Recommended defaults:
|
||||
|
||||
- Engine: browser (`--engine browser`)
|
||||
- Model: GPT‑5.2 Pro (`--model gpt-5.2-pro` or `--model "5.2 Pro"`)
|
||||
|
||||
## Golden path
|
||||
|
||||
1. Pick a tight file set (fewest files that still contain the truth).
|
||||
2. Preview payload + token spend (`--dry-run` + `--files-report`).
|
||||
3. Use browser mode for the usual GPT‑5.2 Pro workflow; use API only when you explicitly want it.
|
||||
4. If the run detaches/timeouts: reattach to the stored session (don’t re-run).
|
||||
|
||||
## Commands (preferred)
|
||||
|
||||
- Help:
|
||||
- `oracle --help`
|
||||
- If the binary isn’t installed: `npx -y @steipete/oracle --help` (avoid `pnpx` here; sqlite bindings).
|
||||
|
||||
- Preview (no tokens):
|
||||
- `oracle --dry-run summary -p "<task>" --file "src/**" --file "!**/*.test.*"`
|
||||
- `oracle --dry-run full -p "<task>" --file "src/**"`
|
||||
|
||||
- Token sanity:
|
||||
- `oracle --dry-run summary --files-report -p "<task>" --file "src/**"`
|
||||
|
||||
- Browser run (main path; long-running is normal):
|
||||
- `oracle --engine browser --model gpt-5.2-pro -p "<task>" --file "src/**"`
|
||||
|
||||
- Manual paste fallback:
|
||||
- `oracle --render --copy -p "<task>" --file "src/**"`
|
||||
- Note: `--copy` is a hidden alias for `--copy-markdown`.
|
||||
|
||||
## Attaching files (`--file`)
|
||||
|
||||
`--file` accepts files, directories, and globs. You can pass it multiple times; entries can be comma-separated.
|
||||
|
||||
- Include:
|
||||
- `--file "src/**"`
|
||||
- `--file src/index.ts`
|
||||
- `--file docs --file README.md`
|
||||
|
||||
- Exclude:
|
||||
- `--file "src/**" --file "!src/**/*.test.ts" --file "!**/*.snap"`
|
||||
|
||||
- Defaults (implementation behavior):
|
||||
- Default-ignored dirs: `node_modules`, `dist`, `coverage`, `.git`, `.turbo`, `.next`, `build`, `tmp` (skipped unless explicitly passed as literal dirs/files).
|
||||
- Honors `.gitignore` when expanding globs.
|
||||
- Does not follow symlinks.
|
||||
- Dotfiles filtered unless opted in via pattern (e.g. `--file ".github/**"`).
|
||||
- Files > 1 MB rejected.
|
||||
|
||||
## Engines (API vs browser)
|
||||
|
||||
- Auto-pick: `api` when `OPENAI_API_KEY` is set; otherwise `browser`.
|
||||
- Browser supports GPT + Gemini only; use `--engine api` for Claude/Grok/Codex or multi-model runs.
|
||||
- Browser attachments:
|
||||
- `--browser-attachments auto|never|always` (auto pastes inline up to ~60k chars then uploads).
|
||||
- Remote browser host:
|
||||
- Host: `oracle serve --host 0.0.0.0 --port 9473 --token <secret>`
|
||||
- Client: `oracle --engine browser --remote-host <host:port> --remote-token <secret> -p "<task>" --file "src/**"`
|
||||
|
||||
## Sessions + slugs
|
||||
|
||||
- Stored under `~/.oracle/sessions` (override with `ORACLE_HOME_DIR`).
|
||||
- Runs may detach or take a long time (browser + GPT‑5.2 Pro often does). If the CLI times out: don’t re-run; reattach.
|
||||
- List: `oracle status --hours 72`
|
||||
- Attach: `oracle session <id> --render`
|
||||
- Use `--slug "<3-5 words>"` to keep session IDs readable.
|
||||
- Duplicate prompt guard exists; use `--force` only when you truly want a fresh run.
|
||||
|
||||
## Prompt template (high signal)
|
||||
|
||||
Oracle starts with **zero** project knowledge. Assume the model cannot infer your stack, build tooling, conventions, or “obvious” paths. Include:
|
||||
|
||||
- Project briefing (stack + build/test commands + platform constraints).
|
||||
- “Where things live” (key directories, entrypoints, config files, boundaries).
|
||||
- Exact question + what you tried + the error text (verbatim).
|
||||
- Constraints (“don’t change X”, “must keep public API”, etc).
|
||||
- Desired output (“return patch plan + tests”, “give 3 options with tradeoffs”).
|
||||
|
||||
## Safety
|
||||
|
||||
- Don’t attach secrets by default (`.env`, key files, auth tokens). Redact aggressively; share only what’s required.
|
||||
|
||||
## “Exhaustive prompt” restoration pattern
|
||||
|
||||
For long investigations, write a standalone prompt + file set so you can rerun days later:
|
||||
|
||||
- 6–30 sentence project briefing + the goal.
|
||||
- Repro steps + exact errors + what you tried.
|
||||
- Attach all context files needed (entrypoints, configs, key modules, docs).
|
||||
|
||||
Oracle runs are one-shot; the model doesn’t remember prior runs. “Restoring context” means re-running with the same prompt + `--file …` set (or reattaching a still-running stored session).
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
48
tools/qmd/flake.nix
Normal file
48
tools/qmd/flake.nix
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
description = "openclaw plugin: qmd";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
root,
|
||||
}:
|
||||
let
|
||||
lib = nixpkgs.lib;
|
||||
systems = builtins.attrNames root.packages;
|
||||
pluginFor =
|
||||
system:
|
||||
let
|
||||
packagesForSystem = root.packages.${system} or { };
|
||||
qmd = packagesForSystem.qmd or null;
|
||||
in
|
||||
if qmd == null then
|
||||
null
|
||||
else
|
||||
{
|
||||
name = "qmd";
|
||||
skills = [ ./skills/qmd ];
|
||||
packages = [ qmd ];
|
||||
needs = {
|
||||
stateDirs = [ ".local/share/qmd" ];
|
||||
requiredEnv = [ ];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = lib.genAttrs systems (
|
||||
system:
|
||||
let
|
||||
qmd = (root.packages.${system} or { }).qmd or null;
|
||||
in
|
||||
if qmd == null then { } else { qmd = qmd; }
|
||||
);
|
||||
|
||||
openclawPlugin = pluginFor;
|
||||
};
|
||||
}
|
||||
143
tools/qmd/skills/qmd/SKILL.md
Normal file
143
tools/qmd/skills/qmd/SKILL.md
Normal file
@ -0,0 +1,143 @@
|
||||
---
|
||||
name: qmd
|
||||
description: Search markdown knowledge bases, notes, and documentation using QMD. Use when users ask to search notes, find documents, or look up information.
|
||||
license: MIT
|
||||
compatibility: Requires qmd CLI or MCP server. In Nix OpenClaw, use the qmd package/plugin from nix-openclaw-tools.
|
||||
metadata:
|
||||
author: tobi
|
||||
version: "2.0.0"
|
||||
allowed-tools: Bash(qmd:*), mcp__qmd__*
|
||||
---
|
||||
|
||||
# QMD - Quick Markdown Search
|
||||
|
||||
Local search engine for markdown content.
|
||||
|
||||
## Status
|
||||
|
||||
!`qmd status 2>/dev/null || echo "qmd CLI not found on this runtime PATH"`
|
||||
|
||||
## MCP: `query`
|
||||
|
||||
```json
|
||||
{
|
||||
"searches": [
|
||||
{ "type": "lex", "query": "CAP theorem consistency" },
|
||||
{ "type": "vec", "query": "tradeoff between consistency and availability" }
|
||||
],
|
||||
"collections": ["docs"],
|
||||
"limit": 10
|
||||
}
|
||||
```
|
||||
|
||||
### Query Types
|
||||
|
||||
| Type | Method | Input |
|
||||
|------|--------|-------|
|
||||
| `lex` | BM25 | Keywords — exact terms, names, code |
|
||||
| `vec` | Vector | Question — natural language |
|
||||
| `hyde` | Vector | Answer — hypothetical result (50-100 words) |
|
||||
|
||||
### Writing Good Queries
|
||||
|
||||
**lex (keyword)**
|
||||
- 2-5 terms, no filler words
|
||||
- Exact phrase: `"connection pool"` (quoted)
|
||||
- Exclude terms: `performance -sports` (minus prefix)
|
||||
- Code identifiers work: `handleError async`
|
||||
|
||||
**vec (semantic)**
|
||||
- Full natural language question
|
||||
- Be specific: `"how does the rate limiter handle burst traffic"`
|
||||
- Include context: `"in the payment service, how are refunds processed"`
|
||||
|
||||
**hyde (hypothetical document)**
|
||||
- Write 50-100 words of what the *answer* looks like
|
||||
- Use the vocabulary you expect in the result
|
||||
|
||||
**expand (auto-expand)**
|
||||
- Use a single-line query (implicit) or `expand: question` on its own line
|
||||
- Lets the local LLM generate lex/vec/hyde variations
|
||||
- Do not mix `expand:` with other typed lines — it's either a standalone expand query or a full query document
|
||||
|
||||
### Intent (Disambiguation)
|
||||
|
||||
When a query term is ambiguous, add `intent` to steer results:
|
||||
|
||||
```json
|
||||
{
|
||||
"searches": [
|
||||
{ "type": "lex", "query": "performance" }
|
||||
],
|
||||
"intent": "web page load times and Core Web Vitals"
|
||||
}
|
||||
```
|
||||
|
||||
Intent affects expansion, reranking, chunk selection, and snippet extraction. It does not search on its own — it's a steering signal that disambiguates queries like "performance" (web-perf vs team health vs fitness).
|
||||
|
||||
### Combining Types
|
||||
|
||||
| Goal | Approach |
|
||||
|------|----------|
|
||||
| Know exact terms | `lex` only |
|
||||
| Don't know vocabulary | Use a single-line query (implicit `expand:`) or `vec` |
|
||||
| Best recall | `lex` + `vec` |
|
||||
| Complex topic | `lex` + `vec` + `hyde` |
|
||||
| Ambiguous query | Add `intent` to any combination above |
|
||||
|
||||
First query gets 2x weight in fusion — put your best guess first.
|
||||
|
||||
### Lex Query Syntax
|
||||
|
||||
| Syntax | Meaning | Example |
|
||||
|--------|---------|---------|
|
||||
| `term` | Prefix match | `perf` matches "performance" |
|
||||
| `"phrase"` | Exact phrase | `"rate limiter"` |
|
||||
| `-term` | Exclude | `performance -sports` |
|
||||
|
||||
Note: `-term` only works in lex queries, not vec/hyde.
|
||||
|
||||
### Collection Filtering
|
||||
|
||||
```json
|
||||
{ "collections": ["docs"] } // Single
|
||||
{ "collections": ["docs", "notes"] } // Multiple (OR)
|
||||
```
|
||||
|
||||
Omit to search all collections.
|
||||
|
||||
## Other MCP Tools
|
||||
|
||||
| Tool | Use |
|
||||
|------|-----|
|
||||
| `get` | Retrieve doc by path or `#docid` |
|
||||
| `multi_get` | Retrieve multiple by glob/list |
|
||||
| `status` | Collections and health |
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
qmd query "question" # Auto-expand + rerank
|
||||
qmd query $'lex: X\nvec: Y' # Structured
|
||||
qmd query $'expand: question' # Explicit expand
|
||||
qmd query --json --explain "q" # Show score traces (RRF + rerank blend)
|
||||
qmd search "keywords" # BM25 only (no LLM)
|
||||
qmd get "#abc123" # By docid
|
||||
qmd multi-get "journals/2026-*.md" -l 40 # Batch pull snippets by glob
|
||||
qmd multi-get notes/foo.md,notes/bar.md # Comma-separated list, preserves order
|
||||
```
|
||||
|
||||
## HTTP API
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8181/query \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"searches": [{"type": "lex", "query": "test"}]}'
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
qmd collection add ~/notes --name notes
|
||||
qmd embed
|
||||
```
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
@ -6,7 +6,7 @@ metadata:
|
||||
{
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "🗣️",
|
||||
"emoji": "🔊",
|
||||
"requires": { "bins": ["sag"], "env": ["ELEVENLABS_API_KEY"] },
|
||||
"primaryEnv": "ELEVENLABS_API_KEY",
|
||||
"install":
|
||||
@ -68,7 +68,7 @@ Confirm voice + speaker before long output.
|
||||
|
||||
## Chat voice responses
|
||||
|
||||
When Peter asks for a "voice" reply (e.g., "crazy scientist voice", "explain in voice"), generate audio and send it:
|
||||
When the user asks for a "voice" reply (e.g., "crazy scientist voice", "explain in voice"), generate audio and send it:
|
||||
|
||||
```bash
|
||||
# Generate audio file
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
@ -26,21 +26,40 @@ metadata:
|
||||
|
||||
Use `sonos` to control Sonos speakers on the local network.
|
||||
|
||||
Quick start
|
||||
## Quick start
|
||||
|
||||
- `sonos discover`
|
||||
- `sonos status --name "Kitchen"`
|
||||
- `sonos play|pause|stop --name "Kitchen"`
|
||||
- `sonos volume set 15 --name "Kitchen"`
|
||||
|
||||
Common tasks
|
||||
## Common tasks
|
||||
|
||||
- Grouping: `sonos group status|join|unjoin|party|solo`
|
||||
- Favorites: `sonos favorites list|open`
|
||||
- Queue: `sonos queue list|play|clear`
|
||||
- Spotify search (via SMAPI): `sonos smapi search --service "Spotify" --category tracks "query"`
|
||||
|
||||
Notes
|
||||
## Notes
|
||||
|
||||
- If SSDP fails, specify `--ip <speaker-ip>`.
|
||||
- Spotify Web API search is optional and requires `SPOTIFY_CLIENT_ID/SECRET`.
|
||||
- If there is an error, check the troubleshooting section and offer advice if there is a decent match.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `sonos discover` - `no route to host`
|
||||
|
||||
- On error `Error: write udp4 0.0.0.0:64326->239.255.255.250:1900: sendto: no route to host (Command exited with code 1)`
|
||||
- Note that the port number after `0.0.0.0:` is ephemeral and will change and the netmask may not match exactly either
|
||||
- The `sendto: no route to host` should stay consistent
|
||||
- Advise the user that in `direct` (no Docker sandbox) mode on Mac OS that Settings -> Privacy & Security -> Local Network will need to be enabled for the top-level host parent process of the Gateway
|
||||
- `node` if running via `launchd`
|
||||
- `Terminal` if running the gateway directly in the terminal
|
||||
- `Visual Studio Code` if running via terminal in VS Code
|
||||
- An alternative option is to use `sandbox` (docker container) with network access allowed for that sandbox
|
||||
|
||||
### `sonos discover` - `bind: operation not permitted`
|
||||
|
||||
- On error `Error: listen udp4 0.0.0.0:0: bind: operation not permitted`
|
||||
- Advise the user that they may be running via a Codex or other sandbox that does not permit network access (this can be replicated by running `sonos discover` within a Codex CLI session with sandbox enabled and not approving the escalation request)
|
||||
|
||||
59
tools/summarize/flake.lock
generated
Normal file
59
tools/summarize/flake.lock
generated
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1767364772,
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1767364772,
|
||||
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"root": "root_2"
|
||||
}
|
||||
},
|
||||
"root_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"path": "../..",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"path": "../..",
|
||||
"type": "path"
|
||||
},
|
||||
"parent": []
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "github:openclaw/nix-steipete-tools?rev=dbf0a31a57407d9140e32357ea8d0215bd9feed9&narHash=sha256-QkPl/Rgk9DXgaVNhjvHHHjy5e81j+MzcVOouZRdUTLA=";
|
||||
root.url = "../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: summarize
|
||||
description: Summarize or extract text/transcripts from URLs, podcasts, and local files (great fallback for “transcribe this YouTube/video”).
|
||||
description: Summarize or transcribe URLs, YouTube/videos, podcasts, articles, transcripts, PDFs, and local files.
|
||||
homepage: https://summarize.sh
|
||||
metadata:
|
||||
{
|
||||
|
||||
38
tools/wacrawl/flake.nix
Normal file
38
tools/wacrawl/flake.nix
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
description = "openclaw plugin: wacrawl";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?rev=16c7794d0a28b5a37904d55bcca36003b9109aaa&narHash=sha256-fFUnEYMla8b7UKjijLnMe%2BoVFOz6HjijGGNS1l7dYaQ%3D";
|
||||
root.url = "path:../..";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, root }:
|
||||
let
|
||||
lib = nixpkgs.lib;
|
||||
systems = builtins.attrNames root.packages;
|
||||
pluginFor = system:
|
||||
let
|
||||
packagesForSystem = root.packages.${system} or {};
|
||||
wacrawl = packagesForSystem.wacrawl or null;
|
||||
in
|
||||
if wacrawl == null then null else {
|
||||
name = "wacrawl";
|
||||
skills = [ ./skills/wacrawl ];
|
||||
packages = [ wacrawl ];
|
||||
needs = {
|
||||
stateDirs = [ ".wacrawl" ];
|
||||
requiredEnv = [ ];
|
||||
};
|
||||
};
|
||||
in {
|
||||
packages = lib.genAttrs systems (system:
|
||||
let
|
||||
wacrawl = (root.packages.${system} or {}).wacrawl or null;
|
||||
in
|
||||
if wacrawl == null then {}
|
||||
else { wacrawl = wacrawl; }
|
||||
);
|
||||
|
||||
openclawPlugin = pluginFor;
|
||||
};
|
||||
}
|
||||
69
tools/wacrawl/skills/wacrawl/SKILL.md
Normal file
69
tools/wacrawl/skills/wacrawl/SKILL.md
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
name: wacrawl
|
||||
description: Read-only local archive and search for WhatsApp Desktop chats, messages, and media metadata.
|
||||
homepage: https://github.com/steipete/wacrawl
|
||||
metadata:
|
||||
{
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "💬",
|
||||
"requires": { "bins": ["wacrawl"] },
|
||||
"install":
|
||||
[
|
||||
{
|
||||
"id": "brew",
|
||||
"kind": "brew",
|
||||
"formula": "steipete/tap/wacrawl",
|
||||
"bins": ["wacrawl"],
|
||||
"label": "Install wacrawl (brew)",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
---
|
||||
|
||||
# wacrawl
|
||||
|
||||
Use `wacrawl` to snapshot local WhatsApp Desktop data into a separate SQLite archive and search it offline.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when the user wants to:
|
||||
|
||||
- inspect local WhatsApp Desktop history without opening the app
|
||||
- archive chats into a local SQLite database for repeat queries
|
||||
- search WhatsApp messages locally with filters
|
||||
- list chats, recent messages, or archive status from a read-only import
|
||||
|
||||
## Requirements
|
||||
|
||||
- local WhatsApp Desktop data on the same machine
|
||||
- enough local disk for `~/.wacrawl/wacrawl.db`
|
||||
- understand that this is read-only inspection, not message sending
|
||||
|
||||
## Setup
|
||||
|
||||
- Default source: `~/Library/Group Containers/group.net.whatsapp.WhatsApp.shared`
|
||||
- Default archive DB: `~/.wacrawl/wacrawl.db`
|
||||
- First sanity check:
|
||||
- `wacrawl doctor`
|
||||
- First import:
|
||||
- `wacrawl import`
|
||||
|
||||
## Common Commands
|
||||
|
||||
- Doctor: `wacrawl doctor`
|
||||
- Import fresh snapshot: `wacrawl import`
|
||||
- Archive status: `wacrawl status`
|
||||
- List chats: `wacrawl chats --limit 20`
|
||||
- Recent messages: `wacrawl messages --limit 20`
|
||||
- One chat: `wacrawl messages --chat 1234567890@s.whatsapp.net --limit 50`
|
||||
- Search: `wacrawl search "release notes"`
|
||||
- Filtered search: `wacrawl --json search "invoice" --from-them --after 2026-01-01`
|
||||
|
||||
## Notes
|
||||
|
||||
- `wacrawl` is read-only and does not send messages.
|
||||
- It copies WhatsApp SQLite files into a temp snapshot before import.
|
||||
- Use `--source` to override the WhatsApp Desktop container path.
|
||||
- Use `--db` to archive somewhere other than `~/.wacrawl/wacrawl.db`.
|
||||
Loading…
Reference in New Issue
Block a user