@nervur-org/dock 0.4.1 → 0.6.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 (225) hide show
  1. package/README.md +17 -15
  2. package/api/route.ts +4 -72
  3. package/beings/avatar.ts +11 -7
  4. package/beings/carry.ts +129 -116
  5. package/beings/clock.ts +11 -7
  6. package/beings/desk.ts +7 -6
  7. package/beings/doorbell.ts +36 -7
  8. package/beings/index.ts +17 -4
  9. package/beings/join.ts +7 -4
  10. package/beings/link.ts +1 -1
  11. package/beings/porter.ts +5 -1
  12. package/beings/presence.ts +15 -5
  13. package/beings/setup.ts +27 -21
  14. package/beings/user.ts +26 -17
  15. package/beings/way.ts +1 -1
  16. package/{dock → box}/agents.ts +37 -44
  17. package/{dock → box}/apns.ts +4 -4
  18. package/box/box.ts +31 -0
  19. package/box/contract.ts +41 -0
  20. package/box/dialers.ts +88 -0
  21. package/box/dock.ts +148 -0
  22. package/box/envoy.ts +96 -0
  23. package/box/faculty.ts +111 -0
  24. package/{dock → box}/fcm.ts +1 -1
  25. package/box/grants.ts +90 -0
  26. package/box/http.ts +93 -0
  27. package/{dock → box}/http2.ts +7 -7
  28. package/box/index.ts +201 -0
  29. package/box/join.ts +78 -0
  30. package/box/local.ts +37 -0
  31. package/{dock → box}/location.ts +15 -3
  32. package/{dock → box}/notifications.ts +17 -5
  33. package/box/push.ts +85 -0
  34. package/box/ring.ts +85 -0
  35. package/{dock → box}/service.ts +2 -2
  36. package/box/timer.ts +150 -0
  37. package/box/tree.ts +22 -0
  38. package/{dock → box}/webpush.ts +2 -2
  39. package/cli/client.ts +1 -1
  40. package/cli/daemon.ts +101 -103
  41. package/cli/estate.ts +2 -3
  42. package/cli/nervur.ts +44 -25
  43. package/cli/stand.ts +1 -1
  44. package/dist/api/route.d.ts +1 -2
  45. package/dist/api/route.js +4 -76
  46. package/dist/beings/avatar.d.ts +4 -3
  47. package/dist/beings/avatar.js +9 -6
  48. package/dist/beings/carry.d.ts +10 -12
  49. package/dist/beings/carry.js +119 -127
  50. package/dist/beings/clock.js +10 -7
  51. package/dist/beings/desk.js +8 -7
  52. package/dist/beings/doorbell.d.ts +3 -1
  53. package/dist/beings/doorbell.js +37 -4
  54. package/dist/beings/index.d.ts +24 -4
  55. package/dist/beings/index.js +15 -4
  56. package/dist/beings/join.d.ts +1 -0
  57. package/dist/beings/join.js +6 -4
  58. package/dist/beings/link.d.ts +0 -1
  59. package/dist/beings/link.js +1 -1
  60. package/dist/beings/porter.js +5 -1
  61. package/dist/beings/presence.d.ts +1 -0
  62. package/dist/beings/presence.js +12 -3
  63. package/dist/beings/setup.d.ts +5 -2
  64. package/dist/beings/setup.js +16 -15
  65. package/dist/beings/user.d.ts +4 -3
  66. package/dist/beings/user.js +18 -11
  67. package/dist/{dock → box}/agents.d.ts +7 -4
  68. package/dist/{dock → box}/agents.js +30 -37
  69. package/dist/{dock → box}/apns.d.ts +1 -1
  70. package/dist/{dock → box}/apns.js +3 -3
  71. package/dist/box/box.d.ts +9 -0
  72. package/dist/box/box.js +18 -0
  73. package/dist/box/contract.d.ts +6 -0
  74. package/dist/box/contract.js +35 -0
  75. package/dist/{dock → box}/dialers.d.ts +7 -4
  76. package/dist/box/dialers.js +76 -0
  77. package/dist/box/dock.d.ts +182 -0
  78. package/dist/box/dock.js +128 -0
  79. package/dist/box/envoy.d.ts +44 -0
  80. package/dist/box/envoy.js +75 -0
  81. package/dist/box/faculty.d.ts +68 -0
  82. package/dist/box/faculty.js +97 -0
  83. package/dist/{dock → box}/fcm.d.ts +1 -1
  84. package/dist/{dock → box}/grants.d.ts +4 -3
  85. package/dist/box/grants.js +78 -0
  86. package/dist/box/http.d.ts +26 -0
  87. package/dist/box/http.js +89 -0
  88. package/dist/{dock → box}/http2.d.ts +1 -1
  89. package/dist/{dock → box}/http2.js +6 -6
  90. package/dist/box/index.d.ts +46 -0
  91. package/dist/box/index.js +179 -0
  92. package/dist/box/join.d.ts +19 -0
  93. package/dist/box/join.js +67 -0
  94. package/dist/{dock → box}/local.d.ts +3 -2
  95. package/dist/box/local.js +27 -0
  96. package/dist/{dock → box}/location.d.ts +3 -0
  97. package/dist/box/location.js +21 -0
  98. package/dist/{dock → box}/notifications.d.ts +3 -0
  99. package/dist/{dock → box}/notifications.js +14 -4
  100. package/dist/{dock → box}/push.d.ts +12 -18
  101. package/dist/box/push.js +83 -0
  102. package/dist/box/ring.d.ts +18 -0
  103. package/dist/box/ring.js +55 -0
  104. package/dist/{dock → box}/service.d.ts +1 -1
  105. package/dist/{dock → box}/service.js +1 -1
  106. package/dist/{dock → box}/timer.d.ts +2 -0
  107. package/dist/box/timer.js +127 -0
  108. package/dist/box/tree.d.ts +5 -0
  109. package/dist/box/tree.js +14 -0
  110. package/dist/{dock → box}/webpush.d.ts +1 -1
  111. package/dist/cli/client.d.ts +1 -0
  112. package/dist/cli/daemon.d.ts +2 -8
  113. package/dist/cli/daemon.js +102 -107
  114. package/dist/cli/estate.js +2 -3
  115. package/dist/cli/nervur.js +44 -24
  116. package/dist/cli/stand.d.ts +0 -14
  117. package/dist/cli/stand.js +1 -1
  118. package/dist/harbor/browser.d.ts +8 -5
  119. package/dist/harbor/browser.js +20 -21
  120. package/dist/harbor/capacitor.d.ts +3 -3
  121. package/dist/harbor/capacitor.js +9 -8
  122. package/dist/harbor/disk.d.ts +9 -9
  123. package/dist/harbor/disk.js +56 -51
  124. package/dist/harbor/edge/alarm.d.ts +4 -2
  125. package/dist/harbor/edge/alarm.js +9 -19
  126. package/dist/harbor/edge/edge.d.ts +7 -6
  127. package/dist/harbor/edge/edge.js +58 -53
  128. package/dist/harbor/edge/exercise.js +4 -4
  129. package/dist/harbor/edge/given.d.ts +0 -1
  130. package/dist/harbor/edge/given.js +0 -29
  131. package/dist/harbor/edge/storage.d.ts +0 -2
  132. package/dist/harbor/edge/storage.js +2 -2
  133. package/dist/harbor/http.js +4 -2
  134. package/dist/harbor/sealed.d.ts +0 -5
  135. package/dist/harbor/sealed.js +0 -39
  136. package/dist/harbor/tauri.d.ts +2 -2
  137. package/dist/harbor/tauri.js +13 -10
  138. package/dist/human/app.d.ts +1 -1
  139. package/dist/human/app.js +10 -12
  140. package/dist/human/door.d.ts +0 -1
  141. package/dist/human/door.js +1 -1
  142. package/dist/human/html.js +1 -1
  143. package/dist/human/move.js +1 -1
  144. package/dist/human/tab.bundle.txt +3629 -2824
  145. package/dist/human/wake.d.ts +5 -2
  146. package/dist/human/wake.js +23 -14
  147. package/dist/human/web.d.ts +0 -1
  148. package/dist/human/web.js +3 -3
  149. package/dist/human/worker.bundle.txt +2945 -2118
  150. package/dist/human/worlds.js +3 -3
  151. package/dist/mcp/agent.d.ts +0 -1
  152. package/dist/mcp/agent.js +1 -1
  153. package/dist/mcp/allow.js +3 -2
  154. package/dist/mcp/http.d.ts +0 -1
  155. package/dist/mcp/http.js +1 -1
  156. package/dist/mcp/oauth.d.ts +3 -4
  157. package/dist/mcp/oauth.js +4 -4
  158. package/dist/mcp/route.d.ts +1 -1
  159. package/dist/mcp/runner.d.ts +0 -1
  160. package/dist/mcp/runner.js +1 -1
  161. package/dist/mcp/server.d.ts +0 -1
  162. package/dist/mcp/server.js +1 -1
  163. package/harbor/browser.ts +22 -24
  164. package/harbor/capacitor.ts +9 -8
  165. package/harbor/disk.ts +53 -48
  166. package/harbor/edge/alarm.ts +15 -12
  167. package/harbor/edge/edge.ts +61 -55
  168. package/harbor/edge/exercise.ts +4 -4
  169. package/harbor/edge/given.ts +3 -33
  170. package/harbor/edge/storage.ts +2 -2
  171. package/harbor/http.ts +5 -3
  172. package/harbor/sealed.ts +0 -35
  173. package/harbor/tauri.ts +13 -10
  174. package/human/app.ts +12 -13
  175. package/human/door.ts +1 -1
  176. package/human/html.ts +1 -1
  177. package/human/local.ts +1 -1
  178. package/human/move.ts +1 -1
  179. package/human/tab.bundle.txt +3629 -2824
  180. package/human/wake.ts +36 -16
  181. package/human/web.ts +4 -4
  182. package/human/worker.bundle.txt +2945 -2118
  183. package/human/worlds.ts +3 -3
  184. package/mcp/agent.ts +1 -1
  185. package/mcp/allow.ts +2 -1
  186. package/mcp/http.ts +1 -1
  187. package/mcp/oauth.ts +4 -4
  188. package/mcp/route.ts +1 -1
  189. package/mcp/runner.ts +1 -1
  190. package/mcp/server.ts +1 -1
  191. package/package.json +10 -18
  192. package/GETTING_STARTED.md +0 -138
  193. package/GLOSSARY.md +0 -118
  194. package/WORLDS.md +0 -569
  195. package/beings/envoy.ts +0 -218
  196. package/dist/beings/envoy.d.ts +0 -54
  197. package/dist/beings/envoy.js +0 -211
  198. package/dist/dock/console.d.ts +0 -6
  199. package/dist/dock/console.js +0 -20
  200. package/dist/dock/dialers.js +0 -75
  201. package/dist/dock/faculty.d.ts +0 -24
  202. package/dist/dock/faculty.js +0 -142
  203. package/dist/dock/grants.js +0 -99
  204. package/dist/dock/index.d.ts +0 -41
  205. package/dist/dock/index.js +0 -113
  206. package/dist/dock/local.js +0 -44
  207. package/dist/dock/location.js +0 -11
  208. package/dist/dock/push.js +0 -132
  209. package/dist/dock/timer.js +0 -149
  210. package/dock/console.ts +0 -23
  211. package/dock/dialers.ts +0 -85
  212. package/dock/faculty.ts +0 -148
  213. package/dock/grants.ts +0 -111
  214. package/dock/index.ts +0 -133
  215. package/dock/local.ts +0 -48
  216. package/dock/push.ts +0 -161
  217. package/dock/timer.ts +0 -174
  218. package/quo-api.md +0 -196
  219. package/quo-app-bells.md +0 -280
  220. package/quo-dock.md +0 -1945
  221. package/quo-harbor.md +0 -874
  222. package/quo-human.md +0 -1042
  223. package/quo-mcp.md +0 -343
  224. /package/dist/{dock → box}/fcm.js +0 -0
  225. /package/dist/{dock → box}/webpush.js +0 -0
