@method-ai-hq/sdk 0.0.0-stage → 0.14.2

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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -2
  3. package/dist/packages/contracts/src/identity.d.ts +16 -0
  4. package/dist/packages/contracts/src/identity.js +41 -0
  5. package/dist/packages/contracts/src/method-package.d.ts +71 -0
  6. package/dist/packages/contracts/src/method-package.js +22 -0
  7. package/dist/packages/method-document/src/conversion-report.d.ts +138 -0
  8. package/dist/packages/method-document/src/conversion-report.js +23 -0
  9. package/dist/packages/sdk/examples/daily-briefing/README.md +31 -0
  10. package/dist/packages/sdk/examples/daily-briefing/TASK.md +3 -0
  11. package/dist/packages/sdk/examples/daily-briefing/approved-report.md +138 -0
  12. package/dist/packages/sdk/examples/daily-briefing/briefing_artifacts.py +39 -0
  13. package/dist/packages/sdk/examples/daily-briefing/briefing_check_inputs.py +47 -0
  14. package/dist/packages/sdk/examples/daily-briefing/briefing_files.py +31 -0
  15. package/dist/packages/sdk/examples/daily-briefing/briefing_manifest.py +23 -0
  16. package/dist/packages/sdk/examples/daily-briefing/briefing_markdown.py +67 -0
  17. package/dist/packages/sdk/examples/daily-briefing/briefing_progress.py +18 -0
  18. package/dist/packages/sdk/examples/daily-briefing/briefing_render.py +71 -0
  19. package/dist/packages/sdk/examples/daily-briefing/briefing_sessions.py +37 -0
  20. package/dist/packages/sdk/examples/daily-briefing/briefing_times.py +158 -0
  21. package/dist/packages/sdk/examples/daily-briefing/briefing_validation.py +101 -0
  22. package/dist/packages/sdk/examples/daily-briefing/briefing_website.py +215 -0
  23. package/dist/packages/sdk/examples/daily-briefing/checks.json +19 -0
  24. package/dist/packages/sdk/examples/daily-briefing/daily-briefing.method +158 -0
  25. package/dist/packages/sdk/examples/daily-briefing/inputs.json +1 -0
  26. package/dist/packages/sdk/examples/daily-briefing/package-lock.json +10 -0
  27. package/dist/packages/sdk/examples/daily-briefing/package.json +4 -0
  28. package/dist/packages/sdk/examples/daily-briefing/pyproject.toml +8 -0
  29. package/dist/packages/sdk/examples/daily-briefing/reader/reader.css +35 -0
  30. package/dist/packages/sdk/examples/daily-briefing/reader/reader.js +42 -0
  31. package/dist/packages/sdk/examples/daily-briefing/sample/context.json +1 -0
  32. package/dist/packages/sdk/examples/daily-briefing/sample/documents/document-001.md +219 -0
  33. package/dist/packages/sdk/examples/daily-briefing/sample/media.json +1 -0
  34. package/dist/packages/sdk/examples/daily-briefing/sample/metadata/documents.json +39 -0
  35. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0001.json +49 -0
  36. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0002.json +49 -0
  37. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0003.json +49 -0
  38. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0004.json +49 -0
  39. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0005.json +49 -0
  40. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0006.json +49 -0
  41. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0007.json +49 -0
  42. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0008.json +49 -0
  43. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0009.json +49 -0
  44. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0010.json +49 -0
  45. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0011.json +49 -0
  46. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0012.json +49 -0
  47. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0013.json +49 -0
  48. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0014.json +49 -0
  49. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0015.json +49 -0
  50. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0016.json +49 -0
  51. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0017.json +49 -0
  52. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0018.json +49 -0
  53. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0019.json +49 -0
  54. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0020.json +49 -0
  55. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0021.json +49 -0
  56. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0022.json +49 -0
  57. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0023.json +49 -0
  58. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0024.json +49 -0
  59. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0025.json +49 -0
  60. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0026.json +49 -0
  61. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0027.json +49 -0
  62. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0028.json +49 -0
  63. package/dist/packages/sdk/examples/daily-briefing/sample/sessions.json +1 -0
  64. package/dist/packages/sdk/examples/daily-briefing/sample/timeline.jsonl +28 -0
  65. package/dist/packages/sdk/examples/daily-briefing/sample/untimed.jsonl +0 -0
  66. package/dist/packages/sdk/examples/daily-briefing/sample-output.zip +0 -0
  67. package/dist/packages/sdk/examples/daily-briefing/sample-report.md +51 -0
  68. package/dist/packages/sdk/examples/daily-briefing/uv.lock +128 -0
  69. package/dist/packages/sdk/examples/daily-briefing/vendor/marked-LICENSE.md +44 -0
  70. package/dist/packages/sdk/examples/daily-briefing/vendor/marked.mjs +77 -0
  71. package/dist/packages/sdk/examples/files.json +126 -0
  72. package/dist/packages/sdk/examples/message-routing/README.md +23 -0
  73. package/dist/packages/sdk/examples/message-routing/TASK.md +5 -0
  74. package/dist/packages/sdk/examples/message-routing/choose-destination.mjs +10 -0
  75. package/dist/packages/sdk/examples/message-routing/create-ticket.mjs +15 -0
  76. package/dist/packages/sdk/examples/message-routing/inputs.json +1 -0
  77. package/dist/packages/sdk/examples/message-routing/message-routing.method +45 -0
  78. package/dist/packages/sdk/examples/message-routing/result.fixture.json +6 -0
  79. package/dist/packages/sdk/examples/message-routing/routing.mjs +29 -0
  80. package/dist/packages/sdk/examples/message-routing/routing.test.mjs +27 -0
  81. package/dist/packages/sdk/examples/message-routing/ticket.method +88 -0
  82. package/dist/packages/sdk/examples/message-routing/ticket.test.mjs +64 -0
  83. package/dist/packages/sdk/examples/notes-summary/README.md +23 -0
  84. package/dist/packages/sdk/examples/notes-summary/TASK.md +3 -0
  85. package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/case.json +36 -0
  86. package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/examples.json +16 -0
  87. package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/files/0 +10 -0
  88. package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/recording.json +195 -0
  89. package/dist/packages/sdk/examples/notes-summary/notes/2026-10-05-standup.md +4 -0
  90. package/dist/packages/sdk/examples/notes-summary/notes/2026-10-07-planning.md +3 -0
  91. package/dist/packages/sdk/examples/notes-summary/notes-summary.method +54 -0
  92. package/dist/packages/sdk/examples/notes-summary/out/.keep +0 -0
  93. package/dist/packages/sdk/examples/notes-summary/read-notes.mjs +4 -0
  94. package/dist/packages/sdk/examples/notes-summary/save.mjs +7 -0
  95. package/dist/packages/sdk/examples/outbound-management/README.md +119 -0
  96. package/dist/packages/sdk/examples/outbound-management/TASK.md +5 -0
  97. package/dist/packages/sdk/examples/outbound-management/check_plan.py +16 -0
  98. package/dist/packages/sdk/examples/outbound-management/check_sources.py +14 -0
  99. package/dist/packages/sdk/examples/outbound-management/crm.py +153 -0
  100. package/dist/packages/sdk/examples/outbound-management/crm_test.py +141 -0
  101. package/dist/packages/sdk/examples/outbound-management/fixtures/README.md +15 -0
  102. package/dist/packages/sdk/examples/outbound-management/fixtures/crm.json +54 -0
  103. package/dist/packages/sdk/examples/outbound-management/fixtures/day-1/messages.json +29 -0
  104. package/dist/packages/sdk/examples/outbound-management/fixtures/day-2/messages.json +38 -0
  105. package/dist/packages/sdk/examples/outbound-management/fixtures/observations.json +12 -0
  106. package/dist/packages/sdk/examples/outbound-management/fixtures/plan.json +69 -0
  107. package/dist/packages/sdk/examples/outbound-management/fixtures/receipt.json +1 -0
  108. package/dist/packages/sdk/examples/outbound-management/fixtures/state.json +99 -0
  109. package/dist/packages/sdk/examples/outbound-management/fixtures/tasks.md +16 -0
  110. package/dist/packages/sdk/examples/outbound-management/inputs.json +4 -0
  111. package/dist/packages/sdk/examples/outbound-management/outbound.method +277 -0
  112. package/dist/packages/sdk/examples/outbound-management/read_crm.py +4 -0
  113. package/dist/packages/sdk/examples/outbound-management/save_day.py +4 -0
  114. package/dist/packages/sdk/examples/outbound-management/starter/crm.json +11 -0
  115. package/dist/packages/sdk/examples/social-briefing/README.md +41 -0
  116. package/dist/packages/sdk/examples/social-briefing/TASK.md +5 -0
  117. package/dist/packages/sdk/examples/social-briefing/inputs.json +5 -0
  118. package/dist/packages/sdk/examples/social-briefing/result.fixture.json +14 -0
  119. package/dist/packages/sdk/examples/social-briefing/social-briefing.method +110 -0
  120. package/dist/packages/sdk/examples/support-triage/README.md +77 -0
  121. package/dist/packages/sdk/examples/support-triage/TASK.md +3 -0
  122. package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/case.json +40 -0
  123. package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/recording.json +460 -0
  124. package/dist/packages/sdk/examples/support-triage/fake-helpdesk.mjs +38 -0
  125. package/dist/packages/sdk/examples/support-triage/helpdesk.mjs +33 -0
  126. package/dist/packages/sdk/examples/support-triage/inputs.json +1 -0
  127. package/dist/packages/sdk/examples/support-triage/read-ticket.mjs +8 -0
  128. package/dist/packages/sdk/examples/support-triage/route.mjs +4 -0
  129. package/dist/packages/sdk/examples/support-triage/sample-tickets.json +5 -0
  130. package/dist/packages/sdk/examples/support-triage/set-ticket.mjs +9 -0
  131. package/dist/packages/sdk/examples/support-triage/support-triage.method +165 -0
  132. package/dist/packages/sdk/examples/support-triage/triage.mjs +38 -0
  133. package/dist/packages/sdk/examples/support-triage/triage.test.mjs +68 -0
  134. package/dist/packages/sdk/observers/mail.delivery/fetch.mjs +87 -0
  135. package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce-by-recipient.json +1 -0
  136. package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce.json +1 -0
  137. package/dist/packages/sdk/observers/mail.delivery/fixtures/delayed.json +1 -0
  138. package/dist/packages/sdk/observers/mail.delivery/fixtures/delivered.json +1 -0
  139. package/dist/packages/sdk/observers/mail.delivery/fixtures/empty.json +1 -0
  140. package/dist/packages/sdk/observers/mail.delivery/fixtures/other-message.json +1 -0
  141. package/dist/packages/sdk/observers/mail.delivery/judge.mjs +13 -0
  142. package/dist/packages/sdk/observers/mail.delivery/observer.json +14 -0
  143. package/dist/packages/sdk/package.json +232 -0
  144. package/dist/packages/sdk/src/authoring-example.d.ts +91 -0
  145. package/dist/packages/sdk/src/authoring-example.js +50 -0
  146. package/dist/packages/sdk/src/authoring-instructions.d.ts +20 -0
  147. package/dist/packages/sdk/src/authoring-instructions.js +151 -0
  148. package/dist/packages/sdk/src/authoring-prompt.d.ts +2 -0
  149. package/dist/packages/sdk/src/authoring-prompt.js +8 -0
  150. package/dist/packages/sdk/src/authoring.d.ts +10 -0
  151. package/dist/packages/sdk/src/authoring.js +287 -0
  152. package/dist/packages/sdk/src/bindings.d.ts +4 -0
  153. package/dist/packages/sdk/src/bindings.js +124 -0
  154. package/dist/packages/sdk/src/bridge.d.ts +2 -0
  155. package/dist/packages/sdk/src/bridge.js +23 -0
  156. package/dist/packages/sdk/src/browser-runtime/pyproject.toml +5 -0
  157. package/dist/packages/sdk/src/browser-runtime/tools.json +189 -0
  158. package/dist/packages/sdk/src/browser-runtime/uv.lock +3894 -0
  159. package/dist/packages/sdk/src/browser-service.py +127 -0
  160. package/dist/packages/sdk/src/browser.d.ts +38 -0
  161. package/dist/packages/sdk/src/browser.js +203 -0
  162. package/dist/packages/sdk/src/capabilities.d.ts +13 -0
  163. package/dist/packages/sdk/src/capabilities.js +68 -0
  164. package/dist/packages/sdk/src/classification-client.d.ts +4 -0
  165. package/dist/packages/sdk/src/classification-client.js +27 -0
  166. package/dist/packages/sdk/src/cloud.d.ts +23 -0
  167. package/dist/packages/sdk/src/cloud.js +43 -0
  168. package/dist/packages/sdk/src/computer-settings.d.ts +28 -0
  169. package/dist/packages/sdk/src/computer-settings.js +57 -0
  170. package/dist/packages/sdk/src/connect.d.ts +47 -0
  171. package/dist/packages/sdk/src/connect.js +203 -0
  172. package/dist/packages/sdk/src/current-inspection.d.ts +3 -0
  173. package/dist/packages/sdk/src/current-inspection.js +146 -0
  174. package/dist/packages/sdk/src/current-runtime.d.ts +14 -0
  175. package/dist/packages/sdk/src/current-runtime.js +167 -0
  176. package/dist/packages/sdk/src/doctor.d.ts +17 -0
  177. package/dist/packages/sdk/src/doctor.js +68 -0
  178. package/dist/packages/sdk/src/executable.d.ts +2 -0
  179. package/dist/packages/sdk/src/executable.js +22 -0
  180. package/dist/packages/sdk/src/explain-effects.d.ts +32 -0
  181. package/dist/packages/sdk/src/explain-effects.js +192 -0
  182. package/dist/packages/sdk/src/explain.d.ts +115 -0
  183. package/dist/packages/sdk/src/explain.js +441 -0
  184. package/dist/packages/sdk/src/files.d.ts +9 -0
  185. package/dist/packages/sdk/src/files.js +50 -0
  186. package/dist/packages/sdk/src/guard-keys.d.ts +5 -0
  187. package/dist/packages/sdk/src/guard-keys.js +42 -0
  188. package/dist/packages/sdk/src/hosted-models.d.ts +49 -0
  189. package/dist/packages/sdk/src/hosted-models.js +81 -0
  190. package/dist/packages/sdk/src/improve.d.ts +55 -0
  191. package/dist/packages/sdk/src/improve.js +159 -0
  192. package/dist/packages/sdk/src/index.d.ts +13 -0
  193. package/dist/packages/sdk/src/index.js +13 -0
  194. package/dist/packages/sdk/src/inspect.d.ts +6 -0
  195. package/dist/packages/sdk/src/inspect.js +17 -0
  196. package/dist/packages/sdk/src/link.d.ts +12 -0
  197. package/dist/packages/sdk/src/link.js +53 -0
  198. package/dist/packages/sdk/src/local-cli.d.ts +24 -0
  199. package/dist/packages/sdk/src/local-cli.js +146 -0
  200. package/dist/packages/sdk/src/local-setup.d.ts +15 -0
  201. package/dist/packages/sdk/src/local-setup.js +42 -0
  202. package/dist/packages/sdk/src/method-client.d.ts +30 -0
  203. package/dist/packages/sdk/src/method-client.js +250 -0
  204. package/dist/packages/sdk/src/method-files.d.ts +29 -0
  205. package/dist/packages/sdk/src/method-files.js +93 -0
  206. package/dist/packages/sdk/src/method-help.d.ts +19 -0
  207. package/dist/packages/sdk/src/method-help.js +404 -0
  208. package/dist/packages/sdk/src/method-issues.d.ts +86 -0
  209. package/dist/packages/sdk/src/method-issues.js +192 -0
  210. package/dist/packages/sdk/src/method-sync.d.ts +46 -0
  211. package/dist/packages/sdk/src/method-sync.js +293 -0
  212. package/dist/packages/sdk/src/method.d.ts +5 -0
  213. package/dist/packages/sdk/src/method.js +359 -0
  214. package/dist/packages/sdk/src/model-checks.d.ts +96 -0
  215. package/dist/packages/sdk/src/model-checks.js +181 -0
  216. package/dist/packages/sdk/src/outbox.d.ts +120 -0
  217. package/dist/packages/sdk/src/outbox.js +297 -0
  218. package/dist/packages/sdk/src/own-model-key.d.ts +22 -0
  219. package/dist/packages/sdk/src/own-model-key.js +76 -0
  220. package/dist/packages/sdk/src/prepare.d.ts +24 -0
  221. package/dist/packages/sdk/src/prepare.js +245 -0
  222. package/dist/packages/sdk/src/process.d.ts +15 -0
  223. package/dist/packages/sdk/src/process.js +107 -0
  224. package/dist/packages/sdk/src/production.d.ts +182 -0
  225. package/dist/packages/sdk/src/production.js +284 -0
  226. package/dist/packages/sdk/src/progress.d.ts +12 -0
  227. package/dist/packages/sdk/src/progress.js +48 -0
  228. package/dist/packages/sdk/src/proposals.d.ts +73 -0
  229. package/dist/packages/sdk/src/proposals.js +284 -0
  230. package/dist/packages/sdk/src/quality.d.ts +82 -0
  231. package/dist/packages/sdk/src/quality.js +294 -0
  232. package/dist/packages/sdk/src/result-files.d.ts +5 -0
  233. package/dist/packages/sdk/src/result-files.js +149 -0
  234. package/dist/packages/sdk/src/result-transfer.d.ts +21 -0
  235. package/dist/packages/sdk/src/result-transfer.js +37 -0
  236. package/dist/packages/sdk/src/run-data.d.ts +11 -0
  237. package/dist/packages/sdk/src/run-data.js +80 -0
  238. package/dist/packages/sdk/src/run-method.d.ts +2 -0
  239. package/dist/packages/sdk/src/run-method.js +2 -0
  240. package/dist/packages/sdk/src/run-saved.d.ts +5 -0
  241. package/dist/packages/sdk/src/run-saved.js +138 -0
  242. package/dist/packages/sdk/src/run-worker.d.ts +6 -0
  243. package/dist/packages/sdk/src/run-worker.js +131 -0
  244. package/dist/packages/sdk/src/secrets.d.ts +37 -0
  245. package/dist/packages/sdk/src/secrets.js +196 -0
  246. package/dist/packages/sdk/src/update.d.ts +5 -0
  247. package/dist/packages/sdk/src/update.js +39 -0
  248. package/dist/packages/sdk/src/versions.d.ts +58 -0
  249. package/dist/packages/sdk/src/versions.js +144 -0
  250. package/dist/packages/sdk/src/worker.d.ts +120 -0
  251. package/dist/packages/sdk/src/worker.js +443 -0
  252. package/dist/packages/workflow-language/src/execution.d.ts +1 -0
  253. package/dist/packages/workflow-language/src/execution.js +1 -0
  254. package/dist/packages/workflow-language/src/inspection.d.ts +990 -0
  255. package/dist/packages/workflow-language/src/inspection.js +130 -0
  256. package/dist/packages/workflow-language/src/result-files.d.ts +160 -0
  257. package/dist/packages/workflow-language/src/result-files.js +67 -0
  258. package/dist/packages/workflow-language/src/schema.d.ts +207 -0
  259. package/dist/packages/workflow-language/src/schema.js +56 -0
  260. package/dist/packages/workflow-language/src/validate.d.ts +11 -0
  261. package/dist/packages/workflow-language/src/validate.js +77 -0
  262. package/package.json +230 -4
