bazilion 0.0.0

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 (260) hide show
  1. package/.understand-anything/.understandignore +25 -0
  2. package/.understand-anything/fingerprints.json +14267 -0
  3. package/.understand-anything/knowledge-graph.json +18128 -0
  4. package/.understand-anything/meta.json +6 -0
  5. package/CLAUDE.md +164 -0
  6. package/LICENSE +21 -0
  7. package/README.md +195 -0
  8. package/apps/cli/package.json +21 -0
  9. package/apps/cli/src/auth-file.ts +18 -0
  10. package/apps/cli/src/client.ts +37 -0
  11. package/apps/cli/src/columnize.ts +32 -0
  12. package/apps/cli/src/commands/agent.ts +574 -0
  13. package/apps/cli/src/commands/auth.ts +110 -0
  14. package/apps/cli/src/commands/backup.ts +135 -0
  15. package/apps/cli/src/commands/completion.ts +155 -0
  16. package/apps/cli/src/commands/config.ts +95 -0
  17. package/apps/cli/src/commands/doctor.ts +131 -0
  18. package/apps/cli/src/commands/group.ts +132 -0
  19. package/apps/cli/src/commands/inbox.ts +82 -0
  20. package/apps/cli/src/commands/login.ts +73 -0
  21. package/apps/cli/src/commands/memory.ts +106 -0
  22. package/apps/cli/src/commands/profile.ts +259 -0
  23. package/apps/cli/src/commands/provider.ts +170 -0
  24. package/apps/cli/src/commands/send.ts +24 -0
  25. package/apps/cli/src/commands/serve.ts +89 -0
  26. package/apps/cli/src/commands/skill.ts +120 -0
  27. package/apps/cli/src/commands/token.ts +148 -0
  28. package/apps/cli/src/commands/trigger.ts +129 -0
  29. package/apps/cli/src/commands/uninstall.ts +156 -0
  30. package/apps/cli/src/index.ts +196 -0
  31. package/apps/cli/src/paths.ts +12 -0
  32. package/apps/cli/test/agent.test.ts +213 -0
  33. package/apps/cli/test/backup.test.ts +95 -0
  34. package/apps/cli/test/chat.test.ts +539 -0
  35. package/apps/cli/test/columnize.test.ts +29 -0
  36. package/apps/cli/test/completion.test.ts +45 -0
  37. package/apps/cli/test/config-page.test.ts +151 -0
  38. package/apps/cli/test/group.test.ts +74 -0
  39. package/apps/cli/test/helpers.ts +70 -0
  40. package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
  41. package/apps/cli/test/inbox.test.ts +147 -0
  42. package/apps/cli/test/memory.test.ts +69 -0
  43. package/apps/cli/test/profile.test.ts +110 -0
  44. package/apps/cli/test/send.test.ts +30 -0
  45. package/apps/cli/test/server-fixture.ts +212 -0
  46. package/apps/cli/test/session-head.test.ts +45 -0
  47. package/apps/cli/test/skill.test.ts +128 -0
  48. package/apps/cli/test/token.test.ts +109 -0
  49. package/apps/cli/test/trigger.test.ts +245 -0
  50. package/apps/cli/tsconfig.json +4 -0
  51. package/apps/daemon/package.json +31 -0
  52. package/apps/daemon/src/app.ts +41 -0
  53. package/apps/daemon/src/core/agent/archive.ts +8 -0
  54. package/apps/daemon/src/core/agent/delete.ts +30 -0
  55. package/apps/daemon/src/core/agent/resolve.ts +31 -0
  56. package/apps/daemon/src/core/agent/spawn.ts +95 -0
  57. package/apps/daemon/src/core/agent/unarchive.ts +11 -0
  58. package/apps/daemon/src/core/availableModels.ts +58 -0
  59. package/apps/daemon/src/core/db/client.ts +113 -0
  60. package/apps/daemon/src/core/db/migrate.ts +41 -0
  61. package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
  62. package/apps/daemon/src/core/group/delete.ts +21 -0
  63. package/apps/daemon/src/core/group/register.ts +68 -0
  64. package/apps/daemon/src/core/index.ts +71 -0
  65. package/apps/daemon/src/core/paths.ts +56 -0
  66. package/apps/daemon/src/core/profile/create.ts +78 -0
  67. package/apps/daemon/src/core/profile/delete.ts +26 -0
  68. package/apps/daemon/src/core/profile/identity.ts +70 -0
  69. package/apps/daemon/src/core/profile/load.ts +45 -0
  70. package/apps/daemon/src/core/profile/seed.ts +74 -0
  71. package/apps/daemon/src/core/profile/templates.ts +60 -0
  72. package/apps/daemon/src/core/profile/update.ts +57 -0
  73. package/apps/daemon/src/core/profile/validate.ts +9 -0
  74. package/apps/daemon/src/core/repos/agents.ts +202 -0
  75. package/apps/daemon/src/core/repos/config.ts +78 -0
  76. package/apps/daemon/src/core/repos/groups.ts +55 -0
  77. package/apps/daemon/src/core/repos/messages.ts +127 -0
  78. package/apps/daemon/src/core/repos/profiles.ts +83 -0
  79. package/apps/daemon/src/core/repos/providerModels.ts +58 -0
  80. package/apps/daemon/src/core/repos/providerState.ts +37 -0
  81. package/apps/daemon/src/core/repos/secrets.ts +145 -0
  82. package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
  83. package/apps/daemon/src/core/repos/triggers.ts +101 -0
  84. package/apps/daemon/src/core/repos/webTokens.ts +87 -0
  85. package/apps/daemon/src/core/secrets.ts +65 -0
  86. package/apps/daemon/src/core/services.ts +264 -0
  87. package/apps/daemon/src/core/skills/discover.ts +28 -0
  88. package/apps/daemon/src/core/skills/import.ts +136 -0
  89. package/apps/daemon/src/core/skills/parse.ts +52 -0
  90. package/apps/daemon/src/core/skills/resolve.ts +50 -0
  91. package/apps/daemon/src/index.ts +45 -0
  92. package/apps/daemon/src/lib/agent-cancel.ts +48 -0
  93. package/apps/daemon/src/lib/agent-id.ts +13 -0
  94. package/apps/daemon/src/lib/agent-turn.ts +56 -0
  95. package/apps/daemon/src/lib/api-key.ts +56 -0
  96. package/apps/daemon/src/lib/auth.ts +41 -0
  97. package/apps/daemon/src/lib/cron.ts +93 -0
  98. package/apps/daemon/src/lib/ctx.ts +80 -0
  99. package/apps/daemon/src/lib/messaging-host.ts +34 -0
  100. package/apps/daemon/src/lib/middleware-auth.ts +52 -0
  101. package/apps/daemon/src/lib/scheduler.ts +294 -0
  102. package/apps/daemon/src/routes/agents.ts +772 -0
  103. package/apps/daemon/src/routes/auth-login.ts +193 -0
  104. package/apps/daemon/src/routes/config.ts +267 -0
  105. package/apps/daemon/src/routes/groups.ts +133 -0
  106. package/apps/daemon/src/routes/messages.ts +29 -0
  107. package/apps/daemon/src/routes/misc.ts +239 -0
  108. package/apps/daemon/src/routes/profiles.ts +197 -0
  109. package/apps/daemon/src/routes/skills.ts +123 -0
  110. package/apps/daemon/src/routes/triggers.ts +29 -0
  111. package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
  112. package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
  113. package/apps/daemon/src/runtime/index.ts +77 -0
  114. package/apps/daemon/src/runtime/memory/files.ts +103 -0
  115. package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
  116. package/apps/daemon/src/runtime/memory/types.ts +16 -0
  117. package/apps/daemon/src/runtime/pi/events.ts +173 -0
  118. package/apps/daemon/src/runtime/pi/session.ts +536 -0
  119. package/apps/daemon/src/runtime/pi/tools.ts +85 -0
  120. package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
  121. package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
  122. package/apps/daemon/src/runtime/providers/registry.ts +374 -0
  123. package/apps/daemon/src/runtime/providers/retry.ts +176 -0
  124. package/apps/daemon/src/runtime/providers/types.ts +33 -0
  125. package/apps/daemon/src/runtime/session/prompt.ts +83 -0
  126. package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
  127. package/apps/daemon/src/runtime/tools/home.ts +114 -0
  128. package/apps/daemon/src/runtime/tools/memory.ts +81 -0
  129. package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
  130. package/apps/daemon/src/runtime/tools/registry.ts +29 -0
  131. package/apps/daemon/src/runtime/tools/types.ts +13 -0
  132. package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
  133. package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
  134. package/apps/daemon/src/runtime/tools/web.ts +273 -0
  135. package/apps/daemon/src/runtime/worker/entry.ts +221 -0
  136. package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
  137. package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
  138. package/apps/daemon/test/core/agents.test.ts +319 -0
  139. package/apps/daemon/test/core/available-models.test.ts +63 -0
  140. package/apps/daemon/test/core/config.test.ts +99 -0
  141. package/apps/daemon/test/core/groups.test.ts +63 -0
  142. package/apps/daemon/test/core/helpers.ts +50 -0
  143. package/apps/daemon/test/core/identity.test.ts +85 -0
  144. package/apps/daemon/test/core/migrations.test.ts +83 -0
  145. package/apps/daemon/test/core/profiles.test.ts +182 -0
  146. package/apps/daemon/test/core/provider-models.test.ts +57 -0
  147. package/apps/daemon/test/core/provider-state.test.ts +36 -0
  148. package/apps/daemon/test/core/skill-meta.test.ts +45 -0
  149. package/apps/daemon/test/core/skills.test.ts +271 -0
  150. package/apps/daemon/test/core/triggers.test.ts +182 -0
  151. package/apps/daemon/test/core/web-tokens.test.ts +79 -0
  152. package/apps/daemon/test/cron.test.ts +90 -0
  153. package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
  154. package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
  155. package/apps/daemon/test/runtime/memory.test.ts +78 -0
  156. package/apps/daemon/test/runtime/messaging.test.ts +213 -0
  157. package/apps/daemon/test/runtime/mock-server.ts +65 -0
  158. package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
  159. package/apps/daemon/test/runtime/providers.test.ts +306 -0
  160. package/apps/daemon/test/runtime/retry.test.ts +191 -0
  161. package/apps/daemon/test/runtime/session-head.test.ts +90 -0
  162. package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
  163. package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
  164. package/apps/daemon/tsconfig.json +4 -0
  165. package/apps/mobile/README.md +60 -0
  166. package/apps/mobile/app/_layout.tsx +58 -0
  167. package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
  168. package/apps/mobile/app/agents/[id]/index.tsx +166 -0
  169. package/apps/mobile/app/agents/index.tsx +212 -0
  170. package/apps/mobile/app/index.tsx +21 -0
  171. package/apps/mobile/app/pair.tsx +226 -0
  172. package/apps/mobile/app/settings.tsx +419 -0
  173. package/apps/mobile/app.json +49 -0
  174. package/apps/mobile/assets/adaptive-icon.png +0 -0
  175. package/apps/mobile/assets/favicon.png +0 -0
  176. package/apps/mobile/assets/icon.png +0 -0
  177. package/apps/mobile/assets/splash-icon.png +0 -0
  178. package/apps/mobile/babel.config.js +6 -0
  179. package/apps/mobile/metro.config.js +28 -0
  180. package/apps/mobile/package.json +44 -0
  181. package/apps/mobile/src/auth.ts +66 -0
  182. package/apps/mobile/src/pair-url.ts +48 -0
  183. package/apps/mobile/src/theme-context.tsx +88 -0
  184. package/apps/mobile/src/theme.ts +135 -0
  185. package/apps/mobile/test/pair-url.test.ts +46 -0
  186. package/apps/mobile/tsconfig.json +23 -0
  187. package/apps/web/components.json +25 -0
  188. package/apps/web/package.json +39 -0
  189. package/apps/web/public/baziu.svg +8 -0
  190. package/apps/web/src/components/AgentTabs.tsx +45 -0
  191. package/apps/web/src/components/BaziuLogo.tsx +21 -0
  192. package/apps/web/src/components/ChatPane.tsx +1033 -0
  193. package/apps/web/src/components/ConfigTabs.tsx +29 -0
  194. package/apps/web/src/components/CopyButton.tsx +68 -0
  195. package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
  196. package/apps/web/src/components/FieldRow.tsx +94 -0
  197. package/apps/web/src/components/Footer.tsx +10 -0
  198. package/apps/web/src/components/PawIcon.tsx +15 -0
  199. package/apps/web/src/components/Sidebar.tsx +287 -0
  200. package/apps/web/src/components/SpawnDialog.tsx +129 -0
  201. package/apps/web/src/components/ThemeToggle.tsx +75 -0
  202. package/apps/web/src/components/TopNav.tsx +34 -0
  203. package/apps/web/src/components/ui/button.tsx +67 -0
  204. package/apps/web/src/components/ui/card.tsx +103 -0
  205. package/apps/web/src/components/ui/checkbox.tsx +31 -0
  206. package/apps/web/src/components/ui/dialog.tsx +168 -0
  207. package/apps/web/src/components/ui/input.tsx +19 -0
  208. package/apps/web/src/components/ui/label.tsx +22 -0
  209. package/apps/web/src/components/ui/radio-group.tsx +44 -0
  210. package/apps/web/src/components/ui/select.tsx +192 -0
  211. package/apps/web/src/components/ui/separator.tsx +26 -0
  212. package/apps/web/src/components/ui/table.tsx +116 -0
  213. package/apps/web/src/components/ui/tabs.tsx +88 -0
  214. package/apps/web/src/components/ui/textarea.tsx +18 -0
  215. package/apps/web/src/lib/auth.ts +50 -0
  216. package/apps/web/src/lib/daemon-client.ts +34 -0
  217. package/apps/web/src/lib/md.ts +45 -0
  218. package/apps/web/src/lib/utils.ts +6 -0
  219. package/apps/web/src/lib/wire-constants.ts +27 -0
  220. package/apps/web/src/routeTree.gen.ts +408 -0
  221. package/apps/web/src/router.tsx +20 -0
  222. package/apps/web/src/routes/__root.tsx +123 -0
  223. package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
  224. package/apps/web/src/routes/agents/$id/index.tsx +527 -0
  225. package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
  226. package/apps/web/src/routes/agents/index.tsx +265 -0
  227. package/apps/web/src/routes/api/$.ts +88 -0
  228. package/apps/web/src/routes/config/index.tsx +315 -0
  229. package/apps/web/src/routes/config/services.tsx +49 -0
  230. package/apps/web/src/routes/config/tokens.tsx +192 -0
  231. package/apps/web/src/routes/groups/$id/index.tsx +153 -0
  232. package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
  233. package/apps/web/src/routes/groups/index.tsx +191 -0
  234. package/apps/web/src/routes/index.tsx +133 -0
  235. package/apps/web/src/routes/login.tsx +63 -0
  236. package/apps/web/src/routes/profiles/$id.tsx +549 -0
  237. package/apps/web/src/routes/profiles/index.tsx +458 -0
  238. package/apps/web/src/routes/skills/index.tsx +297 -0
  239. package/apps/web/src/routes/welcome.tsx +61 -0
  240. package/apps/web/src/styles.css +449 -0
  241. package/apps/web/tsconfig.json +25 -0
  242. package/apps/web/vite.config.ts +25 -0
  243. package/biome.json +23 -0
  244. package/docs/agent-engine.md +219 -0
  245. package/docs/architecture.md +627 -0
  246. package/docs/backlog/README.md +42 -0
  247. package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
  248. package/docs/openclaw-reference.md +210 -0
  249. package/package.json +38 -0
  250. package/packages/api-types/package.json +11 -0
  251. package/packages/api-types/src/entities.ts +146 -0
  252. package/packages/api-types/src/events.ts +55 -0
  253. package/packages/api-types/src/index.ts +488 -0
  254. package/packages/api-types/src/memory.ts +15 -0
  255. package/packages/client/package.json +13 -0
  256. package/packages/client/src/index.ts +117 -0
  257. package/pnpm-workspace.yaml +3 -0
  258. package/tsconfig.base.json +23 -0
  259. package/tsconfig.json +11 -0
  260. package/vitest.config.ts +22 -0
