@elnora-ai/cli 1.3.5 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1819 -1587
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ var VERSION, PRODUCTION_BASE_URL, BASE_URL, AI_SERVER_URL, ENDPOINTS, DEFAULT_HE
|
|
|
25
25
|
var init_config = __esm({
|
|
26
26
|
"src/lib/config.ts"() {
|
|
27
27
|
"use strict";
|
|
28
|
-
VERSION = true ? "1.
|
|
28
|
+
VERSION = true ? "1.5.0" : process.env.npm_package_version ?? "0.0.0-dev";
|
|
29
29
|
PRODUCTION_BASE_URL = "https://platform.elnora.ai/api/v1";
|
|
30
30
|
BASE_URL = process.env.ELNORA_API_URL ?? PRODUCTION_BASE_URL;
|
|
31
31
|
AI_SERVER_URL = process.env.ELNORA_AI_SERVER_URL ?? "https://platform.elnora.ai/ai-server";
|
|
@@ -1132,257 +1132,1065 @@ function buildProgram(registry2) {
|
|
|
1132
1132
|
return program2;
|
|
1133
1133
|
}
|
|
1134
1134
|
|
|
1135
|
-
// src/commands/
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1135
|
+
// src/commands/completion.ts
|
|
1136
|
+
function extractCommandTree(cmd) {
|
|
1137
|
+
return {
|
|
1138
|
+
name: cmd.name(),
|
|
1139
|
+
description: cmd.description(),
|
|
1140
|
+
subcommands: cmd.commands.map(extractCommandTree),
|
|
1141
|
+
options: cmd.options.map((o) => ({
|
|
1142
|
+
flags: o.long ?? o.short ?? "",
|
|
1143
|
+
description: o.description
|
|
1144
|
+
}))
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function generateBash(root) {
|
|
1148
|
+
const lines = [
|
|
1149
|
+
"# Bash completion for elnora",
|
|
1150
|
+
"# Install: elnora completion bash > ~/.local/share/bash-completion/completions/elnora",
|
|
1151
|
+
"",
|
|
1152
|
+
"_elnora() {",
|
|
1153
|
+
' local cur="${COMP_WORDS[COMP_CWORD]}"',
|
|
1154
|
+
' local prev="${COMP_WORDS[COMP_CWORD-1]}"',
|
|
1155
|
+
""
|
|
1156
|
+
];
|
|
1157
|
+
const topCmds = root.subcommands.map((c) => c.name).join(" ");
|
|
1158
|
+
lines.push(` if [ "$COMP_CWORD" -eq 1 ]; then`);
|
|
1159
|
+
lines.push(` COMPREPLY=( $(compgen -W "${topCmds}" -- "$cur") )`);
|
|
1160
|
+
lines.push(" return");
|
|
1161
|
+
lines.push(" fi");
|
|
1162
|
+
lines.push("");
|
|
1163
|
+
lines.push(' case "${COMP_WORDS[1]}" in');
|
|
1164
|
+
for (const group of root.subcommands) {
|
|
1165
|
+
if (group.subcommands.length > 0) {
|
|
1166
|
+
const subs = group.subcommands.map((s) => s.name).join(" ");
|
|
1167
|
+
lines.push(` ${group.name})`);
|
|
1168
|
+
lines.push(` COMPREPLY=( $(compgen -W "${subs}" -- "$cur") )`);
|
|
1169
|
+
lines.push(" ;;");
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
lines.push(" esac");
|
|
1173
|
+
lines.push("}");
|
|
1174
|
+
lines.push("");
|
|
1175
|
+
lines.push("complete -F _elnora elnora");
|
|
1176
|
+
return lines.join("\n");
|
|
1177
|
+
}
|
|
1178
|
+
function generateZsh(root) {
|
|
1179
|
+
const lines = [
|
|
1180
|
+
"#compdef elnora",
|
|
1181
|
+
"# Zsh completion for elnora",
|
|
1182
|
+
"# Install: elnora completion zsh > ~/.zfunc/_elnora",
|
|
1183
|
+
"",
|
|
1184
|
+
"_elnora() {",
|
|
1185
|
+
" local -a commands",
|
|
1186
|
+
""
|
|
1187
|
+
];
|
|
1188
|
+
lines.push(" if (( CURRENT == 2 )); then");
|
|
1189
|
+
lines.push(" commands=(");
|
|
1190
|
+
for (const cmd of root.subcommands) {
|
|
1191
|
+
lines.push(` '${cmd.name}:${cmd.description.replace(/\\/g, "\\\\").replace(/'/g, "")}'`);
|
|
1192
|
+
}
|
|
1193
|
+
lines.push(" )");
|
|
1194
|
+
lines.push(" _describe 'command' commands");
|
|
1195
|
+
lines.push(" return");
|
|
1196
|
+
lines.push(" fi");
|
|
1197
|
+
lines.push("");
|
|
1198
|
+
lines.push(" case $words[2] in");
|
|
1199
|
+
for (const group of root.subcommands) {
|
|
1200
|
+
if (group.subcommands.length > 0) {
|
|
1201
|
+
lines.push(` ${group.name})`);
|
|
1202
|
+
lines.push(" local -a subcommands");
|
|
1203
|
+
lines.push(" subcommands=(");
|
|
1204
|
+
for (const sub of group.subcommands) {
|
|
1205
|
+
lines.push(` '${sub.name}:${sub.description.replace(/\\/g, "\\\\").replace(/'/g, "")}'`);
|
|
1206
|
+
}
|
|
1207
|
+
lines.push(" )");
|
|
1208
|
+
lines.push(" _describe 'subcommand' subcommands");
|
|
1209
|
+
lines.push(" ;;");
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
lines.push(" esac");
|
|
1213
|
+
lines.push("}");
|
|
1214
|
+
lines.push("");
|
|
1215
|
+
lines.push("_elnora");
|
|
1216
|
+
return lines.join("\n");
|
|
1217
|
+
}
|
|
1218
|
+
function generateFish(root) {
|
|
1219
|
+
const lines = [
|
|
1220
|
+
"# Fish completion for elnora",
|
|
1221
|
+
"# Install: elnora completion fish > ~/.config/fish/completions/elnora.fish",
|
|
1222
|
+
""
|
|
1223
|
+
];
|
|
1224
|
+
for (const cmd of root.subcommands) {
|
|
1225
|
+
lines.push(
|
|
1226
|
+
`complete -c elnora -n "__fish_use_subcommand" -a "${cmd.name}" -d "${cmd.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
lines.push("");
|
|
1230
|
+
for (const group of root.subcommands) {
|
|
1231
|
+
for (const sub of group.subcommands) {
|
|
1232
|
+
lines.push(
|
|
1233
|
+
`complete -c elnora -n "__fish_seen_subcommand_from ${group.name}" -a "${sub.name}" -d "${sub.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return lines.join("\n");
|
|
1238
|
+
}
|
|
1239
|
+
function addCompletionCommand(program2) {
|
|
1240
|
+
program2.command("completion").argument("<shell>", "Shell type: bash, zsh, fish").description("Generate shell completion script").action((shell) => {
|
|
1241
|
+
const tree = extractCommandTree(program2);
|
|
1242
|
+
switch (shell) {
|
|
1243
|
+
case "bash":
|
|
1244
|
+
console.log(generateBash(tree));
|
|
1245
|
+
break;
|
|
1246
|
+
case "zsh":
|
|
1247
|
+
console.log(generateZsh(tree));
|
|
1248
|
+
break;
|
|
1249
|
+
case "fish":
|
|
1250
|
+
console.log(generateFish(tree));
|
|
1251
|
+
break;
|
|
1252
|
+
default:
|
|
1253
|
+
console.error(`Unsupported shell: ${shell}. Use bash, zsh, or fish.`);
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/commands/doctor.ts
|
|
1260
|
+
init_client();
|
|
1261
|
+
init_config();
|
|
1262
|
+
init_profiles();
|
|
1263
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
1264
|
+
import { homedir as homedir4 } from "node:os";
|
|
1265
|
+
import { delimiter, join as join4 } from "node:path";
|
|
1266
|
+
import pc3 from "picocolors";
|
|
1267
|
+
|
|
1268
|
+
// src/lib/setup/common.ts
|
|
1269
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1270
|
+
import { homedir as homedir2 } from "node:os";
|
|
1271
|
+
import { join as join2 } from "node:path";
|
|
1272
|
+
import pc from "picocolors";
|
|
1273
|
+
|
|
1274
|
+
// src/lib/tty.ts
|
|
1275
|
+
function isTTY() {
|
|
1276
|
+
return Boolean(process.stdout.isTTY);
|
|
1277
|
+
}
|
|
1278
|
+
function isColorEnabled() {
|
|
1279
|
+
if (process.env.NO_COLOR !== void 0) return false;
|
|
1280
|
+
if (process.env.FORCE_COLOR !== void 0) return true;
|
|
1281
|
+
return isTTY();
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/lib/setup/common.ts
|
|
1285
|
+
var HOME = homedir2();
|
|
1286
|
+
var ELNORA_DIR = join2(HOME, ".elnora");
|
|
1287
|
+
var CLAUDE_DIR = join2(HOME, ".claude");
|
|
1288
|
+
var CLAUDE_MARKETPLACES_FILE = join2(CLAUDE_DIR, "plugins", "known_marketplaces.json");
|
|
1289
|
+
var CLAUDE_SETTINGS_FILE = join2(CLAUDE_DIR, "settings.json");
|
|
1290
|
+
var CURSOR_DIR = join2(HOME, ".cursor");
|
|
1291
|
+
var CURSOR_MCP_GLOBAL = join2(HOME, ".cursor", "mcp.json");
|
|
1292
|
+
var VSCODE_DIR = join2(HOME, ".vscode");
|
|
1293
|
+
var CODEX_DIR = join2(HOME, ".codex");
|
|
1294
|
+
var MARKETPLACE_NAME = "elnora-plugins";
|
|
1295
|
+
var MARKETPLACE_REPO = "Elnora-AI/elnora-plugins";
|
|
1296
|
+
var PLUGIN_ID = `elnora@${MARKETPLACE_NAME}`;
|
|
1297
|
+
var LEGACY_PLUGIN_IDS = ["elnora@elnora-local", "elnora@elnora-ai"];
|
|
1298
|
+
var LEGACY_MARKETPLACE_NAMES = ["elnora-local", "elnora-ai"];
|
|
1299
|
+
function readJsonFile(path) {
|
|
1300
|
+
try {
|
|
1301
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
1302
|
+
} catch {
|
|
1303
|
+
return {};
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
function writeJsonFile(path, data) {
|
|
1307
|
+
mkdirSync2(join2(path, ".."), { recursive: true });
|
|
1308
|
+
writeFileSync2(path, `${JSON.stringify(data, null, 2)}
|
|
1309
|
+
`, "utf-8");
|
|
1310
|
+
}
|
|
1311
|
+
var color = isColorEnabled();
|
|
1312
|
+
function ok(msg) {
|
|
1313
|
+
return color ? ` ${pc.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
1314
|
+
}
|
|
1315
|
+
function fail(msg) {
|
|
1316
|
+
return color ? ` ${pc.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
1317
|
+
}
|
|
1318
|
+
function info(msg) {
|
|
1319
|
+
return color ? ` ${pc.dim(msg)}` : ` ${msg}`;
|
|
1320
|
+
}
|
|
1321
|
+
function detectPlatforms() {
|
|
1322
|
+
return [
|
|
1323
|
+
{ name: "claude", label: "Claude Code", installed: existsSync2(CLAUDE_DIR) },
|
|
1324
|
+
{ name: "cursor", label: "Cursor", installed: existsSync2(CURSOR_DIR) },
|
|
1325
|
+
{ name: "vscode", label: "VS Code", installed: existsSync2(VSCODE_DIR) },
|
|
1326
|
+
{ name: "codex", label: "OpenAI Codex", installed: existsSync2(CODEX_DIR) }
|
|
1327
|
+
];
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// src/lib/update-check.ts
|
|
1331
|
+
init_config();
|
|
1332
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1333
|
+
import { homedir as homedir3 } from "node:os";
|
|
1334
|
+
import { join as join3 } from "node:path";
|
|
1335
|
+
import pc2 from "picocolors";
|
|
1336
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
1337
|
+
var CACHE_DIR = join3(homedir3(), ".elnora");
|
|
1338
|
+
var CACHE_FILE = join3(CACHE_DIR, ".update-check");
|
|
1339
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1340
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
1341
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/;
|
|
1342
|
+
function shouldSkipEntirely() {
|
|
1343
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
|
|
1344
|
+
if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
function readCache() {
|
|
1348
|
+
try {
|
|
1349
|
+
if (!existsSync3(CACHE_FILE)) return null;
|
|
1350
|
+
const raw = readFileSync3(CACHE_FILE, "utf-8");
|
|
1351
|
+
return JSON.parse(raw);
|
|
1352
|
+
} catch {
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
function writeCache(entry) {
|
|
1357
|
+
try {
|
|
1358
|
+
mkdirSync3(CACHE_DIR, { recursive: true });
|
|
1359
|
+
writeFileSync3(CACHE_FILE, JSON.stringify(entry), "utf-8");
|
|
1360
|
+
} catch {
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
function showUpdateNotice(latest) {
|
|
1364
|
+
if (!process.stderr.isTTY) return;
|
|
1365
|
+
const color2 = isColorEnabled();
|
|
1366
|
+
const msg = color2 ? `
|
|
1367
|
+
${pc2.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
|
|
1368
|
+
Run: elnora update
|
|
1369
|
+
` : `
|
|
1370
|
+
Update available: v${VERSION} \u2192 v${latest}
|
|
1371
|
+
Run: elnora update
|
|
1372
|
+
`;
|
|
1373
|
+
process.stderr.write(msg);
|
|
1374
|
+
}
|
|
1375
|
+
function isNewerVersion(a, b) {
|
|
1376
|
+
const pa = a.split(".").map(Number);
|
|
1377
|
+
const pb = b.split(".").map(Number);
|
|
1378
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1379
|
+
const na = pa[i] ?? 0;
|
|
1380
|
+
const nb = pb[i] ?? 0;
|
|
1381
|
+
if (na > nb) return true;
|
|
1382
|
+
if (na < nb) return false;
|
|
1383
|
+
}
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
function registerUpdateCheck() {
|
|
1387
|
+
if (shouldSkipEntirely()) return;
|
|
1388
|
+
const cached = readCache();
|
|
1389
|
+
if (cached) {
|
|
1390
|
+
const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
|
|
1391
|
+
if (elapsed < CHECK_INTERVAL_MS) {
|
|
1392
|
+
if (cached.latest && cached.latest !== VERSION && cached.latest !== "unknown") {
|
|
1393
|
+
if (isNewerVersion(cached.latest, VERSION)) {
|
|
1394
|
+
showUpdateNotice(cached.latest);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
const keepAlive = setInterval(() => {
|
|
1401
|
+
}, 6e4);
|
|
1402
|
+
(async () => {
|
|
1403
|
+
try {
|
|
1404
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
1405
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1406
|
+
});
|
|
1407
|
+
if (!response.ok) return;
|
|
1408
|
+
const data = await response.json();
|
|
1409
|
+
const latest = data.version ?? "unknown";
|
|
1410
|
+
if (latest !== "unknown" && !SEMVER_RE.test(latest)) return;
|
|
1411
|
+
writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest });
|
|
1412
|
+
if (latest !== VERSION && latest !== "unknown" && isNewerVersion(latest, VERSION)) {
|
|
1413
|
+
showUpdateNotice(latest);
|
|
1414
|
+
}
|
|
1415
|
+
} catch {
|
|
1416
|
+
} finally {
|
|
1417
|
+
clearInterval(keepAlive);
|
|
1418
|
+
}
|
|
1419
|
+
})();
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// src/commands/doctor.ts
|
|
1423
|
+
var EXPECTED_SKILLS = [
|
|
1424
|
+
"elnora-admin",
|
|
1425
|
+
"elnora-agent",
|
|
1426
|
+
"elnora-files",
|
|
1427
|
+
"elnora-folders",
|
|
1428
|
+
"elnora-orgs",
|
|
1429
|
+
"elnora-platform",
|
|
1430
|
+
"elnora-projects",
|
|
1431
|
+
"elnora-search",
|
|
1432
|
+
"elnora-tasks"
|
|
1433
|
+
];
|
|
1434
|
+
async function runCheck(fn) {
|
|
1435
|
+
try {
|
|
1436
|
+
return await fn();
|
|
1437
|
+
} catch (e) {
|
|
1438
|
+
return { status: "fail", msg: `${e instanceof Error ? e.message : String(e)}` };
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
async function checkApiReachable() {
|
|
1442
|
+
const start = Date.now();
|
|
1443
|
+
const res = await fetch("https://platform.elnora.ai/health", { signal: AbortSignal.timeout(5e3) });
|
|
1444
|
+
const ms = Date.now() - start;
|
|
1445
|
+
if (res.ok) return { status: "pass", msg: `API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)` };
|
|
1446
|
+
return { status: "fail", msg: `API unreachable platform.elnora.ai (HTTP ${res.status})` };
|
|
1447
|
+
}
|
|
1448
|
+
async function checkAuthenticated() {
|
|
1449
|
+
const key = resolveApiKey();
|
|
1450
|
+
const client = new ElnoraApiClient(key);
|
|
1451
|
+
const result = await client.get("projects", {
|
|
1452
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
1453
|
+
});
|
|
1454
|
+
const count = result?.totalCount ?? result?.items?.length ?? 0;
|
|
1455
|
+
return { status: "pass", msg: `Authenticated profile: default (${count} projects accessible)` };
|
|
1456
|
+
}
|
|
1457
|
+
async function checkVersionCurrent() {
|
|
1458
|
+
try {
|
|
1459
|
+
const res = await fetch("https://registry.npmjs.org/@elnora-ai/cli/latest", {
|
|
1460
|
+
signal: AbortSignal.timeout(3e3)
|
|
1149
1461
|
});
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1462
|
+
if (!res.ok) return { status: "pass", msg: `Version v${VERSION} (registry check failed)` };
|
|
1463
|
+
const data = await res.json();
|
|
1464
|
+
const latest = data.version ?? "unknown";
|
|
1465
|
+
if (latest === VERSION || latest === "unknown" || !isNewerVersion(latest, VERSION)) {
|
|
1466
|
+
return { status: "pass", msg: `Version current v${VERSION} (latest: v${latest})` };
|
|
1467
|
+
}
|
|
1468
|
+
return { status: "warn", msg: `Update available v${VERSION} \u2192 v${latest}` };
|
|
1469
|
+
} catch {
|
|
1470
|
+
return { status: "pass", msg: `Version v${VERSION} (registry unreachable)` };
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
function checkConfigPermissions() {
|
|
1474
|
+
if (process.platform === "win32") {
|
|
1475
|
+
return { status: "pass", msg: "Config permissions (skipped on Windows)" };
|
|
1476
|
+
}
|
|
1477
|
+
if (!existsSync4(PROFILES_FILE)) {
|
|
1478
|
+
return { status: "warn", msg: "Config permissions ~/.elnora/profiles.toml not found" };
|
|
1479
|
+
}
|
|
1480
|
+
const mode = statSync(PROFILES_FILE).mode & 511;
|
|
1481
|
+
if (mode === 384) {
|
|
1482
|
+
return { status: "pass", msg: `Config permissions ${PROFILES_FILE} (0600)` };
|
|
1483
|
+
}
|
|
1484
|
+
return { status: "warn", msg: `Config permissions ${PROFILES_FILE} (0${mode.toString(8)}) \u2014 should be 0600` };
|
|
1485
|
+
}
|
|
1486
|
+
async function checkAiServerReachable() {
|
|
1487
|
+
const start = Date.now();
|
|
1488
|
+
const res = await fetch(`${AI_SERVER_URL}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
1489
|
+
const ms = Date.now() - start;
|
|
1490
|
+
const host = new URL(AI_SERVER_URL).hostname;
|
|
1491
|
+
if (res.ok) return { status: "pass", msg: `AI Server reachable ${host} (${res.status} OK, ${ms}ms)` };
|
|
1492
|
+
return { status: "fail", msg: `AI Server unreachable ${host} (HTTP ${res.status})` };
|
|
1493
|
+
}
|
|
1494
|
+
function checkPathConfigured() {
|
|
1495
|
+
const installDirs = [join4(homedir4(), ".local", "bin"), join4(homedir4(), ".elnora", "bin")];
|
|
1496
|
+
const pathVar = process.env.PATH ?? "";
|
|
1497
|
+
const entries = pathVar.split(delimiter).filter(Boolean);
|
|
1498
|
+
const matched = installDirs.find((d) => entries.includes(d));
|
|
1499
|
+
if (matched) {
|
|
1500
|
+
return { status: "pass", msg: `PATH configured ${matched}` };
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
status: "warn",
|
|
1504
|
+
msg: "PATH configured standard install dirs not in PATH (OK if installed via npm/brew)"
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
function checkClaudePlugin() {
|
|
1508
|
+
if (!existsSync4(CLAUDE_DIR)) {
|
|
1509
|
+
return { status: "skip", msg: "Plugin enabled (Claude Code not installed)" };
|
|
1510
|
+
}
|
|
1511
|
+
if (!existsSync4(CLAUDE_SETTINGS_FILE)) {
|
|
1512
|
+
return { status: "fail", msg: "Plugin enabled settings.json not found \u2014 run 'elnora setup claude'" };
|
|
1513
|
+
}
|
|
1514
|
+
let settings;
|
|
1515
|
+
try {
|
|
1516
|
+
settings = JSON.parse(readFileSync4(CLAUDE_SETTINGS_FILE, "utf-8"));
|
|
1517
|
+
} catch (e) {
|
|
1518
|
+
return {
|
|
1519
|
+
status: "fail",
|
|
1520
|
+
msg: `Plugin enabled settings.json unreadable (${e instanceof Error ? e.message : "parse error"})`
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
const raw = settings.enabledPlugins;
|
|
1524
|
+
const enabledPlugins = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
1525
|
+
const isEnabled = enabledPlugins[PLUGIN_ID] === true;
|
|
1526
|
+
const legacyKeys = Object.keys(enabledPlugins).filter((k) => k.startsWith("elnora@") && k !== PLUGIN_ID);
|
|
1527
|
+
if (isEnabled && legacyKeys.length === 0) {
|
|
1528
|
+
return { status: "pass", msg: `Plugin enabled ${PLUGIN_ID}` };
|
|
1529
|
+
}
|
|
1530
|
+
if (isEnabled && legacyKeys.length > 0) {
|
|
1531
|
+
return {
|
|
1532
|
+
status: "warn",
|
|
1533
|
+
msg: `Plugin enabled ${PLUGIN_ID} \u2014 but stale legacy entries also present: ${legacyKeys.join(", ")}`
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
if (legacyKeys.length > 0) {
|
|
1537
|
+
return {
|
|
1538
|
+
status: "fail",
|
|
1539
|
+
msg: `Plugin enabled found legacy entries ${legacyKeys.join(", ")} but not ${PLUGIN_ID} \u2014 run 'elnora setup claude'`
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
return { status: "fail", msg: "Plugin enabled not found \u2014 run 'elnora setup claude'" };
|
|
1543
|
+
}
|
|
1544
|
+
function checkSkillsInstalled() {
|
|
1545
|
+
if (!existsSync4(CLAUDE_DIR)) {
|
|
1546
|
+
return { status: "skip", msg: "Skills installed (Claude Code not installed)" };
|
|
1547
|
+
}
|
|
1548
|
+
const skillsDir = join4(CLAUDE_DIR, "plugins", "marketplaces", MARKETPLACE_NAME, "elnora", "skills");
|
|
1549
|
+
if (!existsSync4(skillsDir)) {
|
|
1550
|
+
return {
|
|
1551
|
+
status: "warn",
|
|
1552
|
+
msg: "Skills installed marketplace not cloned yet \u2014 restart Claude Code to trigger clone"
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
const found = EXPECTED_SKILLS.filter((s) => existsSync4(join4(skillsDir, s, "SKILL.md")));
|
|
1556
|
+
if (found.length === EXPECTED_SKILLS.length) {
|
|
1557
|
+
return { status: "pass", msg: `Skills installed ${found.length} skills` };
|
|
1558
|
+
}
|
|
1559
|
+
const missing = EXPECTED_SKILLS.filter((s) => !found.includes(s));
|
|
1560
|
+
return {
|
|
1561
|
+
status: "fail",
|
|
1562
|
+
msg: `Skills installed ${found.length}/${EXPECTED_SKILLS.length} \u2014 missing: ${missing.join(", ")}`
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function checkPluginVersion() {
|
|
1566
|
+
if (!existsSync4(CLAUDE_DIR)) {
|
|
1567
|
+
return { status: "skip", msg: "Plugin version (Claude Code not installed)" };
|
|
1568
|
+
}
|
|
1569
|
+
const pluginJsonPath = join4(
|
|
1570
|
+
CLAUDE_DIR,
|
|
1571
|
+
"plugins",
|
|
1572
|
+
"marketplaces",
|
|
1573
|
+
MARKETPLACE_NAME,
|
|
1574
|
+
"elnora",
|
|
1575
|
+
".claude-plugin",
|
|
1576
|
+
"plugin.json"
|
|
1577
|
+
);
|
|
1578
|
+
if (!existsSync4(pluginJsonPath)) {
|
|
1579
|
+
return { status: "skip", msg: "Plugin version (plugin not cloned yet)" };
|
|
1580
|
+
}
|
|
1581
|
+
try {
|
|
1582
|
+
const pj = JSON.parse(readFileSync4(pluginJsonPath, "utf-8"));
|
|
1583
|
+
const pluginVersion = pj.version ?? "unknown";
|
|
1584
|
+
if (pluginVersion === VERSION) {
|
|
1585
|
+
return { status: "pass", msg: `Plugin version v${pluginVersion} (matches CLI)` };
|
|
1586
|
+
}
|
|
1587
|
+
return {
|
|
1588
|
+
status: "warn",
|
|
1589
|
+
msg: `Plugin version v${pluginVersion} (CLI is v${VERSION}) \u2014 restart Claude Code to refresh`
|
|
1590
|
+
};
|
|
1591
|
+
} catch (e) {
|
|
1592
|
+
return {
|
|
1593
|
+
status: "fail",
|
|
1594
|
+
msg: `Plugin version plugin.json unreadable (${e instanceof Error ? e.message : "parse error"})`
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
async function checkMcpReachable() {
|
|
1599
|
+
const start = Date.now();
|
|
1600
|
+
try {
|
|
1601
|
+
const res = await fetch("https://mcp.elnora.ai/mcp", {
|
|
1602
|
+
method: "HEAD",
|
|
1603
|
+
signal: AbortSignal.timeout(5e3)
|
|
1604
|
+
});
|
|
1605
|
+
const ms = Date.now() - start;
|
|
1606
|
+
if (res.status < 500) {
|
|
1607
|
+
return { status: "pass", msg: `Server reachable mcp.elnora.ai (${res.status}, ${ms}ms)` };
|
|
1608
|
+
}
|
|
1609
|
+
return { status: "fail", msg: `Server unreachable mcp.elnora.ai (HTTP ${res.status})` };
|
|
1610
|
+
} catch (e) {
|
|
1611
|
+
return {
|
|
1612
|
+
status: "fail",
|
|
1613
|
+
msg: `Server unreachable mcp.elnora.ai (${e instanceof Error ? e.message : "network error"})`
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function addDoctorCommand(program2) {
|
|
1618
|
+
program2.command("doctor").description("Check Elnora CLI configuration and connectivity").action(async () => {
|
|
1619
|
+
const color2 = isColorEnabled();
|
|
1620
|
+
const ok2 = (msg) => color2 ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
1621
|
+
const fail2 = (msg) => color2 ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
1622
|
+
const warn = (msg) => color2 ? ` ${pc3.yellow("!")} ${msg}` : ` ! ${msg}`;
|
|
1623
|
+
const skip = (msg) => color2 ? ` ${pc3.dim("\u25CB")} ${msg}` : ` \u25CB ${msg}`;
|
|
1624
|
+
const section = (name) => color2 ? pc3.bold(` ${name}`) : ` ${name}`;
|
|
1625
|
+
const render = (r) => {
|
|
1626
|
+
switch (r.status) {
|
|
1627
|
+
case "pass":
|
|
1628
|
+
return ok2(r.msg);
|
|
1629
|
+
case "fail":
|
|
1630
|
+
return fail2(r.msg);
|
|
1631
|
+
case "warn":
|
|
1632
|
+
return warn(r.msg);
|
|
1633
|
+
case "skip":
|
|
1634
|
+
return skip(r.msg);
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
console.error(`
|
|
1638
|
+
Elnora CLI v${VERSION}
|
|
1639
|
+
`);
|
|
1640
|
+
let passed = 0;
|
|
1641
|
+
let failed = 0;
|
|
1642
|
+
let warnings = 0;
|
|
1643
|
+
let total = 0;
|
|
1644
|
+
const tally = (r) => {
|
|
1645
|
+
total++;
|
|
1646
|
+
if (r.status === "pass" || r.status === "skip") passed++;
|
|
1647
|
+
else if (r.status === "warn") {
|
|
1648
|
+
passed++;
|
|
1649
|
+
warnings++;
|
|
1650
|
+
} else failed++;
|
|
1651
|
+
};
|
|
1652
|
+
console.error(section("CLI"));
|
|
1653
|
+
const cliChecks = [
|
|
1654
|
+
await runCheck(() => checkApiReachable()),
|
|
1655
|
+
await runCheck(() => checkAuthenticated()),
|
|
1656
|
+
await runCheck(() => checkVersionCurrent()),
|
|
1657
|
+
await runCheck(() => checkConfigPermissions()),
|
|
1658
|
+
await runCheck(() => checkAiServerReachable()),
|
|
1659
|
+
await runCheck(() => checkPathConfigured())
|
|
1660
|
+
];
|
|
1661
|
+
for (const r of cliChecks) {
|
|
1662
|
+
console.error(render(r));
|
|
1663
|
+
tally(r);
|
|
1664
|
+
}
|
|
1665
|
+
console.error("");
|
|
1666
|
+
console.error(section("Claude Code"));
|
|
1667
|
+
const claudeChecks = [
|
|
1668
|
+
await runCheck(() => checkClaudePlugin()),
|
|
1669
|
+
await runCheck(() => checkSkillsInstalled()),
|
|
1670
|
+
await runCheck(() => checkPluginVersion())
|
|
1671
|
+
];
|
|
1672
|
+
for (const r of claudeChecks) {
|
|
1673
|
+
console.error(render(r));
|
|
1674
|
+
tally(r);
|
|
1675
|
+
}
|
|
1676
|
+
console.error("");
|
|
1677
|
+
console.error(section("MCP"));
|
|
1678
|
+
const mcpChecks = [await runCheck(() => checkMcpReachable())];
|
|
1679
|
+
for (const r of mcpChecks) {
|
|
1680
|
+
console.error(render(r));
|
|
1681
|
+
tally(r);
|
|
1682
|
+
}
|
|
1683
|
+
console.error("");
|
|
1684
|
+
if (failed === 0 && warnings === 0) {
|
|
1685
|
+
console.error("All checks passed.");
|
|
1686
|
+
} else if (failed === 0) {
|
|
1687
|
+
console.error(`${passed}/${total} checks passed (${warnings} warning${warnings === 1 ? "" : "s"}).`);
|
|
1688
|
+
} else {
|
|
1689
|
+
const warnSuffix = warnings > 0 ? ` (${warnings} warning${warnings === 1 ? "" : "s"})` : "";
|
|
1690
|
+
console.error(`${passed}/${total} checks passed \u2014 ${failed} failed${warnSuffix}.`);
|
|
1691
|
+
if (!existsSync4(PROFILES_FILE)) console.error("\nRun: elnora auth login");
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/commands/mcp/serve.ts
|
|
1697
|
+
function requireEnv(name) {
|
|
1698
|
+
const value = process.env[name];
|
|
1699
|
+
if (!value) {
|
|
1700
|
+
throw new Error(`Required environment variable ${name} is not set`);
|
|
1701
|
+
}
|
|
1702
|
+
return value;
|
|
1703
|
+
}
|
|
1704
|
+
function addMcpCommands(program2, registry2) {
|
|
1705
|
+
const mcp = program2.command("mcp").description("MCP server management");
|
|
1706
|
+
mcp.command("serve").description("Start MCP server exposing all CLI commands as tools").option("--http", "HTTP transport (default, for deployment)").option("--stdio", "stdio transport (for local MCP clients)").option("--port <port>", "HTTP port", process.env.PORT ?? "3000").option("--host <host>", "HTTP bind address", process.env.HOST ?? "0.0.0.0").action(async (opts) => {
|
|
1707
|
+
if (opts.stdio) {
|
|
1708
|
+
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_stdio_server(), stdio_server_exports));
|
|
1709
|
+
await startStdioServer2(registry2);
|
|
1710
|
+
} else {
|
|
1711
|
+
const { startHttpServer: startHttpServer2 } = await Promise.resolve().then(() => (init_http_server(), http_server_exports));
|
|
1712
|
+
await startHttpServer2(registry2, {
|
|
1713
|
+
port: Number.parseInt(opts.port, 10),
|
|
1714
|
+
host: opts.host,
|
|
1715
|
+
tokenValidationUrl: requireEnv("ELNORA_TOKEN_VALIDATION_URL"),
|
|
1716
|
+
mcpServiceKey: requireEnv("ELNORA_MCP_SERVICE_KEY")
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// src/commands/open.ts
|
|
1723
|
+
import { exec } from "node:child_process";
|
|
1724
|
+
var URLS = {
|
|
1725
|
+
"": "https://platform.elnora.ai",
|
|
1726
|
+
platform: "https://platform.elnora.ai",
|
|
1727
|
+
docs: "https://docs.elnora.ai",
|
|
1728
|
+
keys: "https://platform.elnora.ai/settings/api-keys",
|
|
1729
|
+
billing: "https://platform.elnora.ai/settings/billing",
|
|
1730
|
+
github: "https://github.com/Elnora-AI/elnora-cli"
|
|
1731
|
+
};
|
|
1732
|
+
function openUrl(url) {
|
|
1733
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
1734
|
+
exec(cmd, (err) => {
|
|
1735
|
+
if (err) {
|
|
1736
|
+
console.error(`Failed to open browser: ${err.message}`);
|
|
1737
|
+
console.log(url);
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
function addOpenCommand(program2) {
|
|
1742
|
+
program2.command("open").argument("[page]", "Page to open: platform, docs, keys, billing, github").description("Open Elnora platform in the browser").action((page) => {
|
|
1743
|
+
const key = page ?? "";
|
|
1744
|
+
const url = URLS[key];
|
|
1745
|
+
if (!url) {
|
|
1746
|
+
console.error(`Unknown page: ${page}`);
|
|
1747
|
+
console.error(`Available: ${Object.keys(URLS).filter(Boolean).join(", ")}`);
|
|
1748
|
+
process.exit(1);
|
|
1749
|
+
}
|
|
1750
|
+
openUrl(url);
|
|
1751
|
+
console.log(`Opening ${url}...`);
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// src/commands/setup.ts
|
|
1756
|
+
init_profiles();
|
|
1757
|
+
|
|
1758
|
+
// src/lib/setup/claude.ts
|
|
1759
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1760
|
+
function setupClaude() {
|
|
1761
|
+
if (!existsSync5(CLAUDE_DIR)) {
|
|
1762
|
+
console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
|
|
1763
|
+
console.error("");
|
|
1764
|
+
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
1765
|
+
return false;
|
|
1766
|
+
}
|
|
1767
|
+
console.error(ok("Claude Code found"));
|
|
1768
|
+
const marketplaces = readJsonFile(CLAUDE_MARKETPLACES_FILE);
|
|
1769
|
+
for (const old of LEGACY_MARKETPLACE_NAMES) {
|
|
1770
|
+
delete marketplaces[old];
|
|
1771
|
+
}
|
|
1772
|
+
marketplaces[MARKETPLACE_NAME] = {
|
|
1773
|
+
source: {
|
|
1774
|
+
source: "github",
|
|
1775
|
+
repo: MARKETPLACE_REPO
|
|
1776
|
+
}
|
|
1777
|
+
};
|
|
1778
|
+
writeJsonFile(CLAUDE_MARKETPLACES_FILE, marketplaces);
|
|
1779
|
+
console.error(ok(`Marketplace registered (GitHub: ${MARKETPLACE_REPO})`));
|
|
1780
|
+
const settings = readJsonFile(CLAUDE_SETTINGS_FILE);
|
|
1781
|
+
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
|
|
1782
|
+
settings.enabledPlugins = {};
|
|
1783
|
+
}
|
|
1784
|
+
const plugins = settings.enabledPlugins;
|
|
1785
|
+
for (const old of LEGACY_PLUGIN_IDS) {
|
|
1786
|
+
delete plugins[old];
|
|
1787
|
+
}
|
|
1788
|
+
plugins[PLUGIN_ID] = true;
|
|
1789
|
+
writeJsonFile(CLAUDE_SETTINGS_FILE, settings);
|
|
1790
|
+
console.error(ok(`Plugin enabled (${PLUGIN_ID})`));
|
|
1791
|
+
console.error("");
|
|
1792
|
+
console.error(" Elnora skills are now available in Claude Code.");
|
|
1793
|
+
console.error(" Restart Claude Code if it's currently running.");
|
|
1794
|
+
return true;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// src/lib/setup/codex.ts
|
|
1798
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1799
|
+
import { join as join5 } from "node:path";
|
|
1800
|
+
var CODEX_MCP_FILE = join5(CODEX_DIR, "mcp.json");
|
|
1801
|
+
function setupCodex(apiKey) {
|
|
1802
|
+
if (!existsSync6(CODEX_DIR)) {
|
|
1803
|
+
console.error(fail("Codex CLI not found (~/.codex/ does not exist)"));
|
|
1804
|
+
console.error("");
|
|
1805
|
+
console.error(" Install Codex first: https://github.com/openai/codex");
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1808
|
+
console.error(ok("Codex CLI found"));
|
|
1809
|
+
const config = readJsonFile(CODEX_MCP_FILE);
|
|
1810
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
1811
|
+
config.mcpServers = {};
|
|
1812
|
+
}
|
|
1813
|
+
config.mcpServers.elnora = {
|
|
1814
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
1815
|
+
headers: {
|
|
1816
|
+
"X-API-Key": apiKey
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
writeJsonFile(CODEX_MCP_FILE, config);
|
|
1820
|
+
console.error(ok("MCP server configured in ~/.codex/mcp.json"));
|
|
1821
|
+
console.error("");
|
|
1822
|
+
console.error(" Elnora MCP server is now available in Codex.");
|
|
1823
|
+
return true;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// src/lib/setup/cursor.ts
|
|
1827
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1828
|
+
function setupCursor(apiKey) {
|
|
1829
|
+
if (!existsSync7(CURSOR_DIR)) {
|
|
1830
|
+
console.error(fail("Cursor not found (~/.cursor/ does not exist)"));
|
|
1831
|
+
console.error("");
|
|
1832
|
+
console.error(" Install Cursor first: https://cursor.com");
|
|
1833
|
+
return false;
|
|
1834
|
+
}
|
|
1835
|
+
console.error(ok("Cursor found"));
|
|
1836
|
+
const config = readJsonFile(CURSOR_MCP_GLOBAL);
|
|
1837
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
1838
|
+
config.mcpServers = {};
|
|
1839
|
+
}
|
|
1840
|
+
config.mcpServers.elnora = {
|
|
1841
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
1842
|
+
headers: {
|
|
1843
|
+
"X-API-Key": apiKey
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
writeJsonFile(CURSOR_MCP_GLOBAL, config);
|
|
1847
|
+
console.error(ok("MCP server configured in ~/.cursor/mcp.json"));
|
|
1848
|
+
console.error("");
|
|
1849
|
+
console.error(" Elnora MCP server is now available in Cursor.");
|
|
1850
|
+
console.error(" Restart Cursor if it's currently running.");
|
|
1851
|
+
return true;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/lib/setup/vscode.ts
|
|
1855
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1856
|
+
import { join as join6 } from "node:path";
|
|
1857
|
+
var VSCODE_MCP_GLOBAL = join6(HOME, ".vscode", "mcp.json");
|
|
1858
|
+
function setupVscode(apiKey) {
|
|
1859
|
+
if (!existsSync8(VSCODE_DIR)) {
|
|
1860
|
+
console.error(fail("VS Code not found (~/.vscode/ does not exist)"));
|
|
1861
|
+
console.error("");
|
|
1862
|
+
console.error(" Install VS Code first: https://code.visualstudio.com");
|
|
1863
|
+
return false;
|
|
1864
|
+
}
|
|
1865
|
+
console.error(ok("VS Code found"));
|
|
1866
|
+
const config = readJsonFile(VSCODE_MCP_GLOBAL);
|
|
1867
|
+
if (!config.servers || typeof config.servers !== "object") {
|
|
1868
|
+
config.servers = {};
|
|
1154
1869
|
}
|
|
1870
|
+
config.servers.elnora = {
|
|
1871
|
+
type: "http",
|
|
1872
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
1873
|
+
headers: {
|
|
1874
|
+
"X-API-Key": apiKey
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
writeJsonFile(VSCODE_MCP_GLOBAL, config);
|
|
1878
|
+
console.error(ok("MCP server configured in ~/.vscode/mcp.json"));
|
|
1879
|
+
console.error("");
|
|
1880
|
+
console.error(" Elnora MCP server is now available in VS Code Copilot.");
|
|
1881
|
+
console.error(" Restart VS Code if it's currently running.");
|
|
1882
|
+
return true;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// src/commands/setup.ts
|
|
1886
|
+
var PLATFORM_SETUP = {
|
|
1887
|
+
claude: () => setupClaude(),
|
|
1888
|
+
cursor: (key) => setupCursor(key),
|
|
1889
|
+
vscode: (key) => setupVscode(key),
|
|
1890
|
+
codex: (key) => setupCodex(key)
|
|
1155
1891
|
};
|
|
1892
|
+
function runSetup(platforms, profileName) {
|
|
1893
|
+
let apiKey;
|
|
1894
|
+
const needsKey = platforms.some((p) => p !== "claude");
|
|
1895
|
+
if (needsKey) {
|
|
1896
|
+
try {
|
|
1897
|
+
apiKey = resolveApiKey(profileName);
|
|
1898
|
+
} catch {
|
|
1899
|
+
if (platforms.every((p) => p !== "claude")) {
|
|
1900
|
+
console.error(fail("No API key found. Run 'elnora auth login' first."));
|
|
1901
|
+
process.exitCode = 1;
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
let anyFailed = false;
|
|
1907
|
+
for (const platform of platforms) {
|
|
1908
|
+
const setupFn = PLATFORM_SETUP[platform];
|
|
1909
|
+
const success = setupFn(apiKey ?? "");
|
|
1910
|
+
if (!success) anyFailed = true;
|
|
1911
|
+
console.error("");
|
|
1912
|
+
}
|
|
1913
|
+
if (anyFailed) {
|
|
1914
|
+
process.exitCode = 1;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
function addSetupCommand(program2) {
|
|
1918
|
+
const setup = program2.command("setup").description("Configure AI coding tools to use Elnora").option("--profile <name>", "API key profile to use", "default");
|
|
1919
|
+
setup.command("claude").description("Set up Claude Code (skills + MCP server)").action(() => {
|
|
1920
|
+
const opts = setup.opts();
|
|
1921
|
+
console.error("");
|
|
1922
|
+
runSetup(["claude"], opts.profile);
|
|
1923
|
+
});
|
|
1924
|
+
setup.command("cursor").description("Set up Cursor (MCP server)").action(() => {
|
|
1925
|
+
const opts = setup.opts();
|
|
1926
|
+
console.error("");
|
|
1927
|
+
runSetup(["cursor"], opts.profile);
|
|
1928
|
+
});
|
|
1929
|
+
setup.command("vscode").description("Set up VS Code Copilot (MCP server)").action(() => {
|
|
1930
|
+
const opts = setup.opts();
|
|
1931
|
+
console.error("");
|
|
1932
|
+
runSetup(["vscode"], opts.profile);
|
|
1933
|
+
});
|
|
1934
|
+
setup.command("codex").description("Set up OpenAI Codex (MCP server)").action(() => {
|
|
1935
|
+
const opts = setup.opts();
|
|
1936
|
+
console.error("");
|
|
1937
|
+
runSetup(["codex"], opts.profile);
|
|
1938
|
+
});
|
|
1939
|
+
setup.action(() => {
|
|
1940
|
+
const opts = setup.opts();
|
|
1941
|
+
console.error("");
|
|
1942
|
+
const detected = detectPlatforms();
|
|
1943
|
+
const installed = detected.filter((p) => p.installed);
|
|
1944
|
+
if (installed.length === 0) {
|
|
1945
|
+
console.error(fail("No supported AI coding tools detected."));
|
|
1946
|
+
console.error("");
|
|
1947
|
+
console.error(" Supported: Claude Code, Cursor, VS Code, OpenAI Codex");
|
|
1948
|
+
console.error(" Install one first, then run 'elnora setup' again.");
|
|
1949
|
+
process.exitCode = 1;
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
console.error(ok(`Detected ${installed.length} platform(s):`));
|
|
1953
|
+
for (const p of installed) {
|
|
1954
|
+
console.error(info(`${p.label}`));
|
|
1955
|
+
}
|
|
1956
|
+
console.error("");
|
|
1957
|
+
const platforms = installed.map((p) => p.name);
|
|
1958
|
+
runSetup(platforms, opts.profile);
|
|
1959
|
+
});
|
|
1960
|
+
program2.command("setup-claude").description("Set up Claude Code (alias for 'setup claude')").option("--profile <name>", "API key profile to use", "default").action((opts) => {
|
|
1961
|
+
console.error("");
|
|
1962
|
+
runSetup(["claude"], opts.profile);
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/commands/update.ts
|
|
1967
|
+
init_config();
|
|
1968
|
+
import { execFileSync } from "node:child_process";
|
|
1969
|
+
import { createHash } from "node:crypto";
|
|
1970
|
+
import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync } from "node:fs";
|
|
1971
|
+
import { tmpdir } from "node:os";
|
|
1972
|
+
import { join as join7 } from "node:path";
|
|
1973
|
+
import { Readable } from "node:stream";
|
|
1974
|
+
import { pipeline } from "node:stream/promises";
|
|
1975
|
+
import pc4 from "picocolors";
|
|
1156
1976
|
|
|
1157
|
-
// src/
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
content: z2.string().min(1).describe("Document content"),
|
|
1163
|
-
effectiveDate: z2.string().optional().describe("Effective date (ISO 8601)")
|
|
1164
|
-
});
|
|
1165
|
-
var accountAddLegalDoc = {
|
|
1166
|
-
name: "account.addLegalDoc",
|
|
1167
|
-
group: "account",
|
|
1168
|
-
description: "Add a legal document version (SystemAdmin only)",
|
|
1169
|
-
inputSchema: inputSchema2,
|
|
1170
|
-
outputSchema: z2.any(),
|
|
1171
|
-
async execute(input, ctx) {
|
|
1172
|
-
const body = {
|
|
1173
|
-
documentType: input.documentType,
|
|
1174
|
-
version: input.version,
|
|
1175
|
-
content: input.content
|
|
1176
|
-
};
|
|
1177
|
-
if (input.effectiveDate) body.effectiveDate = input.effectiveDate;
|
|
1178
|
-
return ctx.client.post("legal_doc_version", body);
|
|
1179
|
-
},
|
|
1180
|
-
formatOutput(output, format) {
|
|
1181
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1182
|
-
return JSON.stringify(output, null, 2);
|
|
1977
|
+
// src/lib/install-method.ts
|
|
1978
|
+
function detectInstallMethod() {
|
|
1979
|
+
const execPath = process.argv[1] ?? "";
|
|
1980
|
+
if (execPath.includes("node_modules")) {
|
|
1981
|
+
return "npm";
|
|
1183
1982
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
// src/commands/account/agreements.ts
|
|
1187
|
-
import { z as z3 } from "zod";
|
|
1188
|
-
var inputSchema3 = z3.object({});
|
|
1189
|
-
var accountAgreements = {
|
|
1190
|
-
name: "account.agreements",
|
|
1191
|
-
group: "account",
|
|
1192
|
-
description: "List user agreements",
|
|
1193
|
-
inputSchema: inputSchema3,
|
|
1194
|
-
outputSchema: z3.any(),
|
|
1195
|
-
annotations: { readOnlyHint: true },
|
|
1196
|
-
async execute(_input, ctx) {
|
|
1197
|
-
return ctx.client.get("user_agreements");
|
|
1198
|
-
},
|
|
1199
|
-
formatOutput(output, format) {
|
|
1200
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1201
|
-
return JSON.stringify(output, null, 2);
|
|
1983
|
+
if (execPath.includes("/Cellar/") || execPath.includes("/homebrew/")) {
|
|
1984
|
+
return "homebrew";
|
|
1202
1985
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
try {
|
|
1214
|
-
const answer = await rl.question(prompt);
|
|
1215
|
-
if (requiredInput) return answer.trim() === requiredInput;
|
|
1216
|
-
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
|
|
1217
|
-
} finally {
|
|
1218
|
-
rl.close();
|
|
1986
|
+
return "binary";
|
|
1987
|
+
}
|
|
1988
|
+
function getUpdateInstruction(method) {
|
|
1989
|
+
switch (method) {
|
|
1990
|
+
case "npm":
|
|
1991
|
+
return "npm install -g @elnora-ai/cli@latest";
|
|
1992
|
+
case "homebrew":
|
|
1993
|
+
return "brew upgrade elnora";
|
|
1994
|
+
case "binary":
|
|
1995
|
+
return "elnora update --install";
|
|
1219
1996
|
}
|
|
1220
1997
|
}
|
|
1221
1998
|
|
|
1222
|
-
// src/commands/
|
|
1223
|
-
var
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
if (!confirmed) {
|
|
1237
|
-
return { deleted: false };
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
await ctx.client.del("account_delete");
|
|
1241
|
-
return { deleted: true };
|
|
1242
|
-
},
|
|
1243
|
-
formatOutput(output, format) {
|
|
1244
|
-
if (format === "compact") return String(output.deleted);
|
|
1245
|
-
return JSON.stringify(output, null, 2);
|
|
1999
|
+
// src/commands/update.ts
|
|
2000
|
+
var NPM_REGISTRY_URL2 = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
2001
|
+
var GITHUB_REPO = "Elnora-AI/elnora-cli";
|
|
2002
|
+
function getPlatformTarget() {
|
|
2003
|
+
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
2004
|
+
switch (process.platform) {
|
|
2005
|
+
case "darwin":
|
|
2006
|
+
return { target: `elnora-macos-${arch}`, ext: "tar.gz" };
|
|
2007
|
+
case "linux":
|
|
2008
|
+
return { target: `elnora-linux-${arch}`, ext: "tar.gz" };
|
|
2009
|
+
case "win32":
|
|
2010
|
+
return { target: `elnora-win-${arch}.exe`, ext: "zip" };
|
|
2011
|
+
default:
|
|
2012
|
+
return null;
|
|
1246
2013
|
}
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
2014
|
+
}
|
|
2015
|
+
async function fetchLatestVersion() {
|
|
2016
|
+
const res = await fetch(NPM_REGISTRY_URL2, { signal: AbortSignal.timeout(5e3) });
|
|
2017
|
+
if (!res.ok) throw new Error(`Registry returned HTTP ${res.status}`);
|
|
2018
|
+
const data = await res.json();
|
|
2019
|
+
return data.version ?? "unknown";
|
|
2020
|
+
}
|
|
2021
|
+
async function downloadToFile(url, dest) {
|
|
2022
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(12e4), redirect: "follow" });
|
|
2023
|
+
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
|
2024
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
2025
|
+
await pipeline(nodeStream, createWriteStream(dest));
|
|
2026
|
+
}
|
|
2027
|
+
async function installBinaryUpdate(latest) {
|
|
2028
|
+
const color2 = isColorEnabled();
|
|
2029
|
+
const platformInfo = getPlatformTarget();
|
|
2030
|
+
if (!platformInfo) {
|
|
2031
|
+
console.error(`Unsupported platform: ${process.platform}.`);
|
|
2032
|
+
console.error("Try: npm install -g @elnora-ai/cli@latest");
|
|
2033
|
+
process.exitCode = 1;
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
const { target, ext } = platformInfo;
|
|
2037
|
+
const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
|
|
2038
|
+
const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
|
|
2039
|
+
const tmp = join7(tmpdir(), `elnora-update-${Date.now()}`);
|
|
2040
|
+
mkdirSync4(tmp, { recursive: true });
|
|
2041
|
+
const archivePath = join7(tmp, `${target}.${ext}`);
|
|
2042
|
+
try {
|
|
2043
|
+
console.error("Downloading...");
|
|
2044
|
+
await downloadToFile(binaryUrl, archivePath);
|
|
2045
|
+
let checksumVerified = false;
|
|
2046
|
+
try {
|
|
2047
|
+
const checksumRes = await fetch(checksumUrl, { signal: AbortSignal.timeout(15e3) });
|
|
2048
|
+
if (checksumRes.ok) {
|
|
2049
|
+
const checksumText = await checksumRes.text();
|
|
2050
|
+
const expected = checksumText.split(/\s/)[0].trim();
|
|
2051
|
+
if (expected.length === 64) {
|
|
2052
|
+
const actual = createHash("sha256").update(readFileSync5(archivePath)).digest("hex");
|
|
2053
|
+
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
2054
|
+
console.error("Checksum verification failed. The download may be corrupted.");
|
|
2055
|
+
process.exitCode = 1;
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
checksumVerified = true;
|
|
2059
|
+
}
|
|
1269
2060
|
}
|
|
2061
|
+
} catch {
|
|
1270
2062
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1298
|
-
},
|
|
1299
|
-
formatOutput(output, format) {
|
|
1300
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1301
|
-
return JSON.stringify(output, null, 2);
|
|
1302
|
-
}
|
|
1303
|
-
};
|
|
1304
|
-
|
|
1305
|
-
// src/commands/account/update.ts
|
|
1306
|
-
init_errors();
|
|
1307
|
-
import { z as z7 } from "zod";
|
|
1308
|
-
var inputSchema7 = z7.object({
|
|
1309
|
-
userId: z7.string().describe("User ID"),
|
|
1310
|
-
firstName: z7.string().optional().describe("First name"),
|
|
1311
|
-
lastName: z7.string().optional().describe("Last name")
|
|
1312
|
-
});
|
|
1313
|
-
var accountUpdate = {
|
|
1314
|
-
name: "account.update",
|
|
1315
|
-
group: "account",
|
|
1316
|
-
description: "Update account details for a user",
|
|
1317
|
-
inputSchema: inputSchema7,
|
|
1318
|
-
outputSchema: z7.any(),
|
|
1319
|
-
async execute(input, ctx) {
|
|
1320
|
-
const body = {};
|
|
1321
|
-
if (input.firstName !== void 0) body.firstName = input.firstName;
|
|
1322
|
-
if (input.lastName !== void 0) body.lastName = input.lastName;
|
|
1323
|
-
if (Object.keys(body).length === 0) {
|
|
1324
|
-
throw new ValidationError("At least one of --firstName or --lastName is required");
|
|
2063
|
+
if (!checksumVerified) {
|
|
2064
|
+
console.error("Warning: Could not verify download checksum. Proceeding anyway.");
|
|
2065
|
+
}
|
|
2066
|
+
if (process.platform === "win32") {
|
|
2067
|
+
const extractDir = join7(tmp, "extracted");
|
|
2068
|
+
execFileSync("powershell", [
|
|
2069
|
+
"-NoProfile",
|
|
2070
|
+
"-Command",
|
|
2071
|
+
`Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
|
|
2072
|
+
]);
|
|
2073
|
+
const installDir = join7(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
2074
|
+
mkdirSync4(installDir, { recursive: true });
|
|
2075
|
+
copyFileSync(join7(extractDir, target), join7(installDir, "elnora.exe"));
|
|
2076
|
+
} else {
|
|
2077
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
|
|
2078
|
+
const installDir = process.env.ELNORA_INSTALL_DIR ?? join7(process.env.HOME ?? "", ".local", "bin");
|
|
2079
|
+
mkdirSync4(installDir, { recursive: true });
|
|
2080
|
+
const dest = join7(installDir, "elnora");
|
|
2081
|
+
copyFileSync(join7(tmp, target), dest);
|
|
2082
|
+
chmodSync2(dest, 493);
|
|
2083
|
+
}
|
|
2084
|
+
const done = color2 ? `${pc4.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
|
|
2085
|
+
console.error(done);
|
|
2086
|
+
} finally {
|
|
2087
|
+
try {
|
|
2088
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
2089
|
+
} catch {
|
|
1325
2090
|
}
|
|
1326
|
-
return ctx.client.patch("account_user", body, {
|
|
1327
|
-
pathParams: { id: input.userId }
|
|
1328
|
-
});
|
|
1329
|
-
},
|
|
1330
|
-
formatOutput(output, format) {
|
|
1331
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1332
|
-
return JSON.stringify(output, null, 2);
|
|
1333
2091
|
}
|
|
1334
|
-
}
|
|
2092
|
+
}
|
|
2093
|
+
function addUpdateCommand(program2) {
|
|
2094
|
+
program2.command("update").description("Check for updates (use --install to apply)").option("--install", "Download and install the update").action(async (opts) => {
|
|
2095
|
+
const color2 = isColorEnabled();
|
|
2096
|
+
const method = detectInstallMethod();
|
|
2097
|
+
let latest;
|
|
2098
|
+
try {
|
|
2099
|
+
latest = await fetchLatestVersion();
|
|
2100
|
+
} catch {
|
|
2101
|
+
console.error("Failed to check for updates.");
|
|
2102
|
+
process.exitCode = 1;
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
if (latest === "unknown") {
|
|
2106
|
+
console.error("Could not determine latest version.");
|
|
2107
|
+
process.exitCode = 1;
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
if (latest === VERSION || !isNewerVersion(latest, VERSION)) {
|
|
2111
|
+
const msg2 = color2 ? `${pc4.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
|
|
2112
|
+
console.error(msg2);
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
const instruction = getUpdateInstruction(method);
|
|
2116
|
+
if (!opts.install) {
|
|
2117
|
+
const msg2 = color2 ? `${pc4.yellow("Update available:")} v${VERSION} \u2192 v${latest}` : `Update available: v${VERSION} \u2192 v${latest}`;
|
|
2118
|
+
console.error(msg2);
|
|
2119
|
+
console.error(`Run: ${instruction}`);
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
const msg = color2 ? `Updating Elnora CLI ${pc4.dim(`v${VERSION}`)} \u2192 ${pc4.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
|
|
2123
|
+
console.error(msg);
|
|
2124
|
+
if (method === "npm") {
|
|
2125
|
+
console.error("Installed via npm. Run:");
|
|
2126
|
+
console.error(` npm install -g @elnora-ai/cli@latest`);
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (method === "homebrew") {
|
|
2130
|
+
console.error("Installed via Homebrew. Run:");
|
|
2131
|
+
console.error(` brew upgrade elnora`);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
try {
|
|
2135
|
+
await installBinaryUpdate(latest);
|
|
2136
|
+
} catch {
|
|
2137
|
+
console.error("Update failed. Try manually:");
|
|
2138
|
+
console.error(
|
|
2139
|
+
process.platform === "win32" ? " irm https://cli.elnora.ai/install.ps1 | iex" : " curl -fsSL https://cli.elnora.ai/install.sh | bash"
|
|
2140
|
+
);
|
|
2141
|
+
process.exitCode = 1;
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
1335
2145
|
|
|
1336
|
-
// src/commands/
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
2146
|
+
// src/commands/whoami.ts
|
|
2147
|
+
init_client();
|
|
2148
|
+
init_profiles();
|
|
2149
|
+
function addWhoamiCommand(program2) {
|
|
2150
|
+
program2.command("whoami").description("Show current authentication context").action(async () => {
|
|
2151
|
+
const parentOpts = program2.opts();
|
|
2152
|
+
const profileName = parentOpts.profile ?? "default";
|
|
2153
|
+
try {
|
|
2154
|
+
const hint = resolveCredentialHint(profileName);
|
|
2155
|
+
let orgName = "unknown";
|
|
2156
|
+
try {
|
|
2157
|
+
const client = new ElnoraApiClient(resolveApiKey(profileName));
|
|
2158
|
+
const orgs = await client.get("organizations");
|
|
2159
|
+
const orgList = Array.isArray(orgs) ? orgs : orgs?.items ?? [];
|
|
2160
|
+
if (orgList.length > 0) orgName = orgList[0]?.name ?? "unknown";
|
|
2161
|
+
} catch {
|
|
2162
|
+
}
|
|
2163
|
+
if (parentOpts.json) {
|
|
2164
|
+
console.log(JSON.stringify({ profile: profileName, credential: hint, orgName }, null, 2));
|
|
2165
|
+
} else {
|
|
2166
|
+
console.log(`
|
|
2167
|
+
Profile: ${profileName}`);
|
|
2168
|
+
console.log(`Credential: ${hint}`);
|
|
2169
|
+
console.log(`Org: ${orgName}`);
|
|
2170
|
+
console.log("");
|
|
2171
|
+
}
|
|
2172
|
+
} catch (e) {
|
|
2173
|
+
console.error(e instanceof Error ? e.message : "Not authenticated");
|
|
2174
|
+
console.error("Run: elnora auth login");
|
|
2175
|
+
process.exit(3);
|
|
1356
2176
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
});
|
|
1360
|
-
},
|
|
1361
|
-
formatOutput(output, format) {
|
|
1362
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1363
|
-
return JSON.stringify(output, null, 2);
|
|
1364
|
-
}
|
|
1365
|
-
};
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
1366
2179
|
|
|
1367
|
-
// src/commands/account/
|
|
1368
|
-
import { z
|
|
1369
|
-
var
|
|
1370
|
-
|
|
1371
|
-
refCode: z9.string().optional().describe("Filter by referral code")
|
|
2180
|
+
// src/commands/account/accept-terms.ts
|
|
2181
|
+
import { z } from "zod";
|
|
2182
|
+
var inputSchema = z.object({
|
|
2183
|
+
documentVersionId: z.string().describe("Document version ID to accept")
|
|
1372
2184
|
});
|
|
1373
|
-
var
|
|
1374
|
-
name: "account.
|
|
2185
|
+
var accountAcceptTerms = {
|
|
2186
|
+
name: "account.acceptTerms",
|
|
1375
2187
|
group: "account",
|
|
1376
|
-
description: "
|
|
1377
|
-
inputSchema
|
|
1378
|
-
outputSchema:
|
|
1379
|
-
annotations: { readOnlyHint: true },
|
|
2188
|
+
description: "Accept a user agreement / terms of service",
|
|
2189
|
+
inputSchema,
|
|
2190
|
+
outputSchema: z.any(),
|
|
1380
2191
|
async execute(input, ctx) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
if (input.refCode) queryParams.refCode = input.refCode;
|
|
1384
|
-
return ctx.client.get("account_users", {
|
|
1385
|
-
queryParams: Object.keys(queryParams).length > 0 ? queryParams : void 0
|
|
2192
|
+
return ctx.client.post("user_agreement", {
|
|
2193
|
+
documentVersionId: input.documentVersionId
|
|
1386
2194
|
});
|
|
1387
2195
|
},
|
|
1388
2196
|
formatOutput(output, format) {
|
|
@@ -1391,118 +2199,28 @@ var accountUsers = {
|
|
|
1391
2199
|
}
|
|
1392
2200
|
};
|
|
1393
2201
|
|
|
1394
|
-
// src/commands/account/
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
accountDelete,
|
|
1402
|
-
accountUsers,
|
|
1403
|
-
accountAddLegalDoc,
|
|
1404
|
-
accountUpdateLegalDoc,
|
|
1405
|
-
accountDeleteLegalDoc
|
|
1406
|
-
];
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
// src/commands/api-keys/create.ts
|
|
1410
|
-
import { z as z10 } from "zod";
|
|
1411
|
-
var inputSchema10 = z10.object({
|
|
1412
|
-
name: z10.string().min(1).describe("API key name"),
|
|
1413
|
-
scopes: z10.string().optional().describe("Comma-separated list of scopes")
|
|
1414
|
-
});
|
|
1415
|
-
var apiKeysCreate = {
|
|
1416
|
-
name: "api-keys.create",
|
|
1417
|
-
group: "api-keys",
|
|
1418
|
-
description: "Create a new API key",
|
|
1419
|
-
inputSchema: inputSchema10,
|
|
1420
|
-
outputSchema: z10.any(),
|
|
1421
|
-
async execute(input, ctx) {
|
|
1422
|
-
const parsedScopes = input.scopes ? input.scopes.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
|
|
1423
|
-
return ctx.client.post("api_keys", { name: input.name, scopes: parsedScopes });
|
|
1424
|
-
},
|
|
1425
|
-
formatOutput(output, format) {
|
|
1426
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1427
|
-
return JSON.stringify(output, null, 2);
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
|
|
1431
|
-
// src/commands/api-keys/get-policy.ts
|
|
1432
|
-
import { z as z11 } from "zod";
|
|
1433
|
-
var inputSchema11 = z11.object({});
|
|
1434
|
-
var apiKeysGetPolicy = {
|
|
1435
|
-
name: "api-keys.getPolicy",
|
|
1436
|
-
group: "api-keys",
|
|
1437
|
-
description: "Get the API key creation policy",
|
|
1438
|
-
inputSchema: inputSchema11,
|
|
1439
|
-
outputSchema: z11.any(),
|
|
1440
|
-
annotations: { readOnlyHint: true },
|
|
1441
|
-
async execute(_input, ctx) {
|
|
1442
|
-
return ctx.client.get("api_key_policy");
|
|
1443
|
-
},
|
|
1444
|
-
formatOutput(output, format) {
|
|
1445
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1446
|
-
return JSON.stringify(output, null, 2);
|
|
1447
|
-
}
|
|
1448
|
-
};
|
|
1449
|
-
|
|
1450
|
-
// src/commands/api-keys/list.ts
|
|
1451
|
-
import { z as z12 } from "zod";
|
|
1452
|
-
var inputSchema12 = z12.object({});
|
|
1453
|
-
var apiKeysList = {
|
|
1454
|
-
name: "api-keys.list",
|
|
1455
|
-
group: "api-keys",
|
|
1456
|
-
description: "List all API keys",
|
|
1457
|
-
inputSchema: inputSchema12,
|
|
1458
|
-
outputSchema: z12.any(),
|
|
1459
|
-
annotations: { readOnlyHint: true },
|
|
1460
|
-
async execute(_input, ctx) {
|
|
1461
|
-
return ctx.client.get("api_keys");
|
|
1462
|
-
},
|
|
1463
|
-
formatOutput(output, format) {
|
|
1464
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1465
|
-
return JSON.stringify(output, null, 2);
|
|
1466
|
-
}
|
|
1467
|
-
};
|
|
1468
|
-
|
|
1469
|
-
// src/commands/api-keys/revoke.ts
|
|
1470
|
-
import { z as z13 } from "zod";
|
|
1471
|
-
var inputSchema13 = z13.object({
|
|
1472
|
-
keyId: z13.string().uuid().describe("API key ID to revoke")
|
|
1473
|
-
});
|
|
1474
|
-
var apiKeysRevoke = {
|
|
1475
|
-
name: "api-keys.revoke",
|
|
1476
|
-
group: "api-keys",
|
|
1477
|
-
description: "Revoke an API key",
|
|
1478
|
-
inputSchema: inputSchema13,
|
|
1479
|
-
outputSchema: z13.any(),
|
|
1480
|
-
annotations: { destructiveHint: true },
|
|
1481
|
-
async execute(input, ctx) {
|
|
1482
|
-
await ctx.client.del("api_key", {
|
|
1483
|
-
pathParams: { id: input.keyId }
|
|
1484
|
-
});
|
|
1485
|
-
return { revoked: true, keyId: input.keyId };
|
|
1486
|
-
},
|
|
1487
|
-
formatOutput(output, format) {
|
|
1488
|
-
if (format === "compact") return output.keyId;
|
|
1489
|
-
return JSON.stringify(output, null, 2);
|
|
1490
|
-
}
|
|
1491
|
-
};
|
|
1492
|
-
|
|
1493
|
-
// src/commands/api-keys/set-policy.ts
|
|
1494
|
-
import { z as z14 } from "zod";
|
|
1495
|
-
var inputSchema14 = z14.object({
|
|
1496
|
-
policy: z14.enum(["all_members", "admins_only"]).describe("API key creation policy")
|
|
2202
|
+
// src/commands/account/add-legal-doc.ts
|
|
2203
|
+
import { z as z2 } from "zod";
|
|
2204
|
+
var inputSchema2 = z2.object({
|
|
2205
|
+
documentType: z2.string().min(1).describe("Document type"),
|
|
2206
|
+
version: z2.string().min(1).describe("Document version string"),
|
|
2207
|
+
content: z2.string().min(1).describe("Document content"),
|
|
2208
|
+
effectiveDate: z2.string().optional().describe("Effective date (ISO 8601)")
|
|
1497
2209
|
});
|
|
1498
|
-
var
|
|
1499
|
-
name: "
|
|
1500
|
-
group: "
|
|
1501
|
-
description: "
|
|
1502
|
-
inputSchema:
|
|
1503
|
-
outputSchema:
|
|
2210
|
+
var accountAddLegalDoc = {
|
|
2211
|
+
name: "account.addLegalDoc",
|
|
2212
|
+
group: "account",
|
|
2213
|
+
description: "Add a legal document version (SystemAdmin only)",
|
|
2214
|
+
inputSchema: inputSchema2,
|
|
2215
|
+
outputSchema: z2.any(),
|
|
1504
2216
|
async execute(input, ctx) {
|
|
1505
|
-
|
|
2217
|
+
const body = {
|
|
2218
|
+
documentType: input.documentType,
|
|
2219
|
+
version: input.version,
|
|
2220
|
+
content: input.content
|
|
2221
|
+
};
|
|
2222
|
+
if (input.effectiveDate) body.effectiveDate = input.effectiveDate;
|
|
2223
|
+
return ctx.client.post("legal_doc_version", body);
|
|
1506
2224
|
},
|
|
1507
2225
|
formatOutput(output, format) {
|
|
1508
2226
|
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
@@ -1510,46 +2228,18 @@ var apiKeysSetPolicy = {
|
|
|
1510
2228
|
}
|
|
1511
2229
|
};
|
|
1512
2230
|
|
|
1513
|
-
// src/commands/
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
import { z as z15 } from "zod";
|
|
1523
|
-
var paginationInput = {
|
|
1524
|
-
page: z15.number().int().min(1).default(1).describe("Page number"),
|
|
1525
|
-
pageSize: z15.number().int().min(1).max(100).default(25).describe("Results per page")
|
|
1526
|
-
};
|
|
1527
|
-
|
|
1528
|
-
// src/commands/audit/list.ts
|
|
1529
|
-
var inputSchema15 = z16.object({
|
|
1530
|
-
orgId: z16.string().uuid().describe("Organization ID"),
|
|
1531
|
-
...paginationInput,
|
|
1532
|
-
action: z16.string().optional().describe("Filter by action type"),
|
|
1533
|
-
userId: z16.string().optional().describe("Filter by user ID")
|
|
1534
|
-
});
|
|
1535
|
-
var auditList = {
|
|
1536
|
-
name: "audit.list",
|
|
1537
|
-
group: "audit",
|
|
1538
|
-
description: "List audit log entries for an organization",
|
|
1539
|
-
inputSchema: inputSchema15,
|
|
1540
|
-
outputSchema: z16.any(),
|
|
2231
|
+
// src/commands/account/agreements.ts
|
|
2232
|
+
import { z as z3 } from "zod";
|
|
2233
|
+
var inputSchema3 = z3.object({});
|
|
2234
|
+
var accountAgreements = {
|
|
2235
|
+
name: "account.agreements",
|
|
2236
|
+
group: "account",
|
|
2237
|
+
description: "List user agreements",
|
|
2238
|
+
inputSchema: inputSchema3,
|
|
2239
|
+
outputSchema: z3.any(),
|
|
1541
2240
|
annotations: { readOnlyHint: true },
|
|
1542
|
-
async execute(
|
|
1543
|
-
|
|
1544
|
-
page: input.page,
|
|
1545
|
-
pageSize: input.pageSize
|
|
1546
|
-
};
|
|
1547
|
-
if (input.action) queryParams.action = input.action;
|
|
1548
|
-
if (input.userId) queryParams.userId = input.userId;
|
|
1549
|
-
return ctx.client.get("audit_log", {
|
|
1550
|
-
pathParams: { orgId: input.orgId },
|
|
1551
|
-
queryParams
|
|
1552
|
-
});
|
|
2241
|
+
async execute(_input, ctx) {
|
|
2242
|
+
return ctx.client.get("user_agreements");
|
|
1553
2243
|
},
|
|
1554
2244
|
formatOutput(output, format) {
|
|
1555
2245
|
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
@@ -1557,645 +2247,671 @@ var auditList = {
|
|
|
1557
2247
|
}
|
|
1558
2248
|
};
|
|
1559
2249
|
|
|
1560
|
-
// src/commands/
|
|
1561
|
-
|
|
1562
|
-
return [auditList];
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
// src/commands/auth/login.ts
|
|
1566
|
-
init_client();
|
|
1567
|
-
init_errors();
|
|
1568
|
-
init_profiles();
|
|
1569
|
-
import { z as z17 } from "zod";
|
|
1570
|
-
|
|
1571
|
-
// src/lib/prompt.ts
|
|
1572
|
-
import { createInterface as createInterface2 } from "node:readline";
|
|
1573
|
-
function promptSecret(message) {
|
|
1574
|
-
return new Promise((resolve, reject) => {
|
|
1575
|
-
const { stdin, stdout } = process;
|
|
1576
|
-
if (!stdin.isTTY) {
|
|
1577
|
-
const rl = createInterface2({ input: stdin, output: stdout });
|
|
1578
|
-
rl.question(message, (answer) => {
|
|
1579
|
-
rl.close();
|
|
1580
|
-
resolve(answer.trim());
|
|
1581
|
-
});
|
|
1582
|
-
return;
|
|
1583
|
-
}
|
|
1584
|
-
stdout.write(message);
|
|
1585
|
-
let input = "";
|
|
1586
|
-
const wasRaw = stdin.isRaw;
|
|
1587
|
-
stdin.setRawMode(true);
|
|
1588
|
-
stdin.resume();
|
|
1589
|
-
stdin.setEncoding("utf8");
|
|
1590
|
-
const onData = (char) => {
|
|
1591
|
-
switch (char) {
|
|
1592
|
-
case "\n":
|
|
1593
|
-
case "\r":
|
|
1594
|
-
case "": {
|
|
1595
|
-
stdin.setRawMode(wasRaw);
|
|
1596
|
-
stdin.pause();
|
|
1597
|
-
stdin.removeListener("data", onData);
|
|
1598
|
-
stdout.write("\n");
|
|
1599
|
-
resolve(input.trim());
|
|
1600
|
-
break;
|
|
1601
|
-
}
|
|
1602
|
-
case "":
|
|
1603
|
-
// Ctrl+C
|
|
1604
|
-
case "\x1B": {
|
|
1605
|
-
stdin.setRawMode(wasRaw);
|
|
1606
|
-
stdin.pause();
|
|
1607
|
-
stdin.removeListener("data", onData);
|
|
1608
|
-
stdout.write("\n");
|
|
1609
|
-
reject(new Error("Cancelled"));
|
|
1610
|
-
break;
|
|
1611
|
-
}
|
|
1612
|
-
case "\x7F":
|
|
1613
|
-
case "\b": {
|
|
1614
|
-
if (input.length > 0) {
|
|
1615
|
-
input = input.slice(0, -1);
|
|
1616
|
-
stdout.write("\b \b");
|
|
1617
|
-
}
|
|
1618
|
-
break;
|
|
1619
|
-
}
|
|
1620
|
-
default: {
|
|
1621
|
-
input += char;
|
|
1622
|
-
stdout.write("*");
|
|
1623
|
-
break;
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
};
|
|
1627
|
-
stdin.on("data", onData);
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
// src/lib/tty.ts
|
|
1632
|
-
function isTTY() {
|
|
1633
|
-
return Boolean(process.stdout.isTTY);
|
|
1634
|
-
}
|
|
1635
|
-
function isColorEnabled() {
|
|
1636
|
-
if (process.env.NO_COLOR !== void 0) return false;
|
|
1637
|
-
if (process.env.FORCE_COLOR !== void 0) return true;
|
|
1638
|
-
return isTTY();
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
// src/commands/auth/login.ts
|
|
1642
|
-
var inputSchema16 = z17.object({
|
|
1643
|
-
apiKey: z17.string().optional().describe("Elnora API key (elnora_live_...)"),
|
|
1644
|
-
profile: z17.string().default("default").describe("Profile name to save key under")
|
|
1645
|
-
});
|
|
1646
|
-
var authLogin = {
|
|
1647
|
-
name: "auth.login",
|
|
1648
|
-
group: "auth",
|
|
1649
|
-
description: "Set up authentication by saving an API key to a profile",
|
|
1650
|
-
inputSchema: inputSchema16,
|
|
1651
|
-
outputSchema: z17.any(),
|
|
1652
|
-
async execute(input, _ctx) {
|
|
1653
|
-
let apiKey = input.apiKey;
|
|
1654
|
-
const profileName = input.profile;
|
|
1655
|
-
validateProfileName(profileName);
|
|
1656
|
-
const existing = listProfileNames();
|
|
1657
|
-
const profileExists = existing.includes(profileName);
|
|
1658
|
-
if (!apiKey) {
|
|
1659
|
-
if (profileExists) {
|
|
1660
|
-
let savedKey;
|
|
1661
|
-
try {
|
|
1662
|
-
savedKey = getApiKey(profileName);
|
|
1663
|
-
} catch {
|
|
1664
|
-
}
|
|
1665
|
-
if (savedKey && isTTY()) {
|
|
1666
|
-
process.stderr.write(`
|
|
1667
|
-
Checking profile "${profileName}"...`);
|
|
1668
|
-
try {
|
|
1669
|
-
const client = new ElnoraApiClient(savedKey);
|
|
1670
|
-
const result = await client.get("projects", {
|
|
1671
|
-
queryParams: { page: 1, pageSize: 1 }
|
|
1672
|
-
});
|
|
1673
|
-
const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
|
|
1674
|
-
process.stderr.write(` authenticated (${projectCount} project${projectCount !== 1 ? "s" : ""}).
|
|
1675
|
-
|
|
1676
|
-
`);
|
|
1677
|
-
process.stderr.write(" To update your API key, run:\n");
|
|
1678
|
-
process.stderr.write(
|
|
1679
|
-
` elnora auth login --api-key <new-key>${profileName !== "default" ? ` --profile ${profileName}` : ""}
|
|
2250
|
+
// src/commands/account/delete.ts
|
|
2251
|
+
import { z as z4 } from "zod";
|
|
1680
2252
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
process.stderr.write("\n Set up Elnora CLI authentication\n");
|
|
1695
|
-
}
|
|
1696
|
-
process.stderr.write(" Get your API key at: https://platform.elnora.ai > Settings > API Keys\n\n");
|
|
1697
|
-
try {
|
|
1698
|
-
apiKey = await promptSecret(" API key (Ctrl+C to cancel): ");
|
|
1699
|
-
} catch {
|
|
1700
|
-
process.stderr.write("\n Login cancelled.\n\n");
|
|
1701
|
-
process.exit(0);
|
|
1702
|
-
}
|
|
1703
|
-
process.stderr.write("\n");
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
if (!apiKey) {
|
|
1707
|
-
throw new ValidationError(
|
|
1708
|
-
"API key is required. Use --api-key flag or run interactively in a terminal.",
|
|
1709
|
-
"Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
const key = apiKey.trim();
|
|
1713
|
-
if (!key.startsWith("elnora_live_")) {
|
|
1714
|
-
throw new AuthError("API key must start with 'elnora_live_'. Check that you copied the full key.", {
|
|
1715
|
-
suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1716
|
-
});
|
|
1717
|
-
}
|
|
1718
|
-
if (key.length < 20) {
|
|
1719
|
-
throw new AuthError("API key looks too short. Make sure you copied the full key.", {
|
|
1720
|
-
suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1721
|
-
});
|
|
1722
|
-
}
|
|
1723
|
-
if (isTTY()) {
|
|
1724
|
-
process.stderr.write(" Verifying...");
|
|
1725
|
-
}
|
|
1726
|
-
try {
|
|
1727
|
-
const tempClient = new ElnoraApiClient(key);
|
|
1728
|
-
const result = await tempClient.get("projects", {
|
|
1729
|
-
queryParams: { page: 1, pageSize: 1 }
|
|
1730
|
-
});
|
|
1731
|
-
const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
|
|
1732
|
-
const configPath = saveProfile(profileName, key);
|
|
1733
|
-
if (isTTY()) {
|
|
1734
|
-
process.stderr.write(` Done! ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.
|
|
1735
|
-
`);
|
|
1736
|
-
process.stderr.write(` Saved to profile "${profileName}" at ${configPath}
|
|
2253
|
+
// src/commands/_shared/confirm.ts
|
|
2254
|
+
import { createInterface } from "node:readline/promises";
|
|
2255
|
+
async function confirmDestructive(prompt, requiredInput) {
|
|
2256
|
+
if (!process.stdin.isTTY) return false;
|
|
2257
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
2258
|
+
try {
|
|
2259
|
+
const answer = await rl.question(prompt);
|
|
2260
|
+
if (requiredInput) return answer.trim() === requiredInput;
|
|
2261
|
+
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
|
|
2262
|
+
} finally {
|
|
2263
|
+
rl.close();
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
1737
2266
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2267
|
+
// src/commands/account/delete.ts
|
|
2268
|
+
var inputSchema4 = z4.object({
|
|
2269
|
+
yes: z4.boolean().optional().default(false).describe("Skip confirmation prompt")
|
|
2270
|
+
});
|
|
2271
|
+
var accountDelete = {
|
|
2272
|
+
name: "account.delete",
|
|
2273
|
+
group: "account",
|
|
2274
|
+
description: "Delete your own account",
|
|
2275
|
+
inputSchema: inputSchema4,
|
|
2276
|
+
outputSchema: z4.any(),
|
|
2277
|
+
annotations: { destructiveHint: true },
|
|
2278
|
+
async execute(input, ctx) {
|
|
2279
|
+
if (!input.yes) {
|
|
2280
|
+
const confirmed = await confirmDestructive("Type DELETE to confirm account deletion: ", "DELETE");
|
|
2281
|
+
if (!confirmed) {
|
|
2282
|
+
return { deleted: false };
|
|
1749
2283
|
}
|
|
1750
|
-
throw err;
|
|
1751
2284
|
}
|
|
2285
|
+
await ctx.client.del("account_delete");
|
|
2286
|
+
return { deleted: true };
|
|
1752
2287
|
},
|
|
1753
2288
|
formatOutput(output, format) {
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
const lines = [
|
|
1757
|
-
`\u2713 Authenticated! API key saved to profile "${data.profile}".`,
|
|
1758
|
-
"",
|
|
1759
|
-
"Next steps:",
|
|
1760
|
-
" elnora projects list See your projects",
|
|
1761
|
-
" elnora tasks create --project <ID> --stream Start a conversation",
|
|
1762
|
-
" elnora doctor Verify your setup"
|
|
1763
|
-
];
|
|
1764
|
-
return lines.join("\n");
|
|
2289
|
+
if (format === "compact") return String(output.deleted);
|
|
2290
|
+
return JSON.stringify(output, null, 2);
|
|
1765
2291
|
}
|
|
1766
2292
|
};
|
|
1767
2293
|
|
|
1768
|
-
// src/commands/
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
all: z18.boolean().default(false).describe("Remove all profiles")
|
|
2294
|
+
// src/commands/account/delete-legal-doc.ts
|
|
2295
|
+
import { z as z5 } from "zod";
|
|
2296
|
+
var inputSchema5 = z5.object({
|
|
2297
|
+
versionId: z5.string().describe("Legal document version ID"),
|
|
2298
|
+
yes: z5.boolean().optional().default(false).describe("Skip confirmation prompt")
|
|
1774
2299
|
});
|
|
1775
|
-
var
|
|
1776
|
-
name: "
|
|
1777
|
-
group: "
|
|
1778
|
-
description: "
|
|
1779
|
-
inputSchema:
|
|
1780
|
-
outputSchema:
|
|
2300
|
+
var accountDeleteLegalDoc = {
|
|
2301
|
+
name: "account.deleteLegalDoc",
|
|
2302
|
+
group: "account",
|
|
2303
|
+
description: "Delete a legal document version (SystemAdmin only)",
|
|
2304
|
+
inputSchema: inputSchema5,
|
|
2305
|
+
outputSchema: z5.any(),
|
|
1781
2306
|
annotations: { destructiveHint: true },
|
|
1782
|
-
async execute(input,
|
|
1783
|
-
if (input.
|
|
1784
|
-
const
|
|
1785
|
-
|
|
1786
|
-
|
|
2307
|
+
async execute(input, ctx) {
|
|
2308
|
+
if (!input.yes) {
|
|
2309
|
+
const confirmed = await confirmDestructive(
|
|
2310
|
+
`Are you sure you want to delete legal document version ${input.versionId}? [y/N] `
|
|
2311
|
+
);
|
|
2312
|
+
if (!confirmed) {
|
|
2313
|
+
return { deleted: false, versionId: input.versionId };
|
|
1787
2314
|
}
|
|
1788
|
-
return { loggedOut: true, profile: "all", removedCount: names.length };
|
|
1789
2315
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2316
|
+
await ctx.client.del("legal_doc_version_id", {
|
|
2317
|
+
pathParams: { id: input.versionId }
|
|
2318
|
+
});
|
|
2319
|
+
return { deleted: true, versionId: input.versionId };
|
|
2320
|
+
},
|
|
2321
|
+
formatOutput(output, format) {
|
|
2322
|
+
if (format === "compact") return output.versionId;
|
|
2323
|
+
return JSON.stringify(output, null, 2);
|
|
2324
|
+
}
|
|
2325
|
+
};
|
|
2326
|
+
|
|
2327
|
+
// src/commands/account/get.ts
|
|
2328
|
+
import { z as z6 } from "zod";
|
|
2329
|
+
var inputSchema6 = z6.object({
|
|
2330
|
+
userId: z6.string().describe("User ID")
|
|
2331
|
+
});
|
|
2332
|
+
var accountGet = {
|
|
2333
|
+
name: "account.get",
|
|
2334
|
+
group: "account",
|
|
2335
|
+
description: "Get account details for a user",
|
|
2336
|
+
inputSchema: inputSchema6,
|
|
2337
|
+
outputSchema: z6.any(),
|
|
2338
|
+
annotations: { readOnlyHint: true },
|
|
2339
|
+
async execute(input, ctx) {
|
|
2340
|
+
return ctx.client.get("account_user", {
|
|
2341
|
+
pathParams: { id: input.userId }
|
|
2342
|
+
});
|
|
2343
|
+
},
|
|
2344
|
+
formatOutput(output, format) {
|
|
2345
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2346
|
+
return JSON.stringify(output, null, 2);
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
|
|
2350
|
+
// src/commands/account/update.ts
|
|
2351
|
+
init_errors();
|
|
2352
|
+
import { z as z7 } from "zod";
|
|
2353
|
+
var inputSchema7 = z7.object({
|
|
2354
|
+
userId: z7.string().describe("User ID"),
|
|
2355
|
+
firstName: z7.string().optional().describe("First name"),
|
|
2356
|
+
lastName: z7.string().optional().describe("Last name")
|
|
2357
|
+
});
|
|
2358
|
+
var accountUpdate = {
|
|
2359
|
+
name: "account.update",
|
|
2360
|
+
group: "account",
|
|
2361
|
+
description: "Update account details for a user",
|
|
2362
|
+
inputSchema: inputSchema7,
|
|
2363
|
+
outputSchema: z7.any(),
|
|
2364
|
+
async execute(input, ctx) {
|
|
2365
|
+
const body = {};
|
|
2366
|
+
if (input.firstName !== void 0) body.firstName = input.firstName;
|
|
2367
|
+
if (input.lastName !== void 0) body.lastName = input.lastName;
|
|
2368
|
+
if (Object.keys(body).length === 0) {
|
|
2369
|
+
throw new ValidationError("At least one of --firstName or --lastName is required");
|
|
2370
|
+
}
|
|
2371
|
+
return ctx.client.patch("account_user", body, {
|
|
2372
|
+
pathParams: { id: input.userId }
|
|
2373
|
+
});
|
|
2374
|
+
},
|
|
2375
|
+
formatOutput(output, format) {
|
|
2376
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2377
|
+
return JSON.stringify(output, null, 2);
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
|
|
2381
|
+
// src/commands/account/update-legal-doc.ts
|
|
2382
|
+
init_errors();
|
|
2383
|
+
import { z as z8 } from "zod";
|
|
2384
|
+
var inputSchema8 = z8.object({
|
|
2385
|
+
versionId: z8.string().describe("Legal document version ID"),
|
|
2386
|
+
content: z8.string().optional().describe("Updated content"),
|
|
2387
|
+
effectiveDate: z8.string().optional().describe("Updated effective date (ISO 8601)")
|
|
2388
|
+
});
|
|
2389
|
+
var accountUpdateLegalDoc = {
|
|
2390
|
+
name: "account.updateLegalDoc",
|
|
2391
|
+
group: "account",
|
|
2392
|
+
description: "Update a legal document version (SystemAdmin only)",
|
|
2393
|
+
inputSchema: inputSchema8,
|
|
2394
|
+
outputSchema: z8.any(),
|
|
2395
|
+
async execute(input, ctx) {
|
|
2396
|
+
const body = {};
|
|
2397
|
+
if (input.content !== void 0) body.content = input.content;
|
|
2398
|
+
if (input.effectiveDate !== void 0) body.effectiveDate = input.effectiveDate;
|
|
2399
|
+
if (Object.keys(body).length === 0) {
|
|
2400
|
+
throw new ValidationError("At least one of --content or --effectiveDate is required");
|
|
2401
|
+
}
|
|
2402
|
+
return ctx.client.patch("legal_doc_version_id", body, {
|
|
2403
|
+
pathParams: { id: input.versionId }
|
|
2404
|
+
});
|
|
2405
|
+
},
|
|
2406
|
+
formatOutput(output, format) {
|
|
2407
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2408
|
+
return JSON.stringify(output, null, 2);
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
|
|
2412
|
+
// src/commands/account/users.ts
|
|
2413
|
+
import { z as z9 } from "zod";
|
|
2414
|
+
var inputSchema9 = z9.object({
|
|
2415
|
+
state: z9.enum(["Active", "Pending", "Deleted"]).optional().describe("Filter by user state"),
|
|
2416
|
+
refCode: z9.string().optional().describe("Filter by referral code")
|
|
2417
|
+
});
|
|
2418
|
+
var accountUsers = {
|
|
2419
|
+
name: "account.users",
|
|
2420
|
+
group: "account",
|
|
2421
|
+
description: "List all users (admin)",
|
|
2422
|
+
inputSchema: inputSchema9,
|
|
2423
|
+
outputSchema: z9.any(),
|
|
2424
|
+
annotations: { readOnlyHint: true },
|
|
2425
|
+
async execute(input, ctx) {
|
|
2426
|
+
const queryParams = {};
|
|
2427
|
+
if (input.state) queryParams.state = input.state;
|
|
2428
|
+
if (input.refCode) queryParams.refCode = input.refCode;
|
|
2429
|
+
return ctx.client.get("account_users", {
|
|
2430
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : void 0
|
|
2431
|
+
});
|
|
2432
|
+
},
|
|
2433
|
+
formatOutput(output, format) {
|
|
2434
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2435
|
+
return JSON.stringify(output, null, 2);
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
|
|
2439
|
+
// src/commands/account/index.ts
|
|
2440
|
+
function registerAccountCommands() {
|
|
2441
|
+
return [
|
|
2442
|
+
accountGet,
|
|
2443
|
+
accountUpdate,
|
|
2444
|
+
accountAgreements,
|
|
2445
|
+
accountAcceptTerms,
|
|
2446
|
+
accountDelete,
|
|
2447
|
+
accountUsers,
|
|
2448
|
+
accountAddLegalDoc,
|
|
2449
|
+
accountUpdateLegalDoc,
|
|
2450
|
+
accountDeleteLegalDoc
|
|
2451
|
+
];
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
// src/commands/api-keys/create.ts
|
|
2455
|
+
import { z as z10 } from "zod";
|
|
2456
|
+
var inputSchema10 = z10.object({
|
|
2457
|
+
name: z10.string().min(1).describe("API key name"),
|
|
2458
|
+
scopes: z10.string().optional().describe("Comma-separated list of scopes")
|
|
2459
|
+
});
|
|
2460
|
+
var keysCreate = {
|
|
2461
|
+
name: "api-keys.create",
|
|
2462
|
+
group: "api-keys",
|
|
2463
|
+
description: "Create a new API key",
|
|
2464
|
+
inputSchema: inputSchema10,
|
|
2465
|
+
outputSchema: z10.any(),
|
|
2466
|
+
async execute(input, ctx) {
|
|
2467
|
+
const parsedScopes = input.scopes ? input.scopes.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
|
|
2468
|
+
return ctx.client.post("api_keys", { name: input.name, scopes: parsedScopes });
|
|
2469
|
+
},
|
|
2470
|
+
formatOutput(output, format) {
|
|
2471
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2472
|
+
return JSON.stringify(output, null, 2);
|
|
2473
|
+
}
|
|
2474
|
+
};
|
|
2475
|
+
|
|
2476
|
+
// src/commands/api-keys/get-policy.ts
|
|
2477
|
+
import { z as z11 } from "zod";
|
|
2478
|
+
var inputSchema11 = z11.object({});
|
|
2479
|
+
var keysGetPolicy = {
|
|
2480
|
+
name: "api-keys.getPolicy",
|
|
2481
|
+
group: "api-keys",
|
|
2482
|
+
description: "Get the API key creation policy",
|
|
2483
|
+
inputSchema: inputSchema11,
|
|
2484
|
+
outputSchema: z11.any(),
|
|
2485
|
+
annotations: { readOnlyHint: true },
|
|
2486
|
+
async execute(_input, ctx) {
|
|
2487
|
+
return ctx.client.get("api_key_policy");
|
|
1793
2488
|
},
|
|
1794
2489
|
formatOutput(output, format) {
|
|
1795
|
-
if (format === "compact") return output.
|
|
2490
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1796
2491
|
return JSON.stringify(output, null, 2);
|
|
1797
2492
|
}
|
|
1798
2493
|
};
|
|
1799
2494
|
|
|
1800
|
-
// src/commands/
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
var
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
outputSchema: z19.any(),
|
|
2495
|
+
// src/commands/api-keys/list.ts
|
|
2496
|
+
import { z as z12 } from "zod";
|
|
2497
|
+
var inputSchema12 = z12.object({});
|
|
2498
|
+
var keysList = {
|
|
2499
|
+
name: "api-keys.list",
|
|
2500
|
+
group: "api-keys",
|
|
2501
|
+
description: "List all API keys",
|
|
2502
|
+
inputSchema: inputSchema12,
|
|
2503
|
+
outputSchema: z12.any(),
|
|
1810
2504
|
annotations: { readOnlyHint: true },
|
|
1811
|
-
async execute(_input,
|
|
1812
|
-
|
|
1813
|
-
const items = [];
|
|
1814
|
-
for (const [name, data] of Object.entries(profiles)) {
|
|
1815
|
-
const key = data.api_key ?? "";
|
|
1816
|
-
const masked = key.length > 16 ? `${key.slice(0, 12)}...${key.slice(-4)}` : "[invalid]";
|
|
1817
|
-
items.push({ name, apiKey: masked });
|
|
1818
|
-
}
|
|
1819
|
-
return { profiles: items };
|
|
2505
|
+
async execute(_input, ctx) {
|
|
2506
|
+
return ctx.client.get("api_keys");
|
|
1820
2507
|
},
|
|
1821
2508
|
formatOutput(output, format) {
|
|
1822
|
-
if (format === "compact")
|
|
1823
|
-
const profiles = output.profiles ?? [];
|
|
1824
|
-
return profiles.map((p) => p.name).join(", ");
|
|
1825
|
-
}
|
|
2509
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1826
2510
|
return JSON.stringify(output, null, 2);
|
|
1827
2511
|
}
|
|
1828
2512
|
};
|
|
1829
2513
|
|
|
1830
|
-
// src/commands/
|
|
1831
|
-
import { z as
|
|
1832
|
-
var
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2514
|
+
// src/commands/api-keys/revoke.ts
|
|
2515
|
+
import { z as z13 } from "zod";
|
|
2516
|
+
var inputSchema13 = z13.object({
|
|
2517
|
+
keyId: z13.string().uuid().describe("API key ID to revoke")
|
|
2518
|
+
});
|
|
2519
|
+
var keysRevoke = {
|
|
2520
|
+
name: "api-keys.revoke",
|
|
2521
|
+
group: "api-keys",
|
|
2522
|
+
description: "Revoke an API key",
|
|
2523
|
+
inputSchema: inputSchema13,
|
|
2524
|
+
outputSchema: z13.any(),
|
|
2525
|
+
annotations: { destructiveHint: true },
|
|
2526
|
+
async execute(input, ctx) {
|
|
2527
|
+
await ctx.client.del("api_key", {
|
|
2528
|
+
pathParams: { id: input.keyId }
|
|
1843
2529
|
});
|
|
1844
|
-
|
|
1845
|
-
return { profile: ctx.profileName, authenticated: true, projectCount };
|
|
2530
|
+
return { revoked: true, keyId: input.keyId };
|
|
1846
2531
|
},
|
|
1847
2532
|
formatOutput(output, format) {
|
|
1848
|
-
if (format === "compact") return
|
|
2533
|
+
if (format === "compact") return output.keyId;
|
|
1849
2534
|
return JSON.stringify(output, null, 2);
|
|
1850
2535
|
}
|
|
1851
2536
|
};
|
|
1852
2537
|
|
|
1853
|
-
// src/commands/
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
token: z21.string().optional().describe("Token to validate (defaults to current API key)")
|
|
2538
|
+
// src/commands/api-keys/set-policy.ts
|
|
2539
|
+
import { z as z14 } from "zod";
|
|
2540
|
+
var inputSchema14 = z14.object({
|
|
2541
|
+
policy: z14.enum(["all_members", "admins_only"]).describe("API key creation policy")
|
|
1858
2542
|
});
|
|
1859
|
-
var
|
|
1860
|
-
name: "
|
|
1861
|
-
group: "
|
|
1862
|
-
description: "
|
|
1863
|
-
inputSchema:
|
|
1864
|
-
outputSchema:
|
|
2543
|
+
var keysSetPolicy = {
|
|
2544
|
+
name: "api-keys.setPolicy",
|
|
2545
|
+
group: "api-keys",
|
|
2546
|
+
description: "Set the API key creation policy",
|
|
2547
|
+
inputSchema: inputSchema14,
|
|
2548
|
+
outputSchema: z14.any(),
|
|
1865
2549
|
async execute(input, ctx) {
|
|
1866
|
-
|
|
1867
|
-
return ctx.client.post("auth_validate", { token: tokenToValidate });
|
|
2550
|
+
return ctx.client.put("api_key_policy", { policy: input.policy });
|
|
1868
2551
|
},
|
|
1869
2552
|
formatOutput(output, format) {
|
|
1870
|
-
if (format === "compact") return JSON.stringify(output);
|
|
2553
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1871
2554
|
return JSON.stringify(output, null, 2);
|
|
1872
2555
|
}
|
|
1873
2556
|
};
|
|
1874
2557
|
|
|
1875
|
-
// src/commands/
|
|
1876
|
-
function
|
|
1877
|
-
return [
|
|
2558
|
+
// src/commands/api-keys/index.ts
|
|
2559
|
+
function registerApiKeyCommands() {
|
|
2560
|
+
return [keysCreate, keysList, keysRevoke, keysGetPolicy, keysSetPolicy];
|
|
1878
2561
|
}
|
|
1879
2562
|
|
|
1880
|
-
// src/commands/
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
if (
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
function
|
|
1924
|
-
|
|
1925
|
-
"#compdef elnora",
|
|
1926
|
-
"# Zsh completion for elnora",
|
|
1927
|
-
"# Install: elnora completion zsh > ~/.zfunc/_elnora",
|
|
1928
|
-
"",
|
|
1929
|
-
"_elnora() {",
|
|
1930
|
-
" local -a commands",
|
|
1931
|
-
""
|
|
1932
|
-
];
|
|
1933
|
-
lines.push(" if (( CURRENT == 2 )); then");
|
|
1934
|
-
lines.push(" commands=(");
|
|
1935
|
-
for (const cmd of root.subcommands) {
|
|
1936
|
-
lines.push(` '${cmd.name}:${cmd.description.replace(/\\/g, "\\\\").replace(/'/g, "")}'`);
|
|
1937
|
-
}
|
|
1938
|
-
lines.push(" )");
|
|
1939
|
-
lines.push(" _describe 'command' commands");
|
|
1940
|
-
lines.push(" return");
|
|
1941
|
-
lines.push(" fi");
|
|
1942
|
-
lines.push("");
|
|
1943
|
-
lines.push(" case $words[2] in");
|
|
1944
|
-
for (const group of root.subcommands) {
|
|
1945
|
-
if (group.subcommands.length > 0) {
|
|
1946
|
-
lines.push(` ${group.name})`);
|
|
1947
|
-
lines.push(" local -a subcommands");
|
|
1948
|
-
lines.push(" subcommands=(");
|
|
1949
|
-
for (const sub of group.subcommands) {
|
|
1950
|
-
lines.push(` '${sub.name}:${sub.description.replace(/\\/g, "\\\\").replace(/'/g, "")}'`);
|
|
1951
|
-
}
|
|
1952
|
-
lines.push(" )");
|
|
1953
|
-
lines.push(" _describe 'subcommand' subcommands");
|
|
1954
|
-
lines.push(" ;;");
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
lines.push(" esac");
|
|
1958
|
-
lines.push("}");
|
|
1959
|
-
lines.push("");
|
|
1960
|
-
lines.push("_elnora");
|
|
1961
|
-
return lines.join("\n");
|
|
1962
|
-
}
|
|
1963
|
-
function generateFish(root) {
|
|
1964
|
-
const lines = [
|
|
1965
|
-
"# Fish completion for elnora",
|
|
1966
|
-
"# Install: elnora completion fish > ~/.config/fish/completions/elnora.fish",
|
|
1967
|
-
""
|
|
1968
|
-
];
|
|
1969
|
-
for (const cmd of root.subcommands) {
|
|
1970
|
-
lines.push(
|
|
1971
|
-
`complete -c elnora -n "__fish_use_subcommand" -a "${cmd.name}" -d "${cmd.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
1972
|
-
);
|
|
1973
|
-
}
|
|
1974
|
-
lines.push("");
|
|
1975
|
-
for (const group of root.subcommands) {
|
|
1976
|
-
for (const sub of group.subcommands) {
|
|
1977
|
-
lines.push(
|
|
1978
|
-
`complete -c elnora -n "__fish_seen_subcommand_from ${group.name}" -a "${sub.name}" -d "${sub.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
1979
|
-
);
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
return lines.join("\n");
|
|
1983
|
-
}
|
|
1984
|
-
function addCompletionCommand(program2) {
|
|
1985
|
-
program2.command("completion").argument("<shell>", "Shell type: bash, zsh, fish").description("Generate shell completion script").action((shell) => {
|
|
1986
|
-
const tree = extractCommandTree(program2);
|
|
1987
|
-
switch (shell) {
|
|
1988
|
-
case "bash":
|
|
1989
|
-
console.log(generateBash(tree));
|
|
1990
|
-
break;
|
|
1991
|
-
case "zsh":
|
|
1992
|
-
console.log(generateZsh(tree));
|
|
1993
|
-
break;
|
|
1994
|
-
case "fish":
|
|
1995
|
-
console.log(generateFish(tree));
|
|
1996
|
-
break;
|
|
1997
|
-
default:
|
|
1998
|
-
console.error(`Unsupported shell: ${shell}. Use bash, zsh, or fish.`);
|
|
1999
|
-
process.exit(1);
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2563
|
+
// src/commands/audit/list.ts
|
|
2564
|
+
import { z as z16 } from "zod";
|
|
2565
|
+
|
|
2566
|
+
// src/commands/_shared/pagination.ts
|
|
2567
|
+
import { z as z15 } from "zod";
|
|
2568
|
+
var paginationInput = {
|
|
2569
|
+
page: z15.number().int().min(1).default(1).describe("Page number"),
|
|
2570
|
+
pageSize: z15.number().int().min(1).max(100).default(25).describe("Results per page")
|
|
2571
|
+
};
|
|
2572
|
+
|
|
2573
|
+
// src/commands/audit/list.ts
|
|
2574
|
+
var inputSchema15 = z16.object({
|
|
2575
|
+
orgId: z16.string().uuid().describe("Organization ID"),
|
|
2576
|
+
...paginationInput,
|
|
2577
|
+
action: z16.string().optional().describe("Filter by action type"),
|
|
2578
|
+
userId: z16.string().optional().describe("Filter by user ID")
|
|
2579
|
+
});
|
|
2580
|
+
var auditList = {
|
|
2581
|
+
name: "audit.list",
|
|
2582
|
+
group: "audit",
|
|
2583
|
+
description: "List audit log entries for an organization",
|
|
2584
|
+
inputSchema: inputSchema15,
|
|
2585
|
+
outputSchema: z16.any(),
|
|
2586
|
+
annotations: { readOnlyHint: true },
|
|
2587
|
+
async execute(input, ctx) {
|
|
2588
|
+
const queryParams = {
|
|
2589
|
+
page: input.page,
|
|
2590
|
+
pageSize: input.pageSize
|
|
2591
|
+
};
|
|
2592
|
+
if (input.action) queryParams.action = input.action;
|
|
2593
|
+
if (input.userId) queryParams.userId = input.userId;
|
|
2594
|
+
return ctx.client.get("audit_log", {
|
|
2595
|
+
pathParams: { orgId: input.orgId },
|
|
2596
|
+
queryParams
|
|
2597
|
+
});
|
|
2598
|
+
},
|
|
2599
|
+
formatOutput(output, format) {
|
|
2600
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2601
|
+
return JSON.stringify(output, null, 2);
|
|
2602
|
+
}
|
|
2603
|
+
};
|
|
2604
|
+
|
|
2605
|
+
// src/commands/audit/index.ts
|
|
2606
|
+
function registerAuditCommands() {
|
|
2607
|
+
return [auditList];
|
|
2002
2608
|
}
|
|
2003
2609
|
|
|
2004
|
-
// src/commands/
|
|
2610
|
+
// src/commands/auth/login.ts
|
|
2005
2611
|
init_client();
|
|
2006
|
-
|
|
2612
|
+
init_errors();
|
|
2007
2613
|
init_profiles();
|
|
2008
|
-
import {
|
|
2009
|
-
import pc2 from "picocolors";
|
|
2614
|
+
import { z as z17 } from "zod";
|
|
2010
2615
|
|
|
2011
|
-
// src/lib/
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/;
|
|
2023
|
-
function shouldSkipEntirely() {
|
|
2024
|
-
if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
|
|
2025
|
-
if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
|
|
2026
|
-
return false;
|
|
2027
|
-
}
|
|
2028
|
-
function readCache() {
|
|
2029
|
-
try {
|
|
2030
|
-
if (!existsSync2(CACHE_FILE)) return null;
|
|
2031
|
-
const raw = readFileSync2(CACHE_FILE, "utf-8");
|
|
2032
|
-
return JSON.parse(raw);
|
|
2033
|
-
} catch {
|
|
2034
|
-
return null;
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
function writeCache(entry) {
|
|
2038
|
-
try {
|
|
2039
|
-
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
2040
|
-
writeFileSync2(CACHE_FILE, JSON.stringify(entry), "utf-8");
|
|
2041
|
-
} catch {
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
function showUpdateNotice(latest) {
|
|
2045
|
-
if (!process.stderr.isTTY) return;
|
|
2046
|
-
const color2 = isColorEnabled();
|
|
2047
|
-
const msg = color2 ? `
|
|
2048
|
-
${pc.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
|
|
2049
|
-
Run: elnora update
|
|
2050
|
-
` : `
|
|
2051
|
-
Update available: v${VERSION} \u2192 v${latest}
|
|
2052
|
-
Run: elnora update
|
|
2053
|
-
`;
|
|
2054
|
-
process.stderr.write(msg);
|
|
2055
|
-
}
|
|
2056
|
-
function isNewerVersion(a, b) {
|
|
2057
|
-
const pa = a.split(".").map(Number);
|
|
2058
|
-
const pb = b.split(".").map(Number);
|
|
2059
|
-
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
2060
|
-
const na = pa[i] ?? 0;
|
|
2061
|
-
const nb = pb[i] ?? 0;
|
|
2062
|
-
if (na > nb) return true;
|
|
2063
|
-
if (na < nb) return false;
|
|
2064
|
-
}
|
|
2065
|
-
return false;
|
|
2066
|
-
}
|
|
2067
|
-
function registerUpdateCheck() {
|
|
2068
|
-
if (shouldSkipEntirely()) return;
|
|
2069
|
-
const cached = readCache();
|
|
2070
|
-
if (cached) {
|
|
2071
|
-
const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
|
|
2072
|
-
if (elapsed < CHECK_INTERVAL_MS) {
|
|
2073
|
-
if (cached.latest && cached.latest !== VERSION && cached.latest !== "unknown") {
|
|
2074
|
-
if (isNewerVersion(cached.latest, VERSION)) {
|
|
2075
|
-
showUpdateNotice(cached.latest);
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2616
|
+
// src/lib/prompt.ts
|
|
2617
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
2618
|
+
function promptSecret(message) {
|
|
2619
|
+
return new Promise((resolve, reject) => {
|
|
2620
|
+
const { stdin, stdout } = process;
|
|
2621
|
+
if (!stdin.isTTY) {
|
|
2622
|
+
const rl = createInterface2({ input: stdin, output: stdout });
|
|
2623
|
+
rl.question(message, (answer) => {
|
|
2624
|
+
rl.close();
|
|
2625
|
+
resolve(answer.trim());
|
|
2626
|
+
});
|
|
2078
2627
|
return;
|
|
2079
2628
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2629
|
+
stdout.write(message);
|
|
2630
|
+
let input = "";
|
|
2631
|
+
const wasRaw = stdin.isRaw;
|
|
2632
|
+
stdin.setRawMode(true);
|
|
2633
|
+
stdin.resume();
|
|
2634
|
+
stdin.setEncoding("utf8");
|
|
2635
|
+
const onData = (char) => {
|
|
2636
|
+
switch (char) {
|
|
2637
|
+
case "\n":
|
|
2638
|
+
case "\r":
|
|
2639
|
+
case "": {
|
|
2640
|
+
stdin.setRawMode(wasRaw);
|
|
2641
|
+
stdin.pause();
|
|
2642
|
+
stdin.removeListener("data", onData);
|
|
2643
|
+
stdout.write("\n");
|
|
2644
|
+
resolve(input.trim());
|
|
2645
|
+
break;
|
|
2646
|
+
}
|
|
2647
|
+
case "":
|
|
2648
|
+
// Ctrl+C
|
|
2649
|
+
case "\x1B": {
|
|
2650
|
+
stdin.setRawMode(wasRaw);
|
|
2651
|
+
stdin.pause();
|
|
2652
|
+
stdin.removeListener("data", onData);
|
|
2653
|
+
stdout.write("\n");
|
|
2654
|
+
reject(new Error("Cancelled"));
|
|
2655
|
+
break;
|
|
2656
|
+
}
|
|
2657
|
+
case "\x7F":
|
|
2658
|
+
case "\b": {
|
|
2659
|
+
if (input.length > 0) {
|
|
2660
|
+
input = input.slice(0, -1);
|
|
2661
|
+
stdout.write("\b \b");
|
|
2662
|
+
}
|
|
2663
|
+
break;
|
|
2664
|
+
}
|
|
2665
|
+
default: {
|
|
2666
|
+
input += char;
|
|
2667
|
+
stdout.write("*");
|
|
2668
|
+
break;
|
|
2669
|
+
}
|
|
2095
2670
|
}
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
}
|
|
2100
|
-
})();
|
|
2671
|
+
};
|
|
2672
|
+
stdin.on("data", onData);
|
|
2673
|
+
});
|
|
2101
2674
|
}
|
|
2102
2675
|
|
|
2103
|
-
// src/commands/
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2676
|
+
// src/commands/auth/login.ts
|
|
2677
|
+
var inputSchema16 = z17.object({
|
|
2678
|
+
apiKey: z17.string().optional().describe("Elnora API key (elnora_live_...)"),
|
|
2679
|
+
profile: z17.string().default("default").describe("Profile name to save key under")
|
|
2680
|
+
});
|
|
2681
|
+
var authLogin = {
|
|
2682
|
+
name: "auth.login",
|
|
2683
|
+
group: "auth",
|
|
2684
|
+
description: "Set up authentication by saving an API key to a profile",
|
|
2685
|
+
inputSchema: inputSchema16,
|
|
2686
|
+
outputSchema: z17.any(),
|
|
2687
|
+
annotations: { exposeInMcp: false },
|
|
2688
|
+
async execute(input, _ctx) {
|
|
2689
|
+
let apiKey = input.apiKey;
|
|
2690
|
+
const profileName = input.profile;
|
|
2691
|
+
validateProfileName(profileName);
|
|
2692
|
+
const existing = listProfileNames();
|
|
2693
|
+
const profileExists = existing.includes(profileName);
|
|
2694
|
+
if (!apiKey) {
|
|
2695
|
+
if (profileExists) {
|
|
2696
|
+
let savedKey;
|
|
2697
|
+
try {
|
|
2698
|
+
savedKey = getApiKey(profileName);
|
|
2699
|
+
} catch {
|
|
2700
|
+
}
|
|
2701
|
+
if (savedKey && isTTY()) {
|
|
2702
|
+
process.stderr.write(`
|
|
2703
|
+
Checking profile "${profileName}"...`);
|
|
2704
|
+
try {
|
|
2705
|
+
const client = new ElnoraApiClient(savedKey);
|
|
2706
|
+
const result = await client.get("projects", {
|
|
2707
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
2708
|
+
});
|
|
2709
|
+
const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
|
|
2710
|
+
process.stderr.write(` authenticated (${projectCount} project${projectCount !== 1 ? "s" : ""}).
|
|
2711
|
+
|
|
2112
2712
|
`);
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2713
|
+
process.stderr.write(" To update your API key, run:\n");
|
|
2714
|
+
process.stderr.write(
|
|
2715
|
+
` elnora auth login --api-key <new-key>${profileName !== "default" ? ` --profile ${profileName}` : ""}
|
|
2716
|
+
|
|
2717
|
+
`
|
|
2718
|
+
);
|
|
2719
|
+
return null;
|
|
2720
|
+
} catch {
|
|
2721
|
+
process.stderr.write(" key is invalid or expired.\n\n");
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
if (isTTY()) {
|
|
2726
|
+
if (profileExists) {
|
|
2727
|
+
process.stderr.write(` Enter a new API key for profile "${profileName}"
|
|
2728
|
+
`);
|
|
2729
|
+
} else {
|
|
2730
|
+
process.stderr.write("\n Set up Elnora CLI authentication\n");
|
|
2731
|
+
}
|
|
2732
|
+
process.stderr.write(" Get your API key at: https://platform.elnora.ai > Settings > API Keys\n\n");
|
|
2733
|
+
try {
|
|
2734
|
+
apiKey = await promptSecret(" API key (Ctrl+C to cancel): ");
|
|
2735
|
+
} catch {
|
|
2736
|
+
process.stderr.write("\n Login cancelled.\n\n");
|
|
2737
|
+
process.exit(0);
|
|
2738
|
+
}
|
|
2739
|
+
process.stderr.write("\n");
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
if (!apiKey) {
|
|
2743
|
+
throw new ValidationError(
|
|
2744
|
+
"API key is required. Use --api-key flag or run interactively in a terminal.",
|
|
2745
|
+
"Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
2127
2746
|
);
|
|
2128
2747
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
const result = await client.get("projects", {
|
|
2134
|
-
queryParams: { page: 1, pageSize: 1 }
|
|
2748
|
+
const key = apiKey.trim();
|
|
2749
|
+
if (!key.startsWith("elnora_live_")) {
|
|
2750
|
+
throw new AuthError("API key must start with 'elnora_live_'. Check that you copied the full key.", {
|
|
2751
|
+
suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
2135
2752
|
});
|
|
2136
|
-
const count = result?.totalCount ?? result?.items?.length ?? 0;
|
|
2137
|
-
console.error(ok2(`Authenticated profile: default (${count} projects accessible)`));
|
|
2138
|
-
passed++;
|
|
2139
|
-
} catch (e) {
|
|
2140
|
-
console.error(fail2(`Authentication failed ${e instanceof Error ? e.message : "no API key"}`));
|
|
2141
2753
|
}
|
|
2142
|
-
|
|
2754
|
+
if (key.length < 20) {
|
|
2755
|
+
throw new AuthError("API key looks too short. Make sure you copied the full key.", {
|
|
2756
|
+
suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
if (isTTY()) {
|
|
2760
|
+
process.stderr.write(" Verifying...");
|
|
2761
|
+
}
|
|
2143
2762
|
try {
|
|
2144
|
-
const
|
|
2145
|
-
|
|
2763
|
+
const tempClient = new ElnoraApiClient(key);
|
|
2764
|
+
const result = await tempClient.get("projects", {
|
|
2765
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
2146
2766
|
});
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
passed++;
|
|
2156
|
-
} else {
|
|
2157
|
-
console.error(ok2(`Version v${VERSION} (registry check failed)`));
|
|
2158
|
-
passed++;
|
|
2767
|
+
const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
|
|
2768
|
+
const configPath = saveProfile(profileName, key);
|
|
2769
|
+
if (isTTY()) {
|
|
2770
|
+
process.stderr.write(` Done! ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.
|
|
2771
|
+
`);
|
|
2772
|
+
process.stderr.write(` Saved to profile "${profileName}" at ${configPath}
|
|
2773
|
+
|
|
2774
|
+
`);
|
|
2159
2775
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2776
|
+
return { profile: profileName, verified: true, configPath };
|
|
2777
|
+
} catch (err) {
|
|
2778
|
+
if (isTTY()) {
|
|
2779
|
+
process.stderr.write("\n");
|
|
2780
|
+
}
|
|
2781
|
+
if (err instanceof AuthError) {
|
|
2782
|
+
throw new AuthError("API key is invalid or has been revoked. Please check your key and try again.", {
|
|
2783
|
+
suggestion: "Get a new key from: https://platform.elnora.ai > Settings > API Keys"
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
throw err;
|
|
2163
2787
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2788
|
+
},
|
|
2789
|
+
formatOutput(output, format) {
|
|
2790
|
+
const data = output;
|
|
2791
|
+
if (format === "compact") return data.profile ?? JSON.stringify(output);
|
|
2792
|
+
const lines = [
|
|
2793
|
+
`\u2713 Authenticated! API key saved to profile "${data.profile}".`,
|
|
2794
|
+
"",
|
|
2795
|
+
"Next steps:",
|
|
2796
|
+
" elnora projects list See your projects",
|
|
2797
|
+
" elnora tasks create --project <ID> --stream Start a conversation",
|
|
2798
|
+
" elnora doctor Verify your setup"
|
|
2799
|
+
];
|
|
2800
|
+
return lines.join("\n");
|
|
2801
|
+
}
|
|
2802
|
+
};
|
|
2803
|
+
|
|
2804
|
+
// src/commands/auth/logout.ts
|
|
2805
|
+
init_profiles();
|
|
2806
|
+
import { z as z18 } from "zod";
|
|
2807
|
+
var inputSchema17 = z18.object({
|
|
2808
|
+
profile: z18.string().optional().describe("Profile name to remove (default: 'default')"),
|
|
2809
|
+
all: z18.boolean().default(false).describe("Remove all profiles")
|
|
2810
|
+
});
|
|
2811
|
+
var authLogout = {
|
|
2812
|
+
name: "auth.logout",
|
|
2813
|
+
group: "auth",
|
|
2814
|
+
description: "Remove saved credentials",
|
|
2815
|
+
inputSchema: inputSchema17,
|
|
2816
|
+
outputSchema: z18.any(),
|
|
2817
|
+
annotations: { destructiveHint: true, exposeInMcp: false },
|
|
2818
|
+
async execute(input, _ctx) {
|
|
2819
|
+
if (input.all) {
|
|
2820
|
+
const names = listProfileNames();
|
|
2821
|
+
for (const name of names) {
|
|
2822
|
+
removeProfile(name);
|
|
2177
2823
|
}
|
|
2824
|
+
return { loggedOut: true, profile: "all", removedCount: names.length };
|
|
2178
2825
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2826
|
+
const profileName = input.profile ?? "default";
|
|
2827
|
+
const removed = removeProfile(profileName);
|
|
2828
|
+
return { loggedOut: true, profile: profileName, removed };
|
|
2829
|
+
},
|
|
2830
|
+
formatOutput(output, format) {
|
|
2831
|
+
if (format === "compact") return output.profile ?? JSON.stringify(output);
|
|
2832
|
+
return JSON.stringify(output, null, 2);
|
|
2833
|
+
}
|
|
2834
|
+
};
|
|
2835
|
+
|
|
2836
|
+
// src/commands/auth/profiles.ts
|
|
2837
|
+
init_profiles();
|
|
2838
|
+
import { z as z19 } from "zod";
|
|
2839
|
+
var inputSchema18 = z19.object({});
|
|
2840
|
+
var authProfiles = {
|
|
2841
|
+
name: "auth.profiles",
|
|
2842
|
+
group: "auth",
|
|
2843
|
+
description: "List all configured profiles",
|
|
2844
|
+
inputSchema: inputSchema18,
|
|
2845
|
+
outputSchema: z19.any(),
|
|
2846
|
+
annotations: { readOnlyHint: true, exposeInMcp: false },
|
|
2847
|
+
async execute(_input, _ctx) {
|
|
2848
|
+
const profiles = loadProfiles();
|
|
2849
|
+
const items = [];
|
|
2850
|
+
for (const [name, data] of Object.entries(profiles)) {
|
|
2851
|
+
const key = data.api_key ?? "";
|
|
2852
|
+
const masked = key.length > 16 ? `${key.slice(0, 12)}...${key.slice(-4)}` : "[invalid]";
|
|
2853
|
+
items.push({ name, apiKey: masked });
|
|
2190
2854
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2855
|
+
return { profiles: items };
|
|
2856
|
+
},
|
|
2857
|
+
formatOutput(output, format) {
|
|
2858
|
+
if (format === "compact") {
|
|
2859
|
+
const profiles = output.profiles ?? [];
|
|
2860
|
+
return profiles.map((p) => p.name).join(", ");
|
|
2197
2861
|
}
|
|
2198
|
-
|
|
2862
|
+
return JSON.stringify(output, null, 2);
|
|
2863
|
+
}
|
|
2864
|
+
};
|
|
2865
|
+
|
|
2866
|
+
// src/commands/auth/status.ts
|
|
2867
|
+
import { z as z20 } from "zod";
|
|
2868
|
+
var inputSchema19 = z20.object({});
|
|
2869
|
+
var authStatus = {
|
|
2870
|
+
name: "auth.status",
|
|
2871
|
+
group: "auth",
|
|
2872
|
+
description: "Verify API key and show connection info",
|
|
2873
|
+
inputSchema: inputSchema19,
|
|
2874
|
+
outputSchema: z20.any(),
|
|
2875
|
+
annotations: { readOnlyHint: true, exposeInMcp: false },
|
|
2876
|
+
async execute(_input, ctx) {
|
|
2877
|
+
const result = await ctx.client.get("projects", {
|
|
2878
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
2879
|
+
});
|
|
2880
|
+
const projectCount = result.totalCount ?? result.items?.length ?? 0;
|
|
2881
|
+
return { profile: ctx.profileName, authenticated: true, projectCount };
|
|
2882
|
+
},
|
|
2883
|
+
formatOutput(output, format) {
|
|
2884
|
+
if (format === "compact") return String(output.authenticated ?? "");
|
|
2885
|
+
return JSON.stringify(output, null, 2);
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
|
|
2889
|
+
// src/commands/auth/validate.ts
|
|
2890
|
+
init_profiles();
|
|
2891
|
+
import { z as z21 } from "zod";
|
|
2892
|
+
var inputSchema20 = z21.object({
|
|
2893
|
+
token: z21.string().optional().describe("Token to validate (defaults to current API key)")
|
|
2894
|
+
});
|
|
2895
|
+
var authValidate = {
|
|
2896
|
+
name: "auth.validate",
|
|
2897
|
+
group: "auth",
|
|
2898
|
+
description: "Validate a JWT or API key token",
|
|
2899
|
+
inputSchema: inputSchema20,
|
|
2900
|
+
outputSchema: z21.any(),
|
|
2901
|
+
annotations: { exposeInMcp: false },
|
|
2902
|
+
async execute(input, ctx) {
|
|
2903
|
+
const tokenToValidate = input.token ?? resolveApiKey();
|
|
2904
|
+
return ctx.client.post("auth_validate", { token: tokenToValidate });
|
|
2905
|
+
},
|
|
2906
|
+
formatOutput(output, format) {
|
|
2907
|
+
if (format === "compact") return JSON.stringify(output);
|
|
2908
|
+
return JSON.stringify(output, null, 2);
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
|
|
2912
|
+
// src/commands/auth/index.ts
|
|
2913
|
+
function registerAuthCommands() {
|
|
2914
|
+
return [authLogin, authStatus, authLogout, authProfiles, authValidate];
|
|
2199
2915
|
}
|
|
2200
2916
|
|
|
2201
2917
|
// src/commands/feedback/submit.ts
|
|
@@ -2586,7 +3302,7 @@ var filesUpdate = {
|
|
|
2586
3302
|
|
|
2587
3303
|
// src/commands/files/upload.ts
|
|
2588
3304
|
init_client();
|
|
2589
|
-
import { readFileSync as
|
|
3305
|
+
import { readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
|
|
2590
3306
|
import { basename } from "node:path";
|
|
2591
3307
|
import { z as z37 } from "zod";
|
|
2592
3308
|
var inputSchema36 = z37.object({
|
|
@@ -2602,7 +3318,7 @@ var filesUpload = {
|
|
|
2602
3318
|
inputSchema: inputSchema36,
|
|
2603
3319
|
outputSchema: z37.any(),
|
|
2604
3320
|
async execute(input, ctx) {
|
|
2605
|
-
const fileBuffer =
|
|
3321
|
+
const fileBuffer = readFileSync6(input.filePath);
|
|
2606
3322
|
const fileName = input.fileName ?? basename(input.filePath);
|
|
2607
3323
|
const contentType = input.contentType ?? "application/octet-stream";
|
|
2608
3324
|
const fileSize = statSync2(input.filePath).size;
|
|
@@ -2633,7 +3349,7 @@ var filesUpload = {
|
|
|
2633
3349
|
// src/commands/files/upload-batch.ts
|
|
2634
3350
|
init_client();
|
|
2635
3351
|
init_errors();
|
|
2636
|
-
import { readFileSync as
|
|
3352
|
+
import { readFileSync as readFileSync7, statSync as statSync3 } from "node:fs";
|
|
2637
3353
|
import { basename as basename2 } from "node:path";
|
|
2638
3354
|
import { z as z38 } from "zod";
|
|
2639
3355
|
var inputSchema37 = z38.object({
|
|
@@ -2662,7 +3378,7 @@ var filesUploadBatch = {
|
|
|
2662
3378
|
for (const filePath of paths) {
|
|
2663
3379
|
const fileName = basename2(filePath);
|
|
2664
3380
|
try {
|
|
2665
|
-
const fileBuffer =
|
|
3381
|
+
const fileBuffer = readFileSync7(filePath);
|
|
2666
3382
|
const contentType = "application/octet-stream";
|
|
2667
3383
|
const fileSize = statSync3(filePath).size;
|
|
2668
3384
|
const uploadInfo = await ctx.client.post("file_upload", {
|
|
@@ -3196,65 +3912,6 @@ function registerLibraryCommands() {
|
|
|
3196
3912
|
return [libraryFiles, libraryFolders, libraryCreateFolder, libraryRenameFolder, libraryDeleteFolder];
|
|
3197
3913
|
}
|
|
3198
3914
|
|
|
3199
|
-
// src/commands/mcp/serve.ts
|
|
3200
|
-
function requireEnv(name) {
|
|
3201
|
-
const value = process.env[name];
|
|
3202
|
-
if (!value) {
|
|
3203
|
-
throw new Error(`Required environment variable ${name} is not set`);
|
|
3204
|
-
}
|
|
3205
|
-
return value;
|
|
3206
|
-
}
|
|
3207
|
-
function addMcpCommands(program2, registry2) {
|
|
3208
|
-
const mcp = program2.command("mcp").description("MCP server management");
|
|
3209
|
-
mcp.command("serve").description("Start MCP server exposing all CLI commands as tools").option("--http", "HTTP transport (default, for deployment)").option("--stdio", "stdio transport (for local MCP clients)").option("--port <port>", "HTTP port", process.env.PORT ?? "3000").option("--host <host>", "HTTP bind address", process.env.HOST ?? "0.0.0.0").action(async (opts) => {
|
|
3210
|
-
if (opts.stdio) {
|
|
3211
|
-
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_stdio_server(), stdio_server_exports));
|
|
3212
|
-
await startStdioServer2(registry2);
|
|
3213
|
-
} else {
|
|
3214
|
-
const { startHttpServer: startHttpServer2 } = await Promise.resolve().then(() => (init_http_server(), http_server_exports));
|
|
3215
|
-
await startHttpServer2(registry2, {
|
|
3216
|
-
port: Number.parseInt(opts.port, 10),
|
|
3217
|
-
host: opts.host,
|
|
3218
|
-
tokenValidationUrl: requireEnv("ELNORA_TOKEN_VALIDATION_URL"),
|
|
3219
|
-
mcpServiceKey: requireEnv("ELNORA_MCP_SERVICE_KEY")
|
|
3220
|
-
});
|
|
3221
|
-
}
|
|
3222
|
-
});
|
|
3223
|
-
}
|
|
3224
|
-
|
|
3225
|
-
// src/commands/open.ts
|
|
3226
|
-
import { exec } from "node:child_process";
|
|
3227
|
-
var URLS = {
|
|
3228
|
-
"": "https://platform.elnora.ai",
|
|
3229
|
-
platform: "https://platform.elnora.ai",
|
|
3230
|
-
docs: "https://docs.elnora.ai",
|
|
3231
|
-
keys: "https://platform.elnora.ai/settings/api-keys",
|
|
3232
|
-
billing: "https://platform.elnora.ai/settings/billing",
|
|
3233
|
-
github: "https://github.com/Elnora-AI/elnora-cli"
|
|
3234
|
-
};
|
|
3235
|
-
function openUrl(url) {
|
|
3236
|
-
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
3237
|
-
exec(cmd, (err) => {
|
|
3238
|
-
if (err) {
|
|
3239
|
-
console.error(`Failed to open browser: ${err.message}`);
|
|
3240
|
-
console.log(url);
|
|
3241
|
-
}
|
|
3242
|
-
});
|
|
3243
|
-
}
|
|
3244
|
-
function addOpenCommand(program2) {
|
|
3245
|
-
program2.command("open").argument("[page]", "Page to open: platform, docs, keys, billing, github").description("Open Elnora platform in the browser").action((page) => {
|
|
3246
|
-
const key = page ?? "";
|
|
3247
|
-
const url = URLS[key];
|
|
3248
|
-
if (!url) {
|
|
3249
|
-
console.error(`Unknown page: ${page}`);
|
|
3250
|
-
console.error(`Available: ${Object.keys(URLS).filter(Boolean).join(", ")}`);
|
|
3251
|
-
process.exit(1);
|
|
3252
|
-
}
|
|
3253
|
-
openUrl(url);
|
|
3254
|
-
console.log(`Opening ${url}...`);
|
|
3255
|
-
});
|
|
3256
|
-
}
|
|
3257
|
-
|
|
3258
3915
|
// src/commands/orgs/accept-invite.ts
|
|
3259
3916
|
import { z as z56 } from "zod";
|
|
3260
3917
|
var inputSchema54 = z56.object({
|
|
@@ -4021,18 +4678,63 @@ function registerProjectCommands() {
|
|
|
4021
4678
|
];
|
|
4022
4679
|
}
|
|
4023
4680
|
|
|
4024
|
-
// src/commands/
|
|
4681
|
+
// src/commands/protocols/generate.ts
|
|
4025
4682
|
import { z as z85 } from "zod";
|
|
4026
4683
|
var inputSchema83 = z85.object({
|
|
4027
|
-
|
|
4684
|
+
description: z85.string().min(10).max(5e3).describe("Protocol description"),
|
|
4685
|
+
title: z85.string().max(200).optional().describe("Task title (defaults to first 100 chars of description)"),
|
|
4686
|
+
project: z85.string().uuid().optional().describe("Project UUID to associate with")
|
|
4687
|
+
});
|
|
4688
|
+
var protocolsGenerate = {
|
|
4689
|
+
name: "protocols.generate",
|
|
4690
|
+
group: "protocols",
|
|
4691
|
+
description: "Generate a bioprotocol \u2014 creates a task and sends the description in one call",
|
|
4692
|
+
inputSchema: inputSchema83,
|
|
4693
|
+
outputSchema: z85.any(),
|
|
4694
|
+
annotations: { mcpScopes: ["tasks:write", "messages:write"] },
|
|
4695
|
+
async execute(input, ctx) {
|
|
4696
|
+
const body = { title: input.title || input.description.slice(0, 100) };
|
|
4697
|
+
if (input.project) body.projectId = input.project;
|
|
4698
|
+
const task = await ctx.client.post("tasks", body);
|
|
4699
|
+
if (!task?.id) {
|
|
4700
|
+
throw new Error("Task creation returned no ID");
|
|
4701
|
+
}
|
|
4702
|
+
try {
|
|
4703
|
+
const response = await ctx.client.post(`tasks/${task.id}/messages`, { content: input.description });
|
|
4704
|
+
return { taskId: task.id, response };
|
|
4705
|
+
} catch (err) {
|
|
4706
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4707
|
+
throw Object.assign(
|
|
4708
|
+
new Error(
|
|
4709
|
+
`Task ${task.id} created but message send failed: ${message}. Retry with 'elnora tasks send --task-id ${task.id}'.`
|
|
4710
|
+
),
|
|
4711
|
+
{ taskId: task.id }
|
|
4712
|
+
);
|
|
4713
|
+
}
|
|
4714
|
+
},
|
|
4715
|
+
formatOutput(output, format) {
|
|
4716
|
+
if (format === "compact") return output.taskId ?? JSON.stringify(output);
|
|
4717
|
+
return JSON.stringify(output, null, 2);
|
|
4718
|
+
}
|
|
4719
|
+
};
|
|
4720
|
+
|
|
4721
|
+
// src/commands/protocols/index.ts
|
|
4722
|
+
function registerProtocolCommands() {
|
|
4723
|
+
return [protocolsGenerate];
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
// src/commands/search/all.ts
|
|
4727
|
+
import { z as z86 } from "zod";
|
|
4728
|
+
var inputSchema84 = z86.object({
|
|
4729
|
+
query: z86.string().min(1).describe("Search query"),
|
|
4028
4730
|
...paginationInput
|
|
4029
4731
|
});
|
|
4030
4732
|
var searchAll = {
|
|
4031
4733
|
name: "search.all",
|
|
4032
4734
|
group: "search",
|
|
4033
4735
|
description: "Search across all entities",
|
|
4034
|
-
inputSchema:
|
|
4035
|
-
outputSchema:
|
|
4736
|
+
inputSchema: inputSchema84,
|
|
4737
|
+
outputSchema: z86.any(),
|
|
4036
4738
|
annotations: { readOnlyHint: true },
|
|
4037
4739
|
async execute(input, ctx) {
|
|
4038
4740
|
return ctx.client.get("search_all", {
|
|
@@ -4046,18 +4748,18 @@ var searchAll = {
|
|
|
4046
4748
|
};
|
|
4047
4749
|
|
|
4048
4750
|
// src/commands/search/file-content.ts
|
|
4049
|
-
import { z as
|
|
4050
|
-
var
|
|
4051
|
-
query:
|
|
4052
|
-
projectId:
|
|
4751
|
+
import { z as z87 } from "zod";
|
|
4752
|
+
var inputSchema85 = z87.object({
|
|
4753
|
+
query: z87.string().min(1).describe("Search query"),
|
|
4754
|
+
projectId: z87.string().uuid().optional().describe("Filter by project ID"),
|
|
4053
4755
|
...paginationInput
|
|
4054
4756
|
});
|
|
4055
4757
|
var searchFileContent = {
|
|
4056
4758
|
name: "search.fileContent",
|
|
4057
4759
|
group: "search",
|
|
4058
4760
|
description: "Search within file contents",
|
|
4059
|
-
inputSchema:
|
|
4060
|
-
outputSchema:
|
|
4761
|
+
inputSchema: inputSchema85,
|
|
4762
|
+
outputSchema: z87.any(),
|
|
4061
4763
|
annotations: { readOnlyHint: true },
|
|
4062
4764
|
async execute(input, ctx) {
|
|
4063
4765
|
const queryParams = {
|
|
@@ -4075,17 +4777,17 @@ var searchFileContent = {
|
|
|
4075
4777
|
};
|
|
4076
4778
|
|
|
4077
4779
|
// src/commands/search/files.ts
|
|
4078
|
-
import { z as
|
|
4079
|
-
var
|
|
4080
|
-
query:
|
|
4780
|
+
import { z as z88 } from "zod";
|
|
4781
|
+
var inputSchema86 = z88.object({
|
|
4782
|
+
query: z88.string().min(1).describe("Search query"),
|
|
4081
4783
|
...paginationInput
|
|
4082
4784
|
});
|
|
4083
4785
|
var searchFiles = {
|
|
4084
4786
|
name: "search.files",
|
|
4085
4787
|
group: "search",
|
|
4086
4788
|
description: "Search files by query",
|
|
4087
|
-
inputSchema:
|
|
4088
|
-
outputSchema:
|
|
4789
|
+
inputSchema: inputSchema86,
|
|
4790
|
+
outputSchema: z88.any(),
|
|
4089
4791
|
annotations: { readOnlyHint: true },
|
|
4090
4792
|
async execute(input, ctx) {
|
|
4091
4793
|
return ctx.client.get("search_files", {
|
|
@@ -4099,17 +4801,17 @@ var searchFiles = {
|
|
|
4099
4801
|
};
|
|
4100
4802
|
|
|
4101
4803
|
// src/commands/search/tasks.ts
|
|
4102
|
-
import { z as
|
|
4103
|
-
var
|
|
4104
|
-
query:
|
|
4804
|
+
import { z as z89 } from "zod";
|
|
4805
|
+
var inputSchema87 = z89.object({
|
|
4806
|
+
query: z89.string().min(1).describe("Search query"),
|
|
4105
4807
|
...paginationInput
|
|
4106
4808
|
});
|
|
4107
4809
|
var searchTasks = {
|
|
4108
4810
|
name: "search.tasks",
|
|
4109
4811
|
group: "search",
|
|
4110
4812
|
description: "Search tasks by query",
|
|
4111
|
-
inputSchema:
|
|
4112
|
-
outputSchema:
|
|
4813
|
+
inputSchema: inputSchema87,
|
|
4814
|
+
outputSchema: z89.any(),
|
|
4113
4815
|
annotations: { readOnlyHint: true },
|
|
4114
4816
|
async execute(input, ctx) {
|
|
4115
4817
|
return ctx.client.get("search_tasks", {
|
|
@@ -4127,280 +4829,17 @@ function registerSearchCommands() {
|
|
|
4127
4829
|
return [searchTasks, searchFiles, searchAll, searchFileContent];
|
|
4128
4830
|
}
|
|
4129
4831
|
|
|
4130
|
-
// src/commands/setup.ts
|
|
4131
|
-
init_profiles();
|
|
4132
|
-
|
|
4133
|
-
// src/lib/setup/claude.ts
|
|
4134
|
-
import { existsSync as existsSync5 } from "node:fs";
|
|
4135
|
-
|
|
4136
|
-
// src/lib/setup/common.ts
|
|
4137
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4138
|
-
import { homedir as homedir3 } from "node:os";
|
|
4139
|
-
import { join as join3 } from "node:path";
|
|
4140
|
-
import pc3 from "picocolors";
|
|
4141
|
-
var HOME = homedir3();
|
|
4142
|
-
var ELNORA_DIR = join3(HOME, ".elnora");
|
|
4143
|
-
var CLAUDE_DIR = join3(HOME, ".claude");
|
|
4144
|
-
var CLAUDE_MARKETPLACES_FILE = join3(CLAUDE_DIR, "plugins", "known_marketplaces.json");
|
|
4145
|
-
var CLAUDE_SETTINGS_FILE = join3(CLAUDE_DIR, "settings.json");
|
|
4146
|
-
var CURSOR_DIR = join3(HOME, ".cursor");
|
|
4147
|
-
var CURSOR_MCP_GLOBAL = join3(HOME, ".cursor", "mcp.json");
|
|
4148
|
-
var VSCODE_DIR = join3(HOME, ".vscode");
|
|
4149
|
-
var CODEX_DIR = join3(HOME, ".codex");
|
|
4150
|
-
var MARKETPLACE_NAME = "elnora-plugins";
|
|
4151
|
-
var MARKETPLACE_REPO = "Elnora-AI/elnora-plugins";
|
|
4152
|
-
var PLUGIN_ID = `elnora@${MARKETPLACE_NAME}`;
|
|
4153
|
-
var LEGACY_PLUGIN_IDS = ["elnora@elnora-local", "elnora@elnora-ai"];
|
|
4154
|
-
var LEGACY_MARKETPLACE_NAMES = ["elnora-local", "elnora-ai"];
|
|
4155
|
-
function readJsonFile(path) {
|
|
4156
|
-
try {
|
|
4157
|
-
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
4158
|
-
} catch {
|
|
4159
|
-
return {};
|
|
4160
|
-
}
|
|
4161
|
-
}
|
|
4162
|
-
function writeJsonFile(path, data) {
|
|
4163
|
-
mkdirSync3(join3(path, ".."), { recursive: true });
|
|
4164
|
-
writeFileSync3(path, `${JSON.stringify(data, null, 2)}
|
|
4165
|
-
`, "utf-8");
|
|
4166
|
-
}
|
|
4167
|
-
var color = isColorEnabled();
|
|
4168
|
-
function ok(msg) {
|
|
4169
|
-
return color ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
4170
|
-
}
|
|
4171
|
-
function fail(msg) {
|
|
4172
|
-
return color ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
4173
|
-
}
|
|
4174
|
-
function info(msg) {
|
|
4175
|
-
return color ? ` ${pc3.dim(msg)}` : ` ${msg}`;
|
|
4176
|
-
}
|
|
4177
|
-
function detectPlatforms() {
|
|
4178
|
-
return [
|
|
4179
|
-
{ name: "claude", label: "Claude Code", installed: existsSync4(CLAUDE_DIR) },
|
|
4180
|
-
{ name: "cursor", label: "Cursor", installed: existsSync4(CURSOR_DIR) },
|
|
4181
|
-
{ name: "vscode", label: "VS Code", installed: existsSync4(VSCODE_DIR) },
|
|
4182
|
-
{ name: "codex", label: "OpenAI Codex", installed: existsSync4(CODEX_DIR) }
|
|
4183
|
-
];
|
|
4184
|
-
}
|
|
4185
|
-
|
|
4186
|
-
// src/lib/setup/claude.ts
|
|
4187
|
-
function setupClaude() {
|
|
4188
|
-
if (!existsSync5(CLAUDE_DIR)) {
|
|
4189
|
-
console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
|
|
4190
|
-
console.error("");
|
|
4191
|
-
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
4192
|
-
return false;
|
|
4193
|
-
}
|
|
4194
|
-
console.error(ok("Claude Code found"));
|
|
4195
|
-
const marketplaces = readJsonFile(CLAUDE_MARKETPLACES_FILE);
|
|
4196
|
-
for (const old of LEGACY_MARKETPLACE_NAMES) {
|
|
4197
|
-
delete marketplaces[old];
|
|
4198
|
-
}
|
|
4199
|
-
marketplaces[MARKETPLACE_NAME] = {
|
|
4200
|
-
source: {
|
|
4201
|
-
source: "github",
|
|
4202
|
-
repo: MARKETPLACE_REPO
|
|
4203
|
-
}
|
|
4204
|
-
};
|
|
4205
|
-
writeJsonFile(CLAUDE_MARKETPLACES_FILE, marketplaces);
|
|
4206
|
-
console.error(ok(`Marketplace registered (GitHub: ${MARKETPLACE_REPO})`));
|
|
4207
|
-
const settings = readJsonFile(CLAUDE_SETTINGS_FILE);
|
|
4208
|
-
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
|
|
4209
|
-
settings.enabledPlugins = {};
|
|
4210
|
-
}
|
|
4211
|
-
const plugins = settings.enabledPlugins;
|
|
4212
|
-
for (const old of LEGACY_PLUGIN_IDS) {
|
|
4213
|
-
delete plugins[old];
|
|
4214
|
-
}
|
|
4215
|
-
plugins[PLUGIN_ID] = true;
|
|
4216
|
-
writeJsonFile(CLAUDE_SETTINGS_FILE, settings);
|
|
4217
|
-
console.error(ok(`Plugin enabled (${PLUGIN_ID})`));
|
|
4218
|
-
console.error("");
|
|
4219
|
-
console.error(" Elnora skills are now available in Claude Code.");
|
|
4220
|
-
console.error(" Restart Claude Code if it's currently running.");
|
|
4221
|
-
return true;
|
|
4222
|
-
}
|
|
4223
|
-
|
|
4224
|
-
// src/lib/setup/codex.ts
|
|
4225
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
4226
|
-
import { join as join4 } from "node:path";
|
|
4227
|
-
var CODEX_MCP_FILE = join4(CODEX_DIR, "mcp.json");
|
|
4228
|
-
function setupCodex(apiKey) {
|
|
4229
|
-
if (!existsSync6(CODEX_DIR)) {
|
|
4230
|
-
console.error(fail("Codex CLI not found (~/.codex/ does not exist)"));
|
|
4231
|
-
console.error("");
|
|
4232
|
-
console.error(" Install Codex first: https://github.com/openai/codex");
|
|
4233
|
-
return false;
|
|
4234
|
-
}
|
|
4235
|
-
console.error(ok("Codex CLI found"));
|
|
4236
|
-
const config = readJsonFile(CODEX_MCP_FILE);
|
|
4237
|
-
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
4238
|
-
config.mcpServers = {};
|
|
4239
|
-
}
|
|
4240
|
-
config.mcpServers.elnora = {
|
|
4241
|
-
url: "https://mcp.elnora.ai/mcp",
|
|
4242
|
-
headers: {
|
|
4243
|
-
"X-API-Key": apiKey
|
|
4244
|
-
}
|
|
4245
|
-
};
|
|
4246
|
-
writeJsonFile(CODEX_MCP_FILE, config);
|
|
4247
|
-
console.error(ok("MCP server configured in ~/.codex/mcp.json"));
|
|
4248
|
-
console.error("");
|
|
4249
|
-
console.error(" Elnora MCP server is now available in Codex.");
|
|
4250
|
-
return true;
|
|
4251
|
-
}
|
|
4252
|
-
|
|
4253
|
-
// src/lib/setup/cursor.ts
|
|
4254
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
4255
|
-
function setupCursor(apiKey) {
|
|
4256
|
-
if (!existsSync7(CURSOR_DIR)) {
|
|
4257
|
-
console.error(fail("Cursor not found (~/.cursor/ does not exist)"));
|
|
4258
|
-
console.error("");
|
|
4259
|
-
console.error(" Install Cursor first: https://cursor.com");
|
|
4260
|
-
return false;
|
|
4261
|
-
}
|
|
4262
|
-
console.error(ok("Cursor found"));
|
|
4263
|
-
const config = readJsonFile(CURSOR_MCP_GLOBAL);
|
|
4264
|
-
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
4265
|
-
config.mcpServers = {};
|
|
4266
|
-
}
|
|
4267
|
-
config.mcpServers.elnora = {
|
|
4268
|
-
url: "https://mcp.elnora.ai/mcp",
|
|
4269
|
-
headers: {
|
|
4270
|
-
"X-API-Key": apiKey
|
|
4271
|
-
}
|
|
4272
|
-
};
|
|
4273
|
-
writeJsonFile(CURSOR_MCP_GLOBAL, config);
|
|
4274
|
-
console.error(ok("MCP server configured in ~/.cursor/mcp.json"));
|
|
4275
|
-
console.error("");
|
|
4276
|
-
console.error(" Elnora MCP server is now available in Cursor.");
|
|
4277
|
-
console.error(" Restart Cursor if it's currently running.");
|
|
4278
|
-
return true;
|
|
4279
|
-
}
|
|
4280
|
-
|
|
4281
|
-
// src/lib/setup/vscode.ts
|
|
4282
|
-
import { existsSync as existsSync8 } from "node:fs";
|
|
4283
|
-
import { join as join5 } from "node:path";
|
|
4284
|
-
var VSCODE_MCP_GLOBAL = join5(HOME, ".vscode", "mcp.json");
|
|
4285
|
-
function setupVscode(apiKey) {
|
|
4286
|
-
if (!existsSync8(VSCODE_DIR)) {
|
|
4287
|
-
console.error(fail("VS Code not found (~/.vscode/ does not exist)"));
|
|
4288
|
-
console.error("");
|
|
4289
|
-
console.error(" Install VS Code first: https://code.visualstudio.com");
|
|
4290
|
-
return false;
|
|
4291
|
-
}
|
|
4292
|
-
console.error(ok("VS Code found"));
|
|
4293
|
-
const config = readJsonFile(VSCODE_MCP_GLOBAL);
|
|
4294
|
-
if (!config.servers || typeof config.servers !== "object") {
|
|
4295
|
-
config.servers = {};
|
|
4296
|
-
}
|
|
4297
|
-
config.servers.elnora = {
|
|
4298
|
-
type: "http",
|
|
4299
|
-
url: "https://mcp.elnora.ai/mcp",
|
|
4300
|
-
headers: {
|
|
4301
|
-
"X-API-Key": apiKey
|
|
4302
|
-
}
|
|
4303
|
-
};
|
|
4304
|
-
writeJsonFile(VSCODE_MCP_GLOBAL, config);
|
|
4305
|
-
console.error(ok("MCP server configured in ~/.vscode/mcp.json"));
|
|
4306
|
-
console.error("");
|
|
4307
|
-
console.error(" Elnora MCP server is now available in VS Code Copilot.");
|
|
4308
|
-
console.error(" Restart VS Code if it's currently running.");
|
|
4309
|
-
return true;
|
|
4310
|
-
}
|
|
4311
|
-
|
|
4312
|
-
// src/commands/setup.ts
|
|
4313
|
-
var PLATFORM_SETUP = {
|
|
4314
|
-
claude: () => setupClaude(),
|
|
4315
|
-
cursor: (key) => setupCursor(key),
|
|
4316
|
-
vscode: (key) => setupVscode(key),
|
|
4317
|
-
codex: (key) => setupCodex(key)
|
|
4318
|
-
};
|
|
4319
|
-
function runSetup(platforms, profileName) {
|
|
4320
|
-
let apiKey;
|
|
4321
|
-
const needsKey = platforms.some((p) => p !== "claude");
|
|
4322
|
-
if (needsKey) {
|
|
4323
|
-
try {
|
|
4324
|
-
apiKey = resolveApiKey(profileName);
|
|
4325
|
-
} catch {
|
|
4326
|
-
if (platforms.every((p) => p !== "claude")) {
|
|
4327
|
-
console.error(fail("No API key found. Run 'elnora auth login' first."));
|
|
4328
|
-
process.exitCode = 1;
|
|
4329
|
-
return;
|
|
4330
|
-
}
|
|
4331
|
-
}
|
|
4332
|
-
}
|
|
4333
|
-
let anyFailed = false;
|
|
4334
|
-
for (const platform of platforms) {
|
|
4335
|
-
const setupFn = PLATFORM_SETUP[platform];
|
|
4336
|
-
const success = setupFn(apiKey ?? "");
|
|
4337
|
-
if (!success) anyFailed = true;
|
|
4338
|
-
console.error("");
|
|
4339
|
-
}
|
|
4340
|
-
if (anyFailed) {
|
|
4341
|
-
process.exitCode = 1;
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
function addSetupCommand(program2) {
|
|
4345
|
-
const setup = program2.command("setup").description("Configure AI coding tools to use Elnora").option("--profile <name>", "API key profile to use", "default");
|
|
4346
|
-
setup.command("claude").description("Set up Claude Code (skills + MCP server)").action(() => {
|
|
4347
|
-
const opts = setup.opts();
|
|
4348
|
-
console.error("");
|
|
4349
|
-
runSetup(["claude"], opts.profile);
|
|
4350
|
-
});
|
|
4351
|
-
setup.command("cursor").description("Set up Cursor (MCP server)").action(() => {
|
|
4352
|
-
const opts = setup.opts();
|
|
4353
|
-
console.error("");
|
|
4354
|
-
runSetup(["cursor"], opts.profile);
|
|
4355
|
-
});
|
|
4356
|
-
setup.command("vscode").description("Set up VS Code Copilot (MCP server)").action(() => {
|
|
4357
|
-
const opts = setup.opts();
|
|
4358
|
-
console.error("");
|
|
4359
|
-
runSetup(["vscode"], opts.profile);
|
|
4360
|
-
});
|
|
4361
|
-
setup.command("codex").description("Set up OpenAI Codex (MCP server)").action(() => {
|
|
4362
|
-
const opts = setup.opts();
|
|
4363
|
-
console.error("");
|
|
4364
|
-
runSetup(["codex"], opts.profile);
|
|
4365
|
-
});
|
|
4366
|
-
setup.action(() => {
|
|
4367
|
-
const opts = setup.opts();
|
|
4368
|
-
console.error("");
|
|
4369
|
-
const detected = detectPlatforms();
|
|
4370
|
-
const installed = detected.filter((p) => p.installed);
|
|
4371
|
-
if (installed.length === 0) {
|
|
4372
|
-
console.error(fail("No supported AI coding tools detected."));
|
|
4373
|
-
console.error("");
|
|
4374
|
-
console.error(" Supported: Claude Code, Cursor, VS Code, OpenAI Codex");
|
|
4375
|
-
console.error(" Install one first, then run 'elnora setup' again.");
|
|
4376
|
-
process.exitCode = 1;
|
|
4377
|
-
return;
|
|
4378
|
-
}
|
|
4379
|
-
console.error(ok(`Detected ${installed.length} platform(s):`));
|
|
4380
|
-
for (const p of installed) {
|
|
4381
|
-
console.error(info(`${p.label}`));
|
|
4382
|
-
}
|
|
4383
|
-
console.error("");
|
|
4384
|
-
const platforms = installed.map((p) => p.name);
|
|
4385
|
-
runSetup(platforms, opts.profile);
|
|
4386
|
-
});
|
|
4387
|
-
program2.command("setup-claude").description("Set up Claude Code (alias for 'setup claude')").option("--profile <name>", "API key profile to use", "default").action((opts) => {
|
|
4388
|
-
console.error("");
|
|
4389
|
-
runSetup(["claude"], opts.profile);
|
|
4390
|
-
});
|
|
4391
|
-
}
|
|
4392
|
-
|
|
4393
4832
|
// src/commands/tasks/archive.ts
|
|
4394
|
-
import { z as
|
|
4395
|
-
var
|
|
4396
|
-
taskId:
|
|
4833
|
+
import { z as z90 } from "zod";
|
|
4834
|
+
var inputSchema88 = z90.object({
|
|
4835
|
+
taskId: z90.string().uuid().describe("Task ID to archive")
|
|
4397
4836
|
});
|
|
4398
4837
|
var tasksArchive = {
|
|
4399
4838
|
name: "tasks.archive",
|
|
4400
4839
|
group: "tasks",
|
|
4401
4840
|
description: "Archive (delete) a task",
|
|
4402
|
-
inputSchema:
|
|
4403
|
-
outputSchema:
|
|
4841
|
+
inputSchema: inputSchema88,
|
|
4842
|
+
outputSchema: z90.any(),
|
|
4404
4843
|
annotations: { destructiveHint: true },
|
|
4405
4844
|
async execute(input, ctx) {
|
|
4406
4845
|
await ctx.client.del("task", {
|
|
@@ -4415,7 +4854,7 @@ var tasksArchive = {
|
|
|
4415
4854
|
};
|
|
4416
4855
|
|
|
4417
4856
|
// src/commands/tasks/create.ts
|
|
4418
|
-
import { z as
|
|
4857
|
+
import { z as z91 } from "zod";
|
|
4419
4858
|
|
|
4420
4859
|
// src/lib/poll.ts
|
|
4421
4860
|
init_errors();
|
|
@@ -4537,7 +4976,7 @@ async function collectStreamResponse(taskId, apiKey, options) {
|
|
|
4537
4976
|
}
|
|
4538
4977
|
|
|
4539
4978
|
// src/lib/stream-renderer.ts
|
|
4540
|
-
import
|
|
4979
|
+
import pc5 from "picocolors";
|
|
4541
4980
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4542
4981
|
var SPINNER_INTERVAL_MS = 80;
|
|
4543
4982
|
var StreamRenderer = class {
|
|
@@ -4574,14 +5013,14 @@ var StreamRenderer = class {
|
|
|
4574
5013
|
this.stopSpinner();
|
|
4575
5014
|
process.stderr.write(
|
|
4576
5015
|
`
|
|
4577
|
-
${this.useColor ?
|
|
5016
|
+
${this.useColor ? pc5.red("Error:") : "Error:"} ${event.content}
|
|
4578
5017
|
`
|
|
4579
5018
|
);
|
|
4580
5019
|
break;
|
|
4581
5020
|
case "timeout":
|
|
4582
5021
|
this.stopSpinner();
|
|
4583
5022
|
process.stderr.write(`
|
|
4584
|
-
${this.useColor ?
|
|
5023
|
+
${this.useColor ? pc5.yellow("Stream timed out.") : "Stream timed out."}
|
|
4585
5024
|
`);
|
|
4586
5025
|
break;
|
|
4587
5026
|
}
|
|
@@ -4603,7 +5042,7 @@ ${this.useColor ? pc4.yellow("Stream timed out.") : "Stream timed out."}
|
|
|
4603
5042
|
}
|
|
4604
5043
|
writeSpinnerFrame() {
|
|
4605
5044
|
const frame = SPINNER_FRAMES[this.spinnerFrame];
|
|
4606
|
-
const text = this.useColor ?
|
|
5045
|
+
const text = this.useColor ? pc5.dim(`${frame} ${this.currentLabel}`) : `${frame} ${this.currentLabel}`;
|
|
4607
5046
|
process.stderr.write(`\r ${text}`);
|
|
4608
5047
|
}
|
|
4609
5048
|
stopSpinner() {
|
|
@@ -4618,19 +5057,19 @@ ${this.useColor ? pc4.yellow("Stream timed out.") : "Stream timed out."}
|
|
|
4618
5057
|
};
|
|
4619
5058
|
|
|
4620
5059
|
// src/commands/tasks/create.ts
|
|
4621
|
-
var
|
|
4622
|
-
project:
|
|
4623
|
-
title:
|
|
4624
|
-
message:
|
|
4625
|
-
wait:
|
|
4626
|
-
stream:
|
|
5060
|
+
var inputSchema89 = z91.object({
|
|
5061
|
+
project: z91.string().uuid().describe("Project ID"),
|
|
5062
|
+
title: z91.string().optional().describe("Task title"),
|
|
5063
|
+
message: z91.string().optional().describe("Initial message"),
|
|
5064
|
+
wait: z91.boolean().default(false).describe("Wait for agent response (polling)"),
|
|
5065
|
+
stream: z91.boolean().default(false).describe("Stream agent response in real-time (SSE)")
|
|
4627
5066
|
});
|
|
4628
5067
|
var tasksCreate = {
|
|
4629
5068
|
name: "tasks.create",
|
|
4630
5069
|
group: "tasks",
|
|
4631
5070
|
description: "Create a new task in a project",
|
|
4632
|
-
inputSchema:
|
|
4633
|
-
outputSchema:
|
|
5071
|
+
inputSchema: inputSchema89,
|
|
5072
|
+
outputSchema: z91.any(),
|
|
4634
5073
|
async execute(input, ctx) {
|
|
4635
5074
|
const result = await ctx.client.post("tasks", {
|
|
4636
5075
|
projectId: input.project,
|
|
@@ -4680,16 +5119,16 @@ var tasksCreate = {
|
|
|
4680
5119
|
};
|
|
4681
5120
|
|
|
4682
5121
|
// src/commands/tasks/get.ts
|
|
4683
|
-
import { z as
|
|
4684
|
-
var
|
|
4685
|
-
taskId:
|
|
5122
|
+
import { z as z92 } from "zod";
|
|
5123
|
+
var inputSchema90 = z92.object({
|
|
5124
|
+
taskId: z92.string().uuid().describe("Task ID")
|
|
4686
5125
|
});
|
|
4687
5126
|
var tasksGet = {
|
|
4688
5127
|
name: "tasks.get",
|
|
4689
5128
|
group: "tasks",
|
|
4690
5129
|
description: "Get details of a specific task",
|
|
4691
|
-
inputSchema:
|
|
4692
|
-
outputSchema:
|
|
5130
|
+
inputSchema: inputSchema90,
|
|
5131
|
+
outputSchema: z92.any(),
|
|
4693
5132
|
annotations: { readOnlyHint: true },
|
|
4694
5133
|
async execute(input, ctx) {
|
|
4695
5134
|
return ctx.client.get("task", {
|
|
@@ -4703,17 +5142,17 @@ var tasksGet = {
|
|
|
4703
5142
|
};
|
|
4704
5143
|
|
|
4705
5144
|
// src/commands/tasks/list.ts
|
|
4706
|
-
import { z as
|
|
4707
|
-
var
|
|
4708
|
-
project:
|
|
5145
|
+
import { z as z93 } from "zod";
|
|
5146
|
+
var inputSchema91 = z93.object({
|
|
5147
|
+
project: z93.string().uuid().optional().describe("Project ID to filter by"),
|
|
4709
5148
|
...paginationInput
|
|
4710
5149
|
});
|
|
4711
5150
|
var tasksList = {
|
|
4712
5151
|
name: "tasks.list",
|
|
4713
5152
|
group: "tasks",
|
|
4714
5153
|
description: "List tasks, optionally filtered by project",
|
|
4715
|
-
inputSchema:
|
|
4716
|
-
outputSchema:
|
|
5154
|
+
inputSchema: inputSchema91,
|
|
5155
|
+
outputSchema: z93.any(),
|
|
4717
5156
|
annotations: { readOnlyHint: true },
|
|
4718
5157
|
async execute(input, ctx) {
|
|
4719
5158
|
if (input.project) {
|
|
@@ -4733,18 +5172,18 @@ var tasksList = {
|
|
|
4733
5172
|
};
|
|
4734
5173
|
|
|
4735
5174
|
// src/commands/tasks/messages.ts
|
|
4736
|
-
import { z as
|
|
4737
|
-
var
|
|
4738
|
-
taskId:
|
|
4739
|
-
cursor:
|
|
4740
|
-
limit:
|
|
5175
|
+
import { z as z94 } from "zod";
|
|
5176
|
+
var inputSchema92 = z94.object({
|
|
5177
|
+
taskId: z94.string().uuid().describe("Task ID"),
|
|
5178
|
+
cursor: z94.string().optional().describe("Pagination cursor"),
|
|
5179
|
+
limit: z94.number().int().min(1).max(100).default(50).describe("Results per page")
|
|
4741
5180
|
});
|
|
4742
5181
|
var tasksMessages = {
|
|
4743
5182
|
name: "tasks.messages",
|
|
4744
5183
|
group: "tasks",
|
|
4745
5184
|
description: "List messages for a task",
|
|
4746
|
-
inputSchema:
|
|
4747
|
-
outputSchema:
|
|
5185
|
+
inputSchema: inputSchema92,
|
|
5186
|
+
outputSchema: z94.any(),
|
|
4748
5187
|
annotations: { readOnlyHint: true },
|
|
4749
5188
|
async execute(input, ctx) {
|
|
4750
5189
|
const queryParams = { limit: input.limit };
|
|
@@ -4763,22 +5202,22 @@ var tasksMessages = {
|
|
|
4763
5202
|
};
|
|
4764
5203
|
|
|
4765
5204
|
// src/commands/tasks/send.ts
|
|
4766
|
-
import { z as
|
|
5205
|
+
import { z as z95 } from "zod";
|
|
4767
5206
|
init_profiles();
|
|
4768
5207
|
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4769
|
-
var
|
|
4770
|
-
taskId:
|
|
4771
|
-
message:
|
|
4772
|
-
fileRefs:
|
|
4773
|
-
wait:
|
|
4774
|
-
stream:
|
|
5208
|
+
var inputSchema93 = z95.object({
|
|
5209
|
+
taskId: z95.string().uuid().describe("Task ID"),
|
|
5210
|
+
message: z95.string().min(1).describe("Message content"),
|
|
5211
|
+
fileRefs: z95.string().optional().describe("Comma-separated file reference UUIDs"),
|
|
5212
|
+
wait: z95.boolean().default(false).describe("Wait for agent response (polling)"),
|
|
5213
|
+
stream: z95.boolean().default(false).describe("Stream agent response in real-time (SSE)")
|
|
4775
5214
|
});
|
|
4776
5215
|
var tasksSend = {
|
|
4777
5216
|
name: "tasks.send",
|
|
4778
5217
|
group: "tasks",
|
|
4779
5218
|
description: "Send a message to a task",
|
|
4780
|
-
inputSchema:
|
|
4781
|
-
outputSchema:
|
|
5219
|
+
inputSchema: inputSchema93,
|
|
5220
|
+
outputSchema: z95.any(),
|
|
4782
5221
|
async execute(input, ctx) {
|
|
4783
5222
|
let referencedFileIds;
|
|
4784
5223
|
if (input.fileRefs) {
|
|
@@ -4835,18 +5274,18 @@ var tasksSend = {
|
|
|
4835
5274
|
|
|
4836
5275
|
// src/commands/tasks/update.ts
|
|
4837
5276
|
init_errors();
|
|
4838
|
-
import { z as
|
|
4839
|
-
var
|
|
4840
|
-
taskId:
|
|
4841
|
-
title:
|
|
4842
|
-
status:
|
|
5277
|
+
import { z as z96 } from "zod";
|
|
5278
|
+
var inputSchema94 = z96.object({
|
|
5279
|
+
taskId: z96.string().uuid().describe("Task ID"),
|
|
5280
|
+
title: z96.string().min(1).optional().describe("New task title"),
|
|
5281
|
+
status: z96.string().optional().describe("New task status")
|
|
4843
5282
|
});
|
|
4844
5283
|
var tasksUpdate = {
|
|
4845
5284
|
name: "tasks.update",
|
|
4846
5285
|
group: "tasks",
|
|
4847
5286
|
description: "Update an existing task",
|
|
4848
|
-
inputSchema:
|
|
4849
|
-
outputSchema:
|
|
5287
|
+
inputSchema: inputSchema94,
|
|
5288
|
+
outputSchema: z96.any(),
|
|
4850
5289
|
annotations: { idempotentHint: true },
|
|
4851
5290
|
async execute(input, ctx) {
|
|
4852
5291
|
const { taskId, ...fields } = input;
|
|
@@ -4871,220 +5310,6 @@ function registerTaskCommands() {
|
|
|
4871
5310
|
return [tasksList, tasksGet, tasksCreate, tasksSend, tasksMessages, tasksUpdate, tasksArchive];
|
|
4872
5311
|
}
|
|
4873
5312
|
|
|
4874
|
-
// src/commands/update.ts
|
|
4875
|
-
init_config();
|
|
4876
|
-
import { execFileSync } from "node:child_process";
|
|
4877
|
-
import { createHash } from "node:crypto";
|
|
4878
|
-
import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
|
|
4879
|
-
import { tmpdir } from "node:os";
|
|
4880
|
-
import { join as join6 } from "node:path";
|
|
4881
|
-
import { Readable } from "node:stream";
|
|
4882
|
-
import { pipeline } from "node:stream/promises";
|
|
4883
|
-
import pc5 from "picocolors";
|
|
4884
|
-
|
|
4885
|
-
// src/lib/install-method.ts
|
|
4886
|
-
function detectInstallMethod() {
|
|
4887
|
-
const execPath = process.argv[1] ?? "";
|
|
4888
|
-
if (execPath.includes("node_modules")) {
|
|
4889
|
-
return "npm";
|
|
4890
|
-
}
|
|
4891
|
-
if (execPath.includes("/Cellar/") || execPath.includes("/homebrew/")) {
|
|
4892
|
-
return "homebrew";
|
|
4893
|
-
}
|
|
4894
|
-
return "binary";
|
|
4895
|
-
}
|
|
4896
|
-
function getUpdateInstruction(method) {
|
|
4897
|
-
switch (method) {
|
|
4898
|
-
case "npm":
|
|
4899
|
-
return "npm install -g @elnora-ai/cli@latest";
|
|
4900
|
-
case "homebrew":
|
|
4901
|
-
return "brew upgrade elnora";
|
|
4902
|
-
case "binary":
|
|
4903
|
-
return "elnora update --install";
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4906
|
-
|
|
4907
|
-
// src/commands/update.ts
|
|
4908
|
-
var NPM_REGISTRY_URL2 = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
4909
|
-
var GITHUB_REPO = "Elnora-AI/elnora-cli";
|
|
4910
|
-
function getPlatformTarget() {
|
|
4911
|
-
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
4912
|
-
switch (process.platform) {
|
|
4913
|
-
case "darwin":
|
|
4914
|
-
return { target: `elnora-macos-${arch}`, ext: "tar.gz" };
|
|
4915
|
-
case "linux":
|
|
4916
|
-
return { target: `elnora-linux-${arch}`, ext: "tar.gz" };
|
|
4917
|
-
case "win32":
|
|
4918
|
-
return { target: `elnora-win-${arch}.exe`, ext: "zip" };
|
|
4919
|
-
default:
|
|
4920
|
-
return null;
|
|
4921
|
-
}
|
|
4922
|
-
}
|
|
4923
|
-
async function fetchLatestVersion() {
|
|
4924
|
-
const res = await fetch(NPM_REGISTRY_URL2, { signal: AbortSignal.timeout(5e3) });
|
|
4925
|
-
if (!res.ok) throw new Error(`Registry returned HTTP ${res.status}`);
|
|
4926
|
-
const data = await res.json();
|
|
4927
|
-
return data.version ?? "unknown";
|
|
4928
|
-
}
|
|
4929
|
-
async function downloadToFile(url, dest) {
|
|
4930
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(12e4), redirect: "follow" });
|
|
4931
|
-
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
|
4932
|
-
const nodeStream = Readable.fromWeb(res.body);
|
|
4933
|
-
await pipeline(nodeStream, createWriteStream(dest));
|
|
4934
|
-
}
|
|
4935
|
-
async function installBinaryUpdate(latest) {
|
|
4936
|
-
const color2 = isColorEnabled();
|
|
4937
|
-
const platformInfo = getPlatformTarget();
|
|
4938
|
-
if (!platformInfo) {
|
|
4939
|
-
console.error(`Unsupported platform: ${process.platform}.`);
|
|
4940
|
-
console.error("Try: npm install -g @elnora-ai/cli@latest");
|
|
4941
|
-
process.exitCode = 1;
|
|
4942
|
-
return;
|
|
4943
|
-
}
|
|
4944
|
-
const { target, ext } = platformInfo;
|
|
4945
|
-
const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
|
|
4946
|
-
const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
|
|
4947
|
-
const tmp = join6(tmpdir(), `elnora-update-${Date.now()}`);
|
|
4948
|
-
mkdirSync4(tmp, { recursive: true });
|
|
4949
|
-
const archivePath = join6(tmp, `${target}.${ext}`);
|
|
4950
|
-
try {
|
|
4951
|
-
console.error("Downloading...");
|
|
4952
|
-
await downloadToFile(binaryUrl, archivePath);
|
|
4953
|
-
let checksumVerified = false;
|
|
4954
|
-
try {
|
|
4955
|
-
const checksumRes = await fetch(checksumUrl, { signal: AbortSignal.timeout(15e3) });
|
|
4956
|
-
if (checksumRes.ok) {
|
|
4957
|
-
const checksumText = await checksumRes.text();
|
|
4958
|
-
const expected = checksumText.split(/\s/)[0].trim();
|
|
4959
|
-
if (expected.length === 64) {
|
|
4960
|
-
const actual = createHash("sha256").update(readFileSync6(archivePath)).digest("hex");
|
|
4961
|
-
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
4962
|
-
console.error("Checksum verification failed. The download may be corrupted.");
|
|
4963
|
-
process.exitCode = 1;
|
|
4964
|
-
return;
|
|
4965
|
-
}
|
|
4966
|
-
checksumVerified = true;
|
|
4967
|
-
}
|
|
4968
|
-
}
|
|
4969
|
-
} catch {
|
|
4970
|
-
}
|
|
4971
|
-
if (!checksumVerified) {
|
|
4972
|
-
console.error("Warning: Could not verify download checksum. Proceeding anyway.");
|
|
4973
|
-
}
|
|
4974
|
-
if (process.platform === "win32") {
|
|
4975
|
-
const extractDir = join6(tmp, "extracted");
|
|
4976
|
-
execFileSync("powershell", [
|
|
4977
|
-
"-NoProfile",
|
|
4978
|
-
"-Command",
|
|
4979
|
-
`Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
|
|
4980
|
-
]);
|
|
4981
|
-
const installDir = join6(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
4982
|
-
mkdirSync4(installDir, { recursive: true });
|
|
4983
|
-
copyFileSync(join6(extractDir, target), join6(installDir, "elnora.exe"));
|
|
4984
|
-
} else {
|
|
4985
|
-
execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
|
|
4986
|
-
const installDir = process.env.ELNORA_INSTALL_DIR ?? join6(process.env.HOME ?? "", ".local", "bin");
|
|
4987
|
-
mkdirSync4(installDir, { recursive: true });
|
|
4988
|
-
const dest = join6(installDir, "elnora");
|
|
4989
|
-
copyFileSync(join6(tmp, target), dest);
|
|
4990
|
-
chmodSync2(dest, 493);
|
|
4991
|
-
}
|
|
4992
|
-
const done = color2 ? `${pc5.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
|
|
4993
|
-
console.error(done);
|
|
4994
|
-
} finally {
|
|
4995
|
-
try {
|
|
4996
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
4997
|
-
} catch {
|
|
4998
|
-
}
|
|
4999
|
-
}
|
|
5000
|
-
}
|
|
5001
|
-
function addUpdateCommand(program2) {
|
|
5002
|
-
program2.command("update").description("Check for updates (use --install to apply)").option("--install", "Download and install the update").action(async (opts) => {
|
|
5003
|
-
const color2 = isColorEnabled();
|
|
5004
|
-
const method = detectInstallMethod();
|
|
5005
|
-
let latest;
|
|
5006
|
-
try {
|
|
5007
|
-
latest = await fetchLatestVersion();
|
|
5008
|
-
} catch {
|
|
5009
|
-
console.error("Failed to check for updates.");
|
|
5010
|
-
process.exitCode = 1;
|
|
5011
|
-
return;
|
|
5012
|
-
}
|
|
5013
|
-
if (latest === "unknown") {
|
|
5014
|
-
console.error("Could not determine latest version.");
|
|
5015
|
-
process.exitCode = 1;
|
|
5016
|
-
return;
|
|
5017
|
-
}
|
|
5018
|
-
if (latest === VERSION || !isNewerVersion(latest, VERSION)) {
|
|
5019
|
-
const msg2 = color2 ? `${pc5.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
|
|
5020
|
-
console.error(msg2);
|
|
5021
|
-
return;
|
|
5022
|
-
}
|
|
5023
|
-
const instruction = getUpdateInstruction(method);
|
|
5024
|
-
if (!opts.install) {
|
|
5025
|
-
const msg2 = color2 ? `${pc5.yellow("Update available:")} v${VERSION} \u2192 v${latest}` : `Update available: v${VERSION} \u2192 v${latest}`;
|
|
5026
|
-
console.error(msg2);
|
|
5027
|
-
console.error(`Run: ${instruction}`);
|
|
5028
|
-
return;
|
|
5029
|
-
}
|
|
5030
|
-
const msg = color2 ? `Updating Elnora CLI ${pc5.dim(`v${VERSION}`)} \u2192 ${pc5.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
|
|
5031
|
-
console.error(msg);
|
|
5032
|
-
if (method === "npm") {
|
|
5033
|
-
console.error("Installed via npm. Run:");
|
|
5034
|
-
console.error(` npm install -g @elnora-ai/cli@latest`);
|
|
5035
|
-
return;
|
|
5036
|
-
}
|
|
5037
|
-
if (method === "homebrew") {
|
|
5038
|
-
console.error("Installed via Homebrew. Run:");
|
|
5039
|
-
console.error(` brew upgrade elnora`);
|
|
5040
|
-
return;
|
|
5041
|
-
}
|
|
5042
|
-
try {
|
|
5043
|
-
await installBinaryUpdate(latest);
|
|
5044
|
-
} catch {
|
|
5045
|
-
console.error("Update failed. Try manually:");
|
|
5046
|
-
console.error(
|
|
5047
|
-
process.platform === "win32" ? " irm https://cli.elnora.ai/install.ps1 | iex" : " curl -fsSL https://cli.elnora.ai/install.sh | bash"
|
|
5048
|
-
);
|
|
5049
|
-
process.exitCode = 1;
|
|
5050
|
-
}
|
|
5051
|
-
});
|
|
5052
|
-
}
|
|
5053
|
-
|
|
5054
|
-
// src/commands/whoami.ts
|
|
5055
|
-
init_client();
|
|
5056
|
-
init_profiles();
|
|
5057
|
-
function addWhoamiCommand(program2) {
|
|
5058
|
-
program2.command("whoami").description("Show current authentication context").action(async () => {
|
|
5059
|
-
const parentOpts = program2.opts();
|
|
5060
|
-
const profileName = parentOpts.profile ?? "default";
|
|
5061
|
-
try {
|
|
5062
|
-
const hint = resolveCredentialHint(profileName);
|
|
5063
|
-
let orgName = "unknown";
|
|
5064
|
-
try {
|
|
5065
|
-
const client = new ElnoraApiClient(resolveApiKey(profileName));
|
|
5066
|
-
const orgs = await client.get("organizations");
|
|
5067
|
-
const orgList = Array.isArray(orgs) ? orgs : orgs?.items ?? [];
|
|
5068
|
-
if (orgList.length > 0) orgName = orgList[0]?.name ?? "unknown";
|
|
5069
|
-
} catch {
|
|
5070
|
-
}
|
|
5071
|
-
if (parentOpts.json) {
|
|
5072
|
-
console.log(JSON.stringify({ profile: profileName, credential: hint, orgName }, null, 2));
|
|
5073
|
-
} else {
|
|
5074
|
-
console.log(`
|
|
5075
|
-
Profile: ${profileName}`);
|
|
5076
|
-
console.log(`Credential: ${hint}`);
|
|
5077
|
-
console.log(`Org: ${orgName}`);
|
|
5078
|
-
console.log("");
|
|
5079
|
-
}
|
|
5080
|
-
} catch (e) {
|
|
5081
|
-
console.error(e instanceof Error ? e.message : "Not authenticated");
|
|
5082
|
-
console.error("Run: elnora auth login");
|
|
5083
|
-
process.exit(3);
|
|
5084
|
-
}
|
|
5085
|
-
});
|
|
5086
|
-
}
|
|
5087
|
-
|
|
5088
5313
|
// src/core/registry.ts
|
|
5089
5314
|
var CommandRegistry = class {
|
|
5090
5315
|
commands = /* @__PURE__ */ new Map();
|
|
@@ -5112,6 +5337,34 @@ var CommandRegistry = class {
|
|
|
5112
5337
|
}
|
|
5113
5338
|
};
|
|
5114
5339
|
|
|
5340
|
+
// src/core/build-registry.ts
|
|
5341
|
+
function buildRegistry() {
|
|
5342
|
+
const registry2 = new CommandRegistry();
|
|
5343
|
+
const commandGroups = [
|
|
5344
|
+
[healthCheck],
|
|
5345
|
+
registerAuthCommands(),
|
|
5346
|
+
registerProjectCommands(),
|
|
5347
|
+
registerTaskCommands(),
|
|
5348
|
+
registerFileCommands(),
|
|
5349
|
+
registerOrgCommands(),
|
|
5350
|
+
registerFolderCommands(),
|
|
5351
|
+
registerSearchCommands(),
|
|
5352
|
+
registerProtocolCommands(),
|
|
5353
|
+
registerLibraryCommands(),
|
|
5354
|
+
registerAccountCommands(),
|
|
5355
|
+
registerApiKeyCommands(),
|
|
5356
|
+
registerAuditCommands(),
|
|
5357
|
+
registerFeedbackCommands(),
|
|
5358
|
+
registerFlagCommands()
|
|
5359
|
+
];
|
|
5360
|
+
for (const commands of commandGroups) {
|
|
5361
|
+
for (const cmd of commands) {
|
|
5362
|
+
registry2.register(cmd);
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
5365
|
+
return registry2;
|
|
5366
|
+
}
|
|
5367
|
+
|
|
5115
5368
|
// src/cli.ts
|
|
5116
5369
|
init_errors();
|
|
5117
5370
|
process.on("uncaughtException", (err) => {
|
|
@@ -5135,28 +5388,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
5135
5388
|
}
|
|
5136
5389
|
process.exitCode = 1;
|
|
5137
5390
|
});
|
|
5138
|
-
var registry =
|
|
5139
|
-
var commandGroups = [
|
|
5140
|
-
[healthCheck],
|
|
5141
|
-
registerAuthCommands(),
|
|
5142
|
-
registerProjectCommands(),
|
|
5143
|
-
registerTaskCommands(),
|
|
5144
|
-
registerFileCommands(),
|
|
5145
|
-
registerOrgCommands(),
|
|
5146
|
-
registerFolderCommands(),
|
|
5147
|
-
registerSearchCommands(),
|
|
5148
|
-
registerLibraryCommands(),
|
|
5149
|
-
registerAccountCommands(),
|
|
5150
|
-
registerApiKeyCommands(),
|
|
5151
|
-
registerAuditCommands(),
|
|
5152
|
-
registerFeedbackCommands(),
|
|
5153
|
-
registerFlagCommands()
|
|
5154
|
-
];
|
|
5155
|
-
for (const commands of commandGroups) {
|
|
5156
|
-
for (const cmd of commands) {
|
|
5157
|
-
registry.register(cmd);
|
|
5158
|
-
}
|
|
5159
|
-
}
|
|
5391
|
+
var registry = buildRegistry();
|
|
5160
5392
|
var program = buildProgram(registry);
|
|
5161
5393
|
addMcpCommands(program, registry);
|
|
5162
5394
|
addDoctorCommand(program);
|