@foldrun/core 0.2.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 (166) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +239 -0
  3. package/dist/index.d.ts +32 -0
  4. package/dist/index.js +43 -0
  5. package/dist/src/agent-tools.d.ts +31 -0
  6. package/dist/src/agent-tools.js +99 -0
  7. package/dist/src/agents-md.d.ts +9 -0
  8. package/dist/src/agents-md.js +83 -0
  9. package/dist/src/api-tools.d.ts +38 -0
  10. package/dist/src/api-tools.js +313 -0
  11. package/dist/src/approvals.d.ts +39 -0
  12. package/dist/src/approvals.js +103 -0
  13. package/dist/src/arrange.d.ts +8 -0
  14. package/dist/src/arrange.js +40 -0
  15. package/dist/src/catalog.d.ts +108 -0
  16. package/dist/src/catalog.js +255 -0
  17. package/dist/src/completions.d.ts +61 -0
  18. package/dist/src/completions.js +578 -0
  19. package/dist/src/confine.d.ts +31 -0
  20. package/dist/src/confine.js +298 -0
  21. package/dist/src/container.d.ts +30 -0
  22. package/dist/src/container.js +172 -0
  23. package/dist/src/context-tools.d.ts +81 -0
  24. package/dist/src/context-tools.js +347 -0
  25. package/dist/src/deploy.d.ts +100 -0
  26. package/dist/src/deploy.js +377 -0
  27. package/dist/src/egress.d.ts +75 -0
  28. package/dist/src/egress.js +133 -0
  29. package/dist/src/evals.d.ts +150 -0
  30. package/dist/src/evals.js +651 -0
  31. package/dist/src/flow-lint.d.ts +16 -0
  32. package/dist/src/flow-lint.js +177 -0
  33. package/dist/src/git-hook.d.ts +1 -0
  34. package/dist/src/git-hook.js +70 -0
  35. package/dist/src/git.d.ts +37 -0
  36. package/dist/src/git.js +101 -0
  37. package/dist/src/gitrepo.d.ts +95 -0
  38. package/dist/src/gitrepo.js +355 -0
  39. package/dist/src/history.d.ts +82 -0
  40. package/dist/src/history.js +259 -0
  41. package/dist/src/kinds.d.ts +95 -0
  42. package/dist/src/kinds.js +421 -0
  43. package/dist/src/library.d.ts +94 -0
  44. package/dist/src/library.js +341 -0
  45. package/dist/src/linkable.d.ts +20 -0
  46. package/dist/src/linkable.js +113 -0
  47. package/dist/src/model-probe.d.ts +21 -0
  48. package/dist/src/model-probe.js +81 -0
  49. package/dist/src/notify.d.ts +61 -0
  50. package/dist/src/notify.js +252 -0
  51. package/dist/src/oauth-presets.d.ts +13 -0
  52. package/dist/src/oauth-presets.js +39 -0
  53. package/dist/src/okf.d.ts +220 -0
  54. package/dist/src/okf.js +656 -0
  55. package/dist/src/openapi.d.ts +66 -0
  56. package/dist/src/openapi.js +342 -0
  57. package/dist/src/paths.d.ts +33 -0
  58. package/dist/src/paths.js +78 -0
  59. package/dist/src/platform.d.ts +32 -0
  60. package/dist/src/platform.js +48 -0
  61. package/dist/src/providers.d.ts +47 -0
  62. package/dist/src/providers.js +114 -0
  63. package/dist/src/refs.d.ts +20 -0
  64. package/dist/src/refs.js +78 -0
  65. package/dist/src/run-container.d.ts +227 -0
  66. package/dist/src/run-container.js +760 -0
  67. package/dist/src/runner.d.ts +241 -0
  68. package/dist/src/runner.js +3215 -0
  69. package/dist/src/runtime.d.ts +41 -0
  70. package/dist/src/runtime.js +357 -0
  71. package/dist/src/script-tools.d.ts +62 -0
  72. package/dist/src/script-tools.js +296 -0
  73. package/dist/src/secret-files.d.ts +12 -0
  74. package/dist/src/secret-files.js +92 -0
  75. package/dist/src/secrets.d.ts +147 -0
  76. package/dist/src/secrets.js +626 -0
  77. package/dist/src/starter.d.ts +21 -0
  78. package/dist/src/starter.js +399 -0
  79. package/dist/src/step-exec.d.ts +136 -0
  80. package/dist/src/step-exec.js +391 -0
  81. package/dist/src/storage.d.ts +123 -0
  82. package/dist/src/storage.js +640 -0
  83. package/dist/src/store.d.ts +1052 -0
  84. package/dist/src/store.js +2378 -0
  85. package/dist/src/tar.d.ts +30 -0
  86. package/dist/src/tar.js +111 -0
  87. package/dist/src/tool-names.d.ts +39 -0
  88. package/dist/src/tool-names.js +79 -0
  89. package/dist/src/tool-programs.d.ts +18 -0
  90. package/dist/src/tool-programs.js +52 -0
  91. package/dist/src/tool-test.d.ts +19 -0
  92. package/dist/src/tool-test.js +335 -0
  93. package/dist/src/translator.d.ts +159 -0
  94. package/dist/src/translator.js +986 -0
  95. package/dist/src/triggers.d.ts +60 -0
  96. package/dist/src/triggers.js +270 -0
  97. package/dist/src/webhook.d.ts +47 -0
  98. package/dist/src/webhook.js +148 -0
  99. package/index.ts +44 -0
  100. package/package.json +64 -0
  101. package/src/agent-tools.ts +138 -0
  102. package/src/agents-md.ts +81 -0
  103. package/src/api-tools.ts +389 -0
  104. package/src/approvals.ts +129 -0
  105. package/src/arrange.ts +42 -0
  106. package/src/catalog.ts +321 -0
  107. package/src/completions.ts +652 -0
  108. package/src/confine.ts +350 -0
  109. package/src/container.ts +220 -0
  110. package/src/context-tools.ts +435 -0
  111. package/src/deploy.ts +458 -0
  112. package/src/egress.ts +176 -0
  113. package/src/evals.ts +812 -0
  114. package/src/flow-lint.ts +215 -0
  115. package/src/git-hook.ts +73 -0
  116. package/src/git.ts +117 -0
  117. package/src/gitrepo.ts +391 -0
  118. package/src/history.ts +329 -0
  119. package/src/kinds.ts +507 -0
  120. package/src/library.ts +419 -0
  121. package/src/linkable.ts +123 -0
  122. package/src/model-probe.ts +112 -0
  123. package/src/notify.ts +281 -0
  124. package/src/oauth-presets.ts +54 -0
  125. package/src/okf.ts +768 -0
  126. package/src/openapi.ts +383 -0
  127. package/src/paths.ts +79 -0
  128. package/src/platform.ts +87 -0
  129. package/src/providers.ts +147 -0
  130. package/src/refs.ts +83 -0
  131. package/src/run-container.ts +883 -0
  132. package/src/runner.ts +3446 -0
  133. package/src/runtime.ts +404 -0
  134. package/src/script-tools.ts +383 -0
  135. package/src/secret-files.ts +101 -0
  136. package/src/secrets.ts +799 -0
  137. package/src/starter.ts +415 -0
  138. package/src/step-exec.ts +498 -0
  139. package/src/storage.ts +771 -0
  140. package/src/store.ts +2985 -0
  141. package/src/tar.ts +124 -0
  142. package/src/tool-names.ts +91 -0
  143. package/src/tool-programs.ts +65 -0
  144. package/src/tool-test.ts +401 -0
  145. package/src/translator.ts +1029 -0
  146. package/src/triggers.ts +287 -0
  147. package/src/webhook.ts +183 -0
  148. package/templates/blog-desk/AGENTS.md +13 -0
  149. package/templates/blog-desk/agents/researcher/agent.md +11 -0
  150. package/templates/blog-desk/agents/writer/agent.md +8 -0
  151. package/templates/blog-desk/evals/writer-quality.md +10 -0
  152. package/templates/blog-desk/flows/publish.md +10 -0
  153. package/templates/blog-desk/knowledge/house-style.md +9 -0
  154. package/templates/blog-desk/knowledge/index.md +9 -0
  155. package/templates/hello/AGENTS.md +18 -0
  156. package/templates/hello/agents/curator/agent.md +32 -0
  157. package/templates/hello/agents/notetaker/agent.md +24 -0
  158. package/templates/hello/evals/house-style.md +16 -0
  159. package/templates/hello/flows/memory-review.md +7 -0
  160. package/templates/hello/flows/note.md +7 -0
  161. package/templates/hello/knowledge/house-style.md +15 -0
  162. package/templates/hello/knowledge/index.md +9 -0
  163. package/templates/hello/skills/plain-english/SKILL.md +11 -0
  164. package/templates/hello/state/publishing.md +7 -0
  165. package/templates/hello/tools/wordcount/run.py +13 -0
  166. package/templates/hello/tools/wordcount/tool.md +22 -0
