toolfactory 0.1.2 → 0.2.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.
- package/README.md +24 -1
- package/dist/commands.d.ts +1 -1
- package/dist/distribution/native.d.ts +50 -0
- package/dist/distribution/native.js +414 -0
- package/dist/distribution/native.test.d.ts +1 -0
- package/dist/distribution/native.test.js +127 -0
- package/dist/distribution/system.d.ts +57 -0
- package/dist/distribution/system.js +637 -0
- package/dist/distribution/system.test.d.ts +1 -0
- package/dist/distribution/system.test.js +116 -0
- package/dist/e2e.test.js +7 -1
- package/dist/hosts/openclaw.js +6 -2
- package/dist/hosts/vscode.d.ts +1 -0
- package/dist/hosts/vscode.js +67 -0
- package/dist/model.d.ts +62 -2
- package/dist/model.js +9 -0
- package/dist/ops.d.ts +52 -0
- package/dist/project/apply.d.ts +1 -1
- package/dist/project/apply.js +194 -17
- package/dist/project/apply.test.js +103 -0
- package/dist/project/gate.d.ts +17 -2
- package/dist/project/gate.js +68 -3
- package/dist/project/history.d.ts +29 -1
- package/dist/project/lock.d.ts +5 -0
- package/dist/project/lock.js +9 -0
- package/dist/surfaces/agent-plugins.js +5 -4
- package/dist/surfaces/agents.js +21 -3
- package/dist/surfaces/agents.test.js +12 -0
- package/dist/surfaces/claude.js +8 -3
- package/dist/surfaces/codex.js +8 -3
- package/dist/surfaces/codex.test.js +8 -8
- package/dist/surfaces/cursor.js +2 -2
- package/dist/surfaces/distribution.test.js +3 -3
- package/dist/surfaces/dsh.js +27 -7
- package/dist/surfaces/dsh.test.js +37 -3
- package/dist/surfaces/gemini.js +2 -2
- package/dist/surfaces/gemini.test.js +4 -4
- package/dist/surfaces/hermes-native.js +38 -9
- package/dist/surfaces/hermes-native.test.js +32 -11
- package/dist/surfaces/mcp-registry.js +43 -5
- package/dist/surfaces/mcp-registry.test.js +18 -0
- package/dist/surfaces/openclaw-native.js +10 -8
- package/dist/surfaces/openclaw-native.test.js +30 -0
- package/dist/surfaces/readme.d.ts +16 -0
- package/dist/surfaces/readme.js +7 -0
- package/dist/surfaces/registry.js +6 -0
- package/dist/surfaces/vscode-extension.d.ts +14 -0
- package/dist/surfaces/vscode-extension.js +312 -0
- package/dist/surfaces/vscode-extension.test.d.ts +1 -0
- package/dist/surfaces/vscode-extension.test.js +81 -0
- package/dist/surfaces/vscode-scaffold.d.ts +43 -0
- package/dist/surfaces/vscode-scaffold.js +43 -0
- package/dist/surfaces/web.d.ts +1 -1
- package/dist/surfaces/web.js +1 -1
- package/dist/surfaces/workflows.js +49 -10
- package/dist/surfaces/workflows.test.js +3 -2
- package/dist/toolfactory/cli.js +1 -1
- package/dist/toolfactory/mcp.js +1 -1
- package/package.json +2 -1
- package/src/distribution/native.test.ts +161 -0
- package/src/distribution/native.ts +460 -0
- package/src/distribution/system.test.ts +136 -0
- package/src/distribution/system.ts +688 -0
- package/src/e2e.test.ts +7 -1
- package/src/hosts/openclaw.ts +4 -2
- package/src/hosts/vscode.ts +79 -0
- package/src/model.ts +15 -1
- package/src/project/apply.test.ts +120 -0
- package/src/project/apply.ts +257 -15
- package/src/project/gate.ts +96 -4
- package/src/project/lock.ts +12 -0
- package/src/surfaces/agent-plugins.ts +5 -4
- package/src/surfaces/agents.test.ts +16 -0
- package/src/surfaces/agents.ts +31 -3
- package/src/surfaces/claude.ts +7 -3
- package/src/surfaces/codex.test.ts +7 -7
- package/src/surfaces/codex.ts +8 -3
- package/src/surfaces/cursor.ts +2 -10
- package/src/surfaces/distribution.test.ts +2 -2
- package/src/surfaces/dsh.test.ts +38 -2
- package/src/surfaces/dsh.ts +28 -7
- package/src/surfaces/gemini.test.ts +3 -3
- package/src/surfaces/gemini.ts +1 -2
- package/src/surfaces/hermes-native.test.ts +39 -10
- package/src/surfaces/hermes-native.ts +41 -11
- package/src/surfaces/mcp-registry.test.ts +20 -0
- package/src/surfaces/mcp-registry.ts +46 -6
- package/src/surfaces/openclaw-native.test.ts +37 -0
- package/src/surfaces/openclaw-native.ts +49 -47
- package/src/surfaces/readme.ts +10 -0
- package/src/surfaces/registry.ts +6 -0
- package/src/surfaces/vscode-extension.test.ts +89 -0
- package/src/surfaces/vscode-extension.ts +345 -0
- package/src/surfaces/vscode-scaffold.ts +55 -0
- package/src/surfaces/web.ts +1 -1
- package/src/surfaces/workflows.test.ts +3 -4
- package/src/surfaces/workflows.ts +49 -10
- package/src/toolfactory/cli.ts +1 -1
- package/src/toolfactory/mcp.ts +1 -1
- package/web/dist/assets/{index-Spapkkvh.js → index-BHR6gCfm.js} +1 -1
- package/web/dist/index.html +1 -1
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/** A native VS Code extension. The author owns activation and all native contributions. */
|
|
2
|
+
import { execArgv } from "node:process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { githubOwner, projectName } from "../identity/name.js";
|
|
6
|
+
import { isPortable, type Project, type Surface } from "../model.js";
|
|
7
|
+
import {
|
|
8
|
+
configProperties,
|
|
9
|
+
dataDirEnvName,
|
|
10
|
+
envName,
|
|
11
|
+
has,
|
|
12
|
+
isSensitive,
|
|
13
|
+
npmName,
|
|
14
|
+
pypiName,
|
|
15
|
+
rootEnvName,
|
|
16
|
+
} from "./shared.js";
|
|
17
|
+
import {
|
|
18
|
+
ESBUILD,
|
|
19
|
+
ESLINT,
|
|
20
|
+
GITIGNORE,
|
|
21
|
+
IGNORE,
|
|
22
|
+
LAUNCH,
|
|
23
|
+
TASKS,
|
|
24
|
+
VSCODE_SCAFFOLD,
|
|
25
|
+
} from "./vscode-scaffold.js";
|
|
26
|
+
|
|
27
|
+
export const HOST_DIR = "hosts/vscode";
|
|
28
|
+
export const vscodeConfigSchema = z.object({
|
|
29
|
+
publisher: z
|
|
30
|
+
.string()
|
|
31
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9-]*$/)
|
|
32
|
+
.optional()
|
|
33
|
+
.describe(
|
|
34
|
+
"Your registered Visual Studio Marketplace/Open VSX publisher; repository owner is the local packaging default.",
|
|
35
|
+
),
|
|
36
|
+
marketplace: z.boolean().default(true),
|
|
37
|
+
openvsx: z.boolean().default(true),
|
|
38
|
+
});
|
|
39
|
+
export const VSCE_VERSION = "3.9.2";
|
|
40
|
+
export const OVSX_VERSION = "1.1.1";
|
|
41
|
+
|
|
42
|
+
export function extensionName(project: Project): string {
|
|
43
|
+
return project.identity.name.replaceAll(".", "-");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function publisher(project: Project): string {
|
|
47
|
+
return (
|
|
48
|
+
project.tool.vscode?.publisher ??
|
|
49
|
+
githubOwner(project.identity.repository) ??
|
|
50
|
+
extensionName(project)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function vsixName(project: Project): string {
|
|
55
|
+
return `${extensionName(project)}-${project.identity.version ?? "0.0.0"}.vsix`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function operations(project: Project) {
|
|
59
|
+
return project.operations.filter(isPortable);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function runtime(project: Project): string {
|
|
63
|
+
const name = extensionName(project);
|
|
64
|
+
const python = project.tool.binding === "python";
|
|
65
|
+
const packageName = python ? pypiName(project) : npmName(project);
|
|
66
|
+
const version = project.identity.version ?? "0.0.0";
|
|
67
|
+
const defaults = python
|
|
68
|
+
? { command: "uvx", args: ["--from", `${packageName}==${version}`, project.identity.name] }
|
|
69
|
+
: { command: "npx", args: ["--yes", `${packageName}@${version}`] };
|
|
70
|
+
const config = Object.entries(configProperties(project)).map(([key, property]) => ({
|
|
71
|
+
key,
|
|
72
|
+
env: envName(key),
|
|
73
|
+
secret: isSensitive(property),
|
|
74
|
+
}));
|
|
75
|
+
return `// Generated by toolfactory. Native extension code belongs in extension.ts.
|
|
76
|
+
import { mkdir } from 'node:fs/promises';
|
|
77
|
+
import { Client } from '@modelcontextprotocol/client';
|
|
78
|
+
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
|
|
79
|
+
import * as vscode from 'vscode';
|
|
80
|
+
|
|
81
|
+
const namespace = ${JSON.stringify(name)};
|
|
82
|
+
const defaults = ${JSON.stringify(defaults)};
|
|
83
|
+
const config: Array<{key: string; env: string; secret: boolean}> = ${JSON.stringify(config)};
|
|
84
|
+
const operations: Array<{name: string; title: string}> = ${JSON.stringify(operations(project).map((op) => ({ name: op.name, title: op.title ?? op.name })))};
|
|
85
|
+
|
|
86
|
+
function launch(context: vscode.ExtensionContext) {
|
|
87
|
+
const settings = vscode.workspace.getConfiguration(namespace);
|
|
88
|
+
const root = vscode.Uri.joinPath(context.extensionUri, '../..').fsPath;
|
|
89
|
+
const development = context.extensionMode !== vscode.ExtensionMode.Production;
|
|
90
|
+
const local = ${python ? `{ command: 'uv', args: ['run', '--quiet', '--directory', root, 'python', '-m', '${projectName.pythonPackage(project.identity.name)}.toolfactory.cli'] }` : `{ command: 'node', args: ['--import', 'tsx', root + '/src/toolfactory/cli.ts'] }`};
|
|
91
|
+
const selected = development ? local : defaults;
|
|
92
|
+
const command = settings.get<string>('runtime.command');
|
|
93
|
+
return {
|
|
94
|
+
command: command || selected.command,
|
|
95
|
+
args: command ? settings.get<string[]>('runtime.args', []) : selected.args,
|
|
96
|
+
cwd: development ? root : undefined,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function environment(context: vscode.ExtensionContext): Promise<Record<string, string>> {
|
|
101
|
+
const settings = vscode.workspace.getConfiguration(namespace);
|
|
102
|
+
const env: Record<string, string> = {};
|
|
103
|
+
for (const property of config) {
|
|
104
|
+
const value = (property.secret ? await context.secrets.get(property.env) : settings.get('config.' + property.key)) ?? process.env[property.env];
|
|
105
|
+
if (value !== undefined) env[property.env] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
106
|
+
}
|
|
107
|
+
await mkdir(context.globalStorageUri.fsPath, { recursive: true });
|
|
108
|
+
env[${JSON.stringify(dataDirEnvName(project))}] = context.globalStorageUri.fsPath;
|
|
109
|
+
const folder = vscode.workspace.workspaceFolders?.find(folder => folder.uri.scheme === 'file');
|
|
110
|
+
if (folder) env[${JSON.stringify(rootEnvName(project))}] = folder.uri.fsPath;
|
|
111
|
+
return env;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function registerGenerated(context: vscode.ExtensionContext): void {
|
|
115
|
+
const changes = new vscode.EventEmitter<void>();
|
|
116
|
+
context.subscriptions.push(changes,
|
|
117
|
+
vscode.workspace.onDidChangeConfiguration(event => { if (event.affectsConfiguration(namespace)) changes.fire(); }),
|
|
118
|
+
context.secrets.onDidChange(() => changes.fire()),
|
|
119
|
+
vscode.lm.registerMcpServerDefinitionProvider(namespace, {
|
|
120
|
+
onDidChangeMcpServerDefinitions: changes.event,
|
|
121
|
+
provideMcpServerDefinitions() {
|
|
122
|
+
const selected = launch(context);
|
|
123
|
+
const server = new vscode.McpStdioServerDefinition(
|
|
124
|
+
${JSON.stringify(projectName.display(project.identity.name))}, selected.command,
|
|
125
|
+
[...selected.args, 'mcp'], {}, ${JSON.stringify(version)},
|
|
126
|
+
);
|
|
127
|
+
server.cwd = selected.cwd ? vscode.Uri.file(selected.cwd) : undefined;
|
|
128
|
+
return [server];
|
|
129
|
+
},
|
|
130
|
+
async resolveMcpServerDefinition(server) {
|
|
131
|
+
if (!vscode.workspace.isTrusted) throw new Error('Trust this workspace before starting the tool runtime.');
|
|
132
|
+
if (server instanceof vscode.McpStdioServerDefinition) server.env = { ...server.env, ...await environment(context) };
|
|
133
|
+
return server;
|
|
134
|
+
},
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
${
|
|
138
|
+
config.some((property) => property.secret)
|
|
139
|
+
? `context.subscriptions.push(vscode.commands.registerCommand(namespace + '.configureSecrets', async () => {
|
|
140
|
+
const chosen = await vscode.window.showQuickPick(config.filter(property => property.secret).map(property => property.env), { title: 'Configure credentials' });
|
|
141
|
+
if (!chosen) return;
|
|
142
|
+
const value = await vscode.window.showInputBox({ title: chosen, password: true, prompt: 'Stored in VS Code SecretStorage. Leave empty to remove.' });
|
|
143
|
+
if (value === undefined) return;
|
|
144
|
+
if (value) await context.secrets.store(chosen, value); else await context.secrets.delete(chosen);
|
|
145
|
+
}));`
|
|
146
|
+
: ""
|
|
147
|
+
}
|
|
148
|
+
for (const operation of operations) {
|
|
149
|
+
context.subscriptions.push(vscode.commands.registerCommand(namespace + '.' + operation.name, async (args?: Record<string, unknown>) => {
|
|
150
|
+
if (!vscode.workspace.isTrusted) throw new Error('Trust this workspace before running tools.');
|
|
151
|
+
const interactive = args === undefined;
|
|
152
|
+
if (interactive) {
|
|
153
|
+
const input = await vscode.window.showInputBox({ title: operation.title, prompt: 'Operation arguments (JSON object)', value: '{}',
|
|
154
|
+
validateInput(value) { try { const parsed = JSON.parse(value); return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? undefined : 'Enter a JSON object'; } catch { return 'Enter a JSON object'; } },
|
|
155
|
+
});
|
|
156
|
+
if (input === undefined) return;
|
|
157
|
+
args = JSON.parse(input) as Record<string, unknown>;
|
|
158
|
+
}
|
|
159
|
+
const selected = launch(context);
|
|
160
|
+
const client = new Client({ name: namespace + '-vscode', version: ${JSON.stringify(version)} });
|
|
161
|
+
try {
|
|
162
|
+
await client.connect(new StdioClientTransport({ command: selected.command, args: [...selected.args, 'mcp'],
|
|
163
|
+
cwd: selected.cwd, env: await environment(context),
|
|
164
|
+
}));
|
|
165
|
+
const response = await client.callTool({ name: operation.name, arguments: args });
|
|
166
|
+
if (response.isError) throw new Error(JSON.stringify(response.content));
|
|
167
|
+
const result = response.structuredContent ?? response.content;
|
|
168
|
+
if (interactive) await vscode.window.showTextDocument(await vscode.workspace.openTextDocument({ language: 'json', content: JSON.stringify(result, null, 2) }));
|
|
169
|
+
return result;
|
|
170
|
+
} finally {
|
|
171
|
+
await client.close();
|
|
172
|
+
}
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function packagePatch(project: Project): Record<string, unknown> {
|
|
180
|
+
const name = extensionName(project);
|
|
181
|
+
const properties = Object.fromEntries(
|
|
182
|
+
Object.entries(configProperties(project))
|
|
183
|
+
.filter(([, property]) => !isSensitive(property))
|
|
184
|
+
.map(([key, property]) => {
|
|
185
|
+
const { "x-toolfactory": _metadata, ...schema } = property;
|
|
186
|
+
return [`${name}.config.${key}`, schema];
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
const commands = operations(project).map((op) => ({
|
|
190
|
+
command: `${name}.${op.name}`,
|
|
191
|
+
title: op.title ?? op.name,
|
|
192
|
+
category: projectName.display(project.identity.name),
|
|
193
|
+
}));
|
|
194
|
+
if (Object.values(configProperties(project)).some(isSensitive))
|
|
195
|
+
commands.push({
|
|
196
|
+
command: `${name}.configureSecrets`,
|
|
197
|
+
title: "Configure credentials",
|
|
198
|
+
category: projectName.display(project.identity.name),
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
name,
|
|
202
|
+
version: project.identity.version ?? "0.0.0",
|
|
203
|
+
displayName: projectName.display(project.identity.name),
|
|
204
|
+
description: project.identity.description ?? `${project.identity.name} tools for VS Code`,
|
|
205
|
+
publisher: publisher(project),
|
|
206
|
+
license: project.identity.license ?? "UNLICENSED",
|
|
207
|
+
...(project.identity.repository
|
|
208
|
+
? { repository: { type: "git", url: project.identity.repository } }
|
|
209
|
+
: {}),
|
|
210
|
+
engines: VSCODE_SCAFFOLD.engines,
|
|
211
|
+
main: "./dist/extension.js",
|
|
212
|
+
capabilities: { untrustedWorkspaces: { supported: false } },
|
|
213
|
+
contributes: {
|
|
214
|
+
commands,
|
|
215
|
+
mcpServerDefinitionProviders: [
|
|
216
|
+
{ id: name, label: projectName.display(project.identity.name) },
|
|
217
|
+
],
|
|
218
|
+
configuration: {
|
|
219
|
+
title: projectName.display(project.identity.name),
|
|
220
|
+
properties: {
|
|
221
|
+
...properties,
|
|
222
|
+
[`${name}.runtime.command`]: {
|
|
223
|
+
type: "string",
|
|
224
|
+
scope: "machine",
|
|
225
|
+
description: "Override the tool runtime executable.",
|
|
226
|
+
},
|
|
227
|
+
[`${name}.runtime.args`]: {
|
|
228
|
+
type: "array",
|
|
229
|
+
items: { type: "string" },
|
|
230
|
+
scope: "machine",
|
|
231
|
+
description: "Arguments for runtime.command, before the mcp subcommand.",
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
scripts: { ...VSCODE_SCAFFOLD.scripts, vsix: "vsce package --out extension.vsix" },
|
|
237
|
+
dependencies: { "@modelcontextprotocol/client": "^2.0.0" },
|
|
238
|
+
devDependencies: { ...VSCODE_SCAFFOLD.devDependencies, "@vscode/vsce": `^${VSCE_VERSION}` },
|
|
239
|
+
overrides: VSCODE_SCAFFOLD.overrides,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const surface: Surface = {
|
|
244
|
+
id: "vscode-extension",
|
|
245
|
+
plan(project) {
|
|
246
|
+
const registry = project.tool.binding === "python" ? "pypi" : "npm";
|
|
247
|
+
if (!has(project, registry))
|
|
248
|
+
throw new Error(`vscode-extension requires ${registry} for its installed runtime.`);
|
|
249
|
+
const entry = `import * as vscode from 'vscode';\nimport { registerGenerated } from './generated';\n`;
|
|
250
|
+
const probe = operations(project).find((op) => project.tool.tests.examples[op.name]);
|
|
251
|
+
return [
|
|
252
|
+
{
|
|
253
|
+
kind: "merge",
|
|
254
|
+
path: `${HOST_DIR}/package.json`,
|
|
255
|
+
format: "json",
|
|
256
|
+
patch: packagePatch(project),
|
|
257
|
+
keyedArrays: {
|
|
258
|
+
"contributes.commands": "command",
|
|
259
|
+
"contributes.mcpServerDefinitionProviders": "id",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{ kind: "file", path: `${HOST_DIR}/src/generated.ts`, content: runtime(project) },
|
|
263
|
+
{
|
|
264
|
+
kind: "region",
|
|
265
|
+
path: `${HOST_DIR}/src/extension.ts`,
|
|
266
|
+
regions: [{ begin: "// tf:imports", end: "// /tf:imports", content: `\n${entry}` }],
|
|
267
|
+
template: `// tf:imports\n// /tf:imports\n\n// This is your extension entry. Use the complete VS Code API here.\nexport function activate(context: vscode.ExtensionContext) {\n registerGenerated(context);\n}\n\nexport function deactivate() {}\n`,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
kind: "merge",
|
|
271
|
+
path: `${HOST_DIR}/tsconfig.json`,
|
|
272
|
+
format: "json",
|
|
273
|
+
patch: {
|
|
274
|
+
compilerOptions: {
|
|
275
|
+
module: "Node16",
|
|
276
|
+
target: "ES2022",
|
|
277
|
+
lib: ["ES2022"],
|
|
278
|
+
types: ["node", "mocha"],
|
|
279
|
+
sourceMap: true,
|
|
280
|
+
rootDir: "src",
|
|
281
|
+
strict: true,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
...[
|
|
286
|
+
["esbuild.js", ESBUILD],
|
|
287
|
+
["eslint.config.mjs", ESLINT],
|
|
288
|
+
[".vscode/launch.json", LAUNCH],
|
|
289
|
+
[".vscode/tasks.json", TASKS],
|
|
290
|
+
[".vscodeignore", IGNORE],
|
|
291
|
+
[".gitignore", GITIGNORE],
|
|
292
|
+
].map(([path, content]) => ({
|
|
293
|
+
kind: "region" as const,
|
|
294
|
+
path: `${HOST_DIR}/${path}`,
|
|
295
|
+
regions: [
|
|
296
|
+
{
|
|
297
|
+
begin: path?.endsWith("ignore") ? "# tf:scaffold" : "// tf:scaffold",
|
|
298
|
+
end: path?.endsWith("ignore") ? "# /tf:scaffold" : "// /tf:scaffold",
|
|
299
|
+
content: "\n",
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
template: `${path?.endsWith("ignore") ? "# tf:scaffold\n# /tf:scaffold" : "// tf:scaffold\n// /tf:scaffold"}\n${content}`,
|
|
303
|
+
})),
|
|
304
|
+
{
|
|
305
|
+
kind: "file",
|
|
306
|
+
path: `${HOST_DIR}/.vscode-test.mjs`,
|
|
307
|
+
content: `import { defineConfig } from '@vscode/test-cli';\nexport default defineConfig({ files: 'out/test/**/*.test.js', launchArgs: ['--disable-workspace-trust', '--skip-welcome', '--skip-release-notes', '--no-sandbox'] });\n`,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
kind: "file",
|
|
311
|
+
path: `${HOST_DIR}/src/test/generated.test.ts`,
|
|
312
|
+
content: `import * as assert from 'node:assert/strict';\nimport * as vscode from 'vscode';\nsuite('Generated extension', () => {\n test('activates and registers the operations', async () => {\n const extension = vscode.extensions.getExtension(${JSON.stringify(`${publisher(project)}.${extensionName(project)}`)});\n assert.ok(extension);\n await extension.activate();\n const commands = await vscode.commands.getCommands(true);\n for (const command of ${JSON.stringify(operations(project).map((op) => `${extensionName(project)}.${op.name}`))}) assert.ok(commands.includes(command), command);\n ${probe ? `await vscode.commands.executeCommand(${JSON.stringify(`${extensionName(project)}.${probe.name}`)}, ${JSON.stringify(project.tool.tests.examples[probe.name])});` : ""}\n });\n});\n`,
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
kind: "file",
|
|
316
|
+
path: `${HOST_DIR}/README.md`,
|
|
317
|
+
content: `# ${projectName.display(project.identity.name)} for VS Code\n\n${project.identity.description ?? ""}\n\nInstall the VSIX with \`code --install-extension ${vsixName(project)}\`. The extension provides an MCP server and commands for each portable operation. ${registry === "npm" ? "Node.js/npm" : "uv"} must be on PATH; installed extensions use the matching published ${registry} version.\n\nDevelop with \`npm install\`, then open this directory in VS Code and press F5. Development and tests use the checkout's kernel. \`npm test\` runs a real Extension Development Host; \`npm run vsix\` creates the installable package.\n\nEdit \`src/extension.ts\` outside its import markers and add native files freely. Use all VS Code APIs: language-model tools, chat participants, views, webviews, commands, editors and language services. Add native contribution points and additional commands directly to \`package.json\`; generated command/provider entries are maintained by identity, preserving your entries. Build scripts, debugger settings and ignore rules are editable scaffold files. Use \`adopt\` only when replacing a generated integration itself.\n\nConfiguration is in VS Code Settings; credentials use SecretStorage through the Configure credentials command when declared. The runtime command/arguments can be overridden in machine settings.\n\nPublishers must be registered separately with Visual Studio Marketplace and Open VSX. Releases attach a VSIX even when no publishing credentials are configured.\n`,
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
},
|
|
321
|
+
validate(project) {
|
|
322
|
+
const cwd = `${project.root}/${HOST_DIR}`;
|
|
323
|
+
const drift = fileURLToPath(new URL("../hosts/vscode.js", import.meta.url));
|
|
324
|
+
return [
|
|
325
|
+
{
|
|
326
|
+
label: "VS Code upstream scaffold",
|
|
327
|
+
command: "node",
|
|
328
|
+
args: [...execArgv, drift, project.root],
|
|
329
|
+
},
|
|
330
|
+
{ label: "VS Code dependencies", command: "npm", args: ["install"], cwd },
|
|
331
|
+
{ label: "VS Code VSIX", command: "npm", args: ["run", "vsix"], cwd },
|
|
332
|
+
{
|
|
333
|
+
label: "VS Code Extension Development Host",
|
|
334
|
+
command: process.platform === "linux" ? "xvfb-run" : "npm",
|
|
335
|
+
args: process.platform === "linux" ? ["-a", "npm", "test"] : ["test"],
|
|
336
|
+
cwd,
|
|
337
|
+
},
|
|
338
|
+
];
|
|
339
|
+
},
|
|
340
|
+
verdict(operation) {
|
|
341
|
+
return isPortable(operation)
|
|
342
|
+
? { kind: "bridged", reason: "bridged:vscode-mcp" }
|
|
343
|
+
: { kind: "excluded", reason: "excluded:implement-in-hosts" };
|
|
344
|
+
},
|
|
345
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Captured by running generator-code@1.12.0 (TypeScript/esbuild); hosts/vscode.ts verifies it. */
|
|
2
|
+
|
|
3
|
+
export const VSCODE_GENERATOR = "1.12.0";
|
|
4
|
+
export const GITIGNORE = "out\ndist\nnode_modules\n.vscode-test/\n*.vsix\n";
|
|
5
|
+
|
|
6
|
+
export const VSCODE_SCAFFOLD = {
|
|
7
|
+
engines: {
|
|
8
|
+
vscode: "^1.136.0",
|
|
9
|
+
},
|
|
10
|
+
scripts: {
|
|
11
|
+
"vscode:prepublish": "npm run package",
|
|
12
|
+
compile: "npm run check-types && npm run lint && node esbuild.js",
|
|
13
|
+
watch: "npm-run-all -p watch:*",
|
|
14
|
+
"watch:esbuild": "node esbuild.js --watch",
|
|
15
|
+
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
|
16
|
+
package: "npm run check-types && npm run lint && node esbuild.js --production",
|
|
17
|
+
"compile-tests": "tsc -p . --outDir out",
|
|
18
|
+
"watch-tests": "tsc -p . -w --outDir out",
|
|
19
|
+
pretest: "npm run compile-tests && npm run compile && npm run lint",
|
|
20
|
+
"check-types": "tsc --noEmit",
|
|
21
|
+
lint: "eslint src",
|
|
22
|
+
test: "vscode-test",
|
|
23
|
+
},
|
|
24
|
+
devDependencies: {
|
|
25
|
+
"@types/vscode": "^1.136.0",
|
|
26
|
+
"@types/mocha": "^10.0.10",
|
|
27
|
+
"@types/node": "24.x",
|
|
28
|
+
"typescript-eslint": "^8.61.1",
|
|
29
|
+
eslint: "^10.5.0",
|
|
30
|
+
esbuild: "^0.28.1",
|
|
31
|
+
"npm-run-all": "^4.1.5",
|
|
32
|
+
typescript: "^6.0.3",
|
|
33
|
+
"@vscode/test-cli": "^0.0.15",
|
|
34
|
+
"@vscode/test-electron": "^3.0.0",
|
|
35
|
+
},
|
|
36
|
+
overrides: {
|
|
37
|
+
diff: "^8.0.4",
|
|
38
|
+
"serialize-javascript": "^7.0.6",
|
|
39
|
+
},
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export const ESBUILD =
|
|
43
|
+
"const esbuild = require(\"esbuild\");\n\nconst production = process.argv.includes('--production');\nconst watch = process.argv.includes('--watch');\n\n/**\n * @type {import('esbuild').Plugin}\n */\nconst esbuildProblemMatcherPlugin = {\n\tname: 'esbuild-problem-matcher',\n\n\tsetup(build) {\n\t\tbuild.onStart(() => {\n\t\t\tconsole.log('[watch] build started');\n\t\t});\n\t\tbuild.onEnd((result) => {\n\t\t\tresult.errors.forEach(({ text, location }) => {\n\t\t\t\tconsole.error(`\u2718 [ERROR] ${text}`);\n\t\t\t\tconsole.error(` ${location.file}:${location.line}:${location.column}:`);\n\t\t\t});\n\t\t\tconsole.log('[watch] build finished');\n\t\t});\n\t},\n};\n\nasync function main() {\n\tconst ctx = await esbuild.context({\n\t\tentryPoints: [\n\t\t\t'src/extension.ts'\n\t\t],\n\t\tbundle: true,\n\t\tformat: 'cjs',\n\t\tminify: production,\n\t\tsourcemap: !production,\n\t\tsourcesContent: false,\n\t\tplatform: 'node',\n\t\toutfile: 'dist/extension.js',\n\t\texternal: ['vscode'],\n\t\tlogLevel: 'silent',\n\t\tplugins: [\n\t\t\t/* add to the end of plugins array */\n\t\t\tesbuildProblemMatcherPlugin,\n\t\t],\n\t});\n\tif (watch) {\n\t\tawait ctx.watch();\n\t} else {\n\t\tawait ctx.rebuild();\n\t\tawait ctx.dispose();\n\t}\n}\n\nmain().catch(e => {\n\tconsole.error(e);\n\tprocess.exit(1);\n});\n";
|
|
44
|
+
|
|
45
|
+
export const ESLINT =
|
|
46
|
+
'import typescriptEslint from "typescript-eslint";\n\nexport default [{\n files: ["**/*.ts"],\n}, {\n plugins: {\n "@typescript-eslint": typescriptEslint.plugin,\n },\n\n languageOptions: {\n parser: typescriptEslint.parser,\n ecmaVersion: 2022,\n sourceType: "module",\n },\n\n rules: {\n "@typescript-eslint/naming-convention": ["warn", {\n selector: "import",\n format: ["camelCase", "PascalCase"],\n }],\n\n curly: "warn",\n eqeqeq: "warn",\n "no-throw-literal": "warn",\n semi: "warn",\n },\n}];';
|
|
47
|
+
|
|
48
|
+
export const IGNORE =
|
|
49
|
+
".vscode/**\n.vscode-test/**\nout/**\nnode_modules/**\nsrc/**\n.gitignore\n.yarnrc\nesbuild.js\nvsc-extension-quickstart.md\n**/tsconfig.json\n**/eslint.config.mjs\n**/*.map\n**/*.ts\n**/.vscode-test.*\n";
|
|
50
|
+
|
|
51
|
+
export const LAUNCH =
|
|
52
|
+
'// A launch configuration that compiles the extension and then opens it inside a new window\n// Use IntelliSense to learn about possible attributes.\n// Hover to view descriptions of existing attributes.\n// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n{\n\t"version": "0.2.0",\n\t"configurations": [\n\t\t{\n\t\t\t"name": "Run Extension",\n\t\t\t"type": "extensionHost",\n\t\t\t"request": "launch",\n\t\t\t"args": [\n\t\t\t\t"--extensionDevelopmentPath=${workspaceFolder}"\n\t\t\t],\n\t\t\t"outFiles": [\n\t\t\t\t"${workspaceFolder}/dist/**/*.js"\n\t\t\t],\n\t\t\t"preLaunchTask": "${defaultBuildTask}"\n\t\t}\n\t]\n}\n';
|
|
53
|
+
|
|
54
|
+
export const TASKS =
|
|
55
|
+
'// See https://go.microsoft.com/fwlink/?LinkId=733558\n// for the documentation about the tasks.json format\n{\n\t"version": "2.0.0",\n\t"tasks": [\n\t\t{\n "label": "watch",\n "dependsOn": [\n "npm: watch:tsc",\n "npm: watch:esbuild"\n ],\n "presentation": {\n "reveal": "never"\n },\n "group": {\n "kind": "build",\n "isDefault": true\n }\n },\n {\n "type": "npm",\n "script": "watch:esbuild",\n "group": "build",\n "problemMatcher": "$esbuild-watch",\n "isBackground": true,\n "label": "npm: watch:esbuild",\n "presentation": {\n "group": "watch",\n "reveal": "never"\n }\n },\n\t\t{\n "type": "npm",\n "script": "watch:tsc",\n "group": "build",\n "problemMatcher": "$tsc-watch",\n "isBackground": true,\n "label": "npm: watch:tsc",\n "presentation": {\n "group": "watch",\n "reveal": "never"\n }\n },\n\t\t{\n\t\t\t"type": "npm",\n\t\t\t"script": "watch-tests",\n\t\t\t"problemMatcher": "$tsc-watch",\n\t\t\t"isBackground": true,\n\t\t\t"presentation": {\n\t\t\t\t"reveal": "never",\n\t\t\t\t"group": "watchers"\n\t\t\t},\n\t\t\t"group": "build"\n\t\t},\n\t\t{\n\t\t\t"label": "tasks: watch-tests",\n\t\t\t"dependsOn": [\n\t\t\t\t"npm: watch",\n\t\t\t\t"npm: watch-tests"\n\t\t\t],\n\t\t\t"problemMatcher": []\n\t\t}\n\t]\n}\n';
|
package/src/surfaces/web.ts
CHANGED
|
@@ -154,7 +154,7 @@ export default defineConfig({
|
|
|
154
154
|
"@tailwindcss/vite": "^4.3.3",
|
|
155
155
|
"class-variance-authority": "^0.7.1",
|
|
156
156
|
cn: "^0.2.6",
|
|
157
|
-
"lucide-react": "^1.
|
|
157
|
+
"lucide-react": "^1.43.0",
|
|
158
158
|
react: "^19.2.8",
|
|
159
159
|
"react-dom": "^19.2.8",
|
|
160
160
|
shadcn: "^4.21.0",
|
|
@@ -193,12 +193,10 @@ describe("workflows", () => {
|
|
|
193
193
|
{
|
|
194
194
|
name: "Publish package distributions to PyPI",
|
|
195
195
|
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
196
|
-
with: { "packages-dir": "release-assets/pypi/" },
|
|
196
|
+
with: { "packages-dir": "release-assets/pypi/", "skip-existing": true },
|
|
197
197
|
},
|
|
198
198
|
]);
|
|
199
|
-
expect(release.jobs.release.steps.at(-1)?.with.files).toBe(
|
|
200
|
-
"release-assets/*\nrelease-assets/pypi/*",
|
|
201
|
-
);
|
|
199
|
+
expect(release.jobs.release.steps.at(-1)?.with.files).toBe("release-assets/**/*");
|
|
202
200
|
expect(packageSteps(python).find((step) => step.name === "web build")).toBeDefined();
|
|
203
201
|
expect(packageSteps(python).find((step) => step.name === "python distributions")?.run).toBe(
|
|
204
202
|
"uv build --out-dir dist/release/pypi",
|
|
@@ -648,6 +646,7 @@ describe("gate", () => {
|
|
|
648
646
|
"toolfactory validate",
|
|
649
647
|
"author checks",
|
|
650
648
|
"author tests",
|
|
649
|
+
"author integration tests",
|
|
651
650
|
"openclaw end-to-end (scripted model, no LLM key)",
|
|
652
651
|
]);
|
|
653
652
|
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { stringify as yamlStringify } from "yaml";
|
|
21
21
|
import { LIVE_TEST_COMMAND } from "../bindings/python.js";
|
|
22
|
+
import { nativePublishJobs } from "../distribution/native.js";
|
|
23
|
+
import { systemPackageJobs, systemPublishJobs } from "../distribution/system.js";
|
|
22
24
|
import { githubOwner } from "../identity/name.js";
|
|
23
25
|
import {
|
|
24
26
|
isInstructionOnly,
|
|
@@ -62,6 +64,7 @@ import {
|
|
|
62
64
|
requiredConfig,
|
|
63
65
|
} from "./shared.js";
|
|
64
66
|
import { skillPath } from "./skill.js";
|
|
67
|
+
import { OVSX_VERSION, VSCE_VERSION, vsixName } from "./vscode-extension.js";
|
|
65
68
|
|
|
66
69
|
const YAML_HEADER =
|
|
67
70
|
"# Generated by toolfactory. Do not edit; run `toolfactory build` to regenerate.\n\n";
|
|
@@ -236,6 +239,7 @@ function ciDocument(project: Project): Record<string, unknown> {
|
|
|
236
239
|
const node = typescript ? "${{ matrix.node-version }}" : "24";
|
|
237
240
|
const job = compact({
|
|
238
241
|
"runs-on": "ubuntu-latest",
|
|
242
|
+
env: { TOOLFACTORY_SYSTEM_NATIVE_JOBS: "true" },
|
|
239
243
|
strategy: typescript ? { matrix: { "node-version": ["22", "24"] } } : undefined,
|
|
240
244
|
steps: [
|
|
241
245
|
...toolchainSteps(project, node),
|
|
@@ -253,7 +257,7 @@ function ciDocument(project: Project): Record<string, unknown> {
|
|
|
253
257
|
workflow_dispatch: live ? {} : undefined,
|
|
254
258
|
}),
|
|
255
259
|
permissions: { contents: "read" },
|
|
256
|
-
jobs: compact({ test: job, live }),
|
|
260
|
+
jobs: compact({ test: job, live, ...systemPackageJobs(project, "${{ github.sha }}") }),
|
|
257
261
|
};
|
|
258
262
|
}
|
|
259
263
|
|
|
@@ -436,11 +440,14 @@ function releaseDocument(
|
|
|
436
440
|
|
|
437
441
|
const cli = toolfactoryCli(project);
|
|
438
442
|
const assert = tagVersionAssert(project);
|
|
443
|
+
const systemJobs = systemPackageJobs(project, RELEASE_SHA);
|
|
444
|
+
for (const job of Object.values(systemJobs)) job.needs = "gate";
|
|
439
445
|
const jobs: Record<string, unknown> = {
|
|
446
|
+
...systemJobs,
|
|
440
447
|
gate: compact({
|
|
441
448
|
"runs-on": "ubuntu-latest",
|
|
442
449
|
permissions: compact({ contents: "read", pages: has(project, "web") ? "read" : undefined }),
|
|
443
|
-
env: { RELEASE_TAG: TAG_NAME },
|
|
450
|
+
env: { RELEASE_TAG: TAG_NAME, TOOLFACTORY_SYSTEM_NATIVE_JOBS: "true" },
|
|
444
451
|
outputs: { sha: "${{ steps.tag.outputs.sha }}", ...presenceOutputs(rows) },
|
|
445
452
|
steps: [
|
|
446
453
|
// The whole history: the tag may exist (a re-run) or not yet (a dispatch cuts it), and
|
|
@@ -459,7 +466,7 @@ function releaseDocument(
|
|
|
459
466
|
],
|
|
460
467
|
}),
|
|
461
468
|
package: compact({
|
|
462
|
-
needs: "gate",
|
|
469
|
+
needs: needsOf(["gate", ...Object.keys(systemJobs)]),
|
|
463
470
|
"runs-on": "ubuntu-latest",
|
|
464
471
|
permissions: { contents: "read" },
|
|
465
472
|
// The Firefox self-hosted xpi packageSteps emits (gate.ts) is opt-in on this pair: present,
|
|
@@ -472,7 +479,11 @@ function releaseDocument(
|
|
|
472
479
|
: undefined,
|
|
473
480
|
steps: [
|
|
474
481
|
...toolchainSteps(project, "24", RELEASE_SHA),
|
|
475
|
-
...packageSteps(project).map((step) => actionStep(step, false)),
|
|
482
|
+
...packageSteps(project, { system: false }).map((step) => actionStep(step, false)),
|
|
483
|
+
...Object.keys(systemJobs).map((name) => ({
|
|
484
|
+
uses: "actions/download-artifact@v8",
|
|
485
|
+
with: { name, path: `${RELEASE_DIR}/system/${name.replace(/^system-/, "")}` },
|
|
486
|
+
})),
|
|
476
487
|
{
|
|
477
488
|
uses: "actions/upload-artifact@v7",
|
|
478
489
|
with: { name: RELEASE_ARTIFACT, path: `${RELEASE_DIR}/` },
|
|
@@ -480,7 +491,10 @@ function releaseDocument(
|
|
|
480
491
|
],
|
|
481
492
|
}),
|
|
482
493
|
};
|
|
483
|
-
|
|
494
|
+
Object.assign(jobs, systemPublishJobs(project, RELEASE_ARTIFACT, RELEASE_SHA));
|
|
495
|
+
const nativeJobs = nativePublishJobs(project, RELEASE_ARTIFACT, RELEASE_SHA);
|
|
496
|
+
Object.assign(jobs, nativeJobs);
|
|
497
|
+
const priorLegs: string[] = Object.keys(nativeJobs);
|
|
484
498
|
|
|
485
499
|
if (npmSelected) {
|
|
486
500
|
// npm treats a slash-containing bare operand as a Git spec; make the downloaded archive a
|
|
@@ -532,7 +546,7 @@ function releaseDocument(
|
|
|
532
546
|
{
|
|
533
547
|
name: "Publish package distributions to PyPI",
|
|
534
548
|
uses: "pypa/gh-action-pypi-publish@release/v1",
|
|
535
|
-
with: { "packages-dir": `${RELEASE_ARTIFACT}/pypi
|
|
549
|
+
with: { "packages-dir": `${RELEASE_ARTIFACT}/pypi/`, "skip-existing": true },
|
|
536
550
|
},
|
|
537
551
|
],
|
|
538
552
|
});
|
|
@@ -682,6 +696,34 @@ function releaseDocument(
|
|
|
682
696
|
}
|
|
683
697
|
}
|
|
684
698
|
|
|
699
|
+
for (const id of ["vscode-marketplace", "open-vsx"] as const) {
|
|
700
|
+
const row = rows.find((entry) => entry.id === id);
|
|
701
|
+
if (!row) continue;
|
|
702
|
+
const file = `./${RELEASE_ARTIFACT}/${vsixName(project)}`;
|
|
703
|
+
const publish =
|
|
704
|
+
id === "vscode-marketplace"
|
|
705
|
+
? `npx --yes @vscode/vsce@${VSCE_VERSION} publish --packagePath ${file} --skip-duplicate`
|
|
706
|
+
: `npx --yes ovsx@${OVSX_VERSION} publish ${file} --skip-duplicate`;
|
|
707
|
+
const job = `publish-${id}`;
|
|
708
|
+
const runtimeJob = project.tool.binding === "python" ? "publish-pypi" : "publish-npm";
|
|
709
|
+
jobs[job] = {
|
|
710
|
+
needs: ["gate", "package", runtimeJob],
|
|
711
|
+
if: `!cancelled() && !contains(needs.*.result, 'failure') && ${gated(id)}`,
|
|
712
|
+
"runs-on": "ubuntu-latest",
|
|
713
|
+
permissions: { contents: "read" },
|
|
714
|
+
env: Object.fromEntries(row.secrets.map((key) => [key, `\${{ secrets.${key} }}`])),
|
|
715
|
+
steps: [
|
|
716
|
+
{ uses: "actions/setup-node@v7", with: { "node-version": "24" } },
|
|
717
|
+
{
|
|
718
|
+
uses: "actions/download-artifact@v8",
|
|
719
|
+
with: { name: RELEASE_ARTIFACT, path: RELEASE_ARTIFACT },
|
|
720
|
+
},
|
|
721
|
+
{ name: `Publish ${id}`, run: publish },
|
|
722
|
+
],
|
|
723
|
+
};
|
|
724
|
+
priorLegs.push(job);
|
|
725
|
+
}
|
|
726
|
+
|
|
685
727
|
jobs.release = {
|
|
686
728
|
// The tag's own release, after every leg: the assets are the same ones the registries got.
|
|
687
729
|
// Skipped legs must not skip it, only failed ones; hence the explicit condition.
|
|
@@ -719,10 +761,7 @@ function releaseDocument(
|
|
|
719
761
|
// A tag the dispatch is cutting does not exist yet: the Release creates it here.
|
|
720
762
|
target_commitish: RELEASE_SHA,
|
|
721
763
|
generate_release_notes: true,
|
|
722
|
-
|
|
723
|
-
files: has(project, "pypi")
|
|
724
|
-
? `${RELEASE_ARTIFACT}/*\n${RELEASE_ARTIFACT}/pypi/*`
|
|
725
|
-
: `${RELEASE_ARTIFACT}/*`,
|
|
764
|
+
files: `${RELEASE_ARTIFACT}/**/*`,
|
|
726
765
|
fail_on_unmatched_files: true,
|
|
727
766
|
},
|
|
728
767
|
},
|
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.1
|
|
16
|
+
.version("0.2.1");
|
|
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.1
|
|
36
|
+
const server = new McpServer({ name: "toolfactory", version: "0.2.1" });
|
|
37
37
|
for (const op of operations) {
|
|
38
38
|
if (!introspecting && !serves(op, "mcp")) continue;
|
|
39
39
|
server.registerTool(
|