@amalgm/shell 0.1.113 → 0.1.114

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 (77) hide show
  1. package/PURPOSE.md +67 -6
  2. package/README.md +36 -7
  3. package/dist/authorization/device-key.d.ts +19 -0
  4. package/dist/authorization/device-key.js +81 -0
  5. package/dist/authorization/device-key.js.map +1 -0
  6. package/dist/authorization/index.d.ts +65 -0
  7. package/dist/authorization/index.js +63 -0
  8. package/dist/authorization/index.js.map +1 -0
  9. package/dist/authorization/issuer.d.ts +11 -0
  10. package/dist/authorization/issuer.js +76 -0
  11. package/dist/authorization/issuer.js.map +1 -0
  12. package/dist/authorization/key-store.d.ts +15 -0
  13. package/dist/authorization/key-store.js +148 -0
  14. package/dist/authorization/key-store.js.map +1 -0
  15. package/dist/authorization/recovery-client.d.ts +19 -0
  16. package/dist/authorization/recovery-client.js +102 -0
  17. package/dist/authorization/recovery-client.js.map +1 -0
  18. package/dist/authorization/recovery-host.d.ts +28 -0
  19. package/dist/authorization/recovery-host.js +93 -0
  20. package/dist/authorization/recovery-host.js.map +1 -0
  21. package/dist/authorization/recovery.d.ts +13 -0
  22. package/dist/authorization/recovery.js +27 -0
  23. package/dist/authorization/recovery.js.map +1 -0
  24. package/dist/authorization/secret-process.d.ts +2 -0
  25. package/dist/authorization/secret-process.js +32 -0
  26. package/dist/authorization/secret-process.js.map +1 -0
  27. package/dist/boot.d.ts +19 -11
  28. package/dist/boot.js +34 -25
  29. package/dist/boot.js.map +1 -1
  30. package/dist/builtin-agents.d.ts +3 -0
  31. package/dist/builtin-agents.js +15 -0
  32. package/dist/builtin-agents.js.map +1 -0
  33. package/dist/chat-composition.d.ts +19 -0
  34. package/dist/chat-composition.js +83 -0
  35. package/dist/chat-composition.js.map +1 -0
  36. package/dist/chat-host.d.ts +9 -25
  37. package/dist/chat-host.js +12 -158
  38. package/dist/chat-host.js.map +1 -1
  39. package/dist/cli.js +21 -13
  40. package/dist/cli.js.map +1 -1
  41. package/dist/content-cache-host.d.ts +7 -0
  42. package/dist/content-cache-host.js +23 -1
  43. package/dist/content-cache-host.js.map +1 -1
  44. package/dist/credentials-host.d.ts +1 -5
  45. package/dist/credentials-host.js +1 -5
  46. package/dist/credentials-host.js.map +1 -1
  47. package/dist/entity-record-store.d.ts +2 -0
  48. package/dist/entity-record-store.js +15 -0
  49. package/dist/entity-record-store.js.map +1 -1
  50. package/dist/forward.js +1 -1
  51. package/dist/forward.js.map +1 -1
  52. package/dist/index.d.ts +7 -6
  53. package/dist/index.js +2 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/machine-host.d.ts +29 -0
  56. package/dist/machine-host.js +240 -10
  57. package/dist/machine-host.js.map +1 -1
  58. package/dist/parity.d.ts +1 -5
  59. package/dist/parity.js +4 -41
  60. package/dist/parity.js.map +1 -1
  61. package/dist/release-catalog.js +7 -4
  62. package/dist/release-catalog.js.map +1 -1
  63. package/dist/runtime-command-host.d.ts +11 -0
  64. package/dist/runtime-command-host.js +65 -0
  65. package/dist/runtime-command-host.js.map +1 -1
  66. package/dist/user-ground-host.js +60 -5
  67. package/dist/user-ground-host.js.map +1 -1
  68. package/package.json +14 -5
  69. package/dist/chat-payloads-host.d.ts +0 -8
  70. package/dist/chat-payloads-host.js +0 -76
  71. package/dist/chat-payloads-host.js.map +0 -1
  72. package/dist/chat-relay-host.d.ts +0 -24
  73. package/dist/chat-relay-host.js +0 -90
  74. package/dist/chat-relay-host.js.map +0 -1
  75. package/dist/drivers/claude-code.d.ts +0 -27
  76. package/dist/drivers/claude-code.js +0 -72
  77. package/dist/drivers/claude-code.js.map +0 -1
