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.
- package/.understand-anything/.understandignore +25 -0
- package/.understand-anything/fingerprints.json +14267 -0
- package/.understand-anything/knowledge-graph.json +18128 -0
- package/.understand-anything/meta.json +6 -0
- package/CLAUDE.md +164 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/apps/cli/package.json +21 -0
- package/apps/cli/src/auth-file.ts +18 -0
- package/apps/cli/src/client.ts +37 -0
- package/apps/cli/src/columnize.ts +32 -0
- package/apps/cli/src/commands/agent.ts +574 -0
- package/apps/cli/src/commands/auth.ts +110 -0
- package/apps/cli/src/commands/backup.ts +135 -0
- package/apps/cli/src/commands/completion.ts +155 -0
- package/apps/cli/src/commands/config.ts +95 -0
- package/apps/cli/src/commands/doctor.ts +131 -0
- package/apps/cli/src/commands/group.ts +132 -0
- package/apps/cli/src/commands/inbox.ts +82 -0
- package/apps/cli/src/commands/login.ts +73 -0
- package/apps/cli/src/commands/memory.ts +106 -0
- package/apps/cli/src/commands/profile.ts +259 -0
- package/apps/cli/src/commands/provider.ts +170 -0
- package/apps/cli/src/commands/send.ts +24 -0
- package/apps/cli/src/commands/serve.ts +89 -0
- package/apps/cli/src/commands/skill.ts +120 -0
- package/apps/cli/src/commands/token.ts +148 -0
- package/apps/cli/src/commands/trigger.ts +129 -0
- package/apps/cli/src/commands/uninstall.ts +156 -0
- package/apps/cli/src/index.ts +196 -0
- package/apps/cli/src/paths.ts +12 -0
- package/apps/cli/test/agent.test.ts +213 -0
- package/apps/cli/test/backup.test.ts +95 -0
- package/apps/cli/test/chat.test.ts +539 -0
- package/apps/cli/test/columnize.test.ts +29 -0
- package/apps/cli/test/completion.test.ts +45 -0
- package/apps/cli/test/config-page.test.ts +151 -0
- package/apps/cli/test/group.test.ts +74 -0
- package/apps/cli/test/helpers.ts +70 -0
- package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
- package/apps/cli/test/inbox.test.ts +147 -0
- package/apps/cli/test/memory.test.ts +69 -0
- package/apps/cli/test/profile.test.ts +110 -0
- package/apps/cli/test/send.test.ts +30 -0
- package/apps/cli/test/server-fixture.ts +212 -0
- package/apps/cli/test/session-head.test.ts +45 -0
- package/apps/cli/test/skill.test.ts +128 -0
- package/apps/cli/test/token.test.ts +109 -0
- package/apps/cli/test/trigger.test.ts +245 -0
- package/apps/cli/tsconfig.json +4 -0
- package/apps/daemon/package.json +31 -0
- package/apps/daemon/src/app.ts +41 -0
- package/apps/daemon/src/core/agent/archive.ts +8 -0
- package/apps/daemon/src/core/agent/delete.ts +30 -0
- package/apps/daemon/src/core/agent/resolve.ts +31 -0
- package/apps/daemon/src/core/agent/spawn.ts +95 -0
- package/apps/daemon/src/core/agent/unarchive.ts +11 -0
- package/apps/daemon/src/core/availableModels.ts +58 -0
- package/apps/daemon/src/core/db/client.ts +113 -0
- package/apps/daemon/src/core/db/migrate.ts +41 -0
- package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
- package/apps/daemon/src/core/group/delete.ts +21 -0
- package/apps/daemon/src/core/group/register.ts +68 -0
- package/apps/daemon/src/core/index.ts +71 -0
- package/apps/daemon/src/core/paths.ts +56 -0
- package/apps/daemon/src/core/profile/create.ts +78 -0
- package/apps/daemon/src/core/profile/delete.ts +26 -0
- package/apps/daemon/src/core/profile/identity.ts +70 -0
- package/apps/daemon/src/core/profile/load.ts +45 -0
- package/apps/daemon/src/core/profile/seed.ts +74 -0
- package/apps/daemon/src/core/profile/templates.ts +60 -0
- package/apps/daemon/src/core/profile/update.ts +57 -0
- package/apps/daemon/src/core/profile/validate.ts +9 -0
- package/apps/daemon/src/core/repos/agents.ts +202 -0
- package/apps/daemon/src/core/repos/config.ts +78 -0
- package/apps/daemon/src/core/repos/groups.ts +55 -0
- package/apps/daemon/src/core/repos/messages.ts +127 -0
- package/apps/daemon/src/core/repos/profiles.ts +83 -0
- package/apps/daemon/src/core/repos/providerModels.ts +58 -0
- package/apps/daemon/src/core/repos/providerState.ts +37 -0
- package/apps/daemon/src/core/repos/secrets.ts +145 -0
- package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
- package/apps/daemon/src/core/repos/triggers.ts +101 -0
- package/apps/daemon/src/core/repos/webTokens.ts +87 -0
- package/apps/daemon/src/core/secrets.ts +65 -0
- package/apps/daemon/src/core/services.ts +264 -0
- package/apps/daemon/src/core/skills/discover.ts +28 -0
- package/apps/daemon/src/core/skills/import.ts +136 -0
- package/apps/daemon/src/core/skills/parse.ts +52 -0
- package/apps/daemon/src/core/skills/resolve.ts +50 -0
- package/apps/daemon/src/index.ts +45 -0
- package/apps/daemon/src/lib/agent-cancel.ts +48 -0
- package/apps/daemon/src/lib/agent-id.ts +13 -0
- package/apps/daemon/src/lib/agent-turn.ts +56 -0
- package/apps/daemon/src/lib/api-key.ts +56 -0
- package/apps/daemon/src/lib/auth.ts +41 -0
- package/apps/daemon/src/lib/cron.ts +93 -0
- package/apps/daemon/src/lib/ctx.ts +80 -0
- package/apps/daemon/src/lib/messaging-host.ts +34 -0
- package/apps/daemon/src/lib/middleware-auth.ts +52 -0
- package/apps/daemon/src/lib/scheduler.ts +294 -0
- package/apps/daemon/src/routes/agents.ts +772 -0
- package/apps/daemon/src/routes/auth-login.ts +193 -0
- package/apps/daemon/src/routes/config.ts +267 -0
- package/apps/daemon/src/routes/groups.ts +133 -0
- package/apps/daemon/src/routes/messages.ts +29 -0
- package/apps/daemon/src/routes/misc.ts +239 -0
- package/apps/daemon/src/routes/profiles.ts +197 -0
- package/apps/daemon/src/routes/skills.ts +123 -0
- package/apps/daemon/src/routes/triggers.ts +29 -0
- package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
- package/apps/daemon/src/runtime/index.ts +77 -0
- package/apps/daemon/src/runtime/memory/files.ts +103 -0
- package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
- package/apps/daemon/src/runtime/memory/types.ts +16 -0
- package/apps/daemon/src/runtime/pi/events.ts +173 -0
- package/apps/daemon/src/runtime/pi/session.ts +536 -0
- package/apps/daemon/src/runtime/pi/tools.ts +85 -0
- package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
- package/apps/daemon/src/runtime/providers/registry.ts +374 -0
- package/apps/daemon/src/runtime/providers/retry.ts +176 -0
- package/apps/daemon/src/runtime/providers/types.ts +33 -0
- package/apps/daemon/src/runtime/session/prompt.ts +83 -0
- package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
- package/apps/daemon/src/runtime/tools/home.ts +114 -0
- package/apps/daemon/src/runtime/tools/memory.ts +81 -0
- package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
- package/apps/daemon/src/runtime/tools/registry.ts +29 -0
- package/apps/daemon/src/runtime/tools/types.ts +13 -0
- package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
- package/apps/daemon/src/runtime/tools/web.ts +273 -0
- package/apps/daemon/src/runtime/worker/entry.ts +221 -0
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
- package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
- package/apps/daemon/test/core/agents.test.ts +319 -0
- package/apps/daemon/test/core/available-models.test.ts +63 -0
- package/apps/daemon/test/core/config.test.ts +99 -0
- package/apps/daemon/test/core/groups.test.ts +63 -0
- package/apps/daemon/test/core/helpers.ts +50 -0
- package/apps/daemon/test/core/identity.test.ts +85 -0
- package/apps/daemon/test/core/migrations.test.ts +83 -0
- package/apps/daemon/test/core/profiles.test.ts +182 -0
- package/apps/daemon/test/core/provider-models.test.ts +57 -0
- package/apps/daemon/test/core/provider-state.test.ts +36 -0
- package/apps/daemon/test/core/skill-meta.test.ts +45 -0
- package/apps/daemon/test/core/skills.test.ts +271 -0
- package/apps/daemon/test/core/triggers.test.ts +182 -0
- package/apps/daemon/test/core/web-tokens.test.ts +79 -0
- package/apps/daemon/test/cron.test.ts +90 -0
- package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
- package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
- package/apps/daemon/test/runtime/memory.test.ts +78 -0
- package/apps/daemon/test/runtime/messaging.test.ts +213 -0
- package/apps/daemon/test/runtime/mock-server.ts +65 -0
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
- package/apps/daemon/test/runtime/providers.test.ts +306 -0
- package/apps/daemon/test/runtime/retry.test.ts +191 -0
- package/apps/daemon/test/runtime/session-head.test.ts +90 -0
- package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
- package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
- package/apps/daemon/tsconfig.json +4 -0
- package/apps/mobile/README.md +60 -0
- package/apps/mobile/app/_layout.tsx +58 -0
- package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
- package/apps/mobile/app/agents/[id]/index.tsx +166 -0
- package/apps/mobile/app/agents/index.tsx +212 -0
- package/apps/mobile/app/index.tsx +21 -0
- package/apps/mobile/app/pair.tsx +226 -0
- package/apps/mobile/app/settings.tsx +419 -0
- package/apps/mobile/app.json +49 -0
- package/apps/mobile/assets/adaptive-icon.png +0 -0
- package/apps/mobile/assets/favicon.png +0 -0
- package/apps/mobile/assets/icon.png +0 -0
- package/apps/mobile/assets/splash-icon.png +0 -0
- package/apps/mobile/babel.config.js +6 -0
- package/apps/mobile/metro.config.js +28 -0
- package/apps/mobile/package.json +44 -0
- package/apps/mobile/src/auth.ts +66 -0
- package/apps/mobile/src/pair-url.ts +48 -0
- package/apps/mobile/src/theme-context.tsx +88 -0
- package/apps/mobile/src/theme.ts +135 -0
- package/apps/mobile/test/pair-url.test.ts +46 -0
- package/apps/mobile/tsconfig.json +23 -0
- package/apps/web/components.json +25 -0
- package/apps/web/package.json +39 -0
- package/apps/web/public/baziu.svg +8 -0
- package/apps/web/src/components/AgentTabs.tsx +45 -0
- package/apps/web/src/components/BaziuLogo.tsx +21 -0
- package/apps/web/src/components/ChatPane.tsx +1033 -0
- package/apps/web/src/components/ConfigTabs.tsx +29 -0
- package/apps/web/src/components/CopyButton.tsx +68 -0
- package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
- package/apps/web/src/components/FieldRow.tsx +94 -0
- package/apps/web/src/components/Footer.tsx +10 -0
- package/apps/web/src/components/PawIcon.tsx +15 -0
- package/apps/web/src/components/Sidebar.tsx +287 -0
- package/apps/web/src/components/SpawnDialog.tsx +129 -0
- package/apps/web/src/components/ThemeToggle.tsx +75 -0
- package/apps/web/src/components/TopNav.tsx +34 -0
- package/apps/web/src/components/ui/button.tsx +67 -0
- package/apps/web/src/components/ui/card.tsx +103 -0
- package/apps/web/src/components/ui/checkbox.tsx +31 -0
- package/apps/web/src/components/ui/dialog.tsx +168 -0
- package/apps/web/src/components/ui/input.tsx +19 -0
- package/apps/web/src/components/ui/label.tsx +22 -0
- package/apps/web/src/components/ui/radio-group.tsx +44 -0
- package/apps/web/src/components/ui/select.tsx +192 -0
- package/apps/web/src/components/ui/separator.tsx +26 -0
- package/apps/web/src/components/ui/table.tsx +116 -0
- package/apps/web/src/components/ui/tabs.tsx +88 -0
- package/apps/web/src/components/ui/textarea.tsx +18 -0
- package/apps/web/src/lib/auth.ts +50 -0
- package/apps/web/src/lib/daemon-client.ts +34 -0
- package/apps/web/src/lib/md.ts +45 -0
- package/apps/web/src/lib/utils.ts +6 -0
- package/apps/web/src/lib/wire-constants.ts +27 -0
- package/apps/web/src/routeTree.gen.ts +408 -0
- package/apps/web/src/router.tsx +20 -0
- package/apps/web/src/routes/__root.tsx +123 -0
- package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
- package/apps/web/src/routes/agents/$id/index.tsx +527 -0
- package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
- package/apps/web/src/routes/agents/index.tsx +265 -0
- package/apps/web/src/routes/api/$.ts +88 -0
- package/apps/web/src/routes/config/index.tsx +315 -0
- package/apps/web/src/routes/config/services.tsx +49 -0
- package/apps/web/src/routes/config/tokens.tsx +192 -0
- package/apps/web/src/routes/groups/$id/index.tsx +153 -0
- package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
- package/apps/web/src/routes/groups/index.tsx +191 -0
- package/apps/web/src/routes/index.tsx +133 -0
- package/apps/web/src/routes/login.tsx +63 -0
- package/apps/web/src/routes/profiles/$id.tsx +549 -0
- package/apps/web/src/routes/profiles/index.tsx +458 -0
- package/apps/web/src/routes/skills/index.tsx +297 -0
- package/apps/web/src/routes/welcome.tsx +61 -0
- package/apps/web/src/styles.css +449 -0
- package/apps/web/tsconfig.json +25 -0
- package/apps/web/vite.config.ts +25 -0
- package/biome.json +23 -0
- package/docs/agent-engine.md +219 -0
- package/docs/architecture.md +627 -0
- package/docs/backlog/README.md +42 -0
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
- package/docs/openclaw-reference.md +210 -0
- package/package.json +38 -0
- package/packages/api-types/package.json +11 -0
- package/packages/api-types/src/entities.ts +146 -0
- package/packages/api-types/src/events.ts +55 -0
- package/packages/api-types/src/index.ts +488 -0
- package/packages/api-types/src/memory.ts +15 -0
- package/packages/client/package.json +13 -0
- package/packages/client/src/index.ts +117 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +11 -0
- 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
|
+
})
|