@parlor/agent-host 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +16 -2
- package/dist/acp.d.ts +165 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +626 -0
- package/dist/acp.js.map +1 -0
- package/dist/adapter-launch.d.ts +7 -0
- package/dist/adapter-launch.d.ts.map +1 -0
- package/dist/adapter-launch.js +35 -0
- package/dist/adapter-launch.js.map +1 -0
- package/dist/agents.d.ts +32 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +132 -0
- package/dist/agents.js.map +1 -0
- package/dist/assertion.d.ts +9 -0
- package/dist/assertion.d.ts.map +1 -0
- package/dist/assertion.js +22 -0
- package/dist/assertion.js.map +1 -0
- package/dist/control.d.ts +106 -0
- package/dist/control.d.ts.map +1 -0
- package/dist/control.js +32 -0
- package/dist/control.js.map +1 -0
- package/dist/daemon.d.ts +50 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +543 -0
- package/dist/daemon.js.map +1 -0
- package/dist/dev-host.d.ts +5 -0
- package/dist/dev-host.d.ts.map +1 -0
- package/dist/dev-host.js +29 -0
- package/dist/dev-host.js.map +1 -0
- package/dist/device-list.d.ts +6 -0
- package/dist/device-list.d.ts.map +1 -0
- package/dist/device-list.js +30 -0
- package/dist/device-list.js.map +1 -0
- package/dist/embedded.d.ts +6 -0
- package/dist/embedded.d.ts.map +1 -0
- package/dist/embedded.js +3 -0
- package/dist/embedded.js.map +1 -0
- package/dist/fold.d.ts +38 -0
- package/dist/fold.d.ts.map +1 -0
- package/dist/fold.js +165 -0
- package/dist/fold.js.map +1 -0
- package/dist/grants.d.ts +54 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +157 -0
- package/dist/grants.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/json-file.d.ts +6 -0
- package/dist/json-file.d.ts.map +1 -0
- package/dist/json-file.js +35 -0
- package/dist/json-file.js.map +1 -0
- package/dist/lines.d.ts +5 -0
- package/dist/lines.d.ts.map +1 -0
- package/dist/lines.js +9 -0
- package/dist/lines.js.map +1 -0
- package/dist/link-device.d.ts +28 -0
- package/dist/link-device.d.ts.map +1 -0
- package/dist/link-device.js +40 -0
- package/dist/link-device.js.map +1 -0
- package/dist/link.d.ts +52 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +277 -0
- package/dist/link.js.map +1 -0
- package/dist/lock.d.ts +7 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +105 -0
- package/dist/lock.js.map +1 -0
- package/dist/log.d.ts +3 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +18 -0
- package/dist/log.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +520 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp-proxy.d.ts +33 -0
- package/dist/mcp-proxy.d.ts.map +1 -0
- package/dist/mcp-proxy.js +317 -0
- package/dist/mcp-proxy.js.map +1 -0
- package/dist/naming.d.ts +7 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +42 -0
- package/dist/naming.js.map +1 -0
- package/dist/pairing.d.ts +68 -0
- package/dist/pairing.d.ts.map +1 -0
- package/dist/pairing.js +312 -0
- package/dist/pairing.js.map +1 -0
- package/dist/paths.d.ts +19 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +23 -0
- package/dist/paths.js.map +1 -0
- package/dist/policy.d.ts +37 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +60 -0
- package/dist/policy.js.map +1 -0
- package/dist/pool.d.ts +34 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +112 -0
- package/dist/pool.js.map +1 -0
- package/dist/prompter.d.ts +33 -0
- package/dist/prompter.d.ts.map +1 -0
- package/dist/prompter.js +98 -0
- package/dist/prompter.js.map +1 -0
- package/dist/release-main.d.ts +2 -0
- package/dist/release-main.d.ts.map +1 -0
- package/dist/release-main.js +50 -0
- package/dist/release-main.js.map +1 -0
- package/dist/release.d.ts +22 -0
- package/dist/release.d.ts.map +1 -0
- package/dist/release.js +81 -0
- package/dist/release.js.map +1 -0
- package/dist/revoke.d.ts +10 -0
- package/dist/revoke.d.ts.map +1 -0
- package/dist/revoke.js +23 -0
- package/dist/revoke.js.map +1 -0
- package/dist/self.d.ts +7 -0
- package/dist/self.d.ts.map +1 -0
- package/dist/self.js +10 -0
- package/dist/self.js.map +1 -0
- package/dist/service.d.ts +97 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +205 -0
- package/dist/service.js.map +1 -0
- package/dist/shell-path.d.ts +10 -0
- package/dist/shell-path.d.ts.map +1 -0
- package/dist/shell-path.js +64 -0
- package/dist/shell-path.js.map +1 -0
- package/dist/skeleton-kit.d.ts +8 -0
- package/dist/skeleton-kit.d.ts.map +1 -0
- package/dist/skeleton-kit.js +143 -0
- package/dist/skeleton-kit.js.map +1 -0
- package/dist/spec-kit.d.ts +102 -0
- package/dist/spec-kit.d.ts.map +1 -0
- package/dist/spec-kit.js +372 -0
- package/dist/spec-kit.js.map +1 -0
- package/dist/testing/fake-agent.d.ts +65 -0
- package/dist/testing/fake-agent.d.ts.map +1 -0
- package/dist/testing/fake-agent.js +238 -0
- package/dist/testing/fake-agent.js.map +1 -0
- package/dist/trust.d.ts +20 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +27 -0
- package/dist/trust.js.map +1 -0
- package/dist/turn.d.ts +32 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +650 -0
- package/dist/turn.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +57 -3
package/dist/grants.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { hostAssertion } from "./assertion.js";
|
|
5
|
+
import { jsonFile } from "./json-file.js";
|
|
6
|
+
/** `~/.parlor/host/grants.json`, 0600, written atomically. */
|
|
7
|
+
export function fileGrantStore(file) {
|
|
8
|
+
const grants = jsonFile(file);
|
|
9
|
+
return {
|
|
10
|
+
list: async () => Object.values(await grants.read()),
|
|
11
|
+
get: async (appOrigin) => (await grants.read())[appOrigin],
|
|
12
|
+
save: (grant) => grants.change((all) => {
|
|
13
|
+
all[grant.appOrigin] = grant;
|
|
14
|
+
}),
|
|
15
|
+
remove: (appOrigin) => grants.change((all) => {
|
|
16
|
+
delete all[appOrigin];
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const EC_P256 = { name: "ECDSA", namedCurve: "P-256" };
|
|
21
|
+
const ES256 = { name: "ECDSA", hash: "SHA-256" };
|
|
22
|
+
const ORIGIN = /^https?:\/\/[A-Za-z0-9.\-[\]:]+$/;
|
|
23
|
+
function thumbprintOf({ crv, kty, x, y }) {
|
|
24
|
+
return createHash("sha256")
|
|
25
|
+
.update(JSON.stringify({ crv, kty, x, y }))
|
|
26
|
+
.digest("base64url");
|
|
27
|
+
}
|
|
28
|
+
const signer = (key) => async (payload) => Buffer.from(await crypto.subtle.sign(ES256, key, new Uint8Array(payload))).toString("base64url");
|
|
29
|
+
function keysIn(secrets) {
|
|
30
|
+
const loaded = new Map();
|
|
31
|
+
const load = (appOrigin) => {
|
|
32
|
+
let entry = loaded.get(appOrigin);
|
|
33
|
+
if (!entry) {
|
|
34
|
+
entry = (async () => {
|
|
35
|
+
const secret = await secrets.read(appOrigin);
|
|
36
|
+
if (secret === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
const stored = JSON.parse(Buffer.from(secret, "base64url").toString("utf8"));
|
|
39
|
+
const key = await crypto.subtle.importKey("jwk", stored.jwk, EC_P256, false, [
|
|
40
|
+
"sign",
|
|
41
|
+
]);
|
|
42
|
+
return { key, kid: stored.kid, grantId: stored.grantId };
|
|
43
|
+
})();
|
|
44
|
+
entry.catch(() => loaded.delete(appOrigin));
|
|
45
|
+
loaded.set(appOrigin, entry);
|
|
46
|
+
}
|
|
47
|
+
return entry;
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
async create(appOrigin) {
|
|
51
|
+
const pair = await crypto.subtle.generateKey(EC_P256, true, ["sign", "verify"]);
|
|
52
|
+
const exported = await crypto.subtle.exportKey("jwk", pair.publicKey);
|
|
53
|
+
const publicKey = {
|
|
54
|
+
kty: exported.kty,
|
|
55
|
+
crv: exported.crv,
|
|
56
|
+
x: exported.x,
|
|
57
|
+
y: exported.y,
|
|
58
|
+
};
|
|
59
|
+
const thumbprint = thumbprintOf(publicKey);
|
|
60
|
+
return {
|
|
61
|
+
publicKey,
|
|
62
|
+
thumbprint,
|
|
63
|
+
sign: signer(pair.privateKey),
|
|
64
|
+
keep: async (grantId) => {
|
|
65
|
+
const jwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
|
|
66
|
+
const stored = { jwk, kid: thumbprint, grantId };
|
|
67
|
+
await secrets.write(appOrigin, Buffer.from(JSON.stringify(stored)).toString("base64url"));
|
|
68
|
+
loaded.delete(appOrigin);
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
async assert(appOrigin, request) {
|
|
73
|
+
const key = await load(appOrigin);
|
|
74
|
+
if (!key)
|
|
75
|
+
throw new Error(`This computer holds no key for ${appOrigin}.`);
|
|
76
|
+
return hostAssertion(signer(key.key), key, request);
|
|
77
|
+
},
|
|
78
|
+
async remove(appOrigin) {
|
|
79
|
+
loaded.delete(appOrigin);
|
|
80
|
+
await secrets.remove(appOrigin);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const run = promisify(execFile);
|
|
85
|
+
const NOT_FOUND = 44;
|
|
86
|
+
function macKeychain(service) {
|
|
87
|
+
const checked = (account) => {
|
|
88
|
+
if (!ORIGIN.test(account))
|
|
89
|
+
throw new Error(`${account} is not an app origin.`);
|
|
90
|
+
return account;
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
async read(account) {
|
|
94
|
+
try {
|
|
95
|
+
const { stdout } = await run("security", [
|
|
96
|
+
"find-generic-password",
|
|
97
|
+
"-s",
|
|
98
|
+
service,
|
|
99
|
+
"-a",
|
|
100
|
+
checked(account),
|
|
101
|
+
"-w",
|
|
102
|
+
]);
|
|
103
|
+
return stdout.trim();
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (error.code === NOT_FOUND)
|
|
107
|
+
return undefined;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
async write(account, secret) {
|
|
112
|
+
// Through `security -i` on stdin, so the private key never shows in a process list.
|
|
113
|
+
const command = `add-generic-password -U -s ${service} -a ${checked(account)} -w ${secret}\n`;
|
|
114
|
+
await new Promise((resolve, reject) => {
|
|
115
|
+
const child = spawn("security", ["-i"], { stdio: ["pipe", "ignore", "pipe"] });
|
|
116
|
+
let stderr = "";
|
|
117
|
+
child.stderr.on("data", (chunk) => (stderr += chunk.toString()));
|
|
118
|
+
child.once("error", reject);
|
|
119
|
+
child.once("exit", (code) => code === 0 && stderr.trim() === ""
|
|
120
|
+
? resolve()
|
|
121
|
+
: reject(new Error(`The Keychain refused the key: ${stderr.trim() || code}`)));
|
|
122
|
+
child.stdin.end(command);
|
|
123
|
+
});
|
|
124
|
+
if ((await this.read(account)) !== secret) {
|
|
125
|
+
throw new Error("The Keychain did not keep the key.");
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
async remove(account) {
|
|
129
|
+
try {
|
|
130
|
+
await run("security", [
|
|
131
|
+
"delete-generic-password",
|
|
132
|
+
"-s",
|
|
133
|
+
service,
|
|
134
|
+
"-a",
|
|
135
|
+
checked(account),
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (error.code !== NOT_FOUND)
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** The macOS Keychain; other platforms are refused before pairing (`unsupportedHostPlatform`). */
|
|
146
|
+
export function keychainHostKeys(service) {
|
|
147
|
+
return keysIn(macKeychain(service));
|
|
148
|
+
}
|
|
149
|
+
/** For `parlor-host dev`: one dev host token for every request, nothing on disk. */
|
|
150
|
+
export function devHostKeys(hostToken) {
|
|
151
|
+
return {
|
|
152
|
+
create: () => Promise.reject(new Error("A parlor dev server needs no pairing.")),
|
|
153
|
+
assert: async () => hostToken,
|
|
154
|
+
remove: async () => undefined,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=grants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.js","sourceRoot":"","sources":["../src/grants.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { promisify } from \"node:util\";\n\nimport type { AccountSummary } from \"@parlor/auth\";\nimport type { GrantId, HostId } from \"@parlor/core\";\n\nimport { hostAssertion } from \"./assertion.js\";\nimport { jsonFile } from \"./json-file.js\";\n\n/** One app this computer is connected to. Its key lives in `HostKeys`, never here. */\nexport interface AppGrant {\n readonly appOrigin: string;\n readonly appName: string;\n readonly grantId: GrantId;\n readonly hostId: HostId;\n readonly account: AccountSummary;\n /** The agents' working directory for this app; only the user changes it, here. */\n readonly workspace: string;\n readonly createdAt: number;\n /** A `parlor dev` grant: in memory only. */\n readonly dev?: true | undefined;\n /** Refused here but not yet revoked by the app: kept only to retry the revoke, never served. */\n readonly declined?: true | undefined;\n /**\n * Handed only to the browser that approved `connect`, through the loopback redirect; that\n * browser proves it on its first prompt and is trusted without asking, then this is dropped.\n */\n readonly pairingSecret?: string | undefined;\n}\n\n/** One key per app grant in the OS keychain; the server holds only its public half. */\nexport interface HostKeys {\n /** A pairing's new key pair, kept only once the pairing completes. */\n create(appOrigin: string): Promise<HostKeyPair>;\n /** A fresh `ParlorHost` assertion for one request. */\n assert(\n appOrigin: string,\n request: { readonly method: \"GET\" | \"POST\"; readonly url: string },\n ): Promise<string>;\n remove(appOrigin: string): Promise<void>;\n}\n\nexport interface HostKeyPair {\n readonly publicKey: JsonWebKey;\n /** RFC 7638, the assertion's `kid`. */\n readonly thumbprint: string;\n sign(payload: Uint8Array): Promise<string>;\n /** Binds the key to the grant the pairing produced and stores it. */\n keep(grantId: GrantId): Promise<void>;\n}\n\nexport interface GrantStore {\n list(): Promise<readonly AppGrant[]>;\n get(appOrigin: string): Promise<AppGrant | undefined>;\n save(grant: AppGrant): Promise<void>;\n remove(appOrigin: string): Promise<void>;\n}\n\n/** `~/.parlor/host/grants.json`, 0600, written atomically. */\nexport function fileGrantStore(file: string): GrantStore {\n const grants = jsonFile<Record<string, AppGrant>>(file);\n return {\n list: async () => Object.values(await grants.read()),\n get: async (appOrigin) => (await grants.read())[appOrigin],\n save: (grant) =>\n grants.change((all) => {\n all[grant.appOrigin] = grant;\n }),\n remove: (appOrigin) =>\n grants.change((all) => {\n delete all[appOrigin];\n }),\n };\n}\n\nconst EC_P256 = { name: \"ECDSA\", namedCurve: \"P-256\" } as const;\nconst ES256 = { name: \"ECDSA\", hash: \"SHA-256\" } as const;\nconst ORIGIN = /^https?:\\/\\/[A-Za-z0-9.\\-[\\]:]+$/;\n\ninterface StoredKey {\n readonly jwk: JsonWebKey;\n readonly kid: string;\n readonly grantId: string;\n}\n\ninterface LoadedKey {\n readonly key: CryptoKey;\n readonly kid: string;\n readonly grantId: string;\n}\n\ninterface SecretStore {\n read(account: string): Promise<string | undefined>;\n write(account: string, secret: string): Promise<void>;\n remove(account: string): Promise<void>;\n}\n\nfunction thumbprintOf({ crv, kty, x, y }: JsonWebKey): string {\n return createHash(\"sha256\")\n .update(JSON.stringify({ crv, kty, x, y }))\n .digest(\"base64url\");\n}\n\nconst signer = (key: CryptoKey) => async (payload: Uint8Array) =>\n Buffer.from(await crypto.subtle.sign(ES256, key, new Uint8Array(payload))).toString(\n \"base64url\",\n );\n\nfunction keysIn(secrets: SecretStore): HostKeys {\n const loaded = new Map<string, Promise<LoadedKey | undefined>>();\n const load = (appOrigin: string) => {\n let entry = loaded.get(appOrigin);\n if (!entry) {\n entry = (async () => {\n const secret = await secrets.read(appOrigin);\n if (secret === undefined) return undefined;\n const stored = JSON.parse(\n Buffer.from(secret, \"base64url\").toString(\"utf8\"),\n ) as StoredKey;\n const key = await crypto.subtle.importKey(\"jwk\", stored.jwk, EC_P256, false, [\n \"sign\",\n ]);\n return { key, kid: stored.kid, grantId: stored.grantId };\n })();\n entry.catch(() => loaded.delete(appOrigin));\n loaded.set(appOrigin, entry);\n }\n return entry;\n };\n return {\n async create(appOrigin) {\n const pair = await crypto.subtle.generateKey(EC_P256, true, [\"sign\", \"verify\"]);\n const exported = await crypto.subtle.exportKey(\"jwk\", pair.publicKey);\n const publicKey: JsonWebKey = {\n kty: exported.kty!,\n crv: exported.crv!,\n x: exported.x!,\n y: exported.y!,\n };\n const thumbprint = thumbprintOf(publicKey);\n return {\n publicKey,\n thumbprint,\n sign: signer(pair.privateKey),\n keep: async (grantId) => {\n const jwk = await crypto.subtle.exportKey(\"jwk\", pair.privateKey);\n const stored: StoredKey = { jwk, kid: thumbprint, grantId };\n await secrets.write(\n appOrigin,\n Buffer.from(JSON.stringify(stored)).toString(\"base64url\"),\n );\n loaded.delete(appOrigin);\n },\n };\n },\n async assert(appOrigin, request) {\n const key = await load(appOrigin);\n if (!key) throw new Error(`This computer holds no key for ${appOrigin}.`);\n return hostAssertion(signer(key.key), key, request);\n },\n async remove(appOrigin) {\n loaded.delete(appOrigin);\n await secrets.remove(appOrigin);\n },\n };\n}\n\nconst run = promisify(execFile);\nconst NOT_FOUND = 44;\n\nfunction macKeychain(service: string): SecretStore {\n const checked = (account: string) => {\n if (!ORIGIN.test(account)) throw new Error(`${account} is not an app origin.`);\n return account;\n };\n return {\n async read(account) {\n try {\n const { stdout } = await run(\"security\", [\n \"find-generic-password\",\n \"-s\",\n service,\n \"-a\",\n checked(account),\n \"-w\",\n ]);\n return stdout.trim();\n } catch (error) {\n if ((error as { code?: unknown }).code === NOT_FOUND) return undefined;\n throw error;\n }\n },\n async write(account, secret) {\n // Through `security -i` on stdin, so the private key never shows in a process list.\n const command = `add-generic-password -U -s ${service} -a ${checked(account)} -w ${secret}\\n`;\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"security\", [\"-i\"], { stdio: [\"pipe\", \"ignore\", \"pipe\"] });\n let stderr = \"\";\n child.stderr.on(\"data\", (chunk: Buffer) => (stderr += chunk.toString()));\n child.once(\"error\", reject);\n child.once(\"exit\", (code) =>\n code === 0 && stderr.trim() === \"\"\n ? resolve()\n : reject(new Error(`The Keychain refused the key: ${stderr.trim() || code}`)),\n );\n child.stdin.end(command);\n });\n if ((await this.read(account)) !== secret) {\n throw new Error(\"The Keychain did not keep the key.\");\n }\n },\n async remove(account) {\n try {\n await run(\"security\", [\n \"delete-generic-password\",\n \"-s\",\n service,\n \"-a\",\n checked(account),\n ]);\n } catch (error) {\n if ((error as { code?: unknown }).code !== NOT_FOUND) throw error;\n }\n },\n };\n}\n\n/** The macOS Keychain; other platforms are refused before pairing (`unsupportedHostPlatform`). */\nexport function keychainHostKeys(service: string): HostKeys {\n return keysIn(macKeychain(service));\n}\n\n/** For `parlor-host dev`: one dev host token for every request, nothing on disk. */\nexport function devHostKeys(hostToken: string): HostKeys {\n return {\n create: () => Promise.reject(new Error(\"A parlor dev server needs no pairing.\")),\n assert: async () => hostToken,\n remove: async () => undefined,\n };\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { type AcpAgentHandlers, type AcpStdio, acpLauncher, type AgentAsk, type AgentAskAnswer, type AgentCapabilities, type AgentConnection, type AgentExit, type AgentLauncher, type AgentModes, type AgentSession, type AgentUpdate, type AskEffect, type LaunchOptions, type McpToolRef, type OpenSessionInput, type PromptBlock, serveAcpAgent, SessionRefused, type StdioMcpServer, type StopReason, type ToolKind, type TurnHandlers, } from "./acp.js";
|
|
2
|
+
export { AGENT_PROFILES, type AgentLaunch, type AgentProfile, DETECT_TIMEOUT_MS, detectAgents, loadProfiles, } from "./agents.js";
|
|
3
|
+
export { connectControl, type ControlClient, type ControlFailure, type ControlOp, CONTROL_PROTOCOL, type ControlRequest, type ControlResponse, } from "./control.js";
|
|
4
|
+
export { createHostDaemon, type DaemonDeps, type HostDaemon } from "./daemon.js";
|
|
5
|
+
export { finishFold, type FoldStep, foldUpdate, startFold, type TurnFold, } from "./fold.js";
|
|
6
|
+
export { type AppGrant, devHostKeys, fileGrantStore, type GrantStore, type HostKeyPair, type HostKeys, keychainHostKeys, } from "./grants.js";
|
|
7
|
+
export { ACTIVE_POLL_MS, type AppLink, type AppLinkDeps, createAppLink, type HostApi, IDLE_POLL_MS, RECONNECT_MAX_MS, } from "./link.js";
|
|
8
|
+
export { createMcpProxy, type McpProxy, NO_TURN_MESSAGE, type ProxySession, runMcpProxyProcess, } from "./mcp-proxy.js";
|
|
9
|
+
export { pair, PAIRING_TIMEOUT_MS, type PairingChannel, type PairInput, type PairOutcome, } from "./pairing.js";
|
|
10
|
+
export { type HostPaths, hostPaths } from "./paths.js";
|
|
11
|
+
export { approvalFor, changePolicy, DEFAULT_POLICY, DEV_POLICY, isAppTool, loadPolicy, type RemotePolicy, } from "./policy.js";
|
|
12
|
+
export { createSessionPool, type LiveSession, type PooledGrant, type SessionPool, } from "./pool.js";
|
|
13
|
+
export { type LocalPrompter, macDialogPrompter, terminalPrompter } from "./prompter.js";
|
|
14
|
+
export { type AdoptOutcome, ensureService, type HostRelease, installLoginItem, type ServiceClient, } from "./service.js";
|
|
15
|
+
export { fileTrustStore, type TrustedDevice, type TrustStore } from "./trust.js";
|
|
16
|
+
export { PROMPT_MAX_AGE_MS, runTurn, type TurnDeps } from "./turn.js";
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,QAAQ,EACb,WAAW,EACX,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,aAAa,EACb,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,UAAU,EACV,KAAK,QAAQ,EACb,UAAU,EACV,SAAS,EACT,KAAK,QAAQ,GACd,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,QAAQ,EACb,WAAW,EACX,cAAc,EACd,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EACd,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,aAAa,EACb,KAAK,OAAO,EACZ,YAAY,EACZ,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,KAAK,QAAQ,EACb,eAAe,EACf,KAAK,YAAY,EACjB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,IAAI,EACJ,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,UAAU,EACV,SAAS,EACT,UAAU,EACV,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACxF,OAAO,EACL,KAAK,YAAY,EACjB,aAAa,EACb,KAAK,WAAW,EAChB,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { acpLauncher, serveAcpAgent, SessionRefused, } from "./acp.js";
|
|
2
|
+
export { AGENT_PROFILES, DETECT_TIMEOUT_MS, detectAgents, loadProfiles, } from "./agents.js";
|
|
3
|
+
export { connectControl, CONTROL_PROTOCOL, } from "./control.js";
|
|
4
|
+
export { createHostDaemon } from "./daemon.js";
|
|
5
|
+
export { finishFold, foldUpdate, startFold, } from "./fold.js";
|
|
6
|
+
export { devHostKeys, fileGrantStore, keychainHostKeys, } from "./grants.js";
|
|
7
|
+
export { ACTIVE_POLL_MS, createAppLink, IDLE_POLL_MS, RECONNECT_MAX_MS, } from "./link.js";
|
|
8
|
+
export { createMcpProxy, NO_TURN_MESSAGE, runMcpProxyProcess, } from "./mcp-proxy.js";
|
|
9
|
+
export { pair, PAIRING_TIMEOUT_MS, } from "./pairing.js";
|
|
10
|
+
export { hostPaths } from "./paths.js";
|
|
11
|
+
export { approvalFor, changePolicy, DEFAULT_POLICY, DEV_POLICY, isAppTool, loadPolicy, } from "./policy.js";
|
|
12
|
+
export { createSessionPool, } from "./pool.js";
|
|
13
|
+
export { macDialogPrompter, terminalPrompter } from "./prompter.js";
|
|
14
|
+
export { ensureService, installLoginItem, } from "./service.js";
|
|
15
|
+
export { fileTrustStore } from "./trust.js";
|
|
16
|
+
export { PROMPT_MAX_AGE_MS, runTurn } from "./turn.js";
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,WAAW,EAeX,aAAa,EACb,cAAc,GAKf,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,cAAc,EAGd,iBAAiB,EACjB,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EAId,gBAAgB,GAGjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAoC,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,UAAU,EAEV,UAAU,EACV,SAAS,GAEV,MAAM,WAAW,CAAC;AACnB,OAAO,EAEL,WAAW,EACX,cAAc,EAId,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,EAGd,aAAa,EAEb,YAAY,EACZ,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EAEd,eAAe,EAEf,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,IAAI,EACJ,kBAAkB,GAInB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAkB,SAAS,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,UAAU,EACV,SAAS,EACT,UAAU,GAEX,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,GAIlB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAsB,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACxF,OAAO,EAEL,aAAa,EAEb,gBAAgB,GAEjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAuC,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAiB,MAAM,WAAW,CAAC","sourcesContent":["export {\n type AcpAgentHandlers,\n type AcpStdio,\n acpLauncher,\n type AgentAsk,\n type AgentAskAnswer,\n type AgentCapabilities,\n type AgentConnection,\n type AgentExit,\n type AgentLauncher,\n type AgentModes,\n type AgentSession,\n type AgentUpdate,\n type AskEffect,\n type LaunchOptions,\n type McpToolRef,\n type OpenSessionInput,\n type PromptBlock,\n serveAcpAgent,\n SessionRefused,\n type StdioMcpServer,\n type StopReason,\n type ToolKind,\n type TurnHandlers,\n} from \"./acp.js\";\nexport {\n AGENT_PROFILES,\n type AgentLaunch,\n type AgentProfile,\n DETECT_TIMEOUT_MS,\n detectAgents,\n loadProfiles,\n} from \"./agents.js\";\nexport {\n connectControl,\n type ControlClient,\n type ControlFailure,\n type ControlOp,\n CONTROL_PROTOCOL,\n type ControlRequest,\n type ControlResponse,\n} from \"./control.js\";\nexport { createHostDaemon, type DaemonDeps, type HostDaemon } from \"./daemon.js\";\nexport {\n finishFold,\n type FoldStep,\n foldUpdate,\n startFold,\n type TurnFold,\n} from \"./fold.js\";\nexport {\n type AppGrant,\n devHostKeys,\n fileGrantStore,\n type GrantStore,\n type HostKeyPair,\n type HostKeys,\n keychainHostKeys,\n} from \"./grants.js\";\nexport {\n ACTIVE_POLL_MS,\n type AppLink,\n type AppLinkDeps,\n createAppLink,\n type HostApi,\n IDLE_POLL_MS,\n RECONNECT_MAX_MS,\n} from \"./link.js\";\nexport {\n createMcpProxy,\n type McpProxy,\n NO_TURN_MESSAGE,\n type ProxySession,\n runMcpProxyProcess,\n} from \"./mcp-proxy.js\";\nexport {\n pair,\n PAIRING_TIMEOUT_MS,\n type PairingChannel,\n type PairInput,\n type PairOutcome,\n} from \"./pairing.js\";\nexport { type HostPaths, hostPaths } from \"./paths.js\";\nexport {\n approvalFor,\n changePolicy,\n DEFAULT_POLICY,\n DEV_POLICY,\n isAppTool,\n loadPolicy,\n type RemotePolicy,\n} from \"./policy.js\";\nexport {\n createSessionPool,\n type LiveSession,\n type PooledGrant,\n type SessionPool,\n} from \"./pool.js\";\nexport { type LocalPrompter, macDialogPrompter, terminalPrompter } from \"./prompter.js\";\nexport {\n type AdoptOutcome,\n ensureService,\n type HostRelease,\n installLoginItem,\n type ServiceClient,\n} from \"./service.js\";\nexport { fileTrustStore, type TrustedDevice, type TrustStore } from \"./trust.js\";\nexport { PROMPT_MAX_AGE_MS, runTurn, type TurnDeps } from \"./turn.js\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-file.d.ts","sourceRoot":"","sources":["../src/json-file.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,MAAM;IACxC,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IACnB,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CA4BpE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { withFileLock } from "./lock.js";
|
|
4
|
+
export function jsonFile(file) {
|
|
5
|
+
let writing = Promise.resolve();
|
|
6
|
+
const read = async () => {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(await readFile(file, "utf8"));
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code === "ENOENT")
|
|
12
|
+
return {};
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
read,
|
|
18
|
+
change(edit) {
|
|
19
|
+
const next = writing.then(async () => {
|
|
20
|
+
await mkdir(dirname(file), { recursive: true, mode: 0o700 });
|
|
21
|
+
await withFileLock(`${file}.lock`, async () => {
|
|
22
|
+
const value = await read();
|
|
23
|
+
edit(value);
|
|
24
|
+
const staged = `${file}.${process.pid}.tmp`;
|
|
25
|
+
await writeFile(staged, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
26
|
+
await chmod(staged, 0o600);
|
|
27
|
+
await rename(staged, file);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
writing = next.catch(() => undefined);
|
|
31
|
+
return next;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=json-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-file.js","sourceRoot":"","sources":["../src/json-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAOzC,MAAM,UAAU,QAAQ,CAAmB,IAAY;IACrD,IAAI,OAAO,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAClD,MAAM,IAAI,GAAG,KAAK,IAAgB,EAAE;QAClC,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,EAAO,CAAC;YACvE,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IACF,OAAO;QACL,IAAI;QACJ,MAAM,CAAC,IAAI;YACT,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;gBACnC,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC7D,MAAM,YAAY,CAAC,GAAG,IAAI,OAAO,EAAE,KAAK,IAAI,EAAE;oBAC5C,MAAM,KAAK,GAAG,MAAM,IAAI,EAAE,CAAC;oBAC3B,IAAI,CAAC,KAAK,CAAC,CAAC;oBACZ,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;oBAC5C,MAAM,SAAS,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;oBAChF,MAAM,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;oBAC3B,MAAM,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAC7B,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { chmod, mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport { withFileLock } from \"./lock.js\";\n\nexport interface JsonFile<T extends object> {\n read(): Promise<T>;\n change(edit: (value: T) => void): Promise<void>;\n}\n\nexport function jsonFile<T extends object>(file: string): JsonFile<T> {\n let writing: Promise<unknown> = Promise.resolve();\n const read = async (): Promise<T> => {\n try {\n return JSON.parse(await readFile(file, \"utf8\")) as T;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {} as T;\n throw error;\n }\n };\n return {\n read,\n change(edit) {\n const next = writing.then(async () => {\n await mkdir(dirname(file), { recursive: true, mode: 0o700 });\n await withFileLock(`${file}.lock`, async () => {\n const value = await read();\n edit(value);\n const staged = `${file}.${process.pid}.tmp`;\n await writeFile(staged, `${JSON.stringify(value, null, 2)}\\n`, { mode: 0o600 });\n await chmod(staged, 0o600);\n await rename(staged, file);\n });\n });\n writing = next.catch(() => undefined);\n return next;\n },\n };\n}\n"]}
|
package/dist/lines.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Interface } from "node:readline";
|
|
2
|
+
import type { Readable } from "node:stream";
|
|
3
|
+
/** Newline-delimited input; a broken stream ends it instead of throwing from readline. */
|
|
4
|
+
export declare function onLines(input: Readable, handle: (line: string) => void): Interface;
|
|
5
|
+
//# sourceMappingURL=lines.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lines.d.ts","sourceRoot":"","sources":["../src/lines.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,0FAA0F;AAC1F,wBAAgB,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,SAAS,CAKlF"}
|
package/dist/lines.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
/** Newline-delimited input; a broken stream ends it instead of throwing from readline. */
|
|
3
|
+
export function onLines(input, handle) {
|
|
4
|
+
const lines = createInterface({ input, crlfDelay: Infinity });
|
|
5
|
+
lines.on("line", handle);
|
|
6
|
+
lines.on("error", () => input.destroy());
|
|
7
|
+
return lines;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=lines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lines.js","sourceRoot":"","sources":["../src/lines.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkB,MAAM,eAAe,CAAC;AAGhE,0FAA0F;AAC1F,MAAM,UAAU,OAAO,CAAC,KAAe,EAAE,MAA8B;IACrE,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9D,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACzC,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import { createInterface, type Interface } from \"node:readline\";\nimport type { Readable } from \"node:stream\";\n\n/** Newline-delimited input; a broken stream ends it instead of throwing from readline. */\nexport function onLines(input: Readable, handle: (line: string) => void): Interface {\n const lines = createInterface({ input, crlfDelay: Infinity });\n lines.on(\"line\", handle);\n lines.on(\"error\", () => input.destroy());\n return lines;\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AssertionRequest } from "./assertion.js";
|
|
2
|
+
import type { TrustedDevice, TrustStore } from "./trust.js";
|
|
3
|
+
export declare const LINK_PATH = "/_parlor/auth/hosts/devices/link";
|
|
4
|
+
export interface LinkDeviceInput {
|
|
5
|
+
readonly appOrigin: string;
|
|
6
|
+
readonly code: string;
|
|
7
|
+
readonly assert: (request: AssertionRequest) => Promise<string>;
|
|
8
|
+
readonly trust: TrustStore;
|
|
9
|
+
/** Asked on this computer once the code checked out; only true links. */
|
|
10
|
+
readonly confirm: (device: {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly platform: string;
|
|
13
|
+
}) => Promise<boolean>;
|
|
14
|
+
readonly now?: (() => number) | undefined;
|
|
15
|
+
readonly fetch?: typeof fetch | undefined;
|
|
16
|
+
}
|
|
17
|
+
export type LinkOutcome = {
|
|
18
|
+
readonly linked: true;
|
|
19
|
+
readonly device: TrustedDevice;
|
|
20
|
+
} | {
|
|
21
|
+
readonly linked: false;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Links the device showing `code` from this computer: the app finds it, this computer checks
|
|
25
|
+
* its keys hash to that code, so the app cannot slip in other keys, and the user confirms here.
|
|
26
|
+
*/
|
|
27
|
+
export declare function linkDevice(input: LinkDeviceInput): Promise<LinkOutcome>;
|
|
28
|
+
//# sourceMappingURL=link-device.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-device.d.ts","sourceRoot":"","sources":["../src/link-device.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,eAAO,MAAM,SAAS,qCAAqC,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE;QACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAAE,GACzD;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAA;CAAE,CAAC;AAE/B;;;GAGG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAuC7E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { deviceCommitment, linkCode, normalizeLinkCode, } from "@parlor/auth";
|
|
2
|
+
import { printable } from "./prompter.js";
|
|
3
|
+
export const LINK_PATH = "/_parlor/auth/hosts/devices/link";
|
|
4
|
+
/**
|
|
5
|
+
* Links the device showing `code` from this computer: the app finds it, this computer checks
|
|
6
|
+
* its keys hash to that code, so the app cannot slip in other keys, and the user confirms here.
|
|
7
|
+
*/
|
|
8
|
+
export async function linkDevice(input) {
|
|
9
|
+
const origin = new URL(input.appOrigin).origin;
|
|
10
|
+
const url = `${origin}${LINK_PATH}`;
|
|
11
|
+
const authorization = `ParlorHost ${await input.assert({ method: "POST", url })}`;
|
|
12
|
+
const response = await (input.fetch ?? fetch)(url, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: { authorization, "content-type": "application/json" },
|
|
15
|
+
body: JSON.stringify({ code: input.code }),
|
|
16
|
+
});
|
|
17
|
+
const body = (await response.json().catch(() => undefined));
|
|
18
|
+
if (!response.ok || !body?.signingKey) {
|
|
19
|
+
throw new Error(body?.error?.message ?? `The app answered HTTP ${response.status}.`);
|
|
20
|
+
}
|
|
21
|
+
const commitment = await deviceCommitment(origin, body.id, body.signingKey, body.presenceKey?.publicKey);
|
|
22
|
+
if (linkCode(commitment) !== normalizeLinkCode(input.code)) {
|
|
23
|
+
throw new Error("The app returned a device whose keys do not match this code, so nothing was linked. Show a new code on the device and try again.");
|
|
24
|
+
}
|
|
25
|
+
const shown = { name: printable(body.name), platform: printable(body.platform) };
|
|
26
|
+
if (!(await input.confirm(shown)))
|
|
27
|
+
return { linked: false };
|
|
28
|
+
const device = {
|
|
29
|
+
deviceId: body.id,
|
|
30
|
+
signingKey: body.signingKey,
|
|
31
|
+
presenceKey: body.presenceKey,
|
|
32
|
+
name: body.name,
|
|
33
|
+
platform: body.platform,
|
|
34
|
+
approvedAt: (input.now ?? Date.now)(),
|
|
35
|
+
via: "local",
|
|
36
|
+
};
|
|
37
|
+
await input.trust.pin(origin, device);
|
|
38
|
+
return { linked: true, device };
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=link-device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-device.js","sourceRoot":"","sources":["../src/link-device.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,QAAQ,EACR,iBAAiB,GAElB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C,MAAM,CAAC,MAAM,SAAS,GAAG,kCAAkC,CAAC;AAoB5D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAsB;IACrD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;IAC/C,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,SAAS,EAAE,CAAC;IACpC,MAAM,aAAa,GAAG,cAAc,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAClF,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,GAAG,EAAE;QACjD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC9D,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;KAC3C,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAE7C,CAAC;IACd,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,IAAI,yBAAyB,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,gBAAgB,CACvC,MAAM,EACN,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,WAAW,EAAE,SAAS,CAC5B,CAAC;IACF,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,kIAAkI,CACnI,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;IACjF,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC5D,MAAM,MAAM,GAAkB;QAC5B,QAAQ,EAAE,IAAI,CAAC,EAAE;QACjB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE;QACrC,GAAG,EAAE,OAAO;KACb,CAAC;IACF,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAClC,CAAC","sourcesContent":["import {\n deviceCommitment,\n linkCode,\n normalizeLinkCode,\n type PendingDevice,\n} from \"@parlor/auth\";\n\nimport type { AssertionRequest } from \"./assertion.js\";\nimport { printable } from \"./prompter.js\";\nimport type { TrustedDevice, TrustStore } from \"./trust.js\";\n\nexport const LINK_PATH = \"/_parlor/auth/hosts/devices/link\";\n\nexport interface LinkDeviceInput {\n readonly appOrigin: string;\n readonly code: string;\n readonly assert: (request: AssertionRequest) => Promise<string>;\n readonly trust: TrustStore;\n /** Asked on this computer once the code checked out; only true links. */\n readonly confirm: (device: {\n readonly name: string;\n readonly platform: string;\n }) => Promise<boolean>;\n readonly now?: (() => number) | undefined;\n readonly fetch?: typeof fetch | undefined;\n}\n\nexport type LinkOutcome =\n | { readonly linked: true; readonly device: TrustedDevice }\n | { readonly linked: false };\n\n/**\n * Links the device showing `code` from this computer: the app finds it, this computer checks\n * its keys hash to that code, so the app cannot slip in other keys, and the user confirms here.\n */\nexport async function linkDevice(input: LinkDeviceInput): Promise<LinkOutcome> {\n const origin = new URL(input.appOrigin).origin;\n const url = `${origin}${LINK_PATH}`;\n const authorization = `ParlorHost ${await input.assert({ method: \"POST\", url })}`;\n const response = await (input.fetch ?? fetch)(url, {\n method: \"POST\",\n headers: { authorization, \"content-type\": \"application/json\" },\n body: JSON.stringify({ code: input.code }),\n });\n const body = (await response.json().catch(() => undefined)) as\n | (PendingDevice & { readonly error?: { readonly message?: string } })\n | undefined;\n if (!response.ok || !body?.signingKey) {\n throw new Error(body?.error?.message ?? `The app answered HTTP ${response.status}.`);\n }\n const commitment = await deviceCommitment(\n origin,\n body.id,\n body.signingKey,\n body.presenceKey?.publicKey,\n );\n if (linkCode(commitment) !== normalizeLinkCode(input.code)) {\n throw new Error(\n \"The app returned a device whose keys do not match this code, so nothing was linked. Show a new code on the device and try again.\",\n );\n }\n const shown = { name: printable(body.name), platform: printable(body.platform) };\n if (!(await input.confirm(shown))) return { linked: false };\n const device: TrustedDevice = {\n deviceId: body.id,\n signingKey: body.signingKey,\n presenceKey: body.presenceKey,\n name: body.name,\n platform: body.platform,\n approvedAt: (input.now ?? Date.now)(),\n via: \"local\",\n };\n await input.trust.pin(origin, device);\n return { linked: true, device };\n}\n"]}
|
package/dist/link.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type HostStreamBatch, type HostWelcome } from "@parlor/chat";
|
|
2
|
+
import { type ChatHostActionMap } from "@parlor/chat/server";
|
|
3
|
+
import { type PluginClient } from "@parlor/client";
|
|
4
|
+
import type { AppGrant, HostKeys } from "./grants.js";
|
|
5
|
+
export declare const RECONNECT_MAX_MS = 4000;
|
|
6
|
+
export declare const ACTIVE_POLL_MS = 2000;
|
|
7
|
+
export declare const IDLE_POLL_MS = 15000;
|
|
8
|
+
export interface HostApi {
|
|
9
|
+
readonly calls: PluginClient<ChatHostActionMap>;
|
|
10
|
+
/** At most one post in flight per turn; frames produced meanwhile coalesce into the next. */
|
|
11
|
+
stream(batch: HostStreamBatch): void;
|
|
12
|
+
heartbeat(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface AppLinkDeps {
|
|
15
|
+
readonly keys: HostKeys;
|
|
16
|
+
readonly hostVersion: string;
|
|
17
|
+
readonly hostName: string;
|
|
18
|
+
readonly platform: string;
|
|
19
|
+
readonly fetch: typeof fetch;
|
|
20
|
+
/** A turn may be waiting: claim until not_found, then ask for running turns' signals. */
|
|
21
|
+
readonly onWake: (link: AppLink) => void;
|
|
22
|
+
/** Whether a turn runs on this link, which keeps a poll deployment's `/sync` at the active interval. */
|
|
23
|
+
readonly busy?: (() => boolean) | undefined;
|
|
24
|
+
/** Any 401, or the stream closed for revocation. */
|
|
25
|
+
readonly onRevoked: (grant: AppGrant) => void;
|
|
26
|
+
/** No shared protocol: shown locally ("update Parlor Host"); the link stays down. */
|
|
27
|
+
readonly onIncompatible: (grant: AppGrant, welcome: Extract<HostWelcome, {
|
|
28
|
+
readonly accepted: false;
|
|
29
|
+
}>) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `idle` until started; `serving` wakes the host for turns; `draining` stays up for the
|
|
33
|
+
* turns it runs but wakes nobody (a newer service took over the claiming); `closed` is final.
|
|
34
|
+
*/
|
|
35
|
+
export type LinkMode = "idle" | "serving" | "draining" | "closed";
|
|
36
|
+
/** The host's whole conversation with one app, each request carrying a fresh assertion. */
|
|
37
|
+
export interface AppLink {
|
|
38
|
+
readonly grant: AppGrant;
|
|
39
|
+
readonly api: HostApi;
|
|
40
|
+
/** Set once `hostHello` was welcomed; every other call waits for it. */
|
|
41
|
+
readonly protocol: number | undefined;
|
|
42
|
+
readonly mode: LinkMode;
|
|
43
|
+
start(): void;
|
|
44
|
+
/** Stops waking the host and keeps everything else, so running turns finish on this link. */
|
|
45
|
+
drain(): void;
|
|
46
|
+
/** With `revoke`, a refused revoke throws and leaves the link running. */
|
|
47
|
+
stop(options?: {
|
|
48
|
+
readonly revoke?: boolean | undefined;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
export declare function createAppLink(grant: AppGrant, deps: AppLinkDeps): AppLink;
|
|
52
|
+
//# sourceMappingURL=link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,iBAAiB,EAAyB,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAIxB,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAItD,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AACtC,eAAO,MAAM,cAAc,OAAQ,CAAC;AACpC,eAAO,MAAM,YAAY,QAAS,CAAC;AAKnC,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAChD,6FAA6F;IAC7F,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;IAC7B,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,wGAAwG;IACxG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,SAAS,CAAC;IAC5C,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC9C,qFAAqF;IACrF,QAAQ,CAAC,cAAc,EAAE,CACvB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,OAAO,CAAC,WAAW,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAA;KAAE,CAAC,KACxD,IAAI,CAAC;CACX;AAED;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;AAElE,2FAA2F;AAC3F,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;IACd,6FAA6F;IAC7F,KAAK,IAAI,IAAI,CAAC;IACd,0EAA0E;IAC1E,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1E;AAuFD,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAyMzE"}
|