@nervur-org/dock 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +7 -4
  2. package/api/route.ts +4 -72
  3. package/beings/avatar.ts +11 -7
  4. package/beings/carry.ts +129 -116
  5. package/beings/clock.ts +11 -7
  6. package/beings/desk.ts +7 -6
  7. package/beings/doorbell.ts +36 -7
  8. package/beings/index.ts +17 -4
  9. package/beings/join.ts +7 -4
  10. package/beings/link.ts +1 -1
  11. package/beings/porter.ts +5 -1
  12. package/beings/presence.ts +15 -5
  13. package/beings/setup.ts +27 -21
  14. package/beings/user.ts +26 -17
  15. package/beings/way.ts +1 -1
  16. package/{dock → box}/agents.ts +37 -44
  17. package/{dock → box}/apns.ts +4 -4
  18. package/box/box.ts +31 -0
  19. package/box/contract.ts +41 -0
  20. package/box/dialers.ts +88 -0
  21. package/box/dock.ts +148 -0
  22. package/box/envoy.ts +96 -0
  23. package/box/faculty.ts +111 -0
  24. package/{dock → box}/fcm.ts +1 -1
  25. package/box/grants.ts +90 -0
  26. package/box/http.ts +93 -0
  27. package/{dock → box}/http2.ts +7 -7
  28. package/box/index.ts +201 -0
  29. package/box/join.ts +78 -0
  30. package/box/local.ts +37 -0
  31. package/{dock → box}/location.ts +15 -3
  32. package/{dock → box}/notifications.ts +17 -5
  33. package/box/push.ts +85 -0
  34. package/box/ring.ts +85 -0
  35. package/{dock → box}/service.ts +2 -2
  36. package/box/timer.ts +150 -0
  37. package/box/tree.ts +22 -0
  38. package/{dock → box}/webpush.ts +2 -2
  39. package/cli/client.ts +1 -1
  40. package/cli/daemon.ts +101 -103
  41. package/cli/estate.ts +2 -3
  42. package/cli/nervur.ts +44 -25
  43. package/cli/stand.ts +1 -1
  44. package/dist/api/route.d.ts +1 -2
  45. package/dist/api/route.js +4 -76
  46. package/dist/beings/avatar.d.ts +4 -3
  47. package/dist/beings/avatar.js +9 -6
  48. package/dist/beings/carry.d.ts +10 -12
  49. package/dist/beings/carry.js +119 -127
  50. package/dist/beings/clock.js +10 -7
  51. package/dist/beings/desk.js +8 -7
  52. package/dist/beings/doorbell.d.ts +3 -1
  53. package/dist/beings/doorbell.js +37 -4
  54. package/dist/beings/index.d.ts +24 -4
  55. package/dist/beings/index.js +15 -4
  56. package/dist/beings/join.d.ts +1 -0
  57. package/dist/beings/join.js +6 -4
  58. package/dist/beings/link.d.ts +0 -1
  59. package/dist/beings/link.js +1 -1
  60. package/dist/beings/porter.js +5 -1
  61. package/dist/beings/presence.d.ts +1 -0
  62. package/dist/beings/presence.js +12 -3
  63. package/dist/beings/setup.d.ts +5 -2
  64. package/dist/beings/setup.js +16 -15
  65. package/dist/beings/user.d.ts +4 -3
  66. package/dist/beings/user.js +18 -11
  67. package/dist/{dock → box}/agents.d.ts +7 -4
  68. package/dist/{dock → box}/agents.js +30 -37
  69. package/dist/{dock → box}/apns.d.ts +1 -1
  70. package/dist/{dock → box}/apns.js +3 -3
  71. package/dist/box/box.d.ts +9 -0
  72. package/dist/box/box.js +18 -0
  73. package/dist/box/contract.d.ts +6 -0
  74. package/dist/box/contract.js +35 -0
  75. package/dist/{dock → box}/dialers.d.ts +7 -4
  76. package/dist/box/dialers.js +76 -0
  77. package/dist/box/dock.d.ts +182 -0
  78. package/dist/box/dock.js +128 -0
  79. package/dist/box/envoy.d.ts +44 -0
  80. package/dist/box/envoy.js +75 -0
  81. package/dist/box/faculty.d.ts +68 -0
  82. package/dist/box/faculty.js +97 -0
  83. package/dist/{dock → box}/fcm.d.ts +1 -1
  84. package/dist/{dock → box}/grants.d.ts +4 -3
  85. package/dist/box/grants.js +78 -0
  86. package/dist/box/http.d.ts +26 -0
  87. package/dist/box/http.js +89 -0
  88. package/dist/{dock → box}/http2.d.ts +1 -1
  89. package/dist/{dock → box}/http2.js +6 -6
  90. package/dist/box/index.d.ts +46 -0
  91. package/dist/box/index.js +179 -0
  92. package/dist/box/join.d.ts +19 -0
  93. package/dist/box/join.js +67 -0
  94. package/dist/{dock → box}/local.d.ts +3 -2
  95. package/dist/box/local.js +27 -0
  96. package/dist/{dock → box}/location.d.ts +3 -0
  97. package/dist/box/location.js +21 -0
  98. package/dist/{dock → box}/notifications.d.ts +3 -0
  99. package/dist/{dock → box}/notifications.js +14 -4
  100. package/dist/{dock → box}/push.d.ts +12 -18
  101. package/dist/box/push.js +83 -0
  102. package/dist/box/ring.d.ts +18 -0
  103. package/dist/box/ring.js +55 -0
  104. package/dist/{dock → box}/service.d.ts +1 -1
  105. package/dist/{dock → box}/service.js +1 -1
  106. package/dist/{dock → box}/timer.d.ts +2 -0
  107. package/dist/box/timer.js +127 -0
  108. package/dist/box/tree.d.ts +5 -0
  109. package/dist/box/tree.js +14 -0
  110. package/dist/{dock → box}/webpush.d.ts +1 -1
  111. package/dist/cli/client.d.ts +1 -0
  112. package/dist/cli/daemon.d.ts +2 -8
  113. package/dist/cli/daemon.js +102 -107
  114. package/dist/cli/estate.js +2 -3
  115. package/dist/cli/nervur.js +44 -24
  116. package/dist/cli/stand.d.ts +0 -14
  117. package/dist/cli/stand.js +1 -1
  118. package/dist/harbor/browser.d.ts +8 -5
  119. package/dist/harbor/browser.js +20 -21
  120. package/dist/harbor/capacitor.d.ts +3 -3
  121. package/dist/harbor/capacitor.js +9 -8
  122. package/dist/harbor/disk.d.ts +9 -9
  123. package/dist/harbor/disk.js +56 -51
  124. package/dist/harbor/edge/alarm.d.ts +4 -2
  125. package/dist/harbor/edge/alarm.js +9 -19
  126. package/dist/harbor/edge/edge.d.ts +7 -6
  127. package/dist/harbor/edge/edge.js +58 -53
  128. package/dist/harbor/edge/exercise.js +4 -4
  129. package/dist/harbor/edge/given.d.ts +0 -1
  130. package/dist/harbor/edge/given.js +0 -29
  131. package/dist/harbor/edge/storage.d.ts +0 -2
  132. package/dist/harbor/edge/storage.js +2 -2
  133. package/dist/harbor/http.js +4 -2
  134. package/dist/harbor/sealed.d.ts +0 -5
  135. package/dist/harbor/sealed.js +0 -39
  136. package/dist/harbor/tauri.d.ts +2 -2
  137. package/dist/harbor/tauri.js +13 -10
  138. package/dist/human/app.d.ts +1 -1
  139. package/dist/human/app.js +10 -12
  140. package/dist/human/door.d.ts +0 -1
  141. package/dist/human/door.js +1 -1
  142. package/dist/human/html.js +1 -1
  143. package/dist/human/move.js +1 -1
  144. package/dist/human/tab.bundle.txt +3629 -2824
  145. package/dist/human/wake.d.ts +5 -2
  146. package/dist/human/wake.js +23 -14
  147. package/dist/human/web.d.ts +0 -1
  148. package/dist/human/web.js +3 -3
  149. package/dist/human/worker.bundle.txt +2945 -2118
  150. package/dist/human/worlds.js +3 -3
  151. package/dist/mcp/agent.d.ts +0 -1
  152. package/dist/mcp/agent.js +1 -1
  153. package/dist/mcp/allow.js +3 -2
  154. package/dist/mcp/http.d.ts +0 -1
  155. package/dist/mcp/http.js +1 -1
  156. package/dist/mcp/oauth.d.ts +3 -4
  157. package/dist/mcp/oauth.js +4 -4
  158. package/dist/mcp/route.d.ts +1 -1
  159. package/dist/mcp/runner.d.ts +0 -1
  160. package/dist/mcp/runner.js +1 -1
  161. package/dist/mcp/server.d.ts +0 -1
  162. package/dist/mcp/server.js +1 -1
  163. package/harbor/browser.ts +22 -24
  164. package/harbor/capacitor.ts +9 -8
  165. package/harbor/disk.ts +53 -48
  166. package/harbor/edge/alarm.ts +15 -12
  167. package/harbor/edge/edge.ts +61 -55
  168. package/harbor/edge/exercise.ts +4 -4
  169. package/harbor/edge/given.ts +3 -33
  170. package/harbor/edge/storage.ts +2 -2
  171. package/harbor/http.ts +5 -3
  172. package/harbor/sealed.ts +0 -35
  173. package/harbor/tauri.ts +13 -10
  174. package/human/app.ts +12 -13
  175. package/human/door.ts +1 -1
  176. package/human/html.ts +1 -1
  177. package/human/local.ts +1 -1
  178. package/human/move.ts +1 -1
  179. package/human/tab.bundle.txt +3629 -2824
  180. package/human/wake.ts +36 -16
  181. package/human/web.ts +4 -4
  182. package/human/worker.bundle.txt +2945 -2118
  183. package/human/worlds.ts +3 -3
  184. package/mcp/agent.ts +1 -1
  185. package/mcp/allow.ts +2 -1
  186. package/mcp/http.ts +1 -1
  187. package/mcp/oauth.ts +4 -4
  188. package/mcp/route.ts +1 -1
  189. package/mcp/runner.ts +1 -1
  190. package/mcp/server.ts +1 -1
  191. package/package.json +9 -8
  192. package/beings/envoy.ts +0 -218
  193. package/dist/beings/envoy.d.ts +0 -54
  194. package/dist/beings/envoy.js +0 -211
  195. package/dist/dock/console.d.ts +0 -6
  196. package/dist/dock/console.js +0 -20
  197. package/dist/dock/dialers.js +0 -75
  198. package/dist/dock/faculty.d.ts +0 -24
  199. package/dist/dock/faculty.js +0 -142
  200. package/dist/dock/grants.js +0 -99
  201. package/dist/dock/index.d.ts +0 -41
  202. package/dist/dock/index.js +0 -113
  203. package/dist/dock/local.js +0 -44
  204. package/dist/dock/location.js +0 -11
  205. package/dist/dock/push.js +0 -132
  206. package/dist/dock/timer.js +0 -149
  207. package/dock/console.ts +0 -23
  208. package/dock/dialers.ts +0 -85
  209. package/dock/faculty.ts +0 -148
  210. package/dock/grants.ts +0 -111
  211. package/dock/index.ts +0 -133
  212. package/dock/local.ts +0 -48
  213. package/dock/push.ts +0 -161
  214. package/dock/timer.ts +0 -174
  215. /package/dist/{dock → box}/fcm.js +0 -0
  216. /package/dist/{dock → box}/webpush.js +0 -0
