skillscript-runtime 0.23.1 → 0.25.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 (77) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +2 -1
  3. package/UPGRADING.md +9 -1
  4. package/dist/bootstrap-from-env.d.ts +46 -0
  5. package/dist/bootstrap-from-env.d.ts.map +1 -0
  6. package/dist/bootstrap-from-env.js +130 -0
  7. package/dist/bootstrap-from-env.js.map +1 -0
  8. package/dist/bootstrap.d.ts +10 -0
  9. package/dist/bootstrap.d.ts.map +1 -1
  10. package/dist/bootstrap.js +7 -0
  11. package/dist/bootstrap.js.map +1 -1
  12. package/dist/cli.js +23 -164
  13. package/dist/cli.js.map +1 -1
  14. package/dist/connectors/registry.d.ts +9 -0
  15. package/dist/connectors/registry.d.ts.map +1 -1
  16. package/dist/connectors/registry.js +23 -0
  17. package/dist/connectors/registry.js.map +1 -1
  18. package/dist/connectors/skill-store.d.ts +1 -0
  19. package/dist/connectors/skill-store.d.ts.map +1 -1
  20. package/dist/connectors/skill-store.js +27 -0
  21. package/dist/connectors/skill-store.js.map +1 -1
  22. package/dist/connectors/sqlite-skill-store.d.ts +1 -0
  23. package/dist/connectors/sqlite-skill-store.d.ts.map +1 -1
  24. package/dist/connectors/sqlite-skill-store.js +10 -0
  25. package/dist/connectors/sqlite-skill-store.js.map +1 -1
  26. package/dist/connectors/types.d.ts +19 -0
  27. package/dist/connectors/types.d.ts.map +1 -1
  28. package/dist/connectors/types.js.map +1 -1
  29. package/dist/dashboard/server.d.ts +2 -0
  30. package/dist/dashboard/server.d.ts.map +1 -1
  31. package/dist/dashboard/server.js +4 -0
  32. package/dist/dashboard/server.js.map +1 -1
  33. package/dist/dashboard/spa/app.js +33 -5
  34. package/dist/help-content.d.ts.map +1 -1
  35. package/dist/help-content.js +3 -0
  36. package/dist/help-content.js.map +1 -1
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/lint.d.ts +1 -1
  42. package/dist/lint.d.ts.map +1 -1
  43. package/dist/lint.js +145 -0
  44. package/dist/lint.js.map +1 -1
  45. package/dist/mcp-server.d.ts +8 -0
  46. package/dist/mcp-server.d.ts.map +1 -1
  47. package/dist/mcp-server.js +24 -1
  48. package/dist/mcp-server.js.map +1 -1
  49. package/dist/parser.d.ts +9 -0
  50. package/dist/parser.d.ts.map +1 -1
  51. package/dist/parser.js +22 -6
  52. package/dist/parser.js.map +1 -1
  53. package/dist/runtime.d.ts +20 -0
  54. package/dist/runtime.d.ts.map +1 -1
  55. package/dist/runtime.js +147 -24
  56. package/dist/runtime.js.map +1 -1
  57. package/dist/scheduler.d.ts +8 -0
  58. package/dist/scheduler.d.ts.map +1 -1
  59. package/dist/scheduler.js +3 -0
  60. package/dist/scheduler.js.map +1 -1
  61. package/dist/secrets.d.ts +119 -0
  62. package/dist/secrets.d.ts.map +1 -0
  63. package/dist/secrets.js +153 -0
  64. package/dist/secrets.js.map +1 -0
  65. package/dist/skill-catalog.d.ts.map +1 -1
  66. package/dist/skill-catalog.js +2 -0
  67. package/dist/skill-catalog.js.map +1 -1
  68. package/docs/adopter-playbook.md +80 -4
  69. package/docs/configuration.md +2 -2
  70. package/docs/connector-contract-reference.md +4 -0
  71. package/docs/language-reference.md +82 -5
  72. package/docs/sqlite-skill-store.md +3 -2
  73. package/examples/connectors/SkillStoreTemplate/SkillStoreTemplate.ts +19 -0
  74. package/examples/custom-bootstrap.example.ts +17 -7
  75. package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
  76. package/package.json +1 -1
  77. package/scaffold/.env.example +13 -0
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Secret references (v0.25.0). A skill names a secret with `{{secret.NAME}}`;
3
+ * the runtime resolves it ONLY at a sink (a `shell(...)` op or a `$`
4
+ * connector dispatch) and injects the value use-only — the resolved value
5
+ * never binds to a skill variable, never lands in a trace, an emit, an
6
+ * `# Output:`, or any other readable surface. This is deliberately distinct
7
+ * from `${VAR}` readable substitution (`substituteRuntime`), which resolves
8
+ * the skill var scope (inputs / `$set` / op outputs / built-ins) and CANNOT
9
+ * reach a secret: `substituteRuntime` never touches a `{{...}}` marker, so a
10
+ * marker that slips onto a readable surface stays an inert literal rather
11
+ * than leaking a value.
12
+ *
13
+ * Source abstraction. `SecretProvider.resolve(name, ctx)` is the single seam.
14
+ * The `.env`-backed `EnvSecretProvider` ships today; a vault-backed provider
15
+ * drops in later with no caller change because `ctx` ALREADY carries the
16
+ * principal (`skillName`) that a per-script-sealed vault provider will key on.
17
+ * Nothing about the call sites assumes the env backing.
18
+ *
19
+ * See the dev-log secret-references design (memory 232bcfc9) for the full
20
+ * arc; this module is its first buildable slice.
21
+ */
22
+ /** Thrown by a provider when a referenced secret has no provisioned value. */
23
+ export class SecretNotProvisionedError extends Error {
24
+ secretName;
25
+ remediation;
26
+ constructor(secretName,
27
+ /** Operator-facing remediation (how to provision it). */
28
+ remediation) {
29
+ super(`Secret '${secretName}' is referenced but not provisioned. ${remediation}`);
30
+ this.secretName = secretName;
31
+ this.remediation = remediation;
32
+ this.name = "SecretNotProvisionedError";
33
+ }
34
+ }
35
+ /**
36
+ * Environment-variable secret backing. A `{{secret.NAME}}` marker resolves
37
+ * the env var `SKILLSCRIPT_SECRET_NAME`. The prefix is deliberate: it scopes
38
+ * which environment is secret-reachable, so a skill cannot pull an arbitrary
39
+ * process env var (`{{secret.PATH}}` looks for `SKILLSCRIPT_SECRET_PATH`, not
40
+ * `$PATH`). The operator provisions secrets in `.env`; a skill author names
41
+ * them but cannot read them back.
42
+ */
43
+ export class EnvSecretProvider {
44
+ env;
45
+ /** Env prefix that scopes which vars are reachable as secrets. */
46
+ static PREFIX = "SKILLSCRIPT_SECRET_";
47
+ constructor(env = process.env) {
48
+ this.env = env;
49
+ }
50
+ async resolve(name, _ctx) {
51
+ const key = EnvSecretProvider.PREFIX + name;
52
+ const value = this.env[key];
53
+ if (value === undefined || value === "") {
54
+ throw new SecretNotProvisionedError(name, `Set ${key} in the runtime environment (the operator's .env) and restart the runtime. ` +
55
+ `Secrets are operator-provisioned; a skill author references them by name but cannot set or read them.`);
56
+ }
57
+ return value;
58
+ }
59
+ }
60
+ /**
61
+ * Op kinds permitted to contain `{{secret.NAME}}` markers — the "sinks."
62
+ * A marker in any OTHER position (emit, `$set`, `# Output:`, conditions,
63
+ * `file_*`, notify) is a lint tier-1 error, because the value would land on a
64
+ * readable or emittable surface. Adding a new sink (e.g. a future `notify`
65
+ * that needs a key) is two edits with no redesign: wire that handler to call
66
+ * {@link expandSecretMarkers} and append its op kind here. Kept minimal on
67
+ * purpose (Scott 2026-06-28: "don't design yourself into a corner" — a list,
68
+ * not a rewrite). This is the single source of truth shared by lint + runtime.
69
+ */
70
+ export const SECRET_SINK_OP_KINDS = ["shell", "$"];
71
+ /**
72
+ * Secret placement marker: `{{secret.NAME}}`, whitespace-tolerant inside the
73
+ * braces. NAME is an identifier (`[A-Za-z_]\w*`); the `secret.` namespace is
74
+ * reserved. Double-brace + reserved namespace keeps it disjoint from `${VAR}`
75
+ * / `$(VAR)` readable refs so the two resolution passes never collide.
76
+ * Global flag: used for both scan and replace.
77
+ */
78
+ const SECRET_MARKER = /\{\{\s*secret\.([A-Za-z_]\w*)\s*\}\}/g;
79
+ /** True iff `text` contains at least one `{{secret.NAME}}` marker. */
80
+ export function hasSecretMarker(text) {
81
+ SECRET_MARKER.lastIndex = 0;
82
+ return SECRET_MARKER.test(text);
83
+ }
84
+ /** Distinct secret names referenced by `{{secret.NAME}}` markers in `text`. */
85
+ export function extractSecretRefs(text) {
86
+ // SECRET_MARKER is a shared global regex; `matchAll` copies its `lastIndex`,
87
+ // so reset first or a prior `.test()` (hasSecretMarker) would start the scan
88
+ // mid-string and miss leading markers.
89
+ SECRET_MARKER.lastIndex = 0;
90
+ const names = new Set();
91
+ for (const m of text.matchAll(SECRET_MARKER))
92
+ names.add(m[1]);
93
+ return [...names];
94
+ }
95
+ /**
96
+ * Detects any `{{secret.…}}` occurrence that is NOT a well-formed static
97
+ * `{{secret.NAME}}` marker — a dynamic interior (`{{secret.${VAR}}}`), an
98
+ * empty name, a filter/extra content, etc. The secret name MUST be a
99
+ * compile-time literal: a dynamically-built name would let `${VAR}`
100
+ * substitution choose (or fabricate) the secret at runtime, evading the
101
+ * declare-before-spend gate and the approver-visible `# Requires` reach.
102
+ * Returns the offending raw substrings (empty when all markers are static).
103
+ * (Perry red-team `d8a5ad0a` Bug A, fix #3.)
104
+ */
105
+ export function findMalformedSecretMarkers(text) {
106
+ const out = [];
107
+ // `[^}]*` so a `{{secret.${NM}}}` dynamic interior is captured (not skipped).
108
+ const anyMarker = /\{\{\s*secret\.[^}]*\}\}/g;
109
+ for (const m of text.matchAll(anyMarker)) {
110
+ if (!/^\{\{\s*secret\.[A-Za-z_]\w*\s*\}\}$/.test(m[0]))
111
+ out.push(m[0]);
112
+ }
113
+ return out;
114
+ }
115
+ /**
116
+ * Replace every `{{secret.NAME}}` marker in `text` with its resolved value.
117
+ * Async — a vault provider does I/O. Returns `text` unchanged when it holds
118
+ * no markers, so a sink handler may call this unconditionally on any input.
119
+ * Fails closed: an unprovisioned secret throws (never substitutes empty).
120
+ *
121
+ * The returned string is USE-ONLY: pass it straight to the sink (spawn argv,
122
+ * connector arg) and never bind it to a var, emit it, or write it to a trace.
123
+ * Resolve happens here, at the sink boundary, precisely so the value's
124
+ * lifetime is the single dispatch call.
125
+ */
126
+ export async function expandSecretMarkers(text, provider, ctx,
127
+ /** Optional sink for each resolved value — lets a caller build a redaction
128
+ * set so a leaked value can be scrubbed from error/trace output (Perry
129
+ * Bug B fix #2, belt-and-suspenders). */
130
+ onResolve) {
131
+ if (!hasSecretMarker(text))
132
+ return text;
133
+ // Resolve each distinct name once (a marker may repeat), then splice.
134
+ const resolved = new Map();
135
+ for (const name of extractSecretRefs(text)) {
136
+ const value = await provider.resolve(name, ctx);
137
+ resolved.set(name, value);
138
+ onResolve?.(name, value);
139
+ }
140
+ return text.replace(SECRET_MARKER, (_m, name) => resolved.get(name));
141
+ }
142
+ /**
143
+ * {@link expandSecretMarkers} over a list (the `shell(...)` argv form, where
144
+ * each element is substituted independently). Markerless elements pass
145
+ * through untouched.
146
+ */
147
+ export async function expandSecretMarkersInList(items, provider, ctx, onResolve) {
148
+ const out = [];
149
+ for (const item of items)
150
+ out.push(await expandSecretMarkers(item, provider, ctx, onResolve));
151
+ return out;
152
+ }
153
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"skill-catalog.d.ts","sourceRoot":"","sources":["../src/skill-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnG,OAAO,KAAK,EAAE,WAAW,EAA2B,MAAM,aAAa,CAAC;AAaxE;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,UAAU,EACtB,MAAM,GAAE,eAAoB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAyEvB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,MAAM,EAAE,WAAW,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAsBZ"}
1
+ {"version":3,"file":"skill-catalog.d.ts","sourceRoot":"","sources":["../src/skill-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnG,OAAO,KAAK,EAAE,WAAW,EAA2B,MAAM,aAAa,CAAC;AAaxE;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,UAAU,EACtB,MAAM,GAAE,eAAoB,GAC3B,OAAO,CAAC,YAAY,CAAC,CA0EvB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,MAAM,EAAE,WAAW,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAuBZ"}
@@ -73,6 +73,7 @@ export async function buildSkillCatalog(skillStore, filter = {}) {
73
73
  triggers: [],
74
74
  returns: [],
75
75
  requires: [],
76
+ secret_requires: [],
76
77
  effectful_footprint: EMPTY_FOOTPRINT,
77
78
  ...(meta.author !== undefined ? { author: meta.author } : { author: null }),
78
79
  });
