@devness/useai 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1112 -315
- package/package.json +17 -9
- package/README.md +0 -127
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,340 +1,1137 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/tools.ts
|
|
13
|
+
import { execSync } from "child_process";
|
|
14
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
15
|
+
import { dirname, join as join3 } from "path";
|
|
16
|
+
import { homedir as homedir2 } from "os";
|
|
17
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
18
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
19
|
+
function hasBinary(name) {
|
|
20
|
+
try {
|
|
21
|
+
execSync(`which ${name}`, { stdio: "ignore" });
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function readJsonFile(path) {
|
|
28
|
+
if (!existsSync3(path)) return {};
|
|
29
|
+
try {
|
|
30
|
+
const raw = readFileSync2(path, "utf-8").trim();
|
|
31
|
+
if (!raw) return {};
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
} catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function writeJsonFile(path, data) {
|
|
38
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
39
|
+
writeFileSync2(path, JSON.stringify(data, null, 2) + "\n");
|
|
40
|
+
}
|
|
41
|
+
function isConfiguredStandard(configPath) {
|
|
42
|
+
const config = readJsonFile(configPath);
|
|
43
|
+
const servers = config["mcpServers"];
|
|
44
|
+
return !!servers?.["useai"];
|
|
45
|
+
}
|
|
46
|
+
function installStandard(configPath) {
|
|
47
|
+
const config = readJsonFile(configPath);
|
|
48
|
+
const servers = config["mcpServers"] ?? {};
|
|
49
|
+
servers["useai"] = { ...MCP_ENTRY };
|
|
50
|
+
config["mcpServers"] = servers;
|
|
51
|
+
writeJsonFile(configPath, config);
|
|
52
|
+
}
|
|
53
|
+
function removeStandard(configPath) {
|
|
54
|
+
const config = readJsonFile(configPath);
|
|
55
|
+
const servers = config["mcpServers"];
|
|
56
|
+
if (servers) {
|
|
57
|
+
delete servers["useai"];
|
|
58
|
+
if (Object.keys(servers).length === 0) {
|
|
59
|
+
delete config["mcpServers"];
|
|
60
|
+
}
|
|
61
|
+
writeJsonFile(configPath, config);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function isConfiguredVscode(configPath) {
|
|
65
|
+
const config = readJsonFile(configPath);
|
|
66
|
+
const servers = config["servers"];
|
|
67
|
+
return !!servers?.["useai"];
|
|
68
|
+
}
|
|
69
|
+
function installVscode(configPath) {
|
|
70
|
+
const config = readJsonFile(configPath);
|
|
71
|
+
const servers = config["servers"] ?? {};
|
|
72
|
+
servers["useai"] = { command: MCP_ENTRY.command, args: MCP_ENTRY.args };
|
|
73
|
+
config["servers"] = servers;
|
|
74
|
+
writeJsonFile(configPath, config);
|
|
75
|
+
}
|
|
76
|
+
function removeVscode(configPath) {
|
|
77
|
+
const config = readJsonFile(configPath);
|
|
78
|
+
const servers = config["servers"];
|
|
79
|
+
if (servers) {
|
|
80
|
+
delete servers["useai"];
|
|
81
|
+
if (Object.keys(servers).length === 0) {
|
|
82
|
+
delete config["servers"];
|
|
83
|
+
}
|
|
84
|
+
writeJsonFile(configPath, config);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function isConfiguredZed(configPath) {
|
|
88
|
+
const config = readJsonFile(configPath);
|
|
89
|
+
const servers = config["context_servers"];
|
|
90
|
+
return !!servers?.["useai"];
|
|
91
|
+
}
|
|
92
|
+
function installZed(configPath) {
|
|
93
|
+
const config = readJsonFile(configPath);
|
|
94
|
+
const servers = config["context_servers"] ?? {};
|
|
95
|
+
servers["useai"] = {
|
|
96
|
+
command: { path: MCP_ENTRY.command, args: MCP_ENTRY.args },
|
|
97
|
+
settings: {}
|
|
98
|
+
};
|
|
99
|
+
config["context_servers"] = servers;
|
|
100
|
+
writeJsonFile(configPath, config);
|
|
101
|
+
}
|
|
102
|
+
function removeZed(configPath) {
|
|
103
|
+
const config = readJsonFile(configPath);
|
|
104
|
+
const servers = config["context_servers"];
|
|
105
|
+
if (servers) {
|
|
106
|
+
delete servers["useai"];
|
|
107
|
+
if (Object.keys(servers).length === 0) {
|
|
108
|
+
delete config["context_servers"];
|
|
109
|
+
}
|
|
110
|
+
writeJsonFile(configPath, config);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function readTomlFile(path) {
|
|
114
|
+
if (!existsSync3(path)) return {};
|
|
115
|
+
try {
|
|
116
|
+
const raw = readFileSync2(path, "utf-8").trim();
|
|
117
|
+
if (!raw) return {};
|
|
118
|
+
return parseToml(raw);
|
|
119
|
+
} catch {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function writeTomlFile(path, data) {
|
|
124
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
125
|
+
writeFileSync2(path, stringifyToml(data) + "\n");
|
|
126
|
+
}
|
|
127
|
+
function isConfiguredToml(configPath) {
|
|
128
|
+
const config = readTomlFile(configPath);
|
|
129
|
+
const servers = config["mcp_servers"];
|
|
130
|
+
return !!servers?.["useai"];
|
|
131
|
+
}
|
|
132
|
+
function installToml(configPath) {
|
|
133
|
+
const config = readTomlFile(configPath);
|
|
134
|
+
const servers = config["mcp_servers"] ?? {};
|
|
135
|
+
servers["useai"] = { command: MCP_ENTRY.command, args: MCP_ENTRY.args };
|
|
136
|
+
config["mcp_servers"] = servers;
|
|
137
|
+
writeTomlFile(configPath, config);
|
|
138
|
+
}
|
|
139
|
+
function removeToml(configPath) {
|
|
140
|
+
const config = readTomlFile(configPath);
|
|
141
|
+
const servers = config["mcp_servers"];
|
|
142
|
+
if (servers) {
|
|
143
|
+
delete servers["useai"];
|
|
144
|
+
if (Object.keys(servers).length === 0) {
|
|
145
|
+
delete config["mcp_servers"];
|
|
146
|
+
}
|
|
147
|
+
writeTomlFile(configPath, config);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function readYamlFile(path) {
|
|
151
|
+
if (!existsSync3(path)) return {};
|
|
152
|
+
try {
|
|
153
|
+
const raw = readFileSync2(path, "utf-8").trim();
|
|
154
|
+
if (!raw) return {};
|
|
155
|
+
return parseYaml(raw) ?? {};
|
|
156
|
+
} catch {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function writeYamlFile(path, data) {
|
|
161
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
162
|
+
writeFileSync2(path, stringifyYaml(data));
|
|
163
|
+
}
|
|
164
|
+
function isConfiguredYaml(configPath) {
|
|
165
|
+
const config = readYamlFile(configPath);
|
|
166
|
+
const extensions = config["extensions"];
|
|
167
|
+
return !!extensions?.["useai"];
|
|
168
|
+
}
|
|
169
|
+
function installYaml(configPath) {
|
|
170
|
+
const config = readYamlFile(configPath);
|
|
171
|
+
const extensions = config["extensions"] ?? {};
|
|
172
|
+
extensions["useai"] = {
|
|
173
|
+
name: "useai",
|
|
174
|
+
cmd: MCP_ENTRY.command,
|
|
175
|
+
args: MCP_ENTRY.args,
|
|
176
|
+
enabled: true,
|
|
177
|
+
type: "stdio"
|
|
178
|
+
};
|
|
179
|
+
config["extensions"] = extensions;
|
|
180
|
+
writeYamlFile(configPath, config);
|
|
181
|
+
}
|
|
182
|
+
function removeYaml(configPath) {
|
|
183
|
+
const config = readYamlFile(configPath);
|
|
184
|
+
const extensions = config["extensions"];
|
|
185
|
+
if (extensions) {
|
|
186
|
+
delete extensions["useai"];
|
|
187
|
+
if (Object.keys(extensions).length === 0) {
|
|
188
|
+
delete config["extensions"];
|
|
189
|
+
}
|
|
190
|
+
writeYamlFile(configPath, config);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function createTool(def) {
|
|
194
|
+
const handler = formatHandlers[def.configFormat];
|
|
195
|
+
return {
|
|
196
|
+
id: def.id,
|
|
197
|
+
name: def.name,
|
|
198
|
+
configFormat: def.configFormat,
|
|
199
|
+
getConfigPath: () => def.configPath,
|
|
200
|
+
detect: def.detect,
|
|
201
|
+
isConfigured: () => handler.isConfigured(def.configPath),
|
|
202
|
+
install: () => handler.install(def.configPath),
|
|
203
|
+
remove: () => handler.remove(def.configPath)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function matchesTool(tool, query) {
|
|
207
|
+
const q = query.toLowerCase().replace(/[\s-_]+/g, "");
|
|
208
|
+
const id = tool.id.toLowerCase().replace(/[\s-_]+/g, "");
|
|
209
|
+
const name = tool.name.toLowerCase().replace(/[\s-_]+/g, "");
|
|
210
|
+
return id === q || name === q || id.includes(q) || name.includes(q);
|
|
211
|
+
}
|
|
212
|
+
function resolveTools(names) {
|
|
213
|
+
const matched = [];
|
|
214
|
+
const unmatched = [];
|
|
215
|
+
for (const name of names) {
|
|
216
|
+
const found = AI_TOOLS.filter((t) => matchesTool(t, name));
|
|
217
|
+
if (found.length > 0) {
|
|
218
|
+
for (const f of found) {
|
|
219
|
+
if (!matched.includes(f)) matched.push(f);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
unmatched.push(name);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { matched, unmatched };
|
|
226
|
+
}
|
|
227
|
+
var MCP_ENTRY, home, formatHandlers, appSupport, AI_TOOLS;
|
|
228
|
+
var init_tools = __esm({
|
|
229
|
+
"src/tools.ts"() {
|
|
230
|
+
"use strict";
|
|
231
|
+
MCP_ENTRY = {
|
|
232
|
+
command: "npx",
|
|
233
|
+
args: ["-y", "@devness/useai"]
|
|
234
|
+
};
|
|
235
|
+
home = homedir2();
|
|
236
|
+
formatHandlers = {
|
|
237
|
+
standard: { isConfigured: isConfiguredStandard, install: installStandard, remove: removeStandard },
|
|
238
|
+
vscode: { isConfigured: isConfiguredVscode, install: installVscode, remove: removeVscode },
|
|
239
|
+
zed: { isConfigured: isConfiguredZed, install: installZed, remove: removeZed },
|
|
240
|
+
toml: { isConfigured: isConfiguredToml, install: installToml, remove: removeToml },
|
|
241
|
+
yaml: { isConfigured: isConfiguredYaml, install: installYaml, remove: removeYaml }
|
|
242
|
+
};
|
|
243
|
+
appSupport = join3(home, "Library", "Application Support");
|
|
244
|
+
AI_TOOLS = [
|
|
245
|
+
createTool({
|
|
246
|
+
id: "claude-code",
|
|
247
|
+
name: "Claude Code",
|
|
248
|
+
configFormat: "standard",
|
|
249
|
+
configPath: join3(home, ".claude.json"),
|
|
250
|
+
detect: () => hasBinary("claude") || existsSync3(join3(home, ".claude.json"))
|
|
251
|
+
}),
|
|
252
|
+
createTool({
|
|
253
|
+
id: "claude-desktop",
|
|
254
|
+
name: "Claude Desktop",
|
|
255
|
+
configFormat: "standard",
|
|
256
|
+
configPath: join3(appSupport, "Claude", "claude_desktop_config.json"),
|
|
257
|
+
detect: () => existsSync3(join3(appSupport, "Claude")) || existsSync3("/Applications/Claude.app")
|
|
258
|
+
}),
|
|
259
|
+
createTool({
|
|
260
|
+
id: "cursor",
|
|
261
|
+
name: "Cursor",
|
|
262
|
+
configFormat: "standard",
|
|
263
|
+
configPath: join3(home, ".cursor", "mcp.json"),
|
|
264
|
+
detect: () => existsSync3(join3(home, ".cursor"))
|
|
265
|
+
}),
|
|
266
|
+
createTool({
|
|
267
|
+
id: "windsurf",
|
|
268
|
+
name: "Windsurf",
|
|
269
|
+
configFormat: "standard",
|
|
270
|
+
configPath: join3(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
271
|
+
detect: () => existsSync3(join3(home, ".codeium", "windsurf"))
|
|
272
|
+
}),
|
|
273
|
+
createTool({
|
|
274
|
+
id: "vscode",
|
|
275
|
+
name: "VS Code",
|
|
276
|
+
configFormat: "vscode",
|
|
277
|
+
configPath: join3(appSupport, "Code", "User", "mcp.json"),
|
|
278
|
+
detect: () => existsSync3(join3(appSupport, "Code"))
|
|
279
|
+
}),
|
|
280
|
+
createTool({
|
|
281
|
+
id: "vscode-insiders",
|
|
282
|
+
name: "VS Code Insiders",
|
|
283
|
+
configFormat: "vscode",
|
|
284
|
+
configPath: join3(appSupport, "Code - Insiders", "User", "mcp.json"),
|
|
285
|
+
detect: () => existsSync3(join3(appSupport, "Code - Insiders"))
|
|
286
|
+
}),
|
|
287
|
+
createTool({
|
|
288
|
+
id: "gemini-cli",
|
|
289
|
+
name: "Gemini CLI",
|
|
290
|
+
configFormat: "standard",
|
|
291
|
+
configPath: join3(home, ".gemini", "settings.json"),
|
|
292
|
+
detect: () => hasBinary("gemini")
|
|
293
|
+
}),
|
|
294
|
+
createTool({
|
|
295
|
+
id: "zed",
|
|
296
|
+
name: "Zed",
|
|
297
|
+
configFormat: "zed",
|
|
298
|
+
configPath: join3(appSupport, "Zed", "settings.json"),
|
|
299
|
+
detect: () => existsSync3(join3(appSupport, "Zed"))
|
|
300
|
+
}),
|
|
301
|
+
createTool({
|
|
302
|
+
id: "cline",
|
|
303
|
+
name: "Cline",
|
|
304
|
+
configFormat: "standard",
|
|
305
|
+
configPath: join3(
|
|
306
|
+
appSupport,
|
|
307
|
+
"Code",
|
|
308
|
+
"User",
|
|
309
|
+
"globalStorage",
|
|
310
|
+
"saoudrizwan.claude-dev",
|
|
311
|
+
"settings",
|
|
312
|
+
"cline_mcp_settings.json"
|
|
313
|
+
),
|
|
314
|
+
detect: () => existsSync3(
|
|
315
|
+
join3(appSupport, "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
|
|
316
|
+
)
|
|
317
|
+
}),
|
|
318
|
+
createTool({
|
|
319
|
+
id: "roo-code",
|
|
320
|
+
name: "Roo Code",
|
|
321
|
+
configFormat: "standard",
|
|
322
|
+
configPath: join3(
|
|
323
|
+
appSupport,
|
|
324
|
+
"Code",
|
|
325
|
+
"User",
|
|
326
|
+
"globalStorage",
|
|
327
|
+
"rooveterinaryinc.roo-cline",
|
|
328
|
+
"settings",
|
|
329
|
+
"cline_mcp_settings.json"
|
|
330
|
+
),
|
|
331
|
+
detect: () => existsSync3(
|
|
332
|
+
join3(appSupport, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline")
|
|
333
|
+
)
|
|
334
|
+
}),
|
|
335
|
+
createTool({
|
|
336
|
+
id: "amazon-q-cli",
|
|
337
|
+
name: "Amazon Q CLI",
|
|
338
|
+
configFormat: "standard",
|
|
339
|
+
configPath: join3(home, ".aws", "amazonq", "mcp.json"),
|
|
340
|
+
detect: () => hasBinary("q") || existsSync3(join3(home, ".aws", "amazonq"))
|
|
341
|
+
}),
|
|
342
|
+
createTool({
|
|
343
|
+
id: "amazon-q-ide",
|
|
344
|
+
name: "Amazon Q IDE",
|
|
345
|
+
configFormat: "standard",
|
|
346
|
+
configPath: join3(home, ".aws", "amazonq", "default.json"),
|
|
347
|
+
detect: () => existsSync3(join3(home, ".amazonq")) || existsSync3(join3(home, ".aws", "amazonq"))
|
|
348
|
+
}),
|
|
349
|
+
createTool({
|
|
350
|
+
id: "codex",
|
|
351
|
+
name: "Codex",
|
|
352
|
+
configFormat: "toml",
|
|
353
|
+
configPath: join3(home, ".codex", "config.toml"),
|
|
354
|
+
detect: () => hasBinary("codex") || existsSync3(join3(home, ".codex")) || existsSync3("/Applications/Codex.app")
|
|
355
|
+
}),
|
|
356
|
+
createTool({
|
|
357
|
+
id: "goose",
|
|
358
|
+
name: "Goose",
|
|
359
|
+
configFormat: "yaml",
|
|
360
|
+
configPath: join3(home, ".config", "goose", "config.yaml"),
|
|
361
|
+
detect: () => existsSync3(join3(home, ".config", "goose"))
|
|
362
|
+
}),
|
|
363
|
+
createTool({
|
|
364
|
+
id: "opencode",
|
|
365
|
+
name: "OpenCode",
|
|
366
|
+
configFormat: "standard",
|
|
367
|
+
configPath: join3(home, ".config", "opencode", "opencode.json"),
|
|
368
|
+
detect: () => hasBinary("opencode") || existsSync3(join3(home, ".config", "opencode"))
|
|
369
|
+
}),
|
|
370
|
+
createTool({
|
|
371
|
+
id: "junie",
|
|
372
|
+
name: "Junie",
|
|
373
|
+
configFormat: "standard",
|
|
374
|
+
configPath: join3(home, ".junie", "mcp", "mcp.json"),
|
|
375
|
+
detect: () => existsSync3(join3(home, ".junie"))
|
|
376
|
+
})
|
|
377
|
+
];
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// src/setup.ts
|
|
382
|
+
var setup_exports = {};
|
|
383
|
+
__export(setup_exports, {
|
|
384
|
+
runSetup: () => runSetup
|
|
385
|
+
});
|
|
386
|
+
import { checkbox } from "@inquirer/prompts";
|
|
387
|
+
import chalk from "chalk";
|
|
388
|
+
function shortenPath(p) {
|
|
389
|
+
const home2 = process.env["HOME"] ?? "";
|
|
390
|
+
return home2 && p.startsWith(home2) ? p.replace(home2, "~") : p;
|
|
391
|
+
}
|
|
392
|
+
function header(text) {
|
|
393
|
+
return chalk.bold.cyan(`
|
|
394
|
+
${text}
|
|
395
|
+
${"\u2500".repeat(text.length)}`);
|
|
396
|
+
}
|
|
397
|
+
function ok(text) {
|
|
398
|
+
return chalk.green(` ${text}`);
|
|
399
|
+
}
|
|
400
|
+
function err(text) {
|
|
401
|
+
return chalk.red(` ${text}`);
|
|
402
|
+
}
|
|
403
|
+
function dim(text) {
|
|
404
|
+
return chalk.dim(` ${text}`);
|
|
405
|
+
}
|
|
406
|
+
function showStatus(tools) {
|
|
407
|
+
console.log(header("AI Tool MCP Status"));
|
|
408
|
+
const rows = [];
|
|
409
|
+
const nameWidth = Math.max(...tools.map((t) => t.name.length));
|
|
410
|
+
const statusWidth = 16;
|
|
411
|
+
for (const tool of tools) {
|
|
412
|
+
const detected = tool.detect();
|
|
413
|
+
const name = tool.name.padEnd(nameWidth);
|
|
414
|
+
if (!detected) {
|
|
415
|
+
rows.push(` ${chalk.dim(name)} ${chalk.dim("\u2014 Not found".padEnd(statusWidth))}`);
|
|
416
|
+
} else if (tool.isConfigured()) {
|
|
417
|
+
rows.push(
|
|
418
|
+
` ${name} ${chalk.green("\u2713 Configured".padEnd(statusWidth))} ${chalk.dim(shortenPath(tool.getConfigPath()))}`
|
|
419
|
+
);
|
|
420
|
+
} else {
|
|
421
|
+
rows.push(
|
|
422
|
+
` ${name} ${chalk.yellow("\u2717 Not set up".padEnd(statusWidth))} ${chalk.dim(shortenPath(tool.getConfigPath()))}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
console.log(rows.join("\n"));
|
|
427
|
+
console.log();
|
|
428
|
+
}
|
|
429
|
+
async function installFlow(tools, autoYes, explicit) {
|
|
430
|
+
if (explicit) {
|
|
431
|
+
const toInstall2 = tools.filter((t) => !t.isConfigured());
|
|
432
|
+
const alreadyDone = tools.filter((t) => t.isConfigured());
|
|
433
|
+
for (const tool of alreadyDone) {
|
|
434
|
+
console.log(dim(`${tool.name} is already configured.`));
|
|
435
|
+
}
|
|
436
|
+
if (toInstall2.length === 0) return;
|
|
437
|
+
console.log();
|
|
438
|
+
for (const tool of toInstall2) {
|
|
439
|
+
try {
|
|
440
|
+
tool.install();
|
|
441
|
+
console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim(shortenPath(tool.getConfigPath()))}`));
|
|
442
|
+
} catch (e) {
|
|
443
|
+
console.log(err(`\u2717 ${tool.name.padEnd(18)} \u2014 ${e.message}`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
console.log();
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
console.log(dim("Scanning for AI tools...\n"));
|
|
450
|
+
const detected = tools.filter((t) => t.detect());
|
|
451
|
+
if (detected.length === 0) {
|
|
452
|
+
console.log(err("No AI tools detected on this machine."));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const alreadyConfigured = detected.filter((t) => t.isConfigured());
|
|
456
|
+
const unconfigured = detected.filter((t) => !t.isConfigured());
|
|
457
|
+
console.log(` Found ${chalk.bold(String(detected.length))} AI tool${detected.length === 1 ? "" : "s"} on this machine:
|
|
458
|
+
`);
|
|
459
|
+
for (const tool of alreadyConfigured) {
|
|
460
|
+
console.log(chalk.green(` \u2705 ${tool.name}`) + chalk.dim(" (already configured)"));
|
|
461
|
+
}
|
|
462
|
+
for (const tool of unconfigured) {
|
|
463
|
+
console.log(chalk.dim(` \u2610 ${tool.name}`));
|
|
464
|
+
}
|
|
465
|
+
console.log();
|
|
466
|
+
if (unconfigured.length === 0) {
|
|
467
|
+
console.log(ok("All detected tools are already configured."));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
let toInstall;
|
|
471
|
+
if (autoYes) {
|
|
472
|
+
toInstall = unconfigured;
|
|
473
|
+
} else {
|
|
474
|
+
const selected = await checkbox({
|
|
475
|
+
message: "Select tools to configure:",
|
|
476
|
+
choices: unconfigured.map((t) => ({
|
|
477
|
+
name: t.name,
|
|
478
|
+
value: t.id,
|
|
479
|
+
checked: true
|
|
480
|
+
}))
|
|
481
|
+
});
|
|
482
|
+
toInstall = unconfigured.filter((t) => selected.includes(t.id));
|
|
483
|
+
}
|
|
484
|
+
if (toInstall.length === 0) {
|
|
485
|
+
console.log(dim("No tools selected."));
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
console.log(`
|
|
489
|
+
Configuring ${toInstall.length} tool${toInstall.length === 1 ? "" : "s"}...
|
|
490
|
+
`);
|
|
491
|
+
for (const tool of toInstall) {
|
|
38
492
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Authorization: `Bearer ${token}`,
|
|
44
|
-
},
|
|
45
|
-
body: JSON.stringify(data),
|
|
46
|
-
signal: AbortSignal.timeout(5000),
|
|
47
|
-
});
|
|
48
|
-
return res.ok;
|
|
493
|
+
tool.install();
|
|
494
|
+
console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim(shortenPath(tool.getConfigPath()))}`));
|
|
495
|
+
} catch (e) {
|
|
496
|
+
console.log(err(`\u2717 ${tool.name.padEnd(18)} \u2014 ${e.message}`));
|
|
49
497
|
}
|
|
50
|
-
|
|
51
|
-
|
|
498
|
+
}
|
|
499
|
+
console.log(`
|
|
500
|
+
Done! useai MCP server configured in ${chalk.bold(String(toInstall.length))} tool${toInstall.length === 1 ? "" : "s"}.
|
|
501
|
+
`);
|
|
502
|
+
}
|
|
503
|
+
async function removeFlow(tools, autoYes, explicit) {
|
|
504
|
+
if (explicit) {
|
|
505
|
+
const toRemove2 = tools.filter((t) => {
|
|
506
|
+
try {
|
|
507
|
+
return t.isConfigured();
|
|
508
|
+
} catch {
|
|
52
509
|
return false;
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
const notConfigured = tools.filter((t) => {
|
|
513
|
+
try {
|
|
514
|
+
return !t.isConfigured();
|
|
515
|
+
} catch {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
for (const tool of notConfigured) {
|
|
520
|
+
console.log(dim(`${tool.name} is not configured \u2014 skipping.`));
|
|
521
|
+
}
|
|
522
|
+
if (toRemove2.length === 0) return;
|
|
523
|
+
console.log();
|
|
524
|
+
for (const tool of toRemove2) {
|
|
525
|
+
try {
|
|
526
|
+
tool.remove();
|
|
527
|
+
console.log(ok(`\u2713 Removed from ${tool.name}`));
|
|
528
|
+
} catch (e) {
|
|
529
|
+
console.log(err(`\u2717 ${tool.name} \u2014 ${e.message}`));
|
|
530
|
+
}
|
|
53
531
|
}
|
|
532
|
+
console.log();
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const configured = tools.filter((t) => {
|
|
536
|
+
try {
|
|
537
|
+
return t.isConfigured();
|
|
538
|
+
} catch {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
if (configured.length === 0) {
|
|
543
|
+
console.log(dim("useai is not configured in any AI tools."));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
console.log(`
|
|
547
|
+
Found useai configured in ${chalk.bold(String(configured.length))} tool${configured.length === 1 ? "" : "s"}:
|
|
548
|
+
`);
|
|
549
|
+
let toRemove;
|
|
550
|
+
if (autoYes) {
|
|
551
|
+
toRemove = configured;
|
|
552
|
+
} else {
|
|
553
|
+
const selected = await checkbox({
|
|
554
|
+
message: "Select tools to remove useai from:",
|
|
555
|
+
choices: configured.map((t) => ({
|
|
556
|
+
name: t.name,
|
|
557
|
+
value: t.id,
|
|
558
|
+
checked: true
|
|
559
|
+
}))
|
|
560
|
+
});
|
|
561
|
+
toRemove = configured.filter((t) => selected.includes(t.id));
|
|
562
|
+
}
|
|
563
|
+
if (toRemove.length === 0) {
|
|
564
|
+
console.log(dim("No tools selected."));
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
console.log(`
|
|
568
|
+
Removing from ${toRemove.length} tool${toRemove.length === 1 ? "" : "s"}...
|
|
569
|
+
`);
|
|
570
|
+
for (const tool of toRemove) {
|
|
571
|
+
try {
|
|
572
|
+
tool.remove();
|
|
573
|
+
console.log(ok(`\u2713 Removed from ${tool.name}`));
|
|
574
|
+
} catch (e) {
|
|
575
|
+
console.log(err(`\u2717 ${tool.name} \u2014 ${e.message}`));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
console.log(`
|
|
579
|
+
Done! useai removed from ${chalk.bold(String(toRemove.length))} tool${toRemove.length === 1 ? "" : "s"}.
|
|
580
|
+
`);
|
|
54
581
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
582
|
+
function showHelp() {
|
|
583
|
+
console.log(`
|
|
584
|
+
${chalk.bold("Usage:")} npx @devness/useai mcp [tools...] [options]
|
|
585
|
+
|
|
586
|
+
Configure useai MCP server in your AI tools.
|
|
587
|
+
|
|
588
|
+
${chalk.bold("Arguments:")}
|
|
589
|
+
tools Specific tool names (e.g. codex cursor vscode)
|
|
590
|
+
|
|
591
|
+
${chalk.bold("Options:")}
|
|
592
|
+
--remove Remove useai from configured tools
|
|
593
|
+
--status Show configuration status without modifying
|
|
594
|
+
-y, --yes Skip confirmation, auto-select all detected tools
|
|
595
|
+
-h, --help Show this help message
|
|
596
|
+
`);
|
|
597
|
+
}
|
|
598
|
+
async function runSetup(args) {
|
|
599
|
+
const flags = new Set(args.filter((a) => a.startsWith("-")));
|
|
600
|
+
const toolNames = args.filter((a) => !a.startsWith("-"));
|
|
601
|
+
if (flags.has("-h") || flags.has("--help")) {
|
|
602
|
+
showHelp();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
const isRemove = flags.has("--remove");
|
|
606
|
+
const isStatus = flags.has("--status");
|
|
607
|
+
const autoYes = flags.has("-y") || flags.has("--yes");
|
|
608
|
+
const explicit = toolNames.length > 0;
|
|
609
|
+
let tools = AI_TOOLS;
|
|
610
|
+
if (explicit) {
|
|
611
|
+
const { matched, unmatched } = resolveTools(toolNames);
|
|
612
|
+
if (unmatched.length > 0) {
|
|
613
|
+
console.log(err(`Unknown tool${unmatched.length === 1 ? "" : "s"}: ${unmatched.join(", ")}`));
|
|
614
|
+
console.log(dim(`Available: ${AI_TOOLS.map((t) => t.id).join(", ")}`));
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
tools = matched;
|
|
618
|
+
}
|
|
619
|
+
if (isStatus) {
|
|
620
|
+
showStatus(tools);
|
|
621
|
+
} else if (isRemove) {
|
|
622
|
+
await removeFlow(tools, autoYes, explicit);
|
|
623
|
+
} else {
|
|
624
|
+
await installFlow(tools, autoYes, explicit);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
var init_setup = __esm({
|
|
628
|
+
"src/setup.ts"() {
|
|
629
|
+
"use strict";
|
|
630
|
+
init_tools();
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// src/index.ts
|
|
635
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
636
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
637
|
+
import { z as z2 } from "zod";
|
|
638
|
+
import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
|
|
639
|
+
import { existsSync as existsSync4, renameSync as renameSync2 } from "fs";
|
|
640
|
+
import { join as join4 } from "path";
|
|
641
|
+
|
|
642
|
+
// ../shared/dist/constants/paths.js
|
|
643
|
+
import { join } from "path";
|
|
644
|
+
import { homedir } from "os";
|
|
645
|
+
var USEAI_DIR = join(homedir(), ".useai");
|
|
646
|
+
var DATA_DIR = join(USEAI_DIR, "data");
|
|
647
|
+
var ACTIVE_DIR = join(DATA_DIR, "active");
|
|
648
|
+
var SEALED_DIR = join(DATA_DIR, "sealed");
|
|
649
|
+
var KEYSTORE_FILE = join(USEAI_DIR, "keystore.json");
|
|
650
|
+
var CONFIG_FILE = join(USEAI_DIR, "config.json");
|
|
651
|
+
var SESSIONS_FILE = join(DATA_DIR, "sessions.json");
|
|
652
|
+
var MILESTONES_FILE = join(DATA_DIR, "milestones.json");
|
|
653
|
+
|
|
654
|
+
// ../shared/dist/constants/version.js
|
|
655
|
+
var VERSION = "0.3.0";
|
|
656
|
+
|
|
657
|
+
// ../shared/dist/constants/clients.js
|
|
658
|
+
var AI_CLIENT_ENV_VARS = {
|
|
659
|
+
CURSOR_EDITOR: "cursor",
|
|
660
|
+
WINDSURF_EDITOR: "windsurf",
|
|
661
|
+
CLAUDE_CODE: "claude-code",
|
|
662
|
+
VSCODE_PID: "vscode",
|
|
663
|
+
CODEX_CLI: "codex",
|
|
664
|
+
GEMINI_CLI: "gemini-cli",
|
|
665
|
+
JETBRAINS_IDE: "jetbrains",
|
|
666
|
+
ZED_EDITOR: "zed"
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// ../shared/dist/constants/defaults.js
|
|
670
|
+
var GENESIS_HASH = "GENESIS";
|
|
671
|
+
|
|
672
|
+
// ../shared/dist/crypto/keystore.js
|
|
673
|
+
import { generateKeyPairSync, randomBytes, createCipheriv, createDecipheriv, createPrivateKey } from "crypto";
|
|
674
|
+
|
|
675
|
+
// ../shared/dist/crypto/derive.js
|
|
676
|
+
import { pbkdf2Sync } from "crypto";
|
|
677
|
+
import { hostname, userInfo } from "os";
|
|
678
|
+
function deriveEncryptionKey(salt) {
|
|
679
|
+
const machineData = `${hostname()}:${userInfo().username}:useai-keystore`;
|
|
680
|
+
return pbkdf2Sync(machineData, salt, 1e5, 32, "sha256");
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// ../shared/dist/crypto/keystore.js
|
|
684
|
+
function decryptKeystore(ks) {
|
|
685
|
+
const salt = Buffer.from(ks.salt, "hex");
|
|
686
|
+
const iv = Buffer.from(ks.iv, "hex");
|
|
687
|
+
const tag = Buffer.from(ks.tag, "hex");
|
|
688
|
+
const encrypted = Buffer.from(ks.encrypted_private_key, "hex");
|
|
689
|
+
const encKey = deriveEncryptionKey(salt);
|
|
690
|
+
const decipher = createDecipheriv("aes-256-gcm", encKey, iv);
|
|
691
|
+
decipher.setAuthTag(tag);
|
|
692
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
693
|
+
return createPrivateKey(decrypted.toString("utf-8"));
|
|
694
|
+
}
|
|
695
|
+
function generateKeystore() {
|
|
696
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
697
|
+
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
698
|
+
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
|
|
699
|
+
const salt = randomBytes(32);
|
|
700
|
+
const encKey = deriveEncryptionKey(salt);
|
|
701
|
+
const iv = randomBytes(12);
|
|
702
|
+
const cipher = createCipheriv("aes-256-gcm", encKey, iv);
|
|
703
|
+
const encrypted = Buffer.concat([cipher.update(privateKeyPem, "utf-8"), cipher.final()]);
|
|
704
|
+
const tag = cipher.getAuthTag();
|
|
705
|
+
const keystore = {
|
|
706
|
+
public_key_pem: publicKeyPem,
|
|
707
|
+
encrypted_private_key: encrypted.toString("hex"),
|
|
708
|
+
iv: iv.toString("hex"),
|
|
709
|
+
tag: tag.toString("hex"),
|
|
710
|
+
salt: salt.toString("hex"),
|
|
711
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
712
|
+
};
|
|
713
|
+
return { keystore, signingKey: privateKey };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ../shared/dist/crypto/chain.js
|
|
717
|
+
import { createHash, sign as cryptoSign, randomUUID } from "crypto";
|
|
718
|
+
function computeHash(recordJson, prevHash) {
|
|
719
|
+
return createHash("sha256").update(recordJson + prevHash).digest("hex");
|
|
720
|
+
}
|
|
721
|
+
function signHash(hash, signingKey2) {
|
|
722
|
+
if (!signingKey2)
|
|
723
|
+
return "unsigned";
|
|
724
|
+
try {
|
|
725
|
+
return cryptoSign(null, Buffer.from(hash), signingKey2).toString("hex");
|
|
726
|
+
} catch {
|
|
727
|
+
return "unsigned";
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function buildChainRecord(type, sessionId2, data, prevHash, signingKey2) {
|
|
731
|
+
const id = `r_${randomUUID().slice(0, 12)}`;
|
|
732
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
733
|
+
const recordCore = JSON.stringify({ id, type, session_id: sessionId2, timestamp, data });
|
|
734
|
+
const hash = computeHash(recordCore, prevHash);
|
|
735
|
+
const signature = signHash(hash, signingKey2);
|
|
736
|
+
return {
|
|
737
|
+
id,
|
|
738
|
+
type,
|
|
739
|
+
session_id: sessionId2,
|
|
740
|
+
timestamp,
|
|
741
|
+
data,
|
|
742
|
+
prev_hash: prevHash,
|
|
743
|
+
hash,
|
|
744
|
+
signature
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// ../shared/dist/crypto/verify.js
|
|
749
|
+
import { createHash as createHash2, createPublicKey, verify as cryptoVerify } from "crypto";
|
|
750
|
+
|
|
751
|
+
// ../shared/dist/validation/schemas.js
|
|
752
|
+
import { z } from "zod";
|
|
753
|
+
var taskTypeSchema = z.enum([
|
|
754
|
+
"coding",
|
|
755
|
+
"debugging",
|
|
756
|
+
"testing",
|
|
757
|
+
"planning",
|
|
758
|
+
"reviewing",
|
|
759
|
+
"documenting",
|
|
760
|
+
"learning",
|
|
761
|
+
"other"
|
|
762
|
+
]);
|
|
763
|
+
var milestoneCategorySchema = z.enum([
|
|
764
|
+
"feature",
|
|
765
|
+
"bugfix",
|
|
766
|
+
"refactor",
|
|
767
|
+
"test",
|
|
768
|
+
"docs",
|
|
769
|
+
"setup",
|
|
770
|
+
"deployment",
|
|
771
|
+
"other"
|
|
772
|
+
]);
|
|
773
|
+
var complexitySchema = z.enum(["simple", "medium", "complex"]);
|
|
774
|
+
var milestoneInputSchema = z.object({
|
|
775
|
+
title: z.string().min(1).max(500),
|
|
776
|
+
category: milestoneCategorySchema,
|
|
777
|
+
complexity: complexitySchema.optional()
|
|
778
|
+
});
|
|
779
|
+
var syncPayloadSchema = z.object({
|
|
780
|
+
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
781
|
+
total_seconds: z.number().int().nonnegative(),
|
|
782
|
+
clients: z.record(z.string(), z.number().int().nonnegative()),
|
|
783
|
+
task_types: z.record(z.string(), z.number().int().nonnegative()),
|
|
784
|
+
languages: z.record(z.string(), z.number().int().nonnegative()),
|
|
785
|
+
sessions: z.array(z.object({
|
|
786
|
+
session_id: z.string(),
|
|
787
|
+
client: z.string(),
|
|
788
|
+
task_type: z.string(),
|
|
789
|
+
languages: z.array(z.string()),
|
|
790
|
+
files_touched: z.number().int().nonnegative(),
|
|
791
|
+
started_at: z.string(),
|
|
792
|
+
ended_at: z.string(),
|
|
793
|
+
duration_seconds: z.number().int().nonnegative(),
|
|
794
|
+
heartbeat_count: z.number().int().nonnegative(),
|
|
795
|
+
record_count: z.number().int().nonnegative(),
|
|
796
|
+
chain_start_hash: z.string(),
|
|
797
|
+
chain_end_hash: z.string(),
|
|
798
|
+
seal_signature: z.string()
|
|
799
|
+
})),
|
|
800
|
+
sync_signature: z.string()
|
|
801
|
+
});
|
|
802
|
+
var publishPayloadSchema = z.object({
|
|
803
|
+
milestones: z.array(z.object({
|
|
804
|
+
id: z.string(),
|
|
805
|
+
title: z.string().min(1).max(500),
|
|
806
|
+
category: milestoneCategorySchema,
|
|
807
|
+
complexity: complexitySchema,
|
|
808
|
+
duration_minutes: z.number().int().nonnegative(),
|
|
809
|
+
languages: z.array(z.string()),
|
|
810
|
+
client: z.string(),
|
|
811
|
+
chain_hash: z.string()
|
|
812
|
+
}))
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
// ../shared/dist/utils/fs.js
|
|
816
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "fs";
|
|
817
|
+
function ensureDir() {
|
|
818
|
+
for (const dir of [USEAI_DIR, DATA_DIR, ACTIVE_DIR, SEALED_DIR]) {
|
|
819
|
+
if (!existsSync(dir)) {
|
|
820
|
+
mkdirSync(dir, { recursive: true });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
57
823
|
}
|
|
824
|
+
function readJson(path, fallback) {
|
|
825
|
+
try {
|
|
826
|
+
if (!existsSync(path))
|
|
827
|
+
return fallback;
|
|
828
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
829
|
+
} catch {
|
|
830
|
+
return fallback;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function writeJson(path, data) {
|
|
834
|
+
ensureDir();
|
|
835
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
836
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
837
|
+
renameSync(tmp, path);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// ../shared/dist/utils/format.js
|
|
58
841
|
function formatDuration(seconds) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
842
|
+
if (seconds < 60)
|
|
843
|
+
return `${seconds}s`;
|
|
844
|
+
const mins = Math.round(seconds / 60);
|
|
845
|
+
if (mins < 60)
|
|
846
|
+
return `${mins}m`;
|
|
847
|
+
const hrs = Math.floor(mins / 60);
|
|
848
|
+
const remainMins = mins % 60;
|
|
849
|
+
return `${hrs}h ${remainMins}m`;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// ../shared/dist/utils/detect-client.js
|
|
853
|
+
function detectClient() {
|
|
854
|
+
const env = process.env;
|
|
855
|
+
for (const [envVar, clientName2] of Object.entries(AI_CLIENT_ENV_VARS)) {
|
|
856
|
+
if (env[envVar])
|
|
857
|
+
return clientName2;
|
|
858
|
+
}
|
|
859
|
+
if (env.MCP_CLIENT_NAME)
|
|
860
|
+
return env.MCP_CLIENT_NAME;
|
|
861
|
+
return "unknown";
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// ../shared/dist/utils/id.js
|
|
865
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
866
|
+
function generateSessionId() {
|
|
867
|
+
return randomUUID2();
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/session.ts
|
|
871
|
+
import { appendFileSync, existsSync as existsSync2 } from "fs";
|
|
872
|
+
import { join as join2 } from "path";
|
|
873
|
+
var sessionStartTime = Date.now();
|
|
874
|
+
var sessionId = generateSessionId();
|
|
875
|
+
var heartbeatCount = 0;
|
|
876
|
+
var sessionRecordCount = 0;
|
|
877
|
+
var clientName = "unknown";
|
|
878
|
+
var sessionTaskType = "coding";
|
|
879
|
+
var chainTipHash = GENESIS_HASH;
|
|
880
|
+
var signingKey = null;
|
|
881
|
+
var signingAvailable = false;
|
|
882
|
+
function resetSession() {
|
|
883
|
+
sessionStartTime = Date.now();
|
|
884
|
+
sessionId = generateSessionId();
|
|
885
|
+
heartbeatCount = 0;
|
|
886
|
+
sessionRecordCount = 0;
|
|
887
|
+
chainTipHash = GENESIS_HASH;
|
|
888
|
+
clientName = "unknown";
|
|
889
|
+
sessionTaskType = "coding";
|
|
890
|
+
}
|
|
891
|
+
function setClient(name) {
|
|
892
|
+
clientName = name;
|
|
893
|
+
}
|
|
894
|
+
function setTaskType(type) {
|
|
895
|
+
sessionTaskType = type;
|
|
896
|
+
}
|
|
897
|
+
function incrementHeartbeat() {
|
|
898
|
+
heartbeatCount++;
|
|
899
|
+
}
|
|
900
|
+
function getSessionDuration() {
|
|
901
|
+
return Math.round((Date.now() - sessionStartTime) / 1e3);
|
|
902
|
+
}
|
|
903
|
+
function initializeKeystore() {
|
|
904
|
+
ensureDir();
|
|
905
|
+
if (existsSync2(KEYSTORE_FILE)) {
|
|
906
|
+
const ks = readJson(KEYSTORE_FILE, null);
|
|
907
|
+
if (ks) {
|
|
908
|
+
try {
|
|
909
|
+
signingKey = decryptKeystore(ks);
|
|
910
|
+
signingAvailable = true;
|
|
911
|
+
return;
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const result = generateKeystore();
|
|
917
|
+
writeJson(KEYSTORE_FILE, result.keystore);
|
|
918
|
+
signingKey = result.signingKey;
|
|
919
|
+
signingAvailable = true;
|
|
920
|
+
}
|
|
921
|
+
function sessionChainPath() {
|
|
922
|
+
return join2(ACTIVE_DIR, `${sessionId}.jsonl`);
|
|
923
|
+
}
|
|
924
|
+
function appendToChain(type, data) {
|
|
925
|
+
const record = buildChainRecord(type, sessionId, data, chainTipHash, signingKey);
|
|
926
|
+
ensureDir();
|
|
927
|
+
appendFileSync(sessionChainPath(), JSON.stringify(record) + "\n", "utf-8");
|
|
928
|
+
chainTipHash = record.hash;
|
|
929
|
+
sessionRecordCount++;
|
|
930
|
+
return record;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/index.ts
|
|
934
|
+
if (process.argv[2] === "mcp") {
|
|
935
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
936
|
+
await runSetup2(process.argv.slice(3));
|
|
937
|
+
process.exit(0);
|
|
938
|
+
}
|
|
939
|
+
function getConfig() {
|
|
940
|
+
return readJson(CONFIG_FILE, {
|
|
941
|
+
milestone_tracking: true,
|
|
942
|
+
auto_sync: true
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
function getSessions() {
|
|
946
|
+
return readJson(SESSIONS_FILE, []);
|
|
947
|
+
}
|
|
948
|
+
function getMilestones() {
|
|
949
|
+
return readJson(MILESTONES_FILE, []);
|
|
950
|
+
}
|
|
951
|
+
var server = new McpServer({
|
|
952
|
+
name: "useai",
|
|
953
|
+
version: VERSION
|
|
72
954
|
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
task_type:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
])
|
|
88
|
-
.optional()
|
|
89
|
-
.describe("What kind of task is the developer working on?"),
|
|
90
|
-
}, async ({ task_type }) => {
|
|
91
|
-
sessionStartTime = Date.now();
|
|
92
|
-
sessionId = crypto.randomUUID();
|
|
93
|
-
heartbeatCount = 0;
|
|
94
|
-
clientName = detectClient();
|
|
95
|
-
await pushMetrics({
|
|
96
|
-
event: "session_start",
|
|
97
|
-
session_id: sessionId,
|
|
98
|
-
client: clientName,
|
|
99
|
-
task_type: task_type ?? "coding",
|
|
100
|
-
timestamp: new Date().toISOString(),
|
|
101
|
-
version: VERSION,
|
|
955
|
+
server.tool(
|
|
956
|
+
"useai_session_start",
|
|
957
|
+
"Start tracking an AI coding session. Call this at the beginning of a conversation. Records which AI tool and task type \u2014 stored LOCALLY at ~/.useai/. No data is sent anywhere. No code, prompts, or private data is captured.",
|
|
958
|
+
{
|
|
959
|
+
task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task is the developer working on?")
|
|
960
|
+
},
|
|
961
|
+
async ({ task_type }) => {
|
|
962
|
+
resetSession();
|
|
963
|
+
setClient(detectClient());
|
|
964
|
+
setTaskType(task_type ?? "coding");
|
|
965
|
+
const record = appendToChain("session_start", {
|
|
966
|
+
client: clientName,
|
|
967
|
+
task_type: sessionTaskType,
|
|
968
|
+
version: VERSION
|
|
102
969
|
});
|
|
103
|
-
const taskLabel = task_type ?? "coding";
|
|
104
970
|
return {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
971
|
+
content: [
|
|
972
|
+
{
|
|
973
|
+
type: "text",
|
|
974
|
+
text: `useai session started \u2014 tracking ${sessionTaskType} session on ${clientName}.
|
|
975
|
+
Session: ${sessionId.slice(0, 8)} \xB7 Chain: ${record.hash.slice(0, 12)} (${signingAvailable ? "signed" : "unsigned"})
|
|
976
|
+
All data stored locally at ${USEAI_DIR}. No network calls made.`
|
|
977
|
+
}
|
|
978
|
+
]
|
|
111
979
|
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
server.tool(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
session_duration_seconds: getSessionDuration(),
|
|
124
|
-
timestamp: new Date().toISOString(),
|
|
980
|
+
}
|
|
981
|
+
);
|
|
982
|
+
server.tool(
|
|
983
|
+
"useai_heartbeat",
|
|
984
|
+
"Record a heartbeat for the current AI coding session. Call this periodically during long conversations (every 10-15 minutes). Only records a timestamp \u2014 stored locally, never sent anywhere.",
|
|
985
|
+
{},
|
|
986
|
+
async () => {
|
|
987
|
+
incrementHeartbeat();
|
|
988
|
+
appendToChain("heartbeat", {
|
|
989
|
+
heartbeat_number: heartbeatCount,
|
|
990
|
+
cumulative_seconds: getSessionDuration()
|
|
125
991
|
});
|
|
126
992
|
return {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
993
|
+
content: [
|
|
994
|
+
{
|
|
995
|
+
type: "text",
|
|
996
|
+
text: `Heartbeat recorded locally. Session active for ${formatDuration(getSessionDuration())}.`
|
|
997
|
+
}
|
|
998
|
+
]
|
|
133
999
|
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
server.tool(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
.optional()
|
|
151
|
-
.describe("What kind of task was the developer working on?"),
|
|
152
|
-
languages: z
|
|
153
|
-
.array(z.string())
|
|
154
|
-
.optional()
|
|
155
|
-
.describe("Programming languages used in this session (e.g. ['typescript', 'python'])"),
|
|
156
|
-
files_touched_count: z
|
|
157
|
-
.number()
|
|
158
|
-
.optional()
|
|
159
|
-
.describe("Approximate number of files created or modified (count only, no names)"),
|
|
160
|
-
tools_used_count: z
|
|
161
|
-
.number()
|
|
162
|
-
.optional()
|
|
163
|
-
.describe("Number of other MCP tools or commands invoked during this session"),
|
|
164
|
-
}, async ({ task_type, languages, files_touched_count, tools_used_count }) => {
|
|
1000
|
+
}
|
|
1001
|
+
);
|
|
1002
|
+
server.tool(
|
|
1003
|
+
"useai_session_end",
|
|
1004
|
+
"End the current AI coding session. Creates a cryptographically sealed session record and optionally records milestones \u2014 what was accomplished. Stats auto-sync to useai.dev daily. To publish milestones: run `useai publish` from the CLI. IMPORTANT: Milestone descriptions must be GENERIC. Do not include project names, file names, file paths, API endpoints, database names, company names, or any project-specific information. Use general terms like 'authentication system', 'data validation layer', 'responsive dashboard'. Think: would this description reveal what company or project this is for? If yes, make it more generic.",
|
|
1005
|
+
{
|
|
1006
|
+
task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task was the developer working on?"),
|
|
1007
|
+
languages: z2.array(z2.string()).optional().describe("Programming languages used (e.g. ['typescript', 'python'])"),
|
|
1008
|
+
files_touched_count: z2.number().optional().describe("Approximate number of files created or modified (count only, no names)"),
|
|
1009
|
+
milestones: z2.array(z2.object({
|
|
1010
|
+
title: z2.string().describe("Generic description of what was accomplished. PRIVACY RULES: Do NOT include project names, repository names, file names, file paths, API endpoints, database names, company names, or any project-specific details. Use generic terms only. GOOD examples: 'Implemented user authentication', 'Fixed race condition in background worker', 'Added unit tests for data validation', 'Refactored state management layer', 'Built responsive dashboard layout'. BAD examples: 'Fixed bug in /api/stripe/webhooks', 'Added auth to acme-corp project', 'Updated UserService.ts in src/services/'"),
|
|
1011
|
+
category: z2.enum(["feature", "bugfix", "refactor", "test", "docs", "setup", "deployment", "other"]).describe("Type of work completed"),
|
|
1012
|
+
complexity: z2.enum(["simple", "medium", "complex"]).optional().describe("How complex was this task?")
|
|
1013
|
+
})).optional().describe("What was accomplished this session? List each distinct piece of work completed. Describe generically \u2014 no project names, file names, or proprietary details.")
|
|
1014
|
+
},
|
|
1015
|
+
async ({ task_type, languages, files_touched_count, milestones: milestonesInput }) => {
|
|
165
1016
|
const duration = getSessionDuration();
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
heartbeats: heartbeatCount,
|
|
176
|
-
timestamp: new Date().toISOString(),
|
|
177
|
-
version: VERSION,
|
|
1017
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1018
|
+
const finalTaskType = task_type ?? sessionTaskType;
|
|
1019
|
+
const chainStartHash = chainTipHash === "GENESIS" ? "GENESIS" : chainTipHash;
|
|
1020
|
+
const endRecord = appendToChain("session_end", {
|
|
1021
|
+
duration_seconds: duration,
|
|
1022
|
+
task_type: finalTaskType,
|
|
1023
|
+
languages: languages ?? [],
|
|
1024
|
+
files_touched: files_touched_count ?? 0,
|
|
1025
|
+
heartbeat_count: heartbeatCount
|
|
178
1026
|
});
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
text: "useai.dev is not connected yet.\n\n" +
|
|
203
|
-
"To get started:\n" +
|
|
204
|
-
"1. Sign up at https://useai.dev\n" +
|
|
205
|
-
'2. Set your token: USEAI_TOKEN="your-token" in your MCP config\n' +
|
|
206
|
-
"3. Your AI coding sessions will be tracked automatically.\n\n" +
|
|
207
|
-
"useai.dev tracks HOW MUCH you use AI tools, never WHAT you use them for.",
|
|
208
|
-
},
|
|
209
|
-
],
|
|
210
|
-
};
|
|
211
|
-
}
|
|
1027
|
+
const sealData = JSON.stringify({
|
|
1028
|
+
session_id: sessionId,
|
|
1029
|
+
client: clientName,
|
|
1030
|
+
task_type: finalTaskType,
|
|
1031
|
+
languages: languages ?? [],
|
|
1032
|
+
files_touched: files_touched_count ?? 0,
|
|
1033
|
+
started_at: new Date(sessionStartTime).toISOString(),
|
|
1034
|
+
ended_at: now,
|
|
1035
|
+
duration_seconds: duration,
|
|
1036
|
+
heartbeat_count: heartbeatCount,
|
|
1037
|
+
record_count: sessionRecordCount,
|
|
1038
|
+
chain_end_hash: endRecord.hash
|
|
1039
|
+
});
|
|
1040
|
+
const sealSignature = signHash(
|
|
1041
|
+
createHash3("sha256").update(sealData).digest("hex"),
|
|
1042
|
+
signingKey
|
|
1043
|
+
);
|
|
1044
|
+
appendToChain("session_seal", {
|
|
1045
|
+
seal: sealData,
|
|
1046
|
+
seal_signature: sealSignature
|
|
1047
|
+
});
|
|
1048
|
+
const activePath = join4(ACTIVE_DIR, `${sessionId}.jsonl`);
|
|
1049
|
+
const sealedPath = join4(SEALED_DIR, `${sessionId}.jsonl`);
|
|
212
1050
|
try {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!res.ok) {
|
|
218
|
-
return {
|
|
219
|
-
content: [
|
|
220
|
-
{
|
|
221
|
-
type: "text",
|
|
222
|
-
text: "Could not fetch stats from useai.dev. The service may be temporarily unavailable.",
|
|
223
|
-
},
|
|
224
|
-
],
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
const stats = (await res.json());
|
|
228
|
-
const lines = [
|
|
229
|
-
`Your useai.dev Stats`,
|
|
230
|
-
`────────────────────`,
|
|
231
|
-
];
|
|
232
|
-
if (stats.streak_days !== undefined)
|
|
233
|
-
lines.push(`Streak: ${stats.streak_days} days`);
|
|
234
|
-
if (stats.total_hours !== undefined)
|
|
235
|
-
lines.push(`Total AI-paired hours: ${stats.total_hours}h`);
|
|
236
|
-
if (stats.sessions_this_week !== undefined)
|
|
237
|
-
lines.push(`Sessions this week: ${stats.sessions_this_week}`);
|
|
238
|
-
if (stats.rank_percentile !== undefined)
|
|
239
|
-
lines.push(`Global rank: Top ${stats.rank_percentile}%`);
|
|
240
|
-
if (stats.top_tools && stats.top_tools.length > 0) {
|
|
241
|
-
lines.push(``, `Tool Breakdown:`);
|
|
242
|
-
for (const tool of stats.top_tools) {
|
|
243
|
-
const bar = "█".repeat(Math.round(tool.percentage / 5));
|
|
244
|
-
lines.push(` ${tool.name.padEnd(14)} ${bar} ${tool.percentage}%`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
if (stats.profile_url) {
|
|
248
|
-
lines.push(``, `Profile: ${stats.profile_url}`);
|
|
249
|
-
}
|
|
250
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1051
|
+
if (existsSync4(activePath)) {
|
|
1052
|
+
renameSync2(activePath, sealedPath);
|
|
1053
|
+
}
|
|
1054
|
+
} catch {
|
|
251
1055
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
type: "text",
|
|
301
|
-
text: "Could not generate share link. The service may be temporarily unavailable.",
|
|
302
|
-
},
|
|
303
|
-
],
|
|
304
|
-
};
|
|
1056
|
+
const seal = {
|
|
1057
|
+
session_id: sessionId,
|
|
1058
|
+
client: clientName,
|
|
1059
|
+
task_type: finalTaskType,
|
|
1060
|
+
languages: languages ?? [],
|
|
1061
|
+
files_touched: files_touched_count ?? 0,
|
|
1062
|
+
started_at: new Date(sessionStartTime).toISOString(),
|
|
1063
|
+
ended_at: now,
|
|
1064
|
+
duration_seconds: duration,
|
|
1065
|
+
heartbeat_count: heartbeatCount,
|
|
1066
|
+
record_count: sessionRecordCount,
|
|
1067
|
+
chain_start_hash: chainStartHash,
|
|
1068
|
+
chain_end_hash: endRecord.hash,
|
|
1069
|
+
seal_signature: sealSignature
|
|
1070
|
+
};
|
|
1071
|
+
const sessions = getSessions();
|
|
1072
|
+
sessions.push(seal);
|
|
1073
|
+
writeJson(SESSIONS_FILE, sessions);
|
|
1074
|
+
let milestoneCount = 0;
|
|
1075
|
+
if (milestonesInput && milestonesInput.length > 0) {
|
|
1076
|
+
const config = getConfig();
|
|
1077
|
+
if (config.milestone_tracking) {
|
|
1078
|
+
const durationMinutes = Math.round(duration / 60);
|
|
1079
|
+
const allMilestones = getMilestones();
|
|
1080
|
+
for (const m of milestonesInput) {
|
|
1081
|
+
const record = appendToChain("milestone", {
|
|
1082
|
+
title: m.title,
|
|
1083
|
+
category: m.category,
|
|
1084
|
+
complexity: m.complexity ?? "medium",
|
|
1085
|
+
duration_minutes: durationMinutes,
|
|
1086
|
+
languages: languages ?? []
|
|
1087
|
+
});
|
|
1088
|
+
const milestone = {
|
|
1089
|
+
id: `m_${randomUUID3().slice(0, 8)}`,
|
|
1090
|
+
session_id: sessionId,
|
|
1091
|
+
title: m.title,
|
|
1092
|
+
category: m.category,
|
|
1093
|
+
complexity: m.complexity ?? "medium",
|
|
1094
|
+
duration_minutes: durationMinutes,
|
|
1095
|
+
languages: languages ?? [],
|
|
1096
|
+
client: clientName,
|
|
1097
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1098
|
+
published: false,
|
|
1099
|
+
published_at: null,
|
|
1100
|
+
chain_hash: record.hash
|
|
1101
|
+
};
|
|
1102
|
+
allMilestones.push(milestone);
|
|
1103
|
+
milestoneCount++;
|
|
305
1104
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
content: [
|
|
309
|
-
{
|
|
310
|
-
type: "text",
|
|
311
|
-
text: `Your workflow is ready to share!\n\n` +
|
|
312
|
-
`Link: ${data.workflow_url ?? "https://useai.dev/workflows"}\n\n` +
|
|
313
|
-
`Share on:\n` +
|
|
314
|
-
`- Twitter: https://twitter.com/intent/tweet?text=${encodeURIComponent(data.share_text ?? "Check out my AI development workflow")}&url=${encodeURIComponent(data.workflow_url ?? "https://useai.dev")}\n` +
|
|
315
|
-
`- LinkedIn: https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(data.workflow_url ?? "https://useai.dev")}`,
|
|
316
|
-
},
|
|
317
|
-
],
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
return {
|
|
322
|
-
content: [
|
|
323
|
-
{
|
|
324
|
-
type: "text",
|
|
325
|
-
text: "Could not reach useai.dev. Check your connection.",
|
|
326
|
-
},
|
|
327
|
-
],
|
|
328
|
-
};
|
|
1105
|
+
writeJson(MILESTONES_FILE, allMilestones);
|
|
1106
|
+
}
|
|
329
1107
|
}
|
|
330
|
-
|
|
331
|
-
|
|
1108
|
+
const durationStr = formatDuration(duration);
|
|
1109
|
+
const langStr = languages && languages.length > 0 ? ` using ${languages.join(", ")}` : "";
|
|
1110
|
+
const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
|
|
1111
|
+
return {
|
|
1112
|
+
content: [
|
|
1113
|
+
{
|
|
1114
|
+
type: "text",
|
|
1115
|
+
text: `Session sealed: ${durationStr} ${finalTaskType} on ${clientName}${langStr}
|
|
1116
|
+
${sessionRecordCount} chain records \xB7 ${signingAvailable ? "Ed25519 signed" : "unsigned"}${milestoneStr}
|
|
1117
|
+
Stored at: ${sealedPath}
|
|
1118
|
+
|
|
1119
|
+
To publish: run \`useai sync\` from your terminal.`
|
|
1120
|
+
}
|
|
1121
|
+
]
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
);
|
|
332
1125
|
async function main() {
|
|
333
|
-
|
|
334
|
-
|
|
1126
|
+
ensureDir();
|
|
1127
|
+
try {
|
|
1128
|
+
initializeKeystore();
|
|
1129
|
+
} catch {
|
|
1130
|
+
}
|
|
1131
|
+
const transport = new StdioServerTransport();
|
|
1132
|
+
await server.connect(transport);
|
|
335
1133
|
}
|
|
336
1134
|
main().catch((error) => {
|
|
337
|
-
|
|
338
|
-
|
|
1135
|
+
console.error("useai MCP server failed to start:", error);
|
|
1136
|
+
process.exit(1);
|
|
339
1137
|
});
|
|
340
|
-
//# sourceMappingURL=index.js.map
|