@devness/useai 0.3.8 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1598 -598
  2. package/package.json +13 -12
  3. package/LICENSE +0 -21
package/dist/index.js CHANGED
@@ -9,25 +9,1127 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/tools.ts
12
+ // ../shared/dist/types/chain.js
13
+ var init_chain = __esm({
14
+ "../shared/dist/types/chain.js"() {
15
+ "use strict";
16
+ }
17
+ });
18
+
19
+ // ../shared/dist/types/config.js
20
+ var init_config = __esm({
21
+ "../shared/dist/types/config.js"() {
22
+ "use strict";
23
+ }
24
+ });
25
+
26
+ // ../shared/dist/types/keystore.js
27
+ var init_keystore = __esm({
28
+ "../shared/dist/types/keystore.js"() {
29
+ "use strict";
30
+ }
31
+ });
32
+
33
+ // ../shared/dist/types/milestone.js
34
+ var init_milestone = __esm({
35
+ "../shared/dist/types/milestone.js"() {
36
+ "use strict";
37
+ }
38
+ });
39
+
40
+ // ../shared/dist/types/session.js
41
+ var init_session = __esm({
42
+ "../shared/dist/types/session.js"() {
43
+ "use strict";
44
+ }
45
+ });
46
+
47
+ // ../shared/dist/types/api.js
48
+ var init_api = __esm({
49
+ "../shared/dist/types/api.js"() {
50
+ "use strict";
51
+ }
52
+ });
53
+
54
+ // ../shared/dist/types/user.js
55
+ var init_user = __esm({
56
+ "../shared/dist/types/user.js"() {
57
+ "use strict";
58
+ }
59
+ });
60
+
61
+ // ../shared/dist/types/leaderboard.js
62
+ var init_leaderboard = __esm({
63
+ "../shared/dist/types/leaderboard.js"() {
64
+ "use strict";
65
+ }
66
+ });
67
+
68
+ // ../shared/dist/types/index.js
69
+ var init_types = __esm({
70
+ "../shared/dist/types/index.js"() {
71
+ "use strict";
72
+ init_chain();
73
+ init_config();
74
+ init_keystore();
75
+ init_milestone();
76
+ init_session();
77
+ init_api();
78
+ init_user();
79
+ init_leaderboard();
80
+ }
81
+ });
82
+
83
+ // ../shared/dist/constants/paths.js
84
+ import { join } from "path";
85
+ import { homedir } from "os";
86
+ var USEAI_DIR, DATA_DIR, ACTIVE_DIR, SEALED_DIR, KEYSTORE_FILE, CONFIG_FILE, SESSIONS_FILE, MILESTONES_FILE, DAEMON_PID_FILE, DAEMON_PORT, DAEMON_LOG_FILE, DAEMON_MCP_URL, DAEMON_HEALTH_URL, LAUNCHD_PLIST_PATH, SYSTEMD_SERVICE_PATH, WINDOWS_STARTUP_SCRIPT_PATH;
87
+ var init_paths = __esm({
88
+ "../shared/dist/constants/paths.js"() {
89
+ "use strict";
90
+ USEAI_DIR = join(homedir(), ".useai");
91
+ DATA_DIR = join(USEAI_DIR, "data");
92
+ ACTIVE_DIR = join(DATA_DIR, "active");
93
+ SEALED_DIR = join(DATA_DIR, "sealed");
94
+ KEYSTORE_FILE = join(USEAI_DIR, "keystore.json");
95
+ CONFIG_FILE = join(USEAI_DIR, "config.json");
96
+ SESSIONS_FILE = join(DATA_DIR, "sessions.json");
97
+ MILESTONES_FILE = join(DATA_DIR, "milestones.json");
98
+ DAEMON_PID_FILE = join(USEAI_DIR, "daemon.pid");
99
+ DAEMON_PORT = 19200;
100
+ DAEMON_LOG_FILE = join(USEAI_DIR, "daemon.log");
101
+ DAEMON_MCP_URL = `http://127.0.0.1:${DAEMON_PORT}/mcp`;
102
+ DAEMON_HEALTH_URL = `http://127.0.0.1:${DAEMON_PORT}/health`;
103
+ LAUNCHD_PLIST_PATH = join(homedir(), "Library", "LaunchAgents", "dev.useai.daemon.plist");
104
+ SYSTEMD_SERVICE_PATH = join(homedir(), ".config", "systemd", "user", "useai-daemon.service");
105
+ WINDOWS_STARTUP_SCRIPT_PATH = join(process.env["APPDATA"] ?? join(homedir(), "AppData", "Roaming"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "useai-daemon.vbs");
106
+ }
107
+ });
108
+
109
+ // ../shared/dist/constants/version.js
110
+ var VERSION;
111
+ var init_version = __esm({
112
+ "../shared/dist/constants/version.js"() {
113
+ "use strict";
114
+ VERSION = "0.4.1";
115
+ }
116
+ });
117
+
118
+ // ../shared/dist/constants/clients.js
119
+ var AI_CLIENT_ENV_VARS;
120
+ var init_clients = __esm({
121
+ "../shared/dist/constants/clients.js"() {
122
+ "use strict";
123
+ AI_CLIENT_ENV_VARS = {
124
+ CURSOR_EDITOR: "cursor",
125
+ WINDSURF_EDITOR: "windsurf",
126
+ CLAUDE_CODE: "claude-code",
127
+ VSCODE_PID: "vscode",
128
+ CODEX_CLI: "codex",
129
+ GEMINI_CLI: "gemini-cli",
130
+ JETBRAINS_IDE: "jetbrains",
131
+ ZED_EDITOR: "zed"
132
+ };
133
+ }
134
+ });
135
+
136
+ // ../shared/dist/constants/defaults.js
137
+ var GENESIS_HASH;
138
+ var init_defaults = __esm({
139
+ "../shared/dist/constants/defaults.js"() {
140
+ "use strict";
141
+ GENESIS_HASH = "GENESIS";
142
+ }
143
+ });
144
+
145
+ // ../shared/dist/constants/index.js
146
+ var init_constants = __esm({
147
+ "../shared/dist/constants/index.js"() {
148
+ "use strict";
149
+ init_paths();
150
+ init_version();
151
+ init_clients();
152
+ init_defaults();
153
+ }
154
+ });
155
+
156
+ // ../shared/dist/crypto/derive.js
157
+ import { pbkdf2Sync } from "crypto";
158
+ import { hostname, userInfo } from "os";
159
+ function deriveEncryptionKey(salt) {
160
+ const machineData = `${hostname()}:${userInfo().username}:useai-keystore`;
161
+ return pbkdf2Sync(machineData, salt, 1e5, 32, "sha256");
162
+ }
163
+ var init_derive = __esm({
164
+ "../shared/dist/crypto/derive.js"() {
165
+ "use strict";
166
+ }
167
+ });
168
+
169
+ // ../shared/dist/crypto/keystore.js
170
+ import { generateKeyPairSync, randomBytes, createCipheriv, createDecipheriv, createPrivateKey } from "crypto";
171
+ function decryptKeystore(ks) {
172
+ const salt = Buffer.from(ks.salt, "hex");
173
+ const iv = Buffer.from(ks.iv, "hex");
174
+ const tag = Buffer.from(ks.tag, "hex");
175
+ const encrypted = Buffer.from(ks.encrypted_private_key, "hex");
176
+ const encKey = deriveEncryptionKey(salt);
177
+ const decipher = createDecipheriv("aes-256-gcm", encKey, iv);
178
+ decipher.setAuthTag(tag);
179
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
180
+ return createPrivateKey(decrypted.toString("utf-8"));
181
+ }
182
+ function generateKeystore() {
183
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
184
+ const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
185
+ const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
186
+ const salt = randomBytes(32);
187
+ const encKey = deriveEncryptionKey(salt);
188
+ const iv = randomBytes(12);
189
+ const cipher = createCipheriv("aes-256-gcm", encKey, iv);
190
+ const encrypted = Buffer.concat([cipher.update(privateKeyPem, "utf-8"), cipher.final()]);
191
+ const tag = cipher.getAuthTag();
192
+ const keystore = {
193
+ public_key_pem: publicKeyPem,
194
+ encrypted_private_key: encrypted.toString("hex"),
195
+ iv: iv.toString("hex"),
196
+ tag: tag.toString("hex"),
197
+ salt: salt.toString("hex"),
198
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
199
+ };
200
+ return { keystore, signingKey: privateKey };
201
+ }
202
+ var init_keystore2 = __esm({
203
+ "../shared/dist/crypto/keystore.js"() {
204
+ "use strict";
205
+ init_derive();
206
+ }
207
+ });
208
+
209
+ // ../shared/dist/crypto/chain.js
210
+ import { createHash, sign as cryptoSign, randomUUID } from "crypto";
211
+ function computeHash(recordJson, prevHash) {
212
+ return createHash("sha256").update(recordJson + prevHash).digest("hex");
213
+ }
214
+ function signHash(hash, signingKey) {
215
+ if (!signingKey)
216
+ return "unsigned";
217
+ try {
218
+ return cryptoSign(null, Buffer.from(hash), signingKey).toString("hex");
219
+ } catch {
220
+ return "unsigned";
221
+ }
222
+ }
223
+ function buildChainRecord(type, sessionId, data, prevHash, signingKey) {
224
+ const id = `r_${randomUUID().slice(0, 12)}`;
225
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
226
+ const recordCore = JSON.stringify({ id, type, session_id: sessionId, timestamp, data });
227
+ const hash = computeHash(recordCore, prevHash);
228
+ const signature = signHash(hash, signingKey);
229
+ return {
230
+ id,
231
+ type,
232
+ session_id: sessionId,
233
+ timestamp,
234
+ data,
235
+ prev_hash: prevHash,
236
+ hash,
237
+ signature
238
+ };
239
+ }
240
+ var init_chain2 = __esm({
241
+ "../shared/dist/crypto/chain.js"() {
242
+ "use strict";
243
+ }
244
+ });
245
+
246
+ // ../shared/dist/crypto/verify.js
247
+ import { createHash as createHash2, createPublicKey, verify as cryptoVerify } from "crypto";
248
+ var init_verify = __esm({
249
+ "../shared/dist/crypto/verify.js"() {
250
+ "use strict";
251
+ }
252
+ });
253
+
254
+ // ../shared/dist/crypto/index.js
255
+ var init_crypto = __esm({
256
+ "../shared/dist/crypto/index.js"() {
257
+ "use strict";
258
+ init_keystore2();
259
+ init_chain2();
260
+ init_verify();
261
+ init_derive();
262
+ }
263
+ });
264
+
265
+ // ../shared/dist/validation/schemas.js
266
+ import { z } from "zod";
267
+ var taskTypeSchema, milestoneCategorySchema, complexitySchema, milestoneInputSchema, syncPayloadSchema, publishPayloadSchema;
268
+ var init_schemas = __esm({
269
+ "../shared/dist/validation/schemas.js"() {
270
+ "use strict";
271
+ taskTypeSchema = z.enum([
272
+ "coding",
273
+ "debugging",
274
+ "testing",
275
+ "planning",
276
+ "reviewing",
277
+ "documenting",
278
+ "learning",
279
+ "other"
280
+ ]);
281
+ milestoneCategorySchema = z.enum([
282
+ "feature",
283
+ "bugfix",
284
+ "refactor",
285
+ "test",
286
+ "docs",
287
+ "setup",
288
+ "deployment",
289
+ "other"
290
+ ]);
291
+ complexitySchema = z.enum(["simple", "medium", "complex"]);
292
+ milestoneInputSchema = z.object({
293
+ title: z.string().min(1).max(500),
294
+ category: milestoneCategorySchema,
295
+ complexity: complexitySchema.optional()
296
+ });
297
+ syncPayloadSchema = z.object({
298
+ date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
299
+ total_seconds: z.number().int().nonnegative(),
300
+ clients: z.record(z.string(), z.number().int().nonnegative()),
301
+ task_types: z.record(z.string(), z.number().int().nonnegative()),
302
+ languages: z.record(z.string(), z.number().int().nonnegative()),
303
+ sessions: z.array(z.object({
304
+ session_id: z.string(),
305
+ client: z.string(),
306
+ task_type: z.string(),
307
+ languages: z.array(z.string()),
308
+ files_touched: z.number().int().nonnegative(),
309
+ started_at: z.string(),
310
+ ended_at: z.string(),
311
+ duration_seconds: z.number().int().nonnegative(),
312
+ heartbeat_count: z.number().int().nonnegative(),
313
+ record_count: z.number().int().nonnegative(),
314
+ chain_start_hash: z.string(),
315
+ chain_end_hash: z.string(),
316
+ seal_signature: z.string()
317
+ })),
318
+ sync_signature: z.string()
319
+ });
320
+ publishPayloadSchema = z.object({
321
+ milestones: z.array(z.object({
322
+ id: z.string(),
323
+ title: z.string().min(1).max(500),
324
+ category: milestoneCategorySchema,
325
+ complexity: complexitySchema,
326
+ duration_minutes: z.number().int().nonnegative(),
327
+ languages: z.array(z.string()),
328
+ client: z.string(),
329
+ chain_hash: z.string()
330
+ }))
331
+ });
332
+ }
333
+ });
334
+
335
+ // ../shared/dist/validation/guards.js
336
+ var init_guards = __esm({
337
+ "../shared/dist/validation/guards.js"() {
338
+ "use strict";
339
+ }
340
+ });
341
+
342
+ // ../shared/dist/validation/index.js
343
+ var init_validation = __esm({
344
+ "../shared/dist/validation/index.js"() {
345
+ "use strict";
346
+ init_schemas();
347
+ init_guards();
348
+ }
349
+ });
350
+
351
+ // ../shared/dist/utils/fs.js
352
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "fs";
353
+ function ensureDir() {
354
+ for (const dir of [USEAI_DIR, DATA_DIR, ACTIVE_DIR, SEALED_DIR]) {
355
+ if (!existsSync(dir)) {
356
+ mkdirSync(dir, { recursive: true });
357
+ }
358
+ }
359
+ }
360
+ function readJson(path, fallback) {
361
+ try {
362
+ if (!existsSync(path))
363
+ return fallback;
364
+ return JSON.parse(readFileSync(path, "utf-8"));
365
+ } catch {
366
+ return fallback;
367
+ }
368
+ }
369
+ function writeJson(path, data) {
370
+ ensureDir();
371
+ const tmp = `${path}.${process.pid}.tmp`;
372
+ writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
373
+ renameSync(tmp, path);
374
+ }
375
+ var init_fs = __esm({
376
+ "../shared/dist/utils/fs.js"() {
377
+ "use strict";
378
+ init_paths();
379
+ }
380
+ });
381
+
382
+ // ../shared/dist/utils/format.js
383
+ function formatDuration(seconds) {
384
+ if (seconds < 60)
385
+ return `${seconds}s`;
386
+ const mins = Math.round(seconds / 60);
387
+ if (mins < 60)
388
+ return `${mins}m`;
389
+ const hrs = Math.floor(mins / 60);
390
+ const remainMins = mins % 60;
391
+ return `${hrs}h ${remainMins}m`;
392
+ }
393
+ var init_format = __esm({
394
+ "../shared/dist/utils/format.js"() {
395
+ "use strict";
396
+ }
397
+ });
398
+
399
+ // ../shared/dist/utils/detect-client.js
400
+ function detectClient() {
401
+ const env = process.env;
402
+ for (const [envVar, clientName] of Object.entries(AI_CLIENT_ENV_VARS)) {
403
+ if (env[envVar])
404
+ return clientName;
405
+ }
406
+ if (env.MCP_CLIENT_NAME)
407
+ return env.MCP_CLIENT_NAME;
408
+ return "unknown";
409
+ }
410
+ var init_detect_client = __esm({
411
+ "../shared/dist/utils/detect-client.js"() {
412
+ "use strict";
413
+ init_clients();
414
+ }
415
+ });
416
+
417
+ // ../shared/dist/utils/id.js
418
+ import { randomUUID as randomUUID2 } from "crypto";
419
+ function generateSessionId() {
420
+ return randomUUID2();
421
+ }
422
+ var init_id = __esm({
423
+ "../shared/dist/utils/id.js"() {
424
+ "use strict";
425
+ }
426
+ });
427
+
428
+ // ../shared/dist/utils/index.js
429
+ var init_utils = __esm({
430
+ "../shared/dist/utils/index.js"() {
431
+ "use strict";
432
+ init_fs();
433
+ init_format();
434
+ init_detect_client();
435
+ init_id();
436
+ }
437
+ });
438
+
439
+ // ../shared/dist/daemon/resolve-npx.js
13
440
  import { execSync } from "child_process";
14
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync } from "fs";
15
- import { dirname, join as join3 } from "path";
441
+ import { existsSync as existsSync2 } from "fs";
442
+ import { join as join2 } from "path";
16
443
  import { homedir as homedir2 } from "os";
