toolfactory 0.2.1 → 0.2.4
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/README.md +1 -1
- package/dist/bindings/typescript.js +1 -1
- package/dist/bindings/typescript.test.js +29 -1
- package/dist/commands.d.ts +6 -0
- package/dist/commands.js +13 -2
- package/dist/hosts/openclaw.d.ts +8 -0
- package/dist/hosts/openclaw.js +70 -4
- package/dist/hosts/openclaw.test.d.ts +1 -0
- package/dist/hosts/openclaw.test.js +58 -0
- package/dist/hosts/web.d.ts +1 -0
- package/dist/hosts/web.js +6 -3
- package/dist/hosts/web.test.d.ts +1 -0
- package/dist/hosts/web.test.js +8 -0
- package/dist/model.d.ts +12 -0
- package/dist/model.js +4 -0
- package/dist/ops.d.ts +26 -0
- package/dist/ops.js +25 -1
- package/dist/project/gate.js +7 -1
- package/dist/project/history.d.ts +1 -0
- package/dist/project/load.js +19 -2
- package/dist/prompt/review.d.ts +23 -0
- package/dist/prompt/review.js +103 -0
- package/dist/prompt/review.test.d.ts +1 -0
- package/dist/prompt/review.test.js +55 -0
- package/dist/prompt/standard.d.ts +26 -0
- package/dist/prompt/standard.js +68 -0
- package/dist/surfaces/openclaw-native.d.ts +11 -1
- package/dist/surfaces/openclaw-native.js +23 -3
- package/dist/surfaces/openclaw-native.test.js +11 -1
- package/dist/surfaces/web.d.ts +6 -6
- package/dist/surfaces/web.js +7 -7
- package/dist/surfaces/web.test.js +1 -1
- package/dist/surfaces/workflows.js +2 -2
- package/dist/toolfactory/cli.js +1 -1
- package/dist/toolfactory/mcp.js +1 -1
- package/package.json +1 -1
- package/src/bindings/typescript.test.ts +32 -1
- package/src/bindings/typescript.ts +1 -1
- package/src/commands.ts +14 -2
- package/src/hosts/openclaw.test.ts +62 -0
- package/src/hosts/openclaw.ts +73 -5
- package/src/hosts/web.test.ts +9 -0
- package/src/hosts/web.ts +6 -3
- package/src/model.ts +15 -0
- package/src/ops.ts +30 -1
- package/src/project/gate.ts +7 -1
- package/src/project/load.ts +23 -2
- package/src/prompt/review.test.ts +68 -0
- package/src/prompt/review.ts +148 -0
- package/src/prompt/standard.ts +93 -0
- package/src/surfaces/openclaw-native.test.ts +11 -1
- package/src/surfaces/openclaw-native.ts +27 -3
- package/src/surfaces/web.test.ts +1 -1
- package/src/surfaces/web.ts +7 -7
- package/src/surfaces/workflows.ts +2 -2
- package/src/toolfactory/cli.ts +1 -1
- package/src/toolfactory/mcp.ts +1 -1
- package/web/dist/assets/{index-BHR6gCfm.js → index-GlWisnfJ.js} +2 -2
- package/web/dist/index.html +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The codex-prompt-standard rubric, as data. The prose canon is
|
|
3
|
+
* `.agents/skills/codex-prompt-standard/SKILL.md` — it ships inside the OpenClaw plugin and
|
|
4
|
+
* syncs to every dev harness; this module is the same guidance shaped for `review-prompt` to
|
|
5
|
+
* evaluate against. `review.test.ts` pins every anchor below to the canon text so the two
|
|
6
|
+
* cannot drift apart silently.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface PromptSection {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
/** Substring of the canon SKILL.md this section is pinned to (drift guard). */
|
|
13
|
+
canonAnchor: string;
|
|
14
|
+
/** Any hit marks the section present in a draft or a reference prompt. */
|
|
15
|
+
anchors: RegExp[];
|
|
16
|
+
/** The next-iteration instruction when the section is missing. */
|
|
17
|
+
directive: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The anatomy a Codex-class prompt reliably contains, in order (canon §2). */
|
|
21
|
+
export const ANATOMY: PromptSection[] = [
|
|
22
|
+
{
|
|
23
|
+
id: "identity",
|
|
24
|
+
title: "one-sentence identity",
|
|
25
|
+
canonAnchor: "One-sentence identity",
|
|
26
|
+
anchors: [/\byou are\b/i, /\bacting (?:for|on behalf of|as)\b/i],
|
|
27
|
+
directive:
|
|
28
|
+
"Open with one sentence of identity: who the model is, where it runs, how it is expected to behave.",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "capabilities",
|
|
32
|
+
title: "capability inventory",
|
|
33
|
+
canonAnchor: "Capability inventory",
|
|
34
|
+
anchors: [/\byou can\b/i, /\bcapabilit/i, /\b(?:available|your) tools?\b/i],
|
|
35
|
+
directive: "State the capability inventory: what the agent can receive and emit.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "how-you-work",
|
|
39
|
+
title: '"How you work" sections',
|
|
40
|
+
canonAnchor: '"How you work" sections',
|
|
41
|
+
anchors: [/how you work/i, /\bpreceden/i, /\bpersisten/i, /\bautonom/i, /\btone\b/i],
|
|
42
|
+
directive:
|
|
43
|
+
'Add "how you work" sections: personality and tone, instruction precedence, the autonomy and persistence contract.',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "planning",
|
|
47
|
+
title: "planning guidance",
|
|
48
|
+
canonAnchor: "Planning guidance",
|
|
49
|
+
anchors: [/\bplan\b/i, /\bpersist until\b/i, /before (?:acting|starting|diving)/i],
|
|
50
|
+
directive:
|
|
51
|
+
"Add planning guidance with explicit high-quality vs low-quality examples for what a good plan is.",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "execution-criteria",
|
|
55
|
+
title: "execution criteria",
|
|
56
|
+
canonAnchor: "Execution criteria",
|
|
57
|
+
anchors: [/\bMUST\b/, /\bNEVER\b/, /scope creep/i, /no unrelated changes/i],
|
|
58
|
+
directive:
|
|
59
|
+
"Add execution criteria: hard MUST/NEVER lists, root-cause fixes, no scope creep, no unrelated changes.",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "validation",
|
|
63
|
+
title: "validation philosophy",
|
|
64
|
+
canonAnchor: "Validation philosophy",
|
|
65
|
+
anchors: [/\bverify\b/i, /\bvalidat/i, /before claiming done/i, /\bdone when\b/i],
|
|
66
|
+
directive:
|
|
67
|
+
"Add the validation philosophy: start specific, broaden as confidence grows, verify before claiming done.",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "final-message",
|
|
71
|
+
title: "final-message guidance",
|
|
72
|
+
canonAnchor: "Final-message guidance",
|
|
73
|
+
anchors: [/final message/i, /\boutput depth\b/i, /\bfinal reply\b/i],
|
|
74
|
+
directive:
|
|
75
|
+
"Add final-message guidance: output depth matches task size; skip formatting for trivial replies.",
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
export interface PromptConvention {
|
|
80
|
+
id: string;
|
|
81
|
+
/** Substring of the canon SKILL.md this convention is pinned to (drift guard). */
|
|
82
|
+
canonAnchor: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Convention anchors the canon must mention; the checks in review.ts implement them. */
|
|
86
|
+
export const CONVENTIONS: PromptConvention[] = [
|
|
87
|
+
{ id: "positive-imperatives", canonAnchor: "Positive imperatives" },
|
|
88
|
+
{ id: "precedence", canonAnchor: "Explicit precedence" },
|
|
89
|
+
{ id: "must-never-discipline", canonAnchor: "MUST/NEVER reserved" },
|
|
90
|
+
{ id: "examples", canonAnchor: "Good-vs-bad examples" },
|
|
91
|
+
{ id: "harness-adaptation", canonAnchor: "Adapt to the target harness" },
|
|
92
|
+
{ id: "checkable-criteria", canonAnchor: "checkable criterion" },
|
|
93
|
+
];
|
|
@@ -133,6 +133,7 @@ describe("openclaw-native", () => {
|
|
|
133
133
|
expect(files[`${HOST_DIR}/README.md`]).toContain("`shoot` — requires browser");
|
|
134
134
|
|
|
135
135
|
const pkg = JSON.parse(files[`${HOST_DIR}/package.json`] ?? "{}");
|
|
136
|
+
expect(pkg.devDependencies).toMatchObject(OPENCLAW_SCAFFOLD.devDependencies);
|
|
136
137
|
expect(pkg.openclaw).toEqual({
|
|
137
138
|
extensions: ["./dist/index.js"],
|
|
138
139
|
compat: { pluginApi: OPENCLAW_SCAFFOLD.pluginApi },
|
|
@@ -212,6 +213,10 @@ describe("openclaw-native", () => {
|
|
|
212
213
|
|
|
213
214
|
it("drives manifest, inspector and test from one `registers` declaration", () => {
|
|
214
215
|
const target = voice();
|
|
216
|
+
target.tool.openclaw = {
|
|
217
|
+
...target.tool.openclaw,
|
|
218
|
+
devDependencies: { openclaw: "2026.9.2", "@types/ws": "^8.18.0" },
|
|
219
|
+
};
|
|
215
220
|
const files = emitted(target);
|
|
216
221
|
const manifest = JSON.parse(files[`${HOST_DIR}/openclaw.plugin.json`] ?? "{}");
|
|
217
222
|
expect(manifest.contracts).toEqual({ tools: [], realtimeVoiceProviders: ["codex"] });
|
|
@@ -223,6 +228,11 @@ describe("openclaw-native", () => {
|
|
|
223
228
|
});
|
|
224
229
|
// The author's extra dependency merges after the scaffold's; the core is not imported at all.
|
|
225
230
|
expect(pkg.dependencies).toEqual({ ...OPENCLAW_SCAFFOLD.dependencies, ws: "^8.18.0" });
|
|
231
|
+
expect(pkg.devDependencies).toEqual({
|
|
232
|
+
...OPENCLAW_SCAFFOLD.devDependencies,
|
|
233
|
+
openclaw: "2026.9.2",
|
|
234
|
+
"@types/ws": "^8.18.0",
|
|
235
|
+
});
|
|
226
236
|
expect(pkg.peerDependencies.openclaw).toBe(">=2026.8.2");
|
|
227
237
|
expect(pkg.openclaw.compat.pluginApi).toBe(">=2026.8.2");
|
|
228
238
|
|
|
@@ -312,7 +322,7 @@ function hasOpenclaw(): boolean {
|
|
|
312
322
|
}
|
|
313
323
|
|
|
314
324
|
describe.skipIf(!hasOpenclaw())("openclaw scaffold", () => {
|
|
315
|
-
it("still matches `openclaw plugins init --type tool`", () => {
|
|
325
|
+
it("still matches `openclaw plugins init --type tool`", { timeout: 60_000 }, () => {
|
|
316
326
|
expect(scaffoldDrift(project())).toEqual([]);
|
|
317
327
|
// A declared `pluginApi` is an intentional override, not drift.
|
|
318
328
|
expect(scaffoldDrift(voice())).toEqual([]);
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
/** Transcribed from `openclaw plugins init --type tool`; `validate()` proves them still current. */
|
|
36
36
|
export const OPENCLAW_SCAFFOLD = {
|
|
37
37
|
/** The OpenClaw release the scaffold was taken from (`package.json openclaw.build`). */
|
|
38
|
-
openclawVersion: "2026.9.
|
|
38
|
+
openclawVersion: "2026.9.4",
|
|
39
39
|
/** `peerDependencies.openclaw` and `openclaw.compat.pluginApi`. */
|
|
40
40
|
pluginApi: ">=2026.5.17",
|
|
41
41
|
private: true,
|
|
@@ -86,6 +86,17 @@ export default defineConfig({
|
|
|
86
86
|
* survive into the projection but allows extra ones: upstream stays the source of truth for
|
|
87
87
|
* everything it does write.
|
|
88
88
|
*/
|
|
89
|
+
/**
|
|
90
|
+
* The version `openclaw plugins init` stamps into its scaffold is the CLI's own version, so
|
|
91
|
+
* the generated manifest carries the openclaw this project actually pins (author override via
|
|
92
|
+
* `openclaw.devDependencies.openclaw`), and the scaffold default only when the project rides
|
|
93
|
+
* `latest`. hosts/openclaw.ts runs the same pin in its drift probe, so the upstream/generated
|
|
94
|
+
* comparison is identical in every environment.
|
|
95
|
+
*/
|
|
96
|
+
export function openclawVersion(project: Project): string {
|
|
97
|
+
return project.tool.openclaw?.devDependencies?.openclaw ?? OPENCLAW_SCAFFOLD.openclawVersion;
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
export const OPENCLAW_ADDITIONS = {
|
|
90
101
|
/** Without it `tsc` emits a `dist/` from source that failed type-checking. */
|
|
91
102
|
compilerOptions: { noEmitOnError: true },
|
|
@@ -123,6 +134,11 @@ export function registrations(project: Project): Registration[] {
|
|
|
123
134
|
return project.tool.openclaw?.registers ?? [];
|
|
124
135
|
}
|
|
125
136
|
|
|
137
|
+
/** Whether the plugin ships `.agents/skills/` directories (`openclaw.skills` in tool.json). */
|
|
138
|
+
export function shipsSkills(project: Project): boolean {
|
|
139
|
+
return (project.tool.openclaw?.skills?.length ?? 0) > 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
126
142
|
/**
|
|
127
143
|
* The gateway route and the Control UI tab the plugin adds when the tool also has a `web`
|
|
128
144
|
* surface. Only for the in-process binding: the route hands requests to the core package's own
|
|
@@ -257,7 +273,7 @@ function packageJson(
|
|
|
257
273
|
type: "module",
|
|
258
274
|
private: OPENCLAW_SCAFFOLD.private,
|
|
259
275
|
scripts: { ...OPENCLAW_SCAFFOLD.scripts, ...e2e },
|
|
260
|
-
files: OPENCLAW_SCAFFOLD.files,
|
|
276
|
+
files: shipsSkills(project) ? [...OPENCLAW_SCAFFOLD.files, "skills"] : OPENCLAW_SCAFFOLD.files,
|
|
261
277
|
peerDependencies: { openclaw: pluginApi(project), ...extra?.peerDependencies },
|
|
262
278
|
dependencies: { ...OPENCLAW_SCAFFOLD.dependencies, ...core, ...extra?.dependencies },
|
|
263
279
|
devDependencies: {
|
|
@@ -268,7 +284,7 @@ function packageJson(
|
|
|
268
284
|
openclaw: {
|
|
269
285
|
extensions: [OPENCLAW_SCAFFOLD.entry],
|
|
270
286
|
compat: { pluginApi: pluginApi(project) },
|
|
271
|
-
build: { openclawVersion:
|
|
287
|
+
build: { openclawVersion: openclawVersion(project) },
|
|
272
288
|
},
|
|
273
289
|
pluginInspector: expectedRegistrations(project, operations).length
|
|
274
290
|
? pluginInspector(project, operations)
|
|
@@ -299,6 +315,7 @@ function pluginManifest(project: Project, operations: Operation[]): Record<strin
|
|
|
299
315
|
configSchema: configSchema(project),
|
|
300
316
|
uiHints: sensitive.length ? Object.fromEntries(sensitive) : undefined,
|
|
301
317
|
activation: activation(project),
|
|
318
|
+
skills: shipsSkills(project) ? ["./skills"] : undefined,
|
|
302
319
|
contracts: {
|
|
303
320
|
tools: operations.map((operation) => operation.name),
|
|
304
321
|
...Object.fromEntries(declared),
|
|
@@ -955,6 +972,13 @@ export const surface: Surface = {
|
|
|
955
972
|
path: `${HOST_DIR}/vitest.config.ts`,
|
|
956
973
|
content: OPENCLAW_SCAFFOLD.vitestConfig,
|
|
957
974
|
},
|
|
975
|
+
...(project.skills ?? []).flatMap((skill) =>
|
|
976
|
+
skill.files.map((file) => ({
|
|
977
|
+
kind: "file" as const,
|
|
978
|
+
path: `${HOST_DIR}/skills/${skill.name}/${file.path}`,
|
|
979
|
+
content: file.content,
|
|
980
|
+
})),
|
|
981
|
+
),
|
|
958
982
|
{
|
|
959
983
|
kind: "merge",
|
|
960
984
|
path: `${HOST_DIR}/openclaw.plugin.json`,
|
package/src/surfaces/web.test.ts
CHANGED
|
@@ -192,7 +192,7 @@ describe("web", () => {
|
|
|
192
192
|
expect(app).toContain("Authorization: `Bearer ${TOKEN}`");
|
|
193
193
|
// The Secrets panel is the developer's `.env`, and it is the kernel's `/env` that answers.
|
|
194
194
|
expect(app).toContain('new URL("env", document.baseURI).href');
|
|
195
|
-
expect(app).toContain("toolfactory secrets check");
|
|
195
|
+
expect(app).toContain("toolfactory secrets-usage check");
|
|
196
196
|
expect(app).toContain('type="password"');
|
|
197
197
|
|
|
198
198
|
// The smoke drives the real kernel over --http instead of a static server of its own.
|
package/src/surfaces/web.ts
CHANGED
|
@@ -153,8 +153,8 @@ export default defineConfig({
|
|
|
153
153
|
"@fontsource-variable/geist": "^5.3.0",
|
|
154
154
|
"@tailwindcss/vite": "^4.3.3",
|
|
155
155
|
"class-variance-authority": "^0.7.1",
|
|
156
|
-
cn: "^0.
|
|
157
|
-
"lucide-react": "^1.
|
|
156
|
+
cn: "^0.3.0",
|
|
157
|
+
"lucide-react": "^1.46.0",
|
|
158
158
|
react: "^19.2.8",
|
|
159
159
|
"react-dom": "^19.2.8",
|
|
160
160
|
shadcn: "^4.21.0",
|
|
@@ -164,11 +164,11 @@ export default defineConfig({
|
|
|
164
164
|
devDependencies: {
|
|
165
165
|
"@types/node": "^24.13.3",
|
|
166
166
|
"@types/react": "^19.2.18",
|
|
167
|
-
"@types/react-dom": "^19.2.
|
|
168
|
-
"@vitejs/plugin-react": "^6.1.
|
|
169
|
-
oxlint: "^1.
|
|
167
|
+
"@types/react-dom": "^19.2.7",
|
|
168
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
169
|
+
oxlint: "^1.81.0",
|
|
170
170
|
typescript: "~6.0.2",
|
|
171
|
-
vite: "^8.
|
|
171
|
+
vite: "^8.3.0",
|
|
172
172
|
},
|
|
173
173
|
},
|
|
174
174
|
componentsJson: {
|
|
@@ -637,7 +637,7 @@ function SecretsPanel() {
|
|
|
637
637
|
<CardTitle>Secrets</CardTitle>
|
|
638
638
|
<CardDescription>
|
|
639
639
|
Written to this checkout's gitignored <code>.env</code>, which the live tests and
|
|
640
|
-
<code> toolfactory bootstrap-repo</code> read. Run <code>toolfactory secrets check</code>{" "}
|
|
640
|
+
<code> toolfactory bootstrap-repo</code> read. Run <code>toolfactory secrets-usage check</code>{" "}
|
|
641
641
|
to ask each registry whether it accepts them.
|
|
642
642
|
</CardDescription>
|
|
643
643
|
</CardHeader>
|
|
@@ -625,7 +625,7 @@ function releaseDocument(
|
|
|
625
625
|
"`gh workflow run release.yml -f tag=vX.Y.Z` — never `gh run rerun`, which replays the",
|
|
626
626
|
"original run's secret snapshot. `release` first retracts every registry a surface dropped",
|
|
627
627
|
"since the previous tag published to (git is the ledger; `toolfactory unpublish`), and",
|
|
628
|
-
"`toolfactory secrets status` / `bootstrap-repo` cover the one-time steps that are left.",
|
|
628
|
+
"`toolfactory secrets-usage status` / `bootstrap-repo` cover the one-time steps that are left.",
|
|
629
629
|
];
|
|
630
630
|
if (clawhubSelected) {
|
|
631
631
|
jobs["publish-clawhub"] = compact({
|
|
@@ -904,7 +904,7 @@ function envExample(project: Project): string {
|
|
|
904
904
|
lines.push(
|
|
905
905
|
"",
|
|
906
906
|
"# Release credentials (repository secrets; `toolfactory bootstrap-repo` pushes them from .env,",
|
|
907
|
-
"# `toolfactory secrets status` explains each). Leave one empty to skip that registry.",
|
|
907
|
+
"# `toolfactory secrets-usage status` explains each). Leave one empty to skip that registry.",
|
|
908
908
|
);
|
|
909
909
|
for (const row of release) {
|
|
910
910
|
lines.push("", `# ${row.id}: ${row.url}`, ...row.secrets.map((name) => `${name}=`));
|
package/src/toolfactory/cli.ts
CHANGED
|
@@ -13,7 +13,7 @@ const operations = [...authored, web];
|
|
|
13
13
|
const program = new Command()
|
|
14
14
|
.name("toolfactory")
|
|
15
15
|
.description("Build an agent tool once; ship it as Agent Skills, MCP servers, Agent Plugins / Claude / Codex / Cursor bundles, OpenClaw and Hermes plugins, CLIs, and packages, with the tests and releases to match.")
|
|
16
|
-
.version("0.2.
|
|
16
|
+
.version("0.2.4");
|
|
17
17
|
|
|
18
18
|
for (const op of operations) {
|
|
19
19
|
if (!serves(op, "cli")) continue;
|
package/src/toolfactory/mcp.ts
CHANGED
|
@@ -33,7 +33,7 @@ const introspecting = Boolean(process.env.TOOLFACTORY_INTROSPECT);
|
|
|
33
33
|
|
|
34
34
|
/** Builds one server instance with every servable operation registered; called once for stdio, once per HTTP request. */
|
|
35
35
|
function createServer(): McpServer {
|
|
36
|
-
const server = new McpServer({ name: "toolfactory", version: "0.2.
|
|
36
|
+
const server = new McpServer({ name: "toolfactory", version: "0.2.4" });
|
|
37
37
|
for (const op of operations) {
|
|
38
38
|
if (!introspecting && !serves(op, "mcp")) continue;
|
|
39
39
|
server.registerTool(
|