@@ -111,6 +112,7 @@ export function buildEntry(name, description, status, parsed, author) {
111
112
  // capability gate uses). All free — the source is already parsed.
112
113
  returns: parsed.returns,
113
114
  requires: parsed.requires,
115
+ secret_requires: parsed.secretRequires,
114
116
  effectful_footprint: extractEffectfulFootprint(parsed),
115
117
  // v0.18.6 — surface author when the substrate populated it; null
116
118
  // when not (substrate-neutral graceful degradation).
@@ -1 +1 @@
1
- {"version":3,"file":"skill-catalog.js","sourceRoot":"","sources":["../src/skill-catalog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skill-catalog.js","sourceRoot":"","sources":["../src/skill-catalog.ts"],"names":[],"mappings":"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"}
@@ -149,7 +149,7 @@ $ my_store.search query="customer feedback" region="eu-west" cluster="prod" -> C
149
149
 
150
150
  This skill body is locked to `my_store` — its specific kwargs (`region`, `cluster`) and response shape. To move to a different substrate, every call site has to be rewritten.
151
151
 
152
- **⚠ Programmatic `bootstrap()` adopters: `connectors.json` is NOT auto-loaded.** The CLI (`skillfile dashboard` / `serve`) discovers and loads `$SKILLSCRIPT_HOME/connectors.json` automatically. The programmatic path does NOT`bootstrap()` only reads `connectors.json` when you pass `connectorsConfigPath`. Skip it and your `$ name.tool` calls fail at runtime with `unknown-connector` and no hint that the file simply wasn't read. See §"Programmatic bootstrap path" below for the explicit-wiring pattern. (Same CLI-auto-vs-programmatic-explicit asymmetry as `.env` and `SKILLSCRIPT_*` env vars three instances of the same pattern now.)
152
+ **⚠ Programmatic adopters:** use **`bootstrapFromEnv()`** and it loads `$SKILLSCRIPT_HOME/connectors.json` (plus `.env` + `SKILLSCRIPT_*`) for you, exactly like the CLI. Only **raw `bootstrap()`** skips themit reads `connectors.json` solely when you pass `connectorsConfigPath`, and skipping it makes `$ name.tool` calls fail at runtime with `unknown-connector` and no hint that the file simply wasn't read. See §"Programmatic bootstrap path" below `bootstrapFromEnv()` is the recommended default; the raw-`bootstrap()` explicit-wiring pattern is for hand-assembled custom substrates.
153
153
 
154
154
  ### Picking — the tradeoff
155
155
 
@@ -481,9 +481,44 @@ skillfile shell-audit
481
481
 
482
482
  Paste into your `$SKILLSCRIPT_HOME/.env` (review/narrow as desired). The audit is the canonical path — running it lets you make explicit policy decisions instead of discovering refusals through runtime errors.
483
483
 
484
- ### Programmatic bootstrap path (`bootstrap()` adopters)
484
+ ### Programmatic bootstrap path
485
485
 
486
- **The CLI-auto-vs-programmatic-explicit asymmetry.** The CLI (`skillfile dashboard` / `serve`) does several discovery steps automaticallyload `$SKILLSCRIPT_HOME/.env`, read `SKILLSCRIPT_*` env vars, load `$SKILLSCRIPT_HOME/connectors.json`. The programmatic path (`bootstrap()` from your own embedder code) does NOT do any of these automatically. Each surface needs explicit wiring on the programmatic path:
486
+ **Most adopters operate via the web dashboard** after a one-time `skillfile init`. To stand that dashboard up from your own code, use **`bootstrapFromEnv()`**the blessed entry point that wires a full runtime + `DashboardServer` from `$SKILLSCRIPT_HOME` *exactly the way the CLI does*: it loads `.env`, `skillscript.config.json`, and `connectors.json`, resolves the whole `SKILLSCRIPT_*` env cascade, calls `bootstrap()`, wires declarative triggers, and assembles the server.
487
+
488
+ ```typescript
489
+ import { bootstrapFromEnv } from "skillscript-runtime";
490
+
491
+ const { wired, server } = await bootstrapFromEnv({ mode: "dashboard" /* or "serve" */ });
492
+ wired.scheduler.start();
493
+ await server.start();
494
+ // On shutdown, reap stdio-bridged connector children (RemoteMcpConnector):
495
+ process.on("SIGTERM", async () => {
496
+ await wired.scheduler.stop();
497
+ await server.stop();
498
+ await wired.registry.disposeAll();
499
+ });
500
+ ```
501
+
502
+ Both are returned **unstarted** — you decide when to `start()`. Options: `mode`, `home` (default `$SKILLSCRIPT_HOME`), `configPath`, `connectorsConfigPath`, `port`, `host`, and `overrides`. **Precedence**: explicit option > `overrides` (passed to `bootstrap()`, wins last) > `SKILLSCRIPT_*` env > `skillscript.config.json` > default.
503
+
504
+ To wire your **own custom substrate** (e.g. a remote `SkillStore`), two paths:
505
+ - **Declarative** — if it's expressible in `connectors.json` via the `{ "type": "custom", "module": "./my-store.js", "export": "MyStore" }` substrate form, just declare it; `bootstrapFromEnv()` honors `substrate.*` like the CLI.
506
+ - **Instance injection** — pass a pre-built instance through `overrides`, the common path for a store that needs constructor wiring you hold in code:
507
+ ```typescript
508
+ const { wired, server } = await bootstrapFromEnv({
509
+ mode: "dashboard",
510
+ overrides: { skillStore: new MyRemoteSkillStore({ /* client, auth, ... */ }) },
511
+ });
512
+ ```
513
+ Everything else (data store, models, connectors, env cascade) still auto-wires from `$SKILLSCRIPT_HOME`. `overrides` accepts any `bootstrap()` option (`skillStore` / `dataStore` / `localModel` / `agentConnector` / allowlists / …).
514
+
515
+ This closes the silent CLI-vs-programmatic asymmetry described below; reach for raw `bootstrap()` only when you're hand-assembling a registry that neither `connectors.json` nor `overrides` can express.
516
+
517
+ **⚠ Migrating an existing hand-assembled bootstrap to `bootstrapFromEnv()`?** Move any options you previously hardcoded on `bootstrap()` / `new DashboardServer({...})` to their `SKILLSCRIPT_*` env equivalents — `bootstrapFromEnv()` resolves them from env, so a value you drop reverts to the default. Two to watch (adopter-verified, 0.24.0): `enableUnsafeShell: true` → `SKILLSCRIPT_ENABLE_UNSAFE_SHELL=true` (fails **loud** — unsafe ops just refuse, you'll notice); and **`mcpCallerIdentityHeader: "X-Agent-Id"` → `SKILLSCRIPT_MCP_CALLER_IDENTITY_HEADER=X-Agent-Id`, which fails *silently*** — drop it and skill-author attribution quietly reverts to the store's default writer identity, with no error. (`bootstrap()`-level opts like `enableUnsafeShell` can instead go through `overrides`; the `DashboardServer`-level `mcpCallerIdentityHeader` is env-only via `bootstrapFromEnv` — set the env var.) Verify after migrating: send your identity header on a `/rpc` `skill_write` and confirm the captured `author`.
518
+
519
+ #### Raw `bootstrap()` — the CLI-auto-vs-programmatic-explicit asymmetry
520
+
521
+ `bootstrapFromEnv()` does the discovery steps below for you. If you call **`bootstrap()` directly** instead (hand-assembling substrates), be aware the CLI (`skillfile dashboard` / `serve`) does several discovery steps automatically — load `$SKILLSCRIPT_HOME/.env`, read `SKILLSCRIPT_*` env vars, load `$SKILLSCRIPT_HOME/connectors.json` — that raw `bootstrap()` does NOT. Each surface then needs explicit wiring:
487
522
 
488
523
  | Surface | CLI path | Programmatic path |
489
524
  |---|---|---|
@@ -594,6 +629,45 @@ SKILLSCRIPT_FS_ALLOWLIST=/srv/skillscript/workspace,/var/skillscript/events
594
629
 
595
630
  TOCTOU note: the check resolves the real path at call time; a symlink swapped between check and open is a residual closed by fd-based opens later. Checking the resolved real path is the standard mitigation shipped today.
596
631
 
632
+ ## Secrets
633
+
634
+ **A skill can reference an operator-provisioned secret by name, use it at a sink, and never read it back.** This is how a credential (a bearer token, an API key) reaches a `shell(...)` or `$ connector.tool` call without living in the skill source, the transcript, or a trace.
635
+
636
+ Three parts:
637
+
638
+ ```
639
+ # Skill: send-mail
640
+ # Requires: secret.AGENTMAIL_KEY # 1. declare — an approver sees which credentials the skill reaches
641
+ # Status: Draft
642
+
643
+ default: run
644
+ run:
645
+ shell(command="curl -sS -H 'Authorization: Bearer {{secret.AGENTMAIL_KEY}}' https://api.agentmail.to/...") -> R
646
+ ```
647
+
648
+ 1. **Declare** with `# Requires: secret.NAME`.
649
+ 2. **Place** with a `{{secret.NAME}}` marker — only inside a sink (a `shell(...)` op or a `$ connector.tool` arg). It is deliberately distinct from `${VAR}`: `${VAR}` is readable substitution over the skill's variable scope and **cannot** reach a secret; `{{secret.NAME}}` resolves *only* at the sink and is never bound to a variable.
650
+ 3. **Provision** the value as an env var (operator side, e.g. `.env`):
651
+
652
+ ```bash
653
+ SKILLSCRIPT_SECRET_AGENTMAIL_KEY=am_live_...
654
+ ```
655
+
656
+ | Operator switch | Controls |
657
+ |---|---|
658
+ | `SKILLSCRIPT_SECRET_<NAME>` | The value `{{secret.NAME}}` resolves to. The `SKILLSCRIPT_SECRET_` prefix scopes which env is secret-reachable — `{{secret.PATH}}` looks for `SKILLSCRIPT_SECRET_PATH`, never `$PATH`. |
659
+
660
+ **Use-only, enforced.** A marker is legal only in a sink; the runtime injects the value for that one call and never lets it reach a readable surface. Three lint rules block misuse at compile (`secret-use-only` — a marker in `emit`/`$set`/a condition/an `# Output:` template/a `file_*`/`notify` op/an op `(fallback:)`; `secret-undeclared` — a marker with no `# Requires`; `secret-dynamic-name` — a non-literal name like `{{secret.${VAR}}}`). The **runtime** is the authoritative gate (it survives a lint bypass): it resolves only names declared in `# Requires`, only at the sink, and the trace + any error message render the `{{marker}}`, never the value. Fail-closed — an unprovisioned or undeclared secret aborts the op.
661
+
662
+ **Provisioning is operator-only.** A skill author *names* a secret; only the operator *sets* it (in the runtime env / `.env`). Keep secret values out of skill source and out of the fs allowlist, exactly like the approval key.
663
+
664
+ **Threat-model boundary — read this before granting a secret.** A secret you authorize a skill to *use* in a shell, that skill can also *exfiltrate* — e.g. `shell("echo {{secret.X}}") -> OUT` then `emit("${OUT}")`, or a `curl` that POSTs it to any host. This is inherent to passing a credential into a shell command and is **not** prevented by the runtime. Two controls:
665
+
666
+ - **Approval review.** In secured mode a human approves the skill before it runs — review shell-sink skills for exactly this (a command that echoes or forwards the credential).
667
+ - **Prefer a connector sink.** A `$ connector.tool` where the connector holds the credential out of band is stronger than a shell `curl`: the value never round-trips through skill-visible stdout. Use a connector sink for any credential whose exposure you can't accept.
668
+
669
+ (On the shell path the resolved value is also briefly visible in the host process list via `ps`; a future vault-backed `SecretProvider` closes that with sink-aware injection. The `SecretProvider` seam is already in place — an adopter can supply a vault-backed provider via `bootstrap({ secretProvider })` or `bootstrapFromEnv`'s `overrides` with no skill changes.)
670
+
597
671
  ## Trigger model
598
672
 
599
673
  **The trigger surface is two primitives.**
@@ -1103,7 +1177,9 @@ The `// ADOPTER:myorg —` prefix is greppable across merges; your future-self c
1103
1177
 
1104
1178
  ### 4. Treat `src/bootstrap.ts` as reference, not canonical
1105
1179
 
1106
- The bundled `bootstrap()` is a starting point. For deployments with custom substrates, write your own bootstrap that imports the public APIs (`Registry`, the connector classes, `loadConnectorsConfig`, `loadSkillscriptConfig`, etc.). Modifying the bundled bootstrap creates churn on every upstream release.
1180
+ For the standard "wire the whole runtime + dashboard from `$SKILLSCRIPT_HOME` like the CLI does" case — which is most adopters — call **`bootstrapFromEnv()`** (see §"Programmatic bootstrap path"). It's the supported public entry point; you don't hand-assemble anything, and you swap substrates declaratively via `connectors.json`. Remember to `await wired.registry.disposeAll()` on shutdown so stdio-bridged connector children (RemoteMcpConnector) are reaped, not orphaned — `bootstrapFromEnv()` callers own teardown (the CLI does it for you).
1181
+
1182
+ Drop to raw `bootstrap()` + `Registry` only when you're hand-assembling a substrate that `connectors.json` can't express — import the public APIs (`Registry`, the connector classes, `loadConnectorsConfig`, `loadSkillscriptConfig`, etc.) rather than modifying the bundled bootstrap, which churns on every upstream release.
1107
1183
 
1108
1184
  See `examples/custom-bootstrap.example.ts` for a worked walkthrough.
1109
1185
 
@@ -375,7 +375,7 @@ Underscore-prefixed top-level keys (`_comment`, `_note_security`, etc.) are igno
375
375
 
376
376
  Write `class FooSkillStore implements SkillStore { ... }` (or DataStore, LocalModel). Wire it via either:
377
377
 
378
- **(a) Programmatic bootstrap (recommended today)** — write your own bootstrap script that constructs the registry directly:
378
+ **(a) Programmatic bootstrap (recommended today)** — for the common case (wire everything from `$SKILLSCRIPT_HOME` like the CLI does), call **`bootstrapFromEnv()`** and declare your substrate in `connectors.json`; it loads `.env` + config + `connectors.json`, resolves the env cascade, and returns a fully-wired `{ wired, server }` (both unstarted). Reach for the raw-`Registry` assembly below only when hand-constructing a substrate `connectors.json` can't express:
379
379
 
380
380
  ```typescript
381
381
  import { Registry, McpServer, Scheduler } from "skillscript-runtime";
@@ -386,7 +386,7 @@ registry.registerSkillStore("primary", new FooSkillStore({ /* config */ }));
386
386
  // ... register other substrates, then construct Scheduler + McpServer + DashboardServer
387
387
  ```
388
388
 
389
- See [`docs/adopter-playbook.md`](adopter-playbook.md) for the full pattern.
389
+ See [`docs/adopter-playbook.md`](adopter-playbook.md) §"Programmatic bootstrap path" for both — `bootstrapFromEnv()` (recommended) and the raw pattern.
390
390
 
391
391
  **(b) `connectors.json` custom form** (deferred to follow-up):
392
392
 
@@ -270,6 +270,10 @@ Adopter substrates with their own ownership concept (e.g., AMP's `author:<id>` t
270
270
 
271
271
  **`delete()` is destructive in the bundled impls — permanent, name-reclaiming.** The contract signature is `delete(name): Promise<void>`. The bundled stores erase rather than tombstone: `FilesystemSkillStore` unlinks the skill file + version sidecar, `SqliteSkillStore` hard-cascades both tables. After delete the skill is gone from `query()`/`load()`/`metadata()`/`versions()`, the name frees up immediately for a fresh `store()` (clean history, no orphan rows), and there is no trash and no restore. Delete is **operator-only** (CLI `skillfile delete` / the dashboard) — there is no agent/MCP delete surface — and both surfaces gate it behind a confirm + reverse-dependency check. Your impl *may* soft-delete instead (tombstone + filter from normal views); the runtime only requires "remove from normal views," and recovery semantics are your store's concern.
272
272
 
273
+ **`version()` — OPTIONAL cheap change-token (helps remote stores).** A `version(): Promise<string>` method returns a store-wide token that fingerprints the whole namespace **without loading any skill bodies**. `skill_list` returns it as `catalog_version` and honors a caller's `if_none_match`: when the token still matches, the response is `{ not_modified: true }` and the catalog rebuild is skipped. That rebuild otherwise costs one `load()` *per skill* (to parse each entry's effectful footprint) — free against a local store, but a network round-trip per entry against a remote one, so a polling dashboard hammers the substrate. **Optional**: a store without `version()` just always rebuilds (today's behavior, no change).
274
+
275
+ **The contract invariant** — `version()` MUST change whenever the catalog's *observable content* changes: an add, a remove, a status change, **and a body edit even if the status is unchanged**. A token that fails this serves a stale catalog. ⚠ **The subtle trap:** a token over only `(id, status)` is exact in **secured mode** (an edit forces the skill back to `Draft`, so status moves) but goes **stale in unsecured mode** on a body-edit-with-unchanged-status. So fold a per-skill **content revision** (a content hash, version, or `updated_at`) into the fingerprint — don't ship a status-only token unless your deployment is secured-mode-only, and if it is, document that limitation. Bundled impls satisfy the invariant in *both* modes: `FilesystemSkillStore` hashes each skill file's `(name, mtime)` (a rewrite moves the mtime); `SqliteSkillStore` hashes `(name, status, current_version)` — `current_version` is the content hash. **Implement it on any network-backed SkillStore**, fingerprinting whatever your substrate exposes cheaply (a list ETag, a max-revision/seq, or a metadata digest), as long as every observable change moves it.
276
+
273
277
  ### DataStore conventions
274
278
 
275
279
  **`summary`/`detail` split is convention, not contract field.** The DataStore contract gives `write()` a single `content: string`. Bundled `SqliteDataStore` maps this to `summary = first line (≤200 chars)` and `detail = full content`. Adopter substrates with native summary/detail concepts (AMP's `summary` + `detail` columns) can pre-compose and pass via `metadata`, but the basic mapping convention is "first line is the preview." Diverge and the dashboard's memory rendering looks weird, but skills still work.
@@ -511,7 +511,7 @@ $ amp.amp_olsen_task task_type="scan" timeout=120 -> DISPATCH
511
511
  $ data_write content="${REPORT}" tags=["oncall"] approved="morning roundup" -> ACK
512
512
  ```
513
513
 
514
- **`(fallback: "value")` trailer** — Fires on dispatch throw OR empty bound value (empty string after trim, empty array, null/undefined). Honored on `$` dispatch and on `shell()` (fires on shell op throw or empty stdout). Coerce-on-bind: the fallback value binds to the output var transparently, downstream targets need no conditional. Permissive value parsing — bare identifiers, quoted strings, bracketed array literals all accepted. A fired fallback is recorded in `result.fallbacks[]`.
514
+ **`(fallback: "value")` trailer** — Fires on dispatch throw (including a `timeout=N` expiry) OR empty bound value (empty string after trim, empty array, null/undefined). Honored on `$` dispatch and on `shell()` (fires on shell op throw or empty stdout). Coerce-on-bind: the fallback value binds to the output var transparently, downstream targets need no conditional. Permissive value parsing — bare identifiers, quoted strings, bracketed array literals all accepted. A fired fallback is recorded in `result.fallbacks[]`.
515
515
 
516
516
  Note: an envelope object like `{items: []}` is a non-empty object and does NOT trigger the fallback even though its contained array is empty. To handle envelope-empty downstream, test the contained collection (`if ${R.items|length} == "0":`) or apply a filter (`${R.items|fallback:[]}`).
517
517
 
@@ -541,11 +541,11 @@ Routes through the wired LocalModel.
541
541
 
542
542
  | Kwarg | Required | Notes |
543
543
  |---|---|---|
544
- | `prompt="..."` | yes | Non-empty string. Substitution-resolved at runtime. |
545
- | `maxTokens=N` | no | Positive integer (number or numeric string). Forwarded as `runOpts.maxTokens`. |
544
+ | `prompt="..."` | yes | Non-empty string (empty/missing throws). Substitution-resolved at runtime. Must be a kwarg — there is no bare-string positional form (`$ llm "..."` is invalid; see the all-kwargs rule at the top of this section). |
545
+ | `maxTokens=N` | no | Positive integer (number or numeric string). Forwarded as `runOpts.maxTokens`. **camelCase — it's `maxTokens`, NOT `max_tokens`; the snake_case form is an unknown kwarg the bridge silently drops, caught by the `unknown-llm-arg` lint.** |
546
546
  | `model="X"` | no | Per-call model selection. Resolves against registered LocalModel aliases via `registry.getLocalModel(X)`; falls through to the default LocalModel with `model=X` passed as an upstream hint (e.g., Ollama tag) when X doesn't resolve. See `unknown-llm-model` lint below. |
547
547
 
548
- Plus the universal op-level kwargs (`timeout`, `approved`, `(fallback:)`, `-> R`).
548
+ Plus the universal op-level kwargs (`timeout`, `approved`, `(fallback:)`, `-> R`). `timeout` is runtime-enforced (popped before the connector sees it); a timeout expiry is a throw, so a `(fallback:)` trailer catches it.
549
549
 
550
550
  Canonical shape:
551
551
 
@@ -795,6 +795,83 @@ Missing-ref in the RHS produces a tier-1 runtime error.
795
795
  - `foreach IDENT in EXPR:` iterator vars are loop-local — `$set` bindings inside the loop don't persist after the loop ends
796
796
  - Target outputs (`${target.output}`) are accessible after the target completes
797
797
 
798
+ ## Secrets — secret.NAME references, {{secret.NAME}} sink markers, SKILLSCRIPT_SECRET_ provisioning, use-only enforcement
799
+
800
+ ## Secrets
801
+
802
+ Skills routinely need a credential — an API token, a deploy key — to reach an external service. Skillscript handles secrets **by reference, never by value**. A skill declares which secrets it may use and marks exactly where each is injected, but it can never read, bind, or print the value. The runtime resolves a secret only at the instant it is handed to a *sink* (a `shell` op or a `$ connector.tool` dispatch).
803
+
804
+ This is what lets a human approve a skill knowing the full set of credentials it can reach — and lets an agent-authored skill run without ever exposing the key to the agent. The author holds a *use*-capability (invoke a skill that reaches the key) without the *access*-capability (read the key).
805
+
806
+ ### Three steps
807
+
808
+ **1. Declare it in frontmatter.** List every secret the skill may reach. An approver reading the skill sees the whole credential surface up front.
809
+
810
+ ```
811
+ # Skill: send-email
812
+ # Requires: secret.AGENTMAIL_KEY
813
+ # Status: Draft
814
+ ```
815
+
816
+ **2. Place it with a `{{secret.NAME}}` marker — only inside a sink.** A secret may appear only where it is *used*: inside a `shell(...)` op or a `$ connector.tool` dispatch.
817
+
818
+ ```
819
+ run:
820
+ shell(command="curl -H 'Authorization: Bearer {{secret.AGENTMAIL_KEY}}' https://api.agentmail.to/...") -> R
821
+ ```
822
+
823
+ …or into a connector argument:
824
+
825
+ ```
826
+ $ http.post url="https://api..." authorization={{secret.AGENTMAIL_KEY}} -> R
827
+ ```
828
+
829
+ **3. Provision it as an env var**, named with the `SKILLSCRIPT_SECRET_` prefix:
830
+
831
+ ```
832
+ SKILLSCRIPT_SECRET_AGENTMAIL_KEY=am_us_...
833
+ ```
834
+
835
+ ### Semantics — the rules that matter
836
+
837
+ - **`{{secret.NAME}}` is not `${VAR}`.** `${VAR}` is readable substitution in the skill's variable scope — it can be bound, emitted, branched on, and it appears in the execution trace. A secret can do none of that. `${VAR}` cannot reach a secret; a `{{secret.NAME}}` marker is never readable as a var.
838
+ - **The name is a compile-time literal.** `${VAR}` substitution never reaches inside a `{{secret.…}}` marker (markers are opaque to substitution), and the runtime resolves a marker only against the declared `# Requires` set by exact-literal match. You cannot build a secret name dynamically.
839
+ - **Use-only, resolved at the sink.** The value is injected at the moment the shell op spawns or the connector dispatches — never bound to a variable, never emitted, never written to a trace. The trace records the *marker* (`{{secret.AGENTMAIL_KEY}}`), not the value. The binary/allowlist gate runs *before* resolution, so a refused op never even resolves the secret, and error/trace output is redacted to the marker form.
840
+ - **Misuse is a compile-time error (tier-1), not a runtime surprise:**
841
+ - `{{secret.NAME}}` in `emit`, `$set`, a condition, an `# Output:` template, a `file_*` / `notify` op, or a `(fallback: ...)` → **`secret-use-only`**.
842
+ - A `{{secret.NAME}}` marker with no matching `# Requires: secret.NAME` declaration → **`secret-undeclared`**.
843
+ - A marker whose name isn't a clean literal (e.g. `{{secret.${NM}}}`) → **`secret-dynamic-name`**.
844
+
845
+ ### Worked example
846
+
847
+ A skill that checks an inbox over a REST API. The key is declared once and used once, at the curl sink — it never touches a variable, the output, or the trace:
848
+
849
+ ```
850
+ # Skill: inbox-check
851
+ # Requires: secret.AGENTMAIL_KEY
852
+ # Returns: COUNT
853
+
854
+ check:
855
+ shell(argv=["curl","-s","-H","Authorization: Bearer {{secret.AGENTMAIL_KEY}}","https://api.agentmail.to/v0/inboxes/me/messages?labels=unread"]) -> RAW (fallback: "{\"count\":0}")
856
+ $ json_parse ${RAW} -> P
857
+ $set COUNT = ${P.count}
858
+ emit(text="Unread: ${COUNT}")
859
+
860
+ default: check
861
+ ```
862
+
863
+ ### Threat-model boundary — what this does NOT protect
864
+
865
+ The guarantees above stop *accidental* leakage and *undeclared* access: a secret is never bound, emitted, traced, leaked through a refused op, or reachable via a dynamically-built or undeclared name. They do **not** stop a *malicious skill author*. A skill authorized to use a secret in a **shell** sink can also make that sink exfiltrate it — e.g. `shell(command="printf %s {{secret.FLAG}}") -> OUT` then `emit(${OUT})` launders the secret through the skill's own stdout, and a `curl` could send it anywhere. This is inherent: once a command may *use* a secret, it may *misuse* it.
866
+
867
+ The controls for that boundary are:
868
+ - **Human approval review** — an approver who sees `printf {{secret}}` followed by `emit` rejects the skill. The `# Requires:` line + the visible sink are exactly what makes this reviewable.
869
+ - **Prefer connector sinks over raw shell.** A connector holds the credential out-of-band and applies it inside the dispatch, so the secret never round-trips through skill-visible output (`$ http.post ... authorization={{secret.NAME}}` returns the API's *data*, not the key). Use a `shell` secret only when no connector is available, and review such skills accordingly.
870
+
871
+ ### Why this shape
872
+
873
+ The marker/var split *is* the security model: the runtime is the reference monitor, the human approval is the grant, and the `# Requires:` line is the auditable statement of reach. Capability without access — the agent can act through the credential without ever holding it.
874
+
798
875
  ## Pipe filters — url, shell, json, trim, fallback, isodate
799
876
 
800
877
  Pipe filters apply transforms to resolved variables before substitution. Syntax: `${VAR|filter}` or `${VAR|filter:"arg"}` for parameterized filters. Filters operate at compile time for static values; for runtime-bound variables, filters apply at substitution time.
@@ -2479,5 +2556,5 @@ When any of these primitives ship, the relevant grammar moves into its canonical
2479
2556
 
2480
2557
  ---
2481
2558
 
2482
- *Rendered from `skillscript/skillscript-language-reference` — 2026-06-16 15:46 EDT*
2559
+ *Rendered from `skillscript/skillscript-language-reference` — 2026-06-28 15:20 EDT*
2483
2560
  *Source of truth: AMP (`amp_render_document("skillscript/skillscript-language-reference")`)*
@@ -141,7 +141,7 @@ SqliteSkillStore is the storage layer. Authoring happens above:
141
141
  - **MCP tool**: agents call `skill_write` directly; same path as the dashboard
142
142
  - **Programmatic**: your code calls `store.store(name, source, metadata?)` directly
143
143
 
144
- The dashboard does NOT default to SqliteSkillStore today `skillfile dashboard` bootstraps with FilesystemSkillStore. If you want a SqliteSkillStore-backed dashboard, write a custom bootstrap (small surface; see the runtime `Registry` API).
144
+ The dashboard defaults to FilesystemSkillStore. For a SqliteSkillStore-backed dashboard, set `substrate.skill_store: "sqlite"` in `connectors.json` both `skillfile dashboard` and the programmatic `bootstrapFromEnv()` honor it; no custom bootstrap needed.
145
145
 
146
146
  ---
147
147
 
@@ -164,6 +164,7 @@ When forking into your codebase:
164
164
  2. Replace the SQL with your substrate's API (HTTP, DataStore, vector DB, etc.)
165
165
  3. Update `staticCapabilities()` to match what your substrate actually supports — drop `supports_versioning` if you can't track history, drop `supports_tag_filter` if querying tags isn't tractable
166
166
  4. Update `manifest()` to describe your substrate (`kind: "amp"` or whatever)
167
- 5. Tests: copy `tests/SqliteSkillStore.test.ts` as a starting point + run the conformance suite (`SkillStoreConformance.buildTests()` from `skillscript-runtime/testing`)
167
+ 5. **Optional but high-value for network-backed forks: implement `version()`** — a cheap store-wide change-token computed WITHOUT loading bodies (a list ETag, max-revision, or metadata digest), where any add/remove/edit/status-change moves it. It lets `skill_list` skip its N+1 catalog rebuild on unchanged polls (each entry otherwise costs a `load()` — one network round-trip per skill against a remote store). `SqliteSkillStore` hashes `(name, status, current_version)` in one body-free query. Skip it and `skill_list` just always rebuilds.
168
+ 6. Tests: copy `tests/SqliteSkillStore.test.ts` as a starting point + run the conformance suite (`SkillStoreConformance.buildTests()` from `skillscript-runtime/testing`)
168
169
 
169
170
  The conformance suite catches drift from the contract surface. If your fork passes, the runtime treats it interchangeably with FilesystemSkillStore / SqliteSkillStore / etc.
@@ -222,4 +222,23 @@ export class SkillStoreTemplate implements SkillStore {
222
222
  // - Return VersionInfo with previous_status populated
223
223
  throw new Error("TODO: update_status() — atomic status transition + audit row.");
224
224
  }
225
+
226
+ // OPTIONAL (v0.24.0) — implement this on a NETWORK-BACKED store. It returns a
227
+ // cheap store-wide change-token computed WITHOUT loading skill bodies. The MCP
228
+ // `skill_list` tool returns it as `catalog_version` and honors a caller's
229
+ // `if_none_match`: when the token is unchanged it replies `{ not_modified: true }`
230
+ // and skips the catalog rebuild — which otherwise loads EVERY skill body (one
231
+ // network round-trip per skill) to parse its effectful footprint. For a remote
232
+ // store this is the difference between a chatty poll and a single cheap probe.
233
+ //
234
+ // Fingerprint whatever your substrate exposes cheaply (a list ETag, a
235
+ // max-revision/seq, or a metadata digest), as long as ANY add/remove/edit/
236
+ // status-change moves the token. DELETE this method if you can't compute one
237
+ // cheaply — it's optional, and `skill_list` just always rebuilds without it.
238
+ async version(): Promise<string> {
239
+ // TODO — e.g. a single metadata-only list:
240
+ // const rows = await this.api.list({ fields: ["id", "status", "rev"] }); // NO bodies
241
+ // return sha256(rows.map(r => `${r.id}:${r.status}:${r.rev}`).join("\n")).slice(0, 16);
242
+ throw new Error("TODO: version() — optional cheap change-token (no body loads). Delete if unsupported.");
243
+ }
225
244
  }
@@ -1,13 +1,23 @@
1
1
  // Worked example: writing a custom bootstrap that wires adopter-specific
2
2
  // substrates against skillscript-runtime's public APIs.
3
3
  //
4
- // **When to write your own bootstrap.** The bundled `bootstrap()` from
5
- // `skillscript-runtime` wires `FilesystemSkillStore` + `OllamaLocalModel` +
6
- // `SqliteDataStore` + the typed-contract bridges. If your deployment uses
7
- // different substrates (your own data store, a hosted LLM API, an MCP
8
- // server for agent delivery, etc.), write your own bootstrap rather than
9
- // modifying the bundled one. Prevents merge conflicts on every upstream
10
- // release that touches `src/bootstrap.ts`.
4
+ // **First: do you even need this?** For the common case — wire the whole
5
+ // runtime + dashboard from `$SKILLSCRIPT_HOME` exactly like the CLI does —
6
+ // call `bootstrapFromEnv()` instead (it loads `.env` + `skillscript.config.json`
7
+ // + `connectors.json`, resolves the SKILLSCRIPT_* env cascade, and returns
8
+ // `{ wired, server }` unstarted). You can swap substrates declaratively via
9
+ // `connectors.json` (`substrate.skill_store: "sqlite"`, a custom class, etc.)
10
+ // without writing any of the assembly below. See docs/adopter-playbook.md
11
+ // §"Programmatic bootstrap path".
12
+ //
13
+ // **When to write your own bootstrap (this file).** Reach for raw `bootstrap()`
14
+ // + `Registry` only when hand-assembling a substrate `connectors.json` can't
15
+ // express. The bundled `bootstrap()` from `skillscript-runtime` wires
16
+ // `FilesystemSkillStore` + `OllamaLocalModel` + `SqliteDataStore` + the typed-
17
+ // contract bridges. If your deployment uses different substrates (your own data
18
+ // store, a hosted LLM API, an MCP server for agent delivery, etc.), assemble
19
+ // your own here rather than modifying the bundled one. Prevents merge conflicts
20
+ // on every upstream release that touches `src/bootstrap.ts`.
11
21
  //
12
22
  // **What to copy from this file.** Take what fits your deployment and
13
23
  // drop the rest. The shape is: construct a Registry, register your
@@ -2,7 +2,7 @@
2
2
  "provenance_version": "1.0",
3
3
  "language_version": "1.0",
4
4
  "compiler_version": "0.1.0-dev",
5
- "compiled_at": "2026-06-23T19:04:21.352Z",
5
+ "compiled_at": "2026-06-28T19:30:48.982Z",
6
6
  "source_skill": {
7
7
  "name": "hello-world"
8
8
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillscript-runtime",
3
- "version": "0.23.1",
3
+ "version": "0.25.0",
4
4
  "description": "Runtime, compiler, lint, CLI, and dashboard for Skillscript — a small declarative language for authoring agent workflows.",
5
5
  "license": "MIT",
6
6
  "author": "Scott Shwarts <scotts@pobox.com>",
@@ -56,6 +56,19 @@
56
56
  # SKILLSCRIPT_SHELL_ALLOWLIST=curl,git,jq,grep
57
57
 
58
58
 
59
+ # ─── Secrets ───────────────────────────────────────────────────────────
60
+
61
+ # Provision a secret a skill can USE but never READ. A skill declares it with
62
+ # `# Requires: secret.NAME` and places it with a `{{secret.NAME}}` marker; the
63
+ # runtime injects the value only at a sink (a shell() op or a `$ connector.tool`
64
+ # dispatch) and never lets it reach an emit, a $set, a trace, or any readable
65
+ # surface. Name each secret SKILLSCRIPT_SECRET_<NAME> — the prefix scopes which
66
+ # env is secret-reachable (so {{secret.PATH}} can't pull $PATH). Operator-only:
67
+ # a skill author references a secret by name but cannot set or read it.
68
+ # SKILLSCRIPT_SECRET_AGENTMAIL_KEY=sk-live-...
69
+ # SKILLSCRIPT_SECRET_GITHUB_TOKEN=ghp_...
70
+
71
+
59
72
  # ─── Network / bind config ─────────────────────────────────────────────
60
73
 
61
74
  # HTTP bind address. Default 127.0.0.1 (localhost-only). Set 0.0.0.0 for