package/cli/nervur.ts 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> write routes.json by the convention: mcp., web., quo. and api. under the domain, over https
17
- // nervur serve [--dir D] [--http PORT] the daemon: harbor from disk, the two sockets, the HTTP door on loopback, a socket held to every route the dialers faculty keeps
16
+ // nervur domain [--dir D] <domain> say the four routes by the convention, mcp., web., quo. and api. under the domain over https, a cell of the Dock
17
+ // nervur apps [--dir D] <json> say the apps this box's links open, { apple, android }, a cell of the Dock; {} forgets them
18
+ // nervur serve [--dir D] [--http PORT] [--host ADDR] the daemon: harbor from disk, the two sockets, the HTTP door on ADDR, 127.0.0.1 unless said, a socket held to every route the dialers faculty keeps
18
19
  // nervur census [--dir D] [--ward W] [--via S] the empty ask: pk and every being
19
20
  // nervur boot [--dir D] [--ward W] [--via S] <json> { key, class, public? }
20
- // nervur invite [--dir D] [--ward W] [--via S] <json> { being, id }; with routes.json naming web., the answer carries the link
21
+ // nervur invite [--dir D] [--ward W] [--via S] <json> { being, id }; with the box's routes naming web., the answer carries the link
21
22
  // nervur knock [--dir D] [--ward W] [--via S] <json> { being | { boot, key }, id, invitation, method?, args?, wanted? }
