@nervur-org/dock 0.3.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 (285) hide show
  1. package/GETTING_STARTED.md +128 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +6 -0
  4. package/README.md +37 -0
  5. package/api/quo-api.md +196 -0
  6. package/api/route.ts +227 -0
  7. package/beings/GLOSSARY.md +118 -0
  8. package/beings/WORLDS.md +567 -0
  9. package/beings/avatar.ts +101 -0
  10. package/beings/carry.ts +159 -0
  11. package/beings/clock.ts +552 -0
  12. package/beings/courier.ts +107 -0
  13. package/beings/desk.ts +128 -0
  14. package/beings/doorbell.ts +129 -0
  15. package/beings/envoy.ts +218 -0
  16. package/beings/index.ts +16 -0
  17. package/beings/join.ts +51 -0
  18. package/beings/link.ts +104 -0
  19. package/beings/look.ts +139 -0
  20. package/beings/porter.ts +43 -0
  21. package/beings/presence.ts +39 -0
  22. package/beings/quo-app-bells.md +280 -0
  23. package/beings/quo-dock.md +1942 -0
  24. package/beings/setup.ts +45 -0
  25. package/beings/side.ts +60 -0
  26. package/beings/user.ts +459 -0
  27. package/beings/way.ts +65 -0
  28. package/cli/bundles.ts +20 -0
  29. package/cli/client.ts +73 -0
  30. package/cli/daemon.ts +581 -0
  31. package/cli/estate/Caddyfile +33 -0
  32. package/cli/estate/quo.service +35 -0
  33. package/cli/estate/systems.quo.plist +44 -0
  34. package/cli/estate.ts +48 -0
  35. package/cli/folder.ts +27 -0
  36. package/cli/oauth.ts +32 -0
  37. package/cli/pilot.ts +38 -0
  38. package/cli/quo-dock.md +1942 -0
  39. package/cli/quo.ts +203 -0
  40. package/cli/stand.ts +160 -0
  41. package/dist/api/route.d.ts +21 -0
  42. package/dist/api/route.js +193 -0
  43. package/dist/beings/avatar.d.ts +32 -0
  44. package/dist/beings/avatar.js +100 -0
  45. package/dist/beings/carry.d.ts +12 -0
  46. package/dist/beings/carry.js +174 -0
  47. package/dist/beings/clock.d.ts +161 -0
  48. package/dist/beings/clock.js +528 -0
  49. package/dist/beings/courier.d.ts +50 -0
  50. package/dist/beings/courier.js +96 -0
  51. package/dist/beings/desk.d.ts +53 -0
  52. package/dist/beings/desk.js +135 -0
  53. package/dist/beings/doorbell.d.ts +78 -0
  54. package/dist/beings/doorbell.js +133 -0
  55. package/dist/beings/envoy.d.ts +54 -0
  56. package/dist/beings/envoy.js +211 -0
  57. package/dist/beings/index.d.ts +14 -0
  58. package/dist/beings/index.js +16 -0
  59. package/dist/beings/join.d.ts +15 -0
  60. package/dist/beings/join.js +42 -0
  61. package/dist/beings/link.d.ts +14 -0
  62. package/dist/beings/link.js +91 -0
  63. package/dist/beings/look.d.ts +30 -0
  64. package/dist/beings/look.js +104 -0
  65. package/dist/beings/porter.d.ts +14 -0
  66. package/dist/beings/porter.js +41 -0
  67. package/dist/beings/presence.d.ts +8 -0
  68. package/dist/beings/presence.js +21 -0
  69. package/dist/beings/setup.d.ts +3 -0
  70. package/dist/beings/setup.js +39 -0
  71. package/dist/beings/side.d.ts +32 -0
  72. package/dist/beings/side.js +37 -0
  73. package/dist/beings/user.d.ts +315 -0
  74. package/dist/beings/user.js +462 -0
  75. package/dist/beings/way.d.ts +19 -0
  76. package/dist/beings/way.js +28 -0
  77. package/dist/cli/bundles.d.ts +5 -0
  78. package/dist/cli/bundles.js +17 -0
  79. package/dist/cli/client.d.ts +15 -0
  80. package/dist/cli/client.js +58 -0
  81. package/dist/cli/daemon.d.ts +50 -0
  82. package/dist/cli/daemon.js +584 -0
  83. package/dist/cli/estate/Caddyfile +33 -0
  84. package/dist/cli/estate/quo.service +35 -0
  85. package/dist/cli/estate/systems.quo.plist +44 -0
  86. package/dist/cli/estate.d.ts +5 -0
  87. package/dist/cli/estate.js +50 -0
  88. package/dist/cli/folder.d.ts +2 -0
  89. package/dist/cli/folder.js +25 -0
  90. package/dist/cli/oauth.d.ts +2 -0
  91. package/dist/cli/oauth.js +32 -0
  92. package/dist/cli/pilot.d.ts +5 -0
  93. package/dist/cli/pilot.js +22 -0
  94. package/dist/cli/quo.d.ts +2 -0
  95. package/dist/cli/quo.js +201 -0
  96. package/dist/cli/stand.d.ts +37 -0
  97. package/dist/cli/stand.js +110 -0
  98. package/dist/dock/agents.d.ts +102 -0
  99. package/dist/dock/agents.js +105 -0
  100. package/dist/dock/apns.d.ts +27 -0
  101. package/dist/dock/apns.js +98 -0
  102. package/dist/dock/console.d.ts +6 -0
  103. package/dist/dock/console.js +20 -0
  104. package/dist/dock/dialers.d.ts +56 -0
  105. package/dist/dock/dialers.js +75 -0
  106. package/dist/dock/faculty.d.ts +24 -0
  107. package/dist/dock/faculty.js +142 -0
  108. package/dist/dock/fcm.d.ts +13 -0
  109. package/dist/dock/fcm.js +77 -0
  110. package/dist/dock/grants.d.ts +41 -0
  111. package/dist/dock/grants.js +99 -0
  112. package/dist/dock/http2.d.ts +4 -0
  113. package/dist/dock/http2.js +68 -0
  114. package/dist/dock/index.d.ts +41 -0
  115. package/dist/dock/index.js +113 -0
  116. package/dist/dock/local.d.ts +13 -0
  117. package/dist/dock/local.js +44 -0
  118. package/dist/dock/location.d.ts +19 -0
  119. package/dist/dock/location.js +11 -0
  120. package/dist/dock/notifications.d.ts +38 -0
  121. package/dist/dock/notifications.js +60 -0
  122. package/dist/dock/push.d.ts +79 -0
  123. package/dist/dock/push.js +132 -0
  124. package/dist/dock/service.d.ts +4 -0
  125. package/dist/dock/service.js +107 -0
  126. package/dist/dock/timer.d.ts +52 -0
  127. package/dist/dock/timer.js +149 -0
  128. package/dist/dock/webpush.d.ts +12 -0
  129. package/dist/dock/webpush.js +144 -0
  130. package/dist/harbor/browser.d.ts +15 -0
  131. package/dist/harbor/browser.js +82 -0
  132. package/dist/harbor/capacitor.d.ts +28 -0
  133. package/dist/harbor/capacitor.js +218 -0
  134. package/dist/harbor/disk.d.ts +27 -0
  135. package/dist/harbor/disk.js +249 -0
  136. package/dist/harbor/edge/alarm.d.ts +10 -0
  137. package/dist/harbor/edge/alarm.js +38 -0
  138. package/dist/harbor/edge/edge.d.ts +54 -0
  139. package/dist/harbor/edge/edge.js +370 -0
  140. package/dist/harbor/edge/exercise.d.ts +7 -0
  141. package/dist/harbor/edge/exercise.js +232 -0
  142. package/dist/harbor/edge/given.d.ts +26 -0
  143. package/dist/harbor/edge/given.js +42 -0
  144. package/dist/harbor/edge/platform.d.ts +39 -0
  145. package/dist/harbor/edge/storage.d.ts +30 -0
  146. package/dist/harbor/edge/storage.js +180 -0
  147. package/dist/harbor/edge/worker.d.ts +17 -0
  148. package/dist/harbor/edge/worker.js +67 -0
  149. package/dist/harbor/files.d.ts +15 -0
  150. package/dist/harbor/files.js +220 -0
  151. package/dist/harbor/http.d.ts +19 -0
  152. package/dist/harbor/http.js +177 -0
  153. package/dist/harbor/idb.d.ts +15 -0
  154. package/dist/harbor/idb.js +0 -0
  155. package/dist/harbor/quo.d.ts +10 -0
  156. package/dist/harbor/quo.js +42 -0
  157. package/dist/harbor/resolve.d.ts +16 -0
  158. package/dist/harbor/resolve.js +17 -0
  159. package/dist/harbor/seal.d.ts +3 -0
  160. package/dist/harbor/seal.js +25 -0
  161. package/dist/harbor/sealed.d.ts +35 -0
  162. package/dist/harbor/sealed.js +162 -0
  163. package/dist/harbor/tauri.d.ts +16 -0
  164. package/dist/harbor/tauri.js +59 -0
  165. package/dist/harbor/ward.d.ts +9 -0
  166. package/dist/harbor/ward.js +15 -0
  167. package/dist/human/app.d.ts +32 -0
  168. package/dist/human/app.js +527 -0
  169. package/dist/human/apps.d.ts +10 -0
  170. package/dist/human/apps.js +29 -0
  171. package/dist/human/dom.d.ts +2 -0
  172. package/dist/human/dom.js +15 -0
  173. package/dist/human/door.d.ts +19 -0
  174. package/dist/human/door.js +92 -0
  175. package/dist/human/guest.d.ts +3 -0
  176. package/dist/human/guest.js +33 -0
  177. package/dist/human/html.d.ts +55 -0
  178. package/dist/human/html.js +451 -0
  179. package/dist/human/local.d.ts +7 -0
  180. package/dist/human/local.js +10 -0
  181. package/dist/human/mark.d.ts +8 -0
  182. package/dist/human/mark.js +47 -0
  183. package/dist/human/move.d.ts +17 -0
  184. package/dist/human/move.js +90 -0
  185. package/dist/human/screen.d.ts +19 -0
  186. package/dist/human/screen.js +119 -0
  187. package/dist/human/style.d.ts +2 -0
  188. package/dist/human/style.js +60 -0
  189. package/dist/human/tab.bundle.txt +4775 -0
  190. package/dist/human/tab.d.ts +15 -0
  191. package/dist/human/tab.js +409 -0
  192. package/dist/human/tree.d.ts +50 -0
  193. package/dist/human/tree.js +157 -0
  194. package/dist/human/wake.d.ts +34 -0
  195. package/dist/human/wake.js +179 -0
  196. package/dist/human/web.d.ts +33 -0
  197. package/dist/human/web.js +184 -0
  198. package/dist/human/worker.bundle.txt +3868 -0
  199. package/dist/human/worker.d.ts +1 -0
  200. package/dist/human/worker.js +81 -0
  201. package/dist/human/worlds.d.ts +24 -0
  202. package/dist/human/worlds.js +99 -0
  203. package/dist/mcp/agent.d.ts +10 -0
  204. package/dist/mcp/agent.js +78 -0
  205. package/dist/mcp/allow.d.ts +20 -0
  206. package/dist/mcp/allow.js +88 -0
  207. package/dist/mcp/http.d.ts +27 -0
  208. package/dist/mcp/http.js +92 -0
  209. package/dist/mcp/oauth.d.ts +91 -0
  210. package/dist/mcp/oauth.js +241 -0
  211. package/dist/mcp/route.d.ts +15 -0
  212. package/dist/mcp/route.js +42 -0
  213. package/dist/mcp/runner.d.ts +45 -0
  214. package/dist/mcp/runner.js +99 -0
  215. package/dist/mcp/server.d.ts +11 -0
  216. package/dist/mcp/server.js +102 -0
  217. package/dock/agents.ts +121 -0
  218. package/dock/apns.ts +138 -0
  219. package/dock/console.ts +23 -0
  220. package/dock/dialers.ts +85 -0
  221. package/dock/faculty.ts +148 -0
  222. package/dock/fcm.ts +103 -0
  223. package/dock/grants.ts +111 -0
  224. package/dock/http2.ts +70 -0
  225. package/dock/index.ts +133 -0
  226. package/dock/local.ts +48 -0
  227. package/dock/location.ts +22 -0
  228. package/dock/notifications.ts +67 -0
  229. package/dock/push.ts +161 -0
  230. package/dock/quo-app-bells.md +280 -0
  231. package/dock/quo-dock.md +1942 -0
  232. package/dock/service.ts +100 -0
  233. package/dock/timer.ts +174 -0
  234. package/dock/webpush.ts +182 -0
  235. package/harbor/browser.ts +97 -0
  236. package/harbor/capacitor.ts +223 -0
  237. package/harbor/disk.ts +237 -0
  238. package/harbor/edge/alarm.ts +48 -0
  239. package/harbor/edge/edge.ts +407 -0
  240. package/harbor/edge/exercise.ts +241 -0
  241. package/harbor/edge/given.ts +106 -0
  242. package/harbor/edge/platform.d.ts +39 -0
  243. package/harbor/edge/storage.ts +179 -0
  244. package/harbor/edge/text.d.ts +15 -0
  245. package/harbor/edge/worker.ts +70 -0
  246. package/harbor/edge/wrangler.toml +30 -0
  247. package/harbor/files.ts +221 -0
  248. package/harbor/http.ts +188 -0
  249. package/harbor/idb.ts +0 -0
  250. package/harbor/quo-harbor.md +850 -0
  251. package/harbor/quo.ts +44 -0
  252. package/harbor/resolve.ts +31 -0
  253. package/harbor/seal.ts +26 -0
  254. package/harbor/sealed.ts +183 -0
  255. package/harbor/tauri.ts +79 -0
  256. package/harbor/ward.ts +28 -0
  257. package/human/app.ts +580 -0
  258. package/human/apps.ts +47 -0
  259. package/human/dom.ts +21 -0
  260. package/human/door.ts +113 -0
  261. package/human/guest.ts +34 -0
  262. package/human/html.ts +490 -0
  263. package/human/local.ts +30 -0
  264. package/human/mark.ts +53 -0
  265. package/human/move.ts +122 -0
  266. package/human/quo-human.md +1042 -0
  267. package/human/screen.ts +136 -0
  268. package/human/style.ts +63 -0
  269. package/human/tab.bundle.txt +4775 -0
  270. package/human/tab.ts +446 -0
  271. package/human/tree.ts +217 -0
  272. package/human/wake.ts +191 -0
  273. package/human/web.ts +259 -0
  274. package/human/worker.bundle.txt +3868 -0
  275. package/human/worker.ts +124 -0
  276. package/human/worlds.ts +127 -0
  277. package/mcp/agent.ts +94 -0
  278. package/mcp/allow.ts +131 -0
  279. package/mcp/http.ts +92 -0
  280. package/mcp/oauth.ts +270 -0
  281. package/mcp/quo-mcp.md +343 -0
  282. package/mcp/route.ts +45 -0
  283. package/mcp/runner.ts +136 -0
  284. package/mcp/server.ts +101 -0
  285. package/package.json +121 -0
