@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.
- package/LICENSE +202 -0
- package/README.md +239 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +43 -0
- package/dist/src/agent-tools.d.ts +31 -0
- package/dist/src/agent-tools.js +99 -0
- package/dist/src/agents-md.d.ts +9 -0
- package/dist/src/agents-md.js +83 -0
- package/dist/src/api-tools.d.ts +38 -0
- package/dist/src/api-tools.js +313 -0
- package/dist/src/approvals.d.ts +39 -0
- package/dist/src/approvals.js +103 -0
- package/dist/src/arrange.d.ts +8 -0
- package/dist/src/arrange.js +40 -0
- package/dist/src/catalog.d.ts +108 -0
- package/dist/src/catalog.js +255 -0
- package/dist/src/completions.d.ts +61 -0
- package/dist/src/completions.js +578 -0
- package/dist/src/confine.d.ts +31 -0
- package/dist/src/confine.js +298 -0
- package/dist/src/container.d.ts +30 -0
- package/dist/src/container.js +172 -0
- package/dist/src/context-tools.d.ts +81 -0
- package/dist/src/context-tools.js +347 -0
- package/dist/src/deploy.d.ts +100 -0
- package/dist/src/deploy.js +377 -0
- package/dist/src/egress.d.ts +75 -0
- package/dist/src/egress.js +133 -0
- package/dist/src/evals.d.ts +150 -0
- package/dist/src/evals.js +651 -0
- package/dist/src/flow-lint.d.ts +16 -0
- package/dist/src/flow-lint.js +177 -0
- package/dist/src/git-hook.d.ts +1 -0
- package/dist/src/git-hook.js +70 -0
- package/dist/src/git.d.ts +37 -0
- package/dist/src/git.js +101 -0
- package/dist/src/gitrepo.d.ts +95 -0
- package/dist/src/gitrepo.js +355 -0
- package/dist/src/history.d.ts +82 -0
- package/dist/src/history.js +259 -0
- package/dist/src/kinds.d.ts +95 -0
- package/dist/src/kinds.js +421 -0
- package/dist/src/library.d.ts +94 -0
- package/dist/src/library.js +341 -0
- package/dist/src/linkable.d.ts +20 -0
- package/dist/src/linkable.js +113 -0
- package/dist/src/model-probe.d.ts +21 -0
- package/dist/src/model-probe.js +81 -0
- package/dist/src/notify.d.ts +61 -0
- package/dist/src/notify.js +252 -0
- package/dist/src/oauth-presets.d.ts +13 -0
- package/dist/src/oauth-presets.js +39 -0
- package/dist/src/okf.d.ts +220 -0
- package/dist/src/okf.js +656 -0
- package/dist/src/openapi.d.ts +66 -0
- package/dist/src/openapi.js +342 -0
- package/dist/src/paths.d.ts +33 -0
- package/dist/src/paths.js +78 -0
- package/dist/src/platform.d.ts +32 -0
- package/dist/src/platform.js +48 -0
- package/dist/src/providers.d.ts +47 -0
- package/dist/src/providers.js +114 -0
- package/dist/src/refs.d.ts +20 -0
- package/dist/src/refs.js +78 -0
- package/dist/src/run-container.d.ts +227 -0
- package/dist/src/run-container.js +760 -0
- package/dist/src/runner.d.ts +241 -0
- package/dist/src/runner.js +3215 -0
- package/dist/src/runtime.d.ts +41 -0
- package/dist/src/runtime.js +357 -0
- package/dist/src/script-tools.d.ts +62 -0
- package/dist/src/script-tools.js +296 -0
- package/dist/src/secret-files.d.ts +12 -0
- package/dist/src/secret-files.js +92 -0
- package/dist/src/secrets.d.ts +147 -0
- package/dist/src/secrets.js +626 -0
- package/dist/src/starter.d.ts +21 -0
- package/dist/src/starter.js +399 -0
- package/dist/src/step-exec.d.ts +136 -0
- package/dist/src/step-exec.js +391 -0
- package/dist/src/storage.d.ts +123 -0
- package/dist/src/storage.js +640 -0
- package/dist/src/store.d.ts +1052 -0
- package/dist/src/store.js +2378 -0
- package/dist/src/tar.d.ts +30 -0
- package/dist/src/tar.js +111 -0
- package/dist/src/tool-names.d.ts +39 -0
- package/dist/src/tool-names.js +79 -0
- package/dist/src/tool-programs.d.ts +18 -0
- package/dist/src/tool-programs.js +52 -0
- package/dist/src/tool-test.d.ts +19 -0
- package/dist/src/tool-test.js +335 -0
- package/dist/src/translator.d.ts +159 -0
- package/dist/src/translator.js +986 -0
- package/dist/src/triggers.d.ts +60 -0
- package/dist/src/triggers.js +270 -0
- package/dist/src/webhook.d.ts +47 -0
- package/dist/src/webhook.js +148 -0
- package/index.ts +44 -0
- package/package.json +64 -0
- package/src/agent-tools.ts +138 -0
- package/src/agents-md.ts +81 -0
- package/src/api-tools.ts +389 -0
- package/src/approvals.ts +129 -0
- package/src/arrange.ts +42 -0
- package/src/catalog.ts +321 -0
- package/src/completions.ts +652 -0
- package/src/confine.ts +350 -0
- package/src/container.ts +220 -0
- package/src/context-tools.ts +435 -0
- package/src/deploy.ts +458 -0
- package/src/egress.ts +176 -0
- package/src/evals.ts +812 -0
- package/src/flow-lint.ts +215 -0
- package/src/git-hook.ts +73 -0
- package/src/git.ts +117 -0
- package/src/gitrepo.ts +391 -0
- package/src/history.ts +329 -0
- package/src/kinds.ts +507 -0
- package/src/library.ts +419 -0
- package/src/linkable.ts +123 -0
- package/src/model-probe.ts +112 -0
- package/src/notify.ts +281 -0
- package/src/oauth-presets.ts +54 -0
- package/src/okf.ts +768 -0
- package/src/openapi.ts +383 -0
- package/src/paths.ts +79 -0
- package/src/platform.ts +87 -0
- package/src/providers.ts +147 -0
- package/src/refs.ts +83 -0
- package/src/run-container.ts +883 -0
- package/src/runner.ts +3446 -0
- package/src/runtime.ts +404 -0
- package/src/script-tools.ts +383 -0
- package/src/secret-files.ts +101 -0
- package/src/secrets.ts +799 -0
- package/src/starter.ts +415 -0
- package/src/step-exec.ts +498 -0
- package/src/storage.ts +771 -0
- package/src/store.ts +2985 -0
- package/src/tar.ts +124 -0
- package/src/tool-names.ts +91 -0
- package/src/tool-programs.ts +65 -0
- package/src/tool-test.ts +401 -0
- package/src/translator.ts +1029 -0
- package/src/triggers.ts +287 -0
- package/src/webhook.ts +183 -0
- package/templates/blog-desk/AGENTS.md +13 -0
- package/templates/blog-desk/agents/researcher/agent.md +11 -0
- package/templates/blog-desk/agents/writer/agent.md +8 -0
- package/templates/blog-desk/evals/writer-quality.md +10 -0
- package/templates/blog-desk/flows/publish.md +10 -0
- package/templates/blog-desk/knowledge/house-style.md +9 -0
- package/templates/blog-desk/knowledge/index.md +9 -0
- package/templates/hello/AGENTS.md +18 -0
- package/templates/hello/agents/curator/agent.md +32 -0
- package/templates/hello/agents/notetaker/agent.md +24 -0
- package/templates/hello/evals/house-style.md +16 -0
- package/templates/hello/flows/memory-review.md +7 -0
- package/templates/hello/flows/note.md +7 -0
- package/templates/hello/knowledge/house-style.md +15 -0
- package/templates/hello/knowledge/index.md +9 -0
- package/templates/hello/skills/plain-english/SKILL.md +11 -0
- package/templates/hello/state/publishing.md +7 -0
- package/templates/hello/tools/wordcount/run.py +13 -0
- 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;
|