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,419 @@
1
+ import type { CreateTokenResponse, ListTokensResponse, WebToken } from '@bazilion/api-types'
2
+ import { ApiClientError } from '@bazilion/client'
3
+ import { router } from 'expo-router'
4
+ import { useCallback, useEffect, useMemo, useState } from 'react'
5
+ import {
6
+ ActivityIndicator,
7
+ Alert,
8
+ FlatList,
9
+ Pressable,
10
+ ScrollView,
11
+ StyleSheet,
12
+ Text,
13
+ TextInput,
14
+ View,
15
+ } from 'react-native'
16
+ import { clearCredentials, clientFor, type Credentials, loadCredentials } from '@/src/auth'
17
+ import { useColors, useThemeMode } from '@/src/theme-context'
18
+ import { type Colors, fonts, radii, type ThemeMode } from '@/src/theme'
19
+
20
+ type Load =
21
+ | { kind: 'loading' }
22
+ | { kind: 'ready'; creds: Credentials; tokens: WebToken[]; bootstrapId: string | null }
23
+ | { kind: 'error'; message: string }
24
+
25
+ export default function Settings() {
26
+ const [load, setLoad] = useState<Load>({ kind: 'loading' })
27
+ const [newLabel, setNewLabel] = useState('')
28
+ const [minting, setMinting] = useState(false)
29
+ const [mintedSecret, setMintedSecret] = useState<{ token: string; meta: WebToken } | null>(null)
30
+ const colors = useColors()
31
+ const styles = useMemo(() => makeStyles(colors), [colors])
32
+
33
+ const fetchTokens = useCallback(async () => {
34
+ setLoad({ kind: 'loading' })
35
+ const creds = await loadCredentials()
36
+ if (!creds) {
37
+ router.replace('/pair')
38
+ return
39
+ }
40
+ try {
41
+ const res = await clientFor(creds).get<ListTokensResponse>('/api/tokens')
42
+ // Identify the bootstrap row so the UI can hide its revoke button
43
+ // (matches the daemon's 409-on-revoke guard).
44
+ const bootstrap = res.tokens.find((t) => t.label === 'bootstrap')
45
+ setLoad({
46
+ kind: 'ready',
47
+ creds,
48
+ tokens: res.tokens,
49
+ bootstrapId: bootstrap?.id ?? null,
50
+ })
51
+ } catch (err) {
52
+ if (err instanceof ApiClientError && err.status === 401) {
53
+ await clearCredentials()
54
+ router.replace('/pair')
55
+ return
56
+ }
57
+ const message = err instanceof Error ? err.message : 'unknown error'
58
+ setLoad({ kind: 'error', message })
59
+ }
60
+ }, [])
61
+
62
+ useEffect(() => {
63
+ fetchTokens()
64
+ }, [fetchTokens])
65
+
66
+ const onMint = useCallback(async () => {
67
+ if (load.kind !== 'ready' || minting) return
68
+ const label = newLabel.trim()
69
+ if (!label) return
70
+ setMinting(true)
71
+ try {
72
+ const res = await clientFor(load.creds).post<CreateTokenResponse>('/api/tokens', { label })
73
+ setMintedSecret({ token: res.token, meta: res.meta })
74
+ setNewLabel('')
75
+ await fetchTokens()
76
+ } catch (err) {
77
+ const message = err instanceof Error ? err.message : 'unknown error'
78
+ Alert.alert('Could not mint token', message)
79
+ } finally {
80
+ setMinting(false)
81
+ }
82
+ }, [load, minting, newLabel, fetchTokens])
83
+
84
+ const onRevoke = useCallback(
85
+ async (id: string) => {
86
+ if (load.kind !== 'ready') return
87
+ Alert.alert(
88
+ 'Revoke token?',
89
+ 'Devices using this token will lose access immediately.',
90
+ [
91
+ { text: 'Cancel', style: 'cancel' },
92
+ {
93
+ text: 'Revoke',
94
+ style: 'destructive',
95
+ onPress: async () => {
96
+ try {
97
+ await clientFor(load.creds).del(`/api/tokens/${id}`)
98
+ await fetchTokens()
99
+ } catch (err) {
100
+ const message = err instanceof Error ? err.message : 'unknown error'
101
+ Alert.alert('Could not revoke', message)
102
+ }
103
+ },
104
+ },
105
+ ],
106
+ { cancelable: true },
107
+ )
108
+ },
109
+ [load, fetchTokens],
110
+ )
111
+
112
+ const onUnpair = useCallback(() => {
113
+ Alert.alert(
114
+ 'Unpair this device?',
115
+ 'You will need to scan a new pairing QR to reconnect.',
116
+ [
117
+ { text: 'Cancel', style: 'cancel' },
118
+ {
119
+ text: 'Unpair',
120
+ style: 'destructive',
121
+ onPress: async () => {
122
+ await clearCredentials()
123
+ router.replace('/pair')
124
+ },
125
+ },
126
+ ],
127
+ { cancelable: true },
128
+ )
129
+ }, [])
130
+
131
+ if (load.kind === 'loading') {
132
+ return (
133
+ <View style={styles.centered}>
134
+ <ActivityIndicator />
135
+ </View>
136
+ )
137
+ }
138
+
139
+ if (load.kind === 'error') {
140
+ return (
141
+ <View style={styles.centered}>
142
+ <Text style={styles.errorTitle}>Couldn't load settings</Text>
143
+ <Text style={styles.errorBody}>{load.message}</Text>
144
+ <Pressable style={styles.primaryBtn} onPress={fetchTokens}>
145
+ <Text style={styles.primaryBtnText}>Retry</Text>
146
+ </Pressable>
147
+ <Pressable style={styles.linkBtn} onPress={onUnpair}>
148
+ <Text style={styles.linkBtnText}>Unpair</Text>
149
+ </Pressable>
150
+ </View>
151
+ )
152
+ }
153
+
154
+ const tokenPreview = `${load.creds.token.slice(0, 6)}…${load.creds.token.slice(-4)}`
155
+
156
+ return (
157
+ <ScrollView style={styles.screen} contentContainerStyle={styles.content}>
158
+ <Section label="server">
159
+ <Text style={styles.mono}>{load.creds.server}</Text>
160
+ </Section>
161
+
162
+ <Section label="this device's token">
163
+ <Text style={styles.mono}>{tokenPreview}</Text>
164
+ </Section>
165
+
166
+ <View style={styles.divider} />
167
+
168
+ <Section label="theme">
169
+ <ThemePicker />
170
+ </Section>
171
+
172
+ <View style={styles.divider} />
173
+
174
+ <Section label="mint a new token">
175
+ <View style={styles.mintRow}>
176
+ <TextInput
177
+ value={newLabel}
178
+ onChangeText={setNewLabel}
179
+ placeholder="e.g. tablet, laptop"
180
+ placeholderTextColor="#aaa"
181
+ style={styles.mintInput}
182
+ editable={!minting}
183
+ autoCapitalize="none"
184
+ />
185
+ <Pressable
186
+ onPress={onMint}
187
+ disabled={minting || !newLabel.trim()}
188
+ style={({ pressed }) => [
189
+ styles.mintBtn,
190
+ (minting || !newLabel.trim()) && styles.mintBtnDisabled,
191
+ pressed && styles.mintBtnPressed,
192
+ ]}
193
+ >
194
+ <Text style={styles.mintBtnText}>{minting ? '…' : 'Mint'}</Text>
195
+ </Pressable>
196
+ </View>
197
+ {mintedSecret ? (
198
+ <View style={styles.mintedBox}>
199
+ <Text style={styles.mintedLabel}>new token (shown once):</Text>
200
+ <Text style={styles.mintedToken} selectable>
201
+ {mintedSecret.token}
202
+ </Text>
203
+ <Text style={styles.mintedHint}>
204
+ copy this now — it cannot be retrieved later. label: {mintedSecret.meta.label}
205
+ </Text>
206
+ </View>
207
+ ) : null}
208
+ </Section>
209
+
210
+ <Section label={`tokens (${load.tokens.length})`}>
211
+ <FlatList
212
+ data={load.tokens}
213
+ scrollEnabled={false}
214
+ keyExtractor={(t) => t.id}
215
+ renderItem={({ item }) => (
216
+ <TokenRow
217
+ token={item}
218
+ isBootstrap={item.id === load.bootstrapId}
219
+ onRevoke={() => onRevoke(item.id)}
220
+ />
221
+ )}
222
+ ItemSeparatorComponent={() => <View style={styles.tokenSep} />}
223
+ />
224
+ </Section>
225
+
226
+ <View style={styles.divider} />
227
+
228
+ <Pressable
229
+ onPress={onUnpair}
230
+ style={({ pressed }) => [styles.dangerBtn, pressed && styles.dangerBtnPressed]}
231
+ >
232
+ <Text style={styles.dangerBtnText}>Unpair this device</Text>
233
+ </Pressable>
234
+ </ScrollView>
235
+ )
236
+ }
237
+
238
+ function Section({ label, children }: { label: string; children: React.ReactNode }) {
239
+ const colors = useColors()
240
+ const styles = useMemo(() => makeStyles(colors), [colors])
241
+ return (
242
+ <View style={styles.section}>
243
+ <Text style={styles.sectionLabel}>{label}</Text>
244
+ <View style={styles.sectionBody}>{children}</View>
245
+ </View>
246
+ )
247
+ }
248
+
249
+ function TokenRow({
250
+ token,
251
+ isBootstrap,
252
+ onRevoke,
253
+ }: {
254
+ token: WebToken
255
+ isBootstrap: boolean
256
+ onRevoke: () => void
257
+ }) {
258
+ const colors = useColors()
259
+ const styles = useMemo(() => makeStyles(colors), [colors])
260
+ const status = token.revokedAt ? 'revoked' : 'active'
261
+ const last = token.lastUsedAt ? new Date(token.lastUsedAt).toLocaleString() : '(never used)'
262
+ return (
263
+ <View style={styles.tokenRow}>
264
+ <View style={styles.tokenInfo}>
265
+ <Text style={styles.tokenLabel}>
266
+ {token.label} {isBootstrap ? <Text style={styles.tokenBootstrap}>· bootstrap</Text> : null}
267
+ </Text>
268
+ <Text style={styles.tokenMeta}>
269
+ {status} · {last}
270
+ </Text>
271
+ </View>
272
+ {!isBootstrap && !token.revokedAt ? (
273
+ <Pressable onPress={onRevoke} hitSlop={10}>
274
+ <Text style={styles.tokenRevoke}>Revoke</Text>
275
+ </Pressable>
276
+ ) : null}
277
+ </View>
278
+ )
279
+ }
280
+
281
+ const THEME_OPTIONS: { value: ThemeMode; label: string }[] = [
282
+ { value: 'system', label: 'System' },
283
+ { value: 'light', label: 'Light' },
284
+ { value: 'dark', label: 'Dark' },
285
+ ]
286
+
287
+ function ThemePicker() {
288
+ const colors = useColors()
289
+ const styles = useMemo(() => makeStyles(colors), [colors])
290
+ const [mode, setMode] = useThemeMode()
291
+ return (
292
+ <View style={styles.segmentedRow}>
293
+ {THEME_OPTIONS.map((opt) => {
294
+ const active = mode === opt.value
295
+ return (
296
+ <Pressable
297
+ key={opt.value}
298
+ onPress={() => {
299
+ void setMode(opt.value)
300
+ }}
301
+ style={[styles.segment, active && styles.segmentActive]}
302
+ >
303
+ <Text style={[styles.segmentText, active && styles.segmentTextActive]}>{opt.label}</Text>
304
+ </Pressable>
305
+ )
306
+ })}
307
+ </View>
308
+ )
309
+ }
310
+
311
+ const makeStyles = (colors: Colors) =>
312
+ StyleSheet.create({
313
+ screen: { flex: 1, backgroundColor: colors.background },
314
+ content: { padding: 20, gap: 4 },
315
+ centered: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24, gap: 12 },
316
+ section: { marginTop: 16 },
317
+ sectionLabel: {
318
+ fontSize: 11,
319
+ color: colors.mocha,
320
+ textTransform: 'uppercase',
321
+ letterSpacing: 0.5,
322
+ marginBottom: 6,
323
+ fontFamily: fonts.bodyMedium,
324
+ },
325
+ sectionBody: { gap: 4 },
326
+ mono: { fontFamily: fonts.mono, fontSize: 13, color: colors.charcoal },
327
+ divider: { height: StyleSheet.hairlineWidth, backgroundColor: colors.border, marginVertical: 16 },
328
+ mintRow: { flexDirection: 'row', gap: 8, alignItems: 'center' },
329
+ mintInput: {
330
+ flex: 1,
331
+ paddingHorizontal: 12,
332
+ paddingVertical: 10,
333
+ borderRadius: radii.md,
334
+ backgroundColor: colors.ivory,
335
+ fontSize: 14,
336
+ color: colors.foreground,
337
+ fontFamily: fonts.body,
338
+ },
339
+ mintBtn: {
340
+ paddingHorizontal: 16,
341
+ paddingVertical: 10,
342
+ borderRadius: radii.md,
343
+ backgroundColor: colors.sapphire,
344
+ },
345
+ mintBtnDisabled: { backgroundColor: colors.mochaLight },
346
+ mintBtnPressed: { backgroundColor: colors.sapphireDeep },
347
+ mintBtnText: { color: colors.primaryForeground, fontSize: 14, fontFamily: fonts.bodyMedium },
348
+ mintedBox: {
349
+ marginTop: 10,
350
+ padding: 12,
351
+ borderRadius: radii.md,
352
+ backgroundColor: colors.sapphireGlow,
353
+ borderWidth: StyleSheet.hairlineWidth,
354
+ borderColor: colors.sapphire,
355
+ gap: 6,
356
+ },
357
+ mintedLabel: {
358
+ fontSize: 11,
359
+ color: colors.accentForeground,
360
+ textTransform: 'uppercase',
361
+ letterSpacing: 0.5,
362
+ fontFamily: fonts.bodyMedium,
363
+ },
364
+ mintedToken: { fontFamily: fonts.mono, fontSize: 12, color: colors.charcoal },
365
+ mintedHint: { fontSize: 11, color: colors.accentForeground, fontFamily: fonts.body },
366
+ tokenRow: {
367
+ flexDirection: 'row',
368
+ alignItems: 'center',
369
+ paddingVertical: 10,
370
+ gap: 12,
371
+ },
372
+ tokenSep: { height: StyleSheet.hairlineWidth, backgroundColor: colors.frost },
373
+ tokenInfo: { flex: 1 },
374
+ tokenLabel: { fontSize: 14, color: colors.foreground, fontFamily: fonts.body },
375
+ tokenBootstrap: { color: colors.mochaLight, fontSize: 11, fontFamily: fonts.body },
376
+ tokenMeta: { fontSize: 11, color: colors.mochaLight, marginTop: 2, fontFamily: fonts.body },
377
+ tokenRevoke: { color: colors.destructive, fontSize: 13, fontFamily: fonts.bodyMedium },
378
+ primaryBtn: {
379
+ marginTop: 12,
380
+ paddingHorizontal: 20,
381
+ paddingVertical: 10,
382
+ borderRadius: radii.md,
383
+ backgroundColor: colors.sapphire,
384
+ },
385
+ primaryBtnText: { color: colors.primaryForeground, fontSize: 14, fontFamily: fonts.bodyMedium },
386
+ linkBtn: { marginTop: 4, padding: 8 },
387
+ linkBtnText: { color: colors.mocha, fontSize: 13, fontFamily: fonts.body },
388
+ dangerBtn: {
389
+ marginTop: 8,
390
+ paddingVertical: 12,
391
+ borderRadius: radii.md,
392
+ backgroundColor: colors.card,
393
+ borderWidth: 1,
394
+ borderColor: colors.destructive,
395
+ alignItems: 'center',
396
+ },
397
+ // ~10% destructive tint, tracks theme via the hex-alpha-suffix trick.
398
+ dangerBtnPressed: { backgroundColor: `${colors.destructive}1A` },
399
+ dangerBtnText: { color: colors.destructive, fontSize: 14, fontFamily: fonts.bodyMedium },
400
+ errorTitle: { fontSize: 18, fontFamily: fonts.bodyBold, color: colors.foreground },
401
+ errorBody: { color: colors.destructive, textAlign: 'center', fontFamily: fonts.body },
402
+ segmentedRow: {
403
+ flexDirection: 'row',
404
+ backgroundColor: colors.ivory,
405
+ borderRadius: radii.md,
406
+ padding: 2,
407
+ gap: 2,
408
+ },
409
+ segment: {
410
+ flex: 1,
411
+ paddingVertical: 8,
412
+ paddingHorizontal: 12,
413
+ borderRadius: radii.sm,
414
+ alignItems: 'center',
415
+ },
416
+ segmentActive: { backgroundColor: colors.card },
417
+ segmentText: { color: colors.mocha, fontSize: 13, fontFamily: fonts.body },
418
+ segmentTextActive: { color: colors.foreground, fontFamily: fonts.bodyMedium },
419
+ })
@@ -0,0 +1,49 @@
1
+ {
2
+ "expo": {
3
+ "name": "Bazilion",
4
+ "slug": "bazilion",
5
+ "version": "0.0.1",
6
+ "orientation": "portrait",
7
+ "scheme": "bazilion",
8
+ "userInterfaceStyle": "automatic",
9
+ "newArchEnabled": true,
10
+ "icon": "./assets/icon.png",
11
+ "splash": {
12
+ "image": "./assets/splash-icon.png",
13
+ "resizeMode": "contain",
14
+ "backgroundColor": "#F5F0E8"
15
+ },
16
+ "ios": {
17
+ "supportsTablet": true,
18
+ "bundleIdentifier": "dev.bazilion.mobile"
19
+ },
20
+ "android": {
21
+ "package": "dev.bazilion.mobile",
22
+ "adaptiveIcon": {
23
+ "foregroundImage": "./assets/adaptive-icon.png",
24
+ "backgroundColor": "#F5F0E8"
25
+ }
26
+ },
27
+ "web": {
28
+ "favicon": "./assets/favicon.png"
29
+ },
30
+ "plugins": [
31
+ "expo-router",
32
+ [
33
+ "expo-camera",
34
+ {
35
+ "cameraPermission": "Allow Bazilion to use the camera to scan pairing QR codes."
36
+ }
37
+ ],
38
+ [
39
+ "expo-secure-store",
40
+ {
41
+ "faceIDPermission": "Allow Bazilion to authenticate with Face ID to unlock stored credentials."
42
+ }
43
+ ]
44
+ ],
45
+ "experiments": {
46
+ "typedRoutes": true
47
+ }
48
+ }
49
+ }
Binary file
Binary file
@@ -0,0 +1,6 @@
1
+ module.exports = (api) => {
2
+ api.cache(true)
3
+ return {
4
+ presets: ['babel-preset-expo'],
5
+ }
6
+ }
@@ -0,0 +1,28 @@
1
+ // pnpm + Expo monorepo config.
2
+ //
3
+ // - `watchFolders` gets the workspace root APPENDED (not replaced) so Metro
4
+ // picks up live edits to `@bazilion/client` / `@bazilion/api-types` during
5
+ // dev while still keeping Expo's own watched paths.
6
+ //
7
+ // - `nodeModulesPaths` also includes the workspace root's `node_modules` so
8
+ // hoisted deps resolve. Hierarchical lookup stays enabled (the Expo
9
+ // default) — this lets Metro fall back to pnpm's `.pnpm/*` symlink store
10
+ // for transitive peers that aren't hoisted to `apps/mobile/node_modules`.
11
+ // Safe as long as there's exactly one copy of `react-native` / `react` in
12
+ // the tree; a fresh `pnpm install` after SDK bumps keeps it that way.
13
+
14
+ const { getDefaultConfig } = require('expo/metro-config')
15
+ const path = require('node:path')
16
+
17
+ const projectRoot = __dirname
18
+ const workspaceRoot = path.resolve(projectRoot, '../..')
19
+
20
+ const config = getDefaultConfig(projectRoot)
21
+
22
+ config.watchFolders = [...(config.watchFolders ?? []), workspaceRoot]
23
+ config.resolver.nodeModulesPaths = [
24
+ path.resolve(projectRoot, 'node_modules'),
25
+ path.resolve(workspaceRoot, 'node_modules'),
26
+ ]
27
+
28
+ module.exports = config
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@bazilion/mobile",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "main": "expo-router/entry",
6
+ "scripts": {
7
+ "start": "expo start",
8
+ "ios": "expo start --ios",
9
+ "android": "expo start --android",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@babel/runtime": "^7.25.0",
14
+ "@bazilion/api-types": "workspace:*",
15
+ "@bazilion/client": "workspace:*",
16
+ "@expo-google-fonts/dm-sans": "^0.4.2",
17
+ "@expo-google-fonts/dm-serif-display": "^0.4.2",
18
+ "@expo-google-fonts/jetbrains-mono": "^0.4.1",
19
+ "@expo/metro-runtime": "^6.1.2",
20
+ "@react-navigation/elements": "^2.9.14",
21
+ "expo": "^54.0.0",
22
+ "expo-camera": "~17.0.10",
23
+ "expo-constants": "~18.0.13",
24
+ "expo-font": "^55.0.6",
25
+ "expo-linking": "~8.0.11",
26
+ "expo-router": "~6.0.23",
27
+ "expo-secure-store": "~15.0.8",
28
+ "expo-status-bar": "~3.0.9",
29
+ "react": "19.1.0",
30
+ "react-dom": "19.1.0",
31
+ "react-native": "0.81.5",
32
+ "react-native-gesture-handler": "~2.28.0",
33
+ "react-native-markdown-display": "^7.0.2",
34
+ "react-native-reanimated": "~4.1.1",
35
+ "react-native-safe-area-context": "~5.6.0",
36
+ "react-native-screens": "~4.16.0",
37
+ "react-native-web": "^0.21.0",
38
+ "react-native-worklets": "0.5.1"
39
+ },
40
+ "devDependencies": {
41
+ "@types/react": "~19.1.10",
42
+ "typescript": "^5.7.0"
43
+ }
44
+ }
@@ -0,0 +1,66 @@
1
+ import { type BazilionClient, createClient } from '@bazilion/client'
2
+ import * as SecureStore from 'expo-secure-store'
3
+
4
+ const KEY_SERVER = 'bazilion.server'
5
+ const KEY_TOKEN = 'bazilion.token'
6
+
7
+ export interface Credentials {
8
+ server: string
9
+ token: string
10
+ }
11
+
12
+ export async function loadCredentials(): Promise<Credentials | null> {
13
+ const [server, token] = await Promise.all([
14
+ SecureStore.getItemAsync(KEY_SERVER),
15
+ SecureStore.getItemAsync(KEY_TOKEN),
16
+ ])
17
+ if (!server || !token) return null
18
+ return { server, token }
19
+ }
20
+
21
+ export async function saveCredentials(creds: Credentials): Promise<void> {
22
+ await Promise.all([
23
+ SecureStore.setItemAsync(KEY_SERVER, creds.server),
24
+ SecureStore.setItemAsync(KEY_TOKEN, creds.token),
25
+ ])
26
+ }
27
+
28
+ export async function clearCredentials(): Promise<void> {
29
+ await Promise.all([
30
+ SecureStore.deleteItemAsync(KEY_SERVER),
31
+ SecureStore.deleteItemAsync(KEY_TOKEN),
32
+ ])
33
+ }
34
+
35
+ /**
36
+ * Probe the daemon so we surface a clear error on the pairing screen instead
37
+ * of a confusing network failure on the first protected request. 5-second
38
+ * timeout because the daemon is a local-network service — if it can't answer
39
+ * in that long, something's wrong.
40
+ */
41
+ export async function verifyCredentials(creds: Credentials): Promise<void> {
42
+ const ctrl = new AbortController()
43
+ const timer = setTimeout(() => ctrl.abort(), 5000)
44
+ try {
45
+ const res = await fetch(`${creds.server}/api/health`, {
46
+ headers: {
47
+ authorization: `Bearer ${creds.token}`,
48
+ origin: creds.server,
49
+ },
50
+ signal: ctrl.signal,
51
+ })
52
+ if (res.status === 401) throw new Error('server rejected the token')
53
+ if (!res.ok) throw new Error(`server returned ${res.status} from /api/health`)
54
+ } catch (err) {
55
+ if (err instanceof Error && err.name === 'AbortError') {
56
+ throw new Error(`could not reach ${creds.server} within 5s`)
57
+ }
58
+ throw err
59
+ } finally {
60
+ clearTimeout(timer)
61
+ }
62
+ }
63
+
64
+ export function clientFor(creds: Credentials): BazilionClient {
65
+ return createClient({ serverUrl: creds.server, token: creds.token })
66
+ }
@@ -0,0 +1,48 @@
1
+ export interface ParsedPairing {
2
+ server: string
3
+ token: string
4
+ }
5
+
6
+ export class PairUrlError extends Error {
7
+ constructor(message: string) {
8
+ super(message)
9
+ this.name = 'PairUrlError'
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Parse a `bazilion://pair?server=<url>&token=<t>` URL emitted by
15
+ * `bazilion token create --qr`. The scheme and host are both mandatory so a
16
+ * stray http(s) URL or a typo surfaces as a clear error instead of a
17
+ * confusing downstream network failure.
18
+ */
19
+ export function parsePairingUrl(raw: string): ParsedPairing {
20
+ let url: URL
21
+ try {
22
+ url = new URL(raw)
23
+ } catch {
24
+ throw new PairUrlError(`not a valid URL: ${raw}`)
25
+ }
26
+ if (url.protocol !== 'bazilion:') {
27
+ throw new PairUrlError(`expected bazilion:// scheme, got ${url.protocol}`)
28
+ }
29
+ // Custom-scheme URLs parse `pair` as the hostname, not pathname.
30
+ if (url.hostname !== 'pair') {
31
+ throw new PairUrlError(`expected bazilion://pair, got bazilion://${url.hostname}`)
32
+ }
33
+ const server = url.searchParams.get('server')
34
+ const token = url.searchParams.get('token')
35
+ if (!server) throw new PairUrlError('missing ?server=')
36
+ if (!token) throw new PairUrlError('missing ?token=')
37
+ try {
38
+ const serverUrl = new URL(server)
39
+ if (serverUrl.protocol !== 'http:' && serverUrl.protocol !== 'https:') {
40
+ throw new PairUrlError(`server must be http(s), got ${serverUrl.protocol}`)
41
+ }
42
+ } catch (err) {
43
+ if (err instanceof PairUrlError) throw err
44
+ throw new PairUrlError(`server is not a valid URL: ${server}`)
45
+ }
46
+ // Strip trailing slash so `${server}${path}` never produces `//`.
47
+ return { server: server.replace(/\/$/, ''), token }
48
+ }