@dcasegr/skein 0.0.0-stage → 0.1.1

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 (176) hide show
  1. package/README.md +522 -2
  2. package/bin/skein-host.js +22 -0
  3. package/bin/skein.js +8 -0
  4. package/dist/client/admin-cli.d.ts +10 -0
  5. package/dist/client/admin-cli.js +370 -0
  6. package/dist/client/admin.d.ts +139 -0
  7. package/dist/client/admin.js +256 -0
  8. package/dist/client/bundle.d.ts +9 -0
  9. package/dist/client/bundle.js +34 -0
  10. package/dist/client/cli.d.ts +44 -0
  11. package/dist/client/cli.js +306 -0
  12. package/dist/client/client.d.ts +161 -0
  13. package/dist/client/client.js +237 -0
  14. package/dist/client/config.d.ts +18 -0
  15. package/dist/client/config.js +31 -0
  16. package/dist/client/conversation.d.ts +62 -0
  17. package/dist/client/conversation.js +113 -0
  18. package/dist/client/raw.d.ts +94 -0
  19. package/dist/client/raw.js +154 -0
  20. package/dist/client/target.d.ts +34 -0
  21. package/dist/client/target.js +158 -0
  22. package/dist/dev/cli.d.ts +8 -0
  23. package/dist/dev/cli.js +248 -0
  24. package/dist/dev/scan.d.ts +15 -0
  25. package/dist/dev/scan.js +173 -0
  26. package/dist/envelope-cbor.d.ts +110 -0
  27. package/dist/envelope-cbor.js +160 -0
  28. package/dist/envelope.d.ts +38 -0
  29. package/dist/envelope.js +101 -0
  30. package/dist/host/arc.d.ts +131 -0
  31. package/dist/host/arc.js +425 -0
  32. package/dist/host/billing.d.ts +102 -0
  33. package/dist/host/billing.js +167 -0
  34. package/dist/host/boot.d.ts +246 -0
  35. package/dist/host/boot.js +643 -0
  36. package/dist/host/cli.d.ts +57 -0
  37. package/dist/host/cli.js +1253 -0
  38. package/dist/host/clock.d.ts +3 -0
  39. package/dist/host/clock.js +8 -0
  40. package/dist/host/control.d.ts +45 -0
  41. package/dist/host/control.js +170 -0
  42. package/dist/host/cron.d.ts +99 -0
  43. package/dist/host/cron.js +196 -0
  44. package/dist/host/deliver.d.ts +53 -0
  45. package/dist/host/deliver.js +251 -0
  46. package/dist/host/deploy.d.ts +29 -0
  47. package/dist/host/deploy.js +55 -0
  48. package/dist/host/door-answer.d.ts +24 -0
  49. package/dist/host/door-answer.js +21 -0
  50. package/dist/host/events.d.ts +89 -0
  51. package/dist/host/events.js +222 -0
  52. package/dist/host/fake-arcade.d.ts +40 -0
  53. package/dist/host/fake-arcade.js +149 -0
  54. package/dist/host/fake-discovery.d.ts +4 -0
  55. package/dist/host/fake-discovery.js +58 -0
  56. package/dist/host/feeds.d.ts +127 -0
  57. package/dist/host/feeds.js +422 -0
  58. package/dist/host/frontdoor.d.ts +92 -0
  59. package/dist/host/frontdoor.js +172 -0
  60. package/dist/host/genesis.d.ts +259 -0
  61. package/dist/host/genesis.js +353 -0
  62. package/dist/host/handles.d.ts +40 -0
  63. package/dist/host/handles.js +88 -0
  64. package/dist/host/hostenv.d.ts +30 -0
  65. package/dist/host/hostenv.js +83 -0
  66. package/dist/host/image-chain.d.ts +127 -0
  67. package/dist/host/image-chain.js +440 -0
  68. package/dist/host/install.d.ts +22 -0
  69. package/dist/host/install.js +139 -0
  70. package/dist/host/instances.d.ts +227 -0
  71. package/dist/host/instances.js +430 -0
  72. package/dist/host/kernel.d.ts +246 -0
  73. package/dist/host/kernel.js +603 -0
  74. package/dist/host/liveness.d.ts +65 -0
  75. package/dist/host/liveness.js +155 -0
  76. package/dist/host/manifest.d.ts +160 -0
  77. package/dist/host/manifest.js +597 -0
  78. package/dist/host/p2p.d.ts +337 -0
  79. package/dist/host/p2p.js +1002 -0
  80. package/dist/host/packet.d.ts +121 -0
  81. package/dist/host/packet.js +562 -0
  82. package/dist/host/plan.d.ts +154 -0
  83. package/dist/host/plan.js +418 -0
  84. package/dist/host/providers.d.ts +238 -0
  85. package/dist/host/providers.js +565 -0
  86. package/dist/host/roster.d.ts +72 -0
  87. package/dist/host/roster.js +172 -0
  88. package/dist/host/router.d.ts +773 -0
  89. package/dist/host/router.js +2389 -0
  90. package/dist/host/signer.d.ts +49 -0
  91. package/dist/host/signer.js +167 -0
  92. package/dist/host/store-sqlite.d.ts +39 -0
  93. package/dist/host/store-sqlite.js +170 -0
  94. package/dist/host/stores.d.ts +43 -0
  95. package/dist/host/stores.js +342 -0
  96. package/dist/host/sync.d.ts +121 -0
  97. package/dist/host/sync.js +467 -0
  98. package/dist/host/testbeef.d.ts +21 -0
  99. package/dist/host/testbeef.js +37 -0
  100. package/dist/host/testhost.d.ts +149 -0
  101. package/dist/host/testhost.js +211 -0
  102. package/dist/host/vcdiff.d.ts +9 -0
  103. package/dist/host/vcdiff.js +210 -0
  104. package/dist/host/wasm-kernel.d.ts +87 -0
  105. package/dist/host/wasm-kernel.js +296 -0
  106. package/dist/host/wasm-worker.d.ts +1 -0
  107. package/dist/host/wasm-worker.js +186 -0
  108. package/dist/peers/cron.d.ts +33 -0
  109. package/dist/peers/cron.js +75 -0
  110. package/dist/peers/infer-main.d.ts +2 -0
  111. package/dist/peers/infer-main.js +74 -0
  112. package/dist/peers/infer.d.ts +163 -0
  113. package/dist/peers/infer.js +367 -0
  114. package/dist/runtime/beef.d.ts +28 -0
  115. package/dist/runtime/beef.js +107 -0
  116. package/dist/runtime/bitcoin.d.ts +36 -0
  117. package/dist/runtime/bitcoin.js +141 -0
  118. package/dist/runtime/cid.d.ts +14 -0
  119. package/dist/runtime/cid.js +51 -0
  120. package/dist/runtime/dispatch.d.ts +92 -0
  121. package/dist/runtime/dispatch.js +142 -0
  122. package/dist/runtime/envelope.d.ts +60 -0
  123. package/dist/runtime/envelope.js +101 -0
  124. package/dist/runtime/heads.d.ts +25 -0
  125. package/dist/runtime/heads.js +37 -0
  126. package/dist/runtime/identity.d.ts +28 -0
  127. package/dist/runtime/identity.js +67 -0
  128. package/dist/runtime/index-store.d.ts +152 -0
  129. package/dist/runtime/index-store.js +715 -0
  130. package/dist/runtime/log.d.ts +62 -0
  131. package/dist/runtime/log.js +104 -0
  132. package/dist/runtime/programs.d.ts +30 -0
  133. package/dist/runtime/programs.js +61 -0
  134. package/dist/runtime/records.d.ts +135 -0
  135. package/dist/runtime/records.js +149 -0
  136. package/dist/runtime/sqlite.d.ts +33 -0
  137. package/dist/runtime/sqlite.js +604 -0
  138. package/dist/runtime/store.d.ts +141 -0
  139. package/dist/runtime/store.js +24 -0
  140. package/dist/runtime/syscalls.d.ts +9 -0
  141. package/dist/runtime/syscalls.js +41 -0
  142. package/dist/runtime/tree.d.ts +60 -0
  143. package/dist/runtime/tree.js +196 -0
  144. package/dist/runtime/types.d.ts +100 -0
  145. package/dist/runtime/types.js +7 -0
  146. package/dist/testapps.d.ts +101 -0
  147. package/dist/testapps.js +275 -0
  148. package/dist/testkit.d.ts +25 -0
  149. package/dist/testkit.js +48 -0
  150. package/dist/wallet.d.ts +35 -0
  151. package/dist/wallet.js +87 -0
  152. package/images/README.md +68 -0
  153. package/images/default/README.md +38 -0
  154. package/images/default/bin/frontdoor.cid +1 -0
  155. package/images/default/bin/frontdoor.json +5 -0
  156. package/images/default/bin/messagebox.cid +1 -0
  157. package/images/default/bin/messagebox.json +8 -0
  158. package/images/default/bin/wallet.cid +1 -0
  159. package/images/default/bin/wallet.json +14 -0
  160. package/images/default/etc/config.json +1 -0
  161. package/images/default/etc/dispatch.json +82 -0
  162. package/images/host/etc/apps.json +8 -0
  163. package/kernel-zig/README.md +1247 -0
  164. package/kernel-zig/web/client.js +65 -0
  165. package/kernel-zig/web/kernel.d.ts +59 -0
  166. package/kernel-zig/web/kernel.js +329 -0
  167. package/kernel-zig/web/store.js +67 -0
  168. package/kernel-zig/web/worker.js +79 -0
  169. package/kernel-zig/zig-out/web/skein-kernel.wasm +0 -0
  170. package/package.json +77 -4
  171. package/wasm/README.md +35 -0
  172. package/wasm/frontdoor.wasm +0 -0
  173. package/wasm/messagebox.wasm +0 -0
  174. package/wasm/resolve.wasm +0 -0
  175. package/wasm/wallet.wasm +0 -0
  176. package/wasm/wire-probe.wasm +0 -0
