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,239 @@
|
|
|
1
|
+
// Single-route resources: /api/health, /api/backup, /api/tokens.
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process'
|
|
4
|
+
import { existsSync } from 'node:fs'
|
|
5
|
+
import { homedir } from 'node:os'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import type {
|
|
8
|
+
CreateTokenRequest,
|
|
9
|
+
CreateTokenResponse,
|
|
10
|
+
HealthReport,
|
|
11
|
+
ListTokensResponse,
|
|
12
|
+
} from '@bazilion/api-types'
|
|
13
|
+
import { Hono } from 'hono'
|
|
14
|
+
import {
|
|
15
|
+
agentRepo,
|
|
16
|
+
discoverSkills,
|
|
17
|
+
groupRepo,
|
|
18
|
+
mergeSecretsIntoEnv,
|
|
19
|
+
parseSkillFile,
|
|
20
|
+
profileRepo,
|
|
21
|
+
resolvePaths,
|
|
22
|
+
webTokenRepo,
|
|
23
|
+
} from '../core/index.ts'
|
|
24
|
+
import { getCtx } from '../lib/ctx.ts'
|
|
25
|
+
import { loadProviderConfigFromEnv } from '../runtime/index.ts'
|
|
26
|
+
|
|
27
|
+
export const miscRouter = new Hono()
|
|
28
|
+
|
|
29
|
+
// /api/health — install diagnostics. Public (auth middleware whitelists it)
|
|
30
|
+
// so the doctor command and external probes can run without a token.
|
|
31
|
+
miscRouter.get('/health', (c) => {
|
|
32
|
+
const paths = resolvePaths()
|
|
33
|
+
|
|
34
|
+
const pathChecks = {
|
|
35
|
+
home: existsSync(paths.home),
|
|
36
|
+
db: existsSync(paths.db),
|
|
37
|
+
auth: existsSync(paths.authFile),
|
|
38
|
+
profiles: existsSync(paths.profilesDir),
|
|
39
|
+
agents: existsSync(paths.agentsDir),
|
|
40
|
+
skills: existsSync(paths.skillsDir),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let database: HealthReport['database'] = null
|
|
44
|
+
const triggersSection: HealthReport['triggers'] = { active: 0, disabled: 0 }
|
|
45
|
+
let tokensSection: HealthReport['tokens'] = { active: 0 }
|
|
46
|
+
if (pathChecks.db) {
|
|
47
|
+
try {
|
|
48
|
+
const { db } = getCtx()
|
|
49
|
+
database = {
|
|
50
|
+
ok: true,
|
|
51
|
+
profiles: profileRepo.list(db).length,
|
|
52
|
+
activeAgents: agentRepo.list(db).length,
|
|
53
|
+
totalAgents: agentRepo.list(db, { includeArchived: true }).length,
|
|
54
|
+
groups: groupRepo.list(db, paths).length,
|
|
55
|
+
}
|
|
56
|
+
const triggerRows = db.raw
|
|
57
|
+
.query<{ enabled: number; n: number }, []>(
|
|
58
|
+
'SELECT enabled, COUNT(*) AS n FROM agent_triggers GROUP BY enabled',
|
|
59
|
+
)
|
|
60
|
+
.all()
|
|
61
|
+
for (const r of triggerRows) {
|
|
62
|
+
if (r.enabled === 1) triggersSection.active = r.n
|
|
63
|
+
else triggersSection.disabled = r.n
|
|
64
|
+
}
|
|
65
|
+
tokensSection = { active: webTokenRepo.list(db).length }
|
|
66
|
+
} catch (err) {
|
|
67
|
+
database = { ok: false, error: (err as Error).message }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const skills = discoverSkills(paths)
|
|
72
|
+
let parseErrors = 0
|
|
73
|
+
for (const s of skills) {
|
|
74
|
+
try {
|
|
75
|
+
parseSkillFile(s.skillFile)
|
|
76
|
+
} catch {
|
|
77
|
+
parseErrors++
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let effectiveEnv: NodeJS.ProcessEnv = process.env
|
|
82
|
+
let oauth: { db: import('../core/index.ts').BazilionDb; authToken: string } | undefined
|
|
83
|
+
if (pathChecks.auth && pathChecks.db) {
|
|
84
|
+
try {
|
|
85
|
+
const { db, authToken } = getCtx()
|
|
86
|
+
effectiveEnv = mergeSecretsIntoEnv(db, authToken)
|
|
87
|
+
oauth = { db, authToken }
|
|
88
|
+
} catch {
|
|
89
|
+
// first-run / partially-initialized — fall through with bare env
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const providerConfig = loadProviderConfigFromEnv(effectiveEnv, oauth)
|
|
93
|
+
const braveKey = effectiveEnv.BRAVE_API_KEY
|
|
94
|
+
const openclawSkillsDir = join(homedir(), '.openclaw', 'skills')
|
|
95
|
+
|
|
96
|
+
const CLOUD_KEYS: Array<[string, keyof typeof providerConfig]> = [
|
|
97
|
+
['anthropic', 'anthropic'],
|
|
98
|
+
['openai', 'openai'],
|
|
99
|
+
['google', 'google'],
|
|
100
|
+
['azure-openai', 'azureOpenai'],
|
|
101
|
+
['bedrock', 'bedrock'],
|
|
102
|
+
['google-vertex', 'googleVertex'],
|
|
103
|
+
['mistral', 'mistral'],
|
|
104
|
+
['groq', 'groq'],
|
|
105
|
+
['cerebras', 'cerebras'],
|
|
106
|
+
['xai', 'xai'],
|
|
107
|
+
['zai', 'zai'],
|
|
108
|
+
['huggingface', 'huggingface'],
|
|
109
|
+
['openrouter', 'openrouter'],
|
|
110
|
+
['vercel-ai-gateway', 'vercelAiGateway'],
|
|
111
|
+
]
|
|
112
|
+
const providerSection: HealthReport['providers'] = {
|
|
113
|
+
configured: CLOUD_KEYS.filter(([, key]) => providerConfig[key]).map(([name]) => name),
|
|
114
|
+
lmstudio: {
|
|
115
|
+
baseURL: providerConfig.lmstudio?.baseURL ?? 'http://localhost:1234/v1',
|
|
116
|
+
hasKey: Boolean(providerConfig.lmstudio?.apiKey),
|
|
117
|
+
},
|
|
118
|
+
ollama: { baseURL: providerConfig.ollama?.baseURL ?? 'http://localhost:11434/v1' },
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const report: HealthReport = {
|
|
122
|
+
ok:
|
|
123
|
+
pathChecks.home &&
|
|
124
|
+
pathChecks.db &&
|
|
125
|
+
pathChecks.auth &&
|
|
126
|
+
pathChecks.profiles &&
|
|
127
|
+
pathChecks.agents &&
|
|
128
|
+
pathChecks.skills &&
|
|
129
|
+
(database === null || database.ok) &&
|
|
130
|
+
parseErrors === 0,
|
|
131
|
+
home: paths.home,
|
|
132
|
+
paths: pathChecks,
|
|
133
|
+
database,
|
|
134
|
+
skills: { installed: skills.length, parseErrors },
|
|
135
|
+
providers: providerSection,
|
|
136
|
+
webSearch: {
|
|
137
|
+
bravePreview: braveKey ? `${braveKey.slice(0, 6)}…` : null,
|
|
138
|
+
searxngUrl: effectiveEnv.SEARXNG_URL ?? null,
|
|
139
|
+
},
|
|
140
|
+
openclaw: {
|
|
141
|
+
path: openclawSkillsDir,
|
|
142
|
+
exists: existsSync(openclawSkillsDir),
|
|
143
|
+
},
|
|
144
|
+
triggers: triggersSection,
|
|
145
|
+
tokens: tokensSection,
|
|
146
|
+
scheduler: {
|
|
147
|
+
enabled: process.env.BAZILION_SCHEDULER !== 'off',
|
|
148
|
+
tickMs: Number(process.env.BAZILION_SCHEDULER_TICK_MS ?? 5_000),
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
return c.json(report)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// /api/backup — streams a tar.gz of $BAZILION_HOME
|
|
155
|
+
miscRouter.get('/backup', (c) => {
|
|
156
|
+
const paths = resolvePaths()
|
|
157
|
+
if (!existsSync(paths.home)) {
|
|
158
|
+
return c.json({ error: `bazilion home not found at ${paths.home}` }, 404)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const proc = spawn('tar', ['-czf', '-', '-C', paths.home, '.'], {
|
|
162
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const stream = new ReadableStream({
|
|
166
|
+
start(controller) {
|
|
167
|
+
proc.stdout.on('data', (chunk: Buffer) => controller.enqueue(chunk))
|
|
168
|
+
proc.stdout.on('end', () => {
|
|
169
|
+
try {
|
|
170
|
+
controller.close()
|
|
171
|
+
} catch {}
|
|
172
|
+
})
|
|
173
|
+
proc.on('error', (err) => {
|
|
174
|
+
try {
|
|
175
|
+
controller.error(err)
|
|
176
|
+
} catch {}
|
|
177
|
+
})
|
|
178
|
+
proc.on('exit', (code) => {
|
|
179
|
+
if (code !== 0) {
|
|
180
|
+
try {
|
|
181
|
+
controller.error(new Error(`tar exited with code ${code}`))
|
|
182
|
+
} catch {}
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
},
|
|
186
|
+
cancel() {
|
|
187
|
+
proc.kill('SIGTERM')
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const date = new Date().toISOString().slice(0, 10)
|
|
192
|
+
return new Response(stream, {
|
|
193
|
+
headers: {
|
|
194
|
+
'content-type': 'application/gzip',
|
|
195
|
+
'content-disposition': `attachment; filename="bazilion-backup-${date}.tar.gz"`,
|
|
196
|
+
},
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// /api/tokens
|
|
201
|
+
miscRouter.get('/tokens', (c) => {
|
|
202
|
+
const { db } = getCtx()
|
|
203
|
+
const includeRevoked = c.req.query('includeRevoked') === '1'
|
|
204
|
+
const tokens = webTokenRepo.list(db, { includeRevoked })
|
|
205
|
+
return c.json({ tokens } satisfies ListTokensResponse)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
miscRouter.post('/tokens', async (c) => {
|
|
209
|
+
const body = (await c.req.json().catch(() => null)) as CreateTokenRequest | null
|
|
210
|
+
if (!body || typeof body.label !== 'string' || !body.label.trim()) {
|
|
211
|
+
return c.json({ error: 'label is required' }, 400)
|
|
212
|
+
}
|
|
213
|
+
const { db } = getCtx()
|
|
214
|
+
const created = webTokenRepo.create(db, body.label.trim())
|
|
215
|
+
return c.json({ token: created.token, meta: created.meta } satisfies CreateTokenResponse, 201)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
miscRouter.delete('/tokens/:id', (c) => {
|
|
219
|
+
const { db, authToken } = getCtx()
|
|
220
|
+
const id = c.req.param('id')
|
|
221
|
+
const existing = webTokenRepo.get(db, id)
|
|
222
|
+
if (!existing) return c.json({ error: `token not found: ${id}` }, 404)
|
|
223
|
+
if (existing.revokedAt) return c.json({ error: 'token already revoked' }, 409)
|
|
224
|
+
// Refuse to revoke the bootstrap token — that's the plaintext in auth.json
|
|
225
|
+
// the local CLI uses for loopback. Revoking it would lock the operator out
|
|
226
|
+
// of their own daemon. Match by hash (label is editable, hash isn't).
|
|
227
|
+
const bootstrap = webTokenRepo.findActiveByToken(db, authToken)
|
|
228
|
+
if (bootstrap && bootstrap.id === id) {
|
|
229
|
+
return c.json(
|
|
230
|
+
{
|
|
231
|
+
error:
|
|
232
|
+
'cannot revoke the bootstrap token — it lives in ~/.bazilion/auth.json and is the local CLI loopback credential',
|
|
233
|
+
},
|
|
234
|
+
409,
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
webTokenRepo.revoke(db, id)
|
|
238
|
+
return c.body(null, 204)
|
|
239
|
+
})
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// /api/profiles/* — profile CRUD + per-profile template files.
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import type {
|
|
6
|
+
CreateProfileRequest,
|
|
7
|
+
FileContentResponse,
|
|
8
|
+
ProfileFileName,
|
|
9
|
+
PutFileRequest,
|
|
10
|
+
SkillsMode,
|
|
11
|
+
UpdateProfileRequest,
|
|
12
|
+
} from '@bazilion/api-types'
|
|
13
|
+
import { PROFILE_FILES } from '@bazilion/api-types'
|
|
14
|
+
import { Hono } from 'hono'
|
|
15
|
+
import {
|
|
16
|
+
agentRepo,
|
|
17
|
+
createProfile,
|
|
18
|
+
DEFAULT_BOOTSTRAP,
|
|
19
|
+
DEFAULT_IDENTITY,
|
|
20
|
+
DEFAULT_SOUL,
|
|
21
|
+
deleteProfile,
|
|
22
|
+
loadProfile,
|
|
23
|
+
profileRepo,
|
|
24
|
+
updateProfile,
|
|
25
|
+
} from '../core/index.ts'
|
|
26
|
+
import { getCtx } from '../lib/ctx.ts'
|
|
27
|
+
|
|
28
|
+
export const profilesRouter = new Hono()
|
|
29
|
+
|
|
30
|
+
profilesRouter.get('/', (c) => {
|
|
31
|
+
const { db } = getCtx()
|
|
32
|
+
const profiles = profileRepo.list(db)
|
|
33
|
+
// Hydrate with the per-profile fields the listing UI needs (agent counts,
|
|
34
|
+
// default-skill list) so callers don't fan out to extra endpoints per row.
|
|
35
|
+
const hydrated = profiles.map((p) => ({
|
|
36
|
+
...p,
|
|
37
|
+
agentCount: agentRepo.countByProfile(db, p.id),
|
|
38
|
+
defaultSkills: profileRepo.getDefaultSkills(db, p.id),
|
|
39
|
+
}))
|
|
40
|
+
return c.json(hydrated)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// /api/profiles/_/templates — built-in defaults for the SOUL/IDENTITY/BOOTSTRAP
|
|
44
|
+
// markdown templates. Underscore prefix avoids clashing with the `:id` route.
|
|
45
|
+
profilesRouter.get('/_/templates', (c) => {
|
|
46
|
+
return c.json({
|
|
47
|
+
soul: DEFAULT_SOUL,
|
|
48
|
+
identity: DEFAULT_IDENTITY,
|
|
49
|
+
bootstrap: DEFAULT_BOOTSTRAP,
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
profilesRouter.post('/', async (c) => {
|
|
54
|
+
const raw = (await c.req.json().catch(() => null)) as
|
|
55
|
+
| (Record<string, unknown> & Partial<CreateProfileRequest>)
|
|
56
|
+
| null
|
|
57
|
+
if (!raw) return c.json({ error: 'invalid JSON body' }, 400)
|
|
58
|
+
const id = typeof raw.id === 'string' ? raw.id : ''
|
|
59
|
+
const defaultModel =
|
|
60
|
+
typeof raw.defaultModel === 'string'
|
|
61
|
+
? raw.defaultModel
|
|
62
|
+
: typeof raw.model === 'string'
|
|
63
|
+
? raw.model
|
|
64
|
+
: ''
|
|
65
|
+
if (!id || !defaultModel) return c.json({ error: 'id and model are required' }, 400)
|
|
66
|
+
const name = typeof raw.name === 'string' ? raw.name : undefined
|
|
67
|
+
|
|
68
|
+
const skillsMode = toSkillsMode(raw.skillsMode) ?? 'selected'
|
|
69
|
+
const defaultSkills = csvToArray(raw.defaultSkills ?? raw.skills)
|
|
70
|
+
|
|
71
|
+
const templates: {
|
|
72
|
+
soul?: string
|
|
73
|
+
identity?: string
|
|
74
|
+
bootstrap?: string | null
|
|
75
|
+
agents?: string
|
|
76
|
+
tools?: string
|
|
77
|
+
heartbeat?: string
|
|
78
|
+
} = {}
|
|
79
|
+
if (typeof raw.soul === 'string' && raw.soul.length > 0) templates.soul = raw.soul
|
|
80
|
+
if (typeof raw.identity === 'string' && raw.identity.length > 0) templates.identity = raw.identity
|
|
81
|
+
if (raw.skipBootstrap === true || raw.bootstrap === null) templates.bootstrap = null
|
|
82
|
+
else if (typeof raw.bootstrap === 'string' && raw.bootstrap.length > 0)
|
|
83
|
+
templates.bootstrap = raw.bootstrap
|
|
84
|
+
if (typeof raw.agents === 'string' && raw.agents.length > 0) templates.agents = raw.agents
|
|
85
|
+
if (typeof raw.tools === 'string' && raw.tools.length > 0) templates.tools = raw.tools
|
|
86
|
+
if (typeof raw.heartbeat === 'string' && raw.heartbeat.length > 0)
|
|
87
|
+
templates.heartbeat = raw.heartbeat
|
|
88
|
+
|
|
89
|
+
const { db, paths } = getCtx()
|
|
90
|
+
try {
|
|
91
|
+
const profile = createProfile(db, paths, {
|
|
92
|
+
id,
|
|
93
|
+
name,
|
|
94
|
+
defaultModel,
|
|
95
|
+
skillsMode,
|
|
96
|
+
defaultSkills,
|
|
97
|
+
...(Object.keys(templates).length > 0 ? { templates } : {}),
|
|
98
|
+
})
|
|
99
|
+
return c.json(profile, 201)
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return c.json({ error: (err as Error).message }, 400)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
profilesRouter.get('/:id', (c) => {
|
|
106
|
+
const { db } = getCtx()
|
|
107
|
+
try {
|
|
108
|
+
return c.json(loadProfile(db, c.req.param('id')))
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return c.json({ error: (err as Error).message }, 404)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
profilesRouter.patch('/:id', async (c) => {
|
|
115
|
+
const raw = (await c.req.json().catch(() => null)) as
|
|
116
|
+
| (UpdateProfileRequest & Record<string, unknown>)
|
|
117
|
+
| null
|
|
118
|
+
if (!raw) return c.json({ error: 'invalid JSON body' }, 400)
|
|
119
|
+
|
|
120
|
+
const input: UpdateProfileRequest = {}
|
|
121
|
+
if (typeof raw.name === 'string') input.name = raw.name
|
|
122
|
+
if (typeof raw.defaultModel === 'string' && raw.defaultModel.length > 0)
|
|
123
|
+
input.defaultModel = raw.defaultModel
|
|
124
|
+
if (raw.skillsMode === 'all' || raw.skillsMode === 'selected') {
|
|
125
|
+
input.skillsMode = raw.skillsMode
|
|
126
|
+
}
|
|
127
|
+
const rawSkills: unknown = raw.defaultSkills
|
|
128
|
+
if (Array.isArray(rawSkills)) {
|
|
129
|
+
input.defaultSkills = rawSkills.filter((s): s is string => typeof s === 'string')
|
|
130
|
+
} else if (typeof rawSkills === 'string') {
|
|
131
|
+
input.defaultSkills = rawSkills
|
|
132
|
+
.split(',')
|
|
133
|
+
.map((s) => s.trim())
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
}
|
|
136
|
+
const { db, paths } = getCtx()
|
|
137
|
+
try {
|
|
138
|
+
return c.json(updateProfile(db, paths, c.req.param('id'), input))
|
|
139
|
+
} catch (err) {
|
|
140
|
+
const msg = (err as Error).message
|
|
141
|
+
return c.json({ error: msg }, msg.startsWith('profile not found') ? 404 : 400)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
profilesRouter.delete('/:id', (c) => {
|
|
146
|
+
const { db } = getCtx()
|
|
147
|
+
try {
|
|
148
|
+
deleteProfile(db, c.req.param('id'))
|
|
149
|
+
return c.body(null, 204)
|
|
150
|
+
} catch (err) {
|
|
151
|
+
return c.json({ error: (err as Error).message }, 400)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
profilesRouter.get('/:id/files/:file', (c) => {
|
|
156
|
+
const { db, paths } = getCtx()
|
|
157
|
+
if (!profileRepo.get(db, c.req.param('id')))
|
|
158
|
+
return c.json({ error: `profile not found: ${c.req.param('id')}` }, 404)
|
|
159
|
+
const path = resolveFilePath(paths.profilesDir, c.req.param('id'), c.req.param('file'))
|
|
160
|
+
if (!path) return c.json({ error: `unsupported file: ${c.req.param('file')}` }, 400)
|
|
161
|
+
if (!existsSync(path)) return c.json({ error: `file not present: ${c.req.param('file')}` }, 404)
|
|
162
|
+
const body: FileContentResponse = { content: readFileSync(path, 'utf8') }
|
|
163
|
+
return c.json(body)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
profilesRouter.put('/:id/files/:file', async (c) => {
|
|
167
|
+
const body = (await c.req.json().catch(() => null)) as PutFileRequest | null
|
|
168
|
+
if (!body || typeof body.content !== 'string')
|
|
169
|
+
return c.json({ error: 'content is required' }, 400)
|
|
170
|
+
const { db, paths } = getCtx()
|
|
171
|
+
if (!profileRepo.get(db, c.req.param('id')))
|
|
172
|
+
return c.json({ error: `profile not found: ${c.req.param('id')}` }, 404)
|
|
173
|
+
const path = resolveFilePath(paths.profilesDir, c.req.param('id'), c.req.param('file'))
|
|
174
|
+
if (!path) return c.json({ error: `unsupported file: ${c.req.param('file')}` }, 400)
|
|
175
|
+
writeFileSync(path, body.content)
|
|
176
|
+
return c.body(null, 204)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
function resolveFilePath(profilesDir: string, id: string, file: string): string | null {
|
|
180
|
+
if (!(PROFILE_FILES as readonly string[]).includes(file)) return null
|
|
181
|
+
return join(profilesDir, id, file as ProfileFileName)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function csvToArray(v: unknown): string[] | undefined {
|
|
185
|
+
if (Array.isArray(v)) return v.filter((s): s is string => typeof s === 'string')
|
|
186
|
+
if (typeof v === 'string' && v.length > 0) {
|
|
187
|
+
return v
|
|
188
|
+
.split(',')
|
|
189
|
+
.map((s) => s.trim())
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
}
|
|
192
|
+
return undefined
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function toSkillsMode(v: unknown): SkillsMode | undefined {
|
|
196
|
+
return v === 'all' || v === 'selected' ? v : undefined
|
|
197
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// /api/skills/* — skill discovery, removal, and import (file-path or zip upload).
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { homedir, tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import type { ImportSkillsRequest, ImportSkillsResponse, SkillInfo } from '@bazilion/api-types'
|
|
7
|
+
import { Hono } from 'hono'
|
|
8
|
+
import { discoverSkills, importSkills, parseSkillFile, skillMetaRepo } from '../core/index.ts'
|
|
9
|
+
import { getCtx } from '../lib/ctx.ts'
|
|
10
|
+
|
|
11
|
+
// 50 MiB cap — generous headroom for a bundle of skills, tight enough to
|
|
12
|
+
// reject obviously-malicious payloads without needing a streaming upload.
|
|
13
|
+
const MAX_ZIP_BYTES = 50 * 1024 * 1024
|
|
14
|
+
|
|
15
|
+
export const skillsRouter = new Hono()
|
|
16
|
+
|
|
17
|
+
skillsRouter.get('/', (c) => {
|
|
18
|
+
const { db, paths } = getCtx()
|
|
19
|
+
const out: SkillInfo[] = []
|
|
20
|
+
for (const s of discoverSkills(paths)) {
|
|
21
|
+
const meta = skillMetaRepo.get(db, s.name)
|
|
22
|
+
const entry: SkillInfo = {
|
|
23
|
+
name: s.name,
|
|
24
|
+
description: '',
|
|
25
|
+
source: meta?.source ?? null,
|
|
26
|
+
importedAt: meta?.importedAt ?? null,
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const parsed = parseSkillFile(s.skillFile)
|
|
30
|
+
entry.description = parsed.frontmatter.description
|
|
31
|
+
} catch (err) {
|
|
32
|
+
entry.parseError = (err as Error).message
|
|
33
|
+
}
|
|
34
|
+
out.push(entry)
|
|
35
|
+
}
|
|
36
|
+
return c.json(out)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
skillsRouter.delete('/:name', (c) => {
|
|
40
|
+
const { db, paths } = getCtx()
|
|
41
|
+
const name = c.req.param('name')
|
|
42
|
+
const dir = paths.skillDir(name)
|
|
43
|
+
if (!existsSync(dir)) return c.json({ error: `skill not found: ${name}` }, 404)
|
|
44
|
+
rmSync(dir, { recursive: true, force: true })
|
|
45
|
+
skillMetaRepo.remove(db, name)
|
|
46
|
+
return c.body(null, 204)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
skillsRouter.post('/import', async (c) => {
|
|
50
|
+
let input: ParsedImportInput
|
|
51
|
+
try {
|
|
52
|
+
input = await parseImportInput(c.req.raw)
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return c.json({ error: (err as Error).message }, 400)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { db, paths } = getCtx()
|
|
58
|
+
try {
|
|
59
|
+
const result = importSkills(paths, { source: input.source, force: input.force })
|
|
60
|
+
const now = Date.now()
|
|
61
|
+
for (const name of result.imported) {
|
|
62
|
+
skillMetaRepo.upsert(db, { name, source: input.sourceLabel, importedAt: now })
|
|
63
|
+
}
|
|
64
|
+
const res: ImportSkillsResponse = { imported: result.imported, skipped: result.skipped }
|
|
65
|
+
return c.json(res)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return c.json({ error: (err as Error).message }, 400)
|
|
68
|
+
} finally {
|
|
69
|
+
if (input.tempZipPath) rmSync(input.tempZipPath, { recursive: true, force: true })
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
interface ParsedImportInput {
|
|
74
|
+
source: string
|
|
75
|
+
force: boolean
|
|
76
|
+
/** when set, a temp zip was written and should be rm'd after import */
|
|
77
|
+
tempZipPath: string | null
|
|
78
|
+
/** label stored in skill_meta.source (e.g. "uploaded:foo.zip" for uploads) */
|
|
79
|
+
sourceLabel: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function parseImportInput(request: Request): Promise<ParsedImportInput> {
|
|
83
|
+
const contentType = request.headers.get('content-type') ?? ''
|
|
84
|
+
if (contentType.startsWith('multipart/form-data')) {
|
|
85
|
+
const form = await request.formData()
|
|
86
|
+
const file = form.get('file')
|
|
87
|
+
if (!(file instanceof File) || file.size === 0) {
|
|
88
|
+
throw new Error('multipart upload missing "file" field')
|
|
89
|
+
}
|
|
90
|
+
if (file.size > MAX_ZIP_BYTES) {
|
|
91
|
+
throw new Error(`zip too large: ${file.size} bytes (max ${MAX_ZIP_BYTES})`)
|
|
92
|
+
}
|
|
93
|
+
const filename = file.name || 'upload.zip'
|
|
94
|
+
if (!filename.toLowerCase().endsWith('.zip')) {
|
|
95
|
+
throw new Error('uploaded file must be a .zip archive')
|
|
96
|
+
}
|
|
97
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'bazilion-skill-upload-'))
|
|
98
|
+
const zipPath = join(tmpDir, filename.replace(/[^\w.-]+/g, '_'))
|
|
99
|
+
const buf = Buffer.from(await file.arrayBuffer())
|
|
100
|
+
writeFileSync(zipPath, buf)
|
|
101
|
+
const forceField = form.get('force')
|
|
102
|
+
return {
|
|
103
|
+
source: zipPath,
|
|
104
|
+
force: forceField === 'true' || forceField === 'on' || forceField === '1',
|
|
105
|
+
tempZipPath: tmpDir,
|
|
106
|
+
sourceLabel: `uploaded:${filename}`,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const body = (await request.json().catch(() => null)) as
|
|
111
|
+
| (Partial<ImportSkillsRequest> & { from?: string })
|
|
112
|
+
| null
|
|
113
|
+
if (!body) throw new Error('invalid JSON body')
|
|
114
|
+
const from = body.source ?? body.from
|
|
115
|
+
if (typeof from !== 'string' || !from) throw new Error('source is required')
|
|
116
|
+
const source = from === 'openclaw' ? join(homedir(), '.openclaw', 'skills') : from
|
|
117
|
+
return {
|
|
118
|
+
source,
|
|
119
|
+
force: Boolean(body.force),
|
|
120
|
+
tempZipPath: null,
|
|
121
|
+
sourceLabel: from,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// /api/triggers/:id — enable/disable + delete. (Listing is per-agent at
|
|
2
|
+
// /api/agents/:id/triggers; creation is also per-agent.)
|
|
3
|
+
|
|
4
|
+
import type { UpdateTriggerRequest } from '@bazilion/api-types'
|
|
5
|
+
import { Hono } from 'hono'
|
|
6
|
+
import { triggerRepo } from '../core/index.ts'
|
|
7
|
+
import { getCtx } from '../lib/ctx.ts'
|
|
8
|
+
|
|
9
|
+
export const triggersRouter = new Hono()
|
|
10
|
+
|
|
11
|
+
triggersRouter.delete('/:id', (c) => {
|
|
12
|
+
const { db } = getCtx()
|
|
13
|
+
const id = c.req.param('id')
|
|
14
|
+
if (!triggerRepo.get(db, id)) return c.json({ error: `trigger not found: ${id}` }, 404)
|
|
15
|
+
triggerRepo.remove(db, id)
|
|
16
|
+
return c.body(null, 204)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
triggersRouter.patch('/:id', async (c) => {
|
|
20
|
+
const id = c.req.param('id')
|
|
21
|
+
const body = (await c.req.json().catch(() => null)) as UpdateTriggerRequest | null
|
|
22
|
+
if (!body) return c.json({ error: 'invalid JSON body' }, 400)
|
|
23
|
+
const { db } = getCtx()
|
|
24
|
+
if (!triggerRepo.get(db, id)) return c.json({ error: `trigger not found: ${id}` }, 404)
|
|
25
|
+
if (typeof body.enabled === 'boolean') {
|
|
26
|
+
triggerRepo.setEnabled(db, id, body.enabled)
|
|
27
|
+
}
|
|
28
|
+
return c.json({ trigger: triggerRepo.get(db, id) })
|
|
29
|
+
})
|