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,135 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { createWriteStream, existsSync, mkdirSync, readdirSync, rmSync } from 'node:fs'
3
+ import { resolve } from 'node:path'
4
+ import { Readable } from 'node:stream'
5
+ import { pipeline } from 'node:stream/promises'
6
+ import { defineCommand } from 'citty'
7
+ import { loadClientConfig } from '../client.ts'
8
+ import { resolveCliPaths } from '../paths.ts'
9
+
10
+ const createCmd = defineCommand({
11
+ meta: { name: 'create', description: 'Download a tar.gz backup of ~/.bazilion from the server' },
12
+ args: {
13
+ output: {
14
+ type: 'positional',
15
+ required: false,
16
+ description: 'Output file path (default: ./bazilion-backup-YYYY-MM-DD.tar.gz)',
17
+ },
18
+ },
19
+ async run({ args }) {
20
+ const cfg = loadClientConfig()
21
+ const date = new Date().toISOString().slice(0, 10)
22
+ const outAbs = resolve(args.output ?? `bazilion-backup-${date}.tar.gz`)
23
+
24
+ console.log(`downloading backup → ${outAbs}`)
25
+ const res = await fetch(`${cfg.serverUrl}/api/backup`, {
26
+ headers: { authorization: `Bearer ${cfg.token}`, origin: cfg.serverUrl },
27
+ })
28
+ if (!res.ok || !res.body) {
29
+ let err: string = res.statusText
30
+ try {
31
+ const body = (await res.json()) as { error?: string }
32
+ err = body.error ?? err
33
+ } catch {}
34
+ throw new Error(`backup failed: ${err}`)
35
+ }
36
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(outAbs))
37
+ console.log('done')
38
+ },
39
+ })
40
+
41
+ /**
42
+ * Offline restore. Server must be stopped first — SQLite's DB files would get
43
+ * corrupted if the running daemon has them open while tar overwrites them.
44
+ * We check the default loopback port as a best-effort safety net.
45
+ */
46
+ async function probeServerRunning(port: number): Promise<boolean> {
47
+ try {
48
+ const ctrl = new AbortController()
49
+ const timer = setTimeout(() => ctrl.abort(), 500)
50
+ // /login is unauthenticated (see middleware.ts PUBLIC_PATHS) — any response
51
+ // means something is listening on the port.
52
+ const res = await fetch(`http://127.0.0.1:${port}/login`, { signal: ctrl.signal })
53
+ clearTimeout(timer)
54
+ return res.status >= 200 && res.status < 600
55
+ } catch {
56
+ return false
57
+ }
58
+ }
59
+
60
+ const restoreCmd = defineCommand({
61
+ meta: {
62
+ name: 'restore',
63
+ description: 'Extract a backup tar.gz into ~/.bazilion (offline; server must be stopped)',
64
+ },
65
+ args: {
66
+ file: {
67
+ type: 'positional',
68
+ required: true,
69
+ description: 'Path to tar.gz backup file',
70
+ },
71
+ home: { type: 'string', description: 'Override BAZILION_HOME' },
72
+ force: {
73
+ type: 'boolean',
74
+ description: 'Overwrite existing non-empty home (destroys existing data)',
75
+ },
76
+ },
77
+ async run({ args }) {
78
+ const file = resolve(args.file)
79
+ if (!existsSync(file)) throw new Error(`backup file not found: ${file}`)
80
+
81
+ const paths = resolveCliPaths(args.home)
82
+ const targetHome = paths.home
83
+
84
+ // Only probe for a running server when restoring to the default home —
85
+ // an explicit `--home` points somewhere the running daemon isn't using,
86
+ // so there's no DB-corruption risk to warn about. `--force` bypasses
87
+ // the probe either way.
88
+ const defaultHome = !args.home && !process.env.BAZILION_HOME
89
+ if (defaultHome && !args.force && (await probeServerRunning(4321))) {
90
+ throw new Error(
91
+ 'bazilion server appears to be running on :4321 — stop it first (DB ' +
92
+ 'corruption risk), or pass --force to override.',
93
+ )
94
+ }
95
+
96
+ if (existsSync(targetHome)) {
97
+ const entries = readdirSync(targetHome)
98
+ if (entries.length > 0) {
99
+ if (!args.force) {
100
+ throw new Error(
101
+ `${targetHome} is not empty. Pass --force to overwrite (destroys existing data).`,
102
+ )
103
+ }
104
+ rmSync(targetHome, { recursive: true, force: true })
105
+ }
106
+ }
107
+ mkdirSync(targetHome, { recursive: true })
108
+
109
+ console.log(`extracting ${file} → ${targetHome}`)
110
+ await new Promise<void>((res, rej) => {
111
+ const proc = spawn('tar', ['-xzf', file, '-C', targetHome], { stdio: 'inherit' })
112
+ proc.on('error', rej)
113
+ proc.on('exit', (code) => {
114
+ if (code === 0) res()
115
+ else rej(new Error(`tar exited with code ${code}`))
116
+ })
117
+ })
118
+
119
+ console.log(`restored to ${targetHome}`)
120
+ console.log('start the server with: bazilion serve (migrations apply automatically)')
121
+ },
122
+ })
123
+
124
+ export const backupCommand = defineCommand({
125
+ meta: { name: 'backup', description: 'Create or restore a ~/.bazilion tar.gz backup' },
126
+ subCommands: {
127
+ create: createCmd,
128
+ restore: restoreCmd,
129
+ },
130
+ // `bazilion backup` with no positional falls through to citty's subcommand
131
+ // prompt ("No command specified"). The previous default (auto-download to
132
+ // cwd) double-fired when a subcommand was used because citty runs parent's
133
+ // `run` after the subcommand completes — explicit `create` / `restore` is
134
+ // the supported form now.
135
+ })
@@ -0,0 +1,155 @@
1
+ import { defineCommand } from 'citty'
2
+
3
+ interface CommandNode {
4
+ subCommands?: Record<string, CommandNode | Promise<CommandNode>>
5
+ args?: Record<string, { type?: string; alias?: string | string[] }>
6
+ }
7
+
8
+ interface Flatten {
9
+ /** full path e.g. ["agent", "chat"]; empty for the top-level root */
10
+ path: string[]
11
+ /** immediate subcommand names available at this path */
12
+ subs: string[]
13
+ /** flag tokens e.g. ["--profile", "--name", "--long", "-l"] */
14
+ flags: string[]
15
+ }
16
+
17
+ async function walk(cmd: CommandNode, path: string[], out: Flatten[]): Promise<void> {
18
+ const subsRaw = cmd.subCommands ?? {}
19
+ const subNames = Object.keys(subsRaw)
20
+ const flags = flagsOf(cmd.args ?? {})
21
+ out.push({ path, subs: subNames, flags })
22
+ for (const name of subNames) {
23
+ const v = subsRaw[name]
24
+ const sub = (await Promise.resolve(v)) as CommandNode
25
+ await walk(sub, [...path, name], out)
26
+ }
27
+ }
28
+
29
+ function flagsOf(args: Record<string, { type?: string; alias?: string | string[] }>): string[] {
30
+ const out: string[] = []
31
+ for (const [name, def] of Object.entries(args)) {
32
+ // Positionals don't get completed as flags.
33
+ if (def.type === 'positional') continue
34
+ out.push(`--${name}`)
35
+ const aliases = Array.isArray(def.alias) ? def.alias : def.alias ? [def.alias] : []
36
+ for (const a of aliases) out.push(a.length === 1 ? `-${a}` : `--${a}`)
37
+ }
38
+ return out
39
+ }
40
+
41
+ function bashScript(flat: Flatten[]): string {
42
+ const cases = flat
43
+ .map((f) => {
44
+ const key = f.path.length === 0 ? '""' : JSON.stringify(f.path.join(' '))
45
+ const words = [...f.subs, ...f.flags].join(' ')
46
+ return ` ${key})\n COMPREPLY=($(compgen -W ${JSON.stringify(words)} -- "$cur"))\n ;;`
47
+ })
48
+ .join('\n')
49
+ return `# bazilion bash completion. Install with:
50
+ # source <(bazilion completion bash)
51
+ # or append to ~/.bashrc / ~/.bash_profile.
52
+ _bazilion_completion() {
53
+ local cur prev cword
54
+ COMPREPLY=()
55
+ cur="\${COMP_WORDS[COMP_CWORD]}"
56
+ cword=$COMP_CWORD
57
+ # Build the current subcommand path by walking words[1..cword-1], skipping
58
+ # flags and their values. We don't try to parse flag arity — treating every
59
+ # flag as standalone is a good-enough approximation for completion.
60
+ local cmd=""
61
+ local i
62
+ for ((i=1; i<cword; i++)); do
63
+ local w="\${COMP_WORDS[i]}"
64
+ [[ "$w" == -* ]] && continue
65
+ cmd+="\${cmd:+ }$w"
66
+ done
67
+ case "$cmd" in
68
+ ${cases}
69
+ *)
70
+ COMPREPLY=()
71
+ ;;
72
+ esac
73
+ }
74
+ complete -F _bazilion_completion bazilion
75
+ `
76
+ }
77
+
78
+ function zshScript(flat: Flatten[]): string {
79
+ // zsh bundles a bashcompinit shim; reusing the bash script sidesteps the
80
+ // cost of a native _arguments/_describe implementation while still giving
81
+ // users a working completion. Ships with a tiny preamble so zsh loads the
82
+ // bash compat helpers first.
83
+ const bash = bashScript(flat)
84
+ return `# bazilion zsh completion. Install with:
85
+ # source <(bazilion completion zsh)
86
+ # If you don't already have bashcompinit loaded, this does it for you.
87
+ autoload -U +X compinit && compinit
88
+ autoload -U +X bashcompinit && bashcompinit
89
+ ${bash}`
90
+ }
91
+
92
+ function fishScript(flat: Flatten[]): string {
93
+ const lines: string[] = [
94
+ '# bazilion fish completion. Install with:',
95
+ '# bazilion completion fish | source',
96
+ '# or write it to ~/.config/fish/completions/bazilion.fish',
97
+ '',
98
+ 'complete -c bazilion -f',
99
+ ]
100
+ for (const f of flat) {
101
+ if (f.path.length === 0) {
102
+ for (const s of f.subs) lines.push(`complete -c bazilion -n '__fish_use_subcommand' -a ${s}`)
103
+ for (const fl of f.flags) {
104
+ const long = fl.startsWith('--') ? fl.slice(2) : ''
105
+ const short = !fl.startsWith('--') ? fl.slice(1) : ''
106
+ const parts: string[] = ["complete -c bazilion -n '__fish_use_subcommand'"]
107
+ if (short) parts.push(`-s ${short}`)
108
+ if (long) parts.push(`-l ${long}`)
109
+ lines.push(parts.join(' '))
110
+ }
111
+ } else {
112
+ // Guard this case on the joined path being the last N tokens in
113
+ // COMP_CWORDS — fish's __fish_seen_subcommand_from handles each token
114
+ // independently, so we require every path segment.
115
+ const guard = f.path.map((p) => `__fish_seen_subcommand_from ${p}`).join('; and ')
116
+ for (const s of f.subs) {
117
+ lines.push(`complete -c bazilion -n '${guard}' -a ${s}`)
118
+ }
119
+ for (const fl of f.flags) {
120
+ const long = fl.startsWith('--') ? fl.slice(2) : ''
121
+ const short = !fl.startsWith('--') ? fl.slice(1) : ''
122
+ const parts: string[] = [`complete -c bazilion -n '${guard}'`]
123
+ if (short) parts.push(`-s ${short}`)
124
+ if (long) parts.push(`-l ${long}`)
125
+ lines.push(parts.join(' '))
126
+ }
127
+ }
128
+ }
129
+ return `${lines.join('\n')}\n`
130
+ }
131
+
132
+ export function completionCommand(main: CommandNode) {
133
+ return defineCommand({
134
+ meta: {
135
+ name: 'completion',
136
+ description: 'Print a shell completion script (bash, zsh, or fish)',
137
+ },
138
+ args: {
139
+ shell: {
140
+ type: 'positional',
141
+ required: true,
142
+ description: 'bash | zsh | fish',
143
+ },
144
+ },
145
+ async run({ args }) {
146
+ const flat: Flatten[] = []
147
+ await walk(main, [], flat)
148
+ const shell = String(args.shell).toLowerCase()
149
+ if (shell === 'bash') console.log(bashScript(flat))
150
+ else if (shell === 'zsh') console.log(zshScript(flat))
151
+ else if (shell === 'fish') console.log(fishScript(flat))
152
+ else throw new Error(`unsupported shell: ${args.shell} (want bash | zsh | fish)`)
153
+ },
154
+ })
155
+ }
@@ -0,0 +1,95 @@
1
+ import type {
2
+ ProviderConfigResponse,
3
+ ServiceCard,
4
+ ServiceConfigResponse,
5
+ ServiceFieldState,
6
+ SetFieldRequest,
7
+ } from '@bazilion/api-types'
8
+ import { defineCommand } from 'citty'
9
+ import { createClient } from '../client.ts'
10
+ import { columnize } from '../columnize.ts'
11
+
12
+ function displayValue(f: ServiceFieldState): string {
13
+ if (!f.set) return '(unset)'
14
+ if (f.kind === 'secret') return f.preview ?? '(set)'
15
+ return f.value ?? '(set)'
16
+ }
17
+
18
+ const listCmd = defineCommand({
19
+ meta: {
20
+ name: 'list',
21
+ description: 'List every configurable field (credentials + URLs) across all services',
22
+ },
23
+ async run() {
24
+ const client = createClient()
25
+ // Both endpoints return ServiceCard-shaped entries — merge and print.
26
+ const { providers } = await client.get<ProviderConfigResponse>('/api/config/providers')
27
+ const { services } = await client.get<ServiceConfigResponse>('/api/config/services')
28
+ const all: ServiceCard[] = [...providers, ...services]
29
+
30
+ const rows: string[][] = []
31
+ for (const svc of all) {
32
+ for (const f of svc.fields) {
33
+ rows.push([svc.id, f.envVar, f.kind, displayValue(f)])
34
+ }
35
+ }
36
+ if (rows.length === 0) {
37
+ console.log('(no fields registered)')
38
+ return
39
+ }
40
+ for (const line of columnize(rows)) console.log(line)
41
+ },
42
+ })
43
+
44
+ const setCmd = defineCommand({
45
+ meta: {
46
+ name: 'set',
47
+ description: 'Set a configurable field (auto-routes to secrets or plaintext store)',
48
+ },
49
+ args: {
50
+ envVar: {
51
+ type: 'positional',
52
+ required: true,
53
+ description: 'Env var name (e.g. ANTHROPIC_API_KEY, LMSTUDIO_URL)',
54
+ },
55
+ value: {
56
+ type: 'positional',
57
+ required: true,
58
+ description: 'New value (empty string clears the field)',
59
+ },
60
+ },
61
+ async run({ args }) {
62
+ const client = createClient()
63
+ const body: SetFieldRequest = { value: args.value }
64
+ const state = await client.put<ServiceFieldState>(
65
+ `/api/config/fields/${encodeURIComponent(args.envVar)}`,
66
+ body,
67
+ )
68
+ console.log(`${state.envVar} (${state.kind}): ${displayValue(state)}`)
69
+ },
70
+ })
71
+
72
+ const rmCmd = defineCommand({
73
+ meta: { name: 'rm', description: 'Remove a configurable field value' },
74
+ args: {
75
+ envVar: { type: 'positional', required: true, description: 'Env var name to clear' },
76
+ },
77
+ async run({ args }) {
78
+ const client = createClient()
79
+ await client.del(`/api/config/fields/${encodeURIComponent(args.envVar)}`)
80
+ console.log(`removed ${args.envVar}`)
81
+ },
82
+ })
83
+
84
+ export const configCommand = defineCommand({
85
+ meta: {
86
+ name: 'config',
87
+ description:
88
+ 'Manage service configuration — credentials (encrypted) and URLs/IDs (plaintext), unified under one CLI',
89
+ },
90
+ subCommands: {
91
+ list: listCmd,
92
+ set: setCmd,
93
+ rm: rmCmd,
94
+ },
95
+ })
@@ -0,0 +1,131 @@
1
+ import type { HealthReport } from '@bazilion/api-types'
2
+ import { defineCommand } from 'citty'
3
+ import { createClient } from '../client.ts'
4
+
5
+ export const doctorCommand = defineCommand({
6
+ meta: { name: 'doctor', description: 'Diagnose your bazilion install' },
7
+ async run() {
8
+ const client = createClient()
9
+ const r = await client.get<HealthReport>('/api/health')
10
+
11
+ function check(label: string, condition: boolean, hint?: string): void {
12
+ const mark = condition ? '✓' : '✗'
13
+ const tail = !condition && hint ? ` — ${hint}` : ''
14
+ console.log(` ${mark} ${label}${tail}`)
15
+ }
16
+
17
+ console.log(`bazilion home: ${r.home}`)
18
+ console.log()
19
+ console.log('paths')
20
+ check('home dir exists', r.paths.home)
21
+ check('database exists', r.paths.db, 'run: bazilion serve (auto-bootstraps on first run)')
22
+ check('auth.json exists', r.paths.auth, 'run: bazilion serve (auto-bootstraps on first run)')
23
+ check('profiles dir exists', r.paths.profiles)
24
+ check('agents dir exists', r.paths.agents)
25
+ check('skills dir exists', r.paths.skills)
26
+
27
+ if (r.database) {
28
+ console.log()
29
+ console.log('database')
30
+ if (r.database.ok) {
31
+ console.log(` ${r.database.profiles} profile(s)`)
32
+ console.log(
33
+ ` ${r.database.activeAgents} active agent(s) (${r.database.totalAgents} total)`,
34
+ )
35
+ console.log(` ${r.database.groups} group(s)`)
36
+ } else {
37
+ check('open database', false, r.database.error)
38
+ }
39
+ }
40
+
41
+ console.log()
42
+ console.log('skills library')
43
+ console.log(` ${r.skills.installed} skill(s) installed`)
44
+ if (r.skills.parseErrors > 0) {
45
+ check('all skills parse', false, `${r.skills.parseErrors} skill(s) have errors`)
46
+ }
47
+
48
+ console.log()
49
+ console.log('providers (at least one needed for chat)')
50
+ if (r.providers.configured.length === 0) {
51
+ console.log(' - no cloud providers configured')
52
+ console.log(' (set e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY)')
53
+ } else {
54
+ for (const name of r.providers.configured) {
55
+ console.log(` ✓ ${name}`)
56
+ }
57
+ }
58
+ console.log(
59
+ ` ✓ lmstudio: ${r.providers.lmstudio.baseURL}${
60
+ r.providers.lmstudio.hasKey ? ' (api key set)' : ''
61
+ }`,
62
+ )
63
+ console.log(` ✓ ollama: ${r.providers.ollama.baseURL}`)
64
+
65
+ console.log()
66
+ console.log('web search (at least one needed for web_search tool)')
67
+ if (r.webSearch.bravePreview) {
68
+ console.log(` ✓ Brave Search (BRAVE_API_KEY set, ${r.webSearch.bravePreview})`)
69
+ } else {
70
+ console.log(' - Brave Search (set BRAVE_API_KEY — free at https://brave.com/search/api/)')
71
+ }
72
+ if (r.webSearch.searxngUrl) {
73
+ console.log(` ✓ SearXNG: ${r.webSearch.searxngUrl}`)
74
+ } else {
75
+ console.log(' - SearXNG (set SEARXNG_URL if you self-host one)')
76
+ }
77
+ if (!r.webSearch.bravePreview && !r.webSearch.searxngUrl) {
78
+ console.log(' ⚠ no search backend — web_search will error until one is configured')
79
+ }
80
+
81
+ console.log()
82
+ console.log('openclaw integration')
83
+ if (r.openclaw.exists) {
84
+ console.log(` ✓ ${r.openclaw.path} found`)
85
+ console.log(' try: bazilion skill import --from openclaw')
86
+ } else {
87
+ console.log(` - ${r.openclaw.path} not found (fine if you don't use OpenClaw)`)
88
+ }
89
+
90
+ console.log()
91
+ console.log('background jobs')
92
+ console.log(
93
+ ` ${r.scheduler.enabled ? '✓' : '-'} scheduler${
94
+ r.scheduler.enabled ? ` (tick ${r.scheduler.tickMs}ms)` : ' (BAZILION_SCHEDULER=off)'
95
+ }`,
96
+ )
97
+
98
+ console.log()
99
+ console.log('operational counts')
100
+ console.log(` ${r.triggers.active} active trigger(s), ${r.triggers.disabled} disabled`)
101
+ console.log(` ${r.tokens.active} active web token(s)`)
102
+
103
+ const anyCloudProvider = r.providers.configured.length > 0
104
+ const hasProfiles = (r.database?.ok ? r.database.profiles : 0) > 0
105
+ const hasAgents = (r.database?.ok ? r.database.totalAgents : 0) > 0
106
+
107
+ console.log()
108
+ if (!r.ok) {
109
+ console.log('issues found ✗')
110
+ process.exit(1)
111
+ }
112
+ // Install is structurally healthy. Differentiate "ready to use" from "just
113
+ // initialized" so a fresh install doesn't masquerade as fully configured.
114
+ // (Local providers — lmstudio/ollama — are reported with default URLs
115
+ // whether or not they're actually running, so we can only advise; cloud
116
+ // providers have api keys we can check.)
117
+ if (!hasProfiles || !hasAgents) {
118
+ console.log('install is healthy, but not yet ready to run ⚠')
119
+ const todo: string[] = []
120
+ if (!anyCloudProvider) {
121
+ todo.push(' - set a cloud api key (ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY)')
122
+ todo.push(' or make sure LMStudio / Ollama is running locally')
123
+ }
124
+ if (!hasProfiles) todo.push(' - create a profile: bazilion profile create <id> --model …')
125
+ if (!hasAgents) todo.push(' - spawn an agent: bazilion agent spawn --profile <id>')
126
+ for (const line of todo) console.log(line)
127
+ } else {
128
+ console.log('all good ✓')
129
+ }
130
+ },
131
+ })
@@ -0,0 +1,132 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { stdin } from 'node:process'
3
+ import type { Group, RegisterGroupRequest, SetGroupUserMdRequest } from '@bazilion/api-types'
4
+ import { defineCommand } from 'citty'
5
+ import { createClient } from '../client.ts'
6
+ import { columnize } from '../columnize.ts'
7
+
8
+ const addCmd = defineCommand({
9
+ meta: {
10
+ name: 'add',
11
+ description:
12
+ 'Register a group at ~/.bazilion/groups/<slug>/ (use --link to point at an existing tree)',
13
+ },
14
+ args: {
15
+ id: { type: 'positional', required: true, description: 'Group slug' },
16
+ name: { type: 'string', description: 'Display name (defaults to slug)' },
17
+ link: {
18
+ type: 'string',
19
+ description: 'Absolute path of an existing directory; the group slot becomes a symlink to it',
20
+ },
21
+ },
22
+ async run({ args }) {
23
+ const client = createClient()
24
+ const body: RegisterGroupRequest = {
25
+ id: args.id,
26
+ ...(args.name ? { name: args.name } : {}),
27
+ ...(args.link ? { link: args.link } : {}),
28
+ }
29
+ const g = await client.post<Group>('/api/groups', body)
30
+ console.log(`registered group ${g.id} at ${g.path}`)
31
+ },
32
+ })
33
+
34
+ const listCmd = defineCommand({
35
+ meta: { name: 'list', description: 'List groups' },
36
+ async run() {
37
+ const client = createClient()
38
+ const list = await client.get<Group[]>('/api/groups')
39
+ if (list.length === 0) {
40
+ console.log('(no groups)')
41
+ return
42
+ }
43
+ const rows = list.map((g) => [g.id, g.path])
44
+ for (const line of columnize(rows)) console.log(line)
45
+ },
46
+ })
47
+
48
+ const rmCmd = defineCommand({
49
+ meta: { name: 'rm', description: 'Remove a group registration' },
50
+ args: {
51
+ id: { type: 'positional', required: true },
52
+ },
53
+ async run({ args }) {
54
+ const client = createClient()
55
+ await client.del(`/api/groups/${args.id}`)
56
+ console.log(`removed group ${args.id}`)
57
+ },
58
+ })
59
+
60
+ async function readStdin(): Promise<string> {
61
+ const chunks: Buffer[] = []
62
+ for await (const chunk of stdin) chunks.push(chunk as Buffer)
63
+ return Buffer.concat(chunks).toString('utf8')
64
+ }
65
+
66
+ const userMdShowCmd = defineCommand({
67
+ meta: { name: 'show', description: "Print the group's USER.md" },
68
+ args: { id: { type: 'positional', required: true } },
69
+ async run({ args }) {
70
+ const client = createClient()
71
+ const g = await client.get<Group>(`/api/groups/${args.id}`)
72
+ process.stdout.write(g.userMd)
73
+ if (g.userMd && !g.userMd.endsWith('\n')) process.stdout.write('\n')
74
+ },
75
+ })
76
+
77
+ const userMdSetCmd = defineCommand({
78
+ meta: {
79
+ name: 'set',
80
+ description: "Replace the group's USER.md (from --file, --from-stdin, or inline text)",
81
+ },
82
+ args: {
83
+ id: { type: 'positional', required: true },
84
+ file: { type: 'string', description: 'Read content from a file path' },
85
+ 'from-stdin': { type: 'boolean', description: 'Read content from stdin' },
86
+ text: { type: 'string', description: 'Inline content' },
87
+ },
88
+ async run({ args }) {
89
+ let content: string
90
+ if (args['from-stdin']) content = await readStdin()
91
+ else if (args.file) content = readFileSync(args.file, 'utf8')
92
+ else if (args.text !== undefined) content = args.text
93
+ else {
94
+ console.error('group user-md set: provide --file, --from-stdin, or --text')
95
+ process.exit(2)
96
+ }
97
+ const client = createClient()
98
+ const body: SetGroupUserMdRequest = { userMd: content }
99
+ await client.put(`/api/groups/${args.id}/user-md`, body)
100
+ console.log(`updated USER.md for group ${args.id} (${content.length} bytes)`)
101
+ },
102
+ })
103
+
104
+ const userMdClearCmd = defineCommand({
105
+ meta: { name: 'clear', description: "Clear the group's USER.md to empty" },
106
+ args: { id: { type: 'positional', required: true } },
107
+ async run({ args }) {
108
+ const client = createClient()
109
+ const body: SetGroupUserMdRequest = { userMd: '' }
110
+ await client.put(`/api/groups/${args.id}/user-md`, body)
111
+ console.log(`cleared USER.md for group ${args.id}`)
112
+ },
113
+ })
114
+
115
+ const userMdCmd = defineCommand({
116
+ meta: { name: 'user-md', description: "View or edit a group's USER.md" },
117
+ subCommands: {
118
+ show: userMdShowCmd,
119
+ set: userMdSetCmd,
120
+ clear: userMdClearCmd,
121
+ },
122
+ })
123
+
124
+ export const groupCommand = defineCommand({
125
+ meta: { name: 'group', description: 'Manage groups (collaboration contexts)' },
126
+ subCommands: {
127
+ add: addCmd,
128
+ list: listCmd,
129
+ rm: rmCmd,
130
+ 'user-md': userMdCmd,
131
+ },
132
+ })