@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
@@ -0,0 +1,83 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "flow"
5
+ },
6
+ "id": "tour.flow-gate",
7
+ "meta": {
8
+ "name": "tour.flow-gate",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "Build, wait for a person to approve the build, then publish",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ }
17
+ ]
18
+ },
19
+ "guardrails": {
20
+ "avoid": [
21
+ "rm -rf"
22
+ ],
23
+ "limit": {
24
+ "seconds": 10,
25
+ "output_bytes": 65536,
26
+ "retries": 0
27
+ }
28
+ },
29
+ "orchestration": {
30
+ "definitions": {
31
+ "build": {
32
+ "engine": "shell",
33
+ "run": [
34
+ "echo",
35
+ "built"
36
+ ],
37
+ "description": "Make the artifact",
38
+ "intention": {
39
+ "objective": "Make the artifact",
40
+ "success_criteria": [
41
+ {
42
+ "stdout_contains": "built"
43
+ }
44
+ ]
45
+ }
46
+ },
47
+ "publish": {
48
+ "engine": "shell",
49
+ "run": [
50
+ "echo",
51
+ "published"
52
+ ],
53
+ "description": "Runs only after a person approved build",
54
+ "intention": {
55
+ "objective": "Publish the approved build",
56
+ "success_criteria": [
57
+ {
58
+ "stdout_contains": "published"
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ },
64
+ "flows": {
65
+ "main": {
66
+ "steps": [
67
+ "build",
68
+ "publish"
69
+ ],
70
+ "gates": {
71
+ "build": {
72
+ "kind": "human"
73
+ }
74
+ }
75
+ },
76
+ "build-only": {
77
+ "steps": [
78
+ "build"
79
+ ]
80
+ }
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,22 @@
1
+ # 04 · use, a template and a digest
2
+
3
+ `lib/greeting.jos` is a `template`: its slot `name` is declared as a JSON Schema in `execution.inputs`
4
+ (a string of letters and spaces, 1 to 64 characters). `use.jos` is a flow that uses it twice, `ana` and `leo`, each
5
+ with its own `with`, and pins the template's content in `requires[].digest` (its `art_`).
6
+
7
+ Run from the folder that holds `04-use-template/`:
8
+
9
+ | Command | Result |
10
+ |---|---|
11
+ | `jos run 04-use-template/use.jos --library 04-use-template/lib` | `use` resolves `tour.greeting@^1.0.0` in the library folder, the digest matches, both steps run; prints `hello, Leo` (the output of the last step); exit 0 |
12
+ | `jos run 04-use-template/use.jos` | no library given: the pinned artifact is not found, `/requires/0: not satisfied`; nothing runs; exit 5 |
13
+ | `jos run 04-use-template/lib/greeting.jos --with '{"name":"Ana"}'` | the template run directly, instantiated with `--with`; prints `hello, Ana`; exit 0 |
14
+ | `jos run 04-use-template/lib/greeting.jos` | no value for the required slot: `/with: required 'name'`; exit 2 |
15
+ | `jos ids 04-use-template/lib/greeting.jos` | the template's `art_`, the value pinned in `use.jos` |
16
+
17
+ Change anything in `lib/greeting.jos` (for example `hello` to `hi`) and run `use.jos` with the library again: the
18
+ template's `art_` no longer equals the digest, the run is refused (exit 5). Put the text back, or pin the new
19
+ `art_` (`jos ids`), and it runs.
20
+
21
+ The same `use` also resolves from a `.jos/` folder above the document (`.jos/templates/`, `.jos/flows/`, …), nearest
22
+ level first, before the library.
@@ -0,0 +1,56 @@
1
+ {
2
+ "jos": {
3
+ "version": "2.0.0",
4
+ "kind": "template"
5
+ },
6
+ "id": "tour.greeting",
7
+ "meta": {
8
+ "name": "tour.greeting",
9
+ "version": "1.0.0"
10
+ },
11
+ "intention": {
12
+ "objective": "Greet {{ name }} by name",
13
+ "success_criteria": [
14
+ {
15
+ "exit": 0
16
+ },
17
+ {
18
+ "stdout_contains": "{{ name }}"
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
+ "execution": {
33
+ "engine": "shell",
34
+ "inputs": {
35
+ "type": "object",
36
+ "required": [
37
+ "name"
38
+ ],
39
+ "properties": {
40
+ "name": {
41
+ "type": "string",
42
+ "minLength": 1,
43
+ "maxLength": 64,
44
+ "pattern": "^[A-Za-z ]+$"
45
+ }
46
+ }
47
+ },
48
+ "run": [
49
+ "echo",
50
+ "hello, {{ name }}"
51
+ ],
52
+ "env": [
53
+ "PATH"
54
+ ]
55
+ }
56
+ }
@@ -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
+ }