@devness/useai-cli 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/LICENSE +21 -0
- package/dist/index.js +971 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 useai.dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command8 } from "commander";
|
|
5
|
+
|
|
6
|
+
// ../shared/dist/constants/paths.js
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
var USEAI_DIR = join(homedir(), ".useai");
|
|
10
|
+
var DATA_DIR = join(USEAI_DIR, "data");
|
|
11
|
+
var ACTIVE_DIR = join(DATA_DIR, "active");
|
|
12
|
+
var SEALED_DIR = join(DATA_DIR, "sealed");
|
|
13
|
+
var KEYSTORE_FILE = join(USEAI_DIR, "keystore.json");
|
|
14
|
+
var CONFIG_FILE = join(USEAI_DIR, "config.json");
|
|
15
|
+
var SESSIONS_FILE = join(DATA_DIR, "sessions.json");
|
|
16
|
+
var MILESTONES_FILE = join(DATA_DIR, "milestones.json");
|
|
17
|
+
|
|
18
|
+
// ../shared/dist/constants/version.js
|
|
19
|
+
var VERSION = "0.3.0";
|
|
20
|
+
|
|
21
|
+
// ../shared/dist/constants/defaults.js
|
|
22
|
+
var DEFAULT_CONFIG = {
|
|
23
|
+
milestone_tracking: true,
|
|
24
|
+
auto_sync: true
|
|
25
|
+
};
|
|
26
|
+
var DEFAULT_SYNC_INTERVAL_HOURS = 24;
|
|
27
|
+
|
|
28
|
+
// src/commands/stats.ts
|
|
29
|
+
import { Command } from "commander";
|
|
30
|
+
import chalk2 from "chalk";
|
|
31
|
+
|
|
32
|
+
// ../shared/dist/utils/fs.js
|
|
33
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "fs";
|
|
34
|
+
function ensureDir() {
|
|
35
|
+
for (const dir of [USEAI_DIR, DATA_DIR, ACTIVE_DIR, SEALED_DIR]) {
|
|
36
|
+
if (!existsSync(dir)) {
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readJson(path, fallback) {
|
|
42
|
+
try {
|
|
43
|
+
if (!existsSync(path))
|
|
44
|
+
return fallback;
|
|
45
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
46
|
+
} catch {
|
|
47
|
+
return fallback;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function writeJson(path, data) {
|
|
51
|
+
ensureDir();
|
|
52
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
53
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
54
|
+
renameSync(tmp, path);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ../shared/dist/utils/format.js
|
|
58
|
+
function formatDuration(seconds) {
|
|
59
|
+
if (seconds < 60)
|
|
60
|
+
return `${seconds}s`;
|
|
61
|
+
const mins = Math.round(seconds / 60);
|
|
62
|
+
if (mins < 60)
|
|
63
|
+
return `${mins}m`;
|
|
64
|
+
const hrs = Math.floor(mins / 60);
|
|
65
|
+
const remainMins = mins % 60;
|
|
66
|
+
return `${hrs}h ${remainMins}m`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ../shared/dist/utils/id.js
|
|
70
|
+
import { randomUUID } from "crypto";
|
|
71
|
+
|
|
72
|
+
// src/services/stats.service.ts
|
|
73
|
+
function getStats() {
|
|
74
|
+
const sessions = readJson(SESSIONS_FILE, []);
|
|
75
|
+
let totalSeconds = 0;
|
|
76
|
+
const byClient = {};
|
|
77
|
+
const byLanguage = {};
|
|
78
|
+
const byTaskType = {};
|
|
79
|
+
for (const s of sessions) {
|
|
80
|
+
totalSeconds += s.duration_seconds;
|
|
81
|
+
byClient[s.client] = (byClient[s.client] ?? 0) + s.duration_seconds;
|
|
82
|
+
for (const lang of s.languages) {
|
|
83
|
+
byLanguage[lang] = (byLanguage[lang] ?? 0) + s.duration_seconds;
|
|
84
|
+
}
|
|
85
|
+
byTaskType[s.task_type] = (byTaskType[s.task_type] ?? 0) + s.duration_seconds;
|
|
86
|
+
}
|
|
87
|
+
const currentStreak = calculateStreak(sessions);
|
|
88
|
+
return {
|
|
89
|
+
totalHours: totalSeconds / 3600,
|
|
90
|
+
totalSessions: sessions.length,
|
|
91
|
+
byClient,
|
|
92
|
+
byLanguage,
|
|
93
|
+
byTaskType,
|
|
94
|
+
currentStreak
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function calculateStreak(sessions) {
|
|
98
|
+
if (sessions.length === 0) return 0;
|
|
99
|
+
const days = /* @__PURE__ */ new Set();
|
|
100
|
+
for (const s of sessions) {
|
|
101
|
+
days.add(s.started_at.slice(0, 10));
|
|
102
|
+
}
|
|
103
|
+
const sorted = [...days].sort().reverse();
|
|
104
|
+
if (sorted.length === 0) return 0;
|
|
105
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
106
|
+
const yesterday = new Date(Date.now() - 864e5).toISOString().slice(0, 10);
|
|
107
|
+
if (sorted[0] !== today && sorted[0] !== yesterday) return 0;
|
|
108
|
+
let streak = 1;
|
|
109
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
110
|
+
const prev = new Date(sorted[i - 1]);
|
|
111
|
+
const curr = new Date(sorted[i]);
|
|
112
|
+
const diffDays = (prev.getTime() - curr.getTime()) / 864e5;
|
|
113
|
+
if (diffDays === 1) {
|
|
114
|
+
streak++;
|
|
115
|
+
} else {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return streak;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/utils/display.ts
|
|
123
|
+
import chalk from "chalk";
|
|
124
|
+
function header(text) {
|
|
125
|
+
return chalk.bold.cyan(`
|
|
126
|
+
${text}
|
|
127
|
+
${"\u2500".repeat(text.length)}`);
|
|
128
|
+
}
|
|
129
|
+
function table(rows) {
|
|
130
|
+
const maxLabel = Math.max(...rows.map(([label]) => label.length));
|
|
131
|
+
return rows.map(([label, value]) => ` ${chalk.dim(label.padEnd(maxLabel))} ${value}`).join("\n");
|
|
132
|
+
}
|
|
133
|
+
function success(text) {
|
|
134
|
+
return chalk.green(` ${text}`);
|
|
135
|
+
}
|
|
136
|
+
function error(text) {
|
|
137
|
+
return chalk.red(` ${text}`);
|
|
138
|
+
}
|
|
139
|
+
function info(text) {
|
|
140
|
+
return chalk.dim(` ${text}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/commands/stats.ts
|
|
144
|
+
var statsCommand = new Command("stats").description("Show aggregated AI development stats").action(() => {
|
|
145
|
+
const stats = getStats();
|
|
146
|
+
if (stats.totalSessions === 0) {
|
|
147
|
+
console.log(info("No sessions recorded yet."));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(header("AI Development Stats"));
|
|
151
|
+
console.log(
|
|
152
|
+
table([
|
|
153
|
+
["Total time", chalk2.bold(formatDuration(Math.round(stats.totalHours * 3600)))],
|
|
154
|
+
["Sessions", chalk2.bold(String(stats.totalSessions))],
|
|
155
|
+
["Current streak", chalk2.bold(`${stats.currentStreak} day${stats.currentStreak !== 1 ? "s" : ""}`)]
|
|
156
|
+
])
|
|
157
|
+
);
|
|
158
|
+
const clientEntries = Object.entries(stats.byClient).sort((a, b) => b[1] - a[1]);
|
|
159
|
+
if (clientEntries.length > 0) {
|
|
160
|
+
console.log(header("By Client"));
|
|
161
|
+
console.log(
|
|
162
|
+
table(clientEntries.map(([name, secs]) => [name, formatDuration(secs)]))
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const langEntries = Object.entries(stats.byLanguage).sort((a, b) => b[1] - a[1]);
|
|
166
|
+
if (langEntries.length > 0) {
|
|
167
|
+
console.log(header("By Language"));
|
|
168
|
+
console.log(
|
|
169
|
+
table(langEntries.map(([name, secs]) => [name, formatDuration(secs)]))
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const taskEntries = Object.entries(stats.byTaskType).sort((a, b) => b[1] - a[1]);
|
|
173
|
+
if (taskEntries.length > 0) {
|
|
174
|
+
console.log(header("By Task Type"));
|
|
175
|
+
console.log(
|
|
176
|
+
table(taskEntries.map(([name, secs]) => [name, formatDuration(secs)]))
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
console.log("");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// src/commands/status.ts
|
|
183
|
+
import { Command as Command2 } from "commander";
|
|
184
|
+
import { existsSync as existsSync2, statSync, readdirSync } from "fs";
|
|
185
|
+
import chalk3 from "chalk";
|
|
186
|
+
|
|
187
|
+
// src/services/config.service.ts
|
|
188
|
+
var DEFAULT_USEAI_CONFIG = {
|
|
189
|
+
...DEFAULT_CONFIG,
|
|
190
|
+
sync_interval_hours: DEFAULT_SYNC_INTERVAL_HOURS
|
|
191
|
+
};
|
|
192
|
+
function getConfig() {
|
|
193
|
+
return readJson(CONFIG_FILE, DEFAULT_USEAI_CONFIG);
|
|
194
|
+
}
|
|
195
|
+
function saveConfig(config) {
|
|
196
|
+
writeJson(CONFIG_FILE, config);
|
|
197
|
+
}
|
|
198
|
+
function updateConfig(updates) {
|
|
199
|
+
const config = getConfig();
|
|
200
|
+
const updated = { ...config, ...updates };
|
|
201
|
+
saveConfig(updated);
|
|
202
|
+
return updated;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/commands/status.ts
|
|
206
|
+
function dirSize(dirPath) {
|
|
207
|
+
if (!existsSync2(dirPath)) return 0;
|
|
208
|
+
let total = 0;
|
|
209
|
+
try {
|
|
210
|
+
const entries = readdirSync(dirPath, { withFileTypes: true, recursive: true });
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
if (entry.isFile()) {
|
|
213
|
+
const parentPath = entry.parentPath ?? entry.path ?? dirPath;
|
|
214
|
+
try {
|
|
215
|
+
total += statSync(`${parentPath}/${entry.name}`).size;
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
}
|
|
222
|
+
return total;
|
|
223
|
+
}
|
|
224
|
+
function formatBytes(bytes) {
|
|
225
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
226
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
227
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
228
|
+
}
|
|
229
|
+
var statusCommand = new Command2("status").description("Full transparency dashboard \u2014 see everything stored locally").action(() => {
|
|
230
|
+
const sessions = readJson(SESSIONS_FILE, []);
|
|
231
|
+
const milestones = readJson(MILESTONES_FILE, []);
|
|
232
|
+
const config = getConfig();
|
|
233
|
+
const totalSeconds = sessions.reduce((sum, s) => sum + s.duration_seconds, 0);
|
|
234
|
+
const unpublished = milestones.filter((m) => !m.published).length;
|
|
235
|
+
const published = milestones.filter((m) => m.published).length;
|
|
236
|
+
const storageSize = dirSize(USEAI_DIR);
|
|
237
|
+
console.log(header("useai Status"));
|
|
238
|
+
console.log(
|
|
239
|
+
table([
|
|
240
|
+
["Sessions recorded", chalk3.bold(String(sessions.length))],
|
|
241
|
+
["Total tracked time", chalk3.bold(formatDuration(totalSeconds))],
|
|
242
|
+
["Milestones", chalk3.bold(`${unpublished} unpublished, ${published} published`)],
|
|
243
|
+
["Local storage", chalk3.bold(formatBytes(storageSize))],
|
|
244
|
+
["Data directory", chalk3.dim(DATA_DIR)]
|
|
245
|
+
])
|
|
246
|
+
);
|
|
247
|
+
console.log(header("Settings"));
|
|
248
|
+
console.log(
|
|
249
|
+
table([
|
|
250
|
+
["Milestone tracking", config.milestone_tracking ? chalk3.green("on") : chalk3.red("off")],
|
|
251
|
+
["Auto sync", config.auto_sync ? chalk3.green("on") : chalk3.red("off")],
|
|
252
|
+
["Sync interval", `${config.sync_interval_hours}h`],
|
|
253
|
+
["Last sync", config.last_sync_at ?? chalk3.dim("never")],
|
|
254
|
+
["Logged in", config.auth ? chalk3.green(config.auth.user.email) : chalk3.dim("no")]
|
|
255
|
+
])
|
|
256
|
+
);
|
|
257
|
+
console.log(header("Privacy"));
|
|
258
|
+
console.log(
|
|
259
|
+
info("useai NEVER captures code, file contents, prompts, or responses.")
|
|
260
|
+
);
|
|
261
|
+
console.log(
|
|
262
|
+
info("Only durations, tool names, languages, and task types are recorded.")
|
|
263
|
+
);
|
|
264
|
+
console.log("");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// src/commands/milestones.ts
|
|
268
|
+
import { Command as Command3 } from "commander";
|
|
269
|
+
import chalk4 from "chalk";
|
|
270
|
+
var milestonesCommand = new Command3("milestones").description("List local milestones").option("-v, --verbose", "Show full milestone details").action((opts) => {
|
|
271
|
+
const milestones = readJson(MILESTONES_FILE, []);
|
|
272
|
+
if (milestones.length === 0) {
|
|
273
|
+
console.log(info("No milestones recorded yet."));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
console.log(header(`Milestones (${milestones.length})`));
|
|
277
|
+
for (const m of milestones) {
|
|
278
|
+
const status = m.published ? chalk4.green("published") : chalk4.yellow("local");
|
|
279
|
+
console.log(
|
|
280
|
+
`
|
|
281
|
+
${chalk4.bold(m.title)} ${status}`
|
|
282
|
+
);
|
|
283
|
+
console.log(
|
|
284
|
+
table([
|
|
285
|
+
["Category", m.category],
|
|
286
|
+
["Complexity", m.complexity],
|
|
287
|
+
["Duration", formatDuration(m.duration_minutes * 60)],
|
|
288
|
+
["Date", m.created_at.slice(0, 10)]
|
|
289
|
+
])
|
|
290
|
+
);
|
|
291
|
+
if (opts.verbose) {
|
|
292
|
+
console.log(
|
|
293
|
+
table([
|
|
294
|
+
["ID", chalk4.dim(m.id)],
|
|
295
|
+
["Session", chalk4.dim(m.session_id)],
|
|
296
|
+
["Client", m.client],
|
|
297
|
+
["Languages", m.languages.join(", ") || "none"],
|
|
298
|
+
["Chain hash", chalk4.dim(m.chain_hash.slice(0, 16) + "...")],
|
|
299
|
+
["Published at", m.published_at ?? "n/a"]
|
|
300
|
+
])
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
console.log("");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// src/commands/config.ts
|
|
308
|
+
import { Command as Command4 } from "commander";
|
|
309
|
+
import chalk5 from "chalk";
|
|
310
|
+
var configCommand = new Command4("config").description("View or update settings").option("--sync", "Enable auto-sync").option("--no-sync", "Disable auto-sync").option("--milestones", "Enable milestone tracking").option("--no-milestones", "Disable milestone tracking").action(() => {
|
|
311
|
+
let changed = false;
|
|
312
|
+
if (process.argv.includes("--no-sync")) {
|
|
313
|
+
updateConfig({ auto_sync: false });
|
|
314
|
+
console.log(success("Auto-sync disabled."));
|
|
315
|
+
changed = true;
|
|
316
|
+
} else if (process.argv.includes("--sync")) {
|
|
317
|
+
updateConfig({ auto_sync: true });
|
|
318
|
+
console.log(success("Auto-sync enabled."));
|
|
319
|
+
changed = true;
|
|
320
|
+
}
|
|
321
|
+
if (process.argv.includes("--no-milestones")) {
|
|
322
|
+
updateConfig({ milestone_tracking: false });
|
|
323
|
+
console.log(success("Milestone tracking disabled."));
|
|
324
|
+
changed = true;
|
|
325
|
+
} else if (process.argv.includes("--milestones")) {
|
|
326
|
+
updateConfig({ milestone_tracking: true });
|
|
327
|
+
console.log(success("Milestone tracking enabled."));
|
|
328
|
+
changed = true;
|
|
329
|
+
}
|
|
330
|
+
const config = getConfig();
|
|
331
|
+
if (!changed) {
|
|
332
|
+
console.log(header("Current Settings"));
|
|
333
|
+
console.log(
|
|
334
|
+
table([
|
|
335
|
+
["Milestone tracking", config.milestone_tracking ? chalk5.green("on") : chalk5.red("off")],
|
|
336
|
+
["Auto sync", config.auto_sync ? chalk5.green("on") : chalk5.red("off")],
|
|
337
|
+
["Sync interval", `${config.sync_interval_hours}h`],
|
|
338
|
+
["Last sync", config.last_sync_at ?? chalk5.dim("never")],
|
|
339
|
+
["Logged in", config.auth ? chalk5.green(config.auth.user.email) : chalk5.dim("no")]
|
|
340
|
+
])
|
|
341
|
+
);
|
|
342
|
+
console.log("");
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// src/commands/export.ts
|
|
347
|
+
import { Command as Command5 } from "commander";
|
|
348
|
+
var exportCommand = new Command5("export").description("Export all local data as JSON to stdout").action(() => {
|
|
349
|
+
const config = getConfig();
|
|
350
|
+
const sessions = readJson(SESSIONS_FILE, []);
|
|
351
|
+
const milestones = readJson(MILESTONES_FILE, []);
|
|
352
|
+
const data = {
|
|
353
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
354
|
+
config,
|
|
355
|
+
sessions,
|
|
356
|
+
milestones
|
|
357
|
+
};
|
|
358
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// src/commands/purge.ts
|
|
362
|
+
import { Command as Command6 } from "commander";
|
|
363
|
+
import { existsSync as existsSync3, rmSync } from "fs";
|
|
364
|
+
import { createInterface } from "readline";
|
|
365
|
+
function confirm(question) {
|
|
366
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
367
|
+
return new Promise((resolve) => {
|
|
368
|
+
rl.question(question, (answer) => {
|
|
369
|
+
rl.close();
|
|
370
|
+
resolve(answer.trim().toLowerCase() === "yes");
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
var purgeCommand = new Command6("purge").description("Delete ALL local useai data").option("-y, --yes", "Skip confirmation").action(async (opts) => {
|
|
375
|
+
if (!existsSync3(USEAI_DIR)) {
|
|
376
|
+
console.log(info("No useai data directory found. Nothing to purge."));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (!opts.yes) {
|
|
380
|
+
console.log(error(`This will permanently delete all data in ${USEAI_DIR}`));
|
|
381
|
+
const confirmed = await confirm(' Type "yes" to confirm: ');
|
|
382
|
+
if (!confirmed) {
|
|
383
|
+
console.log(info("Purge cancelled."));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
rmSync(USEAI_DIR, { recursive: true, force: true });
|
|
388
|
+
console.log(success("All local useai data has been deleted."));
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// src/commands/setup.ts
|
|
392
|
+
import { Command as Command7 } from "commander";
|
|
393
|
+
import { checkbox } from "@inquirer/prompts";
|
|
394
|
+
import chalk6 from "chalk";
|
|
395
|
+
|
|
396
|
+
// src/services/tools.ts
|
|
397
|
+
import { execSync } from "child_process";
|
|
398
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
399
|
+
import { dirname, join as join2 } from "path";
|
|
400
|
+
import { homedir as homedir2 } from "os";
|
|
401
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
402
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
403
|
+
var MCP_ENTRY = {
|
|
404
|
+
command: "npx",
|
|
405
|
+
args: ["-y", "@devness/useai"]
|
|
406
|
+
};
|
|
407
|
+
var home = homedir2();
|
|
408
|
+
function hasBinary(name) {
|
|
409
|
+
try {
|
|
410
|
+
execSync(`which ${name}`, { stdio: "ignore" });
|
|
411
|
+
return true;
|
|
412
|
+
} catch {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function readJsonFile(path) {
|
|
417
|
+
if (!existsSync4(path)) return {};
|
|
418
|
+
try {
|
|
419
|
+
const raw = readFileSync2(path, "utf-8").trim();
|
|
420
|
+
if (!raw) return {};
|
|
421
|
+
return JSON.parse(raw);
|
|
422
|
+
} catch {
|
|
423
|
+
return {};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function writeJsonFile(path, data) {
|
|
427
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
428
|
+
writeFileSync2(path, JSON.stringify(data, null, 2) + "\n");
|
|
429
|
+
}
|
|
430
|
+
function isConfiguredStandard(configPath) {
|
|
431
|
+
const config = readJsonFile(configPath);
|
|
432
|
+
const servers = config["mcpServers"];
|
|
433
|
+
return !!servers?.["useai"];
|
|
434
|
+
}
|
|
435
|
+
function installStandard(configPath) {
|
|
436
|
+
const config = readJsonFile(configPath);
|
|
437
|
+
const servers = config["mcpServers"] ?? {};
|
|
438
|
+
servers["useai"] = { ...MCP_ENTRY };
|
|
439
|
+
config["mcpServers"] = servers;
|
|
440
|
+
writeJsonFile(configPath, config);
|
|
441
|
+
}
|
|
442
|
+
function removeStandard(configPath) {
|
|
443
|
+
const config = readJsonFile(configPath);
|
|
444
|
+
const servers = config["mcpServers"];
|
|
445
|
+
if (servers) {
|
|
446
|
+
delete servers["useai"];
|
|
447
|
+
if (Object.keys(servers).length === 0) {
|
|
448
|
+
delete config["mcpServers"];
|
|
449
|
+
}
|
|
450
|
+
writeJsonFile(configPath, config);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function isConfiguredVscode(configPath) {
|
|
454
|
+
const config = readJsonFile(configPath);
|
|
455
|
+
const servers = config["servers"];
|
|
456
|
+
return !!servers?.["useai"];
|
|
457
|
+
}
|
|
458
|
+
function installVscode(configPath) {
|
|
459
|
+
const config = readJsonFile(configPath);
|
|
460
|
+
const servers = config["servers"] ?? {};
|
|
461
|
+
servers["useai"] = { command: MCP_ENTRY.command, args: MCP_ENTRY.args };
|
|
462
|
+
config["servers"] = servers;
|
|
463
|
+
writeJsonFile(configPath, config);
|
|
464
|
+
}
|
|
465
|
+
function removeVscode(configPath) {
|
|
466
|
+
const config = readJsonFile(configPath);
|
|
467
|
+
const servers = config["servers"];
|
|
468
|
+
if (servers) {
|
|
469
|
+
delete servers["useai"];
|
|
470
|
+
if (Object.keys(servers).length === 0) {
|
|
471
|
+
delete config["servers"];
|
|
472
|
+
}
|
|
473
|
+
writeJsonFile(configPath, config);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function isConfiguredZed(configPath) {
|
|
477
|
+
const config = readJsonFile(configPath);
|
|
478
|
+
const servers = config["context_servers"];
|
|
479
|
+
return !!servers?.["useai"];
|
|
480
|
+
}
|
|
481
|
+
function installZed(configPath) {
|
|
482
|
+
const config = readJsonFile(configPath);
|
|
483
|
+
const servers = config["context_servers"] ?? {};
|
|
484
|
+
servers["useai"] = {
|
|
485
|
+
command: { path: MCP_ENTRY.command, args: MCP_ENTRY.args },
|
|
486
|
+
settings: {}
|
|
487
|
+
};
|
|
488
|
+
config["context_servers"] = servers;
|
|
489
|
+
writeJsonFile(configPath, config);
|
|
490
|
+
}
|
|
491
|
+
function removeZed(configPath) {
|
|
492
|
+
const config = readJsonFile(configPath);
|
|
493
|
+
const servers = config["context_servers"];
|
|
494
|
+
if (servers) {
|
|
495
|
+
delete servers["useai"];
|
|
496
|
+
if (Object.keys(servers).length === 0) {
|
|
497
|
+
delete config["context_servers"];
|
|
498
|
+
}
|
|
499
|
+
writeJsonFile(configPath, config);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function readTomlFile(path) {
|
|
503
|
+
if (!existsSync4(path)) return {};
|
|
504
|
+
try {
|
|
505
|
+
const raw = readFileSync2(path, "utf-8").trim();
|
|
506
|
+
if (!raw) return {};
|
|
507
|
+
return parseToml(raw);
|
|
508
|
+
} catch {
|
|
509
|
+
return {};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function writeTomlFile(path, data) {
|
|
513
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
514
|
+
writeFileSync2(path, stringifyToml(data) + "\n");
|
|
515
|
+
}
|
|
516
|
+
function isConfiguredToml(configPath) {
|
|
517
|
+
const config = readTomlFile(configPath);
|
|
518
|
+
const servers = config["mcp_servers"];
|
|
519
|
+
return !!servers?.["useai"];
|
|
520
|
+
}
|
|
521
|
+
function installToml(configPath) {
|
|
522
|
+
const config = readTomlFile(configPath);
|
|
523
|
+
const servers = config["mcp_servers"] ?? {};
|
|
524
|
+
servers["useai"] = { command: MCP_ENTRY.command, args: MCP_ENTRY.args };
|
|
525
|
+
config["mcp_servers"] = servers;
|
|
526
|
+
writeTomlFile(configPath, config);
|
|
527
|
+
}
|
|
528
|
+
function removeToml(configPath) {
|
|
529
|
+
const config = readTomlFile(configPath);
|
|
530
|
+
const servers = config["mcp_servers"];
|
|
531
|
+
if (servers) {
|
|
532
|
+
delete servers["useai"];
|
|
533
|
+
if (Object.keys(servers).length === 0) {
|
|
534
|
+
delete config["mcp_servers"];
|
|
535
|
+
}
|
|
536
|
+
writeTomlFile(configPath, config);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function readYamlFile(path) {
|
|
540
|
+
if (!existsSync4(path)) return {};
|
|
541
|
+
try {
|
|
542
|
+
const raw = readFileSync2(path, "utf-8").trim();
|
|
543
|
+
if (!raw) return {};
|
|
544
|
+
return parseYaml(raw) ?? {};
|
|
545
|
+
} catch {
|
|
546
|
+
return {};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function writeYamlFile(path, data) {
|
|
550
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
551
|
+
writeFileSync2(path, stringifyYaml(data));
|
|
552
|
+
}
|
|
553
|
+
function isConfiguredYaml(configPath) {
|
|
554
|
+
const config = readYamlFile(configPath);
|
|
555
|
+
const extensions = config["extensions"];
|
|
556
|
+
return !!extensions?.["useai"];
|
|
557
|
+
}
|
|
558
|
+
function installYaml(configPath) {
|
|
559
|
+
const config = readYamlFile(configPath);
|
|
560
|
+
const extensions = config["extensions"] ?? {};
|
|
561
|
+
extensions["useai"] = {
|
|
562
|
+
name: "useai",
|
|
563
|
+
cmd: MCP_ENTRY.command,
|
|
564
|
+
args: MCP_ENTRY.args,
|
|
565
|
+
enabled: true,
|
|
566
|
+
type: "stdio"
|
|
567
|
+
};
|
|
568
|
+
config["extensions"] = extensions;
|
|
569
|
+
writeYamlFile(configPath, config);
|
|
570
|
+
}
|
|
571
|
+
function removeYaml(configPath) {
|
|
572
|
+
const config = readYamlFile(configPath);
|
|
573
|
+
const extensions = config["extensions"];
|
|
574
|
+
if (extensions) {
|
|
575
|
+
delete extensions["useai"];
|
|
576
|
+
if (Object.keys(extensions).length === 0) {
|
|
577
|
+
delete config["extensions"];
|
|
578
|
+
}
|
|
579
|
+
writeYamlFile(configPath, config);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
var formatHandlers = {
|
|
583
|
+
standard: { isConfigured: isConfiguredStandard, install: installStandard, remove: removeStandard },
|
|
584
|
+
vscode: { isConfigured: isConfiguredVscode, install: installVscode, remove: removeVscode },
|
|
585
|
+
zed: { isConfigured: isConfiguredZed, install: installZed, remove: removeZed },
|
|
586
|
+
toml: { isConfigured: isConfiguredToml, install: installToml, remove: removeToml },
|
|
587
|
+
yaml: { isConfigured: isConfiguredYaml, install: installYaml, remove: removeYaml }
|
|
588
|
+
};
|
|
589
|
+
function createTool(def) {
|
|
590
|
+
const handler = formatHandlers[def.configFormat];
|
|
591
|
+
return {
|
|
592
|
+
id: def.id,
|
|
593
|
+
name: def.name,
|
|
594
|
+
configFormat: def.configFormat,
|
|
595
|
+
getConfigPath: () => def.configPath,
|
|
596
|
+
detect: def.detect,
|
|
597
|
+
isConfigured: () => handler.isConfigured(def.configPath),
|
|
598
|
+
install: () => handler.install(def.configPath),
|
|
599
|
+
remove: () => handler.remove(def.configPath)
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
var appSupport = join2(home, "Library", "Application Support");
|
|
603
|
+
function matchesTool(tool, query) {
|
|
604
|
+
const q = query.toLowerCase().replace(/[\s-_]+/g, "");
|
|
605
|
+
const id = tool.id.toLowerCase().replace(/[\s-_]+/g, "");
|
|
606
|
+
const name = tool.name.toLowerCase().replace(/[\s-_]+/g, "");
|
|
607
|
+
return id === q || name === q || id.includes(q) || name.includes(q);
|
|
608
|
+
}
|
|
609
|
+
function resolveTools(names) {
|
|
610
|
+
const matched = [];
|
|
611
|
+
const unmatched = [];
|
|
612
|
+
for (const name of names) {
|
|
613
|
+
const found = AI_TOOLS.filter((t) => matchesTool(t, name));
|
|
614
|
+
if (found.length > 0) {
|
|
615
|
+
for (const f of found) {
|
|
616
|
+
if (!matched.includes(f)) matched.push(f);
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
unmatched.push(name);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return { matched, unmatched };
|
|
623
|
+
}
|
|
624
|
+
var AI_TOOLS = [
|
|
625
|
+
createTool({
|
|
626
|
+
id: "claude-code",
|
|
627
|
+
name: "Claude Code",
|
|
628
|
+
configFormat: "standard",
|
|
629
|
+
configPath: join2(home, ".claude.json"),
|
|
630
|
+
detect: () => hasBinary("claude") || existsSync4(join2(home, ".claude.json"))
|
|
631
|
+
}),
|
|
632
|
+
createTool({
|
|
633
|
+
id: "claude-desktop",
|
|
634
|
+
name: "Claude Desktop",
|
|
635
|
+
configFormat: "standard",
|
|
636
|
+
configPath: join2(appSupport, "Claude", "claude_desktop_config.json"),
|
|
637
|
+
detect: () => existsSync4(join2(appSupport, "Claude")) || existsSync4("/Applications/Claude.app")
|
|
638
|
+
}),
|
|
639
|
+
createTool({
|
|
640
|
+
id: "cursor",
|
|
641
|
+
name: "Cursor",
|
|
642
|
+
configFormat: "standard",
|
|
643
|
+
configPath: join2(home, ".cursor", "mcp.json"),
|
|
644
|
+
detect: () => existsSync4(join2(home, ".cursor"))
|
|
645
|
+
}),
|
|
646
|
+
createTool({
|
|
647
|
+
id: "windsurf",
|
|
648
|
+
name: "Windsurf",
|
|
649
|
+
configFormat: "standard",
|
|
650
|
+
configPath: join2(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
651
|
+
detect: () => existsSync4(join2(home, ".codeium", "windsurf"))
|
|
652
|
+
}),
|
|
653
|
+
createTool({
|
|
654
|
+
id: "vscode",
|
|
655
|
+
name: "VS Code",
|
|
656
|
+
configFormat: "vscode",
|
|
657
|
+
configPath: join2(appSupport, "Code", "User", "mcp.json"),
|
|
658
|
+
detect: () => existsSync4(join2(appSupport, "Code"))
|
|
659
|
+
}),
|
|
660
|
+
createTool({
|
|
661
|
+
id: "vscode-insiders",
|
|
662
|
+
name: "VS Code Insiders",
|
|
663
|
+
configFormat: "vscode",
|
|
664
|
+
configPath: join2(appSupport, "Code - Insiders", "User", "mcp.json"),
|
|
665
|
+
detect: () => existsSync4(join2(appSupport, "Code - Insiders"))
|
|
666
|
+
}),
|
|
667
|
+
createTool({
|
|
668
|
+
id: "gemini-cli",
|
|
669
|
+
name: "Gemini CLI",
|
|
670
|
+
configFormat: "standard",
|
|
671
|
+
configPath: join2(home, ".gemini", "settings.json"),
|
|
672
|
+
detect: () => hasBinary("gemini")
|
|
673
|
+
}),
|
|
674
|
+
createTool({
|
|
675
|
+
id: "zed",
|
|
676
|
+
name: "Zed",
|
|
677
|
+
configFormat: "zed",
|
|
678
|
+
configPath: join2(appSupport, "Zed", "settings.json"),
|
|
679
|
+
detect: () => existsSync4(join2(appSupport, "Zed"))
|
|
680
|
+
}),
|
|
681
|
+
createTool({
|
|
682
|
+
id: "cline",
|
|
683
|
+
name: "Cline",
|
|
684
|
+
configFormat: "standard",
|
|
685
|
+
configPath: join2(
|
|
686
|
+
appSupport,
|
|
687
|
+
"Code",
|
|
688
|
+
"User",
|
|
689
|
+
"globalStorage",
|
|
690
|
+
"saoudrizwan.claude-dev",
|
|
691
|
+
"settings",
|
|
692
|
+
"cline_mcp_settings.json"
|
|
693
|
+
),
|
|
694
|
+
detect: () => existsSync4(
|
|
695
|
+
join2(appSupport, "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
|
|
696
|
+
)
|
|
697
|
+
}),
|
|
698
|
+
createTool({
|
|
699
|
+
id: "roo-code",
|
|
700
|
+
name: "Roo Code",
|
|
701
|
+
configFormat: "standard",
|
|
702
|
+
configPath: join2(
|
|
703
|
+
appSupport,
|
|
704
|
+
"Code",
|
|
705
|
+
"User",
|
|
706
|
+
"globalStorage",
|
|
707
|
+
"rooveterinaryinc.roo-cline",
|
|
708
|
+
"settings",
|
|
709
|
+
"cline_mcp_settings.json"
|
|
710
|
+
),
|
|
711
|
+
detect: () => existsSync4(
|
|
712
|
+
join2(appSupport, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline")
|
|
713
|
+
)
|
|
714
|
+
}),
|
|
715
|
+
createTool({
|
|
716
|
+
id: "amazon-q-cli",
|
|
717
|
+
name: "Amazon Q CLI",
|
|
718
|
+
configFormat: "standard",
|
|
719
|
+
configPath: join2(home, ".aws", "amazonq", "mcp.json"),
|
|
720
|
+
detect: () => hasBinary("q") || existsSync4(join2(home, ".aws", "amazonq"))
|
|
721
|
+
}),
|
|
722
|
+
createTool({
|
|
723
|
+
id: "amazon-q-ide",
|
|
724
|
+
name: "Amazon Q IDE",
|
|
725
|
+
configFormat: "standard",
|
|
726
|
+
configPath: join2(home, ".aws", "amazonq", "default.json"),
|
|
727
|
+
detect: () => existsSync4(join2(home, ".amazonq")) || existsSync4(join2(home, ".aws", "amazonq"))
|
|
728
|
+
}),
|
|
729
|
+
createTool({
|
|
730
|
+
id: "codex",
|
|
731
|
+
name: "Codex",
|
|
732
|
+
configFormat: "toml",
|
|
733
|
+
configPath: join2(home, ".codex", "config.toml"),
|
|
734
|
+
detect: () => hasBinary("codex") || existsSync4(join2(home, ".codex")) || existsSync4("/Applications/Codex.app")
|
|
735
|
+
}),
|
|
736
|
+
createTool({
|
|
737
|
+
id: "goose",
|
|
738
|
+
name: "Goose",
|
|
739
|
+
configFormat: "yaml",
|
|
740
|
+
configPath: join2(home, ".config", "goose", "config.yaml"),
|
|
741
|
+
detect: () => existsSync4(join2(home, ".config", "goose"))
|
|
742
|
+
}),
|
|
743
|
+
createTool({
|
|
744
|
+
id: "opencode",
|
|
745
|
+
name: "OpenCode",
|
|
746
|
+
configFormat: "standard",
|
|
747
|
+
configPath: join2(home, ".config", "opencode", "opencode.json"),
|
|
748
|
+
detect: () => hasBinary("opencode") || existsSync4(join2(home, ".config", "opencode"))
|
|
749
|
+
}),
|
|
750
|
+
createTool({
|
|
751
|
+
id: "junie",
|
|
752
|
+
name: "Junie",
|
|
753
|
+
configFormat: "standard",
|
|
754
|
+
configPath: join2(home, ".junie", "mcp", "mcp.json"),
|
|
755
|
+
detect: () => existsSync4(join2(home, ".junie"))
|
|
756
|
+
})
|
|
757
|
+
];
|
|
758
|
+
|
|
759
|
+
// src/commands/setup.ts
|
|
760
|
+
function shortenPath(p) {
|
|
761
|
+
const home2 = process.env["HOME"] ?? "";
|
|
762
|
+
return home2 && p.startsWith(home2) ? p.replace(home2, "~") : p;
|
|
763
|
+
}
|
|
764
|
+
function showStatus(tools) {
|
|
765
|
+
console.log(header("AI Tool MCP Status"));
|
|
766
|
+
const rows = [];
|
|
767
|
+
const nameWidth = Math.max(...tools.map((t) => t.name.length));
|
|
768
|
+
const statusWidth = 16;
|
|
769
|
+
for (const tool of tools) {
|
|
770
|
+
const detected = tool.detect();
|
|
771
|
+
const name = tool.name.padEnd(nameWidth);
|
|
772
|
+
if (!detected) {
|
|
773
|
+
rows.push(` ${chalk6.dim(name)} ${chalk6.dim("\u2014 Not found".padEnd(statusWidth))}`);
|
|
774
|
+
} else if (tool.isConfigured()) {
|
|
775
|
+
rows.push(
|
|
776
|
+
` ${name} ${chalk6.green("\u2713 Configured".padEnd(statusWidth))} ${chalk6.dim(shortenPath(tool.getConfigPath()))}`
|
|
777
|
+
);
|
|
778
|
+
} else {
|
|
779
|
+
rows.push(
|
|
780
|
+
` ${name} ${chalk6.yellow("\u2717 Not set up".padEnd(statusWidth))} ${chalk6.dim(shortenPath(tool.getConfigPath()))}`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
console.log(rows.join("\n"));
|
|
785
|
+
console.log();
|
|
786
|
+
}
|
|
787
|
+
async function installFlow(tools, autoYes, explicit) {
|
|
788
|
+
if (explicit) {
|
|
789
|
+
const toInstall2 = tools.filter((t) => !t.isConfigured());
|
|
790
|
+
const alreadyDone = tools.filter((t) => t.isConfigured());
|
|
791
|
+
for (const tool of alreadyDone) {
|
|
792
|
+
console.log(info(`${tool.name} is already configured.`));
|
|
793
|
+
}
|
|
794
|
+
if (toInstall2.length === 0) return;
|
|
795
|
+
console.log();
|
|
796
|
+
for (const tool of toInstall2) {
|
|
797
|
+
try {
|
|
798
|
+
tool.install();
|
|
799
|
+
console.log(success(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk6.dim(shortenPath(tool.getConfigPath()))}`));
|
|
800
|
+
} catch (err) {
|
|
801
|
+
console.log(error(`\u2717 ${tool.name.padEnd(18)} \u2014 ${err.message}`));
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
console.log();
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
console.log(info("Scanning for AI tools...\n"));
|
|
808
|
+
const detected = tools.filter((t) => t.detect());
|
|
809
|
+
if (detected.length === 0) {
|
|
810
|
+
console.log(error("No AI tools detected on this machine."));
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const alreadyConfigured = detected.filter((t) => t.isConfigured());
|
|
814
|
+
const unconfigured = detected.filter((t) => !t.isConfigured());
|
|
815
|
+
console.log(` Found ${chalk6.bold(String(detected.length))} AI tool${detected.length === 1 ? "" : "s"} on this machine:
|
|
816
|
+
`);
|
|
817
|
+
for (const tool of alreadyConfigured) {
|
|
818
|
+
console.log(chalk6.green(` \u2705 ${tool.name}`) + chalk6.dim(" (already configured)"));
|
|
819
|
+
}
|
|
820
|
+
for (const tool of unconfigured) {
|
|
821
|
+
console.log(chalk6.dim(` \u2610 ${tool.name}`));
|
|
822
|
+
}
|
|
823
|
+
console.log();
|
|
824
|
+
if (unconfigured.length === 0) {
|
|
825
|
+
console.log(success("All detected tools are already configured."));
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
let toInstall;
|
|
829
|
+
if (autoYes) {
|
|
830
|
+
toInstall = unconfigured;
|
|
831
|
+
} else {
|
|
832
|
+
const selected = await checkbox({
|
|
833
|
+
message: "Select tools to configure:",
|
|
834
|
+
choices: unconfigured.map((t) => ({
|
|
835
|
+
name: t.name,
|
|
836
|
+
value: t.id,
|
|
837
|
+
checked: true
|
|
838
|
+
}))
|
|
839
|
+
});
|
|
840
|
+
toInstall = unconfigured.filter((t) => selected.includes(t.id));
|
|
841
|
+
}
|
|
842
|
+
if (toInstall.length === 0) {
|
|
843
|
+
console.log(info("No tools selected."));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
console.log(`
|
|
847
|
+
Configuring ${toInstall.length} tool${toInstall.length === 1 ? "" : "s"}...
|
|
848
|
+
`);
|
|
849
|
+
for (const tool of toInstall) {
|
|
850
|
+
try {
|
|
851
|
+
tool.install();
|
|
852
|
+
console.log(success(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk6.dim(shortenPath(tool.getConfigPath()))}`));
|
|
853
|
+
} catch (err) {
|
|
854
|
+
console.log(error(`\u2717 ${tool.name.padEnd(18)} \u2014 ${err.message}`));
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
console.log(`
|
|
858
|
+
Done! useai MCP server configured in ${chalk6.bold(String(toInstall.length))} tool${toInstall.length === 1 ? "" : "s"}.
|
|
859
|
+
`);
|
|
860
|
+
}
|
|
861
|
+
async function removeFlow(tools, autoYes, explicit) {
|
|
862
|
+
if (explicit) {
|
|
863
|
+
const toRemove2 = tools.filter((t) => {
|
|
864
|
+
try {
|
|
865
|
+
return t.isConfigured();
|
|
866
|
+
} catch {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
const notConfigured = tools.filter((t) => {
|
|
871
|
+
try {
|
|
872
|
+
return !t.isConfigured();
|
|
873
|
+
} catch {
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
for (const tool of notConfigured) {
|
|
878
|
+
console.log(info(`${tool.name} is not configured \u2014 skipping.`));
|
|
879
|
+
}
|
|
880
|
+
if (toRemove2.length === 0) return;
|
|
881
|
+
console.log();
|
|
882
|
+
for (const tool of toRemove2) {
|
|
883
|
+
try {
|
|
884
|
+
tool.remove();
|
|
885
|
+
console.log(success(`\u2713 Removed from ${tool.name}`));
|
|
886
|
+
} catch (err) {
|
|
887
|
+
console.log(error(`\u2717 ${tool.name} \u2014 ${err.message}`));
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
console.log();
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const configured = tools.filter((t) => {
|
|
894
|
+
try {
|
|
895
|
+
return t.isConfigured();
|
|
896
|
+
} catch {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
if (configured.length === 0) {
|
|
901
|
+
console.log(info("useai is not configured in any AI tools."));
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
console.log(`
|
|
905
|
+
Found useai configured in ${chalk6.bold(String(configured.length))} tool${configured.length === 1 ? "" : "s"}:
|
|
906
|
+
`);
|
|
907
|
+
let toRemove;
|
|
908
|
+
if (autoYes) {
|
|
909
|
+
toRemove = configured;
|
|
910
|
+
} else {
|
|
911
|
+
const selected = await checkbox({
|
|
912
|
+
message: "Select tools to remove useai from:",
|
|
913
|
+
choices: configured.map((t) => ({
|
|
914
|
+
name: t.name,
|
|
915
|
+
value: t.id,
|
|
916
|
+
checked: true
|
|
917
|
+
}))
|
|
918
|
+
});
|
|
919
|
+
toRemove = configured.filter((t) => selected.includes(t.id));
|
|
920
|
+
}
|
|
921
|
+
if (toRemove.length === 0) {
|
|
922
|
+
console.log(info("No tools selected."));
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
console.log(`
|
|
926
|
+
Removing from ${toRemove.length} tool${toRemove.length === 1 ? "" : "s"}...
|
|
927
|
+
`);
|
|
928
|
+
for (const tool of toRemove) {
|
|
929
|
+
try {
|
|
930
|
+
tool.remove();
|
|
931
|
+
console.log(success(`\u2713 Removed from ${tool.name}`));
|
|
932
|
+
} catch (err) {
|
|
933
|
+
console.log(error(`\u2717 ${tool.name} \u2014 ${err.message}`));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
console.log(`
|
|
937
|
+
Done! useai removed from ${chalk6.bold(String(toRemove.length))} tool${toRemove.length === 1 ? "" : "s"}.
|
|
938
|
+
`);
|
|
939
|
+
}
|
|
940
|
+
var mcpCommand = new Command7("mcp").description("Configure useai MCP server in your AI tools").argument("[tools...]", "Specific tool names (e.g. codex cursor vscode)").option("--remove", "Remove useai from configured tools").option("--status", "Show configuration status without modifying").option("-y, --yes", "Skip confirmation, auto-select all detected tools").action(async (toolNames, opts) => {
|
|
941
|
+
const explicit = toolNames.length > 0;
|
|
942
|
+
let tools = AI_TOOLS;
|
|
943
|
+
if (explicit) {
|
|
944
|
+
const { matched, unmatched } = resolveTools(toolNames);
|
|
945
|
+
if (unmatched.length > 0) {
|
|
946
|
+
console.log(error(`Unknown tool${unmatched.length === 1 ? "" : "s"}: ${unmatched.join(", ")}`));
|
|
947
|
+
console.log(info(`Available: ${AI_TOOLS.map((t) => t.id).join(", ")}`));
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
tools = matched;
|
|
951
|
+
}
|
|
952
|
+
if (opts.status) {
|
|
953
|
+
showStatus(tools);
|
|
954
|
+
} else if (opts.remove) {
|
|
955
|
+
await removeFlow(tools, !!opts.yes, explicit);
|
|
956
|
+
} else {
|
|
957
|
+
await installFlow(tools, !!opts.yes, explicit);
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
// src/index.ts
|
|
962
|
+
var program = new Command8();
|
|
963
|
+
program.name("useai").description("useai.dev \u2014 Track your AI-assisted development workflow").version(VERSION);
|
|
964
|
+
program.addCommand(statsCommand);
|
|
965
|
+
program.addCommand(statusCommand);
|
|
966
|
+
program.addCommand(milestonesCommand);
|
|
967
|
+
program.addCommand(configCommand);
|
|
968
|
+
program.addCommand(exportCommand);
|
|
969
|
+
program.addCommand(purgeCommand);
|
|
970
|
+
program.addCommand(mcpCommand);
|
|
971
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devness/useai-cli",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "CLI tool for useai.dev — stats, sync, publish your AI development workflow",
|
|
5
|
+
"author": "nabeelkausari",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"useai": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@inquirer/prompts": "^8.2.1",
|
|
19
|
+
"chalk": "^5.4.1",
|
|
20
|
+
"commander": "^12.1.0",
|
|
21
|
+
"smol-toml": "^1.6.0",
|
|
22
|
+
"yaml": "^2.8.2",
|
|
23
|
+
"@useai/shared": "0.2.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.13.4",
|
|
27
|
+
"tsup": "^8.0.0",
|
|
28
|
+
"typescript": "^5.7.3"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/nabeelkausari/useai"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://useai.dev",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"dev": "tsc --watch",
|
|
41
|
+
"bundle": "tsup src/index.ts --format esm --target node18 --no-splitting",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"clean": "rm -rf dist"
|
|
44
|
+
}
|
|
45
|
+
}
|