@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.
Files changed (64) hide show
  1. package/LICENSE +208 -21
  2. package/PROMISES.md +52 -0
  3. package/README.md +334 -138
  4. package/SECURITY-MCP.md +117 -0
  5. package/SECURITY-SECRETS.md +108 -0
  6. package/STYLE.md +23 -0
  7. package/bin/jos +170 -160
  8. package/examples/01-hello.jos +44 -0
  9. package/examples/02-criteria.jos +54 -0
  10. package/examples/03-flow-gate.jos +83 -0
  11. package/examples/04-use-template/README.md +22 -0
  12. package/examples/04-use-template/lib/greeting.jos +56 -0
  13. package/examples/04-use-template/use.jos +64 -0
  14. package/examples/05-sealed/.jos/manifest.jos +53 -0
  15. package/examples/05-sealed/README.md +25 -0
  16. package/examples/06-requires.jos +42 -0
  17. package/examples/07-sandbox.jos +39 -0
  18. package/examples/08-ids-ledger-meter.md +26 -0
  19. package/examples/10-ollama.jos +47 -0
  20. package/examples/11-openai.jos +47 -0
  21. package/examples/12-gemini.jos +47 -0
  22. package/examples/13-anthropic.jos +47 -0
  23. package/examples/14-serve.jos +88 -0
  24. package/examples/README.md +105 -0
  25. package/examples/hello.jos +47 -0
  26. package/package.json +57 -40
  27. package/promises.json +894 -0
  28. package/src/args.js +47 -0
  29. package/src/commands/_frozen/add.README.md +1 -0
  30. package/src/commands/_frozen/get.README.md +1 -0
  31. package/src/commands/_frozen/llm.README.md +1 -0
  32. package/src/commands/_frozen/prompts.README.md +1 -0
  33. package/src/commands/_frozen/repo.README.md +1 -0
  34. package/src/commands/_frozen/serve-explorer.README.md +1 -0
  35. package/src/commands/ids.js +101 -0
  36. package/src/commands/init.js +128 -162
  37. package/src/commands/ledger.js +94 -0
  38. package/src/commands/meter.js +96 -0
  39. package/src/commands/migrate.js +214 -0
  40. package/src/commands/overview.js +118 -0
  41. package/src/commands/run.js +194 -244
  42. package/src/commands/runs.js +102 -0
  43. package/src/commands/secrets.js +717 -137
  44. package/src/commands/start.js +210 -0
  45. package/src/commands/validate.js +178 -194
  46. package/src/commands/verify.js +56 -98
  47. package/src/commands/version.js +23 -0
  48. package/src/intention.js +114 -0
  49. package/src/kernel.js +69 -0
  50. package/src/ledgers.js +57 -0
  51. package/src/mcp-guide.js +138 -0
  52. package/src/mcp.js +734 -0
  53. package/src/serve.js +48 -781
  54. package/src/ui.js +98 -0
  55. package/src/adapters/index.js +0 -16
  56. package/src/adapters/native.js +0 -41
  57. package/src/adapters/ollama.js +0 -140
  58. package/src/commands/add.js +0 -145
  59. package/src/commands/get.js +0 -245
  60. package/src/commands/llm.js +0 -159
  61. package/src/commands/prompts.js +0 -229
  62. package/src/commands/repo.js +0 -139
  63. package/src/utils/crypto.js +0 -41
  64. package/src/utils/request.js +0 -41