@@ -0,0 +1,148 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readFileSync, rmSync } from "node:fs";
3
+ import { promisify } from "node:util";
4
+ import { atomicJson } from "../filesystem.js";
5
+ import { runSecretProcess } from "./secret-process.js";
6
+ const execFileAsync = promisify(execFile);
7
+ const KEYCHAIN_SERVICE = "com.amalgm.shell.authorization";
8
+ export function createDeviceKeyStore(input) {
9
+ const file = fileStore(input.file);
10
+ const platform = input.platform ?? process.platform;
11
+ if (platform === "darwin")
12
+ return preferredStore(keychainStore(input.account), file);
13
+ if (platform === "win32")
14
+ return preferredStore(dpapiStore(input.file), file);
15
+ return file;
16
+ }
17
+ function fileStore(file) {
18
+ return {
19
+ load: async () => readStoredJson(file),
20
+ save: async (key) => atomicJson(file, key),
21
+ remove: async () => rmSync(file, { force: true }),
22
+ };
23
+ }
24
+ function keychainStore(account) {
25
+ return {
26
+ load: async () => {
27
+ try {
28
+ const { stdout } = await execFileAsync("/usr/bin/security", [
29
+ "find-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE, "-w",
30
+ ], { encoding: "utf8", maxBuffer: 64 * 1024 });
31
+ return JSON.parse(stdout.trim());
32
+ }
33
+ catch (error) {
34
+ const message = errorText(error);
35
+ if (message.includes("could not be found") || message.includes("SecKeychainSearchCopyNext: -25300")) {
36
+ return null;
37
+ }
38
+ throw error;
39
+ }
40
+ },
41
+ save: async (key) => {
42
+ await runSecretProcess("/usr/bin/security", [
43
+ "add-generic-password", "-U", "-a", account, "-s", KEYCHAIN_SERVICE,
44
+ "-w",
45
+ ], `${JSON.stringify(key)}\n`);
46
+ },
47
+ remove: async () => {
48
+ try {
49
+ await execFileAsync("/usr/bin/security", [
50
+ "delete-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE,
51
+ ], { encoding: "utf8", maxBuffer: 64 * 1024 });
52
+ }
53
+ catch (error) {
54
+ const message = errorText(error);
55
+ if (!message.includes("could not be found") && !message.includes("SecKeychainSearchCopyNext: -25300")) {
56
+ throw error;
57
+ }
58
+ }
59
+ },
60
+ };
61
+ }
62
+ function dpapiStore(file) {
63
+ const protect = [
64
+ "$ErrorActionPreference='Stop'",
65
+ "$p=[Text.Encoding]::UTF8.GetBytes([Console]::In.ReadToEnd())",
66
+ "$b=[Security.Cryptography.ProtectedData]::Protect($p,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser)",
67
+ "[Console]::Out.Write([Convert]::ToBase64String($b))",
68
+ ].join(";");
69
+ const unprotect = [
70
+ "$ErrorActionPreference='Stop'",
71
+ "$b=[Convert]::FromBase64String([Console]::In.ReadToEnd().Trim())",
72
+ "$p=[Security.Cryptography.ProtectedData]::Unprotect($b,$null,[Security.Cryptography.DataProtectionScope]::CurrentUser)",
73
+ "[Console]::Out.Write([Text.Encoding]::UTF8.GetString($p))",
74
+ ].join(";");
75
+ return {
76
+ load: async () => {
77
+ const stored = readStoredJson(file);
78
+ if (stored?.storage !== "windows-dpapi" || !stored.ciphertext)
79
+ return null;
80
+ const value = await runSecretProcess("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", unprotect], stored.ciphertext);
81
+ return JSON.parse(value);
82
+ },
83
+ save: async (key) => {
84
+ const ciphertext = await runSecretProcess("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", protect], JSON.stringify(key));
85
+ atomicJson(file, { storage: "windows-dpapi", ciphertext });
86
+ },
87
+ remove: async () => rmSync(file, { force: true }),
88
+ };
89
+ }
90
+ function preferredStore(primary, fallback) {
91
+ let selected = null;
92
+ return {
93
+ load: async () => {
94
+ const primaryValue = await primary.load();
95
+ if (primaryValue) {
96
+ selected = "primary";
97
+ return primaryValue;
98
+ }
99
+ const fallbackValue = await fallback.load();
100
+ if (fallbackValue)
101
+ selected = "fallback";
102
+ return fallbackValue;
103
+ },
104
+ save: async (key) => {
105
+ if (selected === "primary")
106
+ return primary.save(key);
107
+ if (selected === "fallback")
108
+ return fallback.save(key);
109
+ try {
110
+ await primary.save(key);
111
+ selected = "primary";
112
+ }
113
+ catch {
114
+ await fallback.save(key);
115
+ selected = "fallback";
116
+ }
117
+ },
118
+ remove: async () => {
119
+ const errors = [];
120
+ await primary.remove().catch((error) => errors.push(error));
121
+ await fallback.remove().catch((error) => errors.push(error));
122
+ if (errors.length > 0)
123
+ throw errors[0];
124
+ selected = null;
125
+ },
126
+ };
127
+ }
128
+ function readStoredJson(file) {
129
+ try {
130
+ return JSON.parse(readFileSync(file, "utf8"));
131
+ }
132
+ catch (error) {
133
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
134
+ return null;
135
+ }
136
+ throw Object.assign(new Error("Stored authorization key is unreadable or corrupt"), {
137
+ code: "authorization_key_corrupt",
138
+ cause: error,
139
+ });
140
+ }
141
+ }
142
+ function errorText(error) {
143
+ if (!error || typeof error !== "object")
144
+ return String(error);
145
+ const value = error;
146
+ return `${String(value.message ?? "")} ${String(value.stderr ?? "")}`;
147
+ }
148
+ //# sourceMappingURL=key-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-store.js","sourceRoot":"","sources":["../../src/authorization/key-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { DeviceKey } from "./device-key.js";
2
+ type Dict = Record<string, unknown>;
3
+ export interface AuthorizationRecoveryClientOptions {
4
+ readonly appUrl: string;
5
+ readonly body: Dict;
6
+ readonly signer: DeviceKey;
7
+ readonly setupCode?: string;
8
+ readonly fetch?: typeof globalThis.fetch;
9
+ readonly nowMs?: () => number;
10
+ readonly sleep?: (milliseconds: number) => Promise<void>;
11
+ readonly showSetup?: (input: {
12
+ readonly verificationUrl: string;
13
+ readonly setupCode: string;
14
+ readonly keyThumbprint: string;
15
+ }) => Promise<void> | void;
16
+ }
17
+ /** Browser-approved recovery with proof of the staged candidate private key. */
18
+ export declare function requestAuthorizationKeyRecovery(options: AuthorizationRecoveryClientOptions): Promise<Dict>;
19
+ export {};
@@ -0,0 +1,102 @@
1
+ import { assertSetupCode } from "@amalgm/core/identity";
2
+ /** Browser-approved recovery with proof of the staged candidate private key. */
3
+ export async function requestAuthorizationKeyRecovery(options) {
4
+ const fetchImpl = options.fetch ?? globalThis.fetch;
5
+ const nowMs = options.nowMs ?? (() => Date.now());
6
+ const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
7
+ const origin = recoveryOrigin(options.appUrl);
8
+ const registrationToken = options.setupCode
9
+ ? text((await postJson(fetchImpl, origin, "/api/auth/computer-setup/redeem", {
10
+ ...options.body,
11
+ setup_code: assertSetupCode(options.setupCode),
12
+ })).computer_registration_token)
13
+ : await waitForApproval(options, fetchImpl, origin, nowMs, sleep);
14
+ if (!registrationToken)
15
+ throw recoveryError("authorization_recovery_token_missing");
16
+ const registerUrl = new URL("/api/computers/register", origin).toString();
17
+ const proof = await options.signer.signProof({ method: "POST", url: registerUrl });
18
+ return postJson(fetchImpl, origin, "/api/computers/register", options.body, {
19
+ authorization: `Bearer ${registrationToken}`,
20
+ dpop: proof,
21
+ });
22
+ }
23
+ async function waitForApproval(options, fetchImpl, origin, nowMs, sleep) {
24
+ const started = await postJson(fetchImpl, origin, "/api/auth/computer-setup/cli/start", options.body);
25
+ const deviceCode = text(started.device_code);
26
+ const setupCode = text(started.setup_code);
27
+ const verificationUrl = text(started.verification_url);
28
+ if (!deviceCode || !setupCode || !verificationUrl) {
29
+ throw recoveryError("authorization_recovery_start_invalid");
30
+ }
31
+ await options.showSetup?.({
32
+ verificationUrl,
33
+ setupCode,
34
+ keyThumbprint: options.signer.keyThumbprint,
35
+ });
36
+ const deadline = nowMs() + Math.max(60, number(started.expires_in, 180)) * 1000;
37
+ let intervalMs = Math.max(1, number(started.interval, 2)) * 1000;
38
+ while (nowMs() < deadline) {
39
+ const polled = await postJson(fetchImpl, origin, "/api/auth/computer-setup/cli/poll", {
40
+ ...options.body,
41
+ device_code: deviceCode,
42
+ });
43
+ const status = text(polled.status);
44
+ if (status === "approved")
45
+ return text(polled.computer_registration_token);
46
+ if (status !== "pending")
47
+ throw recoveryError(`authorization_recovery_${status || "invalid"}`);
48
+ intervalMs = Math.max(1, number(polled.interval, intervalMs / 1000)) * 1000;
49
+ await sleep(intervalMs);
50
+ }
51
+ throw recoveryError("authorization_recovery_timeout");
52
+ }
53
+ function recoveryOrigin(value) {
54
+ const url = new URL(value);
55
+ const loopback = url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "::1";
56
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
57
+ throw recoveryError("authorization_recovery_origin_insecure");
58
+ }
59
+ url.pathname = "/";
60
+ url.search = "";
61
+ url.hash = "";
62
+ return url.toString();
63
+ }
64
+ async function postJson(fetchImpl, origin, path, body, headers = {}) {
65
+ const response = await fetchImpl(new URL(path, origin), {
66
+ method: "POST",
67
+ redirect: "error",
68
+ headers: { accept: "application/json", "content-type": "application/json", ...headers },
69
+ body: JSON.stringify(body),
70
+ });
71
+ const bytes = new Uint8Array(await response.arrayBuffer());
72
+ if (bytes.byteLength > 64 * 1024)
73
+ throw recoveryError("authorization_recovery_response_too_large");
74
+ let payload = {};
75
+ try {
76
+ payload = bytes.byteLength > 0
77
+ ? JSON.parse(Buffer.from(bytes).toString("utf8"))
78
+ : {};
79
+ }
80
+ catch {
81
+ throw recoveryError("authorization_recovery_response_invalid");
82
+ }
83
+ if (!response.ok) {
84
+ throw Object.assign(new Error(`${response.status} ${text(payload.error) || response.statusText}`), {
85
+ code: `http_${response.status}`,
86
+ status: response.status,
87
+ payload,
88
+ });
89
+ }
90
+ return payload;
91
+ }
92
+ function number(value, fallback) {
93
+ const parsed = Number(value);
94
+ return Number.isFinite(parsed) ? parsed : fallback;
95
+ }
96
+ function text(value) {
97
+ return typeof value === "string" ? value.trim() : "";
98
+ }
99
+ function recoveryError(code) {
100
+ return Object.assign(new Error(code), { code });
101
+ }
102
+ //# sourceMappingURL=recovery-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-client.js","sourceRoot":"","sources":["../../src/authorization/recovery-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type StoredMachine } from "@amalgm/core/identity";
2
+ import type { DeviceKeyStore } from "./key-store.js";
3
+ export interface AuthorizationKeyRecoveryResult {
4
+ readonly status: "healthy" | "recovered";
5
+ readonly deviceId: string;
6
+ readonly computerId: string;
7
+ readonly keyThumbprint: string;
8
+ }
9
+ export declare function recoverMachineAuthorization(options: Readonly<{
10
+ machine: StoredMachine;
11
+ appUrl: string;
12
+ runtimeVersion: string;
13
+ runtimeLabel: string;
14
+ branch: string;
15
+ fallbackName: string;
16
+ fallbackPlatform: string;
17
+ stateDir: string;
18
+ platform?: NodeJS.Platform;
19
+ keyStore?: DeviceKeyStore;
20
+ recoveryKeyStore?: DeviceKeyStore;
21
+ setupCode?: string;
22
+ showSetup?: (input: {
23
+ verificationUrl: string;
24
+ setupCode: string;
25
+ keyThumbprint?: string;
26
+ }) => Promise<void> | void;
27
+ save(machine: StoredMachine): Promise<void>;
28
+ }>): Promise<AuthorizationKeyRecoveryResult>;
@@ -0,0 +1,93 @@
1
+ import { authFromRecord, mergeRecordAuth, publicComputerRecord, } from "@amalgm/core/identity";
2
+ import { deviceKeyFromStored, installationDeviceKeyStores, promoteInstallationDeviceKeyRecovery, requireInstallationDeviceKey, stageInstallationDeviceKeyRecovery, } from "./index.js";
3
+ import { requestAuthorizationKeyRecovery } from "./recovery-client.js";
4
+ export async function recoverMachineAuthorization(options) {
5
+ const record = mergeRecordAuth(options.machine.publicRecord, options.machine.secretAuth) ?? {};
6
+ const deviceId = text(record.device_id);
7
+ const computerId = text(record.computer_id);
8
+ const userId = text(record.user_id);
9
+ const expectedLocalKey = text(record.authorization_key_thumbprint);
10
+ if (!deviceId || !computerId || !userId)
11
+ throw recoveryError("authorization_recovery_identity_incomplete");
12
+ const keyOptions = {
13
+ stateDir: options.stateDir,
14
+ deviceId,
15
+ ...(options.platform ? { platform: options.platform } : {}),
16
+ ...(options.keyStore ? { keyStore: options.keyStore } : {}),
17
+ ...(options.recoveryKeyStore ? { recoveryKeyStore: options.recoveryKeyStore } : {}),
18
+ };
19
+ try {
20
+ const active = await requireInstallationDeviceKey(keyOptions);
21
+ if (!expectedLocalKey || active.keyThumbprint === expectedLocalKey) {
22
+ return result("healthy", deviceId, computerId, active.keyThumbprint);
23
+ }
24
+ }
25
+ catch (error) {
26
+ const code = text(error?.code);
27
+ if (code !== "authorization_key_missing" && code !== "authorization_key_corrupt")
28
+ throw error;
29
+ }
30
+ const pending = await installationDeviceKeyStores(keyOptions).pending.load();
31
+ if (pending) {
32
+ const staged = deviceKeyFromStored(pending, {});
33
+ if (expectedLocalKey && staged.keyThumbprint === expectedLocalKey) {
34
+ await promoteInstallationDeviceKeyRecovery({
35
+ ...keyOptions,
36
+ acceptedKeyThumbprint: staged.keyThumbprint,
37
+ });
38
+ return result("recovered", deviceId, computerId, staged.keyThumbprint);
39
+ }
40
+ }
41
+ const candidate = await stageInstallationDeviceKeyRecovery(keyOptions);
42
+ const response = await requestAuthorizationKeyRecovery({
43
+ appUrl: options.appUrl,
44
+ body: recoveryBody(options, record, deviceId, candidate.keyThumbprint),
45
+ signer: candidate,
46
+ ...(options.setupCode ? { setupCode: options.setupCode } : {}),
47
+ ...(options.showSetup ? { showSetup: options.showSetup } : {}),
48
+ });
49
+ assertRecoveryResponse(response, { deviceId, computerId, userId, key: candidate.keyThumbprint });
50
+ const merged = { ...record, ...response, app_url: options.appUrl };
51
+ await options.save({
52
+ publicRecord: publicComputerRecord(merged),
53
+ secretAuth: authFromRecord(merged, options.machine.secretAuth, { nowIso: () => new Date().toISOString() }),
54
+ installation: options.machine.installation,
55
+ ready: options.machine.ready,
56
+ });
57
+ await promoteInstallationDeviceKeyRecovery({
58
+ ...keyOptions,
59
+ acceptedKeyThumbprint: candidate.keyThumbprint,
60
+ });
61
+ return result("recovered", deviceId, computerId, candidate.keyThumbprint);
62
+ }
63
+ function recoveryBody(options, record, deviceId, key) {
64
+ return {
65
+ device_id: deviceId,
66
+ name: text(record.name) || options.fallbackName,
67
+ platform: text(record.platform) || options.fallbackPlatform,
68
+ app_version: options.runtimeVersion,
69
+ runtime_label: options.runtimeLabel,
70
+ branch: options.branch,
71
+ purpose: "authorization_key_recovery",
72
+ authorization_key_thumbprint: key,
73
+ };
74
+ }
75
+ function assertRecoveryResponse(response, expected) {
76
+ if (text(response.device_id) !== expected.deviceId
77
+ || text(response.computer_id) !== expected.computerId
78
+ || text(response.user_id) !== expected.userId
79
+ || text(response.authorization_key_thumbprint) !== expected.key
80
+ || !text(response.computer_auth_token)
81
+ || !text(response.tunnel_token))
82
+ throw recoveryError("authorization_recovery_response_mismatch");
83
+ }
84
+ function result(status, deviceId, computerId, keyThumbprint) {
85
+ return Object.freeze({ status, deviceId, computerId, keyThumbprint });
86
+ }
87
+ function recoveryError(code) {
88
+ return Object.assign(new Error(code), { code });
89
+ }
90
+ function text(value) {
91
+ return typeof value === "string" ? value.trim() : "";
92
+ }
93
+ //# sourceMappingURL=recovery-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-host.js","sourceRoot":"","sources":["../../src/authorization/recovery-host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { DeviceKey } from "./device-key.js";
2
+ import type { DeviceKeyStore } from "./key-store.js";
3
+ export interface DeviceKeyRecoveryStores {
4
+ readonly active: DeviceKeyStore;
5
+ readonly pending: DeviceKeyStore;
6
+ }
7
+ /** Reuse one pending candidate so a lost server response is safe to retry. */
8
+ export declare function stageDeviceKeyRecovery(stores: DeviceKeyRecoveryStores, ports?: Readonly<{
9
+ nowMs?: () => number;
10
+ randomUuid?: () => string;
11
+ }>): Promise<DeviceKey>;
12
+ /** Promote only the exact server-accepted candidate, then erase staging. */
13
+ export declare function promoteDeviceKeyRecovery(stores: DeviceKeyRecoveryStores, acceptedKeyThumbprint: string): Promise<DeviceKey>;
@@ -0,0 +1,27 @@
1
+ import { deviceKeyFromStored, generateStoredDeviceKey, } from "./device-key.js";
2
+ /** Reuse one pending candidate so a lost server response is safe to retry. */
3
+ export async function stageDeviceKeyRecovery(stores, ports = {}) {
4
+ let candidate = await stores.pending.load();
5
+ if (!candidate) {
6
+ candidate = generateStoredDeviceKey();
7
+ await stores.pending.save(candidate);
8
+ }
9
+ return deviceKeyFromStored(candidate, ports);
10
+ }
11
+ /** Promote only the exact server-accepted candidate, then erase staging. */
12
+ export async function promoteDeviceKeyRecovery(stores, acceptedKeyThumbprint) {
13
+ const candidate = await stores.pending.load();
14
+ if (!candidate)
15
+ throw recoveryError("authorization_recovery_candidate_missing");
16
+ const signer = deviceKeyFromStored(candidate, {});
17
+ if (signer.keyThumbprint !== acceptedKeyThumbprint) {
18
+ throw recoveryError("authorization_recovery_candidate_mismatch");
19
+ }
20
+ await stores.active.save(candidate);
21
+ await stores.pending.remove();
22
+ return signer;
23
+ }
24
+ function recoveryError(code) {
25
+ return Object.assign(new Error(code), { code });
26
+ }
27
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/authorization/recovery.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AAQzB,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAA+B,EAC/B,QAAuE,EAAE;IAEzE,IAAI,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,uBAAuB,EAAE,CAAC;QACtC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAA+B,EAC/B,qBAA6B;IAE7B,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC9C,IAAI,CAAC,SAAS;QAAE,MAAM,aAAa,CAAC,0CAA0C,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAClD,IAAI,MAAM,CAAC,aAAa,KAAK,qBAAqB,EAAE,CAAC;QACnD,MAAM,aAAa,CAAC,2CAA2C,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;IAC9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** Run a local credential helper without placing secret input in argv. */
2
+ export declare function runSecretProcess(file: string, args: readonly string[], input: string): Promise<string>;
@@ -0,0 +1,32 @@
1
+ import { spawn } from "node:child_process";
2
+ const OUTPUT_LIMIT = 64 * 1024;
3
+ /** Run a local credential helper without placing secret input in argv. */
4
+ export function runSecretProcess(file, args, input) {
5
+ return new Promise((resolve, reject) => {
6
+ const child = spawn(file, args, { stdio: ["pipe", "pipe", "pipe"] });
7
+ const stdout = [];
8
+ const stderr = [];
9
+ let outputBytes = 0;
10
+ const collect = (target) => (chunk) => {
11
+ outputBytes += chunk.byteLength;
12
+ if (outputBytes > OUTPUT_LIMIT)
13
+ child.kill();
14
+ else
15
+ target.push(Buffer.from(chunk));
16
+ };
17
+ child.stdout.on("data", collect(stdout));
18
+ child.stderr.on("data", collect(stderr));
19
+ child.once("error", reject);
20
+ child.once("close", (code) => {
21
+ if (outputBytes > OUTPUT_LIMIT)
22
+ return reject(new Error("credential helper output exceeded limit"));
23
+ if (code === 0)
24
+ return resolve(Buffer.concat(stdout).toString("utf8"));
25
+ const error = new Error(`credential helper exited with code ${code ?? "unknown"}`);
26
+ error.stderr = Buffer.concat(stderr).toString("utf8");
27
+ reject(error);
28
+ });
29
+ child.stdin.end(input);
30
+ });
31
+ }
32
+ //# sourceMappingURL=secret-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-process.js","sourceRoot":"","sources":["../../src/authorization/secret-process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC;AAE/B,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,IAAuB,EACvB,KAAa;IAEb,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACrE,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,MAAM,OAAO,GAAG,CAAC,MAAgB,EAAE,EAAE,CAAC,CAAC,KAAiB,EAAE,EAAE;YAC1D,WAAW,IAAI,KAAK,CAAC,UAAU,CAAC;YAChC,IAAI,WAAW,GAAG,YAAY;gBAAE,KAAK,CAAC,IAAI,EAAE,CAAC;;gBACxC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,CAAC,CAAC;QACF,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QACzC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC3B,IAAI,WAAW,GAAG,YAAY;gBAAE,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC,CAAC;YACpG,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;YACvE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,sCAAsC,IAAI,IAAI,SAAS,EAAE,CAEhF,CAAC;YACF,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACtD,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/boot.d.ts CHANGED
@@ -1,23 +1,24 @@
1
1
  import type { Server } from "node:http";
