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,458 @@
1
+ import type { Profile, SkillInfo } from '@bazilion/api-types'
2
+ import { createFileRoute, useRouter } from '@tanstack/react-router'
3
+ import { createServerFn } from '@tanstack/react-start'
4
+ import { useState } from 'react'
5
+ import { daemonClient } from '../../lib/daemon-client'
6
+
7
+ interface ProfileWithCounts extends Profile {
8
+ agentCount: number
9
+ defaultSkills: string[]
10
+ }
11
+ interface ModelGroup {
12
+ provider: string
13
+ models: string[]
14
+ }
15
+ interface AvailableModelsResponse {
16
+ groups: ModelGroup[]
17
+ }
18
+ interface TemplatesResponse {
19
+ soul: string
20
+ identity: string
21
+ bootstrap: string
22
+ }
23
+
24
+ interface ProfilesView {
25
+ profiles: ProfileWithCounts[]
26
+ modelGroups: ModelGroup[]
27
+ skills: SkillInfo[]
28
+ templates: TemplatesResponse
29
+ }
30
+
31
+ const fetchProfiles = createServerFn({ method: 'GET' }).handler(async (): Promise<ProfilesView> => {
32
+ const c = daemonClient()
33
+ const [profiles, models, skills, templates] = await Promise.all([
34
+ c.get<ProfileWithCounts[]>('/api/profiles'),
35
+ c.get<AvailableModelsResponse>('/api/config/available-models'),
36
+ c.get<SkillInfo[]>('/api/skills'),
37
+ c.get<TemplatesResponse>('/api/profiles/_/templates'),
38
+ ])
39
+ return { profiles, modelGroups: models.groups, skills, templates }
40
+ })
41
+
42
+ export const Route = createFileRoute('/profiles/')({
43
+ loader: () => fetchProfiles(),
44
+ component: ProfilesPage,
45
+ })
46
+
47
+ function ProfilesPage() {
48
+ const { profiles, modelGroups, skills, templates } = Route.useLoaderData()
49
+ const router = useRouter()
50
+
51
+ async function del(id: string) {
52
+ if (!confirm('delete profile and all its files?')) return
53
+ await fetch(`/api/profiles/${id}`, { method: 'DELETE' })
54
+ await router.invalidate()
55
+ }
56
+
57
+ return (
58
+ <div>
59
+ <h1>profiles</h1>
60
+ <p className="muted">
61
+ A profile is a spawn template — SOUL/IDENTITY templates, a default model, a skills
62
+ policy. Agents choose a group at spawn time, independent of their profile.
63
+ </p>
64
+
65
+ <CreateProfileForm
66
+ modelGroups={modelGroups}
67
+ skills={skills}
68
+ templates={templates}
69
+ onCreated={() => router.invalidate()}
70
+ />
71
+
72
+ <table>
73
+ <thead>
74
+ <tr>
75
+ <th>id</th>
76
+ <th>name</th>
77
+ <th>model</th>
78
+ <th>agents</th>
79
+ <th>skills</th>
80
+ <th />
81
+ </tr>
82
+ </thead>
83
+ <tbody>
84
+ {profiles.length === 0 && (
85
+ <tr>
86
+ <td colSpan={6} className="muted">
87
+ no profiles yet
88
+ </td>
89
+ </tr>
90
+ )}
91
+ {profiles.map((p) => {
92
+ const skillsLabel =
93
+ p.skillsMode === 'all'
94
+ ? 'all'
95
+ : p.defaultSkills.length === 0
96
+ ? '—'
97
+ : p.defaultSkills.join(', ')
98
+ return (
99
+ <tr key={p.id}>
100
+ <td>
101
+ <a href={`/profiles/${p.id}`}>
102
+ <code>{p.id}</code>
103
+ </a>
104
+ </td>
105
+ <td>{p.name}</td>
106
+ <td>
107
+ <code>{p.defaultModel}</code>
108
+ </td>
109
+ <td>{p.agentCount}</td>
110
+ <td className="text-xs text-mocha-light">{skillsLabel}</td>
111
+ <td>
112
+ {p.agentCount === 0 ? (
113
+ <button type="button" className="ghost-btn" onClick={() => del(p.id)}>
114
+ delete
115
+ </button>
116
+ ) : (
117
+ <span className="muted" title={`${p.agentCount} agent(s) use this profile`}>
118
+ in use
119
+ </span>
120
+ )}
121
+ </td>
122
+ </tr>
123
+ )
124
+ })}
125
+ </tbody>
126
+ </table>
127
+ </div>
128
+ )
129
+ }
130
+
131
+ function CreateProfileForm({
132
+ modelGroups,
133
+ skills,
134
+ templates,
135
+ onCreated,
136
+ }: {
137
+ modelGroups: ModelGroup[]
138
+ skills: SkillInfo[]
139
+ templates: TemplatesResponse
140
+ onCreated: () => void
141
+ }) {
142
+ const [tab, setTab] = useState<'basics' | 'skills'>('basics')
143
+ const [id, setId] = useState('')
144
+ const [name, setName] = useState('')
145
+ const [model, setModel] = useState('')
146
+ const [soul, setSoul] = useState(templates.soul)
147
+ const [identity, setIdentity] = useState(templates.identity)
148
+ const [bootstrap, setBootstrap] = useState(templates.bootstrap)
149
+ const [skipBootstrap, setSkipBootstrap] = useState(false)
150
+ const [agentsTpl, setAgentsTpl] = useState('')
151
+ const [toolsTpl, setToolsTpl] = useState('')
152
+ const [heartbeatTpl, setHeartbeatTpl] = useState('')
153
+ const [skillsMode, setSkillsMode] = useState<'all' | 'selected'>('all')
154
+ const [picked, setPicked] = useState<Set<string>>(new Set())
155
+ const [error, setError] = useState<string | null>(null)
156
+ const [submitting, setSubmitting] = useState(false)
157
+
158
+ function togglePicked(name: string) {
159
+ setPicked((prev) => {
160
+ const next = new Set(prev)
161
+ if (next.has(name)) next.delete(name)
162
+ else next.add(name)
163
+ return next
164
+ })
165
+ }
166
+
167
+ async function submit(e: React.FormEvent<HTMLFormElement>) {
168
+ e.preventDefault()
169
+ setError(null)
170
+ if (!id.trim()) {
171
+ setError('id is required')
172
+ return
173
+ }
174
+ if (!model.trim()) {
175
+ setError('default model is required')
176
+ return
177
+ }
178
+ setSubmitting(true)
179
+ try {
180
+ const body: Record<string, unknown> = {
181
+ id: id.trim(),
182
+ name: name.trim() || undefined,
183
+ model: model.trim(),
184
+ skillsMode,
185
+ defaultSkills: skillsMode === 'selected' ? Array.from(picked) : [],
186
+ soul,
187
+ identity,
188
+ bootstrap,
189
+ skipBootstrap,
190
+ agents: agentsTpl,
191
+ tools: toolsTpl,
192
+ heartbeat: heartbeatTpl,
193
+ }
194
+ const res = await fetch('/api/profiles', {
195
+ method: 'POST',
196
+ headers: { 'content-type': 'application/json' },
197
+ body: JSON.stringify(body),
198
+ })
199
+ if (!res.ok) {
200
+ const e2 = (await res.json().catch(() => null)) as { error?: string } | null
201
+ throw new Error(e2?.error ?? res.statusText)
202
+ }
203
+ // Reset
204
+ setId('')
205
+ setName('')
206
+ setModel('')
207
+ setAgentsTpl('')
208
+ setToolsTpl('')
209
+ setHeartbeatTpl('')
210
+ setPicked(new Set())
211
+ setSkillsMode('all')
212
+ setTab('basics')
213
+ onCreated()
214
+ } catch (err) {
215
+ setError((err as Error).message)
216
+ } finally {
217
+ setSubmitting(false)
218
+ }
219
+ }
220
+
221
+ return (
222
+ <form className="card" onSubmit={submit}>
223
+ <h3>create profile</h3>
224
+ {error && <div className="err">{error}</div>}
225
+
226
+ <Tabs
227
+ items={[
228
+ { id: 'basics', label: '1. basics' },
229
+ { id: 'skills', label: '2. skills' },
230
+ ]}
231
+ active={tab}
232
+ onChange={(t) => setTab(t as 'basics' | 'skills')}
233
+ />
234
+
235
+ {tab === 'basics' && (
236
+ <div>
237
+ <div className="flex gap-4">
238
+ <label className="flex-1">
239
+ id (slug)
240
+ <input value={id} onChange={(e) => setId(e.target.value)} required />
241
+ </label>
242
+ <label className="flex-1">
243
+ name
244
+ <input value={name} onChange={(e) => setName(e.target.value)} />
245
+ </label>
246
+ </div>
247
+ <label>
248
+ default model
249
+ {modelGroups.length === 0 ? (
250
+ <>
251
+ <select disabled>
252
+ <option>(no models available)</option>
253
+ </select>
254
+ <span className="muted text-[0.85em]">
255
+ Enable a provider and curate models on <a href="/config">/config</a> first.
256
+ </span>
257
+ </>
258
+ ) : (
259
+ <select value={model} onChange={(e) => setModel(e.target.value)} required>
260
+ <option value="">-- pick a model --</option>
261
+ {modelGroups.map((g) => (
262
+ <optgroup key={g.provider} label={g.provider}>
263
+ {g.models.map((m) => (
264
+ <option key={m} value={`${g.provider}:${m}`}>
265
+ {`${g.provider}:${m}`}
266
+ </option>
267
+ ))}
268
+ </optgroup>
269
+ ))}
270
+ </select>
271
+ )}
272
+ </label>
273
+
274
+ <details className="mt-4 [&>summary]:cursor-pointer [&>summary]:py-1.5 [&>summary]:font-medium [&>summary]:text-mocha [&[open]>summary]:text-sapphire">
275
+ <summary>templates (SOUL · IDENTITY · BOOTSTRAP)</summary>
276
+ <p className="muted my-2">
277
+ These seed each agent spawned from this profile. Every agent gets its own copy at
278
+ spawn time — editing the profile later doesn't affect existing agents.
279
+ </p>
280
+ <label>
281
+ SOUL.md
282
+ <textarea
283
+ rows={10}
284
+ value={soul}
285
+ onChange={(e) => setSoul(e.target.value)}
286
+ className="font-mono text-[0.88em] leading-[1.55]"
287
+ />
288
+ </label>
289
+ <label>
290
+ IDENTITY.md
291
+ <textarea
292
+ rows={6}
293
+ value={identity}
294
+ onChange={(e) => setIdentity(e.target.value)}
295
+ className="font-mono text-[0.88em] leading-[1.55]"
296
+ />
297
+ </label>
298
+ <label>
299
+ BOOTSTRAP.md
300
+ <textarea
301
+ rows={8}
302
+ value={bootstrap}
303
+ onChange={(e) => setBootstrap(e.target.value)}
304
+ className="font-mono text-[0.88em] leading-[1.55]"
305
+ />
306
+ </label>
307
+ <label className="inline-flex items-center gap-2">
308
+ <input
309
+ type="checkbox"
310
+ checked={skipBootstrap}
311
+ onChange={(e) => setSkipBootstrap(e.target.checked)}
312
+ />
313
+ skip bootstrap (no first-run intro)
314
+ </label>
315
+ </details>
316
+
317
+ <details className="mt-4 [&>summary]:cursor-pointer [&>summary]:py-1.5 [&>summary]:font-medium [&>summary]:text-mocha [&[open]>summary]:text-sapphire">
318
+ <summary>optional files (AGENTS · TOOLS · HEARTBEAT)</summary>
319
+ <p className="muted my-2">
320
+ Optional. Paste in content to seed these files. Left blank, the files are not
321
+ created — you can always add them later from the profile detail page.
322
+ </p>
323
+ <label>
324
+ AGENTS.md
325
+ <textarea
326
+ rows={6}
327
+ placeholder="Peers this agent can route to."
328
+ value={agentsTpl}
329
+ onChange={(e) => setAgentsTpl(e.target.value)}
330
+ className="font-mono text-[0.88em] leading-[1.55]"
331
+ />
332
+ </label>
333
+ <label>
334
+ TOOLS.md
335
+ <textarea
336
+ rows={6}
337
+ placeholder="Agent-specific tool usage notes."
338
+ value={toolsTpl}
339
+ onChange={(e) => setToolsTpl(e.target.value)}
340
+ className="font-mono text-[0.88em] leading-[1.55]"
341
+ />
342
+ </label>
343
+ <label>
344
+ HEARTBEAT.md
345
+ <textarea
346
+ rows={6}
347
+ placeholder="Tasks to check on scheduled wake-ups."
348
+ value={heartbeatTpl}
349
+ onChange={(e) => setHeartbeatTpl(e.target.value)}
350
+ className="font-mono text-[0.88em] leading-[1.55]"
351
+ />
352
+ </label>
353
+ </details>
354
+ </div>
355
+ )}
356
+
357
+ {tab === 'skills' && (
358
+ <div>
359
+ <p className="muted my-2">
360
+ Choose which skills agents inherit at spawn time. In both modes each agent can still
361
+ attach or detach skills individually after it's spawned.
362
+ </p>
363
+ <label className="flex items-start gap-2 my-2 cursor-pointer">
364
+ <input
365
+ type="radio"
366
+ name="skillsMode"
367
+ value="all"
368
+ checked={skillsMode === 'all'}
369
+ onChange={() => setSkillsMode('all')}
370
+ />
371
+ <span>
372
+ <strong>allow all skills</strong> — every skill on disk is attached when an agent
373
+ is spawned.
374
+ </span>
375
+ </label>
376
+ <label className="flex items-start gap-2 my-2 cursor-pointer">
377
+ <input
378
+ type="radio"
379
+ name="skillsMode"
380
+ value="selected"
381
+ checked={skillsMode === 'selected'}
382
+ onChange={() => setSkillsMode('selected')}
383
+ />
384
+ <span>
385
+ <strong>select skills</strong> — attach only the ones you pick below.
386
+ </span>
387
+ </label>
388
+
389
+ <div
390
+ className={`mt-3 max-h-[22rem] overflow-y-auto rounded-md border border-frost p-3 ${skillsMode === 'all' ? 'opacity-50 pointer-events-none' : ''}`}
391
+ >
392
+ {skills.length === 0 ? (
393
+ <p className="muted">
394
+ No skills installed yet. Import some on <a href="/skills">/skills</a>.
395
+ </p>
396
+ ) : (
397
+ skills.map((s) => (
398
+ <label
399
+ key={s.name}
400
+ className="my-1.5 flex cursor-pointer items-start gap-2"
401
+ >
402
+ <input
403
+ type="checkbox"
404
+ checked={picked.has(s.name)}
405
+ onChange={() => togglePicked(s.name)}
406
+ />
407
+ <span>
408
+ <code>{s.name}</code>
409
+ {s.description && (
410
+ <span className="text-[0.9em] text-mocha-light"> — {s.description}</span>
411
+ )}
412
+ </span>
413
+ </label>
414
+ ))
415
+ )}
416
+ </div>
417
+ </div>
418
+ )}
419
+
420
+ <div className="mt-5 flex gap-2">
421
+ <button type="submit" disabled={submitting}>
422
+ {submitting ? 'creating…' : 'create'}
423
+ </button>
424
+ </div>
425
+ </form>
426
+ )
427
+ }
428
+
429
+ interface TabsProps<T extends string> {
430
+ items: { id: T; label: string }[]
431
+ active: T
432
+ onChange: (id: T) => void
433
+ }
434
+ function Tabs<T extends string>({ items, active, onChange }: TabsProps<T>) {
435
+ return (
436
+ <nav
437
+ role="tablist"
438
+ className="-mb-px mb-5 flex gap-1 border-b border-frost"
439
+ >
440
+ {items.map((it) => (
441
+ <button
442
+ key={it.id}
443
+ type="button"
444
+ role="tab"
445
+ aria-selected={active === it.id}
446
+ onClick={() => onChange(it.id)}
447
+ className={`unstyled cursor-pointer border-b-2 bg-transparent px-4 py-2 text-[0.9em] font-medium transition-colors ${
448
+ active === it.id
449
+ ? 'border-sapphire text-sapphire'
450
+ : 'border-transparent text-mocha hover:text-sapphire'
451
+ }`}
452
+ >
453
+ {it.label}
454
+ </button>
455
+ ))}
456
+ </nav>
457
+ )
458
+ }