@devness/useai 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.js +651 -96
- package/package.json +11 -12
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 useai.dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
CHANGED
|
@@ -83,7 +83,7 @@ var init_types = __esm({
|
|
|
83
83
|
// ../shared/dist/constants/paths.js
|
|
84
84
|
import { join } from "path";
|
|
85
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;
|
|
86
|
+
var USEAI_DIR, DATA_DIR, ACTIVE_DIR, SEALED_DIR, KEYSTORE_FILE, CONFIG_FILE, SESSIONS_FILE, MILESTONES_FILE, DAEMON_PID_FILE, USEAI_HOOKS_DIR, DAEMON_PORT, DAEMON_LOG_FILE, DAEMON_MCP_URL, DAEMON_HEALTH_URL, LAUNCHD_PLIST_PATH, SYSTEMD_SERVICE_PATH, WINDOWS_STARTUP_SCRIPT_PATH;
|
|
87
87
|
var init_paths = __esm({
|
|
88
88
|
"../shared/dist/constants/paths.js"() {
|
|
89
89
|
"use strict";
|
|
@@ -96,6 +96,7 @@ var init_paths = __esm({
|
|
|
96
96
|
SESSIONS_FILE = join(DATA_DIR, "sessions.json");
|
|
97
97
|
MILESTONES_FILE = join(DATA_DIR, "milestones.json");
|
|
98
98
|
DAEMON_PID_FILE = join(USEAI_DIR, "daemon.pid");
|
|
99
|
+
USEAI_HOOKS_DIR = join(USEAI_DIR, "hooks");
|
|
99
100
|
DAEMON_PORT = 19200;
|
|
100
101
|
DAEMON_LOG_FILE = join(USEAI_DIR, "daemon.log");
|
|
101
102
|
DAEMON_MCP_URL = `http://127.0.0.1:${DAEMON_PORT}/mcp`;
|
|
@@ -111,7 +112,7 @@ var VERSION;
|
|
|
111
112
|
var init_version = __esm({
|
|
112
113
|
"../shared/dist/constants/version.js"() {
|
|
113
114
|
"use strict";
|
|
114
|
-
VERSION = "0.4.
|
|
115
|
+
VERSION = "0.4.6";
|
|
115
116
|
}
|
|
116
117
|
});
|
|
117
118
|
|
|
@@ -836,6 +837,136 @@ var init_daemon = __esm({
|
|
|
836
837
|
}
|
|
837
838
|
});
|
|
838
839
|
|
|
840
|
+
// ../shared/dist/hooks/claude-code.js
|
|
841
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, chmodSync } from "fs";
|
|
842
|
+
import { join as join3 } from "path";
|
|
843
|
+
import { homedir as homedir3 } from "os";
|
|
844
|
+
function readSettings() {
|
|
845
|
+
if (!existsSync5(CLAUDE_SETTINGS_PATH))
|
|
846
|
+
return {};
|
|
847
|
+
try {
|
|
848
|
+
return JSON.parse(readFileSync3(CLAUDE_SETTINGS_PATH, "utf-8"));
|
|
849
|
+
} catch {
|
|
850
|
+
return {};
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function writeSettings(settings) {
|
|
854
|
+
mkdirSync3(join3(homedir3(), ".claude"), { recursive: true });
|
|
855
|
+
writeFileSync3(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
|
|
856
|
+
}
|
|
857
|
+
function installClaudeCodeHooks() {
|
|
858
|
+
mkdirSync3(USEAI_HOOKS_DIR, { recursive: true });
|
|
859
|
+
writeFileSync3(STOP_GUARD_PATH, STOP_GUARD_SCRIPT);
|
|
860
|
+
try {
|
|
861
|
+
chmodSync(STOP_GUARD_PATH, "755");
|
|
862
|
+
} catch {
|
|
863
|
+
}
|
|
864
|
+
const settings = readSettings();
|
|
865
|
+
const hooks = settings["hooks"] ?? {};
|
|
866
|
+
const stopCmd = `node "${STOP_GUARD_PATH}"`;
|
|
867
|
+
const sealCmd = `curl -sf -X POST http://127.0.0.1:${DAEMON_PORT}/api/seal-active --max-time 3 2>/dev/null || true`;
|
|
868
|
+
let changed = false;
|
|
869
|
+
if (!hooks["Stop"])
|
|
870
|
+
hooks["Stop"] = [];
|
|
871
|
+
const stopArr = hooks["Stop"];
|
|
872
|
+
const hasStop = stopArr.some((g) => {
|
|
873
|
+
const inner = g["hooks"];
|
|
874
|
+
return inner?.some((h) => h["command"]?.includes("stop-guard"));
|
|
875
|
+
});
|
|
876
|
+
if (!hasStop) {
|
|
877
|
+
stopArr.push({ hooks: [{ type: "command", command: stopCmd, timeout: 10 }] });
|
|
878
|
+
changed = true;
|
|
879
|
+
}
|
|
880
|
+
if (!hooks["SessionEnd"])
|
|
881
|
+
hooks["SessionEnd"] = [];
|
|
882
|
+
const endArr = hooks["SessionEnd"];
|
|
883
|
+
const hasEnd = endArr.some((g) => {
|
|
884
|
+
const inner = g["hooks"];
|
|
885
|
+
return inner?.some((h) => h["command"]?.includes("seal-active"));
|
|
886
|
+
});
|
|
887
|
+
if (!hasEnd) {
|
|
888
|
+
endArr.push({ hooks: [{ type: "command", command: sealCmd, timeout: 5 }] });
|
|
889
|
+
changed = true;
|
|
890
|
+
}
|
|
891
|
+
settings["hooks"] = hooks;
|
|
892
|
+
writeSettings(settings);
|
|
893
|
+
return changed;
|
|
894
|
+
}
|
|
895
|
+
function removeClaudeCodeHooks() {
|
|
896
|
+
if (existsSync5(CLAUDE_SETTINGS_PATH)) {
|
|
897
|
+
try {
|
|
898
|
+
const settings = readSettings();
|
|
899
|
+
const hooks = settings["hooks"];
|
|
900
|
+
if (hooks) {
|
|
901
|
+
if (hooks["Stop"]) {
|
|
902
|
+
hooks["Stop"] = hooks["Stop"].filter((g) => {
|
|
903
|
+
const inner = g["hooks"];
|
|
904
|
+
return !inner?.some((h) => h["command"]?.includes("stop-guard"));
|
|
905
|
+
});
|
|
906
|
+
if (hooks["Stop"].length === 0)
|
|
907
|
+
delete hooks["Stop"];
|
|
908
|
+
}
|
|
909
|
+
if (hooks["SessionEnd"]) {
|
|
910
|
+
hooks["SessionEnd"] = hooks["SessionEnd"].filter((g) => {
|
|
911
|
+
const inner = g["hooks"];
|
|
912
|
+
return !inner?.some((h) => h["command"]?.includes("seal-active"));
|
|
913
|
+
});
|
|
914
|
+
if (hooks["SessionEnd"].length === 0)
|
|
915
|
+
delete hooks["SessionEnd"];
|
|
916
|
+
}
|
|
917
|
+
if (Object.keys(hooks).length === 0)
|
|
918
|
+
delete settings["hooks"];
|
|
919
|
+
}
|
|
920
|
+
writeSettings(settings);
|
|
921
|
+
} catch {
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
try {
|
|
925
|
+
if (existsSync5(STOP_GUARD_PATH))
|
|
926
|
+
unlinkSync3(STOP_GUARD_PATH);
|
|
927
|
+
} catch {
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
var STOP_GUARD_PATH, CLAUDE_SETTINGS_PATH, STOP_GUARD_SCRIPT;
|
|
931
|
+
var init_claude_code = __esm({
|
|
932
|
+
"../shared/dist/hooks/claude-code.js"() {
|
|
933
|
+
"use strict";
|
|
934
|
+
init_paths();
|
|
935
|
+
STOP_GUARD_PATH = join3(USEAI_HOOKS_DIR, "stop-guard.js");
|
|
936
|
+
CLAUDE_SETTINGS_PATH = join3(homedir3(), ".claude", "settings.json");
|
|
937
|
+
STOP_GUARD_SCRIPT = `#!/usr/bin/env node
|
|
938
|
+
'use strict';
|
|
939
|
+
const { readFileSync } = require('node:fs');
|
|
940
|
+
let input = '';
|
|
941
|
+
process.stdin.setEncoding('utf-8');
|
|
942
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
943
|
+
process.stdin.on('end', () => {
|
|
944
|
+
try {
|
|
945
|
+
const hook = JSON.parse(input);
|
|
946
|
+
if (hook.stop_hook_active) process.exit(0);
|
|
947
|
+
if (!hook.transcript_path) process.exit(0);
|
|
948
|
+
let transcript;
|
|
949
|
+
try { transcript = readFileSync(hook.transcript_path, 'utf-8'); } catch { process.exit(0); }
|
|
950
|
+
if (!transcript.includes('useai_session_start')) process.exit(0);
|
|
951
|
+
if (transcript.includes('useai_session_end')) process.exit(0);
|
|
952
|
+
process.stdout.write(JSON.stringify({
|
|
953
|
+
decision: 'block',
|
|
954
|
+
reason: 'Before ending, call useai_session_end with milestones (generic descriptions of accomplishments), languages used, and files_touched_count.'
|
|
955
|
+
}));
|
|
956
|
+
} catch { process.exit(0); }
|
|
957
|
+
});
|
|
958
|
+
`;
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// ../shared/dist/hooks/index.js
|
|
963
|
+
var init_hooks = __esm({
|
|
964
|
+
"../shared/dist/hooks/index.js"() {
|
|
965
|
+
"use strict";
|
|
966
|
+
init_claude_code();
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
839
970
|
// ../shared/dist/index.js
|
|
840
971
|
var init_dist = __esm({
|
|
841
972
|
"../shared/dist/index.js"() {
|
|
@@ -846,12 +977,13 @@ var init_dist = __esm({
|
|
|
846
977
|
init_validation();
|
|
847
978
|
init_utils();
|
|
848
979
|
init_daemon();
|
|
980
|
+
init_hooks();
|
|
849
981
|
}
|
|
850
982
|
});
|
|
851
983
|
|
|
852
984
|
// src/session-state.ts
|
|
853
|
-
import { appendFileSync, existsSync as
|
|
854
|
-
import { join as
|
|
985
|
+
import { appendFileSync, existsSync as existsSync6 } from "fs";
|
|
986
|
+
import { join as join4 } from "path";
|
|
855
987
|
var SessionState;
|
|
856
988
|
var init_session_state = __esm({
|
|
857
989
|
"src/session-state.ts"() {
|
|
@@ -901,7 +1033,7 @@ var init_session_state = __esm({
|
|
|
901
1033
|
}
|
|
902
1034
|
initializeKeystore() {
|
|
903
1035
|
ensureDir();
|
|
904
|
-
if (
|
|
1036
|
+
if (existsSync6(KEYSTORE_FILE)) {
|
|
905
1037
|
const ks = readJson(KEYSTORE_FILE, null);
|
|
906
1038
|
if (ks) {
|
|
907
1039
|
try {
|
|
@@ -919,7 +1051,7 @@ var init_session_state = __esm({
|
|
|
919
1051
|
}
|
|
920
1052
|
/** Path to this session's chain file in the active directory */
|
|
921
1053
|
sessionChainPath() {
|
|
922
|
-
return
|
|
1054
|
+
return join4(ACTIVE_DIR, `${this.sessionId}.jsonl`);
|
|
923
1055
|
}
|
|
924
1056
|
appendToChain(type, data) {
|
|
925
1057
|
const record = buildChainRecord(type, this.sessionId, data, this.chainTipHash, this.signingKey);
|
|
@@ -936,8 +1068,8 @@ var init_session_state = __esm({
|
|
|
936
1068
|
// src/register-tools.ts
|
|
937
1069
|
import { z as z2 } from "zod";
|
|
938
1070
|
import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
|
|
939
|
-
import { existsSync as
|
|
940
|
-
import { join as
|
|
1071
|
+
import { existsSync as existsSync7, renameSync as renameSync2 } from "fs";
|
|
1072
|
+
import { join as join5 } from "path";
|
|
941
1073
|
function getConfig() {
|
|
942
1074
|
return readJson(CONFIG_FILE, {
|
|
943
1075
|
milestone_tracking: true,
|
|
@@ -1043,10 +1175,10 @@ Session: ${session2.sessionId.slice(0, 8)} \xB7 Chain: ${record.hash.slice(0, 12
|
|
|
1043
1175
|
seal: sealData,
|
|
1044
1176
|
seal_signature: sealSignature
|
|
1045
1177
|
});
|
|
1046
|
-
const activePath =
|
|
1047
|
-
const sealedPath =
|
|
1178
|
+
const activePath = join5(ACTIVE_DIR, `${session2.sessionId}.jsonl`);
|
|
1179
|
+
const sealedPath = join5(SEALED_DIR, `${session2.sessionId}.jsonl`);
|
|
1048
1180
|
try {
|
|
1049
|
-
if (
|
|
1181
|
+
if (existsSync7(activePath)) {
|
|
1050
1182
|
renameSync2(activePath, sealedPath);
|
|
1051
1183
|
}
|
|
1052
1184
|
} catch {
|
|
@@ -1126,9 +1258,9 @@ var init_register_tools = __esm({
|
|
|
1126
1258
|
|
|
1127
1259
|
// src/tools.ts
|
|
1128
1260
|
import { execSync as execSync3 } from "child_process";
|
|
1129
|
-
import { existsSync as
|
|
1130
|
-
import { dirname as dirname2, join as
|
|
1131
|
-
import { homedir as
|
|
1261
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
|
|
1262
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
1263
|
+
import { homedir as homedir4 } from "os";
|
|
1132
1264
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
1133
1265
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
1134
1266
|
function installStandardHttp(configPath) {
|
|
@@ -1156,9 +1288,9 @@ function hasBinary(name) {
|
|
|
1156
1288
|
}
|
|
1157
1289
|
}
|
|
1158
1290
|
function readJsonFile(path) {
|
|
1159
|
-
if (!
|
|
1291
|
+
if (!existsSync8(path)) return {};
|
|
1160
1292
|
try {
|
|
1161
|
-
const raw =
|
|
1293
|
+
const raw = readFileSync4(path, "utf-8").trim();
|
|
1162
1294
|
if (!raw) return {};
|
|
1163
1295
|
return JSON.parse(raw);
|
|
1164
1296
|
} catch {
|
|
@@ -1166,8 +1298,8 @@ function readJsonFile(path) {
|
|
|
1166
1298
|
}
|
|
1167
1299
|
}
|
|
1168
1300
|
function writeJsonFile(path, data) {
|
|
1169
|
-
|
|
1170
|
-
|
|
1301
|
+
mkdirSync4(dirname2(path), { recursive: true });
|
|
1302
|
+
writeFileSync4(path, JSON.stringify(data, null, 2) + "\n");
|
|
1171
1303
|
}
|
|
1172
1304
|
function isConfiguredStandard(configPath) {
|
|
1173
1305
|
const config = readJsonFile(configPath);
|
|
@@ -1248,9 +1380,9 @@ function removeZed(configPath) {
|
|
|
1248
1380
|
}
|
|
1249
1381
|
}
|
|
1250
1382
|
function readTomlFile(path) {
|
|
1251
|
-
if (!
|
|
1383
|
+
if (!existsSync8(path)) return {};
|
|
1252
1384
|
try {
|
|
1253
|
-
const raw =
|
|
1385
|
+
const raw = readFileSync4(path, "utf-8").trim();
|
|
1254
1386
|
if (!raw) return {};
|
|
1255
1387
|
return parseToml(raw);
|
|
1256
1388
|
} catch {
|
|
@@ -1258,8 +1390,8 @@ function readTomlFile(path) {
|
|
|
1258
1390
|
}
|
|
1259
1391
|
}
|
|
1260
1392
|
function writeTomlFile(path, data) {
|
|
1261
|
-
|
|
1262
|
-
|
|
1393
|
+
mkdirSync4(dirname2(path), { recursive: true });
|
|
1394
|
+
writeFileSync4(path, stringifyToml(data) + "\n");
|
|
1263
1395
|
}
|
|
1264
1396
|
function isConfiguredToml(configPath) {
|
|
1265
1397
|
const config = readTomlFile(configPath);
|
|
@@ -1287,9 +1419,9 @@ function removeToml(configPath) {
|
|
|
1287
1419
|
}
|
|
1288
1420
|
}
|
|
1289
1421
|
function readYamlFile(path) {
|
|
1290
|
-
if (!
|
|
1422
|
+
if (!existsSync8(path)) return {};
|
|
1291
1423
|
try {
|
|
1292
|
-
const raw =
|
|
1424
|
+
const raw = readFileSync4(path, "utf-8").trim();
|
|
1293
1425
|
if (!raw) return {};
|
|
1294
1426
|
return parseYaml(raw) ?? {};
|
|
1295
1427
|
} catch {
|
|
@@ -1297,8 +1429,8 @@ function readYamlFile(path) {
|
|
|
1297
1429
|
}
|
|
1298
1430
|
}
|
|
1299
1431
|
function writeYamlFile(path, data) {
|
|
1300
|
-
|
|
1301
|
-
|
|
1432
|
+
mkdirSync4(dirname2(path), { recursive: true });
|
|
1433
|
+
writeFileSync4(path, stringifyYaml(data));
|
|
1302
1434
|
}
|
|
1303
1435
|
function isConfiguredYaml(configPath) {
|
|
1304
1436
|
const config = readYamlFile(configPath);
|
|
@@ -1332,49 +1464,49 @@ function removeYaml(configPath) {
|
|
|
1332
1464
|
}
|
|
1333
1465
|
}
|
|
1334
1466
|
function hasInstructionsBlock(filePath) {
|
|
1335
|
-
if (!
|
|
1467
|
+
if (!existsSync8(filePath)) return false;
|
|
1336
1468
|
try {
|
|
1337
|
-
return
|
|
1469
|
+
return readFileSync4(filePath, "utf-8").includes(INSTRUCTIONS_START);
|
|
1338
1470
|
} catch {
|
|
1339
1471
|
return false;
|
|
1340
1472
|
}
|
|
1341
1473
|
}
|
|
1342
1474
|
function injectInstructions(config) {
|
|
1343
|
-
|
|
1475
|
+
mkdirSync4(dirname2(config.path), { recursive: true });
|
|
1344
1476
|
if (config.method === "create") {
|
|
1345
|
-
|
|
1477
|
+
writeFileSync4(config.path, USEAI_INSTRUCTIONS + "\n");
|
|
1346
1478
|
return;
|
|
1347
1479
|
}
|
|
1348
1480
|
if (hasInstructionsBlock(config.path)) return;
|
|
1349
1481
|
let existing = "";
|
|
1350
|
-
if (
|
|
1351
|
-
existing =
|
|
1482
|
+
if (existsSync8(config.path)) {
|
|
1483
|
+
existing = readFileSync4(config.path, "utf-8");
|
|
1352
1484
|
}
|
|
1353
1485
|
const separator = existing && !existing.endsWith("\n") ? "\n\n" : existing ? "\n" : "";
|
|
1354
|
-
|
|
1486
|
+
writeFileSync4(config.path, existing + separator + USEAI_INSTRUCTIONS_BLOCK + "\n");
|
|
1355
1487
|
}
|
|
1356
1488
|
function removeInstructions(config) {
|
|
1357
1489
|
if (config.method === "create") {
|
|
1358
|
-
if (
|
|
1490
|
+
if (existsSync8(config.path)) {
|
|
1359
1491
|
try {
|
|
1360
|
-
|
|
1492
|
+
unlinkSync4(config.path);
|
|
1361
1493
|
} catch {
|
|
1362
1494
|
}
|
|
1363
1495
|
}
|
|
1364
1496
|
return;
|
|
1365
1497
|
}
|
|
1366
|
-
if (!
|
|
1498
|
+
if (!existsSync8(config.path)) return;
|
|
1367
1499
|
try {
|
|
1368
|
-
const content =
|
|
1500
|
+
const content = readFileSync4(config.path, "utf-8");
|
|
1369
1501
|
const escaped = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1370
1502
|
const regex = new RegExp(
|
|
1371
1503
|
`\\n?${escaped(INSTRUCTIONS_START)}[\\s\\S]*?${escaped(INSTRUCTIONS_END)}\\n?`
|
|
1372
1504
|
);
|
|
1373
1505
|
const cleaned = content.replace(regex, "").trim();
|
|
1374
1506
|
if (cleaned) {
|
|
1375
|
-
|
|
1507
|
+
writeFileSync4(config.path, cleaned + "\n");
|
|
1376
1508
|
} else {
|
|
1377
|
-
|
|
1509
|
+
unlinkSync4(config.path);
|
|
1378
1510
|
}
|
|
1379
1511
|
} catch {
|
|
1380
1512
|
}
|
|
@@ -1450,7 +1582,7 @@ var init_tools = __esm({
|
|
|
1450
1582
|
};
|
|
1451
1583
|
MCP_HTTP_URL = DAEMON_MCP_URL;
|
|
1452
1584
|
MCP_HTTP_ENTRY = { type: "http", url: MCP_HTTP_URL };
|
|
1453
|
-
home =
|
|
1585
|
+
home = homedir4();
|
|
1454
1586
|
INSTRUCTIONS_START = "<!-- useai:start -->";
|
|
1455
1587
|
INSTRUCTIONS_END = "<!-- useai:end -->";
|
|
1456
1588
|
USEAI_INSTRUCTIONS = [
|
|
@@ -1470,23 +1602,23 @@ ${INSTRUCTIONS_END}`;
|
|
|
1470
1602
|
toml: { isConfigured: isConfiguredToml, install: installToml, remove: removeToml },
|
|
1471
1603
|
yaml: { isConfigured: isConfiguredYaml, install: installYaml, remove: removeYaml }
|
|
1472
1604
|
};
|
|
1473
|
-
appSupport =
|
|
1605
|
+
appSupport = join6(home, "Library", "Application Support");
|
|
1474
1606
|
AI_TOOLS = [
|
|
1475
1607
|
createTool({
|
|
1476
1608
|
id: "claude-code",
|
|
1477
1609
|
name: "Claude Code",
|
|
1478
1610
|
configFormat: "standard",
|
|
1479
|
-
configPath:
|
|
1480
|
-
detect: () => hasBinary("claude") ||
|
|
1481
|
-
instructions: { method: "append", path:
|
|
1611
|
+
configPath: join6(home, ".claude.json"),
|
|
1612
|
+
detect: () => hasBinary("claude") || existsSync8(join6(home, ".claude.json")),
|
|
1613
|
+
instructions: { method: "append", path: join6(home, ".claude", "CLAUDE.md") },
|
|
1482
1614
|
supportsUrl: true
|
|
1483
1615
|
}),
|
|
1484
1616
|
createTool({
|
|
1485
1617
|
id: "cursor",
|
|
1486
1618
|
name: "Cursor",
|
|
1487
1619
|
configFormat: "standard",
|
|
1488
|
-
configPath:
|
|
1489
|
-
detect: () =>
|
|
1620
|
+
configPath: join6(home, ".cursor", "mcp.json"),
|
|
1621
|
+
detect: () => existsSync8(join6(home, ".cursor")),
|
|
1490
1622
|
manualHint: "Open Cursor Settings \u2192 Rules \u2192 User Rules and paste the instructions below.",
|
|
1491
1623
|
supportsUrl: true
|
|
1492
1624
|
}),
|
|
@@ -1494,51 +1626,51 @@ ${INSTRUCTIONS_END}`;
|
|
|
1494
1626
|
id: "windsurf",
|
|
1495
1627
|
name: "Windsurf",
|
|
1496
1628
|
configFormat: "standard",
|
|
1497
|
-
configPath:
|
|
1498
|
-
detect: () =>
|
|
1499
|
-
instructions: { method: "append", path:
|
|
1629
|
+
configPath: join6(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
1630
|
+
detect: () => existsSync8(join6(home, ".codeium", "windsurf")),
|
|
1631
|
+
instructions: { method: "append", path: join6(home, ".codeium", "windsurf", "memories", "global_rules.md") },
|
|
1500
1632
|
supportsUrl: true
|
|
1501
1633
|
}),
|
|
1502
1634
|
createTool({
|
|
1503
1635
|
id: "vscode",
|
|
1504
1636
|
name: "VS Code",
|
|
1505
1637
|
configFormat: "vscode",
|
|
1506
|
-
configPath:
|
|
1507
|
-
detect: () =>
|
|
1508
|
-
instructions: { method: "create", path:
|
|
1638
|
+
configPath: join6(appSupport, "Code", "User", "mcp.json"),
|
|
1639
|
+
detect: () => existsSync8(join6(appSupport, "Code")),
|
|
1640
|
+
instructions: { method: "create", path: join6(appSupport, "Code", "User", "prompts", "useai.instructions.md") },
|
|
1509
1641
|
supportsUrl: true
|
|
1510
1642
|
}),
|
|
1511
1643
|
createTool({
|
|
1512
1644
|
id: "vscode-insiders",
|
|
1513
1645
|
name: "VS Code Insiders",
|
|
1514
1646
|
configFormat: "vscode",
|
|
1515
|
-
configPath:
|
|
1516
|
-
detect: () =>
|
|
1517
|
-
instructions: { method: "create", path:
|
|
1647
|
+
configPath: join6(appSupport, "Code - Insiders", "User", "mcp.json"),
|
|
1648
|
+
detect: () => existsSync8(join6(appSupport, "Code - Insiders")),
|
|
1649
|
+
instructions: { method: "create", path: join6(appSupport, "Code - Insiders", "User", "prompts", "useai.instructions.md") },
|
|
1518
1650
|
supportsUrl: true
|
|
1519
1651
|
}),
|
|
1520
1652
|
createTool({
|
|
1521
1653
|
id: "gemini-cli",
|
|
1522
1654
|
name: "Gemini CLI",
|
|
1523
1655
|
configFormat: "standard",
|
|
1524
|
-
configPath:
|
|
1656
|
+
configPath: join6(home, ".gemini", "settings.json"),
|
|
1525
1657
|
detect: () => hasBinary("gemini"),
|
|
1526
|
-
instructions: { method: "append", path:
|
|
1658
|
+
instructions: { method: "append", path: join6(home, ".gemini", "GEMINI.md") },
|
|
1527
1659
|
supportsUrl: true
|
|
1528
1660
|
}),
|
|
1529
1661
|
createTool({
|
|
1530
1662
|
id: "zed",
|
|
1531
1663
|
name: "Zed",
|
|
1532
1664
|
configFormat: "zed",
|
|
1533
|
-
configPath:
|
|
1534
|
-
detect: () =>
|
|
1665
|
+
configPath: join6(appSupport, "Zed", "settings.json"),
|
|
1666
|
+
detect: () => existsSync8(join6(appSupport, "Zed")),
|
|
1535
1667
|
manualHint: "Open Rules Library (\u2318\u2325L) \u2192 click + \u2192 paste the instructions below."
|
|
1536
1668
|
}),
|
|
1537
1669
|
createTool({
|
|
1538
1670
|
id: "cline",
|
|
1539
1671
|
name: "Cline",
|
|
1540
1672
|
configFormat: "standard",
|
|
1541
|
-
configPath:
|
|
1673
|
+
configPath: join6(
|
|
1542
1674
|
appSupport,
|
|
1543
1675
|
"Code",
|
|
1544
1676
|
"User",
|
|
@@ -1547,17 +1679,17 @@ ${INSTRUCTIONS_END}`;
|
|
|
1547
1679
|
"settings",
|
|
1548
1680
|
"cline_mcp_settings.json"
|
|
1549
1681
|
),
|
|
1550
|
-
detect: () =>
|
|
1551
|
-
|
|
1682
|
+
detect: () => existsSync8(
|
|
1683
|
+
join6(appSupport, "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
|
|
1552
1684
|
),
|
|
1553
|
-
instructions: { method: "create", path:
|
|
1685
|
+
instructions: { method: "create", path: join6(home, "Documents", "Cline", "Rules", "useai.md") },
|
|
1554
1686
|
supportsUrl: true
|
|
1555
1687
|
}),
|
|
1556
1688
|
createTool({
|
|
1557
1689
|
id: "roo-code",
|
|
1558
1690
|
name: "Roo Code",
|
|
1559
1691
|
configFormat: "standard",
|
|
1560
|
-
configPath:
|
|
1692
|
+
configPath: join6(
|
|
1561
1693
|
appSupport,
|
|
1562
1694
|
"Code",
|
|
1563
1695
|
"User",
|
|
@@ -1566,59 +1698,59 @@ ${INSTRUCTIONS_END}`;
|
|
|
1566
1698
|
"settings",
|
|
1567
1699
|
"cline_mcp_settings.json"
|
|
1568
1700
|
),
|
|
1569
|
-
detect: () =>
|
|
1570
|
-
|
|
1701
|
+
detect: () => existsSync8(
|
|
1702
|
+
join6(appSupport, "Code", "User", "globalStorage", "rooveterinaryinc.roo-cline")
|
|
1571
1703
|
),
|
|
1572
|
-
instructions: { method: "create", path:
|
|
1704
|
+
instructions: { method: "create", path: join6(home, ".roo", "rules", "useai.md") },
|
|
1573
1705
|
supportsUrl: true
|
|
1574
1706
|
}),
|
|
1575
1707
|
createTool({
|
|
1576
1708
|
id: "amazon-q-cli",
|
|
1577
1709
|
name: "Amazon Q CLI",
|
|
1578
1710
|
configFormat: "standard",
|
|
1579
|
-
configPath:
|
|
1580
|
-
detect: () => hasBinary("q") ||
|
|
1711
|
+
configPath: join6(home, ".aws", "amazonq", "mcp.json"),
|
|
1712
|
+
detect: () => hasBinary("q") || existsSync8(join6(home, ".aws", "amazonq")),
|
|
1581
1713
|
manualHint: "Create .amazonq/rules/useai.md in your project root with the instructions below."
|
|
1582
1714
|
}),
|
|
1583
1715
|
createTool({
|
|
1584
1716
|
id: "amazon-q-ide",
|
|
1585
1717
|
name: "Amazon Q IDE",
|
|
1586
1718
|
configFormat: "standard",
|
|
1587
|
-
configPath:
|
|
1588
|
-
detect: () =>
|
|
1719
|
+
configPath: join6(home, ".aws", "amazonq", "default.json"),
|
|
1720
|
+
detect: () => existsSync8(join6(home, ".amazonq")) || existsSync8(join6(home, ".aws", "amazonq")),
|
|
1589
1721
|
manualHint: "Create .amazonq/rules/useai.md in your project root with the instructions below."
|
|
1590
1722
|
}),
|
|
1591
1723
|
createTool({
|
|
1592
1724
|
id: "codex",
|
|
1593
1725
|
name: "Codex",
|
|
1594
1726
|
configFormat: "toml",
|
|
1595
|
-
configPath:
|
|
1596
|
-
detect: () => hasBinary("codex") ||
|
|
1597
|
-
instructions: { method: "append", path:
|
|
1727
|
+
configPath: join6(home, ".codex", "config.toml"),
|
|
1728
|
+
detect: () => hasBinary("codex") || existsSync8(join6(home, ".codex")) || existsSync8("/Applications/Codex.app"),
|
|
1729
|
+
instructions: { method: "append", path: join6(home, ".codex", "AGENTS.md") }
|
|
1598
1730
|
}),
|
|
1599
1731
|
createTool({
|
|
1600
1732
|
id: "goose",
|
|
1601
1733
|
name: "Goose",
|
|
1602
1734
|
configFormat: "yaml",
|
|
1603
|
-
configPath:
|
|
1604
|
-
detect: () =>
|
|
1605
|
-
instructions: { method: "append", path:
|
|
1735
|
+
configPath: join6(home, ".config", "goose", "config.yaml"),
|
|
1736
|
+
detect: () => existsSync8(join6(home, ".config", "goose")),
|
|
1737
|
+
instructions: { method: "append", path: join6(home, ".config", "goose", ".goosehints") }
|
|
1606
1738
|
}),
|
|
1607
1739
|
createTool({
|
|
1608
1740
|
id: "opencode",
|
|
1609
1741
|
name: "OpenCode",
|
|
1610
1742
|
configFormat: "standard",
|
|
1611
|
-
configPath:
|
|
1612
|
-
detect: () => hasBinary("opencode") ||
|
|
1613
|
-
instructions: { method: "append", path:
|
|
1743
|
+
configPath: join6(home, ".config", "opencode", "opencode.json"),
|
|
1744
|
+
detect: () => hasBinary("opencode") || existsSync8(join6(home, ".config", "opencode")),
|
|
1745
|
+
instructions: { method: "append", path: join6(home, ".config", "opencode", "AGENTS.md") },
|
|
1614
1746
|
supportsUrl: true
|
|
1615
1747
|
}),
|
|
1616
1748
|
createTool({
|
|
1617
1749
|
id: "junie",
|
|
1618
1750
|
name: "Junie",
|
|
1619
1751
|
configFormat: "standard",
|
|
1620
|
-
configPath:
|
|
1621
|
-
detect: () =>
|
|
1752
|
+
configPath: join6(home, ".junie", "mcp", "mcp.json"),
|
|
1753
|
+
detect: () => existsSync8(join6(home, ".junie")),
|
|
1622
1754
|
manualHint: "Add the instructions below to .junie/guidelines.md in your project root."
|
|
1623
1755
|
})
|
|
1624
1756
|
];
|
|
@@ -1734,6 +1866,14 @@ async function daemonInstallFlow(tools, explicit) {
|
|
|
1734
1866
|
console.log(err(`\u2717 ${tool.name.padEnd(18)} \u2014 ${e.message}`));
|
|
1735
1867
|
}
|
|
1736
1868
|
}
|
|
1869
|
+
try {
|
|
1870
|
+
const hooksInstalled = installClaudeCodeHooks();
|
|
1871
|
+
if (hooksInstalled) {
|
|
1872
|
+
console.log(ok("\u2713 Claude Code hooks installed (Stop + SessionEnd)"));
|
|
1873
|
+
}
|
|
1874
|
+
} catch {
|
|
1875
|
+
console.log(chalk.yellow(" \u26A0 Could not install Claude Code hooks"));
|
|
1876
|
+
}
|
|
1737
1877
|
showManualHints(targetTools);
|
|
1738
1878
|
const mode = useDaemon ? "daemon mode" : "stdio mode";
|
|
1739
1879
|
console.log(`
|
|
@@ -1908,6 +2048,11 @@ async function fullRemoveFlow(tools, autoYes, explicit) {
|
|
|
1908
2048
|
}
|
|
1909
2049
|
}
|
|
1910
2050
|
}
|
|
2051
|
+
try {
|
|
2052
|
+
removeClaudeCodeHooks();
|
|
2053
|
+
console.log(ok("\u2713 Claude Code hooks removed"));
|
|
2054
|
+
} catch {
|
|
2055
|
+
}
|
|
1911
2056
|
console.log();
|
|
1912
2057
|
try {
|
|
1913
2058
|
await killDaemon();
|
|
@@ -2157,6 +2302,12 @@ function getDashboardHtml() {
|
|
|
2157
2302
|
color: var(--muted);
|
|
2158
2303
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
2159
2304
|
}
|
|
2305
|
+
.milestone-duration {
|
|
2306
|
+
font-size: 0.7rem;
|
|
2307
|
+
color: var(--muted);
|
|
2308
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
2309
|
+
white-space: nowrap;
|
|
2310
|
+
}
|
|
2160
2311
|
.milestone-date {
|
|
2161
2312
|
font-size: 0.72rem;
|
|
2162
2313
|
color: var(--muted);
|
|
@@ -2203,6 +2354,77 @@ function getDashboardHtml() {
|
|
|
2203
2354
|
text-decoration: underline;
|
|
2204
2355
|
}
|
|
2205
2356
|
|
|
2357
|
+
/* Login form */
|
|
2358
|
+
.login-form {
|
|
2359
|
+
max-width: 320px;
|
|
2360
|
+
margin: 0 auto;
|
|
2361
|
+
text-align: center;
|
|
2362
|
+
}
|
|
2363
|
+
.login-form h3 {
|
|
2364
|
+
font-size: 0.95rem;
|
|
2365
|
+
color: var(--text);
|
|
2366
|
+
margin-bottom: 4px;
|
|
2367
|
+
font-weight: 600;
|
|
2368
|
+
}
|
|
2369
|
+
.login-form .login-sub {
|
|
2370
|
+
font-size: 0.8rem;
|
|
2371
|
+
color: var(--muted);
|
|
2372
|
+
margin-bottom: 16px;
|
|
2373
|
+
}
|
|
2374
|
+
.login-input {
|
|
2375
|
+
width: 100%;
|
|
2376
|
+
padding: 10px 12px;
|
|
2377
|
+
background: var(--bg);
|
|
2378
|
+
border: 1px solid var(--border);
|
|
2379
|
+
border-radius: var(--radius);
|
|
2380
|
+
color: var(--text);
|
|
2381
|
+
font-size: 0.88rem;
|
|
2382
|
+
font-family: system-ui, sans-serif;
|
|
2383
|
+
outline: none;
|
|
2384
|
+
margin-bottom: 10px;
|
|
2385
|
+
}
|
|
2386
|
+
.login-input:focus { border-color: var(--amber); }
|
|
2387
|
+
.login-input::placeholder { color: #5a5248; }
|
|
2388
|
+
.login-input.otp-input {
|
|
2389
|
+
text-align: center;
|
|
2390
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
2391
|
+
font-size: 1.2rem;
|
|
2392
|
+
letter-spacing: 6px;
|
|
2393
|
+
}
|
|
2394
|
+
.login-btn {
|
|
2395
|
+
width: 100%;
|
|
2396
|
+
padding: 10px;
|
|
2397
|
+
background: var(--amber);
|
|
2398
|
+
color: var(--bg);
|
|
2399
|
+
border: none;
|
|
2400
|
+
border-radius: var(--radius);
|
|
2401
|
+
font-weight: 600;
|
|
2402
|
+
font-size: 0.88rem;
|
|
2403
|
+
cursor: pointer;
|
|
2404
|
+
font-family: system-ui, sans-serif;
|
|
2405
|
+
transition: opacity 0.15s;
|
|
2406
|
+
}
|
|
2407
|
+
.login-btn:hover { opacity: 0.85; }
|
|
2408
|
+
.login-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
2409
|
+
.login-msg {
|
|
2410
|
+
margin-top: 10px;
|
|
2411
|
+
font-size: 0.8rem;
|
|
2412
|
+
}
|
|
2413
|
+
.login-msg.error { color: var(--red); }
|
|
2414
|
+
.login-msg.success { color: var(--green); }
|
|
2415
|
+
.login-msg.dim { color: var(--muted); }
|
|
2416
|
+
.login-link {
|
|
2417
|
+
background: none;
|
|
2418
|
+
border: none;
|
|
2419
|
+
color: var(--amber);
|
|
2420
|
+
cursor: pointer;
|
|
2421
|
+
font-size: 0.8rem;
|
|
2422
|
+
font-family: system-ui, sans-serif;
|
|
2423
|
+
text-decoration: underline;
|
|
2424
|
+
padding: 0;
|
|
2425
|
+
margin-top: 6px;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2206
2428
|
.empty {
|
|
2207
2429
|
text-align: center;
|
|
2208
2430
|
color: var(--muted);
|
|
@@ -2305,6 +2527,14 @@ function getDashboardHtml() {
|
|
|
2305
2527
|
return d.innerHTML;
|
|
2306
2528
|
}
|
|
2307
2529
|
|
|
2530
|
+
function fmtDuration(mins) {
|
|
2531
|
+
if (!mins || mins <= 0) return '';
|
|
2532
|
+
if (mins < 60) return mins + 'm';
|
|
2533
|
+
var h = Math.floor(mins / 60);
|
|
2534
|
+
var m = mins % 60;
|
|
2535
|
+
return m > 0 ? h + 'h ' + m + 'm' : h + 'h';
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2308
2538
|
function renderMilestones(milestones) {
|
|
2309
2539
|
var section = document.getElementById('milestones-section');
|
|
2310
2540
|
var list = document.getElementById('milestones-list');
|
|
@@ -2317,17 +2547,21 @@ function getDashboardHtml() {
|
|
|
2317
2547
|
var recent = milestones.slice(-20).reverse();
|
|
2318
2548
|
list.innerHTML = recent.map(function(m) {
|
|
2319
2549
|
var date = m.created_at ? m.created_at.slice(0, 10) : '';
|
|
2550
|
+
var dur = fmtDuration(m.duration_minutes);
|
|
2320
2551
|
return '<div class="milestone-item">' +
|
|
2321
2552
|
'<div class="milestone-title">' + escapeHtml(m.title) + '</div>' +
|
|
2322
2553
|
'<div class="milestone-meta">' +
|
|
2323
2554
|
'<span class="' + badgeClass(m.category) + '">' + escapeHtml(m.category) + '</span>' +
|
|
2324
2555
|
(m.complexity ? '<span class="complexity">' + escapeHtml(m.complexity) + '</span>' : '') +
|
|
2556
|
+
(dur ? '<span class="milestone-duration">' + dur + '</span>' : '') +
|
|
2325
2557
|
'<span class="milestone-date">' + escapeHtml(date) + '</span>' +
|
|
2326
2558
|
'</div>' +
|
|
2327
2559
|
'</div>';
|
|
2328
2560
|
}).join('');
|
|
2329
2561
|
}
|
|
2330
2562
|
|
|
2563
|
+
var loginEmail = '';
|
|
2564
|
+
|
|
2331
2565
|
function renderSync(config) {
|
|
2332
2566
|
var section = document.getElementById('sync-section');
|
|
2333
2567
|
if (!config) { section.style.display = 'none'; return; }
|
|
@@ -2337,20 +2571,134 @@ function getDashboardHtml() {
|
|
|
2337
2571
|
var lastSync = config.last_sync_at ? 'Last sync: ' + config.last_sync_at : 'Never synced';
|
|
2338
2572
|
section.innerHTML = '<div class="sync-section">' +
|
|
2339
2573
|
'<h2 style="font-size:0.85rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:12px;font-weight:600;">Sync</h2>' +
|
|
2574
|
+
'<div class="sync-status" id="sync-status" style="margin-bottom:12px;">Logged in as <strong>' + escapeHtml(config.email || '') + '</strong></div>' +
|
|
2340
2575
|
'<button class="sync-btn" id="sync-btn">Sync to useai.dev</button>' +
|
|
2341
|
-
'<div class="sync-status" id="sync-
|
|
2576
|
+
'<div class="sync-status" id="sync-time">' + escapeHtml(lastSync) + '</div>' +
|
|
2342
2577
|
'<div class="sync-result" id="sync-result"></div>' +
|
|
2343
2578
|
'</div>';
|
|
2344
2579
|
document.getElementById('sync-btn').addEventListener('click', doSync);
|
|
2345
2580
|
} else {
|
|
2346
|
-
|
|
2581
|
+
renderLoginForm();
|
|
2347
2582
|
}
|
|
2348
2583
|
}
|
|
2349
2584
|
|
|
2585
|
+
function renderLoginForm() {
|
|
2586
|
+
var section = document.getElementById('sync-section');
|
|
2587
|
+
section.innerHTML = '<div class="login-form" id="login-form">' +
|
|
2588
|
+
'<h3>Sign in to sync</h3>' +
|
|
2589
|
+
'<div class="login-sub">Sync sessions & milestones to useai.dev</div>' +
|
|
2590
|
+
'<input class="login-input" id="login-email" type="email" placeholder="you@email.com" autocomplete="email">' +
|
|
2591
|
+
'<button class="login-btn" id="login-send-btn">Send verification code</button>' +
|
|
2592
|
+
'<div class="login-msg" id="login-msg"></div>' +
|
|
2593
|
+
'</div>';
|
|
2594
|
+
document.getElementById('login-send-btn').addEventListener('click', sendOtp);
|
|
2595
|
+
document.getElementById('login-email').addEventListener('keydown', function(e) {
|
|
2596
|
+
if (e.key === 'Enter') sendOtp();
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
function renderOtpForm() {
|
|
2601
|
+
var section = document.getElementById('sync-section');
|
|
2602
|
+
section.innerHTML = '<div class="login-form" id="otp-form">' +
|
|
2603
|
+
'<h3>Check your email</h3>' +
|
|
2604
|
+
'<div class="login-sub">Enter the 6-digit code sent to ' + escapeHtml(loginEmail) + '</div>' +
|
|
2605
|
+
'<input class="login-input otp-input" id="login-otp" type="text" maxlength="6" placeholder="000000" inputmode="numeric" autocomplete="one-time-code">' +
|
|
2606
|
+
'<button class="login-btn" id="login-verify-btn">Verify</button>' +
|
|
2607
|
+
'<div class="login-msg" id="login-msg"></div>' +
|
|
2608
|
+
'<button class="login-link" id="login-resend">Resend code</button>' +
|
|
2609
|
+
'</div>';
|
|
2610
|
+
document.getElementById('login-verify-btn').addEventListener('click', verifyOtp);
|
|
2611
|
+
document.getElementById('login-resend').addEventListener('click', function() { sendOtp(); });
|
|
2612
|
+
document.getElementById('login-otp').addEventListener('keydown', function(e) {
|
|
2613
|
+
if (e.key === 'Enter') verifyOtp();
|
|
2614
|
+
});
|
|
2615
|
+
document.getElementById('login-otp').focus();
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
function sendOtp() {
|
|
2619
|
+
var emailEl = document.getElementById('login-email');
|
|
2620
|
+
var msg = document.getElementById('login-msg');
|
|
2621
|
+
var btn = document.getElementById('login-send-btn') || document.getElementById('login-resend');
|
|
2622
|
+
var email = emailEl ? emailEl.value.trim() : loginEmail;
|
|
2623
|
+
|
|
2624
|
+
if (!email || email.indexOf('@') === -1) {
|
|
2625
|
+
msg.textContent = 'Please enter a valid email';
|
|
2626
|
+
msg.className = 'login-msg error';
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
loginEmail = email;
|
|
2631
|
+
if (btn) { btn.disabled = true; btn.textContent = btn.id === 'login-resend' ? 'Sending...' : 'Sending code...'; }
|
|
2632
|
+
|
|
2633
|
+
fetch(API + '/api/local/auth/send-otp', {
|
|
2634
|
+
method: 'POST',
|
|
2635
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2636
|
+
body: JSON.stringify({ email: email }),
|
|
2637
|
+
})
|
|
2638
|
+
.then(function(r) { return r.json().then(function(d) { return { ok: r.ok, data: d }; }); })
|
|
2639
|
+
.then(function(res) {
|
|
2640
|
+
if (!res.ok) {
|
|
2641
|
+
msg.textContent = res.data.message || 'Failed to send code';
|
|
2642
|
+
msg.className = 'login-msg error';
|
|
2643
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Send verification code'; }
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
renderOtpForm();
|
|
2647
|
+
})
|
|
2648
|
+
.catch(function(err) {
|
|
2649
|
+
msg.textContent = 'Error: ' + err.message;
|
|
2650
|
+
msg.className = 'login-msg error';
|
|
2651
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Send verification code'; }
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
function verifyOtp() {
|
|
2656
|
+
var otpEl = document.getElementById('login-otp');
|
|
2657
|
+
var msg = document.getElementById('login-msg');
|
|
2658
|
+
var btn = document.getElementById('login-verify-btn');
|
|
2659
|
+
var code = otpEl ? otpEl.value.trim() : '';
|
|
2660
|
+
|
|
2661
|
+
if (!/^d{6}$/.test(code)) {
|
|
2662
|
+
msg.textContent = 'Please enter the 6-digit code';
|
|
2663
|
+
msg.className = 'login-msg error';
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
btn.disabled = true;
|
|
2668
|
+
btn.textContent = 'Verifying...';
|
|
2669
|
+
msg.textContent = '';
|
|
2670
|
+
|
|
2671
|
+
fetch(API + '/api/local/auth/verify-otp', {
|
|
2672
|
+
method: 'POST',
|
|
2673
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2674
|
+
body: JSON.stringify({ email: loginEmail, code: code }),
|
|
2675
|
+
})
|
|
2676
|
+
.then(function(r) { return r.json().then(function(d) { return { ok: r.ok, data: d }; }); })
|
|
2677
|
+
.then(function(res) {
|
|
2678
|
+
if (!res.ok) {
|
|
2679
|
+
msg.textContent = res.data.message || 'Invalid code';
|
|
2680
|
+
msg.className = 'login-msg error';
|
|
2681
|
+
btn.disabled = false;
|
|
2682
|
+
btn.textContent = 'Verify';
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
// Success \u2014 reload config and re-render sync section
|
|
2686
|
+
msg.textContent = 'Logged in!';
|
|
2687
|
+
msg.className = 'login-msg success';
|
|
2688
|
+
setTimeout(function() { loadAll(); }, 500);
|
|
2689
|
+
})
|
|
2690
|
+
.catch(function(err) {
|
|
2691
|
+
msg.textContent = 'Error: ' + err.message;
|
|
2692
|
+
msg.className = 'login-msg error';
|
|
2693
|
+
btn.disabled = false;
|
|
2694
|
+
btn.textContent = 'Verify';
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2350
2698
|
function doSync() {
|
|
2351
2699
|
var btn = document.getElementById('sync-btn');
|
|
2352
2700
|
var result = document.getElementById('sync-result');
|
|
2353
|
-
var
|
|
2701
|
+
var timeEl = document.getElementById('sync-time');
|
|
2354
2702
|
btn.disabled = true;
|
|
2355
2703
|
btn.textContent = 'Syncing...';
|
|
2356
2704
|
result.textContent = '';
|
|
@@ -2364,7 +2712,7 @@ function getDashboardHtml() {
|
|
|
2364
2712
|
if (data.success) {
|
|
2365
2713
|
result.textContent = 'Synced successfully';
|
|
2366
2714
|
result.className = 'sync-result success';
|
|
2367
|
-
if (data.last_sync_at)
|
|
2715
|
+
if (data.last_sync_at && timeEl) timeEl.textContent = 'Last sync: ' + data.last_sync_at;
|
|
2368
2716
|
} else {
|
|
2369
2717
|
result.textContent = 'Sync failed: ' + (data.error || 'Unknown error');
|
|
2370
2718
|
result.className = 'sync-result error';
|
|
@@ -2580,10 +2928,66 @@ async function handleLocalSync(req, res) {
|
|
|
2580
2928
|
json(res, 500, { success: false, error: err2.message });
|
|
2581
2929
|
}
|
|
2582
2930
|
}
|
|
2931
|
+
async function handleLocalSendOtp(req, res) {
|
|
2932
|
+
try {
|
|
2933
|
+
const raw = await readBody(req);
|
|
2934
|
+
const body = raw ? JSON.parse(raw) : {};
|
|
2935
|
+
const apiRes = await fetch(`${USEAI_API}/api/auth/send-otp`, {
|
|
2936
|
+
method: "POST",
|
|
2937
|
+
headers: { "Content-Type": "application/json" },
|
|
2938
|
+
body: JSON.stringify({ email: body.email })
|
|
2939
|
+
});
|
|
2940
|
+
const data = await apiRes.json();
|
|
2941
|
+
if (!apiRes.ok) {
|
|
2942
|
+
json(res, apiRes.status, data);
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
json(res, 200, data);
|
|
2946
|
+
} catch (err2) {
|
|
2947
|
+
json(res, 500, { error: err2.message });
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
async function handleLocalVerifyOtp(req, res) {
|
|
2951
|
+
try {
|
|
2952
|
+
const raw = await readBody(req);
|
|
2953
|
+
const body = raw ? JSON.parse(raw) : {};
|
|
2954
|
+
const apiRes = await fetch(`${USEAI_API}/api/auth/verify-otp`, {
|
|
2955
|
+
method: "POST",
|
|
2956
|
+
headers: { "Content-Type": "application/json" },
|
|
2957
|
+
body: JSON.stringify({ email: body.email, code: body.code })
|
|
2958
|
+
});
|
|
2959
|
+
const data = await apiRes.json();
|
|
2960
|
+
if (!apiRes.ok) {
|
|
2961
|
+
json(res, apiRes.status, data);
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
if (data.token && data.user) {
|
|
2965
|
+
const config = readJson(CONFIG_FILE, {
|
|
2966
|
+
milestone_tracking: true,
|
|
2967
|
+
auto_sync: true,
|
|
2968
|
+
sync_interval_hours: 24
|
|
2969
|
+
});
|
|
2970
|
+
config.auth = {
|
|
2971
|
+
token: data.token,
|
|
2972
|
+
user: {
|
|
2973
|
+
id: data.user.id,
|
|
2974
|
+
email: data.user.email,
|
|
2975
|
+
username: data.user.username
|
|
2976
|
+
}
|
|
2977
|
+
};
|
|
2978
|
+
writeJson(CONFIG_FILE, config);
|
|
2979
|
+
}
|
|
2980
|
+
json(res, 200, { success: true, email: data.user?.email, username: data.user?.username });
|
|
2981
|
+
} catch (err2) {
|
|
2982
|
+
json(res, 500, { error: err2.message });
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
var USEAI_API;
|
|
2583
2986
|
var init_local_api = __esm({
|
|
2584
2987
|
"src/dashboard/local-api.ts"() {
|
|
2585
2988
|
"use strict";
|
|
2586
2989
|
init_dist();
|
|
2990
|
+
USEAI_API = "https://api.useai.dev";
|
|
2587
2991
|
}
|
|
2588
2992
|
});
|
|
2589
2993
|
|
|
@@ -2594,11 +2998,125 @@ __export(daemon_exports, {
|
|
|
2594
2998
|
});
|
|
2595
2999
|
import { createServer } from "http";
|
|
2596
3000
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
2597
|
-
import { existsSync as
|
|
2598
|
-
import { join as
|
|
3001
|
+
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync5, appendFileSync as appendFileSync2, renameSync as renameSync3, writeFileSync as writeFileSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3002
|
+
import { join as join7 } from "path";
|
|
2599
3003
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2600
3004
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2601
3005
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
3006
|
+
function getActiveUseaiSessionIds() {
|
|
3007
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3008
|
+
for (const [, active] of sessions) {
|
|
3009
|
+
ids.add(active.session.sessionId);
|
|
3010
|
+
}
|
|
3011
|
+
return ids;
|
|
3012
|
+
}
|
|
3013
|
+
function sealOrphanFile(sessionId) {
|
|
3014
|
+
const filePath = join7(ACTIVE_DIR, `${sessionId}.jsonl`);
|
|
3015
|
+
if (!existsSync9(filePath)) return;
|
|
3016
|
+
try {
|
|
3017
|
+
const content = readFileSync5(filePath, "utf-8").trim();
|
|
3018
|
+
if (!content) return;
|
|
3019
|
+
const lines = content.split("\n").filter(Boolean);
|
|
3020
|
+
if (lines.length === 0) return;
|
|
3021
|
+
const firstRecord = JSON.parse(lines[0]);
|
|
3022
|
+
const lastRecord = JSON.parse(lines[lines.length - 1]);
|
|
3023
|
+
if (lastRecord.type === "session_end" || lastRecord.type === "session_seal") {
|
|
3024
|
+
try {
|
|
3025
|
+
renameSync3(filePath, join7(SEALED_DIR, `${sessionId}.jsonl`));
|
|
3026
|
+
} catch {
|
|
3027
|
+
}
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
const startData = firstRecord.data;
|
|
3031
|
+
const client = startData["client"] ?? "unknown";
|
|
3032
|
+
const taskType = startData["task_type"] ?? "coding";
|
|
3033
|
+
const startTime = firstRecord.timestamp;
|
|
3034
|
+
let heartbeatCount = 0;
|
|
3035
|
+
for (const line of lines) {
|
|
3036
|
+
try {
|
|
3037
|
+
if (JSON.parse(line).type === "heartbeat") heartbeatCount++;
|
|
3038
|
+
} catch {
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
const chainTip = lastRecord.hash;
|
|
3042
|
+
const duration = Math.round((Date.now() - new Date(startTime).getTime()) / 1e3);
|
|
3043
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3044
|
+
const endRecord = buildChainRecord("session_end", sessionId, {
|
|
3045
|
+
duration_seconds: duration,
|
|
3046
|
+
task_type: taskType,
|
|
3047
|
+
languages: [],
|
|
3048
|
+
files_touched: 0,
|
|
3049
|
+
heartbeat_count: heartbeatCount,
|
|
3050
|
+
auto_sealed: true
|
|
3051
|
+
}, chainTip, daemonSigningKey);
|
|
3052
|
+
appendFileSync2(filePath, JSON.stringify(endRecord) + "\n");
|
|
3053
|
+
const sealData = JSON.stringify({
|
|
3054
|
+
session_id: sessionId,
|
|
3055
|
+
client,
|
|
3056
|
+
task_type: taskType,
|
|
3057
|
+
languages: [],
|
|
3058
|
+
files_touched: 0,
|
|
3059
|
+
started_at: startTime,
|
|
3060
|
+
ended_at: now,
|
|
3061
|
+
duration_seconds: duration,
|
|
3062
|
+
heartbeat_count: heartbeatCount,
|
|
3063
|
+
record_count: lines.length + 2,
|
|
3064
|
+
chain_end_hash: endRecord.hash
|
|
3065
|
+
});
|
|
3066
|
+
const sealSignature = signHash(
|
|
3067
|
+
createHash4("sha256").update(sealData).digest("hex"),
|
|
3068
|
+
daemonSigningKey
|
|
3069
|
+
);
|
|
3070
|
+
appendFileSync2(filePath, JSON.stringify(
|
|
3071
|
+
buildChainRecord("session_seal", sessionId, {
|
|
3072
|
+
seal: sealData,
|
|
3073
|
+
seal_signature: sealSignature,
|
|
3074
|
+
auto_sealed: true
|
|
3075
|
+
}, endRecord.hash, daemonSigningKey)
|
|
3076
|
+
) + "\n");
|
|
3077
|
+
try {
|
|
3078
|
+
renameSync3(filePath, join7(SEALED_DIR, `${sessionId}.jsonl`));
|
|
3079
|
+
} catch {
|
|
3080
|
+
}
|
|
3081
|
+
const seal = {
|
|
3082
|
+
session_id: sessionId,
|
|
3083
|
+
client,
|
|
3084
|
+
task_type: taskType,
|
|
3085
|
+
languages: [],
|
|
3086
|
+
files_touched: 0,
|
|
3087
|
+
started_at: startTime,
|
|
3088
|
+
ended_at: now,
|
|
3089
|
+
duration_seconds: duration,
|
|
3090
|
+
heartbeat_count: heartbeatCount,
|
|
3091
|
+
record_count: lines.length + 2,
|
|
3092
|
+
chain_start_hash: firstRecord.prev_hash,
|
|
3093
|
+
chain_end_hash: endRecord.hash,
|
|
3094
|
+
seal_signature: sealSignature
|
|
3095
|
+
};
|
|
3096
|
+
const allSessions = readJson(SESSIONS_FILE, []);
|
|
3097
|
+
allSessions.push(seal);
|
|
3098
|
+
writeJson(SESSIONS_FILE, allSessions);
|
|
3099
|
+
} catch {
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
function sealOrphanedSessions() {
|
|
3103
|
+
if (!existsSync9(ACTIVE_DIR)) return;
|
|
3104
|
+
const activeIds = getActiveUseaiSessionIds();
|
|
3105
|
+
let sealed = 0;
|
|
3106
|
+
try {
|
|
3107
|
+
const files = readdirSync(ACTIVE_DIR).filter((f) => f.endsWith(".jsonl"));
|
|
3108
|
+
for (const file of files) {
|
|
3109
|
+
const sessionId = file.replace(".jsonl", "");
|
|
3110
|
+
if (activeIds.has(sessionId)) continue;
|
|
3111
|
+
sealOrphanFile(sessionId);
|
|
3112
|
+
sealed++;
|
|
3113
|
+
}
|
|
3114
|
+
} catch {
|
|
3115
|
+
}
|
|
3116
|
+
if (sealed > 0) {
|
|
3117
|
+
console.log(`Sealed ${sealed} orphaned session${sealed === 1 ? "" : "s"}`);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
2602
3120
|
function autoSealSession(active) {
|
|
2603
3121
|
const { session: session2 } = active;
|
|
2604
3122
|
if (session2.sessionRecordCount === 0) return;
|
|
@@ -2634,10 +3152,10 @@ function autoSealSession(active) {
|
|
|
2634
3152
|
seal_signature: sealSignature,
|
|
2635
3153
|
auto_sealed: true
|
|
2636
3154
|
});
|
|
2637
|
-
const activePath =
|
|
2638
|
-
const sealedPath =
|
|
3155
|
+
const activePath = join7(ACTIVE_DIR, `${session2.sessionId}.jsonl`);
|
|
3156
|
+
const sealedPath = join7(SEALED_DIR, `${session2.sessionId}.jsonl`);
|
|
2639
3157
|
try {
|
|
2640
|
-
if (
|
|
3158
|
+
if (existsSync9(activePath)) {
|
|
2641
3159
|
renameSync3(activePath, sealedPath);
|
|
2642
3160
|
}
|
|
2643
3161
|
} catch {
|
|
@@ -2714,6 +3232,21 @@ function parseBody(req) {
|
|
|
2714
3232
|
async function startDaemon(port) {
|
|
2715
3233
|
const listenPort = port ?? DAEMON_PORT;
|
|
2716
3234
|
ensureDir();
|
|
3235
|
+
try {
|
|
3236
|
+
if (existsSync9(KEYSTORE_FILE)) {
|
|
3237
|
+
const ks = readJson(KEYSTORE_FILE, null);
|
|
3238
|
+
if (ks) daemonSigningKey = decryptKeystore(ks);
|
|
3239
|
+
}
|
|
3240
|
+
if (!daemonSigningKey) {
|
|
3241
|
+
const result = generateKeystore();
|
|
3242
|
+
writeJson(KEYSTORE_FILE, result.keystore);
|
|
3243
|
+
daemonSigningKey = result.signingKey;
|
|
3244
|
+
}
|
|
3245
|
+
} catch {
|
|
3246
|
+
}
|
|
3247
|
+
sealOrphanedSessions();
|
|
3248
|
+
const sweepInterval = setInterval(sealOrphanedSessions, ORPHAN_SWEEP_INTERVAL_MS);
|
|
3249
|
+
sweepInterval.unref();
|
|
2717
3250
|
const server2 = createServer(async (req, res) => {
|
|
2718
3251
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
2719
3252
|
if (url.pathname === "/health" && req.method === "GET") {
|
|
@@ -2743,7 +3276,27 @@ async function startDaemon(port) {
|
|
|
2743
3276
|
await handleLocalSync(req, res);
|
|
2744
3277
|
return;
|
|
2745
3278
|
}
|
|
2746
|
-
if (url.pathname
|
|
3279
|
+
if (url.pathname === "/api/local/auth/send-otp" && req.method === "POST") {
|
|
3280
|
+
await handleLocalSendOtp(req, res);
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
if (url.pathname === "/api/local/auth/verify-otp" && req.method === "POST") {
|
|
3284
|
+
await handleLocalVerifyOtp(req, res);
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
if (url.pathname === "/api/seal-active" && req.method === "POST") {
|
|
3288
|
+
const sids = [...sessions.keys()];
|
|
3289
|
+
for (const sid of sids) {
|
|
3290
|
+
await cleanupSession(sid);
|
|
3291
|
+
}
|
|
3292
|
+
res.writeHead(200, {
|
|
3293
|
+
"Content-Type": "application/json",
|
|
3294
|
+
"Access-Control-Allow-Origin": "*"
|
|
3295
|
+
});
|
|
3296
|
+
res.end(JSON.stringify({ sealed: sids.length }));
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
if ((url.pathname.startsWith("/api/local/") || url.pathname === "/api/seal-active") && req.method === "OPTIONS") {
|
|
2747
3300
|
res.writeHead(204, {
|
|
2748
3301
|
"Access-Control-Allow-Origin": "*",
|
|
2749
3302
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
@@ -2856,14 +3409,14 @@ async function startDaemon(port) {
|
|
|
2856
3409
|
port: listenPort,
|
|
2857
3410
|
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2858
3411
|
});
|
|
2859
|
-
|
|
3412
|
+
writeFileSync5(DAEMON_PID_FILE, pidData + "\n");
|
|
2860
3413
|
const shutdown = async (signal) => {
|
|
2861
3414
|
for (const [sid] of sessions) {
|
|
2862
3415
|
await cleanupSession(sid);
|
|
2863
3416
|
}
|
|
2864
3417
|
try {
|
|
2865
|
-
if (
|
|
2866
|
-
|
|
3418
|
+
if (existsSync9(DAEMON_PID_FILE)) {
|
|
3419
|
+
unlinkSync5(DAEMON_PID_FILE);
|
|
2867
3420
|
}
|
|
2868
3421
|
} catch {
|
|
2869
3422
|
}
|
|
@@ -2879,7 +3432,7 @@ async function startDaemon(port) {
|
|
|
2879
3432
|
console.log(`PID: ${process.pid}`);
|
|
2880
3433
|
});
|
|
2881
3434
|
}
|
|
2882
|
-
var IDLE_TIMEOUT_MS, sessions, startedAt;
|
|
3435
|
+
var IDLE_TIMEOUT_MS, sessions, daemonSigningKey, ORPHAN_SWEEP_INTERVAL_MS, startedAt;
|
|
2883
3436
|
var init_daemon2 = __esm({
|
|
2884
3437
|
"src/daemon.ts"() {
|
|
2885
3438
|
"use strict";
|
|
@@ -2890,6 +3443,8 @@ var init_daemon2 = __esm({
|
|
|
2890
3443
|
init_local_api();
|
|
2891
3444
|
IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
2892
3445
|
sessions = /* @__PURE__ */ new Map();
|
|
3446
|
+
daemonSigningKey = null;
|
|
3447
|
+
ORPHAN_SWEEP_INTERVAL_MS = 15 * 60 * 1e3;
|
|
2893
3448
|
startedAt = Date.now();
|
|
2894
3449
|
}
|
|
2895
3450
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devness/useai",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Track your AI-assisted development workflow. MCP server that records usage metrics across all your AI tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -28,14 +28,6 @@
|
|
|
28
28
|
"README.md",
|
|
29
29
|
"LICENSE"
|
|
30
30
|
],
|
|
31
|
-
"scripts": {
|
|
32
|
-
"build": "tsc -p tsconfig.build.json",
|
|
33
|
-
"dev": "tsc --watch",
|
|
34
|
-
"bundle": "tsup src/index.ts --format esm --target node18 --no-splitting",
|
|
35
|
-
"prepublishOnly": "pnpm run bundle",
|
|
36
|
-
"typecheck": "tsc --noEmit",
|
|
37
|
-
"clean": "rm -rf dist"
|
|
38
|
-
},
|
|
39
31
|
"dependencies": {
|
|
40
32
|
"@inquirer/prompts": "^8.2.1",
|
|
41
33
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
@@ -46,9 +38,9 @@
|
|
|
46
38
|
},
|
|
47
39
|
"devDependencies": {
|
|
48
40
|
"@types/node": "^22.13.4",
|
|
49
|
-
"@useai/shared": "workspace:*",
|
|
50
41
|
"tsup": "^8.0.0",
|
|
51
|
-
"typescript": "^5.7.3"
|
|
42
|
+
"typescript": "^5.7.3",
|
|
43
|
+
"@useai/shared": "0.3.0"
|
|
52
44
|
},
|
|
53
45
|
"repository": {
|
|
54
46
|
"type": "git",
|
|
@@ -57,5 +49,12 @@
|
|
|
57
49
|
"homepage": "https://useai.dev",
|
|
58
50
|
"engines": {
|
|
59
51
|
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -p tsconfig.build.json",
|
|
55
|
+
"dev": "tsc --watch",
|
|
56
|
+
"bundle": "tsup src/index.ts --format esm --target node18 --no-splitting",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"clean": "rm -rf dist"
|
|
60
59
|
}
|
|
61
|
-
}
|
|
60
|
+
}
|