@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.
Files changed (305) hide show
  1. package/.claude/settings.json +1 -0
  2. package/.claude/skills/add-compact/SKILL.md +135 -0
  3. package/.claude/skills/add-discord/SKILL.md +203 -0
  4. package/.claude/skills/add-gmail/SKILL.md +220 -0
  5. package/.claude/skills/add-image-vision/SKILL.md +94 -0
  6. package/.claude/skills/add-ollama-tool/SKILL.md +153 -0
  7. package/.claude/skills/add-parallel/SKILL.md +290 -0
  8. package/.claude/skills/add-pdf-reader/SKILL.md +104 -0
  9. package/.claude/skills/add-reactions/SKILL.md +117 -0
  10. package/.claude/skills/add-slack/SKILL.md +207 -0
  11. package/.claude/skills/add-telegram/SKILL.md +222 -0
  12. package/.claude/skills/add-telegram-swarm/SKILL.md +384 -0
  13. package/.claude/skills/add-voice-transcription/SKILL.md +148 -0
  14. package/.claude/skills/add-whatsapp/SKILL.md +372 -0
  15. package/.claude/skills/convert-to-apple-container/SKILL.md +175 -0
  16. package/.claude/skills/customize/SKILL.md +110 -0
  17. package/.claude/skills/debug/SKILL.md +349 -0
  18. package/.claude/skills/get-qodo-rules/SKILL.md +122 -0
  19. package/.claude/skills/get-qodo-rules/references/output-format.md +41 -0
  20. package/.claude/skills/get-qodo-rules/references/pagination.md +33 -0
  21. package/.claude/skills/get-qodo-rules/references/repository-scope.md +26 -0
  22. package/.claude/skills/qodo-pr-resolver/SKILL.md +326 -0
  23. package/.claude/skills/qodo-pr-resolver/resources/providers.md +329 -0
  24. package/.claude/skills/setup/SKILL.md +218 -0
  25. package/.claude/skills/update-nanoclaw/SKILL.md +235 -0
  26. package/.claude/skills/update-skills/SKILL.md +130 -0
  27. package/.claude/skills/use-local-whisper/SKILL.md +152 -0
  28. package/.claude/skills/x-integration/SKILL.md +417 -0
  29. package/.claude/skills/x-integration/agent.ts +243 -0
  30. package/.claude/skills/x-integration/host.ts +159 -0
  31. package/.claude/skills/x-integration/lib/browser.ts +148 -0
  32. package/.claude/skills/x-integration/lib/config.ts +62 -0
  33. package/.claude/skills/x-integration/scripts/like.ts +56 -0
  34. package/.claude/skills/x-integration/scripts/post.ts +66 -0
  35. package/.claude/skills/x-integration/scripts/quote.ts +80 -0
  36. package/.claude/skills/x-integration/scripts/reply.ts +74 -0
  37. package/.claude/skills/x-integration/scripts/retweet.ts +62 -0
  38. package/.claude/skills/x-integration/scripts/setup.ts +87 -0
  39. package/.env.example +1 -0
  40. package/.github/CODEOWNERS +10 -0
  41. package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  42. package/.github/workflows/bump-version.yml +32 -0
  43. package/.github/workflows/ci.yml +25 -0
  44. package/.github/workflows/merge-forward-skills.yml +160 -0
  45. package/.github/workflows/update-tokens.yml +42 -0
  46. package/.husky/pre-commit +1 -0
  47. package/.mcp.json +3 -0
  48. package/.nvmrc +1 -0
  49. package/.prettierrc +3 -0
  50. package/CHANGELOG.md +8 -0
  51. package/CLAUDE.md +64 -0
  52. package/CONTRIBUTING.md +23 -0
  53. package/CONTRIBUTORS.md +15 -0
  54. package/LICENSE +21 -0
  55. package/NanoClaw_with_Web-Support.md +290 -0
  56. package/README.md +261 -0
  57. package/README_zh.md +200 -0
  58. package/assets/nanoclaw-favicon.png +0 -0
  59. package/assets/nanoclaw-icon.png +0 -0
  60. package/assets/nanoclaw-logo-dark.png +0 -0
  61. package/assets/nanoclaw-logo.png +0 -0
  62. package/assets/nanoclaw-profile.jpeg +0 -0
  63. package/assets/nanoclaw-sales.png +0 -0
  64. package/assets/social-preview.jpg +0 -0
  65. package/config-examples/mount-allowlist.json +25 -0
  66. package/container/Dockerfile +70 -0
  67. package/container/agent-runner/package-lock.json +1524 -0
  68. package/container/agent-runner/package.json +21 -0
  69. package/container/agent-runner/src/index.ts +558 -0
  70. package/container/agent-runner/src/ipc-mcp-stdio.ts +338 -0
  71. package/container/agent-runner/tsconfig.json +15 -0
  72. package/container/build.sh +23 -0
  73. package/container/skills/agent-browser/SKILL.md +159 -0
  74. package/container/skills/capabilities/SKILL.md +100 -0
  75. package/container/skills/status/SKILL.md +104 -0
  76. package/dist/channels/index.d.ts +2 -0
  77. package/dist/channels/index.d.ts.map +1 -0
  78. package/dist/channels/index.js +9 -0
  79. package/dist/channels/index.js.map +1 -0
  80. package/dist/channels/registry.d.ts +13 -0
  81. package/dist/channels/registry.d.ts.map +1 -0
  82. package/dist/channels/registry.js +11 -0
  83. package/dist/channels/registry.js.map +1 -0
  84. package/dist/channels/registry.test.d.ts +2 -0
  85. package/dist/channels/registry.test.d.ts.map +1 -0
  86. package/dist/channels/registry.test.js +32 -0
  87. package/dist/channels/registry.test.js.map +1 -0
  88. package/dist/channels/web.d.ts +2 -0
  89. package/dist/channels/web.d.ts.map +1 -0
  90. package/dist/channels/web.js +1738 -0
  91. package/dist/channels/web.js.map +1 -0
  92. package/dist/cli.d.ts +11 -0
  93. package/dist/cli.d.ts.map +1 -0
  94. package/dist/cli.js +182 -0
  95. package/dist/cli.js.map +1 -0
  96. package/dist/config.d.ts +19 -0
  97. package/dist/config.d.ts.map +1 -0
  98. package/dist/config.js +36 -0
  99. package/dist/config.js.map +1 -0
  100. package/dist/container-runner.d.ts +44 -0
  101. package/dist/container-runner.d.ts.map +1 -0
  102. package/dist/container-runner.js +467 -0
  103. package/dist/container-runner.js.map +1 -0
  104. package/dist/container-runner.test.d.ts +2 -0
  105. package/dist/container-runner.test.d.ts.map +1 -0
  106. package/dist/container-runner.test.js +150 -0
  107. package/dist/container-runner.test.js.map +1 -0
  108. package/dist/container-runtime.d.ts +22 -0
  109. package/dist/container-runtime.d.ts.map +1 -0
  110. package/dist/container-runtime.js +96 -0
  111. package/dist/container-runtime.js.map +1 -0
  112. package/dist/container-runtime.test.d.ts +2 -0
  113. package/dist/container-runtime.test.d.ts.map +1 -0
  114. package/dist/container-runtime.test.js +93 -0
  115. package/dist/container-runtime.test.js.map +1 -0
  116. package/dist/credential-proxy.d.ts +21 -0
  117. package/dist/credential-proxy.d.ts.map +1 -0
  118. package/dist/credential-proxy.js +95 -0
  119. package/dist/credential-proxy.js.map +1 -0
  120. package/dist/credential-proxy.test.d.ts +2 -0
  121. package/dist/credential-proxy.test.d.ts.map +1 -0
  122. package/dist/credential-proxy.test.js +134 -0
  123. package/dist/credential-proxy.test.js.map +1 -0
  124. package/dist/db.d.ts +115 -0
  125. package/dist/db.d.ts.map +1 -0
  126. package/dist/db.js +549 -0
  127. package/dist/db.js.map +1 -0
  128. package/dist/db.test.d.ts +2 -0
  129. package/dist/db.test.d.ts.map +1 -0
  130. package/dist/db.test.js +360 -0
  131. package/dist/db.test.js.map +1 -0
  132. package/dist/env.d.ts +8 -0
  133. package/dist/env.d.ts.map +1 -0
  134. package/dist/env.js +42 -0
  135. package/dist/env.js.map +1 -0
  136. package/dist/formatting.test.d.ts +2 -0
  137. package/dist/formatting.test.d.ts.map +1 -0
  138. package/dist/formatting.test.js +183 -0
  139. package/dist/formatting.test.js.map +1 -0
  140. package/dist/group-folder.d.ts +5 -0
  141. package/dist/group-folder.d.ts.map +1 -0
  142. package/dist/group-folder.js +44 -0
  143. package/dist/group-folder.js.map +1 -0
  144. package/dist/group-folder.test.d.ts +2 -0
  145. package/dist/group-folder.test.d.ts.map +1 -0
  146. package/dist/group-folder.test.js +29 -0
  147. package/dist/group-folder.test.js.map +1 -0
  148. package/dist/group-queue.d.ts +34 -0
  149. package/dist/group-queue.d.ts.map +1 -0
  150. package/dist/group-queue.js +263 -0
  151. package/dist/group-queue.js.map +1 -0
  152. package/dist/group-queue.test.d.ts +2 -0
  153. package/dist/group-queue.test.d.ts.map +1 -0
  154. package/dist/group-queue.test.js +341 -0
  155. package/dist/group-queue.test.js.map +1 -0
  156. package/dist/index.d.ts +12 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +518 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/ipc-auth.test.d.ts +2 -0
  161. package/dist/ipc-auth.test.d.ts.map +1 -0
  162. package/dist/ipc-auth.test.js +434 -0
  163. package/dist/ipc-auth.test.js.map +1 -0
  164. package/dist/ipc.d.ts +32 -0
  165. package/dist/ipc.d.ts.map +1 -0
  166. package/dist/ipc.js +311 -0
  167. package/dist/ipc.js.map +1 -0
  168. package/dist/logger.d.ts +3 -0
  169. package/dist/logger.d.ts.map +1 -0
  170. package/dist/logger.js +14 -0
  171. package/dist/logger.js.map +1 -0
  172. package/dist/mount-security.d.ts +34 -0
  173. package/dist/mount-security.d.ts.map +1 -0
  174. package/dist/mount-security.js +325 -0
  175. package/dist/mount-security.js.map +1 -0
  176. package/dist/remote-control.d.ts +32 -0
  177. package/dist/remote-control.d.ts.map +1 -0
  178. package/dist/remote-control.js +185 -0
  179. package/dist/remote-control.js.map +1 -0
  180. package/dist/remote-control.test.d.ts +2 -0
  181. package/dist/remote-control.test.d.ts.map +1 -0
  182. package/dist/remote-control.test.js +321 -0
  183. package/dist/remote-control.test.js.map +1 -0
  184. package/dist/router.d.ts +8 -0
  185. package/dist/router.d.ts.map +1 -0
  186. package/dist/router.js +37 -0
  187. package/dist/router.js.map +1 -0
  188. package/dist/routing.test.d.ts +2 -0
  189. package/dist/routing.test.d.ts.map +1 -0
  190. package/dist/routing.test.js +81 -0
  191. package/dist/routing.test.js.map +1 -0
  192. package/dist/sender-allowlist.d.ts +14 -0
  193. package/dist/sender-allowlist.d.ts.map +1 -0
  194. package/dist/sender-allowlist.js +79 -0
  195. package/dist/sender-allowlist.js.map +1 -0
  196. package/dist/sender-allowlist.test.d.ts +2 -0
  197. package/dist/sender-allowlist.test.d.ts.map +1 -0
  198. package/dist/sender-allowlist.test.js +186 -0
  199. package/dist/sender-allowlist.test.js.map +1 -0
  200. package/dist/session-commands.d.ts +47 -0
  201. package/dist/session-commands.d.ts.map +1 -0
  202. package/dist/session-commands.js +102 -0
  203. package/dist/session-commands.js.map +1 -0
  204. package/dist/session-commands.test.d.ts +2 -0
  205. package/dist/session-commands.test.d.ts.map +1 -0
  206. package/dist/session-commands.test.js +190 -0
  207. package/dist/session-commands.test.js.map +1 -0
  208. package/dist/task-scheduler.d.ts +22 -0
  209. package/dist/task-scheduler.d.ts.map +1 -0
  210. package/dist/task-scheduler.js +210 -0
  211. package/dist/task-scheduler.js.map +1 -0
  212. package/dist/task-scheduler.test.d.ts +2 -0
  213. package/dist/task-scheduler.test.d.ts.map +1 -0
  214. package/dist/task-scheduler.test.js +107 -0
  215. package/dist/task-scheduler.test.js.map +1 -0
  216. package/dist/timezone.d.ts +6 -0
  217. package/dist/timezone.d.ts.map +1 -0
  218. package/dist/timezone.js +17 -0
  219. package/dist/timezone.js.map +1 -0
  220. package/dist/timezone.test.d.ts +2 -0
  221. package/dist/timezone.test.d.ts.map +1 -0
  222. package/dist/timezone.test.js +23 -0
  223. package/dist/timezone.test.js.map +1 -0
  224. package/dist/types.d.ts +78 -0
  225. package/dist/types.d.ts.map +1 -0
  226. package/dist/types.js +2 -0
  227. package/dist/types.js.map +1 -0
  228. package/docs/APPLE-CONTAINER-NETWORKING.md +90 -0
  229. package/docs/DEBUG_CHECKLIST.md +143 -0
  230. package/docs/REQUIREMENTS.md +196 -0
  231. package/docs/SDK_DEEP_DIVE.md +643 -0
  232. package/docs/SECURITY.md +122 -0
  233. package/docs/SPEC.md +785 -0
  234. package/docs/docker-sandboxes.md +359 -0
  235. package/docs/nanoclaw-architecture-final.md +1063 -0
  236. package/docs/nanorepo-architecture.md +168 -0
  237. package/docs/skills-as-branches.md +662 -0
  238. package/groups/global/CLAUDE.md +58 -0
  239. package/groups/main/CLAUDE.md +246 -0
  240. package/launchd/com.nanoclaw.plist +32 -0
  241. package/package.json +45 -0
  242. package/repo-tokens/README.md +113 -0
  243. package/repo-tokens/action.yml +186 -0
  244. package/repo-tokens/badge.svg +23 -0
  245. package/repo-tokens/examples/green.svg +14 -0
  246. package/repo-tokens/examples/red.svg +14 -0
  247. package/repo-tokens/examples/yellow-green.svg +14 -0
  248. package/repo-tokens/examples/yellow.svg +14 -0
  249. package/scripts/run-migrations.ts +105 -0
  250. package/setup/container.ts +144 -0
  251. package/setup/environment.test.ts +121 -0
  252. package/setup/environment.ts +94 -0
  253. package/setup/groups.ts +229 -0
  254. package/setup/index.ts +58 -0
  255. package/setup/mounts.ts +115 -0
  256. package/setup/platform.test.ts +120 -0
  257. package/setup/platform.ts +132 -0
  258. package/setup/register.test.ts +257 -0
  259. package/setup/register.ts +177 -0
  260. package/setup/service.test.ts +187 -0
  261. package/setup/service.ts +362 -0
  262. package/setup/status.ts +16 -0
  263. package/setup/verify.ts +192 -0
  264. package/setup.sh +161 -0
  265. package/src/channels/index.ts +12 -0
  266. package/src/channels/registry.test.ts +42 -0
  267. package/src/channels/registry.ts +32 -0
  268. package/src/channels/web.ts +1856 -0
  269. package/src/cli.ts +209 -0
  270. package/src/config.ts +73 -0
  271. package/src/container-runner.test.ts +210 -0
  272. package/src/container-runner.ts +707 -0
  273. package/src/container-runtime.test.ts +149 -0
  274. package/src/container-runtime.ts +127 -0
  275. package/src/credential-proxy.test.ts +192 -0
  276. package/src/credential-proxy.ts +125 -0
  277. package/src/db.test.ts +484 -0
  278. package/src/db.ts +803 -0
  279. package/src/env.ts +42 -0
  280. package/src/formatting.test.ts +256 -0
  281. package/src/group-folder.test.ts +43 -0
  282. package/src/group-folder.ts +44 -0
  283. package/src/group-queue.test.ts +484 -0
  284. package/src/group-queue.ts +365 -0
  285. package/src/index.ts +731 -0
  286. package/src/ipc-auth.test.ts +679 -0
  287. package/src/ipc.ts +461 -0
  288. package/src/logger.ts +16 -0
  289. package/src/mount-security.ts +419 -0
  290. package/src/remote-control.test.ts +397 -0
  291. package/src/remote-control.ts +224 -0
  292. package/src/router.ts +52 -0
  293. package/src/routing.test.ts +170 -0
  294. package/src/sender-allowlist.test.ts +216 -0
  295. package/src/sender-allowlist.ts +128 -0
  296. package/src/session-commands.test.ts +247 -0
  297. package/src/session-commands.ts +163 -0
  298. package/src/task-scheduler.test.ts +129 -0
  299. package/src/task-scheduler.ts +295 -0
  300. package/src/timezone.test.ts +29 -0
  301. package/src/timezone.ts +16 -0
  302. package/src/types.ts +107 -0
  303. package/tsconfig.json +20 -0
  304. package/vitest.config.ts +7 -0
  305. package/vitest.skills.config.ts +7 -0