22
23
  // nervur remove [--dir D] [--ward W] [--via S] <json> { being, id }: a relation out of a being; on the ward pk, an owner
23
24
  // nervur unboot [--dir D] [--ward W] [--via S] <json> { being }: a being out of the ward, with every relation she holds
@@ -29,7 +30,9 @@
29
30
  // nervur estate <dir> --domain D an estate folder of the shape every estate has: a droplet's harbor directory, unit and Caddyfile, a Mac's launchd agent, on this dock
30
31
  //
31
32
  // D defaults to $NERVUR_DIR, then ~/.nervur. W defaults to the default ward, and
32
- // init names a new ward main when none is given. init takes
33
+ // init names a new ward main when none is given. serve takes the HTTP port
34
+ // as --http or $NERVUR_HTTP and the address it binds as --host or
35
+ // $NERVUR_HOST, 127.0.0.1 when neither says one. init takes
33
36
  // --user NAME for the user being, defaulting to the device's user. run
34
37
  // takes the model's base URL as --url or $NERVUR_MODEL_URL, its name as
35
38
  // --model or $NERVUR_MODEL, a bearer from $NERVUR_MODEL_KEY, read here and never
@@ -42,7 +45,8 @@ import { join } from 'node:path';
42
45
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
43
46
  import { isInvitation } from 'nervur';
