@josfox/jos 4.0.8 → 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 (59) hide show
  1. package/LICENSE +208 -21
  2. package/PROMISES.md +52 -0
  3. package/README.md +294 -146
  4. package/SECURITY-MCP.md +117 -0
  5. package/SECURITY-SECRETS.md +108 -0
  6. package/STYLE.md +23 -0
  7. package/bin/jos +139 -67
  8. package/bin/jos.cmd +5 -0
  9. package/examples/01-hello.jos +44 -0
  10. package/examples/02-criteria.jos +54 -0
  11. package/examples/03-flow-gate.jos +83 -0
  12. package/examples/04-use-template/README.md +22 -0
  13. package/examples/04-use-template/lib/greeting.jos +56 -0
  14. package/examples/04-use-template/use.jos +64 -0
  15. package/examples/05-sealed/.jos/manifest.jos +53 -0
  16. package/examples/05-sealed/README.md +25 -0
  17. package/examples/06-requires.jos +42 -0
  18. package/examples/07-sandbox.jos +39 -0
  19. package/examples/08-ids-ledger-meter.md +26 -0
  20. package/examples/10-ollama.jos +47 -0
  21. package/examples/11-openai.jos +47 -0
  22. package/examples/12-gemini.jos +47 -0
  23. package/examples/13-anthropic.jos +47 -0
  24. package/examples/14-serve.jos +88 -0
  25. package/examples/README.md +105 -0
  26. package/examples/hello.jos +47 -0
  27. package/package.json +31 -9
  28. package/promises.json +894 -0
  29. package/src/args.js +47 -0
  30. package/src/commands/_frozen/add.README.md +1 -0
  31. package/src/commands/_frozen/get.README.md +1 -0
  32. package/src/commands/_frozen/llm.README.md +1 -0
  33. package/src/commands/_frozen/prompts.README.md +1 -0
  34. package/src/commands/_frozen/repo.README.md +1 -0
  35. package/src/commands/_frozen/serve-explorer.README.md +1 -0
  36. package/src/commands/ids.js +101 -0
  37. package/src/commands/init.js +113 -147
  38. package/src/commands/ledger.js +94 -0
  39. package/src/commands/meter.js +96 -0
  40. package/src/commands/migrate.js +214 -0
  41. package/src/commands/overview.js +118 -0
  42. package/src/commands/run.js +164 -214
  43. package/src/commands/runs.js +102 -0
  44. package/src/commands/secrets.js +695 -115
  45. package/src/commands/start.js +210 -0
  46. package/src/commands/validate.js +153 -165
  47. package/src/commands/verify.js +56 -0
  48. package/src/commands/version.js +23 -0
  49. package/src/intention.js +114 -0
  50. package/src/kernel.js +69 -0
  51. package/src/ledgers.js +57 -0
  52. package/src/mcp-guide.js +138 -0
  53. package/src/mcp.js +734 -0
  54. package/src/serve.js +38 -770
  55. package/src/ui.js +98 -0
  56. package/src/commands/add.js +0 -145
  57. package/src/commands/get.js +0 -245
  58. package/src/commands/repo.js +0 -139
  59. package/src/index.js +0 -0
