@josfox/jos 4.1.0 → 5.0.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/LICENSE +208 -21
- package/PROMISES.md +52 -0
- package/README.md +334 -138
- package/SECURITY-MCP.md +117 -0
- package/SECURITY-SECRETS.md +108 -0
- package/STYLE.md +23 -0
- package/bin/jos +170 -160
- package/examples/01-hello.jos +44 -0
- package/examples/02-criteria.jos +54 -0
- package/examples/03-flow-gate.jos +83 -0
- package/examples/04-use-template/README.md +22 -0
- package/examples/04-use-template/lib/greeting.jos +56 -0
- package/examples/04-use-template/use.jos +64 -0
- package/examples/05-sealed/.jos/manifest.jos +53 -0
- package/examples/05-sealed/README.md +25 -0
- package/examples/06-requires.jos +42 -0
- package/examples/07-sandbox.jos +39 -0
- package/examples/08-ids-ledger-meter.md +26 -0
- package/examples/10-ollama.jos +47 -0
- package/examples/11-openai.jos +47 -0
- package/examples/12-gemini.jos +47 -0
- package/examples/13-anthropic.jos +47 -0
- package/examples/14-serve.jos +88 -0
- package/examples/README.md +105 -0
- package/examples/hello.jos +47 -0
- package/package.json +57 -40
- package/promises.json +894 -0
- package/src/args.js +47 -0
- package/src/commands/_frozen/add.README.md +1 -0
- package/src/commands/_frozen/get.README.md +1 -0
- package/src/commands/_frozen/llm.README.md +1 -0
- package/src/commands/_frozen/prompts.README.md +1 -0
- package/src/commands/_frozen/repo.README.md +1 -0
- package/src/commands/_frozen/serve-explorer.README.md +1 -0
- package/src/commands/ids.js +101 -0
- package/src/commands/init.js +128 -162
- package/src/commands/ledger.js +94 -0
- package/src/commands/meter.js +96 -0
- package/src/commands/migrate.js +214 -0
- package/src/commands/overview.js +118 -0
- package/src/commands/run.js +194 -244
- package/src/commands/runs.js +102 -0
- package/src/commands/secrets.js +717 -137
- package/src/commands/start.js +210 -0
- package/src/commands/validate.js +178 -194
- package/src/commands/verify.js +56 -98
- package/src/commands/version.js +23 -0
- package/src/intention.js +114 -0
- package/src/kernel.js +69 -0
- package/src/ledgers.js +57 -0
- package/src/mcp-guide.js +138 -0
- package/src/mcp.js +734 -0
- package/src/serve.js +48 -781
- package/src/ui.js +98 -0
- package/src/adapters/index.js +0 -16
- package/src/adapters/native.js +0 -41
- package/src/adapters/ollama.js +0 -140
- package/src/commands/add.js +0 -145
- package/src/commands/get.js +0 -245
- package/src/commands/llm.js +0 -159
- package/src/commands/prompts.js +0 -229
- package/src/commands/repo.js +0 -139
- package/src/utils/crypto.js +0 -41
- package/src/utils/request.js +0 -41
package/src/commands/run.js
CHANGED
|
@@ -1,244 +1,194 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JOS RUN
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
if (!
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (artifact.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
process.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
logWitness(home, 'step_start', { step: stepName, type: def.type, index: stepIndex });
|
|
196
|
-
|
|
197
|
-
const typeIcon = def.type === 'shell' ? '⚡' : def.type === 'jos' ? '📦' : '●';
|
|
198
|
-
|
|
199
|
-
if (dryRun) {
|
|
200
|
-
console.log(`${C.yellow} [${stepIndex}] ${typeIcon} ${stepName}${C.reset}`);
|
|
201
|
-
if (def.description) console.log(`${C.gray} ${def.description}${C.reset}`);
|
|
202
|
-
if (def.type === 'shell') {
|
|
203
|
-
const cmdPreview = def.command.length > 60 ? def.command.substring(0, 60) + '...' : def.command;
|
|
204
|
-
console.log(`${C.dim} $ ${cmdPreview}${C.reset}`);
|
|
205
|
-
} else if (def.type === 'jos') {
|
|
206
|
-
console.log(`${C.dim} → ${def.artifact}${C.reset}`);
|
|
207
|
-
}
|
|
208
|
-
logWitness(home, 'step_dry_run', { step: stepName });
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Real execution
|
|
213
|
-
console.log(`${C.blue} [${stepIndex}] ${typeIcon} ${stepName}...${C.reset}`);
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
if (def.type === 'shell') {
|
|
217
|
-
execSync(def.command, { stdio: 'inherit' });
|
|
218
|
-
console.log(`${C.green} ✓ Complete${C.reset}`);
|
|
219
|
-
} else if (def.type === 'jos') {
|
|
220
|
-
console.log(`${C.purple} Running sub-artifact: ${def.artifact}${C.reset}`);
|
|
221
|
-
const subPath = path.resolve(path.dirname(artifactPath), def.artifact);
|
|
222
|
-
const binJos = process.argv[1];
|
|
223
|
-
execSync(`${process.execPath} "${binJos}" run "${subPath}" ${insecure ? '--insecure' : ''} --flow main`, {
|
|
224
|
-
stdio: 'inherit'
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
logWitness(home, 'step_complete', { step: stepName, success: true });
|
|
228
|
-
} catch (e) {
|
|
229
|
-
console.log(`${C.red} ✖ Failed: ${e.message}${C.reset}`);
|
|
230
|
-
logWitness(home, 'step_failed', { step: stepName, error: e.message });
|
|
231
|
-
logWitness(home, 'run_failed', { lastStep: stepName });
|
|
232
|
-
process.exit(1);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Success
|
|
237
|
-
console.log(`\n${C.green}${C.bold}✓ ${dryRun ? 'Dry run complete' : 'Execution complete'}${C.reset}`);
|
|
238
|
-
if (artifact.intention?.success_criteria) {
|
|
239
|
-
console.log(`${C.gray} Success criteria: ${artifact.intention.success_criteria}${C.reset}`);
|
|
240
|
-
}
|
|
241
|
-
console.log('');
|
|
242
|
-
|
|
243
|
-
logWitness(home, 'success', { dryRun: dryRun, stepsExecuted: stepIndex });
|
|
244
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* JOS RUN - Execute a .jos document through the jos-core kernel
|
|
3
|
+
* Accepts 2.0.0, 1.1.0, 1.0.0 and 0.0.7 documents (jos-core normalizes them).
|
|
4
|
+
* Exit codes: SPEC-2.0 section 7.1; 64 for command-line usage errors.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const kernel = require('../kernel');
|
|
9
|
+
const ui = require('../ui');
|
|
10
|
+
const { RULE } = ui;
|
|
11
|
+
const { parse, problem } = require('../args');
|
|
12
|
+
const intention = require('../intention');
|
|
13
|
+
|
|
14
|
+
// The options of `jos run`; each one passed to jos-core as is (SPEC-2.0 §5: flows, `use`, gates, data flow).
|
|
15
|
+
const OPTIONS = {
|
|
16
|
+
'--json': false, '--dry-run': false,
|
|
17
|
+
'--flow': true, '--library': true, '--approve': 'many', '--input': true, '--with': true,
|
|
18
|
+
};
|
|
19
|
+
const USAGE = 'usage: jos run <file.jos | folder> [--json] [--dry-run] [--flow <name>] [--library <dir>] [--approve <step>]... [--input <json>] [--with <json>]';
|
|
20
|
+
|
|
21
|
+
const CODE_MEANING = {
|
|
22
|
+
0: 'done', 1: 'not done', 2: 'invalid document', 3: 'blocked', 4: 'budget exceeded',
|
|
23
|
+
5: 'unexecutable', 124: 'timeout', 137: 'output limit or kill',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const HELP = (C) => `
|
|
27
|
+
${C.purple}${RULE}${C.reset}
|
|
28
|
+
${C.bold}JOS RUN${C.reset} // Execute .jos documents with jos-core
|
|
29
|
+
${C.purple}${RULE}${C.reset}
|
|
30
|
+
|
|
31
|
+
${C.bold}Usage:${C.reset}
|
|
32
|
+
jos run <file.jos | folder> [options]
|
|
33
|
+
jos run [options] -- <file.jos | folder> (-- ends the options)
|
|
34
|
+
|
|
35
|
+
A .jos document runs in its own folder, not in the current directory.
|
|
36
|
+
A folder D with a .jos/ directory (D or D/.jos) runs D/.jos/manifest.jos in D;
|
|
37
|
+
a sealed folder is verified by its directory hash (SPEC-2.0 9.3).
|
|
38
|
+
A 0.0.7 document sealed by security.integrity_ref is verified against its
|
|
39
|
+
manifest before anything starts, --dry-run too: a mismatch is code 3.
|
|
40
|
+
|
|
41
|
+
${C.bold}Options:${C.reset}
|
|
42
|
+
--json Print the result object (SPEC-2.0 7.4) as JSON
|
|
43
|
+
--dry-run All checks, no launch; prints the plan
|
|
44
|
+
--flow <name> Run this flow of orchestration.flows (default: main, or the only one)
|
|
45
|
+
--library <dir> Also resolve \`use\` references in this folder (after the .jos/ chain)
|
|
46
|
+
--approve <step> Approve the human gate of <step> (repeat for several; nested: outer/step,
|
|
47
|
+
second occurrence: step#2); without it the flow stops there, code 1
|
|
48
|
+
--input <json> The run's input: \`input\` and \`last\` in the flow, a step's stdin
|
|
49
|
+
--with <json> Slot values for a template run directly (SPEC-2.0 5.7)
|
|
50
|
+
|
|
51
|
+
${C.bold}Files written:${C.reset}
|
|
52
|
+
jos-core keeps a ledger of each run (not of --dry-run): <run>/ledger.jsonl in .jos/replays/
|
|
53
|
+
of the nearest .jos/ folder with a manifest.jos above the document (or ~/.jos, if it has
|
|
54
|
+
one and there is no other); otherwise in ~/.jos/runs/.
|
|
55
|
+
|
|
56
|
+
After a run: jos runs, jos ledger verify <run_id>, jos meter <run_id>.
|
|
57
|
+
|
|
58
|
+
${C.bold}Output:${C.reset}
|
|
59
|
+
Before it runs: the document's intention (objective, "Done when" criteria, avoid,
|
|
60
|
+
limit). After: each receipt with the verdict of each criterion, as jos-core
|
|
61
|
+
evaluated it, and "Intention met" or "Intention NOT met (code N: ...)".
|
|
62
|
+
|
|
63
|
+
${C.bold}Exit codes:${C.reset}
|
|
64
|
+
0 done, 1 not done, 2 invalid, 3 blocked, 4 budget, 5 unexecutable,
|
|
65
|
+
124 timeout, 137 output limit, 64 usage
|
|
66
|
+
|
|
67
|
+
${C.bold}Examples:${C.reset}
|
|
68
|
+
jos run hello.jos --dry-run
|
|
69
|
+
jos run ./my-project --json
|
|
70
|
+
jos run release.jos --approve build
|
|
71
|
+
jos run greet.jos --with '{"name":"Ana"}'
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
exports.execute = async (args) => {
|
|
75
|
+
const err = ui.printer(process.stderr);
|
|
76
|
+
const { C } = err;
|
|
77
|
+
const { opts, files, help, error } = parse(args, OPTIONS);
|
|
78
|
+
if (help) {
|
|
79
|
+
const out = ui.printer(process.stdout);
|
|
80
|
+
out.text(HELP(out.C));
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
if (error) {
|
|
84
|
+
err.fail('jos run: ' + problem(error));
|
|
85
|
+
if (error.option === '--insecure') err.hint('(removed in 5.0.0: jos-core checks integrity itself)');
|
|
86
|
+
err.hint(USAGE);
|
|
87
|
+
return 64;
|
|
88
|
+
}
|
|
89
|
+
if (files.length !== 1) {
|
|
90
|
+
err.fail('jos run: takes exactly one file');
|
|
91
|
+
err.hint(USAGE);
|
|
92
|
+
return 64;
|
|
93
|
+
}
|
|
94
|
+
const json = opts['--json'] === true;
|
|
95
|
+
const dryRun = opts['--dry-run'] === true;
|
|
96
|
+
const runOpts = {};
|
|
97
|
+
if (opts['--flow'] !== undefined) runOpts.flow = opts['--flow'];
|
|
98
|
+
if (opts['--library'] !== undefined) runOpts.library = path.resolve(opts['--library']);
|
|
99
|
+
if (opts['--approve'] !== undefined) runOpts.approvals = opts['--approve'];
|
|
100
|
+
for (const [flag, key] of [['--input', 'input'], ['--with', 'with']]) {
|
|
101
|
+
if (opts[flag] === undefined) continue;
|
|
102
|
+
try { runOpts[key] = JSON.parse(opts[flag]); } catch (e) {
|
|
103
|
+
err.fail('jos run: ' + flag + ' takes JSON', '(' + e.message + ')');
|
|
104
|
+
err.hint("example: " + flag + " '{\"name\":\"Ana\"}'");
|
|
105
|
+
return 64;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let k;
|
|
110
|
+
let core;
|
|
111
|
+
try { k = kernel.locate(); core = kernel.need(k, ['loadFileBytes']); } catch (e) { return kernel.fail(e); }
|
|
112
|
+
// SPEC-2.0 §9.1: a folder D (or D/.jos) runs its D/.jos/manifest.jos, in D; older kernels without documentPath get the path as given
|
|
113
|
+
const given = path.resolve(files[0]);
|
|
114
|
+
const file = typeof core.documentPath === 'function' ? core.documentPath(given) : given;
|
|
115
|
+
const base = path.basename(file) === 'manifest.jos' && path.basename(path.dirname(file)) === '.jos' ? path.dirname(path.dirname(file)) : path.dirname(file);
|
|
116
|
+
|
|
117
|
+
// The file is read once: the kernel hashes the same octets it parses (a 0.0.7 document sealed by
|
|
118
|
+
// security.integrity_ref is verified against them, jos-core 2.0.0-rc.8), so they travel with the document.
|
|
119
|
+
let doc = null;
|
|
120
|
+
let source = null;
|
|
121
|
+
let loadErrors = null;
|
|
122
|
+
try { ({ doc, bytes: source } = core.loadFileBytes(file)); } catch (e) {
|
|
123
|
+
if (!(e instanceof core.JosError)) throw e;
|
|
124
|
+
loadErrors = e.errors;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Text mode: the header and the intention of the document go out before it runs (a long step, a server, a gate
|
|
128
|
+
// waiting: the person sees what was asked first); the receipts and the verdict after.
|
|
129
|
+
if (!json) {
|
|
130
|
+
err.header(`${C.cyan}JOS RUN`, 'jos-core ' + k.version);
|
|
131
|
+
if (dryRun) err.warn(`${C.bold}DRY RUN MODE${C.reset}${C.yellow}: no commands will be executed`);
|
|
132
|
+
err.field('File', file === path.join(base, '.jos', 'manifest.jos') ? path.join(path.basename(base), '.jos', 'manifest.jos') : path.basename(file));
|
|
133
|
+
const v = doc === null ? null : core.validate(doc);
|
|
134
|
+
if (v !== null && v.valid) intention.intention(err, v.document);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let result;
|
|
138
|
+
if (loadErrors) {
|
|
139
|
+
result = { ok: false, code: 2, run_id: core.josid.mint('run'), artifact: null, input_version: null, capability: null, receipts: [], output: null, duration_ms: 0, errors: loadErrors };
|
|
140
|
+
if (dryRun) { result.dry_run = true; delete result.ok; delete result.receipts; delete result.output; result.plan = null; }
|
|
141
|
+
} else {
|
|
142
|
+
// dir: the document's directory, as the kernel's own CLI passes it; for D/.jos/manifest.jos it is D/.jos, which is how
|
|
143
|
+
// the kernel recognizes a directory run and verifies the directory hash (SPEC-2.0 §9.3)
|
|
144
|
+
// folder: a folder D (or D/.jos) names its manifest, which the kernel requires to be of kind directory before
|
|
145
|
+
// any step, with the code and text of `jos validate D` (SPEC-2.0 §9.1; readiness rc.7 pass 5 F-19)
|
|
146
|
+
const folder = file !== given ? { folder: true } : {};
|
|
147
|
+
result = await core.run(doc, Object.assign({ cwd: base, dir: path.dirname(file), source, env: process.env, dryRun }, folder, runOpts));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (json) {
|
|
151
|
+
ui.json(process.stdout, result);
|
|
152
|
+
return result.code;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (result.artifact || result.input_version || result.capability) err.line();
|
|
156
|
+
if (result.artifact) err.field('ID', result.artifact);
|
|
157
|
+
if (result.input_version) err.field('Input version', result.input_version);
|
|
158
|
+
if (result.capability) err.field('Capability', `${result.capability.cap} ${C.gray}(${result.capability.trust})${C.reset}`);
|
|
159
|
+
if (result.integrity_ref) err.field('Integrity', `${C.green}✓${C.reset} verified by ${result.integrity_ref.manifest} ${C.gray}(0.0.7 integrity_ref, sha256 ${result.integrity_ref.sha256.slice(0, 12)}…, ${result.integrity_ref.eol})${C.reset}`);
|
|
160
|
+
|
|
161
|
+
if (result.plan) {
|
|
162
|
+
const p = result.plan;
|
|
163
|
+
err.line();
|
|
164
|
+
err.line(`${C.cyan}▶ Plan:${C.reset} ${C.bold}${p.engine}${C.reset}`);
|
|
165
|
+
if (Array.isArray(p.run)) err.cmd(p.run.join(' '));
|
|
166
|
+
else if (p.run) err.cmd(p.run);
|
|
167
|
+
if (p.limits) err.hint('limits: ' + JSON.stringify(p.limits));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (result.ledger) err.field('Ledger', result.ledger);
|
|
171
|
+
if (result.output) {
|
|
172
|
+
if (result.output.stdout || result.output.stderr) err.line();
|
|
173
|
+
ui.data(process.stdout, result.output.stdout);
|
|
174
|
+
ui.data(process.stderr, result.output.stderr);
|
|
175
|
+
}
|
|
176
|
+
if (!dryRun && result.receipts.length) intention.verdict(err, result);
|
|
177
|
+
|
|
178
|
+
const detail = result.errors ? result.errors.join('; ') : (result.reason || result.unexecutable || '');
|
|
179
|
+
const meaning = CODE_MEANING[result.code] || 'error';
|
|
180
|
+
err.line();
|
|
181
|
+
if (result.code === 0) {
|
|
182
|
+
err.ok(`${C.bold}${dryRun ? 'Dry run complete' : 'Intention met'}`, `(code 0, ${result.run_id})`);
|
|
183
|
+
} else {
|
|
184
|
+
err.fail(`${C.bold}${dryRun ? 'Dry run refused' : 'Intention NOT met'} (code ${result.code}: ${meaning})`, `(${result.run_id})`);
|
|
185
|
+
if (detail) err.hint(detail);
|
|
186
|
+
if (result.waiting) {
|
|
187
|
+
const approvals = (runOpts.approvals || []).concat(result.waiting.step).map((a) => ' --approve ' + ui.shellArg(a)).join('');
|
|
188
|
+
const target = ui.shellArg(files[0]);
|
|
189
|
+
err.hint('To approve it: jos run ' + (files[0].startsWith('-') ? approvals.slice(1) + ' -- ' + target : target + approvals));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
err.line();
|
|
193
|
+
return result.code;
|
|
194
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JOS RUNS - lists the runs whose ledgers jos-core kept in ~/.jos/runs/ (runs of documents outside any .jos/ folder):
|
|
3
|
+
* id, document, exit code, start time and metered cost, newest first. Each ledger is verified by jos-core; one that
|
|
4
|
+
* does not verify is listed with the reason and nothing else. Read-only: it never creates ~/.jos.
|
|
5
|
+
* Exit codes: 0, 5 no kernel, 64 usage.
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const kernel = require('../kernel');
|
|
11
|
+
const ledgers = require('../ledgers');
|
|
12
|
+
const ui = require('../ui');
|
|
13
|
+
const { RULE } = ui;
|
|
14
|
+
const { parse, problem } = require('../args');
|
|
15
|
+
|
|
16
|
+
const USAGE = 'usage: jos runs [--limit <n>] [--json]';
|
|
17
|
+
const DEFAULT_LIMIT = 20;
|
|
18
|
+
|
|
19
|
+
const HELP = (C) => `
|
|
20
|
+
${C.purple}${RULE}${C.reset}
|
|
21
|
+
${C.bold}JOS RUNS${C.reset} // The runs recorded in ~/.jos/runs
|
|
22
|
+
${C.purple}${RULE}${C.reset}
|
|
23
|
+
|
|
24
|
+
${C.bold}Usage:${C.reset}
|
|
25
|
+
jos runs [--limit <n>] [--json]
|
|
26
|
+
|
|
27
|
+
jos-core writes a ledger for every run (not --dry-run). Runs of documents inside a
|
|
28
|
+
folder with .jos/manifest.jos keep it in that folder's .jos/replays/; the others in
|
|
29
|
+
~/.jos/runs/, which this command lists, newest first. Every ledger is verified;
|
|
30
|
+
the cost is the metered cost_usd, shown only when an engine reported one.
|
|
31
|
+
|
|
32
|
+
${C.bold}Options:${C.reset}
|
|
33
|
+
--limit <n> Show at most n runs (default ${DEFAULT_LIMIT}; 0 shows all)
|
|
34
|
+
--json Print the list as JSON
|
|
35
|
+
|
|
36
|
+
${C.bold}See also:${C.reset}
|
|
37
|
+
jos ledger verify <run_id> jos meter <run_id>
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const CODE_MEANING = {
|
|
41
|
+
0: 'done', 1: 'not done', 2: 'invalid', 3: 'blocked', 4: 'budget', 5: 'unexecutable', 124: 'timeout', 137: 'output limit',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
exports.execute = async (args, josHome) => {
|
|
45
|
+
const err = ui.printer(process.stderr);
|
|
46
|
+
const { opts, files, help, error } = parse(args, { '--json': false, '--limit': true });
|
|
47
|
+
if (help) {
|
|
48
|
+
const out = ui.printer(process.stdout);
|
|
49
|
+
out.text(HELP(out.C));
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
const limit = opts['--limit'] === undefined ? DEFAULT_LIMIT : Number(opts['--limit']);
|
|
53
|
+
if (error || files.length || !Number.isInteger(limit) || limit < 0) {
|
|
54
|
+
err.fail('jos runs: ' + (error ? problem(error) : files.length ? 'takes no arguments' : '--limit takes a whole number, 0 or more'));
|
|
55
|
+
err.hint(USAGE);
|
|
56
|
+
return 64;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let core;
|
|
60
|
+
try { core = kernel.need(kernel.locate(), ['ledger.read', 'ledger.verify', 'ledger.meter']); } catch (e) { return kernel.fail(e); }
|
|
61
|
+
const dir = path.join(josHome, 'runs');
|
|
62
|
+
let names = [];
|
|
63
|
+
try { names = fs.readdirSync(dir); } catch (e) { /* no runs yet */ }
|
|
64
|
+
const ids = names.filter((n) => ledgers.RUN_ID.test(n) && fs.existsSync(path.join(dir, n, 'ledger.jsonl'))).sort().reverse();
|
|
65
|
+
const shownIds = limit === 0 ? ids : ids.slice(0, limit);
|
|
66
|
+
const rows = shownIds.map((id) => {
|
|
67
|
+
const file = path.join(dir, id, 'ledger.jsonl');
|
|
68
|
+
const r = ledgers.read(core, file);
|
|
69
|
+
const s = r.errors ? { ledger_ok: false, reason: r.errors.join('; '), started: null, document: null, artifact: null, code: null, cost_usd: null } : ledgers.summary(core, r.lines);
|
|
70
|
+
return Object.assign({ run: id, file }, s);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (opts['--json']) {
|
|
74
|
+
ui.json(process.stdout, rows);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
const out = ui.printer(process.stdout);
|
|
78
|
+
const { C } = out;
|
|
79
|
+
out.header('JOS RUNS', dir);
|
|
80
|
+
if (!ids.length) {
|
|
81
|
+
out.line(' No runs recorded here yet.');
|
|
82
|
+
out.hint('Runs of documents outside a .jos/ folder land here. Try: jos start');
|
|
83
|
+
out.line();
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
for (const r of rows) {
|
|
87
|
+
if (!r.ledger_ok) {
|
|
88
|
+
out.warn(r.run, '(ledger does not verify: ' + r.reason + ')');
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const when = r.started === null ? '' : r.started.replace(/\.\d+Z$/, 'Z');
|
|
92
|
+
const meaning = r.code === null ? 'no code' : (CODE_MEANING[r.code] || 'error');
|
|
93
|
+
const mark = r.code === 0 ? `${C.green}✓${C.reset}` : `${C.red}✖${C.reset}`;
|
|
94
|
+
out.line(`${mark} ${C.bold}${r.run}${C.reset} ${when} code ${r.code === null ? '-' : r.code} ${C.gray}(${meaning})${C.reset}`);
|
|
95
|
+
out.hint((r.document === null ? '(no receipt)' : r.document) + (r.artifact === null ? '' : ' ' + r.artifact) + (r.cost_usd === null ? '' : ' ' + r.cost_usd + ' USD'));
|
|
96
|
+
}
|
|
97
|
+
if (shownIds.length < ids.length) out.hint('… and ' + (ids.length - shownIds.length) + ' more (--limit 0 shows all)');
|
|
98
|
+
out.line();
|
|
99
|
+
out.hint('Next: jos ledger verify <run_id> jos meter <run_id>');
|
|
100
|
+
out.line();
|
|
101
|
+
return 0;
|
|
102
|
+
};
|