appduct 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -0
  3. package/bin.js +2 -0
  4. package/dist/artifact-inspect.d.ts +110 -0
  5. package/dist/artifact-inspect.d.ts.map +1 -0
  6. package/dist/artifact-inspect.js +315 -0
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +4 -0
  10. package/dist/cli/command-options.d.ts +44 -0
  11. package/dist/cli/command-options.d.ts.map +1 -0
  12. package/dist/cli/command-options.js +95 -0
  13. package/dist/cli/create-cli.d.ts +2 -0
  14. package/dist/cli/create-cli.d.ts.map +1 -0
  15. package/dist/cli/create-cli.js +56 -0
  16. package/dist/cli/dispatch.d.ts +3 -0
  17. package/dist/cli/dispatch.d.ts.map +1 -0
  18. package/dist/cli/dispatch.js +275 -0
  19. package/dist/cli/open-target.d.ts +112 -0
  20. package/dist/cli/open-target.d.ts.map +1 -0
  21. package/dist/cli/open-target.js +438 -0
  22. package/dist/cli/result-types.d.ts +194 -0
  23. package/dist/cli/result-types.d.ts.map +1 -0
  24. package/dist/cli/result-types.js +6 -0
  25. package/dist/cli/runner.d.ts +35 -0
  26. package/dist/cli/runner.d.ts.map +1 -0
  27. package/dist/cli/runner.js +111 -0
  28. package/dist/cli/types.d.ts +20 -0
  29. package/dist/cli/types.d.ts.map +1 -0
  30. package/dist/cli/types.js +10 -0
  31. package/dist/cli.d.ts +4 -0
  32. package/dist/cli.d.ts.map +1 -0
  33. package/dist/cli.js +2 -0
  34. package/dist/client/app-client.d.ts +126 -0
  35. package/dist/client/app-client.d.ts.map +1 -0
  36. package/dist/client/app-client.js +223 -0
  37. package/dist/client/bootstrap.d.ts +48 -0
  38. package/dist/client/bootstrap.d.ts.map +1 -0
  39. package/dist/client/bootstrap.js +108 -0
  40. package/dist/client/connect.d.ts +17 -0
  41. package/dist/client/connect.d.ts.map +1 -0
  42. package/dist/client/connect.js +38 -0
  43. package/dist/client/errors.d.ts +26 -0
  44. package/dist/client/errors.d.ts.map +1 -0
  45. package/dist/client/errors.js +40 -0
  46. package/dist/client/index.d.ts +19 -0
  47. package/dist/client/index.d.ts.map +1 -0
  48. package/dist/client/index.js +16 -0
  49. package/dist/commands/daemon.d.ts +22 -0
  50. package/dist/commands/daemon.d.ts.map +1 -0
  51. package/dist/commands/daemon.js +128 -0
  52. package/dist/commands/doctor.d.ts +23 -0
  53. package/dist/commands/doctor.d.ts.map +1 -0
  54. package/dist/commands/doctor.js +42 -0
  55. package/dist/commands/events.d.ts +37 -0
  56. package/dist/commands/events.d.ts.map +1 -0
  57. package/dist/commands/events.js +76 -0
  58. package/dist/commands/init.d.ts +51 -0
  59. package/dist/commands/init.d.ts.map +1 -0
  60. package/dist/commands/init.js +231 -0
  61. package/dist/commands/invoke.d.ts +22 -0
  62. package/dist/commands/invoke.d.ts.map +1 -0
  63. package/dist/commands/invoke.js +54 -0
  64. package/dist/commands/keygen.d.ts +23 -0
  65. package/dist/commands/keygen.d.ts.map +1 -0
  66. package/dist/commands/keygen.js +46 -0
  67. package/dist/commands/link.d.ts +30 -0
  68. package/dist/commands/link.d.ts.map +1 -0
  69. package/dist/commands/link.js +43 -0
  70. package/dist/commands/ls.d.ts +9 -0
  71. package/dist/commands/ls.d.ts.map +1 -0
  72. package/dist/commands/ls.js +7 -0
  73. package/dist/commands/mcp.d.ts +49 -0
  74. package/dist/commands/mcp.d.ts.map +1 -0
  75. package/dist/commands/mcp.js +98 -0
  76. package/dist/commands/revoke.d.ts +12 -0
  77. package/dist/commands/revoke.d.ts.map +1 -0
  78. package/dist/commands/revoke.js +7 -0
  79. package/dist/commands/tools.d.ts +23 -0
  80. package/dist/commands/tools.d.ts.map +1 -0
  81. package/dist/commands/tools.js +52 -0
  82. package/dist/daemon/address.d.ts +30 -0
  83. package/dist/daemon/address.d.ts.map +1 -0
  84. package/dist/daemon/address.js +71 -0
  85. package/dist/daemon/audit.d.ts +107 -0
  86. package/dist/daemon/audit.d.ts.map +1 -0
  87. package/dist/daemon/audit.js +237 -0
  88. package/dist/daemon/calls.d.ts +84 -0
  89. package/dist/daemon/calls.d.ts.map +1 -0
  90. package/dist/daemon/calls.js +158 -0
  91. package/dist/daemon/config.d.ts +79 -0
  92. package/dist/daemon/config.d.ts.map +1 -0
  93. package/dist/daemon/config.js +170 -0
  94. package/dist/daemon/daemon.d.ts +43 -0
  95. package/dist/daemon/daemon.d.ts.map +1 -0
  96. package/dist/daemon/daemon.js +560 -0
  97. package/dist/daemon/event-bus.d.ts +49 -0
  98. package/dist/daemon/event-bus.d.ts.map +1 -0
  99. package/dist/daemon/event-bus.js +111 -0
  100. package/dist/daemon/links.d.ts +54 -0
  101. package/dist/daemon/links.d.ts.map +1 -0
  102. package/dist/daemon/links.js +118 -0
  103. package/dist/daemon/listener.d.ts +34 -0
  104. package/dist/daemon/listener.d.ts.map +1 -0
  105. package/dist/daemon/listener.js +132 -0
  106. package/dist/daemon/log-rotation.d.ts +82 -0
  107. package/dist/daemon/log-rotation.d.ts.map +1 -0
  108. package/dist/daemon/log-rotation.js +136 -0
  109. package/dist/daemon/pidfile.d.ts +38 -0
  110. package/dist/daemon/pidfile.d.ts.map +1 -0
  111. package/dist/daemon/pidfile.js +90 -0
  112. package/dist/daemon/policy.d.ts +22 -0
  113. package/dist/daemon/policy.d.ts.map +1 -0
  114. package/dist/daemon/policy.js +24 -0
  115. package/dist/daemon/registry.d.ts +19 -0
  116. package/dist/daemon/registry.d.ts.map +1 -0
  117. package/dist/daemon/registry.js +31 -0
  118. package/dist/daemon/rpc-server.d.ts +54 -0
  119. package/dist/daemon/rpc-server.d.ts.map +1 -0
  120. package/dist/daemon/rpc-server.js +203 -0
  121. package/dist/daemon/sessions.d.ts +86 -0
  122. package/dist/daemon/sessions.d.ts.map +1 -0
  123. package/dist/daemon/sessions.js +429 -0
  124. package/dist/daemon/socket-probe.d.ts +16 -0
  125. package/dist/daemon/socket-probe.d.ts.map +1 -0
  126. package/dist/daemon/socket-probe.js +28 -0
  127. package/dist/daemon/state-dir.d.ts +31 -0
  128. package/dist/daemon/state-dir.d.ts.map +1 -0
  129. package/dist/daemon/state-dir.js +43 -0
  130. package/dist/daemon/timers.d.ts +16 -0
  131. package/dist/daemon/timers.d.ts.map +1 -0
  132. package/dist/daemon/timers.js +12 -0
  133. package/dist/daemon/tls.d.ts +58 -0
  134. package/dist/daemon/tls.d.ts.map +1 -0
  135. package/dist/daemon/tls.js +79 -0
  136. package/dist/errors.d.ts +28 -0
  137. package/dist/errors.d.ts.map +1 -0
  138. package/dist/errors.js +162 -0
  139. package/dist/host-certificate.d.ts +8 -0
  140. package/dist/host-certificate.d.ts.map +1 -0
  141. package/dist/host-certificate.js +111 -0
  142. package/dist/index.d.ts +4 -0
  143. package/dist/index.d.ts.map +1 -0
  144. package/dist/index.js +3 -0
  145. package/dist/key-material.d.ts +14 -0
  146. package/dist/key-material.d.ts.map +1 -0
  147. package/dist/key-material.js +42 -0
  148. package/dist/link.d.ts +76 -0
  149. package/dist/link.d.ts.map +1 -0
  150. package/dist/link.js +117 -0
  151. package/dist/mcp/connect-tool.d.ts +115 -0
  152. package/dist/mcp/connect-tool.d.ts.map +1 -0
  153. package/dist/mcp/connect-tool.js +396 -0
  154. package/dist/mcp/daemon-tools.d.ts +13 -0
  155. package/dist/mcp/daemon-tools.d.ts.map +1 -0
  156. package/dist/mcp/daemon-tools.js +29 -0
  157. package/dist/mcp/events-tool.d.ts +111 -0
  158. package/dist/mcp/events-tool.d.ts.map +1 -0
  159. package/dist/mcp/events-tool.js +322 -0
  160. package/dist/mcp/server.d.ts +58 -0
  161. package/dist/mcp/server.d.ts.map +1 -0
  162. package/dist/mcp/server.js +538 -0
  163. package/dist/mcp/tool-mapping.d.ts +58 -0
  164. package/dist/mcp/tool-mapping.d.ts.map +1 -0
  165. package/dist/mcp/tool-mapping.js +143 -0
  166. package/dist/mcp/tool-namespace.d.ts +40 -0
  167. package/dist/mcp/tool-namespace.d.ts.map +1 -0
  168. package/dist/mcp/tool-namespace.js +65 -0
  169. package/dist/native-scheme.d.ts +59 -0
  170. package/dist/native-scheme.d.ts.map +1 -0
  171. package/dist/native-scheme.js +465 -0
  172. package/dist/output.d.ts +28 -0
  173. package/dist/output.d.ts.map +1 -0
  174. package/dist/output.js +342 -0
  175. package/dist/package-version.d.ts +30 -0
  176. package/dist/package-version.d.ts.map +1 -0
  177. package/dist/package-version.js +47 -0
  178. package/dist/qr-terminal.d.ts +13 -0
  179. package/dist/qr-terminal.d.ts.map +1 -0
  180. package/dist/qr-terminal.js +45 -0
  181. package/dist/rpc/client.d.ts +137 -0
  182. package/dist/rpc/client.d.ts.map +1 -0
  183. package/dist/rpc/client.js +819 -0
  184. package/dist/scheme.d.ts +181 -0
  185. package/dist/scheme.d.ts.map +1 -0
  186. package/dist/scheme.js +321 -0
  187. package/dist/spki-pin.d.ts +3 -0
  188. package/dist/spki-pin.d.ts.map +1 -0
  189. package/dist/spki-pin.js +14 -0
  190. package/package.json +65 -0
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,12 @@
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
+ };
@@ -0,0 +1,58 @@
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 type { AgentEndpoint } from "@appduct/shared";
18
+ import type { AdvertisedAddress } from "./address.js";
19
+ export declare class HostKeyError extends Error {
20
+ constructor(message: string);
21
+ }
22
+ /** Reports informational (non-error) messages, e.g. the fingerprint of an auto-generated key.
23
+ * Deliberately the same shape as `ConfigWarnFn` (`config.ts`) so callers can pass the same
24
+ * function through without an adapter. */
25
+ export type TlsWarnFn = (message: string) => void;
26
+ /** Refuses a key file that is group/world-readable. Missing files are handled by the caller
27
+ * (`loadOrGenerateHostKeyPem`), which auto-generates instead of throwing. */
28
+ export declare const loadHostKeyPem: (keyPath: string) => Promise<string>;
29
+ /**
30
+ * Loads the host key at `keyPath`, generating one in its place if none exists yet (dev-mode
31
+ * zero-config bootstrap). Only the ENOENT case auto-generates: any other `stat`/`readFile` failure
32
+ * (including bad permissions) still propagates as before, so a deliberately hardened/misconfigured
33
+ * key file is never silently overwritten.
34
+ */
35
+ export declare const loadOrGenerateHostKeyPem: (keyPath: string, warn?: TlsWarnFn) => Promise<string>;
36
+ export type TlsMaterial = {
37
+ certPem: string;
38
+ keyPem: string;
39
+ spkiPin: string;
40
+ advertisedAddress: AdvertisedAddress;
41
+ };
42
+ export type TlsManager = {
43
+ /** Current cert/key material for the TLS listener. */
44
+ current: () => TlsMaterial;
45
+ /** Re-mints the leaf cert if the advertised address changed; returns the (possibly unchanged) material. */
46
+ refresh: (address?: AdvertisedAddress) => Promise<TlsMaterial>;
47
+ /** SPKI pin-set for `daemon.status`. Single-entry today (no key rotation in v2.0 — ARCHITECTURE.md §14). */
48
+ pinnedKeys: () => string[];
49
+ };
50
+ export type CreateTlsManagerOptions = {
51
+ keyPath: string;
52
+ detectAddress: () => AdvertisedAddress;
53
+ /** Reports the fingerprint when a missing key is auto-generated (dev-mode bootstrap). */
54
+ warn?: TlsWarnFn;
55
+ };
56
+ export declare const createTlsManager: (options: CreateTlsManagerOptions) => Promise<TlsManager>;
57
+ export declare const toAgentEndpoint: (address: AdvertisedAddress, port: number) => AgentEndpoint;
58
+ //# sourceMappingURL=tls.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,79 @@
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
+ };
@@ -0,0 +1,28 @@
1
+ import type { CliError, CliErrorType } from "./cli/result-types.js";
2
+ export declare class AppductCliError extends Error {
3
+ readonly type: CliErrorType;
4
+ readonly details?: unknown;
5
+ constructor(type: CliErrorType, message: string, details?: unknown);
6
+ }
7
+ export declare const usageError: (message: string, details?: unknown) => AppductCliError;
8
+ export declare const validationError: (message: string, details?: unknown) => AppductCliError;
9
+ export declare const connectionError: (message: string, details?: unknown) => AppductCliError;
10
+ export declare const sessionError: (message: string, details?: unknown) => AppductCliError;
11
+ export declare const toolError: (message: string, details?: unknown) => AppductCliError;
12
+ export declare const permissionError: (message: string, details?: unknown) => AppductCliError;
13
+ export declare const internalError: (message: string, details?: unknown) => AppductCliError;
14
+ export declare const inspectionError: (message: string, details?: unknown) => AppductCliError;
15
+ export declare const assertionError: (message: string, details?: unknown) => AppductCliError;
16
+ export declare const isAppductCliError: (value: unknown) => value is AppductCliError;
17
+ /**
18
+ * Converts any error thrown from a command handler into the CLI's rendered `CliError` shape.
19
+ * RPC/app-originated failures (`DaemonRpcError` with a preserved `data.type`) keep that type
20
+ * **verbatim** in `type` — never re-wrapped under a generic bucket (ARCHITECTURE.md §5) — so e.g.
21
+ * `invoke --json` on a failing tool call renders `error.type === "tool_execution_error"`, not
22
+ * `"tool_error"`. `getExitCodeForError` still buckets that same error into a sysexits class.
23
+ */
24
+ export declare const toCliError: (error: unknown) => CliError;
25
+ /** Computes the process exit code for the *original* thrown error (not the rendered `CliError`) so
26
+ * that the sysexits classification never depends on — or leaks into — the rendered `type` field. */
27
+ export declare const getExitCodeForError: (error: unknown) => number;
28
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
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 ADDED
@@ -0,0 +1,162 @@
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
+ };
@@ -0,0 +1,8 @@
1
+ import "reflect-metadata";
2
+ type GeneratedHostCertificate = {
3
+ certPem: string;
4
+ spkiPin: string;
5
+ };
6
+ export declare const generateHostCertificate: (keyPem: string, advertisedIp: string) => Promise<GeneratedHostCertificate>;
7
+ export {};
8
+ //# sourceMappingURL=host-certificate.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,111 @@
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
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./cli.js";
2
+ export * from "./errors.js";
3
+ export * from "./output.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
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"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./cli.js";
2
+ export * from "./errors.js";
3
+ export * from "./output.js";
@@ -0,0 +1,14 @@
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
+ export declare const generatePrivateKeyPem: () => string;
9
+ /** Writes `keyPem` at `path` atomically (temp file + rename) with mode 0600. The temp file uses
10
+ * `wx` so two concurrent writers can never interleave; callers that want to overwrite an existing
11
+ * `path` still succeed, since `rename` replaces the destination on POSIX regardless of whether it
12
+ * already existed — only the *temp* path must be exclusive. */
13
+ export declare const writePrivateKeyAtomically: (path: string, keyPem: string) => Promise<void>;
14
+ //# sourceMappingURL=key-material.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,42 @@
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 ADDED
@@ -0,0 +1,76 @@
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 { type AgentEndpoint, type LinkCreateResult } from "@appduct/shared";
20
+ import { type ExecFn, type OpenTarget } from "./cli/open-target.js";
21
+ import { type SpawnFn } from "./rpc/client.js";
22
+ /**
23
+ * The deep-link shape, in one place: `<scheme>:///?appduct=<payload>&pin=<spki-pin>`. Exported
24
+ * so `mcp/connect-tool.ts` composes byte-identical links rather than a second, drifting copy — the
25
+ * `pin` param went missing from the MCP side exactly because there were two of these.
26
+ *
27
+ * The pin is standard (non-URL-safe) base64 (`sha256/<44 chars>`, possibly containing `+`/`/`/`=`),
28
+ * so it is percent-encoded; parsers use `URLSearchParams`, which decodes it back. It is appended
29
+ * *after* the bootstrap blob, so anything reading `appduct=` must stop at the `&`.
30
+ */
31
+ export declare const composeDeepLink: (scheme: string, result: LinkCreateResult) => string;
32
+ export type MintLinkOptions = {
33
+ stateDir: string;
34
+ spawn?: SpawnFn;
35
+ autoSpawn?: boolean;
36
+ ttlSeconds?: number;
37
+ /** Delivers the link directly to a booted Android emulator/device or iOS simulator instead of
38
+ * leaving delivery to the caller. */
39
+ target?: OpenTarget;
40
+ /** An adb device serial (`target: "android"`), a simulator udid (`target: "ios-sim"`) or a
41
+ * paired-device udid (`target: "ios-device"`). Only meaningful alongside a target. */
42
+ device?: string;
43
+ /** Overrides `config.json`'s `iosBundleId`. Only meaningful with `target: "ios-device"`. */
44
+ bundleId?: string;
45
+ /** `target: "ios-device"` only: terminate a running instance before launching
46
+ * (`--terminate-existing`). Off by default. */
47
+ relaunch?: boolean;
48
+ /** Highest-precedence scheme source (`--scheme`); see `scheme.ts` for the full order. */
49
+ scheme?: string;
50
+ /** Where scheme discovery starts (project `.appduct/config.json` walk-up, then `app.json`).
51
+ * Defaults to `process.cwd()`. */
52
+ cwd?: string;
53
+ exec?: ExecFn;
54
+ /** The environment handed to `adb`/`simctl` when delivering to a device. This is deliberately
55
+ * *not* where `APPDUCT_SCHEME` is read from — callers narrow this env on purpose, and reading
56
+ * the scheme out of it would silently drop one the user really had exported. See {@link schemeEnv}. */
57
+ env?: NodeJS.ProcessEnv;
58
+ /** The environment `APPDUCT_SCHEME` is read from; defaults to `process.env`. Separate from
59
+ * {@link env} so a narrowed exec environment cannot change scheme resolution. */
60
+ schemeEnv?: NodeJS.ProcessEnv;
61
+ };
62
+ export type MintLinkResult = {
63
+ sessionId: string;
64
+ deepLink: string;
65
+ endpoint: AgentEndpoint;
66
+ /** Unix seconds. */
67
+ expiresAt: number;
68
+ pin: string;
69
+ delivered?: true;
70
+ target?: OpenTarget;
71
+ };
72
+ /** Throws `usageError`/`AppductCliError` on validation failure — `commands/link.ts` renders
73
+ * that straight through the CLI's own error path; `client/bootstrap.ts` converts it to a
74
+ * `AppductError` via `toAppductError` (which already understands `AppductCliError`). */
75
+ export declare const mintLink: (options: MintLinkOptions) => Promise<MintLinkResult>;
76
+ //# sourceMappingURL=link.d.ts.map
@@ -0,0 +1 @@
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"}