@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/human/worlds.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Avatar, USER } from '../beings/avatar.js';
|
|
2
|
-
import { settled } from '../
|
|
2
|
+
import { settled } from '../box/join.js';
|
|
3
3
|
// The list as a browser keeps it, the tab's and the app's alike: one item
|
|
4
4
|
// under one key. A device with no storage still works and forgets, which is
|
|
5
5
|
// why every read has a floor.
|
|
@@ -84,7 +84,7 @@ export async function fresh(ward) {
|
|
|
84
84
|
// is a tab that hangs.
|
|
85
85
|
for (let n = 1; n <= 64; n++) {
|
|
86
86
|
const key = `r${n}`;
|
|
87
|
-
const out = (await ward.ask('boot', { key, class: 'Presence' }));
|
|
87
|
+
const out = (await ward.ask('boot', { key, class: 'beings/Presence' }));
|
|
88
88
|
if (out.error)
|
|
89
89
|
continue;
|
|
90
90
|
// She takes what the box lends at her birth, and birth is synchronous
|
|
@@ -92,7 +92,7 @@ export async function fresh(ward) {
|
|
|
92
92
|
// landed, not before: a screen painted from her describe a moment early
|
|
93
93
|
// is a page missing every faculty of the box, and nothing paints it
|
|
94
94
|
// again until the person navigates.
|
|
95
|
-
await settled();
|
|
95
|
+
await settled(ward);
|
|
96
96
|
return { key, avatar: ward.being(key) };
|
|
97
97
|
}
|
|
98
98
|
throw new Error('the tab could not boot an avatar: every key it tries is taken');
|
package/dist/mcp/agent.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { Json, JsonObject } from 'nervur';
|
|
|
2
2
|
import type { Avatar } from '../beings/avatar.ts';
|
|
3
3
|
import { type Serving } from '../beings/side.ts';
|
|
4
4
|
export type Run = (event: JsonObject) => Promise<Json>;
|
|
5
|
-
export declare const REPORT = "report";
|
|
6
5
|
export declare function processRun(command: string, args: string[], dir: string): Run;
|
|
7
6
|
export type Agent = Serving & {
|
|
8
7
|
idle(): Promise<void>;
|
package/dist/mcp/agent.js
CHANGED
|
@@ -13,7 +13,7 @@ import { spawn } from 'node:child_process';
|
|
|
13
13
|
import { word, wordText, SILENCE_TEXT, UNREACHED_TEXT } from '../beings/side.js';
|
|
14
14
|
// The ask on the user being that takes the result. Gated to devices; the
|
|
15
15
|
// agent's client identity is what the gate reads.
|
|
16
|
-
|
|
16
|
+
const REPORT = 'report';
|
|
17
17
|
// `claude -p` or any CLI agent: the command runs in the agent's folder with
|
|
18
18
|
// the event, as JSON, as its last argument. The result is what it printed.
|
|
19
19
|
// A non-zero exit is an error object with what it said on stderr.
|
package/dist/mcp/allow.js
CHANGED
|
@@ -49,8 +49,9 @@ export class Exchange {
|
|
|
49
49
|
if (!world)
|
|
50
50
|
return again('That invitation is for a world that does not live here.');
|
|
51
51
|
const identity = word(f.get('identity'));
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// the ward judges the rest: an identity that is one of its beings is refused at the join
|
|
53
|
+
if (identity === null)
|
|
54
|
+
return again('An identity is one word.');
|
|
54
55
|
const joined = await this.o.join(identity, inv, world.ward);
|
|
55
56
|
if (joined.error)
|
|
56
57
|
return again(`Not in: ${joined.error}.`);
|
package/dist/mcp/http.d.ts
CHANGED
package/dist/mcp/http.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// for an hour is dropped, since HTTP tells the server nothing else.
|
|
11
11
|
import { WebStandardStreamableHTTPServerTransport as StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
12
12
|
import { mcpSide } from './server.js';
|
|
13
|
-
|
|
13
|
+
const SESSION_IDLE = 60 * 60 * 1000;
|
|
14
14
|
export class McpHttp {
|
|
15
15
|
sessions = new Map();
|
|
16
16
|
resolve;
|
package/dist/mcp/oauth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Grants } from '../
|
|
2
|
-
export { ACCESS_TTL, REFRESH_TTL } from '../
|
|
1
|
+
import { type Bearers as Grants } from '../box/grants.ts';
|
|
2
|
+
export { ACCESS_TTL, REFRESH_TTL } from '../box/grants.ts';
|
|
3
3
|
export declare const CODE_TTL: number;
|
|
4
4
|
export type Client = {
|
|
5
5
|
client_id: string;
|
|
@@ -82,10 +82,9 @@ export declare class OAuth {
|
|
|
82
82
|
error: string;
|
|
83
83
|
}>;
|
|
84
84
|
token(body: URLSearchParams): Promise<Record<string, unknown>>;
|
|
85
|
-
issue(identity: string, ward: string, client_id: string): Promise<import("../
|
|
85
|
+
issue(identity: string, ward: string, client_id: string): Promise<import("../box/grants.ts").Tokens>;
|
|
86
86
|
sweep(): void;
|
|
87
87
|
handle(req: Request, rest: string): Promise<Response | undefined>;
|
|
88
88
|
challenge(): Response;
|
|
89
89
|
}
|
|
90
|
-
export declare function readBody(req: Request, limit?: number): Promise<string>;
|
|
91
90
|
export declare function readForm(req: Request): Promise<URLSearchParams>;
|
package/dist/mcp/oauth.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// S256, the code grant, refresh, opaque tokens. It is a route, not Quo. Its
|
|
6
6
|
// one job is to end with a client identity, which the front desk trades for
|
|
7
7
|
// an invitation and the avatar knocks with; the grant it issues is the
|
|
8
|
-
// box's, `
|
|
8
|
+
// box's, `box/grants.ts`, honoured on every route, and what this file
|
|
9
9
|
// keeps for itself is only the dance: clients, pending requests and codes.
|
|
10
10
|
// No secret of Quo's is ever here.
|
|
11
11
|
//
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
// The web route finishes a request by calling `complete(request, identity)`
|
|
19
19
|
// after its allow page, where the human hands the invitation the root minted;
|
|
20
20
|
// that returns the redirect that carries the code back to the client.
|
|
21
|
-
import { REFRESH_TTL } from '../
|
|
22
|
-
export { ACCESS_TTL, REFRESH_TTL } from '../
|
|
21
|
+
import { REFRESH_TTL } from '../box/grants.js';
|
|
22
|
+
export { ACCESS_TTL, REFRESH_TTL } from '../box/grants.js';
|
|
23
23
|
// A code, a client id: every key here is a string the far side chose, and a
|
|
24
24
|
// bare lookup would find `__proto__` and hand back an object with no
|
|
25
25
|
// identity. Own keys only.
|
|
@@ -212,7 +212,7 @@ function withQuery(base, params) {
|
|
|
212
212
|
u.searchParams.set(k, v);
|
|
213
213
|
return u.toString();
|
|
214
214
|
}
|
|
215
|
-
|
|
215
|
+
async function readBody(req, limit = 64 * 1024) {
|
|
216
216
|
const s = await req.text();
|
|
217
217
|
if (s.length > limit)
|
|
218
218
|
throw new Error('too large');
|
package/dist/mcp/route.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OAuth, type Store } from './oauth.ts';
|
|
2
2
|
import type { McpHttp } from './http.ts';
|
|
3
3
|
import type { Handler } from '../harbor/http.ts';
|
|
4
|
-
import type { Grants } from '../
|
|
4
|
+
import type { Bearers as Grants } from '../box/grants.ts';
|
|
5
5
|
export type Routes = {
|
|
6
6
|
mcp: string;
|
|
7
7
|
web: string;
|
package/dist/mcp/runner.d.ts
CHANGED
package/dist/mcp/runner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { word, wordText, SILENCE_TEXT, UNREACHED_TEXT } from '../beings/side.js';
|
|
2
2
|
import { presentation } from '../beings/look.js';
|
|
3
|
-
|
|
3
|
+
const TURNS = 10;
|
|
4
4
|
// Her describe, spoken as a tools array. Name, description and input are
|
|
5
5
|
// verbatim, with the one narrowing an endpoint has asked for: an ask that
|
|
6
6
|
// declares no properties is sent with an empty `properties`, because
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -6,6 +6,5 @@ import { type Look } from '../beings/look.ts';
|
|
|
6
6
|
export declare const NAME = "nervur";
|
|
7
7
|
export declare const VERSION = "0.0.0";
|
|
8
8
|
export type { Subject } from '../beings/side.ts';
|
|
9
|
-
export declare const DESCRIBE: Tool;
|
|
10
9
|
export declare function tools(bp: Blueprint, look?: Look): Tool[];
|
|
11
10
|
export declare function mcpSide(avatar: Subject, transport: Transport, after?: () => Promise<void>): Promise<Serving>;
|
package/dist/mcp/server.js
CHANGED
|
@@ -16,7 +16,7 @@ import { isSilence, isWord, digest } from 'nervur';
|
|
|
16
16
|
export const NAME = 'nervur';
|
|
17
17
|
export const VERSION = '0.0.0';
|
|
18
18
|
// The empty ask as a tool: her describe whole, asks and notes.
|
|
19
|
-
|
|
19
|
+
const DESCRIBE = { name: 'describe', description: 'the empty ask: her describe, the asks and the notes', inputSchema: { type: 'object' } };
|
|
20
20
|
// Her describe, spoken as tools, the empty ask first. Name, description and
|
|
21
21
|
// input are verbatim; an output schema crosses when she declared one. Her
|
|
22
22
|
// look, when she has one, is the hints: a title, and the annotations a host
|
package/harbor/browser.ts
CHANGED
|
@@ -7,56 +7,54 @@
|
|
|
7
7
|
// a second tab on one origin meets the lock and is a screen, not a harbor.
|
|
8
8
|
// In an app the store is handed in, and the process that holds it is the
|
|
9
9
|
// lease: an app has one harbor and nothing else can open its files. The
|
|
10
|
-
// classes are the
|
|
11
|
-
//
|
|
10
|
+
// classes are the dock's beings under `beings/`, plus whatever a page hands
|
|
11
|
+
// in beside them under `classes/`.
|
|
12
12
|
import type { BeingClass } from 'nervur';
|
|
13
|
-
import {
|
|
14
|
-
import { Harbor, dial, type Dialer, type
|
|
15
|
-
import {
|
|
13
|
+
import { BEINGS } from '../beings/index.ts';
|
|
14
|
+
import { Harbor, dial, type Dialer, type Store } from 'nervur/harbor';
|
|
15
|
+
import { BOX_SEED, harborageOver, keyed, settled, standHarbor, type Tree } from '../box/index.ts';
|
|
16
16
|
import { Idb } from './idb.ts';
|
|
17
17
|
|
|
18
|
-
export const BUILT_IN: Record<string, BeingClass> = { User, Desk, Avatar, Presence, Courier, ...FACULTY_CLASSES };
|
|
19
|
-
|
|
20
18
|
export class BrowserHarbor extends Harbor {
|
|
21
19
|
readonly name: string;
|
|
22
20
|
readonly dialers: Dialer[] = [];
|
|
23
21
|
readonly #locked: boolean;
|
|
24
22
|
#release: (() => void) | undefined;
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
// to an invitation on a being of its own ward. `stand` builds one; a bare
|
|
28
|
-
// tab hands none and every name answers null.
|
|
29
|
-
constructor(store: string | Store = 'nervur', classes: Record<string, BeingClass> = {}, lend?: Lend) {
|
|
24
|
+
constructor(store: string | Store = 'nervur', classes: Record<string, BeingClass> = {}) {
|
|
30
25
|
const s = typeof store === 'string' ? new Idb(store) : store;
|
|
31
|
-
super(s, async () => ({ ...
|
|
26
|
+
super(s, async () => keyed({ ...BEINGS, classes }));
|
|
32
27
|
this.name = typeof store === 'string' ? store : 'nervur';
|
|
33
28
|
this.#locked = typeof store === 'string';
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
//
|
|
37
|
-
// its own and lends nothing.
|
|
38
|
-
|
|
31
|
+
// The classes this terrain brings to its own ward, as a tree. A bare tab
|
|
32
|
+
// brings none, stands no ward of its own and lends nothing.
|
|
33
|
+
faculties: Tree | undefined;
|
|
34
|
+
// What this harbor is to its faculties, up once it has booted.
|
|
35
|
+
readonly box = harborageOver(() => this.wards.get(BOX_SEED)?.save() ?? Promise.resolve());
|
|
39
36
|
|
|
40
|
-
// What this box
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// nothing until
|
|
44
|
-
async stand(
|
|
45
|
-
this.
|
|
46
|
-
if (this.wards.size > 0)
|
|
37
|
+
// What this box brings is said before it boots, and standing its own ward
|
|
38
|
+
// is act one of booting: a being born in any other ward takes the box at
|
|
39
|
+
// her ward's birth, and a box that was not standing yet would leave that
|
|
40
|
+
// ward holding nothing until its next boot.
|
|
41
|
+
async stand(faculties: Tree): Promise<void> {
|
|
42
|
+
this.faculties = faculties;
|
|
43
|
+
if (this.wards.size > 0) await standHarbor(this, faculties, this.box);
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
// Take the lock and boot every ward kept here. A lock already held is
|
|
50
47
|
// another tab's harbor over this seed, and this one refuses to boot.
|
|
51
48
|
override async boot(): Promise<void> {
|
|
52
49
|
if (this.#locked) await this.#lease();
|
|
53
|
-
if (this.
|
|
50
|
+
if (this.faculties) await standHarbor(this, this.faculties, this.box);
|
|
54
51
|
await super.boot();
|
|
55
52
|
// Every being who takes what this box lends does it at her birth, and
|
|
56
53
|
// birth is synchronous while taking a standing is not. A box is up when
|
|
57
54
|
// they have landed, not before, here as on a daemon: a page painted
|
|
58
55
|
// ahead of them shows a device that can do nothing.
|
|
59
|
-
await settled();
|
|
56
|
+
await settled(this);
|
|
57
|
+
await this.box.raise();
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
// Hold one socket to a world's quo. route.
|
package/harbor/capacitor.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { Geolocation } from '@capacitor/geolocation';
|
|
|
17
17
|
import { LocalNotifications } from '@capacitor/local-notifications';
|
|
18
18
|
import { PushNotifications } from '@capacitor/push-notifications';
|
|
19
19
|
import type { BeingClass, JsonObject } from 'nervur';
|
|
20
|
-
import {
|
|
20
|
+
import { Location, Notifications, type Token, type Where } from '../box/index.ts';
|
|
21
21
|
import { BrowserHarbor } from './browser.ts';
|
|
22
22
|
import { Sealed, type Files, type Secrets } from './sealed.ts';
|
|
23
23
|
|
|
@@ -34,6 +34,9 @@ const asked = (what: string) => new Error(`the human is being asked to allow ${w
|
|
|
34
34
|
const refused = (what: string) => new Error(`${what} is not allowed on this device`);
|
|
35
35
|
|
|
36
36
|
export class PhoneLocation extends Location {
|
|
37
|
+
static override stands(): boolean {
|
|
38
|
+
return Capacitor.isPluginAvailable('Geolocation');
|
|
39
|
+
}
|
|
37
40
|
override async where(): Promise<Where | JsonObject> {
|
|
38
41
|
const { location } = await Geolocation.checkPermissions();
|
|
39
42
|
if (location === 'prompt' || location === 'prompt-with-rationale') {
|
|
@@ -47,6 +50,9 @@ export class PhoneLocation extends Location {
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
export class PhoneNotifications extends Notifications {
|
|
53
|
+
static override stands(): boolean {
|
|
54
|
+
return Capacitor.isPluginAvailable('LocalNotifications');
|
|
55
|
+
}
|
|
50
56
|
override async notify(args: JsonObject): Promise<JsonObject> {
|
|
51
57
|
const title = typeof args.title === 'string' ? args.title : '';
|
|
52
58
|
const body = typeof args.body === 'string' ? args.body : '';
|
|
@@ -101,11 +107,6 @@ export class PhoneNotifications extends Notifications {
|
|
|
101
107
|
}
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
// What a phone lends: the two faculties above under the names every box
|
|
105
|
-
// lends them under, and nothing a phone has not. The harbor holds these
|
|
106
|
-
// class bodies for its own ward, and boots one being of each in it.
|
|
107
|
-
export const PHONE_FACULTIES: Record<string, BeingClass> = { Location: PhoneLocation, Notifications: PhoneNotifications };
|
|
108
|
-
export const phoneLent: Record<string, string> = { [LOCATION]: 'Location', [NOTIFICATIONS]: 'Notifications' };
|
|
109
110
|
|
|
110
111
|
// The phone's ear, as `human/app.ts` asks for one: the token this device is
|
|
111
112
|
// reachable at while it sleeps, or null.
|
|
@@ -163,8 +164,8 @@ export function phoneEar(sandbox = false, within = 10_000, named: { bell?: strin
|
|
|
163
164
|
// told `wake` every time the app comes to the foreground, since a phone
|
|
164
165
|
// asleep loses its sockets silently.
|
|
165
166
|
export async function nativeHarbor(name = 'nervur', classes: Record<string, BeingClass> = {}): Promise<BrowserHarbor> {
|
|
166
|
-
const h = new BrowserHarbor(await Native.open(name),
|
|
167
|
-
await h.stand(
|
|
167
|
+
const h = new BrowserHarbor(await Native.open(name), classes);
|
|
168
|
+
await h.stand({ harbor: { PhoneLocation, PhoneNotifications } });
|
|
168
169
|
await h.boot();
|
|
169
170
|
await App.addListener('appStateChange', ({ isActive }) => {
|
|
170
171
|
if (isActive) h.wake();
|
package/harbor/disk.ts
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
// The node daemon's harbor: the core over a folder on disk. The store is
|
|
3
3
|
// `files.ts`, one folder per ward; the class bodies come from a module the
|
|
4
4
|
// ward record names, loaded from the folder; the lease is a pid file. The
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// class source adds to them.
|
|
5
|
+
// dock's beings are held by every harbor here under `beings/`, and a
|
|
6
|
+
// folder's class source adds to them under the folder it sits in.
|
|
8
7
|
//
|
|
9
8
|
// <dir>/
|
|
10
9
|
// lease pid of the one process that holds this harbor
|
|
@@ -16,26 +15,25 @@
|
|
|
16
15
|
// reach.json the directory's hints
|
|
17
16
|
import { mkdir, readFile, writeFile, unlink, stat } from 'node:fs/promises';
|
|
18
17
|
import { existsSync } from 'node:fs';
|
|
19
|
-
import { join, resolve, isAbsolute } from 'node:path';
|
|
18
|
+
import { dirname, join, resolve, isAbsolute, relative } from 'node:path';
|
|
20
19
|
import { pathToFileURL } from 'node:url';
|
|
21
20
|
import { register } from 'node:module';
|
|
22
21
|
import process from 'node:process';
|
|
23
22
|
import type { BeingClass } from 'nervur';
|
|
24
|
-
import {
|
|
23
|
+
import { BEINGS } from '../beings/index.ts';
|
|
25
24
|
import { setup } from '../beings/setup.ts';
|
|
26
|
-
import { Harbor, DEFAULT_CODE,
|
|
27
|
-
import {
|
|
25
|
+
import { Harbor, DEFAULT_CODE, BOX_SEED, type Hosted } from 'nervur/harbor';
|
|
26
|
+
import { BOX, harborageOver, keyed, settled, standHarbor, type Tree } from '../box/index.ts';
|
|
28
27
|
import { Files } from './files.ts';
|
|
29
28
|
|
|
30
29
|
export type { Hosted, Bound } from 'nervur/harbor';
|
|
31
30
|
export type { WardRecord } from 'nervur/harbor';
|
|
32
|
-
export const BUILT_IN: Record<string, BeingClass> = { User, Desk, Avatar, Presence, Clock, Courier, ...FACULTY_CLASSES };
|
|
33
31
|
|
|
34
32
|
// The default ward, the one a caller means when naming none: the one marked,
|
|
35
33
|
// or the one ward hosted when there is exactly one, which is every harbor an
|
|
36
34
|
// adopter stands with one `nervur init`. Several wards and none marked is
|
|
37
35
|
// refused by name, since agents and a door would otherwise hang off a
|
|
38
|
-
// guess. The
|
|
36
|
+
// guess. The box ward is furniture, never a candidate.
|
|
39
37
|
export function defaultWard(marked: string[], hosted: string[]): string {
|
|
40
38
|
if (marked.length > 1) throw new Error(`this device marks ${marked.join(', ')} as default, and one at most may be`);
|
|
41
39
|
const one = marked[0];
|
|
@@ -43,7 +41,7 @@ export function defaultWard(marked: string[], hosted: string[]): string {
|
|
|
43
41
|
if (!hosted.includes(one)) throw new Error(`this device says its default ward is ${one}, and hosts no such ward`);
|
|
44
42
|
return one;
|
|
45
43
|
}
|
|
46
|
-
const [first, ...more] = hosted.filter((n) => n !==
|
|
44
|
+
const [first, ...more] = hosted.filter((n) => n !== BOX_SEED);
|
|
47
45
|
if (first !== undefined && more.length === 0) return first;
|
|
48
46
|
const names = first === undefined ? [] : [first, ...more];
|
|
49
47
|
throw new Error(names.length === 0 ? 'this device hosts no ward: run `nervur init`' : `this device hosts ${names.join(', ')} and says which is the default: nervur default <name>`);
|
|
@@ -89,19 +87,30 @@ export class DiskHarbor extends Harbor {
|
|
|
89
87
|
// ward and nothing in Quo knows of it: a harbor is not a being and has no
|
|
90
88
|
// self to be, so it can only point at one of its wards. Its agents boot
|
|
91
89
|
// there, and its door answers for it when a caller names no ward. The one
|
|
92
|
-
// ward it hosts when there is one, the one
|
|
93
|
-
// the daemon at boot;
|
|
90
|
+
// ward it hosts when there is one, the one the Dock's cells say otherwise,
|
|
91
|
+
// placed by the root's `default` ask and resolved by the daemon at boot;
|
|
92
|
+
// empty until then.
|
|
94
93
|
defaultWard = '';
|
|
95
94
|
|
|
96
|
-
// The
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
95
|
+
// The root saying what the box is, with no daemon over the folder: the
|
|
96
|
+
// Dock's own asks, `domain`, `apps` and `default`, on the box ward stood
|
|
97
|
+
// alone, which the lease refuses while a daemon serves. The daemon reads
|
|
98
|
+
// what she keeps at its next boot, so a domain said is stop, say, start,
|
|
99
|
+
// like an init.
|
|
100
|
+
static async say(dir: string, method: 'domain' | 'apps' | 'default', args: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
100
101
|
const h = new DiskHarbor(dir);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
h.faculties = {};
|
|
103
|
+
await h.boot();
|
|
104
|
+
try {
|
|
105
|
+
const ward = h.wards.get(BOX_SEED);
|
|
106
|
+
if (!ward) throw new Error('this box stands no ward of its own');
|
|
107
|
+
const out = (await ward.ask('ask', { being: BOX, method, args })) as Record<string, unknown>;
|
|
108
|
+
if (typeof out.error === 'string') throw new Error(out.error);
|
|
109
|
+
await ward.save();
|
|
110
|
+
return out;
|
|
111
|
+
} finally {
|
|
112
|
+
await h.close();
|
|
113
|
+
}
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
// Whether a ward is shown at the web route: listed at its root and its
|
|
@@ -122,52 +131,45 @@ export class DiskHarbor extends Harbor {
|
|
|
122
131
|
return existsSync(join(resolve(dir), 'wards', name, 'shown'));
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
static async marked(dir: string): Promise<string[]> {
|
|
126
|
-
const h = new DiskHarbor(dir);
|
|
127
|
-
return (await h.store.list()).filter((n) => existsSync(join(h.dir, 'wards', n, 'default')));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// The default ward read from the folder alone, for a command with no
|
|
131
|
-
// daemon in front of it and no `--ward` given.
|
|
132
|
-
static async defaultOf(dir: string): Promise<string> {
|
|
133
|
-
const h = new DiskHarbor(dir);
|
|
134
|
-
return defaultWard(await DiskHarbor.marked(dir), await h.store.list());
|
|
135
|
-
}
|
|
136
134
|
#held = false;
|
|
137
135
|
|
|
138
|
-
|
|
139
|
-
// an invitation on a being of its own ward, which `stand` puts up. A
|
|
140
|
-
// folder booted by a command lends nothing.
|
|
141
|
-
// `own` are this terrain's own class bodies, the faculty subclasses a box
|
|
142
|
-
// has: they stand in front of the built-ins, so a daemon that can ring
|
|
143
|
-
// hands its own `Push` and the dock ward boots that one.
|
|
144
|
-
constructor(dir: string, lend?: Lend, own: Record<string, BeingClass> = {}) {
|
|
136
|
+
constructor(dir: string) {
|
|
145
137
|
const abs = resolve(dir);
|
|
146
138
|
// The key the environment gives, the edge's name for it: from the
|
|
147
139
|
// Keychain through the app that spawned this daemon, or nothing on a
|
|
148
140
|
// droplet, whose folder stays plain under its file modes.
|
|
149
|
-
|
|
141
|
+
// A class is keyed where it lives: the dock's under `beings/`, the
|
|
142
|
+
// folder's under the folder its source sits in, `classes/Corpus`.
|
|
143
|
+
const lookup = async (rec: { code: string }) => {
|
|
144
|
+
const path = isAbsolute(rec.code) ? rec.code : join(abs, rec.code);
|
|
145
|
+
return keyed({ ...BEINGS, [relative(abs, dirname(path))]: await loadClasses(path) });
|
|
146
|
+
};
|
|
147
|
+
super(new Files(abs, process.env.NERVUR_SEED_KEY), lookup);
|
|
150
148
|
this.dir = abs;
|
|
151
149
|
}
|
|
152
150
|
|
|
153
|
-
//
|
|
154
|
-
// that
|
|
155
|
-
|
|
151
|
+
// The classes this terrain brings to its own ward, as a tree. Set before
|
|
152
|
+
// `boot` and read there; a box that brings none stands no ward of its own
|
|
153
|
+
// and lends nothing. The folder's own classes join them under the folder
|
|
154
|
+
// they live in, so an estate's faculty stands beside the terrain's.
|
|
155
|
+
faculties: Tree | undefined;
|
|
156
|
+
// What this harbor is to its faculties. The terrain raises it once the box
|
|
157
|
+
// is up, which on a daemon is after its routes are mounted.
|
|
158
|
+
readonly box = harborageOver(() => this.wards.get(BOX_SEED)?.save() ?? Promise.resolve());
|
|
156
159
|
|
|
157
160
|
// Create a harbor folder with one ward, and the root's setup in it: the
|
|
158
161
|
// front desk, public, and one user being, with the desk holding a standing
|
|
159
162
|
// to her placed by the root's knock. Boots the ward once, with no lease, to
|
|
160
163
|
// do that and to learn its pk, and writes the record. Refuses a folder that
|
|
161
164
|
// has one.
|
|
162
|
-
static async init(dir: string, name = 'main', user = 'me', cls
|
|
165
|
+
static async init(dir: string, name = 'main', user = 'me', cls?: string, shown = false): Promise<{ dir: string; name: string; pk: string; user: string }> {
|
|
163
166
|
const h = new DiskHarbor(dir);
|
|
164
167
|
if ((await h.store.list()).includes(name)) throw new Error(`ward ${name} already exists in ${h.dir}`);
|
|
165
168
|
await mkdir(join(h.dir, 'classes'), { recursive: true });
|
|
166
169
|
const classes = join(h.dir, DEFAULT_CODE);
|
|
167
|
-
if (!existsSync(classes)) await writeFile(classes,
|
|
170
|
+
if (!existsSync(classes)) await writeFile(classes, "// The classes this harbor holds beside the dock's beings. Each export is classes/<its name>.\nexport {};\n");
|
|
168
171
|
const hosted = await h.create(name, user);
|
|
169
|
-
await setup(hosted, user, cls);
|
|
170
|
-
if (mark) await DiskHarbor.setDefault(h.dir, name);
|
|
172
|
+
await setup(hosted, user, cls ? { home: cls } : {});
|
|
171
173
|
if (shown) await DiskHarbor.setShown(h.dir, name, true);
|
|
172
174
|
return { dir: h.dir, name, pk: hosted.pk, user };
|
|
173
175
|
}
|
|
@@ -181,12 +183,15 @@ export class DiskHarbor extends Harbor {
|
|
|
181
183
|
// it yet would leave her holding nothing until her next birth.
|
|
182
184
|
override async boot(): Promise<void> {
|
|
183
185
|
await this.#lease();
|
|
184
|
-
if (this.
|
|
186
|
+
if (this.faculties) {
|
|
187
|
+
const tree = { ...this.faculties, [dirname(DEFAULT_CODE)]: await loadClasses(join(this.dir, DEFAULT_CODE)) };
|
|
188
|
+
await standHarbor(this, tree, this.box);
|
|
189
|
+
}
|
|
185
190
|
await super.boot();
|
|
186
191
|
// Every being who takes what this box lends does it at her birth, and
|
|
187
192
|
// birth is synchronous while taking a standing is not. A box is up when
|
|
188
193
|
// they have landed, not before.
|
|
189
|
-
await settled();
|
|
194
|
+
await settled(this);
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
// Every ward's writing finished, then the lease. A ward says it wrote and
|
package/harbor/edge/alarm.ts
CHANGED
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
// is a terrain's answer and what it does when it wakes is not.
|
|
7
7
|
//
|
|
8
8
|
// One harbor is one Durable Object, and every ward of it lives in that
|
|
9
|
-
// object, so the alarm wakes the whole harbor
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import { Timer } from '../../dock/index.ts';
|
|
9
|
+
// object, so the alarm wakes the whole harbor. Only the object may set its
|
|
10
|
+
// own alarm, so the two calls are handed to her at birth by the class body
|
|
11
|
+
// the object brings.
|
|
12
|
+
import type { Stance } from 'nervur';
|
|
13
|
+
import { Timer, type Harborage } from '../../box/index.ts';
|
|
15
14
|
import type { Storage } from './platform.d.ts';
|
|
16
15
|
|
|
17
16
|
// Be woken at this moment, or at no moment at all. One alarm per object,
|
|
@@ -27,11 +26,16 @@ export const alarmsIn =
|
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
export class Alarm extends Timer {
|
|
30
|
-
// Handed
|
|
31
|
-
// and wakes nobody
|
|
32
|
-
alarms: Alarms | undefined;
|
|
29
|
+
// Handed at birth by the object that brings her. A timer holding none arms
|
|
30
|
+
// nothing and wakes nobody.
|
|
31
|
+
readonly alarms: Alarms | undefined;
|
|
33
32
|
#setting: Promise<void> = Promise.resolve();
|
|
34
33
|
|
|
34
|
+
constructor(stance: Stance, box?: Harborage, alarms?: Alarms) {
|
|
35
|
+
super(stance, box);
|
|
36
|
+
this.alarms = alarms;
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
// The arming written, for the device's own code and a suite: on this
|
|
36
40
|
// terrain an arming is a write to storage, and a reader of the alarm waits
|
|
37
41
|
// for it. It crosses no door.
|
|
@@ -40,9 +44,8 @@ export class Alarm extends Timer {
|
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
protected override arm(when: number | null): void {
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// own. An alarm the platform refused is armed again by the next round.
|
|
47
|
+
// An arming is left to finish on its own; an alarm the platform refused is
|
|
48
|
+
// armed again by the next round.
|
|
46
49
|
this.#setting = (this.alarms?.(when) ?? Promise.resolve()).catch(() => {});
|
|
47
50
|
}
|
|
48
51
|
}
|