444
+ function resolveNpxPath() {
445
+ const whichCmd = isWindows ? "where npx" : "which npx";
446
+ try {
447
+ const result = execSync(whichCmd, { stdio: ["pipe", "pipe", "ignore"], encoding: "utf-8" }).trim();
448
+ if (result)
449
+ return result.split("\n")[0].trim();
450
+ } catch {
451
+ }
452
+ if (!isWindows && process.env["SHELL"]) {
453
+ try {
454
+ const result = execSync(`${process.env["SHELL"]} -lc "which npx"`, {
455
+ stdio: ["pipe", "pipe", "ignore"],
456
+ encoding: "utf-8"
457
+ }).trim();
458
+ if (result)
459
+ return result;
460
+ } catch {
461
+ }
462
+ }
463
+ for (const p of KNOWN_PATHS) {
464
+ if (existsSync2(p))
465
+ return p;
466
+ }
467
+ throw new Error("Could not find npx. Ensure Node.js is installed and npx is in your PATH.");
468
+ }
469
+ function buildNodePath() {
470
+ const home2 = homedir2();
471
+ const dirs = [
472
+ "/usr/local/bin",
473
+ "/opt/homebrew/bin",
474
+ "/usr/bin",
475
+ "/bin",
476
+ join2(home2, ".nvm", "current", "bin"),
477
+ join2(home2, ".volta", "bin"),
478
+ join2(home2, ".bun", "bin")
479
+ ];
480
+ try {
481
+ const npx = resolveNpxPath();
482
+ const npxDir = npx.substring(0, npx.lastIndexOf("/"));
483
+ if (npxDir && !dirs.includes(npxDir)) {
484
+ dirs.unshift(npxDir);
485
+ }
486
+ } catch {
487
+ }
488
+ return dirs.filter((d) => existsSync2(d)).join(":");
489
+ }
490
+ var isWindows, KNOWN_PATHS;
491
+ var init_resolve_npx = __esm({
492
+ "../shared/dist/daemon/resolve-npx.js"() {
493
+ "use strict";
494
+ isWindows = process.platform === "win32";
495
+ KNOWN_PATHS = isWindows ? [] : [
496
+ "/usr/local/bin/npx",
497
+ "/opt/homebrew/bin/npx",
498
+ join2(homedir2(), ".nvm", "current", "bin", "npx"),
499
+ join2(homedir2(), ".volta", "bin", "npx"),
500
+ join2(homedir2(), ".bun", "bin", "npx")
501
+ ];
502
+ }
503
+ });
504
+
505
+ // ../shared/dist/daemon/ensure.js
506
+ import { existsSync as existsSync3, readFileSync as readFileSync2, unlinkSync } from "fs";
507
+ import { spawn } from "child_process";
508
+ function readPidFile() {
509
+ if (!existsSync3(DAEMON_PID_FILE))
510
+ return null;
511
+ try {
512
+ const raw = readFileSync2(DAEMON_PID_FILE, "utf-8").trim();
513
+ return JSON.parse(raw);
514
+ } catch {
515
+ return null;
516
+ }
517
+ }
518
+ function isProcessRunning(pid) {
519
+ try {
520
+ process.kill(pid, 0);
521
+ return true;
522
+ } catch {
523
+ return false;
524
+ }
525
+ }
526
+ async function checkDaemonHealth() {
527
+ try {
528
+ const res = await fetch(DAEMON_HEALTH_URL, { signal: AbortSignal.timeout(3e3) });
529
+ return res.ok;
530
+ } catch {
531
+ return false;
532
+ }
533
+ }
534
+ async function killDaemon() {
535
+ const pid = readPidFile();
536
+ if (!pid)
537
+ return;
538
+ if (!isProcessRunning(pid.pid)) {
539
+ try {
540
+ unlinkSync(DAEMON_PID_FILE);
541
+ } catch {
542
+ }
543
+ return;
544
+ }
545
+ try {
546
+ process.kill(pid.pid, "SIGTERM");
547
+ } catch {
548
+ return;
549
+ }
550
+ const start = Date.now();
551
+ while (Date.now() - start < 5e3) {
552
+ if (!isProcessRunning(pid.pid)) {
553
+ return;
554
+ }
555
+ await new Promise((r) => setTimeout(r, 200));
556
+ }
557
+ try {
558
+ process.kill(pid.pid, "SIGKILL");
559
+ } catch {
560
+ }
561
+ try {
562
+ if (existsSync3(DAEMON_PID_FILE))
563
+ unlinkSync(DAEMON_PID_FILE);
564
+ } catch {
565
+ }
566
+ }
567
+ async function ensureDaemon() {
568
+ if (await checkDaemonHealth())
569
+ return true;
570
+ const pid = readPidFile();
571
+ if (pid && isProcessRunning(pid.pid)) {
572
+ await killDaemon();
573
+ } else if (pid) {
574
+ try {
575
+ unlinkSync(DAEMON_PID_FILE);
576
+ } catch {
577
+ }
578
+ }
579
+ let npxPath;
580
+ try {
581
+ npxPath = resolveNpxPath();
582
+ } catch {
583
+ npxPath = "npx";
584
+ }
585
+ const child = spawn(npxPath, ["-y", "@devness/useai@latest", "daemon", "--port", String(DAEMON_PORT)], {
586
+ detached: true,
587
+ stdio: "ignore"
588
+ });
589
+ child.unref();
590
+ const start = Date.now();
591
+ while (Date.now() - start < 8e3) {
592
+ await new Promise((r) => setTimeout(r, 300));
593
+ if (await checkDaemonHealth())
594
+ return true;
595
+ }
596
+ return false;
597
+ }
598
+ var init_ensure = __esm({
599
+ "../shared/dist/daemon/ensure.js"() {
600
+ "use strict";
601
+ init_paths();
602
+ init_resolve_npx();
603
+ }
604
+ });
605
+
606
+ // ../shared/dist/daemon/autostart.js
607
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
608
+ import { execSync as execSync2 } from "child_process";
609
+ import { dirname } from "path";
610
+ function detectPlatform() {
611
+ switch (process.platform) {
612
+ case "darwin":
613
+ return "macos";
614
+ case "linux":
615
+ return "linux";
616
+ case "win32":
617
+ return "windows";
618
+ default:
619
+ return "unsupported";
620
+ }
621
+ }
622
+ function buildPlist(npxPath, nodePath) {
623
+ return `<?xml version="1.0" encoding="UTF-8"?>
624
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
625
+ <plist version="1.0">
626
+ <dict>
627
+ <key>Label</key>
628
+ <string>dev.useai.daemon</string>
629
+ <key>ProgramArguments</key>
630
+ <array>
631
+ <string>${npxPath}</string>
632
+ <string>-y</string>
633
+ <string>@devness/useai</string>
634
+ <string>daemon</string>
635
+ <string>--port</string>
636
+ <string>${DAEMON_PORT}</string>
637
+ </array>
638
+ <key>RunAtLoad</key>
639
+ <true/>
640
+ <key>KeepAlive</key>
641
+ <dict>
642
+ <key>SuccessfulExit</key>
643
+ <false/>
644
+ </dict>
645
+ <key>ThrottleInterval</key>
646
+ <integer>10</integer>
647
+ <key>StandardOutPath</key>
648
+ <string>${DAEMON_LOG_FILE}</string>
649
+ <key>StandardErrorPath</key>
650
+ <string>${DAEMON_LOG_FILE}</string>
651
+ <key>EnvironmentVariables</key>
652
+ <dict>
653
+ <key>PATH</key>
654
+ <string>${nodePath}</string>
655
+ </dict>
656
+ </dict>
657
+ </plist>
658
+ `;
659
+ }
660
+ function installMacos() {
661
+ const npxPath = resolveNpxPath();
662
+ const nodePath = buildNodePath();
663
+ mkdirSync2(dirname(LAUNCHD_PLIST_PATH), { recursive: true });
664
+ writeFileSync2(LAUNCHD_PLIST_PATH, buildPlist(npxPath, nodePath));
665
+ try {
666
+ execSync2(`launchctl unload "${LAUNCHD_PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
667
+ } catch {
668
+ }
669
+ execSync2(`launchctl load "${LAUNCHD_PLIST_PATH}"`, { stdio: "ignore" });
670
+ }
671
+ function removeMacos() {
672
+ try {
673
+ execSync2(`launchctl unload "${LAUNCHD_PLIST_PATH}" 2>/dev/null`, { stdio: "ignore" });
674
+ } catch {
675
+ }
676
+ try {
677
+ if (existsSync4(LAUNCHD_PLIST_PATH))
678
+ unlinkSync2(LAUNCHD_PLIST_PATH);
679
+ } catch {
680
+ }
681
+ }
682
+ function isMacosInstalled() {
683
+ return existsSync4(LAUNCHD_PLIST_PATH);
684
+ }
685
+ function buildSystemdUnit(npxPath, nodePath) {
686
+ return `[Unit]
687
+ Description=UseAI Daemon
688
+ After=network.target
689
+
690
+ [Service]
691
+ Type=simple
692
+ ExecStart=${npxPath} -y @devness/useai daemon --port ${DAEMON_PORT}
693
+ Restart=on-failure
694
+ RestartSec=10
695
+ Environment=PATH=${nodePath}
696
+
697
+ [Install]
698
+ WantedBy=default.target
699
+ `;
700
+ }
701
+ function installLinux() {
702
+ const npxPath = resolveNpxPath();
703
+ const nodePath = buildNodePath();
704
+ mkdirSync2(dirname(SYSTEMD_SERVICE_PATH), { recursive: true });
705
+ writeFileSync2(SYSTEMD_SERVICE_PATH, buildSystemdUnit(npxPath, nodePath));
706
+ execSync2("systemctl --user daemon-reload", { stdio: "ignore" });
707
+ execSync2("systemctl --user enable --now useai-daemon.service", { stdio: "ignore" });
708
+ }
709
+ function removeLinux() {
710
+ try {
711
+ execSync2("systemctl --user disable --now useai-daemon.service", { stdio: "ignore" });
712
+ } catch {
713
+ }
714
+ try {
715
+ if (existsSync4(SYSTEMD_SERVICE_PATH))
716
+ unlinkSync2(SYSTEMD_SERVICE_PATH);
717
+ } catch {
718
+ }
719
+ try {
720
+ execSync2("systemctl --user daemon-reload", { stdio: "ignore" });
721
+ } catch {
722
+ }
723
+ }
724
+ function isLinuxInstalled() {
725
+ return existsSync4(SYSTEMD_SERVICE_PATH);
726
+ }
727
+ function buildVbsScript(npxPath) {
728
+ return `Set WshShell = CreateObject("WScript.Shell")
729
+ WshShell.Run """${npxPath}"" -y @devness/useai daemon --port ${DAEMON_PORT}", 0, False
730
+ `;
731
+ }
732
+ function installWindows() {
733
+ const npxPath = resolveNpxPath();
734
+ mkdirSync2(dirname(WINDOWS_STARTUP_SCRIPT_PATH), { recursive: true });
735
+ writeFileSync2(WINDOWS_STARTUP_SCRIPT_PATH, buildVbsScript(npxPath));
736
+ }
737
+ function removeWindows() {
738
+ try {
739
+ if (existsSync4(WINDOWS_STARTUP_SCRIPT_PATH))
740
+ unlinkSync2(WINDOWS_STARTUP_SCRIPT_PATH);
741
+ } catch {
742
+ }
743
+ }
744
+ function isWindowsInstalled() {
745
+ return existsSync4(WINDOWS_STARTUP_SCRIPT_PATH);
746
+ }
747
+ function installAutostart() {
748
+ const platform = detectPlatform();
749
+ switch (platform) {
750
+ case "macos":
751
+ installMacos();
752
+ break;
753
+ case "linux":
754
+ installLinux();
755
+ break;
756
+ case "windows":
757
+ installWindows();
758
+ break;
759
+ case "unsupported":
760
+ throw new Error(`Auto-start is not supported on ${process.platform}`);
761
+ }
762
+ }
763
+ function removeAutostart() {
764
+ const platform = detectPlatform();
765
+ switch (platform) {
766
+ case "macos":
767
+ removeMacos();
768
+ break;
769
+ case "linux":
770
+ removeLinux();
771
+ break;
772
+ case "windows":
773
+ removeWindows();
774
+ break;
775
+ case "unsupported":
776
+ break;
777
+ }
778
+ }
779
+ function isAutostartInstalled() {
780
+ const platform = detectPlatform();
781
+ switch (platform) {
782
+ case "macos":
783
+ return isMacosInstalled();
784
+ case "linux":
785
+ return isLinuxInstalled();
786
+ case "windows":
787
+ return isWindowsInstalled();
788
+ default:
789
+ return false;
790
+ }
791
+ }
792
+ var init_autostart = __esm({
793
+ "../shared/dist/daemon/autostart.js"() {
794
+ "use strict";
795
+ init_paths();
796
+ init_resolve_npx();
797
+ }
798
+ });
799
+
800
+ // ../shared/dist/daemon/index.js
801
+ var init_daemon = __esm({
802
+ "../shared/dist/daemon/index.js"() {
803
+ "use strict";
804
+ init_resolve_npx();
805
+ init_ensure();
806
+ init_autostart();
807
+ }
808
+ });
809
+
810
+ // ../shared/dist/index.js
811
+ var init_dist = __esm({
812
+ "../shared/dist/index.js"() {
813
+ "use strict";
814
+ init_types();
815
+ init_constants();
816
+ init_crypto();
817
+ init_validation();
818
+ init_utils();
819
+ init_daemon();
820
+ }
821
+ });
822
+
823
+ // src/session-state.ts
824
+ import { appendFileSync, existsSync as existsSync5 } from "fs";
825
+ import { join as join3 } from "path";
826
+ var SessionState;
827
+ var init_session_state = __esm({
828
+ "src/session-state.ts"() {
829
+ "use strict";
830
+ init_dist();
831
+ SessionState = class {
832
+ sessionId;
833
+ sessionStartTime;
834
+ heartbeatCount;
835
+ sessionRecordCount;
836
+ clientName;
837
+ sessionTaskType;
838
+ chainTipHash;
839
+ signingKey;
840
+ signingAvailable;
841
+ constructor() {
842
+ this.sessionId = generateSessionId();
843
+ this.sessionStartTime = Date.now();
844
+ this.heartbeatCount = 0;
845
+ this.sessionRecordCount = 0;
846
+ this.clientName = "unknown";
847
+ this.sessionTaskType = "coding";
848
+ this.chainTipHash = GENESIS_HASH;
849
+ this.signingKey = null;
850
+ this.signingAvailable = false;
851
+ }
852
+ reset() {
853
+ this.sessionStartTime = Date.now();
854
+ this.sessionId = generateSessionId();
855
+ this.heartbeatCount = 0;
856
+ this.sessionRecordCount = 0;
857
+ this.chainTipHash = GENESIS_HASH;
858
+ this.clientName = "unknown";
859
+ this.sessionTaskType = "coding";
860
+ }
861
+ setClient(name) {
862
+ this.clientName = name;
863
+ }
864
+ setTaskType(type) {
865
+ this.sessionTaskType = type;
866
+ }
867
+ incrementHeartbeat() {
868
+ this.heartbeatCount++;
869
+ }
870
+ getSessionDuration() {
871
+ return Math.round((Date.now() - this.sessionStartTime) / 1e3);
872
+ }
873
+ initializeKeystore() {
874
+ ensureDir();
875
+ if (existsSync5(KEYSTORE_FILE)) {
876
+ const ks = readJson(KEYSTORE_FILE, null);
877
+ if (ks) {
878
+ try {
879
+ this.signingKey = decryptKeystore(ks);
880
+ this.signingAvailable = true;
881
+ return;
882
+ } catch {
883
+ }
884
+ }
885
+ }
886
+ const result = generateKeystore();
887
+ writeJson(KEYSTORE_FILE, result.keystore);
888
+ this.signingKey = result.signingKey;
889
+ this.signingAvailable = true;
890
+ }
891
+ /** Path to this session's chain file in the active directory */
892
+ sessionChainPath() {
893
+ return join3(ACTIVE_DIR, `${this.sessionId}.jsonl`);
894
+ }
895
+ appendToChain(type, data) {
896
+ const record = buildChainRecord(type, this.sessionId, data, this.chainTipHash, this.signingKey);
897
+ ensureDir();
898
+ appendFileSync(this.sessionChainPath(), JSON.stringify(record) + "\n", "utf-8");
899
+ this.chainTipHash = record.hash;
900
+ this.sessionRecordCount++;
901
+ return record;
902
+ }
903
+ };
904
+ }
905
+ });
906
+
907
+ // src/register-tools.ts
908
+ import { z as z2 } from "zod";
909
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
910
+ import { existsSync as existsSync6, renameSync as renameSync2 } from "fs";
911
+ import { join as join4 } from "path";
912
+ function getConfig() {
913
+ return readJson(CONFIG_FILE, {
914
+ milestone_tracking: true,
915
+ auto_sync: true
916
+ });
917
+ }
918
+ function getSessions() {
919
+ return readJson(SESSIONS_FILE, []);
920
+ }
921
+ function getMilestones() {
922
+ return readJson(MILESTONES_FILE, []);
923
+ }
924
+ function registerTools(server2, session2) {
925
+ server2.tool(
926
+ "useai_session_start",
927
+ "Start tracking an AI coding session. Call this at the beginning of a conversation.",
928
+ {
929
+ task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task is the developer working on?")
930
+ },
931
+ async ({ task_type }) => {
932
+ session2.reset();
933
+ session2.setClient(detectClient());
934
+ session2.setTaskType(task_type ?? "coding");
935
+ const record = session2.appendToChain("session_start", {
936
+ client: session2.clientName,
937
+ task_type: session2.sessionTaskType,
938
+ version: VERSION
939
+ });
940
+ return {
941
+ content: [
942
+ {
943
+ type: "text",
944
+ text: `useai session started \u2014 tracking ${session2.sessionTaskType} session on ${session2.clientName}.
945
+ Session: ${session2.sessionId.slice(0, 8)} \xB7 Chain: ${record.hash.slice(0, 12)} (${session2.signingAvailable ? "signed" : "unsigned"})`
946
+ }
947
+ ]
948
+ };
949
+ }
950
+ );
951
+ server2.tool(
952
+ "useai_heartbeat",
953
+ "Record a heartbeat for the current AI coding session. Call this periodically during long conversations (every 10-15 minutes).",
954
+ {},
955
+ async () => {
956
+ session2.incrementHeartbeat();
957
+ session2.appendToChain("heartbeat", {
958
+ heartbeat_number: session2.heartbeatCount,
959
+ cumulative_seconds: session2.getSessionDuration()
960
+ });
961
+ return {
962
+ content: [
963
+ {
964
+ type: "text",
965
+ text: `Heartbeat recorded. Session active for ${formatDuration(session2.getSessionDuration())}.`
966
+ }
967
+ ]
968
+ };
969
+ }
970
+ );
971
+ server2.tool(
972
+ "useai_session_end",
973
+ 'End the current AI coding session and record milestones. CRITICAL PRIVACY REQUIREMENT: Milestone titles must be COMPLETELY GENERIC. NEVER include project names, repository names, product names, package names, file names, file paths, class names, API endpoints, database names, or company names. Write as if describing the work to a stranger with zero knowledge of the codebase. GOOD: "Implemented user authentication", "Fixed race condition in background worker", "Added unit tests for data validation". BAD: "Fixed bug in Acme auth service", "Updated UserService.ts", "Investigated ProjectX pipeline".',
974
+ {
975
+ task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task was the developer working on?"),
976
+ languages: z2.array(z2.string()).optional().describe("Programming languages used (e.g. ['typescript', 'python'])"),
977
+ files_touched_count: z2.number().optional().describe("Approximate number of files created or modified (count only, no names)"),
978
+ milestones: z2.array(z2.object({
979
+ title: z2.string().describe("PRIVACY-CRITICAL: Generic description of what was accomplished. NEVER include project names, repo names, product names, package names, file names, file paths, class names, API endpoints, database names, company names, or ANY identifier that could reveal which codebase this work was done in. Write as if describing the work to a stranger. GOOD: 'Implemented user authentication', 'Fixed race condition in background worker', 'Added unit tests for data validation', 'Refactored state management layer'. BAD: 'Fixed bug in Acme auth', 'Investigated ProjectX pipeline', 'Updated UserService.ts in src/services/', 'Added tests for coverit MCP tool'"),
980
+ category: z2.enum(["feature", "bugfix", "refactor", "test", "docs", "setup", "deployment", "other"]).describe("Type of work completed"),
981
+ complexity: z2.enum(["simple", "medium", "complex"]).optional().describe("How complex was this task?")
982
+ })).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.")
983
+ },
984
+ async ({ task_type, languages, files_touched_count, milestones: milestonesInput }) => {
985
+ const duration = session2.getSessionDuration();
986
+ const now = (/* @__PURE__ */ new Date()).toISOString();
987
+ const finalTaskType = task_type ?? session2.sessionTaskType;
988
+ const chainStartHash = session2.chainTipHash === "GENESIS" ? "GENESIS" : session2.chainTipHash;
989
+ const endRecord = session2.appendToChain("session_end", {
990
+ duration_seconds: duration,
991
+ task_type: finalTaskType,
992
+ languages: languages ?? [],
993
+ files_touched: files_touched_count ?? 0,
994
+ heartbeat_count: session2.heartbeatCount
995
+ });
996
+ const sealData = JSON.stringify({
997
+ session_id: session2.sessionId,
998
+ client: session2.clientName,
999
+ task_type: finalTaskType,
1000
+ languages: languages ?? [],
1001
+ files_touched: files_touched_count ?? 0,
1002
+ started_at: new Date(session2.sessionStartTime).toISOString(),
1003
+ ended_at: now,
1004
+ duration_seconds: duration,
1005
+ heartbeat_count: session2.heartbeatCount,
1006
+ record_count: session2.sessionRecordCount,
1007
+ chain_end_hash: endRecord.hash
1008
+ });
1009
+ const sealSignature = signHash(
1010
+ createHash3("sha256").update(sealData).digest("hex"),
1011
+ session2.signingKey
1012
+ );
1013
+ session2.appendToChain("session_seal", {
1014
+ seal: sealData,
1015
+ seal_signature: sealSignature
1016
+ });
1017
+ const activePath = join4(ACTIVE_DIR, `${session2.sessionId}.jsonl`);
1018
+ const sealedPath = join4(SEALED_DIR, `${session2.sessionId}.jsonl`);
1019
+ try {
1020
+ if (existsSync6(activePath)) {
1021
+ renameSync2(activePath, sealedPath);
1022
+ }
1023
+ } catch {
1024
+ }
1025
+ const seal = {
1026
+ session_id: session2.sessionId,
1027
+ client: session2.clientName,
1028
+ task_type: finalTaskType,
1029
+ languages: languages ?? [],
1030
+ files_touched: files_touched_count ?? 0,
1031
+ started_at: new Date(session2.sessionStartTime).toISOString(),
1032
+ ended_at: now,
1033
+ duration_seconds: duration,
1034
+ heartbeat_count: session2.heartbeatCount,
1035
+ record_count: session2.sessionRecordCount,
1036
+ chain_start_hash: chainStartHash,
1037
+ chain_end_hash: endRecord.hash,
1038
+ seal_signature: sealSignature
1039
+ };
1040
+ const sessions2 = getSessions();
1041
+ sessions2.push(seal);
1042
+ writeJson(SESSIONS_FILE, sessions2);
1043
+ let milestoneCount = 0;
1044
+ if (milestonesInput && milestonesInput.length > 0) {
1045
+ const config = getConfig();
1046
+ if (config.milestone_tracking) {
1047
+ const durationMinutes = Math.round(duration / 60);
1048
+ const allMilestones = getMilestones();
1049
+ for (const m of milestonesInput) {
1050
+ const record = session2.appendToChain("milestone", {
1051
+ title: m.title,
1052
+ category: m.category,
1053
+ complexity: m.complexity ?? "medium",
1054
+ duration_minutes: durationMinutes,
1055
+ languages: languages ?? []
1056
+ });
1057
+ const milestone = {
1058
+ id: `m_${randomUUID3().slice(0, 8)}`,
1059
+ session_id: session2.sessionId,
1060
+ title: m.title,
1061
+ category: m.category,
1062
+ complexity: m.complexity ?? "medium",
1063
+ duration_minutes: durationMinutes,
1064
+ languages: languages ?? [],
1065
+ client: session2.clientName,
1066
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1067
+ published: false,
1068
+ published_at: null,
1069
+ chain_hash: record.hash
1070
+ };
1071
+ allMilestones.push(milestone);
1072
+ milestoneCount++;
1073
+ }
1074
+ writeJson(MILESTONES_FILE, allMilestones);
1075
+ }
1076
+ }
1077
+ const durationStr = formatDuration(duration);
1078
+ const langStr = languages && languages.length > 0 ? ` using ${languages.join(", ")}` : "";
1079
+ const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
1080
+ return {
1081
+ content: [
1082
+ {
1083
+ type: "text",
1084
+ text: `Session ended: ${durationStr} ${finalTaskType}${langStr}${milestoneStr}`
1085
+ }
1086
+ ]
1087
+ };
1088
+ }
1089
+ );
1090
+ }
1091
+ var init_register_tools = __esm({
1092
+ "src/register-tools.ts"() {
1093
+ "use strict";
1094
+ init_dist();
1095
+ }
1096
+ });
1097
+
1098
+ // src/tools.ts
1099
+ import { execSync as execSync3 } from "child_process";
1100
+ import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
1101
+ import { dirname as dirname2, join as join5 } from "path";
1102
+ import { homedir as homedir3 } from "os";
17
1103
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
18
1104
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
1105
+ function installStandardHttp(configPath) {
1106
+ const config = readJsonFile(configPath);
1107
+ const servers = config["mcpServers"] ?? {};
1108
+ delete servers["useai"];
1109
+ servers["UseAI"] = { ...MCP_HTTP_ENTRY };
1110
+ config["mcpServers"] = servers;
1111
+ writeJsonFile(configPath, config);
1112
+ }
1113
+ function installVscodeHttp(configPath) {
1114
+ const config = readJsonFile(configPath);
1115
+ const servers = config["servers"] ?? {};
1116
+ delete servers["useai"];
1117
+ servers["UseAI"] = { type: "http", url: MCP_HTTP_URL };
1118
+ config["servers"] = servers;
1119
+ writeJsonFile(configPath, config);
1120
+ }
19
1121
  function hasBinary(name) {
20
1122
  try {
21
- execSync(`which ${name}`, { stdio: "ignore" });
1123
+ execSync3(`which ${name}`, { stdio: "ignore" });
22
1124
  return true;
23
1125
  } catch {
24
1126
  return false;
25
1127
  }
26
1128
  }
27
1129
  function readJsonFile(path) {
28
- if (!existsSync3(path)) return {};
1130
+ if (!existsSync7(path)) return {};
29
1131
  try {
30
- const raw = readFileSync2(path, "utf-8").trim();
1132
+ const raw = readFileSync3(path, "utf-8").trim();
31
1133
  if (!raw) return {};
32
1134
  return JSON.parse(raw);
33
1135
  } catch {
@@ -35,8 +1137,8 @@ function readJsonFile(path) {
35
1137
  }
36
1138
  }
37
1139
  function writeJsonFile(path, data) {
38
- mkdirSync2(dirname(path), { recursive: true });
39
- writeFileSync2(path, JSON.stringify(data, null, 2) + "\n");
1140
+ mkdirSync3(dirname2(path), { recursive: true });
1141
+ writeFileSync3(path, JSON.stringify(data, null, 2) + "\n");
40
1142
  }
41
1143
  function isConfiguredStandard(configPath) {
42
1144
  const config = readJsonFile(configPath);
@@ -117,9 +1219,9 @@ function removeZed(configPath) {
117
1219
  }
118
1220
  }
119
1221
  function readTomlFile(path) {
120
- if (!existsSync3(path)) return {};
1222
+ if (!existsSync7(path)) return {};
121
1223
  try {
122
- const raw = readFileSync2(path, "utf-8").trim();
1224
+ const raw = readFileSync3(path, "utf-8").trim();
123
1225
  if (!raw) return {};
124
1226
  return parseToml(raw);
125
1227
  } catch {
@@ -127,8 +1229,8 @@ function readTomlFile(path) {
127
1229
  }
128
1230
  }
129
1231
  function writeTomlFile(path, data) {
130
- mkdirSync2(dirname(path), { recursive: true });
131
- writeFileSync2(path, stringifyToml(data) + "\n");
1232
+ mkdirSync3(dirname2(path), { recursive: true });
1233
+ writeFileSync3(path, stringifyToml(data) + "\n");
132
1234
  }
133
1235
  function isConfiguredToml(configPath) {
134
1236
  const config = readTomlFile(configPath);
@@ -156,9 +1258,9 @@ function removeToml(configPath) {
156
1258
  }
157
1259
  }
158
1260
  function readYamlFile(path) {
159
- if (!existsSync3(path)) return {};
1261
+ if (!existsSync7(path)) return {};
160
1262
  try {
161
- const raw = readFileSync2(path, "utf-8").trim();
1263
+ const raw = readFileSync3(path, "utf-8").trim();
162
1264
  if (!raw) return {};
163
1265
  return parseYaml(raw) ?? {};
164
1266
  } catch {
@@ -166,8 +1268,8 @@ function readYamlFile(path) {
166
1268
  }
167
1269
  }
168
1270
  function writeYamlFile(path, data) {
169
- mkdirSync2(dirname(path), { recursive: true });
170
- writeFileSync2(path, stringifyYaml(data));
1271
+ mkdirSync3(dirname2(path), { recursive: true });
1272
+ writeFileSync3(path, stringifyYaml(data));
171
1273
  }
172
1274
  function isConfiguredYaml(configPath) {
173
1275
  const config = readYamlFile(configPath);
@@ -201,59 +1303,61 @@ function removeYaml(configPath) {
201
1303
  }
202
1304
  }
203
1305
  function hasInstructionsBlock(filePath) {
204
- if (!existsSync3(filePath)) return false;
1306
+ if (!existsSync7(filePath)) return false;
205
1307
  try {
206
- return readFileSync2(filePath, "utf-8").includes(INSTRUCTIONS_START);
1308
+ return readFileSync3(filePath, "utf-8").includes(INSTRUCTIONS_START);
207
1309
  } catch {
208
1310
  return false;
209
1311
  }
210
1312
  }
211
1313
  function injectInstructions(config) {
212
- mkdirSync2(dirname(config.path), { recursive: true });
1314
+ mkdirSync3(dirname2(config.path), { recursive: true });
213
1315
  if (config.method === "create") {
214
- writeFileSync2(config.path, USEAI_INSTRUCTIONS + "\n");
1316
+ writeFileSync3(config.path, USEAI_INSTRUCTIONS + "\n");
215
1317
  return;
216
1318
  }
217
1319
  if (hasInstructionsBlock(config.path)) return;
218
1320
  let existing = "";
219
- if (existsSync3(config.path)) {
220
- existing = readFileSync2(config.path, "utf-8");
1321
+ if (existsSync7(config.path)) {
1322
+ existing = readFileSync3(config.path, "utf-8");
221
1323
  }
222
1324
  const separator = existing && !existing.endsWith("\n") ? "\n\n" : existing ? "\n" : "";
223
- writeFileSync2(config.path, existing + separator + USEAI_INSTRUCTIONS_BLOCK + "\n");
1325
+ writeFileSync3(config.path, existing + separator + USEAI_INSTRUCTIONS_BLOCK + "\n");
224
1326
  }
225
1327
  function removeInstructions(config) {
226
1328
  if (config.method === "create") {
227
- if (existsSync3(config.path)) {
1329
+ if (existsSync7(config.path)) {
228
1330
  try {
229
- unlinkSync(config.path);
1331
+ unlinkSync3(config.path);
230
1332
  } catch {
231
1333
  }
232
1334
  }
233
1335
  return;
234
1336
  }
235
- if (!existsSync3(config.path)) return;
1337
+ if (!existsSync7(config.path)) return;
236
1338
  try {
237
- const content = readFileSync2(config.path, "utf-8");
1339
+ const content = readFileSync3(config.path, "utf-8");
238
1340
  const escaped = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
239
1341
  const regex = new RegExp(
240
1342
  `\\n?${escaped(INSTRUCTIONS_START)}[\\s\\S]*?${escaped(INSTRUCTIONS_END)}\\n?`
241
1343
  );
242
1344
  const cleaned = content.replace(regex, "").trim();
243
1345
  if (cleaned) {
244
- writeFileSync2(config.path, cleaned + "\n");
1346
+ writeFileSync3(config.path, cleaned + "\n");
245
1347
  } else {
246
- unlinkSync(config.path);
1348
+ unlinkSync3(config.path);
247
1349
  }
248
1350
  } catch {
249
1351
  }
250
1352
  }
251
1353
  function createTool(def) {
252
1354
  const handler = formatHandlers[def.configFormat];
1355
+ const urlSupported = def.supportsUrl ?? false;
253
1356
  return {
254
1357
  id: def.id,
255
1358
  name: def.name,
256
1359
  configFormat: def.configFormat,
1360
+ supportsUrl: urlSupported,
257
1361
  getConfigPath: () => def.configPath,
258
1362
  detect: def.detect,
259
1363
  isConfigured: () => handler.isConfigured(def.configPath),
@@ -261,6 +1365,16 @@ function createTool(def) {
261
1365
  handler.install(def.configPath);
262
1366
  if (def.instructions) injectInstructions(def.instructions);
263
1367
  },
1368
+ installHttp: () => {
1369
+ if (def.configFormat === "vscode") {
1370
+ installVscodeHttp(def.configPath);
1371
+ } else if (def.configFormat === "standard") {
1372
+ installStandardHttp(def.configPath);
1373
+ } else {
1374
+ handler.install(def.configPath);
1375
+ }
1376
+ if (def.instructions) injectInstructions(def.instructions);
1377
+ },
264
1378
  remove: () => {
265
1379
  handler.remove(def.configPath);
266
1380
  if (def.instructions) removeInstructions(def.instructions);
@@ -289,28 +1403,33 @@ function resolveTools(names) {
289
1403
  }
290
1404
  return { matched, unmatched };
291
1405
  }
292
- var USEAI_INSTRUCTIONS_TEXT, MCP_ENTRY, home, INSTRUCTIONS_START, INSTRUCTIONS_END, USEAI_INSTRUCTIONS, USEAI_INSTRUCTIONS_BLOCK, formatHandlers, appSupport, AI_TOOLS;
1406
+ var USEAI_INSTRUCTIONS_TEXT, MCP_ENTRY, MCP_HTTP_URL, MCP_HTTP_ENTRY, home, INSTRUCTIONS_START, INSTRUCTIONS_END, USEAI_INSTRUCTIONS, USEAI_INSTRUCTIONS_BLOCK, formatHandlers, appSupport, AI_TOOLS;
293
1407
  var init_tools = __esm({
294
1408
  "src/tools.ts"() {
295
1409
  "use strict";
1410
+ init_dist();
296
1411
  USEAI_INSTRUCTIONS_TEXT = [
297
1412
  "## UseAI Session Tracking",
298
1413
  "- At the START of every conversation, call `useai_session_start` with the appropriate task_type.",
299
1414
  "- For LONG sessions (15+ min), call `useai_heartbeat` periodically.",
300
- "- 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)."
1415
+ "- At the END of every conversation, call `useai_session_end` with languages used, files_touched_count, and milestones.",
1416
+ '- MILESTONES PRIVACY: Milestone titles must be COMPLETELY GENERIC. NEVER mention project names, repository names, product names, file names, file paths, class names, or company names. Write as if describing the work to a stranger who knows nothing about the codebase. GOOD: "Fixed race condition in background worker". BAD: "Fixed bug in ProjectX worker service".'
301
1417
  ].join("\n");
302
1418
  MCP_ENTRY = {
303
1419
  command: "npx",
304
1420
  args: ["-y", "@devness/useai"]
305
1421
  };
306
- home = homedir2();
1422
+ MCP_HTTP_URL = DAEMON_MCP_URL;
1423
+ MCP_HTTP_ENTRY = { url: MCP_HTTP_URL };
1424
+ home = homedir3();
307
1425
  INSTRUCTIONS_START = "<!-- useai:start -->";
308
1426
  INSTRUCTIONS_END = "<!-- useai:end -->";
309
1427
  USEAI_INSTRUCTIONS = [
310
1428
  "## UseAI Session Tracking",
311
1429
  "- At the START of every conversation, call `useai_session_start` with the appropriate task_type.",
312
1430
  "- For LONG sessions (15+ min), call `useai_heartbeat` periodically.",
313
- "- 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)."
1431
+ "- At the END of every conversation, call `useai_session_end` with languages used, files_touched_count, and milestones.",
1432
+ '- MILESTONES PRIVACY: Milestone titles must be COMPLETELY GENERIC. NEVER mention project names, repository names, product names, file names, file paths, class names, or company names. Write as if describing the work to a stranger who knows nothing about the codebase. GOOD: "Fixed race condition in background worker". BAD: "Fixed bug in ProjectX worker service".'
314
1433
  ].join("\n");
315
1434
  USEAI_INSTRUCTIONS_BLOCK = `${INSTRUCTIONS_START}
316
1435
  ${USEAI_INSTRUCTIONS}
@@ -322,69 +1441,75 @@ ${INSTRUCTIONS_END}`;
322
1441
  toml: { isConfigured: isConfiguredToml, install: installToml, remove: removeToml },
323
1442
  yaml: { isConfigured: isConfiguredYaml, install: installYaml, remove: removeYaml }
324
1443
  };
325
- appSupport = join3(home, "Library", "Application Support");
1444
+ appSupport = join5(home, "Library", "Application Support");
326
1445
  AI_TOOLS = [
327
1446
  createTool({
328
1447
  id: "claude-code",
329
1448
  name: "Claude Code",
330
1449
  configFormat: "standard",
331
- configPath: join3(home, ".claude.json"),
332
- detect: () => hasBinary("claude") || existsSync3(join3(home, ".claude.json")),
333
- instructions: { method: "append", path: join3(home, ".claude", "CLAUDE.md") }
1450
+ configPath: join5(home, ".claude.json"),
1451
+ detect: () => hasBinary("claude") || existsSync7(join5(home, ".claude.json")),
1452
+ instructions: { method: "append", path: join5(home, ".claude", "CLAUDE.md") },
1453
+ supportsUrl: true
334
1454
  }),
335
1455
  createTool({
336
1456
  id: "cursor",
337
1457
  name: "Cursor",
338
1458
  configFormat: "standard",
339
- configPath: join3(home, ".cursor", "mcp.json"),
340
- detect: () => existsSync3(join3(home, ".cursor")),
341
- manualHint: "Open Cursor Settings \u2192 Rules \u2192 User Rules and paste the instructions below."
1459
+ configPath: join5(home, ".cursor", "mcp.json"),
1460
+ detect: () => existsSync7(join5(home, ".cursor")),
1461
+ manualHint: "Open Cursor Settings \u2192 Rules \u2192 User Rules and paste the instructions below.",
1462
+ supportsUrl: true
342
1463
  }),
343
1464
  createTool({
344
1465
  id: "windsurf",
345
1466
  name: "Windsurf",
346
1467
  configFormat: "standard",
347
- configPath: join3(home, ".codeium", "windsurf", "mcp_config.json"),
348
- detect: () => existsSync3(join3(home, ".codeium", "windsurf")),
349
- instructions: { method: "append", path: join3(home, ".codeium", "windsurf", "memories", "global_rules.md") }
1468
+ configPath: join5(home, ".codeium", "windsurf", "mcp_config.json"),
1469
+ detect: () => existsSync7(join5(home, ".codeium", "windsurf")),
1470
+ instructions: { method: "append", path: join5(home, ".codeium", "windsurf", "memories", "global_rules.md") },
1471
+ supportsUrl: true
350
1472
  }),
351
1473
  createTool({
352
1474
  id: "vscode",
353
1475
  name: "VS Code",
354
1476
  configFormat: "vscode",
355
- configPath: join3(appSupport, "Code", "User", "mcp.json"),
356
- detect: () => existsSync3(join3(appSupport, "Code")),
357
- instructions: { method: "create", path: join3(appSupport, "Code", "User", "prompts", "useai.instructions.md") }
1477
+ configPath: join5(appSupport, "Code", "User", "mcp.json"),
1478
+ detect: () => existsSync7(join5(appSupport, "Code")),
1479
+ instructions: { method: "create", path: join5(appSupport, "Code", "User", "prompts", "useai.instructions.md") },
1480
+ supportsUrl: true
358
1481
  }),
359
1482
  createTool({
360
1483
  id: "vscode-insiders",
361
1484
  name: "VS Code Insiders",
362
1485
  configFormat: "vscode",
363
- configPath: join3(appSupport, "Code - Insiders", "User", "mcp.json"),
364
- detect: () => existsSync3(join3(appSupport, "Code - Insiders")),
365
- instructions: { method: "create", path: join3(appSupport, "Code - Insiders", "User", "prompts", "useai.instructions.md") }
1486
+ configPath: join5(appSupport, "Code - Insiders", "User", "mcp.json"),
1487
+ detect: () => existsSync7(join5(appSupport, "Code - Insiders")),
1488
+ instructions: { method: "create", path: join5(appSupport, "Code - Insiders", "User", "prompts", "useai.instructions.md") },
1489
+ supportsUrl: true
366
1490
  }),
367
1491
  createTool({
368
1492
  id: "gemini-cli",
369
1493
  name: "Gemini CLI",
370
1494
  configFormat: "standard",
371
- configPath: join3(home, ".gemini", "settings.json"),
1495
+ configPath: join5(home, ".gemini", "settings.json"),
372
1496
  detect: () => hasBinary("gemini"),
373
- instructions: { method: "append", path: join3(home, ".gemini", "GEMINI.md") }
1497
+ instructions: { method: "append", path: join5(home, ".gemini", "GEMINI.md") },
1498
+ supportsUrl: true
374
1499
  }),
375
1500
  createTool({
376
1501
  id: "zed",
377
1502
  name: "Zed",
378
1503
  configFormat: "zed",
379
- configPath: join3(appSupport, "Zed", "settings.json"),
380
- detect: () => existsSync3(join3(appSupport, "Zed")),
1504
+ configPath: join5(appSupport, "Zed", "settings.json"),
1505
+ detect: () => existsSync7(join5(appSupport, "Zed")),
381
1506
  manualHint: "Open Rules Library (\u2318\u2325L) \u2192 click + \u2192 paste the instructions below."
382
1507
  }),
383
1508
  createTool({
384
1509
  id: "cline",
385
1510
  name: "Cline",
386
1511
  configFormat: "standard",
387
- configPath: join3(
1512
+ configPath: join5(
388
1513
  appSupport,
389
1514
  "Code",
390
1515
  "User",
@@ -393,16 +1518,17 @@ ${INSTRUCTIONS_END}`;
393
1518
  "settings",
394
1519
  "cline_mcp_settings.json"
395
1520
  ),
396
- detect: () => existsSync3(
397
- join3(appSupport, "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
1521
+ detect: () => existsSync7(
1522
+ join5(appSupport, "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
398
1523
  ),
399
- instructions: { method: "create", path: join3(home, "Documents", "Cline", "Rules", "useai.md") }
1524
+ instructions: { method: "create", path: join5(home, "Documents", "Cline", "Rules", "useai.md") },
1525
+ supportsUrl: true
400
1526
  }),
401
1527
  createTool({
402
1528
  id: "roo-code",
403
1529
  name: "Roo Code",
404
1530
  configFormat: "standard",
405
- configPath: join3(
1531
+ configPath: join5(
406
1532
  appSupport,
407
1533
  "Code",
408
1534
  "User",
@@ -411,57 +1537,59 @@ ${INSTRUCTIONS_END}`;
411
1537
  "settings",
412
1538
  "cline_mcp_settings.json"
413
1539
  ),
414
- detect: () => existsSync3(
415
- join3(appSupport, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline")
1540
+ detect: () => existsSync7(
1541
+ join5(appSupport, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline")
416
1542
  ),
417
- instructions: { method: "create", path: join3(home, ".roo", "rules", "useai.md") }
1543
+ instructions: { method: "create", path: join5(home, ".roo", "rules", "useai.md") },
1544
+ supportsUrl: true
418
1545
  }),
419
1546
  createTool({
420
1547
  id: "amazon-q-cli",
421
1548
  name: "Amazon Q CLI",
422
1549
  configFormat: "standard",
423
- configPath: join3(home, ".aws", "amazonq", "mcp.json"),
424
- detect: () => hasBinary("q") || existsSync3(join3(home, ".aws", "amazonq")),
1550
+ configPath: join5(home, ".aws", "amazonq", "mcp.json"),
1551
+ detect: () => hasBinary("q") || existsSync7(join5(home, ".aws", "amazonq")),
425
1552
  manualHint: "Create .amazonq/rules/useai.md in your project root with the instructions below."
426
1553
  }),
427
1554
  createTool({
428
1555
  id: "amazon-q-ide",
429
1556
  name: "Amazon Q IDE",
430
1557
  configFormat: "standard",
431
- configPath: join3(home, ".aws", "amazonq", "default.json"),
432
- detect: () => existsSync3(join3(home, ".amazonq")) || existsSync3(join3(home, ".aws", "amazonq")),
1558
+ configPath: join5(home, ".aws", "amazonq", "default.json"),
1559
+ detect: () => existsSync7(join5(home, ".amazonq")) || existsSync7(join5(home, ".aws", "amazonq")),
433
1560
  manualHint: "Create .amazonq/rules/useai.md in your project root with the instructions below."
434
1561
  }),
435
1562
  createTool({
436
1563
  id: "codex",
437
1564
  name: "Codex",
438
1565
  configFormat: "toml",
439
- configPath: join3(home, ".codex", "config.toml"),
440
- detect: () => hasBinary("codex") || existsSync3(join3(home, ".codex")) || existsSync3("/Applications/Codex.app"),
441
- instructions: { method: "append", path: join3(home, ".codex", "AGENTS.md") }
1566
+ configPath: join5(home, ".codex", "config.toml"),
1567
+ detect: () => hasBinary("codex") || existsSync7(join5(home, ".codex")) || existsSync7("/Applications/Codex.app"),
1568
+ instructions: { method: "append", path: join5(home, ".codex", "AGENTS.md") }
442
1569
  }),
443
1570
  createTool({
444
1571
  id: "goose",
445
1572
  name: "Goose",
446
1573
  configFormat: "yaml",
447
- configPath: join3(home, ".config", "goose", "config.yaml"),
448
- detect: () => existsSync3(join3(home, ".config", "goose")),
449
- instructions: { method: "append", path: join3(home, ".config", "goose", ".goosehints") }
1574
+ configPath: join5(home, ".config", "goose", "config.yaml"),
1575
+ detect: () => existsSync7(join5(home, ".config", "goose")),
1576
+ instructions: { method: "append", path: join5(home, ".config", "goose", ".goosehints") }
450
1577
  }),
451
1578
  createTool({
452
1579
  id: "opencode",
453
1580
  name: "OpenCode",
454
1581
  configFormat: "standard",
455
- configPath: join3(home, ".config", "opencode", "opencode.json"),
456
- detect: () => hasBinary("opencode") || existsSync3(join3(home, ".config", "opencode")),
457
- instructions: { method: "append", path: join3(home, ".config", "opencode", "AGENTS.md") }
1582
+ configPath: join5(home, ".config", "opencode", "opencode.json"),
1583
+ detect: () => hasBinary("opencode") || existsSync7(join5(home, ".config", "opencode")),
1584
+ instructions: { method: "append", path: join5(home, ".config", "opencode", "AGENTS.md") },
1585
+ supportsUrl: true
458
1586
  }),
459
1587
  createTool({
460
1588
  id: "junie",
461
1589
  name: "Junie",
462
1590
  configFormat: "standard",
463
- configPath: join3(home, ".junie", "mcp", "mcp.json"),
464
- detect: () => existsSync3(join3(home, ".junie")),
1591
+ configPath: join5(home, ".junie", "mcp", "mcp.json"),
1592
+ detect: () => existsSync7(join5(home, ".junie")),
465
1593
  manualHint: "Add the instructions below to .junie/guidelines.md in your project root."
466
1594
  })
467
1595
  ];
@@ -531,7 +1659,59 @@ function showStatus(tools) {
531
1659
  console.log(rows.join("\n"));
532
1660
  console.log();
533
1661
  }
534
- async function installFlow(tools, autoYes, explicit) {
1662
+ async function daemonInstallFlow(tools, explicit) {
1663
+ console.log(dim("Ensuring UseAI daemon is running..."));
1664
+ const daemonOk = await ensureDaemon();
1665
+ let useDaemon = true;
1666
+ if (daemonOk) {
1667
+ console.log(ok(`\u2713 Daemon running on port ${DAEMON_PORT}`));
1668
+ } else {
1669
+ useDaemon = false;
1670
+ console.log(err("\u2717 Could not start daemon \u2014 falling back to stdio config"));
1671
+ console.log(dim(`(Run with --foreground to debug: npx @devness/useai daemon --port ${DAEMON_PORT})`));
1672
+ }
1673
+ if (useDaemon) {
1674
+ const platform = detectPlatform();
1675
+ if (platform !== "unsupported") {
1676
+ try {
1677
+ installAutostart();
1678
+ console.log(ok(`\u2713 Auto-start service installed (${platform})`));
1679
+ } catch {
1680
+ console.log(chalk.yellow(` \u26A0 Could not install auto-start service`));
1681
+ }
1682
+ }
1683
+ }
1684
+ const targetTools = explicit ? tools : tools.filter((t) => t.detect());
1685
+ if (targetTools.length === 0) {
1686
+ console.log(err("\n No AI tools detected on this machine."));
1687
+ return;
1688
+ }
1689
+ let configuredCount = 0;
1690
+ console.log();
1691
+ for (const tool of targetTools) {
1692
+ try {
1693
+ if (useDaemon && tool.supportsUrl) {
1694
+ tool.installHttp();
1695
+ console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim("HTTP (daemon)")}`));
1696
+ } else if (useDaemon && !tool.supportsUrl) {
1697
+ tool.install();
1698
+ console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim("stdio (no URL support)")}`));
1699
+ } else {
1700
+ tool.install();
1701
+ console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim("stdio")}`));
1702
+ }
1703
+ configuredCount++;
1704
+ } catch (e) {
1705
+ console.log(err(`\u2717 ${tool.name.padEnd(18)} \u2014 ${e.message}`));
1706
+ }
1707
+ }
1708
+ showManualHints(targetTools);
1709
+ const mode = useDaemon ? "daemon mode" : "stdio mode";
1710
+ console.log(`
1711
+ Done! UseAI configured in ${chalk.bold(String(configuredCount))} tool${configuredCount === 1 ? "" : "s"} (${mode}).
1712
+ `);
1713
+ }
1714
+ async function stdioInstallFlow(tools, autoYes, explicit) {
535
1715
  if (explicit) {
536
1716
  console.log();
537
1717
  for (const tool of tools) {
@@ -618,9 +1798,9 @@ async function installFlow(tools, autoYes, explicit) {
618
1798
  Done! UseAI MCP server configured in ${chalk.bold(String(toInstall.length))} tool${toInstall.length === 1 ? "" : "s"}.
619
1799
  `);
620
1800
  }
621
- async function removeFlow(tools, autoYes, explicit) {
1801
+ async function fullRemoveFlow(tools, autoYes, explicit) {
622
1802
  if (explicit) {
623
- const toRemove2 = tools.filter((t) => {
1803
+ const toRemove = tools.filter((t) => {
624
1804
  try {
625
1805
  return t.isConfigured();
626
1806
  } catch {
@@ -637,83 +1817,98 @@ async function removeFlow(tools, autoYes, explicit) {
637
1817
  for (const tool of notConfigured) {
638
1818
  console.log(dim(`${tool.name} is not configured \u2014 skipping.`));
639
1819
  }
640
- if (toRemove2.length === 0) return;
641
- console.log();
642
- for (const tool of toRemove2) {
643
- try {
644
- tool.remove();
645
- console.log(ok(`\u2713 Removed from ${tool.name}`));
646
- } catch (e) {
647
- console.log(err(`\u2717 ${tool.name} \u2014 ${e.message}`));
1820
+ if (toRemove.length > 0) {
1821
+ console.log();
1822
+ for (const tool of toRemove) {
1823
+ try {
1824
+ tool.remove();
1825
+ console.log(ok(`\u2713 Removed from ${tool.name}`));
1826
+ } catch (e) {
1827
+ console.log(err(`\u2717 ${tool.name} \u2014 ${e.message}`));
1828
+ }
648
1829
  }
649
1830
  }
650
- console.log();
651
- return;
652
- }
653
- const configured = tools.filter((t) => {
654
- try {
655
- return t.isConfigured();
656
- } catch {
657
- return false;
658
- }
659
- });
660
- if (configured.length === 0) {
661
- console.log(dim("UseAI is not configured in any AI tools."));
662
- return;
663
- }
664
- console.log(`
1831
+ } else {
1832
+ const configured = tools.filter((t) => {
1833
+ try {
1834
+ return t.isConfigured();
1835
+ } catch {
1836
+ return false;
1837
+ }
1838
+ });
1839
+ if (configured.length === 0) {
1840
+ console.log(dim("UseAI is not configured in any AI tools."));
1841
+ } else {
1842
+ console.log(`
665
1843
  Found UseAI configured in ${chalk.bold(String(configured.length))} tool${configured.length === 1 ? "" : "s"}:
666
1844
  `);
667
- let toRemove;
668
- if (autoYes) {
669
- toRemove = configured;
670
- } else {
671
- let selected;
672
- try {
673
- selected = await checkbox({
674
- message: "Select tools to remove UseAI from:",
675
- choices: configured.map((t) => ({
676
- name: t.name,
677
- value: t.id,
678
- checked: true
679
- }))
680
- });
681
- } catch {
682
- console.log("\n");
683
- return;
1845
+ let toRemove;
1846
+ if (autoYes) {
1847
+ toRemove = configured;
1848
+ } else {
1849
+ let selected;
1850
+ try {
1851
+ selected = await checkbox({
1852
+ message: "Select tools to remove UseAI from:",
1853
+ choices: configured.map((t) => ({
1854
+ name: t.name,
1855
+ value: t.id,
1856
+ checked: true
1857
+ }))
1858
+ });
1859
+ } catch {
1860
+ console.log("\n");
1861
+ return;
1862
+ }
1863
+ toRemove = configured.filter((t) => selected.includes(t.id));
1864
+ }
1865
+ if (toRemove.length === 0) {
1866
+ console.log(dim("No tools selected."));
1867
+ } else {
1868
+ console.log(`
1869
+ Removing from ${toRemove.length} tool${toRemove.length === 1 ? "" : "s"}...
1870
+ `);
1871
+ for (const tool of toRemove) {
1872
+ try {
1873
+ tool.remove();
1874
+ console.log(ok(`\u2713 Removed from ${tool.name}`));
1875
+ } catch (e) {
1876
+ console.log(err(`\u2717 ${tool.name} \u2014 ${e.message}`));
1877
+ }
1878
+ }
1879
+ }
684
1880
  }
685
- toRemove = configured.filter((t) => selected.includes(t.id));
686
1881
  }
687
- if (toRemove.length === 0) {
688
- console.log(dim("No tools selected."));
689
- return;
1882
+ console.log();
1883
+ try {
1884
+ await killDaemon();
1885
+ console.log(ok("\u2713 Daemon stopped"));
1886
+ } catch {
1887
+ console.log(dim("Daemon was not running"));
690
1888
  }
691
- console.log(`
692
- Removing from ${toRemove.length} tool${toRemove.length === 1 ? "" : "s"}...
693
- `);
694
- for (const tool of toRemove) {
1889
+ if (isAutostartInstalled()) {
695
1890
  try {
696
- tool.remove();
697
- console.log(ok(`\u2713 Removed from ${tool.name}`));
698
- } catch (e) {
699
- console.log(err(`\u2717 ${tool.name} \u2014 ${e.message}`));
1891
+ removeAutostart();
1892
+ console.log(ok("\u2713 Auto-start service removed"));
1893
+ } catch {
1894
+ console.log(err("\u2717 Failed to remove auto-start service"));
700
1895
  }
701
1896
  }
702
- console.log(`
703
- Done! UseAI removed from ${chalk.bold(String(toRemove.length))} tool${toRemove.length === 1 ? "" : "s"}.
704
- `);
1897
+ console.log(dim("\nDone! UseAI fully removed.\n"));
705
1898
  }
706
1899
  function showHelp() {
707
1900
  console.log(`
708
1901
  ${chalk.bold("Usage:")} npx @devness/useai mcp [tools...] [options]
709
1902
 
710
1903
  Configure UseAI MCP server in your AI tools.
1904
+ Default: starts daemon, installs auto-start, configures tools with HTTP.
711
1905
 
712
1906
  ${chalk.bold("Arguments:")}
713
1907
  tools Specific tool names (e.g. codex cursor vscode)
714
1908
 
715
1909
  ${chalk.bold("Options:")}
716
- --remove Remove UseAI from configured tools
1910
+ --stdio Use stdio config (legacy mode for containers/CI)
1911
+ --remove Remove UseAI from configured tools, stop daemon, remove auto-start
717
1912
  --status Show configuration status without modifying
718
1913
  -y, --yes Skip confirmation, auto-select all detected tools
719
1914
  -h, --help Show this help message
@@ -728,6 +1923,7 @@ async function runSetup(args) {
728
1923
  }
729
1924
  const isRemove = flags.has("--remove");
730
1925
  const isStatus = flags.has("--status");
1926
+ const isStdio = flags.has("--stdio");
731
1927
  const autoYes = flags.has("-y") || flags.has("--yes");
732
1928
  const explicit = toolNames.length > 0;
733
1929
  let tools = AI_TOOLS;
@@ -743,508 +1939,312 @@ async function runSetup(args) {
743
1939
  if (isStatus) {
744
1940
  showStatus(tools);
745
1941
  } else if (isRemove) {
746
- await removeFlow(tools, autoYes, explicit);
1942
+ await fullRemoveFlow(tools, autoYes, explicit);
1943
+ } else if (isStdio) {
1944
+ await stdioInstallFlow(tools, autoYes, explicit);
747
1945
  } else {
748
- await installFlow(tools, autoYes, explicit);
1946
+ await daemonInstallFlow(tools, explicit);
749
1947
  }
750
1948
  }
751
1949
  var init_setup = __esm({
752
1950
  "src/setup.ts"() {
753
1951
  "use strict";
1952
+ init_dist();
754
1953
  init_tools();
755
1954
  }
756
1955
  });
757
1956
 
758
- // src/index.ts
1957
+ // src/daemon.ts
1958
+ var daemon_exports = {};
1959
+ __export(daemon_exports, {
1960
+ startDaemon: () => startDaemon
1961
+ });
1962
+ import { createServer } from "http";
1963
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
1964
+ import { existsSync as existsSync8, renameSync as renameSync3, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
1965
+ import { join as join6 } from "path";
759
1966
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
760
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
761
- import { z as z2 } from "zod";
762
- import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
763
- import { existsSync as existsSync4, renameSync as renameSync2 } from "fs";
764
- import { join as join4 } from "path";
765
-
766
- // ../shared/dist/constants/paths.js
767
- import { join } from "path";
768
- import { homedir } from "os";
769
- var USEAI_DIR = join(homedir(), ".useai");
770
- var DATA_DIR = join(USEAI_DIR, "data");
771
- var ACTIVE_DIR = join(DATA_DIR, "active");
772
- var SEALED_DIR = join(DATA_DIR, "sealed");
773
- var KEYSTORE_FILE = join(USEAI_DIR, "keystore.json");
774
- var CONFIG_FILE = join(USEAI_DIR, "config.json");
775
- var SESSIONS_FILE = join(DATA_DIR, "sessions.json");
776
- var MILESTONES_FILE = join(DATA_DIR, "milestones.json");
777
-
778
- // ../shared/dist/constants/version.js
779
- var VERSION = "0.3.0";
780
-
781
- // ../shared/dist/constants/clients.js
782
- var AI_CLIENT_ENV_VARS = {
783
- CURSOR_EDITOR: "cursor",
784
- WINDSURF_EDITOR: "windsurf",
785
- CLAUDE_CODE: "claude-code",
786
- VSCODE_PID: "vscode",
787
- CODEX_CLI: "codex",
788
- GEMINI_CLI: "gemini-cli",
789
- JETBRAINS_IDE: "jetbrains",
790
- ZED_EDITOR: "zed"
791
- };
792
-
793
- // ../shared/dist/constants/defaults.js
794
- var GENESIS_HASH = "GENESIS";
795
-
796
- // ../shared/dist/crypto/keystore.js
797
- import { generateKeyPairSync, randomBytes, createCipheriv, createDecipheriv, createPrivateKey } from "crypto";
798
-
799
- // ../shared/dist/crypto/derive.js
800
- import { pbkdf2Sync } from "crypto";
801
- import { hostname, userInfo } from "os";
802
- function deriveEncryptionKey(salt) {
803
- const machineData = `${hostname()}:${userInfo().username}:useai-keystore`;
804
- return pbkdf2Sync(machineData, salt, 1e5, 32, "sha256");
805
- }
806
-
807
- // ../shared/dist/crypto/keystore.js
808
- function decryptKeystore(ks) {
809
- const salt = Buffer.from(ks.salt, "hex");
810
- const iv = Buffer.from(ks.iv, "hex");
811
- const tag = Buffer.from(ks.tag, "hex");
812
- const encrypted = Buffer.from(ks.encrypted_private_key, "hex");
813
- const encKey = deriveEncryptionKey(salt);
814
- const decipher = createDecipheriv("aes-256-gcm", encKey, iv);
815
- decipher.setAuthTag(tag);
816
- const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
817
- return createPrivateKey(decrypted.toString("utf-8"));
818
- }
819
- function generateKeystore() {
820
- const { publicKey, privateKey } = generateKeyPairSync("ed25519");
821
- const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
822
- const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
823
- const salt = randomBytes(32);
824
- const encKey = deriveEncryptionKey(salt);
825
- const iv = randomBytes(12);
826
- const cipher = createCipheriv("aes-256-gcm", encKey, iv);
827
- const encrypted = Buffer.concat([cipher.update(privateKeyPem, "utf-8"), cipher.final()]);
828
- const tag = cipher.getAuthTag();
829
- const keystore = {
830
- public_key_pem: publicKeyPem,
831
- encrypted_private_key: encrypted.toString("hex"),
832
- iv: iv.toString("hex"),
833
- tag: tag.toString("hex"),
834
- salt: salt.toString("hex"),
835
- created_at: (/* @__PURE__ */ new Date()).toISOString()
836
- };
837
- return { keystore, signingKey: privateKey };
838
- }
839
-
840
- // ../shared/dist/crypto/chain.js
841
- import { createHash, sign as cryptoSign, randomUUID } from "crypto";
842
- function computeHash(recordJson, prevHash) {
843
- return createHash("sha256").update(recordJson + prevHash).digest("hex");
844
- }
845
- function signHash(hash, signingKey2) {
846
- if (!signingKey2)
847
- return "unsigned";
1967
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1968
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1969
+ function autoSealSession(active) {
1970
+ const { session: session2 } = active;
1971
+ if (session2.sessionRecordCount === 0) return;
1972
+ const duration = session2.getSessionDuration();
1973
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1974
+ const endRecord = session2.appendToChain("session_end", {
1975
+ duration_seconds: duration,
1976
+ task_type: session2.sessionTaskType,
1977
+ languages: [],
1978
+ files_touched: 0,
1979
+ heartbeat_count: session2.heartbeatCount,
1980
+ auto_sealed: true
1981
+ });
1982
+ const sealData = JSON.stringify({
1983
+ session_id: session2.sessionId,
1984
+ client: session2.clientName,
1985
+ task_type: session2.sessionTaskType,
1986
+ languages: [],
1987
+ files_touched: 0,
1988
+ started_at: new Date(session2.sessionStartTime).toISOString(),
1989
+ ended_at: now,
1990
+ duration_seconds: duration,
1991
+ heartbeat_count: session2.heartbeatCount,
1992
+ record_count: session2.sessionRecordCount,
1993
+ chain_end_hash: endRecord.hash
1994
+ });
1995
+ const sealSignature = signHash(
1996
+ createHash4("sha256").update(sealData).digest("hex"),
1997
+ session2.signingKey
1998
+ );
1999
+ session2.appendToChain("session_seal", {
2000
+ seal: sealData,
2001
+ seal_signature: sealSignature,
2002
+ auto_sealed: true
2003
+ });
2004
+ const activePath = join6(ACTIVE_DIR, `${session2.sessionId}.jsonl`);
2005
+ const sealedPath = join6(SEALED_DIR, `${session2.sessionId}.jsonl`);
848
2006
  try {
849
- return cryptoSign(null, Buffer.from(hash), signingKey2).toString("hex");
2007
+ if (existsSync8(activePath)) {
2008
+ renameSync3(activePath, sealedPath);
2009
+ }
850
2010
  } catch {
851
- return "unsigned";
852
2011
  }
853
- }
854
- function buildChainRecord(type, sessionId2, data, prevHash, signingKey2) {
855
- const id = `r_${randomUUID().slice(0, 12)}`;
856
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
857
- const recordCore = JSON.stringify({ id, type, session_id: sessionId2, timestamp, data });
858
- const hash = computeHash(recordCore, prevHash);
859
- const signature = signHash(hash, signingKey2);
860
- return {
861
- id,
862
- type,
863
- session_id: sessionId2,
864
- timestamp,
865
- data,
866
- prev_hash: prevHash,
867
- hash,
868
- signature
2012
+ const chainStartHash = session2.chainTipHash === "GENESIS" ? "GENESIS" : session2.chainTipHash;
2013
+ const seal = {
2014
+ session_id: session2.sessionId,
2015
+ client: session2.clientName,
2016
+ task_type: session2.sessionTaskType,
2017
+ languages: [],
2018
+ files_touched: 0,
2019
+ started_at: new Date(session2.sessionStartTime).toISOString(),
2020
+ ended_at: now,
2021
+ duration_seconds: duration,
2022
+ heartbeat_count: session2.heartbeatCount,
2023
+ record_count: session2.sessionRecordCount,
2024
+ chain_start_hash: chainStartHash,
2025
+ chain_end_hash: endRecord.hash,
2026
+ seal_signature: sealSignature
869
2027
  };
870
- }
871
-
872
- // ../shared/dist/crypto/verify.js
873
- import { createHash as createHash2, createPublicKey, verify as cryptoVerify } from "crypto";
874
-
875
- // ../shared/dist/validation/schemas.js
876
- import { z } from "zod";
877
- var taskTypeSchema = z.enum([
878
- "coding",
879
- "debugging",
880
- "testing",
881
- "planning",
882
- "reviewing",
883
- "documenting",
884
- "learning",
885
- "other"
886
- ]);
887
- var milestoneCategorySchema = z.enum([
888
- "feature",
889
- "bugfix",
890
- "refactor",
891
- "test",
892
- "docs",
893
- "setup",
894
- "deployment",
895
- "other"
896
- ]);
897
- var complexitySchema = z.enum(["simple", "medium", "complex"]);
898
- var milestoneInputSchema = z.object({
899
- title: z.string().min(1).max(500),
900
- category: milestoneCategorySchema,
901
- complexity: complexitySchema.optional()
902
- });
903
- var syncPayloadSchema = z.object({
904
- date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
905
- total_seconds: z.number().int().nonnegative(),
906
- clients: z.record(z.string(), z.number().int().nonnegative()),
907
- task_types: z.record(z.string(), z.number().int().nonnegative()),
908
- languages: z.record(z.string(), z.number().int().nonnegative()),
909
- sessions: z.array(z.object({
910
- session_id: z.string(),
911
- client: z.string(),
912
- task_type: z.string(),
913
- languages: z.array(z.string()),
914
- files_touched: z.number().int().nonnegative(),
915
- started_at: z.string(),
916
- ended_at: z.string(),
917
- duration_seconds: z.number().int().nonnegative(),
918
- heartbeat_count: z.number().int().nonnegative(),
919
- record_count: z.number().int().nonnegative(),
920
- chain_start_hash: z.string(),
921
- chain_end_hash: z.string(),
922
- seal_signature: z.string()
923
- })),
924
- sync_signature: z.string()
925
- });
926
- var publishPayloadSchema = z.object({
927
- milestones: z.array(z.object({
928
- id: z.string(),
929
- title: z.string().min(1).max(500),
930
- category: milestoneCategorySchema,
931
- complexity: complexitySchema,
932
- duration_minutes: z.number().int().nonnegative(),
933
- languages: z.array(z.string()),
934
- client: z.string(),
935
- chain_hash: z.string()
936
- }))
937
- });
938
-
939
- // ../shared/dist/utils/fs.js
940
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "fs";
941
- function ensureDir() {
942
- for (const dir of [USEAI_DIR, DATA_DIR, ACTIVE_DIR, SEALED_DIR]) {
943
- if (!existsSync(dir)) {
944
- mkdirSync(dir, { recursive: true });
2028
+ const allSessions = readJson(SESSIONS_FILE, []);
2029
+ allSessions.push(seal);
2030
+ writeJson(SESSIONS_FILE, allSessions);
2031
+ }
2032
+ function resetIdleTimer(sessionId) {
2033
+ const active = sessions.get(sessionId);
2034
+ if (!active) return;
2035
+ clearTimeout(active.idleTimer);
2036
+ active.idleTimer = setTimeout(async () => {
2037
+ autoSealSession(active);
2038
+ try {
2039
+ await active.transport.close();
2040
+ } catch {
945
2041
  }
946
- }
947
- }
948
- function readJson(path, fallback) {
2042
+ sessions.delete(sessionId);
2043
+ }, IDLE_TIMEOUT_MS);
2044
+ }
2045
+ async function cleanupSession(sessionId) {
2046
+ const active = sessions.get(sessionId);
2047
+ if (!active) return;
2048
+ clearTimeout(active.idleTimer);
2049
+ autoSealSession(active);
949
2050
  try {
950
- if (!existsSync(path))
951
- return fallback;
952
- return JSON.parse(readFileSync(path, "utf-8"));
2051
+ await active.transport.close();
953
2052
  } catch {
954
- return fallback;
955
- }
956
- }
957
- function writeJson(path, data) {
958
- ensureDir();
959
- const tmp = `${path}.${process.pid}.tmp`;
960
- writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
961
- renameSync(tmp, path);
962
- }
963
-
964
- // ../shared/dist/utils/format.js
965
- function formatDuration(seconds) {
966
- if (seconds < 60)
967
- return `${seconds}s`;
968
- const mins = Math.round(seconds / 60);
969
- if (mins < 60)
970
- return `${mins}m`;
971
- const hrs = Math.floor(mins / 60);
972
- const remainMins = mins % 60;
973
- return `${hrs}h ${remainMins}m`;
974
- }
975
-
976
- // ../shared/dist/utils/detect-client.js
977
- function detectClient() {
978
- const env = process.env;
979
- for (const [envVar, clientName2] of Object.entries(AI_CLIENT_ENV_VARS)) {
980
- if (env[envVar])
981
- return clientName2;
982
2053
  }
983
- if (env.MCP_CLIENT_NAME)
984
- return env.MCP_CLIENT_NAME;
985
- return "unknown";
986
- }
987
-
988
- // ../shared/dist/utils/id.js
989
- import { randomUUID as randomUUID2 } from "crypto";
990
- function generateSessionId() {
991
- return randomUUID2();
992
- }
993
-
994
- // src/session.ts
995
- import { appendFileSync, existsSync as existsSync2 } from "fs";
996
- import { join as join2 } from "path";
997
- var sessionStartTime = Date.now();
998
- var sessionId = generateSessionId();
999
- var heartbeatCount = 0;
1000
- var sessionRecordCount = 0;
1001
- var clientName = "unknown";
1002
- var sessionTaskType = "coding";
1003
- var chainTipHash = GENESIS_HASH;
1004
- var signingKey = null;
1005
- var signingAvailable = false;
1006
- function resetSession() {
1007
- sessionStartTime = Date.now();
1008
- sessionId = generateSessionId();
1009
- heartbeatCount = 0;
1010
- sessionRecordCount = 0;
1011
- chainTipHash = GENESIS_HASH;
1012
- clientName = "unknown";
1013
- sessionTaskType = "coding";
1014
- }
1015
- function setClient(name) {
1016
- clientName = name;
1017
- }
1018
- function setTaskType(type) {
1019
- sessionTaskType = type;
1020
- }
1021
- function incrementHeartbeat() {
1022
- heartbeatCount++;
1023
- }
1024
- function getSessionDuration() {
1025
- return Math.round((Date.now() - sessionStartTime) / 1e3);
1026
- }
1027
- function initializeKeystore() {
1028
- ensureDir();
1029
- if (existsSync2(KEYSTORE_FILE)) {
1030
- const ks = readJson(KEYSTORE_FILE, null);
1031
- if (ks) {
2054
+ sessions.delete(sessionId);
2055
+ }
2056
+ function handleHealth(res) {
2057
+ const body = JSON.stringify({
2058
+ status: "ok",
2059
+ version: VERSION,
2060
+ active_sessions: sessions.size,
2061
+ uptime_seconds: Math.round((Date.now() - startedAt) / 1e3)
2062
+ });
2063
+ res.writeHead(200, { "Content-Type": "application/json" });
2064
+ res.end(body);
2065
+ }
2066
+ function parseBody(req) {
2067
+ return new Promise((resolve, reject) => {
2068
+ const chunks = [];
2069
+ req.on("data", (chunk) => chunks.push(chunk));
2070
+ req.on("end", () => {
1032
2071
  try {
1033
- signingKey = decryptKeystore(ks);
1034
- signingAvailable = true;
1035
- return;
1036
- } catch {
2072
+ const raw = Buffer.concat(chunks).toString();
2073
+ resolve(raw ? JSON.parse(raw) : void 0);
2074
+ } catch (e) {
2075
+ reject(e);
1037
2076
  }
1038
- }
1039
- }
1040
- const result = generateKeystore();
1041
- writeJson(KEYSTORE_FILE, result.keystore);
1042
- signingKey = result.signingKey;
1043
- signingAvailable = true;
1044
- }
1045
- function sessionChainPath() {
1046
- return join2(ACTIVE_DIR, `${sessionId}.jsonl`);
2077
+ });
2078
+ req.on("error", reject);
2079
+ });
1047
2080
  }
1048
- function appendToChain(type, data) {
1049
- const record = buildChainRecord(type, sessionId, data, chainTipHash, signingKey);
2081
+ async function startDaemon(port) {
2082
+ const listenPort = port ?? DAEMON_PORT;
1050
2083
  ensureDir();
1051
- appendFileSync(sessionChainPath(), JSON.stringify(record) + "\n", "utf-8");
1052
- chainTipHash = record.hash;
1053
- sessionRecordCount++;
1054
- return record;
2084
+ const server2 = createServer(async (req, res) => {
2085
+ const url = new URL(req.url, `http://${req.headers.host}`);
2086
+ if (url.pathname === "/health" && req.method === "GET") {
2087
+ handleHealth(res);
2088
+ return;
2089
+ }
2090
+ if (url.pathname !== "/mcp") {
2091
+ res.writeHead(404, { "Content-Type": "application/json" });
2092
+ res.end(JSON.stringify({ error: "Not found" }));
2093
+ return;
2094
+ }
2095
+ try {
2096
+ if (req.method === "POST") {
2097
+ const body = await parseBody(req);
2098
+ const sid = req.headers["mcp-session-id"];
2099
+ if (sid && sessions.has(sid)) {
2100
+ resetIdleTimer(sid);
2101
+ await sessions.get(sid).transport.handleRequest(req, res, body);
2102
+ } else if (!sid && isInitializeRequest(body)) {
2103
+ const sessionState = new SessionState();
2104
+ try {
2105
+ sessionState.initializeKeystore();
2106
+ } catch {
2107
+ }
2108
+ const mcpServer = new McpServer({
2109
+ name: "UseAI",
2110
+ version: VERSION
2111
+ });
2112
+ registerTools(mcpServer, sessionState);
2113
+ const transport = new StreamableHTTPServerTransport({
2114
+ sessionIdGenerator: () => randomUUID4(),
2115
+ onsessioninitialized: (newSid) => {
2116
+ const idleTimer = setTimeout(async () => {
2117
+ await cleanupSession(newSid);
2118
+ }, IDLE_TIMEOUT_MS);
2119
+ sessions.set(newSid, {
2120
+ transport,
2121
+ server: mcpServer,
2122
+ session: sessionState,
2123
+ idleTimer
2124
+ });
2125
+ }
2126
+ });
2127
+ transport.onclose = () => {
2128
+ const closedSid = transport.sessionId;
2129
+ if (closedSid && sessions.has(closedSid)) {
2130
+ const active = sessions.get(closedSid);
2131
+ clearTimeout(active.idleTimer);
2132
+ autoSealSession(active);
2133
+ sessions.delete(closedSid);
2134
+ }
2135
+ };
2136
+ await mcpServer.connect(transport);
2137
+ await transport.handleRequest(req, res, body);
2138
+ } else {
2139
+ res.writeHead(400, { "Content-Type": "application/json" });
2140
+ res.end(JSON.stringify({
2141
+ jsonrpc: "2.0",
2142
+ error: { code: -32e3, message: "Bad Request: No valid session ID provided" },
2143
+ id: null
2144
+ }));
2145
+ }
2146
+ } else if (req.method === "GET") {
2147
+ const sid = req.headers["mcp-session-id"];
2148
+ if (!sid || !sessions.has(sid)) {
2149
+ res.writeHead(400, { "Content-Type": "application/json" });
2150
+ res.end(JSON.stringify({ error: "Invalid or missing session ID" }));
2151
+ return;
2152
+ }
2153
+ resetIdleTimer(sid);
2154
+ await sessions.get(sid).transport.handleRequest(req, res);
2155
+ } else if (req.method === "DELETE") {
2156
+ const sid = req.headers["mcp-session-id"];
2157
+ if (!sid || !sessions.has(sid)) {
2158
+ res.writeHead(400, { "Content-Type": "application/json" });
2159
+ res.end(JSON.stringify({ error: "Invalid or missing session ID" }));
2160
+ return;
2161
+ }
2162
+ await sessions.get(sid).transport.handleRequest(req, res);
2163
+ } else {
2164
+ res.writeHead(405, { "Content-Type": "application/json" });
2165
+ res.end(JSON.stringify({ error: "Method not allowed" }));
2166
+ }
2167
+ } catch (error) {
2168
+ if (!res.headersSent) {
2169
+ res.writeHead(500, { "Content-Type": "application/json" });
2170
+ res.end(JSON.stringify({
2171
+ jsonrpc: "2.0",
2172
+ error: { code: -32603, message: "Internal server error" },
2173
+ id: null
2174
+ }));
2175
+ }
2176
+ }
2177
+ });
2178
+ const pidData = JSON.stringify({
2179
+ pid: process.pid,
2180
+ port: listenPort,
2181
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
2182
+ });
2183
+ writeFileSync4(DAEMON_PID_FILE, pidData + "\n");
2184
+ const shutdown = async (signal) => {
2185
+ for (const [sid] of sessions) {
2186
+ await cleanupSession(sid);
2187
+ }
2188
+ try {
2189
+ if (existsSync8(DAEMON_PID_FILE)) {
2190
+ unlinkSync4(DAEMON_PID_FILE);
2191
+ }
2192
+ } catch {
2193
+ }
2194
+ server2.close(() => {
2195
+ process.exit(0);
2196
+ });
2197
+ setTimeout(() => process.exit(0), 5e3).unref();
2198
+ };
2199
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
2200
+ process.on("SIGINT", () => shutdown("SIGINT"));
2201
+ server2.listen(listenPort, "127.0.0.1", () => {
2202
+ console.log(`UseAI daemon listening on http://127.0.0.1:${listenPort}`);
2203
+ console.log(`PID: ${process.pid}`);
2204
+ });
1055
2205
  }
2206
+ var IDLE_TIMEOUT_MS, sessions, startedAt;
2207
+ var init_daemon2 = __esm({
2208
+ "src/daemon.ts"() {
2209
+ "use strict";
2210
+ init_dist();
2211
+ init_session_state();
2212
+ init_register_tools();
2213
+ IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
2214
+ sessions = /* @__PURE__ */ new Map();
2215
+ startedAt = Date.now();
2216
+ }
2217
+ });
1056
2218
 
1057
2219
  // src/index.ts
2220
+ init_dist();
2221
+ init_session_state();
2222
+ init_register_tools();
2223
+ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
2224
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1058
2225
  if (process.argv[2] === "mcp") {
1059
2226
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
1060
2227
  await runSetup2(process.argv.slice(3));
1061
2228
  process.exit(0);
1062
2229
  }
1063
- function getConfig() {
1064
- return readJson(CONFIG_FILE, {
1065
- milestone_tracking: true,
1066
- auto_sync: true
2230
+ if (process.argv[2] === "daemon") {
2231
+ const { startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
2232
+ const portArg = process.argv.indexOf("--port");
2233
+ const port = portArg !== -1 ? parseInt(process.argv[portArg + 1], 10) : void 0;
2234
+ await startDaemon2(port);
2235
+ await new Promise(() => {
1067
2236
  });
1068
2237
  }
1069
- function getSessions() {
1070
- return readJson(SESSIONS_FILE, []);
1071
- }
1072
- function getMilestones() {
1073
- return readJson(MILESTONES_FILE, []);
1074
- }
1075
- var server = new McpServer({
2238
+ var session = new SessionState();
2239
+ var server = new McpServer2({
1076
2240
  name: "UseAI",
1077
2241
  version: VERSION
1078
2242
  });
1079
- server.tool(
1080
- "useai_session_start",
1081
- "Start tracking an AI coding session. Call this at the beginning of a conversation.",
1082
- {
1083
- task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task is the developer working on?")
1084
- },
1085
- async ({ task_type }) => {
1086
- resetSession();
1087
- setClient(detectClient());
1088
- setTaskType(task_type ?? "coding");
1089
- const record = appendToChain("session_start", {
1090
- client: clientName,
1091
- task_type: sessionTaskType,
1092
- version: VERSION
1093
- });
1094
- return {
1095
- content: [
1096
- {
1097
- type: "text",
1098
- text: `useai session started \u2014 tracking ${sessionTaskType} session on ${clientName}.
1099
- Session: ${sessionId.slice(0, 8)} \xB7 Chain: ${record.hash.slice(0, 12)} (${signingAvailable ? "signed" : "unsigned"})`
1100
- }
1101
- ]
1102
- };
1103
- }
1104
- );
1105
- server.tool(
1106
- "useai_heartbeat",
1107
- "Record a heartbeat for the current AI coding session. Call this periodically during long conversations (every 10-15 minutes).",
1108
- {},
1109
- async () => {
1110
- incrementHeartbeat();
1111
- appendToChain("heartbeat", {
1112
- heartbeat_number: heartbeatCount,
1113
- cumulative_seconds: getSessionDuration()
1114
- });
1115
- return {
1116
- content: [
1117
- {
1118
- type: "text",
1119
- text: `Heartbeat recorded. Session active for ${formatDuration(getSessionDuration())}.`
1120
- }
1121
- ]
1122
- };
1123
- }
1124
- );
1125
- server.tool(
1126
- "useai_session_end",
1127
- "End the current AI coding session and record milestones. IMPORTANT: Milestone descriptions must be GENERIC \u2014 no project names, file names, file paths, or company names. Use general terms like 'authentication system', 'data validation layer', 'responsive dashboard'.",
1128
- {
1129
- task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task was the developer working on?"),
1130
- languages: z2.array(z2.string()).optional().describe("Programming languages used (e.g. ['typescript', 'python'])"),
1131
- files_touched_count: z2.number().optional().describe("Approximate number of files created or modified (count only, no names)"),
1132
- milestones: z2.array(z2.object({
1133
- 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/'"),
1134
- category: z2.enum(["feature", "bugfix", "refactor", "test", "docs", "setup", "deployment", "other"]).describe("Type of work completed"),
1135
- complexity: z2.enum(["simple", "medium", "complex"]).optional().describe("How complex was this task?")
1136
- })).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.")
1137
- },
1138
- async ({ task_type, languages, files_touched_count, milestones: milestonesInput }) => {
1139
- const duration = getSessionDuration();
1140
- const now = (/* @__PURE__ */ new Date()).toISOString();
1141
- const finalTaskType = task_type ?? sessionTaskType;
1142
- const chainStartHash = chainTipHash === "GENESIS" ? "GENESIS" : chainTipHash;
1143
- const endRecord = appendToChain("session_end", {
1144
- duration_seconds: duration,
1145
- task_type: finalTaskType,
1146
- languages: languages ?? [],
1147
- files_touched: files_touched_count ?? 0,
1148
- heartbeat_count: heartbeatCount
1149
- });
1150
- const sealData = JSON.stringify({
1151
- session_id: sessionId,
1152
- client: clientName,
1153
- task_type: finalTaskType,
1154
- languages: languages ?? [],
1155
- files_touched: files_touched_count ?? 0,
1156
- started_at: new Date(sessionStartTime).toISOString(),
1157
- ended_at: now,
1158
- duration_seconds: duration,
1159
- heartbeat_count: heartbeatCount,
1160
- record_count: sessionRecordCount,
1161
- chain_end_hash: endRecord.hash
1162
- });
1163
- const sealSignature = signHash(
1164
- createHash3("sha256").update(sealData).digest("hex"),
1165
- signingKey
1166
- );
1167
- appendToChain("session_seal", {
1168
- seal: sealData,
1169
- seal_signature: sealSignature
1170
- });
1171
- const activePath = join4(ACTIVE_DIR, `${sessionId}.jsonl`);
1172
- const sealedPath = join4(SEALED_DIR, `${sessionId}.jsonl`);
1173
- try {
1174
- if (existsSync4(activePath)) {
1175
- renameSync2(activePath, sealedPath);
1176
- }
1177
- } catch {
1178
- }
1179
- const seal = {
1180
- session_id: sessionId,
1181
- client: clientName,
1182
- task_type: finalTaskType,
1183
- languages: languages ?? [],
1184
- files_touched: files_touched_count ?? 0,
1185
- started_at: new Date(sessionStartTime).toISOString(),
1186
- ended_at: now,
1187
- duration_seconds: duration,
1188
- heartbeat_count: heartbeatCount,
1189
- record_count: sessionRecordCount,
1190
- chain_start_hash: chainStartHash,
1191
- chain_end_hash: endRecord.hash,
1192
- seal_signature: sealSignature
1193
- };
1194
- const sessions = getSessions();
1195
- sessions.push(seal);
1196
- writeJson(SESSIONS_FILE, sessions);
1197
- let milestoneCount = 0;
1198
- if (milestonesInput && milestonesInput.length > 0) {
1199
- const config = getConfig();
1200
- if (config.milestone_tracking) {
1201
- const durationMinutes = Math.round(duration / 60);
1202
- const allMilestones = getMilestones();
1203
- for (const m of milestonesInput) {
1204
- const record = appendToChain("milestone", {
1205
- title: m.title,
1206
- category: m.category,
1207
- complexity: m.complexity ?? "medium",
1208
- duration_minutes: durationMinutes,
1209
- languages: languages ?? []
1210
- });
1211
- const milestone = {
1212
- id: `m_${randomUUID3().slice(0, 8)}`,
1213
- session_id: sessionId,
1214
- title: m.title,
1215
- category: m.category,
1216
- complexity: m.complexity ?? "medium",
1217
- duration_minutes: durationMinutes,
1218
- languages: languages ?? [],
1219
- client: clientName,
1220
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
1221
- published: false,
1222
- published_at: null,
1223
- chain_hash: record.hash
1224
- };
1225
- allMilestones.push(milestone);
1226
- milestoneCount++;
1227
- }
1228
- writeJson(MILESTONES_FILE, allMilestones);
1229
- }
1230
- }
1231
- const durationStr = formatDuration(duration);
1232
- const langStr = languages && languages.length > 0 ? ` using ${languages.join(", ")}` : "";
1233
- const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
1234
- return {
1235
- content: [
1236
- {
1237
- type: "text",
1238
- text: `Session ended: ${durationStr} ${finalTaskType}${langStr}${milestoneStr}`
1239
- }
1240
- ]
1241
- };
1242
- }
1243
- );
2243
+ registerTools(server, session);
1244
2244
  async function main() {
1245
2245
  ensureDir();
1246
2246
  try {
1247
- initializeKeystore();
2247
+ session.initializeKeystore();
1248
2248
  } catch {
1249
2249
  }
1250
2250
  const transport = new StdioServerTransport();