@@ -32,11 +32,11 @@
32
32
  // <- { ok: as } | { error }, then one line per final text
33
33
  //
34
34
  // The proof for a local side is that it reached side.sock: the daemon mints
35
- // a nonce for the connection on the `local` faculty of its dock ward, the
35
+ // a nonce for the connection on the `local` faculty of its box ward, the
36
36
  // box's own way in, and the desk trades it at a standing there, once. A
37
37
  // `local` proof arriving any other way names no nonce and is refused.
38
38
  //
39
- // Agents have no door: the `agents` faculty of the dock ward holds each
39
+ // Agents have no door: the `agents` faculty of the box ward holds each
40
40
  // agent's identity, her ward, her command and her folder, placed by the
41
41
  // root with `keep` as a schedule is placed on the clock, and the daemon
42
42
  // admits her on the local proof and runs the agent side beside her avatar
@@ -79,8 +79,7 @@
79
79
  // wards, and reconnects with backoff, so a Mac behind NAT is reached
80
80
  // through the droplet it dialed.
81
81
  import { createServer } from 'node:net';
82
- import { unlink, chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
83
- import { existsSync } from 'node:fs';
82
+ import { unlink, chmod } from 'node:fs/promises';
84
83
  import { createHash, randomBytes } from 'node:crypto';
85
84
  import { tmpdir } from 'node:os';
86
85
  import { join, resolve } from 'node:path';
@@ -89,12 +88,10 @@ import { isSilence, isWord, wordOf } from 'nervur';
89
88
  import { DiskHarbor, defaultWard } from '../harbor/disk.js';
90
89
  // an identity in a world: the three asks any terrain's routes make at a ward
91
90
  export { avatarOf, letIn, find } from '../beings/join.js';
92
- import { avatarOf, letIn, find } from '../beings/join.js';
93
- import { DOCK, FACULTIES, LOCAL, PUSH, TIMER, Local, Push, Timer, facultyOf, publicOf, routed, settled, webPush } from '../dock/index.js';
94
- import { apnsHttp2, apnsOf } from '../dock/http2.js';
95
- import { GRANTS, Grants } from '../dock/grants.js';
96
- import { AGENTS, Agents, isProcess } from '../dock/agents.js';
97
- import { DIALERS, Dialers } from '../dock/dialers.js';
91
+ import { avatarOf, isIdentity, letIn, find } from '../beings/join.js';
92
+ import { Http as Wire, routed, webPush } from '../box/index.js';
93
+ import { apnsHttp2, apnsOf } from '../box/http2.js';
94
+ import { AGENTS, Agents, BOX, BOX_SEED, DIALERS, Dialers, GRANTS, Grants, LOCAL, Local, PUSH, Push, TIMER, Timer, WIRE, boxOf, isProcess, keyLent, lent, settled } from '../box/index.js';
98
95
  import { mcpSide } from '../mcp/server.js';
99
96
  import { runnerSide } from '../mcp/runner.js';
100
97
  import { agentSide, processRun } from '../mcp/agent.js';
@@ -126,11 +123,11 @@ export const sidePath = (dir) => socketPath(dir, 'side');
126
123
  // the first time only; to change one, remove the occupant and admit again.
127
124
  // The one path for a side on the device: agents, and sides on side.sock.
128
125
  //
129
- // The nonce is minted on the `local` faculty of this box's dock ward, in
126
+ // The nonce is minted on the `local` faculty of this box's box ward, in
130
127
  // this process, as a device fires its own timer. A box that lends no such
131
128
  // name has no local way in, and nothing here can make one up.
132
129
  export async function admit(harbor, hosted, identity, wake = false, reach = false) {
133
- const local = facultyOf(harbor, LOCAL);
130
+ const local = await lent(harbor, LOCAL);
134
131
  if (!local)
135
132
  return { error: 'this box has no local way in' };
136
133
  const found = await avatarOf(hosted, identity);
@@ -138,7 +135,7 @@ export async function admit(harbor, hosted, identity, wake = false, reach = fals
138
135
  return found;
139
136
  const nonce = randomBytes(16).toString('hex');
140
137
  local.mint(nonce, { user: hosted.record.user, client: identity, ...(wake ? { wake: true } : {}), ...(reach ? { reach: true } : {}) });
141
- const entered = await found.avatar.enter({ ward: hosted.pk }, { kind: LOCAL, nonce });
138
+ const entered = await found.avatar.enter({ ward: hosted.pk }, { kind: 'local', nonce });
142
139
  local.drop(nonce);
143
140
  await hosted.save(); // the knock went through the ward's own door, which the harbor never sees
144
141
  if ('error' in entered && !('asks' in entered))
@@ -158,8 +155,8 @@ async function tick(harbor, now) {
158
155
  // booted a moment ago is still taking one, and a round that ran past her
159
156
  // would be a schedule placed and a minute missed for no reason a reader
160
157
  // could see.
161
- await settled();
162
- const timer = facultyOf(harbor, TIMER);
158
+ await settled(harbor);
159
+ const timer = await lent(harbor, TIMER);
163
160
  if (!timer)
164
161
  return;
165
162
  try {
@@ -194,17 +191,58 @@ export async function serve(dir, options = {}) {
194
191
  // world's own web origin; the push faculty reads the map at each ring, so
195
192
  // it is this daemon's `Push` body and no static set on a class.
196
193
  const rings = { ...options.rings };
194
+ // The web ring is hers alone: Web Push signed under the doorbell key she
195
+ // minted at the root's first ask, with this world's web origin as the
196
+ // subject a push service may complain to, known once the routes are.
197
+ let subject;
197
198
  class DaemonPush extends Push {
199
+ static stands() {
200
+ return true;
201
+ }
202
+ #web;
198
203
  get rings() {
199
- return rings;
204
+ const key = this.key;
205
+ if (rings.web || !key || !subject)
206
+ return rings;
207
+ return { ...rings, web: (this.#web ??= webPush({ key, subject })) };
208
+ }
209
+ }
210
+ const harbor = new DiskHarbor(dir);
211
+ // The lines this box holds open are dialed for its own harbor, and an
212
+ // agent runs as an identity admitted on the local proof with a side over
213
+ // her avatar: the two live things only this process has, handed in the
214
+ // class bodies it brings.
215
+ class DaemonDialers extends Dialers {
216
+ static stands() {
217
+ return true;
218
+ }
219
+ constructor(stance, box) {
220
+ super(stance, box, harbor);
221
+ }
222
+ }
223
+ class DaemonAgents extends Agents {
224
+ static stands() {
225
+ return true;
226
+ }
227
+ constructor(stance, box) {
228
+ super(stance, box, async (row) => {
229
+ const hosted = harbor.wards.get(row.ward);
230
+ if (!hosted)
231
+ throw new Error(`agent ${row.id}: this device hosts no ward ${row.ward}`);
232
+ const admitted = await admit(harbor, hosted, row.id);
233
+ if (!admitted.avatar)
234
+ throw new Error(`agent ${row.id}: ${admitted.error}`);
235
+ // a process is the agent side, a model is the runner: both take a push as the event
236
+ return isProcess(row) ? agentSide(admitted.avatar, processRun(row.command, row.args, row.dir), () => hosted.save()) : runnerSide(admitted.avatar, row, () => { }, () => hosted.save());
237
+ });
200
238
  }
201
239
  }
202
- const harbor = new DiskHarbor(dir, undefined, { Push: DaemonPush, Grants, Agents, Dialers });
203
- // What a daemon lends beyond every box's faculties: `local`, the way in
204
- // for a process it saw on its own socket, and `grants`, the bearers its
205
- // routes honour. A device with no daemon has neither name, and the desk
206
- // there is a world nobody walks into locally.
207
- harbor.lent = { ...FACULTIES, [LOCAL]: 'Local', [GRANTS]: 'Grants', [AGENTS]: 'Agents', [DIALERS]: 'Dialers' };
240
+ // What a daemon brings to its own ward: the timer, `local`, the way in for
241
+ // a process it saw on its own socket, the grants its routes honour, and
242
+ // the ring, the lines and the agents it builds here. A device with no
243
+ // daemon brings none of the last five, and the desk there is a world
244
+ // nobody walks into locally.
245
+ harbor.faculties = { box: { Timer, Local, Grants, Http: Wire }, cli: { DaemonPush, DaemonDialers, DaemonAgents } };
208
246
  // A store that refused a save, once per ward. Every asker whose write was
209
247
  // refused has already been told, at the door that caused it, and none of
210
248
  // them is this operator: a disk that has stopped taking one world's writes
@@ -215,6 +253,15 @@ export async function serve(dir, options = {}) {
215
253
  console.log(JSON.stringify({ at: new Date().toISOString(), refused: name }));
216
254
  };
217
255
  await harbor.boot();
256
+ // A contract two classes clashed under is lent to nobody, and the Dock
257
+ // keeps which; an operator reads it here, once, at boot, and the box is
258
+ // up either way.
259
+ const stood = (await harbor.wards.get(BOX_SEED)?.ask('ask', { being: BOX, method: 'lends' }));
260
+ for (const [word, keys] of Object.entries(stood?.clashes ?? {}))
261
+ console.log(JSON.stringify({ at: new Date().toISOString(), clash: word, between: keys }));
262
+ // What the box is, asked of the Dock and read from no file: its routes,
263
+ // its apps and its default ward, each placed by the root through her asks.
264
+ const box = await boxOf(harbor);
218
265
  // A ward the folder keeps and the harbor could not open stops this daemon
219
266
  // here. The core is right to carry on without it, since a device with many
220
267
  // worlds must not lose them all to one, but a daemon's folder is an
@@ -239,7 +286,7 @@ export async function serve(dir, options = {}) {
239
286
  let mcp = null;
240
287
  let agents = null;
241
288
  // the dialers faculty holds the lines; the route's view of them is hers
242
- const dialers = facultyOf(harbor, DIALERS);
289
+ const dialers = await lent(harbor, DIALERS);
243
290
  if (!dialers)
244
291
  throw new Error('this box has no dialers faculty');
245
292
  const quo = {
@@ -266,40 +313,41 @@ export async function serve(dir, options = {}) {
266
313
  // The box's grants, one faculty for every route: a bearer names an
267
314
  // identity in a world, the identity names her avatar there, and the
268
315
  // route only says the grammar. Nothing is minted for a bearer. Her
269
- // cells are the dock ward's to keep, so a token issued saves it.
270
- grants = facultyOf(harbor, GRANTS) ?? null;
316
+ // cells are the box ward's to keep, so a token issued saves it.
317
+ grants = (await lent(harbor, GRANTS)) ?? null;
271
318
  if (!grants)
272
319
  throw new Error('this box has no grants');
273
- grants.persist = () => harbor.wards.get(DOCK)?.save() ?? Promise.resolve();
274
- const resolve = async (identity, ward) => {
320
+ const findIn = async (identity, ward) => {
275
321
  const hosted = harbor.wards.get(ward);
276
322
  return hosted ? find(hosted, identity) : { error: 'no such world' };
277
323
  };
278
- const join = async (identity, invitation, ward) => {
324
+ const joinIn = async (identity, invitation, ward) => {
279
325
  const hosted = harbor.wards.get(ward);
280
326
  if (!hosted)
281
327
  return { error: 'no such world' };
282
328
  const r = await letIn(hosted, identity, invitation);
283
329
  return r.error ? { error: r.error } : {};
284
330
  };
285
- const routes = options.routes ?? (await readRoutes(harbor.dir));
331
+ const routes = options.routes ?? box.routes;
286
332
  const here = `http://${http.host}:${http.port}`;
287
333
  const at = { quo: routes?.quo ?? `${here}/quo`, web: routes?.web ?? `${here}/web` };
334
+ subject = new URL(at.web).origin;
288
335
  // a stranger's public being as {}, or an occupant's avatar under a
289
336
  // bearer; `web` is for its one human arrival, a redirect landing back
290
337
  // which ward a caller who names none is at is the terrain's word, and
291
338
  // on a disk it is the mark an operator wrote, read below at `defaultWard`
292
- http.mount('/api', apiRoute({ wards: harbor.wards, here: () => harbor.defaultWard }, { grants, resolve, join, web: at.web }));
339
+ http.mount('/api', apiRoute({ wards: harbor.wards, here: () => harbor.defaultWard }, { grants, resolve: findIn, join: joinIn }));
293
340
  // The web ring, under the world's own doorbell key: the push faculty
294
341
  // mints it the first time this box serves a web route and keeps it in
295
342
  // her cells, so it lives with the wards and in no file. The ring is
296
343
  // Web Push signed under it, and the subject a push service may
297
344
  // complain to is this world's own web route, which every world has.
298
- const push = facultyOf(harbor, PUSH);
345
+ const push = await lent(harbor, PUSH);
299
346
  if (!push)
300
347
  throw new Error('this box has no push faculty');
301
- const key = await push.mint();
302
- rings.web ??= webPush({ key, subject: new URL(at.web).origin });
348
+ const bell = (await harbor.wards.get(BOX_SEED)?.ask('ask', { being: await keyLent(harbor, PUSH), method: 'bell' }));
349
+ if (typeof bell?.bell !== 'string')
350
+ throw new Error('this box minted no doorbell key');
303
351
  // The two rings the stores own. A world holds no platform key and
304
352
  // never will, because Apple and Google sign a push only for the team
305
353
  // that published the app, so a ring to a phone is handed to a bell
@@ -322,16 +370,16 @@ export async function serve(dir, options = {}) {
322
370
  // the worlds' pages: always, on the daemon's own door when no route names a public one
323
371
  // the bundles the dock's build emitted: read here, because reading a
324
372
  // file is the terrain's and a route is handed the code
325
- const web = webRoute(harbor, { at, code: await bundles(), folder: diskFolder(harbor.dir), apps: options.apps ?? (await readApps(harbor.dir)), bell: publicOf(key), shown: (n) => DiskHarbor.isShown(harbor.dir, n) });
373
+ const web = webRoute(harbor, { at, code: await bundles(), folder: diskFolder(harbor.dir), apps: options.apps ?? box.apps, bell: bell.bell, shown: (n) => DiskHarbor.isShown(harbor.dir, n) });
326
374
  if (routes) {
327
375
  // the worlds a client may be allowed into: every ward here but the
328
376
  // dock, which is the box's and nobody's world, by name and pk, the device's own first
329
377
  const worlds = () => [...harbor.wards]
330
- .filter(([ward]) => ward !== DOCK)
378
+ .filter(([ward]) => ward !== BOX_SEED)
331
379
  .sort(([a], [b]) => (a === harbor.defaultWard ? -1 : b === harbor.defaultWard ? 1 : a.localeCompare(b)))
332
380
  .map(([ward, h]) => ({ ward, pk: h.pk, user: h.record.user }));
333
381
  // the MCP endpoint: a bearer names an identity in a world, the identity names her avatar there, the side runs beside her
334
- mcp = new McpHttp(resolve, async () => {
382
+ mcp = new McpHttp(findIn, async () => {
335
383
  for (const h of harbor.wards.values())
336
384
  await h.save();
337
385
  });
@@ -341,7 +389,7 @@ export async function serve(dir, options = {}) {
341
389
  http.mount('/.well-known', route.wellKnown); // the same discovery at the origin's root, where a client looks for an issuer that has a path
342
390
  const g = grants;
343
391
  mcp.gone = (identity, ward) => g.revoke(identity, ward); // removal at the ward ends the grant at the box
344
- exchange = new Exchange({ oauth, worlds, join });
392
+ exchange = new Exchange({ oauth, worlds, join: joinIn });
345
393
  }
346
394
  const ex = exchange;
347
395
  http.mount('/web', async (req, rest) =>
@@ -351,35 +399,28 @@ export async function serve(dir, options = {}) {
351
399
  (await web(req, rest)) ??
352
400
  (ex && (await ex.handle(req, rest))) ??
353
401
  new Response('no such page', { status: 404, headers: { 'content-type': 'text/plain' } }));
402
+ // Every other path is the box's wire: a faculty that claims it answers.
403
+ // Mounted last, so every route above is tried first.
404
+ const wire = await lent(harbor, WIRE);
405
+ if (wire)
406
+ http.mount('/', (req) => wire.handle(req));
354
407
  }
355
408
  // the routes this box holds a socket to are rows on the dialers faculty,
356
409
  // dialed from her cells at every boot; `dial` is a suite's way of placing one
357
410
  for (const url of options.dial ?? []) {
358
411
  const kept = dialers.keep({ url });
359
412
  if ('error' in kept)
360
- throw new Error(String(kept.error));
413
+ throw new Error(JSON.stringify(kept.error));
361
414
  }
362
- dialers.start(harbor);
363
- // Which of its wards is the default: the agents and the ward the door
364
- // answers for hang off it, and a guess failing in silence is not a way
365
- // to find out.
366
- harbor.defaultWard = defaultWard(await DiskHarbor.marked(harbor.dir), [...harbor.wards.keys()]);
367
- // The agents faculty holds the table; this daemon is what runs a row:
368
- // the identity admitted into her ward on the local proof, and the agent
369
- // side over her avatar for as long as the daemon serves.
370
- agents = facultyOf(harbor, AGENTS) ?? null;
415
+ // Which of its wards is the default: the ward the door answers for hangs
416
+ // off it, and a guess failing in silence is not a way to find out.
417
+ harbor.defaultWard = defaultWard(box.default === null ? [] : [box.default], [...harbor.wards.keys()]);
418
+ agents = (await lent(harbor, AGENTS)) ?? null;
371
419
  if (!agents)
372
420
  throw new Error('this box has no agents faculty');
373
- await agents.start(async (row) => {
374
- const hosted = harbor.wards.get(row.ward);
375
- if (!hosted)
376
- throw new Error(`agent ${row.id}: this device hosts no ward ${row.ward}`);
377
- const admitted = await admit(harbor, hosted, row.id);
378
- if (!admitted.avatar)
379
- throw new Error(`agent ${row.id}: ${admitted.error}`);
380
- // a process is the agent side, a model is the runner: both take a push as the event
381
- return isProcess(row) ? agentSide(admitted.avatar, processRun(row.command, row.args, row.dir), () => hosted.save()) : runnerSide(admitted.avatar, row, () => { }, () => hosted.save());
382
- });
421
+ // The box is up: the lines are dialed and the agents run, and the daemon
422
+ // serves once they have.
423
+ await harbor.box.raise();
383
424
  }
384
425
  catch (e) {
385
426
  for (const s of servers)
@@ -405,7 +446,7 @@ export async function serve(dir, options = {}) {
405
446
  // daemon fire what is due right now.
406
447
  tick: (now) => tick(harbor, now),
407
448
  close: async () => {
408
- facultyOf(harbor, TIMER)?.stop();
449
+ (await lent(harbor, TIMER))?.stop();
409
450
  await mcp?.close();
410
451
  await agents?.close();
411
452
  dialers.close();
@@ -431,7 +472,7 @@ async function sideConnection(harbor, c) {
431
472
  hosted = harbor.wards.get(req.ward ?? harbor.defaultWard);
432
473
  if (!hosted)
433
474
  throw new Error('no such ward');
434
- if (typeof req.as !== 'string' || !/^[\w.-]+$/.test(req.as) || req.as === hosted.record.user || req.as === 'desk')
475
+ if (!isIdentity(hosted, req.as))
435
476
  throw new Error('as is a word, and not a being of the ward');
436
477
  as = req.as;
437
478
  if (req.run !== undefined) {
@@ -529,56 +570,10 @@ async function handle(harbor, line) {
529
570
  }
530
571
  }
531
572
  const out = await hosted.ask(req.method, req.args ?? {});
532
- return JSON.stringify({ id, result: isSilence(out) ? { silence: true } : out });
573
+ // the ward it landed on, named back, since a request naming none is on the default and the caller may need its name
574
+ return JSON.stringify({ id, ward: hosted.name, result: isSilence(out) ? { silence: true } : out });
533
575
  }
534
576
  catch (e) {
535
577
  return JSON.stringify({ id, error: e instanceof Error ? e.message : String(e) });
536
578
  }
537
579
  }
538
- // The four routes by the one convention: `mcp.`, `web.`, `quo.` and `api.`
539
- // under one domain, over https. A domain is said once and the names follow;
540
- // an estate whose names break the convention edits the file it wrote.
541
- export const DOMAIN = /^[a-z0-9.-]+\.[a-z]{2,}$/i;
542
- export function routesFor(domain) {
543
- if (!DOMAIN.test(domain))
544
- throw new Error(`${domain} is not a domain`);
545
- return { mcp: `https://mcp.${domain}`, web: `https://web.${domain}`, quo: `https://quo.${domain}`, api: `https://api.${domain}` };
546
- }
547
- export async function writeRoutes(dir, domain) {
548
- const routes = routesFor(domain);
549
- await mkdir(dir, { recursive: true });
550
- await writeFile(join(dir, 'routes.json'), JSON.stringify(routes) + '\n');
551
- return routes;
552
- }
553
- // `<dir>/routes.json`: { mcp, web, quo?, api? }, the public origins the proxy serves.
554
- export async function readRoutes(dir) {
555
- const p = join(dir, 'routes.json');
556
- if (!existsSync(p))
557
- return null;
558
- const r = JSON.parse(await readFile(p, 'utf8'));
559
- if (typeof r.mcp !== 'string' || typeof r.web !== 'string')
560
- return null;
561
- const out = { mcp: r.mcp.replace(/\/$/, ''), web: r.web.replace(/\/$/, '') };
562
- if (typeof r.quo === 'string')
563
- out.quo = r.quo.replace(/\/$/, '');
564
- if (typeof r.api === 'string')
565
- out.api = r.api.replace(/\/$/, '');
566
- return out;
567
- }
568
- // `<dir>/app.json`: { apple: [...], android: [{ package, fingerprints }] }, the
569
- // apps whose links this world's hostname opens, as the two platforms want
570
- // them named. An estate that ships no app of its own writes no such file,
571
- // serves no association file, and its links open the tab.
572
- export async function readApps(dir) {
573
- const p = join(dir, 'app.json');
574
- if (!existsSync(p))
575
- return null;
576
- const r = JSON.parse(await readFile(p, 'utf8'));
577
- const apple = Array.isArray(r.apple) ? r.apple.filter((x) => typeof x === 'string') : [];
578
- const android = Array.isArray(r.android)
579
- ? r.android
580
- .filter((a) => typeof a?.package === 'string' && Array.isArray(a.fingerprints))
581
- .map((a) => ({ package: a.package, fingerprints: a.fingerprints.filter((f) => typeof f === 'string') }))
582
- : [];
583
- return apple.length === 0 && android.length === 0 ? null : { apple, android };
584
- }
@@ -4,7 +4,7 @@
4
4
  // that device runs minus what it mints, its routes under the domain, no
5
5
  // agents, no schedules, no classes of its own, and that device's unit and
6
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
7
+ // operator's Mac as a dialer. A package file
8
8
  // that depends on this dock and nothing else, and one document to fill in.
9
9
  // An estate needs nothing the dock does not give it, and this is the dock
10
10
  // giving it.
@@ -12,7 +12,7 @@ import { existsSync } from 'node:fs';
12
12
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
13
13
  import { basename, join } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
- import { DOMAIN, routesFor } from './daemon.js';
15
+ import { DOMAIN } from '../box/index.js';
16
16
  // This package's own version, read beside the emitted or the source tree.
17
17
  async function version() {
18
18
  for (const rel of ['../package.json', '../../package.json']) {
@@ -35,7 +35,6 @@ export async function estate(dir, domain) {
35
35
  const files = {
36
36
  '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',
37
37
  [`${name}.md`]: `# ${name}\n\nAn estate: all of one owner's harbors. One folder per harbor, each the harbor\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`,
38
- 'droplet/routes.json': JSON.stringify(routesFor(domain)) + '\n',
39
38
  '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.NERVUR_DIR ?? '', { boot: [], wire: [] }));\n`,
40
39
  'droplet/classes/index.ts': '// The classes this harbor holds beside the dock\'s built-in ones, exported\n// by name.\nexport {};\n',
41
40
  'droplet/nervur.service': (await template('nervur.service')).replaceAll('ESTATE', name),
@@ -10,14 +10,15 @@
10
10
  // device holds for the daemon is either: the unit, the unix user, the home
11
11
  // and the environment file all say nervur.
12
12
  //
13
- // nervur init [--dir D] [--ward W] [--user U] [--class C] [--default] [--show] [--domain DOM] mint a seed, boot a ward over a disk store, write both; C is the class of the ward's home being, User unless said; --default marks it the default ward; --show shows it at the web route; --domain writes the four routes under DOM
13
+ // nervur init [--dir D] [--ward W] [--user U] [--class C] [--default] [--show] [--domain DOM] mint a seed, boot a ward over a disk store, write both; C is the class of the ward's home being, beings/User unless said; --default marks it the default ward; --show shows it at the web route; --domain writes the four routes under DOM
14
14
  // nervur default [--dir D] <ward> mark one ward the default, the one a caller means when naming none; the one ward hosted is the default unmarked
15
15
  // nervur show|hide [--dir D] <ward> show a ward at the web route, listed at its root and its public being's page painted for a stranger; hidden at init, and a hidden ward's page is the plain door
16
- // nervur domain [--dir D] <domain> write routes.json by the convention: mcp., web., quo. and api. under the domain, over https
17
- // nervur serve [--dir D] [--http PORT] the daemon: harbor from disk, the two sockets, the HTTP door on loopback, a socket held to every route the dialers faculty keeps
16
+ // nervur domain [--dir D] <domain> say the four routes by the convention, mcp., web., quo. and api. under the domain over https, a cell of the Dock
17
+ // nervur apps [--dir D] <json> say the apps this box's links open, { apple, android }, a cell of the Dock; {} forgets them
18
+ // nervur serve [--dir D] [--http PORT] [--host ADDR] the daemon: harbor from disk, the two sockets, the HTTP door on ADDR, 127.0.0.1 unless said, a socket held to every route the dialers faculty keeps
18
19
  // nervur census [--dir D] [--ward W] [--via S] the empty ask: pk and every being
19
20
  // nervur boot [--dir D] [--ward W] [--via S] <json> { key, class, public? }
20
- // nervur invite [--dir D] [--ward W] [--via S] <json> { being, id }; with routes.json naming web., the answer carries the link
21
+ // nervur invite [--dir D] [--ward W] [--via S] <json> { being, id }; with the box's routes naming web., the answer carries the link
21
22
  // nervur knock [--dir D] [--ward W] [--via S] <json> { being | { boot, key }, id, invitation, method?, args?, wanted? }
22
23
  // nervur remove [--dir D] [--ward W] [--via S] <json> { being, id }: a relation out of a being; on the ward pk, an owner
23
24
  // nervur unboot [--dir D] [--ward W] [--via S] <json> { being }: a being out of the ward, with every relation she holds
@@ -29,7 +30,9 @@
29
30
  // nervur estate <dir> --domain D an estate folder of the shape every estate has: a droplet's harbor directory, unit and Caddyfile, a Mac's launchd agent, on this dock
30
31
  //
31
32
  // D defaults to $NERVUR_DIR, then ~/.nervur. W defaults to the default ward, and
32
- // init names a new ward main when none is given. init takes
33
+ // init names a new ward main when none is given. serve takes the HTTP port
34
+ // as --http or $NERVUR_HTTP and the address it binds as --host or
35
+ // $NERVUR_HOST, 127.0.0.1 when neither says one. init takes
33
36
  // --user NAME for the user being, defaulting to the device's user. run
34
37
  // takes the model's base URL as --url or $NERVUR_MODEL_URL, its name as
35
38
  // --model or $NERVUR_MODEL, a bearer from $NERVUR_MODEL_KEY, read here and never
@@ -42,7 +45,8 @@ import { join } from 'node:path';
42
45
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
43
46
  import { isInvitation } from 'nervur';
44
47
  import { DiskHarbor } from '../harbor/disk.js';
45
- import { serve, readRoutes, writeRoutes } from './daemon.js';
48
+ import { serve } from './daemon.js';
49
+ import { BOX, BOX_SEED } from '../box/index.js';
46
50
  import { ask, askBeing, side, reach } from './client.js';
47
51
  import { link } from '../beings/link.js';
48
52
  import { pilotSide } from './pilot.js';
@@ -67,8 +71,11 @@ const print = (v) => console.log(JSON.stringify(v, null, 2));
67
71
  async function main(argv) {
68
72
  const { cmd, json, more, third, flags, dir, via } = parse(argv);
69
73
  if (cmd === 'init') {
70
- const made = await DiskHarbor.init(dir, flags.ward, flags.user ?? userInfo().username, flags.class, 'default' in flags, 'show' in flags);
71
- print(flags.domain ? { ...made, routes: await writeRoutes(dir, flags.domain) } : made);
74
+ const made = await DiskHarbor.init(dir, flags.ward, flags.user ?? userInfo().username, flags.class, 'show' in flags);
75
+ // the box's own cells, said to the Dock with no daemon over the folder
76
+ if ('default' in flags)
77
+ await DiskHarbor.say(dir, 'default', { ward: made.name });
78
+ print(flags.domain ? { ...made, ...(await DiskHarbor.say(dir, 'domain', { domain: flags.domain })) } : made);
72
79
  return 0;
73
80
  }
74
81
  if (cmd === 'default') {
@@ -76,8 +83,15 @@ async function main(argv) {
76
83
  console.error('nervur default <ward>');
77
84
  return 2;
78
85
  }
79
- await DiskHarbor.setDefault(dir, json);
80
- print({ default: json });
86
+ print(await DiskHarbor.say(dir, 'default', { ward: json }));
87
+ return 0;
88
+ }
89
+ if (cmd === 'apps') {
90
+ if (!json) {
91
+ console.error('nervur apps <json> { apple: [...], android: [{ package, fingerprints }] }; {} forgets them');
92
+ return 2;
93
+ }
94
+ print(await DiskHarbor.say(dir, 'apps', JSON.parse(json)));
81
95
  return 0;
82
96
  }
83
97
  if (cmd === 'show' || cmd === 'hide') {
@@ -94,13 +108,12 @@ async function main(argv) {
94
108
  console.error('nervur domain <domain>');
95
109
  return 2;
96
110
  }
97
- print({ routes: await writeRoutes(dir, json) });
111
+ print(await DiskHarbor.say(dir, 'domain', { domain: json }));
98
112
  return 0;
99
113
  }
100
114
  // Every command that reaches a ward is on one: the one named, or the
101
- // default, read from the folder the way the daemon reads it, and only once
102
- // the command is held right.
103
- const wardOf = async () => flags.ward ?? (await DiskHarbor.defaultOf(dir));
115
+ // default, which the daemon resolves for a request naming none.
116
+ const wardOf = () => flags.ward;
104
117
  if (cmd === 'side' || cmd === 'run') {
105
118
  if (!flags.as) {
106
119
  console.error(`nervur ${cmd} needs --as NAME`);
@@ -122,11 +135,11 @@ async function main(argv) {
122
135
  }
123
136
  run = { url, model, ...(key ? { key } : {}), ...(turns !== undefined ? { turns } : {}) };
124
137
  }
125
- await side(dir, flags.as, await wardOf(), run); // stdio to the daemon, until one end closes
138
+ await side(dir, flags.as, wardOf(), run); // stdio to the daemon, until one end closes
126
139
  return -1;
127
140
  }
128
141
  if (cmd === 'pilot') {
129
- const ward = await wardOf();
142
+ const ward = wardOf();
130
143
  await ask(dir, undefined, {}, ward); // no daemon is a clear word now, not at the first tool call
131
144
  const serving = await pilotSide(dir, ward, new StdioServerTransport(), via);
132
145
  process.stdin.once('end', () => void serving.close().then(() => process.exit(0)));
@@ -134,7 +147,11 @@ async function main(argv) {
134
147
  }
135
148
  if (cmd === 'serve') {
136
149
  const port = flags.http ?? process.env.NERVUR_HTTP;
137
- const s = await serve(dir, port !== undefined ? { http: { port: Number(port) } } : {});
150
+ // Loopback unless an address is said. A daemon reachable from the
151
+ // network is the operator's own word and never a default, so the flag
152
+ // exists and is never filled in for him.
153
+ const host = flags.host || process.env.NERVUR_HOST;
154
+ const s = await serve(dir, port !== undefined ? { http: { port: Number(port), ...(host ? { host } : {}) } } : {});
138
155
  print({ dir: s.harbor.dir, sock: s.sock, side: s.side, http: s.http ? `http://${s.http.host}:${s.http.port}` : null, dial: s.quo.dialers.map((d) => d.url), wards: Object.fromEntries([...s.harbor.wards].map(([n, h]) => [n, h.pk])) });
139
156
  const stop = () => void s.close().then(() => process.exit(0));
140
157
  process.once('SIGINT', stop);
@@ -158,7 +175,7 @@ async function main(argv) {
158
175
  console.error('nervur ask needs <being> [method] [json]');
159
176
  return 2;
160
177
  }
161
- const out = await askBeing(dir, json, more, third ? JSON.parse(third) : {}, await wardOf());
178
+ const out = await askBeing(dir, json, more, third ? JSON.parse(third) : {}, wardOf());
162
179
  print('error' in out ? { error: out.error } : out.result);
163
180
  const failed = 'error' in out || (typeof out.result === 'object' && out.result !== null && 'error' in out.result);
164
181
  return failed ? 1 : 0;
@@ -174,20 +191,23 @@ async function main(argv) {
174
191
  }
175
192
  if (cmd === 'census' || (cmd !== undefined && OWNER_ASKS.has(cmd))) {
176
193
  const args = json ? JSON.parse(json) : {};
177
- const ward = await wardOf();
194
+ const ward = wardOf();
178
195
  let out = await ask(dir, cmd === 'census' ? undefined : cmd, args, ward, via);
179
- // an invitation minted here is handed on as a link when the harbor's
196
+ // an invitation minted here is handed on as a link when the box's
180
197
  // routes name a web. origin: the world's page with the invitation in
181
- // the fragment, one string to send to the person it is for
198
+ // the fragment, one string to send to the person it is for. The routes
199
+ // are the Dock's cells, asked of her through the daemon.
182
200
  const minted = cmd === 'invite' && !via && 'result' in out && isInvitation(out.result) ? out.result : null;
183
- const routes = minted ? await readRoutes(dir) : null;
201
+ const box = minted ? await askBeing(dir, BOX, 'box', {}, BOX_SEED) : null;
202
+ const routes = box && 'result' in box ? (box.result.routes ?? null) : null;
203
+ const on = ('ward' in out ? out.ward : undefined) ?? ward ?? '';
184
204
  // the ward answers null to an invite on an id already held, standing or
185
205
  // occupant, and null on a terminal is nothing to read: it is a refusal
186
206
  // here, in words, and an exit code a script can tell apart
187
207
  if (cmd === 'invite' && 'result' in out && out.result === null)
188
208
  out = { result: { error: 'id taken' } };
189
209
  if (minted && routes)
190
- print({ ...minted, link: link(`${routes.web}/${encodeURIComponent(ward)}`, minted) });
210
+ print({ ...minted, link: link(`${routes.web}/${encodeURIComponent(on)}`, minted) });
191
211
  else
192
212
  print('error' in out ? { error: out.error } : out.result);
193
213
  // an error object the ward answered is an ordinary answer, and still a
@@ -195,7 +215,7 @@ async function main(argv) {
195
215
  const failed = 'error' in out || (typeof out.result === 'object' && out.result !== null && 'error' in out.result);
196
216
  return failed ? 1 : 0;
197
217
  }
198
- console.error('usage: nervur init|default|show|hide|domain|serve|census|boot|public|invite|knock|remove|unboot|ask|side|run|pilot|reach|estate [--dir D] [--ward W] [--via S] [--as NAME] [--url URL --model NAME] [json]');
218
+ console.error('usage: nervur init|default|show|hide|domain|apps|serve|census|boot|public|invite|knock|remove|unboot|ask|side|run|pilot|reach|estate [--dir D] [--ward W] [--via S] [--as NAME] [--http PORT] [--host ADDR] [--url URL --model NAME] [json]');
199
219
  return 2;
200
220
  }
201
221
  main(process.argv.slice(2)).then((code) => {
@@ -1,5 +1,4 @@
1
1
  import type { JsonObject } from 'nervur';
2
- import type { Harbor } from 'nervur/harbor';
3
2
  export type Boot = {
4
3
  ward: string;
5
4
  key: string;
@@ -17,21 +16,8 @@ export type Stand = {
17
16
  boot?: Boot[];
18
17
  wire?: Wire[];
19
18
  };
20
- type Wards = {
21
- wards: Map<string, {
22
- being(key: string): unknown;
23
- ask(method: string, args: JsonObject): Promise<unknown>;
24
- }>;
25
- partitionOf(name: string): Record<string, unknown> | undefined;
26
- };
27
- export declare function standUp(harbor: Harbor | Wards, decl: Stand): Promise<{
28
- booted: string[];
29
- wired: string[];
30
- already: string[];
31
- }>;
32
19
  export declare function stand(dir: string, decl: Stand): Promise<{
33
20
  booted: string[];
34
21
  wired: string[];
35
22
  already: string[];
36
23
  }>;
37
- export {};
package/dist/cli/stand.js CHANGED
@@ -31,7 +31,7 @@ const ward = (h, name, why) => h.wards.get(name) ?? fail(`${why}: this device ho
31
31
  // for exactly one reason once the reader has held the id to a word the ward
32
32
  // does not reserve, which is that the id is taken, and an id taken is this
33
33
  // wire, placed the last time the script ran.
34
- export async function standUp(harbor, decl) {
34
+ async function standUp(harbor, decl) {
35
35
  const h = harbor;
36
36
  const booted = [];
37
37
  for (const b of decl.boot ?? []) {
@@ -1,13 +1,16 @@
1
1
  import type { BeingClass } from 'nervur';
2
- import { Harbor, type Dialer, type Lend, type Store } from 'nervur/harbor';
3
- export declare const BUILT_IN: Record<string, BeingClass>;
2
+ import { Harbor, type Dialer, type Store } from 'nervur/harbor';
3
+ import { type Tree } from '../box/index.ts';
4
4
  export declare class BrowserHarbor extends Harbor {
5
5
  #private;
6
6
  readonly name: string;
7
7
  readonly dialers: Dialer[];
8
- constructor(store?: string | Store, classes?: Record<string, BeingClass>, lend?: Lend);
9
- lent: Record<string, string> | undefined;
10
- stand(lent: Record<string, string>): Promise<void>;
8
+ constructor(store?: string | Store, classes?: Record<string, BeingClass>);
9
+ faculties: Tree | undefined;
10
+ readonly box: import("../box/box.ts").Harborage & {
11
+ raise(): Promise<void>;
12
+ };
13
+ stand(faculties: Tree): Promise<void>;
11
14
  boot(): Promise<void>;
12
15
  dial(url: string): Dialer;
13
16
  wake(): void;