@@ -0,0 +1,24 @@
1
+ import type { Message, SendMessageRequest } from '@bazilion/api-types'
2
+ import { defineCommand } from 'citty'
3
+ import { createClient } from '../client.ts'
4
+
5
+ export const sendCommand = defineCommand({
6
+ meta: {
7
+ name: 'send',
8
+ description: 'Send a message from one agent to another',
9
+ },
10
+ args: {
11
+ from: { type: 'positional', required: true, description: 'Sender agent id' },
12
+ to: { type: 'positional', required: true, description: 'Recipient agent id' },
13
+ message: { type: 'positional', required: true, description: 'Message text' },
14
+ },
15
+ async run({ args }) {
16
+ const client = createClient()
17
+ const body: SendMessageRequest = {
18
+ from: args.from,
19
+ payload: { text: args.message },
20
+ }
21
+ const msg = await client.post<Message>(`/api/agents/${args.to}/messages`, body)
22
+ console.log(`sent message ${msg.id}`)
23
+ },
24
+ })
@@ -0,0 +1,89 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+ import { join } from 'node:path'
4
+ import { defineCommand } from 'citty'
5
+
6
+ // apps/cli/src/commands/serve.ts → apps/daemon/src/index.ts
7
+ const daemonEntry = join(import.meta.dirname, '..', '..', '..', 'daemon', 'src', 'index.ts')
8
+
9
+ export const serveCommand = defineCommand({
10
+ meta: {
11
+ name: 'serve',
12
+ description: 'Start the bazilion daemon (HTTP API)',
13
+ },
14
+ args: {
15
+ port: { type: 'string', description: 'Port (default 4321)' },
16
+ host: {
17
+ type: 'string',
18
+ description: 'Host (default 127.0.0.1). Put a TLS proxy in front before binding 0.0.0.0',
19
+ },
20
+ },
21
+ async run({ args }) {
22
+ if (!existsSync(daemonEntry)) {
23
+ throw new Error(
24
+ `bazilion daemon not found at ${daemonEntry}. Are you running from a development checkout?`,
25
+ )
26
+ }
27
+
28
+ const env: NodeJS.ProcessEnv = { ...process.env }
29
+ if (args.port) env.PORT = args.port
30
+ if (args.host) env.HOST = args.host
31
+
32
+ const host = args.host ?? '127.0.0.1'
33
+ const port = args.port ?? '4321'
34
+
35
+ if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') {
36
+ console.error('')
37
+ console.error(`⚠ binding to ${host} — the daemon is now reachable beyond loopback.`)
38
+ console.error(' anyone on this network who has a valid token can reach every API.')
39
+ console.error(' put a TLS proxy in front for untrusted networks.')
40
+ console.error('')
41
+ }
42
+
43
+ console.log(`starting bazilion daemon at http://${host}:${port}`)
44
+ console.log('(web UI runs separately: cd apps/web && pnpm dev)')
45
+
46
+ // Spawn the daemon under tsx. Stdio inherited so the daemon's startup log
47
+ // and any console.error go to the user's terminal directly. The daemon
48
+ // installs its own SIGINT/SIGTERM handlers and shuts the HTTP server
49
+ // gracefully — we just need to keep this process alive long enough for it.
50
+ //
51
+ // No vite anywhere in this tree → the daemon never flips the TTY into raw
52
+ // mode, so the post-Ctrl+C arrow-key-echo bug is gone by construction.
53
+ const proc = spawn('node', ['--import', 'tsx/esm', daemonEntry], {
54
+ env,
55
+ stdio: 'inherit',
56
+ })
57
+
58
+ let signalled = false
59
+ const onSigterm = (): void => {
60
+ signalled = true
61
+ try {
62
+ proc.kill('SIGTERM')
63
+ } catch {}
64
+ }
65
+ // SIGINT from the kernel (Ctrl+C) goes to the foreground pgroup, so the
66
+ // daemon already gets it directly — we just have to stay alive.
67
+ process.on('SIGINT', () => {
68
+ signalled = true
69
+ })
70
+ process.on('SIGTERM', onSigterm)
71
+
72
+ await new Promise<void>((resolve, reject) => {
73
+ proc.on('error', (err) => reject(err))
74
+ proc.on('close', (code) => {
75
+ // signalled exits get translated to 0 so the outer `pnpm tsx … serve`
76
+ // wrapper doesn't print `ELIFECYCLE Command failed` after a clean Ctrl+C.
77
+ if (signalled) {
78
+ process.exitCode = 0
79
+ resolve()
80
+ } else if (code === 0 || code === null) {
81
+ resolve()
82
+ } else {
83
+ process.exitCode = code
84
+ resolve()
85
+ }
86
+ })
87
+ })
88
+ },
89
+ })
@@ -0,0 +1,120 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs'
2
+ import { basename, resolve } from 'node:path'
3
+ import type {
4
+ ImportSkillsRequest,
5
+ ImportSkillsResponse,
6
+ ResolvedSkillsResponse,
7
+ SkillInfo,
8
+ } from '@bazilion/api-types'
9
+ import { defineCommand } from 'citty'
10
+ import { createClient } from '../client.ts'
11
+ import { columnize } from '../columnize.ts'
12
+
13
+ const listCmd = defineCommand({
14
+ meta: { name: 'list', description: 'List installed skills (or those attached to an agent)' },
15
+ args: {
16
+ agent: { type: 'string', description: 'Filter to skills attached to this agent' },
17
+ },
18
+ async run({ args }) {
19
+ const client = createClient()
20
+
21
+ if (args.agent) {
22
+ const set = await client.get<ResolvedSkillsResponse>(`/api/agents/${args.agent}/skills`)
23
+ if (set.resolved.length === 0 && set.missing.length === 0) {
24
+ console.log('(no skills attached to this agent)')
25
+ return
26
+ }
27
+ const rows = set.resolved.map((s) => [s.name, s.description])
28
+ for (const line of columnize(rows)) console.log(line)
29
+ for (const m of set.missing) {
30
+ console.log(`(missing: ${m.name} — ${m.reason})`)
31
+ }
32
+ return
33
+ }
34
+
35
+ const skills = await client.get<SkillInfo[]>('/api/skills')
36
+ if (skills.length === 0) {
37
+ console.log('(no skills installed; use "bazilion skill import" to add some)')
38
+ return
39
+ }
40
+ const rows = skills.map((s) => [
41
+ s.name,
42
+ s.parseError ? `(parse error: ${s.parseError})` : s.description,
43
+ ])
44
+ for (const line of columnize(rows)) console.log(line)
45
+ },
46
+ })
47
+
48
+ const importCmd = defineCommand({
49
+ meta: {
50
+ name: 'import',
51
+ description: 'Import skills from openclaw, a directory, or a local .zip archive',
52
+ },
53
+ args: {
54
+ from: {
55
+ type: 'string',
56
+ required: true,
57
+ description:
58
+ 'Source: "openclaw", a filesystem path on the server, or a local .zip file to upload',
59
+ },
60
+ force: { type: 'boolean', description: 'Overwrite existing skills' },
61
+ },
62
+ async run({ args }) {
63
+ const client = createClient()
64
+
65
+ // Auto-detect local .zip uploads: if `--from` points at an existing local
66
+ // file ending in .zip, stream it to the server as multipart. Otherwise
67
+ // fall through to the legacy JSON path (server reads the source directly).
68
+ const absFrom = resolve(args.from)
69
+ const isLocalZip =
70
+ args.from.toLowerCase().endsWith('.zip') && existsSync(absFrom) && statSync(absFrom).isFile()
71
+
72
+ let result: ImportSkillsResponse
73
+ if (isLocalZip) {
74
+ const bytes = readFileSync(absFrom)
75
+ const file = new File([bytes], basename(absFrom), { type: 'application/zip' })
76
+ const fd = new FormData()
77
+ fd.set('file', file)
78
+ if (args.force) fd.set('force', 'true')
79
+ result = await client.postMultipart<ImportSkillsResponse>('/api/skills/import', fd)
80
+ } else {
81
+ const body: ImportSkillsRequest = {
82
+ source: args.from,
83
+ force: args.force,
84
+ }
85
+ result = await client.post<ImportSkillsResponse>('/api/skills/import', body)
86
+ }
87
+
88
+ if (result.imported.length === 0 && result.skipped.length === 0) {
89
+ console.log('(nothing to import)')
90
+ return
91
+ }
92
+ for (const name of result.imported) {
93
+ console.log(`imported ${name}`)
94
+ }
95
+ for (const s of result.skipped) {
96
+ console.log(`skipped ${s.name}: ${s.reason}`)
97
+ }
98
+ },
99
+ })
100
+
101
+ const rmCmd = defineCommand({
102
+ meta: { name: 'rm', description: 'Remove an installed skill' },
103
+ args: {
104
+ name: { type: 'positional', required: true },
105
+ },
106
+ async run({ args }) {
107
+ const client = createClient()
108
+ await client.del(`/api/skills/${encodeURIComponent(args.name)}`)
109
+ console.log(`removed skill ${args.name}`)
110
+ },
111
+ })
112
+
113
+ export const skillCommand = defineCommand({
114
+ meta: { name: 'skill', description: 'Manage the skill library' },
115
+ subCommands: {
116
+ list: listCmd,
117
+ import: importCmd,
118
+ rm: rmCmd,
119
+ },
120
+ })
@@ -0,0 +1,148 @@
1
+ import { networkInterfaces } from 'node:os'
2
+ import type {
3
+ CreateTokenRequest,
4
+ CreateTokenResponse,
5
+ ListTokensResponse,
6
+ WebToken,
7
+ } from '@bazilion/api-types'
8
+ import { defineCommand } from 'citty'
9
+ import qrcode from 'qrcode-terminal'
10
+ import { readAuthFile } from '../auth-file.ts'
11
+ import { createClient, loadClientConfig } from '../client.ts'
12
+ import { columnize } from '../columnize.ts'
13
+ import { resolveCliPaths } from '../paths.ts'
14
+
15
+ function tokenRow(t: WebToken): string[] {
16
+ const last = t.lastUsedAt ? new Date(t.lastUsedAt).toISOString() : '(never)'
17
+ const state = t.revokedAt ? 'revoked' : 'active'
18
+ return [t.id, state, t.label, `last: ${last}`]
19
+ }
20
+
21
+ /**
22
+ * Best-effort LAN host detection for QR pairing. A phone can't reach
23
+ * `127.0.0.1`, so when the server URL is loopback we swap in a routable
24
+ * interface address. Multi-NIC machines get a warning listing every
25
+ * candidate — the user can override with --server.
26
+ */
27
+ function detectLanOrigin(port: string): { origin: string; warning?: string } | null {
28
+ const candidates: string[] = []
29
+ for (const ifs of Object.values(networkInterfaces())) {
30
+ for (const i of ifs ?? []) {
31
+ if (i.family === 'IPv4' && !i.internal) candidates.push(i.address)
32
+ }
33
+ }
34
+ const [first, ...rest] = candidates
35
+ if (!first) return null
36
+ const origin = `http://${first}:${port}`
37
+ return rest.length > 0
38
+ ? { origin, warning: `multiple LAN IPs found (${candidates.join(', ')}) — using ${first}` }
39
+ : { origin }
40
+ }
41
+
42
+ function resolveQrServer(override: string | undefined): string {
43
+ if (override) return override.replace(/\/$/, '')
44
+
45
+ const cfg = loadClientConfig()
46
+ const current = new URL(cfg.serverUrl)
47
+ const isLoopback =
48
+ current.hostname === '127.0.0.1' ||
49
+ current.hostname === 'localhost' ||
50
+ current.hostname === '::1'
51
+
52
+ if (!isLoopback) return cfg.serverUrl.replace(/\/$/, '')
53
+
54
+ const detected = detectLanOrigin(current.port || '4321')
55
+ if (!detected) {
56
+ throw new Error(
57
+ 'server URL is loopback-only and no LAN interface was found. ' +
58
+ 'Pass --server http://<host>:<port> so the mobile client knows where to connect.',
59
+ )
60
+ }
61
+ if (detected.warning) console.warn(`⚠ ${detected.warning}`)
62
+ return detected.origin
63
+ }
64
+
65
+ const createCmd = defineCommand({
66
+ meta: { name: 'create', description: 'Mint a new web token (shown once)' },
67
+ args: {
68
+ label: { type: 'positional', required: true, description: 'Human-readable label' },
69
+ qr: {
70
+ type: 'boolean',
71
+ description: 'Also render a pairing QR code for mobile clients (bazilion://pair?...)',
72
+ },
73
+ server: {
74
+ type: 'string',
75
+ description:
76
+ 'Server URL to embed in the pairing QR (default: detect LAN IP; ignored without --qr)',
77
+ },
78
+ },
79
+ async run({ args }) {
80
+ const client = createClient()
81
+ const body: CreateTokenRequest = { label: args.label }
82
+ const res = await client.post<CreateTokenResponse>('/api/tokens', body)
83
+ console.log(`id: ${res.meta.id}`)
84
+ console.log(`label: ${res.meta.label}`)
85
+ console.log(`token: ${res.token}`)
86
+ console.log('')
87
+ console.log('store the token now — it is not recoverable later.')
88
+
89
+ if (!args.qr) return
90
+
91
+ const serverUrl = resolveQrServer(args.server)
92
+ const pairUrl = `bazilion://pair?server=${encodeURIComponent(serverUrl)}&token=${encodeURIComponent(res.token)}`
93
+ console.log('')
94
+ console.log(`pairing URL: ${pairUrl}`)
95
+ console.log('')
96
+ qrcode.generate(pairUrl, { small: true }, (qr) => console.log(qr))
97
+ },
98
+ })
99
+
100
+ const listCmd = defineCommand({
101
+ meta: { name: 'list', description: 'List web tokens' },
102
+ args: {
103
+ all: { type: 'boolean', description: 'Include revoked tokens' },
104
+ },
105
+ async run({ args }) {
106
+ const client = createClient()
107
+ const qs = args.all ? '?includeRevoked=1' : ''
108
+ const { tokens } = await client.get<ListTokensResponse>(`/api/tokens${qs}`)
109
+ if (tokens.length === 0) {
110
+ console.log('(no tokens)')
111
+ return
112
+ }
113
+ for (const line of columnize(tokens.map(tokenRow))) console.log(line)
114
+ },
115
+ })
116
+
117
+ const showLocalCmd = defineCommand({
118
+ meta: {
119
+ name: 'show-local',
120
+ description: 'Print the bootstrap web token stored in ~/.bazilion/auth.json',
121
+ },
122
+ run() {
123
+ const paths = resolveCliPaths()
124
+ console.log(readAuthFile(paths.authFile).token)
125
+ },
126
+ })
127
+
128
+ const revokeCmd = defineCommand({
129
+ meta: { name: 'revoke', description: 'Revoke a web token' },
130
+ args: {
131
+ id: { type: 'positional', required: true },
132
+ },
133
+ async run({ args }) {
134
+ const client = createClient()
135
+ await client.del(`/api/tokens/${args.id}`)
136
+ console.log(`revoked token ${args.id}`)
137
+ },
138
+ })
139
+
140
+ export const tokenCommand = defineCommand({
141
+ meta: { name: 'token', description: 'Manage web tokens for API/CLI clients' },
142
+ subCommands: {
143
+ create: createCmd,
144
+ list: listCmd,
145
+ revoke: revokeCmd,
146
+ 'show-local': showLocalCmd,
147
+ },
148
+ })
@@ -0,0 +1,129 @@
1
+ import type {
2
+ AgentTrigger,
3
+ CreateTriggerRequest,
4
+ CreateTriggerResponse,
5
+ ListTriggersResponse,
6
+ UpdateTriggerRequest,
7
+ } from '@bazilion/api-types'
8
+ import { defineCommand } from 'citty'
9
+ import { createClient } from '../client.ts'
10
+ import { columnize } from '../columnize.ts'
11
+
12
+ const addCmd = defineCommand({
13
+ meta: { name: 'add', description: 'Add a heartbeat / cron trigger to an agent' },
14
+ args: {
15
+ agent: { type: 'positional', required: true },
16
+ every: {
17
+ type: 'string',
18
+ description: 'Interval in seconds (e.g. --every 300 = every 5 minutes)',
19
+ },
20
+ cron: {
21
+ type: 'string',
22
+ description: 'Cron expression (5 fields: minute hour dom month dow)',
23
+ },
24
+ message: {
25
+ type: 'string',
26
+ required: true,
27
+ description: 'Message to inject when the trigger fires',
28
+ },
29
+ disabled: { type: 'boolean', description: 'Create in disabled state' },
30
+ },
31
+ async run({ args }) {
32
+ if (!args.every === !args.cron) {
33
+ throw new Error('specify exactly one of --every <seconds> or --cron "<expr>"')
34
+ }
35
+ const client = createClient()
36
+ const body: CreateTriggerRequest = args.every
37
+ ? {
38
+ kind: 'interval',
39
+ intervalSec: Number(args.every),
40
+ message: args.message,
41
+ enabled: !args.disabled,
42
+ }
43
+ : {
44
+ kind: 'cron',
45
+ cronExpr: args.cron as string,
46
+ message: args.message,
47
+ enabled: !args.disabled,
48
+ }
49
+ const res = await client.post<CreateTriggerResponse>(`/api/agents/${args.agent}/triggers`, body)
50
+ const t = res.trigger
51
+ const spec = t.kind === 'interval' ? `every ${t.intervalSec}s` : `cron "${t.cronExpr}"`
52
+ console.log(`${t.id}\t${spec}\t${t.enabled ? 'enabled' : 'disabled'}`)
53
+ },
54
+ })
55
+
56
+ function triggerRow(t: AgentTrigger): string[] {
57
+ const spec = t.kind === 'interval' ? `every ${t.intervalSec}s` : `cron "${t.cronExpr}"`
58
+ const last = t.lastFiredAt ? new Date(t.lastFiredAt).toISOString() : '(never)'
59
+ const state = t.enabled ? 'enabled' : 'disabled'
60
+ const msgPreview = t.message.length > 60 ? `${t.message.slice(0, 60)}…` : t.message
61
+ return [t.id, state, spec, `last: ${last}`, `"${msgPreview}"`]
62
+ }
63
+
64
+ const listCmd = defineCommand({
65
+ meta: { name: 'list', description: 'List triggers for an agent' },
66
+ args: {
67
+ agent: { type: 'positional', required: true },
68
+ },
69
+ async run({ args }) {
70
+ const client = createClient()
71
+ const { triggers } = await client.get<ListTriggersResponse>(
72
+ `/api/agents/${args.agent}/triggers`,
73
+ )
74
+ if (triggers.length === 0) {
75
+ console.log('(no triggers)')
76
+ return
77
+ }
78
+ for (const line of columnize(triggers.map(triggerRow))) console.log(line)
79
+ },
80
+ })
81
+
82
+ const rmCmd = defineCommand({
83
+ meta: { name: 'rm', description: 'Delete a trigger' },
84
+ args: {
85
+ id: { type: 'positional', required: true },
86
+ },
87
+ async run({ args }) {
88
+ const client = createClient()
89
+ await client.del(`/api/triggers/${args.id}`)
90
+ console.log(`removed trigger ${args.id}`)
91
+ },
92
+ })
93
+
94
+ const enableCmd = defineCommand({
95
+ meta: { name: 'enable', description: 'Enable a trigger' },
96
+ args: {
97
+ id: { type: 'positional', required: true },
98
+ },
99
+ async run({ args }) {
100
+ const client = createClient()
101
+ const body: UpdateTriggerRequest = { enabled: true }
102
+ await client.patch(`/api/triggers/${args.id}`, body)
103
+ console.log(`enabled trigger ${args.id}`)
104
+ },
105
+ })
106
+
107
+ const disableCmd = defineCommand({
108
+ meta: { name: 'disable', description: 'Disable a trigger' },
109
+ args: {
110
+ id: { type: 'positional', required: true },
111
+ },
112
+ async run({ args }) {
113
+ const client = createClient()
114
+ const body: UpdateTriggerRequest = { enabled: false }
115
+ await client.patch(`/api/triggers/${args.id}`, body)
116
+ console.log(`disabled trigger ${args.id}`)
117
+ },
118
+ })
119
+
120
+ export const triggerCommand = defineCommand({
121
+ meta: { name: 'trigger', description: 'Manage agent heartbeats / cron triggers' },
122
+ subCommands: {
123
+ add: addCmd,
124
+ list: listCmd,
125
+ rm: rmCmd,
126
+ enable: enableCmd,
127
+ disable: disableCmd,
128
+ },
129
+ })
@@ -0,0 +1,156 @@
1
+ import { existsSync, readdirSync, rmSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { defineCommand } from 'citty'
4
+ import { resolveCliPaths } from '../paths.ts'
5
+
6
+ // Line-buffered stdin reader. `node:readline/promises` drops data after EOF
7
+ // on piped input (a known Node quirk with back-to-back `question()` calls),
8
+ // so we read chunks ourselves and emit one line per call.
9
+ function makeLineReader(): () => Promise<string> {
10
+ let buffer = ''
11
+ let ended = false
12
+ const queue: ((line: string) => void)[] = []
13
+
14
+ const tryDeliver = (): void => {
15
+ while (queue.length > 0) {
16
+ const nlIdx = buffer.indexOf('\n')
17
+ if (nlIdx === -1) {
18
+ if (ended) {
19
+ const resolve = queue.shift()
20
+ resolve?.(buffer)
21
+ buffer = ''
22
+ } else {
23
+ return
24
+ }
25
+ } else {
26
+ const line = buffer.slice(0, nlIdx)
27
+ buffer = buffer.slice(nlIdx + 1)
28
+ const resolve = queue.shift()
29
+ resolve?.(line)
30
+ }
31
+ }
32
+ }
33
+
34
+ process.stdin.setEncoding('utf8')
35
+ process.stdin.on('data', (chunk) => {
36
+ buffer += chunk
37
+ tryDeliver()
38
+ })
39
+ process.stdin.on('end', () => {
40
+ ended = true
41
+ tryDeliver()
42
+ })
43
+
44
+ return () =>
45
+ new Promise<string>((resolve) => {
46
+ queue.push(resolve)
47
+ tryDeliver()
48
+ })
49
+ }
50
+
51
+ async function askYes(readLine: () => Promise<string>, prompt: string): Promise<boolean> {
52
+ process.stdout.write(`${prompt} [y/N] `)
53
+ const answer = (await readLine()).trim().toLowerCase()
54
+ return answer === 'y' || answer === 'yes'
55
+ }
56
+
57
+ function removePath(p: string): boolean {
58
+ if (!existsSync(p)) return false
59
+ rmSync(p, { recursive: true, force: true })
60
+ return true
61
+ }
62
+
63
+ export const uninstallCommand = defineCommand({
64
+ meta: {
65
+ name: 'uninstall',
66
+ description: 'Wipe bazilion state from ~/.bazilion (or BAZILION_HOME)',
67
+ },
68
+ args: {
69
+ home: { type: 'string', description: 'Override BAZILION_HOME' },
70
+ yes: {
71
+ type: 'boolean',
72
+ description: 'Skip confirmations (data tier only unless --all is set)',
73
+ },
74
+ all: {
75
+ type: 'boolean',
76
+ description: 'Also remove configs, logs, and skills (full wipe)',
77
+ },
78
+ },
79
+ async run({ args }) {
80
+ const paths = resolveCliPaths(args.home)
81
+ const dbFile = join(paths.home, 'bazilion.db')
82
+ const profilesDir = join(paths.home, 'profiles')
83
+ const agentsDir = join(paths.home, 'agents')
84
+ const groupsDir = join(paths.home, 'groups')
85
+ const skillsDir = join(paths.home, 'skills')
86
+ const logsDir = join(paths.home, 'logs')
87
+
88
+ if (!existsSync(paths.home)) {
89
+ console.log(`nothing to remove: ${paths.home} does not exist`)
90
+ return
91
+ }
92
+
93
+ console.log(`about to uninstall bazilion at ${paths.home}`)
94
+ console.log('')
95
+
96
+ const dataTargets = [
97
+ dbFile,
98
+ `${dbFile}-wal`,
99
+ `${dbFile}-shm`,
100
+ profilesDir,
101
+ agentsDir,
102
+ groupsDir,
103
+ ]
104
+
105
+ const needsPrompt = !args.yes
106
+ const readLine = needsPrompt ? makeLineReader() : null
107
+ const wipeData =
108
+ args.yes ||
109
+ (await askYes(
110
+ readLine as () => Promise<string>,
111
+ 'remove DB + agent / profile / workspace data?',
112
+ ))
113
+ if (!wipeData) {
114
+ console.log('aborted')
115
+ process.stdin.pause()
116
+ return
117
+ }
118
+
119
+ // Tier 2: ask only after data wipe is confirmed — configs/logs/skills are
120
+ // reusable across reinstalls, so the two-tier split lets an operator
121
+ // factory-reset agents while keeping credentials + imported skills.
122
+ const wipeAll =
123
+ args.all ||
124
+ (args.yes
125
+ ? false
126
+ : await askYes(
127
+ readLine as () => Promise<string>,
128
+ 'also remove configs, logs, and skills? (full wipe of ~/.bazilion)',
129
+ ))
130
+
131
+ process.stdin.pause()
132
+
133
+ for (const p of dataTargets) {
134
+ if (removePath(p)) console.log(`removed ${p}`)
135
+ }
136
+
137
+ if (wipeAll) {
138
+ const configTargets = [paths.authFile, logsDir, skillsDir]
139
+ for (const p of configTargets) {
140
+ if (removePath(p)) console.log(`removed ${p}`)
141
+ }
142
+ // If nothing else is left in $BAZILION_HOME, drop the empty dir too so
143
+ // a subsequent `bazilion serve` re-bootstraps into a truly fresh home.
144
+ if (readdirSync(paths.home).length === 0) {
145
+ rmSync(paths.home, { recursive: true, force: true })
146
+ console.log(`removed ${paths.home}`)
147
+ } else {
148
+ console.log(`${paths.home} still has unmanaged files; left in place`)
149
+ }
150
+ } else {
151
+ console.log('')
152
+ console.log('kept: auth.json, logs/, skills/')
153
+ console.log(`re-run 'bazilion serve' to recreate the DB when you're ready.`)
154
+ }
155
+ },
156
+ })