2
2
  import { Agents } from "@amalgm/agents";
3
3
  import type { Apps } from "@amalgm/apps";
4
+ import { type BrowserAuthorization, type BrowserDriver, type BrowserEventSink, type BrowserRuntimeSelector, type BrowserService } from "@amalgm/browser";
5
+ import type { ChatRuntimePort } from "@amalgm/chat";
6
+ import type { PlatformCredentialBroker } from "@amalgm/chat/host";
4
7
  import { StateRail } from "@amalgm/core/state";
5
8
  import { Notifications } from "@amalgm/tools";
6
9
  import type { EmailDelivery, Toolbox } from "@amalgm/tools";
7
10
  import type { PtySessionManager } from "@amalgm/core/pty";
8
11
  import { WorkspaceRegistry } from "@amalgm/live/workspace";
9
12
  import type { AutomationsHostConfig } from "./automations-host.js";
10
- import { ChatHost } from "./chat-host.js";
11
- import type { ChatDriver } from "./chat-host.js";
12
- import type { ChatRelayHostConfig } from "./chat-relay-host.js";
13
+ import type { ChatHost } from "./chat-host.js";
13
14
  import type { ShellConfig } from "./compose.js";
14
15
  import type { AutomationEventsHostConfig } from "./events-host.js";
15
16
  import type { BrickMount } from "./gateway.js";