@@ -0,0 +1,64 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "flow"
5
+ },
6
+ "id": "tour.use-template",
7
+ "meta": {
8
+ "name": "tour.use-template",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "Greet two people with one template, pinned by its digest",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ },
17
+ {
18
+ "stdout_contains": "Leo"
19
+ }
20
+ ]
21
+ },
22
+ "guardrails": {
23
+ "avoid": [
24
+ "rm -rf"
25
+ ],
26
+ "limit": {
27
+ "seconds": 10,
28
+ "output_bytes": 65536,
29
+ "retries": 0
30
+ }
31
+ },
32
+ "orchestration": {
33
+ "definitions": {
34
+ "ana": {
35
+ "use": "tour.greeting@^1.0.0",
36
+ "with": {
37
+ "name": "Ana"
38
+ }
39
+ },
40
+ "leo": {
41
+ "use": "tour.greeting@^1.0.0",
42
+ "with": {
43
+ "name": "Leo"
44
+ }
45
+ }
46
+ },
47
+ "flows": {
48
+ "main": {
49
+ "steps": [
50
+ "ana",
51
+ "leo"
52
+ ]
53
+ }
54
+ }
55
+ },
56
+ "requires": [
57
+ {
58
+ "kind": "artifact",
59
+ "name": "tour.greeting",
60
+ "version": "^1.0.0",
61
+ "digest": "art_7ed518676186fe910bc5441ba4ae0126b6ebdeaedec7ef14b7a96422da764210"
62
+ }
63
+ ]
64
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "directory"
5
+ },
6
+ "id": "tour.sealed",
7
+ "meta": {
8
+ "name": "tour.sealed",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "Run only while this folder is exactly as it was sealed",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ },
17
+ {
18
+ "stdout_contains": "sealed folder ran"
19
+ }
20
+ ]
21
+ },
22
+ "guardrails": {
23
+ "avoid": [
24
+ "rm -rf"
25
+ ],
26
+ "limit": {
27
+ "seconds": 10,
28
+ "output_bytes": 65536,
29
+ "retries": 0
30
+ }
31
+ },
32
+ "orchestration": {
33
+ "definitions": {
34
+ "say": {
35
+ "engine": "shell",
36
+ "run": [
37
+ "echo",
38
+ "the sealed folder ran"
39
+ ]
40
+ }
41
+ },
42
+ "flows": {
43
+ "main": {
44
+ "steps": [
45
+ "say"
46
+ ]
47
+ }
48
+ }
49
+ },
50
+ "integrity": {
51
+ "hash": "sha256:621c560a601e2274e1e3009f226e8b3cd3934eff0adb32f62e6b95018ad945eb"
52
+ }
53
+ }
@@ -0,0 +1,25 @@
1
+ # 05 · a sealed folder
2
+
3
+ A folder with a `.jos/manifest.jos` is itself a `.jos` document (kind `directory`). This one is sealed: its
4
+ `integrity.hash` is the hash jos-core computes over everything in `.jos/` (the manifest with `integrity.hash`
5
+ blanked, and every other file; `replays/`, `logs/` and `dev/` are not counted). `jos run` recomputes it before
6
+ anything starts.
7
+
8
+ | Command | Result |
9
+ |---|---|
10
+ | `jos run 05-sealed` | the hash matches: prints `the sealed folder ran`; exit 0. The ledger of the run goes to `05-sealed/.jos/replays/` (not counted in the hash) |
11
+ | `jos ids 05-sealed` | `directory: sha256:… (sealed, matches)` |
12
+
13
+ To see the seal refuse a change, edit the text `the sealed folder ran` in `.jos/manifest.jos` (or add any file
14
+ under `.jos/`) and run it again:
15
+
16
+ ```
17
+ $ jos run 05-sealed
18
+ …
19
+ ✖ Intention NOT met (code 3: blocked) (run_…)
20
+ integrity: declared hash does not match the directory hash (SPEC-2.0 §9.3)
21
+ ```
22
+
23
+ Exit 3, and nothing ran. `jos ids 05-sealed` now says `sealed, does NOT match`. Undo the edit and it runs again.
24
+
25
+ What the seal covers is `.jos/` only: this README, next to `.jos/`, is not part of the hash.
@@ -0,0 +1,42 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "atom"
5
+ },
6
+ "id": "tour.requires",
7
+ "meta": {
8
+ "name": "tour.requires",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "Charge a card through the payments service",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ }
17
+ ]
18
+ },
19
+ "guardrails": {
20
+ "limit": {
21
+ "seconds": 10,
22
+ "output_bytes": 65536,
23
+ "retries": 0
24
+ }
25
+ },
26
+ "execution": {
27
+ "engine": "shell",
28
+ "run": [
29
+ "echo",
30
+ "charged"
31
+ ],
32
+ "env": [
33
+ "PATH"
34
+ ]
35
+ },
36
+ "requires": [
37
+ {
38
+ "kind": "service",
39
+ "name": "payments"
40
+ }
41
+ ]
42
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "atom"
5
+ },
6
+ "id": "tour.sandbox",
7
+ "meta": {
8
+ "name": "tour.sandbox",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "Run a step that must not reach the network or files outside its folder",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ }
17
+ ]
18
+ },
19
+ "guardrails": {
20
+ "limit": {
21
+ "seconds": 10,
22
+ "output_bytes": 65536,
23
+ "retries": 0
24
+ }
25
+ },
26
+ "execution": {
27
+ "engine": "shell",
28
+ "run": [
29
+ "echo",
30
+ "confined"
31
+ ],
32
+ "env": [
33
+ "PATH"
34
+ ]
35
+ },
36
+ "security": {
37
+ "tier": "sandbox"
38
+ }
39
+ }
@@ -0,0 +1,26 @@
1
+ # 08 · identities, the ledger and the meter
2
+
3
+ Every `jos run` (not `--dry-run`) leaves a hash-chained ledger. These commands read what jos-core recorded; none of
4
+ them runs a document or writes anything.
5
+
6
+ ```
7
+ jos ids 01-hello.jos 05-sealed # art_ (content), ent_ (name), and the directory hash of a folder
8
+ jos run 01-hello.jos # note the run id it prints: run_…
9
+ jos runs # the runs in ~/.jos/runs, newest first: id, start, code, document, art_
10
+ jos ledger verify run_… # every entry's hash recomputed, chain continuous, run ended
11
+ jos meter run_… # steps, attempts, time; tokens and cost when an engine reported them
12
+ ```
13
+
14
+ What to expect:
15
+
16
+ | Command | Result |
17
+ |---|---|
18
+ | `jos ids 01-hello.jos` | `artifact: art_40762df5…`: the same value on every machine and in both reference kernels; `entity: ent_…` from `atom:tour.hello` |
19
+ | `jos runs` | `✓ run_… <start> code 0 (done)` and, below it, `tour.hello art_…`; a ledger that does not verify is listed with `⚠` and the reason |
20
+ | `jos ledger verify run_…` | `✓ LEDGER VERIFIED`, with the number of entries and the head hash; exit 0. Change one byte of the run's `ledger.jsonl` and it is `✖ LEDGER DOES NOT VERIFY`, `Reason: hash_mismatch`, exit 1 |
21
+ | `jos meter run_…` | `Code: 0`, `Steps: tour.hello`, `Engines: shell`, `Time: … ms`, `Tokens in: not reported`, `Cost: not reported` (the shell engine reports neither; never shown as 0) |
22
+
23
+ `jos ledger verify` and `jos meter` also take the path of a `ledger.jsonl` or of a run folder. A run id is looked up
24
+ in `.jos/replays/` of the folders above the current directory (that is where `05-sealed` keeps its runs: run the
25
+ command from inside `05-sealed/`, or pass the path), then in `~/.jos/runs/`. Add `--json` to any of them for the
26
+ machine-readable form.
@@ -0,0 +1,47 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "atom"
5
+ },
6
+ "id": "tour.ollama",
7
+ "meta": {
8
+ "name": "tour.ollama",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "In one sentence: what is a declared intention?",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ },
17
+ {
18
+ "stdout_matches": "\\S"
19
+ }
20
+ ]
21
+ },
22
+ "guardrails": {
23
+ "avoid": [
24
+ "rm -rf"
25
+ ],
26
+ "limit": {
27
+ "seconds": 300,
28
+ "output_bytes": 1048576,
29
+ "retries": 0
30
+ }
31
+ },
32
+ "execution": {
33
+ "engine": "shell",
34
+ "run": [
35
+ "/bin/sh",
36
+ "-c",
37
+ "url=http://localhost:11434\nif ! command -v curl >/dev/null 2>&1; then echo \"curl is not installed: cannot check $url\" >&2; exit 3; fi\nif ! curl -sf --max-time 3 -o /dev/null \"$url/api/tags\"; then\n echo \"Ollama is not answering on $url: nothing was sent.\" >&2\n echo \"Start it (ollama serve, or the Ollama app), then run this again.\" >&2\n exit 3\nfi\ncommand -v ollama >/dev/null 2>&1 || { echo \"Ollama answers on $url but the ollama command is not on PATH\" >&2; exit 4; }\nexec ollama run \"${OLLAMA_MODEL:-llama3.2}\" \"$1\"",
38
+ "10-ollama",
39
+ "In one sentence: what is a declared intention?"
40
+ ],
41
+ "env": [
42
+ "PATH",
43
+ "HOME",
44
+ "OLLAMA_MODEL"
45
+ ]
46
+ }
47
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "atom"
5
+ },
6
+ "id": "tour.openai",
7
+ "meta": {
8
+ "name": "tour.openai",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "In one sentence: what is a declared intention?",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ },
17
+ {
18
+ "stdout_matches": "\\S"
19
+ }
20
+ ]
21
+ },
22
+ "guardrails": {
23
+ "avoid": [
24
+ "rm -rf"
25
+ ],
26
+ "limit": {
27
+ "seconds": 180,
28
+ "output_bytes": 1048576,
29
+ "retries": 0
30
+ }
31
+ },
32
+ "execution": {
33
+ "engine": "shell",
34
+ "run": [
35
+ "/bin/sh",
36
+ "-c",
37
+ "if [ -z \"$OPENAI_API_KEY\" ]; then\n echo \"OPENAI_API_KEY is not set: no request was sent.\" >&2\n echo \"Store the key once: jos secrets set OPENAI_API_KEY --stdin\" >&2\n echo \"Then run: jos secrets exec --only OPENAI_API_KEY -- jos run 11-openai.jos\" >&2\n exit 3\nfi\ncommand -v curl >/dev/null 2>&1 || { echo \"curl is not installed\" >&2; exit 4; }\nmodel=\"${OPENAI_MODEL:-gpt-5-mini}\"\nbody=$(node -e 'process.stdout.write(JSON.stringify({ model: process.argv[1], input: process.argv[2] }))' \"$model\" \"$1\") || exit 4\nresp=$(printf \"Authorization: Bearer %s\\n\" \"$OPENAI_API_KEY\" | curl -sS --max-time 150 -H @- -H \"Content-Type: application/json\" -d \"$body\" https://api.openai.com/v1/responses) || { echo \"the request failed (curl exit $?)\" >&2; exit 5; }\nprintf %s \"$resp\" | node -e 'let s = \"\";\nprocess.stdin.on(\"data\", (d) => { s += d; }).on(\"end\", () => {\n const key = process.env[process.argv[1]] || \"\";\n const clean = (t) => { let x = String(t); if (key) x = x.split(key).join(\"[key]\"); return x.replace(/(sk-|AIza)[A-Za-z0-9_*-]{6,}/g, \"[key]\"); };\n let j;\n try { j = JSON.parse(s); } catch (e) { console.error(\"the API did not answer JSON: \" + clean(s.slice(0, 300))); process.exit(6); }\n if (j.error) { console.error(\"API error: \" + clean(j.error.message || JSON.stringify(j.error))); process.exit(6); }\n const text = (j.output || []).flatMap((o) => o.content || []).filter((c) => c.type === \"output_text\").map((c) => c.text).join(\"\");\n if (!text) { console.error(\"no text in the answer: \" + clean(JSON.stringify(j).slice(0, 300))); process.exit(6); }\n console.log(text);\n});' OPENAI_API_KEY",
38
+ "11-openai",
39
+ "In one sentence: what is a declared intention?"
40
+ ],
41
+ "env": [
42
+ "PATH",
43
+ "OPENAI_API_KEY",
44
+ "OPENAI_MODEL"
45
+ ]
46
+ }
47
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "atom"
5
+ },
6
+ "id": "tour.gemini",
7
+ "meta": {
8
+ "name": "tour.gemini",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "In one sentence: what is a declared intention?",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ },
17
+ {
18
+ "stdout_matches": "\\S"
19
+ }
20
+ ]
21
+ },
22
+ "guardrails": {
23
+ "avoid": [
24
+ "rm -rf"
25
+ ],
26
+ "limit": {
27
+ "seconds": 180,
28
+ "output_bytes": 1048576,
29
+ "retries": 0
30
+ }
31
+ },
32
+ "execution": {
33
+ "engine": "shell",
34
+ "run": [
35
+ "/bin/sh",
36
+ "-c",
37
+ "if [ -z \"$GEMINI_API_KEY\" ]; then\n echo \"GEMINI_API_KEY is not set: no request was sent.\" >&2\n echo \"Store the key once: jos secrets set GEMINI_API_KEY --stdin\" >&2\n echo \"Then run: jos secrets exec --only GEMINI_API_KEY -- jos run 12-gemini.jos\" >&2\n exit 3\nfi\ncommand -v curl >/dev/null 2>&1 || { echo \"curl is not installed\" >&2; exit 4; }\nmodel=\"${GEMINI_MODEL:-gemini-2.5-flash}\"\nbody=$(node -e 'process.stdout.write(JSON.stringify({ contents: [{ parts: [{ text: process.argv[2] }] }] }))' \"$model\" \"$1\") || exit 4\nresp=$(printf \"x-goog-api-key: %s\\n\" \"$GEMINI_API_KEY\" | curl -sS --max-time 150 -H @- -H \"Content-Type: application/json\" -d \"$body\" \"https://generativelanguage.googleapis.com/v1beta/models/$model:generateContent\") || { echo \"the request failed (curl exit $?)\" >&2; exit 5; }\nprintf %s \"$resp\" | node -e 'let s = \"\";\nprocess.stdin.on(\"data\", (d) => { s += d; }).on(\"end\", () => {\n const key = process.env[process.argv[1]] || \"\";\n const clean = (t) => { let x = String(t); if (key) x = x.split(key).join(\"[key]\"); return x.replace(/(sk-|AIza)[A-Za-z0-9_*-]{6,}/g, \"[key]\"); };\n let j;\n try { j = JSON.parse(s); } catch (e) { console.error(\"the API did not answer JSON: \" + clean(s.slice(0, 300))); process.exit(6); }\n if (j.error) { console.error(\"API error: \" + clean(j.error.message || JSON.stringify(j.error))); process.exit(6); }\n const text = ((j.candidates || [])[0] || { content: { parts: [] } }).content.parts.map((p) => p.text || \"\").join(\"\");\n if (!text) { console.error(\"no text in the answer: \" + clean(JSON.stringify(j).slice(0, 300))); process.exit(6); }\n console.log(text);\n});' GEMINI_API_KEY",
38
+ "12-gemini",
39
+ "In one sentence: what is a declared intention?"
40
+ ],
41
+ "env": [
42
+ "PATH",
43
+ "GEMINI_API_KEY",
44
+ "GEMINI_MODEL"
45
+ ]
46
+ }
47
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "atom"
5
+ },
6
+ "id": "tour.anthropic",
7
+ "meta": {
8
+ "name": "tour.anthropic",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "In one sentence: what is a declared intention?",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ },
17
+ {
18
+ "stdout_matches": "\\S"
19
+ }
20
+ ]
21
+ },
22
+ "guardrails": {
23
+ "avoid": [
24
+ "rm -rf"
25
+ ],
26
+ "limit": {
27
+ "seconds": 180,
28
+ "output_bytes": 1048576,
29
+ "retries": 0
30
+ }
31
+ },
32
+ "execution": {
33
+ "engine": "shell",
34
+ "run": [
35
+ "/bin/sh",
36
+ "-c",
37
+ "if [ -z \"$ANTHROPIC_API_KEY\" ]; then\n echo \"ANTHROPIC_API_KEY is not set: no request was sent.\" >&2\n echo \"Store the key once: jos secrets set ANTHROPIC_API_KEY --stdin\" >&2\n echo \"Then run: jos secrets exec --only ANTHROPIC_API_KEY -- jos run 13-anthropic.jos\" >&2\n exit 3\nfi\ncommand -v curl >/dev/null 2>&1 || { echo \"curl is not installed\" >&2; exit 4; }\nmodel=\"${ANTHROPIC_MODEL:-claude-opus-5-5}\"\nbody=$(node -e 'process.stdout.write(JSON.stringify({ model: process.argv[1], max_tokens: 1024, messages: [{ role: \"user\", content: process.argv[2] }] }))' \"$model\" \"$1\") || exit 4\nresp=$(printf \"x-api-key: %s\\n\" \"$ANTHROPIC_API_KEY\" | curl -sS --max-time 150 -H @- -H \"anthropic-version: 2023-06-01\" -H \"Content-Type: application/json\" -d \"$body\" https://api.anthropic.com/v1/messages) || { echo \"the request failed (curl exit $?)\" >&2; exit 5; }\nprintf %s \"$resp\" | node -e 'let s = \"\";\nprocess.stdin.on(\"data\", (d) => { s += d; }).on(\"end\", () => {\n const key = process.env[process.argv[1]] || \"\";\n const clean = (t) => { let x = String(t); if (key) x = x.split(key).join(\"[key]\"); return x.replace(/(sk-|AIza)[A-Za-z0-9_*-]{6,}/g, \"[key]\"); };\n let j;\n try { j = JSON.parse(s); } catch (e) { console.error(\"the API did not answer JSON: \" + clean(s.slice(0, 300))); process.exit(6); }\n if (j.error) { console.error(\"API error: \" + clean(j.error.message || JSON.stringify(j.error))); process.exit(6); }\n const text = (j.content || []).filter((c) => c.type === \"text\").map((c) => c.text).join(\"\");\n if (!text) { console.error(\"no text in the answer: \" + clean(JSON.stringify(j).slice(0, 300))); process.exit(6); }\n console.log(text);\n});' ANTHROPIC_API_KEY",
38
+ "13-anthropic",
39
+ "In one sentence: what is a declared intention?"
40
+ ],
41
+ "env": [
42
+ "PATH",
43
+ "ANTHROPIC_API_KEY",
44
+ "ANTHROPIC_MODEL"
45
+ ]
46
+ }
47
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "flow"
5
+ },
6
+ "id": "tour.serve",
7
+ "meta": {
8
+ "name": "tour.serve",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "Serve a read-only HTML index of ~/.jos/runs on http://127.0.0.1:8765/ for 60 s, from a temporary folder (never ~/.jos itself)",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ },
17
+ {
18
+ "stdout_contains": "stopped and removed"
19
+ }
20
+ ]
21
+ },
22
+ "guardrails": {
23
+ "avoid": [
24
+ "rm -rf",
25
+ "curl",
26
+ "wget"
27
+ ],
28
+ "limit": {
29
+ "seconds": 900,
30
+ "output_bytes": 65536,
31
+ "retries": 0
32
+ }
33
+ },
34
+ "orchestration": {
35
+ "definitions": {
36
+ "index": {
37
+ "engine": "shell",
38
+ "run": [
39
+ "/bin/sh",
40
+ "-c",
41
+ "dir=$(mktemp -d \"${TMPDIR:-/tmp}/jos-runs-index.XXXXXX\") || exit 1\nnode -e 'const fs = require(\"fs\"), path = require(\"path\");\nconst [runs, out] = process.argv.slice(1);\nconst esc = (s) => String(s).replace(/[&<>\"]/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", \"\\\"\": \"&quot;\" })[c]);\nlet ids = [];\ntry { ids = fs.readdirSync(runs).filter((n) => /^run_[0-9A-Za-z]+$/.test(n)).sort().reverse(); } catch (e) { ids = []; }\nconst rows = ids.map((id) => {\n let es = [];\n try { es = fs.readFileSync(path.join(runs, id, \"ledger.jsonl\"), \"utf8\").split(\"\\n\").filter(Boolean).map((l) => JSON.parse(l)); } catch (e) { es = []; }\n const b = (e) => (e && e.body) || {};\n const end = b(es.find((e) => e.kind === \"end\"));\n const top = b(es.filter((e) => e.kind === \"receipt\").pop());\n const cells = [id, top.step || \"\", end.code === undefined ? \"\" : end.code, es.length ? es[0].ts : \"\"];\n return \"<tr>\" + cells.map((c) => \"<td>\" + esc(c) + \"</td>\").join(\"\") + \"</tr>\";\n});\nfs.writeFileSync(out, \"<!doctype html>\\n<meta charset=\\\"utf-8\\\">\\n<title>jos runs</title>\\n<h1>Runs in ~/.jos/runs</h1>\\n<p>A read-only snapshot. Check a run with <code>jos ledger verify &lt;run_id&gt;</code>.</p>\\n<table>\\n<tr><th>run</th><th>document</th><th>code</th><th>started</th></tr>\\n\" + rows.join(\"\\n\") + \"\\n</table>\\n\");\nconsole.error(ids.length + \" runs indexed\");' \"$HOME/.jos/runs\" \"$dir/index.html\" || { rmdir \"$dir\"; exit 1; }\nprintf %s \"$dir\"",
42
+ "index"
43
+ ],
44
+ "execution": {
45
+ "env": [
46
+ "PATH",
47
+ "HOME",
48
+ "TMPDIR"
49
+ ]
50
+ },
51
+ "as": "site",
52
+ "description": "Write index.html for ~/.jos/runs into a new temporary folder"
53
+ },
54
+ "serve": {
55
+ "engine": "shell",
56
+ "run": [
57
+ "/bin/sh",
58
+ "-c",
59
+ "dir=$(node -e 'let s = \"\"; process.stdin.on(\"data\", (d) => { s += d; }).on(\"end\", () => { process.stdout.write(String(JSON.parse(s).input.dir)); });') || exit 2\ncase \"$dir\" in \"\"|\"$HOME/.jos\"|\"$HOME/.jos/\"*) echo \"refusing to serve $dir\" >&2; exit 2;; esac\n[ -f \"$dir/index.html\" ] || { echo \"no index in $dir\" >&2; exit 2; }\ncommand -v python3 >/dev/null 2>&1 || { echo \"python3 is not installed\" >&2; rm -f \"$dir/index.html\"; rmdir \"$dir\"; exit 3; }\nport=\"${JOS_SERVE_PORT:-8765}\"\nsecs=\"${JOS_SERVE_SECONDS:-60}\"\nlog=\"$dir.log\"\npython3 -u -m http.server \"$port\" --bind 127.0.0.1 --directory \"$dir\" >\"$log\" 2>/dev/null &\npid=$!\nn=0\nuntil grep -q '^Serving HTTP on' \"$log\" 2>/dev/null; do\n if ! kill -0 \"$pid\" 2>/dev/null; then echo \"python3 could not serve on 127.0.0.1:$port (is the port in use?)\" >&2; rm -f \"$dir/index.html\" \"$log\"; rmdir \"$dir\"; exit 1; fi\n if [ \"$n\" -ge 600 ]; then echo \"python3 did not start serving on 127.0.0.1:$port within 60 s\" >&2; kill \"$pid\" 2>/dev/null; wait \"$pid\" 2>/dev/null; rm -f \"$dir/index.html\" \"$log\"; rmdir \"$dir\"; exit 1; fi\n n=$((n + 1))\n sleep 0.1\ndone\nsleep \"$secs\"\nkill \"$pid\" 2>/dev/null\nwait \"$pid\" 2>/dev/null\nrm -f \"$dir/index.html\" \"$log\"\nrmdir \"$dir\"\necho \"served a read-only index of ~/.jos/runs from $dir on http://127.0.0.1:$port/ for $secs s; stopped and removed it\"",
60
+ "serve"
61
+ ],
62
+ "execution": {
63
+ "env": [
64
+ "PATH",
65
+ "HOME",
66
+ "JOS_SERVE_PORT",
67
+ "JOS_SERVE_SECONDS"
68
+ ]
69
+ },
70
+ "needs": [
71
+ "index"
72
+ ],
73
+ "with": {
74
+ "dir": "{{ site }}"
75
+ },
76
+ "description": "python3 -m http.server --bind 127.0.0.1 on that folder, counted from the moment it says it is serving, then remove it"
77
+ }
78
+ },
79
+ "flows": {
80
+ "main": {
81
+ "steps": [
82
+ "index",
83
+ "serve"
84
+ ]
85
+ }
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,105 @@
1
+ # The tour
2
+
3
+ Each example is one capability of `jos`, and each one runs. Copy the folder first, so the runs do not write into
4
+ the installed package (a run of `05-sealed` keeps its ledger in `05-sealed/.jos/replays/`; the others in
5
+ `~/.jos/runs/`):
6
+
7
+ ```
8
+ cp -R "$(npm root -g)/@josfox/jos/examples" jos-tour && cd jos-tour
9
+ ```
10
+
11
+ Every `jos run` prints the document's intention before it runs (`Intention`, `Done when`, `Avoid`, `Limit`), then
12
+ the verdict of each criterion as jos-core evaluated it, then `Intention met` or `Intention NOT met (code N: …)`.
13
+ `--json` prints jos-core's result object instead, unchanged.
14
+
15
+ | Example | Command | Result |
16
+ |---|---|---|
17
+ | `01-hello.jos` | `jos run 01-hello.jos` | prints `hello, .jos`; `✓ Intention met`; exit 0 |
18
+ | `02-criteria.jos` | `jos run 02-criteria.jos` | one verdict line per criterion: `exit`, `stdout_contains`, `stdout_matches`, `stderr_empty`, `not`, and a descriptive one (recorded, never evaluated); exit 0. Change the `echo` text and it is exit 1 |
19
+ | `03-flow-gate.jos` | `jos run 03-flow-gate.jos` | `build` runs, the human gate stops the flow: `Intention NOT met (code 1: not done)` and the command to approve; exit 1 |
20
+ | | `jos run 03-flow-gate.jos --approve build` | `build`, then `publish`; prints `published`; exit 0 |
21
+ | | `jos run 03-flow-gate.jos --flow build-only` | runs the other flow of the document (`build` only); exit 0 |
22
+ | `04-use-template/` | `jos run 04-use-template/use.jos --library 04-use-template/lib` | one template, used twice with `with`, pinned by its digest; prints `hello, Leo`; exit 0. See [its README](04-use-template/README.md) |
23
+ | | `jos run 04-use-template/lib/greeting.jos --with '{"name":"Ana"}'` | the template run directly; prints `hello, Ana`; exit 0 |
24
+ | `05-sealed/` | `jos run 05-sealed` | the folder runs while its hash matches `integrity.hash`; exit 0. Edit it and it is exit 3. See [its README](05-sealed/README.md) |
25
+ | `06-requires.jos` | `jos run 06-requires.jos` | needs a `payments` service this runtime does not provide: refused before anything starts, `/requires/0: not satisfied`; exit 5 |
26
+ | `07-sandbox.jos` | `jos run 07-sandbox.jos` | `security.tier: sandbox`, and this kernel cannot confine a process: refused, `sandbox not available`; exit 5 |
27
+ | `08-ids-ledger-meter.md` | the commands in it | `jos ids`, `jos runs`, `jos ledger verify`, `jos meter` over the runs above |
28
+ | `10-ollama.jos` | `jos run 10-ollama.jos` | checks `http://localhost:11434`; without Ollama it says so and sends nothing (exit 1); with Ollama the objective goes to `ollama run ${OLLAMA_MODEL:-llama3.2}` and the answer is printed (exit 0) |
29
+ | `11-openai.jos` | `jos secrets exec --only OPENAI_API_KEY -- jos run 11-openai.jos` | the objective to the OpenAI Responses API (`${OPENAI_MODEL:-gpt-5-mini}`); prints the answer; exit 0. Without the key: `OPENAI_API_KEY is not set: no request was sent.`, exit 1 |
30
+ | `12-gemini.jos` | `jos secrets exec --only GEMINI_API_KEY -- jos run 12-gemini.jos` | the same with the Gemini API (`${GEMINI_MODEL:-gemini-2.5-flash}`) |
31
+ | `13-anthropic.jos` | `jos secrets exec --only ANTHROPIC_API_KEY -- jos run 13-anthropic.jos` | the same with the Anthropic Messages API (`${ANTHROPIC_MODEL:-claude-opus-5-5}`) |
32
+ | `14-serve.jos` | `jos run 14-serve.jos` | writes a read-only HTML index of `~/.jos/runs` into a new temporary folder and serves only that folder with `python3 -m http.server --bind 127.0.0.1` on port `${JOS_SERVE_PORT:-8765}` for `${JOS_SERVE_SECONDS:-60}` seconds, counted from the moment python3 says it is serving (it may take a few seconds to listen; at most 60 s), then removes it; exit 0 |
33
+
34
+ `hello.jos` is the document `jos verify` runs.
35
+
36
+ ## The provider examples and your keys
37
+
38
+ `11`, `12` and `13` are shell steps that call the provider's REST API with `curl`. They are paid calls on your
39
+ account. Store a key once, then give it only to the run that needs it:
40
+
41
+ ```
42
+ jos secrets set OPENAI_API_KEY --stdin # type or paste the key; it is not echoed
43
+ jos secrets exec --only OPENAI_API_KEY -- jos run 11-openai.jos
44
+ ```
45
+
46
+ What happens to the key: `jos secrets exec` puts it only in the environment of that `jos run`; the document's
47
+ `execution.env` lets it reach the step; the step hands it to `curl` on stdin (`-H @-`), so it is never on a command
48
+ line; the examples never print it, and an error message from the API is printed with the key replaced by `[key]`.
49
+ The key is not in the receipts or the ledger: a receipt records the step's code, its criteria verdicts and hashes
50
+ of its input and output, never the environment (the test suite checks the ledger, the result and the output for
51
+ the key). The answer itself is printed
52
+ to your terminal and not stored. The model's tokens and cost are not recorded either: the `shell` engine does not
53
+ report them, so `jos meter` shows them as "not reported".
54
+
55
+ ## What `jos run` prints (NO_COLOR)
56
+
57
+ ```
58
+ $ jos run 03-flow-gate.jos
59
+
60
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
61
+ JOS RUN // jos-core 2.0.0
62
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
63
+
64
+ File: 03-flow-gate.jos
65
+
66
+ Intention: Build, wait for a person to approve the build, then publish
67
+ Done when:
68
+ • exit 0
69
+ Avoid: "rm -rf"
70
+ Limit: 10 s, 65536 output bytes per stream, 0 retries
71
+
72
+ ID: art_572a58662e9e76761b6844bfd091ed08636023e39ce10cd13dc1cdf463741d96
73
+ Input version: 2.0.0
74
+ Capability: cap_01M5DX8R8WT15PS3SQV70CV2PM (local-trusted)
75
+ Ledger: ~/.jos/runs/run_01M5DX8R8TCEQ2D7DESRZQH8JG/ledger.jsonl
76
+
77
+ built
78
+
79
+ Verdict:
80
+ [1] build ✓ code 0
81
+ ✓ stdout_contains: found
82
+ [1] tour.flow-gate (flow main) ✖ code 1
83
+ ✓ exit: exit 0 (want 0)
84
+
85
+ ✖ Intention NOT met (code 1: not done) (run_01M5DX8R8TCEQ2D7DESRZQH8JG)
86
+ gate human: waiting for an approval of "build"
87
+ To approve it: jos run 03-flow-gate.jos --approve build
88
+
89
+ $ jos run 03-flow-gate.jos --approve build
90
+ …
91
+ published
92
+
93
+ Verdict:
94
+ [1] build ✓ code 0
95
+ ✓ stdout_contains: found
96
+ [1] publish ✓ code 0
97
+ ✓ stdout_contains: found
98
+ [1] tour.flow-gate (flow main) ✓ code 0
99
+ ✓ exit: exit 0 (want 0)
100
+
101
+ ✓ Intention met (code 0, run_01M5DX8R9YSZT1S7GKJJJNS9MM)
102
+ ```
103
+
104
+ The `run_` and `cap_` ids and the ledger path are yours; the `art_` is the same on every machine and in both
105
+ reference kernels, because it is the hash of the document's content.