@herbertgao/pi-extensions 2026.8.9 → 2026.8.10

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 (194) hide show
  1. package/README.md +3 -0
  2. package/node_modules/resume-from/LICENSE +21 -0
  3. package/node_modules/resume-from/README.md +161 -0
  4. package/node_modules/resume-from/dist/adapters/claude-code/adapter.d.ts +25 -0
  5. package/node_modules/resume-from/dist/adapters/claude-code/adapter.js +60 -0
  6. package/node_modules/resume-from/dist/adapters/claude-code/contract.d.ts +8 -0
  7. package/node_modules/resume-from/dist/adapters/claude-code/contract.js +4 -0
  8. package/node_modules/resume-from/dist/adapters/claude-code/entries.d.ts +30 -0
  9. package/node_modules/resume-from/dist/adapters/claude-code/entries.js +107 -0
  10. package/node_modules/resume-from/dist/adapters/claude-code/index.d.ts +3 -0
  11. package/node_modules/resume-from/dist/adapters/claude-code/index.js +2 -0
  12. package/node_modules/resume-from/dist/adapters/claude-code/layout.d.ts +29 -0
  13. package/node_modules/resume-from/dist/adapters/claude-code/layout.js +80 -0
  14. package/node_modules/resume-from/dist/adapters/claude-code/readback.d.ts +9 -0
  15. package/node_modules/resume-from/dist/adapters/claude-code/readback.js +39 -0
  16. package/node_modules/resume-from/dist/adapters/claude-code/reader.d.ts +37 -0
  17. package/node_modules/resume-from/dist/adapters/claude-code/reader.js +415 -0
  18. package/node_modules/resume-from/dist/adapters/claude-code/redaction.d.ts +26 -0
  19. package/node_modules/resume-from/dist/adapters/claude-code/redaction.js +132 -0
  20. package/node_modules/resume-from/dist/adapters/claude-code/validation.d.ts +4 -0
  21. package/node_modules/resume-from/dist/adapters/claude-code/validation.js +39 -0
  22. package/node_modules/resume-from/dist/adapters/claude-code/writer.d.ts +23 -0
  23. package/node_modules/resume-from/dist/adapters/claude-code/writer.js +306 -0
  24. package/node_modules/resume-from/dist/adapters/codex/contract.d.ts +15 -0
  25. package/node_modules/resume-from/dist/adapters/codex/contract.js +4 -0
  26. package/node_modules/resume-from/dist/adapters/codex/index.d.ts +6 -0
  27. package/node_modules/resume-from/dist/adapters/codex/index.js +44 -0
  28. package/node_modules/resume-from/dist/adapters/codex/read.d.ts +28 -0
  29. package/node_modules/resume-from/dist/adapters/codex/read.js +270 -0
  30. package/node_modules/resume-from/dist/adapters/codex/readback.d.ts +9 -0
  31. package/node_modules/resume-from/dist/adapters/codex/readback.js +47 -0
  32. package/node_modules/resume-from/dist/adapters/codex/redaction.d.ts +26 -0
  33. package/node_modules/resume-from/dist/adapters/codex/redaction.js +132 -0
  34. package/node_modules/resume-from/dist/adapters/codex/rollout.d.ts +75 -0
  35. package/node_modules/resume-from/dist/adapters/codex/rollout.js +185 -0
  36. package/node_modules/resume-from/dist/adapters/codex/validation.d.ts +7 -0
  37. package/node_modules/resume-from/dist/adapters/codex/validation.js +78 -0
  38. package/node_modules/resume-from/dist/adapters/codex/write.d.ts +17 -0
  39. package/node_modules/resume-from/dist/adapters/codex/write.js +141 -0
  40. package/node_modules/resume-from/dist/adapters/contract.d.ts +74 -0
  41. package/node_modules/resume-from/dist/adapters/contract.js +4 -0
  42. package/node_modules/resume-from/dist/adapters/pi/adapter.d.ts +15 -0
  43. package/node_modules/resume-from/dist/adapters/pi/adapter.js +209 -0
  44. package/node_modules/resume-from/dist/adapters/pi/contract.d.ts +24 -0
  45. package/node_modules/resume-from/dist/adapters/pi/contract.js +4 -0
  46. package/node_modules/resume-from/dist/adapters/pi/format.d.ts +162 -0
  47. package/node_modules/resume-from/dist/adapters/pi/format.js +223 -0
  48. package/node_modules/resume-from/dist/adapters/pi/index.d.ts +7 -0
  49. package/node_modules/resume-from/dist/adapters/pi/index.js +10 -0
  50. package/node_modules/resume-from/dist/adapters/pi/parse.d.ts +41 -0
  51. package/node_modules/resume-from/dist/adapters/pi/parse.js +371 -0
  52. package/node_modules/resume-from/dist/adapters/pi/redaction.d.ts +26 -0
  53. package/node_modules/resume-from/dist/adapters/pi/redaction.js +132 -0
  54. package/node_modules/resume-from/dist/adapters/pi/serialize.d.ts +22 -0
  55. package/node_modules/resume-from/dist/adapters/pi/serialize.js +87 -0
  56. package/node_modules/resume-from/dist/adapters/pi/validate.d.ts +14 -0
  57. package/node_modules/resume-from/dist/adapters/pi/validate.js +134 -0
  58. package/node_modules/resume-from/dist/bin.d.ts +35 -0
  59. package/node_modules/resume-from/dist/bin.js +135 -0
  60. package/node_modules/resume-from/dist/contract.d.ts +10 -0
  61. package/node_modules/resume-from/dist/contract.js +4 -0
  62. package/node_modules/resume-from/dist/host/agents.d.ts +29 -0
  63. package/node_modules/resume-from/dist/host/agents.js +20 -0
  64. package/node_modules/resume-from/dist/host/cli/args.d.ts +21 -0
  65. package/node_modules/resume-from/dist/host/cli/args.js +187 -0
  66. package/node_modules/resume-from/dist/host/cli/contract.d.ts +27 -0
  67. package/node_modules/resume-from/dist/host/cli/contract.js +4 -0
  68. package/node_modules/resume-from/dist/host/cli/index.d.ts +2 -0
  69. package/node_modules/resume-from/dist/host/cli/index.js +1 -0
  70. package/node_modules/resume-from/dist/host/cli/presentation.d.ts +2 -0
  71. package/node_modules/resume-from/dist/host/cli/presentation.js +11 -0
  72. package/node_modules/resume-from/dist/host/cli/render.d.ts +3 -0
  73. package/node_modules/resume-from/dist/host/cli/render.js +70 -0
  74. package/node_modules/resume-from/dist/host/cli/runner.d.ts +8 -0
  75. package/node_modules/resume-from/dist/host/cli/runner.js +125 -0
  76. package/node_modules/resume-from/dist/host/contract.d.ts +42 -0
  77. package/node_modules/resume-from/dist/host/contract.js +4 -0
  78. package/node_modules/resume-from/dist/host/entry.d.ts +52 -0
  79. package/node_modules/resume-from/dist/host/entry.js +91 -0
  80. package/node_modules/resume-from/dist/host/index.d.ts +11 -0
  81. package/node_modules/resume-from/dist/host/index.js +10 -0
  82. package/node_modules/resume-from/dist/host/pi-extension/command.d.ts +18 -0
  83. package/node_modules/resume-from/dist/host/pi-extension/command.js +103 -0
  84. package/node_modules/resume-from/dist/host/pi-extension/contract.d.ts +34 -0
  85. package/node_modules/resume-from/dist/host/pi-extension/contract.js +4 -0
  86. package/node_modules/resume-from/dist/host/pi-extension/index.d.ts +6 -0
  87. package/node_modules/resume-from/dist/host/pi-extension/index.js +4 -0
  88. package/node_modules/resume-from/dist/host/pi-extension/picker.d.ts +16 -0
  89. package/node_modules/resume-from/dist/host/pi-extension/picker.js +35 -0
  90. package/node_modules/resume-from/dist/host/pi-extension/presentation.d.ts +2 -0
  91. package/node_modules/resume-from/dist/host/pi-extension/presentation.js +11 -0
  92. package/node_modules/resume-from/dist/host/pi-extension/register.d.ts +25 -0
  93. package/node_modules/resume-from/dist/host/pi-extension/register.js +11 -0
  94. package/node_modules/resume-from/dist/host/pi-extension/ui.d.ts +20 -0
  95. package/node_modules/resume-from/dist/host/pi-extension/ui.js +1 -0
  96. package/node_modules/resume-from/dist/host/profile.d.ts +8 -0
  97. package/node_modules/resume-from/dist/host/profile.js +31 -0
  98. package/node_modules/resume-from/dist/host/registry.d.ts +8 -0
  99. package/node_modules/resume-from/dist/host/registry.js +33 -0
  100. package/node_modules/resume-from/dist/host/wiring.d.ts +41 -0
  101. package/node_modules/resume-from/dist/host/wiring.js +67 -0
  102. package/node_modules/resume-from/dist/import/confirmation.d.ts +6 -0
  103. package/node_modules/resume-from/dist/import/confirmation.js +27 -0
  104. package/node_modules/resume-from/dist/import/contract.d.ts +41 -0
  105. package/node_modules/resume-from/dist/import/contract.js +4 -0
  106. package/node_modules/resume-from/dist/import/discovery/contract.d.ts +58 -0
  107. package/node_modules/resume-from/dist/import/discovery/contract.js +4 -0
  108. package/node_modules/resume-from/dist/import/discovery/errors.d.ts +9 -0
  109. package/node_modules/resume-from/dist/import/discovery/errors.js +12 -0
  110. package/node_modules/resume-from/dist/import/discovery/finder.d.ts +10 -0
  111. package/node_modules/resume-from/dist/import/discovery/finder.js +129 -0
  112. package/node_modules/resume-from/dist/import/discovery/homes.d.ts +23 -0
  113. package/node_modules/resume-from/dist/import/discovery/homes.js +83 -0
  114. package/node_modules/resume-from/dist/import/discovery/index.d.ts +2 -0
  115. package/node_modules/resume-from/dist/import/discovery/index.js +2 -0
  116. package/node_modules/resume-from/dist/import/discovery/ordering.d.ts +3 -0
  117. package/node_modules/resume-from/dist/import/discovery/ordering.js +21 -0
  118. package/node_modules/resume-from/dist/import/errors.d.ts +22 -0
  119. package/node_modules/resume-from/dist/import/errors.js +20 -0
  120. package/node_modules/resume-from/dist/import/index.d.ts +3 -0
  121. package/node_modules/resume-from/dist/import/index.js +5 -0
  122. package/node_modules/resume-from/dist/import/landing/contract.d.ts +44 -0
  123. package/node_modules/resume-from/dist/import/landing/contract.js +4 -0
  124. package/node_modules/resume-from/dist/import/landing/errors.d.ts +15 -0
  125. package/node_modules/resume-from/dist/import/landing/errors.js +15 -0
  126. package/node_modules/resume-from/dist/import/landing/handover.d.ts +5 -0
  127. package/node_modules/resume-from/dist/import/landing/handover.js +12 -0
  128. package/node_modules/resume-from/dist/import/landing/index.d.ts +4 -0
  129. package/node_modules/resume-from/dist/import/landing/index.js +4 -0
  130. package/node_modules/resume-from/dist/import/landing/lander.d.ts +7 -0
  131. package/node_modules/resume-from/dist/import/landing/lander.js +133 -0
  132. package/node_modules/resume-from/dist/import/landing/marker.d.ts +9 -0
  133. package/node_modules/resume-from/dist/import/landing/marker.js +46 -0
  134. package/node_modules/resume-from/dist/import/pipeline.d.ts +17 -0
  135. package/node_modules/resume-from/dist/import/pipeline.js +134 -0
  136. package/node_modules/resume-from/dist/import/preview/builder.d.ts +6 -0
  137. package/node_modules/resume-from/dist/import/preview/builder.js +76 -0
  138. package/node_modules/resume-from/dist/import/preview/contract.d.ts +39 -0
  139. package/node_modules/resume-from/dist/import/preview/contract.js +4 -0
  140. package/node_modules/resume-from/dist/import/preview/format.d.ts +19 -0
  141. package/node_modules/resume-from/dist/import/preview/format.js +45 -0
  142. package/node_modules/resume-from/dist/import/preview/index.d.ts +2 -0
  143. package/node_modules/resume-from/dist/import/preview/index.js +1 -0
  144. package/node_modules/resume-from/dist/import/preview/warnings.d.ts +11 -0
  145. package/node_modules/resume-from/dist/import/preview/warnings.js +80 -0
  146. package/node_modules/resume-from/dist/import/transfer/contract.d.ts +48 -0
  147. package/node_modules/resume-from/dist/import/transfer/contract.js +4 -0
  148. package/node_modules/resume-from/dist/import/transfer/index.d.ts +2 -0
  149. package/node_modules/resume-from/dist/import/transfer/index.js +1 -0
  150. package/node_modules/resume-from/dist/import/transfer/rules.d.ts +3 -0
  151. package/node_modules/resume-from/dist/import/transfer/rules.js +228 -0
  152. package/node_modules/resume-from/dist/import/wiring.d.ts +24 -0
  153. package/node_modules/resume-from/dist/import/wiring.js +27 -0
  154. package/node_modules/resume-from/dist/index.d.ts +36 -0
  155. package/node_modules/resume-from/dist/index.js +36 -0
  156. package/node_modules/resume-from/dist/platform/config/contract.d.ts +35 -0
  157. package/node_modules/resume-from/dist/platform/config/contract.js +4 -0
  158. package/node_modules/resume-from/dist/platform/config/defaults.d.ts +16 -0
  159. package/node_modules/resume-from/dist/platform/config/defaults.js +22 -0
  160. package/node_modules/resume-from/dist/platform/config/errors.d.ts +9 -0
  161. package/node_modules/resume-from/dist/platform/config/errors.js +12 -0
  162. package/node_modules/resume-from/dist/platform/config/index.d.ts +5 -0
  163. package/node_modules/resume-from/dist/platform/config/index.js +6 -0
  164. package/node_modules/resume-from/dist/platform/config/loader.d.ts +10 -0
  165. package/node_modules/resume-from/dist/platform/config/loader.js +47 -0
  166. package/node_modules/resume-from/dist/platform/config/paths.d.ts +13 -0
  167. package/node_modules/resume-from/dist/platform/config/paths.js +37 -0
  168. package/node_modules/resume-from/dist/platform/config/validate.d.ts +9 -0
  169. package/node_modules/resume-from/dist/platform/config/validate.js +117 -0
  170. package/node_modules/resume-from/dist/platform/repo/contract.d.ts +30 -0
  171. package/node_modules/resume-from/dist/platform/repo/contract.js +4 -0
  172. package/node_modules/resume-from/dist/platform/repo/git.d.ts +21 -0
  173. package/node_modules/resume-from/dist/platform/repo/git.js +74 -0
  174. package/node_modules/resume-from/dist/platform/repo/index.d.ts +2 -0
  175. package/node_modules/resume-from/dist/platform/repo/index.js +1 -0
  176. package/node_modules/resume-from/dist/platform/repo/reader.d.ts +8 -0
  177. package/node_modules/resume-from/dist/platform/repo/reader.js +130 -0
  178. package/node_modules/resume-from/dist/platform/store/contract.d.ts +31 -0
  179. package/node_modules/resume-from/dist/platform/store/contract.js +4 -0
  180. package/node_modules/resume-from/dist/platform/store/file-committer.d.ts +3 -0
  181. package/node_modules/resume-from/dist/platform/store/file-committer.js +391 -0
  182. package/node_modules/resume-from/dist/platform/store/index.d.ts +2 -0
  183. package/node_modules/resume-from/dist/platform/store/index.js +2 -0
  184. package/node_modules/resume-from/dist/platform/tokens/contract.d.ts +12 -0
  185. package/node_modules/resume-from/dist/platform/tokens/contract.js +4 -0
  186. package/node_modules/resume-from/dist/platform/tokens/estimator.d.ts +12 -0
  187. package/node_modules/resume-from/dist/platform/tokens/estimator.js +111 -0
  188. package/node_modules/resume-from/dist/platform/tokens/index.d.ts +2 -0
  189. package/node_modules/resume-from/dist/platform/tokens/index.js +1 -0
  190. package/node_modules/resume-from/dist/session/contract.d.ts +109 -0
  191. package/node_modules/resume-from/dist/session/contract.js +4 -0
  192. package/node_modules/resume-from/package.json +80 -0
  193. package/node_modules/resume-from/shims/pi/extensions/resume-from.js +118 -0
  194. package/package.json +7 -3
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Credential redaction — shared implementation kept in three adapter subfolders.
3
+ *
4
+ * Files:
5
+ * src/adapters/claude-code/redaction.ts
6
+ * src/adapters/codex/redaction.ts
7
+ * src/adapters/pi/redaction.ts
8
+ *
9
+ * These three files are byte-identical by design. A fix in one MUST be applied to all three.
10
+ * src/adapters/boundary.test.ts asserts byte equality and will fail if they drift.
11
+ *
12
+ * Why not a single shared module: src/adapters/contract.ts is types-only (no behaviour). A
13
+ * platform service would be imported as behaviour, and T-ROO-7 requires adapter-to-platform
14
+ * cross-module imports to go through contract.js — which can only export types, not functions.
15
+ */
16
+ /** Stable replacement used for credentials that must not cross an adapter boundary. */
17
+ export const REDACTED_VALUE = "[REDACTED]";
18
+ const ENVIRONMENT_KEYS = new Set(["env", "environment", "environmentvariables"]);
19
+ const SENSITIVE_KEYS = new Set([
20
+ "authorization",
21
+ "clientsecret",
22
+ "cookie",
23
+ "credentials",
24
+ "password",
25
+ "passwd",
26
+ "privatekey",
27
+ "proxyauthorization",
28
+ "secret",
29
+ "setcookie",
30
+ "signingkey",
31
+ "token",
32
+ ]);
33
+ const PRIVATE_KEY_PATTERN = /-----BEGIN ([A-Z0-9 ]*PRIVATE KEY)-----[\s\S]*?-----END \1-----/g;
34
+ const AUTHORIZATION_PATTERN = /(\b(?:authorization|proxy-authorization)\s*:\s*)(?:Bearer|Basic)\s+[^\s,;]+/gi;
35
+ const API_HEADER_PATTERN = /(\b(?:x-api-key|api-key|x-auth-token)\s*:\s*)[^\s,;]+/gi;
36
+ const CREDENTIAL_PATTERN = /\b(?:sk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{12,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{30,})\b/g;
37
+ const ASSIGNMENT_PATTERN = /\b([A-Za-z_][A-Za-z0-9_]*)(\s*=\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/g;
38
+ const FLAG_PATTERN = /(--(?:api[-_]?key|access[-_]?token|auth[-_]?token|token|password|passwd|secret|client[-_]?secret|private[-_]?key))(=|\s+)("[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gi;
39
+ const USER_CREDENTIAL_PATTERN = /(^|[\s;&|])(-u|--user)(=|\s+)("[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/g;
40
+ const URI_USERINFO_PATTERN = /\b([a-z][a-z0-9+.-]*:\/\/)([^\s/:@]+):([^\s/@]+)@/gi;
41
+ // Unquoted value requires a structural delimiter or line/string end following it, OR a digit
42
+ // somewhere in the token (C-RED-1: "token: expired" is plain English and stays, while
43
+ // "password: hunter2 # prod" carries a credential that must not cross even mid-line).
44
+ const JSON_LIKE_PATTERN = /(["']?)([A-Za-z_][A-Za-z0-9_-]*)\1(\s*:\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^\s,;}]+(?=[,;}]|[\r\n]|$)|[^\s,;}]*\d[^\s,;}]*)/g;
45
+ function normalizedKey(key) {
46
+ return key.toLowerCase().replaceAll(/[^a-z0-9]/g, "");
47
+ }
48
+ function isSensitiveKey(key) {
49
+ const normalized = normalizedKey(key);
50
+ return (SENSITIVE_KEYS.has(normalized) ||
51
+ normalized.endsWith("apikey") ||
52
+ normalized.endsWith("accesskey") ||
53
+ normalized.endsWith("password") ||
54
+ normalized.endsWith("privatekey") ||
55
+ normalized.endsWith("secret") ||
56
+ normalized.endsWith("token"));
57
+ }
58
+ function redactEnvironmentValue(value) {
59
+ if (Array.isArray(value))
60
+ return value.map(redactEnvironmentValue);
61
+ if (typeof value === "object" && value !== null) {
62
+ return Object.fromEntries(Object.keys(value).map((key) => [
63
+ key,
64
+ redactEnvironmentValue(value[key]),
65
+ ]));
66
+ }
67
+ return value === undefined ? undefined : REDACTED_VALUE;
68
+ }
69
+ function redactSensitiveArray(values) {
70
+ const redacted = values.map(redactSensitiveStructure);
71
+ for (let index = 0; index < values.length - 1; index += 1) {
72
+ const flag = values[index];
73
+ if (typeof flag === "string" &&
74
+ (flag.trim() === "-u" || flag.trim() === "--user") &&
75
+ typeof values[index + 1] === "string") {
76
+ redacted[index + 1] = REDACTED_VALUE;
77
+ }
78
+ }
79
+ return redacted;
80
+ }
81
+ /**
82
+ * Redact credentials in JSON-like tool inputs without changing the caller's value.
83
+ * Adapter-specific parsing stays in each adapter; this function only handles secret values.
84
+ */
85
+ export function redactSensitiveStructure(value) {
86
+ if (typeof value === "string")
87
+ return redactSensitiveText(value);
88
+ if (Array.isArray(value))
89
+ return redactSensitiveArray(value);
90
+ if (typeof value !== "object" || value === null)
91
+ return value;
92
+ const redacted = {};
93
+ for (const [key, child] of Object.entries(value)) {
94
+ if (isSensitiveKey(key)) {
95
+ redacted[key] = REDACTED_VALUE;
96
+ }
97
+ else if ((normalizedKey(key) === "user" || normalizedKey(key) === "username") &&
98
+ typeof child === "string" &&
99
+ child.includes(":")) {
100
+ redacted[key] = REDACTED_VALUE;
101
+ }
102
+ else if (ENVIRONMENT_KEYS.has(normalizedKey(key))) {
103
+ redacted[key] = redactEnvironmentValue(child);
104
+ }
105
+ else {
106
+ redacted[key] = redactSensitiveStructure(child);
107
+ }
108
+ }
109
+ return redacted;
110
+ }
111
+ /** Redact recognizable credentials from shell commands, headers, and unstructured arguments. */
112
+ export function redactSensitiveText(text) {
113
+ return text
114
+ .replace(PRIVATE_KEY_PATTERN, REDACTED_VALUE)
115
+ .replace(AUTHORIZATION_PATTERN, `$1${REDACTED_VALUE}`)
116
+ .replace(API_HEADER_PATTERN, `$1${REDACTED_VALUE}`)
117
+ .replace(FLAG_PATTERN, `$1$2${REDACTED_VALUE}`)
118
+ .replace(USER_CREDENTIAL_PATTERN, `$1$2$3${REDACTED_VALUE}`)
119
+ .replace(URI_USERINFO_PATTERN, `$1$2:${REDACTED_VALUE}@`)
120
+ .replace(ASSIGNMENT_PATTERN, (match, key, separator) => isSensitiveKey(key) ? `${key}${separator}${REDACTED_VALUE}` : match)
121
+ .replace(JSON_LIKE_PATTERN, (match, quote, key, separator) => isSensitiveKey(key) ? `${quote}${key}${quote}${separator}${REDACTED_VALUE}` : match)
122
+ .replace(CREDENTIAL_PATTERN, REDACTED_VALUE);
123
+ }
124
+ /** Redact a source-recorded argument string, preserving valid JSON when it is JSON. */
125
+ export function redactSensitiveArgumentsText(text) {
126
+ try {
127
+ return JSON.stringify(redactSensitiveStructure(JSON.parse(text)));
128
+ }
129
+ catch {
130
+ return redactSensitiveText(text);
131
+ }
132
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Writing a canonical session into a new Pi session file (FR-41).
3
+ *
4
+ * Two shapes were deliberately not used:
5
+ *
6
+ * - a `compaction` entry for a summary turn. Pi's `buildContextEntries` treats the latest
7
+ * compaction as authoritative and drops everything before its `firstKeptEntryId`, so a
8
+ * summary written that way would hide every imported turn from the model.
9
+ * - a `toolCall` content block for a tool-call turn. Pi — and the provider behind it —
10
+ * expects a matching `toolResult`, and FR-24 dropped the body that result would need.
11
+ * An imported tool call is therefore one assistant text entry carrying its outcome line,
12
+ * which also gives T-PI-4 its "one message entry per canonical turn".
13
+ */
14
+ import type { CanonicalSession, ProvenanceMarker, SerializedSession, TargetProfile } from "./contract.js";
15
+ export interface PiSerializeDeps {
16
+ /** The repository the new session belongs to. Pi buckets its sessions by it. */
17
+ cwd(): string;
18
+ now(): Date;
19
+ newSessionId(): string;
20
+ newEntryId(): string;
21
+ }
22
+ export declare function serializeSession(session: CanonicalSession, target: TargetProfile, marker: ProvenanceMarker, deps: PiSerializeDeps): SerializedSession;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Writing a canonical session into a new Pi session file (FR-41).
3
+ *
4
+ * Two shapes were deliberately not used:
5
+ *
6
+ * - a `compaction` entry for a summary turn. Pi's `buildContextEntries` treats the latest
7
+ * compaction as authoritative and drops everything before its `firstKeptEntryId`, so a
8
+ * summary written that way would hide every imported turn from the model.
9
+ * - a `toolCall` content block for a tool-call turn. Pi — and the provider behind it —
10
+ * expects a matching `toolResult`, and FR-24 dropped the body that result would need.
11
+ * An imported tool call is therefore one assistant text entry carrying its outcome line,
12
+ * which also gives T-PI-4 its "one message entry per canonical turn".
13
+ */
14
+ import { join } from "node:path";
15
+ import { emptyUsage, IMPORTED_API, IMPORTED_MODEL, IMPORTED_PROVIDER, MARKER_CUSTOM_TYPE, PI_SESSION_VERSION, sessionDirFor, sessionFileName, toIsoUtc, } from "./format.js";
16
+ /** A turn with no visible text at all. The shape still needs one entry (T-PI-4). */
17
+ const EMPTY_TURN_TEXT = "(empty turn)";
18
+ function turnText(turn) {
19
+ const text = turn.kind === "tool-call" ? (turn.toolCall?.outcomeLine ?? "") : turn.text;
20
+ return text.trim().length > 0 ? text : EMPTY_TURN_TEXT;
21
+ }
22
+ export function serializeSession(session, target, marker, deps) {
23
+ const cwd = deps.cwd();
24
+ const sessionId = deps.newSessionId();
25
+ const createdAt = deps.now().toISOString();
26
+ const fallbackTime = toIsoUtc(session.provenance.updatedAt) ?? createdAt;
27
+ const header = {
28
+ type: "session",
29
+ version: PI_SESSION_VERSION,
30
+ id: sessionId,
31
+ timestamp: createdAt,
32
+ cwd,
33
+ };
34
+ const entries = [];
35
+ let parentId = null;
36
+ const chain = (entry, timestamp) => {
37
+ const id = deps.newEntryId();
38
+ entries.push({ ...entry, id, parentId, timestamp });
39
+ parentId = id;
40
+ };
41
+ // The marker first: Pi stores and shows it, and never sends it to the model (FR-47, FR-48).
42
+ chain({
43
+ type: "custom",
44
+ customType: MARKER_CUSTOM_TYPE,
45
+ data: {
46
+ sourceAgent: marker.sourceAgent,
47
+ sourceHome: marker.sourceHome,
48
+ sourceSessionId: marker.sourceSessionId,
49
+ importedAt: marker.importedAt,
50
+ droppedSummary: marker.droppedSummary,
51
+ lines: marker.lines,
52
+ },
53
+ }, createdAt);
54
+ for (const turn of session.turns) {
55
+ const timestamp = toIsoUtc(turn.timestamp) ?? fallbackTime;
56
+ const epoch = Date.parse(timestamp);
57
+ const text = turnText(turn);
58
+ if (turn.role === "user") {
59
+ const message = {
60
+ role: "user",
61
+ content: [{ type: "text", text }],
62
+ timestamp: epoch,
63
+ };
64
+ chain({ type: "message", message }, timestamp);
65
+ continue;
66
+ }
67
+ const message = {
68
+ role: "assistant",
69
+ content: [{ type: "text", text }],
70
+ api: IMPORTED_API,
71
+ provider: IMPORTED_PROVIDER,
72
+ model: IMPORTED_MODEL,
73
+ // Zero is honest — importing spent no tokens — and it is what C-11 demands: the
74
+ // object must be there, with numbers Pi can add up.
75
+ usage: emptyUsage(),
76
+ stopReason: "stop",
77
+ timestamp: epoch,
78
+ };
79
+ chain({ type: "message", message }, timestamp);
80
+ }
81
+ const lines = [header, ...entries].map((entry) => JSON.stringify(entry));
82
+ const file = {
83
+ absolutePath: join(sessionDirFor(target.home, cwd), sessionFileName(createdAt, sessionId)),
84
+ bytes: Buffer.from(`${lines.join("\n")}\n`, "utf8"),
85
+ };
86
+ return { sessionId, files: [file], itemCount: entries.length };
87
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The check that exists because of C-11 (FR-50).
3
+ *
4
+ * Pi reads six numbers of an assistant message's `usage` object without a guard —
5
+ * `usage.input`, `usage.output`, `usage.cacheRead`, `usage.cacheWrite`,
6
+ * `usage.totalTokens` and `usage.cost.total`. A missing `usage` stops Pi with
7
+ * `TypeError: Cannot read properties of undefined (reading 'input')`, and it happens
8
+ * after the file is already in the user's home. So the check runs before placement,
9
+ * on the bytes, on every message, and it reports every defect it finds.
10
+ *
11
+ * Zero is a valid value. `Number.isFinite` is the test, never truthiness.
12
+ */
13
+ import type { SerializedSession, ValidationDefect } from "./contract.js";
14
+ export declare function validateSerialized(serialized: SerializedSession): ValidationDefect[];
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The check that exists because of C-11 (FR-50).
3
+ *
4
+ * Pi reads six numbers of an assistant message's `usage` object without a guard —
5
+ * `usage.input`, `usage.output`, `usage.cacheRead`, `usage.cacheWrite`,
6
+ * `usage.totalTokens` and `usage.cost.total`. A missing `usage` stops Pi with
7
+ * `TypeError: Cannot read properties of undefined (reading 'input')`, and it happens
8
+ * after the file is already in the user's home. So the check runs before placement,
9
+ * on the bytes, on every message, and it reports every defect it finds.
10
+ *
11
+ * Zero is a valid value. `Number.isFinite` is the test, never truthiness.
12
+ */
13
+ import { asHeader, isRecord } from "./format.js";
14
+ /** The fields Pi dereferences without a guard (C-11). */
15
+ const REQUIRED_USAGE_NUMBERS = [
16
+ "input",
17
+ "output",
18
+ "cacheRead",
19
+ "cacheWrite",
20
+ "totalTokens",
21
+ ];
22
+ const CRASH = "Pi reads this without a guard and stops with an uncaught TypeError (C-11)";
23
+ function usageDefects(usage, path) {
24
+ if (!isRecord(usage)) {
25
+ return [
26
+ {
27
+ path,
28
+ message: `assistant message has no usage object. ${CRASH}`,
29
+ },
30
+ ];
31
+ }
32
+ const defects = [];
33
+ for (const field of REQUIRED_USAGE_NUMBERS) {
34
+ if (!Number.isFinite(usage[field])) {
35
+ defects.push({
36
+ path: `${path}/${field}`,
37
+ message: `usage.${field} is not a number. ${CRASH}`,
38
+ });
39
+ }
40
+ }
41
+ const cost = usage.cost;
42
+ if (!isRecord(cost)) {
43
+ defects.push({ path: `${path}/cost`, message: `usage.cost is not an object. ${CRASH}` });
44
+ }
45
+ else if (!Number.isFinite(cost.total)) {
46
+ defects.push({
47
+ path: `${path}/cost/total`,
48
+ message: `usage.cost.total is not a number. ${CRASH}`,
49
+ });
50
+ }
51
+ return defects;
52
+ }
53
+ export function validateSerialized(serialized) {
54
+ const defects = [];
55
+ if (serialized.files.length === 0) {
56
+ return [{ path: "files", message: "a Pi session needs one session file, and none was made" }];
57
+ }
58
+ let storedItems = 0;
59
+ for (const [fileIndex, file] of serialized.files.entries()) {
60
+ const prefix = serialized.files.length > 1 ? `files/${fileIndex}/` : "";
61
+ const lines = file.bytes
62
+ .toString("utf8")
63
+ .split("\n")
64
+ .filter((line) => line.trim().length > 0);
65
+ if (lines.length === 0) {
66
+ defects.push({ path: `${prefix}items`, message: "the session file is empty" });
67
+ continue;
68
+ }
69
+ for (const [index, line] of lines.entries()) {
70
+ const path = `${prefix}items/${index}`;
71
+ let value;
72
+ try {
73
+ value = JSON.parse(line);
74
+ }
75
+ catch {
76
+ defects.push({ path, message: "the line is not JSON, so Pi would skip or misread it" });
77
+ continue;
78
+ }
79
+ if (index === 0) {
80
+ const header = asHeader(value);
81
+ if (!header) {
82
+ defects.push({
83
+ path,
84
+ message: "the first line is not a Pi session header, so Pi cannot open the file",
85
+ });
86
+ }
87
+ else if (header.id !== serialized.sessionId) {
88
+ defects.push({
89
+ path: `${path}/id`,
90
+ message: `the header id ${header.id} is not the session id ${serialized.sessionId}`,
91
+ });
92
+ }
93
+ continue;
94
+ }
95
+ storedItems += 1;
96
+ if (!isRecord(value) || typeof value.type !== "string" || value.type.length === 0) {
97
+ defects.push({ path, message: "the entry has no type, so Pi cannot place it" });
98
+ continue;
99
+ }
100
+ if (typeof value.id !== "string" || value.id.length === 0) {
101
+ defects.push({ path: `${path}/id`, message: "the entry has no id, so Pi's tree breaks" });
102
+ }
103
+ if (!(typeof value.parentId === "string" || value.parentId === null)) {
104
+ defects.push({
105
+ path: `${path}/parentId`,
106
+ message: "the entry has no parentId, so Pi's tree breaks",
107
+ });
108
+ }
109
+ if (typeof value.timestamp !== "string" || Number.isNaN(Date.parse(value.timestamp))) {
110
+ defects.push({
111
+ path: `${path}/timestamp`,
112
+ message: "the entry has no ISO-8601 timestamp",
113
+ });
114
+ }
115
+ if (value.type !== "message")
116
+ continue;
117
+ const message = isRecord(value.message) ? value.message : null;
118
+ if (!message) {
119
+ defects.push({ path: `${path}/message`, message: "the message entry carries no message" });
120
+ continue;
121
+ }
122
+ if (message.role !== "assistant")
123
+ continue;
124
+ defects.push(...usageDefects(message.usage, `${path}/usage`));
125
+ }
126
+ }
127
+ if (storedItems !== serialized.itemCount) {
128
+ defects.push({
129
+ path: "itemCount",
130
+ message: `the session promises ${serialized.itemCount} items and the bytes hold ${storedItems}`,
131
+ });
132
+ }
133
+ return defects;
134
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The command binary. The Codex prompt file and the Claude Code slash-command file call it
4
+ * (C-1, FR-10), and it is a process wrapper and nothing more: read the two facts only the
5
+ * caller knows, hand the rest to the host, print what came back.
6
+ *
7
+ * The shim states which agent the binary is running inside. This file never guesses it from the
8
+ * environment, and never checks the name against a list of its own — an unknown agent is the
9
+ * registry's error to raise, with the agents it does know in the message (FR-56). That is what
10
+ * keeps FR-57's "one new folder and one line" true of this file too.
11
+ */
12
+ export declare const HELP_TEXT = "Usage: resume-from --target-agent <agent> [--target-home <path>] -- [<source-agent>] [<row> | <session-id> | <file-path>] [options]\n\nSelectors:\n no selector List sessions available for the current repository.\n <row> Select a numbered row from that list (1 or greater).\n <session-id> Select an exact session ID.\n <file-path> Select a session file by absolute, relative or ~/ path.\n\nOptions:\n --agent <name> Filter source sessions: pi, codex, claude, or claude-code.\n --home <path> Search one source home, even one outside the configuration.\n --confirm <token> Import the exact selection and preview identified by the token.\n --target-agent Target host ID; normally supplied by the installed plugin.\n --target-home Target agent home; omit to use that agent's default.\n -h, --help Show this help.\n\nExamples:\n resume-from --target-agent codex --\n resume-from --target-agent codex -- claude 2\n resume-from --target-agent claude-code -- session-id\n resume-from --target-agent codex -- ~/sessions/session.jsonl";
13
+ /** The invocation split in two: what the shim states, and what the user typed. */
14
+ export interface ShimArgs {
15
+ /** The agent the shim is running inside. Null when it did not say (FR-56). */
16
+ targetAgent: string | null;
17
+ /** The home that agent is running, or null to use the adapter's declared default (FR-3). */
18
+ targetHome: string | null;
19
+ /** Everything else, in the order it was given. The host's own parser reads it. */
20
+ rest: string[];
21
+ }
22
+ export type ShimArgsResult = {
23
+ ok: true;
24
+ value: ShimArgs;
25
+ } | {
26
+ ok: false;
27
+ problem: string;
28
+ };
29
+ /**
30
+ * Both forms of both flags are accepted (`--flag value` and `--flag=value`) because a shim is a
31
+ * text file a user edits. A flag with no value is left for the caller to report rather than
32
+ * silently swallowing the argument that follows it.
33
+ */
34
+ export declare function readShimArgs(argv: string[]): ShimArgsResult;
35
+ export declare function main(argv: string[], cwd: string): Promise<number>;
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The command binary. The Codex prompt file and the Claude Code slash-command file call it
4
+ * (C-1, FR-10), and it is a process wrapper and nothing more: read the two facts only the
5
+ * caller knows, hand the rest to the host, print what came back.
6
+ *
7
+ * The shim states which agent the binary is running inside. This file never guesses it from the
8
+ * environment, and never checks the name against a list of its own — an unknown agent is the
9
+ * registry's error to raise, with the agents it does know in the message (FR-56). That is what
10
+ * keeps FR-57's "one new folder and one line" true of this file too.
11
+ */
12
+ import { realpathSync } from "node:fs";
13
+ import { fileURLToPath } from "node:url";
14
+ import { runCommandBinary } from "./index.js";
15
+ const TARGET_AGENT = "--target-agent";
16
+ const TARGET_HOME = "--target-home";
17
+ const USER_ARGUMENTS = "--";
18
+ const USAGE = `Usage: resume-from ${TARGET_AGENT} <agent> [${TARGET_HOME} <path>] ${USER_ARGUMENTS} ` +
19
+ "[<source-agent>] [<row> | <session-id> | <file-path>] [options]";
20
+ export const HELP_TEXT = `${USAGE}
21
+
22
+ Selectors:
23
+ no selector List sessions available for the current repository.
24
+ <row> Select a numbered row from that list (1 or greater).
25
+ <session-id> Select an exact session ID.
26
+ <file-path> Select a session file by absolute, relative or ~/ path.
27
+
28
+ Options:
29
+ --agent <name> Filter source sessions: pi, codex, claude, or claude-code.
30
+ --home <path> Search one source home, even one outside the configuration.
31
+ --confirm <token> Import the exact selection and preview identified by the token.
32
+ --target-agent Target host ID; normally supplied by the installed plugin.
33
+ --target-home Target agent home; omit to use that agent's default.
34
+ -h, --help Show this help.
35
+
36
+ Examples:
37
+ resume-from --target-agent codex --
38
+ resume-from --target-agent codex -- claude 2
39
+ resume-from --target-agent claude-code -- session-id
40
+ resume-from --target-agent codex -- ~/sessions/session.jsonl`;
41
+ /**
42
+ * Both forms of both flags are accepted (`--flag value` and `--flag=value`) because a shim is a
43
+ * text file a user edits. A flag with no value is left for the caller to report rather than
44
+ * silently swallowing the argument that follows it.
45
+ */
46
+ export function readShimArgs(argv) {
47
+ let targetAgent = null;
48
+ let targetHome = null;
49
+ const separator = argv.indexOf(USER_ARGUMENTS);
50
+ if (separator < 0) {
51
+ return {
52
+ ok: false,
53
+ problem: `${USER_ARGUMENTS} is missing after the shim-owned target flags.`,
54
+ };
55
+ }
56
+ const trusted = argv.slice(0, separator);
57
+ const rest = argv.slice(separator + 1);
58
+ for (let index = 0; index < trusted.length; index += 1) {
59
+ const arg = trusted[index];
60
+ if (arg === undefined)
61
+ continue;
62
+ const named = [TARGET_AGENT, TARGET_HOME].find((flag) => arg === flag || arg.startsWith(`${flag}=`));
63
+ if (named === undefined) {
64
+ return {
65
+ ok: false,
66
+ problem: `Unexpected shim argument before ${USER_ARGUMENTS}: ${arg}.`,
67
+ };
68
+ }
69
+ const inline = arg.startsWith(`${named}=`);
70
+ const value = inline ? arg.slice(named.length + 1) : trusted[index + 1];
71
+ if (value === undefined || value === "" || value.startsWith("-")) {
72
+ return { ok: false, problem: `${named} needs one non-empty value.` };
73
+ }
74
+ if (!inline)
75
+ index += 1;
76
+ if (named === TARGET_AGENT) {
77
+ if (targetAgent !== null) {
78
+ return {
79
+ ok: false,
80
+ problem: `${TARGET_AGENT} was given twice by the shim.`,
81
+ };
82
+ }
83
+ targetAgent = value;
84
+ }
85
+ else {
86
+ if (targetHome !== null) {
87
+ return {
88
+ ok: false,
89
+ problem: `${TARGET_HOME} was given twice by the shim.`,
90
+ };
91
+ }
92
+ targetHome = value;
93
+ }
94
+ }
95
+ return { ok: true, value: { targetAgent, targetHome, rest } };
96
+ }
97
+ export async function main(argv, cwd) {
98
+ if (argv.includes("--help") || argv.includes("-h")) {
99
+ process.stdout.write(`${HELP_TEXT}\n`);
100
+ return 0;
101
+ }
102
+ const parsed = readShimArgs(argv);
103
+ if (!parsed.ok) {
104
+ process.stderr.write(`${parsed.problem}\n${USAGE}\nRun resume-from --help for details.\n`);
105
+ return 2;
106
+ }
107
+ const { targetAgent, targetHome, rest } = parsed.value;
108
+ if (targetAgent === null) {
109
+ process.stderr.write(`${TARGET_AGENT} is missing: the shim that calls this binary states which agent it is ` +
110
+ `running inside, because guessing it would import into the wrong agent.\n${USAGE}\n` +
111
+ "Run resume-from --help for selectors, options, and examples.\n");
112
+ return 2;
113
+ }
114
+ const outcome = await runCommandBinary({
115
+ argv: rest,
116
+ cwd,
117
+ // Unchecked on purpose: the registry knows which agents exist and says so (FR-56).
118
+ targetAgent: targetAgent,
119
+ targetHome: targetHome,
120
+ });
121
+ for (const line of outcome.stdout)
122
+ process.stdout.write(`${line}\n`);
123
+ for (const line of outcome.stderr)
124
+ process.stderr.write(`${line}\n`);
125
+ return outcome.exitCode;
126
+ }
127
+ /**
128
+ * Run only when this file is the program. `realpathSync` because the installed binary is reached
129
+ * through a symlink in `node_modules/.bin`, and the unresolved path would never match.
130
+ * `process.exitCode` rather than `process.exit`, so the streams above are flushed.
131
+ */
132
+ const invokedPath = process.argv[1];
133
+ if (invokedPath !== undefined && realpathSync(invokedPath) === fileURLToPath(import.meta.url)) {
134
+ process.exitCode = await main(process.argv.slice(2), process.cwd());
135
+ }
@@ -0,0 +1,10 @@
1
+ import type { AgentId, AgentRuntime, HandoverInstruction, HomePath, HostWiring, ImportPipeline, ImportRequest, LandingResult, SelectionInput, SessionId, SessionRef, TargetProfile } from "./host/contract.js";
2
+ export type { AgentId, AgentRuntime, HandoverInstruction, HomePath, HostWiring, ImportPipeline, ImportRequest, LandingResult, SelectionInput, SessionId, SessionRef, TargetProfile, };
3
+ /** The package entry point (FR-57: adding an agent never changes this file). */
4
+ export interface ResumeFrom {
5
+ /**
6
+ * Loads configuration, builds the agent list, and returns the host wiring.
7
+ * Called once by the command binary and once by the Pi extension.
8
+ */
9
+ createHost(): Promise<HostWiring>;
10
+ }
@@ -0,0 +1,4 @@
1
+ // GENERATED from src/module.md — the Public Contract section is the normative home.
2
+ // Declarations only: no behaviour, no defaults. If this file and module.md disagree,
3
+ // the document wins and this file is corrected.
4
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The agent list — the one place in the tree that knows which adapters exist (FR-57).
3
+ *
4
+ * It lives in the composition root rather than in `src/adapters/` because nothing depends on the
5
+ * composition root. A list inside `src/adapters/` would close the cycle
6
+ * list → `adapters/pi` → `import` → adapter port, and the Pi entry point could not exist.
7
+ *
8
+ * Adding an agent is a new folder under `src/adapters/`, a new value in `AgentId`, and one line
9
+ * below. There is no `switch` anywhere in this module for the same reason: a branch on the agent
10
+ * name would be a second place to edit.
11
+ */
12
+ import type { AgentAdapter, EstimatorFamily } from "./contract.js";
13
+ /** One agent, as the composition root needs it. */
14
+ export interface AgentEntry {
15
+ /**
16
+ * Builds the adapter. Called once per process, after the configuration is loaded, so a
17
+ * rejected configuration constructs nothing (FR-56).
18
+ */
19
+ create(): AgentAdapter;
20
+ /**
21
+ * How tokens are counted for an import *into* this agent. The budget is a share of the target
22
+ * window (FR-30), so the counting rule is a property of the target. `AgentCapabilities` does
23
+ * not declare it, so it is stated here, where the implementation is already named — which
24
+ * keeps adding an agent a one-line change.
25
+ */
26
+ family: EstimatorFamily;
27
+ }
28
+ /** The agent list. Adding an agent adds one line here (FR-57). */
29
+ export declare const AGENTS: readonly AgentEntry[];
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The agent list — the one place in the tree that knows which adapters exist (FR-57).
3
+ *
4
+ * It lives in the composition root rather than in `src/adapters/` because nothing depends on the
5
+ * composition root. A list inside `src/adapters/` would close the cycle
6
+ * list → `adapters/pi` → `import` → adapter port, and the Pi entry point could not exist.
7
+ *
8
+ * Adding an agent is a new folder under `src/adapters/`, a new value in `AgentId`, and one line
9
+ * below. There is no `switch` anywhere in this module for the same reason: a branch on the agent
10
+ * name would be a second place to edit.
11
+ */
12
+ import { claudeCodeAdapter } from "../adapters/claude-code/index.js";
13
+ import { codexAdapterFactory } from "../adapters/codex/index.js";
14
+ import { piAdapterFactory } from "../adapters/pi/index.js";
15
+ /** The agent list. Adding an agent adds one line here (FR-57). */
16
+ export const AGENTS = [
17
+ { create: () => piAdapterFactory.create(), family: "claude" },
18
+ { create: () => codexAdapterFactory.create(), family: "gpt" },
19
+ { create: () => claudeCodeAdapter.create(), family: "claude" },
20
+ ];
@@ -0,0 +1,21 @@
1
+ import type { AgentId, HomePath, SelectionInput } from "./contract.js";
2
+ export declare const USAGE = "Usage: /resume-from [<agent>] [<row> | <session-id> | <file-path>] [--home <path>] [--agent <name>] [--confirm <token>]";
3
+ /** The one confirmation flag. `runner.ts` strips it from re-run hints with the same predicate. */
4
+ export declare const CONFIRM_FLAG = "--confirm";
5
+ /** True for both accepted forms: `--confirm <token>` and `--confirm=<token>`. */
6
+ export declare function isConfirmFlag(arg: string): boolean;
7
+ export interface ParsedArgs {
8
+ /** Null when the user asked for the list (FR-10). */
9
+ selection: SelectionInput | null;
10
+ onlyAgent: AgentId | null;
11
+ onlyHome: HomePath | null;
12
+ confirmationToken: string | null;
13
+ }
14
+ export type ParseResult = {
15
+ ok: true;
16
+ args: ParsedArgs;
17
+ } | {
18
+ ok: false;
19
+ problem: string;
20
+ };
21
+ export declare function parseArgs(argv: string[], cwd: string): ParseResult;