@devness/useai 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1594 -601
  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.3.0";
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", "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. 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'.",
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("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/'"),
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,10 +1403,11 @@ 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.",
@@ -303,7 +1418,9 @@ var init_tools = __esm({
303
1418
  command: "npx",
304
1419
  args: ["-y", "@devness/useai"]
305
1420
  };
306
- home = homedir2();
1421
+ MCP_HTTP_URL = DAEMON_MCP_URL;
1422
+ MCP_HTTP_ENTRY = { url: MCP_HTTP_URL };
1423
+ home = homedir3();
307
1424
  INSTRUCTIONS_START = "<!-- useai:start -->";
308
1425
  INSTRUCTIONS_END = "<!-- useai:end -->";
309
1426
  USEAI_INSTRUCTIONS = [
@@ -322,69 +1439,75 @@ ${INSTRUCTIONS_END}`;
322
1439
  toml: { isConfigured: isConfiguredToml, install: installToml, remove: removeToml },
323
1440
  yaml: { isConfigured: isConfiguredYaml, install: installYaml, remove: removeYaml }
324
1441
  };
325
- appSupport = join3(home, "Library", "Application Support");
1442
+ appSupport = join5(home, "Library", "Application Support");
326
1443
  AI_TOOLS = [
327
1444
  createTool({
328
1445
  id: "claude-code",
329
1446
  name: "Claude Code",
330
1447
  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") }
1448
+ configPath: join5(home, ".claude.json"),
1449
+ detect: () => hasBinary("claude") || existsSync7(join5(home, ".claude.json")),
1450
+ instructions: { method: "append", path: join5(home, ".claude", "CLAUDE.md") },
1451
+ supportsUrl: true
334
1452
  }),
335
1453
  createTool({
336
1454
  id: "cursor",
337
1455
  name: "Cursor",
338
1456
  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."
1457
+ configPath: join5(home, ".cursor", "mcp.json"),
1458
+ detect: () => existsSync7(join5(home, ".cursor")),
1459
+ manualHint: "Open Cursor Settings \u2192 Rules \u2192 User Rules and paste the instructions below.",
1460
+ supportsUrl: true
342
1461
  }),
343
1462
  createTool({
344
1463
  id: "windsurf",
345
1464
  name: "Windsurf",
346
1465
  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") }
1466
+ configPath: join5(home, ".codeium", "windsurf", "mcp_config.json"),
1467
+ detect: () => existsSync7(join5(home, ".codeium", "windsurf")),
1468
+ instructions: { method: "append", path: join5(home, ".codeium", "windsurf", "memories", "global_rules.md") },
1469
+ supportsUrl: true
350
1470
  }),
351
1471
  createTool({
352
1472
  id: "vscode",
353
1473
  name: "VS Code",
354
1474
  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") }
1475
+ configPath: join5(appSupport, "Code", "User", "mcp.json"),
1476
+ detect: () => existsSync7(join5(appSupport, "Code")),
1477
+ instructions: { method: "create", path: join5(appSupport, "Code", "User", "prompts", "useai.instructions.md") },
1478
+ supportsUrl: true
358
1479
  }),
359
1480
  createTool({
360
1481
  id: "vscode-insiders",
361
1482
  name: "VS Code Insiders",
362
1483
  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") }
1484
+ configPath: join5(appSupport, "Code - Insiders", "User", "mcp.json"),
1485
+ detect: () => existsSync7(join5(appSupport, "Code - Insiders")),
1486
+ instructions: { method: "create", path: join5(appSupport, "Code - Insiders", "User", "prompts", "useai.instructions.md") },
1487
+ supportsUrl: true
366
1488
  }),
367
1489
  createTool({
368
1490
  id: "gemini-cli",
369
1491
  name: "Gemini CLI",
370
1492
  configFormat: "standard",
371
- configPath: join3(home, ".gemini", "settings.json"),
1493
+ configPath: join5(home, ".gemini", "settings.json"),
372
1494
  detect: () => hasBinary("gemini"),
373
- instructions: { method: "append", path: join3(home, ".gemini", "GEMINI.md") }
1495
+ instructions: { method: "append", path: join5(home, ".gemini", "GEMINI.md") },
1496
+ supportsUrl: true
374
1497
  }),
375
1498
  createTool({
376
1499
  id: "zed",
377
1500
  name: "Zed",
378
1501
  configFormat: "zed",
379
- configPath: join3(appSupport, "Zed", "settings.json"),
380
- detect: () => existsSync3(join3(appSupport, "Zed")),
1502
+ configPath: join5(appSupport, "Zed", "settings.json"),
1503
+ detect: () => existsSync7(join5(appSupport, "Zed")),
381
1504
  manualHint: "Open Rules Library (\u2318\u2325L) \u2192 click + \u2192 paste the instructions below."
382
1505
  }),
383
1506
  createTool({
384
1507
  id: "cline",
385
1508
  name: "Cline",
386
1509
  configFormat: "standard",
387
- configPath: join3(
1510
+ configPath: join5(
388
1511
  appSupport,
389
1512
  "Code",
390
1513
  "User",
@@ -393,16 +1516,17 @@ ${INSTRUCTIONS_END}`;
393
1516
  "settings",
394
1517
  "cline_mcp_settings.json"
395
1518
  ),
396
- detect: () => existsSync3(
397
- join3(appSupport, "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
1519
+ detect: () => existsSync7(
1520
+ join5(appSupport, "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
398
1521
  ),
399
- instructions: { method: "create", path: join3(home, "Documents", "Cline", "Rules", "useai.md") }
1522
+ instructions: { method: "create", path: join5(home, "Documents", "Cline", "Rules", "useai.md") },
1523
+ supportsUrl: true
400
1524
  }),
401
1525
  createTool({
402
1526
  id: "roo-code",
403
1527
  name: "Roo Code",
404
1528
  configFormat: "standard",
405
- configPath: join3(
1529
+ configPath: join5(
406
1530
  appSupport,
407
1531
  "Code",
408
1532
  "User",
@@ -411,57 +1535,59 @@ ${INSTRUCTIONS_END}`;
411
1535
  "settings",
412
1536
  "cline_mcp_settings.json"
413
1537
  ),
414
- detect: () => existsSync3(
415
- join3(appSupport, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline")
1538
+ detect: () => existsSync7(
1539
+ join5(appSupport, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline")
416
1540
  ),
417
- instructions: { method: "create", path: join3(home, ".roo", "rules", "useai.md") }
1541
+ instructions: { method: "create", path: join5(home, ".roo", "rules", "useai.md") },
1542
+ supportsUrl: true
418
1543
  }),
419
1544
  createTool({
420
1545
  id: "amazon-q-cli",
421
1546
  name: "Amazon Q CLI",
422
1547
  configFormat: "standard",
423
- configPath: join3(home, ".aws", "amazonq", "mcp.json"),
424
- detect: () => hasBinary("q") || existsSync3(join3(home, ".aws", "amazonq")),
1548
+ configPath: join5(home, ".aws", "amazonq", "mcp.json"),
1549
+ detect: () => hasBinary("q") || existsSync7(join5(home, ".aws", "amazonq")),
425
1550
  manualHint: "Create .amazonq/rules/useai.md in your project root with the instructions below."
426
1551
  }),
427
1552
  createTool({
428
1553
  id: "amazon-q-ide",
429
1554
  name: "Amazon Q IDE",
430
1555
  configFormat: "standard",
431
- configPath: join3(home, ".aws", "amazonq", "default.json"),
432
- detect: () => existsSync3(join3(home, ".amazonq")) || existsSync3(join3(home, ".aws", "amazonq")),
1556
+ configPath: join5(home, ".aws", "amazonq", "default.json"),
1557
+ detect: () => existsSync7(join5(home, ".amazonq")) || existsSync7(join5(home, ".aws", "amazonq")),
433
1558
  manualHint: "Create .amazonq/rules/useai.md in your project root with the instructions below."
434
1559
  }),
435
1560
  createTool({
436
1561
  id: "codex",
437
1562
  name: "Codex",
438
1563
  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") }
1564
+ configPath: join5(home, ".codex", "config.toml"),
1565
+ detect: () => hasBinary("codex") || existsSync7(join5(home, ".codex")) || existsSync7("/Applications/Codex.app"),
1566
+ instructions: { method: "append", path: join5(home, ".codex", "AGENTS.md") }
442
1567
  }),
443
1568
  createTool({
444
1569
  id: "goose",
445
1570
  name: "Goose",
446
1571
  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") }
1572
+ configPath: join5(home, ".config", "goose", "config.yaml"),
1573
+ detect: () => existsSync7(join5(home, ".config", "goose")),
1574
+ instructions: { method: "append", path: join5(home, ".config", "goose", ".goosehints") }
450
1575
  }),
451
1576
  createTool({
452
1577
  id: "opencode",
453
1578
  name: "OpenCode",
454
1579
  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") }
1580
+ configPath: join5(home, ".config", "opencode", "opencode.json"),
1581
+ detect: () => hasBinary("opencode") || existsSync7(join5(home, ".config", "opencode")),
1582
+ instructions: { method: "append", path: join5(home, ".config", "opencode", "AGENTS.md") },
1583
+ supportsUrl: true
458
1584
  }),
459
1585
  createTool({
460
1586
  id: "junie",
461
1587
  name: "Junie",
462
1588
  configFormat: "standard",
463
- configPath: join3(home, ".junie", "mcp", "mcp.json"),
464
- detect: () => existsSync3(join3(home, ".junie")),
1589
+ configPath: join5(home, ".junie", "mcp", "mcp.json"),
1590
+ detect: () => existsSync7(join5(home, ".junie")),
465
1591
  manualHint: "Add the instructions below to .junie/guidelines.md in your project root."
466
1592
  })
467
1593
  ];
@@ -531,7 +1657,59 @@ function showStatus(tools) {
531
1657
  console.log(rows.join("\n"));
532
1658
  console.log();
533
1659
  }
534
- async function installFlow(tools, autoYes, explicit) {
1660
+ async function daemonInstallFlow(tools, explicit) {
1661
+ console.log(dim("Ensuring UseAI daemon is running..."));
1662
+ const daemonOk = await ensureDaemon();
1663
+ let useDaemon = true;
1664
+ if (daemonOk) {
1665
+ console.log(ok(`\u2713 Daemon running on port ${DAEMON_PORT}`));
1666
+ } else {
1667
+ useDaemon = false;
1668
+ console.log(err("\u2717 Could not start daemon \u2014 falling back to stdio config"));
1669
+ console.log(dim(`(Run with --foreground to debug: npx @devness/useai daemon --port ${DAEMON_PORT})`));
1670
+ }
1671
+ if (useDaemon) {
1672
+ const platform = detectPlatform();
1673
+ if (platform !== "unsupported") {
1674
+ try {
1675
+ installAutostart();
1676
+ console.log(ok(`\u2713 Auto-start service installed (${platform})`));
1677
+ } catch {
1678
+ console.log(chalk.yellow(` \u26A0 Could not install auto-start service`));
1679
+ }
1680
+ }
1681
+ }
1682
+ const targetTools = explicit ? tools : tools.filter((t) => t.detect());
1683
+ if (targetTools.length === 0) {
1684
+ console.log(err("\n No AI tools detected on this machine."));
1685
+ return;
1686
+ }
1687
+ let configuredCount = 0;
1688
+ console.log();
1689
+ for (const tool of targetTools) {
1690
+ try {
1691
+ if (useDaemon && tool.supportsUrl) {
1692
+ tool.installHttp();
1693
+ console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim("HTTP (daemon)")}`));
1694
+ } else if (useDaemon && !tool.supportsUrl) {
1695
+ tool.install();
1696
+ console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim("stdio (no URL support)")}`));
1697
+ } else {
1698
+ tool.install();
1699
+ console.log(ok(`\u2713 ${tool.name.padEnd(18)} \u2192 ${chalk.dim("stdio")}`));
1700
+ }
1701
+ configuredCount++;
1702
+ } catch (e) {
1703
+ console.log(err(`\u2717 ${tool.name.padEnd(18)} \u2014 ${e.message}`));
1704
+ }
1705
+ }
1706
+ showManualHints(targetTools);
1707
+ const mode = useDaemon ? "daemon mode" : "stdio mode";
1708
+ console.log(`
1709
+ Done! UseAI configured in ${chalk.bold(String(configuredCount))} tool${configuredCount === 1 ? "" : "s"} (${mode}).
1710
+ `);
1711
+ }
1712
+ async function stdioInstallFlow(tools, autoYes, explicit) {
535
1713
  if (explicit) {
536
1714
  console.log();
537
1715
  for (const tool of tools) {
@@ -618,9 +1796,9 @@ async function installFlow(tools, autoYes, explicit) {
618
1796
  Done! UseAI MCP server configured in ${chalk.bold(String(toInstall.length))} tool${toInstall.length === 1 ? "" : "s"}.
619
1797
  `);
620
1798
  }
621
- async function removeFlow(tools, autoYes, explicit) {
1799
+ async function fullRemoveFlow(tools, autoYes, explicit) {
622
1800
  if (explicit) {
623
- const toRemove2 = tools.filter((t) => {
1801
+ const toRemove = tools.filter((t) => {
624
1802
  try {
625
1803
  return t.isConfigured();
626
1804
  } catch {
@@ -637,83 +1815,98 @@ async function removeFlow(tools, autoYes, explicit) {
637
1815
  for (const tool of notConfigured) {
638
1816
  console.log(dim(`${tool.name} is not configured \u2014 skipping.`));
639
1817
  }
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}`));
1818
+ if (toRemove.length > 0) {
1819
+ console.log();
1820
+ for (const tool of toRemove) {
1821
+ try {
1822
+ tool.remove();
1823
+ console.log(ok(`\u2713 Removed from ${tool.name}`));
1824
+ } catch (e) {
1825
+ console.log(err(`\u2717 ${tool.name} \u2014 ${e.message}`));
1826
+ }
648
1827
  }
649
1828
  }
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(`
1829
+ } else {
1830
+ const configured = tools.filter((t) => {
1831
+ try {
1832
+ return t.isConfigured();
1833
+ } catch {
1834
+ return false;
1835
+ }
1836
+ });
1837
+ if (configured.length === 0) {
1838
+ console.log(dim("UseAI is not configured in any AI tools."));
1839
+ } else {
1840
+ console.log(`
665
1841
  Found UseAI configured in ${chalk.bold(String(configured.length))} tool${configured.length === 1 ? "" : "s"}:
666
1842
  `);
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;
1843
+ let toRemove;
1844
+ if (autoYes) {
1845
+ toRemove = configured;
1846
+ } else {
1847
+ let selected;
1848
+ try {
1849
+ selected = await checkbox({
1850
+ message: "Select tools to remove UseAI from:",
1851
+ choices: configured.map((t) => ({
1852
+ name: t.name,
1853
+ value: t.id,
1854
+ checked: true
1855
+ }))
1856
+ });
1857
+ } catch {
1858
+ console.log("\n");
1859
+ return;
1860
+ }
1861
+ toRemove = configured.filter((t) => selected.includes(t.id));
1862
+ }
1863
+ if (toRemove.length === 0) {
1864
+ console.log(dim("No tools selected."));
1865
+ } else {
1866
+ console.log(`
1867
+ Removing from ${toRemove.length} tool${toRemove.length === 1 ? "" : "s"}...
1868
+ `);
1869
+ for (const tool of toRemove) {
1870
+ try {
1871
+ tool.remove();
1872
+ console.log(ok(`\u2713 Removed from ${tool.name}`));
1873
+ } catch (e) {
1874
+ console.log(err(`\u2717 ${tool.name} \u2014 ${e.message}`));
1875
+ }
1876
+ }
1877
+ }
684
1878
  }
685
- toRemove = configured.filter((t) => selected.includes(t.id));
686
1879
  }
687
- if (toRemove.length === 0) {
688
- console.log(dim("No tools selected."));
689
- return;
1880
+ console.log();
1881
+ try {
1882
+ await killDaemon();
1883
+ console.log(ok("\u2713 Daemon stopped"));
1884
+ } catch {
1885
+ console.log(dim("Daemon was not running"));
690
1886
  }
691
- console.log(`
692
- Removing from ${toRemove.length} tool${toRemove.length === 1 ? "" : "s"}...
693
- `);
694
- for (const tool of toRemove) {
1887
+ if (isAutostartInstalled()) {
695
1888
  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}`));
1889
+ removeAutostart();
1890
+ console.log(ok("\u2713 Auto-start service removed"));
1891
+ } catch {
1892
+ console.log(err("\u2717 Failed to remove auto-start service"));
700
1893
  }
701
1894
  }
702
- console.log(`
703
- Done! UseAI removed from ${chalk.bold(String(toRemove.length))} tool${toRemove.length === 1 ? "" : "s"}.
704
- `);
1895
+ console.log(dim("\nDone! UseAI fully removed.\n"));
705
1896
  }
706
1897
  function showHelp() {
707
1898
  console.log(`
708
1899
  ${chalk.bold("Usage:")} npx @devness/useai mcp [tools...] [options]
709
1900
 
710
1901
  Configure UseAI MCP server in your AI tools.
1902
+ Default: starts daemon, installs auto-start, configures tools with HTTP.
711
1903
 
712
1904
  ${chalk.bold("Arguments:")}
713
1905
  tools Specific tool names (e.g. codex cursor vscode)
714
1906
 
715
1907
  ${chalk.bold("Options:")}
716
- --remove Remove UseAI from configured tools
1908
+ --stdio Use stdio config (legacy mode for containers/CI)
1909
+ --remove Remove UseAI from configured tools, stop daemon, remove auto-start
717
1910
  --status Show configuration status without modifying
718
1911
  -y, --yes Skip confirmation, auto-select all detected tools
719
1912
  -h, --help Show this help message
@@ -728,6 +1921,7 @@ async function runSetup(args) {
728
1921
  }
729
1922
  const isRemove = flags.has("--remove");
730
1923
  const isStatus = flags.has("--status");
1924
+ const isStdio = flags.has("--stdio");
731
1925
  const autoYes = flags.has("-y") || flags.has("--yes");
732
1926
  const explicit = toolNames.length > 0;
733
1927
  let tools = AI_TOOLS;
@@ -743,513 +1937,312 @@ async function runSetup(args) {
743
1937
  if (isStatus) {
744
1938
  showStatus(tools);
745
1939
  } else if (isRemove) {
746
- await removeFlow(tools, autoYes, explicit);
1940
+ await fullRemoveFlow(tools, autoYes, explicit);
1941
+ } else if (isStdio) {
1942
+ await stdioInstallFlow(tools, autoYes, explicit);
747
1943
  } else {
748
- await installFlow(tools, autoYes, explicit);
1944
+ await daemonInstallFlow(tools, explicit);
749
1945
  }
750
1946
  }
751
1947
  var init_setup = __esm({
752
1948
  "src/setup.ts"() {
753
1949
  "use strict";
1950
+ init_dist();
754
1951
  init_tools();
755
1952
  }
756
1953
  });
757
1954
 
758
- // src/index.ts
1955
+ // src/daemon.ts
1956
+ var daemon_exports = {};
1957
+ __export(daemon_exports, {
1958
+ startDaemon: () => startDaemon
1959
+ });
1960
+ import { createServer } from "http";
1961
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
1962
+ import { existsSync as existsSync8, renameSync as renameSync3, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
1963
+ import { join as join6 } from "path";
759
1964
  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";
1965
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1966
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1967
+ function autoSealSession(active) {
1968
+ const { session: session2 } = active;
1969
+ if (session2.sessionRecordCount === 0) return;
1970
+ const duration = session2.getSessionDuration();
1971
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1972
+ const endRecord = session2.appendToChain("session_end", {
1973
+ duration_seconds: duration,
1974
+ task_type: session2.sessionTaskType,
1975
+ languages: [],
1976
+ files_touched: 0,
1977
+ heartbeat_count: session2.heartbeatCount,
1978
+ auto_sealed: true
1979
+ });
1980
+ const sealData = JSON.stringify({
1981
+ session_id: session2.sessionId,
1982
+ client: session2.clientName,
1983
+ task_type: session2.sessionTaskType,
1984
+ languages: [],
1985
+ files_touched: 0,
1986
+ started_at: new Date(session2.sessionStartTime).toISOString(),
1987
+ ended_at: now,
1988
+ duration_seconds: duration,
1989
+ heartbeat_count: session2.heartbeatCount,
1990
+ record_count: session2.sessionRecordCount,
1991
+ chain_end_hash: endRecord.hash
1992
+ });
1993
+ const sealSignature = signHash(
1994
+ createHash4("sha256").update(sealData).digest("hex"),
1995
+ session2.signingKey
1996
+ );
1997
+ session2.appendToChain("session_seal", {
1998
+ seal: sealData,
1999
+ seal_signature: sealSignature,
2000
+ auto_sealed: true
2001
+ });
2002
+ const activePath = join6(ACTIVE_DIR, `${session2.sessionId}.jsonl`);
2003
+ const sealedPath = join6(SEALED_DIR, `${session2.sessionId}.jsonl`);
848
2004
  try {
849
- return cryptoSign(null, Buffer.from(hash), signingKey2).toString("hex");
2005
+ if (existsSync8(activePath)) {
2006
+ renameSync3(activePath, sealedPath);
2007
+ }
850
2008
  } catch {
851
- return "unsigned";
852
2009
  }
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
2010
+ const chainStartHash = session2.chainTipHash === "GENESIS" ? "GENESIS" : session2.chainTipHash;
2011
+ const seal = {
2012
+ session_id: session2.sessionId,
2013
+ client: session2.clientName,
2014
+ task_type: session2.sessionTaskType,
2015
+ languages: [],
2016
+ files_touched: 0,
2017
+ started_at: new Date(session2.sessionStartTime).toISOString(),
2018
+ ended_at: now,
2019
+ duration_seconds: duration,
2020
+ heartbeat_count: session2.heartbeatCount,
2021
+ record_count: session2.sessionRecordCount,
2022
+ chain_start_hash: chainStartHash,
2023
+ chain_end_hash: endRecord.hash,
2024
+ seal_signature: sealSignature
869
2025
  };
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 });
2026
+ const allSessions = readJson(SESSIONS_FILE, []);
2027
+ allSessions.push(seal);
2028
+ writeJson(SESSIONS_FILE, allSessions);
2029
+ }
2030
+ function resetIdleTimer(sessionId) {
2031
+ const active = sessions.get(sessionId);
2032
+ if (!active) return;
2033
+ clearTimeout(active.idleTimer);
2034
+ active.idleTimer = setTimeout(async () => {
2035
+ autoSealSession(active);
2036
+ try {
2037
+ await active.transport.close();
2038
+ } catch {
945
2039
  }
946
- }
947
- }
948
- function readJson(path, fallback) {
2040
+ sessions.delete(sessionId);
2041
+ }, IDLE_TIMEOUT_MS);
2042
+ }
2043
+ async function cleanupSession(sessionId) {
2044
+ const active = sessions.get(sessionId);
2045
+ if (!active) return;
2046
+ clearTimeout(active.idleTimer);
2047
+ autoSealSession(active);
949
2048
  try {
950
- if (!existsSync(path))
951
- return fallback;
952
- return JSON.parse(readFileSync(path, "utf-8"));
2049
+ await active.transport.close();
953
2050
  } 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
2051
  }
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) {
2052
+ sessions.delete(sessionId);
2053
+ }
2054
+ function handleHealth(res) {
2055
+ const body = JSON.stringify({
2056
+ status: "ok",
2057
+ version: VERSION,
2058
+ active_sessions: sessions.size,
2059
+ uptime_seconds: Math.round((Date.now() - startedAt) / 1e3)
2060
+ });
2061
+ res.writeHead(200, { "Content-Type": "application/json" });
2062
+ res.end(body);
2063
+ }
2064
+ function parseBody(req) {
2065
+ return new Promise((resolve, reject) => {
2066
+ const chunks = [];
2067
+ req.on("data", (chunk) => chunks.push(chunk));
2068
+ req.on("end", () => {
1032
2069
  try {
1033
- signingKey = decryptKeystore(ks);
1034
- signingAvailable = true;
1035
- return;
1036
- } catch {
2070
+ const raw = Buffer.concat(chunks).toString();
2071
+ resolve(raw ? JSON.parse(raw) : void 0);
2072
+ } catch (e) {
2073
+ reject(e);
1037
2074
  }
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`);
2075
+ });
2076
+ req.on("error", reject);
2077
+ });
1047
2078
  }
1048
- function appendToChain(type, data) {
1049
- const record = buildChainRecord(type, sessionId, data, chainTipHash, signingKey);
2079
+ async function startDaemon(port) {
2080
+ const listenPort = port ?? DAEMON_PORT;
1050
2081
  ensureDir();
1051
- appendFileSync(sessionChainPath(), JSON.stringify(record) + "\n", "utf-8");
1052
- chainTipHash = record.hash;
1053
- sessionRecordCount++;
1054
- return record;
2082
+ const server2 = createServer(async (req, res) => {
2083
+ const url = new URL(req.url, `http://${req.headers.host}`);
2084
+ if (url.pathname === "/health" && req.method === "GET") {
2085
+ handleHealth(res);
2086
+ return;
2087
+ }
2088
+ if (url.pathname !== "/mcp") {
2089
+ res.writeHead(404, { "Content-Type": "application/json" });
2090
+ res.end(JSON.stringify({ error: "Not found" }));
2091
+ return;
2092
+ }
2093
+ try {
2094
+ if (req.method === "POST") {
2095
+ const body = await parseBody(req);
2096
+ const sid = req.headers["mcp-session-id"];
2097
+ if (sid && sessions.has(sid)) {
2098
+ resetIdleTimer(sid);
2099
+ await sessions.get(sid).transport.handleRequest(req, res, body);
2100
+ } else if (!sid && isInitializeRequest(body)) {
2101
+ const sessionState = new SessionState();
2102
+ try {
2103
+ sessionState.initializeKeystore();
2104
+ } catch {
2105
+ }
2106
+ const mcpServer = new McpServer({
2107
+ name: "UseAI",
2108
+ version: VERSION
2109
+ });
2110
+ registerTools(mcpServer, sessionState);
2111
+ const transport = new StreamableHTTPServerTransport({
2112
+ sessionIdGenerator: () => randomUUID4(),
2113
+ onsessioninitialized: (newSid) => {
2114
+ const idleTimer = setTimeout(async () => {
2115
+ await cleanupSession(newSid);
2116
+ }, IDLE_TIMEOUT_MS);
2117
+ sessions.set(newSid, {
2118
+ transport,
2119
+ server: mcpServer,
2120
+ session: sessionState,
2121
+ idleTimer
2122
+ });
2123
+ }
2124
+ });
2125
+ transport.onclose = () => {
2126
+ const closedSid = transport.sessionId;
2127
+ if (closedSid && sessions.has(closedSid)) {
2128
+ const active = sessions.get(closedSid);
2129
+ clearTimeout(active.idleTimer);
2130
+ autoSealSession(active);
2131
+ sessions.delete(closedSid);
2132
+ }
2133
+ };
2134
+ await mcpServer.connect(transport);
2135
+ await transport.handleRequest(req, res, body);
2136
+ } else {
2137
+ res.writeHead(400, { "Content-Type": "application/json" });
2138
+ res.end(JSON.stringify({
2139
+ jsonrpc: "2.0",
2140
+ error: { code: -32e3, message: "Bad Request: No valid session ID provided" },
2141
+ id: null
2142
+ }));
2143
+ }
2144
+ } else if (req.method === "GET") {
2145
+ const sid = req.headers["mcp-session-id"];
2146
+ if (!sid || !sessions.has(sid)) {
2147
+ res.writeHead(400, { "Content-Type": "application/json" });
2148
+ res.end(JSON.stringify({ error: "Invalid or missing session ID" }));
2149
+ return;
2150
+ }
2151
+ resetIdleTimer(sid);
2152
+ await sessions.get(sid).transport.handleRequest(req, res);
2153
+ } else if (req.method === "DELETE") {
2154
+ const sid = req.headers["mcp-session-id"];
2155
+ if (!sid || !sessions.has(sid)) {
2156
+ res.writeHead(400, { "Content-Type": "application/json" });
2157
+ res.end(JSON.stringify({ error: "Invalid or missing session ID" }));
2158
+ return;
2159
+ }
2160
+ await sessions.get(sid).transport.handleRequest(req, res);
2161
+ } else {
2162
+ res.writeHead(405, { "Content-Type": "application/json" });
2163
+ res.end(JSON.stringify({ error: "Method not allowed" }));
2164
+ }
2165
+ } catch (error) {
2166
+ if (!res.headersSent) {
2167
+ res.writeHead(500, { "Content-Type": "application/json" });
2168
+ res.end(JSON.stringify({
2169
+ jsonrpc: "2.0",
2170
+ error: { code: -32603, message: "Internal server error" },
2171
+ id: null
2172
+ }));
2173
+ }
2174
+ }
2175
+ });
2176
+ const pidData = JSON.stringify({
2177
+ pid: process.pid,
2178
+ port: listenPort,
2179
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
2180
+ });
2181
+ writeFileSync4(DAEMON_PID_FILE, pidData + "\n");
2182
+ const shutdown = async (signal) => {
2183
+ for (const [sid] of sessions) {
2184
+ await cleanupSession(sid);
2185
+ }
2186
+ try {
2187
+ if (existsSync8(DAEMON_PID_FILE)) {
2188
+ unlinkSync4(DAEMON_PID_FILE);
2189
+ }
2190
+ } catch {
2191
+ }
2192
+ server2.close(() => {
2193
+ process.exit(0);
2194
+ });
2195
+ setTimeout(() => process.exit(0), 5e3).unref();
2196
+ };
2197
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
2198
+ process.on("SIGINT", () => shutdown("SIGINT"));
2199
+ server2.listen(listenPort, "127.0.0.1", () => {
2200
+ console.log(`UseAI daemon listening on http://127.0.0.1:${listenPort}`);
2201
+ console.log(`PID: ${process.pid}`);
2202
+ });
1055
2203
  }
2204
+ var IDLE_TIMEOUT_MS, sessions, startedAt;
2205
+ var init_daemon2 = __esm({
2206
+ "src/daemon.ts"() {
2207
+ "use strict";
2208
+ init_dist();
2209
+ init_session_state();
2210
+ init_register_tools();
2211
+ IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
2212
+ sessions = /* @__PURE__ */ new Map();
2213
+ startedAt = Date.now();
2214
+ }
2215
+ });
1056
2216
 
1057
2217
  // src/index.ts
2218
+ init_dist();
2219
+ init_session_state();
2220
+ init_register_tools();
2221
+ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
2222
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1058
2223
  if (process.argv[2] === "mcp") {
1059
2224
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
1060
2225
  await runSetup2(process.argv.slice(3));
1061
2226
  process.exit(0);
1062
2227
  }
1063
- function getConfig() {
1064
- return readJson(CONFIG_FILE, {
1065
- milestone_tracking: true,
1066
- auto_sync: true
2228
+ if (process.argv[2] === "daemon") {
2229
+ const { startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_daemon2(), daemon_exports));
2230
+ const portArg = process.argv.indexOf("--port");
2231
+ const port = portArg !== -1 ? parseInt(process.argv[portArg + 1], 10) : void 0;
2232
+ await startDaemon2(port);
2233
+ await new Promise(() => {
1067
2234
  });
1068
2235
  }
1069
- function getSessions() {
1070
- return readJson(SESSIONS_FILE, []);
1071
- }
1072
- function getMilestones() {
1073
- return readJson(MILESTONES_FILE, []);
1074
- }
1075
- var server = new McpServer({
2236
+ var session = new SessionState();
2237
+ var server = new McpServer2({
1076
2238
  name: "UseAI",
1077
2239
  version: VERSION
1078
2240
  });
1079
- server.tool(
1080
- "useai_session_start",
1081
- "Start tracking an AI coding session. Call this at the beginning of a conversation. Records which AI tool and task type \u2014 stored LOCALLY at ~/.useai/. No data is sent anywhere. No code, prompts, or private data is captured.",
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
- All data stored locally at ${USEAI_DIR}. No network calls made.`
1101
- }
1102
- ]
1103
- };
1104
- }
1105
- );
1106
- server.tool(
1107
- "useai_heartbeat",
1108
- "Record a heartbeat for the current AI coding session. Call this periodically during long conversations (every 10-15 minutes). Only records a timestamp \u2014 stored locally, never sent anywhere.",
1109
- {},
1110
- async () => {
1111
- incrementHeartbeat();
1112
- appendToChain("heartbeat", {
1113
- heartbeat_number: heartbeatCount,
1114
- cumulative_seconds: getSessionDuration()
1115
- });
1116
- return {
1117
- content: [
1118
- {
1119
- type: "text",
1120
- text: `Heartbeat recorded locally. Session active for ${formatDuration(getSessionDuration())}.`
1121
- }
1122
- ]
1123
- };
1124
- }
1125
- );
1126
- server.tool(
1127
- "useai_session_end",
1128
- "End the current AI coding session. Creates a cryptographically sealed session record and optionally records milestones \u2014 what was accomplished. Stats auto-sync to useai.dev daily. To publish milestones: run `useai publish` from the CLI. IMPORTANT: Milestone descriptions must be GENERIC. Do not include project names, file names, file paths, API endpoints, database names, company names, or any project-specific information. Use general terms like 'authentication system', 'data validation layer', 'responsive dashboard'. Think: would this description reveal what company or project this is for? If yes, make it more generic.",
1129
- {
1130
- task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task was the developer working on?"),
1131
- languages: z2.array(z2.string()).optional().describe("Programming languages used (e.g. ['typescript', 'python'])"),
1132
- files_touched_count: z2.number().optional().describe("Approximate number of files created or modified (count only, no names)"),
1133
- milestones: z2.array(z2.object({
1134
- 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/'"),
1135
- category: z2.enum(["feature", "bugfix", "refactor", "test", "docs", "setup", "deployment", "other"]).describe("Type of work completed"),
1136
- complexity: z2.enum(["simple", "medium", "complex"]).optional().describe("How complex was this task?")
1137
- })).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.")
1138
- },
1139
- async ({ task_type, languages, files_touched_count, milestones: milestonesInput }) => {
1140
- const duration = getSessionDuration();
1141
- const now = (/* @__PURE__ */ new Date()).toISOString();
1142
- const finalTaskType = task_type ?? sessionTaskType;
1143
- const chainStartHash = chainTipHash === "GENESIS" ? "GENESIS" : chainTipHash;
1144
- const endRecord = appendToChain("session_end", {
1145
- duration_seconds: duration,
1146
- task_type: finalTaskType,
1147
- languages: languages ?? [],
1148
- files_touched: files_touched_count ?? 0,
1149
- heartbeat_count: heartbeatCount
1150
- });
1151
- const sealData = JSON.stringify({
1152
- session_id: sessionId,
1153
- client: clientName,
1154
- task_type: finalTaskType,
1155
- languages: languages ?? [],
1156
- files_touched: files_touched_count ?? 0,
1157
- started_at: new Date(sessionStartTime).toISOString(),
1158
- ended_at: now,
1159
- duration_seconds: duration,
1160
- heartbeat_count: heartbeatCount,
1161
- record_count: sessionRecordCount,
1162
- chain_end_hash: endRecord.hash
1163
- });
1164
- const sealSignature = signHash(
1165
- createHash3("sha256").update(sealData).digest("hex"),
1166
- signingKey
1167
- );
1168
- appendToChain("session_seal", {
1169
- seal: sealData,
1170
- seal_signature: sealSignature
1171
- });
1172
- const activePath = join4(ACTIVE_DIR, `${sessionId}.jsonl`);
1173
- const sealedPath = join4(SEALED_DIR, `${sessionId}.jsonl`);
1174
- try {
1175
- if (existsSync4(activePath)) {
1176
- renameSync2(activePath, sealedPath);
1177
- }
1178
- } catch {
1179
- }
1180
- const seal = {
1181
- session_id: sessionId,
1182
- client: clientName,
1183
- task_type: finalTaskType,
1184
- languages: languages ?? [],
1185
- files_touched: files_touched_count ?? 0,
1186
- started_at: new Date(sessionStartTime).toISOString(),
1187
- ended_at: now,
1188
- duration_seconds: duration,
1189
- heartbeat_count: heartbeatCount,
1190
- record_count: sessionRecordCount,
1191
- chain_start_hash: chainStartHash,
1192
- chain_end_hash: endRecord.hash,
1193
- seal_signature: sealSignature
1194
- };
1195
- const sessions = getSessions();
1196
- sessions.push(seal);
1197
- writeJson(SESSIONS_FILE, sessions);
1198
- let milestoneCount = 0;
1199
- if (milestonesInput && milestonesInput.length > 0) {
1200
- const config = getConfig();
1201
- if (config.milestone_tracking) {
1202
- const durationMinutes = Math.round(duration / 60);
1203
- const allMilestones = getMilestones();
1204
- for (const m of milestonesInput) {
1205
- const record = appendToChain("milestone", {
1206
- title: m.title,
1207
- category: m.category,
1208
- complexity: m.complexity ?? "medium",
1209
- duration_minutes: durationMinutes,
1210
- languages: languages ?? []
1211
- });
1212
- const milestone = {
1213
- id: `m_${randomUUID3().slice(0, 8)}`,
1214
- session_id: sessionId,
1215
- title: m.title,
1216
- category: m.category,
1217
- complexity: m.complexity ?? "medium",
1218
- duration_minutes: durationMinutes,
1219
- languages: languages ?? [],
1220
- client: clientName,
1221
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
1222
- published: false,
1223
- published_at: null,
1224
- chain_hash: record.hash
1225
- };
1226
- allMilestones.push(milestone);
1227
- milestoneCount++;
1228
- }
1229
- writeJson(MILESTONES_FILE, allMilestones);
1230
- }
1231
- }
1232
- const durationStr = formatDuration(duration);
1233
- const langStr = languages && languages.length > 0 ? ` using ${languages.join(", ")}` : "";
1234
- const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
1235
- return {
1236
- content: [
1237
- {
1238
- type: "text",
1239
- text: `Session sealed: ${durationStr} ${finalTaskType} on ${clientName}${langStr}
1240
- ${sessionRecordCount} chain records \xB7 ${signingAvailable ? "Ed25519 signed" : "unsigned"}${milestoneStr}
1241
- Stored at: ${sealedPath}
1242
-
1243
- To publish: run \`useai sync\` from your terminal.`
1244
- }
1245
- ]
1246
- };
1247
- }
1248
- );
2241
+ registerTools(server, session);
1249
2242
  async function main() {
1250
2243
  ensureDir();
1251
2244
  try {
1252
- initializeKeystore();
2245
+ session.initializeKeystore();
1253
2246
  } catch {
1254
2247
  }
1255
2248
  const transport = new StdioServerTransport();