@@ -1,244 +1,194 @@
1
- /**
2
- * JOS RUN Command - Execute .jos artifacts
3
- * Complies with JOSFOXAI MAGIC contract
4
- * Integrity Strategy: Detached Manifest (.sig.json)
5
- * Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
- const { execSync, spawn } = require('child_process');
11
- const crypto = require('crypto');
12
-
13
- // AURORA colors
14
- const C = {
15
- reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
16
- purple: '\x1b[38;5;135m', cyan: '\x1b[38;5;51m',
17
- green: '\x1b[38;5;78m', red: '\x1b[38;5;196m',
18
- yellow: '\x1b[38;5;220m', white: '\x1b[38;5;255m',
19
- gray: '\x1b[38;5;245m', blue: '\x1b[38;5;39m'
20
- };
21
-
22
- // Witness Logging
23
- function logWitness(home, eventType, data) {
24
- const runId = global.JOS_RUN_ID || new Date().toISOString().replace(/[:.]/g, '-');
25
- global.JOS_RUN_ID = runId;
26
-
27
- const runDir = path.join(home, 'runs', runId);
28
- if (!fs.existsSync(runDir)) fs.mkdirSync(runDir, { recursive: true });
29
-
30
- const event = {
31
- timestamp: new Date().toISOString(),
32
- type: `run:${eventType}`,
33
- ...data
34
- };
35
-
36
- fs.appendFileSync(path.join(runDir, 'events.jsonl'), JSON.stringify(event) + '\n');
37
- }
38
-
39
- // Compute SHA-256 integrity
40
- function computeIntegrity(content) {
41
- return crypto.createHash('sha256').update(content).digest('hex');
42
- }
43
-
44
- // Verify Integrity using Detached Manifest
45
- function verifyIntegrity(artifactPath, content, artifact) {
46
- const integrityRef = artifact.security?.integrity_ref;
47
- if (!integrityRef) return { valid: true, warning: "open (no integrity_ref)" };
48
-
49
- const manifestPath = path.resolve(path.dirname(artifactPath), integrityRef);
50
- if (!fs.existsSync(manifestPath)) {
51
- return { valid: false, error: `Manifest not found at ${manifestPath}` };
52
- }
53
-
54
- let manifest;
55
- try {
56
- manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
57
- } catch (e) {
58
- return { valid: false, error: "Invalid manifest JSON" };
59
- }
60
-
61
- const actualHash = computeIntegrity(content);
62
- if (actualHash !== manifest.artifact_sha256) {
63
- return {
64
- valid: false,
65
- error: `Hash mismatch. Expected: ${manifest.artifact_sha256.substring(0, 16)}..., Got: ${actualHash.substring(0, 16)}...`
66
- };
67
- }
68
-
69
- return { valid: true, verified: true, hash: actualHash };
70
- }
71
-
72
- exports.execute = async (args, home) => {
73
- const target = args[0];
74
- const dryRun = args.includes('--dry-run');
75
- const insecure = args.includes('--insecure');
76
-
77
- if (args.includes('--help') || !target) {
78
- console.log(`
79
- ${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
80
- ${C.bold}JOS RUN${C.reset} // Execute .jos artifacts
81
- ${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
82
-
83
- ${C.bold}Usage:${C.reset}
84
- jos run <file.jos> [options]
85
-
86
- ${C.bold}Options:${C.reset}
87
- --flow <name> Flow to execute (default: main)
88
- --dry-run Simulate without executing
89
- --insecure Skip integrity verification
90
-
91
- ${C.bold}Example:${C.reset}
92
- jos run orchestration.jos --flow publish_all --dry-run
93
- `);
94
- return;
95
- }
96
-
97
- let artifactPath = path.resolve(target);
98
- if (!fs.existsSync(artifactPath)) {
99
- if (fs.existsSync(path.join(home, 'artifacts', target))) {
100
- artifactPath = path.join(home, 'artifacts', target);
101
- } else {
102
- console.log(`${C.red}✖ Artifact not found:${C.reset} ${target}`);
103
- process.exit(1);
104
- }
105
- }
106
-
107
- const content = fs.readFileSync(artifactPath, 'utf8');
108
- const artifact = JSON.parse(content);
109
-
110
- // Init Witness
111
- logWitness(home, 'start', { artifact: artifactPath, dryRun: dryRun });
112
-
113
- // Header
114
- console.log(`\n${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
115
- console.log(`${C.cyan}${C.bold}JOS RUN${C.reset} // ${C.gray}JOSFOXAI MAGIC Runtime${C.reset}`);
116
- console.log(`${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`);
117
-
118
- // Mode indicator
119
- if (dryRun) {
120
- console.log(`${C.yellow}${C.bold}⚡ DRY RUN MODE${C.reset} ${C.gray}— No commands will be executed${C.reset}\n`);
121
- }
122
-
123
- // Artifact info
124
- console.log(`${C.cyan}📦 Artifact:${C.reset} ${artifact.meta?.name || path.basename(artifactPath)}`);
125
- console.log(`${C.cyan}📄 File:${C.reset} ${path.basename(artifactPath)}`);
126
- console.log(`${C.cyan}🆔 ID:${C.reset} ${artifact.id_jos || 'N/A'}`);
127
-
128
- // Integrity verification
129
- if (!insecure) {
130
- const integrity = verifyIntegrity(artifactPath, content, artifact);
131
- if (!integrity.valid) {
132
- console.log(`${C.cyan}🔐 Integrity:${C.reset} ${C.red}FAILED - ${integrity.error}${C.reset}`);
133
- logWitness(home, 'integrity_failed', { error: integrity.error });
134
- process.exit(1);
135
- } else if (integrity.verified) {
136
- console.log(`${C.cyan}🔐 Integrity:${C.reset} ${C.green}✓ Verified${C.reset} ${C.gray}(${integrity.hash.substring(0, 16)}...)${C.reset}`);
137
- logWitness(home, 'integrity_verified', { hash: integrity.hash });
138
- } else {
139
- console.log(`${C.cyan}🔐 Integrity:${C.reset} ${C.yellow}⚠ ${integrity.warning}${C.reset}`);
140
- }
141
- } else {
142
- console.log(`${C.cyan}🔐 Integrity:${C.reset} ${C.yellow}⚠ Skipped (--insecure)${C.reset}`);
143
- }
144
-
145
- // MAGIC validation
146
- const hasMagic = artifact.intention && artifact.guardrails && artifact.capabilities;
147
- console.log(`${C.cyan}✨ MAGIC:${C.reset} ${hasMagic ? C.green + '✓ Valid' : C.yellow + '⚠ Partial'}${C.reset}`);
148
-
149
- // Intention
150
- if (artifact.intention?.objective) {
151
- console.log(`${C.cyan}🎯 Intention:${C.reset} ${artifact.intention.objective}`);
152
- }
153
-
154
- // Guardrails
155
- if (artifact.guardrails?.avoid?.length > 0) {
156
- console.log(`${C.cyan}🛡️ Guardrails:${C.reset} ${artifact.guardrails.avoid.join(', ')}`);
157
- }
158
-
159
- // Flow selection
160
- const flowName = args.includes('--flow') ? args[args.indexOf('--flow') + 1] : 'main';
161
- const flow = artifact.orchestration?.flows?.[flowName] ||
162
- artifact.orchestration?.pipelines?.[flowName];
163
-
164
- if (!flow) {
165
- console.log(`\n${C.red}✖ Flow '${flowName}' not found.${C.reset}`);
166
- const available = [
167
- ...Object.keys(artifact.orchestration?.flows || {}),
168
- ...Object.keys(artifact.orchestration?.pipelines || {})
169
- ];
170
- if (available.length > 0) {
171
- console.log(`${C.gray}Available flows: ${available.join(', ')}${C.reset}`);
172
- }
173
- process.exit(1);
174
- }
175
-
176
- // Execution plan
177
- console.log(`\n${C.cyan}▶ Flow:${C.reset} ${C.bold}${flowName}${C.reset}`);
178
- if (flow.description) {
179
- console.log(`${C.gray} ${flow.description}${C.reset}`);
180
- }
181
- console.log(`${C.gray} Steps: ${flow.steps?.join(' → ') || 'none'}${C.reset}\n`);
182
-
183
- // Execute steps
184
- let stepIndex = 0;
185
- for (const stepName of flow.steps || []) {
186
- stepIndex++;
187
- const def = artifact.orchestration.definitions?.[stepName];
188
-
189
- if (!def) {
190
- console.log(`${C.red} [${stepIndex}] ✖ ${stepName}: Definition not found${C.reset}`);
191
- logWitness(home, 'step_error', { step: stepName, error: 'definition not found' });
192
- continue;
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
+ };