toolfactory 0.3.1 → 0.4.2
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.test.js +11 -0
- package/dist/commands.js +26 -2
- package/dist/commands.test.js +30 -0
- package/dist/hosts/github.js +2 -3
- package/dist/hosts/openclaw.js +3 -2
- package/dist/model.d.ts +4 -0
- package/dist/model.js +16 -1
- package/dist/project/gate.js +3 -4
- package/dist/project/history.d.ts +4 -0
- package/dist/surfaces/agents.js +7 -1
- package/dist/surfaces/agents.test.js +1 -0
- package/dist/surfaces/openclaw-native.js +1 -0
- package/dist/surfaces/openclaw-native.test.js +4 -1
- package/dist/surfaces/shared.js +2 -0
- package/dist/surfaces/vscode-extension.js +1 -1
- package/dist/surfaces/web.d.ts +2 -2
- package/dist/surfaces/web.js +2 -2
- package/dist/surfaces/workflows.js +8 -1
- package/dist/surfaces/workflows.test.js +64 -1
- 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 +13 -0
- package/src/commands.test.ts +34 -0
- package/src/commands.ts +29 -2
- package/src/hosts/github.ts +2 -3
- package/src/hosts/openclaw.ts +3 -3
- package/src/model.ts +20 -1
- package/src/project/gate.ts +3 -4
- package/src/surfaces/agents.test.ts +1 -0
- package/src/surfaces/agents.ts +7 -1
- package/src/surfaces/openclaw-native.test.ts +4 -1
- package/src/surfaces/openclaw-native.ts +1 -0
- package/src/surfaces/shared.ts +1 -0
- package/src/surfaces/vscode-extension.ts +1 -1
- package/src/surfaces/web.ts +2 -2
- package/src/surfaces/workflows.test.ts +75 -1
- package/src/surfaces/workflows.ts +9 -3
- package/src/toolfactory/cli.ts +1 -1
- package/src/toolfactory/mcp.ts +1 -1
- package/web/dist/assets/index-AwHZabeT.js +10 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-D95aMjZ6.js +0 -10
package/src/project/gate.ts
CHANGED
|
@@ -307,11 +307,10 @@ export function packageSteps(project: Project, options: { system?: boolean } = {
|
|
|
307
307
|
run: [
|
|
308
308
|
`npm --prefix ${OPENCLAW_HOST_DIR} install`,
|
|
309
309
|
`npm --prefix ${OPENCLAW_HOST_DIR} run build`,
|
|
310
|
-
`v=$(node -p "require('./package.json').version")`,
|
|
311
310
|
`cp ${OPENCLAW_HOST_DIR}/package.json ${OPENCLAW_HOST_DIR}/package.json.prepack`,
|
|
312
|
-
`(
|
|
313
|
-
|
|
314
|
-
|
|
311
|
+
`(trap 'mv ${OPENCLAW_HOST_DIR}/package.json.prepack ${OPENCLAW_HOST_DIR}/package.json' EXIT; ` +
|
|
312
|
+
`(cd ${OPENCLAW_HOST_DIR} && npm pkg set 'dependencies.${npmName(project)}=${project.identity.version}') && ` +
|
|
313
|
+
`npm pack ./${OPENCLAW_HOST_DIR} --pack-destination ${RELEASE_DIR})`,
|
|
315
314
|
].join(" && "),
|
|
316
315
|
});
|
|
317
316
|
}
|
|
@@ -126,6 +126,7 @@ describe("agents", () => {
|
|
|
126
126
|
expect(setup).not.toContain("# Project setup goes here");
|
|
127
127
|
// Owner ask #11: a commit cannot leave a generated file stale, with or without CI.
|
|
128
128
|
expect(setup).toContain("npx toolfactory check >/dev/null");
|
|
129
|
+
expect(setup).toContain("if [ -f package-lock.json ]; then\n npm ci --no-audit --no-fund");
|
|
129
130
|
expect(setup).toContain("npm install --no-audit --no-fund");
|
|
130
131
|
});
|
|
131
132
|
|
package/src/surfaces/agents.ts
CHANGED
|
@@ -637,7 +637,13 @@ function setupTail(project: Project): string {
|
|
|
637
637
|
" exit 1",
|
|
638
638
|
"fi",
|
|
639
639
|
]
|
|
640
|
-
: [
|
|
640
|
+
: [
|
|
641
|
+
"if [ -f package-lock.json ]; then",
|
|
642
|
+
" npm ci --no-audit --no-fund",
|
|
643
|
+
"else",
|
|
644
|
+
" npm install --no-audit --no-fund",
|
|
645
|
+
"fi",
|
|
646
|
+
];
|
|
641
647
|
return [
|
|
642
648
|
"",
|
|
643
649
|
"# The pre-commit hook installed above converges agent config; toolfactory's drift gate goes in",
|
|
@@ -133,7 +133,10 @@ 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(
|
|
136
|
+
expect(pkg.devDependencies).toMatchObject({
|
|
137
|
+
...OPENCLAW_SCAFFOLD.devDependencies,
|
|
138
|
+
openclaw: OPENCLAW_SCAFFOLD.openclawVersion,
|
|
139
|
+
});
|
|
137
140
|
expect(pkg.openclaw).toEqual({
|
|
138
141
|
extensions: ["./dist/index.js"],
|
|
139
142
|
compat: { pluginApi: OPENCLAW_SCAFFOLD.pluginApi },
|
|
@@ -288,6 +288,7 @@ function packageJson(
|
|
|
288
288
|
dependencies: { ...OPENCLAW_SCAFFOLD.dependencies, ...core, ...extra?.dependencies },
|
|
289
289
|
devDependencies: {
|
|
290
290
|
...OPENCLAW_SCAFFOLD.devDependencies,
|
|
291
|
+
openclaw: openclawVersion(project),
|
|
291
292
|
...(kase ? { "@copilotkit/aimock": OPENCLAW_ADDITIONS.aimock } : {}),
|
|
292
293
|
...extra?.devDependencies,
|
|
293
294
|
},
|
package/src/surfaces/shared.ts
CHANGED
|
@@ -118,6 +118,7 @@ export function dataDirEnvName(project: Project): string {
|
|
|
118
118
|
* A tool with none has nothing to gate a live tier on, so no live test and no `live` CI job.
|
|
119
119
|
*/
|
|
120
120
|
export function liveCredentials(project: Project): string[] {
|
|
121
|
+
if (project.tool.tests.live?.credentials) return project.tool.tests.live.credentials;
|
|
121
122
|
const properties = configProperties(project);
|
|
122
123
|
const required = new Set(requiredConfig(project));
|
|
123
124
|
return Object.keys(properties).filter(
|
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.4.0",
|
|
157
|
+
"lucide-react": "^1.48.0",
|
|
158
158
|
react: "^19.2.8",
|
|
159
159
|
"react-dom": "^19.2.8",
|
|
160
160
|
shadcn: "^4.21.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
-
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
@@ -457,6 +457,20 @@ describe("workflows", () => {
|
|
|
457
457
|
expect(composeHermes.services.openclaw).toBeUndefined();
|
|
458
458
|
});
|
|
459
459
|
|
|
460
|
+
it("supports explicit live credentials without making runtime configuration mandatory", () => {
|
|
461
|
+
const target = project(["cli"]);
|
|
462
|
+
target.tool.config = undefined;
|
|
463
|
+
target.tool.tests.live = { credentials: ["LIVE_ACCOUNT_READY"] };
|
|
464
|
+
const ci = yamlParse(emitted(target)[".github/workflows/ci.yml"]);
|
|
465
|
+
expect(ci.jobs.live.env).toEqual({ LIVE_ACCOUNT_READY: "${{ secrets.LIVE_ACCOUNT_READY }}" });
|
|
466
|
+
expect(ci.jobs.live.environment).toBe("live-tests");
|
|
467
|
+
|
|
468
|
+
target.tool.tests.live.ci = false;
|
|
469
|
+
const localOnly = yamlParse(emitted(target)[".github/workflows/ci.yml"]);
|
|
470
|
+
expect(localOnly.jobs.live).toBeUndefined();
|
|
471
|
+
expect(localOnly.on.workflow_dispatch).toBeUndefined();
|
|
472
|
+
});
|
|
473
|
+
|
|
460
474
|
it("adds the T4 live job, and workflow_dispatch, iff a config key is required and sensitive", () => {
|
|
461
475
|
const ci = yamlParse(emitted(project(["cli"]))[".github/workflows/ci.yml"]);
|
|
462
476
|
expect(ci.on.workflow_dispatch).toEqual({});
|
|
@@ -626,6 +640,11 @@ describe("bootstrap-repo", () => {
|
|
|
626
640
|
);
|
|
627
641
|
expect(release.manual.join(" ")).toContain("NPM_TRUSTED_PUBLISHER");
|
|
628
642
|
expect(release.manual.filter((step) => step.startsWith("npm:"))).toHaveLength(1);
|
|
643
|
+
|
|
644
|
+
noCredential.tool.tests.live = { credentials: ["LOCAL_VAULT_READY"], ci: false };
|
|
645
|
+
const localOnly = bootstrapRepo(noCredential, { dryRun: true });
|
|
646
|
+
expect(localOnly.secrets).toEqual([]);
|
|
647
|
+
expect(localOnly.commands.some((command) => command.includes("live-tests"))).toBe(false);
|
|
629
648
|
});
|
|
630
649
|
});
|
|
631
650
|
|
|
@@ -662,6 +681,61 @@ describe("gate", () => {
|
|
|
662
681
|
]);
|
|
663
682
|
});
|
|
664
683
|
|
|
684
|
+
it("packs the actual core dependency and restores the host manifest after success or failure", () => {
|
|
685
|
+
const root = mkdtempSync(join(tmpdir(), "toolfactory-plugin-pack-"));
|
|
686
|
+
mkdirSync(join(root, "hosts/openclaw"), { recursive: true });
|
|
687
|
+
mkdirSync(join(root, "bin"));
|
|
688
|
+
const manifest = {
|
|
689
|
+
name: "openclaw-plugin-example",
|
|
690
|
+
dependencies: { "@example/core": "file:../.." },
|
|
691
|
+
};
|
|
692
|
+
writeFileSync(join(root, "hosts/openclaw/package.json"), JSON.stringify(manifest));
|
|
693
|
+
writeFileSync(
|
|
694
|
+
join(root, "bin/npm"),
|
|
695
|
+
`#!/usr/bin/env node
|
|
696
|
+
const fs = require('node:fs');
|
|
697
|
+
const path = require('node:path');
|
|
698
|
+
const args = process.argv.slice(2);
|
|
699
|
+
if (args[0] === 'pkg') {
|
|
700
|
+
const value = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
701
|
+
const [key, version] = args[2].slice('dependencies.'.length).split('=');
|
|
702
|
+
value.dependencies[key] = version;
|
|
703
|
+
fs.writeFileSync('package.json', JSON.stringify(value));
|
|
704
|
+
} else if (args[0] === 'pack') {
|
|
705
|
+
fs.copyFileSync('hosts/openclaw/package.json', 'packed.json');
|
|
706
|
+
process.exit(Number(process.env.PACK_EXIT || 0));
|
|
707
|
+
}
|
|
708
|
+
`,
|
|
709
|
+
{ mode: 0o755 },
|
|
710
|
+
);
|
|
711
|
+
const target = project(["npm", "openclaw-native"], {
|
|
712
|
+
identity: { name: "core", version: "1.2.3", description: "Example" },
|
|
713
|
+
});
|
|
714
|
+
// npmName is the publication mapping, not the Tool Factory package's name.
|
|
715
|
+
target.tool.npm = { scope: "@example" };
|
|
716
|
+
const command = packageSteps(target).find(
|
|
717
|
+
(step) => step.name === "OpenClaw plugin tarball",
|
|
718
|
+
)?.run;
|
|
719
|
+
if (!command) throw new Error("OpenClaw packaging step is missing");
|
|
720
|
+
for (const code of [0, 7]) {
|
|
721
|
+
const result = spawnSync("sh", ["-c", command], {
|
|
722
|
+
cwd: root,
|
|
723
|
+
env: {
|
|
724
|
+
...process.env,
|
|
725
|
+
PATH: `${join(root, "bin")}:${process.env.PATH}`,
|
|
726
|
+
PACK_EXIT: String(code),
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
expect(result.status, result.stderr.toString()).toBe(code);
|
|
730
|
+
expect(JSON.parse(readFileSync(join(root, "packed.json"), "utf8")).dependencies).toEqual({
|
|
731
|
+
"@example/core": "1.2.3",
|
|
732
|
+
});
|
|
733
|
+
expect(JSON.parse(readFileSync(join(root, "hosts/openclaw/package.json"), "utf8"))).toEqual(
|
|
734
|
+
manifest,
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
665
739
|
it("packages one asset per selected distribution surface into dist/release", () => {
|
|
666
740
|
const target = project([
|
|
667
741
|
"npm",
|
|
@@ -199,11 +199,17 @@ function actionStep(step: GateStep, matrix: boolean): Step {
|
|
|
199
199
|
* (an environment gates deployments, not the checkout an untrusted PR ships).
|
|
200
200
|
*/
|
|
201
201
|
function liveJob(project: Project): Record<string, unknown> | undefined {
|
|
202
|
+
if (project.tool.tests.live?.ci === false) return undefined;
|
|
202
203
|
if (liveCredentials(project).length === 0) return undefined;
|
|
203
204
|
const properties = configProperties(project);
|
|
204
|
-
const secrets =
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
const secrets = [
|
|
206
|
+
...new Set([
|
|
207
|
+
...Object.keys(properties).filter((key) =>
|
|
208
|
+
isSensitive(properties[key] as Record<string, unknown>),
|
|
209
|
+
),
|
|
210
|
+
...liveCredentials(project),
|
|
211
|
+
]),
|
|
212
|
+
];
|
|
207
213
|
const pmName = project.packageManager ?? "npm";
|
|
208
214
|
const pm = PACKAGE_MANAGER_COMMANDS[pmName];
|
|
209
215
|
const steps: Step[] =
|
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.
|
|
16
|
+
.version("0.4.2");
|
|
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.
|
|
36
|
+
const server = new McpServer({ name: "toolfactory", version: "0.4.2" });
|
|
37
37
|
for (const op of operations) {
|
|
38
38
|
if (!introspecting && !serves(op, "mcp")) continue;
|
|
39
39
|
server.registerTool(
|