appduct 0.10.0 → 0.11.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.
- package/README.md +122 -52
- package/dist/artifact-inspect.d.ts +0 -1
- package/dist/bin.d.ts +0 -1
- package/dist/bin.js +1250 -2
- package/dist/cli/command-options.d.ts +8 -1
- package/dist/cli/create-cli.d.ts +0 -1
- package/dist/cli/dispatch.d.ts +10 -1
- package/dist/cli/envelope.d.ts +19 -0
- package/dist/cli/global-flags.d.ts +30 -0
- package/dist/cli/open-target.d.ts +25 -8
- package/dist/cli/result-types.d.ts +31 -5
- package/dist/cli/router.d.ts +78 -0
- package/dist/cli/routes/daemon/index.d.ts +9 -0
- package/dist/cli/routes/daemon/index.js +848 -0
- package/dist/cli/routes/daemon/run.d.ts +3 -0
- package/dist/cli/routes/daemon/run.js +3246 -0
- package/dist/cli/routes/daemon/start.d.ts +3 -0
- package/dist/cli/routes/daemon/start.js +1677 -0
- package/dist/cli/routes/daemon/status.d.ts +4 -0
- package/dist/cli/routes/daemon/status.js +1685 -0
- package/dist/cli/routes/daemon/stop.d.ts +4 -0
- package/dist/cli/routes/daemon/stop.js +1655 -0
- package/dist/cli/routes/doctor.d.ts +3 -0
- package/dist/cli/routes/doctor.js +1075 -0
- package/dist/cli/routes/events.d.ts +3 -0
- package/dist/cli/routes/events.js +1915 -0
- package/dist/cli/routes/init.d.ts +3 -0
- package/dist/cli/routes/init.js +1486 -0
- package/dist/cli/routes/invoke.d.ts +3 -0
- package/dist/cli/routes/invoke.js +1893 -0
- package/dist/cli/routes/keygen.d.ts +3 -0
- package/dist/cli/routes/keygen.js +928 -0
- package/dist/cli/routes/link.d.ts +3 -0
- package/dist/cli/routes/link.js +2714 -0
- package/dist/cli/routes/ls.d.ts +3 -0
- package/dist/cli/routes/ls.js +1668 -0
- package/dist/cli/routes/mcp.d.ts +5 -0
- package/dist/cli/routes/mcp.js +3929 -0
- package/dist/cli/routes/revoke.d.ts +3 -0
- package/dist/cli/routes/revoke.js +1684 -0
- package/dist/cli/routes/tools.d.ts +3 -0
- package/dist/cli/routes/tools.js +1860 -0
- package/dist/cli/runner.d.ts +3 -11
- package/dist/cli/types.d.ts +9 -6
- package/dist/cli/version-guard.d.ts +20 -0
- package/dist/cli.d.ts +0 -1
- package/dist/client/app-client.d.ts +4 -4
- package/dist/client/bootstrap.d.ts +4 -4
- package/dist/client/connect.d.ts +0 -1
- package/dist/client/errors.d.ts +0 -1
- package/dist/client/index.d.ts +1 -2
- package/dist/client/index.js +2389 -16
- package/dist/commands/daemon/context.d.ts +14 -0
- package/dist/commands/daemon/run.d.ts +11 -0
- package/dist/commands/daemon/start.d.ts +5 -0
- package/dist/commands/daemon/status.d.ts +5 -0
- package/dist/commands/daemon/stop.d.ts +5 -0
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/events.d.ts +0 -1
- package/dist/commands/init.d.ts +17 -4
- package/dist/commands/invoke.d.ts +0 -1
- package/dist/commands/keygen.d.ts +0 -1
- package/dist/commands/link.d.ts +1 -2
- package/dist/commands/ls.d.ts +0 -1
- package/dist/commands/mcp.d.ts +2 -4
- package/dist/commands/revoke.d.ts +0 -1
- package/dist/commands/tools.d.ts +19 -3
- package/dist/daemon/address.d.ts +0 -1
- package/dist/daemon/audit.d.ts +6 -11
- package/dist/daemon/calls.d.ts +0 -1
- package/dist/daemon/config.d.ts +13 -16
- package/dist/daemon/daemon.d.ts +0 -1
- package/dist/daemon/event-bus.d.ts +0 -1
- package/dist/daemon/links.d.ts +0 -1
- package/dist/daemon/listener.d.ts +0 -1
- package/dist/daemon/log-rotation.d.ts +0 -1
- package/dist/daemon/pidfile.d.ts +14 -8
- package/dist/daemon/policy.d.ts +0 -1
- package/dist/daemon/registry.d.ts +0 -1
- package/dist/daemon/rpc-errors.d.ts +16 -0
- package/dist/daemon/rpc-server.d.ts +0 -11
- package/dist/daemon/sessions.d.ts +0 -1
- package/dist/daemon/socket-probe.d.ts +0 -1
- package/dist/daemon/state-dir.d.ts +0 -1
- package/dist/daemon/timers.d.ts +0 -1
- package/dist/daemon/tls.d.ts +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/host-certificate.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1307 -3
- package/dist/key-material.d.ts +0 -1
- package/dist/link.d.ts +8 -7
- package/dist/mcp/app-tools.d.ts +153 -0
- package/dist/mcp/connect-tool.d.ts +14 -8
- package/dist/mcp/daemon-tools.d.ts +2 -11
- package/dist/mcp/events-tool.d.ts +0 -1
- package/dist/mcp/server.d.ts +33 -14
- package/dist/native-scheme.d.ts +0 -1
- package/dist/output.d.ts +8 -11
- package/dist/package-root.d.ts +11 -0
- package/dist/package-version.d.ts +0 -1
- package/dist/qr-terminal.d.ts +0 -1
- package/dist/rpc/client.d.ts +3 -3
- package/dist/scheme.d.ts +60 -2
- package/dist/spki-pin.d.ts +0 -1
- package/package.json +6 -3
- package/dist/artifact-inspect.d.ts.map +0 -1
- package/dist/artifact-inspect.js +0 -334
- package/dist/bin.d.ts.map +0 -1
- package/dist/cli/command-options.d.ts.map +0 -1
- package/dist/cli/command-options.js +0 -95
- package/dist/cli/create-cli.d.ts.map +0 -1
- package/dist/cli/create-cli.js +0 -56
- package/dist/cli/dispatch.d.ts.map +0 -1
- package/dist/cli/dispatch.js +0 -275
- package/dist/cli/open-target.d.ts.map +0 -1
- package/dist/cli/open-target.js +0 -438
- package/dist/cli/result-types.d.ts.map +0 -1
- package/dist/cli/result-types.js +0 -6
- package/dist/cli/runner.d.ts.map +0 -1
- package/dist/cli/runner.js +0 -111
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/types.js +0 -10
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2
- package/dist/client/app-client.d.ts.map +0 -1
- package/dist/client/app-client.js +0 -223
- package/dist/client/bootstrap.d.ts.map +0 -1
- package/dist/client/bootstrap.js +0 -108
- package/dist/client/connect.d.ts.map +0 -1
- package/dist/client/connect.js +0 -38
- package/dist/client/errors.d.ts.map +0 -1
- package/dist/client/errors.js +0 -40
- package/dist/client/index.d.ts.map +0 -1
- package/dist/commands/daemon.d.ts +0 -22
- package/dist/commands/daemon.d.ts.map +0 -1
- package/dist/commands/daemon.js +0 -128
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -42
- package/dist/commands/events.d.ts.map +0 -1
- package/dist/commands/events.js +0 -76
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -231
- package/dist/commands/invoke.d.ts.map +0 -1
- package/dist/commands/invoke.js +0 -54
- package/dist/commands/keygen.d.ts.map +0 -1
- package/dist/commands/keygen.js +0 -46
- package/dist/commands/link.d.ts.map +0 -1
- package/dist/commands/link.js +0 -43
- package/dist/commands/ls.d.ts.map +0 -1
- package/dist/commands/ls.js +0 -7
- package/dist/commands/mcp.d.ts.map +0 -1
- package/dist/commands/mcp.js +0 -98
- package/dist/commands/revoke.d.ts.map +0 -1
- package/dist/commands/revoke.js +0 -7
- package/dist/commands/tools.d.ts.map +0 -1
- package/dist/commands/tools.js +0 -52
- package/dist/daemon/address.d.ts.map +0 -1
- package/dist/daemon/address.js +0 -71
- package/dist/daemon/audit.d.ts.map +0 -1
- package/dist/daemon/audit.js +0 -237
- package/dist/daemon/calls.d.ts.map +0 -1
- package/dist/daemon/calls.js +0 -158
- package/dist/daemon/config.d.ts.map +0 -1
- package/dist/daemon/config.js +0 -170
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js +0 -560
- package/dist/daemon/event-bus.d.ts.map +0 -1
- package/dist/daemon/event-bus.js +0 -111
- package/dist/daemon/links.d.ts.map +0 -1
- package/dist/daemon/links.js +0 -118
- package/dist/daemon/listener.d.ts.map +0 -1
- package/dist/daemon/listener.js +0 -132
- package/dist/daemon/log-rotation.d.ts.map +0 -1
- package/dist/daemon/log-rotation.js +0 -136
- package/dist/daemon/pidfile.d.ts.map +0 -1
- package/dist/daemon/pidfile.js +0 -90
- package/dist/daemon/policy.d.ts.map +0 -1
- package/dist/daemon/policy.js +0 -24
- package/dist/daemon/registry.d.ts.map +0 -1
- package/dist/daemon/registry.js +0 -31
- package/dist/daemon/rpc-server.d.ts.map +0 -1
- package/dist/daemon/rpc-server.js +0 -203
- package/dist/daemon/sessions.d.ts.map +0 -1
- package/dist/daemon/sessions.js +0 -429
- package/dist/daemon/socket-probe.d.ts.map +0 -1
- package/dist/daemon/socket-probe.js +0 -28
- package/dist/daemon/state-dir.d.ts.map +0 -1
- package/dist/daemon/state-dir.js +0 -43
- package/dist/daemon/timers.d.ts.map +0 -1
- package/dist/daemon/timers.js +0 -12
- package/dist/daemon/tls.d.ts.map +0 -1
- package/dist/daemon/tls.js +0 -79
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -162
- package/dist/host-certificate.d.ts.map +0 -1
- package/dist/host-certificate.js +0 -111
- package/dist/index.d.ts.map +0 -1
- package/dist/key-material.d.ts.map +0 -1
- package/dist/key-material.js +0 -42
- package/dist/link.d.ts.map +0 -1
- package/dist/link.js +0 -117
- package/dist/mcp/connect-tool.d.ts.map +0 -1
- package/dist/mcp/connect-tool.js +0 -396
- package/dist/mcp/daemon-tools.d.ts.map +0 -1
- package/dist/mcp/daemon-tools.js +0 -29
- package/dist/mcp/events-tool.d.ts.map +0 -1
- package/dist/mcp/events-tool.js +0 -322
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -538
- package/dist/mcp/tool-mapping.d.ts +0 -58
- package/dist/mcp/tool-mapping.d.ts.map +0 -1
- package/dist/mcp/tool-mapping.js +0 -143
- package/dist/mcp/tool-namespace.d.ts +0 -40
- package/dist/mcp/tool-namespace.d.ts.map +0 -1
- package/dist/mcp/tool-namespace.js +0 -65
- package/dist/native-scheme.d.ts.map +0 -1
- package/dist/native-scheme.js +0 -465
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -342
- package/dist/package-version.d.ts.map +0 -1
- package/dist/package-version.js +0 -47
- package/dist/qr-terminal.d.ts.map +0 -1
- package/dist/qr-terminal.js +0 -45
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -819
- package/dist/scheme.d.ts.map +0 -1
- package/dist/scheme.js +0 -321
- package/dist/spki-pin.d.ts.map +0 -1
- package/dist/spki-pin.js +0 -14
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* "Is anything listening on the control socket?" — the cheapest liveness signal the state dir
|
|
3
|
-
* offers, and the one that answers fastest when a daemon is up.
|
|
4
|
-
*
|
|
5
|
-
* It lives in its own module rather than in `rpc/client.ts` (its first caller) because
|
|
6
|
-
* `daemon/log-rotation.ts` needs it too, and `rpc/client.ts` already imports *that*. Keeping the
|
|
7
|
-
* probe here means neither has to import the other.
|
|
8
|
-
*/
|
|
9
|
-
import { connect } from "node:net";
|
|
10
|
-
/**
|
|
11
|
-
* Resolves `true` when a connection to `socketPath` is accepted. A refusal, a missing socket file,
|
|
12
|
-
* or any other error resolves `false` — this never rejects, so callers can use it as a plain
|
|
13
|
-
* predicate. Note it proves a listener exists *right now*; it cannot prove one will not appear a
|
|
14
|
-
* moment later.
|
|
15
|
-
*/
|
|
16
|
-
export const isSocketConnectable = (socketPath) => {
|
|
17
|
-
return new Promise((resolve) => {
|
|
18
|
-
const socket = connect(socketPath);
|
|
19
|
-
socket.once("connect", () => {
|
|
20
|
-
socket.destroy();
|
|
21
|
-
resolve(true);
|
|
22
|
-
});
|
|
23
|
-
socket.once("error", () => {
|
|
24
|
-
socket.destroy();
|
|
25
|
-
resolve(false);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"state-dir.d.ts","sourceRoot":"","sources":["../../src/daemon/state-dir.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,MAAM,aAAa,GAAG;IAC1B,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,oGAAoG;IACpG,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,sGAAsG;AACtG,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,KAAG,MAEnD,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,UAAU,MAAM,KAAG,aAWnD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,aAAa,KAAG,MAEpD,CAAC;AAEF,wGAAwG;AACxG,eAAO,MAAM,cAAc,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,aAAa,CAU5E,CAAC"}
|
package/dist/daemon/state-dir.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State directory resolution and layout (ARCHITECTURE.md §3).
|
|
3
|
-
*
|
|
4
|
-
* Default `~/.appduct/`, overridable with `APPDUCT_STATE_DIR` (tests rely on the
|
|
5
|
-
* override). Created lazily with mode `0700`.
|
|
6
|
-
*/
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { mkdir, chmod } from "node:fs/promises";
|
|
10
|
-
/** Resolves the state directory: explicit `override`, else `APPDUCT_STATE_DIR`, else `~/.appduct`. */
|
|
11
|
-
export const resolveStateDir = (override) => {
|
|
12
|
-
return override ?? process.env.APPDUCT_STATE_DIR ?? join(homedir(), ".appduct");
|
|
13
|
-
};
|
|
14
|
-
export const getStateDirPaths = (stateDir) => {
|
|
15
|
-
return {
|
|
16
|
-
root: stateDir,
|
|
17
|
-
socketPath: join(stateDir, "daemon.sock"),
|
|
18
|
-
pidFilePath: join(stateDir, "daemon.pid"),
|
|
19
|
-
logFilePath: join(stateDir, "daemon.log"),
|
|
20
|
-
keyPath: join(stateDir, "key.pem"),
|
|
21
|
-
configPath: join(stateDir, "config.json"),
|
|
22
|
-
auditDir: join(stateDir, "audit"),
|
|
23
|
-
spawnLockPath: join(stateDir, "daemon.spawn.lock"),
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Single seam for the control-socket address. Today this is always the UDS path; a Windows
|
|
28
|
-
* named-pipe implementation (`\\.\pipe\appduct-<user>`) plugs in here without touching any
|
|
29
|
-
* caller (ARCHITECTURE.md §13 — best-effort, not blocking v2.0).
|
|
30
|
-
*/
|
|
31
|
-
export const getSocketPath = (paths) => {
|
|
32
|
-
return paths.socketPath;
|
|
33
|
-
};
|
|
34
|
-
/** Creates the state dir (and `audit/`) with mode `0700` if missing; tightens the mode if it exists. */
|
|
35
|
-
export const ensureStateDir = async (stateDir) => {
|
|
36
|
-
const paths = getStateDirPaths(stateDir);
|
|
37
|
-
await mkdir(paths.auditDir, { recursive: true });
|
|
38
|
-
await chmod(paths.root, 0o700);
|
|
39
|
-
// `mkdir` honors the process umask (commonly 0o022, yielding drwxr-xr-x); tighten explicitly so
|
|
40
|
-
// `audit/` matches every other path under the state dir (ARCHITECTURE.md §3).
|
|
41
|
-
await chmod(paths.auditDir, 0o700);
|
|
42
|
-
return paths;
|
|
43
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"timers.d.ts","sourceRoot":"","sources":["../../src/daemon/timers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAE5D,MAAM,MAAM,QAAQ,GAAG;IACrB,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,WAAW,CAAC;IAC9D,YAAY,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IAC5C,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,cAAc,CAAC;IAClE,aAAa,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,QAK1B,CAAC"}
|
package/dist/daemon/timers.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Injectable timer seam (LOOP.md: "use injectable clock/timer seams for tests"). Every daemon
|
|
3
|
-
* module that schedules a TTL/grace/pre-claim/keepalive timer takes a {@link TimerFns} so tests
|
|
4
|
-
* can substitute an implementation that fires deterministically instead of waiting on real wall
|
|
5
|
-
* time, and so every timer this package creates can be traced back to a single seam for cleanup.
|
|
6
|
-
*/
|
|
7
|
-
export const systemTimers = {
|
|
8
|
-
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
|
9
|
-
clearTimeout: (handle) => clearTimeout(handle),
|
|
10
|
-
setInterval: (callback, ms) => setInterval(callback, ms),
|
|
11
|
-
clearInterval: (handle) => clearInterval(handle),
|
|
12
|
-
};
|
package/dist/daemon/tls.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tls.d.ts","sourceRoot":"","sources":["../../src/daemon/tls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED;;0CAE0C;AAC1C,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAElD;6EAC6E;AAC7E,eAAO,MAAM,cAAc,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,MAAM,CAWpE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GAAU,SAAS,MAAM,EAAE,OAAO,SAAS,KAAG,OAAO,CAAC,MAAM,CAiBhG,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,iBAAiB,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,sDAAsD;IACtD,OAAO,EAAE,MAAM,WAAW,CAAC;IAC3B,2GAA2G;IAC3G,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/D,4GAA4G;IAC5G,UAAU,EAAE,MAAM,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,iBAAiB,CAAC;IACvC,yFAAyF;IACzF,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAU,SAAS,uBAAuB,KAAG,OAAO,CAAC,UAAU,CAuB3F,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,iBAAiB,EAAE,MAAM,MAAM,KAAG,aAE1E,CAAC"}
|
package/dist/daemon/tls.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host key loading and leaf-certificate minting (ARCHITECTURE.md §3, §8). Reuses
|
|
3
|
-
* `host-certificate.ts` for the actual X.509 generation; this module
|
|
4
|
-
* owns key-file hygiene (refusing over-permissive key files) and re-minting the leaf when
|
|
5
|
-
* the advertised address changes so the certificate's SAN stays valid.
|
|
6
|
-
*
|
|
7
|
-
* The SPKI pin is derived from the key's public key alone (`spki-pin.ts`), so it never changes
|
|
8
|
-
* across re-mints — only the certificate's SAN entries do.
|
|
9
|
-
*
|
|
10
|
-
* Opt-in hardening, dev-mode zero-config bootstrap: a *missing* key file is no longer fatal.
|
|
11
|
-
* `createTlsManager` auto-generates one (same material/hygiene as `appduct keygen`, via
|
|
12
|
-
* `key-material.ts`) so a first-time `appduct daemon` needs no manual keygen step, and reports
|
|
13
|
-
* the new key's SPKI fingerprint through the optional `warn` callback. An existing key with bad
|
|
14
|
-
* permissions is still refused outright — auto-generation only fills the *missing* case, it never
|
|
15
|
-
* silently replaces a key that's already there.
|
|
16
|
-
*/
|
|
17
|
-
import { stat, readFile } from "node:fs/promises";
|
|
18
|
-
import { generateHostCertificate } from "../host-certificate.js";
|
|
19
|
-
import { generatePrivateKeyPem, writePrivateKeyAtomically } from "../key-material.js";
|
|
20
|
-
import { getSpkiPinFromPrivateKeyPem } from "../spki-pin.js";
|
|
21
|
-
export class HostKeyError extends Error {
|
|
22
|
-
constructor(message) {
|
|
23
|
-
super(message);
|
|
24
|
-
this.name = "HostKeyError";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/** Refuses a key file that is group/world-readable. Missing files are handled by the caller
|
|
28
|
-
* (`loadOrGenerateHostKeyPem`), which auto-generates instead of throwing. */
|
|
29
|
-
export const loadHostKeyPem = async (keyPath) => {
|
|
30
|
-
const fileStat = await stat(keyPath);
|
|
31
|
-
if ((fileStat.mode & 0o077) !== 0) {
|
|
32
|
-
throw new HostKeyError(`Appduct host key at "${keyPath}" is group- or world-readable (mode ${(fileStat.mode & 0o777).toString(8)}). ` +
|
|
33
|
-
`Refusing to load it — run "chmod 600 ${keyPath}" or re-run "appduct keygen".`);
|
|
34
|
-
}
|
|
35
|
-
return readFile(keyPath, "utf8");
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Loads the host key at `keyPath`, generating one in its place if none exists yet (dev-mode
|
|
39
|
-
* zero-config bootstrap). Only the ENOENT case auto-generates: any other `stat`/`readFile` failure
|
|
40
|
-
* (including bad permissions) still propagates as before, so a deliberately hardened/misconfigured
|
|
41
|
-
* key file is never silently overwritten.
|
|
42
|
-
*/
|
|
43
|
-
export const loadOrGenerateHostKeyPem = async (keyPath, warn) => {
|
|
44
|
-
try {
|
|
45
|
-
return await loadHostKeyPem(keyPath);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
if (error.code !== "ENOENT") {
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const keyPem = generatePrivateKeyPem();
|
|
53
|
-
await writePrivateKeyAtomically(keyPath, keyPem);
|
|
54
|
-
warn?.(`Appduct: no host key found at "${keyPath}" — generated one (dev mode). ` +
|
|
55
|
-
`SPKI pin: ${getSpkiPinFromPrivateKeyPem(keyPem)}`);
|
|
56
|
-
return keyPem;
|
|
57
|
-
};
|
|
58
|
-
export const createTlsManager = async (options) => {
|
|
59
|
-
const keyPem = await loadOrGenerateHostKeyPem(options.keyPath, options.warn);
|
|
60
|
-
const mint = async (address) => {
|
|
61
|
-
const { certPem, spkiPin } = await generateHostCertificate(keyPem, address.address);
|
|
62
|
-
return { certPem, keyPem, spkiPin, advertisedAddress: address };
|
|
63
|
-
};
|
|
64
|
-
let material = await mint(options.detectAddress());
|
|
65
|
-
return {
|
|
66
|
-
current: () => material,
|
|
67
|
-
refresh: async (address) => {
|
|
68
|
-
const next = address ?? options.detectAddress();
|
|
69
|
-
if (next.address !== material.advertisedAddress.address || next.family !== material.advertisedAddress.family) {
|
|
70
|
-
material = await mint(next);
|
|
71
|
-
}
|
|
72
|
-
return material;
|
|
73
|
-
},
|
|
74
|
-
pinnedKeys: () => [material.spkiPin],
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
export const toAgentEndpoint = (address, port) => {
|
|
78
|
-
return { family: address.family, address: address.address, port };
|
|
79
|
-
};
|
package/dist/errors.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AA4CpE,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;gBAEf,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAMnE;AAED,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAE/D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAEpE,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAEpE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAEjE,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAE9D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAEpE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAElE,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAEpE,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,UAAU,OAAO,KAAG,eAEnE,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAE3D,CAAC;AAUF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,QAqD3C,CAAC;AAEF;oGACoG;AACpG,eAAO,MAAM,mBAAmB,GAAI,OAAO,OAAO,KAAG,MAmBpD,CAAC"}
|
package/dist/errors.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { DaemonRpcError, DaemonVersionMismatchError, isDaemonUnreachableError } from "./rpc/client.js";
|
|
2
|
-
const EXIT_CODE_BY_ERROR_TYPE = {
|
|
3
|
-
usage_error: 64,
|
|
4
|
-
validation_error: 65,
|
|
5
|
-
// EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
|
|
6
|
-
// missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
|
|
7
|
-
// code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
|
|
8
|
-
// assertion failure (docs/tasks/08-appduct-doctor.md).
|
|
9
|
-
inspection_error: 66,
|
|
10
|
-
connection_error: 70,
|
|
11
|
-
session_error: 71,
|
|
12
|
-
tool_error: 72,
|
|
13
|
-
permission_error: 77,
|
|
14
|
-
internal_error: 1,
|
|
15
|
-
// `doctor --assert-present`/`--assert-absent`: the artifact was inspected successfully and the
|
|
16
|
-
// assertion did not hold. Distinct from `inspection_error` (66) — this means the answer is known
|
|
17
|
-
// and it's "no", not "couldn't tell" — and distinct from `internal_error` (1) so a caller can't
|
|
18
|
-
// conflate a normal assertion failure with an unexpected crash.
|
|
19
|
-
assertion_error: 3,
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Maps the daemon's wire `error.data.type` (ARCHITECTURE.md §5) onto a sysexits class for exit-code
|
|
23
|
-
* purposes only — the CLI error's rendered `type` field still preserves the wire type verbatim
|
|
24
|
-
* (see {@link toCliError}); this table never leaks into rendered output.
|
|
25
|
-
*/
|
|
26
|
-
const RPC_ERROR_EXIT_CLASS = {
|
|
27
|
-
no_session: "session_error",
|
|
28
|
-
ambiguous_session: "session_error",
|
|
29
|
-
unknown_session: "session_error",
|
|
30
|
-
session_not_active: "session_error",
|
|
31
|
-
session_suspended: "session_error",
|
|
32
|
-
tool_not_found: "tool_error",
|
|
33
|
-
tool_input_validation_error: "tool_error",
|
|
34
|
-
tool_output_validation_error: "tool_error",
|
|
35
|
-
tool_execution_error: "tool_error",
|
|
36
|
-
tool_serialization_error: "tool_error",
|
|
37
|
-
tool_timeout: "tool_error",
|
|
38
|
-
tool_cancelled: "tool_error",
|
|
39
|
-
policy_denied: "permission_error",
|
|
40
|
-
invalid_request: "validation_error",
|
|
41
|
-
};
|
|
42
|
-
export class AppductCliError extends Error {
|
|
43
|
-
type;
|
|
44
|
-
details;
|
|
45
|
-
constructor(type, message, details) {
|
|
46
|
-
super(message);
|
|
47
|
-
this.name = "AppductCliError";
|
|
48
|
-
this.type = type;
|
|
49
|
-
this.details = details;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
export const usageError = (message, details) => {
|
|
53
|
-
return new AppductCliError("usage_error", message, details);
|
|
54
|
-
};
|
|
55
|
-
export const validationError = (message, details) => {
|
|
56
|
-
return new AppductCliError("validation_error", message, details);
|
|
57
|
-
};
|
|
58
|
-
export const connectionError = (message, details) => {
|
|
59
|
-
return new AppductCliError("connection_error", message, details);
|
|
60
|
-
};
|
|
61
|
-
export const sessionError = (message, details) => {
|
|
62
|
-
return new AppductCliError("session_error", message, details);
|
|
63
|
-
};
|
|
64
|
-
export const toolError = (message, details) => {
|
|
65
|
-
return new AppductCliError("tool_error", message, details);
|
|
66
|
-
};
|
|
67
|
-
export const permissionError = (message, details) => {
|
|
68
|
-
return new AppductCliError("permission_error", message, details);
|
|
69
|
-
};
|
|
70
|
-
export const internalError = (message, details) => {
|
|
71
|
-
return new AppductCliError("internal_error", message, details);
|
|
72
|
-
};
|
|
73
|
-
export const inspectionError = (message, details) => {
|
|
74
|
-
return new AppductCliError("inspection_error", message, details);
|
|
75
|
-
};
|
|
76
|
-
export const assertionError = (message, details) => {
|
|
77
|
-
return new AppductCliError("assertion_error", message, details);
|
|
78
|
-
};
|
|
79
|
-
export const isAppductCliError = (value) => {
|
|
80
|
-
return value instanceof AppductCliError;
|
|
81
|
-
};
|
|
82
|
-
/** True for a `DaemonRpcError` whose `data.type` is a known wire `ErrorType` (always the case for
|
|
83
|
-
* `RpcApplicationError`-originated failures; JSON-RPC transport-level errors like "Method not
|
|
84
|
-
* found" carry no `data` and fall through to `internal_error`). */
|
|
85
|
-
const rpcErrorClass = (error) => {
|
|
86
|
-
const type = error.data?.type;
|
|
87
|
-
return type ? RPC_ERROR_EXIT_CLASS[type] : undefined;
|
|
88
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* Converts any error thrown from a command handler into the CLI's rendered `CliError` shape.
|
|
91
|
-
* RPC/app-originated failures (`DaemonRpcError` with a preserved `data.type`) keep that type
|
|
92
|
-
* **verbatim** in `type` — never re-wrapped under a generic bucket (ARCHITECTURE.md §5) — so e.g.
|
|
93
|
-
* `invoke --json` on a failing tool call renders `error.type === "tool_execution_error"`, not
|
|
94
|
-
* `"tool_error"`. `getExitCodeForError` still buckets that same error into a sysexits class.
|
|
95
|
-
*/
|
|
96
|
-
export const toCliError = (error) => {
|
|
97
|
-
if (isAppductCliError(error)) {
|
|
98
|
-
return {
|
|
99
|
-
type: error.type,
|
|
100
|
-
message: error.message,
|
|
101
|
-
details: error.details,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
// Daemon/CLI version drift that could not be resolved automatically (issue #30). Rendered as a
|
|
105
|
-
// `connection_error` — the daemon is reachable, but not one this client can safely talk to —
|
|
106
|
-
// with both versions and the live-session count in `details` so `--json` consumers can act on it
|
|
107
|
-
// without parsing the message.
|
|
108
|
-
if (error instanceof DaemonVersionMismatchError) {
|
|
109
|
-
return {
|
|
110
|
-
type: "connection_error",
|
|
111
|
-
message: error.message,
|
|
112
|
-
details: {
|
|
113
|
-
daemon_version: error.daemonVersion,
|
|
114
|
-
client_version: error.clientVersion,
|
|
115
|
-
session_count: error.sessionCount,
|
|
116
|
-
pending_link_count: error.pendingLinkCount,
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
if (error instanceof DaemonRpcError && error.data?.type) {
|
|
121
|
-
return {
|
|
122
|
-
type: error.data.type,
|
|
123
|
-
message: error.message,
|
|
124
|
-
details: error.data.details,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
if (isDaemonUnreachableError(error)) {
|
|
128
|
-
return {
|
|
129
|
-
type: "connection_error",
|
|
130
|
-
message: error instanceof Error ? error.message : "The Appduct daemon is unreachable.",
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
if (error instanceof Error) {
|
|
134
|
-
return {
|
|
135
|
-
type: "internal_error",
|
|
136
|
-
message: error.message,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
type: "internal_error",
|
|
141
|
-
message: "An unexpected error occurred.",
|
|
142
|
-
details: error,
|
|
143
|
-
};
|
|
144
|
-
};
|
|
145
|
-
/** Computes the process exit code for the *original* thrown error (not the rendered `CliError`) so
|
|
146
|
-
* that the sysexits classification never depends on — or leaks into — the rendered `type` field. */
|
|
147
|
-
export const getExitCodeForError = (error) => {
|
|
148
|
-
if (isAppductCliError(error)) {
|
|
149
|
-
return EXIT_CODE_BY_ERROR_TYPE[error.type];
|
|
150
|
-
}
|
|
151
|
-
if (error instanceof DaemonVersionMismatchError) {
|
|
152
|
-
return EXIT_CODE_BY_ERROR_TYPE.connection_error;
|
|
153
|
-
}
|
|
154
|
-
if (error instanceof DaemonRpcError) {
|
|
155
|
-
const cliErrorClass = rpcErrorClass(error);
|
|
156
|
-
return EXIT_CODE_BY_ERROR_TYPE[cliErrorClass ?? "internal_error"];
|
|
157
|
-
}
|
|
158
|
-
if (isDaemonUnreachableError(error)) {
|
|
159
|
-
return EXIT_CODE_BY_ERROR_TYPE.connection_error;
|
|
160
|
-
}
|
|
161
|
-
return EXIT_CODE_BY_ERROR_TYPE.internal_error;
|
|
162
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"host-certificate.d.ts","sourceRoot":"","sources":["../src/host-certificate.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAyB1B,KAAK,wBAAwB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAwHF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,EACd,cAAc,MAAM,KACnB,OAAO,CAAC,wBAAwB,CAyClC,CAAC"}
|
package/dist/host-certificate.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import "reflect-metadata";
|
|
2
|
-
import { createPrivateKey, createPublicKey, randomBytes, webcrypto, } from "node:crypto";
|
|
3
|
-
import { BasicConstraintsExtension, DNS, ExtendedKeyUsage, ExtendedKeyUsageExtension, IP, KeyUsageFlags, KeyUsagesExtension, SubjectAlternativeNameExtension, SubjectKeyIdentifierExtension, X509CertificateGenerator, } from "@peculiar/x509";
|
|
4
|
-
import { getSpkiPinFromPrivateKeyPem } from "./spki-pin.js";
|
|
5
|
-
const CERT_VALIDITY_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
6
|
-
const CERT_CLOCK_SKEW_MS = 5 * 60 * 1000;
|
|
7
|
-
const toArrayBuffer = (view) => {
|
|
8
|
-
return Uint8Array.from(view).buffer;
|
|
9
|
-
};
|
|
10
|
-
const decodeBase64Url = (value) => {
|
|
11
|
-
return Uint8Array.from(Buffer.from(value, "base64url"));
|
|
12
|
-
};
|
|
13
|
-
const resolveEcHash = (curve) => {
|
|
14
|
-
switch (curve) {
|
|
15
|
-
case "P-256":
|
|
16
|
-
return "SHA-256";
|
|
17
|
-
case "P-384":
|
|
18
|
-
return "SHA-384";
|
|
19
|
-
case "P-521":
|
|
20
|
-
return "SHA-512";
|
|
21
|
-
default:
|
|
22
|
-
throw new Error(`Unsupported EC named curve "${curve}" for Appduct host certificates.`);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
const importSigningKeyPair = async (keyPem) => {
|
|
26
|
-
const privateKeyObject = createPrivateKey(keyPem);
|
|
27
|
-
const publicKeyObject = createPublicKey(privateKeyObject);
|
|
28
|
-
const privateKeyDer = Buffer.from(privateKeyObject.export({ format: "der", type: "pkcs8" }));
|
|
29
|
-
const publicKeyDer = Buffer.from(publicKeyObject.export({ format: "der", type: "spki" }));
|
|
30
|
-
const jwk = privateKeyObject.export({ format: "jwk" });
|
|
31
|
-
if (jwk.kty === "RSA") {
|
|
32
|
-
const modulusLength = privateKeyObject.asymmetricKeyDetails?.modulusLength;
|
|
33
|
-
if (typeof modulusLength !== "number" || typeof jwk.e !== "string") {
|
|
34
|
-
throw new Error("Appduct could not determine RSA key details for certificate generation.");
|
|
35
|
-
}
|
|
36
|
-
const publicExponent = decodeBase64Url(jwk.e);
|
|
37
|
-
const importAlgorithm = {
|
|
38
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
39
|
-
hash: "SHA-256",
|
|
40
|
-
modulusLength,
|
|
41
|
-
publicExponent,
|
|
42
|
-
};
|
|
43
|
-
return {
|
|
44
|
-
keyType: "rsa",
|
|
45
|
-
privateKey: await webcrypto.subtle.importKey("pkcs8", toArrayBuffer(privateKeyDer), importAlgorithm, false, ["sign"]),
|
|
46
|
-
publicKey: await webcrypto.subtle.importKey("spki", toArrayBuffer(publicKeyDer), importAlgorithm, true, ["verify"]),
|
|
47
|
-
signingAlgorithm: {
|
|
48
|
-
name: "RSASSA-PKCS1-v1_5",
|
|
49
|
-
hash: "SHA-256",
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (jwk.kty === "EC") {
|
|
54
|
-
const namedCurve = jwk.crv;
|
|
55
|
-
if (typeof namedCurve !== "string") {
|
|
56
|
-
throw new Error("Appduct could not determine the EC named curve for certificate generation.");
|
|
57
|
-
}
|
|
58
|
-
const importAlgorithm = {
|
|
59
|
-
name: "ECDSA",
|
|
60
|
-
namedCurve,
|
|
61
|
-
};
|
|
62
|
-
const signingAlgorithm = {
|
|
63
|
-
...importAlgorithm,
|
|
64
|
-
hash: resolveEcHash(namedCurve),
|
|
65
|
-
};
|
|
66
|
-
return {
|
|
67
|
-
keyType: "ec",
|
|
68
|
-
privateKey: await webcrypto.subtle.importKey("pkcs8", toArrayBuffer(privateKeyDer), importAlgorithm, false, ["sign"]),
|
|
69
|
-
publicKey: await webcrypto.subtle.importKey("spki", toArrayBuffer(publicKeyDer), importAlgorithm, true, ["verify"]),
|
|
70
|
-
signingAlgorithm: {
|
|
71
|
-
name: "ECDSA",
|
|
72
|
-
hash: resolveEcHash(namedCurve),
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
throw new Error(`Appduct does not support "${jwk.kty ?? privateKeyObject.asymmetricKeyType}" host keys.`);
|
|
77
|
-
};
|
|
78
|
-
export const generateHostCertificate = async (keyPem, advertisedIp) => {
|
|
79
|
-
const keys = await importSigningKeyPair(keyPem);
|
|
80
|
-
const sanEntries = [
|
|
81
|
-
{ type: DNS, value: "localhost" },
|
|
82
|
-
{ type: IP, value: "127.0.0.1" },
|
|
83
|
-
];
|
|
84
|
-
if (advertisedIp !== "127.0.0.1") {
|
|
85
|
-
sanEntries.push({ type: IP, value: advertisedIp });
|
|
86
|
-
}
|
|
87
|
-
const keyUsage = KeyUsageFlags.digitalSignature |
|
|
88
|
-
(keys.keyType === "rsa" ? KeyUsageFlags.keyEncipherment : KeyUsageFlags.keyAgreement);
|
|
89
|
-
const certificate = await X509CertificateGenerator.createSelfSigned({
|
|
90
|
-
name: "CN=Appduct Host",
|
|
91
|
-
notBefore: new Date(Date.now() - CERT_CLOCK_SKEW_MS),
|
|
92
|
-
notAfter: new Date(Date.now() + CERT_VALIDITY_WINDOW_MS),
|
|
93
|
-
signingAlgorithm: keys.signingAlgorithm,
|
|
94
|
-
keys: {
|
|
95
|
-
privateKey: keys.privateKey,
|
|
96
|
-
publicKey: keys.publicKey,
|
|
97
|
-
},
|
|
98
|
-
extensions: [
|
|
99
|
-
new BasicConstraintsExtension(false, undefined, true),
|
|
100
|
-
new KeyUsagesExtension(keyUsage, true),
|
|
101
|
-
new ExtendedKeyUsageExtension([ExtendedKeyUsage.serverAuth], false),
|
|
102
|
-
new SubjectAlternativeNameExtension(sanEntries, false),
|
|
103
|
-
await SubjectKeyIdentifierExtension.create(keys.publicKey),
|
|
104
|
-
],
|
|
105
|
-
});
|
|
106
|
-
const certPem = certificate.toString("pem");
|
|
107
|
-
return {
|
|
108
|
-
certPem,
|
|
109
|
-
spkiPin: getSpkiPinFromPrivateKeyPem(keyPem),
|
|
110
|
-
};
|
|
111
|
-
};
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"key-material.d.ts","sourceRoot":"","sources":["../src/key-material.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,eAAO,MAAM,qBAAqB,QAAO,MAYxC,CAAC;AAEF;;;+DAG+D;AAC/D,eAAO,MAAM,yBAAyB,GAAU,MAAM,MAAM,EAAE,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAgB1F,CAAC"}
|
package/dist/key-material.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host private-key generation and atomic persistence (ARCHITECTURE.md §3, §10). Shared by
|
|
3
|
-
* `appduct keygen` (explicit, user-invoked) and the daemon's zero-config dev-mode
|
|
4
|
-
* auto-generation at startup (`daemon/tls.ts`) — both call sites need byte-identical key material
|
|
5
|
-
* (RSA-2048, PKCS8 PEM) and identical file hygiene (0600, atomic rename via a `wx`-flagged temp
|
|
6
|
-
* file so a concurrent writer can never observe a partial key).
|
|
7
|
-
*/
|
|
8
|
-
import { generateKeyPairSync } from "node:crypto";
|
|
9
|
-
import { dirname } from "node:path";
|
|
10
|
-
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
11
|
-
export const generatePrivateKeyPem = () => {
|
|
12
|
-
const { privateKey } = generateKeyPairSync("rsa", {
|
|
13
|
-
modulusLength: 2048,
|
|
14
|
-
publicExponent: 0x10001,
|
|
15
|
-
});
|
|
16
|
-
return privateKey
|
|
17
|
-
.export({
|
|
18
|
-
format: "pem",
|
|
19
|
-
type: "pkcs8",
|
|
20
|
-
})
|
|
21
|
-
.toString("utf8");
|
|
22
|
-
};
|
|
23
|
-
/** Writes `keyPem` at `path` atomically (temp file + rename) with mode 0600. The temp file uses
|
|
24
|
-
* `wx` so two concurrent writers can never interleave; callers that want to overwrite an existing
|
|
25
|
-
* `path` still succeed, since `rename` replaces the destination on POSIX regardless of whether it
|
|
26
|
-
* already existed — only the *temp* path must be exclusive. */
|
|
27
|
-
export const writePrivateKeyAtomically = async (path, keyPem) => {
|
|
28
|
-
const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
29
|
-
await mkdir(dirname(path), { recursive: true });
|
|
30
|
-
try {
|
|
31
|
-
await writeFile(temporaryPath, keyPem, {
|
|
32
|
-
encoding: "utf8",
|
|
33
|
-
mode: 0o600,
|
|
34
|
-
flag: "wx",
|
|
35
|
-
});
|
|
36
|
-
await rename(temporaryPath, path);
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
await rm(temporaryPath, { force: true }).catch(() => { });
|
|
40
|
-
throw error;
|
|
41
|
-
}
|
|
42
|
-
};
|
package/dist/link.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAe,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEzF,OAAO,EAUL,KAAK,MAAM,EACX,KAAK,UAAU,EAChB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAU3D;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,EAAE,QAAQ,gBAAgB,KAAG,MACc,CAAC;AAE1F,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;yCACqC;IACrC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;0FACsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;mDAC+C;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;sCACkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;2GAEuG;IACvG,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;qFACiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,aAAa,CAAC;IACxB,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF;;wFAEwF;AACxF,eAAO,MAAM,QAAQ,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,cAAc,CA8F/E,CAAC"}
|
package/dist/link.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared core of `link.create` + deep-link composition + optional emulator/simulator delivery
|
|
3
|
-
* (ARCHITECTURE.md §8, §10): mints a pending session via `link.create`, then composes
|
|
4
|
-
* `<scheme>:///?appduct=<payload>&pin=<spki-pin>` and optionally delivers it to a booted
|
|
5
|
-
* Android emulator/device or iOS simulator. Used by `commands/link.ts` (`appduct link`) and
|
|
6
|
-
* `client/bootstrap.ts` (`appduct/client`'s `link()`) so this shape — scheme resolution, the
|
|
7
|
-
* `pin` query param, the `127.0.0.1` emulator/simulator address override (which the experimental
|
|
8
|
-
* `ios-device` target deliberately opts out of) — can't drift between the CLI and the programmatic
|
|
9
|
-
* client.
|
|
10
|
-
*
|
|
11
|
-
* The `pin` param is separate from the `appduct` bootstrap blob (opt-in hardening dev-mode) — it
|
|
12
|
-
* is never part of that binary v2 payload, so old apps that don't know about it simply ignore it.
|
|
13
|
-
* It carries the daemon's SPKI pin (same value as `daemon.status`'s `pinnedKeys[0]` / `appduct
|
|
14
|
-
* keygen`'s output) so a debug build with no build-time `cliPins` configured can trust it for this
|
|
15
|
-
* one connection instead of requiring a native rebuild just to test locally. The pin is standard
|
|
16
|
-
* (non-URL-safe) base64 (`sha256/<44-char-base64>`, may contain `+`/`/`/`=`), so it is percent-
|
|
17
|
-
* encoded here; native/JS parsers use `URLSearchParams`, which decodes it back.
|
|
18
|
-
*/
|
|
19
|
-
import { RPC_METHODS } from "@appduct/shared";
|
|
20
|
-
import { deliverToOpenTarget, isOpenTarget, isValidBundleId, invalidBundleIdMessage, isLoopbackAddress, loopbackAddressMessage, usesLoopbackAddress, MISSING_BUNDLE_ID_MESSAGE, OPEN_TARGETS, } from "./cli/open-target.js";
|
|
21
|
-
import { loadConfig } from "./daemon/config.js";
|
|
22
|
-
import { getStateDirPaths } from "./daemon/state-dir.js";
|
|
23
|
-
import { usageError } from "./errors.js";
|
|
24
|
-
import { callDaemon } from "./rpc/client.js";
|
|
25
|
-
import { resolveSchemeOrThrow } from "./scheme.js";
|
|
26
|
-
/** The emulator/simulator fast path forces `127.0.0.1`: the daemon's wss listener already binds
|
|
27
|
-
* all interfaces, and both delivery mechanisms (adb reverse, the iOS simulator's shared host
|
|
28
|
-
* network) make the daemon reachable there regardless of the machine's real LAN address. A
|
|
29
|
-
* physical iOS device (`ios-device`) has no such tunnel, so it keeps the detected LAN address —
|
|
30
|
-
* see `usesLoopbackAddress`. */
|
|
31
|
-
const OPEN_TARGET_ADDRESS_OVERRIDE = "127.0.0.1";
|
|
32
|
-
/**
|
|
33
|
-
* The deep-link shape, in one place: `<scheme>:///?appduct=<payload>&pin=<spki-pin>`. Exported
|
|
34
|
-
* so `mcp/connect-tool.ts` composes byte-identical links rather than a second, drifting copy — the
|
|
35
|
-
* `pin` param went missing from the MCP side exactly because there were two of these.
|
|
36
|
-
*
|
|
37
|
-
* The pin is standard (non-URL-safe) base64 (`sha256/<44 chars>`, possibly containing `+`/`/`/`=`),
|
|
38
|
-
* so it is percent-encoded; parsers use `URLSearchParams`, which decodes it back. It is appended
|
|
39
|
-
* *after* the bootstrap blob, so anything reading `appduct=` must stop at the `&`.
|
|
40
|
-
*/
|
|
41
|
-
export const composeDeepLink = (scheme, result) => `${scheme}:///?appduct=${result.deepLinkPayload}&pin=${encodeURIComponent(result.pin)}`;
|
|
42
|
-
/** Throws `usageError`/`AppductCliError` on validation failure — `commands/link.ts` renders
|
|
43
|
-
* that straight through the CLI's own error path; `client/bootstrap.ts` converts it to a
|
|
44
|
-
* `AppductError` via `toAppductError` (which already understands `AppductCliError`). */
|
|
45
|
-
export const mintLink = async (options) => {
|
|
46
|
-
if (options.target !== undefined && !isOpenTarget(options.target)) {
|
|
47
|
-
throw usageError(`"target" must be one of ${OPEN_TARGETS.map((target) => `"${target}"`).join(", ")} (got "${options.target}").`);
|
|
48
|
-
}
|
|
49
|
-
if (options.device !== undefined && options.target === undefined) {
|
|
50
|
-
throw usageError('"device" only applies alongside a target.');
|
|
51
|
-
}
|
|
52
|
-
if (options.bundleId !== undefined && options.target !== "ios-device") {
|
|
53
|
-
throw usageError('"bundleId" only applies alongside target "ios-device".');
|
|
54
|
-
}
|
|
55
|
-
if (options.relaunch !== undefined && options.target !== "ios-device") {
|
|
56
|
-
throw usageError('"relaunch" only applies alongside target "ios-device".');
|
|
57
|
-
}
|
|
58
|
-
const paths = getStateDirPaths(options.stateDir);
|
|
59
|
-
const config = await loadConfig(paths);
|
|
60
|
-
const scheme = await resolveSchemeOrThrow({
|
|
61
|
-
flagScheme: options.scheme,
|
|
62
|
-
env: options.schemeEnv,
|
|
63
|
-
cwd: options.cwd,
|
|
64
|
-
configScheme: config.scheme,
|
|
65
|
-
stateConfigPath: paths.configPath,
|
|
66
|
-
stateDirRoot: paths.root,
|
|
67
|
-
});
|
|
68
|
-
// `ios-device` needs a bundle id `devicectl` can launch; resolved (and required) *before* the
|
|
69
|
-
// link is minted so a missing one is a plain usage error rather than a stranded pending session.
|
|
70
|
-
const bundleId = options.target === "ios-device" ? (options.bundleId ?? config.iosBundleId) : undefined;
|
|
71
|
-
if (options.target === "ios-device" && !bundleId) {
|
|
72
|
-
throw usageError(MISSING_BUNDLE_ID_MESSAGE);
|
|
73
|
-
}
|
|
74
|
-
if (bundleId !== undefined && !isValidBundleId(bundleId)) {
|
|
75
|
-
throw usageError(invalidBundleIdMessage(bundleId));
|
|
76
|
-
}
|
|
77
|
-
const result = await callDaemon(RPC_METHODS.linkCreate, {
|
|
78
|
-
ttlSeconds: options.ttlSeconds,
|
|
79
|
-
addressOverride: options.target && usesLoopbackAddress(options.target) ? OPEN_TARGET_ADDRESS_OVERRIDE : undefined,
|
|
80
|
-
}, { stateDir: options.stateDir, spawn: options.spawn, autoSpawn: options.autoSpawn });
|
|
81
|
-
// A physical device is the one target that cannot reach a loopback address, and
|
|
82
|
-
// `detectAdvertisedAddress` falls back to `127.0.0.1` when it finds no routable interface. Left
|
|
83
|
-
// unchecked, the link would be delivered happily and then never claimed — a silent hang in
|
|
84
|
-
// `wait_for_session` with nothing pointing at the cause.
|
|
85
|
-
if (options.target === "ios-device" && isLoopbackAddress(result.endpoint.address)) {
|
|
86
|
-
throw usageError(loopbackAddressMessage(result.endpoint.address));
|
|
87
|
-
}
|
|
88
|
-
const deepLink = composeDeepLink(scheme, result);
|
|
89
|
-
if (options.target) {
|
|
90
|
-
await deliverToOpenTarget({
|
|
91
|
-
target: options.target,
|
|
92
|
-
deepLink,
|
|
93
|
-
wssPort: result.endpoint.port,
|
|
94
|
-
device: options.device,
|
|
95
|
-
bundleId,
|
|
96
|
-
relaunch: options.relaunch,
|
|
97
|
-
exec: options.exec,
|
|
98
|
-
env: options.env,
|
|
99
|
-
});
|
|
100
|
-
return {
|
|
101
|
-
sessionId: result.sessionId,
|
|
102
|
-
deepLink,
|
|
103
|
-
endpoint: result.endpoint,
|
|
104
|
-
expiresAt: result.expiresAt,
|
|
105
|
-
pin: result.pin,
|
|
106
|
-
delivered: true,
|
|
107
|
-
target: options.target,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
sessionId: result.sessionId,
|
|
112
|
-
deepLink,
|
|
113
|
-
endpoint: result.endpoint,
|
|
114
|
-
expiresAt: result.expiresAt,
|
|
115
|
-
pin: result.pin,
|
|
116
|
-
};
|
|
117
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"connect-tool.d.ts","sourceRoot":"","sources":["../../src/mcp/connect-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,OAAO,EAUL,KAAK,MAAM,EACX,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EAKL,KAAK,OAAO,EACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AACnD,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AA6BrE,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;CA2B1B,CAAC;AAEX,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;CAiBnC,CAAC;AAEX;;mGAEmG;AACnG,qBAAa,mBAAoB,SAAQ,KAAK;IAE1C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,GAAG,sBAAsB;gBAAjE,IAAI,EAAE,iBAAiB,GAAG,cAAc,GAAG,sBAAsB,EAC1E,OAAO,EAAE,MAAM;CAKlB;AA6DD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB;;0EAEsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;qFACiF;IACjF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;sEACkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB;kDAC8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sFAAsF;IACtF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,uGAAuG;IACvG,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAsEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,OAAO,EAChB,MAAM,eAAe,KACpB,OAAO,CAAC,iBAAiB,CA4I3B,CAAC;AA6BF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,OAAO,EAChB,MAAM,sBAAsB,KAC3B,OAAO,CAAC,wBAAwB,CAiHlC,CAAC"}
|