@devness/useai 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +130 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ var __export = (target, all) => {
11
11
 
12
12
  // src/tools.ts
13
13
  import { execSync } from "child_process";
14
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
14
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync } from "fs";
15
15
  import { dirname, join as join3 } from "path";
16
16
  import { homedir as homedir2 } from "os";
17
17
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
@@ -200,6 +200,54 @@ function removeYaml(configPath) {
200
200
  writeYamlFile(configPath, config);
201
201
  }
202
202
  }
203
+ function hasInstructionsBlock(filePath) {
204
+ if (!existsSync3(filePath)) return false;
205
+ try {
206
+ return readFileSync2(filePath, "utf-8").includes(INSTRUCTIONS_START);
207
+ } catch {
208
+ return false;
209
+ }
210
+ }
211
+ function injectInstructions(config) {
212
+ mkdirSync2(dirname(config.path), { recursive: true });
213
+ if (config.method === "create") {
214
+ writeFileSync2(config.path, USEAI_INSTRUCTIONS + "\n");
215
+ return;
216
+ }
217
+ if (hasInstructionsBlock(config.path)) return;
218
+ let existing = "";
219
+ if (existsSync3(config.path)) {
220
+ existing = readFileSync2(config.path, "utf-8");
221
+ }
222
+ const separator = existing && !existing.endsWith("\n") ? "\n\n" : existing ? "\n" : "";
223
+ writeFileSync2(config.path, existing + separator + USEAI_INSTRUCTIONS_BLOCK + "\n");
224
+ }
225
+ function removeInstructions(config) {
226
+ if (config.method === "create") {
227
+ if (existsSync3(config.path)) {
228
+ try {
229
+ unlinkSync(config.path);
230
+ } catch {
231
+ }
232
+ }
233
+ return;
234
+ }
235
+ if (!existsSync3(config.path)) return;
236
+ try {
237
+ const content = readFileSync2(config.path, "utf-8");
238
+ const escaped = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
239
+ const regex = new RegExp(
240
+ `\\n?${escaped(INSTRUCTIONS_START)}[\\s\\S]*?${escaped(INSTRUCTIONS_END)}\\n?`
241
+ );
242
+ const cleaned = content.replace(regex, "").trim();
243
+ if (cleaned) {
244
+ writeFileSync2(config.path, cleaned + "\n");
245
+ } else {
246
+ unlinkSync(config.path);
247
+ }
248
+ } catch {
249
+ }
250
+ }
203
251
  function createTool(def) {
204
252
  const handler = formatHandlers[def.configFormat];
205
253
  return {
@@ -209,8 +257,14 @@ function createTool(def) {
209
257
  getConfigPath: () => def.configPath,
210
258
  detect: def.detect,
211
259
  isConfigured: () => handler.isConfigured(def.configPath),
212
- install: () => handler.install(def.configPath),
213
- remove: () => handler.remove(def.configPath)
260
+ install: () => {
261
+ handler.install(def.configPath);
262
+ if (def.instructions) injectInstructions(def.instructions);
263
+ },
264
+ remove: () => {
265
+ handler.remove(def.configPath);
266
+ if (def.instructions) removeInstructions(def.instructions);
267
+ }
214
268
  };
215
269
  }
216
270
  function matchesTool(tool, query) {
@@ -234,7 +288,7 @@ function resolveTools(names) {
234
288
  }
235
289
  return { matched, unmatched };
236
290
  }
237
- var MCP_ENTRY, home, formatHandlers, appSupport, AI_TOOLS;
291
+ var MCP_ENTRY, home, INSTRUCTIONS_START, INSTRUCTIONS_END, USEAI_INSTRUCTIONS, USEAI_INSTRUCTIONS_BLOCK, formatHandlers, appSupport, AI_TOOLS;
238
292
  var init_tools = __esm({
239
293
  "src/tools.ts"() {
240
294
  "use strict";
@@ -243,6 +297,17 @@ var init_tools = __esm({
243
297
  args: ["-y", "@devness/useai"]
244
298
  };
245
299
  home = homedir2();
300
+ INSTRUCTIONS_START = "<!-- useai:start -->";
301
+ INSTRUCTIONS_END = "<!-- useai:end -->";
302
+ USEAI_INSTRUCTIONS = [
303
+ "## UseAI Session Tracking",
304
+ "- At the START of every conversation, call `useai_session_start` with the appropriate task_type.",
305
+ "- For LONG sessions (15+ min), call `useai_heartbeat` periodically.",
306
+ "- At the END of every conversation, call `useai_session_end` with languages used, files_touched_count, and milestones (generic descriptions only \u2014 no project names, file paths, or company names)."
307
+ ].join("\n");
308
+ USEAI_INSTRUCTIONS_BLOCK = `${INSTRUCTIONS_START}
309
+ ${USEAI_INSTRUCTIONS}
310
+ ${INSTRUCTIONS_END}`;
246
311
  formatHandlers = {
247
312
  standard: { isConfigured: isConfiguredStandard, install: installStandard, remove: removeStandard },
248
313
  vscode: { isConfigured: isConfiguredVscode, install: installVscode, remove: removeVscode },
@@ -257,7 +322,8 @@ var init_tools = __esm({
257
322
  name: "Claude Code",
258
323
  configFormat: "standard",
259
324
  configPath: join3(home, ".claude.json"),
260
- detect: () => hasBinary("claude") || existsSync3(join3(home, ".claude.json"))
325
+ detect: () => hasBinary("claude") || existsSync3(join3(home, ".claude.json")),
326
+ instructions: { method: "append", path: join3(home, ".claude", "CLAUDE.md") }
261
327
  }),
262
328
  createTool({
263
329
  id: "claude-desktop",
@@ -278,28 +344,32 @@ var init_tools = __esm({
278
344
  name: "Windsurf",
279
345
  configFormat: "standard",
280
346
  configPath: join3(home, ".codeium", "windsurf", "mcp_config.json"),
281
- detect: () => existsSync3(join3(home, ".codeium", "windsurf"))
347
+ detect: () => existsSync3(join3(home, ".codeium", "windsurf")),
348
+ instructions: { method: "append", path: join3(home, ".codeium", "windsurf", "memories", "global_rules.md") }
282
349
  }),
283
350
  createTool({
284
351
  id: "vscode",
285
352
  name: "VS Code",
286
353
  configFormat: "vscode",
287
354
  configPath: join3(appSupport, "Code", "User", "mcp.json"),
288
- detect: () => existsSync3(join3(appSupport, "Code"))
355
+ detect: () => existsSync3(join3(appSupport, "Code")),
356
+ instructions: { method: "create", path: join3(appSupport, "Code", "User", "prompts", "useai.instructions.md") }
289
357
  }),
290
358
  createTool({
291
359
  id: "vscode-insiders",
292
360
  name: "VS Code Insiders",
293
361
  configFormat: "vscode",
294
362
  configPath: join3(appSupport, "Code - Insiders", "User", "mcp.json"),
295
- detect: () => existsSync3(join3(appSupport, "Code - Insiders"))
363
+ detect: () => existsSync3(join3(appSupport, "Code - Insiders")),
364
+ instructions: { method: "create", path: join3(appSupport, "Code - Insiders", "User", "prompts", "useai.instructions.md") }
296
365
  }),
297
366
  createTool({
298
367
  id: "gemini-cli",
299
368
  name: "Gemini CLI",
300
369
  configFormat: "standard",
301
370
  configPath: join3(home, ".gemini", "settings.json"),
302
- detect: () => hasBinary("gemini")
371
+ detect: () => hasBinary("gemini"),
372
+ instructions: { method: "append", path: join3(home, ".gemini", "GEMINI.md") }
303
373
  }),
304
374
  createTool({
305
375
  id: "zed",
@@ -323,7 +393,8 @@ var init_tools = __esm({
323
393
  ),
324
394
  detect: () => existsSync3(
325
395
  join3(appSupport, "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
326
- )
396
+ ),
397
+ instructions: { method: "create", path: join3(home, "Documents", "Cline", "Rules", "useai.md") }
327
398
  }),
328
399
  createTool({
329
400
  id: "roo-code",
@@ -340,7 +411,8 @@ var init_tools = __esm({
340
411
  ),
341
412
  detect: () => existsSync3(
342
413
  join3(appSupport, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline")
343
- )
414
+ ),
415
+ instructions: { method: "create", path: join3(home, ".roo", "rules", "useai.md") }
344
416
  }),
345
417
  createTool({
346
418
  id: "amazon-q-cli",
@@ -361,21 +433,24 @@ var init_tools = __esm({
361
433
  name: "Codex",
362
434
  configFormat: "toml",
363
435
  configPath: join3(home, ".codex", "config.toml"),
364
- detect: () => hasBinary("codex") || existsSync3(join3(home, ".codex")) || existsSync3("/Applications/Codex.app")
436
+ detect: () => hasBinary("codex") || existsSync3(join3(home, ".codex")) || existsSync3("/Applications/Codex.app"),
437
+ instructions: { method: "append", path: join3(home, ".codex", "AGENTS.md") }
365
438
  }),
366
439
  createTool({
367
440
  id: "goose",
368
441
  name: "Goose",
369
442
  configFormat: "yaml",
370
443
  configPath: join3(home, ".config", "goose", "config.yaml"),
371
- detect: () => existsSync3(join3(home, ".config", "goose"))
444
+ detect: () => existsSync3(join3(home, ".config", "goose")),
445
+ instructions: { method: "append", path: join3(home, ".config", "goose", ".goosehints") }
372
446
  }),
373
447
  createTool({
374
448
  id: "opencode",
375
449
  name: "OpenCode",
376
450
  configFormat: "standard",
377
451
  configPath: join3(home, ".config", "opencode", "opencode.json"),
378
- detect: () => hasBinary("opencode") || existsSync3(join3(home, ".config", "opencode"))
452
+ detect: () => hasBinary("opencode") || existsSync3(join3(home, ".config", "opencode")),
453
+ instructions: { method: "append", path: join3(home, ".config", "opencode", "AGENTS.md") }
379
454
  }),
380
455
  createTool({
381
456
  id: "junie",
@@ -438,17 +513,16 @@ function showStatus(tools) {
438
513
  }
439
514
  async function installFlow(tools, autoYes, explicit) {
440
515
  if (explicit) {
441
- const toInstall2 = tools.filter((t) => !t.isConfigured());
442
- const alreadyDone = tools.filter((t) => t.isConfigured());
443
- for (const tool of alreadyDone) {
444
- console.log(dim(`${tool.name} is already configured.`));
445
- }
446
- if (toInstall2.length === 0) return;
447
516
  console.log();
448
- for (const tool of toInstall2) {
517
+ for (const tool of tools) {
449
518
  try {
519
+ const wasConfigured = tool.isConfigured();
450
520
  tool.install();
451
- console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim(shortenPath(tool.getConfigPath()))}`));
521
+ if (wasConfigured) {
522
+ console.log(ok(`\u2713 ${tool.name.padEnd(18)} ${chalk.dim("(updated)")}`));
523
+ } else {
524
+ console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim(shortenPath(tool.getConfigPath()))}`));
525
+ }
452
526
  } catch (e) {
453
527
  console.log(err(`\u2717 ${tool.name.padEnd(18)} \u2014 ${e.message}`));
454
528
  }
@@ -481,14 +555,20 @@ async function installFlow(tools, autoYes, explicit) {
481
555
  if (autoYes) {
482
556
  toInstall = unconfigured;
483
557
  } else {
484
- const selected = await checkbox({
485
- message: "Select tools to configure:",
486
- choices: unconfigured.map((t) => ({
487
- name: t.name,
488
- value: t.id,
489
- checked: true
490
- }))
491
- });
558
+ let selected;
559
+ try {
560
+ selected = await checkbox({
561
+ message: "Select tools to configure:",
562
+ choices: unconfigured.map((t) => ({
563
+ name: t.name,
564
+ value: t.id,
565
+ checked: true
566
+ }))
567
+ });
568
+ } catch {
569
+ console.log("\n");
570
+ return;
571
+ }
492
572
  toInstall = unconfigured.filter((t) => selected.includes(t.id));
493
573
  }
494
574
  if (toInstall.length === 0) {
@@ -506,6 +586,12 @@ async function installFlow(tools, autoYes, explicit) {
506
586
  console.log(err(`\u2717 ${tool.name.padEnd(18)} \u2014 ${e.message}`));
507
587
  }
508
588
  }
589
+ for (const tool of alreadyConfigured) {
590
+ try {
591
+ tool.install();
592
+ } catch {
593
+ }
594
+ }
509
595
  console.log(`
510
596
  Done! UseAI MCP server configured in ${chalk.bold(String(toInstall.length))} tool${toInstall.length === 1 ? "" : "s"}.
511
597
  `);
@@ -560,14 +646,20 @@ async function removeFlow(tools, autoYes, explicit) {
560
646
  if (autoYes) {
561
647
  toRemove = configured;
562
648
  } else {
563
- const selected = await checkbox({
564
- message: "Select tools to remove UseAI from:",
565
- choices: configured.map((t) => ({
566
- name: t.name,
567
- value: t.id,
568
- checked: true
569
- }))
570
- });
649
+ let selected;
650
+ try {
651
+ selected = await checkbox({
652
+ message: "Select tools to remove UseAI from:",
653
+ choices: configured.map((t) => ({
654
+ name: t.name,
655
+ value: t.id,
656
+ checked: true
657
+ }))
658
+ });
659
+ } catch {
660
+ console.log("\n");
661
+ return;
662
+ }
571
663
  toRemove = configured.filter((t) => selected.includes(t.id));
572
664
  }
573
665
  if (toRemove.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devness/useai",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Track your AI-assisted development workflow. MCP server that records usage metrics across all your AI tools.",
5
5
  "keywords": [
6
6
  "mcp",