44
47
  import { DiskHarbor } from '../harbor/disk.ts';
45
- import { serve, readRoutes, writeRoutes } from './daemon.ts';
48
+ import { serve } from './daemon.ts';
49
+ import { BOX, BOX_SEED, type Routes } from '../box/index.ts';
46
50
  import { ask, askBeing, side, reach, type Run } from './client.ts';
47
51
  import { link } from '../beings/link.ts';
48
52
  import { pilotSide } from './pilot.ts';
@@ -57,7 +61,7 @@ function parse(argv: string[]) {
57
61
  const a = argv[i] ?? '';
58
62
  // a flag takes the word after it, unless that word is a flag or there is
59
63
  // none: --default stands alone
60
- if (a.startsWith('--')) flags[a.slice(2)] = argv[i + 1] === undefined || argv[i + 1]!.startsWith('--') ? '' : argv[++i]!;
64
+ if (a.startsWith('--')) flags[a.slice(2)] = argv[i + 1] === undefined || argv[i + 1].startsWith('--') ? '' : argv[++i];
61
65
  else rest.push(a);
62
66
  }
63
67
  const [cmd, json, more, third] = rest;
@@ -69,8 +73,10 @@ const print = (v: unknown) => console.log(JSON.stringify(v, null, 2));
69
73
  async function main(argv: string[]): Promise<number> {
70
74
  const { cmd, json, more, third, flags, dir, via } = parse(argv);
71
75
  if (cmd === 'init') {
72
- const made = await DiskHarbor.init(dir, flags.ward, flags.user ?? userInfo().username, flags.class, 'default' in flags, 'show' in flags);
73
- print(flags.domain ? { ...made, routes: await writeRoutes(dir, flags.domain) } : made);
76
+ const made = await DiskHarbor.init(dir, flags.ward, flags.user ?? userInfo().username, flags.class, 'show' in flags);
77
+ // the box's own cells, said to the Dock with no daemon over the folder
78
+ if ('default' in flags) await DiskHarbor.say(dir, 'default', { ward: made.name });
79
+ print(flags.domain ? { ...made, ...(await DiskHarbor.say(dir, 'domain', { domain: flags.domain })) } : made);
74
80
  return 0;
75
81
  }
76
82
  if (cmd === 'default') {
@@ -78,8 +84,15 @@ async function main(argv: string[]): Promise<number> {
78
84
  console.error('nervur default <ward>');
79
85
  return 2;
80
86
  }
81
- await DiskHarbor.setDefault(dir, json);
82
- print({ default: json });
87
+ print(await DiskHarbor.say(dir, 'default', { ward: json }));
88
+ return 0;
89
+ }
90
+ if (cmd === 'apps') {
91
+ if (!json) {
92
+ console.error('nervur apps <json> { apple: [...], android: [{ package, fingerprints }] }; {} forgets them');
93
+ return 2;
94
+ }
95
+ print(await DiskHarbor.say(dir, 'apps', JSON.parse(json) as Record<string, unknown>));
83
96
  return 0;
84
97
  }
85
98
  if (cmd === 'show' || cmd === 'hide') {
@@ -96,13 +109,12 @@ async function main(argv: string[]): Promise<number> {
96
109
  console.error('nervur domain <domain>');
97
110
  return 2;
98
111
  }
99
- print({ routes: await writeRoutes(dir, json) });
112
+ print(await DiskHarbor.say(dir, 'domain', { domain: json }));
100
113
  return 0;
101
114
  }
102
115
  // Every command that reaches a ward is on one: the one named, or the
103
- // default, read from the folder the way the daemon reads it, and only once
104
- // the command is held right.
105
- const wardOf = async () => flags.ward ?? (await DiskHarbor.defaultOf(dir));
116
+ // default, which the daemon resolves for a request naming none.
117
+ const wardOf = () => flags.ward;
106
118
  if (cmd === 'side' || cmd === 'run') {
107
119
  if (!flags.as) {
108
120
  console.error(`nervur ${cmd} needs --as NAME`);
@@ -124,11 +136,11 @@ async function main(argv: string[]): Promise<number> {
124
136
  }
125
137
  run = { url, model, ...(key ? { key } : {}), ...(turns !== undefined ? { turns } : {}) };
126
138
  }
127
- await side(dir, flags.as, await wardOf(), run); // stdio to the daemon, until one end closes
139
+ await side(dir, flags.as, wardOf(), run); // stdio to the daemon, until one end closes
128
140
  return -1;
129
141
  }
130
142
  if (cmd === 'pilot') {
131
- const ward = await wardOf();
143
+ const ward = wardOf();
132
144
  await ask(dir, undefined, {}, ward); // no daemon is a clear word now, not at the first tool call
133
145
  const serving = await pilotSide(dir, ward, new StdioServerTransport(), via);
134
146
  process.stdin.once('end', () => void serving.close().then(() => process.exit(0)));
@@ -136,7 +148,11 @@ async function main(argv: string[]): Promise<number> {
136
148
  }
137
149
  if (cmd === 'serve') {
138
150
  const port = flags.http ?? process.env.NERVUR_HTTP;
139
- const s = await serve(dir, port !== undefined ? { http: { port: Number(port) } } : {});
151
+ // Loopback unless an address is said. A daemon reachable from the
152
+ // network is the operator's own word and never a default, so the flag
153
+ // exists and is never filled in for him.
154
+ const host = flags.host || process.env.NERVUR_HOST;
155
+ const s = await serve(dir, port !== undefined ? { http: { port: Number(port), ...(host ? { host } : {}) } } : {});
140
156
  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])) });