@@ -0,0 +1,421 @@
1
+ // Every kind of document foldrun knows about, in one place.
2
+ //
3
+ // The naming, settled: these are **documents**. Not "resources" — MCP already
4
+ // owns that word for something specific, and we speak MCP, so a "resource" in
5
+ // our docs would be genuinely ambiguous. Not "entities" or "assets" either;
6
+ // both are ORM words for things that are, plainly, markdown files. OKF calls
7
+ // them documents and gives every one a `type:`, and we adopted OKF — so the
8
+ // spec says *document*, the UI says the actual noun (agent, flow, skill), and
9
+ // "resource" stays reserved for MCP.
10
+ //
11
+ // That leaves one rule, which this file exists to enforce:
12
+ //
13
+ // **A document's kind is its path. Only OKF documents declare anything.**
14
+ //
15
+ // Agents, flows, evals, skills and tools carry no field naming what they are.
16
+ // The directory already says it — `agents/x/agent.md` is an agent — and every
17
+ // reader here resolves by path: listAgents scans agents/, parseFlow is handed
18
+ // files from flows/. A field restating that is derived data sitting next to
19
+ // its source, free to disagree with it, and nothing consumed it: the runtime
20
+ // never branched on it and the model never saw it, because a system prompt is
21
+ // built from the body, not the frontmatter.
22
+ //
23
+ // `type:` is different and stays. It is OKF's one required field, an open
24
+ // vocabulary saying what a piece of knowledge is *about*, and it is genuinely
25
+ // not derivable — two files side by side in knowledge/ can be a Policy and a
26
+ // Price List. It also does real work: buildIndex groups index.md by it.
27
+ //
28
+ // So the asymmetry is not an oversight. Ours was redundant; OKF's carries
29
+ // information nothing else has.
30
+ //
31
+ // Tools declare `transport:` (http | script | mcp) — how the tool reaches the
32
+ // thing it calls, which is a different question from what the file is. A
33
+ // legacy `type: http` is still read as a transport; see readTransport below.
34
+ //
35
+ // Everything downstream — creation templates, `foldrun init`, the scaffolder,
36
+ // the dashboard's New button — reads this table. Adding a kind means adding a
37
+ // row here and nothing else; that is the point.
38
+ const envName = (name) => name.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
39
+ /**
40
+ * A slug as a starting display label: `house-style` → `House style`.
41
+ *
42
+ * A placeholder, not a guess to keep — the author renames it to whatever the
43
+ * document is actually called. Its job is to make the field present, because
44
+ * an absent `title` is the case that silently falls back to the slug.
45
+ */
46
+ const titleCase = (slug) => {
47
+ const words = slug.replace(/[-_]+/g, " ").trim();
48
+ return words ? words[0].toUpperCase() + words.slice(1) : slug;
49
+ };
50
+ export const KINDS = {
51
+ agents: {
52
+ kind: "agents",
53
+ one: "agent",
54
+ docType: null,
55
+ docKey: null,
56
+ scopes: ["workspace"],
57
+ placeholder: "researcher",
58
+ hint: "Someone who does one job. Its instructions are the body of the file.",
59
+ file: (name) => `agents/${name}/agent.md`,
60
+ template: (name) => `---
61
+ name: ${name}
62
+ description: What this agent is for, in one line.
63
+ model: fast
64
+ effort: high
65
+ tools: [files]
66
+ ---
67
+
68
+ # ${name}
69
+
70
+ What you do, in plain English. Be specific about what "done" looks like —
71
+ this text is the whole of the agent's instructions.
72
+ `,
73
+ },
74
+ evals: {
75
+ kind: "evals",
76
+ one: "eval",
77
+ docType: null,
78
+ docKey: null,
79
+ scopes: ["workspace"],
80
+ placeholder: "researcher-quality",
81
+ hint: "Cases an agent must pass. Run them after every edit to its prompt.",
82
+ file: (name) => `evals/${name}.md`,
83
+ template: (name) => `---
84
+ name: ${name}
85
+ agent: agent-name
86
+ description: What this eval protects against.
87
+ ---
88
+
89
+ ## a short name for the case
90
+ task: What to ask the agent.
91
+ expect:
92
+ - contains: something the answer must mention
93
+ - judge: a sentence describing what a good answer does
94
+ `,
95
+ },
96
+ flows: {
97
+ kind: "flows",
98
+ one: "flow",
99
+ docType: null,
100
+ docKey: null,
101
+ scopes: ["workspace"],
102
+ placeholder: "publish",
103
+ hint: "An ordered list of agents. Same number means they run in parallel.",
104
+ file: (name) => `flows/${name}.md`,
105
+ template: (name, ctx) => `---
106
+ name: ${name}
107
+ description: What this flow accomplishes, end to end.
108
+ trigger: manual
109
+ ---
110
+
111
+ 1. [[${ctx?.firstAgent ?? "agent-name"}]] — what this step does
112
+ `,
113
+ },
114
+ tools: {
115
+ kind: "tools",
116
+ one: "tool",
117
+ docType: null,
118
+ docKey: null,
119
+ scopes: ["account", "workspace"],
120
+ placeholder: "uuid-service",
121
+ hint: "Something an agent can call. Pick a type, name it, and the files are written for you.",
122
+ file: (name) => `tools/${name}.md`,
123
+ template: (name) => `---
124
+ transport: http
125
+ name: ${name}
126
+ description: What this does, and when to call it.
127
+ base: https://api.example.com/v1
128
+ methods: [GET]
129
+ headers:
130
+ Authorization: Bearer \${${envName(name)}_TOKEN}
131
+ ---
132
+
133
+ Notes for whoever maintains this. Agents opt in with:
134
+
135
+ \`\`\`yaml
136
+ tools: [${name}]
137
+ \`\`\`
138
+ `,
139
+ },
140
+ // Last, and not navigable: a folder tool holds its own code, so scripts
141
+ // is material a tool carries rather than a shelf anyone picks from. The
142
+ // directory still exists and library/x.py still resolves — it just isn't
143
+ // a decision, so the sidebar has no door for it.
144
+ knowledge: {
145
+ kind: "knowledge",
146
+ one: "knowledge doc",
147
+ docType: "Reference",
148
+ // OKF's field and OKF's vocabulary — `Reference` is only the starting point.
149
+ docKey: "type",
150
+ scopes: ["account", "workspace", "agent"],
151
+ placeholder: "pricing",
152
+ hint: "Reference material, given not learned. Agents read it, never write it.",
153
+ file: (name) => `knowledge/${name}.md`,
154
+ // See the memory template: OKF's fields only.
155
+ template: (name) => `---
156
+ type: Reference
157
+ title: ${titleCase(name)}
158
+ description: Reference material agents look up. Given, not learned.
159
+ status: stable
160
+ ---
161
+
162
+ The reference content. Agents read this and never rewrite it.
163
+ `,
164
+ },
165
+ memory: {
166
+ kind: "memory",
167
+ one: "memory",
168
+ docType: "Fact",
169
+ // OKF's field and OKF's vocabulary — `Fact` is only the starting point.
170
+ docKey: "type",
171
+ scopes: ["account", "workspace", "agent"],
172
+ placeholder: "house-tone",
173
+ hint: "One durable fact, learned. Agents write here themselves.",
174
+ file: (name) => `memory/${name}.md`,
175
+ // `title` and nothing of ours. A bundle carries the format's fields, so a
176
+ // reader that has never heard of this platform loses nothing: `name` was
177
+ // ours, undefined by OKF, and an outside consumer had no reason to read
178
+ // it — it fell back to the filename and showed a slug.
179
+ template: (name) => `---
180
+ type: Fact
181
+ title: ${titleCase(name)}
182
+ description: One durable fact.
183
+ status: stable
184
+ ---
185
+
186
+ The fact.
187
+
188
+ **Why:** what made it worth remembering.
189
+ `,
190
+ },
191
+ skills: {
192
+ kind: "skills",
193
+ one: "skill",
194
+ docType: null,
195
+ docKey: null,
196
+ scopes: ["account", "workspace", "agent"],
197
+ placeholder: "plain-english",
198
+ hint: "A procedure an agent loads when it needs it. Folder, not one file.",
199
+ file: (name) => `skills/${name}/SKILL.md`,
200
+ template: (name) => `---
201
+ name: ${name}
202
+ description: What this skill does, and when an agent should use it.
203
+ ---
204
+
205
+ # ${name}
206
+
207
+ Step-by-step instructions. Bundle code in this folder's scripts/ if it needs any.
208
+ `,
209
+ },
210
+ scripts: {
211
+ kind: "scripts",
212
+ one: "script",
213
+ // Code, not a document — no frontmatter to carry either field. A tool
214
+ // file points at it.
215
+ docType: null,
216
+ docKey: null,
217
+ scopes: ["account", "workspace", "agent"],
218
+ placeholder: "slugify",
219
+ hint: "Code an agent runs through a tool. Deterministic work belongs here.",
220
+ file: (name) => `scripts/${name}.py`,
221
+ template: (name) => `#!/usr/bin/env python3
222
+ """${name}"""
223
+ import argparse
224
+
225
+ p = argparse.ArgumentParser()
226
+ p.add_argument("--input")
227
+ args = p.parse_args()
228
+
229
+ print(args.input)
230
+ `,
231
+ },
232
+ };
233
+ /**
234
+ * What a script tool can be written in.
235
+ *
236
+ * Three, not every language the runner can execute. The runner picks an
237
+ * interpreter from the file extension, so `.rb` and the rest keep working the
238
+ * moment someone renames a file — but a picker with nine entries is a quiz,
239
+ * and three covers what people actually reach for. The point of asking at all
240
+ * is that "script tool" silently meaning JavaScript was a thing you had to
241
+ * find out by reading the file it made.
242
+ */
243
+ export const SCRIPT_LANGUAGES = [
244
+ { value: "javascript", label: "JavaScript", ext: ".mjs", hint: "node — the default" },
245
+ { value: "python", label: "Python", ext: ".py", hint: "python3" },
246
+ { value: "bash", label: "Shell", ext: ".sh", hint: "bash — for wrapping a command" },
247
+ ];
248
+ /** The starting program, in each language. Every one of them does the same
249
+ * three things, because those three are the tool contract:
250
+ *
251
+ * validate its own arguments — every declared arg is OPTIONAL at the
252
+ * call site, so required means checked here
253
+ * print the result on stdout — that is what the agent reads back
254
+ * exit non-zero on failure — that is what tells the agent it failed
255
+ */
256
+ const PROGRAMS = {
257
+ javascript: `import { parseArgs } from "node:util";
258
+
259
+ // Every declared arg is optional at the call site — the model may omit any of
260
+ // them, and an empty value is never passed — so required means checked here.
261
+ const { values } = parseArgs({ options: { input: { type: "string" } } });
262
+ if (!values.input) {
263
+ console.error("need --input");
264
+ process.exit(1);
265
+ }
266
+
267
+ // One JSON object on stdout: small, structured, and the same shape every call.
268
+ console.log(JSON.stringify({ input: values.input }));
269
+ `,
270
+ python: `#!/usr/bin/env python3
271
+ import argparse, json
272
+
273
+ # Every declared arg is optional at the call site — the model may omit any of
274
+ # them, and an empty value is never passed — so required means checked here.
275
+ # argparse exits 2 with a usage line on stderr, which is exactly the signal
276
+ # the agent should get.
277
+ p = argparse.ArgumentParser()
278
+ p.add_argument("--input", required=True)
279
+ a = p.parse_args()
280
+
281
+ # One JSON object on stdout: small, structured, and the same shape every call.
282
+ print(json.dumps({"input": a.input}))
283
+ `,
284
+ bash: `#!/usr/bin/env bash
285
+ set -euo pipefail
286
+
287
+ # Declared args arrive as long flags: --input value
288
+ input=""
289
+ while [ $# -gt 0 ]; do
290
+ case "$1" in
291
+ --input) input="\${2-}"; shift 2 ;;
292
+ *) echo "unexpected argument: $1" >&2; exit 1 ;;
293
+ esac
294
+ done
295
+
296
+ # Every declared arg is optional at the call site, so required means checked
297
+ # here. A non-zero exit is what tells the agent the call failed.
298
+ [ -n "$input" ] || { echo "need --input" >&2; exit 1; }
299
+
300
+ # Whatever this prints on stdout is what the agent reads back. Keep it small
301
+ # and regular — print JSON when the result has structure.
302
+ echo "$input"
303
+ `,
304
+ };
305
+ /**
306
+ * A new tool, as the files it starts as.
307
+ *
308
+ * Three transports, two shapes. An API or an MCP server is a definition and
309
+ * nothing else, so it stays one flat file. A script tool is a definition and
310
+ * the code it runs, which cannot be separated without the tool silently
311
+ * resolving to nothing — so it starts as a folder holding both, with `run:`
312
+ * relative to itself and naming no scope.
313
+ *
314
+ * Returned as a list because "create a tool" is not always one write, and
315
+ * the callers that stamp templates should not have to know which case they
316
+ * are in.
317
+ */
318
+ export function toolStarter(name, transport, language = "javascript") {
319
+ if (transport === "script") {
320
+ // A folder, because the program is a file. Code in a fenced block gets
321
+ // no linter, no type checker, no formatter and no way to run it except
322
+ // through a run — and the agents editing these are file-shaped tools:
323
+ // they can patch one line of run.mjs and execute it, where a fence has
324
+ // to be re-emitted whole and can only be tested by shipping it. The
325
+ // single-file form still parses, for a tool small enough to read at a
326
+ // glance; it is no longer what a new one starts as.
327
+ //
328
+ // The language is asked for rather than assumed. "Script tool" used to
329
+ // mean JavaScript silently, which you found out by opening the file it
330
+ // wrote; the runner has always picked an interpreter from the extension,
331
+ // so the choice was already real and merely hidden.
332
+ const lang = SCRIPT_LANGUAGES.find((l) => l.value === language) ?? SCRIPT_LANGUAGES[0];
333
+ const program = `run${lang.ext}`;
334
+ return [
335
+ {
336
+ file: `tools/${name}/tool.md`,
337
+ content: `---
338
+ transport: script
339
+ name: ${name}
340
+ description: What this does, and when an agent should call it.
341
+ run: ${program}
342
+ args:
343
+ input: What the caller passes in
344
+ timeout: 60
345
+ ---
346
+
347
+ \`${program}\` beside this file is the tool. Arguments arrive as --flags, one
348
+ per \`args:\` entry; whatever it prints on stdout is what the agent reads back,
349
+ and a non-zero exit is what tells the agent the call failed.
350
+
351
+ \`run:\` names the file, never the scope it sits in, so this folder is correct
352
+ copied into any workspace or installed at the account.
353
+
354
+ Needs a package? Declare it here and it is installed for every agent that
355
+ grants this tool — the tool is the unit of code, so its dependencies travel
356
+ with it:
357
+
358
+ \`\`\`yaml
359
+ runtime:
360
+ packages: [requests] # pip
361
+ npm: [cheerio] # npm
362
+ \`\`\`
363
+
364
+ Agents opt in with:
365
+
366
+ \`\`\`yaml
367
+ tools: [${name}]
368
+ \`\`\`
369
+
370
+ Run it by hand while you are writing it:
371
+
372
+ \`\`\`console
373
+ ${lang.value === "python" ? "python3" : lang.value === "bash" ? "bash" : "node"} tools/${name}/${program} --input hello
374
+ \`\`\`
375
+ `,
376
+ },
377
+ { file: `tools/${name}/${program}`, content: PROGRAMS[lang.value] },
378
+ ];
379
+ }
380
+ if (transport === "mcp") {
381
+ return [
382
+ {
383
+ file: `tools/${name}.md`,
384
+ content: `---
385
+ transport: mcp
386
+ name: ${name}
387
+ description: What this server provides, and when an agent should reach for it.
388
+ command: npx
389
+ args: ["-y", "@example/mcp-server"]
390
+ env:
391
+ EXAMPLE_TOKEN: \${${envName(name)}_TOKEN}
392
+ ---
393
+
394
+ Every tool this server exposes reaches the agent that opts in with:
395
+
396
+ \`\`\`yaml
397
+ tools: [${name}]
398
+ \`\`\`
399
+ `,
400
+ },
401
+ ];
402
+ }
403
+ return [{ file: KINDS.tools.file(name), content: KINDS.tools.template(name) }];
404
+ }
405
+ export const ALL_KINDS = Object.keys(KINDS);
406
+ /** The kinds that exist at a given scope. */
407
+ export const kindsAt = (scope) => ALL_KINDS.filter((k) => KINDS[k].scopes.includes(scope));
408
+ /** The noun a kind declares, for asserting a file says what it is. */
409
+ export const docTypeOf = (kind) => KINDS[kind].docType;
410
+ /** Which frontmatter key carries that noun — `kind:` for ours, `type:` for OKF's. */
411
+ export const docKeyOf = (kind) => KINDS[kind].docKey;
412
+ const TRANSPORTS = new Set(["http", "script", "mcp"]);
413
+ export function readTransport(data) {
414
+ const explicit = typeof data.transport === "string" ? data.transport.toLowerCase() : null;
415
+ if (explicit && TRANSPORTS.has(explicit))
416
+ return explicit;
417
+ const legacy = typeof data.type === "string" ? data.type.toLowerCase() : null;
418
+ if (legacy && TRANSPORTS.has(legacy))
419
+ return legacy;
420
+ return null;
421
+ }
@@ -0,0 +1,94 @@
1
+ import { type ToolDef } from "./store.ts";
2
+ export type LibraryKind = "skills" | "scripts" | "memory" | "knowledge" | "tools";
3
+ export declare const LIBRARY_KINDS: LibraryKind[];
4
+ export declare function libraryDir(tenant: string, kind?: LibraryKind): string;
5
+ export interface LibraryEntry {
6
+ /** Path relative to this kind's directory. */
7
+ path: string;
8
+ name: string;
9
+ description: string;
10
+ /** For skills: true when the folder bundles scripts/. */
11
+ hasScripts?: boolean;
12
+ /**
13
+ * For tools: how this one connects — http, script or mcp. Surfaced so a
14
+ * list of tools reads as one vocabulary with three transports, rather
15
+ * than a page of names whose differences are invisible until you open
16
+ * them. For a script tool it also names the file it runs, which is the
17
+ * link between this shelf and the code shelf.
18
+ */
19
+ transport?: "http" | "script" | "mcp";
20
+ runs?: string;
21
+ updatedAt: string;
22
+ }
23
+ export declare function listLibrary(tenant: string, kind: LibraryKind): LibraryEntry[];
24
+ export declare function readLibraryFile(tenant: string, kind: LibraryKind, rel: string): string;
25
+ /** Is there already something here? For refusing to create over it. */
26
+ export declare function libraryFileExists(tenant: string, kind: LibraryKind, rel: string): boolean;
27
+ export declare function writeLibraryFile(tenant: string, kind: LibraryKind, rel: string, content: string, meta?: {
28
+ by?: string;
29
+ message?: string;
30
+ }): void;
31
+ export declare function deleteLibraryPath(tenant: string, kind: LibraryKind, rel: string, meta?: {
32
+ by?: string;
33
+ message?: string;
34
+ }): void;
35
+ /** Library tool definitions, keyed by name — http or script, both grantable by name in `tools:`. */
36
+ export declare function libraryTools(tenant: string): Record<string, ToolDef>;
37
+ /** Shared memory index — derived from the files, like every other scope. */
38
+ export declare function libraryMemoryIndex(tenant: string): string | null;
39
+ /**
40
+ * Account-scope creation, expressed as a path inside `library/<kind>/`.
41
+ *
42
+ * The content comes from KINDS — an account skill and a workspace skill are
43
+ * the same document in a different place, and used to be two templates that
44
+ * drifted. `file()` returns a path from the scope root (`skills/x/SKILL.md`);
45
+ * here the kind directory is already the root, so the leading segment goes.
46
+ */
47
+ export declare const LIBRARY_TEMPLATES: Record<LibraryKind, (name: string) => {
48
+ path: string;
49
+ content: string;
50
+ }>;
51
+ /**
52
+ * Who actually depends on a shared file.
53
+ *
54
+ * The account library is the one place where editing a single file changes
55
+ * behaviour in workspaces you are not looking at, and nothing in the product
56
+ * could answer "if I rotate this credential, what breaks?" The graph shows
57
+ * containment — a library tool hangs off the account node — never consumption,
58
+ * so the edge that matters was the one edge missing.
59
+ *
60
+ * Two relations, because the shared scopes work in two different ways:
61
+ *
62
+ * use an explicit grant. Only tools have one (`tools: [name]`), which
63
+ * is exactly why tools are the kind worth tracing: a credential
64
+ * reaches precisely the agents that named it, and no others.
65
+ *
66
+ * shadowed a nearer file of the same name wins. This is the quiet one — the
67
+ * account file still exists, still looks live on this page, and is
68
+ * not what runs in that workspace. Resolution is nearest-wins, so
69
+ * a name collision is a silent override rather than an error.
70
+ *
71
+ * Everything else (memory, knowledge) is indexed for every agent in scope with
72
+ * no per-agent opt-in, so "used by" is "everyone" — true, and not information.
73
+ * Those kinds report shadowing only.
74
+ */
75
+ export interface LibraryUse {
76
+ workspace: string;
77
+ /** The agent that opted in or overrides it; null when it is workspace-wide. */
78
+ agent: string | null;
79
+ relation: "use" | "shadowed";
80
+ }
81
+ export declare function libraryUsage(tenant: string, kind: LibraryKind, name: string): LibraryUse[];
82
+ /**
83
+ * Make the shelf match a tree — what a push to the library repository does.
84
+ * Written with plain fs and recorded as ONE revision at the end: going
85
+ * through writeLibraryFile per file would commit each one on top of the
86
+ * pushed commit, and the history would show a push as fifty edits.
87
+ */
88
+ export declare function syncLibraryFromTree(tenant: string, files: {
89
+ path: string;
90
+ content: string;
91
+ }[], meta?: {
92
+ by?: string;
93
+ commit?: string | null;
94
+ }): number;