@method-ai-hq/sdk 0.14.6 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/packages/sdk/package.json +1 -1
- package/dist/packages/sdk/src/authoring-instructions.d.ts +1 -1
- package/dist/packages/sdk/src/authoring-instructions.js +3 -1
- package/dist/packages/sdk/src/inputs-from.d.ts +19 -0
- package/dist/packages/sdk/src/inputs-from.js +102 -0
- package/dist/packages/sdk/src/local-cli.d.ts +1 -0
- package/dist/packages/sdk/src/local-cli.js +11 -1
- package/dist/packages/sdk/src/method-help.js +5 -4
- package/dist/packages/sdk/src/method.js +13 -8
- package/dist/packages/sdk/src/proposals.js +5 -4
- package/dist/packages/sdk/src/run-saved.js +2 -0
- package/dist/packages/workflow-language/src/inspection.d.ts +2 -2
- package/dist/packages/workflow-language/src/inspection.js +5 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
MIT-licensed JavaScript API and the `method` CLI. Source and releases: https://github.com/method-ai-hq/method-sdk
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm install https://github.com/method-ai-hq/method-sdk/releases/download/v0.
|
|
6
|
+
npm install https://github.com/method-ai-hq/method-sdk/releases/download/v0.15.0/method-ai-hq-sdk-0.15.0.tgz
|
|
7
7
|
npx method --version
|
|
8
8
|
```
|
|
9
9
|
|
|
@@ -5,7 +5,7 @@ export declare const designProcedure = "# Choose the design\n\n| Requirement | S
|
|
|
5
5
|
/** The full check rules; in concepts. */
|
|
6
6
|
export declare const checkRules = "# Checks\n\nAdd a check only when it catches a concrete failure that matters to the result. Output types and the runtime already validate shapes; do not repeat them. Do not check wording, headings, keywords, lengths, or counts unless the task requires them: \"write accurate prose\" is not a reason to match strings.\n\nUse the simplest check that proves the fact: a built-in equals, count, present, or file check, a script, or an agent check. Local file writes need no check: the runtime observes files connections itself. A change to a service, an API, email, or a browser that sends needs an effect that reads the result back, because a receipt or a 200 status shows only that the request was accepted. Use a built-in observer (http, sqlite, file) when one fits. When a change cannot or need not be observed, such as a browser step that only reads, write no_effect_reason instead.\n\nExisting checks and tests are implementation choices, not user requirements. Remove a check that is unnecessary, repeats other validation, or enforces a requirement that nobody asked for. Delete the tests and instructions that exist only for it, and do not replace it. Do not change useful output only to satisfy a check.\n";
|
|
7
7
|
/** The rules that make a first Method work quickly. They come before every other part of the guide. */
|
|
8
|
-
export declare const firstMethodRules = "# Build a Method\n\n1. **Sign in first.** Run `method status`. If it is not signed in, run `method login` and let the user approve in the browser. Signed in, model and classification steps need no keys, each run saves a version when the file changed, and runs appear on the dashboard.\n2. **Each model or classifier request is its own step**: `call`, `agent`, or `classify`, with its prompt in the Method, so the user can change one prompt, run again, and compare. A script never calls a model API. When the user already has code that does the work, keep its fixed logic as `run` steps and move each prompt and rubric into its own step; do not wrap the code. \"The same thing\" means the same behavior in steps. If a request cannot become a step because Method lacks a feature, tell the user what is missing.\n3. **Show the design, then build it in the same turn.** Show each step with its type, purpose, and output, and a table of the prompts and rubrics in the user's existing work, each with the step that holds it. Do not wait for approval unless the user asked to approve first. Ask only for information that you cannot find and that would change the design.\n4. **Run early and often.** Write the first steps, validate, run, then add the next steps. Fix errors. Fix each warning, or accept it with the user's reason. Never add a check only to remove a warning. A new run reuses every step whose definition and inputs did not change, so each run executes only what changed. `--rerun STEP` runs a step again anyway; `--fresh` runs every step.\n5. **Keep the inputs of the existing code.** If the code takes an ID and looks up the record, the Method takes the same ID and looks it up the same way.\n6. **Use the defaults without asking.** Model steps use the account's hosted models and `classify` uses Method's classifier, so no model or classifier keys are needed. To keep the model that existing code uses, add `models: {writer: provider/model}` to the Method and use `model: writer` in the steps (or `model: provider/model` on the step); it needs no key. Keep the models that the Method or the existing code names: when a model step fails, show the error and ask before you change its model. Run records go to the user's account. When the user asks that run data stay on this computer or not be uploaded, set `run_data: device`: the run's inputs, outputs, and files stay here, and the account keeps only each step's status and timing. Model steps still send each step's input to the model to answer it; nothing is kept. Tell the user that in one line, then run again.\n7. **Keys stay out of chat.** Declare each key that a script needs under `secrets:` with its purpose; every script of the Method receives all of its declared secrets. Run `method secret find` in the Method's folder: it lists the key files nearby and the names in each, never the values, and prints the import command. Run that command and tell the user which file each key came from: the key stays on this computer, so there is nothing to ask. If it finds nothing, ask the user to run `method secret set NAME`, which opens a private form in their browser. Never open, print, or search a key file (`cat`, `grep`, an editor), including Method's own secret store, and never ask for a value in chat: the values would go into the chat. When a run says Method's model credit is used up, run `method config model-key` yourself: it takes the user's own OpenRouter key from the one nearby key file that holds it, or opens a private form for it. Then run again.\n8. **Choose a sample yourself** from the user's data, and check that it has the sources the Method needs. Ask only when there is no good sample.\n9. **Iterate.** `method run` prints a `dashboard` link when the run starts and again with its result. Give the user the link as soon as the run starts. After a good run, show the result and the link, and ask what to change. Change the step and run again. Show only results that a run made: never write or edit a result by hand, and fix a wrong result in the Method, not in the data it reads. If you cannot run, say so.\n10. **Publish** with `method publish FILE --reason TEXT` when the user wants to share or schedule a version. It runs the Method's cases first.\n11. **Talk about the work, not about Method.** Unless the user asks, do not mention Method's versions, formats, settings files, or other internal files, and do not ask the user about them, about models, or about which saved version to use. Method updates itself and converts older Method files when it runs them. When the user asks whether a Method works, or asks you to make it work, run it with the inputs of a recent run and show the result and the dashboard link in a few sentences. Say that it works only after a run completed.\n12. **One file for each Method.** Change and run a Method in its own file. Do not copy it to a temporary or other folder to change it: Method does not run or save the copy while the Method's file exists, and names that file.\n13. **Questions in a run are for the user.** When a run stops with `needs_input`, ask the user its `question` in plain words, wait for the answer, and continue the run with it as the output says. Never answer it yourself.\n\nWhen a run fails, read its `fix` and `diagnostics`, change the step, and run again.\n";
|
|
8
|
+
export declare const firstMethodRules = "# Build a Method\n\n1. **Sign in first.** Run `method status`. If it is not signed in, run `method login` and let the user approve in the browser. Signed in, model and classification steps need no keys, each run saves a version when the file changed, and runs appear on the dashboard.\n2. **Each model or classifier request is its own step**: `call`, `agent`, or `classify`, with its prompt in the Method, so the user can change one prompt, run again, and compare. A script never calls a model API. When the user already has code that does the work, keep its fixed logic as `run` steps and move each prompt and rubric into its own step; do not wrap the code. \"The same thing\" means the same behavior in steps. If a request cannot become a step because Method lacks a feature, tell the user what is missing.\n3. **Show the design, then build it in the same turn.** Show each step with its type, purpose, and output, and a table of the prompts and rubrics in the user's existing work, each with the step that holds it. Do not wait for approval unless the user asked to approve first. Ask only for information that you cannot find and that would change the design.\n4. **Run early and often.** Write the first steps, validate, run, then add the next steps. Fix errors. Fix each warning, or accept it with the user's reason. Never add a check only to remove a warning. A new run reuses every step whose definition and inputs did not change, so each run executes only what changed. `--rerun STEP` runs a step again anyway; `--fresh` runs every step.\n5. **Keep the inputs of the existing code.** If the code takes an ID and looks up the record, the Method takes the same ID and looks it up the same way.\n6. **Use the defaults without asking.** Model steps use the account's hosted models and `classify` uses Method's classifier, so no model or classifier keys are needed. To keep the model that existing code uses, add `models: {writer: provider/model}` to the Method and use `model: writer` in the steps (or `model: provider/model` on the step); it needs no key. Keep the models that the Method or the existing code names: when a model step fails, show the error and ask before you change its model. Run records go to the user's account. When the user asks that run data stay on this computer or not be uploaded, set `run_data: device`: the run's inputs, outputs, and files stay here, and the account keeps only each step's status and timing. Model steps still send each step's input to the model to answer it; nothing is kept. Tell the user that in one line, then run again.\n7. **Keys stay out of chat.** Declare each key that a script needs under `secrets:` with its purpose; every script of the Method receives all of its declared secrets. Run `method secret find` in the Method's folder: it lists the key files nearby and the names in each, never the values, and prints the import command. Run that command and tell the user which file each key came from: the key stays on this computer, so there is nothing to ask. If it finds nothing, ask the user to run `method secret set NAME`, which opens a private form in their browser. Never open, print, or search a key file (`cat`, `grep`, an editor), including Method's own secret store, and never ask for a value in chat: the values would go into the chat. When a run says Method's model credit is used up, run `method config model-key` yourself: it takes the user's own OpenRouter key from the one nearby key file that holds it, or opens a private form for it. Then run again.\n8. **Choose a sample yourself** from the user's data, and check that it has the sources the Method needs. Ask only when there is no good sample.\n9. **Iterate.** `method run` prints a `dashboard` link when the run starts and again with its result. Give the user the link as soon as the run starts. After a good run, show the result and the link, and ask what to change. Change the step and run again. Show only results that a run made: never write or edit a result by hand, and fix a wrong result in the Method, not in the data it reads. If you cannot run, say so.\n10. **Publish** with `method publish FILE --reason TEXT` when the user wants to share or schedule a version. It runs the Method's cases first.\n11. **Talk about the work, not about Method.** Unless the user asks, do not mention Method's versions, formats, settings files, or other internal files, and do not ask the user about them, about models, or about which saved version to use. Method updates itself and converts older Method files when it runs them. When the user asks whether a Method works, or asks you to make it work, run it with the inputs of a recent run (`--inputs-from RUN_ID`) and show the result and the dashboard link in a few sentences. Say that it works only after a run completed.\n12. **One file for each Method.** Change and run a Method in its own file. Do not copy it to a temporary or other folder to change it: Method does not run or save the copy while the Method's file exists, and names that file.\n13. **Questions in a run are for the user.** When a run stops with `needs_input`, ask the user its `question` in plain words, wait for the answer, and continue the run with it as the output says. Never answer it yourself.\n14. **Make each run easy to find and to run again.** Give the Method a `run_label` that tells its runs apart, such as `inputs.topic`. When an input can be empty and a default picks the value, point `run_label` at the step output that holds the resolved value, not at the input. Put the readable output, such as the report text or the message, in `result`, not only in files. Write a `run_prompt` that says where each input comes from (for example \"as_of is today's date in YYYY-MM-DD\") and what to show at the end. Leave SDK, runtime, and version details out of it.\n15. **Run again from the dashboard.** When the user pastes a \"Run again\" prompt from the dashboard, run with `--inputs-from RUN_ID` and change only what the prompt names: put those values in a file and add `--inputs FILE`.\n\nWhen a run fails, read its `fix` and `diagnostics`, change the step, and run again.\n";
|
|
9
9
|
/** How to write the prompt inside a call, agent, or classify step. */
|
|
10
10
|
export declare const promptRules = "# Write each prompt\n\n- **One task per prompt.** If a prompt says \"then\" or has several jobs, make several steps.\n- **Say what to do.** Leave out background that does not change the output.\n- **Show 1 to 3 examples of good output.** Take them from the user's work: examples in an existing prompt, a past output that the user approved, or the first run that the user accepts. Pass a long example as an input, not as text in the prompt.\n- **Put the output shape in `out`**, not in the prompt.\n- **Say what to do when the input does not have the answer**, for example \"leave due empty\". For classify, add an option such as `unclear`.\n\nBefore:\n\n```text\nYou are an expert support analyst with years of experience. Our company values clear\ncommunication. Read the tickets, decide which ones are urgent, write a summary, and\nreturn JSON with a \"summary\" key. Make it good.\n```\n\nAfter (urgency is a classify step before this one; the shape is in `out`):\n\n```text\nWrite a five-sentence summary of this week's support tickets for the team.\nStart with the high-urgency tickets. Quote no customer names.\nWrite it in the style of this summary from an earlier week:\n\n{{example}}\n```\n";
|
|
11
11
|
/** Enough of the format to write a Method without reading the JSON schema. */
|
|
@@ -39,9 +39,11 @@ export const firstMethodRules = `# Build a Method
|
|
|
39
39
|
8. **Choose a sample yourself** from the user's data, and check that it has the sources the Method needs. Ask only when there is no good sample.
|
|
40
40
|
9. **Iterate.** \`method run\` prints a \`dashboard\` link when the run starts and again with its result. Give the user the link as soon as the run starts. After a good run, show the result and the link, and ask what to change. Change the step and run again. Show only results that a run made: never write or edit a result by hand, and fix a wrong result in the Method, not in the data it reads. If you cannot run, say so.
|
|
41
41
|
10. **Publish** with \`method publish FILE --reason TEXT\` when the user wants to share or schedule a version. It runs the Method's cases first.
|
|
42
|
-
11. **Talk about the work, not about Method.** Unless the user asks, do not mention Method's versions, formats, settings files, or other internal files, and do not ask the user about them, about models, or about which saved version to use. Method updates itself and converts older Method files when it runs them. When the user asks whether a Method works, or asks you to make it work, run it with the inputs of a recent run and show the result and the dashboard link in a few sentences. Say that it works only after a run completed.
|
|
42
|
+
11. **Talk about the work, not about Method.** Unless the user asks, do not mention Method's versions, formats, settings files, or other internal files, and do not ask the user about them, about models, or about which saved version to use. Method updates itself and converts older Method files when it runs them. When the user asks whether a Method works, or asks you to make it work, run it with the inputs of a recent run (\`--inputs-from RUN_ID\`) and show the result and the dashboard link in a few sentences. Say that it works only after a run completed.
|
|
43
43
|
12. **One file for each Method.** Change and run a Method in its own file. Do not copy it to a temporary or other folder to change it: Method does not run or save the copy while the Method's file exists, and names that file.
|
|
44
44
|
13. **Questions in a run are for the user.** When a run stops with \`needs_input\`, ask the user its \`question\` in plain words, wait for the answer, and continue the run with it as the output says. Never answer it yourself.
|
|
45
|
+
14. **Make each run easy to find and to run again.** Give the Method a \`run_label\` that tells its runs apart, such as \`inputs.topic\`. When an input can be empty and a default picks the value, point \`run_label\` at the step output that holds the resolved value, not at the input. Put the readable output, such as the report text or the message, in \`result\`, not only in files. Write a \`run_prompt\` that says where each input comes from (for example "as_of is today's date in YYYY-MM-DD") and what to show at the end. Leave SDK, runtime, and version details out of it.
|
|
46
|
+
15. **Run again from the dashboard.** When the user pastes a "Run again" prompt from the dashboard, run with \`--inputs-from RUN_ID\` and change only what the prompt names: put those values in a file and add \`--inputs FILE\`.
|
|
45
47
|
|
|
46
48
|
When a run fails, read its \`fix\` and \`diagnostics\`, change the step, and run again.
|
|
47
49
|
`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MethodClient } from "./method-client.js";
|
|
2
|
+
import type { parse } from "./local-cli.js";
|
|
3
|
+
type Flags = ReturnType<typeof parse>["values"];
|
|
4
|
+
type Inputs = Record<string, unknown>;
|
|
5
|
+
/** The run folders on this computer where an earlier run can be: this folder's, the Method folder's, and the CLI's cache. */
|
|
6
|
+
export declare function runFolders(methodFile?: string): string[];
|
|
7
|
+
/**
|
|
8
|
+
* The inputs of an earlier run. An account run gives them from the server. A device run keeps them on the computer that
|
|
9
|
+
* ran it, so they come from its run folder; so do the inputs of any run when the server cannot be reached.
|
|
10
|
+
*/
|
|
11
|
+
export declare function inputsOfRun(runId: string, client: MethodClient | undefined, folders: string[]): Promise<Inputs>;
|
|
12
|
+
/**
|
|
13
|
+
* --inputs-from RUN_ID: the inputs of that run become this run's inputs, saved as inputs-from.json in the run folder.
|
|
14
|
+
* --inputs FILE replaces single values. The returned flags name that file with --inputs.
|
|
15
|
+
*/
|
|
16
|
+
export declare function withInputsFrom(flags: Flags, runDir: string, methodFile: string | undefined, client: MethodClient | undefined): Promise<Flags>;
|
|
17
|
+
/** Command arguments without the named options and their values. */
|
|
18
|
+
export declare function withoutOptions(args: string[], names: string[]): string[];
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { inspectRun } from "./inspect.js";
|
|
4
|
+
import { methodCache } from "./prepare.js";
|
|
5
|
+
import { writePrivateJson } from "./files.js";
|
|
6
|
+
import { authoringPath } from "./authoring.js";
|
|
7
|
+
/** The run folders on this computer where an earlier run can be: this folder's, the Method folder's, and the CLI's cache. */
|
|
8
|
+
export function runFolders(methodFile) {
|
|
9
|
+
return [...new Set([join(process.cwd(), ".method-runs"), ...(methodFile ? [join(dirname(resolve(methodFile)), ".method-runs")] : []), join(methodCache(), "runs")].map(path => resolve(path)))];
|
|
10
|
+
}
|
|
11
|
+
/** The inputs of a local run folder whose method-sync.json links it to the dashboard run RUN_ID. */
|
|
12
|
+
function localInputs(runId, folders) {
|
|
13
|
+
for (const folder of folders) {
|
|
14
|
+
let names = [];
|
|
15
|
+
try {
|
|
16
|
+
names = readdirSync(folder);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
for (const name of names) {
|
|
22
|
+
const directory = join(folder, name), link = join(directory, "method-sync.json");
|
|
23
|
+
if (!existsSync(link))
|
|
24
|
+
continue;
|
|
25
|
+
let state;
|
|
26
|
+
try {
|
|
27
|
+
state = JSON.parse(readFileSync(link, "utf8"));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (state?.dashboard_id !== runId && state?.id !== runId)
|
|
33
|
+
continue;
|
|
34
|
+
try {
|
|
35
|
+
return inspectRun(directory).inputs;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
for (const file of ["setup-inspection.json", "requested-inputs.json"]) {
|
|
39
|
+
const path = join(directory, file);
|
|
40
|
+
if (!existsSync(path))
|
|
41
|
+
continue;
|
|
42
|
+
const saved = JSON.parse(readFileSync(path, "utf8"));
|
|
43
|
+
return file === "setup-inspection.json" ? saved.inputs ?? {} : saved;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The inputs of an earlier run. An account run gives them from the server. A device run keeps them on the computer that
|
|
52
|
+
* ran it, so they come from its run folder; so do the inputs of any run when the server cannot be reached.
|
|
53
|
+
*/
|
|
54
|
+
export async function inputsOfRun(runId, client, folders) {
|
|
55
|
+
let reason = "This computer is not signed in, so the account was not asked.";
|
|
56
|
+
if (client?.token()) {
|
|
57
|
+
try {
|
|
58
|
+
const { run } = await client.request(`/api/workspace/runs/${encodeURIComponent(runId)}`);
|
|
59
|
+
if (run?.inspection && run.inspection.content !== "device")
|
|
60
|
+
return run.inspection.inputs ?? {};
|
|
61
|
+
reason = "The account keeps no inputs for this run: its content stays on the computer that ran it.";
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
reason = `The account did not give the run (${error instanceof Error ? error.message : String(error)}).`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const found = localInputs(runId, folders);
|
|
68
|
+
if (found)
|
|
69
|
+
return found;
|
|
70
|
+
throw Object.assign(Error(`Found no inputs of run ${runId}. ${reason} No run folder for it (method-sync.json) is in ${folders.join(", ")}. Run this command on the computer that ran it, from its folder, or give the inputs with --inputs FILE.`), { code: "inputs_not_found" });
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* --inputs-from RUN_ID: the inputs of that run become this run's inputs, saved as inputs-from.json in the run folder.
|
|
74
|
+
* --inputs FILE replaces single values. The returned flags name that file with --inputs.
|
|
75
|
+
*/
|
|
76
|
+
export async function withInputsFrom(flags, runDir, methodFile, client) {
|
|
77
|
+
const runId = flags["inputs-from"];
|
|
78
|
+
if (!runId)
|
|
79
|
+
return flags;
|
|
80
|
+
if (flags.resume)
|
|
81
|
+
throw Error("A resumed run keeps the inputs it started with. Leave out --inputs-from.");
|
|
82
|
+
const earlier = await inputsOfRun(runId, client, runFolders(methodFile));
|
|
83
|
+
const explicit = flags.inputs ? JSON.parse(readFileSync(authoringPath(flags.inputs), "utf8")) : {};
|
|
84
|
+
const file = join(resolve(runDir), "inputs-from.json");
|
|
85
|
+
writePrivateJson(file, { ...earlier, ...explicit });
|
|
86
|
+
const { "inputs-from": _, ...rest } = flags;
|
|
87
|
+
return { ...rest, inputs: file };
|
|
88
|
+
}
|
|
89
|
+
/** Command arguments without the named options and their values. */
|
|
90
|
+
export function withoutOptions(args, names) {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (let i = 0; i < args.length; i++) {
|
|
93
|
+
const match = args[i].match(/^--([^=]+)(=.*)?$/);
|
|
94
|
+
if (match && names.includes(match[1])) {
|
|
95
|
+
if (match[2] === undefined)
|
|
96
|
+
i++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
out.push(args[i]);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
@@ -17,12 +17,13 @@ import { Outbox, savedVersions } from "./outbox.js";
|
|
|
17
17
|
import { readComputerSettings } from "./computer-settings.js";
|
|
18
18
|
import { localSetup } from "./local-setup.js";
|
|
19
19
|
import { readDocument } from "./authoring.js";
|
|
20
|
+
import { withInputsFrom } from "./inputs-from.js";
|
|
20
21
|
import { join } from "node:path";
|
|
21
22
|
import { randomUUID } from "node:crypto";
|
|
22
23
|
const help = "Use method run, steps, prompt, inspect, or doctor. See method help.";
|
|
23
24
|
export function parse(args) {
|
|
24
25
|
return parseArgs({ args, allowPositionals: true, options: {
|
|
25
|
-
server: { type: "string" }, agent: { type: "string" }, background: { type: "boolean" }, state: { type: "string" }, out: { type: "string" }, inputs: { type: "string" }, workspace: { type: "string" },
|
|
26
|
+
server: { type: "string" }, agent: { type: "string" }, background: { type: "boolean" }, state: { type: "string" }, out: { type: "string" }, inputs: { type: "string" }, "inputs-from": { type: "string" }, workspace: { type: "string" },
|
|
26
27
|
"run-dir": { type: "string" }, fresh: { type: "boolean" }, rerun: { type: "string", multiple: true }, resume: { type: "boolean" }, human: { type: "string" }, retry: { type: "string", multiple: true },
|
|
27
28
|
"include-files": { type: "boolean" }, verbose: { type: "boolean" }, help: { type: "boolean" }
|
|
28
29
|
} });
|
|
@@ -38,6 +39,15 @@ export async function run(target, flags, syncFactory) {
|
|
|
38
39
|
process.stderr.write(notice + "\n");
|
|
39
40
|
(await import("./prepare.js")).pruneCachedRuns();
|
|
40
41
|
flags = { ...flags, "run-dir": flags["run-dir"] ?? join(process.cwd(), ".method-runs", randomUUID()) };
|
|
42
|
+
if (flags["inputs-from"]) {
|
|
43
|
+
let client;
|
|
44
|
+
if (!syncFactory)
|
|
45
|
+
try {
|
|
46
|
+
client = new MethodClient(flags.server);
|
|
47
|
+
}
|
|
48
|
+
catch { /* not signed in to this server */ }
|
|
49
|
+
flags = await withInputsFrom(flags, flags["run-dir"], path, client);
|
|
50
|
+
}
|
|
41
51
|
const account = syncFactory ? undefined : await accountSync(path, flags);
|
|
42
52
|
try {
|
|
43
53
|
await runCurrentFile(path, flags, syncFactory ?? account?.sync);
|
|
@@ -37,7 +37,7 @@ export const commandHelp = {
|
|
|
37
37
|
explain: { usage: "method explain FILE [--step ID] [--no-round-trip] [--server URL]", purpose: "Explain each script step of the saved version in a script card.", arguments: "FILE is a local Method whose current content is a saved version (run or publish it while signed in). For each run step whose script has no card, code analysis finds the hosts, secrets, environment variables, files, and commands the script uses; a hosted model writes a one-sentence summary and 3 to 8 steps; every number, quoted name, and host in that text must appear in the code (one retry). The round trip writes a program from the steps alone and replays up to 3 recorded inputs of the step from local runs through both programs without network access. A script that calls the network is not replayed. --step explains one step. --no-round-trip skips the replay. A signed-in run starts this in the background; publish waits for it.", result: "One line per step with its summary and check results. Cards upload to the version; the dashboard shows them beside each script. A failed check is shown on the card and never blocks publish.", errors: "No saved version, local files that differ from the saved version, or no hosted-model credit. Without sign-in it prints one line and makes no card.", example: "method explain leads.method --step score", remote: true },
|
|
38
38
|
publish: { usage: "method publish FILE [--reason TEXT] [--accept-failing-case ID[,ID]] [--cloud|--workers] [--env NAME] [--server URL]", purpose: "Mark a version as published, for sharing, schedules, and production runs.", arguments: "Runs the Method's cases first; a version that breaks an approved case is refused unless --accept-failing-case names it, and the version records that. Uses the version of the file's current content: it finishes that upload (no time limit while bytes move), checks the cases, then marks it published with the reason (default: Published.). --accept-failing-case needs --reason. The first save of a new file creates the Method. --cloud runs the production runs of the environment on Method Cloud; --workers runs them on your workers again. --env NAME selects the environment (default production).", result: "JSON with workflow_id, version_id, url, published_at, the cases line, and the open warnings and notes (issues). With --cloud or --workers: placement. Warnings and notes never block a publish.", errors: "An error issue (nothing is published), failing case, --accept-failing-case without --reason, or no network.", example: "method publish task.method --reason 'Shorter introduction'", remote: true },
|
|
39
39
|
improve: { usage: "method improve FILE [--case ID] [--note TEXT] [--step ID] [--server URL]", purpose: "Ask Method to propose a better version of a Method.", arguments: "--note: the correction, in your words; it is about the newest run of the Method on this computer, which the improvement reads and a suggested case records. --step: improve only this step. --case: the case the change must pass. A Method with account run data improves in your account; see the progress on the dashboard. A Method with run_data: device improves on this computer, so its run content stays here; the result is a local proposal in .method/proposals/.", result: "JSON with the improvement and the dashboard link, or the local proposal ID. Nothing is published, and the file does not change.", errors: "No id: line (run the file once while signed in), an unknown step, or an improvement that is already running.", example: "method improve task.method --step summary --note 'Name the customer in the first sentence.'", remote: true },
|
|
40
|
-
proposals: { usage: "method proposals FILE [--wait] [--server URL]", purpose: "List the proposals of a Method.", arguments: "Lists the proposals in your account and the local proposals beside the file. --wait: when an improvement is running, wait until it ends (usually a few minutes), printing each step it reaches.", result: "JSON list of proposals with id, source, status, kind, cause, and steps, and the newest improvement that has no proposal yet, with its status. No changes.", errors: "", example: "method proposals task.method", remote: true },
|
|
40
|
+
proposals: { usage: "method proposals FILE [--wait] [--server URL]", purpose: "List the proposals of a Method.", arguments: "Lists the proposals in your account and the local proposals beside the file. --wait: when an improvement is running, wait until it ends (usually a few minutes), printing each step it reaches.", result: "JSON list of proposals with id, source, status, kind, cause, and steps, and the newest improvement that has no proposal yet, with its status. An improvement that found no safe change has outcome none, and next says what to do instead. No changes.", errors: "", example: "method proposals task.method", remote: true },
|
|
41
41
|
apply: { usage: "method apply FILE [PROPOSAL_ID] [--resolved] [--server URL]", purpose: "Merge a proposal into the local file.", arguments: "Default: the newest accepted proposal, or else the newest local proposal. The merge is per step: a part that only the proposal changed takes the change; a part that you and the proposal both changed is a conflict. --resolved: you merged a conflict by hand; this marks the proposal applied. Commands that take a signed-in FILE apply an accepted proposal first and print one line.", result: "JSON with status applied and the changed steps; the file changes and the proposal is marked applied. Nothing is published. A conflict lists each part with the base, your, and the proposed value, changes nothing, and exits 1.", errors: "No proposal, a proposal that is not accepted, or a conflict.", example: "method apply task.method", remote: true },
|
|
42
42
|
secret: { usage: "method secret find [FILE]\nmethod secret import FILE NAME...\nmethod secret set NAME\nmethod secret list [NAME...]", purpose: "Give this computer the values of a Method's declared secrets.", arguments: "find lists the KEY=VALUE files in this folder (3 levels down) and in the parent folder (2 levels down) with the key names in each, never values; for the Method FILE (or the only Method here) it says which declared secrets each file holds and prints the import command. import copies the named values from a KEY=VALUE file that the user names, without printing them. set opens a private form on 127.0.0.1 in the browser for one value. list shows names and where each value is found (shell, this computer, or missing), never values. Values are kept in ~/.config/method/secrets.json (mode 0600) and are never sent to Method. A value exported in the shell is used first. Every script of the Method receives all of its declared secrets.", result: "JSON with the saved names, or the list.", errors: "A name is missing from the file, or no value was entered.", example: "method secret find\nmethod secret import ../service/.env ARCHIVE_TOKEN" },
|
|
43
43
|
connect: { usage: "method connect [APP_FOLDER] [--file METHOD_FILE]", purpose: "Connect an app to the Method in this folder, so the app runs it in production.", arguments: "Run it in the Method's folder. APP_FOLDER (default: this folder) is the app's folder. It makes a service key named after the app folder and writes METHOD_API_KEY to the app's .env (a new file, or a new or replaced line); it never prints the key. It adds .env to .gitignore in a git repository. When the Method has no published version, it publishes the current file. It detects Node (package.json) or Python (pyproject.toml, requirements.txt), and prints the install line and the code to add. Run it again: an app with a working key keeps it.", result: "JSON with the method_id, the published version, where the key is, install, code, secrets_to_set_on_the_host, and next.", errors: "No or several .method files (give --file), an app folder with no language marker, or a failing case at publish.", example: "method connect ../app", remote: true },
|
|
@@ -58,6 +58,7 @@ export const commandHelp = {
|
|
|
58
58
|
run: { usage: "method run FILE.method [OPTIONS]\nmethod run WORKFLOW_ID [--version VERSION_ID] [--server URL] [OPTIONS]", purpose: "Run a local .method file, or a saved Method by ID. When this computer is signed in, a run of a local file starts at once and saves in the background: the version of the file's content and the run's records go to the dashboard through the outbox (~/.cache/method/outbox), and the last line says Saved as version N, or Will save when online; without sign-in the records stay in .method-runs. Steps whose definition and inputs match an earlier accepted run on this computer are reused.", arguments: `Models come from the Method's models: (or the account default); --agent codex|claude or method config agent runs model steps with a local agent. --workspace selects a different helper folder. See method authoring execution. Optional --state FILE initializes state for a new run. Resume with --resume --run-dir DIR; authorize unfinished work with --retry STEP:ITERATION.
|
|
59
59
|
|
|
60
60
|
--inputs FILE: JSON input values.
|
|
61
|
+
--inputs-from RUN_ID: use the inputs of that run; --inputs FILE overrides single values.
|
|
61
62
|
--run-dir DIR: the folder for this run's records (new runs too; default .method-runs/ID).
|
|
62
63
|
--agent codex|claude: run every model step of a new run with this local agent.
|
|
63
64
|
--resume: continue the same saved run with its saved agent.
|
|
@@ -180,9 +181,9 @@ ${designExamples}
|
|
|
180
181
|
A method has format, name, goal, steps, and result, and optional id, inputs, secrets, state, environment, files, models, limits, tools, run_data, run_label, and run_prompt (see the field reference in method authoring).
|
|
181
182
|
Each step uses do or ask. The do kinds are run, call, agent, and classify. Give script actions a name and a purpose; describe script checks in reading.check. A classify action takes bound inputs, a question, and one of options (a named choice with probabilities), answer: yes_no (answer and the probability of yes), or levels (2-10 ordered names; the most likely level, an expected level index as score, and probabilities). Use a script to apply business rules to that result.
|
|
182
183
|
run uses runtime and entrypoint; call uses model and prompt; agent can select browser: environment.NAME and optional custom tools.
|
|
183
|
-
Optional run_label selects one saved text, number, or boolean value, such as inputs.topic or steps.prepare.outputs.plan.date. Step references must select a step without each or repeat. Choose a short non-sensitive value. The site uses the current Method's reference with each run's recorded inputs or outputs; absent or empty values keep timestamps. Set it with method set task.method /run_label --json '"steps.prepare.outputs.plan.date"'.
|
|
184
|
+
Optional run_label selects one saved text, number, or boolean value, such as inputs.topic or steps.prepare.outputs.plan.date. Step references must select a step without each or repeat. Choose a short non-sensitive value that tells runs apart. When an input can be empty and a default picks the value, select the step output that holds the resolved value, not the input. The site uses the current Method's reference with each run's recorded inputs or outputs; absent or empty values keep timestamps. Set it with method set task.method /run_label --json '"steps.prepare.outputs.plan.date"'.
|
|
184
185
|
|
|
185
|
-
Optional run_prompt is plain text for the outside agent that starts a saved Method. Write which Method to run, where
|
|
186
|
+
Optional run_prompt is plain text for the outside agent that starts a saved Method. Write which Method to run, where each input comes from (for example "as_of is today's date in YYYY-MM-DD"), and what to show when finished. Leave out SDK, runtime, and version details. Save it with the Method version and update it when inputs or outputs change. The copy button appends the exact version link and shared CLI setup; do not repeat them in run_prompt. This field is not a step prompt and does not expand variables. Set it with method set task.method /run_prompt --text-file run-prompt.txt.
|
|
186
187
|
|
|
187
188
|
Model and human prompts use {{date}} for the step input declared as in.date. Nested fields such as {{customer.name}} are allowed. Only text, numbers, and booleans can be inserted; pass lists and records as structured inputs. Whitespace inside braces is allowed. Escape a literal placeholder with a backslash before its opening braces (use a YAML block scalar). Values are inserted once, never evaluated or expanded again. Unknown variables, invalid paths, and non-scalar values fail validation. Missing runtime values fail before model execution. Defaults belong in input declarations. Human ask text uses the same scope; agent check prompts use {{inputs.date}} and {{outputs.answer}}. Script commands, labels, and tool descriptions are not templates. Single braces are ordinary text.
|
|
188
189
|
Runs record prompt.rendered with the template and expanded instructions for each invocation and phase; the run page shows the recorded expansion, with templates in technical details. Model and agent work uses finite default limits; steps can override them.
|
|
@@ -252,7 +253,7 @@ destination and the rule used.”
|
|
|
252
253
|
const execution = `
|
|
253
254
|
# Execution setup
|
|
254
255
|
|
|
255
|
-
Use method run FILE_OR_ID [--inputs inputs.json] [--state state.json] [--run-dir DIR] [--workspace HELPERS_FOLDER] [--agent codex|claude] [--background].
|
|
256
|
+
Use method run FILE_OR_ID [--inputs inputs.json] [--inputs-from RUN_ID] [--state state.json] [--run-dir DIR] [--workspace HELPERS_FOLDER] [--agent codex|claude] [--background].
|
|
256
257
|
A Method needs no configuration file. What changes the result is in the Method: models, limits, tools, and secrets (the names). What belongs to this computer is in ~/.config/method/computer.json: method config (local agent, own model key) and method bind (folders and connections). python and node are found on PATH and prepared automatically. A saved version carries its helpers; versions saved without files still need --workspace DIR.
|
|
257
258
|
|
|
258
259
|
## Models
|
|
@@ -32,23 +32,28 @@ export function setupFix(error) {
|
|
|
32
32
|
function safePath(path) {
|
|
33
33
|
return authoringPath(path);
|
|
34
34
|
}
|
|
35
|
-
async function dispatchRunWorker(args, flags, extra = []) {
|
|
35
|
+
async function dispatchRunWorker(args, flags, extra = [], savedClient) {
|
|
36
36
|
if (process.env.METHOD_RUN_WORKER === '1' || (!/[/\\]method\.(?:js|ts)$/.test(process.argv[1] ?? '') && !flags.background))
|
|
37
37
|
return false;
|
|
38
38
|
if (flags.resume && !flags['run-dir'])
|
|
39
39
|
throw Error('Resume needs --run-dir.');
|
|
40
40
|
const directory = safePath(flags['run-dir'] ?? join(methodCache(), 'runs', randomUUID()));
|
|
41
|
-
|
|
41
|
+
let workerArgs = [...args.filter(arg => arg !== '--background'), ...extra];
|
|
42
42
|
if (!flags['run-dir'])
|
|
43
43
|
workerArgs.push('--run-dir', directory);
|
|
44
|
-
|
|
45
|
-
let server;
|
|
44
|
+
let client = savedClient;
|
|
46
45
|
try {
|
|
47
|
-
|
|
48
|
-
if (!flags.resume && client.token())
|
|
49
|
-
server = client.server;
|
|
46
|
+
client ??= new MethodClient(flags.server);
|
|
50
47
|
}
|
|
51
48
|
catch { /* Not signed in to this server. */ }
|
|
49
|
+
// The earlier run's inputs are read before the run starts, so a run that cannot find them does not start.
|
|
50
|
+
if (flags['inputs-from']) {
|
|
51
|
+
const { withInputsFrom, withoutOptions } = await import('./inputs-from.js');
|
|
52
|
+
const resolved = await withInputsFrom(flags, directory, /\.method$/i.test(args[1] ?? '') ? args[1] : undefined, client);
|
|
53
|
+
workerArgs = [...withoutOptions(workerArgs, ['inputs-from', 'inputs']), '--inputs', resolved.inputs];
|
|
54
|
+
}
|
|
55
|
+
// A new run of a signed-in computer is synced; a resumed run keeps the link that it has.
|
|
56
|
+
const server = !flags.resume && client?.token() ? client.server : undefined;
|
|
52
57
|
await startRunWorker(workerArgs, directory, flags.background, server);
|
|
53
58
|
return true;
|
|
54
59
|
}
|
|
@@ -370,7 +375,7 @@ export async function methodMain(args = process.argv.slice(2), clientFactory = s
|
|
|
370
375
|
return;
|
|
371
376
|
}
|
|
372
377
|
const flags = parse(rest.slice(2)).values;
|
|
373
|
-
if (await dispatchRunWorker(args, flags, values.version ? [] : ['--version', saved.version_id]))
|
|
378
|
+
if (await dispatchRunWorker(args, flags, values.version ? [] : ['--version', saved.version_id], client))
|
|
374
379
|
return;
|
|
375
380
|
await runSaved(saved, flags, client);
|
|
376
381
|
}
|
|
@@ -220,7 +220,7 @@ export async function proposalCommand(args, clientFactory) {
|
|
|
220
220
|
client.request(`/api/methods/${encodeURIComponent(methodId)}/proposals`),
|
|
221
221
|
client.request(`/api/methods/${encodeURIComponent(methodId)}/improvements`)
|
|
222
222
|
]);
|
|
223
|
-
// The newest improvement that has no proposal
|
|
223
|
+
// The newest improvement that has no proposal: still running, failed, or ended with no safe change (outcome none).
|
|
224
224
|
const latest = (improvements ?? []).find(i => !i.proposal_id);
|
|
225
225
|
return { proposals, pending: latest && (latest.created_at ?? '') > String((proposals ?? [])[0]?.created_at ?? '') ? latest : undefined };
|
|
226
226
|
};
|
|
@@ -239,10 +239,11 @@ export async function proposalCommand(args, clientFactory) {
|
|
|
239
239
|
const failedStep = pending?.steps?.find((s) => s.status === 'failed')?.step_id;
|
|
240
240
|
const next = !pending ? undefined
|
|
241
241
|
: ['queued', 'running'].includes(pending.status) ? `Improvement ${pending.id} is ${pending.status}. Run method proposals ${target} --wait: it returns when the proposal is ready.`
|
|
242
|
-
: pending.
|
|
243
|
-
:
|
|
242
|
+
: pending.outcome === 'none' ? `The improvement found no safe change. Say what is wrong in a note on a run (method improve ${target} --note TEXT), or record a case with method case new.`
|
|
243
|
+
: pending.status === 'failed' || pending.status === 'cancelled' ? `Improvement ${pending.id} ${pending.status}${failedStep ? ` at step ${failedStep}` : ''}${pending.error?.message ? `: ${pending.error.message}` : ''}. Run method improve ${target} again, or record the correction with method case new.`
|
|
244
|
+
: undefined;
|
|
244
245
|
return print({ file, method_id: methodId, proposals: [...(proposals ?? []).map(p => summary({ ...p, source: 'account' })), ...local].sort((a, b) => String(b.created_at).localeCompare(String(a.created_at))),
|
|
245
|
-
...(pending ? { improvement: { id: pending.id, status: pending.status, ...(failedStep ? { failed_step: failedStep } : {}), ...(pending.error ? { error: pending.error } : {}) } } : {}), ...(next ? { next } : {}) });
|
|
246
|
+
...(pending ? { improvement: { id: pending.id, status: pending.status, ...(pending.outcome === 'none' ? { outcome: 'none' } : {}), ...(failedStep ? { failed_step: failedStep } : {}), ...(pending.error ? { error: pending.error } : {}) } } : {}), ...(next ? { next } : {}) });
|
|
246
247
|
}
|
|
247
248
|
const local = localProposals(file);
|
|
248
249
|
let proposal = wanted ? local.find(p => p.id === wanted) : undefined;
|
|
@@ -10,6 +10,7 @@ import { MethodSync } from './method-sync.js';
|
|
|
10
10
|
import { recordKnownVersion } from './outbox.js';
|
|
11
11
|
import { writePrivateJson } from './files.js';
|
|
12
12
|
import { readDocument } from './authoring.js';
|
|
13
|
+
import { withInputsFrom } from './inputs-from.js';
|
|
13
14
|
// Older versions are listed only after their saved-package tests pass.
|
|
14
15
|
const supportedPackageRuntimes = new Set([runtimeVersion, '0.11.1', '0.11.0', '0.10.0', '0.9.5', '0.9.4', '0.9.3', '0.7.0', '0.7.1', '0.7.2', '0.8.0', '0.8.1', '0.8.2', '0.8.3']);
|
|
15
16
|
/** A run whose steps all finished: completed, or unconfirmed (an external change could not be confirmed). */
|
|
@@ -44,6 +45,7 @@ export async function runSaved(saved, flags, client) {
|
|
|
44
45
|
return completed;
|
|
45
46
|
}
|
|
46
47
|
}
|
|
48
|
+
flags = await withInputsFrom(flags, directory, undefined, client);
|
|
47
49
|
const inputFile = join(directory, 'requested-inputs.json');
|
|
48
50
|
const inputs = flags.inputs ? JSON.parse(readFileSync(resolve(flags.inputs), 'utf8')) : existsSync(inputFile) ? JSON.parse(readFileSync(inputFile, 'utf8')) : {};
|
|
49
51
|
writePrivateJson(inputFile, inputs);
|
|
@@ -984,7 +984,7 @@ export declare function inspectCatalog(workflow: Workflow): {
|
|
|
984
984
|
export declare function copyInstructions(step: Step, inputs?: Record<string, Json>, resources?: Record<string, unknown>): string;
|
|
985
985
|
export declare function observedValue(item: InspectItem, inspection: RunInspection, invocation?: string): Json | undefined;
|
|
986
986
|
/**
|
|
987
|
-
* The device form of a run record (run_data: device). Keep step status, kinds, names, and
|
|
988
|
-
* prompts, files, and free text. The server accepts only this form when an organization keeps run content on devices.
|
|
987
|
+
* The device form of a run record (run_data: device). Keep step status, kinds, names, timing, and the phase of a failure. Remove inputs, outputs,
|
|
988
|
+
* prompts, files, errors, and free text. The server accepts only this form when an organization keeps run content on devices.
|
|
989
989
|
*/
|
|
990
990
|
export declare function deviceOnly(inspection: RunInspection): RunInspection;
|
|
@@ -112,18 +112,19 @@ export function observedValue(item, inspection, invocation) {
|
|
|
112
112
|
return value;
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
|
-
* The device form of a run record (run_data: device). Keep step status, kinds, names, and
|
|
116
|
-
* prompts, files, and free text. The server accepts only this form when an organization keeps run content on devices.
|
|
115
|
+
* The device form of a run record (run_data: device). Keep step status, kinds, names, timing, and the phase of a failure. Remove inputs, outputs,
|
|
116
|
+
* prompts, files, errors, and free text. The server accepts only this form when an organization keeps run content on devices.
|
|
117
117
|
*/
|
|
118
118
|
export function deviceOnly(inspection) {
|
|
119
|
+
const failure = (value) => value ? { failure: { phase: value.phase, expected: '', observed: '', evidence: [] } } : {};
|
|
119
120
|
const event = ({ at, type, sequence, phase, duration_ms, usage, provider, model, kind, exit_code }) => Object.fromEntries(Object.entries({ at, type, sequence, phase, duration_ms, usage, provider, model, kind, exit_code }).filter(([, value]) => value !== undefined));
|
|
120
121
|
return {
|
|
121
|
-
schema: inspection.schema, content: 'device', workflow: inspection.workflow, run_id: inspection.run_id, status: inspection.status,
|
|
122
|
+
schema: inspection.schema, content: 'device', workflow: inspection.workflow, run_id: inspection.run_id, status: inspection.status, ...failure(inspection.failure),
|
|
122
123
|
...(inspection.started_at ? { started_at: inspection.started_at } : {}), ...(inspection.device_name ? { device_name: inspection.device_name } : {}),
|
|
123
124
|
inputs: {}, resources: Object.fromEntries(Object.entries(inspection.resources).map(([name, value]) => [name, { description: value.description }])),
|
|
124
125
|
...(inspection.events ? { events: inspection.events.map(event) } : {}),
|
|
125
126
|
invocations: Object.fromEntries(Object.entries(inspection.invocations).map(([id, value]) => [id, {
|
|
126
|
-
step_id: value.step_id, status: value.status, ...(value.verification ? { verification: value.verification } : {}),
|
|
127
|
+
step_id: value.step_id, status: value.status, ...(value.verification ? { verification: value.verification } : {}), ...failure(value.failure),
|
|
127
128
|
checks: value.checks.map(check => ({ id: check.id, result: check.result, summary: '', evidence: [], method: check.method })), changes: {}, events: value.events.map(event),
|
|
128
129
|
}])),
|
|
129
130
|
};
|