141
157
  const stop = () => void s.close().then(() => process.exit(0));
142
158
  process.once('SIGINT', stop);
@@ -160,7 +176,7 @@ async function main(argv: string[]): Promise<number> {
160
176
  console.error('nervur ask needs <being> [method] [json]');
161
177
  return 2;
162
178
  }
163
- const out = await askBeing(dir, json, more, third ? (JSON.parse(third) as Record<string, unknown>) : {}, await wardOf());
179
+ const out = await askBeing(dir, json, more, third ? (JSON.parse(third) as Record<string, unknown>) : {}, wardOf());
164
180
  print('error' in out ? { error: out.error } : out.result);
165
181
  const failed = 'error' in out || (typeof out.result === 'object' && out.result !== null && 'error' in out.result);
166
182
  return failed ? 1 : 0;
@@ -176,25 +192,28 @@ async function main(argv: string[]): Promise<number> {
176
192
  }
177
193
  if (cmd === 'census' || (cmd !== undefined && OWNER_ASKS.has(cmd))) {
178
194
  const args = json ? (JSON.parse(json) as Record<string, unknown>) : {};
179
- const ward = await wardOf();
195
+ const ward = wardOf();
180
196
  let out = await ask(dir, cmd === 'census' ? undefined : cmd, args, ward, via);
181
- // an invitation minted here is handed on as a link when the harbor's
197
+ // an invitation minted here is handed on as a link when the box's
182
198
  // routes name a web. origin: the world's page with the invitation in
183
- // the fragment, one string to send to the person it is for
199
+ // the fragment, one string to send to the person it is for. The routes
200
+ // are the Dock's cells, asked of her through the daemon.
184
201
  const minted = cmd === 'invite' && !via && 'result' in out && isInvitation(out.result) ? out.result : null;
185
- const routes = minted ? await readRoutes(dir) : null;
202
+ const box = minted ? await askBeing(dir, BOX, 'box', {}, BOX_SEED) : null;
203
+ const routes = box && 'result' in box ? ((box.result as { routes?: Routes | null }).routes ?? null) : null;
204
+ const on = ('ward' in out ? out.ward : undefined) ?? ward ?? '';
186
205
  // the ward answers null to an invite on an id already held, standing or
187
206
  // occupant, and null on a terminal is nothing to read: it is a refusal
188
207
  // here, in words, and an exit code a script can tell apart
189
208
  if (cmd === 'invite' && 'result' in out && out.result === null) out = { result: { error: 'id taken' } };
190
- if (minted && routes) print({ ...minted, link: link(`${routes.web}/${encodeURIComponent(ward)}`, minted) });
209
+ if (minted && routes) print({ ...minted, link: link(`${routes.web}/${encodeURIComponent(on)}`, minted) });
191
210
  else print('error' in out ? { error: out.error } : out.result);
192
211
  // an error object the ward answered is an ordinary answer, and still a
193
212
  // non-zero exit, so a script can tell a boot that happened from one that did not.
194
213
  const failed = 'error' in out || (typeof out.result === 'object' && out.result !== null && 'error' in out.result);
195
214
  return failed ? 1 : 0;
196
215
  }
