toolfactory 0.1.1

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 (228) hide show
  1. package/README.md +166 -0
  2. package/dist/bindings/index.d.ts +23 -0
  3. package/dist/bindings/index.js +29 -0
  4. package/dist/bindings/python.d.ts +33 -0
  5. package/dist/bindings/python.js +805 -0
  6. package/dist/bindings/python.test.d.ts +1 -0
  7. package/dist/bindings/python.test.js +316 -0
  8. package/dist/bindings/typescript.d.ts +39 -0
  9. package/dist/bindings/typescript.js +758 -0
  10. package/dist/bindings/typescript.test.d.ts +1 -0
  11. package/dist/bindings/typescript.test.js +264 -0
  12. package/dist/commands.d.ts +188 -0
  13. package/dist/commands.js +662 -0
  14. package/dist/commands.test.d.ts +1 -0
  15. package/dist/commands.test.js +215 -0
  16. package/dist/e2e.test.d.ts +1 -0
  17. package/dist/e2e.test.js +124 -0
  18. package/dist/hosts/browser.d.ts +10 -0
  19. package/dist/hosts/browser.js +94 -0
  20. package/dist/hosts/github.d.ts +66 -0
  21. package/dist/hosts/github.js +247 -0
  22. package/dist/hosts/openclaw.d.ts +4 -0
  23. package/dist/hosts/openclaw.js +94 -0
  24. package/dist/hosts/template.d.ts +15 -0
  25. package/dist/hosts/template.js +142 -0
  26. package/dist/hosts/web.d.ts +4 -0
  27. package/dist/hosts/web.js +89 -0
  28. package/dist/identity/name.d.ts +20 -0
  29. package/dist/identity/name.js +33 -0
  30. package/dist/identity/name.test.d.ts +1 -0
  31. package/dist/identity/name.test.js +37 -0
  32. package/dist/identity/read.d.ts +9 -0
  33. package/dist/identity/read.js +106 -0
  34. package/dist/identity/read.test.d.ts +1 -0
  35. package/dist/identity/read.test.js +76 -0
  36. package/dist/index.d.ts +6 -0
  37. package/dist/index.js +6 -0
  38. package/dist/introspect/index.d.ts +15 -0
  39. package/dist/introspect/index.js +63 -0
  40. package/dist/model.d.ts +236 -0
  41. package/dist/model.js +214 -0
  42. package/dist/ops.d.ts +255 -0
  43. package/dist/ops.js +297 -0
  44. package/dist/project/apply.d.ts +35 -0
  45. package/dist/project/apply.js +360 -0
  46. package/dist/project/apply.test.d.ts +1 -0
  47. package/dist/project/apply.test.js +285 -0
  48. package/dist/project/gate.d.ts +129 -0
  49. package/dist/project/gate.js +597 -0
  50. package/dist/project/history.d.ts +62 -0
  51. package/dist/project/history.js +41 -0
  52. package/dist/project/load.d.ts +21 -0
  53. package/dist/project/load.js +82 -0
  54. package/dist/project/lock.d.ts +28 -0
  55. package/dist/project/lock.js +42 -0
  56. package/dist/project/plan.d.ts +5 -0
  57. package/dist/project/plan.js +96 -0
  58. package/dist/report/coverage.d.ts +22 -0
  59. package/dist/report/coverage.js +72 -0
  60. package/dist/report/coverage.test.d.ts +1 -0
  61. package/dist/report/coverage.test.js +93 -0
  62. package/dist/surfaces/agent-plugins.d.ts +10 -0
  63. package/dist/surfaces/agent-plugins.js +36 -0
  64. package/dist/surfaces/agents.d.ts +35 -0
  65. package/dist/surfaces/agents.js +609 -0
  66. package/dist/surfaces/agents.template.d.ts +8 -0
  67. package/dist/surfaces/agents.template.js +21 -0
  68. package/dist/surfaces/agents.test.d.ts +1 -0
  69. package/dist/surfaces/agents.test.js +181 -0
  70. package/dist/surfaces/browser-extension.d.ts +101 -0
  71. package/dist/surfaces/browser-extension.js +1034 -0
  72. package/dist/surfaces/browser-extension.test.d.ts +1 -0
  73. package/dist/surfaces/browser-extension.test.js +145 -0
  74. package/dist/surfaces/claude.d.ts +2 -0
  75. package/dist/surfaces/claude.js +81 -0
  76. package/dist/surfaces/codex.d.ts +13 -0
  77. package/dist/surfaces/codex.js +101 -0
  78. package/dist/surfaces/codex.test.d.ts +1 -0
  79. package/dist/surfaces/codex.test.js +67 -0
  80. package/dist/surfaces/cursor.d.ts +2 -0
  81. package/dist/surfaces/cursor.js +50 -0
  82. package/dist/surfaces/distribution.test.d.ts +1 -0
  83. package/dist/surfaces/distribution.test.js +146 -0
  84. package/dist/surfaces/dsh.d.ts +25 -0
  85. package/dist/surfaces/dsh.js +147 -0
  86. package/dist/surfaces/dsh.test.d.ts +1 -0
  87. package/dist/surfaces/dsh.test.js +100 -0
  88. package/dist/surfaces/external.d.ts +9 -0
  89. package/dist/surfaces/external.js +4 -0
  90. package/dist/surfaces/gemini.d.ts +29 -0
  91. package/dist/surfaces/gemini.js +68 -0
  92. package/dist/surfaces/gemini.test.d.ts +1 -0
  93. package/dist/surfaces/gemini.test.js +74 -0
  94. package/dist/surfaces/hermes-native.d.ts +53 -0
  95. package/dist/surfaces/hermes-native.js +507 -0
  96. package/dist/surfaces/hermes-native.test.d.ts +1 -0
  97. package/dist/surfaces/hermes-native.test.js +156 -0
  98. package/dist/surfaces/kernel.d.ts +5 -0
  99. package/dist/surfaces/kernel.js +63 -0
  100. package/dist/surfaces/mcp-registry.d.ts +19 -0
  101. package/dist/surfaces/mcp-registry.js +160 -0
  102. package/dist/surfaces/mcp-registry.test.d.ts +1 -0
  103. package/dist/surfaces/mcp-registry.test.js +79 -0
  104. package/dist/surfaces/mcpb.d.ts +11 -0
  105. package/dist/surfaces/mcpb.js +118 -0
  106. package/dist/surfaces/mcpb.test.d.ts +1 -0
  107. package/dist/surfaces/mcpb.test.js +104 -0
  108. package/dist/surfaces/npm.d.ts +6 -0
  109. package/dist/surfaces/npm.js +72 -0
  110. package/dist/surfaces/openclaw-native.d.ts +93 -0
  111. package/dist/surfaces/openclaw-native.js +956 -0
  112. package/dist/surfaces/openclaw-native.test.d.ts +1 -0
  113. package/dist/surfaces/openclaw-native.test.js +246 -0
  114. package/dist/surfaces/pypi.d.ts +5 -0
  115. package/dist/surfaces/pypi.js +82 -0
  116. package/dist/surfaces/readme.d.ts +7 -0
  117. package/dist/surfaces/readme.js +181 -0
  118. package/dist/surfaces/registry.d.ts +6 -0
  119. package/dist/surfaces/registry.js +73 -0
  120. package/dist/surfaces/shared.d.ts +49 -0
  121. package/dist/surfaces/shared.js +112 -0
  122. package/dist/surfaces/skill.d.ts +7 -0
  123. package/dist/surfaces/skill.js +118 -0
  124. package/dist/surfaces/skill.test.d.ts +1 -0
  125. package/dist/surfaces/skill.test.js +87 -0
  126. package/dist/surfaces/web.d.ts +97 -0
  127. package/dist/surfaces/web.js +1285 -0
  128. package/dist/surfaces/web.test.d.ts +1 -0
  129. package/dist/surfaces/web.test.js +255 -0
  130. package/dist/surfaces/workflows.d.ts +2 -0
  131. package/dist/surfaces/workflows.js +809 -0
  132. package/dist/surfaces/workflows.test.d.ts +1 -0
  133. package/dist/surfaces/workflows.test.js +563 -0
  134. package/dist/toolfactory/cli.d.ts +2 -0
  135. package/dist/toolfactory/cli.js +65 -0
  136. package/dist/toolfactory/config.d.ts +3 -0
  137. package/dist/toolfactory/config.js +27 -0
  138. package/dist/toolfactory/mcp.d.ts +37 -0
  139. package/dist/toolfactory/mcp.js +269 -0
  140. package/dist/toolfactory/types.d.ts +31 -0
  141. package/dist/toolfactory/types.js +10 -0
  142. package/dist/toolfactory/web.d.ts +4 -0
  143. package/dist/toolfactory/web.js +40 -0
  144. package/dist/validate/agent-plugins.d.ts +5 -0
  145. package/dist/validate/agent-plugins.js +46 -0
  146. package/package.json +68 -0
  147. package/schemas/agent-plugins/mcp.schema.json +120 -0
  148. package/schemas/agent-plugins/plugin.schema.json +65 -0
  149. package/src/bindings/index.ts +47 -0
  150. package/src/bindings/python.test.ts +374 -0
  151. package/src/bindings/python.ts +862 -0
  152. package/src/bindings/typescript.test.ts +329 -0
  153. package/src/bindings/typescript.ts +818 -0
  154. package/src/commands.test.ts +253 -0
  155. package/src/commands.ts +886 -0
  156. package/src/e2e.test.ts +169 -0
  157. package/src/hosts/browser.ts +106 -0
  158. package/src/hosts/github.ts +321 -0
  159. package/src/hosts/openclaw.ts +110 -0
  160. package/src/hosts/template.ts +149 -0
  161. package/src/hosts/web.ts +111 -0
  162. package/src/identity/name.test.ts +43 -0
  163. package/src/identity/name.ts +39 -0
  164. package/src/identity/read.test.ts +92 -0
  165. package/src/identity/read.ts +119 -0
  166. package/src/index.ts +11 -0
  167. package/src/introspect/index.ts +72 -0
  168. package/src/model.ts +386 -0
  169. package/src/ops.ts +333 -0
  170. package/src/project/apply.test.ts +348 -0
  171. package/src/project/apply.ts +417 -0
  172. package/src/project/gate.ts +717 -0
  173. package/src/project/history.ts +46 -0
  174. package/src/project/load.ts +104 -0
  175. package/src/project/lock.ts +54 -0
  176. package/src/project/plan.ts +101 -0
  177. package/src/report/coverage.test.ts +108 -0
  178. package/src/report/coverage.ts +113 -0
  179. package/src/surfaces/agent-plugins.ts +47 -0
  180. package/src/surfaces/agents.template.ts +32 -0
  181. package/src/surfaces/agents.test.ts +206 -0
  182. package/src/surfaces/agents.ts +729 -0
  183. package/src/surfaces/browser-extension.test.ts +175 -0
  184. package/src/surfaces/browser-extension.ts +1095 -0
  185. package/src/surfaces/claude.ts +99 -0
  186. package/src/surfaces/codex.test.ts +71 -0
  187. package/src/surfaces/codex.ts +105 -0
  188. package/src/surfaces/cursor.ts +62 -0
  189. package/src/surfaces/distribution.test.ts +166 -0
  190. package/src/surfaces/dsh.test.ts +110 -0
  191. package/src/surfaces/dsh.ts +159 -0
  192. package/src/surfaces/external.ts +13 -0
  193. package/src/surfaces/gemini.test.ts +80 -0
  194. package/src/surfaces/gemini.ts +84 -0
  195. package/src/surfaces/hermes-native.test.ts +179 -0
  196. package/src/surfaces/hermes-native.ts +584 -0
  197. package/src/surfaces/kernel.ts +68 -0
  198. package/src/surfaces/mcp-registry.test.ts +99 -0
  199. package/src/surfaces/mcp-registry.ts +190 -0
  200. package/src/surfaces/mcpb.test.ts +112 -0
  201. package/src/surfaces/mcpb.ts +130 -0
  202. package/src/surfaces/npm.ts +78 -0
  203. package/src/surfaces/openclaw-native.test.ts +283 -0
  204. package/src/surfaces/openclaw-native.ts +1070 -0
  205. package/src/surfaces/pypi.ts +84 -0
  206. package/src/surfaces/readme.ts +218 -0
  207. package/src/surfaces/registry.ts +79 -0
  208. package/src/surfaces/shared.ts +140 -0
  209. package/src/surfaces/skill.test.ts +103 -0
  210. package/src/surfaces/skill.ts +136 -0
  211. package/src/surfaces/web.test.ts +303 -0
  212. package/src/surfaces/web.ts +1310 -0
  213. package/src/surfaces/workflows.test.ts +677 -0
  214. package/src/surfaces/workflows.ts +936 -0
  215. package/src/toolfactory/cli.ts +74 -0
  216. package/src/toolfactory/config.ts +33 -0
  217. package/src/toolfactory/mcp.ts +311 -0
  218. package/src/toolfactory/types.ts +52 -0
  219. package/src/toolfactory/web.ts +45 -0
  220. package/src/validate/agent-plugins.ts +53 -0
  221. package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  222. package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  223. package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  224. package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  225. package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  226. package/web/dist/assets/index-CCXhQKK3.css +2 -0
  227. package/web/dist/assets/index-D1PwZ3qS.js +10 -0
  228. package/web/dist/index.html +14 -0
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { assertValidName, githubOwner, projectName } from "./name.js";
3
+ describe("projectName", () => {
4
+ it("projects the canonical name N onto every wire-visible identifier", () => {
5
+ expect(projectName.npm("hello")).toBe("hello");
6
+ expect(projectName.npm("hello", "acme")).toBe("@acme/hello");
7
+ expect(projectName.npm("hello", "@acme")).toBe("@acme/hello");
8
+ expect(projectName.pypi("hello.tool")).toBe("hello-tool");
9
+ expect(projectName.pythonPackage("hello.tool-kit")).toBe("hello_tool_kit");
10
+ expect(projectName.cursor("hello.tool")).toBe("hello-tool");
11
+ expect(projectName.openclawPackage("hello")).toBe("openclaw-plugin-hello");
12
+ expect(projectName.mcpRegistry("hello", "acme")).toBe("io.github.acme/hello");
13
+ expect(projectName.hermesToolset("hello.tool-kit")).toBe("hello_tool_kit");
14
+ });
15
+ });
16
+ describe("githubOwner", () => {
17
+ it("reads the owner out of a github repository URL", () => {
18
+ expect(githubOwner("https://github.com/acme/hello")).toBe("acme");
19
+ expect(githubOwner("git@github.com:acme/hello.git")).toBe("acme");
20
+ expect(githubOwner(undefined)).toBeUndefined();
21
+ expect(githubOwner("https://example.com/acme/hello")).toBeUndefined();
22
+ });
23
+ });
24
+ describe("assertValidName", () => {
25
+ it("accepts a lowercase, hyphen/dot-separated name", () => {
26
+ expect(() => assertValidName("hello-tool.kit")).not.toThrow();
27
+ });
28
+ it("rejects a double hyphen", () => {
29
+ expect(() => assertValidName("Bad--Name")).toThrow(/Invalid tool name/);
30
+ });
31
+ it("rejects a leading hyphen", () => {
32
+ expect(() => assertValidName("-abc")).toThrow(/Invalid tool name/);
33
+ });
34
+ it("rejects uppercase", () => {
35
+ expect(() => assertValidName("ABC")).toThrow(/Invalid tool name/);
36
+ });
37
+ });
@@ -0,0 +1,9 @@
1
+ import type { Identity } from "../model.js";
2
+ export interface IdentityFile {
3
+ identity: Identity;
4
+ extra: Record<string, unknown>;
5
+ /** Object → text serializer matching the source format, used when projecting back. */
6
+ format: "json" | "toml" | "yaml";
7
+ }
8
+ /** Read the identity file named by `tool.json.identity`. */
9
+ export declare function readIdentityFile(path: string): IdentityFile;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Identity readers. These are shallow structural reads that pull the eight identity
3
+ * fields out of a file the ecosystem defines and keep every other top-level key as an
4
+ * opaque bag, so a rewrite never deletes what toolfactory does not understand.
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { basename } from "node:path";
8
+ import { parse as parseToml } from "smol-toml";
9
+ import { parse as parseYaml } from "yaml";
10
+ import { assertValidName } from "./name.js";
11
+ const IDENTITY_KEYS = [
12
+ "name",
13
+ "version",
14
+ "description",
15
+ "author",
16
+ "homepage",
17
+ "repository",
18
+ "license",
19
+ "keywords",
20
+ ];
21
+ function asString(value) {
22
+ return typeof value === "string" ? value : undefined;
23
+ }
24
+ function asAuthor(value) {
25
+ if (typeof value === "string") {
26
+ const match = value.match(/^([^<(]+?)\s*(?:<([^>]+)>)?\s*(?:\(([^)]+)\))?\s*$/);
27
+ return match ? { name: match[1]?.trim(), email: match[2], url: match[3] } : { name: value };
28
+ }
29
+ if (value && typeof value === "object") {
30
+ const record = value;
31
+ return {
32
+ name: asString(record.name),
33
+ email: asString(record.email),
34
+ url: asString(record.url),
35
+ };
36
+ }
37
+ return undefined;
38
+ }
39
+ function asRepository(value) {
40
+ if (typeof value === "string")
41
+ return value;
42
+ if (value && typeof value === "object")
43
+ return asString(value.url);
44
+ return undefined;
45
+ }
46
+ function split(record, keys) {
47
+ return Object.fromEntries(Object.entries(record).filter(([key]) => !keys.includes(key)));
48
+ }
49
+ function fromGeneric(record, nameKey = "name") {
50
+ const rawName = asString(record[nameKey]) ?? "";
51
+ const name = rawName.replace(/^@[^/]+\//, "");
52
+ assertValidName(name);
53
+ const identity = {
54
+ name,
55
+ version: asString(record.version),
56
+ description: asString(record.description),
57
+ author: asAuthor(record.author),
58
+ homepage: asString(record.homepage),
59
+ repository: asRepository(record.repository),
60
+ license: asString(record.license),
61
+ keywords: Array.isArray(record.keywords)
62
+ ? record.keywords.filter((k) => typeof k === "string")
63
+ : undefined,
64
+ };
65
+ const owned = [...IDENTITY_KEYS.filter((key) => key !== "name"), nameKey];
66
+ return { identity, extra: split(record, owned), format: "json" };
67
+ }
68
+ function fromPyproject(text) {
69
+ const document = parseToml(text);
70
+ const project = (document.project ?? {});
71
+ const urls = (project.urls ?? {});
72
+ const authors = Array.isArray(project.authors)
73
+ ? project.authors
74
+ : [];
75
+ const name = asString(project.name) ?? "";
76
+ assertValidName(name);
77
+ const license = project.license;
78
+ const identity = {
79
+ name,
80
+ version: asString(project.version),
81
+ description: asString(project.description),
82
+ author: authors[0] ? asAuthor(authors[0]) : undefined,
83
+ homepage: asString(urls.Homepage ?? urls.homepage),
84
+ repository: asString(urls.Repository ?? urls.repository ?? urls.Source),
85
+ license: typeof license === "string"
86
+ ? license
87
+ : asString(license?.text),
88
+ keywords: Array.isArray(project.keywords) ? project.keywords : undefined,
89
+ };
90
+ return { identity, extra: document, format: "toml" };
91
+ }
92
+ /** Read the identity file named by `tool.json.identity`. */
93
+ export function readIdentityFile(path) {
94
+ const text = readFileSync(path, "utf8");
95
+ const file = basename(path);
96
+ if (file === "pyproject.toml")
97
+ return fromPyproject(text);
98
+ if (file === "plugin.yaml" || file === "plugin.yml") {
99
+ const document = (parseYaml(text) ?? {});
100
+ return { ...fromGeneric(document), format: "yaml" };
101
+ }
102
+ const document = JSON.parse(text);
103
+ if (file === "openclaw.plugin.json")
104
+ return fromGeneric(document, "id");
105
+ return fromGeneric(document);
106
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ import { mkdtempSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { readIdentityFile } from "./read.js";
6
+ let dir;
7
+ function file(name, content) {
8
+ const path = join(dir, name);
9
+ writeFileSync(path, content);
10
+ return path;
11
+ }
12
+ describe("readIdentityFile", () => {
13
+ afterEach(() => {
14
+ dir = "";
15
+ });
16
+ it("reads plugin.json, preserving unknown top-level keys as extra", () => {
17
+ dir = mkdtempSync(join(tmpdir(), "tf-identity-"));
18
+ const path = file("plugin.json", JSON.stringify({
19
+ name: "hello",
20
+ version: "0.1.0",
21
+ description: "Say hello.",
22
+ author: { name: "Ada", email: "ada@example.com" },
23
+ license: "MIT",
24
+ extensions: { toolfactory: {} },
25
+ }));
26
+ const result = readIdentityFile(path);
27
+ expect(result.format).toBe("json");
28
+ expect(result.identity).toMatchObject({
29
+ name: "hello",
30
+ version: "0.1.0",
31
+ description: "Say hello.",
32
+ author: { name: "Ada", email: "ada@example.com" },
33
+ license: "MIT",
34
+ });
35
+ expect(result.extra).toEqual({ extensions: { toolfactory: {} } });
36
+ });
37
+ it("reads package.json, stripping the npm scope off a scoped name", () => {
38
+ dir = mkdtempSync(join(tmpdir(), "tf-identity-"));
39
+ const path = file("package.json", JSON.stringify({ name: "@acme/hello", version: "0.2.0" }));
40
+ const result = readIdentityFile(path);
41
+ expect(result.identity.name).toBe("hello");
42
+ expect(result.identity.version).toBe("0.2.0");
43
+ });
44
+ it("reads pyproject.toml", () => {
45
+ dir = mkdtempSync(join(tmpdir(), "tf-identity-"));
46
+ const path = file("pyproject.toml", [
47
+ "[project]",
48
+ 'name = "hello-py"',
49
+ 'version = "0.3.0"',
50
+ 'description = "Say hello."',
51
+ 'authors = [{ name = "Ada", email = "ada@example.com" }]',
52
+ "",
53
+ "[project.urls]",
54
+ 'Repository = "https://github.com/acme/hello-py"',
55
+ "",
56
+ ].join("\n"));
57
+ const result = readIdentityFile(path);
58
+ expect(result.format).toBe("toml");
59
+ expect(result.identity).toMatchObject({
60
+ name: "hello-py",
61
+ version: "0.3.0",
62
+ description: "Say hello.",
63
+ author: { name: "Ada", email: "ada@example.com" },
64
+ repository: "https://github.com/acme/hello-py",
65
+ });
66
+ });
67
+ it("reads openclaw.plugin.json off the `id` field", () => {
68
+ dir = mkdtempSync(join(tmpdir(), "tf-identity-"));
69
+ const path = file("openclaw.plugin.json", JSON.stringify({ id: "hello", version: "0.1.0", commands: ["run"] }));
70
+ const result = readIdentityFile(path);
71
+ expect(result.identity.name).toBe("hello");
72
+ expect(result.identity.version).toBe("0.1.0");
73
+ // Unknown keys other than the generic identity set round-trip as extra.
74
+ expect(result.extra).toEqual({ commands: ["run"] });
75
+ });
76
+ });
@@ -0,0 +1,6 @@
1
+ export * as commands from "./commands.js";
2
+ export * from "./model.js";
3
+ export { loadProject, TOOLFACTORY_VERSION } from "./project/load.js";
4
+ export { buildPlan } from "./project/plan.js";
5
+ export { computeCoverage } from "./report/coverage.js";
6
+ export { assertSurfaceRequirements, getSurface, registerSurface, selectedSurfaces, } from "./surfaces/registry.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * as commands from "./commands.js";
2
+ export * from "./model.js";
3
+ export { loadProject, TOOLFACTORY_VERSION } from "./project/load.js";
4
+ export { buildPlan } from "./project/plan.js";
5
+ export { computeCoverage } from "./report/coverage.js";
6
+ export { assertSurfaceRequirements, getSurface, registerSurface, selectedSurfaces, } from "./surfaces/registry.js";
@@ -0,0 +1,15 @@
1
+ import type { Project } from "../model.js";
2
+ import { type Ops } from "../project/load.js";
3
+ export declare function listTools(root: string, command: string, args: string[]): Promise<Ops>;
4
+ export declare function serializeOps(ops: Ops): string;
5
+ /** Spawn the kernel and compare its `tools/list` with the committed snapshot; writes nothing. */
6
+ export declare function snapshot(project: Project): Promise<{
7
+ ops: Ops;
8
+ next: string;
9
+ changed: boolean;
10
+ }>;
11
+ export declare function introspect(project: Project): Promise<{
12
+ path: string;
13
+ changed: boolean;
14
+ ops: Ops;
15
+ }>;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Operations come from the code: spawn the kernel MCP server, list its tools, and
3
+ * snapshot the result verbatim to ops.json so every generator runs offline.
4
+ */
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { Client } from "@modelcontextprotocol/client";
8
+ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
9
+ import { getBinding } from "../bindings/index.js";
10
+ import { OPS_PATH, TOOLFACTORY_VERSION, toOperation } from "../project/load.js";
11
+ export async function listTools(root, command, args) {
12
+ const transport = new StdioClientTransport({
13
+ command,
14
+ args,
15
+ cwd: root,
16
+ stderr: "pipe",
17
+ env: { ...process.env, TOOLFACTORY_INTROSPECT: "1" },
18
+ });
19
+ const stderr = [];
20
+ transport.stderr?.on("data", (chunk) => stderr.push(String(chunk)));
21
+ const client = new Client({ name: "toolfactory", version: TOOLFACTORY_VERSION });
22
+ try {
23
+ await client.connect(transport).catch((error) => {
24
+ const detail = stderr.join("").trim();
25
+ throw new Error(`Could not start the kernel MCP server (${[command, ...args].join(" ")}): ${error instanceof Error ? error.message : String(error)}${detail ? `\n${detail}` : ""}`);
26
+ });
27
+ const tools = [];
28
+ let cursor;
29
+ do {
30
+ const page = await client.listTools(cursor ? { cursor } : undefined);
31
+ tools.push(...page.tools);
32
+ cursor = page.nextCursor;
33
+ } while (cursor);
34
+ tools.sort((a, b) => a.name.localeCompare(b.name));
35
+ return { tools };
36
+ }
37
+ finally {
38
+ await client.close().catch(() => { });
39
+ }
40
+ }
41
+ export function serializeOps(ops) {
42
+ return `${JSON.stringify(ops, null, 2)}\n`;
43
+ }
44
+ /** Spawn the kernel and compare its `tools/list` with the committed snapshot; writes nothing. */
45
+ export async function snapshot(project) {
46
+ const kernel = project.tool.kernel ?? getBinding(project.tool.binding).kernelCommand(project);
47
+ const ops = await listTools(project.root, kernel.command, kernel.args);
48
+ for (const tool of ops.tools)
49
+ toOperation(tool); // rejects a capability outside the closed set
50
+ const next = serializeOps(ops);
51
+ const path = join(project.root, OPS_PATH);
52
+ const changed = !existsSync(path) || readFileSync(path, "utf8") !== next;
53
+ return { ops, next, changed };
54
+ }
55
+ export async function introspect(project) {
56
+ const { ops, next, changed } = await snapshot(project);
57
+ if (changed) {
58
+ const path = join(project.root, OPS_PATH);
59
+ mkdirSync(dirname(path), { recursive: true });
60
+ writeFileSync(path, next);
61
+ }
62
+ return { path: OPS_PATH, changed, ops };
63
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * The shared plane. Every generator and command works only with these types.
3
+ *
4
+ * Identity is authored in one file the ecosystem already defines (plugin.json,
5
+ * package.json, pyproject.toml, or a host manifest). Operations come from the
6
+ * author's code, snapshotted from the kernel MCP server's `tools/list`. The
7
+ * rest is derived and written in-tree, tracked by a lock file.
8
+ */
9
+ import { z } from "zod";
10
+ export declare const CAPABILITIES: readonly ["net", "fs", "shell", "secret", "browser", "model", "user-input", "channel"];
11
+ export type Capability = (typeof CAPABILITIES)[number];
12
+ export declare const PORTABLE_CAPABILITIES: ReadonlySet<Capability>;
13
+ export declare const SURFACE_IDS: readonly ["skill", "agent-plugins", "claude", "codex", "cursor", "mcp", "mcp-registry", "cli", "npm", "pypi", "openclaw-native", "hermes-native", "clawhub", "web", "dsh", "workflows", "agents", "readme", "gemini", "mcpb", "browser-extension"];
14
+ export type SurfaceId = (typeof SURFACE_IDS)[number];
15
+ export declare const BINDINGS: readonly ["typescript", "python"];
16
+ export type Binding = (typeof BINDINGS)[number];
17
+ /**
18
+ * What `init` selects when asked for nothing: the skills-first minimum that already serves every
19
+ * harness — a skill (installed anywhere by `npx skills add`), the Agent Plugins bundle, the
20
+ * kernel as an MCP server and a CLI, and the binding's package registry to distribute them. Every
21
+ * host-specific plugin is opt-in: add one only when that host needs what a skill and an MCP
22
+ * server cannot give it (a gateway tab, browser capability, a store listing).
23
+ */
24
+ export declare function defaultSurfaces(binding: Binding): SurfaceId[];
25
+ /** Package managers the generated workflows know how to drive; read from package.json `packageManager`. */
26
+ export declare const PACKAGE_MANAGERS: readonly ["npm", "pnpm"];
27
+ export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
28
+ /** Agent Plugins 1.0.0 name rule; the canonical name N every projection derives from. */
29
+ export declare const NAME_PATTERN: RegExp;
30
+ /** `dev.toolfactory/tool.json` — the one file toolfactory owns. */
31
+ export declare const ToolConfigSchema: z.ZodObject<{
32
+ $schema: z.ZodOptional<z.ZodString>;
33
+ schemaVersion: z.ZodLiteral<1>;
34
+ identity: z.ZodString;
35
+ binding: z.ZodEnum<{
36
+ typescript: "typescript";
37
+ python: "python";
38
+ }>;
39
+ runtime: z.ZodOptional<z.ZodEnum<{
40
+ kernel: "kernel";
41
+ none: "none";
42
+ }>>;
43
+ surfaces: z.ZodArray<z.ZodEnum<{
44
+ skill: "skill";
45
+ "agent-plugins": "agent-plugins";
46
+ claude: "claude";
47
+ codex: "codex";
48
+ cursor: "cursor";
49
+ mcp: "mcp";
50
+ "mcp-registry": "mcp-registry";
51
+ cli: "cli";
52
+ npm: "npm";
53
+ pypi: "pypi";
54
+ "openclaw-native": "openclaw-native";
55
+ "hermes-native": "hermes-native";
56
+ clawhub: "clawhub";
57
+ web: "web";
58
+ dsh: "dsh";
59
+ workflows: "workflows";
60
+ agents: "agents";
61
+ readme: "readme";
62
+ gemini: "gemini";
63
+ mcpb: "mcpb";
64
+ "browser-extension": "browser-extension";
65
+ }>>;
66
+ kernel: z.ZodOptional<z.ZodObject<{
67
+ command: z.ZodString;
68
+ args: z.ZodDefault<z.ZodArray<z.ZodString>>;
69
+ }, z.core.$strip>>;
70
+ bundle: z.ZodDefault<z.ZodObject<{
71
+ runtime: z.ZodDefault<z.ZodEnum<{
72
+ package: "package";
73
+ bundled: "bundled";
74
+ }>>;
75
+ }, z.core.$strip>>;
76
+ config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
77
+ tests: z.ZodDefault<z.ZodObject<{
78
+ examples: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
79
+ }, z.core.$strip>>;
80
+ npm: z.ZodOptional<z.ZodObject<{
81
+ scope: z.ZodOptional<z.ZodString>;
82
+ }, z.core.$strip>>;
83
+ codex: z.ZodOptional<z.ZodObject<{
84
+ interface: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
85
+ }, z.core.$strip>>;
86
+ browserExtension: z.ZodOptional<z.ZodObject<{
87
+ endpoint: z.ZodOptional<z.ZodString>;
88
+ authDomains: z.ZodOptional<z.ZodArray<z.ZodString>>;
89
+ cookieExport: z.ZodOptional<z.ZodBoolean>;
90
+ geckoId: z.ZodOptional<z.ZodString>;
91
+ icon: z.ZodOptional<z.ZodString>;
92
+ sidePanel: z.ZodOptional<z.ZodBoolean>;
93
+ safari: z.ZodOptional<z.ZodBoolean>;
94
+ }, z.core.$strip>>;
95
+ mcpb: z.ZodOptional<z.ZodObject<{
96
+ privacyPolicies: z.ZodOptional<z.ZodArray<z.ZodString>>;
97
+ }, z.core.$strip>>;
98
+ hermes: z.ZodOptional<z.ZodObject<{
99
+ toolset: z.ZodOptional<z.ZodString>;
100
+ }, z.core.$strip>>;
101
+ openclaw: z.ZodOptional<z.ZodObject<{
102
+ registers: z.ZodOptional<z.ZodArray<z.ZodObject<{
103
+ api: z.ZodString;
104
+ contract: z.ZodString;
105
+ ids: z.ZodDefault<z.ZodArray<z.ZodString>>;
106
+ }, z.core.$strip>>>;
107
+ activation: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
108
+ pluginApi: z.ZodOptional<z.ZodString>;
109
+ dependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
110
+ peerDependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
111
+ devDependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
112
+ }, z.core.$strip>>;
113
+ }, z.core.$strict>;
114
+ export type ToolConfig = z.infer<typeof ToolConfigSchema>;
115
+ /** An instruction-only plugin has author prose but no generated executable or MCP server. */
116
+ export declare function isInstructionOnly(tool: ToolConfig): boolean;
117
+ export interface Author {
118
+ name?: string;
119
+ email?: string;
120
+ url?: string;
121
+ }
122
+ /** Authored once, in the identity file; projected everywhere else. */
123
+ export interface Identity {
124
+ name: string;
125
+ version?: string;
126
+ description?: string;
127
+ author?: Author;
128
+ homepage?: string;
129
+ repository?: string;
130
+ license?: string;
131
+ keywords?: string[];
132
+ }
133
+ /** One MCP tool definition as returned by `tools/list`, plus the requirements it declares. */
134
+ export interface Operation {
135
+ name: string;
136
+ title?: string;
137
+ description?: string;
138
+ inputSchema: Record<string, unknown>;
139
+ outputSchema?: Record<string, unknown>;
140
+ annotations?: Record<string, unknown>;
141
+ requires: Capability[];
142
+ }
143
+ export type VerdictKind = "native" | "bridged" | "degraded" | "excluded";
144
+ export interface Verdict {
145
+ kind: VerdictKind;
146
+ /** Machine-readable reason, e.g. `excluded:mcp-no-host-capabilities`. Empty for `native`. */
147
+ reason?: string;
148
+ }
149
+ /** A whole generated file. */
150
+ export interface FullFile {
151
+ kind: "file";
152
+ path: string;
153
+ content: string;
154
+ mode?: number;
155
+ /**
156
+ * A build output: generated and locked like any other file, but not expected to exist.
157
+ * `check` does not report it missing (it is gitignored and rebuilt), only stale when present.
158
+ */
159
+ output?: true;
160
+ /** `content` is a link target, not file bytes: the file is a symbolic link (`.agents/skills/<N>`). */
161
+ symlink?: true;
162
+ }
163
+ export interface Region {
164
+ begin: string;
165
+ end: string;
166
+ content: string;
167
+ }
168
+ /**
169
+ * A file the author owns, in which toolfactory maintains one or more delimited regions.
170
+ * `template` is written when the file does not exist and must contain every marker.
171
+ */
172
+ export interface RegionFile {
173
+ kind: "region";
174
+ path: string;
175
+ regions: Region[];
176
+ template: string;
177
+ }
178
+ /**
179
+ * A structured file shared with the author (package.json, pyproject.toml): toolfactory
180
+ * owns exactly the keys in `patch` (deep-merged) and leaves every other key alone.
181
+ */
182
+ export interface MergeFile {
183
+ kind: "merge";
184
+ path: string;
185
+ format: "json" | "toml";
186
+ patch: Record<string, unknown>;
187
+ /** Dotted paths inside `patch` whose object toolfactory owns whole: replaced, never merged. */
188
+ owned?: string[];
189
+ }
190
+ export type PlannedFile = FullFile | RegionFile | MergeFile;
191
+ export interface Command {
192
+ /** Human label used in reports. */
193
+ label: string;
194
+ command: string;
195
+ args: string[];
196
+ cwd?: string;
197
+ env?: Record<string, string>;
198
+ }
199
+ export interface Project {
200
+ root: string;
201
+ tool: ToolConfig;
202
+ identity: Identity;
203
+ /** Unknown top-level keys of the identity file, preserved on rewrite. */
204
+ identityExtra: Record<string, unknown>;
205
+ operations: Operation[];
206
+ toolfactoryVersion: string;
207
+ /** From the root package.json `packageManager` field; npm when absent. */
208
+ packageManager?: PackageManager;
209
+ }
210
+ export interface Surface {
211
+ id: SurfaceId;
212
+ /** Pure: no I/O. Everything this surface writes for the project. */
213
+ plan(project: Project): PlannedFile[];
214
+ /** Upstream validators to run for this surface (tier 1). */
215
+ validate?(project: Project): Command[];
216
+ /** Per-operation verdict on this surface. Default: portable ⇒ native, else excluded. */
217
+ verdict?(operation: Operation, project: Project): Verdict;
218
+ /**
219
+ * Surfaces this one reads the output of. A selection that omits one is refused by name at
220
+ * plan time instead of emitting an artifact that points at a file nobody writes.
221
+ */
222
+ requires?: SurfaceId[];
223
+ }
224
+ export declare function isPortable(operation: Operation): boolean;
225
+ /**
226
+ * The other half of the sensitive-config law above: a sensitive key must not declare a default
227
+ * (it would be committed into every manifest), and **no operation may take one as an argument**.
228
+ *
229
+ * An operation's `inputSchema` is projected onto MCP `tools/call`, the CLI flags, the skill's
230
+ * operations block, the web form and every host manifest at once, so a property named like a
231
+ * secret is a paste-into-chat path on every surface simultaneously. A secret reaches the kernel
232
+ * through config — the environment a host injects from its own masked store — and never through
233
+ * an argument. Enforced wherever a project is loaded, so `introspect`, `build` and `check` all
234
+ * refuse it.
235
+ */
236
+ export declare function assertNoSensitiveArgument(config: Record<string, unknown> | undefined, operations: readonly Operation[]): void;