@rozek/nanoclaw 1.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +1 -0
- package/.claude/skills/add-compact/SKILL.md +135 -0
- package/.claude/skills/add-discord/SKILL.md +203 -0
- package/.claude/skills/add-gmail/SKILL.md +220 -0
- package/.claude/skills/add-image-vision/SKILL.md +94 -0
- package/.claude/skills/add-ollama-tool/SKILL.md +153 -0
- package/.claude/skills/add-parallel/SKILL.md +290 -0
- package/.claude/skills/add-pdf-reader/SKILL.md +104 -0
- package/.claude/skills/add-reactions/SKILL.md +117 -0
- package/.claude/skills/add-slack/SKILL.md +207 -0
- package/.claude/skills/add-telegram/SKILL.md +222 -0
- package/.claude/skills/add-telegram-swarm/SKILL.md +384 -0
- package/.claude/skills/add-voice-transcription/SKILL.md +148 -0
- package/.claude/skills/add-whatsapp/SKILL.md +372 -0
- package/.claude/skills/convert-to-apple-container/SKILL.md +175 -0
- package/.claude/skills/customize/SKILL.md +110 -0
- package/.claude/skills/debug/SKILL.md +349 -0
- package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
- package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
- package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
- package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
- package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
- package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
- package/.claude/skills/setup/SKILL.md +218 -0
- package/.claude/skills/update-nanoclaw/SKILL.md +235 -0
- package/.claude/skills/update-skills/SKILL.md +130 -0
- package/.claude/skills/use-local-whisper/SKILL.md +152 -0
- package/.claude/skills/x-integration/SKILL.md +417 -0
- package/.claude/skills/x-integration/agent.ts +243 -0
- package/.claude/skills/x-integration/host.ts +159 -0
- package/.claude/skills/x-integration/lib/browser.ts +148 -0
- package/.claude/skills/x-integration/lib/config.ts +62 -0
- package/.claude/skills/x-integration/scripts/like.ts +56 -0
- package/.claude/skills/x-integration/scripts/post.ts +66 -0
- package/.claude/skills/x-integration/scripts/quote.ts +80 -0
- package/.claude/skills/x-integration/scripts/reply.ts +74 -0
- package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
- package/.claude/skills/x-integration/scripts/setup.ts +87 -0
- package/.env.example +1 -0
- package/.github/CODEOWNERS +10 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- package/.github/workflows/bump-version.yml +32 -0
- package/.github/workflows/ci.yml +25 -0
- package/.github/workflows/merge-forward-skills.yml +160 -0
- package/.github/workflows/update-tokens.yml +42 -0
- package/.husky/pre-commit +1 -0
- package/.mcp.json +3 -0
- package/.nvmrc +1 -0
- package/.prettierrc +3 -0
- package/CHANGELOG.md +8 -0
- package/CLAUDE.md +64 -0
- package/CONTRIBUTING.md +23 -0
- package/CONTRIBUTORS.md +15 -0
- package/LICENSE +21 -0
- package/NanoClaw_with_Web-Support.md +290 -0
- package/README.md +261 -0
- package/README_zh.md +200 -0
- package/assets/nanoclaw-favicon.png +0 -0
- package/assets/nanoclaw-icon.png +0 -0
- package/assets/nanoclaw-logo-dark.png +0 -0
- package/assets/nanoclaw-logo.png +0 -0
- package/assets/nanoclaw-profile.jpeg +0 -0
- package/assets/nanoclaw-sales.png +0 -0
- package/assets/social-preview.jpg +0 -0
- package/config-examples/mount-allowlist.json +25 -0
- package/container/Dockerfile +70 -0
- package/container/agent-runner/package-lock.json +1524 -0
- package/container/agent-runner/package.json +21 -0
- package/container/agent-runner/src/index.ts +558 -0
- package/container/agent-runner/src/ipc-mcp-stdio.ts +338 -0
- package/container/agent-runner/tsconfig.json +15 -0
- package/container/build.sh +23 -0
- package/container/skills/agent-browser/SKILL.md +159 -0
- package/container/skills/capabilities/SKILL.md +100 -0
- package/container/skills/status/SKILL.md +104 -0
- package/dist/channels/index.d.ts +2 -0
- package/dist/channels/index.d.ts.map +1 -0
- package/dist/channels/index.js +9 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/registry.d.ts +13 -0
- package/dist/channels/registry.d.ts.map +1 -0
- package/dist/channels/registry.js +11 -0
- package/dist/channels/registry.js.map +1 -0
- package/dist/channels/registry.test.d.ts +2 -0
- package/dist/channels/registry.test.d.ts.map +1 -0
- package/dist/channels/registry.test.js +32 -0
- package/dist/channels/registry.test.js.map +1 -0
- package/dist/channels/web.d.ts +2 -0
- package/dist/channels/web.d.ts.map +1 -0
- package/dist/channels/web.js +1738 -0
- package/dist/channels/web.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +182 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -0
- package/dist/container-runner.d.ts +44 -0
- package/dist/container-runner.d.ts.map +1 -0
- package/dist/container-runner.js +467 -0
- package/dist/container-runner.js.map +1 -0
- package/dist/container-runner.test.d.ts +2 -0
- package/dist/container-runner.test.d.ts.map +1 -0
- package/dist/container-runner.test.js +150 -0
- package/dist/container-runner.test.js.map +1 -0
- package/dist/container-runtime.d.ts +22 -0
- package/dist/container-runtime.d.ts.map +1 -0
- package/dist/container-runtime.js +96 -0
- package/dist/container-runtime.js.map +1 -0
- package/dist/container-runtime.test.d.ts +2 -0
- package/dist/container-runtime.test.d.ts.map +1 -0
- package/dist/container-runtime.test.js +93 -0
- package/dist/container-runtime.test.js.map +1 -0
- package/dist/credential-proxy.d.ts +21 -0
- package/dist/credential-proxy.d.ts.map +1 -0
- package/dist/credential-proxy.js +95 -0
- package/dist/credential-proxy.js.map +1 -0
- package/dist/credential-proxy.test.d.ts +2 -0
- package/dist/credential-proxy.test.d.ts.map +1 -0
- package/dist/credential-proxy.test.js +134 -0
- package/dist/credential-proxy.test.js.map +1 -0
- package/dist/db.d.ts +115 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +549 -0
- package/dist/db.js.map +1 -0
- package/dist/db.test.d.ts +2 -0
- package/dist/db.test.d.ts.map +1 -0
- package/dist/db.test.js +360 -0
- package/dist/db.test.js.map +1 -0
- package/dist/env.d.ts +8 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +42 -0
- package/dist/env.js.map +1 -0
- package/dist/formatting.test.d.ts +2 -0
- package/dist/formatting.test.d.ts.map +1 -0
- package/dist/formatting.test.js +183 -0
- package/dist/formatting.test.js.map +1 -0
- package/dist/group-folder.d.ts +5 -0
- package/dist/group-folder.d.ts.map +1 -0
- package/dist/group-folder.js +44 -0
- package/dist/group-folder.js.map +1 -0
- package/dist/group-folder.test.d.ts +2 -0
- package/dist/group-folder.test.d.ts.map +1 -0
- package/dist/group-folder.test.js +29 -0
- package/dist/group-folder.test.js.map +1 -0
- package/dist/group-queue.d.ts +34 -0
- package/dist/group-queue.d.ts.map +1 -0
- package/dist/group-queue.js +263 -0
- package/dist/group-queue.js.map +1 -0
- package/dist/group-queue.test.d.ts +2 -0
- package/dist/group-queue.test.d.ts.map +1 -0
- package/dist/group-queue.test.js +341 -0
- package/dist/group-queue.test.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +518 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc-auth.test.d.ts +2 -0
- package/dist/ipc-auth.test.d.ts.map +1 -0
- package/dist/ipc-auth.test.js +434 -0
- package/dist/ipc-auth.test.js.map +1 -0
- package/dist/ipc.d.ts +32 -0
- package/dist/ipc.d.ts.map +1 -0
- package/dist/ipc.js +311 -0
- package/dist/ipc.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -0
- package/dist/mount-security.d.ts +34 -0
- package/dist/mount-security.d.ts.map +1 -0
- package/dist/mount-security.js +325 -0
- package/dist/mount-security.js.map +1 -0
- package/dist/remote-control.d.ts +32 -0
- package/dist/remote-control.d.ts.map +1 -0
- package/dist/remote-control.js +185 -0
- package/dist/remote-control.js.map +1 -0
- package/dist/remote-control.test.d.ts +2 -0
- package/dist/remote-control.test.d.ts.map +1 -0
- package/dist/remote-control.test.js +321 -0
- package/dist/remote-control.test.js.map +1 -0
- package/dist/router.d.ts +8 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +37 -0
- package/dist/router.js.map +1 -0
- package/dist/routing.test.d.ts +2 -0
- package/dist/routing.test.d.ts.map +1 -0
- package/dist/routing.test.js +81 -0
- package/dist/routing.test.js.map +1 -0
- package/dist/sender-allowlist.d.ts +14 -0
- package/dist/sender-allowlist.d.ts.map +1 -0
- package/dist/sender-allowlist.js +79 -0
- package/dist/sender-allowlist.js.map +1 -0
- package/dist/sender-allowlist.test.d.ts +2 -0
- package/dist/sender-allowlist.test.d.ts.map +1 -0
- package/dist/sender-allowlist.test.js +186 -0
- package/dist/sender-allowlist.test.js.map +1 -0
- package/dist/session-commands.d.ts +47 -0
- package/dist/session-commands.d.ts.map +1 -0
- package/dist/session-commands.js +102 -0
- package/dist/session-commands.js.map +1 -0
- package/dist/session-commands.test.d.ts +2 -0
- package/dist/session-commands.test.d.ts.map +1 -0
- package/dist/session-commands.test.js +190 -0
- package/dist/session-commands.test.js.map +1 -0
- package/dist/task-scheduler.d.ts +22 -0
- package/dist/task-scheduler.d.ts.map +1 -0
- package/dist/task-scheduler.js +210 -0
- package/dist/task-scheduler.js.map +1 -0
- package/dist/task-scheduler.test.d.ts +2 -0
- package/dist/task-scheduler.test.d.ts.map +1 -0
- package/dist/task-scheduler.test.js +107 -0
- package/dist/task-scheduler.test.js.map +1 -0
- package/dist/timezone.d.ts +6 -0
- package/dist/timezone.d.ts.map +1 -0
- package/dist/timezone.js +17 -0
- package/dist/timezone.js.map +1 -0
- package/dist/timezone.test.d.ts +2 -0
- package/dist/timezone.test.d.ts.map +1 -0
- package/dist/timezone.test.js +23 -0
- package/dist/timezone.test.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
- package/docs/DEBUG_CHECKLIST.md +143 -0
- package/docs/REQUIREMENTS.md +196 -0
- package/docs/SDK_DEEP_DIVE.md +643 -0
- package/docs/SECURITY.md +122 -0
- package/docs/SPEC.md +785 -0
- package/docs/docker-sandboxes.md +359 -0
- package/docs/nanoclaw-architecture-final.md +1063 -0
- package/docs/nanorepo-architecture.md +168 -0
- package/docs/skills-as-branches.md +662 -0
- package/groups/global/CLAUDE.md +58 -0
- package/groups/main/CLAUDE.md +246 -0
- package/launchd/com.nanoclaw.plist +32 -0
- package/package.json +45 -0
- package/repo-tokens/README.md +113 -0
- package/repo-tokens/action.yml +186 -0
- package/repo-tokens/badge.svg +23 -0
- package/repo-tokens/examples/green.svg +14 -0
- package/repo-tokens/examples/red.svg +14 -0
- package/repo-tokens/examples/yellow-green.svg +14 -0
- package/repo-tokens/examples/yellow.svg +14 -0
- package/scripts/run-migrations.ts +105 -0
- package/setup/container.ts +144 -0
- package/setup/environment.test.ts +121 -0
- package/setup/environment.ts +94 -0
- package/setup/groups.ts +229 -0
- package/setup/index.ts +58 -0
- package/setup/mounts.ts +115 -0
- package/setup/platform.test.ts +120 -0
- package/setup/platform.ts +132 -0
- package/setup/register.test.ts +257 -0
- package/setup/register.ts +177 -0
- package/setup/service.test.ts +187 -0
- package/setup/service.ts +362 -0
- package/setup/status.ts +16 -0
- package/setup/verify.ts +192 -0
- package/setup.sh +161 -0
- package/src/channels/index.ts +12 -0
- package/src/channels/registry.test.ts +42 -0
- package/src/channels/registry.ts +32 -0
- package/src/channels/web.ts +1856 -0
- package/src/cli.ts +209 -0
- package/src/config.ts +73 -0
- package/src/container-runner.test.ts +210 -0
- package/src/container-runner.ts +707 -0
- package/src/container-runtime.test.ts +149 -0
- package/src/container-runtime.ts +127 -0
- package/src/credential-proxy.test.ts +192 -0
- package/src/credential-proxy.ts +125 -0
- package/src/db.test.ts +484 -0
- package/src/db.ts +803 -0
- package/src/env.ts +42 -0
- package/src/formatting.test.ts +256 -0
- package/src/group-folder.test.ts +43 -0
- package/src/group-folder.ts +44 -0
- package/src/group-queue.test.ts +484 -0
- package/src/group-queue.ts +365 -0
- package/src/index.ts +731 -0
- package/src/ipc-auth.test.ts +679 -0
- package/src/ipc.ts +461 -0
- package/src/logger.ts +16 -0
- package/src/mount-security.ts +419 -0
- package/src/remote-control.test.ts +397 -0
- package/src/remote-control.ts +224 -0
- package/src/router.ts +52 -0
- package/src/routing.test.ts +170 -0
- package/src/sender-allowlist.test.ts +216 -0
- package/src/sender-allowlist.ts +128 -0
- package/src/session-commands.test.ts +247 -0
- package/src/session-commands.ts +163 -0
- package/src/task-scheduler.test.ts +129 -0
- package/src/task-scheduler.ts +295 -0
- package/src/timezone.test.ts +29 -0
- package/src/timezone.ts +16 -0
- package/src/types.ts +107 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +7 -0
- package/vitest.skills.config.ts +7 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Andy
|
|
2
|
+
|
|
3
|
+
You are Andy, a personal assistant. You help with tasks, answer questions, and can schedule reminders.
|
|
4
|
+
|
|
5
|
+
## What You Can Do
|
|
6
|
+
|
|
7
|
+
- Answer questions and have conversations
|
|
8
|
+
- Search the web and fetch content from URLs
|
|
9
|
+
- **Browse the web** with `agent-browser` — open pages, click, fill forms, take screenshots, extract data (run `agent-browser open <url>` to start, then `agent-browser snapshot -i` to see interactive elements)
|
|
10
|
+
- Read and write files in your workspace
|
|
11
|
+
- Run bash commands in your sandbox
|
|
12
|
+
- Schedule tasks to run later or on a recurring basis
|
|
13
|
+
- Send messages back to the chat
|
|
14
|
+
|
|
15
|
+
## Communication
|
|
16
|
+
|
|
17
|
+
Your output is sent to the user or group.
|
|
18
|
+
|
|
19
|
+
You also have `mcp__nanoclaw__send_message` which sends a message immediately while you're still working. This is useful when you want to acknowledge a request before starting longer work.
|
|
20
|
+
|
|
21
|
+
### Internal thoughts
|
|
22
|
+
|
|
23
|
+
If part of your output is internal reasoning rather than something for the user, wrap it in `<internal>` tags:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
<internal>Compiled all three reports, ready to summarize.</internal>
|
|
27
|
+
|
|
28
|
+
Here are the key findings from the research...
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Text inside `<internal>` tags is logged but not sent to the user. If you've already sent the key information via `send_message`, you can wrap the recap in `<internal>` to avoid sending it again.
|
|
32
|
+
|
|
33
|
+
### Sub-agents and teammates
|
|
34
|
+
|
|
35
|
+
When working as a sub-agent or teammate, only use `send_message` if instructed to by the main agent.
|
|
36
|
+
|
|
37
|
+
## Your Workspace
|
|
38
|
+
|
|
39
|
+
Files you create are saved in `/workspace/group/`. Use this for notes, research, or anything that should persist.
|
|
40
|
+
|
|
41
|
+
## Memory
|
|
42
|
+
|
|
43
|
+
The `conversations/` folder contains searchable history of past conversations. Use this to recall context from previous sessions.
|
|
44
|
+
|
|
45
|
+
When you learn something important:
|
|
46
|
+
- Create files for structured data (e.g., `customers.md`, `preferences.md`)
|
|
47
|
+
- Split files larger than 500 lines into folders
|
|
48
|
+
- Keep an index in your memory for the files you create
|
|
49
|
+
|
|
50
|
+
## Message Formatting
|
|
51
|
+
|
|
52
|
+
NEVER use markdown. Only use WhatsApp/Telegram formatting:
|
|
53
|
+
- *single asterisks* for bold (NEVER **double asterisks**)
|
|
54
|
+
- _underscores_ for italic
|
|
55
|
+
- • bullet points
|
|
56
|
+
- ```triple backticks``` for code
|
|
57
|
+
|
|
58
|
+
No ## headings. No [links](url). No **double stars**.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Andy
|
|
2
|
+
|
|
3
|
+
You are Andy, a personal assistant. You help with tasks, answer questions, and can schedule reminders.
|
|
4
|
+
|
|
5
|
+
## What You Can Do
|
|
6
|
+
|
|
7
|
+
- Answer questions and have conversations
|
|
8
|
+
- Search the web and fetch content from URLs
|
|
9
|
+
- **Browse the web** with `agent-browser` — open pages, click, fill forms, take screenshots, extract data (run `agent-browser open <url>` to start, then `agent-browser snapshot -i` to see interactive elements)
|
|
10
|
+
- Read and write files in your workspace
|
|
11
|
+
- Run bash commands in your sandbox
|
|
12
|
+
- Schedule tasks to run later or on a recurring basis
|
|
13
|
+
- Send messages back to the chat
|
|
14
|
+
|
|
15
|
+
## Communication
|
|
16
|
+
|
|
17
|
+
Your output is sent to the user or group.
|
|
18
|
+
|
|
19
|
+
You also have `mcp__nanoclaw__send_message` which sends a message immediately while you're still working. This is useful when you want to acknowledge a request before starting longer work.
|
|
20
|
+
|
|
21
|
+
### Internal thoughts
|
|
22
|
+
|
|
23
|
+
If part of your output is internal reasoning rather than something for the user, wrap it in `<internal>` tags:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
<internal>Compiled all three reports, ready to summarize.</internal>
|
|
27
|
+
|
|
28
|
+
Here are the key findings from the research...
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Text inside `<internal>` tags is logged but not sent to the user. If you've already sent the key information via `send_message`, you can wrap the recap in `<internal>` to avoid sending it again.
|
|
32
|
+
|
|
33
|
+
### Sub-agents and teammates
|
|
34
|
+
|
|
35
|
+
When working as a sub-agent or teammate, only use `send_message` if instructed to by the main agent.
|
|
36
|
+
|
|
37
|
+
## Memory
|
|
38
|
+
|
|
39
|
+
The `conversations/` folder contains searchable history of past conversations. Use this to recall context from previous sessions.
|
|
40
|
+
|
|
41
|
+
When you learn something important:
|
|
42
|
+
- Create files for structured data (e.g., `customers.md`, `preferences.md`)
|
|
43
|
+
- Split files larger than 500 lines into folders
|
|
44
|
+
- Keep an index in your memory for the files you create
|
|
45
|
+
|
|
46
|
+
## WhatsApp Formatting (and other messaging apps)
|
|
47
|
+
|
|
48
|
+
Do NOT use markdown headings (##) in WhatsApp messages. Only use:
|
|
49
|
+
- *Bold* (single asterisks) (NEVER **double asterisks**)
|
|
50
|
+
- _Italic_ (underscores)
|
|
51
|
+
- • Bullets (bullet points)
|
|
52
|
+
- ```Code blocks``` (triple backticks)
|
|
53
|
+
|
|
54
|
+
Keep messages clean and readable for WhatsApp.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Admin Context
|
|
59
|
+
|
|
60
|
+
This is the **main channel**, which has elevated privileges.
|
|
61
|
+
|
|
62
|
+
## Container Mounts
|
|
63
|
+
|
|
64
|
+
Main has read-only access to the project and read-write access to its group folder:
|
|
65
|
+
|
|
66
|
+
| Container Path | Host Path | Access |
|
|
67
|
+
|----------------|-----------|--------|
|
|
68
|
+
| `/workspace/project` | Project root | read-only |
|
|
69
|
+
| `/workspace/group` | `groups/main/` | read-write |
|
|
70
|
+
|
|
71
|
+
Key paths inside the container:
|
|
72
|
+
- `/workspace/project/store/messages.db` - SQLite database
|
|
73
|
+
- `/workspace/project/store/messages.db` (registered_groups table) - Group config
|
|
74
|
+
- `/workspace/project/groups/` - All group folders
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Managing Groups
|
|
79
|
+
|
|
80
|
+
### Finding Available Groups
|
|
81
|
+
|
|
82
|
+
Available groups are provided in `/workspace/ipc/available_groups.json`:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"groups": [
|
|
87
|
+
{
|
|
88
|
+
"jid": "120363336345536173@g.us",
|
|
89
|
+
"name": "Family Chat",
|
|
90
|
+
"lastActivity": "2026-01-31T12:00:00.000Z",
|
|
91
|
+
"isRegistered": false
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"lastSync": "2026-01-31T12:00:00.000Z"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Groups are ordered by most recent activity. The list is synced from WhatsApp daily.
|
|
99
|
+
|
|
100
|
+
If a group the user mentions isn't in the list, request a fresh sync:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
echo '{"type": "refresh_groups"}' > /workspace/ipc/tasks/refresh_$(date +%s).json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Then wait a moment and re-read `available_groups.json`.
|
|
107
|
+
|
|
108
|
+
**Fallback**: Query the SQLite database directly:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
sqlite3 /workspace/project/store/messages.db "
|
|
112
|
+
SELECT jid, name, last_message_time
|
|
113
|
+
FROM chats
|
|
114
|
+
WHERE jid LIKE '%@g.us' AND jid != '__group_sync__'
|
|
115
|
+
ORDER BY last_message_time DESC
|
|
116
|
+
LIMIT 10;
|
|
117
|
+
"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Registered Groups Config
|
|
121
|
+
|
|
122
|
+
Groups are registered in the SQLite `registered_groups` table:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"1234567890-1234567890@g.us": {
|
|
127
|
+
"name": "Family Chat",
|
|
128
|
+
"folder": "whatsapp_family-chat",
|
|
129
|
+
"trigger": "@Andy",
|
|
130
|
+
"added_at": "2024-01-31T12:00:00.000Z"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Fields:
|
|
136
|
+
- **Key**: The chat JID (unique identifier — WhatsApp, Telegram, Slack, Discord, etc.)
|
|
137
|
+
- **name**: Display name for the group
|
|
138
|
+
- **folder**: Channel-prefixed folder name under `groups/` for this group's files and memory
|
|
139
|
+
- **trigger**: The trigger word (usually same as global, but could differ)
|
|
140
|
+
- **requiresTrigger**: Whether `@trigger` prefix is needed (default: `true`). Set to `false` for solo/personal chats where all messages should be processed
|
|
141
|
+
- **isMain**: Whether this is the main control group (elevated privileges, no trigger required)
|
|
142
|
+
- **added_at**: ISO timestamp when registered
|
|
143
|
+
|
|
144
|
+
### Trigger Behavior
|
|
145
|
+
|
|
146
|
+
- **Main group** (`isMain: true`): No trigger needed — all messages are processed automatically
|
|
147
|
+
- **Groups with `requiresTrigger: false`**: No trigger needed — all messages processed (use for 1-on-1 or solo chats)
|
|
148
|
+
- **Other groups** (default): Messages must start with `@AssistantName` to be processed
|
|
149
|
+
|
|
150
|
+
### Adding a Group
|
|
151
|
+
|
|
152
|
+
1. Query the database to find the group's JID
|
|
153
|
+
2. Use the `register_group` MCP tool with the JID, name, folder, and trigger
|
|
154
|
+
3. Optionally include `containerConfig` for additional mounts
|
|
155
|
+
4. The group folder is created automatically: `/workspace/project/groups/{folder-name}/`
|
|
156
|
+
5. Optionally create an initial `CLAUDE.md` for the group
|
|
157
|
+
|
|
158
|
+
Folder naming convention — channel prefix with underscore separator:
|
|
159
|
+
- WhatsApp "Family Chat" → `whatsapp_family-chat`
|
|
160
|
+
- Telegram "Dev Team" → `telegram_dev-team`
|
|
161
|
+
- Discord "General" → `discord_general`
|
|
162
|
+
- Slack "Engineering" → `slack_engineering`
|
|
163
|
+
- Use lowercase, hyphens for the group name part
|
|
164
|
+
|
|
165
|
+
#### Adding Additional Directories for a Group
|
|
166
|
+
|
|
167
|
+
Groups can have extra directories mounted. Add `containerConfig` to their entry:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"1234567890@g.us": {
|
|
172
|
+
"name": "Dev Team",
|
|
173
|
+
"folder": "dev-team",
|
|
174
|
+
"trigger": "@Andy",
|
|
175
|
+
"added_at": "2026-01-31T12:00:00Z",
|
|
176
|
+
"containerConfig": {
|
|
177
|
+
"additionalMounts": [
|
|
178
|
+
{
|
|
179
|
+
"hostPath": "~/projects/webapp",
|
|
180
|
+
"containerPath": "webapp",
|
|
181
|
+
"readonly": false
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The directory will appear at `/workspace/extra/webapp` in that group's container.
|
|
190
|
+
|
|
191
|
+
#### Sender Allowlist
|
|
192
|
+
|
|
193
|
+
After registering a group, explain the sender allowlist feature to the user:
|
|
194
|
+
|
|
195
|
+
> This group can be configured with a sender allowlist to control who can interact with me. There are two modes:
|
|
196
|
+
>
|
|
197
|
+
> - **Trigger mode** (default): Everyone's messages are stored for context, but only allowed senders can trigger me with @{AssistantName}.
|
|
198
|
+
> - **Drop mode**: Messages from non-allowed senders are not stored at all.
|
|
199
|
+
>
|
|
200
|
+
> For closed groups with trusted members, I recommend setting up an allow-only list so only specific people can trigger me. Want me to configure that?
|
|
201
|
+
|
|
202
|
+
If the user wants to set up an allowlist, edit `~/.config/nanoclaw/sender-allowlist.json` on the host:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"default": { "allow": "*", "mode": "trigger" },
|
|
207
|
+
"chats": {
|
|
208
|
+
"<chat-jid>": {
|
|
209
|
+
"allow": ["sender-id-1", "sender-id-2"],
|
|
210
|
+
"mode": "trigger"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"logDenied": true
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Notes:
|
|
218
|
+
- Your own messages (`is_from_me`) explicitly bypass the allowlist in trigger checks. Bot messages are filtered out by the database query before trigger evaluation, so they never reach the allowlist.
|
|
219
|
+
- If the config file doesn't exist or is invalid, all senders are allowed (fail-open)
|
|
220
|
+
- The config file is on the host at `~/.config/nanoclaw/sender-allowlist.json`, not inside the container
|
|
221
|
+
|
|
222
|
+
### Removing a Group
|
|
223
|
+
|
|
224
|
+
1. Read `/workspace/project/data/registered_groups.json`
|
|
225
|
+
2. Remove the entry for that group
|
|
226
|
+
3. Write the updated JSON back
|
|
227
|
+
4. The group folder and its files remain (don't delete them)
|
|
228
|
+
|
|
229
|
+
### Listing Groups
|
|
230
|
+
|
|
231
|
+
Read `/workspace/project/data/registered_groups.json` and format it nicely.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Global Memory
|
|
236
|
+
|
|
237
|
+
You can read and write to `/workspace/project/groups/global/CLAUDE.md` for facts that should apply to all groups. Only update global memory when explicitly asked to "remember this globally" or similar.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Scheduling for Other Groups
|
|
242
|
+
|
|
243
|
+
When scheduling tasks for other groups, use the `target_group_jid` parameter with the group's JID from `registered_groups.json`:
|
|
244
|
+
- `schedule_task(prompt: "...", schedule_type: "cron", schedule_value: "0 9 * * 1", target_group_jid: "120363336345536173@g.us")`
|
|
245
|
+
|
|
246
|
+
The task will run in that group's context with access to their files and memory.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key>
|
|
6
|
+
<string>com.nanoclaw</string>
|
|
7
|
+
<key>ProgramArguments</key>
|
|
8
|
+
<array>
|
|
9
|
+
<string>{{NODE_PATH}}</string>
|
|
10
|
+
<string>{{PROJECT_ROOT}}/dist/index.js</string>
|
|
11
|
+
</array>
|
|
12
|
+
<key>WorkingDirectory</key>
|
|
13
|
+
<string>{{PROJECT_ROOT}}</string>
|
|
14
|
+
<key>RunAtLoad</key>
|
|
15
|
+
<true/>
|
|
16
|
+
<key>KeepAlive</key>
|
|
17
|
+
<true/>
|
|
18
|
+
<key>EnvironmentVariables</key>
|
|
19
|
+
<dict>
|
|
20
|
+
<key>PATH</key>
|
|
21
|
+
<string>{{HOME}}/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
22
|
+
<key>HOME</key>
|
|
23
|
+
<string>{{HOME}}</string>
|
|
24
|
+
<key>ASSISTANT_NAME</key>
|
|
25
|
+
<string>Andy</string>
|
|
26
|
+
</dict>
|
|
27
|
+
<key>StandardOutPath</key>
|
|
28
|
+
<string>{{PROJECT_ROOT}}/logs/nanoclaw.log</string>
|
|
29
|
+
<key>StandardErrorPath</key>
|
|
30
|
+
<string>{{PROJECT_ROOT}}/logs/nanoclaw.error.log</string>
|
|
31
|
+
</dict>
|
|
32
|
+
</plist>
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rozek/nanoclaw",
|
|
3
|
+
"version": "1.2.17",
|
|
4
|
+
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"nanoclaw": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
15
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
16
|
+
"format:fix": "prettier --write \"src/**/*.ts\"",
|
|
17
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
18
|
+
"prepare": "husky",
|
|
19
|
+
"setup": "tsx setup/index.ts",
|
|
20
|
+
"auth": "tsx src/whatsapp-auth.ts",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"better-sqlite3": "^11.8.1",
|
|
26
|
+
"cron-parser": "^5.5.0",
|
|
27
|
+
"pino": "^9.6.0",
|
|
28
|
+
"pino-pretty": "^13.0.0",
|
|
29
|
+
"yaml": "^2.8.2",
|
|
30
|
+
"zod": "^4.3.6"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
34
|
+
"@types/node": "^22.10.0",
|
|
35
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
36
|
+
"husky": "^9.1.7",
|
|
37
|
+
"prettier": "^3.8.1",
|
|
38
|
+
"tsx": "^4.19.0",
|
|
39
|
+
"typescript": "^5.7.0",
|
|
40
|
+
"vitest": "^4.0.18"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Repo Tokens
|
|
2
|
+
|
|
3
|
+
A GitHub Action that calculates the size of your codebase in terms of tokens and updates a badge in your README.
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<img src="examples/green.svg" alt="tokens 12.4k">
|
|
7
|
+
<img src="examples/yellow-green.svg" alt="tokens 74.8k">
|
|
8
|
+
<img src="examples/yellow.svg" alt="tokens 120k">
|
|
9
|
+
<img src="examples/red.svg" alt="tokens 158k">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
- uses: qwibitai/nanoclaw/repo-tokens@v1
|
|
16
|
+
with:
|
|
17
|
+
include: 'src/**/*.ts'
|
|
18
|
+
exclude: 'src/**/*.test.ts'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This counts tokens using [tiktoken](https://github.com/openai/tiktoken) and writes the result between HTML comment markers in your README:
|
|
22
|
+
|
|
23
|
+
The badge color reflects what percentage of an LLMs context window the codebase fills (context window size is configurable, defaults to 200k which is the size of Claude Opus). Green for under 30%, yellow-green for 30%-50%, yellow for 50%-70%, red for 70%+.
|
|
24
|
+
|
|
25
|
+
## Why
|
|
26
|
+
|
|
27
|
+
Small codebases were always a good thing. With coding agents, there's now a huge advantage to having a codebase small enough that an agent can hold the full thing in context.
|
|
28
|
+
|
|
29
|
+
This badge gives some indication of how easy it will be to work with an agent on the codebase, and will hopefully be a visual reminder to avoid bloat.
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
Repos using repo-tokens:
|
|
34
|
+
|
|
35
|
+
| Repo | Badge |
|
|
36
|
+
|------|-------|
|
|
37
|
+
| [NanoClaw](https://github.com/qwibitai/NanoClaw) |  |
|
|
38
|
+
|
|
39
|
+
### Full workflow example
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
name: Update token count
|
|
43
|
+
|
|
44
|
+
on:
|
|
45
|
+
push:
|
|
46
|
+
branches: [main]
|
|
47
|
+
paths: ['src/**']
|
|
48
|
+
|
|
49
|
+
permissions:
|
|
50
|
+
contents: write
|
|
51
|
+
|
|
52
|
+
jobs:
|
|
53
|
+
update-tokens:
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
|
|
58
|
+
- uses: actions/setup-python@v5
|
|
59
|
+
with:
|
|
60
|
+
python-version: '3.12'
|
|
61
|
+
|
|
62
|
+
- uses: qwibitai/nanoclaw/repo-tokens@v1
|
|
63
|
+
id: tokens
|
|
64
|
+
with:
|
|
65
|
+
include: 'src/**/*.ts'
|
|
66
|
+
exclude: 'src/**/*.test.ts'
|
|
67
|
+
badge-path: '.github/badges/tokens.svg'
|
|
68
|
+
|
|
69
|
+
- name: Commit if changed
|
|
70
|
+
run: |
|
|
71
|
+
git add README.md .github/badges/tokens.svg
|
|
72
|
+
git diff --cached --quiet && exit 0
|
|
73
|
+
git config user.name "github-actions[bot]"
|
|
74
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
75
|
+
git commit -m "docs: update token count to ${{ steps.tokens.outputs.badge }}"
|
|
76
|
+
git push
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### README setup
|
|
80
|
+
|
|
81
|
+
Add markers where you want the token count text to appear:
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<!-- token-count --><!-- /token-count -->
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The action replaces everything between the markers with the token count.
|
|
88
|
+
|
|
89
|
+
## Inputs
|
|
90
|
+
|
|
91
|
+
| Input | Default | Description |
|
|
92
|
+
|-------|---------|-------------|
|
|
93
|
+
| `include` | *required* | Glob patterns for files to count (space-separated) |
|
|
94
|
+
| `exclude` | `''` | Glob patterns to exclude (space-separated) |
|
|
95
|
+
| `context-window` | `200000` | Context window size for percentage calculation |
|
|
96
|
+
| `readme` | `README.md` | Path to README file |
|
|
97
|
+
| `encoding` | `cl100k_base` | Tiktoken encoding name |
|
|
98
|
+
| `marker` | `token-count` | HTML comment marker name |
|
|
99
|
+
| `badge-path` | `''` | Path to write SVG badge (empty = no SVG) |
|
|
100
|
+
|
|
101
|
+
## Outputs
|
|
102
|
+
|
|
103
|
+
| Output | Description |
|
|
104
|
+
|--------|-------------|
|
|
105
|
+
| `tokens` | Total token count (e.g., `34940`) |
|
|
106
|
+
| `percentage` | Percentage of context window (e.g., `17`) |
|
|
107
|
+
| `badge` | The formatted text that was inserted (e.g., `34.9k tokens · 17% of context window`) |
|
|
108
|
+
|
|
109
|
+
## How it works
|
|
110
|
+
|
|
111
|
+
Composite GitHub Action. Installs tiktoken, runs ~60 lines of inline Python. Takes about 10 seconds.
|
|
112
|
+
|
|
113
|
+
The action counts tokens and updates the README but does not commit. Your workflow decides the git strategy.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
name: Repo Tokens
|
|
2
|
+
description: Count codebase tokens with tiktoken and update a README badge
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
include:
|
|
6
|
+
description: 'Glob patterns for files to count (space-separated)'
|
|
7
|
+
required: true
|
|
8
|
+
exclude:
|
|
9
|
+
description: 'Glob patterns to exclude (space-separated)'
|
|
10
|
+
required: false
|
|
11
|
+
default: ''
|
|
12
|
+
context-window:
|
|
13
|
+
description: 'Context window size for percentage calculation'
|
|
14
|
+
required: false
|
|
15
|
+
default: '200000'
|
|
16
|
+
readme:
|
|
17
|
+
description: 'Path to README file'
|
|
18
|
+
required: false
|
|
19
|
+
default: 'README.md'
|
|
20
|
+
encoding:
|
|
21
|
+
description: 'Tiktoken encoding name'
|
|
22
|
+
required: false
|
|
23
|
+
default: 'cl100k_base'
|
|
24
|
+
marker:
|
|
25
|
+
description: 'HTML comment marker name'
|
|
26
|
+
required: false
|
|
27
|
+
default: 'token-count'
|
|
28
|
+
badge-path:
|
|
29
|
+
description: 'Path to write SVG badge (empty = no SVG)'
|
|
30
|
+
required: false
|
|
31
|
+
default: ''
|
|
32
|
+
|
|
33
|
+
outputs:
|
|
34
|
+
tokens:
|
|
35
|
+
description: 'Total token count'
|
|
36
|
+
value: ${{ steps.count.outputs.tokens }}
|
|
37
|
+
percentage:
|
|
38
|
+
description: 'Percentage of context window'
|
|
39
|
+
value: ${{ steps.count.outputs.percentage }}
|
|
40
|
+
badge:
|
|
41
|
+
description: 'Badge text that was inserted'
|
|
42
|
+
value: ${{ steps.count.outputs.badge }}
|
|
43
|
+
|
|
44
|
+
runs:
|
|
45
|
+
using: composite
|
|
46
|
+
steps:
|
|
47
|
+
- name: Install tiktoken
|
|
48
|
+
shell: bash
|
|
49
|
+
run: pip install tiktoken
|
|
50
|
+
|
|
51
|
+
- name: Count tokens and update README
|
|
52
|
+
id: count
|
|
53
|
+
shell: python
|
|
54
|
+
env:
|
|
55
|
+
INPUT_INCLUDE: ${{ inputs.include }}
|
|
56
|
+
INPUT_EXCLUDE: ${{ inputs.exclude }}
|
|
57
|
+
INPUT_CONTEXT_WINDOW: ${{ inputs.context-window }}
|
|
58
|
+
INPUT_README: ${{ inputs.readme }}
|
|
59
|
+
INPUT_ENCODING: ${{ inputs.encoding }}
|
|
60
|
+
INPUT_MARKER: ${{ inputs.marker }}
|
|
61
|
+
INPUT_BADGE_PATH: ${{ inputs.badge-path }}
|
|
62
|
+
run: |
|
|
63
|
+
import glob, os, re, tiktoken
|
|
64
|
+
|
|
65
|
+
include_patterns = os.environ["INPUT_INCLUDE"].split()
|
|
66
|
+
exclude_patterns = os.environ["INPUT_EXCLUDE"].split()
|
|
67
|
+
context_window = int(os.environ["INPUT_CONTEXT_WINDOW"])
|
|
68
|
+
readme_path = os.environ["INPUT_README"]
|
|
69
|
+
encoding_name = os.environ["INPUT_ENCODING"]
|
|
70
|
+
marker = os.environ["INPUT_MARKER"]
|
|
71
|
+
badge_path = os.environ.get("INPUT_BADGE_PATH", "").strip()
|
|
72
|
+
|
|
73
|
+
# Expand globs
|
|
74
|
+
included = set()
|
|
75
|
+
for pattern in include_patterns:
|
|
76
|
+
included.update(glob.glob(pattern, recursive=True))
|
|
77
|
+
|
|
78
|
+
excluded = set()
|
|
79
|
+
for pattern in exclude_patterns:
|
|
80
|
+
excluded.update(glob.glob(pattern, recursive=True))
|
|
81
|
+
|
|
82
|
+
files = sorted(included - excluded)
|
|
83
|
+
files = [f for f in files if os.path.isfile(f)]
|
|
84
|
+
|
|
85
|
+
# Count tokens
|
|
86
|
+
enc = tiktoken.get_encoding(encoding_name)
|
|
87
|
+
total = 0
|
|
88
|
+
for path in files:
|
|
89
|
+
try:
|
|
90
|
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
|
91
|
+
total += len(enc.encode(f.read()))
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"Skipping {path}: {e}")
|
|
94
|
+
|
|
95
|
+
# Format
|
|
96
|
+
if total >= 100000:
|
|
97
|
+
display = f"{round(total / 1000)}k"
|
|
98
|
+
elif total >= 1000:
|
|
99
|
+
display = f"{total / 1000:.1f}k"
|
|
100
|
+
else:
|
|
101
|
+
display = str(total)
|
|
102
|
+
|
|
103
|
+
pct = round(total / context_window * 100)
|
|
104
|
+
badge = f"{display} tokens \u00b7 {pct}% of context window"
|
|
105
|
+
|
|
106
|
+
print(f"Files: {len(files)}, Tokens: {total}, Badge: {badge}")
|
|
107
|
+
|
|
108
|
+
# Update README (text between markers)
|
|
109
|
+
marker_re = re.compile(
|
|
110
|
+
rf"(<!--\s*{re.escape(marker)}\s*-->).*?(<!--\s*/{re.escape(marker)}\s*-->)",
|
|
111
|
+
re.DOTALL,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
with open(readme_path, "r", encoding="utf-8") as f:
|
|
115
|
+
content = f.read()
|
|
116
|
+
|
|
117
|
+
repo_tokens_url = "https://github.com/qwibitai/nanoclaw/tree/main/repo-tokens"
|
|
118
|
+
linked_badge = f'<a href="{repo_tokens_url}">{badge}</a>'
|
|
119
|
+
new_content = marker_re.sub(rf"\1{linked_badge}\2", content)
|
|
120
|
+
|
|
121
|
+
if new_content != content:
|
|
122
|
+
with open(readme_path, "w", encoding="utf-8") as f:
|
|
123
|
+
f.write(new_content)
|
|
124
|
+
print("README updated")
|
|
125
|
+
else:
|
|
126
|
+
print("No change to README")
|
|
127
|
+
|
|
128
|
+
# Generate SVG badge
|
|
129
|
+
if badge_path:
|
|
130
|
+
label_text = "tokens"
|
|
131
|
+
value_text = display
|
|
132
|
+
full_desc = f"{display} tokens, {pct}% of context window"
|
|
133
|
+
|
|
134
|
+
cw = 7.0
|
|
135
|
+
label_w = round(len(label_text) * cw) + 10
|
|
136
|
+
value_w = round(len(value_text) * cw) + 10
|
|
137
|
+
total_w = label_w + value_w
|
|
138
|
+
|
|
139
|
+
if pct < 30:
|
|
140
|
+
color = "#4c1"
|
|
141
|
+
elif pct < 50:
|
|
142
|
+
color = "#97ca00"
|
|
143
|
+
elif pct < 70:
|
|
144
|
+
color = "#dfb317"
|
|
145
|
+
else:
|
|
146
|
+
color = "#e05d44"
|
|
147
|
+
|
|
148
|
+
lx = label_w // 2
|
|
149
|
+
vx = label_w + value_w // 2
|
|
150
|
+
|
|
151
|
+
repo_tokens_url = "https://github.com/qwibitai/nanoclaw/tree/main/repo-tokens"
|
|
152
|
+
|
|
153
|
+
svg = f'''<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{total_w}" height="20" role="img" aria-label="{full_desc}">
|
|
154
|
+
<title>{full_desc}</title>
|
|
155
|
+
<linearGradient id="s" x2="0" y2="100%">
|
|
156
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
157
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
158
|
+
</linearGradient>
|
|
159
|
+
<clipPath id="r">
|
|
160
|
+
<rect width="{total_w}" height="20" rx="3" fill="#fff"/>
|
|
161
|
+
</clipPath>
|
|
162
|
+
<a xlink:href="{repo_tokens_url}">
|
|
163
|
+
<g clip-path="url(#r)">
|
|
164
|
+
<rect width="{label_w}" height="20" fill="#555"/>
|
|
165
|
+
<rect x="{label_w}" width="{value_w}" height="20" fill="{color}"/>
|
|
166
|
+
<rect width="{total_w}" height="20" fill="url(#s)"/>
|
|
167
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
|
|
168
|
+
<text aria-hidden="true" x="{lx}" y="15" fill="#010101" fill-opacity=".3">{label_text}</text>
|
|
169
|
+
<text x="{lx}" y="14">{label_text}</text>
|
|
170
|
+
<text aria-hidden="true" x="{vx}" y="15" fill="#010101" fill-opacity=".3">{value_text}</text>
|
|
171
|
+
<text x="{vx}" y="14">{value_text}</text>
|
|
172
|
+
</g>
|
|
173
|
+
</g>
|
|
174
|
+
</a>
|
|
175
|
+
</svg>'''
|
|
176
|
+
|
|
177
|
+
os.makedirs(os.path.dirname(badge_path) or ".", exist_ok=True)
|
|
178
|
+
with open(badge_path, "w", encoding="utf-8") as f:
|
|
179
|
+
f.write(svg)
|
|
180
|
+
print(f"Badge SVG written to {badge_path}")
|
|
181
|
+
|
|
182
|
+
# Set outputs
|
|
183
|
+
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
|
184
|
+
f.write(f"tokens={total}\n")
|
|
185
|
+
f.write(f"percentage={pct}\n")
|
|
186
|
+
f.write(f"badge={badge}\n")
|