197
- 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]');
216
+ 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]');
198
217
  return 2;
199
218
  }
200
219
 
package/cli/stand.ts CHANGED
@@ -89,7 +89,7 @@ const ward = (h: Wards, name: string, why: string) => h.wards.get(name) ?? fail(
89
89
  // for exactly one reason once the reader has held the id to a word the ward
90
90
  // does not reserve, which is that the id is taken, and an id taken is this
91
91
  // wire, placed the last time the script ran.
92
- export async function standUp(harbor: Harbor | Wards, decl: Stand): Promise<{ booted: string[]; wired: string[]; already: string[] }> {
92
+ async function standUp(harbor: Harbor | Wards, decl: Stand): Promise<{ booted: string[]; wired: string[]; already: string[] }> {
93
93
  const h = harbor as Wards;
94
94
  const booted: string[] = [];
95
95
  for (const b of decl.boot ?? []) {
@@ -1,7 +1,7 @@
1
1
  import type { Handler } from '../harbor/http.ts';
2
2
  import type { Hosted } from 'nervur/harbor';
3
3
  import type { Avatar } from '../beings/avatar.ts';
4
- import type { Grants } from '../dock/grants.ts';
4
+ import type { Bearers as Grants } from '../box/grants.ts';
5
5
  import { type Invitation } from 'nervur';
6
6
  export type Held = {
7
7
  wards: ReadonlyMap<string, Hosted>;
@@ -16,6 +16,5 @@ export type Options = {
16
16
  join: (identity: string, invitation: Invitation, ward: string) => Promise<{
17
17
  error?: string;
18
18
  }>;
19
- web?: string;
20
19
  };
21
20
  export declare function apiRoute(harbor: Held, o: Options): Handler;
package/dist/api/route.js CHANGED
@@ -1,35 +1,12 @@
1
1
  import { readText } from '../harbor/http.js';
2
2
  import { invitationOf } from '../beings/link.js';
3
- import { landed, POLICY } from '../human/door.js';
4
- import { CSS } from '../human/style.js';
3
+ import { isIdentity } from '../beings/join.js';
5
4
  import { isInvitation, isSilence, isWord, wordOf } from 'nervur';
6
5
  const open = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'GET, POST, OPTIONS', 'access-control-allow-headers': 'content-type, authorization' };
7
6
  const word = (s) => (typeof s === 'string' && /^[\w.-]{1,40}$/.test(s) ? s : null);
8
7
  export function apiRoute(harbor, o) {
9
8
  return async (req, rest) => {
10
9
  const json = (status, body) => Response.json(body, { status, headers: open });
11
- // The one page this road serves, and it wears the one stylesheet every
12
- // screen wears. No cross-origin header on it: a page is for the browser
13
- // that was sent here and never for a script somewhere else. Its policy
14
- // is the allow page's, and for the same reason: a page that runs no
15
- // script of its own should be allowed none.
16
- const page = (status, body) => new Response(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>nervur</title>\n<style>${CSS}</style>\n</head><body>${body}</body></html>`, {
17
- status,
18
- headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'content-security-policy': POLICY, 'referrer-policy': 'no-referrer' },
19
- });
20
- // Where this world lives, as the daemon was told and not as the request
21
- // said: a Host header is the caller's to write, and a page that printed
22
- // one would print whatever a caller wanted a human to read. Falling back
23
- // to it is for a box configured with no web address at all, where the
24
- // request is the only thing that knows.
25
- const hostOf = (r) => {
26
- try {
27
- return o.web === undefined ? (r.headers.get('host') ?? '') : new URL(o.web).host;
28
- }
29
- catch {
30
- return r.headers.get('host') ?? '';
31
- }
32
- };
33
10
  if (req.method === 'OPTIONS')
34
11
  return new Response(null, { status: 204, headers: open });
35
12
  if (req.method !== 'GET' && req.method !== 'POST')
@@ -64,8 +41,8 @@ export function apiRoute(harbor, o) {
64
41
  return json(400, { error: 'that invitation is for a world that does not live here' });
65
42
  const [ward, hosted] = world;
66
43
  const identity = word(b.identity);
67
- if (identity === null || identity === hosted.record.user || identity === 'desk')
68
- return json(400, { error: 'an identity is one word, and not the user or the desk' });
44
+ if (!isIdentity(hosted, identity))
45
+ return json(400, { error: 'an identity is one word, and not a being of the ward' });
69
46
  // Nobody at a page vouches for a program, so a trade is a knock that
70
47
  // spends: an identity already in refreshes, or is removed at the user
71
48
  // being and invited again, and never re-enters on a spent invitation.
@@ -81,57 +58,8 @@ export function apiRoute(harbor, o) {
81
58
  // device's, and its door answers for it.
82
59
  const [, named = '', ask = '', ...more] = rest.split('/');
83
60
  const ward = named === '' ? harbor.here() : named;
84
- // The delivery: a vendor's webhook to an envoy, `/api/<ward>/<being>/deliver`.
85
- // Not a stranger's ask and not an occupant's: the owner asking her
86
- // `deliver` with the raw bytes and the headers, and nothing else. The
87
- // body is read as a string and never parsed here, since the vendor
88
- // signed the bytes and a reparsed body fails her check; no bearer is
89
- // read, since the rung is not the caller's to prove. She answers `{ ok }`
90
- // or her error object, and the route carries it.
91
- // A GET on the same path is the same ask with an empty body: where an
92
- // OAuth redirect lands, the code in the query. The query crosses inside
93
- // the headers under `:query`, a name no header on the wire can have, so
94
- // her one ask in stays one.
95
- if (more.length === 1 && more[0] === 'deliver') {
96
- if (req.method !== 'POST' && req.method !== 'GET')
97
- return json(405, { error: 'POST, or GET for a redirect' });
98
- const hosted = harbor.wards.get(ward);
99
- if (!hosted || !/^[\w-]+$/.test(ask))
100
- return json(404, { error: 'no such world' });
101
- const raw = req.method === 'GET' ? '' : await readText(req);
102
- if (raw === undefined)
103
- return json(413, { error: 'too big' });
104
- const headers = {};
105
- req.headers.forEach((v, k) => (headers[k] = v));
106
- const query = new URL(req.url).search.slice(1);
107
- if (query)
108
- headers[':query'] = query;
109
- const out = await hosted.ask('ask', { being: ask, method: 'deliver', args: { body: raw, headers } });
110
- await hosted.save();
111
- // A GET here is a browser and a browser is a human: a vendor a world
112
- // sent her to has opened this URL in her tab, so what she is looking
113
- // at is whatever the envoy answered a machine. She gets a page
114
- // instead, and only she does; a vendor asking for JSON is answered in
115
- // JSON as it always was, and so is a GET that says nothing about what
116
- // it wants.
117
- const wants = req.method === 'GET' && (req.headers.get('accept') ?? '').includes('text/html');
118
- if (wants) {
119
- const ok = out !== null && typeof out === 'object' && !Array.isArray(out) && typeof out.error !== 'string';
120
- return page(ok ? 200 : 400, landed({ world: ward, host: hostOf(req), ...(o.web ? { at: `${o.web.replace(/\/$/, '')}/${encodeURIComponent(ward)}` } : {}), ok }));
121
- }
122
- if (isSilence(out) || out === null || typeof out !== 'object' || Array.isArray(out))
123
- return json(503, { error: 'silence' });
124
- const said = out.error;
125
- if (said === 'no such being' || said === 'unknown ask')
126
- return json(404, { error: 'no such envoy' });
127
- if (said === 'silence' || said === 'threw' || said === 'late')
128
- return json(503, { error: 'silence' });
129
- if (typeof said === 'string')
130
- return json(400, out);
131
- return json(200, out);
132
- }
133
61
  if (!/^[\w.-]+$/.test(ward) || more.length > 0 || (ask !== '' && !/^[\w-]+$/.test(ask)))
134
- return json(404, { error: 'GET /api for the device’s ward, GET /api/<ward> for the describe, GET or POST /api/<ward>/<ask> for one ask, POST /api/<ward>/<being>/deliver for a delivery' });
62
+ return json(404, { error: 'GET /api for the device’s ward, GET /api/<ward> for the describe, GET or POST /api/<ward>/<ask> for one ask' });
135
63
  const hosted = harbor.wards.get(ward);
136
64
  if (!hosted)
137
65
  return json(404, { error: 'no such world' });
@@ -1,8 +1,8 @@
1
- import type { Answer, Blueprint, Invitation, JsonObject, OccupantRecord, Wanted } from 'nervur';
2
- import { Carrier } from './carry.ts';
1
+ import { Being } from 'nervur';
2
+ import type { Answer, Asker, Blueprint, Invitation, JsonObject, OccupantRecord, Reply, Wanted } from 'nervur';
3
3
  export declare const USER = "user";
4
4
  export declare const PUSHER = "user:push";
5
- export declare class Avatar extends Carrier {
5
+ export declare class Avatar extends Being {
6
6
  static cells: {
7
7
  inbox: JsonObject[];
8
8
  };
@@ -15,6 +15,7 @@ export declare class Avatar extends Carrier {
15
15
  for: (occ: OccupantRecord | undefined) => boolean;
16
16
  };
17
17
  };
18
+ answer(asker: Asker, method?: string, args?: JsonObject): Promise<Reply>;
18
19
  readonly ears: Set<(object: JsonObject) => void>;
19
20
  notify(args: JsonObject): {
20
21
  notified: boolean;
@@ -7,21 +7,24 @@
7
7
  // calls the four methods below: one of the two ways in, `enter` behind a
8
8
  // route's proof or `join` with an invitation, then `tools` and `call`.
9
9
  // Nothing of a side is in her cells.
10
- import { isSilence, isWord, wordOf } from 'nervur';
11
- import { Carrier } from './carry.js';
10
+ import { Being, isSilence, isWord, wordOf } from 'nervur';
11
+ import { carry } from './carry.js';
12
12
  // Her one standing, and the id under which the user being pushes to her.
13
13
  // Two ids for one far being, because standings and occupants share one
14
14
  // namespace and a record holds one or the other.
15
15
  export const USER = 'user';
16
16
  export const PUSHER = 'user:push';
17
- // A carrier who carries for nobody, until a subclass says who: the presence
18
- // being carries the box's faculties to her user being, and a bare avatar
19
- // holds one standing and shows nothing of it.
20
- export class Avatar extends Carrier {
17
+ // She carries for nobody, until a subclass says who: the presence being
18
+ // carries the box's faculties to her user being, and a bare avatar holds
19
+ // one standing and shows nothing of it.
20
+ export class Avatar extends Being {
21
21
  static cells = { inbox: [] };
22
22
  static asks = {
23
23
  notify: { description: 'a push from the user being', input: { type: 'object' }, for: (occ) => occ?.id === PUSHER },
24
24
  };
25
+ answer(asker, method, args = {}) {
26
+ return carry(this, asker, method, args, (a, m, g) => super.answer(a, m, g));
27
+ }
25
28
  // Her sides' ears. One identity is one avatar for life, and two sessions
26
29
  // of that identity are two sides on her at once, so a push goes to every
27
30
  // ear present; each side adds its own on open and removes it on close.
@@ -1,12 +1,10 @@
1
- import { Being } from 'nervur';
2
- import type { Asker, Blueprint, JsonObject, OccupantRecord, Reply } from 'nervur';
3
- export declare class Carrier extends Being {
4
- static carries(_occupant: OccupantRecord | undefined, _asker: Asker): boolean;
5
- static hidden(id: string): boolean;
6
- static skips(_id: string): boolean;
7
- static shows(_id: string, _occupant: OccupantRecord | undefined, _asker: Asker): boolean;
8
- private get carried();
9
- private get looks();
10
- blueprint(asker: Asker): Promise<Blueprint>;
11
- answer(asker: Asker, method?: string, args?: JsonObject): Promise<Reply>;
12
- }
1
+ import type { Asker, Being, Blueprint, JsonObject, OccupantRecord, Reply } from 'nervur';
2
+ export type Carrying = {
3
+ carries(occupant: OccupantRecord | undefined, asker: Asker): boolean;
4
+ hidden(id: string): boolean;
5
+ skips(id: string): boolean;
6
+ shows(id: string, occupant: OccupantRecord | undefined, asker: Asker): boolean;
7
+ };
8
+ export declare const CARRIES: Carrying;
9
+ export declare function blueprint(self: Being, asker: Asker): Promise<Blueprint>;
10
+ export declare function carry(self: Being, asker: Asker, method: string | undefined, args: JsonObject, own: (asker: Asker, method: string, args: JsonObject) => Promise<Reply>): Promise<Reply>;