@@ -0,0 +1,101 @@
1
+ import { type WalletInterface } from "@bsv/sdk";
2
+ import type { CID } from "multiformats/cid";
3
+ import { type AdminPlan } from "./client/admin.ts";
4
+ import { type InstanceView } from "./host/install.ts";
5
+ import type { Store } from "./runtime/store.ts";
6
+ export interface PinnedApp {
7
+ name: string;
8
+ repo: string;
9
+ rev: string;
10
+ dir?: string;
11
+ }
12
+ /** shruggr/skein-shell: `run` and the shell (brush, coreutils, the toolset); 0.2.0 (#143): route `run` → run.run, roles {root: [run]}. */
13
+ export declare const SHELL_APP: PinnedApp;
14
+ /** shruggr/skein-chat: the chat loop (`chat`); 0.2.0 (#143): one open route `chat` → loop.chat. */
15
+ export declare const CHAT_APP: PinnedApp;
16
+ /** shruggr/skein-onboard (#90, #113): the onboarding app, installed in the host skein: onboard.create, registration, BRC-169; #127: onboard.create takes the caller's signed claim (0.3.0); #126: the manager and the certifier by peerAt (0.3.2, skein-sdk 0.7.1); #135: /register takes a session, the registrant its identity (0.3.3); resolve, search, the manifest and the paymail PKI are reads, reads[] (0.3.4); #143: routes, filters and roles ({user: [call, register, profile]}), reads are filter answers, root from the grants head (0.4.0). */
17
+ export declare const ONBOARD_APP: PinnedApp;
18
+ /** shruggr/skein-git (#91): the git app — `git.clone {url, hash}` into the store, the app record answered; #126: its fetch is the `fetch` intention (0.1.1, skein-sdk 0.7.1); 0.1.2: the manifest checked by every rule of src/host/manifest.ts (an overlay may list no topics, #120; boxes relative to the app, #128); 0.1.3: reads[] in the record, /lookup a read (#135); 0.2.0: routes, filters and roles, the record checked by every rule of #143's checkManifest. */
19
+ export declare const GIT_APP: PinnedApp;
20
+ /** shruggr/skein-site (#125): the management site as an app — its page at /site/, served from its own tree's www (0.6.2: bundles on a skein with no address-book roles, #126; 0.6.3: the catalog pins the current releases, git 0.1.2 among them; 0.7.3: the page in the skein brand — header and wallet chip, skein cards, the handle/Register card, the logged-out landing page; 0.7.6: Register over the wallet's session, #135; 0.7.7: the page a read, reads[], #135; 0.9.0: routes, filters and roles — the read route `get`, the Routes view, the roles/grants editor, no owner rows, #143). */
21
+ export declare const SITE_APP: PinnedApp;
22
+ /** shruggr/skein-chain (#78): the chain app, `chain/1`; v0.7.0 (#156: its state points at the host chain), the version the equivs pin. */
23
+ export declare const CHAIN_APP: PinnedApp;
24
+ /**
25
+ * #152: chain, git and site — what an image installed at birth before images carried apps — in that
26
+ * order, for installApps after the claim (each at its pin, or $SKEIN_CHAIN_DIR / $SKEIN_GIT_DIR /
27
+ * $SKEIN_SITE_DIR).
28
+ */
29
+ export declare const CHAIN_GIT_SITE: PinnedApp[];
30
+ /** A checkout of the app: its env directory, else the pinned commit, fetched once. */
31
+ export declare function appCheckout(app: PinnedApp): string;
32
+ /** What installing needs of a host: its SKEIN_HOME (host.db), the router's port, the owner's wallet, a wait for the router to settle. */
33
+ export interface InstallHost {
34
+ home: string;
35
+ port: number;
36
+ owner: WalletInterface;
37
+ settled(): Promise<void>;
38
+ }
39
+ /** The instance `handle` as the plan reads it: its store file (host.db's row), read only, while the router runs it. */
40
+ export declare function viewOf(h: Pick<InstallHost, "home">, handle: string): Promise<InstanceView & {
41
+ close(): void;
42
+ }>;
43
+ /**
44
+ * Deliver a plan as the operator's client does (#142): each message on the wallet's BRC-104 session with the
45
+ * instance's front door (dag-cbor bodies, src/client/raw.ts), in order; throws at the first refusal.
46
+ */
47
+ export declare function sendPlan(h: Pick<InstallHost, "port" | "owner" | "settled">, handle: string, p: AdminPlan, wallet?: WalletInterface): Promise<number>;
48
+ /**
49
+ * Install the app in `dir` into `handle` as its owner: planned from the instance's store (as `skein install
50
+ * --store` plans), `config` merged over the manifest's, then sent (sendPlan). The prompt.
51
+ */
52
+ export declare function installApp(h: InstallHost, handle: string, dir: string, o?: {
53
+ config?: Record<string, unknown>;
54
+ wallet?: WalletInterface;
55
+ }): Promise<string[]>;
56
+ /** Uninstall `app` from `handle` as its owner (planned as `skein uninstall` plans, then sent). The prompt. */
57
+ export declare function uninstallApp(h: InstallHost, handle: string, app: string, o?: {
58
+ wallet?: WalletInterface;
59
+ }): Promise<string[]>;
60
+ /**
61
+ * What the equivs ran as `skein-host install|uninstall` before #124, now the owner's path: `install <dir>
62
+ * --instance <h> [--config json] [--dry-run]` or `uninstall <app> --instance <h>` — planned from the
63
+ * instance's store and, unless a dry run, sent by `wallet` (default the owner) on its session (sendPlan). `out`: the prompt, then `<h>: <app> <version> installed|upgraded: <n>
64
+ * messages sent` (or `<h>: <app> uninstalled: …`); `err` and code 1 when planning or a send fails.
65
+ */
66
+ export declare function ownerCli(h: InstallHost, args: string[], wallet?: WalletInterface): Promise<{
67
+ code: number;
68
+ out: string[];
69
+ err: string[];
70
+ }>;
71
+ /**
72
+ * #152: root's own route `/` to the site's page (`skein routes add --transport http --prefix --filters
73
+ * site.get --settings '{"root":"www"}' /`), sent as the owner; the site installed first.
74
+ */
75
+ export declare function siteAtRoot(h: InstallHost, handle: string, o?: {
76
+ wallet?: WalletInterface;
77
+ }): Promise<void>;
78
+ /** Install apps into an instance as its owner (installApp each; the instance's store must exist: hydrate it first). */
79
+ export declare function installApps(h: InstallHost, handle: string, apps?: PinnedApp[], o?: {
80
+ config?: Record<string, unknown>;
81
+ wallet?: WalletInterface;
82
+ }): Promise<void>;
83
+ /**
84
+ * The shell app's shell program put straight into a store with no instance
85
+ * (the shell cases and git in the VM run `skein-kernel shell <store>
86
+ * <program>`, no log): the record and the blocks the install would send for
87
+ * it, built by the install's own code (readApp, shellProgram). Its CID.
88
+ */
89
+ export declare function putShellProgram(store: Pick<Store, "put" | "putBlock" | "has">, app?: PinnedApp): Promise<CID>;
90
+ /**
91
+ * #135: a BRC-22 submission as a signed request — POST <base>/submit is a message route. The stock AuthFetch
92
+ * carries only content-type, authorization and x-bsv-* headers, so X-Topics rides beside the signed ones (added
93
+ * on the wire by the transport, not covered by the signature). One session per origin. An answer that is not
94
+ * signed (a 503 at the router's bound) comes back as its status. A fetch for the SDK's
95
+ * HTTPSOverlayBroadcastFacilitator too.
96
+ */
97
+ export declare function signedSubmitter(wallet: WalletInterface): (url: string, init?: {
98
+ method?: string;
99
+ headers?: Record<string, string>;
100
+ body?: unknown;
101
+ }) => Promise<Response>;
@@ -0,0 +1,275 @@
1
+ // The shell app and the chat app for tests and equivs (#83), the
2
+ // onboarding app (#90), the git app (#91) and the management site (#125). A genesis has no shell, no
3
+ // `run`, no `chat`: a test that runs them installs the apps first, through
4
+ // the real install path (the owner's messages src/client/admin.ts builds, sent
5
+ // on the owner's BRC-104 session as the client sends them, #142), from a
6
+ // checkout at the commit pinned here — $SKEIN_SHELL_DIR / $SKEIN_CHAT_DIR /
7
+ // $SKEIN_ONBOARD_DIR / $SKEIN_GIT_DIR name a checkout instead,
8
+ // $SKEIN_SHELL_REV / $SKEIN_CHAT_REV / $SKEIN_ONBOARD_REV / $SKEIN_GIT_REV
9
+ // another commit. A pinned commit is fetched once into
10
+ // <tmpdir>/skein-apps/<name>-<rev> and reused. Not part of anything that runs.
11
+ import { spawnSync } from "node:child_process";
12
+ import { existsSync, mkdirSync, mkdtempSync, renameSync, rmSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { AuthFetch } from "@bsv/sdk";
16
+ import { join as joinPath } from "node:path";
17
+ import { planInstallApp, planRoute, planUninstallApp } from "./client/admin.js";
18
+ import { RawBox } from "./client/raw.js";
19
+ import { WASM_DIR, wasmDirObjects } from "./host/boot.js";
20
+ import { HostDb } from "./host/instances.js";
21
+ import { fetchApp, instanceView, readApp, shellProgram } from "./host/install.js";
22
+ import { storeSpec } from "./runtime/index-store.js";
23
+ import { openStoreFile, storeExists } from "./host/stores.js";
24
+ /** shruggr/skein-shell: `run` and the shell (brush, coreutils, the toolset); 0.2.0 (#143): route `run` → run.run, roles {root: [run]}. */
25
+ export const SHELL_APP = {
26
+ name: "shell", repo: "https://github.com/shruggr/skein-shell",
27
+ rev: process.env.SKEIN_SHELL_REV ?? "86c834368dd81893069c0e131dd33f432d69e44f", dir: process.env.SKEIN_SHELL_DIR,
28
+ };
29
+ /** shruggr/skein-chat: the chat loop (`chat`); 0.2.0 (#143): one open route `chat` → loop.chat. */
30
+ export const CHAT_APP = {
31
+ name: "chat", repo: "https://github.com/shruggr/skein-chat",
32
+ rev: process.env.SKEIN_CHAT_REV ?? "e01eb737b234f4abdfb2704bb3cc0d6747b8a01f", dir: process.env.SKEIN_CHAT_DIR,
33
+ };
34
+ /** shruggr/skein-onboard (#90, #113): the onboarding app, installed in the host skein: onboard.create, registration, BRC-169; #127: onboard.create takes the caller's signed claim (0.3.0); #126: the manager and the certifier by peerAt (0.3.2, skein-sdk 0.7.1); #135: /register takes a session, the registrant its identity (0.3.3); resolve, search, the manifest and the paymail PKI are reads, reads[] (0.3.4); #143: routes, filters and roles ({user: [call, register, profile]}), reads are filter answers, root from the grants head (0.4.0). */
35
+ export const ONBOARD_APP = {
36
+ name: "onboard", repo: "https://github.com/shruggr/skein-onboard",
37
+ rev: process.env.SKEIN_ONBOARD_REV ?? "189b7c2df3041c4914dcf189ade4c8ca241a9b5f", dir: process.env.SKEIN_ONBOARD_DIR,
38
+ };
39
+ /** shruggr/skein-git (#91): the git app — `git.clone {url, hash}` into the store, the app record answered; #126: its fetch is the `fetch` intention (0.1.1, skein-sdk 0.7.1); 0.1.2: the manifest checked by every rule of src/host/manifest.ts (an overlay may list no topics, #120; boxes relative to the app, #128); 0.1.3: reads[] in the record, /lookup a read (#135); 0.2.0: routes, filters and roles, the record checked by every rule of #143's checkManifest. */
40
+ export const GIT_APP = {
41
+ name: "git", repo: "https://github.com/shruggr/skein-git",
42
+ rev: process.env.SKEIN_GIT_REV ?? "01e804adcd86bfbad0139667ac362949c067d4e3", dir: process.env.SKEIN_GIT_DIR,
43
+ };
44
+ /** shruggr/skein-site (#125): the management site as an app — its page at /site/, served from its own tree's www (0.6.2: bundles on a skein with no address-book roles, #126; 0.6.3: the catalog pins the current releases, git 0.1.2 among them; 0.7.3: the page in the skein brand — header and wallet chip, skein cards, the handle/Register card, the logged-out landing page; 0.7.6: Register over the wallet's session, #135; 0.7.7: the page a read, reads[], #135; 0.9.0: routes, filters and roles — the read route `get`, the Routes view, the roles/grants editor, no owner rows, #143). */
45
+ export const SITE_APP = {
46
+ name: "site", repo: "https://github.com/shruggr/skein-site",
47
+ rev: process.env.SKEIN_SITE_REV ?? "3913f1927e401a43861150583ffa5342b1cc28f3", dir: process.env.SKEIN_SITE_DIR,
48
+ };
49
+ /** shruggr/skein-chain (#78): the chain app, `chain/1`; v0.7.0 (#156: its state points at the host chain), the version the equivs pin. */
50
+ export const CHAIN_APP = {
51
+ name: "chain", repo: "https://github.com/shruggr/skein-chain",
52
+ rev: process.env.SKEIN_CHAIN_REV ?? "4d3f595c0f073db63223907fcd0909d53e770b91", dir: process.env.SKEIN_CHAIN_DIR,
53
+ };
54
+ /**
55
+ * #152: chain, git and site — what an image installed at birth before images carried apps — in that
56
+ * order, for installApps after the claim (each at its pin, or $SKEIN_CHAIN_DIR / $SKEIN_GIT_DIR /
57
+ * $SKEIN_SITE_DIR).
58
+ */
59
+ export const CHAIN_GIT_SITE = [CHAIN_APP, GIT_APP, SITE_APP];
60
+ /** A checkout of the app: its env directory, else the pinned commit, fetched once. */
61
+ export function appCheckout(app) {
62
+ if (app.dir)
63
+ return app.dir;
64
+ const cache = join(tmpdir(), "skein-apps");
65
+ const dir = join(cache, `${app.name}-${app.rev}`);
66
+ if (existsSync(join(dir, "etc/app.json")))
67
+ return dir;
68
+ mkdirSync(cache, { recursive: true });
69
+ // Fetched beside, then renamed into place: two test files may want it at once.
70
+ const tmp = mkdtempSync(join(cache, `${app.name}-`));
71
+ const run = (...args) => {
72
+ const r = spawnSync("git", args, { cwd: tmp, encoding: "utf8" });
73
+ if (r.status !== 0)
74
+ throw new Error(`git ${args.join(" ")}: ${r.stderr.trim() || `exit ${r.status}`}`);
75
+ };
76
+ run("init", "-q");
77
+ run("fetch", "-q", "--depth", "1", app.repo, app.rev);
78
+ run("checkout", "-q", "FETCH_HEAD");
79
+ try {
80
+ renameSync(tmp, dir);
81
+ }
82
+ catch {
83
+ rmSync(tmp, { recursive: true, force: true });
84
+ }
85
+ return dir;
86
+ }
87
+ /** The instance `handle` as the plan reads it: its store file (host.db's row), read only, while the router runs it. */
88
+ export async function viewOf(h, handle) {
89
+ const db = new HostDb(joinPath(h.home, "host.db"));
90
+ const row = db.get(handle);
91
+ db.close();
92
+ if (!row)
93
+ throw new Error(`no instance ${handle}`);
94
+ if (!storeExists(storeSpec(row)))
95
+ throw new Error(`${handle}: no store at ${storeSpec(row)} yet (hydrate it first)`);
96
+ const s = openStoreFile(storeSpec(row), { readOnly: true });
97
+ try {
98
+ return { ...(await instanceView(s)), close: () => s.close() };
99
+ }
100
+ catch (e) {
101
+ s.close();
102
+ throw e;
103
+ }
104
+ }
105
+ /**
106
+ * Deliver a plan as the operator's client does (#142): each message on the wallet's BRC-104 session with the
107
+ * instance's front door (dag-cbor bodies, src/client/raw.ts), in order; throws at the first refusal.
108
+ */
109
+ export async function sendPlan(h, handle, p, wallet = h.owner) {
110
+ const box = new RawBox(wallet, `http://127.0.0.1:${h.port}/@${handle}`);
111
+ for (const m of p.messages)
112
+ await box.send(p.recipient, m.box, m.body);
113
+ await h.settled();
114
+ return p.messages.length;
115
+ }
116
+ /**
117
+ * Install the app in `dir` into `handle` as its owner: planned from the instance's store (as `skein install
118
+ * --store` plans), `config` merged over the manifest's, then sent (sendPlan). The prompt.
119
+ */
120
+ export async function installApp(h, handle, dir, o = {}) {
121
+ await h.settled();
122
+ const view = await viewOf(h, handle);
123
+ let p;
124
+ try {
125
+ p = await planInstallApp(await readApp(dir), view, { config: o.config, modules: wasmDirObjects(WASM_DIR) });
126
+ }
127
+ finally {
128
+ view.close();
129
+ }
130
+ await sendPlan(h, handle, p, o.wallet);
131
+ return p.prompt;
132
+ }
133
+ /** Uninstall `app` from `handle` as its owner (planned as `skein uninstall` plans, then sent). The prompt. */
134
+ export async function uninstallApp(h, handle, app, o = {}) {
135
+ await h.settled();
136
+ const view = await viewOf(h, handle);
137
+ let p;
138
+ try {
139
+ p = await planUninstallApp(app, view);
140
+ }
141
+ finally {
142
+ view.close();
143
+ }
144
+ await sendPlan(h, handle, p, o.wallet);
145
+ return p.prompt;
146
+ }
147
+ /**
148
+ * What the equivs ran as `skein-host install|uninstall` before #124, now the owner's path: `install <dir>
149
+ * --instance <h> [--config json] [--dry-run]` or `uninstall <app> --instance <h>` — planned from the
150
+ * instance's store and, unless a dry run, sent by `wallet` (default the owner) on its session (sendPlan). `out`: the prompt, then `<h>: <app> <version> installed|upgraded: <n>
151
+ * messages sent` (or `<h>: <app> uninstalled: …`); `err` and code 1 when planning or a send fails.
152
+ */
153
+ export async function ownerCli(h, args, wallet = h.owner) {
154
+ const out = [], err = [];
155
+ const [cmd, what] = args;
156
+ const opt = (name) => { const i = args.indexOf(name); return i > 0 ? args[i + 1] : undefined; };
157
+ const handle = opt("--instance");
158
+ if ((cmd !== "install" && cmd !== "uninstall") || !what || !handle)
159
+ return { code: 2, out, err: [`ownerCli: ${args.join(" ")}`] };
160
+ try {
161
+ await h.settled();
162
+ const view = await viewOf(h, handle);
163
+ let p;
164
+ try {
165
+ if (cmd === "install") {
166
+ const c = opt("--config");
167
+ const r = await planInstallApp(await readApp(fetchApp(what)), view, { config: c ? JSON.parse(c) : undefined, modules: wasmDirObjects(WASM_DIR) });
168
+ p = { ...r, upgrade: r.prompt[0].startsWith("upgrade") };
169
+ }
170
+ else
171
+ p = { ...(await planUninstallApp(what, view)), app: what };
172
+ }
173
+ finally {
174
+ view.close();
175
+ }
176
+ out.push(...p.prompt);
177
+ if (args.includes("--dry-run"))
178
+ return { code: 0, out, err };
179
+ const n = await sendPlan(h, handle, p, wallet);
180
+ out.push(cmd === "install" ? `${handle}: ${p.app} ${p.version} ${p.upgrade ? "upgraded" : "installed"}: ${n} messages sent` : `${handle}: ${what} uninstalled: ${n} messages sent`);
181
+ return { code: 0, out, err };
182
+ }
183
+ catch (e) {
184
+ err.push(`${cmd}: ${e.message}`);
185
+ return { code: 1, out, err };
186
+ }
187
+ }
188
+ /**
189
+ * #152: root's own route `/` to the site's page (`skein routes add --transport http --prefix --filters
190
+ * site.get --settings '{"root":"www"}' /`), sent as the owner; the site installed first.
191
+ */
192
+ export async function siteAtRoot(h, handle, o = {}) {
193
+ await h.settled();
194
+ const view = await viewOf(h, handle);
195
+ let p;
196
+ try {
197
+ p = await planRoute(view, { op: "add", transport: "http", address: "/", prefix: true, filters: ["site.get"], settings: { root: "www" } });
198
+ }
199
+ finally {
200
+ view.close();
201
+ }
202
+ await sendPlan(h, handle, p, o.wallet);
203
+ }
204
+ /** Install apps into an instance as its owner (installApp each; the instance's store must exist: hydrate it first). */
205
+ export async function installApps(h, handle, apps = [SHELL_APP, CHAT_APP], o = {}) {
206
+ for (const app of apps) {
207
+ try {
208
+ await installApp(h, handle, appCheckout(app), o);
209
+ }
210
+ catch (e) {
211
+ throw new Error(`install ${app.name} into ${handle}: ${e.message}`);
212
+ }
213
+ }
214
+ }
215
+ /**
216
+ * The shell app's shell program put straight into a store with no instance
217
+ * (the shell cases and git in the VM run `skein-kernel shell <store>
218
+ * <program>`, no log): the record and the blocks the install would send for
219
+ * it, built by the install's own code (readApp, shellProgram). Its CID.
220
+ */
221
+ export async function putShellProgram(store, app = SHELL_APP) {
222
+ const dir = appCheckout(app);
223
+ const t = await readApp(dir);
224
+ const src = t.checked.sources.shell;
225
+ if (src?.kind !== "shell")
226
+ throw new Error(`${dir}: etc/app.json has no shell program`);
227
+ const { record, blocks } = shellProgram(t.read, "shell", src, t.checked.manifest.name);
228
+ for (const b of blocks)
229
+ if (!(await store.has(b.cid)))
230
+ await store.putBlock(b.cid, b.bytes);
231
+ return await store.put(record);
232
+ }
233
+ /**
234
+ * #135: a BRC-22 submission as a signed request — POST <base>/submit is a message route. The stock AuthFetch
235
+ * carries only content-type, authorization and x-bsv-* headers, so X-Topics rides beside the signed ones (added
236
+ * on the wire by the transport, not covered by the signature). One session per origin. An answer that is not
237
+ * signed (a 503 at the router's bound) comes back as its status. A fetch for the SDK's
238
+ * HTTPSOverlayBroadcastFacilitator too.
239
+ */
240
+ export function signedSubmitter(wallet) {
241
+ const sessions = new Map();
242
+ return async (url, init = {}) => {
243
+ const u = new URL(url);
244
+ // The dev form /@<handle>/…: the session is that instance's, its handshake under the prefix (as RawBox's).
245
+ const prefix = /^\/@[^/]+/.exec(u.pathname)?.[0] ?? "";
246
+ let s = sessions.get(u.origin + prefix);
247
+ if (!s) {
248
+ const slot = {};
249
+ const wire = ((input, i) => {
250
+ const w = new URL(input instanceof Request ? input.url : String(input));
251
+ if (prefix && w.pathname === "/.well-known/auth")
252
+ w.pathname = `${prefix}/.well-known/auth`;
253
+ const h = new Headers(i?.headers);
254
+ if (slot.topics !== undefined && w.pathname.endsWith("/submit"))
255
+ h.set("x-topics", slot.topics);
256
+ return fetch(w, { ...i, headers: h });
257
+ });
258
+ slot.af = new AuthFetch(wallet, undefined, undefined, undefined, {}, wire);
259
+ s = slot;
260
+ sessions.set(u.origin + prefix, s);
261
+ }
262
+ const hs = Object.fromEntries(Object.entries(init.headers ?? {}).map(([k, v]) => [k.toLowerCase(), v]));
263
+ s.topics = hs["x-topics"];
264
+ const body = init.body instanceof Uint8Array ? init.body : Uint8Array.from(init.body);
265
+ try {
266
+ return await s.af.fetch(url, { method: init.method ?? "POST", headers: { "content-type": hs["content-type"] ?? "application/octet-stream" }, body: Buffer.from(body) });
267
+ }
268
+ catch (e) {
269
+ const m = /Received HTTP (\d+)/.exec(e.message);
270
+ if (m)
271
+ return new Response(JSON.stringify({ status: "error" }), { status: Number(m[1]), headers: { "content-type": "application/json" } });
272
+ throw e;
273
+ }
274
+ };
275
+ }
@@ -0,0 +1,25 @@
1
+ import type { CID } from "multiformats/cid";
2
+ import type { Listed, MessageBox } from "./peers/infer.ts";
3
+ import type { Stamp } from "./runtime/syscalls.ts";
4
+ export declare function collect<T>(it: AsyncIterable<T>): Promise<T[]>;
5
+ /** An in-process BRC-33 messagebox: sendMessage / listMessages / acknowledgeMessage, per identity. */
6
+ export declare function messageBoxHub(): {
7
+ as(identity: string): MessageBox;
8
+ /** Put a message in a box as if `sender` had authenticated (tests of forged senders). */
9
+ inject: (recipient: string, box: string, m: Omit<Listed, "messageId" | "created_at">) => void;
10
+ pending: (recipient: string, box: string) => Listed[];
11
+ };
12
+ export type Hub = ReturnType<typeof messageBoxHub>;
13
+ export declare const T0: Stamp;
14
+ export declare const iso: (s: Stamp) => string;
15
+ /** A settable clock for admission stamps. */
16
+ export declare function scriptClock(start?: Stamp): {
17
+ now: () => Stamp;
18
+ set(s: Stamp): void;
19
+ };
20
+ /** A directory as the client sends it: {root, bundles of ≤ 1 MiB, the last naming the root}. */
21
+ export declare function bundlesOf(dir: string, limit?: number): Promise<{
22
+ root: CID;
23
+ bundles: Uint8Array[];
24
+ records: number;
25
+ }>;
@@ -0,0 +1,48 @@
1
+ // Test helpers shared across directories (and kernel-zig/equiv). Not part of
2
+ // anything that runs.
3
+ import { dirBundles } from "./client/client.js";
4
+ export async function collect(it) {
5
+ const out = [];
6
+ for await (const x of it)
7
+ out.push(x);
8
+ return out;
9
+ }
10
+ /** An in-process BRC-33 messagebox: sendMessage / listMessages / acknowledgeMessage, per identity. */
11
+ export function messageBoxHub() {
12
+ const boxes = new Map();
13
+ let n = 0;
14
+ const key = (recipient, box) => `${recipient} ${box}`;
15
+ const deliver = (recipient, box, m) => {
16
+ const q = boxes.get(key(recipient, box)) ?? [];
17
+ n++;
18
+ q.push({ ...m, messageId: `m${n}`, created_at: String(n).padStart(8, "0") });
19
+ boxes.set(key(recipient, box), q);
20
+ };
21
+ return {
22
+ as(identity) {
23
+ return {
24
+ list: async (box) => [...(boxes.get(key(identity, box)) ?? [])],
25
+ ack: async (ids) => {
26
+ for (const [k, q] of boxes)
27
+ if (k.startsWith(`${identity} `))
28
+ boxes.set(k, q.filter((m) => !ids.includes(m.messageId)));
29
+ },
30
+ send: async ({ recipient, box, body }) => deliver(recipient, box, { sender: identity, body: JSON.stringify(body) }),
31
+ };
32
+ },
33
+ /** Put a message in a box as if `sender` had authenticated (tests of forged senders). */
34
+ inject: deliver,
35
+ pending: (recipient, box) => [...(boxes.get(key(recipient, box)) ?? [])],
36
+ };
37
+ }
38
+ export const T0 = [1_790_000_000, 250_000_000];
39
+ export const iso = (s) => new Date(s[0] * 1000 + Math.floor(s[1] / 1e6)).toISOString();
40
+ /** A settable clock for admission stamps. */
41
+ export function scriptClock(start = T0) {
42
+ let now = start;
43
+ return { now: () => now, set(s) { now = s; } };
44
+ }
45
+ /** A directory as the client sends it: {root, bundles of ≤ 1 MiB, the last naming the root}. */
46
+ export async function bundlesOf(dir, limit) {
47
+ return dirBundles(dir, { limit });
48
+ }
@@ -0,0 +1,35 @@
1
+ import { PrivateKey, ProtoWallet, type WalletInterface } from "@bsv/sdk";
2
+ export type { WalletInterface };
3
+ export * from "./runtime/identity.ts";
4
+ export type WalletConfig =
5
+ /** An in-process @1sat/wallet-node wallet. The key comes from the host's config/env, never from skein's files. */
6
+ {
7
+ kind: "node";
8
+ chain?: "main" | "test";
9
+ privateKey?: string;
10
+ storage: string;
11
+ storageIdentityKey?: string;
12
+ }
13
+ /** A running BRC-100 JSON endpoint, e.g. `1sat serve wallet-api` (default http://127.0.0.1:3321). */
14
+ | {
15
+ kind: "remote";
16
+ url?: string;
17
+ originator?: string;
18
+ }
19
+ /** Tests only: a random key that lives as long as the process. */
20
+ | {
21
+ kind: "ephemeral";
22
+ };
23
+ export declare function connectWallet(config: WalletConfig): Promise<WalletInterface>;
24
+ /**
25
+ * Tests only. Never persisted, so nothing it signs is attributable after the
26
+ * process exits. A ProtoWallet has keys but no actions; those reject.
27
+ */
28
+ export declare function ephemeralWallet(key?: PrivateKey): WalletInterface;
29
+ /**
30
+ * A wallet over one private key (#142: the operator's, from its key file): it signs, derives and opens
31
+ * BRC-103/104 sessions (a ProtoWallet); it has no actions (no coins), and those reject. `base`: the
32
+ * ProtoWallet over that key it wraps (#160: the one-key host's, signer.ts OneKeyWallet), by default a plain one.
33
+ */
34
+ export declare function keyWallet(key: PrivateKey, base?: ProtoWallet): WalletInterface;
35
+ export declare function remoteWallet(url?: string, originator?: string): WalletInterface;
package/dist/wallet.js ADDED
@@ -0,0 +1,87 @@
1
+ // Connecting to a BRC-100 wallet: the runtime's second edge (docs/ARCH.md).
2
+ // This file is outside src/runtime because a remote wallet is reached over
3
+ // HTTP; identities under it are derived with src/runtime/identity.ts. The
4
+ // host, the client, the peers and skein-dev use it.
5
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
6
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
7
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
8
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
9
+ });
10
+ }
11
+ return path;
12
+ };
13
+ import { HTTPWalletJSON, PrivateKey, ProtoWallet } from "@bsv/sdk";
14
+ export * from "./runtime/identity.js";
15
+ export async function connectWallet(config) {
16
+ switch (config.kind) {
17
+ case "remote": return remoteWallet(config.url, config.originator);
18
+ case "node": return nodeWallet(config);
19
+ case "ephemeral": return ephemeralWallet();
20
+ }
21
+ }
22
+ /**
23
+ * Tests only. Never persisted, so nothing it signs is attributable after the
24
+ * process exits. A ProtoWallet has keys but no actions; those reject.
25
+ */
26
+ export function ephemeralWallet(key = PrivateKey.fromRandom()) {
27
+ return keyWallet(key);
28
+ }
29
+ /**
30
+ * A wallet over one private key (#142: the operator's, from its key file): it signs, derives and opens
31
+ * BRC-103/104 sessions (a ProtoWallet); it has no actions (no coins), and those reject. `base`: the
32
+ * ProtoWallet over that key it wraps (#160: the one-key host's, signer.ts OneKeyWallet), by default a plain one.
33
+ */
34
+ export function keyWallet(key, base = new ProtoWallet(key)) {
35
+ return new Proxy(base, {
36
+ get(t, p) {
37
+ // `then` must stay undefined or awaiting the wallet would call it.
38
+ if (typeof p !== "string" || p === "then" || p in t)
39
+ return Reflect.get(t, p);
40
+ return () => Promise.reject(new Error(`ephemeral wallet: ${p} is not supported`));
41
+ },
42
+ });
43
+ }
44
+ export function remoteWallet(url = "http://127.0.0.1:3321", originator = "skein") {
45
+ return new HTTPWalletJSON(originator, url, relayErrors);
46
+ }
47
+ // wallet-api answers a refusal with 400 {"error": "..."}; HTTPWalletJSON only
48
+ // understands {isError, ...} and would reduce the message to a status code,
49
+ // losing the `1sat permissions grant …` command the refusal names.
50
+ async function relayErrors(input, init) {
51
+ const res = await fetch(input, init);
52
+ if (res.ok)
53
+ return res;
54
+ let body;
55
+ try {
56
+ body = await res.clone().json();
57
+ }
58
+ catch {
59
+ return res;
60
+ }
61
+ const b = body;
62
+ if (b && typeof b.error === "string" && !b.isError)
63
+ throw new Error(`wallet: ${b.error}`);
64
+ return res;
65
+ }
66
+ async function nodeWallet(c) {
67
+ const privateKey = c.privateKey ?? process.env.PRIVATE_KEY_WIF;
68
+ if (!privateKey)
69
+ throw new Error("node wallet: no privateKey in config and $PRIVATE_KEY_WIF unset");
70
+ // Optional dependency: loaded by name so skein installs and typechecks without it.
71
+ const spec = "@1sat/wallet-node";
72
+ let mod;
73
+ try {
74
+ mod = await import(__rewriteRelativeImportExtension(spec));
75
+ }
76
+ catch (e) {
77
+ throw new Error(`node wallet: ${spec} is not installed (${e.message}); use {kind:"remote"} against \`1sat serve wallet-api\``);
78
+ }
79
+ const { wallet } = await mod.createNodeWallet({
80
+ privateKey,
81
+ chain: c.chain ?? "main",
82
+ storageIdentityKey: c.storageIdentityKey ?? "skein",
83
+ storage: { provider: "bun-sqlite", filename: c.storage }, // picks node:sqlite under Node
84
+ skipInitialMonitor: true,
85
+ });
86
+ return wallet;
87
+ }
@@ -0,0 +1,68 @@
1
+ # Images
2
+
3
+ An image is a system tree a skein is born from (docs/BOOTSTRAP.md, "The
4
+ system tree"), with no root in its genesis and a claim route. It is the
5
+ boundary programs and, optionally, a list of apps to install at birth.
6
+
7
+ - `default/`: the default image — the front door, the messagebox, the
8
+ wallet and the claim route; no apps. `skein-host add <h> --image default`,
9
+ and every skein the instance manager creates.
10
+ - `host/`: the host image's own part, merged over `default/` (src/host/boot.ts
11
+ `mergeImages`): the host skein's image, with skein-onboard. `skein-host
12
+ add <h> --image host` boots an unclaimed skein from it.
13
+
14
+ `skein-host add <h> --image <dir>` boots from an image directory. One
15
+ with `bin/` is a complete image. One without `bin/` is merged over
16
+ `default/` first, exactly as `host/` is (#152), so an app repository's
17
+ `image/` needs only `etc/apps.json`.
18
+
19
+ ## An image's form
20
+
21
+ ```
22
+ bin/<name>.wasm | bin/<name>.cid a program's module, or its CID (the kernel's pinned modules: wasm/)
23
+ bin/<name>.json the program record's inputs, services and description
24
+ etc/dispatch.json the routes; an image's carry the claim route
25
+ etc/config.json optional: defaults, peers, names, collect, feeds, roles
26
+ etc/apps.json optional: the apps installed at birth, and root's own routes
27
+ ```
28
+
29
+ `etc/apps.json`:
30
+
31
+ ```json
32
+ {
33
+ "install": [
34
+ "apps/one",
35
+ "..",
36
+ { "url": "https://github.com/shruggr/skein-onboard/archive/refs/tags/v0.4.0.tar.gz",
37
+ "hash": "ba29f99adb0d2d1849aa7f0cf72ceeefde88c5ae" }
38
+ ],
39
+ "routes": [{ "transport": "http", "address": "/", "prefix": true, "filters": ["site.get"], "root": "www" }]
40
+ }
41
+ ```
42
+
43
+ Each `install` entry is one app, installed in order:
44
+
45
+ - **a directory**, relative to the image: a directory of the image's tree
46
+ (`apps/one`), or one beside it (`..`, `../<dir>`), read from disk when the
47
+ image is booted from its directory, as `skein install <dir>` reads it
48
+ (`zig-out`, `.zig-cache`, `zig-pkg`, `.git` and `node_modules` left
49
+ out). An app repository can carry its image as `image/`, listing `..`;
50
+ - **a release tarball** `{url, hash}`: `hash` is the git id of the app's
51
+ tree (`git rev-parse <tag>^{tree}`). The host fetches it (gzip or plain
52
+ tar; a tarball holding one directory, as a tag's archive does, is that
53
+ directory), scans it as a directory, and refuses it unless its tree is
54
+ `hash`. A source that already holds the tree is not fetched.
55
+
56
+ Each is installed as a directory install by message would install it — the
57
+ app's objects, its record under `<app>/app`, its routes — and the genesis
58
+ carries the result, so root upgrades it later by installing a newer version
59
+ the same way, keeping its state. An app with a `start` message is refused
60
+ at birth (a genesis carries no message): install it after the claim.
61
+ `routes` are root's own routes, added after the apps (a handler may be
62
+ `<app>.<role>` of an app above).
63
+
64
+ Two images merge (`mergeImages`): their `install` lists are joined (an app
65
+ in both is refused), and a route of the second at the same transport,
66
+ address and prefix as one of the first replaces it. Any other path in both
67
+ (`etc/dispatch.json`, `etc/config.json`, `bin/<name>.json`) is refused
68
+ unless it is the same file: a merge adds paths, it does not replace one.