@nervur-org/dock 0.3.0 → 0.4.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/GETTING_STARTED.md +22 -12
- package/{beings/GLOSSARY.md → GLOSSARY.md} +3 -3
- package/NOTICE +1 -1
- package/README.md +20 -16
- package/{beings/WORLDS.md → WORLDS.md} +6 -4
- package/api/route.ts +3 -3
- package/beings/avatar.ts +2 -2
- package/beings/carry.ts +2 -2
- package/beings/clock.ts +2 -2
- package/beings/courier.ts +2 -2
- package/beings/desk.ts +2 -2
- package/beings/doorbell.ts +2 -2
- package/beings/envoy.ts +2 -2
- package/beings/join.ts +2 -2
- package/beings/link.ts +5 -5
- package/beings/look.ts +1 -1
- package/beings/porter.ts +2 -2
- package/beings/presence.ts +1 -1
- package/beings/setup.ts +3 -3
- package/beings/side.ts +2 -2
- package/beings/user.ts +2 -2
- package/beings/way.ts +1 -1
- package/cli/client.ts +6 -6
- package/cli/daemon.ts +7 -7
- package/cli/estate/{quo.service → nervur.service} +12 -12
- package/cli/estate/org.nervur.plist +44 -0
- package/cli/estate.ts +6 -6
- package/cli/{quo.ts → nervur.ts} +43 -37
- package/cli/pilot.ts +3 -3
- package/cli/stand.ts +3 -3
- package/dist/api/route.d.ts +2 -2
- package/dist/api/route.js +2 -2
- package/dist/beings/avatar.d.ts +1 -1
- package/dist/beings/avatar.js +1 -1
- package/dist/beings/carry.d.ts +2 -2
- package/dist/beings/carry.js +1 -1
- package/dist/beings/clock.d.ts +2 -2
- package/dist/beings/clock.js +1 -1
- package/dist/beings/courier.d.ts +2 -2
- package/dist/beings/courier.js +1 -1
- package/dist/beings/desk.d.ts +3 -3
- package/dist/beings/desk.js +1 -1
- package/dist/beings/doorbell.d.ts +3 -3
- package/dist/beings/doorbell.js +1 -1
- package/dist/beings/envoy.d.ts +2 -2
- package/dist/beings/envoy.js +1 -1
- package/dist/beings/join.d.ts +2 -2
- package/dist/beings/link.d.ts +2 -2
- package/dist/beings/link.js +3 -3
- package/dist/beings/look.d.ts +1 -1
- package/dist/beings/porter.d.ts +2 -2
- package/dist/beings/porter.js +1 -1
- package/dist/beings/presence.d.ts +1 -1
- package/dist/beings/setup.d.ts +1 -1
- package/dist/beings/side.d.ts +1 -1
- package/dist/beings/side.js +1 -1
- package/dist/beings/user.d.ts +3 -3
- package/dist/beings/user.js +1 -1
- package/dist/beings/way.d.ts +1 -1
- package/dist/cli/client.js +4 -4
- package/dist/cli/daemon.d.ts +1 -1
- package/dist/cli/daemon.js +6 -6
- package/dist/cli/estate/{quo.service → nervur.service} +12 -12
- package/dist/cli/estate/org.nervur.plist +44 -0
- package/dist/cli/estate.js +6 -6
- package/dist/cli/{quo.js → nervur.js} +43 -37
- package/dist/cli/stand.d.ts +2 -2
- package/dist/dock/agents.d.ts +1 -1
- package/dist/dock/agents.js +1 -1
- package/dist/dock/apns.js +1 -1
- package/dist/dock/console.d.ts +1 -1
- package/dist/dock/dialers.d.ts +2 -2
- package/dist/dock/dialers.js +2 -2
- package/dist/dock/faculty.d.ts +2 -2
- package/dist/dock/faculty.js +1 -1
- package/dist/dock/grants.d.ts +1 -1
- package/dist/dock/grants.js +1 -1
- package/dist/dock/http2.js +1 -1
- package/dist/dock/index.d.ts +1 -1
- package/dist/dock/index.js +1 -1
- package/dist/dock/local.d.ts +2 -2
- package/dist/dock/location.d.ts +1 -1
- package/dist/dock/notifications.d.ts +1 -1
- package/dist/dock/notifications.js +1 -1
- package/dist/dock/push.d.ts +1 -1
- package/dist/dock/push.js +3 -3
- package/dist/dock/service.js +1 -1
- package/dist/dock/timer.d.ts +1 -1
- package/dist/harbor/browser.d.ts +2 -2
- package/dist/harbor/browser.js +4 -4
- package/dist/harbor/capacitor.d.ts +1 -1
- package/dist/harbor/capacitor.js +1 -1
- package/dist/harbor/disk.d.ts +4 -4
- package/dist/harbor/disk.js +5 -5
- package/dist/harbor/edge/edge.d.ts +2 -2
- package/dist/harbor/edge/edge.js +5 -5
- package/dist/harbor/edge/exercise.js +8 -8
- package/dist/harbor/edge/given.d.ts +1 -1
- package/dist/harbor/edge/platform.d.ts +1 -1
- package/dist/harbor/edge/storage.d.ts +1 -1
- package/dist/harbor/edge/storage.js +2 -2
- package/dist/harbor/edge/worker.js +2 -2
- package/dist/harbor/files.d.ts +1 -1
- package/dist/harbor/files.js +2 -2
- package/dist/harbor/http.d.ts +2 -2
- package/dist/harbor/http.js +1 -1
- package/dist/harbor/idb.d.ts +1 -1
- package/dist/harbor/idb.js +0 -0
- package/dist/harbor/quo.js +1 -1
- package/dist/harbor/seal.js +1 -1
- package/dist/harbor/sealed.d.ts +1 -1
- package/dist/harbor/sealed.js +4 -4
- package/dist/harbor/tauri.d.ts +1 -1
- package/dist/harbor/tauri.js +1 -1
- package/dist/harbor/ward.d.ts +1 -1
- package/dist/human/app.d.ts +2 -2
- package/dist/human/door.js +4 -4
- package/dist/human/guest.js +1 -1
- package/dist/human/html.d.ts +1 -1
- package/dist/human/html.js +1 -1
- package/dist/human/local.d.ts +1 -1
- package/dist/human/mark.js +2 -2
- package/dist/human/move.d.ts +2 -2
- package/dist/human/move.js +1 -1
- package/dist/human/screen.d.ts +1 -1
- package/dist/human/screen.js +3 -3
- package/dist/human/tab.bundle.txt +32 -32
- package/dist/human/tab.js +7 -7
- package/dist/human/tree.d.ts +1 -1
- package/dist/human/wake.d.ts +2 -2
- package/dist/human/wake.js +1 -1
- package/dist/human/web.d.ts +1 -1
- package/dist/human/web.js +4 -4
- package/dist/human/worker.bundle.txt +27 -27
- package/dist/human/worker.js +4 -4
- package/dist/human/worlds.d.ts +2 -2
- package/dist/human/worlds.js +4 -4
- package/dist/mcp/agent.d.ts +1 -1
- package/dist/mcp/allow.d.ts +1 -1
- package/dist/mcp/allow.js +2 -2
- package/dist/mcp/oauth.js +1 -1
- package/dist/mcp/runner.d.ts +1 -1
- package/dist/mcp/server.d.ts +2 -2
- package/dist/mcp/server.js +2 -2
- package/dock/agents.ts +2 -2
- package/dock/apns.ts +1 -1
- package/dock/console.ts +1 -1
- package/dock/dialers.ts +3 -3
- package/dock/faculty.ts +2 -2
- package/dock/grants.ts +2 -2
- package/dock/http2.ts +1 -1
- package/dock/index.ts +3 -3
- package/dock/local.ts +1 -1
- package/dock/location.ts +1 -1
- package/dock/notifications.ts +2 -2
- package/dock/push.ts +4 -4
- package/dock/service.ts +1 -1
- package/dock/timer.ts +1 -1
- package/harbor/browser.ts +5 -5
- package/harbor/capacitor.ts +2 -2
- package/harbor/disk.ts +8 -8
- package/harbor/edge/edge.ts +7 -7
- package/harbor/edge/exercise.ts +11 -11
- package/harbor/edge/given.ts +1 -1
- package/harbor/edge/platform.d.ts +1 -1
- package/harbor/edge/storage.ts +2 -2
- package/harbor/edge/worker.ts +2 -2
- package/harbor/edge/wrangler.toml +3 -3
- package/harbor/files.ts +2 -2
- package/harbor/http.ts +2 -2
- package/harbor/idb.ts +0 -0
- package/harbor/quo.ts +1 -1
- package/harbor/resolve.ts +1 -1
- package/harbor/seal.ts +1 -1
- package/harbor/sealed.ts +5 -5
- package/harbor/tauri.ts +2 -2
- package/harbor/ward.ts +1 -1
- package/human/app.ts +2 -2
- package/human/apps.ts +1 -1
- package/human/door.ts +4 -4
- package/human/guest.ts +2 -2
- package/human/html.ts +2 -2
- package/human/local.ts +1 -1
- package/human/mark.ts +2 -2
- package/human/move.ts +3 -3
- package/human/screen.ts +5 -5
- package/human/tab.bundle.txt +32 -32
- package/human/tab.ts +10 -10
- package/human/tree.ts +1 -1
- package/human/wake.ts +3 -3
- package/human/web.ts +5 -5
- package/human/worker.bundle.txt +27 -27
- package/human/worker.ts +6 -6
- package/human/worlds.ts +5 -5
- package/mcp/agent.ts +1 -1
- package/mcp/allow.ts +5 -5
- package/mcp/oauth.ts +1 -1
- package/mcp/runner.ts +1 -1
- package/mcp/server.ts +3 -3
- package/package.json +15 -7
- package/{api/quo-api.md → quo-api.md} +1 -1
- package/{beings/quo-dock.md → quo-dock.md} +74 -71
- package/{harbor/quo-harbor.md → quo-harbor.md} +54 -30
- package/{human/quo-human.md → quo-human.md} +7 -7
- package/{mcp/quo-mcp.md → quo-mcp.md} +10 -10
- package/cli/estate/systems.quo.plist +0 -44
- package/cli/quo-dock.md +0 -1942
- package/dist/cli/estate/systems.quo.plist +0 -44
- package/dock/quo-app-bells.md +0 -280
- package/dock/quo-dock.md +0 -1942
- /package/dist/cli/{quo.d.ts → nervur.d.ts} +0 -0
- /package/{beings/quo-app-bells.md → quo-app-bells.md} +0 -0
package/dist/beings/way.d.ts
CHANGED
package/dist/cli/client.js
CHANGED
|
@@ -10,14 +10,14 @@ import { sockPath, sidePath } from './daemon.js';
|
|
|
10
10
|
export function ask(dir, method, args = {}, ward, via) {
|
|
11
11
|
return line(dir, { ...(ward === undefined ? {} : { ward }), method, args, ...(via === undefined ? {} : { via }) });
|
|
12
12
|
}
|
|
13
|
-
// `
|
|
13
|
+
// `nervur ask`: the root asking a being of its own ward, as the device. The six
|
|
14
14
|
// owner asks make and place beings; this is the other half of the owner role,
|
|
15
15
|
// reaching into one, and without it a being's owner-gated ask had no caller
|
|
16
16
|
// but the daemon itself.
|
|
17
17
|
export function askBeing(dir, being, method, args = {}, ward) {
|
|
18
18
|
return line(dir, { ...(ward === undefined ? {} : { ward }), being, method, args });
|
|
19
19
|
}
|
|
20
|
-
// `
|
|
20
|
+
// `nervur reach`: a hint for the harbor's directory, which is the harbor's and
|
|
21
21
|
// not a ward's, so it crosses the root socket as its own line.
|
|
22
22
|
export function reach(dir, pk, url) {
|
|
23
23
|
return line(dir, { reach: { pk, url } });
|
|
@@ -26,7 +26,7 @@ function line(dir, body) {
|
|
|
26
26
|
return new Promise((ok, no) => {
|
|
27
27
|
const c = connect(sockPath(dir));
|
|
28
28
|
const id = Math.random().toString(36).slice(2);
|
|
29
|
-
c.once('error', (e) => no(e.code === 'ENOENT' || e.code === 'ECONNREFUSED' ? new Error(`no daemon at ${dir}: run \`
|
|
29
|
+
c.once('error', (e) => no(e.code === 'ENOENT' || e.code === 'ECONNREFUSED' ? new Error(`no daemon at ${dir}: run \`nervur serve\``) : e));
|
|
30
30
|
c.once('connect', () => {
|
|
31
31
|
c.write(JSON.stringify({ id, ...body }) + '\n');
|
|
32
32
|
createInterface({ input: c }).once('line', (reply) => {
|
|
@@ -40,7 +40,7 @@ function line(dir, body) {
|
|
|
40
40
|
export function side(dir, as, ward, run) {
|
|
41
41
|
return new Promise((ok, no) => {
|
|
42
42
|
const c = connect(sidePath(dir));
|
|
43
|
-
c.once('error', (e) => no(e.code === 'ENOENT' || e.code === 'ECONNREFUSED' ? new Error(`no daemon at ${dir}: run \`
|
|
43
|
+
c.once('error', (e) => no(e.code === 'ENOENT' || e.code === 'ECONNREFUSED' ? new Error(`no daemon at ${dir}: run \`nervur serve\``) : e));
|
|
44
44
|
c.once('connect', () => {
|
|
45
45
|
c.write(JSON.stringify(run ? { as, ward, run } : { as, ward }) + '\n');
|
|
46
46
|
const lines = createInterface({ input: c });
|
package/dist/cli/daemon.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DiskHarbor, type Hosted } from '../harbor/disk.ts';
|
|
2
|
-
import type { Harbor } from '
|
|
2
|
+
import type { Harbor } from 'nervur/harbor';
|
|
3
3
|
import type { Avatar } from '../beings/index.ts';
|
|
4
4
|
export { avatarOf, letIn, find } from '../beings/join.ts';
|
|
5
5
|
import { type Ring } from '../dock/index.ts';
|
package/dist/cli/daemon.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The daemon: `
|
|
2
|
+
// The daemon: `nervur serve`. One process holds the harbor and the ask pointer
|
|
3
3
|
// of every ward in it. Two local doors, never mixed, each a unix socket only
|
|
4
4
|
// the device's user can read:
|
|
5
5
|
//
|
|
6
|
-
//
|
|
6
|
+
// nervur.sock the root. One line of JSON in, one line out.
|
|
7
7
|
// -> { id, ward?, method?, args?, via? }
|
|
8
8
|
// <- { id, result } | { id, error }
|
|
9
9
|
// With `being`, the line is the owner's `ask`, forwarded like
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
// push faculty with one ring per token kind this device can ring.
|
|
60
60
|
//
|
|
61
61
|
// The default ward is the one a caller means when naming none: the one ward
|
|
62
|
-
// hosted, or the one marked in its own folder by `
|
|
62
|
+
// hosted, or the one marked in its own folder by `nervur default`. A harbor is
|
|
63
63
|
// not a being and has no self to be, so the one thing it can do is point at
|
|
64
64
|
// one of its wards. Its agents boot there and its door answers for it when a
|
|
65
65
|
// caller names no ward. Several wards and no mark is refused here, by name,
|
|
@@ -85,7 +85,7 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
85
85
|
import { tmpdir } from 'node:os';
|
|
86
86
|
import { join, resolve } from 'node:path';
|
|
87
87
|
import { createInterface } from 'node:readline';
|
|
88
|
-
import { isSilence, isWord, wordOf } from '
|
|
88
|
+
import { isSilence, isWord, wordOf } from 'nervur';
|
|
89
89
|
import { DiskHarbor, defaultWard } from '../harbor/disk.js';
|
|
90
90
|
// an identity in a world: the three asks any terrain's routes make at a ward
|
|
91
91
|
export { avatarOf, letIn, find } from '../beings/join.js';
|
|
@@ -116,9 +116,9 @@ const socketPath = (dir, name) => {
|
|
|
116
116
|
const inside = join(resolve(dir), `${name}.sock`);
|
|
117
117
|
if (inside.length < 100)
|
|
118
118
|
return inside;
|
|
119
|
-
return join(tmpdir(), `
|
|
119
|
+
return join(tmpdir(), `nervur-${createHash('sha256').update(resolve(dir)).digest('hex').slice(0, 16)}-${name}.sock`);
|
|
120
120
|
};
|
|
121
|
-
export const sockPath = (dir) => socketPath(dir, '
|
|
121
|
+
export const sockPath = (dir) => socketPath(dir, 'nervur');
|
|
122
122
|
export const sidePath = (dir) => socketPath(dir, 'side');
|
|
123
123
|
// Admit a local client identity into a ward: find or boot her avatar, and
|
|
124
124
|
// enter her with a nonce the box's way in honours once. On a reconnect she
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
# The daemon on a droplet, as one user, forever. Install with:
|
|
2
|
-
# sudo cp droplet/
|
|
3
|
-
# sudo systemctl enable --now
|
|
4
|
-
# The unit runs as the `
|
|
2
|
+
# sudo cp droplet/nervur.service /etc/systemd/system/nervur.service
|
|
3
|
+
# sudo systemctl enable --now nervur
|
|
4
|
+
# The unit runs as the `nervur` user, whose home holds the estate folder and
|
|
5
5
|
# the harbor folder, so the seed, the lease and the sockets are that user's
|
|
6
6
|
# alone. The HTTP door is loopback only; Caddy fronts it. See Caddyfile
|
|
7
7
|
# beside this file.
|
|
8
8
|
[Unit]
|
|
9
|
-
Description=
|
|
9
|
+
Description=nervur: one harbor on this device
|
|
10
10
|
After=network.target
|
|
11
11
|
|
|
12
12
|
[Service]
|
|
13
|
-
User=
|
|
14
|
-
Group=
|
|
15
|
-
Environment=
|
|
16
|
-
Environment=
|
|
13
|
+
User=nervur
|
|
14
|
+
Group=nervur
|
|
15
|
+
Environment=NERVUR_DIR=/home/nervur/.nervur
|
|
16
|
+
Environment=NERVUR_HTTP=8787
|
|
17
17
|
# CLAUDE_CODE_OAUTH_TOKEN, when an agent folder runs `claude -p`: the agent
|
|
18
18
|
# reads its credential from the device, never from cells.
|
|
19
|
-
EnvironmentFile=-/etc/
|
|
20
|
-
WorkingDirectory=/home/
|
|
21
|
-
ExecStart=/usr/bin/node /home/
|
|
19
|
+
EnvironmentFile=-/etc/nervur/env
|
|
20
|
+
WorkingDirectory=/home/nervur/ESTATE
|
|
21
|
+
ExecStart=/usr/bin/node /home/nervur/ESTATE/node_modules/@nervur-org/dock/dist/cli/nervur.js serve
|
|
22
22
|
Restart=always
|
|
23
23
|
RestartSec=2
|
|
24
24
|
KillSignal=SIGTERM
|
|
@@ -28,7 +28,7 @@ ProtectSystem=strict
|
|
|
28
28
|
# Agents run in their folders and Claude Code keeps its own state in the
|
|
29
29
|
# user's home; both must be writable for a run to happen, and a harbor
|
|
30
30
|
# with no agent has neither.
|
|
31
|
-
ReadWritePaths=/home/
|
|
31
|
+
ReadWritePaths=/home/nervur/.nervur -/home/nervur/agents -/home/nervur/.claude -/home/nervur/.claude.json /tmp
|
|
32
32
|
PrivateTmp=false
|
|
33
33
|
|
|
34
34
|
[Install]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
The daemon on a Mac, as the user, forever: a launchd agent. It holds the
|
|
4
|
+
harbor at ~/.nervur and the socket to every quo. route ~/.nervur/dial.json
|
|
5
|
+
names, so a Mac behind NAT is reached through the droplet it dialed. The
|
|
6
|
+
daemon runs the dock's emitted `dist/cli/nervur.js` from where the estate
|
|
7
|
+
installed it, never the source. Install, with node on the path:
|
|
8
|
+
|
|
9
|
+
sed "s#HOME#$HOME#g; s#NODE#$(which node)#g" mac/org.nervur.plist > ~/Library/LaunchAgents/org.nervur.plist
|
|
10
|
+
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.nervur.plist
|
|
11
|
+
|
|
12
|
+
Remove with `launchctl bootout gui/$(id -u)/org.nervur`; restart after a
|
|
13
|
+
wire change with `launchctl kickstart -k gui/$(id -u)/org.nervur`. The
|
|
14
|
+
log is ~/.nervur/serve.log. No HTTP door: a Mac is a dialer, not a listener.
|
|
15
|
+
-->
|
|
16
|
+
<plist version="1.0">
|
|
17
|
+
<dict>
|
|
18
|
+
<key>Label</key>
|
|
19
|
+
<string>org.nervur</string>
|
|
20
|
+
<key>ProgramArguments</key>
|
|
21
|
+
<array>
|
|
22
|
+
<string>NODE</string>
|
|
23
|
+
<string>HOME/ESTATE/node_modules/@nervur-org/dock/dist/cli/nervur.js</string>
|
|
24
|
+
<string>serve</string>
|
|
25
|
+
</array>
|
|
26
|
+
<key>EnvironmentVariables</key>
|
|
27
|
+
<dict>
|
|
28
|
+
<key>NERVUR_DIR</key>
|
|
29
|
+
<string>HOME/.nervur</string>
|
|
30
|
+
</dict>
|
|
31
|
+
<key>WorkingDirectory</key>
|
|
32
|
+
<string>HOME/ESTATE</string>
|
|
33
|
+
<key>RunAtLoad</key>
|
|
34
|
+
<true/>
|
|
35
|
+
<key>KeepAlive</key>
|
|
36
|
+
<true/>
|
|
37
|
+
<key>ThrottleInterval</key>
|
|
38
|
+
<integer>2</integer>
|
|
39
|
+
<key>StandardOutPath</key>
|
|
40
|
+
<string>HOME/.nervur/serve.log</string>
|
|
41
|
+
<key>StandardErrorPath</key>
|
|
42
|
+
<string>HOME/.nervur/serve.log</string>
|
|
43
|
+
</dict>
|
|
44
|
+
</plist>
|
package/dist/cli/estate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// `
|
|
3
|
-
// every estate has. One folder per harbor: `droplet/`, the
|
|
2
|
+
// `nervur estate DIR --domain D`: an estate folder, written once, of the shape
|
|
3
|
+
// every estate has. One folder per harbor: `droplet/`, the harbor directory
|
|
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
|
|
@@ -34,13 +34,13 @@ export async function estate(dir, domain) {
|
|
|
34
34
|
const name = basename(dir);
|
|
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
|
-
[`${name}.md`]: `# ${name}\n\nAn estate: all of one owner's harbors. One folder per harbor, each the
|
|
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
38
|
'droplet/routes.json': JSON.stringify(routesFor(domain)) + '\n',
|
|
39
|
-
'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.
|
|
39
|
+
'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
40
|
'droplet/classes/index.ts': '// The classes this harbor holds beside the dock\'s built-in ones, exported\n// by name.\nexport {};\n',
|
|
41
|
-
'droplet/
|
|
41
|
+
'droplet/nervur.service': (await template('nervur.service')).replaceAll('ESTATE', name),
|
|
42
42
|
'droplet/Caddyfile': (await template('Caddyfile')).replaceAll('DOMAIN', domain),
|
|
43
|
-
'mac/
|
|
43
|
+
'mac/org.nervur.plist': (await template('org.nervur.plist')).replaceAll('ESTATE', name),
|
|
44
44
|
};
|
|
45
45
|
for (const [rel, text] of Object.entries(files)) {
|
|
46
46
|
await mkdir(join(dir, rel, '..'), { recursive: true });
|
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
// `
|
|
3
|
+
// `nervur`: the one command of the estate. Thin on purpose: each subcommand
|
|
4
4
|
// picks a folder, reaches the daemon or the disk, and hands off. Logic that
|
|
5
5
|
// appears here was missing from a being or a side, and moves there.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// quo census [--dir D] [--ward W] [--via S] the empty ask: pk and every being
|
|
13
|
-
// quo boot [--dir D] [--ward W] [--via S] <json> { key, class, public? }
|
|
14
|
-
// quo invite [--dir D] [--ward W] [--via S] <json> { being, id }; with routes.json naming web., the answer carries the link
|
|
15
|
-
// quo knock [--dir D] [--ward W] [--via S] <json> { being | { boot, key }, id, invitation, method?, args?, wanted? }
|
|
16
|
-
// quo remove [--dir D] [--ward W] [--via S] <json> { being, id }: a relation out of a being; on the ward pk, an owner
|
|
17
|
-
// quo unboot [--dir D] [--ward W] [--via S] <json> { being }: a being out of the ward, with every relation she holds
|
|
18
|
-
// quo ask [--dir D] [--ward W] <being> [method] [json] the root asking a being of its own ward, as the device
|
|
19
|
-
// quo side [--dir D] [--ward W] --as NAME an avatar over stdio for a local MCP client; NAME is the client identity
|
|
20
|
-
// quo run [--dir D] [--ward W] --as NAME --url URL --model NAME [--turns N] a human at a terminal talking to a model through an avatar: a line in, the model's final text out
|
|
21
|
-
// quo pilot [--dir D] [--ward W] [--via S] the owner pilot over stdio: census, boot, invite, knock, remove
|
|
22
|
-
// quo reach [--dir D] <pk> <url> a hint for the harbor's directory: that ward pk lives at that quo. route
|
|
23
|
-
// quo estate <dir> --domain D an estate folder of the shape every estate has: a droplet's quo directory, unit and Caddyfile, a Mac's launchd agent, on this dock
|
|
7
|
+
// The command carries the kit's name and never the protocol's. `quo` is
|
|
8
|
+
// Quo's word, and it stays on the wire: the `quo.` route, a ward's door, the
|
|
9
|
+
// sealed wards' folder. Nothing a person types is called quo, and nothing a
|
|
10
|
+
// device holds for the daemon is either: the unit, the unix user, the home
|
|
11
|
+
// and the environment file all say nervur.
|
|
24
12
|
//
|
|
25
|
-
// D
|
|
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
|
|
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
|
+
// 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
|
|
18
|
+
// nervur census [--dir D] [--ward W] [--via S] the empty ask: pk and every being
|
|
19
|
+
// 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 knock [--dir D] [--ward W] [--via S] <json> { being | { boot, key }, id, invitation, method?, args?, wanted? }
|
|
22
|
+
// nervur remove [--dir D] [--ward W] [--via S] <json> { being, id }: a relation out of a being; on the ward pk, an owner
|
|
23
|
+
// nervur unboot [--dir D] [--ward W] [--via S] <json> { being }: a being out of the ward, with every relation she holds
|
|
24
|
+
// nervur ask [--dir D] [--ward W] <being> [method] [json] the root asking a being of its own ward, as the device
|
|
25
|
+
// nervur side [--dir D] [--ward W] --as NAME an avatar over stdio for a local MCP client; NAME is the client identity
|
|
26
|
+
// nervur run [--dir D] [--ward W] --as NAME --url URL --model NAME [--turns N] a human at a terminal talking to a model through an avatar: a line in, the model's final text out
|
|
27
|
+
// nervur pilot [--dir D] [--ward W] [--via S] the owner pilot over stdio: census, boot, invite, knock, remove
|
|
28
|
+
// nervur reach [--dir D] <pk> <url> a hint for the harbor's directory: that ward pk lives at that quo. route
|
|
29
|
+
// 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
|
+
// D defaults to $NERVUR_DIR, then ~/.nervur. W defaults to the default ward, and
|
|
26
32
|
// init names a new ward main when none is given. init takes
|
|
27
33
|
// --user NAME for the user being, defaulting to the device's user. run
|
|
28
|
-
// takes the model's base URL as --url or $
|
|
29
|
-
// --model or $
|
|
34
|
+
// takes the model's base URL as --url or $NERVUR_MODEL_URL, its name as
|
|
35
|
+
// --model or $NERVUR_MODEL, a bearer from $NERVUR_MODEL_KEY, read here and never
|
|
30
36
|
// kept, and --turns as the ceiling of model calls in one turn. S is a
|
|
31
37
|
// standing the user being holds at another ward, one the root there invited
|
|
32
38
|
// as an owner: with --via, the owner asks go to that ward's door, sealed,
|
|
@@ -34,7 +40,7 @@
|
|
|
34
40
|
import { homedir, userInfo } from 'node:os';
|
|
35
41
|
import { join } from 'node:path';
|
|
36
42
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
37
|
-
import { isInvitation } from '
|
|
43
|
+
import { isInvitation } from 'nervur';
|
|
38
44
|
import { DiskHarbor } from '../harbor/disk.js';
|
|
39
45
|
import { serve, readRoutes, writeRoutes } from './daemon.js';
|
|
40
46
|
import { ask, askBeing, side, reach } from './client.js';
|
|
@@ -55,7 +61,7 @@ function parse(argv) {
|
|
|
55
61
|
rest.push(a);
|
|
56
62
|
}
|
|
57
63
|
const [cmd, json, more, third] = rest;
|
|
58
|
-
return { cmd, json, more, third, flags, dir: flags.dir ?? process.env.
|
|
64
|
+
return { cmd, json, more, third, flags, dir: flags.dir ?? process.env.NERVUR_DIR ?? join(homedir(), '.nervur'), ward: flags.ward, via: flags.via };
|
|
59
65
|
}
|
|
60
66
|
const print = (v) => console.log(JSON.stringify(v, null, 2));
|
|
61
67
|
async function main(argv) {
|
|
@@ -67,7 +73,7 @@ async function main(argv) {
|
|
|
67
73
|
}
|
|
68
74
|
if (cmd === 'default') {
|
|
69
75
|
if (!json) {
|
|
70
|
-
console.error('
|
|
76
|
+
console.error('nervur default <ward>');
|
|
71
77
|
return 2;
|
|
72
78
|
}
|
|
73
79
|
await DiskHarbor.setDefault(dir, json);
|
|
@@ -76,7 +82,7 @@ async function main(argv) {
|
|
|
76
82
|
}
|
|
77
83
|
if (cmd === 'show' || cmd === 'hide') {
|
|
78
84
|
if (!json) {
|
|
79
|
-
console.error(`
|
|
85
|
+
console.error(`nervur ${cmd} <ward>`);
|
|
80
86
|
return 2;
|
|
81
87
|
}
|
|
82
88
|
await DiskHarbor.setShown(dir, json, cmd === 'show');
|
|
@@ -85,7 +91,7 @@ async function main(argv) {
|
|
|
85
91
|
}
|
|
86
92
|
if (cmd === 'domain') {
|
|
87
93
|
if (!json) {
|
|
88
|
-
console.error('
|
|
94
|
+
console.error('nervur domain <domain>');
|
|
89
95
|
return 2;
|
|
90
96
|
}
|
|
91
97
|
print({ routes: await writeRoutes(dir, json) });
|
|
@@ -97,18 +103,18 @@ async function main(argv) {
|
|
|
97
103
|
const wardOf = async () => flags.ward ?? (await DiskHarbor.defaultOf(dir));
|
|
98
104
|
if (cmd === 'side' || cmd === 'run') {
|
|
99
105
|
if (!flags.as) {
|
|
100
|
-
console.error(`
|
|
106
|
+
console.error(`nervur ${cmd} needs --as NAME`);
|
|
101
107
|
return 2;
|
|
102
108
|
}
|
|
103
109
|
let run;
|
|
104
110
|
if (cmd === 'run') {
|
|
105
|
-
const url = flags.url ?? process.env.
|
|
106
|
-
const model = flags.model ?? process.env.
|
|
111
|
+
const url = flags.url ?? process.env.NERVUR_MODEL_URL;
|
|
112
|
+
const model = flags.model ?? process.env.NERVUR_MODEL;
|
|
107
113
|
if (!url || !model) {
|
|
108
|
-
console.error('
|
|
114
|
+
console.error('nervur run needs --url URL and --model NAME, or NERVUR_MODEL_URL and NERVUR_MODEL');
|
|
109
115
|
return 2;
|
|
110
116
|
}
|
|
111
|
-
const key = process.env.
|
|
117
|
+
const key = process.env.NERVUR_MODEL_KEY;
|
|
112
118
|
const turns = flags.turns !== undefined ? Number(flags.turns) : undefined;
|
|
113
119
|
if (turns !== undefined && !(Number.isInteger(turns) && turns > 0)) {
|
|
114
120
|
console.error('--turns is a positive integer');
|
|
@@ -127,7 +133,7 @@ async function main(argv) {
|
|
|
127
133
|
return -1;
|
|
128
134
|
}
|
|
129
135
|
if (cmd === 'serve') {
|
|
130
|
-
const port = flags.http ?? process.env.
|
|
136
|
+
const port = flags.http ?? process.env.NERVUR_HTTP;
|
|
131
137
|
const s = await serve(dir, port !== undefined ? { http: { port: Number(port) } } : {});
|
|
132
138
|
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])) });
|
|
133
139
|
const stop = () => void s.close().then(() => process.exit(0));
|
|
@@ -137,19 +143,19 @@ async function main(argv) {
|
|
|
137
143
|
}
|
|
138
144
|
if (cmd === 'estate') {
|
|
139
145
|
if (!json || !flags.domain) {
|
|
140
|
-
console.error('
|
|
146
|
+
console.error('nervur estate needs <dir> --domain D');
|
|
141
147
|
return 2;
|
|
142
148
|
}
|
|
143
149
|
print(await estate(json, flags.domain));
|
|
144
150
|
return 0;
|
|
145
151
|
}
|
|
146
152
|
// The root reaching into a being of its own ward. Two positionals and the
|
|
147
|
-
// args as JSON, because a human writes this one by hand: `
|
|
153
|
+
// args as JSON, because a human writes this one by hand: `nervur ask clock keep
|
|
148
154
|
// '{"id":"nightly","ask":"desk.hello","cron":"0 3 * * *"}'`. With no
|
|
149
155
|
// method it is that being's describe, as the device sees her.
|
|
150
156
|
if (cmd === 'ask') {
|
|
151
157
|
if (!json) {
|
|
152
|
-
console.error('
|
|
158
|
+
console.error('nervur ask needs <being> [method] [json]');
|
|
153
159
|
return 2;
|
|
154
160
|
}
|
|
155
161
|
const out = await askBeing(dir, json, more, third ? JSON.parse(third) : {}, await wardOf());
|
|
@@ -159,7 +165,7 @@ async function main(argv) {
|
|
|
159
165
|
}
|
|
160
166
|
if (cmd === 'reach') {
|
|
161
167
|
if (!json || !more) {
|
|
162
|
-
console.error('
|
|
168
|
+
console.error('nervur reach needs <pk> <url>');
|
|
163
169
|
return 2;
|
|
164
170
|
}
|
|
165
171
|
const out = await reach(dir, json, more);
|
|
@@ -189,7 +195,7 @@ async function main(argv) {
|
|
|
189
195
|
const failed = 'error' in out || (typeof out.result === 'object' && out.result !== null && 'error' in out.result);
|
|
190
196
|
return failed ? 1 : 0;
|
|
191
197
|
}
|
|
192
|
-
console.error('usage:
|
|
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]');
|
|
193
199
|
return 2;
|
|
194
200
|
}
|
|
195
201
|
main(process.argv.slice(2)).then((code) => {
|
package/dist/cli/stand.d.ts
CHANGED
package/dist/dock/agents.d.ts
CHANGED
package/dist/dock/agents.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// to persist: running an agent is admitting through a socket only a daemon
|
|
22
22
|
// has, and this file is not in the dock's index, since a tab has no process
|
|
23
23
|
// to spawn. A box with no runner keeps the table and runs nobody.
|
|
24
|
-
import { OWNER } from '
|
|
24
|
+
import { OWNER } from 'nervur';
|
|
25
25
|
import { Faculty } from './faculty.js';
|
|
26
26
|
// The name the dock ward holds her under.
|
|
27
27
|
export const AGENTS = 'agents';
|
package/dist/dock/apns.js
CHANGED
|
@@ -61,7 +61,7 @@ export function device(token) {
|
|
|
61
61
|
export function request(a, jwt, token, sealed) {
|
|
62
62
|
const said = sealed === undefined
|
|
63
63
|
? { aps: { 'content-available': 1 } }
|
|
64
|
-
: { aps: { alert: { 'loc-key': '
|
|
64
|
+
: { aps: { alert: { 'loc-key': 'nervur.ring' }, 'mutable-content': 1, sound: 'default' }, sealed };
|
|
65
65
|
return {
|
|
66
66
|
path: `/3/device/${device(token)}`,
|
|
67
67
|
headers: {
|
package/dist/dock/console.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Carrier } from '../beings/carry.ts';
|
|
2
|
-
import type { Asker, OccupantRecord } from '
|
|
2
|
+
import type { Asker, OccupantRecord } from 'nervur';
|
|
3
3
|
export declare const CONSOLE = "console";
|
|
4
4
|
export declare class Console extends Carrier {
|
|
5
5
|
static carries(occupant: OccupantRecord | undefined, _asker: Asker): boolean;
|
package/dist/dock/dialers.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Asker, JsonObject, OccupantRecord } from '
|
|
2
|
-
import { type Dialer, type Harbor } from '
|
|
1
|
+
import type { Asker, JsonObject, OccupantRecord } from 'nervur';
|
|
2
|
+
import { type Dialer, type Harbor } from 'nervur/harbor';
|
|
3
3
|
import { Faculty } from './faculty.ts';
|
|
4
4
|
export declare const DIALERS = "dialers";
|
|
5
5
|
export type Row = {
|
package/dist/dock/dialers.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
// library's, written on the standard socket surface, so this faculty holds
|
|
15
15
|
// on every terrain that has a socket; the daemon hands her the harbor to
|
|
16
16
|
// dial for, as it hands the agents faculty how to run a row.
|
|
17
|
-
import { OWNER } from '
|
|
18
|
-
import { dial } from '
|
|
17
|
+
import { OWNER } from 'nervur';
|
|
18
|
+
import { dial } from 'nervur/harbor';
|
|
19
19
|
import { Faculty } from './faculty.js';
|
|
20
20
|
// The name the dock ward holds her under.
|
|
21
21
|
export const DIALERS = 'dialers';
|
package/dist/dock/faculty.d.ts
CHANGED
package/dist/dock/faculty.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// connection, the interval, the worker, the route are opened in her
|
|
18
18
|
// constructor, and she is born whether or not the thing is reachable,
|
|
19
19
|
// answering an error object while it is not.
|
|
20
|
-
import { Being, isInvitation, isSilence, isWord, wordOf } from '
|
|
20
|
+
import { Being, isInvitation, isSilence, isWord, wordOf } from 'nervur';
|
|
21
21
|
export class Faculty extends Being {
|
|
22
22
|
static cells = { watched: {}, minted: 0 };
|
|
23
23
|
// Someone wants waking. She hands the invitation she minted and says what
|
package/dist/dock/grants.d.ts
CHANGED
package/dist/dock/grants.js
CHANGED
|
@@ -57,7 +57,7 @@ export class Grants extends Faculty {
|
|
|
57
57
|
this.kept.refresh[refresh_token] = { identity, ward, client, exp: this.now() + REFRESH_TTL };
|
|
58
58
|
this.sweep();
|
|
59
59
|
await this.persist();
|
|
60
|
-
return { access_token, token_type: 'Bearer', expires_in: ACCESS_TTL / 1000, refresh_token, scope: '
|
|
60
|
+
return { access_token, token_type: 'Bearer', expires_in: ACCESS_TTL / 1000, refresh_token, scope: 'nervur' };
|
|
61
61
|
}
|
|
62
62
|
// Rotated: the old one is gone with the new one's birth. Null is a token
|
|
63
63
|
// nobody issued, or one that died.
|
package/dist/dock/http2.js
CHANGED
|
@@ -21,7 +21,7 @@ import { HOSTS, answered, provider, request } from './apns.js';
|
|
|
21
21
|
// which app is being rung. A box holding none is a box that rings no
|
|
22
22
|
// iPhones, which is every box but a bell's.
|
|
23
23
|
export function apnsOf(env) {
|
|
24
|
-
const {
|
|
24
|
+
const { NERVUR_APNS_KEY: key, NERVUR_APNS_KEY_ID: keyId, NERVUR_APNS_TEAM: teamId, NERVUR_APNS_TOPIC: topic } = env;
|
|
25
25
|
return key && keyId && teamId && topic ? { key, keyId, teamId, topic } : null;
|
|
26
26
|
}
|
|
27
27
|
// How long a ring waits on Apple before it is a ring that did not leave. A
|
package/dist/dock/index.d.ts
CHANGED
package/dist/dock/index.js
CHANGED
package/dist/dock/local.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Faculty } from './faculty.ts';
|
|
2
|
-
import type { JsonObject } from '
|
|
2
|
+
import type { JsonObject } from 'nervur';
|
|
3
3
|
import { type Verified } from '../beings/way.ts';
|
|
4
4
|
export declare const LOCAL = "local";
|
|
5
5
|
export declare class Local extends Faculty {
|
|
6
6
|
#private;
|
|
7
7
|
static asks: {
|
|
8
|
-
verify: import("
|
|
8
|
+
verify: import("nervur").AskSpec;
|
|
9
9
|
};
|
|
10
10
|
mint(nonce: string, who: Verified): void;
|
|
11
11
|
drop(nonce: string): void;
|
package/dist/dock/location.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// is kept so that it never leaves the device at all. This one opens
|
|
22
22
|
// banners, and if the process that draws them were ever turned against her
|
|
23
23
|
// it would cost her a line of text rather than her world.
|
|
24
|
-
import { arithmetic } from '
|
|
24
|
+
import { arithmetic } from 'nervur/ward';
|
|
25
25
|
import { Faculty } from './faculty.js';
|
|
26
26
|
// A key is thirty two bytes of the platform's own randomness, as every seed
|
|
27
27
|
// in this kit is.
|
package/dist/dock/push.d.ts
CHANGED
package/dist/dock/push.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// The rings are live objects and not values, so they are not cells: a
|
|
13
13
|
// terrain subclasses and builds its own in `rings`, out of what its
|
|
14
14
|
// environment and this being's cells give it.
|
|
15
|
-
import { OWNER } from '
|
|
16
|
-
import { arithmetic } from '
|
|
15
|
+
import { OWNER } from 'nervur';
|
|
16
|
+
import { arithmetic } from 'nervur/ward';
|
|
17
17
|
import { Faculty } from './faculty.js';
|
|
18
18
|
import { mintVapid } from './webpush.js';
|
|
19
19
|
export const isToken = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) && typeof v.kind === 'string';
|
|
@@ -38,7 +38,7 @@ export const SEAL = 4096;
|
|
|
38
38
|
// object is shown as it is when they are not.
|
|
39
39
|
export function banner(o) {
|
|
40
40
|
const text = (v) => (typeof v === 'string' && v.trim() ? v.trim() : '');
|
|
41
|
-
const title = text(o.title) || text(o.from) || '
|
|
41
|
+
const title = text(o.title) || text(o.from) || 'Nervur';
|
|
42
42
|
const body = text(o.body) ||
|
|
43
43
|
text(o.said) ||
|
|
44
44
|
Object.entries(o)
|
package/dist/dock/service.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Why there is one at all. Apple and Google accept a push only when it is
|
|
7
7
|
// signed with the key of the team that published the app, so a world's
|
|
8
|
-
// droplet cannot ring the
|
|
8
|
+
// droplet cannot ring the Nervur app however much its human wants it to: the
|
|
9
9
|
// key belongs to whoever publishes the app and to nobody else. So a world's
|
|
10
10
|
// world hands the ring on, a token and a sealed banner, to a service the
|
|
11
11
|
// publisher runs. It is the one server the app has. An estate that ships
|
package/dist/dock/timer.d.ts
CHANGED