@@ -0,0 +1,68 @@
1
+ import { execFile } from "node:child_process";
2
+ import { closeSync, existsSync, mkdirSync, openSync, rmdirSync, unlinkSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ import { resolveExecutable } from "./executable.js";
6
+ export const MINIMUM_CODEX_VERSION = "0.153.0";
7
+ export const MINIMUM_NODE_MAJOR = 22;
8
+ function execute(command, args) {
9
+ return new Promise(done => {
10
+ execFile(command, args, { timeout: 30_000, maxBuffer: 4_000_000, windowsHide: true }, (error, stdout, stderr) => {
11
+ const failure = error;
12
+ done({ code: failure ? typeof failure.code === "number" ? failure.code : null : 0,
13
+ stdout: String(stdout), stderr: String(stderr), missing: failure?.code === "ENOENT" });
14
+ });
15
+ });
16
+ }
17
+ export function parseVersion(text) {
18
+ return text.match(/(\d+)\.(\d+)\.(\d+)/)?.slice(1, 4).join(".") ?? null;
19
+ }
20
+ /** Negative when left is older than right. */
21
+ export function compareVersions(left, right) {
22
+ const a = left.split(".").map(Number), b = right.split(".").map(Number);
23
+ for (let i = 0; i < 3; i++)
24
+ if ((a[i] ?? 0) !== (b[i] ?? 0))
25
+ return (a[i] ?? 0) - (b[i] ?? 0);
26
+ return 0;
27
+ }
28
+ const chatgpt = "Install or update the Codex CLI";
29
+ const again = "then check with `codex --version` and run this command again.";
30
+ export function checkNode(version = process.versions.node) {
31
+ const major = Number(version.split(".")[0]);
32
+ if (major >= MINIMUM_NODE_MAJOR)
33
+ return { ok: true, detail: `Node.js v${version}` };
34
+ return { ok: false, detail: `Node.js v${version} is running, but Method needs Node.js ${MINIMUM_NODE_MAJOR} or later. Install the current Node.js from nodejs.org, then run this command again.` };
35
+ }
36
+ /** Requires codex ${MINIMUM_CODEX_VERSION}+ with `codex exec --dangerously-bypass-approvals-and-sandbox`. */
37
+ export async function checkCodex(command = "codex") {
38
+ command = resolveExecutable(command);
39
+ const version = await execute(command, ["--version"]);
40
+ if (version.missing)
41
+ return { ok: false, detail: `The Codex CLI was not found on PATH. ${chatgpt}: ${again}` };
42
+ const found = parseVersion(version.stdout);
43
+ if (version.code !== 0 || !found)
44
+ return { ok: false, detail: `\`codex --version\` did not print a version (${version.code === null ? "no exit code" : `exit ${version.code}`}${version.stderr.trim() ? `: ${version.stderr.trim().slice(0, 300)}` : ""}). ${chatgpt}: ${again}` };
45
+ if (compareVersions(found, MINIMUM_CODEX_VERSION) < 0)
46
+ return { ok: false, detail: `Codex CLI ${found} is installed, but ${MINIMUM_CODEX_VERSION} or later is required. ${chatgpt}: ${again}` };
47
+ const help = await execute(command, ["exec", "--help"]);
48
+ if (help.code !== 0 || !help.stdout.includes("--dangerously-bypass-approvals-and-sandbox"))
49
+ return { ok: false, detail: `Codex CLI ${found} does not support \`codex exec --dangerously-bypass-approvals-and-sandbox\`, which the local runner needs. ${chatgpt}: ${again}` };
50
+ return { ok: true, detail: `Codex CLI ${found} with \`codex exec --dangerously-bypass-approvals-and-sandbox\`` };
51
+ }
52
+ /** The default run directory root must accept a private file. */
53
+ export function checkRunDirectory(root) {
54
+ const directory = resolve(root, ".method-runs");
55
+ const created = !existsSync(directory);
56
+ try {
57
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
58
+ const probe = join(directory, `.probe-${randomUUID()}`);
59
+ closeSync(openSync(probe, "wx", 0o600));
60
+ unlinkSync(probe);
61
+ if (created)
62
+ rmdirSync(directory);
63
+ return { ok: true, detail: `Run directory ${directory} is writable` };
64
+ }
65
+ catch (error) {
66
+ return { ok: false, detail: `Cannot write to ${directory}: ${error instanceof Error ? error.message : String(error)}. Run this command from a directory you can write to.` };
67
+ }
68
+ }
@@ -0,0 +1,2 @@
1
+ /** Keep bundled helpers beside their executable when a PATH entry is a symlink. */
2
+ export declare function resolveExecutable(command: string): string;
@@ -0,0 +1,22 @@
1
+ import { accessSync, constants, realpathSync, statSync } from "node:fs";
2
+ import { delimiter, isAbsolute, join, resolve } from "node:path";
3
+ /** Keep bundled helpers beside their executable when a PATH entry is a symlink. */
4
+ export function resolveExecutable(command) {
5
+ const candidates = isAbsolute(command) || command.includes("/") || command.includes("\\")
6
+ ? [resolve(command)]
7
+ : (process.env.PATH ?? "").split(delimiter).flatMap(directory => {
8
+ const path = join(directory || process.cwd(), command);
9
+ return process.platform === "win32" && !/\.[^\\/]+$/.test(command)
10
+ ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map(extension => path + extension)
11
+ : [path];
12
+ });
13
+ for (const path of candidates) {
14
+ try {
15
+ accessSync(path, constants.X_OK);
16
+ if (statSync(path).isFile())
17
+ return realpathSync(path);
18
+ }
19
+ catch { /* Let the process launcher report a missing command. */ }
20
+ }
21
+ return command;
22
+ }
@@ -0,0 +1,32 @@
1
+ /** What code analysis finds in a script. A model never adds to it. */
2
+ export type ScriptEffects = {
3
+ network: string[];
4
+ secrets: string[];
5
+ env: string[];
6
+ reads: string[];
7
+ writes: string[];
8
+ runs: string[];
9
+ input_fields: string[];
10
+ output_fields: string[];
11
+ };
12
+ /** Facts that the card does not show but that the prompt and the entity check use. */
13
+ export type ScriptFacts = {
14
+ stdin_keys: string[];
15
+ output_keys: string[];
16
+ };
17
+ export type Source = {
18
+ path: string;
19
+ text: string;
20
+ };
21
+ export type Language = 'python' | 'node';
22
+ export declare function scriptLanguage(entrypoint: string): Language | undefined;
23
+ export declare function hostOf(url: string): string | undefined;
24
+ /**
25
+ * Effects of a `run` step's entrypoint and declared helper files. Input and output fields come from the step's
26
+ * declaration; secrets are the declared secrets whose names appear in the code.
27
+ */
28
+ export declare function staticEffects(language: Language, sources: Source[], step: any, secrets?: string[]): {
29
+ effects: ScriptEffects;
30
+ facts: ScriptFacts;
31
+ errors: string[];
32
+ };
@@ -0,0 +1,192 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ export function scriptLanguage(entrypoint) {
3
+ if (/\.py$/i.test(entrypoint))
4
+ return 'python';
5
+ if (/\.(?:js|mjs|cjs|ts|mts|cts)$/i.test(entrypoint))
6
+ return 'node';
7
+ return undefined;
8
+ }
9
+ const sorted = (values) => [...new Set([...values].filter(Boolean))].sort();
10
+ export function hostOf(url) {
11
+ try {
12
+ return new URL(url).hostname.toLowerCase() || undefined;
13
+ }
14
+ catch {
15
+ return /^https?:\/\/([^/:?#\s'"`]+)/i.exec(url)?.[1]?.toLowerCase();
16
+ }
17
+ }
18
+ // The Python analysis uses the ast module of the local python3. The script reads [{path, text}] on stdin.
19
+ const pythonAnalysis = String.raw `
20
+ import ast, json, re, sys
21
+ from urllib.parse import urlparse
22
+ sources = json.load(sys.stdin)
23
+ found = {k: set() for k in ("hosts", "env", "reads", "writes", "runs", "stdin_keys", "output_keys")}
24
+ errors = []
25
+ URL = re.compile(r"https?://[^\s'\"<>{}]+")
26
+ HTTP = {"get", "post", "put", "patch", "delete", "head", "request", "stream", "urlopen", "Request", "Client", "AsyncClient"}
27
+ RUNS = {"run", "call", "check_call", "check_output", "Popen", "system", "popen"}
28
+ def text(node, names):
29
+ if isinstance(node, ast.Constant) and isinstance(node.value, str): return node.value
30
+ if isinstance(node, ast.JoinedStr):
31
+ out = ""
32
+ for value in node.values:
33
+ if isinstance(value, ast.Constant) and isinstance(value.value, str): out += value.value
34
+ else: return out if out.startswith("http") else None
35
+ return out
36
+ if isinstance(node, ast.Name) and node.id in names: return names[node.id]
37
+ if isinstance(node, ast.Call) and dotted(node.func) in ("Path", "pathlib.Path") and node.args: return text(node.args[0], names)
38
+ return None
39
+ def dotted(node):
40
+ if isinstance(node, ast.Name): return node.id
41
+ if isinstance(node, ast.Attribute):
42
+ base = dotted(node.value)
43
+ return base + "." + node.attr if base else node.attr
44
+ if isinstance(node, ast.Call): return dotted(node.func) + "()"
45
+ return ""
46
+ def host(url):
47
+ try: return (urlparse(url).hostname or "").lower()
48
+ except Exception: return ""
49
+ def is_stdin_load(node):
50
+ if not isinstance(node, ast.Call): return False
51
+ name = dotted(node.func)
52
+ if name == "json.load" and node.args and dotted(node.args[0]) == "sys.stdin": return True
53
+ if name == "json.loads" and node.args and dotted(node.args[0]) in ("sys.stdin.read()", "input()", "sys.stdin.buffer.read()"): return True
54
+ return False
55
+ def dict_keys(node):
56
+ return [k.value for k in node.keys if isinstance(k, ast.Constant) and isinstance(k.value, str)] if isinstance(node, ast.Dict) else []
57
+ for source in sources:
58
+ try: tree = ast.parse(source["text"], filename=source["path"])
59
+ except SyntaxError as error:
60
+ errors.append(f"{source['path']}: {error.msg} (line {error.lineno})"); continue
61
+ names, inputs, dicts, printed = {}, set(), {}, set()
62
+ for node in ast.walk(tree):
63
+ if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
64
+ target = node.targets[0].id
65
+ value = text(node.value, {})
66
+ if value is not None: names[target] = value
67
+ if is_stdin_load(node.value): inputs.add(target)
68
+ if isinstance(node.value, ast.Dict): dicts.setdefault(target, set()).update(dict_keys(node.value))
69
+ for node in ast.walk(tree):
70
+ if isinstance(node, ast.Constant) and isinstance(node.value, str):
71
+ for url in URL.findall(node.value):
72
+ if host(url): found["hosts"].add(host(url))
73
+ if isinstance(node, ast.JoinedStr):
74
+ value = text(node, {})
75
+ if value and host(value): found["hosts"].add(host(value))
76
+ if isinstance(node, ast.Subscript):
77
+ base = dotted(node.value)
78
+ key = node.slice.value if isinstance(node.slice, ast.Constant) and isinstance(node.slice.value, str) else None
79
+ if key is None: continue
80
+ if base in ("os.environ", "environ"): found["env"].add(key)
81
+ if isinstance(node.value, ast.Name) and node.value.id in inputs and isinstance(node.ctx, ast.Load): found["stdin_keys"].add(key)
82
+ if isinstance(node.value, ast.Name) and isinstance(node.ctx, ast.Store): dicts.setdefault(node.value.id, set()).add(key)
83
+ if is_stdin_load(node.value): found["stdin_keys"].add(key)
84
+ if not isinstance(node, ast.Call): continue
85
+ name = dotted(node.func)
86
+ last = name.split(".")[-1]
87
+ first = text(node.args[0], names) if node.args else None
88
+ if name in ("os.getenv", "os.environ.get", "environ.get", "getenv", "os.environ.setdefault") and first: found["env"].add(first)
89
+ if last == "get" and isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id in inputs and first: found["stdin_keys"].add(first)
90
+ if name in ("open", "io.open", "builtins.open") and first is not None:
91
+ mode = text(node.args[1], {}) if len(node.args) > 1 else next((text(k.value, {}) for k in node.keywords if k.arg == "mode"), "r") or "r"
92
+ if any(c in mode for c in "wax"): found["writes"].add(first)
93
+ else: found["reads"].add(first)
94
+ if "+" in mode: found["reads"].add(first)
95
+ if isinstance(node.func, ast.Attribute) and last in ("read_text", "read_bytes", "write_text", "write_bytes", "open", "unlink", "mkdir", "touch"):
96
+ path = text(node.func.value, names)
97
+ if path is not None and not name.startswith(("os.", "io.", "sys.", "builtins.")):
98
+ (found["reads"] if last in ("read_text", "read_bytes", "open") else found["writes"]).add(path)
99
+ if (name.startswith(("requests.", "httpx.", "urllib.request.", "aiohttp.")) or name in ("urlopen", "Request")) and last in HTTP and first and host(first):
100
+ found["hosts"].add(host(first))
101
+ if last in ("HTTPSConnection", "HTTPConnection") and first: found["hosts"].add(first.lower())
102
+ if (name.startswith("subprocess.") or name.startswith("os.") or name in ("Popen", "check_output")) and last in RUNS and node.args:
103
+ arg = node.args[0]
104
+ if isinstance(arg, (ast.List, ast.Tuple)):
105
+ parts = [text(e, names) for e in arg.elts]
106
+ if parts and parts[0]: found["runs"].add(" ".join(p if p is not None else "…" for p in parts))
107
+ elif first: found["runs"].add(first)
108
+ dumped = None
109
+ if name == "print" and node.args and isinstance(node.args[0], ast.Call) and dotted(node.args[0].func) == "json.dumps" and node.args[0].args: dumped = node.args[0].args[0]
110
+ if name == "sys.stdout.write" and node.args and isinstance(node.args[0], ast.Call) and dotted(node.args[0].func) == "json.dumps" and node.args[0].args: dumped = node.args[0].args[0]
111
+ if name == "json.dump" and len(node.args) > 1 and dotted(node.args[1]) == "sys.stdout": dumped = node.args[0]
112
+ if dumped is not None:
113
+ printed.update(dict_keys(dumped))
114
+ if isinstance(dumped, ast.Name): printed.update(dicts.get(dumped.id, set()))
115
+ found["output_keys"].update(printed)
116
+ print(json.dumps({**{k: sorted(v) for k, v in found.items()}, "errors": errors}))
117
+ `;
118
+ function pythonFacts(sources) {
119
+ const result = spawnSync('python3', ['-I', '-c', pythonAnalysis], { input: JSON.stringify(sources), encoding: 'utf8', timeout: 30_000, maxBuffer: 8 * 1024 * 1024 });
120
+ if (result.error || result.status !== 0)
121
+ throw Error(`Python analysis failed: ${(result.error?.message ?? result.stderr).trim().split('\n').at(-1)}`);
122
+ return JSON.parse(result.stdout);
123
+ }
124
+ /** A light token scan for JavaScript and TypeScript. It finds literal values only. */
125
+ function nodeFacts(sources) {
126
+ const found = { hosts: new Set(), env: new Set(), reads: new Set(), writes: new Set(), runs: new Set(), stdin_keys: new Set(), output_keys: new Set() };
127
+ // A string literal after one capture group: the quote is group 2 and the text group 3.
128
+ const literal = String.raw `(['"\x60])([^'"\x60$\n]+)\2`;
129
+ for (const { text: raw } of sources) {
130
+ const text = raw.replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/(^|[^:'"\x60\\])\/\/[^\n]*/g, '$1');
131
+ for (const match of text.matchAll(/https?:\/\/[^\s'"\x60<>{}$]+/g)) {
132
+ const host = hostOf(match[0]);
133
+ if (host)
134
+ found.hosts.add(host);
135
+ }
136
+ for (const match of text.matchAll(/\bhost(?:name)?\s*:\s*['"]([a-z0-9.-]+\.[a-z]{2,})['"]/gi))
137
+ found.hosts.add(match[1].toLowerCase());
138
+ for (const match of text.matchAll(/process\.env\.([A-Za-z_][A-Za-z0-9_]*)|process\.env\[\s*['"]([^'"]+)['"]\s*\]/g))
139
+ found.env.add(match[1] ?? match[2]);
140
+ for (const match of text.matchAll(/\{([^{}]+)\}\s*=\s*process\.env\b/g))
141
+ for (const name of match[1].split(',')) {
142
+ const id = name.split(/[:=]/)[0].trim();
143
+ if (/^[A-Za-z_]\w*$/.test(id))
144
+ found.env.add(id);
145
+ }
146
+ for (const match of text.matchAll(new RegExp(String.raw `\b(readFileSync|readFile|createReadStream|readdirSync|readdir|existsSync|statSync|stat|access)\(\s*` + literal, 'g')))
147
+ found.reads.add(match[3]);
148
+ for (const match of text.matchAll(new RegExp(String.raw `\b(writeFileSync|writeFile|appendFileSync|appendFile|createWriteStream|mkdirSync|mkdir|rmSync|rm|unlinkSync|unlink|renameSync|rename|copyFileSync|copyFile)\(\s*` + literal, 'g')))
149
+ found.writes.add(match[3]);
150
+ for (const match of text.matchAll(new RegExp(String.raw `\b(execSync|execFileSync|execFile|spawnSync|spawn|exec)\(\s*` + literal + String.raw `(?:\s*,\s*\[([^\]]*)\])?`, 'g'))) {
151
+ const args = (match[4] ?? '').split(',').map(arg => arg.trim()).filter(Boolean).map(arg => /^(['"\x60])(.*)\1$/.exec(arg)?.[2] ?? '…');
152
+ found.runs.add([match[3], ...args].join(' '));
153
+ }
154
+ const inputs = new Set();
155
+ for (const match of text.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:await\s+)?JSON\.parse\(/g))
156
+ inputs.add(match[1]);
157
+ for (const match of text.matchAll(/(?:const|let|var)\s+\{([^{}]+)\}\s*=\s*(?:await\s+)?JSON\.parse\(/g))
158
+ for (const name of match[1].split(',')) {
159
+ const id = name.split(/[:=]/)[0].trim();
160
+ if (/^[A-Za-z_$][\w$]*$/.test(id))
161
+ found.stdin_keys.add(id);
162
+ }
163
+ for (const name of inputs)
164
+ for (const match of text.matchAll(new RegExp(String.raw `\b${name.replace(/\$/g, '\\$')}(?:\.([A-Za-z_$][\w$]*)|\[\s*['"]([^'"]+)['"]\s*\])`, 'g')))
165
+ found.stdin_keys.add(match[1] ?? match[2]);
166
+ for (const match of text.matchAll(/JSON\.stringify\(\s*\{([^{}]*)\}/g))
167
+ for (const part of match[1].split(',')) {
168
+ const key = /^\s*(?:['"]([^'"]+)['"]|([A-Za-z_$][\w$]*))\s*(?::|$)/.exec(part);
169
+ if (key)
170
+ found.output_keys.add(key[1] ?? key[2]);
171
+ }
172
+ }
173
+ return { ...Object.fromEntries(Object.entries(found).map(([key, value]) => [key, sorted(value)])), errors: [] };
174
+ }
175
+ /**
176
+ * Effects of a `run` step's entrypoint and declared helper files. Input and output fields come from the step's
177
+ * declaration; secrets are the declared secrets whose names appear in the code.
178
+ */
179
+ export function staticEffects(language, sources, step, secrets = []) {
180
+ const found = language === 'python' ? pythonFacts(sources) : nodeFacts(sources);
181
+ const code = sources.map(source => source.text).join('\n');
182
+ const used = sorted(secrets.filter(name => new RegExp(String.raw `(^|[^A-Za-z0-9_])${name}([^A-Za-z0-9_]|$)`).test(code)));
183
+ return {
184
+ effects: {
185
+ network: sorted(found.hosts), secrets: used, env: sorted(found.env.filter(name => !used.includes(name))),
186
+ reads: sorted(found.reads), writes: sorted(found.writes), runs: sorted(found.runs),
187
+ input_fields: Object.keys(step?.in ?? {}).sort(), output_fields: Object.keys(step?.out ?? {}).sort(),
188
+ },
189
+ facts: { stdin_keys: sorted(found.stdin_keys), output_keys: sorted(found.output_keys) },
190
+ errors: found.errors,
191
+ };
192
+ }
@@ -0,0 +1,115 @@
1
+ import type { MethodClient } from './method-client.js';
2
+ import { type Language, type ScriptEffects, type Source } from './explain-effects.js';
3
+ /** The contract shared with the server and the dashboard (docs/design/step-cards-and-jev.md, section 3). */
4
+ export type ScriptCard = {
5
+ schema: 'method-script-card/1';
6
+ step_id: string;
7
+ entrypoint: string;
8
+ script_sha256: string;
9
+ summary: string;
10
+ changes: 'nothing' | 'data';
11
+ effects: ScriptEffects;
12
+ steps: {
13
+ text: string;
14
+ lines: [number, number];
15
+ }[];
16
+ not_done: string[];
17
+ checks: {
18
+ entities: 'passed' | 'failed';
19
+ entity_problems: string[];
20
+ round_trip: 'passed' | 'failed' | 'not_run';
21
+ round_trip_inputs: number;
22
+ };
23
+ generated_at: string;
24
+ model: string;
25
+ };
26
+ type Draft = {
27
+ summary: string;
28
+ changes: 'nothing' | 'data';
29
+ steps: {
30
+ text: string;
31
+ lines: number[];
32
+ }[];
33
+ not_done: string[];
34
+ };
35
+ /** The hosted-model client: managedModels in production, a fake in tests. */
36
+ export type Models = {
37
+ model(): Promise<string>;
38
+ request(body: Record<string, any>, signal: AbortSignal): Promise<any>;
39
+ };
40
+ type Method = {
41
+ files?: string[];
42
+ secrets?: Record<string, unknown>;
43
+ steps: Record<string, any>;
44
+ };
45
+ /** The script steps of a Method: steps whose action is a script. */
46
+ export declare const scriptSteps: (method: Method) => string[];
47
+ /**
48
+ * The files a script step's card describes: its entrypoint and every declared Method file that is not another step's
49
+ * entrypoint (the step cache counts the same helper files, because the runtime does not trace imports).
50
+ */
51
+ export declare function scriptFiles(method: Method, id: string): string[];
52
+ /** sha256 over "<file sha256> <path>\n" lines, sorted by path. The server computes the same value from the package. */
53
+ export declare function scriptSha256(method: Method, id: string, hashes: Map<string, string> | Record<string, string>): string | undefined;
54
+ /**
55
+ * Every number, quoted or backticked name, and host in the card's text must appear in the code or in the step's
56
+ * declaration. A percentage may appear as a fraction (50% as 0.5).
57
+ */
58
+ export declare function entityProblems(draft: Pick<Draft, 'summary' | 'steps' | 'not_done'>, code: string, step: unknown): string[];
59
+ /** Recorded inputs of a step from local runs of the same entrypoint, newest first, without duplicates. */
60
+ export declare function recordedInputs(id: string, entrypoint: string, roots: string[], limit?: number): Record<string, unknown>[];
61
+ export declare function canonical(value: unknown): string;
62
+ /** Two outputs agree when their declared fields are JSON-equal after sorting keys (all fields when none are declared). */
63
+ export declare function sameOutput(a: unknown, b: unknown, fields: string[]): boolean;
64
+ type Outcome = {
65
+ ok: true;
66
+ output: unknown;
67
+ } | {
68
+ ok: false;
69
+ error: string;
70
+ };
71
+ /** Run one program on one input with network calls refused. The process gets no secrets and a private temporary folder. */
72
+ export declare function runProgram(language: Language, file: string, cwd: string, input: unknown, scratch: string): Promise<Outcome>;
73
+ export type CardContext = {
74
+ method: Method;
75
+ id: string;
76
+ root: string;
77
+ sources: Source[];
78
+ scriptHash: string;
79
+ models: Models;
80
+ roundTrip: boolean;
81
+ runRoots: string[];
82
+ };
83
+ export declare function makeCard(c: CardContext): Promise<{
84
+ card: ScriptCard;
85
+ note?: string;
86
+ }>;
87
+ export type ExplainOptions = {
88
+ step?: string | undefined;
89
+ roundTrip?: boolean;
90
+ models?: Models;
91
+ runRoots?: string[];
92
+ write?: (line: string) => void;
93
+ };
94
+ /**
95
+ * Make, check, and upload a card for each script step of the saved version of FILE's current content, when the
96
+ * version has no card for the step's current script. A card made earlier for the same script and step is reused.
97
+ */
98
+ export declare function explain(client: MethodClient, file: string, options?: ExplainOptions): Promise<{
99
+ status: "skipped";
100
+ cards: never[];
101
+ } | {
102
+ status: "done";
103
+ cards: ScriptCard[];
104
+ }>;
105
+ export declare function explainCommand(client: MethodClient, args: string[]): Promise<void>;
106
+ /**
107
+ * After a signed-in run saved a version with script steps, explain them in a detached process. The run neither waits
108
+ * nor fails because of it. Output goes to a log under the Method cache.
109
+ */
110
+ export declare function startBackgroundExplain(file: string, method: Method, versionId: string, server: string): void;
111
+ /** Wait for a background explain of this draft to finish. */
112
+ export declare function waitForExplain(file: string, timeoutMs?: number): Promise<void>;
113
+ /** Used by tests: python3 is required for Python analysis and replay. */
114
+ export declare const hasPython: () => boolean;
115
+ export {};