@@ -0,0 +1,23 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="97" height="20" role="img" aria-label="40.6k tokens, 20% of context window">
2
+ <title>40.6k tokens, 20% of context window</title>
3
+ <linearGradient id="s" x2="0" y2="100%">
4
+ <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
5
+ <stop offset="1" stop-opacity=".1"/>
6
+ </linearGradient>
7
+ <clipPath id="r">
8
+ <rect width="97" height="20" rx="3" fill="#fff"/>
9
+ </clipPath>
10
+ <a xlink:href="https://github.com/qwibitai/nanoclaw/tree/main/repo-tokens">
11
+ <g clip-path="url(#r)">
12
+ <rect width="52" height="20" fill="#555"/>
13
+ <rect x="52" width="45" height="20" fill="#4c1"/>
14
+ <rect width="97" height="20" fill="url(#s)"/>
15
+ <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
16
+ <text aria-hidden="true" x="26" y="15" fill="#010101" fill-opacity=".3">tokens</text>
17
+ <text x="26" y="14">tokens</text>
18
+ <text aria-hidden="true" x="74" y="15" fill="#010101" fill-opacity=".3">40.6k</text>
19
+ <text x="74" y="14">40.6k</text>
20
+ </g>
21
+ </g>
22
+ </a>
23
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="97" height="20" role="img" aria-label="12.4k tokens">
2
+ <title>12.4k tokens</title>
3
+ <linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient>
4
+ <clipPath id="r"><rect width="97" height="20" rx="3"/></clipPath>
5
+ <g clip-path="url(#r)">
6
+ <rect width="52" height="20" fill="#555"/>
7
+ <rect x="52" width="45" height="20" fill="#4c1"/>
8
+ <rect width="97" height="20" fill="url(#s)"/>
9
+ <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
10
+ <text x="26" y="15" fill="#010101" fill-opacity=".3">tokens</text><text x="26" y="14">tokens</text>
11
+ <text x="74" y="15" fill="#010101" fill-opacity=".3">12.4k</text><text x="74" y="14">12.4k</text>
12
+ </g>
13
+ </g>
14
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="90" height="20" role="img" aria-label="158k tokens">
2
+ <title>158k tokens</title>
3
+ <linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient>
4
+ <clipPath id="r"><rect width="90" height="20" rx="3"/></clipPath>
5
+ <g clip-path="url(#r)">
6
+ <rect width="52" height="20" fill="#555"/>
7
+ <rect x="52" width="38" height="20" fill="#e05d44"/>
8
+ <rect width="90" height="20" fill="url(#s)"/>
9
+ <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
10
+ <text x="26" y="15" fill="#010101" fill-opacity=".3">tokens</text><text x="26" y="14">tokens</text>
11
+ <text x="71" y="15" fill="#010101" fill-opacity=".3">158k</text><text x="71" y="14">158k</text>
12
+ </g>
13
+ </g>
14
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="97" height="20" role="img" aria-label="74.8k tokens">
2
+ <title>74.8k tokens</title>
3
+ <linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient>
4
+ <clipPath id="r"><rect width="97" height="20" rx="3"/></clipPath>
5
+ <g clip-path="url(#r)">
6
+ <rect width="52" height="20" fill="#555"/>
7
+ <rect x="52" width="45" height="20" fill="#97ca00"/>
8
+ <rect width="97" height="20" fill="url(#s)"/>
9
+ <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
10
+ <text x="26" y="15" fill="#010101" fill-opacity=".3">tokens</text><text x="26" y="14">tokens</text>
11
+ <text x="74" y="15" fill="#010101" fill-opacity=".3">74.8k</text><text x="74" y="14">74.8k</text>
12
+ </g>
13
+ </g>
14
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="90" height="20" role="img" aria-label="120k tokens">
2
+ <title>120k tokens</title>
3
+ <linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient>
4
+ <clipPath id="r"><rect width="90" height="20" rx="3"/></clipPath>
5
+ <g clip-path="url(#r)">
6
+ <rect width="52" height="20" fill="#555"/>
7
+ <rect x="52" width="38" height="20" fill="#dfb317"/>
8
+ <rect width="90" height="20" fill="url(#s)"/>
9
+ <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
10
+ <text x="26" y="15" fill="#010101" fill-opacity=".3">tokens</text><text x="26" y="14">tokens</text>
11
+ <text x="71" y="15" fill="#010101" fill-opacity=".3">120k</text><text x="71" y="14">120k</text>
12
+ </g>
13
+ </g>
14
+ </svg>
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env tsx
2
+ import { execFileSync, execSync } from 'child_process';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ function compareSemver(a: string, b: string): number {
7
+ const partsA = a.split('.').map(Number);
8
+ const partsB = b.split('.').map(Number);
9
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
10
+ const diff = (partsA[i] || 0) - (partsB[i] || 0);
11
+ if (diff !== 0) return diff;
12
+ }
13
+ return 0;
14
+ }
15
+
16
+ // Resolve tsx binary once to avoid npx race conditions across migrations
17
+ function resolveTsx(): string {
18
+ // Check local node_modules first
19
+ const local = path.resolve('node_modules/.bin/tsx');
20
+ if (fs.existsSync(local)) return local;
21
+ // Fall back to whichever tsx is in PATH
22
+ try {
23
+ return execSync('which tsx', { encoding: 'utf-8' }).trim();
24
+ } catch {
25
+ return 'npx'; // last resort
26
+ }
27
+ }
28
+
29
+ const tsxBin = resolveTsx();
30
+
31
+ const fromVersion = process.argv[2];
32
+ const toVersion = process.argv[3];
33
+ const newCorePath = process.argv[4];
34
+
35
+ if (!fromVersion || !toVersion || !newCorePath) {
36
+ console.error(
37
+ 'Usage: tsx scripts/run-migrations.ts <from-version> <to-version> <new-core-path>',
38
+ );
39
+ process.exit(1);
40
+ }
41
+
42
+ interface MigrationResult {
43
+ version: string;
44
+ success: boolean;
45
+ error?: string;
46
+ }
47
+
48
+ const results: MigrationResult[] = [];
49
+
50
+ // Look for migrations in the new core
51
+ const migrationsDir = path.join(newCorePath, 'migrations');
52
+
53
+ if (!fs.existsSync(migrationsDir)) {
54
+ console.log(JSON.stringify({ migrationsRun: 0, results: [] }, null, 2));
55
+ process.exit(0);
56
+ }
57
+
58
+ // Discover migration directories (version-named)
59
+ const entries = fs.readdirSync(migrationsDir, { withFileTypes: true });
60
+ const migrationVersions = entries
61
+ .filter((e) => e.isDirectory() && /^\d+\.\d+\.\d+$/.test(e.name))
62
+ .map((e) => e.name)
63
+ .filter(
64
+ (v) =>
65
+ compareSemver(v, fromVersion) > 0 && compareSemver(v, toVersion) <= 0,
66
+ )
67
+ .sort(compareSemver);
68
+
69
+ const projectRoot = process.cwd();
70
+
71
+ for (const version of migrationVersions) {
72
+ const migrationIndex = path.join(migrationsDir, version, 'index.ts');
73
+ if (!fs.existsSync(migrationIndex)) {
74
+ results.push({
75
+ version,
76
+ success: false,
77
+ error: `Migration ${version}/index.ts not found`,
78
+ });
79
+ continue;
80
+ }
81
+
82
+ try {
83
+ const tsxArgs = tsxBin.endsWith('npx')
84
+ ? ['tsx', migrationIndex, projectRoot]
85
+ : [migrationIndex, projectRoot];
86
+ execFileSync(tsxBin, tsxArgs, {
87
+ stdio: 'pipe',
88
+ cwd: projectRoot,
89
+ timeout: 120_000,
90
+ });
91
+ results.push({ version, success: true });
92
+ } catch (err) {
93
+ const message = err instanceof Error ? err.message : String(err);
94
+ results.push({ version, success: false, error: message });
95
+ }
96
+ }
97
+
98
+ console.log(
99
+ JSON.stringify({ migrationsRun: results.length, results }, null, 2),
100
+ );
101
+
102
+ // Exit with error if any migration failed
103
+ if (results.some((r) => !r.success)) {
104
+ process.exit(1);
105
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Step: container — Build container image and verify with test run.
3
+ * Replaces 03-setup-container.sh
4
+ */
5
+ import { execSync } from 'child_process';
6
+ import path from 'path';
7
+
8
+ import { logger } from '../src/logger.js';
9
+ import { commandExists } from './platform.js';
10
+ import { emitStatus } from './status.js';
11
+
12
+ function parseArgs(args: string[]): { runtime: string } {
13
+ let runtime = '';
14
+ for (let i = 0; i < args.length; i++) {
15
+ if (args[i] === '--runtime' && args[i + 1]) {
16
+ runtime = args[i + 1];
17
+ i++;
18
+ }
19
+ }
20
+ return { runtime };
21
+ }
22
+
23
+ export async function run(args: string[]): Promise<void> {
24
+ const projectRoot = process.cwd();
25
+ const { runtime } = parseArgs(args);
26
+ const image = 'nanoclaw-agent:latest';
27
+ const logFile = path.join(projectRoot, 'logs', 'setup.log');
28
+
29
+ if (!runtime) {
30
+ emitStatus('SETUP_CONTAINER', {
31
+ RUNTIME: 'unknown',
32
+ IMAGE: image,
33
+ BUILD_OK: false,
34
+ TEST_OK: false,
35
+ STATUS: 'failed',
36
+ ERROR: 'missing_runtime_flag',
37
+ LOG: 'logs/setup.log',
38
+ });
39
+ process.exit(4);
40
+ }
41
+
42
+ // Validate runtime availability
43
+ if (runtime === 'apple-container' && !commandExists('container')) {
44
+ emitStatus('SETUP_CONTAINER', {
45
+ RUNTIME: runtime,
46
+ IMAGE: image,
47
+ BUILD_OK: false,
48
+ TEST_OK: false,
49
+ STATUS: 'failed',
50
+ ERROR: 'runtime_not_available',
51
+ LOG: 'logs/setup.log',
52
+ });
53
+ process.exit(2);
54
+ }
55
+
56
+ if (runtime === 'docker') {
57
+ if (!commandExists('docker')) {
58
+ emitStatus('SETUP_CONTAINER', {
59
+ RUNTIME: runtime,
60
+ IMAGE: image,
61
+ BUILD_OK: false,
62
+ TEST_OK: false,
63
+ STATUS: 'failed',
64
+ ERROR: 'runtime_not_available',
65
+ LOG: 'logs/setup.log',
66
+ });
67
+ process.exit(2);
68
+ }
69
+ try {
70
+ execSync('docker info', { stdio: 'ignore' });
71
+ } catch {
72
+ emitStatus('SETUP_CONTAINER', {
73
+ RUNTIME: runtime,
74
+ IMAGE: image,
75
+ BUILD_OK: false,
76
+ TEST_OK: false,
77
+ STATUS: 'failed',
78
+ ERROR: 'runtime_not_available',
79
+ LOG: 'logs/setup.log',
80
+ });
81
+ process.exit(2);
82
+ }
83
+ }
84
+
85
+ if (!['apple-container', 'docker'].includes(runtime)) {
86
+ emitStatus('SETUP_CONTAINER', {
87
+ RUNTIME: runtime,
88
+ IMAGE: image,
89
+ BUILD_OK: false,
90
+ TEST_OK: false,
91
+ STATUS: 'failed',
92
+ ERROR: 'unknown_runtime',
93
+ LOG: 'logs/setup.log',
94
+ });
95
+ process.exit(4);
96
+ }
97
+
98
+ const buildCmd =
99
+ runtime === 'apple-container' ? 'container build' : 'docker build';
100
+ const runCmd = runtime === 'apple-container' ? 'container' : 'docker';
101
+
102
+ // Build
103
+ let buildOk = false;
104
+ logger.info({ runtime }, 'Building container');
105
+ try {
106
+ execSync(`${buildCmd} -t ${image} .`, {
107
+ cwd: path.join(projectRoot, 'container'),
108
+ stdio: ['ignore', 'pipe', 'pipe'],
109
+ });
110
+ buildOk = true;
111
+ logger.info('Container build succeeded');
112
+ } catch (err) {
113
+ logger.error({ err }, 'Container build failed');
114
+ }
115
+
116
+ // Test
117
+ let testOk = false;
118
+ if (buildOk) {
119
+ logger.info('Testing container');
120
+ try {
121
+ const output = execSync(
122
+ `echo '{}' | ${runCmd} run -i --rm --entrypoint /bin/echo ${image} "Container OK"`,
123
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
124
+ );
125
+ testOk = output.includes('Container OK');
126
+ logger.info({ testOk }, 'Container test result');
127
+ } catch {
128
+ logger.error('Container test failed');
129
+ }
130
+ }
131
+
132
+ const status = buildOk && testOk ? 'success' : 'failed';
133
+
134
+ emitStatus('SETUP_CONTAINER', {
135
+ RUNTIME: runtime,
136
+ IMAGE: image,
137
+ BUILD_OK: buildOk,
138
+ TEST_OK: testOk,
139
+ STATUS: status,
140
+ LOG: 'logs/setup.log',
141
+ });
142
+
143
+ if (status === 'failed') process.exit(1);
144
+ }
@@ -0,0 +1,121 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import fs from 'fs';
3
+
4
+ import Database from 'better-sqlite3';
5
+
6
+ /**
7
+ * Tests for the environment check step.
8
+ *
9
+ * Verifies: config detection, Docker/AC detection, DB queries.
10
+ */
11
+
12
+ describe('environment detection', () => {
13
+ it('detects platform correctly', async () => {
14
+ const { getPlatform } = await import('./platform.js');
15
+ const platform = getPlatform();
16
+ expect(['macos', 'linux', 'unknown']).toContain(platform);
17
+ });
18
+ });
19
+
20
+ describe('registered groups DB query', () => {
21
+ let db: Database.Database;
22
+
23
+ beforeEach(() => {
24
+ db = new Database(':memory:');
25
+ db.exec(`CREATE TABLE IF NOT EXISTS registered_groups (
26
+ jid TEXT PRIMARY KEY,
27
+ name TEXT NOT NULL,
28
+ folder TEXT NOT NULL UNIQUE,
29
+ trigger_pattern TEXT NOT NULL,
30
+ added_at TEXT NOT NULL,
31
+ container_config TEXT,
32
+ requires_trigger INTEGER DEFAULT 1
33
+ )`);
34
+ });
35
+
36
+ it('returns 0 for empty table', () => {
37
+ const row = db
38
+ .prepare('SELECT COUNT(*) as count FROM registered_groups')
39
+ .get() as { count: number };
40
+ expect(row.count).toBe(0);
41
+ });
42
+
43
+ it('returns correct count after inserts', () => {
44
+ db.prepare(
45
+ `INSERT INTO registered_groups (jid, name, folder, trigger_pattern, added_at, requires_trigger)
46
+ VALUES (?, ?, ?, ?, ?, ?)`,
47
+ ).run(
48
+ '123@g.us',
49
+ 'Group 1',
50
+ 'group-1',
51
+ '@Andy',
52
+ '2024-01-01T00:00:00.000Z',
53
+ 1,
54
+ );
55
+
56
+ db.prepare(
57
+ `INSERT INTO registered_groups (jid, name, folder, trigger_pattern, added_at, requires_trigger)
58
+ VALUES (?, ?, ?, ?, ?, ?)`,
59
+ ).run(
60
+ '456@g.us',
61
+ 'Group 2',
62
+ 'group-2',
63
+ '@Andy',
64
+ '2024-01-01T00:00:00.000Z',
65
+ 1,
66
+ );
67
+
68
+ const row = db
69
+ .prepare('SELECT COUNT(*) as count FROM registered_groups')
70
+ .get() as { count: number };
71
+ expect(row.count).toBe(2);
72
+ });
73
+ });
74
+
75
+ describe('credentials detection', () => {
76
+ it('detects ANTHROPIC_API_KEY in env content', () => {
77
+ const content =
78
+ 'SOME_KEY=value\nANTHROPIC_API_KEY=sk-ant-test123\nOTHER=foo';
79
+ const hasCredentials =
80
+ /^(CLAUDE_CODE_OAUTH_TOKEN|ANTHROPIC_API_KEY)=/m.test(content);
81
+ expect(hasCredentials).toBe(true);
82
+ });
83
+
84
+ it('detects CLAUDE_CODE_OAUTH_TOKEN in env content', () => {
85
+ const content = 'CLAUDE_CODE_OAUTH_TOKEN=token123';
86
+ const hasCredentials =
87
+ /^(CLAUDE_CODE_OAUTH_TOKEN|ANTHROPIC_API_KEY)=/m.test(content);
88
+ expect(hasCredentials).toBe(true);
89
+ });
90
+
91
+ it('returns false when no credentials', () => {
92
+ const content = 'ASSISTANT_NAME="Andy"\nOTHER=foo';
93
+ const hasCredentials =
94
+ /^(CLAUDE_CODE_OAUTH_TOKEN|ANTHROPIC_API_KEY)=/m.test(content);
95
+ expect(hasCredentials).toBe(false);
96
+ });
97
+ });
98
+
99
+ describe('Docker detection logic', () => {
100
+ it('commandExists returns boolean', async () => {
101
+ const { commandExists } = await import('./platform.js');
102
+ expect(typeof commandExists('docker')).toBe('boolean');
103
+ expect(typeof commandExists('nonexistent_binary_xyz')).toBe('boolean');
104
+ });
105
+ });
106
+
107
+ describe('channel auth detection', () => {
108
+ it('detects non-empty auth directory', () => {
109
+ const hasAuth = (authDir: string) => {
110
+ try {
111
+ return fs.existsSync(authDir) && fs.readdirSync(authDir).length > 0;
112
+ } catch {
113
+ return false;
114
+ }
115
+ };
116
+
117
+ // Non-existent directory
118
+ expect(hasAuth('/tmp/nonexistent_auth_dir_xyz')).toBe(false);
119
+ });
120
+ });
121
+
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Step: environment — Detect OS, Node, container runtimes, existing config.
3
+ * Replaces 01-check-environment.sh
4
+ */
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+
8
+ import Database from 'better-sqlite3';
9
+
10
+ import { STORE_DIR } from '../src/config.js';
11
+ import { logger } from '../src/logger.js';
12
+ import { commandExists, getPlatform, isHeadless, isWSL } from './platform.js';
13
+ import { emitStatus } from './status.js';
14
+
15
+ export async function run(_args: string[]): Promise<void> {
16
+ const projectRoot = process.cwd();
17
+
18
+ logger.info('Starting environment check');
19
+
20
+ const platform = getPlatform();
21
+ const wsl = isWSL();
22
+ const headless = isHeadless();
23
+
24
+ // Check Apple Container
25
+ let appleContainer: 'installed' | 'not_found' = 'not_found';
26
+ if (commandExists('container')) {
27
+ appleContainer = 'installed';
28
+ }
29
+
30
+ // Check Docker
31
+ let docker: 'running' | 'installed_not_running' | 'not_found' = 'not_found';
32
+ if (commandExists('docker')) {
33
+ try {
34
+ const { execSync } = await import('child_process');
35
+ execSync('docker info', { stdio: 'ignore' });
36
+ docker = 'running';
37
+ } catch {
38
+ docker = 'installed_not_running';
39
+ }
40
+ }
41
+
42
+ // Check existing config
43
+ const hasEnv = fs.existsSync(path.join(projectRoot, '.env'));
44
+
45
+ const authDir = path.join(projectRoot, 'store', 'auth');
46
+ const hasAuth = fs.existsSync(authDir) && fs.readdirSync(authDir).length > 0;
47
+
48
+ let hasRegisteredGroups = false;
49
+ // Check JSON file first (pre-migration)
50
+ if (fs.existsSync(path.join(projectRoot, 'data', 'registered_groups.json'))) {
51
+ hasRegisteredGroups = true;
52
+ } else {
53
+ // Check SQLite directly using better-sqlite3 (no sqlite3 CLI needed)
54
+ const dbPath = path.join(STORE_DIR, 'messages.db');
55
+ if (fs.existsSync(dbPath)) {
56
+ try {
57
+ const db = new Database(dbPath, { readonly: true });
58
+ const row = db
59
+ .prepare('SELECT COUNT(*) as count FROM registered_groups')
60
+ .get() as { count: number };
61
+ if (row.count > 0) hasRegisteredGroups = true;
62
+ db.close();
63
+ } catch {
64
+ // Table might not exist yet
65
+ }
66
+ }
67
+ }
68
+
69
+ logger.info(
70
+ {
71
+ platform,
72
+ wsl,
73
+ appleContainer,
74
+ docker,
75
+ hasEnv,
76
+ hasAuth,
77
+ hasRegisteredGroups,
78
+ },
79
+ 'Environment check complete',
80
+ );
81
+
82
+ emitStatus('CHECK_ENVIRONMENT', {
83
+ PLATFORM: platform,
84
+ IS_WSL: wsl,
85
+ IS_HEADLESS: headless,
86
+ APPLE_CONTAINER: appleContainer,
87
+ DOCKER: docker,
88
+ HAS_ENV: hasEnv,
89
+ HAS_AUTH: hasAuth,
90
+ HAS_REGISTERED_GROUPS: hasRegisteredGroups,
91
+ STATUS: 'success',
92
+ LOG: 'logs/setup.log',
93
+ });
94
+ }