@@ -0,0 +1,33 @@
1
+ # Four hostnames, one daemon, one loopback port. Caddy faces the world and
2
+ # terminates TLS; the daemon never does. Each hostname is a route, and a
3
+ # route is a path on the daemon's HTTP door. A route says the grammar a
4
+ # caller speaks and never who the caller is: a bearer the box granted is
5
+ # honoured on every one of them. Replace DOMAIN.
6
+ #
7
+ # mcp. the model side: streamable HTTP MCP, with the OAuth dance in front
8
+ # web. the bundle for plain tabs, and the exchange pages
9
+ # quo. the socket door for other harbors: requests in, sockets held, the rendezvous
10
+ # api. a describe and its asks as plain JSON, for a program
11
+ #
12
+ # There is no cli. route. The root owner is the unix socket, reached on the
13
+ # device or over SSH, and never through here.
14
+
15
+ mcp.DOMAIN {
16
+ reverse_proxy 127.0.0.1:8787
17
+ rewrite * /mcp{uri}
18
+ }
19
+
20
+ web.DOMAIN {
21
+ reverse_proxy 127.0.0.1:8787
22
+ rewrite * /web{uri}
23
+ }
24
+
25
+ quo.DOMAIN {
26
+ reverse_proxy 127.0.0.1:8787
27
+ rewrite * /quo{uri}
28
+ }
29
+
30
+ api.DOMAIN {
31
+ reverse_proxy 127.0.0.1:8787
32
+ rewrite * /api{uri}
33
+ }
@@ -0,0 +1,35 @@
1
+ # The daemon on a droplet, as one user, forever. Install with:
2
+ # sudo cp droplet/quo.service /etc/systemd/system/quo.service
3
+ # sudo systemctl enable --now quo
4
+ # The unit runs as the `quo` user, whose home holds the estate folder and
5
+ # the harbor folder, so the seed, the lease and the sockets are that user's
6
+ # alone. The HTTP door is loopback only; Caddy fronts it. See Caddyfile
7
+ # beside this file.
8
+ [Unit]
9
+ Description=quo: one harbor on this device
10
+ After=network.target
11
+
12
+ [Service]
13
+ User=quo
14
+ Group=quo
15
+ Environment=QUO_DIR=/home/quo/.quo
16
+ Environment=QUO_HTTP=8787
17
+ # CLAUDE_CODE_OAUTH_TOKEN, when an agent folder runs `claude -p`: the agent
18
+ # reads its credential from the device, never from cells.
19
+ EnvironmentFile=-/etc/quo/env
20
+ WorkingDirectory=/home/quo/ESTATE
21
+ ExecStart=/usr/bin/node /home/quo/ESTATE/node_modules/@nervur-org/dock/dist/cli/quo.js serve
22
+ Restart=always
23
+ RestartSec=2
24
+ KillSignal=SIGTERM
25
+ TimeoutStopSec=10
26
+ NoNewPrivileges=true
27
+ ProtectSystem=strict
28
+ # Agents run in their folders and Claude Code keeps its own state in the
29
+ # user's home; both must be writable for a run to happen, and a harbor
30
+ # with no agent has neither.
31
+ ReadWritePaths=/home/quo/.quo -/home/quo/agents -/home/quo/.claude -/home/quo/.claude.json /tmp
32
+ PrivateTmp=false
33
+
34
+ [Install]
35
+ WantedBy=multi-user.target
@@ -0,0 +1,44 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ The daemon on a Mac, as the user, forever: a launchd agent. It holds the
4
+ harbor at ~/.quo and the socket to every quo. route ~/.quo/dial.json
5
+ names, so a Mac behind NAT is reached through the droplet it dialed. The
6
+ daemon runs the dock's emitted `dist/cli/quo.js` from where the estate
7
+ installed it, never the source. Install, with node on the path:
8
+
9
+ sed "s#HOME#$HOME#g; s#NODE#$(which node)#g" mac/systems.quo.plist > ~/Library/LaunchAgents/systems.quo.plist
10
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/systems.quo.plist
11
+
12
+ Remove with `launchctl bootout gui/$(id -u)/systems.quo`; restart after a
13
+ wire change with `launchctl kickstart -k gui/$(id -u)/systems.quo`. The
14
+ log is ~/.quo/serve.log. No HTTP door: a Mac is a dialer, not a listener.
15
+ -->
16
+ <plist version="1.0">
17
+ <dict>
18
+ <key>Label</key>
19
+ <string>systems.quo</string>
20
+ <key>ProgramArguments</key>
21
+ <array>
22
+ <string>NODE</string>
23
+ <string>HOME/ESTATE/node_modules/@nervur-org/dock/dist/cli/quo.js</string>
24
+ <string>serve</string>
25
+ </array>
26
+ <key>EnvironmentVariables</key>
27
+ <dict>
28
+ <key>QUO_DIR</key>
29
+ <string>HOME/.quo</string>
30
+ </dict>
31
+ <key>WorkingDirectory</key>
32
+ <string>HOME/ESTATE</string>
33
+ <key>RunAtLoad</key>
34
+ <true/>
35
+ <key>KeepAlive</key>
36
+ <true/>
37
+ <key>ThrottleInterval</key>
38
+ <integer>2</integer>
39
+ <key>StandardOutPath</key>
40
+ <string>HOME/.quo/serve.log</string>
41
+ <key>StandardErrorPath</key>
42
+ <string>HOME/.quo/serve.log</string>
43
+ </dict>
44
+ </plist>
package/cli/estate.ts ADDED
@@ -0,0 +1,48 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // `quo estate DIR --domain D`: an estate folder, written once, of the shape
3
+ // every estate has. One folder per harbor: `droplet/`, the quo directory
4
+ // that device runs minus what it mints, its routes under the domain, no
5
+ // agents, no schedules, no classes of its own, and that device's unit and
6
+ // Caddyfile; and `mac/`, the launchd agent that runs the daemon on an
7
+ // operator's Mac as a dialer, with the dial list it holds. A package file
8
+ // that depends on this dock and nothing else, and one document to fill in.
9
+ // An estate needs nothing the dock does not give it, and this is the dock
10
+ // giving it.
11
+ import { existsSync } from 'node:fs';
12
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
13
+ import { basename, join } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { DOMAIN, routesFor } from './daemon.ts';
16
+
17
+ // This package's own version, read beside the emitted or the source tree.
18
+ async function version(): Promise<string> {
19
+ for (const rel of ['../package.json', '../../package.json']) {
20
+ const at = fileURLToPath(new URL(rel, import.meta.url));
21
+ if (!existsSync(at)) continue;
22
+ const pkg = JSON.parse(await readFile(at, 'utf8')) as { name?: string; version?: string };
23
+ if (pkg.name === '@nervur-org/dock' && pkg.version) return pkg.version;
24
+ }
25
+ return '0.1.0';
26
+ }
27
+ const template = (name: string) => readFile(fileURLToPath(new URL(`estate/${name}`, import.meta.url)), 'utf8');
28
+
29
+ export async function estate(dir: string, domain: string): Promise<{ dir: string; name: string; files: string[] }> {
30
+ if (!DOMAIN.test(domain)) throw new Error(`estate: ${domain} is not a domain`);
31
+ if (existsSync(join(dir, 'package.json'))) throw new Error(`estate: ${dir} already holds a package`);
32
+ const name = basename(dir);
33
+ const files: Record<string, string> = {
34
+ 'package.json': JSON.stringify({ name, private: true, description: `The ${name} estate: one owner's harbors, built on the dock and nothing else.`, type: 'module', dependencies: { '@nervur-org/dock': `^${await version()}` } }, null, 2) + '\n',
35
+ [`${name}.md`]: `# ${name}\n\nAn estate: all of one owner's harbors. One folder per harbor, each the quo\ndirectory that device runs minus what it mints, plus that device's unit.\n\`droplet/\` is the first: its routes under \`${domain}\`, its agents, its\nclasses, its systemd unit and its Caddyfile. This folder depends on the\ndock and nothing else.\n\n## The droplet\n\nWhere it is, how it is reached, and what was done to stand it up: yours\nto write.\n`,
36
+ 'droplet/routes.json': JSON.stringify(routesFor(domain)) + '\n',
37
+ 'droplet/stand.ts': `// SPDX-License-Identifier: Apache-2.0\n// What the root stands up on this harbor, once, after the inits and with no\n// daemon over the folder: the beings the estate boots for its own working\n// and the relations between them, placed on real invitations. Run as\n// \`node droplet/stand.ts <dir>\`; twice is once.\nimport { stand } from '@nervur-org/dock/stand';\n\nconsole.log(await stand(process.argv[2] ?? process.env.QUO_DIR ?? '', { boot: [], wire: [] }));\n`,
38
+ 'droplet/classes/index.ts': '// The classes this harbor holds beside the dock\'s built-in ones, exported\n// by name.\nexport {};\n',
39
+ 'droplet/quo.service': (await template('quo.service')).replaceAll('ESTATE', name),
40
+ 'droplet/Caddyfile': (await template('Caddyfile')).replaceAll('DOMAIN', domain),
41
+ 'mac/systems.quo.plist': (await template('systems.quo.plist')).replaceAll('ESTATE', name),
42
+ };
43
+ for (const [rel, text] of Object.entries(files)) {
44
+ await mkdir(join(dir, rel, '..'), { recursive: true });
45
+ await writeFile(join(dir, rel), text);
46
+ }
47
+ return { dir, name, files: Object.keys(files) };
48
+ }
package/cli/folder.ts ADDED
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The harbor folder as the web route asks for it. A route reads no file, so
3
+ // what a world keeps beside its wards is answered here, where the daemon
4
+ // already has a disk: `design/` for the face it wears and `tab/` for its own
5
+ // code for the tab. Nothing is held between requests, because a folder is
6
+ // worn, built and changed while the daemon runs and a restart is not how a
7
+ // world puts on a face.
8
+ //
9
+ // A world wears a design only when it holds `tokens.css`: that file is the
10
+ // design, and a folder with fonts and no tokens wears nothing, so nothing of
11
+ // it is served.
12
+ import { access, readFile } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ import { TAB_CODE, TAB_SOURCE, type Folder } from '../human/web.ts';
15
+
16
+ const DESIGN = 'design';
17
+
18
+ export function diskFolder(dir: string): Folder {
19
+ const there = (at: string) => access(at).then(() => true, () => false);
20
+ const tokens = join(dir, DESIGN, 'tokens.css');
21
+ return {
22
+ wearing: async () => ((await there(tokens)) ? { tokens: true, fonts: await there(join(dir, DESIGN, 'fonts.css')) } : { tokens: false, fonts: false }),
23
+ design: async (file) => ((await there(tokens)) ? readFile(join(dir, DESIGN, file)).then((b) => new Uint8Array(b), () => null) : null),
24
+ serving: async () => (await there(join(dir, TAB_CODE))) || (await there(join(dir, TAB_SOURCE))),
25
+ beings: () => readFile(join(dir, TAB_CODE), 'utf8').catch(() => null),
26
+ };
27
+ }
package/cli/oauth.ts ADDED
@@ -0,0 +1,32 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The OAuth dance as the daemon keeps it: `<dir>/oauth.json`, the route's
3
+ // own file and nobody else's, written whole through a temporary name so a
4
+ // crash mid-write leaves the last good dance behind. A route reads no file,
5
+ // so this is where the disk is, beside the folder and the wards.
6
+ //
7
+ // The writes are queued because the dance moves on a request and requests
8
+ // overlap: two saves racing on one name is how a file ends up half of each.
9
+ import { readFile, writeFile, rename } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { emptyDance, type Dance, type Store } from '../mcp/oauth.ts';
12
+
13
+ export function danceOnDisk(dir: string): Store {
14
+ const file = join(dir, 'oauth.json');
15
+ let queue = Promise.resolve();
16
+ return {
17
+ load: async () => {
18
+ const read = await readFile(file, 'utf8').catch(() => null);
19
+ if (read === null) return emptyDance();
20
+ try {
21
+ return { ...emptyDance(), ...(JSON.parse(read) as Partial<Dance>) };
22
+ } catch {
23
+ return emptyDance(); // a dance nobody can read is a dance nobody is in the middle of
24
+ }
25
+ },
26
+ save: (dance: Dance) =>
27
+ (queue = queue.then(async () => {
28
+ await writeFile(file + '.tmp', JSON.stringify(dance), { mode: 0o600 });
29
+ await rename(file + '.tmp', file);
30
+ })),
31
+ };
32
+ }
package/cli/pilot.ts ADDED
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The owner pilot: `quo pilot`. The model side pointed at a ward instead of
3
+ // an avatar. The ward is a being to her owner, so her describe is the tool
4
+ // list and a tool call is an owner ask, exactly as for anyone; nothing here
5
+ // names an ask. Whoever runs this process is the owner, by the device's own
6
+ // rules: the root of the ward here, through the daemon's socket, or, with
7
+ // `via`, an owner at another ward's door, on a standing the user being here
8
+ // holds there. The owner creates and places; the work goes through
9
+ // `quo side`, under a gate.
10
+ //
11
+ // It stands here and not in `mcp/`, because what it is made of is the
12
+ // daemon's socket: `mcp/` holds the model side itself, the subject it is
13
+ // given comes from a client of the daemon, and the client is this folder's.
14
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
15
+ import type { Answer, Blueprint, JsonObject } from '@nervur-org/nervur';
16
+ import { ask } from './client.ts';
17
+ import type { Serving } from '../beings/side.ts';
18
+ import { mcpSide, type Subject } from '../mcp/server.ts';
19
+
20
+ const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
21
+
22
+ // The owner hears objects: the socket's own failure, a silence and a word
23
+ // each come back as an error object named for what it was.
24
+ export function owner(dir: string, ward: string, via?: string): Subject {
25
+ const one = async (method: string | undefined, args: JsonObject): Promise<Answer> => {
26
+ const out = await ask(dir, method, args, ward, via);
27
+ if ('error' in out) return { error: out.error };
28
+ const r = out.result;
29
+ if (isRecord(r) && r.silence === true) return { error: 'silence' };
30
+ if (isRecord(r) && typeof r.word === 'string') return { error: r.word };
31
+ return r as Answer;
32
+ };
33
+ return { tools: () => one(undefined, {}) as Promise<Blueprint | { error: string }>, call: (name, args) => one(name, args ?? {}), ears: new Set() };
34
+ }
35
+
36
+ export function pilotSide(dir: string, ward: string, transport: Transport, via?: string): Promise<Serving> {
37
+ return mcpSide(owner(dir, ward, via), transport);
38
+ }