@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.
- package/README.md +17 -15
- package/api/route.ts +4 -72
- package/beings/avatar.ts +11 -7
- package/beings/carry.ts +129 -116
- package/beings/clock.ts +11 -7
- package/beings/desk.ts +7 -6
- package/beings/doorbell.ts +36 -7
- package/beings/index.ts +17 -4
- package/beings/join.ts +7 -4
- package/beings/link.ts +1 -1
- package/beings/porter.ts +5 -1
- package/beings/presence.ts +15 -5
- package/beings/setup.ts +27 -21
- package/beings/user.ts +26 -17
- package/beings/way.ts +1 -1
- package/{dock → box}/agents.ts +37 -44
- package/{dock → box}/apns.ts +4 -4
- package/box/box.ts +31 -0
- package/box/contract.ts +41 -0
- package/box/dialers.ts +88 -0
- package/box/dock.ts +148 -0
- package/box/envoy.ts +96 -0
- package/box/faculty.ts +111 -0
- package/{dock → box}/fcm.ts +1 -1
- package/box/grants.ts +90 -0
- package/box/http.ts +93 -0
- package/{dock → box}/http2.ts +7 -7
- package/box/index.ts +201 -0
- package/box/join.ts +78 -0
- package/box/local.ts +37 -0
- package/{dock → box}/location.ts +15 -3
- package/{dock → box}/notifications.ts +17 -5
- package/box/push.ts +85 -0
- package/box/ring.ts +85 -0
- package/{dock → box}/service.ts +2 -2
- package/box/timer.ts +150 -0
- package/box/tree.ts +22 -0
- package/{dock → box}/webpush.ts +2 -2
- package/cli/client.ts +1 -1
- package/cli/daemon.ts +101 -103
- package/cli/estate.ts +2 -3
- package/cli/nervur.ts +44 -25
- package/cli/stand.ts +1 -1
- package/dist/api/route.d.ts +1 -2
- package/dist/api/route.js +4 -76
- package/dist/beings/avatar.d.ts +4 -3
- package/dist/beings/avatar.js +9 -6
- package/dist/beings/carry.d.ts +10 -12
- package/dist/beings/carry.js +119 -127
- package/dist/beings/clock.js +10 -7
- package/dist/beings/desk.js +8 -7
- package/dist/beings/doorbell.d.ts +3 -1
- package/dist/beings/doorbell.js +37 -4
- package/dist/beings/index.d.ts +24 -4
- package/dist/beings/index.js +15 -4
- package/dist/beings/join.d.ts +1 -0
- package/dist/beings/join.js +6 -4
- package/dist/beings/link.d.ts +0 -1
- package/dist/beings/link.js +1 -1
- package/dist/beings/porter.js +5 -1
- package/dist/beings/presence.d.ts +1 -0
- package/dist/beings/presence.js +12 -3
- package/dist/beings/setup.d.ts +5 -2
- package/dist/beings/setup.js +16 -15
- package/dist/beings/user.d.ts +4 -3
- package/dist/beings/user.js +18 -11
- package/dist/{dock → box}/agents.d.ts +7 -4
- package/dist/{dock → box}/agents.js +30 -37
- package/dist/{dock → box}/apns.d.ts +1 -1
- package/dist/{dock → box}/apns.js +3 -3
- package/dist/box/box.d.ts +9 -0
- package/dist/box/box.js +18 -0
- package/dist/box/contract.d.ts +6 -0
- package/dist/box/contract.js +35 -0
- package/dist/{dock → box}/dialers.d.ts +7 -4
- package/dist/box/dialers.js +76 -0
- package/dist/box/dock.d.ts +182 -0
- package/dist/box/dock.js +128 -0
- package/dist/box/envoy.d.ts +44 -0
- package/dist/box/envoy.js +75 -0
- package/dist/box/faculty.d.ts +68 -0
- package/dist/box/faculty.js +97 -0
- package/dist/{dock → box}/fcm.d.ts +1 -1
- package/dist/{dock → box}/grants.d.ts +4 -3
- package/dist/box/grants.js +78 -0
- package/dist/box/http.d.ts +26 -0
- package/dist/box/http.js +89 -0
- package/dist/{dock → box}/http2.d.ts +1 -1
- package/dist/{dock → box}/http2.js +6 -6
- package/dist/box/index.d.ts +46 -0
- package/dist/box/index.js +179 -0
- package/dist/box/join.d.ts +19 -0
- package/dist/box/join.js +67 -0
- package/dist/{dock → box}/local.d.ts +3 -2
- package/dist/box/local.js +27 -0
- package/dist/{dock → box}/location.d.ts +3 -0
- package/dist/box/location.js +21 -0
- package/dist/{dock → box}/notifications.d.ts +3 -0
- package/dist/{dock → box}/notifications.js +14 -4
- package/dist/{dock → box}/push.d.ts +12 -18
- package/dist/box/push.js +83 -0
- package/dist/box/ring.d.ts +18 -0
- package/dist/box/ring.js +55 -0
- package/dist/{dock → box}/service.d.ts +1 -1
- package/dist/{dock → box}/service.js +1 -1
- package/dist/{dock → box}/timer.d.ts +2 -0
- package/dist/box/timer.js +127 -0
- package/dist/box/tree.d.ts +5 -0
- package/dist/box/tree.js +14 -0
- package/dist/{dock → box}/webpush.d.ts +1 -1
- package/dist/cli/client.d.ts +1 -0
- package/dist/cli/daemon.d.ts +2 -8
- package/dist/cli/daemon.js +102 -107
- package/dist/cli/estate.js +2 -3
- package/dist/cli/nervur.js +44 -24
- package/dist/cli/stand.d.ts +0 -14
- package/dist/cli/stand.js +1 -1
- package/dist/harbor/browser.d.ts +8 -5
- package/dist/harbor/browser.js +20 -21
- package/dist/harbor/capacitor.d.ts +3 -3
- package/dist/harbor/capacitor.js +9 -8
- package/dist/harbor/disk.d.ts +9 -9
- package/dist/harbor/disk.js +56 -51
- package/dist/harbor/edge/alarm.d.ts +4 -2
- package/dist/harbor/edge/alarm.js +9 -19
- package/dist/harbor/edge/edge.d.ts +7 -6
- package/dist/harbor/edge/edge.js +58 -53
- package/dist/harbor/edge/exercise.js +4 -4
- package/dist/harbor/edge/given.d.ts +0 -1
- package/dist/harbor/edge/given.js +0 -29
- package/dist/harbor/edge/storage.d.ts +0 -2
- package/dist/harbor/edge/storage.js +2 -2
- package/dist/harbor/http.js +4 -2
- package/dist/harbor/sealed.d.ts +0 -5
- package/dist/harbor/sealed.js +0 -39
- package/dist/harbor/tauri.d.ts +2 -2
- package/dist/harbor/tauri.js +13 -10
- package/dist/human/app.d.ts +1 -1
- package/dist/human/app.js +10 -12
- package/dist/human/door.d.ts +0 -1
- package/dist/human/door.js +1 -1
- package/dist/human/html.js +1 -1
- package/dist/human/move.js +1 -1
- package/dist/human/tab.bundle.txt +3629 -2824
- package/dist/human/wake.d.ts +5 -2
- package/dist/human/wake.js +23 -14
- package/dist/human/web.d.ts +0 -1
- package/dist/human/web.js +3 -3
- package/dist/human/worker.bundle.txt +2945 -2118
- package/dist/human/worlds.js +3 -3
- package/dist/mcp/agent.d.ts +0 -1
- package/dist/mcp/agent.js +1 -1
- package/dist/mcp/allow.js +3 -2
- package/dist/mcp/http.d.ts +0 -1
- package/dist/mcp/http.js +1 -1
- package/dist/mcp/oauth.d.ts +3 -4
- package/dist/mcp/oauth.js +4 -4
- package/dist/mcp/route.d.ts +1 -1
- package/dist/mcp/runner.d.ts +0 -1
- package/dist/mcp/runner.js +1 -1
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/server.js +1 -1
- package/harbor/browser.ts +22 -24
- package/harbor/capacitor.ts +9 -8
- package/harbor/disk.ts +53 -48
- package/harbor/edge/alarm.ts +15 -12
- package/harbor/edge/edge.ts +61 -55
- package/harbor/edge/exercise.ts +4 -4
- package/harbor/edge/given.ts +3 -33
- package/harbor/edge/storage.ts +2 -2
- package/harbor/http.ts +5 -3
- package/harbor/sealed.ts +0 -35
- package/harbor/tauri.ts +13 -10
- package/human/app.ts +12 -13
- package/human/door.ts +1 -1
- package/human/html.ts +1 -1
- package/human/local.ts +1 -1
- package/human/move.ts +1 -1
- package/human/tab.bundle.txt +3629 -2824
- package/human/wake.ts +36 -16
- package/human/web.ts +4 -4
- package/human/worker.bundle.txt +2945 -2118
- package/human/worlds.ts +3 -3
- package/mcp/agent.ts +1 -1
- package/mcp/allow.ts +2 -1
- package/mcp/http.ts +1 -1
- package/mcp/oauth.ts +4 -4
- package/mcp/route.ts +1 -1
- package/mcp/runner.ts +1 -1
- package/mcp/server.ts +1 -1
- package/package.json +10 -18
- package/GETTING_STARTED.md +0 -138
- package/GLOSSARY.md +0 -118
- package/WORLDS.md +0 -569
- package/beings/envoy.ts +0 -218
- package/dist/beings/envoy.d.ts +0 -54
- package/dist/beings/envoy.js +0 -211
- package/dist/dock/console.d.ts +0 -6
- package/dist/dock/console.js +0 -20
- package/dist/dock/dialers.js +0 -75
- package/dist/dock/faculty.d.ts +0 -24
- package/dist/dock/faculty.js +0 -142
- package/dist/dock/grants.js +0 -99
- package/dist/dock/index.d.ts +0 -41
- package/dist/dock/index.js +0 -113
- package/dist/dock/local.js +0 -44
- package/dist/dock/location.js +0 -11
- package/dist/dock/push.js +0 -132
- package/dist/dock/timer.js +0 -149
- package/dock/console.ts +0 -23
- package/dock/dialers.ts +0 -85
- package/dock/faculty.ts +0 -148
- package/dock/grants.ts +0 -111
- package/dock/index.ts +0 -133
- package/dock/local.ts +0 -48
- package/dock/push.ts +0 -161
- package/dock/timer.ts +0 -174
- package/quo-api.md +0 -196
- package/quo-app-bells.md +0 -280
- package/quo-dock.md +0 -1945
- package/quo-harbor.md +0 -874
- package/quo-human.md +0 -1042
- package/quo-mcp.md +0 -343
- /package/dist/{dock → box}/fcm.js +0 -0
- /package/dist/{dock → box}/webpush.js +0 -0
package/dist/cli/daemon.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 {
|
|
94
|
-
import { apnsHttp2, apnsOf } from '../
|
|
95
|
-
import { GRANTS, Grants } from '../
|
|
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
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
harbor.
|
|
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 =
|
|
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
|
|
270
|
-
grants =
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
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 =
|
|
345
|
+
const push = await lent(harbor, PUSH);
|
|
299
346
|
if (!push)
|
|
300
347
|
throw new Error('this box has no push faculty');
|
|
301
|
-
const
|
|
302
|
-
|
|
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 ??
|
|
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 !==
|
|
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(
|
|
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(
|
|
413
|
+
throw new Error(JSON.stringify(kept.error));
|
|
361
414
|
}
|
|
362
|
-
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
}
|
package/dist/cli/estate.js
CHANGED
|
@@ -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
|
|
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
|
|
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),
|
package/dist/cli/nervur.js
CHANGED
|
@@ -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>
|
|
17
|
-
// nervur
|
|
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
|
|
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.
|
|
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
|
|
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, '
|
|
71
|
-
|
|
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.
|
|
80
|
-
|
|
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(
|
|
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,
|
|
102
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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) : {},
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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) => {
|
package/dist/cli/stand.d.ts
CHANGED
|
@@ -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
|
-
|
|
34
|
+
async function standUp(harbor, decl) {
|
|
35
35
|
const h = harbor;
|
|
36
36
|
const booted = [];
|
|
37
37
|
for (const b of decl.boot ?? []) {
|
package/dist/harbor/browser.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { BeingClass } from 'nervur';
|
|
2
|
-
import { Harbor, type Dialer, type
|
|
3
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
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;
|