16
17
  export interface BootConfig extends ShellConfig {
17
- /** Turn execution capability; when present the chat service mounts. */
18
- chatDriver?: ChatDriver;
19
- /** Chat relay contract/network ports; destinations remain contract-bound. */
20
- chatRelay?: Partial<Omit<ChatRelayHostConfig, "turnForSession">>;
18
+ /** Optional runtime injection for tests or non-native hosts. */
19
+ chatRuntime?: ChatRuntimePort<any, any>;
20
+ /** Live platform grant capability. Required only for platform-auth Chat. */
21
+ credentialBroker?: PlatformCredentialBroker;
21
22
  /** Automations storage capability; when present /automations mounts. */
22
23
  automations?: AutomationsHostConfig;
23
24
  /** Automations delivery capability; when present /events mounts. */
@@ -26,16 +27,24 @@ export interface BootConfig extends ShellConfig {
26
27
  notifications?: {
27
28
  email?: EmailDelivery;
28
29
  };
30
+ /** Optional host capabilities for Shell's one Browser service. */
31
+ browser?: {
32
+ electronDriver?: BrowserDriver;
33
+ runtimeSelector?: BrowserRuntimeSelector;
34
+ eventSink?: BrowserEventSink;
35
+ authorization?: BrowserAuthorization;
36
+ };
29
37
  }
30
38
  export interface BootedShell {
31
39
  readonly gateway: Server;
32
40
  readonly stateDirs: Record<string, string>;
33
41
  readonly mounts: readonly BrickMount[];
34
- readonly chat: ChatHost | null;
42
+ readonly chat: ChatHost;
35
43
  readonly agents: Agents;
36
44
  readonly apps: Apps;
37
45
  readonly toolbox: Toolbox;
38
46
  readonly notifications: Notifications;
47
+ readonly browser: BrowserService;
39
48
  readonly state: StateRail;
40
49
  readonly workspace: WorkspaceRegistry;
41
50
  readonly pty: PtySessionManager;
@@ -44,10 +53,9 @@ export interface BootedShell {
44
53
  }
45
54
  /**
46
55
  * Boot the shell: identity law hands every brick its state dir, each
47
- * brick's surface mounts on its engine prefix, and kernel law guards
56
+ * brick's surface mounts on its Shell prefix, and kernel law guards
48
57
  * the door. Bricks that ship their own HTTP server (agents, browser)
49
- * run on loopback and are forwarded to — the engine's own
50
- * prefix-to-service-port architecture, applied in-process.
58
+ * run on loopback and are forwarded through the same gateway.
51
59
  * Composition only — no brick behavior is defined here.
52
60
  */
53
61
  export declare function bootShell(config?: BootConfig): Promise<BootedShell>;