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.
- package/CHANGELOG.md +22 -0
- package/README.md +2 -1
- package/UPGRADING.md +9 -1
- package/dist/bootstrap-from-env.d.ts +46 -0
- package/dist/bootstrap-from-env.d.ts.map +1 -0
- package/dist/bootstrap-from-env.js +130 -0
- package/dist/bootstrap-from-env.js.map +1 -0
- package/dist/bootstrap.d.ts +10 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +7 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/cli.js +23 -164
- package/dist/cli.js.map +1 -1
- package/dist/connectors/registry.d.ts +9 -0
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +23 -0
- package/dist/connectors/registry.js.map +1 -1
- package/dist/connectors/skill-store.d.ts +1 -0
- package/dist/connectors/skill-store.d.ts.map +1 -1
- package/dist/connectors/skill-store.js +27 -0
- package/dist/connectors/skill-store.js.map +1 -1
- package/dist/connectors/sqlite-skill-store.d.ts +1 -0
- package/dist/connectors/sqlite-skill-store.d.ts.map +1 -1
- package/dist/connectors/sqlite-skill-store.js +10 -0
- package/dist/connectors/sqlite-skill-store.js.map +1 -1
- package/dist/connectors/types.d.ts +19 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/connectors/types.js.map +1 -1
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +4 -0
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/spa/app.js +33 -5
- package/dist/help-content.d.ts.map +1 -1
- package/dist/help-content.js +3 -0
- package/dist/help-content.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lint.d.ts +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +145 -0
- package/dist/lint.js.map +1 -1
- package/dist/mcp-server.d.ts +8 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +24 -1
- package/dist/mcp-server.js.map +1 -1
- package/dist/parser.d.ts +9 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +22 -6
- package/dist/parser.js.map +1 -1
- package/dist/runtime.d.ts +20 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +147 -24
- package/dist/runtime.js.map +1 -1
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +3 -0
- package/dist/scheduler.js.map +1 -1
- package/dist/secrets.d.ts +119 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +153 -0
- package/dist/secrets.js.map +1 -0
- package/dist/skill-catalog.d.ts.map +1 -1
- package/dist/skill-catalog.js +2 -0
- package/dist/skill-catalog.js.map +1 -1
- package/docs/adopter-playbook.md +80 -4
- package/docs/configuration.md +2 -2
- package/docs/connector-contract-reference.md +4 -0
- package/docs/language-reference.md +82 -5
- package/docs/sqlite-skill-store.md +3 -2
- package/examples/connectors/SkillStoreTemplate/SkillStoreTemplate.ts +19 -0
- package/examples/custom-bootstrap.example.ts +17 -7
- package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
- package/package.json +1 -1
- package/scaffold/.env.example +13 -0
package/dist/secrets.js
ADDED
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA6BH,8EAA8E;AAC9E,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAEhC;IAEA;IAHlB,YACkB,UAAkB;IAClC,yDAAyD;IACzC,WAAmB;QAEnC,KAAK,CAAC,WAAW,UAAU,wCAAwC,WAAW,EAAE,CAAC,CAAC;QAJlE,eAAU,GAAV,UAAU,CAAQ;QAElB,gBAAW,GAAX,WAAW,CAAQ;QAGnC,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAiB;IAIC;IAH7B,kEAAkE;IAClE,MAAM,CAAU,MAAM,GAAG,qBAAqB,CAAC;IAE/C,YAA6B,MAAyB,OAAO,CAAC,GAAG;QAApC,QAAG,GAAH,GAAG,CAAiC;IAAG,CAAC;IAErE,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,IAAsB;QAChD,MAAM,GAAG,GAAG,iBAAiB,CAAC,MAAM,GAAG,IAAI,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACxC,MAAM,IAAI,yBAAyB,CACjC,IAAI,EACJ,OAAO,GAAG,6EAA6E;gBACrF,uGAAuG,CAC1G,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;;AAGH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAsB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,uCAAuC,CAAC;AAE9D,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;IAC5B,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,6EAA6E;IAC7E,6EAA6E;IAC7E,uCAAuC;IACvC,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAY;IACrD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,8EAA8E;IAC9E,MAAM,SAAS,GAAG,2BAA2B,CAAC;IAC9C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,sCAAsC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAY,EACZ,QAAwB,EACxB,GAAqB;AACrB;;yCAEyC;AACzC,SAAiD;IAEjD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,sEAAsE;IACtE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChD,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC1B,SAAS,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,KAAe,EACf,QAAwB,EACxB,GAAqB,EACrB,SAAiD;IAEjD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,GAAG,CAAC,IAAI,CAAC,MAAM,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9F,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -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,
|
|
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"}
|
package/dist/skill-catalog.js
CHANGED
|
@@ -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":"AAUA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,4EAA4E;AAC5E,MAAM,eAAe,GAAsC;IACzD,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE;IAChD,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;CAC5D,CAAC;AAEF,MAAM,gBAAgB,GAAgC,OAAO,CAAC;AAC9D,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAsB,EACtB,SAA0B,EAAE;IAE5B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,gBAAgB,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,cAAc,CAAC;IAE/C,sEAAsE;IACtE,uEAAuE;IACvE,kEAAkE;IAClE,8DAA8D;IAC9D,qEAAqE;IACrE,qEAAqE;IACrE,kCAAkC;IAClC,MAAM,WAAW,GAA+C,EAAE,MAAM,EAAE,CAAC;IAC3E,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;QAAE,WAAW,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IACpE,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAElD,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YAClF,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtE,MAAM,SAAS,GAAI,IAAI,CAAC,YAAY,EAAE,CAAC,aAAa,CAA0B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,CAAyB,IAAI,EAAE,CAAC;YAC5I,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS;QACxE,CAAC;QACD,iEAAiE;QACjE,iEAAiE;QACjE,mEAAmE;QACnE,gEAAgE;QAChE,wDAAwD;QACxD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;YACjE,SAAS;QACX,CAAC;QAED,IAAI,MAAM,CAAC;QACX,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YACvB,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;YAC7D,0EAA0E;YAC1E,iEAAiE;YACjE,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,UAAU;gBACpB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;gBACnC,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,OAAO,EAAE,oBAAoB,CAAC,MAAM,CAAC,CAAC,EAAE;gBACxC,IAAI,EAAE,EAAE;gBACR,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE,EAAE;gBACZ,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,EAAE;gBACZ,eAAe,EAAE,EAAE;gBACnB,mBAAmB,EAAE,eAAe;gBACpC,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;aAC5E,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACrH,4EAA4E;QAC5E,KAAK,CAAC,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAEhD,0FAA0F;QAC1F,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;YACrG,SAAS;QACX,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,IAAY,EACZ,WAAmB,EACnB,MAA4B,EAC5B,MAAmB,EACnB,MAAe;IAEf,OAAO;QACL,IAAI;QACJ,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC;QACzD,WAAW;QACX,MAAM;QACN,kEAAkE;QAClE,mEAAmE;QACnE,OAAO,EAAE,MAAM,KAAK,UAAU;QAC9B,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC;QAC7B,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC;QACrC,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC;QACzC,yEAAyE;QACzE,0EAA0E;QAC1E,kEAAkE;QAClE,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,eAAe,EAAE,MAAM,CAAC,cAAc;QACtC,mBAAmB,EAAE,yBAAyB,CAAC,MAAM,CAAC;QACtD,iEAAiE;QACjE,qDAAqD;QACrD,MAAM,EAAE,MAAM,IAAI,IAAI;KACvB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,cAAc,CAAC,OAAqB,EAAE,QAAuB;IACpE,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC;QAAE,OAAO,YAAY,CAAC;IACjE,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IAClE,8CAA8C;IAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,UAAU,CAAC;IAC7C,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,UAAU,CAAC,UAAyE;IAC3F,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,OAAO,KAAK,SAAS;QACjC,OAAO,EAAE,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO;KACpD,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,aAAa,CAAC,aAA2B;IAChD,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACpH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,cAA6B;IACnD,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAkC,EAAE;QAC9D,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrE,0DAA0D;QAC1D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,OAAqB,EAAE,QAAqC;IACnF,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,MAAM,YAAY,GAAG,QAAQ,IAAI,gBAAgB,CAAC;IAClD,MAAM,iBAAiB,GAAG,YAAY,KAAK,OAAO,IAAI,YAAY,KAAK,KAAK,CAAC;IAC7E,MAAM,eAAe,GAAG,YAAY,KAAK,OAAO,IAAI,YAAY,KAAK,KAAK,CAAC;IAC3E,MAAM,eAAe,GAAG,YAAY,KAAK,KAAK,IAAI,YAAY,KAAK,UAAU,CAAC;IAE9E,IAAI,iBAAiB;QAAE,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC;IAC7F,IAAI,eAAe;QAAE,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IACvF,IAAI,eAAe;QAAE,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IACzF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,MAAc;IACvC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,EAAE;YAAE,SAAS;QAC7B,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACtC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
package/docs/adopter-playbook.md
CHANGED
|
@@ -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
|
|
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 them — it 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
|
|
484
|
+
### Programmatic bootstrap path
|
|
485
485
|
|
|
486
|
-
**
|
|
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
|
-
|
|
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
|
|
package/docs/configuration.md
CHANGED
|
@@ -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)** —
|
|
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
|
|
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-
|
|
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
|
|
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.
|
|
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
|
-
// **
|
|
5
|
-
//
|
|
6
|
-
// `
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillscript-runtime",
|
|
3
|
-
"version": "0.
|
|
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>",
|
package/scaffold/.env.example
CHANGED
|
@@ -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
|