@elnora-ai/cli 1.4.0 → 1.5.3
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 +1877 -1774
- 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.3" : 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,1298 +1132,1833 @@ 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
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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);
|
|
1183
|
-
}
|
|
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);
|
|
1202
|
-
}
|
|
1203
|
-
};
|
|
1204
|
-
|
|
1205
|
-
// src/commands/account/delete.ts
|
|
1206
|
-
import { z as z4 } from "zod";
|
|
1207
|
-
|
|
1208
|
-
// src/commands/_shared/confirm.ts
|
|
1209
|
-
import { createInterface } from "node:readline/promises";
|
|
1210
|
-
async function confirmDestructive(prompt, requiredInput) {
|
|
1211
|
-
if (!process.stdin.isTTY) return false;
|
|
1212
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
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();
|
|
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
|
+
}
|
|
1219
1171
|
}
|
|
1172
|
+
lines.push(" esac");
|
|
1173
|
+
lines.push("}");
|
|
1174
|
+
lines.push("");
|
|
1175
|
+
lines.push("complete -F _elnora elnora");
|
|
1176
|
+
return lines.join("\n");
|
|
1220
1177
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
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, "")}'`);
|
|
1238
1206
|
}
|
|
1207
|
+
lines.push(" )");
|
|
1208
|
+
lines.push(" _describe 'subcommand' subcommands");
|
|
1209
|
+
lines.push(" ;;");
|
|
1239
1210
|
}
|
|
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);
|
|
1246
1211
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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, '\\"')}"`
|
|
1266
1234
|
);
|
|
1267
|
-
if (!confirmed) {
|
|
1268
|
-
return { deleted: false, versionId: input.versionId };
|
|
1269
|
-
}
|
|
1270
1235
|
}
|
|
1271
|
-
await ctx.client.del("legal_doc_version_id", {
|
|
1272
|
-
pathParams: { id: input.versionId }
|
|
1273
|
-
});
|
|
1274
|
-
return { deleted: true, versionId: input.versionId };
|
|
1275
|
-
},
|
|
1276
|
-
formatOutput(output, format) {
|
|
1277
|
-
if (format === "compact") return output.versionId;
|
|
1278
|
-
return JSON.stringify(output, null, 2);
|
|
1279
1236
|
}
|
|
1280
|
-
|
|
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
|
+
}
|
|
1281
1258
|
|
|
1282
|
-
// src/commands/
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
description: "Get account details for a user",
|
|
1291
|
-
inputSchema: inputSchema6,
|
|
1292
|
-
outputSchema: z6.any(),
|
|
1293
|
-
annotations: { readOnlyHint: true },
|
|
1294
|
-
async execute(input, ctx) {
|
|
1295
|
-
return ctx.client.get("account_user", {
|
|
1296
|
-
pathParams: { id: input.userId }
|
|
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
|
-
};
|
|
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";
|
|
1304
1267
|
|
|
1305
|
-
// src/
|
|
1306
|
-
|
|
1307
|
-
import {
|
|
1308
|
-
|
|
1309
|
-
|
|
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");
|
|
1325
|
-
}
|
|
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
|
-
}
|
|
1334
|
-
};
|
|
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";
|
|
1335
1273
|
|
|
1336
|
-
// src/
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
name: "account.updateLegalDoc",
|
|
1346
|
-
group: "account",
|
|
1347
|
-
description: "Update a legal document version (SystemAdmin only)",
|
|
1348
|
-
inputSchema: inputSchema8,
|
|
1349
|
-
outputSchema: z8.any(),
|
|
1350
|
-
async execute(input, ctx) {
|
|
1351
|
-
const body = {};
|
|
1352
|
-
if (input.content !== void 0) body.content = input.content;
|
|
1353
|
-
if (input.effectiveDate !== void 0) body.effectiveDate = input.effectiveDate;
|
|
1354
|
-
if (Object.keys(body).length === 0) {
|
|
1355
|
-
throw new ValidationError("At least one of --content or --effectiveDate is required");
|
|
1356
|
-
}
|
|
1357
|
-
return ctx.client.patch("legal_doc_version_id", body, {
|
|
1358
|
-
pathParams: { id: input.versionId }
|
|
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
|
-
};
|
|
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
|
+
}
|
|
1366
1283
|
|
|
1367
|
-
// src/
|
|
1368
|
-
|
|
1369
|
-
var
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
var
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
return
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
formatOutput(output, format) {
|
|
1389
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1390
|
-
return JSON.stringify(output, null, 2);
|
|
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 MARKETPLACE_GITHUB_URL = `https://github.com/${MARKETPLACE_REPO}`;
|
|
1297
|
+
var PLUGIN_ID = `elnora@${MARKETPLACE_NAME}`;
|
|
1298
|
+
function readJsonFile(path) {
|
|
1299
|
+
if (!existsSync2(path)) return {};
|
|
1300
|
+
try {
|
|
1301
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
1302
|
+
} catch (err) {
|
|
1303
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1304
|
+
throw new Error(`Cannot parse JSON config at ${path}: ${reason}`);
|
|
1391
1305
|
}
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1306
|
+
}
|
|
1307
|
+
function writeJsonFile(path, data) {
|
|
1308
|
+
mkdirSync2(join2(path, ".."), { recursive: true });
|
|
1309
|
+
writeFileSync2(path, `${JSON.stringify(data, null, 2)}
|
|
1310
|
+
`, "utf-8");
|
|
1311
|
+
}
|
|
1312
|
+
var color = isColorEnabled();
|
|
1313
|
+
function ok(msg) {
|
|
1314
|
+
return color ? ` ${pc.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
1315
|
+
}
|
|
1316
|
+
function fail(msg) {
|
|
1317
|
+
return color ? ` ${pc.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
1318
|
+
}
|
|
1319
|
+
function info(msg) {
|
|
1320
|
+
return color ? ` ${pc.dim(msg)}` : ` ${msg}`;
|
|
1321
|
+
}
|
|
1322
|
+
function detectPlatforms() {
|
|
1396
1323
|
return [
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
accountDelete,
|
|
1402
|
-
accountUsers,
|
|
1403
|
-
accountAddLegalDoc,
|
|
1404
|
-
accountUpdateLegalDoc,
|
|
1405
|
-
accountDeleteLegalDoc
|
|
1324
|
+
{ name: "claude", label: "Claude Code", installed: existsSync2(CLAUDE_DIR) },
|
|
1325
|
+
{ name: "cursor", label: "Cursor", installed: existsSync2(CURSOR_DIR) },
|
|
1326
|
+
{ name: "vscode", label: "VS Code", installed: existsSync2(VSCODE_DIR) },
|
|
1327
|
+
{ name: "codex", label: "OpenAI Codex", installed: existsSync2(CODEX_DIR) }
|
|
1406
1328
|
];
|
|
1407
1329
|
}
|
|
1408
1330
|
|
|
1409
|
-
// src/
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
var
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1331
|
+
// src/lib/update-check.ts
|
|
1332
|
+
init_config();
|
|
1333
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1334
|
+
import { homedir as homedir3 } from "node:os";
|
|
1335
|
+
import { join as join3 } from "node:path";
|
|
1336
|
+
import pc2 from "picocolors";
|
|
1337
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
1338
|
+
var CACHE_DIR = join3(homedir3(), ".elnora");
|
|
1339
|
+
var CACHE_FILE = join3(CACHE_DIR, ".update-check");
|
|
1340
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1341
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
1342
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/;
|
|
1343
|
+
function shouldSkipEntirely() {
|
|
1344
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
|
|
1345
|
+
if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
|
|
1346
|
+
return false;
|
|
1347
|
+
}
|
|
1348
|
+
function readCache() {
|
|
1349
|
+
try {
|
|
1350
|
+
if (!existsSync3(CACHE_FILE)) return null;
|
|
1351
|
+
const raw = readFileSync3(CACHE_FILE, "utf-8");
|
|
1352
|
+
return JSON.parse(raw);
|
|
1353
|
+
} catch {
|
|
1354
|
+
return null;
|
|
1428
1355
|
}
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
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);
|
|
1356
|
+
}
|
|
1357
|
+
function writeCache(entry) {
|
|
1358
|
+
try {
|
|
1359
|
+
mkdirSync3(CACHE_DIR, { recursive: true });
|
|
1360
|
+
writeFileSync3(CACHE_FILE, JSON.stringify(entry), "utf-8");
|
|
1361
|
+
} catch {
|
|
1447
1362
|
}
|
|
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")
|
|
1497
|
-
});
|
|
1498
|
-
var apiKeysSetPolicy = {
|
|
1499
|
-
name: "api-keys.setPolicy",
|
|
1500
|
-
group: "api-keys",
|
|
1501
|
-
description: "Set the API key creation policy",
|
|
1502
|
-
inputSchema: inputSchema14,
|
|
1503
|
-
outputSchema: z14.any(),
|
|
1504
|
-
async execute(input, ctx) {
|
|
1505
|
-
return ctx.client.put("api_key_policy", { policy: input.policy });
|
|
1506
|
-
},
|
|
1507
|
-
formatOutput(output, format) {
|
|
1508
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1509
|
-
return JSON.stringify(output, null, 2);
|
|
1510
|
-
}
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
|
-
// src/commands/api-keys/index.ts
|
|
1514
|
-
function registerApiKeyCommands() {
|
|
1515
|
-
return [apiKeysCreate, apiKeysList, apiKeysRevoke, apiKeysGetPolicy, apiKeysSetPolicy];
|
|
1516
1363
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
group: "audit",
|
|
1538
|
-
description: "List audit log entries for an organization",
|
|
1539
|
-
inputSchema: inputSchema15,
|
|
1540
|
-
outputSchema: z16.any(),
|
|
1541
|
-
annotations: { readOnlyHint: true },
|
|
1542
|
-
async execute(input, ctx) {
|
|
1543
|
-
const queryParams = {
|
|
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
|
-
});
|
|
1553
|
-
},
|
|
1554
|
-
formatOutput(output, format) {
|
|
1555
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1556
|
-
return JSON.stringify(output, null, 2);
|
|
1364
|
+
function showUpdateNotice(latest) {
|
|
1365
|
+
if (!process.stderr.isTTY) return;
|
|
1366
|
+
const color2 = isColorEnabled();
|
|
1367
|
+
const msg = color2 ? `
|
|
1368
|
+
${pc2.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
|
|
1369
|
+
Run: elnora update
|
|
1370
|
+
` : `
|
|
1371
|
+
Update available: v${VERSION} \u2192 v${latest}
|
|
1372
|
+
Run: elnora update
|
|
1373
|
+
`;
|
|
1374
|
+
process.stderr.write(msg);
|
|
1375
|
+
}
|
|
1376
|
+
function isNewerVersion(a, b) {
|
|
1377
|
+
const pa = a.split(".").map(Number);
|
|
1378
|
+
const pb = b.split(".").map(Number);
|
|
1379
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1380
|
+
const na = pa[i] ?? 0;
|
|
1381
|
+
const nb = pb[i] ?? 0;
|
|
1382
|
+
if (na > nb) return true;
|
|
1383
|
+
if (na < nb) return false;
|
|
1557
1384
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
// src/commands/audit/index.ts
|
|
1561
|
-
function registerAuditCommands() {
|
|
1562
|
-
return [auditList];
|
|
1385
|
+
return false;
|
|
1563
1386
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
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
|
-
});
|
|
1387
|
+
function registerUpdateCheck() {
|
|
1388
|
+
if (shouldSkipEntirely()) return;
|
|
1389
|
+
const cached = readCache();
|
|
1390
|
+
if (cached) {
|
|
1391
|
+
const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
|
|
1392
|
+
if (elapsed < CHECK_INTERVAL_MS) {
|
|
1393
|
+
if (cached.latest && cached.latest !== VERSION && cached.latest !== "unknown") {
|
|
1394
|
+
if (isNewerVersion(cached.latest, VERSION)) {
|
|
1395
|
+
showUpdateNotice(cached.latest);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1582
1398
|
return;
|
|
1583
1399
|
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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
|
-
}
|
|
1400
|
+
}
|
|
1401
|
+
const keepAlive = setInterval(() => {
|
|
1402
|
+
}, 6e4);
|
|
1403
|
+
(async () => {
|
|
1404
|
+
try {
|
|
1405
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
1406
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1407
|
+
});
|
|
1408
|
+
if (!response.ok) return;
|
|
1409
|
+
const data = await response.json();
|
|
1410
|
+
const latest = data.version ?? "unknown";
|
|
1411
|
+
if (latest !== "unknown" && !SEMVER_RE.test(latest)) return;
|
|
1412
|
+
writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest });
|
|
1413
|
+
if (latest !== VERSION && latest !== "unknown" && isNewerVersion(latest, VERSION)) {
|
|
1414
|
+
showUpdateNotice(latest);
|
|
1625
1415
|
}
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1416
|
+
} catch {
|
|
1417
|
+
} finally {
|
|
1418
|
+
clearInterval(keepAlive);
|
|
1419
|
+
}
|
|
1420
|
+
})();
|
|
1629
1421
|
}
|
|
1630
1422
|
|
|
1631
|
-
// src/
|
|
1632
|
-
|
|
1633
|
-
|
|
1423
|
+
// src/commands/doctor.ts
|
|
1424
|
+
var EXPECTED_SKILLS = [
|
|
1425
|
+
"elnora-admin",
|
|
1426
|
+
"elnora-agent",
|
|
1427
|
+
"elnora-files",
|
|
1428
|
+
"elnora-folders",
|
|
1429
|
+
"elnora-orgs",
|
|
1430
|
+
"elnora-platform",
|
|
1431
|
+
"elnora-projects",
|
|
1432
|
+
"elnora-search",
|
|
1433
|
+
"elnora-tasks"
|
|
1434
|
+
];
|
|
1435
|
+
async function runCheck(fn) {
|
|
1436
|
+
try {
|
|
1437
|
+
return await fn();
|
|
1438
|
+
} catch (e) {
|
|
1439
|
+
return { status: "fail", msg: `${e instanceof Error ? e.message : String(e)}` };
|
|
1440
|
+
}
|
|
1634
1441
|
}
|
|
1635
|
-
function
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1442
|
+
async function checkApiReachable() {
|
|
1443
|
+
const start = Date.now();
|
|
1444
|
+
const res = await fetch("https://platform.elnora.ai/health", { signal: AbortSignal.timeout(5e3) });
|
|
1445
|
+
const ms = Date.now() - start;
|
|
1446
|
+
if (res.ok) return { status: "pass", msg: `API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)` };
|
|
1447
|
+
return { status: "fail", msg: `API unreachable platform.elnora.ai (HTTP ${res.status})` };
|
|
1639
1448
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
});
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
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}` : ""}
|
|
1680
|
-
|
|
1681
|
-
`
|
|
1682
|
-
);
|
|
1683
|
-
return null;
|
|
1684
|
-
} catch {
|
|
1685
|
-
process.stderr.write(" key is invalid or expired.\n\n");
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
if (isTTY()) {
|
|
1690
|
-
if (profileExists) {
|
|
1691
|
-
process.stderr.write(` Enter a new API key for profile "${profileName}"
|
|
1692
|
-
`);
|
|
1693
|
-
} else {
|
|
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}
|
|
1737
|
-
|
|
1738
|
-
`);
|
|
1739
|
-
}
|
|
1740
|
-
return { profile: profileName, verified: true, configPath };
|
|
1741
|
-
} catch (err) {
|
|
1742
|
-
if (isTTY()) {
|
|
1743
|
-
process.stderr.write("\n");
|
|
1744
|
-
}
|
|
1745
|
-
if (err instanceof AuthError) {
|
|
1746
|
-
throw new AuthError("API key is invalid or has been revoked. Please check your key and try again.", {
|
|
1747
|
-
suggestion: "Get a new key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1748
|
-
});
|
|
1749
|
-
}
|
|
1750
|
-
throw err;
|
|
1449
|
+
async function checkAuthenticated() {
|
|
1450
|
+
const key = resolveApiKey();
|
|
1451
|
+
const client = new ElnoraApiClient(key);
|
|
1452
|
+
const result = await client.get("projects", {
|
|
1453
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
1454
|
+
});
|
|
1455
|
+
const count = result?.totalCount ?? result?.items?.length ?? 0;
|
|
1456
|
+
return { status: "pass", msg: `Authenticated profile: default (${count} projects accessible)` };
|
|
1457
|
+
}
|
|
1458
|
+
async function checkVersionCurrent() {
|
|
1459
|
+
try {
|
|
1460
|
+
const res = await fetch("https://registry.npmjs.org/@elnora-ai/cli/latest", {
|
|
1461
|
+
signal: AbortSignal.timeout(3e3)
|
|
1462
|
+
});
|
|
1463
|
+
if (!res.ok) return { status: "pass", msg: `Version v${VERSION} (registry check failed)` };
|
|
1464
|
+
const data = await res.json();
|
|
1465
|
+
const latest = data.version ?? "unknown";
|
|
1466
|
+
if (latest === VERSION || latest === "unknown" || !isNewerVersion(latest, VERSION)) {
|
|
1467
|
+
return { status: "pass", msg: `Version current v${VERSION} (latest: v${latest})` };
|
|
1751
1468
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
if (format === "compact") return data.profile ?? JSON.stringify(output);
|
|
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");
|
|
1469
|
+
return { status: "warn", msg: `Update available v${VERSION} \u2192 v${latest}` };
|
|
1470
|
+
} catch {
|
|
1471
|
+
return { status: "pass", msg: `Version v${VERSION} (registry unreachable)` };
|
|
1765
1472
|
}
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
import { z as z18 } from "zod";
|
|
1771
|
-
var inputSchema17 = z18.object({
|
|
1772
|
-
profile: z18.string().optional().describe("Profile name to remove (default: 'default')"),
|
|
1773
|
-
all: z18.boolean().default(false).describe("Remove all profiles")
|
|
1774
|
-
});
|
|
1775
|
-
var authLogout = {
|
|
1776
|
-
name: "auth.logout",
|
|
1777
|
-
group: "auth",
|
|
1778
|
-
description: "Remove saved credentials",
|
|
1779
|
-
inputSchema: inputSchema17,
|
|
1780
|
-
outputSchema: z18.any(),
|
|
1781
|
-
annotations: { destructiveHint: true },
|
|
1782
|
-
async execute(input, _ctx) {
|
|
1783
|
-
if (input.all) {
|
|
1784
|
-
const names = listProfileNames();
|
|
1785
|
-
for (const name of names) {
|
|
1786
|
-
removeProfile(name);
|
|
1787
|
-
}
|
|
1788
|
-
return { loggedOut: true, profile: "all", removedCount: names.length };
|
|
1789
|
-
}
|
|
1790
|
-
const profileName = input.profile ?? "default";
|
|
1791
|
-
const removed = removeProfile(profileName);
|
|
1792
|
-
return { loggedOut: true, profile: profileName, removed };
|
|
1793
|
-
},
|
|
1794
|
-
formatOutput(output, format) {
|
|
1795
|
-
if (format === "compact") return output.profile ?? JSON.stringify(output);
|
|
1796
|
-
return JSON.stringify(output, null, 2);
|
|
1473
|
+
}
|
|
1474
|
+
function checkConfigPermissions() {
|
|
1475
|
+
if (process.platform === "win32") {
|
|
1476
|
+
return { status: "pass", msg: "Config permissions (skipped on Windows)" };
|
|
1797
1477
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
// src/commands/auth/profiles.ts
|
|
1801
|
-
init_profiles();
|
|
1802
|
-
import { z as z19 } from "zod";
|
|
1803
|
-
var inputSchema18 = z19.object({});
|
|
1804
|
-
var authProfiles = {
|
|
1805
|
-
name: "auth.profiles",
|
|
1806
|
-
group: "auth",
|
|
1807
|
-
description: "List all configured profiles",
|
|
1808
|
-
inputSchema: inputSchema18,
|
|
1809
|
-
outputSchema: z19.any(),
|
|
1810
|
-
annotations: { readOnlyHint: true },
|
|
1811
|
-
async execute(_input, _ctx) {
|
|
1812
|
-
const profiles = loadProfiles();
|
|
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 };
|
|
1820
|
-
},
|
|
1821
|
-
formatOutput(output, format) {
|
|
1822
|
-
if (format === "compact") {
|
|
1823
|
-
const profiles = output.profiles ?? [];
|
|
1824
|
-
return profiles.map((p) => p.name).join(", ");
|
|
1825
|
-
}
|
|
1826
|
-
return JSON.stringify(output, null, 2);
|
|
1478
|
+
if (!existsSync4(PROFILES_FILE)) {
|
|
1479
|
+
return { status: "warn", msg: "Config permissions ~/.elnora/profiles.toml not found" };
|
|
1827
1480
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
import { z as z20 } from "zod";
|
|
1832
|
-
var inputSchema19 = z20.object({});
|
|
1833
|
-
var authStatus = {
|
|
1834
|
-
name: "auth.status",
|
|
1835
|
-
group: "auth",
|
|
1836
|
-
description: "Verify API key and show connection info",
|
|
1837
|
-
inputSchema: inputSchema19,
|
|
1838
|
-
outputSchema: z20.any(),
|
|
1839
|
-
annotations: { readOnlyHint: true },
|
|
1840
|
-
async execute(_input, ctx) {
|
|
1841
|
-
const result = await ctx.client.get("projects", {
|
|
1842
|
-
queryParams: { page: 1, pageSize: 1 }
|
|
1843
|
-
});
|
|
1844
|
-
const projectCount = result.totalCount ?? result.items?.length ?? 0;
|
|
1845
|
-
return { profile: ctx.profileName, authenticated: true, projectCount };
|
|
1846
|
-
},
|
|
1847
|
-
formatOutput(output, format) {
|
|
1848
|
-
if (format === "compact") return String(output.authenticated ?? "");
|
|
1849
|
-
return JSON.stringify(output, null, 2);
|
|
1481
|
+
const mode = statSync(PROFILES_FILE).mode & 511;
|
|
1482
|
+
if (mode === 384) {
|
|
1483
|
+
return { status: "pass", msg: `Config permissions ${PROFILES_FILE} (0600)` };
|
|
1850
1484
|
}
|
|
1851
|
-
};
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
});
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
return
|
|
1868
|
-
},
|
|
1869
|
-
formatOutput(output, format) {
|
|
1870
|
-
if (format === "compact") return JSON.stringify(output);
|
|
1871
|
-
return JSON.stringify(output, null, 2);
|
|
1485
|
+
return { status: "warn", msg: `Config permissions ${PROFILES_FILE} (0${mode.toString(8)}) \u2014 should be 0600` };
|
|
1486
|
+
}
|
|
1487
|
+
async function checkAiServerReachable() {
|
|
1488
|
+
const start = Date.now();
|
|
1489
|
+
const res = await fetch(`${AI_SERVER_URL}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
1490
|
+
const ms = Date.now() - start;
|
|
1491
|
+
const host = new URL(AI_SERVER_URL).hostname;
|
|
1492
|
+
if (res.ok) return { status: "pass", msg: `AI Server reachable ${host} (${res.status} OK, ${ms}ms)` };
|
|
1493
|
+
return { status: "fail", msg: `AI Server unreachable ${host} (HTTP ${res.status})` };
|
|
1494
|
+
}
|
|
1495
|
+
function checkPathConfigured() {
|
|
1496
|
+
const installDirs = [join4(homedir4(), ".local", "bin"), join4(homedir4(), ".elnora", "bin")];
|
|
1497
|
+
const pathVar = process.env.PATH ?? "";
|
|
1498
|
+
const entries = pathVar.split(delimiter).filter(Boolean);
|
|
1499
|
+
const matched = installDirs.find((d) => entries.includes(d));
|
|
1500
|
+
if (matched) {
|
|
1501
|
+
return { status: "pass", msg: `PATH configured ${matched}` };
|
|
1872
1502
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
return [authLogin, authStatus, authLogout, authProfiles, authValidate];
|
|
1503
|
+
return {
|
|
1504
|
+
status: "warn",
|
|
1505
|
+
msg: "PATH configured standard install dirs not in PATH (OK if installed via npm/brew)"
|
|
1506
|
+
};
|
|
1878
1507
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1508
|
+
function checkClaudePlugin() {
|
|
1509
|
+
if (!existsSync4(CLAUDE_DIR)) {
|
|
1510
|
+
return { status: "skip", msg: "Plugin enabled (Claude Code not installed)" };
|
|
1511
|
+
}
|
|
1512
|
+
if (!existsSync4(CLAUDE_SETTINGS_FILE)) {
|
|
1513
|
+
return { status: "fail", msg: "Plugin enabled settings.json not found \u2014 run 'elnora setup claude'" };
|
|
1514
|
+
}
|
|
1515
|
+
let settings;
|
|
1516
|
+
try {
|
|
1517
|
+
settings = JSON.parse(readFileSync4(CLAUDE_SETTINGS_FILE, "utf-8"));
|
|
1518
|
+
} catch (e) {
|
|
1519
|
+
return {
|
|
1520
|
+
status: "fail",
|
|
1521
|
+
msg: `Plugin enabled settings.json unreadable (${e instanceof Error ? e.message : "parse error"})`
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
const raw = settings.enabledPlugins;
|
|
1525
|
+
const enabledPlugins = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
1526
|
+
const isEnabled = enabledPlugins[PLUGIN_ID] === true;
|
|
1527
|
+
const legacyKeys = Object.keys(enabledPlugins).filter((k) => k.startsWith("elnora@") && k !== PLUGIN_ID);
|
|
1528
|
+
if (isEnabled && legacyKeys.length === 0) {
|
|
1529
|
+
return { status: "pass", msg: `Plugin enabled ${PLUGIN_ID}` };
|
|
1530
|
+
}
|
|
1531
|
+
if (isEnabled && legacyKeys.length > 0) {
|
|
1532
|
+
return {
|
|
1533
|
+
status: "warn",
|
|
1534
|
+
msg: `Plugin enabled ${PLUGIN_ID} \u2014 but stale legacy entries also present: ${legacyKeys.join(", ")}`
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
if (legacyKeys.length > 0) {
|
|
1538
|
+
return {
|
|
1539
|
+
status: "fail",
|
|
1540
|
+
msg: `Plugin enabled found legacy entries ${legacyKeys.join(", ")} but not ${PLUGIN_ID} \u2014 run 'elnora setup claude'`
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
return { status: "fail", msg: "Plugin enabled not found \u2014 run 'elnora setup claude'" };
|
|
1544
|
+
}
|
|
1545
|
+
function checkSkillsInstalled() {
|
|
1546
|
+
if (!existsSync4(CLAUDE_DIR)) {
|
|
1547
|
+
return { status: "skip", msg: "Skills installed (Claude Code not installed)" };
|
|
1548
|
+
}
|
|
1549
|
+
const skillsDir = join4(CLAUDE_DIR, "plugins", "marketplaces", MARKETPLACE_NAME, "elnora", "skills");
|
|
1550
|
+
if (!existsSync4(skillsDir)) {
|
|
1551
|
+
return {
|
|
1552
|
+
status: "warn",
|
|
1553
|
+
msg: "Skills installed marketplace not cloned yet \u2014 restart Claude Code to trigger clone"
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
const found = EXPECTED_SKILLS.filter((s) => existsSync4(join4(skillsDir, s, "SKILL.md")));
|
|
1557
|
+
if (found.length === EXPECTED_SKILLS.length) {
|
|
1558
|
+
return { status: "pass", msg: `Skills installed ${found.length} skills` };
|
|
1559
|
+
}
|
|
1560
|
+
const missing = EXPECTED_SKILLS.filter((s) => !found.includes(s));
|
|
1882
1561
|
return {
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
subcommands: cmd.commands.map(extractCommandTree),
|
|
1886
|
-
options: cmd.options.map((o) => ({
|
|
1887
|
-
flags: o.long ?? o.short ?? "",
|
|
1888
|
-
description: o.description
|
|
1889
|
-
}))
|
|
1562
|
+
status: "fail",
|
|
1563
|
+
msg: `Skills installed ${found.length}/${EXPECTED_SKILLS.length} \u2014 missing: ${missing.join(", ")}`
|
|
1890
1564
|
};
|
|
1891
1565
|
}
|
|
1892
|
-
function
|
|
1893
|
-
|
|
1894
|
-
"
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
lines.push(` COMPREPLY=( $(compgen -W "${subs}" -- "$cur") )`);
|
|
1914
|
-
lines.push(" ;;");
|
|
1566
|
+
function checkPluginVersion() {
|
|
1567
|
+
if (!existsSync4(CLAUDE_DIR)) {
|
|
1568
|
+
return { status: "skip", msg: "Plugin version (Claude Code not installed)" };
|
|
1569
|
+
}
|
|
1570
|
+
const pluginJsonPath = join4(
|
|
1571
|
+
CLAUDE_DIR,
|
|
1572
|
+
"plugins",
|
|
1573
|
+
"marketplaces",
|
|
1574
|
+
MARKETPLACE_NAME,
|
|
1575
|
+
"elnora",
|
|
1576
|
+
".claude-plugin",
|
|
1577
|
+
"plugin.json"
|
|
1578
|
+
);
|
|
1579
|
+
if (!existsSync4(pluginJsonPath)) {
|
|
1580
|
+
return { status: "skip", msg: "Plugin version (plugin not cloned yet)" };
|
|
1581
|
+
}
|
|
1582
|
+
try {
|
|
1583
|
+
const pj = JSON.parse(readFileSync4(pluginJsonPath, "utf-8"));
|
|
1584
|
+
const pluginVersion = pj.version ?? "unknown";
|
|
1585
|
+
if (pluginVersion === VERSION) {
|
|
1586
|
+
return { status: "pass", msg: `Plugin version v${pluginVersion} (matches CLI)` };
|
|
1915
1587
|
}
|
|
1588
|
+
return {
|
|
1589
|
+
status: "warn",
|
|
1590
|
+
msg: `Plugin version v${pluginVersion} (CLI is v${VERSION}) \u2014 restart Claude Code to refresh`
|
|
1591
|
+
};
|
|
1592
|
+
} catch (e) {
|
|
1593
|
+
return {
|
|
1594
|
+
status: "fail",
|
|
1595
|
+
msg: `Plugin version plugin.json unreadable (${e instanceof Error ? e.message : "parse error"})`
|
|
1596
|
+
};
|
|
1916
1597
|
}
|
|
1917
|
-
lines.push(" esac");
|
|
1918
|
-
lines.push("}");
|
|
1919
|
-
lines.push("");
|
|
1920
|
-
lines.push("complete -F _elnora elnora");
|
|
1921
|
-
return lines.join("\n");
|
|
1922
1598
|
}
|
|
1923
|
-
function
|
|
1924
|
-
const
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1599
|
+
async function checkMcpReachable() {
|
|
1600
|
+
const start = Date.now();
|
|
1601
|
+
try {
|
|
1602
|
+
const res = await fetch("https://mcp.elnora.ai/mcp", {
|
|
1603
|
+
method: "HEAD",
|
|
1604
|
+
signal: AbortSignal.timeout(5e3)
|
|
1605
|
+
});
|
|
1606
|
+
const ms = Date.now() - start;
|
|
1607
|
+
if (res.status < 500) {
|
|
1608
|
+
return { status: "pass", msg: `Server reachable mcp.elnora.ai (${res.status}, ${ms}ms)` };
|
|
1609
|
+
}
|
|
1610
|
+
return { status: "fail", msg: `Server unreachable mcp.elnora.ai (HTTP ${res.status})` };
|
|
1611
|
+
} catch (e) {
|
|
1612
|
+
return {
|
|
1613
|
+
status: "fail",
|
|
1614
|
+
msg: `Server unreachable mcp.elnora.ai (${e instanceof Error ? e.message : "network error"})`
|
|
1615
|
+
};
|
|
1937
1616
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1617
|
+
}
|
|
1618
|
+
function addDoctorCommand(program2) {
|
|
1619
|
+
program2.command("doctor").description("Check Elnora CLI configuration and connectivity").action(async () => {
|
|
1620
|
+
const color2 = isColorEnabled();
|
|
1621
|
+
const ok2 = (msg) => color2 ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
1622
|
+
const fail2 = (msg) => color2 ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
1623
|
+
const warn = (msg) => color2 ? ` ${pc3.yellow("!")} ${msg}` : ` ! ${msg}`;
|
|
1624
|
+
const skip = (msg) => color2 ? ` ${pc3.dim("\u25CB")} ${msg}` : ` \u25CB ${msg}`;
|
|
1625
|
+
const section = (name) => color2 ? pc3.bold(` ${name}`) : ` ${name}`;
|
|
1626
|
+
const render = (r) => {
|
|
1627
|
+
switch (r.status) {
|
|
1628
|
+
case "pass":
|
|
1629
|
+
return ok2(r.msg);
|
|
1630
|
+
case "fail":
|
|
1631
|
+
return fail2(r.msg);
|
|
1632
|
+
case "warn":
|
|
1633
|
+
return warn(r.msg);
|
|
1634
|
+
case "skip":
|
|
1635
|
+
return skip(r.msg);
|
|
1951
1636
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1637
|
+
};
|
|
1638
|
+
console.error(`
|
|
1639
|
+
Elnora CLI v${VERSION}
|
|
1640
|
+
`);
|
|
1641
|
+
let passed = 0;
|
|
1642
|
+
let failed = 0;
|
|
1643
|
+
let warnings = 0;
|
|
1644
|
+
let total = 0;
|
|
1645
|
+
const tally = (r) => {
|
|
1646
|
+
total++;
|
|
1647
|
+
if (r.status === "pass" || r.status === "skip") passed++;
|
|
1648
|
+
else if (r.status === "warn") {
|
|
1649
|
+
passed++;
|
|
1650
|
+
warnings++;
|
|
1651
|
+
} else failed++;
|
|
1652
|
+
};
|
|
1653
|
+
console.error(section("CLI"));
|
|
1654
|
+
const cliChecks = [
|
|
1655
|
+
await runCheck(() => checkApiReachable()),
|
|
1656
|
+
await runCheck(() => checkAuthenticated()),
|
|
1657
|
+
await runCheck(() => checkVersionCurrent()),
|
|
1658
|
+
await runCheck(() => checkConfigPermissions()),
|
|
1659
|
+
await runCheck(() => checkAiServerReachable()),
|
|
1660
|
+
await runCheck(() => checkPathConfigured())
|
|
1661
|
+
];
|
|
1662
|
+
for (const r of cliChecks) {
|
|
1663
|
+
console.error(render(r));
|
|
1664
|
+
tally(r);
|
|
1955
1665
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1666
|
+
console.error("");
|
|
1667
|
+
console.error(section("Claude Code"));
|
|
1668
|
+
const claudeChecks = [
|
|
1669
|
+
await runCheck(() => checkClaudePlugin()),
|
|
1670
|
+
await runCheck(() => checkSkillsInstalled()),
|
|
1671
|
+
await runCheck(() => checkPluginVersion())
|
|
1672
|
+
];
|
|
1673
|
+
for (const r of claudeChecks) {
|
|
1674
|
+
console.error(render(r));
|
|
1675
|
+
tally(r);
|
|
1676
|
+
}
|
|
1677
|
+
console.error("");
|
|
1678
|
+
console.error(section("MCP"));
|
|
1679
|
+
const mcpChecks = [await runCheck(() => checkMcpReachable())];
|
|
1680
|
+
for (const r of mcpChecks) {
|
|
1681
|
+
console.error(render(r));
|
|
1682
|
+
tally(r);
|
|
1683
|
+
}
|
|
1684
|
+
console.error("");
|
|
1685
|
+
if (failed === 0 && warnings === 0) {
|
|
1686
|
+
console.error("All checks passed.");
|
|
1687
|
+
} else if (failed === 0) {
|
|
1688
|
+
console.error(`${passed}/${total} checks passed (${warnings} warning${warnings === 1 ? "" : "s"}).`);
|
|
1689
|
+
} else {
|
|
1690
|
+
const warnSuffix = warnings > 0 ? ` (${warnings} warning${warnings === 1 ? "" : "s"})` : "";
|
|
1691
|
+
console.error(`${passed}/${total} checks passed \u2014 ${failed} failed${warnSuffix}.`);
|
|
1692
|
+
if (!existsSync4(PROFILES_FILE)) console.error("\nRun: elnora auth login");
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1962
1695
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
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
|
-
);
|
|
1696
|
+
|
|
1697
|
+
// src/commands/mcp/serve.ts
|
|
1698
|
+
function requireEnv(name) {
|
|
1699
|
+
const value = process.env[name];
|
|
1700
|
+
if (!value) {
|
|
1701
|
+
throw new Error(`Required environment variable ${name} is not set`);
|
|
1973
1702
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1703
|
+
return value;
|
|
1704
|
+
}
|
|
1705
|
+
function addMcpCommands(program2, registry2) {
|
|
1706
|
+
const mcp = program2.command("mcp").description("MCP server management");
|
|
1707
|
+
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) => {
|
|
1708
|
+
if (opts.stdio) {
|
|
1709
|
+
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_stdio_server(), stdio_server_exports));
|
|
1710
|
+
await startStdioServer2(registry2);
|
|
1711
|
+
} else {
|
|
1712
|
+
const { startHttpServer: startHttpServer2 } = await Promise.resolve().then(() => (init_http_server(), http_server_exports));
|
|
1713
|
+
await startHttpServer2(registry2, {
|
|
1714
|
+
port: Number.parseInt(opts.port, 10),
|
|
1715
|
+
host: opts.host,
|
|
1716
|
+
tokenValidationUrl: requireEnv("ELNORA_TOKEN_VALIDATION_URL"),
|
|
1717
|
+
mcpServiceKey: requireEnv("ELNORA_MCP_SERVICE_KEY")
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
// src/commands/open.ts
|
|
1724
|
+
import { exec } from "node:child_process";
|
|
1725
|
+
var URLS = {
|
|
1726
|
+
"": "https://platform.elnora.ai",
|
|
1727
|
+
platform: "https://platform.elnora.ai",
|
|
1728
|
+
docs: "https://docs.elnora.ai",
|
|
1729
|
+
keys: "https://platform.elnora.ai/settings/api-keys",
|
|
1730
|
+
billing: "https://platform.elnora.ai/settings/billing",
|
|
1731
|
+
github: "https://github.com/Elnora-AI/elnora-cli"
|
|
1732
|
+
};
|
|
1733
|
+
function openUrl(url) {
|
|
1734
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
1735
|
+
exec(cmd, (err) => {
|
|
1736
|
+
if (err) {
|
|
1737
|
+
console.error(`Failed to open browser: ${err.message}`);
|
|
1738
|
+
console.log(url);
|
|
1980
1739
|
}
|
|
1981
|
-
}
|
|
1982
|
-
return lines.join("\n");
|
|
1740
|
+
});
|
|
1983
1741
|
}
|
|
1984
|
-
function
|
|
1985
|
-
program2.command("
|
|
1986
|
-
const
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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);
|
|
1742
|
+
function addOpenCommand(program2) {
|
|
1743
|
+
program2.command("open").argument("[page]", "Page to open: platform, docs, keys, billing, github").description("Open Elnora platform in the browser").action((page) => {
|
|
1744
|
+
const key = page ?? "";
|
|
1745
|
+
const url = URLS[key];
|
|
1746
|
+
if (!url) {
|
|
1747
|
+
console.error(`Unknown page: ${page}`);
|
|
1748
|
+
console.error(`Available: ${Object.keys(URLS).filter(Boolean).join(", ")}`);
|
|
1749
|
+
process.exit(1);
|
|
2000
1750
|
}
|
|
1751
|
+
openUrl(url);
|
|
1752
|
+
console.log(`Opening ${url}...`);
|
|
2001
1753
|
});
|
|
2002
1754
|
}
|
|
2003
1755
|
|
|
2004
|
-
// src/commands/
|
|
2005
|
-
init_client();
|
|
2006
|
-
init_config();
|
|
1756
|
+
// src/commands/setup.ts
|
|
2007
1757
|
init_profiles();
|
|
2008
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
2009
|
-
import { homedir as homedir4 } from "node:os";
|
|
2010
|
-
import { delimiter, join as join4 } from "node:path";
|
|
2011
|
-
import pc3 from "picocolors";
|
|
2012
1758
|
|
|
2013
|
-
// src/lib/setup/
|
|
2014
|
-
import {
|
|
2015
|
-
import {
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
1759
|
+
// src/lib/setup/claude.ts
|
|
1760
|
+
import { spawn } from "node:child_process";
|
|
1761
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1762
|
+
function runClaudeCli(args) {
|
|
1763
|
+
return new Promise((resolve, reject) => {
|
|
1764
|
+
const child = spawn("claude", args, {
|
|
1765
|
+
stdio: "inherit",
|
|
1766
|
+
shell: process.platform === "win32"
|
|
1767
|
+
});
|
|
1768
|
+
child.on("error", (err) => reject(err));
|
|
1769
|
+
child.on("close", (code) => {
|
|
1770
|
+
if (code === 0) {
|
|
1771
|
+
resolve();
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
const err = new Error(`claude ${args.join(" ")} exited with code ${code}`);
|
|
1775
|
+
err.code = code ?? -1;
|
|
1776
|
+
reject(err);
|
|
1777
|
+
});
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
async function setupClaude() {
|
|
1781
|
+
if (!existsSync5(CLAUDE_DIR)) {
|
|
1782
|
+
console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
|
|
1783
|
+
console.error("");
|
|
1784
|
+
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
console.error(ok("Claude Code found"));
|
|
2033
1788
|
try {
|
|
2034
|
-
|
|
2035
|
-
} catch {
|
|
2036
|
-
return
|
|
1789
|
+
await runClaudeCli(["plugin", "marketplace", "add", MARKETPLACE_GITHUB_URL]);
|
|
1790
|
+
} catch (err) {
|
|
1791
|
+
return reportClaudeCliError(err, "marketplace add");
|
|
1792
|
+
}
|
|
1793
|
+
try {
|
|
1794
|
+
await runClaudeCli(["plugin", "install", PLUGIN_ID]);
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
return reportClaudeCliError(err, "plugin install");
|
|
2037
1797
|
}
|
|
1798
|
+
console.error("");
|
|
1799
|
+
console.error(" Elnora skills are now available in Claude Code.");
|
|
1800
|
+
console.error(" Restart Claude Code if it's currently running.");
|
|
1801
|
+
return true;
|
|
2038
1802
|
}
|
|
2039
|
-
function
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
1803
|
+
function reportClaudeCliError(err, step) {
|
|
1804
|
+
const e = err;
|
|
1805
|
+
if (e?.code === "ENOENT") {
|
|
1806
|
+
console.error("");
|
|
1807
|
+
console.error(fail("`claude` not found on PATH"));
|
|
1808
|
+
console.error("");
|
|
1809
|
+
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
1810
|
+
console.error(" Then from a fresh terminal, run:");
|
|
1811
|
+
console.error(` claude plugin marketplace add ${MARKETPLACE_GITHUB_URL}`);
|
|
1812
|
+
console.error(` claude plugin install ${PLUGIN_ID}`);
|
|
1813
|
+
return false;
|
|
1814
|
+
}
|
|
1815
|
+
console.error("");
|
|
1816
|
+
console.error(fail(`Setup step "${step}" failed`));
|
|
1817
|
+
console.error(" To retry manually:");
|
|
1818
|
+
console.error(` claude plugin marketplace add ${MARKETPLACE_GITHUB_URL}`);
|
|
1819
|
+
console.error(` claude plugin install ${PLUGIN_ID}`);
|
|
1820
|
+
return false;
|
|
2043
1821
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
1822
|
+
|
|
1823
|
+
// src/lib/setup/codex.ts
|
|
1824
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1825
|
+
import { join as join5 } from "node:path";
|
|
1826
|
+
var CODEX_MCP_FILE = join5(CODEX_DIR, "mcp.json");
|
|
1827
|
+
function setupCodex(apiKey) {
|
|
1828
|
+
if (!existsSync6(CODEX_DIR)) {
|
|
1829
|
+
console.error(fail("Codex CLI not found (~/.codex/ does not exist)"));
|
|
1830
|
+
console.error("");
|
|
1831
|
+
console.error(" Install Codex first: https://github.com/openai/codex");
|
|
1832
|
+
return false;
|
|
1833
|
+
}
|
|
1834
|
+
console.error(ok("Codex CLI found"));
|
|
1835
|
+
let config;
|
|
1836
|
+
try {
|
|
1837
|
+
config = readJsonFile(CODEX_MCP_FILE);
|
|
1838
|
+
} catch (err) {
|
|
1839
|
+
console.error(fail(err instanceof Error ? err.message : String(err)));
|
|
1840
|
+
console.error(" Repair or delete the file and retry.");
|
|
1841
|
+
return false;
|
|
1842
|
+
}
|
|
1843
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
1844
|
+
config.mcpServers = {};
|
|
1845
|
+
}
|
|
1846
|
+
config.mcpServers.elnora = {
|
|
1847
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
1848
|
+
headers: {
|
|
1849
|
+
"X-API-Key": apiKey
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
writeJsonFile(CODEX_MCP_FILE, config);
|
|
1853
|
+
console.error(ok("MCP server configured in ~/.codex/mcp.json"));
|
|
1854
|
+
console.error("");
|
|
1855
|
+
console.error(" Elnora MCP server is now available in Codex.");
|
|
1856
|
+
return true;
|
|
2047
1857
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
1858
|
+
|
|
1859
|
+
// src/lib/setup/cursor.ts
|
|
1860
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1861
|
+
function setupCursor(apiKey) {
|
|
1862
|
+
if (!existsSync7(CURSOR_DIR)) {
|
|
1863
|
+
console.error(fail("Cursor not found (~/.cursor/ does not exist)"));
|
|
1864
|
+
console.error("");
|
|
1865
|
+
console.error(" Install Cursor first: https://cursor.com");
|
|
1866
|
+
return false;
|
|
1867
|
+
}
|
|
1868
|
+
console.error(ok("Cursor found"));
|
|
1869
|
+
let config;
|
|
1870
|
+
try {
|
|
1871
|
+
config = readJsonFile(CURSOR_MCP_GLOBAL);
|
|
1872
|
+
} catch (err) {
|
|
1873
|
+
console.error(fail(err instanceof Error ? err.message : String(err)));
|
|
1874
|
+
console.error(" Repair or delete the file and retry.");
|
|
1875
|
+
return false;
|
|
1876
|
+
}
|
|
1877
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
1878
|
+
config.mcpServers = {};
|
|
1879
|
+
}
|
|
1880
|
+
config.mcpServers.elnora = {
|
|
1881
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
1882
|
+
headers: {
|
|
1883
|
+
"X-API-Key": apiKey
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
writeJsonFile(CURSOR_MCP_GLOBAL, config);
|
|
1887
|
+
console.error(ok("MCP server configured in ~/.cursor/mcp.json"));
|
|
1888
|
+
console.error("");
|
|
1889
|
+
console.error(" Elnora MCP server is now available in Cursor.");
|
|
1890
|
+
console.error(" Restart Cursor if it's currently running.");
|
|
1891
|
+
return true;
|
|
2050
1892
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
1893
|
+
|
|
1894
|
+
// src/lib/setup/vscode.ts
|
|
1895
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1896
|
+
import { join as join6 } from "node:path";
|
|
1897
|
+
var VSCODE_MCP_GLOBAL = join6(HOME, ".vscode", "mcp.json");
|
|
1898
|
+
function setupVscode(apiKey) {
|
|
1899
|
+
if (!existsSync8(VSCODE_DIR)) {
|
|
1900
|
+
console.error(fail("VS Code not found (~/.vscode/ does not exist)"));
|
|
1901
|
+
console.error("");
|
|
1902
|
+
console.error(" Install VS Code first: https://code.visualstudio.com");
|
|
1903
|
+
return false;
|
|
1904
|
+
}
|
|
1905
|
+
console.error(ok("VS Code found"));
|
|
1906
|
+
let config;
|
|
1907
|
+
try {
|
|
1908
|
+
config = readJsonFile(VSCODE_MCP_GLOBAL);
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
console.error(fail(err instanceof Error ? err.message : String(err)));
|
|
1911
|
+
console.error(" Repair or delete the file and retry.");
|
|
1912
|
+
return false;
|
|
1913
|
+
}
|
|
1914
|
+
if (!config.servers || typeof config.servers !== "object") {
|
|
1915
|
+
config.servers = {};
|
|
1916
|
+
}
|
|
1917
|
+
config.servers.elnora = {
|
|
1918
|
+
type: "http",
|
|
1919
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
1920
|
+
headers: {
|
|
1921
|
+
"X-API-Key": apiKey
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
writeJsonFile(VSCODE_MCP_GLOBAL, config);
|
|
1925
|
+
console.error(ok("MCP server configured in ~/.vscode/mcp.json"));
|
|
1926
|
+
console.error("");
|
|
1927
|
+
console.error(" Elnora MCP server is now available in VS Code Copilot.");
|
|
1928
|
+
console.error(" Restart VS Code if it's currently running.");
|
|
1929
|
+
return true;
|
|
2053
1930
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
1931
|
+
|
|
1932
|
+
// src/commands/setup.ts
|
|
1933
|
+
var PLATFORM_SETUP = {
|
|
1934
|
+
claude: () => setupClaude(),
|
|
1935
|
+
cursor: async (key) => setupCursor(key),
|
|
1936
|
+
vscode: async (key) => setupVscode(key),
|
|
1937
|
+
codex: async (key) => setupCodex(key)
|
|
1938
|
+
};
|
|
1939
|
+
async function runSetup(platforms, profileName) {
|
|
1940
|
+
let apiKey;
|
|
1941
|
+
const needsKey = platforms.some((p) => p !== "claude");
|
|
1942
|
+
if (needsKey) {
|
|
1943
|
+
try {
|
|
1944
|
+
apiKey = resolveApiKey(profileName);
|
|
1945
|
+
} catch {
|
|
1946
|
+
if (platforms.every((p) => p !== "claude")) {
|
|
1947
|
+
console.error(fail("No API key found. Run 'elnora auth login' first."));
|
|
1948
|
+
process.exitCode = 1;
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
let anyFailed = false;
|
|
1954
|
+
for (const platform of platforms) {
|
|
1955
|
+
const setupFn = PLATFORM_SETUP[platform];
|
|
1956
|
+
const success = await setupFn(apiKey ?? "");
|
|
1957
|
+
if (!success) anyFailed = true;
|
|
1958
|
+
console.error("");
|
|
1959
|
+
}
|
|
1960
|
+
if (anyFailed) {
|
|
1961
|
+
process.exitCode = 1;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
function addSetupCommand(program2) {
|
|
1965
|
+
const setup = program2.command("setup").description("Configure AI coding tools to use Elnora").option("--profile <name>", "API key profile to use", "default");
|
|
1966
|
+
setup.command("claude").description("Set up Claude Code (skills + MCP server)").action(async () => {
|
|
1967
|
+
const opts = setup.opts();
|
|
1968
|
+
console.error("");
|
|
1969
|
+
await runSetup(["claude"], opts.profile);
|
|
1970
|
+
});
|
|
1971
|
+
setup.command("cursor").description("Set up Cursor (MCP server)").action(async () => {
|
|
1972
|
+
const opts = setup.opts();
|
|
1973
|
+
console.error("");
|
|
1974
|
+
await runSetup(["cursor"], opts.profile);
|
|
1975
|
+
});
|
|
1976
|
+
setup.command("vscode").description("Set up VS Code Copilot (MCP server)").action(async () => {
|
|
1977
|
+
const opts = setup.opts();
|
|
1978
|
+
console.error("");
|
|
1979
|
+
await runSetup(["vscode"], opts.profile);
|
|
1980
|
+
});
|
|
1981
|
+
setup.command("codex").description("Set up OpenAI Codex (MCP server)").action(async () => {
|
|
1982
|
+
const opts = setup.opts();
|
|
1983
|
+
console.error("");
|
|
1984
|
+
await runSetup(["codex"], opts.profile);
|
|
1985
|
+
});
|
|
1986
|
+
setup.action(async () => {
|
|
1987
|
+
const opts = setup.opts();
|
|
1988
|
+
console.error("");
|
|
1989
|
+
const detected = detectPlatforms();
|
|
1990
|
+
const installed = detected.filter((p) => p.installed);
|
|
1991
|
+
if (installed.length === 0) {
|
|
1992
|
+
console.error(fail("No supported AI coding tools detected."));
|
|
1993
|
+
console.error("");
|
|
1994
|
+
console.error(" Supported: Claude Code, Cursor, VS Code, OpenAI Codex");
|
|
1995
|
+
console.error(" Install one first, then run 'elnora setup' again.");
|
|
1996
|
+
process.exitCode = 1;
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
console.error(ok(`Detected ${installed.length} platform(s):`));
|
|
2000
|
+
for (const p of installed) {
|
|
2001
|
+
console.error(info(`${p.label}`));
|
|
2002
|
+
}
|
|
2003
|
+
console.error("");
|
|
2004
|
+
const platforms = installed.map((p) => p.name);
|
|
2005
|
+
await runSetup(platforms, opts.profile);
|
|
2006
|
+
});
|
|
2007
|
+
program2.command("setup-claude").description("Set up Claude Code (alias for 'setup claude')").option("--profile <name>", "API key profile to use", "default").action(async (opts) => {
|
|
2008
|
+
console.error("");
|
|
2009
|
+
await runSetup(["claude"], opts.profile);
|
|
2010
|
+
});
|
|
2061
2011
|
}
|
|
2062
2012
|
|
|
2063
|
-
// src/
|
|
2013
|
+
// src/commands/update.ts
|
|
2064
2014
|
init_config();
|
|
2065
|
-
import {
|
|
2066
|
-
import {
|
|
2067
|
-
import {
|
|
2068
|
-
import
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
function
|
|
2076
|
-
|
|
2077
|
-
if (
|
|
2078
|
-
|
|
2015
|
+
import { execFileSync } from "node:child_process";
|
|
2016
|
+
import { createHash } from "node:crypto";
|
|
2017
|
+
import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync } from "node:fs";
|
|
2018
|
+
import { tmpdir } from "node:os";
|
|
2019
|
+
import { join as join7 } from "node:path";
|
|
2020
|
+
import { Readable } from "node:stream";
|
|
2021
|
+
import { pipeline } from "node:stream/promises";
|
|
2022
|
+
import pc4 from "picocolors";
|
|
2023
|
+
|
|
2024
|
+
// src/lib/install-method.ts
|
|
2025
|
+
function detectInstallMethod() {
|
|
2026
|
+
const execPath = process.argv[1] ?? "";
|
|
2027
|
+
if (execPath.includes("node_modules")) {
|
|
2028
|
+
return "npm";
|
|
2029
|
+
}
|
|
2030
|
+
if (execPath.includes("/Cellar/") || execPath.includes("/homebrew/")) {
|
|
2031
|
+
return "homebrew";
|
|
2032
|
+
}
|
|
2033
|
+
return "binary";
|
|
2079
2034
|
}
|
|
2080
|
-
function
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2035
|
+
function getUpdateInstruction(method) {
|
|
2036
|
+
switch (method) {
|
|
2037
|
+
case "npm":
|
|
2038
|
+
return "npm install -g @elnora-ai/cli@latest";
|
|
2039
|
+
case "homebrew":
|
|
2040
|
+
return "brew upgrade elnora";
|
|
2041
|
+
case "binary":
|
|
2042
|
+
return "elnora update --install";
|
|
2087
2043
|
}
|
|
2088
2044
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2045
|
+
|
|
2046
|
+
// src/commands/update.ts
|
|
2047
|
+
var NPM_REGISTRY_URL2 = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
2048
|
+
var GITHUB_REPO = "Elnora-AI/elnora-cli";
|
|
2049
|
+
function getPlatformTarget() {
|
|
2050
|
+
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
2051
|
+
switch (process.platform) {
|
|
2052
|
+
case "darwin":
|
|
2053
|
+
return { target: `elnora-macos-${arch}`, ext: "tar.gz" };
|
|
2054
|
+
case "linux":
|
|
2055
|
+
return { target: `elnora-linux-${arch}`, ext: "tar.gz" };
|
|
2056
|
+
case "win32":
|
|
2057
|
+
return { target: `elnora-win-${arch}.exe`, ext: "zip" };
|
|
2058
|
+
default:
|
|
2059
|
+
return null;
|
|
2094
2060
|
}
|
|
2095
2061
|
}
|
|
2096
|
-
function
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
const
|
|
2100
|
-
|
|
2101
|
-
Run: elnora update
|
|
2102
|
-
` : `
|
|
2103
|
-
Update available: v${VERSION} \u2192 v${latest}
|
|
2104
|
-
Run: elnora update
|
|
2105
|
-
`;
|
|
2106
|
-
process.stderr.write(msg);
|
|
2062
|
+
async function fetchLatestVersion() {
|
|
2063
|
+
const res = await fetch(NPM_REGISTRY_URL2, { signal: AbortSignal.timeout(5e3) });
|
|
2064
|
+
if (!res.ok) throw new Error(`Registry returned HTTP ${res.status}`);
|
|
2065
|
+
const data = await res.json();
|
|
2066
|
+
return data.version ?? "unknown";
|
|
2107
2067
|
}
|
|
2108
|
-
function
|
|
2109
|
-
const
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
const nb = pb[i] ?? 0;
|
|
2114
|
-
if (na > nb) return true;
|
|
2115
|
-
if (na < nb) return false;
|
|
2116
|
-
}
|
|
2117
|
-
return false;
|
|
2068
|
+
async function downloadToFile(url, dest) {
|
|
2069
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(12e4), redirect: "follow" });
|
|
2070
|
+
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
|
2071
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
2072
|
+
await pipeline(nodeStream, createWriteStream(dest));
|
|
2118
2073
|
}
|
|
2119
|
-
function
|
|
2120
|
-
|
|
2121
|
-
const
|
|
2122
|
-
if (
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2074
|
+
async function installBinaryUpdate(latest) {
|
|
2075
|
+
const color2 = isColorEnabled();
|
|
2076
|
+
const platformInfo = getPlatformTarget();
|
|
2077
|
+
if (!platformInfo) {
|
|
2078
|
+
console.error(`Unsupported platform: ${process.platform}.`);
|
|
2079
|
+
console.error("Try: npm install -g @elnora-ai/cli@latest");
|
|
2080
|
+
process.exitCode = 1;
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
const { target, ext } = platformInfo;
|
|
2084
|
+
const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
|
|
2085
|
+
const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
|
|
2086
|
+
const tmp = join7(tmpdir(), `elnora-update-${Date.now()}`);
|
|
2087
|
+
mkdirSync4(tmp, { recursive: true });
|
|
2088
|
+
const archivePath = join7(tmp, `${target}.${ext}`);
|
|
2089
|
+
try {
|
|
2090
|
+
console.error("Downloading...");
|
|
2091
|
+
await downloadToFile(binaryUrl, archivePath);
|
|
2092
|
+
let checksumVerified = false;
|
|
2093
|
+
try {
|
|
2094
|
+
const checksumRes = await fetch(checksumUrl, { signal: AbortSignal.timeout(15e3) });
|
|
2095
|
+
if (checksumRes.ok) {
|
|
2096
|
+
const checksumText = await checksumRes.text();
|
|
2097
|
+
const expected = checksumText.split(/\s/)[0].trim();
|
|
2098
|
+
if (expected.length === 64) {
|
|
2099
|
+
const actual = createHash("sha256").update(readFileSync5(archivePath)).digest("hex");
|
|
2100
|
+
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
2101
|
+
console.error("Checksum verification failed. The download may be corrupted.");
|
|
2102
|
+
process.exitCode = 1;
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
checksumVerified = true;
|
|
2128
2106
|
}
|
|
2129
2107
|
}
|
|
2130
|
-
|
|
2108
|
+
} catch {
|
|
2109
|
+
}
|
|
2110
|
+
if (!checksumVerified) {
|
|
2111
|
+
console.error("Warning: Could not verify download checksum. Proceeding anyway.");
|
|
2112
|
+
}
|
|
2113
|
+
if (process.platform === "win32") {
|
|
2114
|
+
const extractDir = join7(tmp, "extracted");
|
|
2115
|
+
execFileSync("powershell", [
|
|
2116
|
+
"-NoProfile",
|
|
2117
|
+
"-Command",
|
|
2118
|
+
`Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
|
|
2119
|
+
]);
|
|
2120
|
+
const installDir = join7(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
2121
|
+
mkdirSync4(installDir, { recursive: true });
|
|
2122
|
+
copyFileSync(join7(extractDir, target), join7(installDir, "elnora.exe"));
|
|
2123
|
+
} else {
|
|
2124
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
|
|
2125
|
+
const installDir = process.env.ELNORA_INSTALL_DIR ?? join7(process.env.HOME ?? "", ".local", "bin");
|
|
2126
|
+
mkdirSync4(installDir, { recursive: true });
|
|
2127
|
+
const dest = join7(installDir, "elnora");
|
|
2128
|
+
copyFileSync(join7(tmp, target), dest);
|
|
2129
|
+
chmodSync2(dest, 493);
|
|
2130
|
+
}
|
|
2131
|
+
const done = color2 ? `${pc4.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
|
|
2132
|
+
console.error(done);
|
|
2133
|
+
} finally {
|
|
2134
|
+
try {
|
|
2135
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
2136
|
+
} catch {
|
|
2131
2137
|
}
|
|
2132
2138
|
}
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
(async () => {
|
|
2139
|
+
}
|
|
2140
|
+
function addUpdateCommand(program2) {
|
|
2141
|
+
program2.command("update").description("Check for updates (use --install to apply)").option("--install", "Download and install the update").action(async (opts) => {
|
|
2142
|
+
const color2 = isColorEnabled();
|
|
2143
|
+
const method = detectInstallMethod();
|
|
2144
|
+
let latest;
|
|
2136
2145
|
try {
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2146
|
+
latest = await fetchLatestVersion();
|
|
2147
|
+
} catch {
|
|
2148
|
+
console.error("Failed to check for updates.");
|
|
2149
|
+
process.exitCode = 1;
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
if (latest === "unknown") {
|
|
2153
|
+
console.error("Could not determine latest version.");
|
|
2154
|
+
process.exitCode = 1;
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
if (latest === VERSION || !isNewerVersion(latest, VERSION)) {
|
|
2158
|
+
const msg2 = color2 ? `${pc4.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
|
|
2159
|
+
console.error(msg2);
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const instruction = getUpdateInstruction(method);
|
|
2163
|
+
if (!opts.install) {
|
|
2164
|
+
const msg2 = color2 ? `${pc4.yellow("Update available:")} v${VERSION} \u2192 v${latest}` : `Update available: v${VERSION} \u2192 v${latest}`;
|
|
2165
|
+
console.error(msg2);
|
|
2166
|
+
console.error(`Run: ${instruction}`);
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
const msg = color2 ? `Updating Elnora CLI ${pc4.dim(`v${VERSION}`)} \u2192 ${pc4.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
|
|
2170
|
+
console.error(msg);
|
|
2171
|
+
if (method === "npm") {
|
|
2172
|
+
console.error("Installed via npm. Run:");
|
|
2173
|
+
console.error(` npm install -g @elnora-ai/cli@latest`);
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
if (method === "homebrew") {
|
|
2177
|
+
console.error("Installed via Homebrew. Run:");
|
|
2178
|
+
console.error(` brew upgrade elnora`);
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
try {
|
|
2182
|
+
await installBinaryUpdate(latest);
|
|
2183
|
+
} catch {
|
|
2184
|
+
console.error("Update failed. Try manually:");
|
|
2185
|
+
console.error(
|
|
2186
|
+
process.platform === "win32" ? " irm https://cli.elnora.ai/install.ps1 | iex" : " curl -fsSL https://cli.elnora.ai/install.sh | bash"
|
|
2187
|
+
);
|
|
2188
|
+
process.exitCode = 1;
|
|
2189
|
+
}
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
// src/commands/whoami.ts
|
|
2194
|
+
init_client();
|
|
2195
|
+
init_profiles();
|
|
2196
|
+
function addWhoamiCommand(program2) {
|
|
2197
|
+
program2.command("whoami").description("Show current authentication context").action(async () => {
|
|
2198
|
+
const parentOpts = program2.opts();
|
|
2199
|
+
const profileName = parentOpts.profile ?? "default";
|
|
2200
|
+
try {
|
|
2201
|
+
const hint = resolveCredentialHint(profileName);
|
|
2202
|
+
let orgName = "unknown";
|
|
2203
|
+
try {
|
|
2204
|
+
const client = new ElnoraApiClient(resolveApiKey(profileName));
|
|
2205
|
+
const orgs = await client.get("organizations");
|
|
2206
|
+
const orgList = Array.isArray(orgs) ? orgs : orgs?.items ?? [];
|
|
2207
|
+
if (orgList.length > 0) orgName = orgList[0]?.name ?? "unknown";
|
|
2208
|
+
} catch {
|
|
2147
2209
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2210
|
+
if (parentOpts.json) {
|
|
2211
|
+
console.log(JSON.stringify({ profile: profileName, credential: hint, orgName }, null, 2));
|
|
2212
|
+
} else {
|
|
2213
|
+
console.log(`
|
|
2214
|
+
Profile: ${profileName}`);
|
|
2215
|
+
console.log(`Credential: ${hint}`);
|
|
2216
|
+
console.log(`Org: ${orgName}`);
|
|
2217
|
+
console.log("");
|
|
2218
|
+
}
|
|
2219
|
+
} catch (e) {
|
|
2220
|
+
console.error(e instanceof Error ? e.message : "Not authenticated");
|
|
2221
|
+
console.error("Run: elnora auth login");
|
|
2222
|
+
process.exit(3);
|
|
2151
2223
|
}
|
|
2152
|
-
})();
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
// src/commands/doctor.ts
|
|
2156
|
-
var EXPECTED_SKILLS = [
|
|
2157
|
-
"elnora-admin",
|
|
2158
|
-
"elnora-agent",
|
|
2159
|
-
"elnora-files",
|
|
2160
|
-
"elnora-folders",
|
|
2161
|
-
"elnora-orgs",
|
|
2162
|
-
"elnora-platform",
|
|
2163
|
-
"elnora-projects",
|
|
2164
|
-
"elnora-search",
|
|
2165
|
-
"elnora-tasks"
|
|
2166
|
-
];
|
|
2167
|
-
async function runCheck(fn) {
|
|
2168
|
-
try {
|
|
2169
|
-
return await fn();
|
|
2170
|
-
} catch (e) {
|
|
2171
|
-
return { status: "fail", msg: `${e instanceof Error ? e.message : String(e)}` };
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
async function checkApiReachable() {
|
|
2175
|
-
const start = Date.now();
|
|
2176
|
-
const res = await fetch("https://platform.elnora.ai/health", { signal: AbortSignal.timeout(5e3) });
|
|
2177
|
-
const ms = Date.now() - start;
|
|
2178
|
-
if (res.ok) return { status: "pass", msg: `API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)` };
|
|
2179
|
-
return { status: "fail", msg: `API unreachable platform.elnora.ai (HTTP ${res.status})` };
|
|
2180
|
-
}
|
|
2181
|
-
async function checkAuthenticated() {
|
|
2182
|
-
const key = resolveApiKey();
|
|
2183
|
-
const client = new ElnoraApiClient(key);
|
|
2184
|
-
const result = await client.get("projects", {
|
|
2185
|
-
queryParams: { page: 1, pageSize: 1 }
|
|
2186
2224
|
});
|
|
2187
|
-
const count = result?.totalCount ?? result?.items?.length ?? 0;
|
|
2188
|
-
return { status: "pass", msg: `Authenticated profile: default (${count} projects accessible)` };
|
|
2189
2225
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2226
|
+
|
|
2227
|
+
// src/commands/account/accept-terms.ts
|
|
2228
|
+
import { z } from "zod";
|
|
2229
|
+
var inputSchema = z.object({
|
|
2230
|
+
documentVersionId: z.string().describe("Document version ID to accept")
|
|
2231
|
+
});
|
|
2232
|
+
var accountAcceptTerms = {
|
|
2233
|
+
name: "account.acceptTerms",
|
|
2234
|
+
group: "account",
|
|
2235
|
+
description: "Accept a user agreement / terms of service",
|
|
2236
|
+
inputSchema,
|
|
2237
|
+
outputSchema: z.any(),
|
|
2238
|
+
async execute(input, ctx) {
|
|
2239
|
+
return ctx.client.post("user_agreement", {
|
|
2240
|
+
documentVersionId: input.documentVersionId
|
|
2194
2241
|
});
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
return { status: "pass", msg: `Version current v${VERSION} (latest: v${latest})` };
|
|
2200
|
-
}
|
|
2201
|
-
return { status: "warn", msg: `Update available v${VERSION} \u2192 v${latest}` };
|
|
2202
|
-
} catch {
|
|
2203
|
-
return { status: "pass", msg: `Version v${VERSION} (registry unreachable)` };
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
function checkConfigPermissions() {
|
|
2207
|
-
if (process.platform === "win32") {
|
|
2208
|
-
return { status: "pass", msg: "Config permissions (skipped on Windows)" };
|
|
2242
|
+
},
|
|
2243
|
+
formatOutput(output, format) {
|
|
2244
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2245
|
+
return JSON.stringify(output, null, 2);
|
|
2209
2246
|
}
|
|
2210
|
-
|
|
2211
|
-
|
|
2247
|
+
};
|
|
2248
|
+
|
|
2249
|
+
// src/commands/account/add-legal-doc.ts
|
|
2250
|
+
import { z as z2 } from "zod";
|
|
2251
|
+
var inputSchema2 = z2.object({
|
|
2252
|
+
documentType: z2.string().min(1).describe("Document type"),
|
|
2253
|
+
version: z2.string().min(1).describe("Document version string"),
|
|
2254
|
+
content: z2.string().min(1).describe("Document content"),
|
|
2255
|
+
effectiveDate: z2.string().optional().describe("Effective date (ISO 8601)")
|
|
2256
|
+
});
|
|
2257
|
+
var accountAddLegalDoc = {
|
|
2258
|
+
name: "account.addLegalDoc",
|
|
2259
|
+
group: "account",
|
|
2260
|
+
description: "Add a legal document version (SystemAdmin only)",
|
|
2261
|
+
inputSchema: inputSchema2,
|
|
2262
|
+
outputSchema: z2.any(),
|
|
2263
|
+
async execute(input, ctx) {
|
|
2264
|
+
const body = {
|
|
2265
|
+
documentType: input.documentType,
|
|
2266
|
+
version: input.version,
|
|
2267
|
+
content: input.content
|
|
2268
|
+
};
|
|
2269
|
+
if (input.effectiveDate) body.effectiveDate = input.effectiveDate;
|
|
2270
|
+
return ctx.client.post("legal_doc_version", body);
|
|
2271
|
+
},
|
|
2272
|
+
formatOutput(output, format) {
|
|
2273
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2274
|
+
return JSON.stringify(output, null, 2);
|
|
2212
2275
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2276
|
+
};
|
|
2277
|
+
|
|
2278
|
+
// src/commands/account/agreements.ts
|
|
2279
|
+
import { z as z3 } from "zod";
|
|
2280
|
+
var inputSchema3 = z3.object({});
|
|
2281
|
+
var accountAgreements = {
|
|
2282
|
+
name: "account.agreements",
|
|
2283
|
+
group: "account",
|
|
2284
|
+
description: "List user agreements",
|
|
2285
|
+
inputSchema: inputSchema3,
|
|
2286
|
+
outputSchema: z3.any(),
|
|
2287
|
+
annotations: { readOnlyHint: true },
|
|
2288
|
+
async execute(_input, ctx) {
|
|
2289
|
+
return ctx.client.get("user_agreements");
|
|
2290
|
+
},
|
|
2291
|
+
formatOutput(output, format) {
|
|
2292
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2293
|
+
return JSON.stringify(output, null, 2);
|
|
2216
2294
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
return { status: "pass", msg: `PATH configured ${matched}` };
|
|
2295
|
+
};
|
|
2296
|
+
|
|
2297
|
+
// src/commands/account/delete.ts
|
|
2298
|
+
import { z as z4 } from "zod";
|
|
2299
|
+
|
|
2300
|
+
// src/commands/_shared/confirm.ts
|
|
2301
|
+
import { createInterface } from "node:readline/promises";
|
|
2302
|
+
async function confirmDestructive(prompt, requiredInput) {
|
|
2303
|
+
if (!process.stdin.isTTY) return false;
|
|
2304
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
2305
|
+
try {
|
|
2306
|
+
const answer = await rl.question(prompt);
|
|
2307
|
+
if (requiredInput) return answer.trim() === requiredInput;
|
|
2308
|
+
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
|
|
2309
|
+
} finally {
|
|
2310
|
+
rl.close();
|
|
2234
2311
|
}
|
|
2235
|
-
return {
|
|
2236
|
-
status: "warn",
|
|
2237
|
-
msg: "PATH configured standard install dirs not in PATH (OK if installed via npm/brew)"
|
|
2238
|
-
};
|
|
2239
2312
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2313
|
+
|
|
2314
|
+
// src/commands/account/delete.ts
|
|
2315
|
+
var inputSchema4 = z4.object({
|
|
2316
|
+
yes: z4.boolean().optional().default(false).describe("Skip confirmation prompt")
|
|
2317
|
+
});
|
|
2318
|
+
var accountDelete = {
|
|
2319
|
+
name: "account.delete",
|
|
2320
|
+
group: "account",
|
|
2321
|
+
description: "Delete your own account",
|
|
2322
|
+
inputSchema: inputSchema4,
|
|
2323
|
+
outputSchema: z4.any(),
|
|
2324
|
+
annotations: { destructiveHint: true },
|
|
2325
|
+
async execute(input, ctx) {
|
|
2326
|
+
if (!input.yes) {
|
|
2327
|
+
const confirmed = await confirmDestructive("Type DELETE to confirm account deletion: ", "DELETE");
|
|
2328
|
+
if (!confirmed) {
|
|
2329
|
+
return { deleted: false };
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
await ctx.client.del("account_delete");
|
|
2333
|
+
return { deleted: true };
|
|
2334
|
+
},
|
|
2335
|
+
formatOutput(output, format) {
|
|
2336
|
+
if (format === "compact") return String(output.deleted);
|
|
2337
|
+
return JSON.stringify(output, null, 2);
|
|
2243
2338
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2339
|
+
};
|
|
2340
|
+
|
|
2341
|
+
// src/commands/account/delete-legal-doc.ts
|
|
2342
|
+
import { z as z5 } from "zod";
|
|
2343
|
+
var inputSchema5 = z5.object({
|
|
2344
|
+
versionId: z5.string().describe("Legal document version ID"),
|
|
2345
|
+
yes: z5.boolean().optional().default(false).describe("Skip confirmation prompt")
|
|
2346
|
+
});
|
|
2347
|
+
var accountDeleteLegalDoc = {
|
|
2348
|
+
name: "account.deleteLegalDoc",
|
|
2349
|
+
group: "account",
|
|
2350
|
+
description: "Delete a legal document version (SystemAdmin only)",
|
|
2351
|
+
inputSchema: inputSchema5,
|
|
2352
|
+
outputSchema: z5.any(),
|
|
2353
|
+
annotations: { destructiveHint: true },
|
|
2354
|
+
async execute(input, ctx) {
|
|
2355
|
+
if (!input.yes) {
|
|
2356
|
+
const confirmed = await confirmDestructive(
|
|
2357
|
+
`Are you sure you want to delete legal document version ${input.versionId}? [y/N] `
|
|
2358
|
+
);
|
|
2359
|
+
if (!confirmed) {
|
|
2360
|
+
return { deleted: false, versionId: input.versionId };
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
await ctx.client.del("legal_doc_version_id", {
|
|
2364
|
+
pathParams: { id: input.versionId }
|
|
2365
|
+
});
|
|
2366
|
+
return { deleted: true, versionId: input.versionId };
|
|
2367
|
+
},
|
|
2368
|
+
formatOutput(output, format) {
|
|
2369
|
+
if (format === "compact") return output.versionId;
|
|
2370
|
+
return JSON.stringify(output, null, 2);
|
|
2246
2371
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2372
|
+
};
|
|
2373
|
+
|
|
2374
|
+
// src/commands/account/get.ts
|
|
2375
|
+
import { z as z6 } from "zod";
|
|
2376
|
+
var inputSchema6 = z6.object({
|
|
2377
|
+
userId: z6.string().describe("User ID")
|
|
2378
|
+
});
|
|
2379
|
+
var accountGet = {
|
|
2380
|
+
name: "account.get",
|
|
2381
|
+
group: "account",
|
|
2382
|
+
description: "Get account details for a user",
|
|
2383
|
+
inputSchema: inputSchema6,
|
|
2384
|
+
outputSchema: z6.any(),
|
|
2385
|
+
annotations: { readOnlyHint: true },
|
|
2386
|
+
async execute(input, ctx) {
|
|
2387
|
+
return ctx.client.get("account_user", {
|
|
2388
|
+
pathParams: { id: input.userId }
|
|
2389
|
+
});
|
|
2390
|
+
},
|
|
2391
|
+
formatOutput(output, format) {
|
|
2392
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2393
|
+
return JSON.stringify(output, null, 2);
|
|
2255
2394
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2395
|
+
};
|
|
2396
|
+
|
|
2397
|
+
// src/commands/account/update.ts
|
|
2398
|
+
init_errors();
|
|
2399
|
+
import { z as z7 } from "zod";
|
|
2400
|
+
var inputSchema7 = z7.object({
|
|
2401
|
+
userId: z7.string().describe("User ID"),
|
|
2402
|
+
firstName: z7.string().optional().describe("First name"),
|
|
2403
|
+
lastName: z7.string().optional().describe("Last name")
|
|
2404
|
+
});
|
|
2405
|
+
var accountUpdate = {
|
|
2406
|
+
name: "account.update",
|
|
2407
|
+
group: "account",
|
|
2408
|
+
description: "Update account details for a user",
|
|
2409
|
+
inputSchema: inputSchema7,
|
|
2410
|
+
outputSchema: z7.any(),
|
|
2411
|
+
async execute(input, ctx) {
|
|
2412
|
+
const body = {};
|
|
2413
|
+
if (input.firstName !== void 0) body.firstName = input.firstName;
|
|
2414
|
+
if (input.lastName !== void 0) body.lastName = input.lastName;
|
|
2415
|
+
if (Object.keys(body).length === 0) {
|
|
2416
|
+
throw new ValidationError("At least one of --firstName or --lastName is required");
|
|
2417
|
+
}
|
|
2418
|
+
return ctx.client.patch("account_user", body, {
|
|
2419
|
+
pathParams: { id: input.userId }
|
|
2420
|
+
});
|
|
2421
|
+
},
|
|
2422
|
+
formatOutput(output, format) {
|
|
2423
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2424
|
+
return JSON.stringify(output, null, 2);
|
|
2262
2425
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2426
|
+
};
|
|
2427
|
+
|
|
2428
|
+
// src/commands/account/update-legal-doc.ts
|
|
2429
|
+
init_errors();
|
|
2430
|
+
import { z as z8 } from "zod";
|
|
2431
|
+
var inputSchema8 = z8.object({
|
|
2432
|
+
versionId: z8.string().describe("Legal document version ID"),
|
|
2433
|
+
content: z8.string().optional().describe("Updated content"),
|
|
2434
|
+
effectiveDate: z8.string().optional().describe("Updated effective date (ISO 8601)")
|
|
2435
|
+
});
|
|
2436
|
+
var accountUpdateLegalDoc = {
|
|
2437
|
+
name: "account.updateLegalDoc",
|
|
2438
|
+
group: "account",
|
|
2439
|
+
description: "Update a legal document version (SystemAdmin only)",
|
|
2440
|
+
inputSchema: inputSchema8,
|
|
2441
|
+
outputSchema: z8.any(),
|
|
2442
|
+
async execute(input, ctx) {
|
|
2443
|
+
const body = {};
|
|
2444
|
+
if (input.content !== void 0) body.content = input.content;
|
|
2445
|
+
if (input.effectiveDate !== void 0) body.effectiveDate = input.effectiveDate;
|
|
2446
|
+
if (Object.keys(body).length === 0) {
|
|
2447
|
+
throw new ValidationError("At least one of --content or --effectiveDate is required");
|
|
2448
|
+
}
|
|
2449
|
+
return ctx.client.patch("legal_doc_version_id", body, {
|
|
2450
|
+
pathParams: { id: input.versionId }
|
|
2451
|
+
});
|
|
2452
|
+
},
|
|
2453
|
+
formatOutput(output, format) {
|
|
2454
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2455
|
+
return JSON.stringify(output, null, 2);
|
|
2268
2456
|
}
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2457
|
+
};
|
|
2458
|
+
|
|
2459
|
+
// src/commands/account/users.ts
|
|
2460
|
+
import { z as z9 } from "zod";
|
|
2461
|
+
var inputSchema9 = z9.object({
|
|
2462
|
+
state: z9.enum(["Active", "Pending", "Deleted"]).optional().describe("Filter by user state"),
|
|
2463
|
+
refCode: z9.string().optional().describe("Filter by referral code")
|
|
2464
|
+
});
|
|
2465
|
+
var accountUsers = {
|
|
2466
|
+
name: "account.users",
|
|
2467
|
+
group: "account",
|
|
2468
|
+
description: "List all users (admin)",
|
|
2469
|
+
inputSchema: inputSchema9,
|
|
2470
|
+
outputSchema: z9.any(),
|
|
2471
|
+
annotations: { readOnlyHint: true },
|
|
2472
|
+
async execute(input, ctx) {
|
|
2473
|
+
const queryParams = {};
|
|
2474
|
+
if (input.state) queryParams.state = input.state;
|
|
2475
|
+
if (input.refCode) queryParams.refCode = input.refCode;
|
|
2476
|
+
return ctx.client.get("account_users", {
|
|
2477
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : void 0
|
|
2478
|
+
});
|
|
2479
|
+
},
|
|
2480
|
+
formatOutput(output, format) {
|
|
2481
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2482
|
+
return JSON.stringify(output, null, 2);
|
|
2274
2483
|
}
|
|
2275
|
-
|
|
2484
|
+
};
|
|
2485
|
+
|
|
2486
|
+
// src/commands/account/index.ts
|
|
2487
|
+
function registerAccountCommands() {
|
|
2488
|
+
return [
|
|
2489
|
+
accountGet,
|
|
2490
|
+
accountUpdate,
|
|
2491
|
+
accountAgreements,
|
|
2492
|
+
accountAcceptTerms,
|
|
2493
|
+
accountDelete,
|
|
2494
|
+
accountUsers,
|
|
2495
|
+
accountAddLegalDoc,
|
|
2496
|
+
accountUpdateLegalDoc,
|
|
2497
|
+
accountDeleteLegalDoc
|
|
2498
|
+
];
|
|
2276
2499
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2500
|
+
|
|
2501
|
+
// src/commands/api-keys/create.ts
|
|
2502
|
+
import { z as z10 } from "zod";
|
|
2503
|
+
var inputSchema10 = z10.object({
|
|
2504
|
+
name: z10.string().min(1).describe("API key name"),
|
|
2505
|
+
scopes: z10.string().optional().describe("Comma-separated list of scopes")
|
|
2506
|
+
});
|
|
2507
|
+
var keysCreate = {
|
|
2508
|
+
name: "api-keys.create",
|
|
2509
|
+
group: "api-keys",
|
|
2510
|
+
description: "Create a new API key",
|
|
2511
|
+
inputSchema: inputSchema10,
|
|
2512
|
+
outputSchema: z10.any(),
|
|
2513
|
+
async execute(input, ctx) {
|
|
2514
|
+
const parsedScopes = input.scopes ? input.scopes.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
|
|
2515
|
+
return ctx.client.post("api_keys", { name: input.name, scopes: parsedScopes });
|
|
2516
|
+
},
|
|
2517
|
+
formatOutput(output, format) {
|
|
2518
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2519
|
+
return JSON.stringify(output, null, 2);
|
|
2280
2520
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2521
|
+
};
|
|
2522
|
+
|
|
2523
|
+
// src/commands/api-keys/get-policy.ts
|
|
2524
|
+
import { z as z11 } from "zod";
|
|
2525
|
+
var inputSchema11 = z11.object({});
|
|
2526
|
+
var keysGetPolicy = {
|
|
2527
|
+
name: "api-keys.getPolicy",
|
|
2528
|
+
group: "api-keys",
|
|
2529
|
+
description: "Get the API key creation policy",
|
|
2530
|
+
inputSchema: inputSchema11,
|
|
2531
|
+
outputSchema: z11.any(),
|
|
2532
|
+
annotations: { readOnlyHint: true },
|
|
2533
|
+
async execute(_input, ctx) {
|
|
2534
|
+
return ctx.client.get("api_key_policy");
|
|
2535
|
+
},
|
|
2536
|
+
formatOutput(output, format) {
|
|
2537
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2538
|
+
return JSON.stringify(output, null, 2);
|
|
2287
2539
|
}
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2540
|
+
};
|
|
2541
|
+
|
|
2542
|
+
// src/commands/api-keys/list.ts
|
|
2543
|
+
import { z as z12 } from "zod";
|
|
2544
|
+
var inputSchema12 = z12.object({});
|
|
2545
|
+
var keysList = {
|
|
2546
|
+
name: "api-keys.list",
|
|
2547
|
+
group: "api-keys",
|
|
2548
|
+
description: "List all API keys",
|
|
2549
|
+
inputSchema: inputSchema12,
|
|
2550
|
+
outputSchema: z12.any(),
|
|
2551
|
+
annotations: { readOnlyHint: true },
|
|
2552
|
+
async execute(_input, ctx) {
|
|
2553
|
+
return ctx.client.get("api_keys");
|
|
2554
|
+
},
|
|
2555
|
+
formatOutput(output, format) {
|
|
2556
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2557
|
+
return JSON.stringify(output, null, 2);
|
|
2291
2558
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2559
|
+
};
|
|
2560
|
+
|
|
2561
|
+
// src/commands/api-keys/revoke.ts
|
|
2562
|
+
import { z as z13 } from "zod";
|
|
2563
|
+
var inputSchema13 = z13.object({
|
|
2564
|
+
keyId: z13.string().uuid().describe("API key ID to revoke")
|
|
2565
|
+
});
|
|
2566
|
+
var keysRevoke = {
|
|
2567
|
+
name: "api-keys.revoke",
|
|
2568
|
+
group: "api-keys",
|
|
2569
|
+
description: "Revoke an API key",
|
|
2570
|
+
inputSchema: inputSchema13,
|
|
2571
|
+
outputSchema: z13.any(),
|
|
2572
|
+
annotations: { destructiveHint: true },
|
|
2573
|
+
async execute(input, ctx) {
|
|
2574
|
+
await ctx.client.del("api_key", {
|
|
2575
|
+
pathParams: { id: input.keyId }
|
|
2576
|
+
});
|
|
2577
|
+
return { revoked: true, keyId: input.keyId };
|
|
2578
|
+
},
|
|
2579
|
+
formatOutput(output, format) {
|
|
2580
|
+
if (format === "compact") return output.keyId;
|
|
2581
|
+
return JSON.stringify(output, null, 2);
|
|
2301
2582
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2583
|
+
};
|
|
2584
|
+
|
|
2585
|
+
// src/commands/api-keys/set-policy.ts
|
|
2586
|
+
import { z as z14 } from "zod";
|
|
2587
|
+
var inputSchema14 = z14.object({
|
|
2588
|
+
policy: z14.enum(["all_members", "admins_only"]).describe("API key creation policy")
|
|
2589
|
+
});
|
|
2590
|
+
var keysSetPolicy = {
|
|
2591
|
+
name: "api-keys.setPolicy",
|
|
2592
|
+
group: "api-keys",
|
|
2593
|
+
description: "Set the API key creation policy",
|
|
2594
|
+
inputSchema: inputSchema14,
|
|
2595
|
+
outputSchema: z14.any(),
|
|
2596
|
+
async execute(input, ctx) {
|
|
2597
|
+
return ctx.client.put("api_key_policy", { policy: input.policy });
|
|
2598
|
+
},
|
|
2599
|
+
formatOutput(output, format) {
|
|
2600
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2601
|
+
return JSON.stringify(output, null, 2);
|
|
2313
2602
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2603
|
+
};
|
|
2604
|
+
|
|
2605
|
+
// src/commands/api-keys/index.ts
|
|
2606
|
+
function registerApiKeyCommands() {
|
|
2607
|
+
return [keysCreate, keysList, keysRevoke, keysGetPolicy, keysSetPolicy];
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
// src/commands/audit/list.ts
|
|
2611
|
+
import { z as z16 } from "zod";
|
|
2612
|
+
|
|
2613
|
+
// src/commands/_shared/pagination.ts
|
|
2614
|
+
import { z as z15 } from "zod";
|
|
2615
|
+
var paginationInput = {
|
|
2616
|
+
page: z15.number().int().min(1).default(1).describe("Page number"),
|
|
2617
|
+
pageSize: z15.number().int().min(1).max(100).default(25).describe("Results per page")
|
|
2618
|
+
};
|
|
2619
|
+
|
|
2620
|
+
// src/commands/audit/list.ts
|
|
2621
|
+
var inputSchema15 = z16.object({
|
|
2622
|
+
orgId: z16.string().uuid().describe("Organization ID"),
|
|
2623
|
+
...paginationInput,
|
|
2624
|
+
action: z16.string().optional().describe("Filter by action type"),
|
|
2625
|
+
userId: z16.string().optional().describe("Filter by user ID")
|
|
2626
|
+
});
|
|
2627
|
+
var auditList = {
|
|
2628
|
+
name: "audit.list",
|
|
2629
|
+
group: "audit",
|
|
2630
|
+
description: "List audit log entries for an organization",
|
|
2631
|
+
inputSchema: inputSchema15,
|
|
2632
|
+
outputSchema: z16.any(),
|
|
2633
|
+
annotations: { readOnlyHint: true },
|
|
2634
|
+
async execute(input, ctx) {
|
|
2635
|
+
const queryParams = {
|
|
2636
|
+
page: input.page,
|
|
2637
|
+
pageSize: input.pageSize
|
|
2328
2638
|
};
|
|
2639
|
+
if (input.action) queryParams.action = input.action;
|
|
2640
|
+
if (input.userId) queryParams.userId = input.userId;
|
|
2641
|
+
return ctx.client.get("audit_log", {
|
|
2642
|
+
pathParams: { orgId: input.orgId },
|
|
2643
|
+
queryParams
|
|
2644
|
+
});
|
|
2645
|
+
},
|
|
2646
|
+
formatOutput(output, format) {
|
|
2647
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2648
|
+
return JSON.stringify(output, null, 2);
|
|
2329
2649
|
}
|
|
2650
|
+
};
|
|
2651
|
+
|
|
2652
|
+
// src/commands/audit/index.ts
|
|
2653
|
+
function registerAuditCommands() {
|
|
2654
|
+
return [auditList];
|
|
2330
2655
|
}
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2656
|
+
|
|
2657
|
+
// src/commands/auth/login.ts
|
|
2658
|
+
init_client();
|
|
2659
|
+
init_errors();
|
|
2660
|
+
init_profiles();
|
|
2661
|
+
import { z as z17 } from "zod";
|
|
2662
|
+
|
|
2663
|
+
// src/lib/prompt.ts
|
|
2664
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
2665
|
+
function promptSecret(message) {
|
|
2666
|
+
return new Promise((resolve, reject) => {
|
|
2667
|
+
const { stdin, stdout } = process;
|
|
2668
|
+
if (!stdin.isTTY) {
|
|
2669
|
+
const rl = createInterface2({ input: stdin, output: stdout });
|
|
2670
|
+
rl.question(message, (answer) => {
|
|
2671
|
+
rl.close();
|
|
2672
|
+
resolve(answer.trim());
|
|
2673
|
+
});
|
|
2674
|
+
return;
|
|
2341
2675
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2676
|
+
stdout.write(message);
|
|
2677
|
+
let input = "";
|
|
2678
|
+
const wasRaw = stdin.isRaw;
|
|
2679
|
+
stdin.setRawMode(true);
|
|
2680
|
+
stdin.resume();
|
|
2681
|
+
stdin.setEncoding("utf8");
|
|
2682
|
+
const onData = (char) => {
|
|
2683
|
+
switch (char) {
|
|
2684
|
+
case "\n":
|
|
2685
|
+
case "\r":
|
|
2686
|
+
case "": {
|
|
2687
|
+
stdin.setRawMode(wasRaw);
|
|
2688
|
+
stdin.pause();
|
|
2689
|
+
stdin.removeListener("data", onData);
|
|
2690
|
+
stdout.write("\n");
|
|
2691
|
+
resolve(input.trim());
|
|
2692
|
+
break;
|
|
2693
|
+
}
|
|
2694
|
+
case "":
|
|
2695
|
+
// Ctrl+C
|
|
2696
|
+
case "\x1B": {
|
|
2697
|
+
stdin.setRawMode(wasRaw);
|
|
2698
|
+
stdin.pause();
|
|
2699
|
+
stdin.removeListener("data", onData);
|
|
2700
|
+
stdout.write("\n");
|
|
2701
|
+
reject(new Error("Cancelled"));
|
|
2702
|
+
break;
|
|
2703
|
+
}
|
|
2704
|
+
case "\x7F":
|
|
2705
|
+
case "\b": {
|
|
2706
|
+
if (input.length > 0) {
|
|
2707
|
+
input = input.slice(0, -1);
|
|
2708
|
+
stdout.write("\b \b");
|
|
2709
|
+
}
|
|
2710
|
+
break;
|
|
2711
|
+
}
|
|
2712
|
+
default: {
|
|
2713
|
+
input += char;
|
|
2714
|
+
stdout.write("*");
|
|
2715
|
+
break;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2347
2718
|
};
|
|
2348
|
-
|
|
2719
|
+
stdin.on("data", onData);
|
|
2720
|
+
});
|
|
2349
2721
|
}
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2722
|
+
|
|
2723
|
+
// src/commands/auth/login.ts
|
|
2724
|
+
var inputSchema16 = z17.object({
|
|
2725
|
+
apiKey: z17.string().optional().describe("Elnora API key (elnora_live_...)"),
|
|
2726
|
+
profile: z17.string().default("default").describe("Profile name to save key under")
|
|
2727
|
+
});
|
|
2728
|
+
var authLogin = {
|
|
2729
|
+
name: "auth.login",
|
|
2730
|
+
group: "auth",
|
|
2731
|
+
description: "Set up authentication by saving an API key to a profile",
|
|
2732
|
+
inputSchema: inputSchema16,
|
|
2733
|
+
outputSchema: z17.any(),
|
|
2734
|
+
annotations: { exposeInMcp: false },
|
|
2735
|
+
async execute(input, _ctx) {
|
|
2736
|
+
let apiKey = input.apiKey;
|
|
2737
|
+
const profileName = input.profile;
|
|
2738
|
+
validateProfileName(profileName);
|
|
2739
|
+
const existing = listProfileNames();
|
|
2740
|
+
const profileExists = existing.includes(profileName);
|
|
2741
|
+
if (!apiKey) {
|
|
2742
|
+
if (profileExists) {
|
|
2743
|
+
let savedKey;
|
|
2744
|
+
try {
|
|
2745
|
+
savedKey = getApiKey(profileName);
|
|
2746
|
+
} catch {
|
|
2747
|
+
}
|
|
2748
|
+
if (savedKey && isTTY()) {
|
|
2749
|
+
process.stderr.write(`
|
|
2750
|
+
Checking profile "${profileName}"...`);
|
|
2751
|
+
try {
|
|
2752
|
+
const client = new ElnoraApiClient(savedKey);
|
|
2753
|
+
const result = await client.get("projects", {
|
|
2754
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
2755
|
+
});
|
|
2756
|
+
const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
|
|
2757
|
+
process.stderr.write(` authenticated (${projectCount} project${projectCount !== 1 ? "s" : ""}).
|
|
2758
|
+
|
|
2759
|
+
`);
|
|
2760
|
+
process.stderr.write(" To update your API key, run:\n");
|
|
2761
|
+
process.stderr.write(
|
|
2762
|
+
` elnora auth login --api-key <new-key>${profileName !== "default" ? ` --profile ${profileName}` : ""}
|
|
2763
|
+
|
|
2764
|
+
`
|
|
2765
|
+
);
|
|
2766
|
+
return null;
|
|
2767
|
+
} catch {
|
|
2768
|
+
process.stderr.write(" key is invalid or expired.\n\n");
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2368
2771
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2772
|
+
if (isTTY()) {
|
|
2773
|
+
if (profileExists) {
|
|
2774
|
+
process.stderr.write(` Enter a new API key for profile "${profileName}"
|
|
2775
|
+
`);
|
|
2776
|
+
} else {
|
|
2777
|
+
process.stderr.write("\n Set up Elnora CLI authentication\n");
|
|
2778
|
+
}
|
|
2779
|
+
process.stderr.write(" Get your API key at: https://platform.elnora.ai > Settings > API Keys\n\n");
|
|
2780
|
+
try {
|
|
2781
|
+
apiKey = await promptSecret(" API key (Ctrl+C to cancel): ");
|
|
2782
|
+
} catch {
|
|
2783
|
+
process.stderr.write("\n Login cancelled.\n\n");
|
|
2784
|
+
process.exit(0);
|
|
2785
|
+
}
|
|
2786
|
+
process.stderr.write("\n");
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
if (!apiKey) {
|
|
2790
|
+
throw new ValidationError(
|
|
2791
|
+
"API key is required. Use --api-key flag or run interactively in a terminal.",
|
|
2792
|
+
"Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
const key = apiKey.trim();
|
|
2796
|
+
if (!key.startsWith("elnora_live_")) {
|
|
2797
|
+
throw new AuthError("API key must start with 'elnora_live_'. Check that you copied the full key.", {
|
|
2798
|
+
suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
2799
|
+
});
|
|
2800
|
+
}
|
|
2801
|
+
if (key.length < 20) {
|
|
2802
|
+
throw new AuthError("API key looks too short. Make sure you copied the full key.", {
|
|
2803
|
+
suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
if (isTTY()) {
|
|
2807
|
+
process.stderr.write(" Verifying...");
|
|
2808
|
+
}
|
|
2809
|
+
try {
|
|
2810
|
+
const tempClient = new ElnoraApiClient(key);
|
|
2811
|
+
const result = await tempClient.get("projects", {
|
|
2812
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
2813
|
+
});
|
|
2814
|
+
const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
|
|
2815
|
+
const configPath = saveProfile(profileName, key);
|
|
2816
|
+
if (isTTY()) {
|
|
2817
|
+
process.stderr.write(` Done! ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.
|
|
2372
2818
|
`);
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
if (
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
await runCheck(() => checkAuthenticated()),
|
|
2389
|
-
await runCheck(() => checkVersionCurrent()),
|
|
2390
|
-
await runCheck(() => checkConfigPermissions()),
|
|
2391
|
-
await runCheck(() => checkAiServerReachable()),
|
|
2392
|
-
await runCheck(() => checkPathConfigured())
|
|
2393
|
-
];
|
|
2394
|
-
for (const r of cliChecks) {
|
|
2395
|
-
console.error(render(r));
|
|
2396
|
-
tally(r);
|
|
2819
|
+
process.stderr.write(` Saved to profile "${profileName}" at ${configPath}
|
|
2820
|
+
|
|
2821
|
+
`);
|
|
2822
|
+
}
|
|
2823
|
+
return { profile: profileName, verified: true, configPath };
|
|
2824
|
+
} catch (err) {
|
|
2825
|
+
if (isTTY()) {
|
|
2826
|
+
process.stderr.write("\n");
|
|
2827
|
+
}
|
|
2828
|
+
if (err instanceof AuthError) {
|
|
2829
|
+
throw new AuthError("API key is invalid or has been revoked. Please check your key and try again.", {
|
|
2830
|
+
suggestion: "Get a new key from: https://platform.elnora.ai > Settings > API Keys"
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
throw err;
|
|
2397
2834
|
}
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
const
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2835
|
+
},
|
|
2836
|
+
formatOutput(output, format) {
|
|
2837
|
+
const data = output;
|
|
2838
|
+
if (format === "compact") return data.profile ?? JSON.stringify(output);
|
|
2839
|
+
const lines = [
|
|
2840
|
+
`\u2713 Authenticated! API key saved to profile "${data.profile}".`,
|
|
2841
|
+
"",
|
|
2842
|
+
"Next steps:",
|
|
2843
|
+
" elnora projects list See your projects",
|
|
2844
|
+
" elnora tasks create --project <ID> --stream Start a conversation",
|
|
2845
|
+
" elnora doctor Verify your setup"
|
|
2404
2846
|
];
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2847
|
+
return lines.join("\n");
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
|
|
2851
|
+
// src/commands/auth/logout.ts
|
|
2852
|
+
init_profiles();
|
|
2853
|
+
import { z as z18 } from "zod";
|
|
2854
|
+
var inputSchema17 = z18.object({
|
|
2855
|
+
profile: z18.string().optional().describe("Profile name to remove (default: 'default')"),
|
|
2856
|
+
all: z18.boolean().default(false).describe("Remove all profiles")
|
|
2857
|
+
});
|
|
2858
|
+
var authLogout = {
|
|
2859
|
+
name: "auth.logout",
|
|
2860
|
+
group: "auth",
|
|
2861
|
+
description: "Remove saved credentials",
|
|
2862
|
+
inputSchema: inputSchema17,
|
|
2863
|
+
outputSchema: z18.any(),
|
|
2864
|
+
annotations: { destructiveHint: true, exposeInMcp: false },
|
|
2865
|
+
async execute(input, _ctx) {
|
|
2866
|
+
if (input.all) {
|
|
2867
|
+
const names = listProfileNames();
|
|
2868
|
+
for (const name of names) {
|
|
2869
|
+
removeProfile(name);
|
|
2870
|
+
}
|
|
2871
|
+
return { loggedOut: true, profile: "all", removedCount: names.length };
|
|
2408
2872
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2873
|
+
const profileName = input.profile ?? "default";
|
|
2874
|
+
const removed = removeProfile(profileName);
|
|
2875
|
+
return { loggedOut: true, profile: profileName, removed };
|
|
2876
|
+
},
|
|
2877
|
+
formatOutput(output, format) {
|
|
2878
|
+
if (format === "compact") return output.profile ?? JSON.stringify(output);
|
|
2879
|
+
return JSON.stringify(output, null, 2);
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2882
|
+
|
|
2883
|
+
// src/commands/auth/profiles.ts
|
|
2884
|
+
init_profiles();
|
|
2885
|
+
import { z as z19 } from "zod";
|
|
2886
|
+
var inputSchema18 = z19.object({});
|
|
2887
|
+
var authProfiles = {
|
|
2888
|
+
name: "auth.profiles",
|
|
2889
|
+
group: "auth",
|
|
2890
|
+
description: "List all configured profiles",
|
|
2891
|
+
inputSchema: inputSchema18,
|
|
2892
|
+
outputSchema: z19.any(),
|
|
2893
|
+
annotations: { readOnlyHint: true, exposeInMcp: false },
|
|
2894
|
+
async execute(_input, _ctx) {
|
|
2895
|
+
const profiles = loadProfiles();
|
|
2896
|
+
const items = [];
|
|
2897
|
+
for (const [name, data] of Object.entries(profiles)) {
|
|
2898
|
+
const key = data.api_key ?? "";
|
|
2899
|
+
const masked = key.length > 16 ? `${key.slice(0, 12)}...${key.slice(-4)}` : "[invalid]";
|
|
2900
|
+
items.push({ name, apiKey: masked });
|
|
2415
2901
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
const warnSuffix = warnings > 0 ? ` (${warnings} warning${warnings === 1 ? "" : "s"})` : "";
|
|
2423
|
-
console.error(`${passed}/${total} checks passed \u2014 ${failed} failed${warnSuffix}.`);
|
|
2424
|
-
if (!existsSync4(PROFILES_FILE)) console.error("\nRun: elnora auth login");
|
|
2902
|
+
return { profiles: items };
|
|
2903
|
+
},
|
|
2904
|
+
formatOutput(output, format) {
|
|
2905
|
+
if (format === "compact") {
|
|
2906
|
+
const profiles = output.profiles ?? [];
|
|
2907
|
+
return profiles.map((p) => p.name).join(", ");
|
|
2425
2908
|
}
|
|
2426
|
-
|
|
2909
|
+
return JSON.stringify(output, null, 2);
|
|
2910
|
+
}
|
|
2911
|
+
};
|
|
2912
|
+
|
|
2913
|
+
// src/commands/auth/status.ts
|
|
2914
|
+
import { z as z20 } from "zod";
|
|
2915
|
+
var inputSchema19 = z20.object({});
|
|
2916
|
+
var authStatus = {
|
|
2917
|
+
name: "auth.status",
|
|
2918
|
+
group: "auth",
|
|
2919
|
+
description: "Verify API key and show connection info",
|
|
2920
|
+
inputSchema: inputSchema19,
|
|
2921
|
+
outputSchema: z20.any(),
|
|
2922
|
+
annotations: { readOnlyHint: true, exposeInMcp: false },
|
|
2923
|
+
async execute(_input, ctx) {
|
|
2924
|
+
const result = await ctx.client.get("projects", {
|
|
2925
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
2926
|
+
});
|
|
2927
|
+
const projectCount = result.totalCount ?? result.items?.length ?? 0;
|
|
2928
|
+
return { profile: ctx.profileName, authenticated: true, projectCount };
|
|
2929
|
+
},
|
|
2930
|
+
formatOutput(output, format) {
|
|
2931
|
+
if (format === "compact") return String(output.authenticated ?? "");
|
|
2932
|
+
return JSON.stringify(output, null, 2);
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
|
|
2936
|
+
// src/commands/auth/validate.ts
|
|
2937
|
+
init_profiles();
|
|
2938
|
+
import { z as z21 } from "zod";
|
|
2939
|
+
var inputSchema20 = z21.object({
|
|
2940
|
+
token: z21.string().optional().describe("Token to validate (defaults to current API key)")
|
|
2941
|
+
});
|
|
2942
|
+
var authValidate = {
|
|
2943
|
+
name: "auth.validate",
|
|
2944
|
+
group: "auth",
|
|
2945
|
+
description: "Validate a JWT or API key token",
|
|
2946
|
+
inputSchema: inputSchema20,
|
|
2947
|
+
outputSchema: z21.any(),
|
|
2948
|
+
annotations: { exposeInMcp: false },
|
|
2949
|
+
async execute(input, ctx) {
|
|
2950
|
+
const tokenToValidate = input.token ?? resolveApiKey();
|
|
2951
|
+
return ctx.client.post("auth_validate", { token: tokenToValidate });
|
|
2952
|
+
},
|
|
2953
|
+
formatOutput(output, format) {
|
|
2954
|
+
if (format === "compact") return JSON.stringify(output);
|
|
2955
|
+
return JSON.stringify(output, null, 2);
|
|
2956
|
+
}
|
|
2957
|
+
};
|
|
2958
|
+
|
|
2959
|
+
// src/commands/auth/index.ts
|
|
2960
|
+
function registerAuthCommands() {
|
|
2961
|
+
return [authLogin, authStatus, authLogout, authProfiles, authValidate];
|
|
2427
2962
|
}
|
|
2428
2963
|
|
|
2429
2964
|
// src/commands/feedback/submit.ts
|
|
@@ -2814,7 +3349,7 @@ var filesUpdate = {
|
|
|
2814
3349
|
|
|
2815
3350
|
// src/commands/files/upload.ts
|
|
2816
3351
|
init_client();
|
|
2817
|
-
import { readFileSync as
|
|
3352
|
+
import { readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
|
|
2818
3353
|
import { basename } from "node:path";
|
|
2819
3354
|
import { z as z37 } from "zod";
|
|
2820
3355
|
var inputSchema36 = z37.object({
|
|
@@ -2830,7 +3365,7 @@ var filesUpload = {
|
|
|
2830
3365
|
inputSchema: inputSchema36,
|
|
2831
3366
|
outputSchema: z37.any(),
|
|
2832
3367
|
async execute(input, ctx) {
|
|
2833
|
-
const fileBuffer =
|
|
3368
|
+
const fileBuffer = readFileSync6(input.filePath);
|
|
2834
3369
|
const fileName = input.fileName ?? basename(input.filePath);
|
|
2835
3370
|
const contentType = input.contentType ?? "application/octet-stream";
|
|
2836
3371
|
const fileSize = statSync2(input.filePath).size;
|
|
@@ -2861,7 +3396,7 @@ var filesUpload = {
|
|
|
2861
3396
|
// src/commands/files/upload-batch.ts
|
|
2862
3397
|
init_client();
|
|
2863
3398
|
init_errors();
|
|
2864
|
-
import { readFileSync as
|
|
3399
|
+
import { readFileSync as readFileSync7, statSync as statSync3 } from "node:fs";
|
|
2865
3400
|
import { basename as basename2 } from "node:path";
|
|
2866
3401
|
import { z as z38 } from "zod";
|
|
2867
3402
|
var inputSchema37 = z38.object({
|
|
@@ -2890,7 +3425,7 @@ var filesUploadBatch = {
|
|
|
2890
3425
|
for (const filePath of paths) {
|
|
2891
3426
|
const fileName = basename2(filePath);
|
|
2892
3427
|
try {
|
|
2893
|
-
const fileBuffer =
|
|
3428
|
+
const fileBuffer = readFileSync7(filePath);
|
|
2894
3429
|
const contentType = "application/octet-stream";
|
|
2895
3430
|
const fileSize = statSync3(filePath).size;
|
|
2896
3431
|
const uploadInfo = await ctx.client.post("file_upload", {
|
|
@@ -3411,76 +3946,17 @@ var libraryRenameFolder = {
|
|
|
3411
3946
|
{
|
|
3412
3947
|
pathParams: { orgId: input.orgId, id: input.folderId }
|
|
3413
3948
|
}
|
|
3414
|
-
);
|
|
3415
|
-
},
|
|
3416
|
-
formatOutput(output, format) {
|
|
3417
|
-
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3418
|
-
return JSON.stringify(output, null, 2);
|
|
3419
|
-
}
|
|
3420
|
-
};
|
|
3421
|
-
|
|
3422
|
-
// src/commands/library/index.ts
|
|
3423
|
-
function registerLibraryCommands() {
|
|
3424
|
-
return [libraryFiles, libraryFolders, libraryCreateFolder, libraryRenameFolder, libraryDeleteFolder];
|
|
3425
|
-
}
|
|
3426
|
-
|
|
3427
|
-
// src/commands/mcp/serve.ts
|
|
3428
|
-
function requireEnv(name) {
|
|
3429
|
-
const value = process.env[name];
|
|
3430
|
-
if (!value) {
|
|
3431
|
-
throw new Error(`Required environment variable ${name} is not set`);
|
|
3432
|
-
}
|
|
3433
|
-
return value;
|
|
3434
|
-
}
|
|
3435
|
-
function addMcpCommands(program2, registry2) {
|
|
3436
|
-
const mcp = program2.command("mcp").description("MCP server management");
|
|
3437
|
-
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) => {
|
|
3438
|
-
if (opts.stdio) {
|
|
3439
|
-
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_stdio_server(), stdio_server_exports));
|
|
3440
|
-
await startStdioServer2(registry2);
|
|
3441
|
-
} else {
|
|
3442
|
-
const { startHttpServer: startHttpServer2 } = await Promise.resolve().then(() => (init_http_server(), http_server_exports));
|
|
3443
|
-
await startHttpServer2(registry2, {
|
|
3444
|
-
port: Number.parseInt(opts.port, 10),
|
|
3445
|
-
host: opts.host,
|
|
3446
|
-
tokenValidationUrl: requireEnv("ELNORA_TOKEN_VALIDATION_URL"),
|
|
3447
|
-
mcpServiceKey: requireEnv("ELNORA_MCP_SERVICE_KEY")
|
|
3448
|
-
});
|
|
3449
|
-
}
|
|
3450
|
-
});
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
// src/commands/open.ts
|
|
3454
|
-
import { exec } from "node:child_process";
|
|
3455
|
-
var URLS = {
|
|
3456
|
-
"": "https://platform.elnora.ai",
|
|
3457
|
-
platform: "https://platform.elnora.ai",
|
|
3458
|
-
docs: "https://docs.elnora.ai",
|
|
3459
|
-
keys: "https://platform.elnora.ai/settings/api-keys",
|
|
3460
|
-
billing: "https://platform.elnora.ai/settings/billing",
|
|
3461
|
-
github: "https://github.com/Elnora-AI/elnora-cli"
|
|
3462
|
-
};
|
|
3463
|
-
function openUrl(url) {
|
|
3464
|
-
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
3465
|
-
exec(cmd, (err) => {
|
|
3466
|
-
if (err) {
|
|
3467
|
-
console.error(`Failed to open browser: ${err.message}`);
|
|
3468
|
-
console.log(url);
|
|
3469
|
-
}
|
|
3470
|
-
});
|
|
3471
|
-
}
|
|
3472
|
-
function addOpenCommand(program2) {
|
|
3473
|
-
program2.command("open").argument("[page]", "Page to open: platform, docs, keys, billing, github").description("Open Elnora platform in the browser").action((page) => {
|
|
3474
|
-
const key = page ?? "";
|
|
3475
|
-
const url = URLS[key];
|
|
3476
|
-
if (!url) {
|
|
3477
|
-
console.error(`Unknown page: ${page}`);
|
|
3478
|
-
console.error(`Available: ${Object.keys(URLS).filter(Boolean).join(", ")}`);
|
|
3479
|
-
process.exit(1);
|
|
3480
|
-
}
|
|
3481
|
-
openUrl(url);
|
|
3482
|
-
console.log(`Opening ${url}...`);
|
|
3483
|
-
});
|
|
3949
|
+
);
|
|
3950
|
+
},
|
|
3951
|
+
formatOutput(output, format) {
|
|
3952
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3953
|
+
return JSON.stringify(output, null, 2);
|
|
3954
|
+
}
|
|
3955
|
+
};
|
|
3956
|
+
|
|
3957
|
+
// src/commands/library/index.ts
|
|
3958
|
+
function registerLibraryCommands() {
|
|
3959
|
+
return [libraryFiles, libraryFolders, libraryCreateFolder, libraryRenameFolder, libraryDeleteFolder];
|
|
3484
3960
|
}
|
|
3485
3961
|
|
|
3486
3962
|
// src/commands/orgs/accept-invite.ts
|
|
@@ -4249,18 +4725,63 @@ function registerProjectCommands() {
|
|
|
4249
4725
|
];
|
|
4250
4726
|
}
|
|
4251
4727
|
|
|
4252
|
-
// src/commands/
|
|
4728
|
+
// src/commands/protocols/generate.ts
|
|
4253
4729
|
import { z as z85 } from "zod";
|
|
4254
4730
|
var inputSchema83 = z85.object({
|
|
4255
|
-
|
|
4731
|
+
description: z85.string().min(10).max(5e3).describe("Protocol description"),
|
|
4732
|
+
title: z85.string().max(200).optional().describe("Task title (defaults to first 100 chars of description)"),
|
|
4733
|
+
project: z85.string().uuid().optional().describe("Project UUID to associate with")
|
|
4734
|
+
});
|
|
4735
|
+
var protocolsGenerate = {
|
|
4736
|
+
name: "protocols.generate",
|
|
4737
|
+
group: "protocols",
|
|
4738
|
+
description: "Generate a bioprotocol \u2014 creates a task and sends the description in one call",
|
|
4739
|
+
inputSchema: inputSchema83,
|
|
4740
|
+
outputSchema: z85.any(),
|
|
4741
|
+
annotations: { mcpScopes: ["tasks:write", "messages:write"] },
|
|
4742
|
+
async execute(input, ctx) {
|
|
4743
|
+
const body = { title: input.title || input.description.slice(0, 100) };
|
|
4744
|
+
if (input.project) body.projectId = input.project;
|
|
4745
|
+
const task = await ctx.client.post("tasks", body);
|
|
4746
|
+
if (!task?.id) {
|
|
4747
|
+
throw new Error("Task creation returned no ID");
|
|
4748
|
+
}
|
|
4749
|
+
try {
|
|
4750
|
+
const response = await ctx.client.post(`tasks/${task.id}/messages`, { content: input.description });
|
|
4751
|
+
return { taskId: task.id, response };
|
|
4752
|
+
} catch (err) {
|
|
4753
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4754
|
+
throw Object.assign(
|
|
4755
|
+
new Error(
|
|
4756
|
+
`Task ${task.id} created but message send failed: ${message}. Retry with 'elnora tasks send --task-id ${task.id}'.`
|
|
4757
|
+
),
|
|
4758
|
+
{ taskId: task.id }
|
|
4759
|
+
);
|
|
4760
|
+
}
|
|
4761
|
+
},
|
|
4762
|
+
formatOutput(output, format) {
|
|
4763
|
+
if (format === "compact") return output.taskId ?? JSON.stringify(output);
|
|
4764
|
+
return JSON.stringify(output, null, 2);
|
|
4765
|
+
}
|
|
4766
|
+
};
|
|
4767
|
+
|
|
4768
|
+
// src/commands/protocols/index.ts
|
|
4769
|
+
function registerProtocolCommands() {
|
|
4770
|
+
return [protocolsGenerate];
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
// src/commands/search/all.ts
|
|
4774
|
+
import { z as z86 } from "zod";
|
|
4775
|
+
var inputSchema84 = z86.object({
|
|
4776
|
+
query: z86.string().min(1).describe("Search query"),
|
|
4256
4777
|
...paginationInput
|
|
4257
4778
|
});
|
|
4258
4779
|
var searchAll = {
|
|
4259
4780
|
name: "search.all",
|
|
4260
4781
|
group: "search",
|
|
4261
4782
|
description: "Search across all entities",
|
|
4262
|
-
inputSchema:
|
|
4263
|
-
outputSchema:
|
|
4783
|
+
inputSchema: inputSchema84,
|
|
4784
|
+
outputSchema: z86.any(),
|
|
4264
4785
|
annotations: { readOnlyHint: true },
|
|
4265
4786
|
async execute(input, ctx) {
|
|
4266
4787
|
return ctx.client.get("search_all", {
|
|
@@ -4274,18 +4795,18 @@ var searchAll = {
|
|
|
4274
4795
|
};
|
|
4275
4796
|
|
|
4276
4797
|
// src/commands/search/file-content.ts
|
|
4277
|
-
import { z as
|
|
4278
|
-
var
|
|
4279
|
-
query:
|
|
4280
|
-
projectId:
|
|
4798
|
+
import { z as z87 } from "zod";
|
|
4799
|
+
var inputSchema85 = z87.object({
|
|
4800
|
+
query: z87.string().min(1).describe("Search query"),
|
|
4801
|
+
projectId: z87.string().uuid().optional().describe("Filter by project ID"),
|
|
4281
4802
|
...paginationInput
|
|
4282
4803
|
});
|
|
4283
4804
|
var searchFileContent = {
|
|
4284
4805
|
name: "search.fileContent",
|
|
4285
4806
|
group: "search",
|
|
4286
4807
|
description: "Search within file contents",
|
|
4287
|
-
inputSchema:
|
|
4288
|
-
outputSchema:
|
|
4808
|
+
inputSchema: inputSchema85,
|
|
4809
|
+
outputSchema: z87.any(),
|
|
4289
4810
|
annotations: { readOnlyHint: true },
|
|
4290
4811
|
async execute(input, ctx) {
|
|
4291
4812
|
const queryParams = {
|
|
@@ -4303,17 +4824,17 @@ var searchFileContent = {
|
|
|
4303
4824
|
};
|
|
4304
4825
|
|
|
4305
4826
|
// src/commands/search/files.ts
|
|
4306
|
-
import { z as
|
|
4307
|
-
var
|
|
4308
|
-
query:
|
|
4827
|
+
import { z as z88 } from "zod";
|
|
4828
|
+
var inputSchema86 = z88.object({
|
|
4829
|
+
query: z88.string().min(1).describe("Search query"),
|
|
4309
4830
|
...paginationInput
|
|
4310
4831
|
});
|
|
4311
4832
|
var searchFiles = {
|
|
4312
4833
|
name: "search.files",
|
|
4313
4834
|
group: "search",
|
|
4314
4835
|
description: "Search files by query",
|
|
4315
|
-
inputSchema:
|
|
4316
|
-
outputSchema:
|
|
4836
|
+
inputSchema: inputSchema86,
|
|
4837
|
+
outputSchema: z88.any(),
|
|
4317
4838
|
annotations: { readOnlyHint: true },
|
|
4318
4839
|
async execute(input, ctx) {
|
|
4319
4840
|
return ctx.client.get("search_files", {
|
|
@@ -4327,17 +4848,17 @@ var searchFiles = {
|
|
|
4327
4848
|
};
|
|
4328
4849
|
|
|
4329
4850
|
// src/commands/search/tasks.ts
|
|
4330
|
-
import { z as
|
|
4331
|
-
var
|
|
4332
|
-
query:
|
|
4851
|
+
import { z as z89 } from "zod";
|
|
4852
|
+
var inputSchema87 = z89.object({
|
|
4853
|
+
query: z89.string().min(1).describe("Search query"),
|
|
4333
4854
|
...paginationInput
|
|
4334
4855
|
});
|
|
4335
4856
|
var searchTasks = {
|
|
4336
4857
|
name: "search.tasks",
|
|
4337
4858
|
group: "search",
|
|
4338
4859
|
description: "Search tasks by query",
|
|
4339
|
-
inputSchema:
|
|
4340
|
-
outputSchema:
|
|
4860
|
+
inputSchema: inputSchema87,
|
|
4861
|
+
outputSchema: z89.any(),
|
|
4341
4862
|
annotations: { readOnlyHint: true },
|
|
4342
4863
|
async execute(input, ctx) {
|
|
4343
4864
|
return ctx.client.get("search_tasks", {
|
|
@@ -4355,228 +4876,17 @@ function registerSearchCommands() {
|
|
|
4355
4876
|
return [searchTasks, searchFiles, searchAll, searchFileContent];
|
|
4356
4877
|
}
|
|
4357
4878
|
|
|
4358
|
-
// src/commands/setup.ts
|
|
4359
|
-
init_profiles();
|
|
4360
|
-
|
|
4361
|
-
// src/lib/setup/claude.ts
|
|
4362
|
-
import { existsSync as existsSync5 } from "node:fs";
|
|
4363
|
-
function setupClaude() {
|
|
4364
|
-
if (!existsSync5(CLAUDE_DIR)) {
|
|
4365
|
-
console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
|
|
4366
|
-
console.error("");
|
|
4367
|
-
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
4368
|
-
return false;
|
|
4369
|
-
}
|
|
4370
|
-
console.error(ok("Claude Code found"));
|
|
4371
|
-
const marketplaces = readJsonFile(CLAUDE_MARKETPLACES_FILE);
|
|
4372
|
-
for (const old of LEGACY_MARKETPLACE_NAMES) {
|
|
4373
|
-
delete marketplaces[old];
|
|
4374
|
-
}
|
|
4375
|
-
marketplaces[MARKETPLACE_NAME] = {
|
|
4376
|
-
source: {
|
|
4377
|
-
source: "github",
|
|
4378
|
-
repo: MARKETPLACE_REPO
|
|
4379
|
-
}
|
|
4380
|
-
};
|
|
4381
|
-
writeJsonFile(CLAUDE_MARKETPLACES_FILE, marketplaces);
|
|
4382
|
-
console.error(ok(`Marketplace registered (GitHub: ${MARKETPLACE_REPO})`));
|
|
4383
|
-
const settings = readJsonFile(CLAUDE_SETTINGS_FILE);
|
|
4384
|
-
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
|
|
4385
|
-
settings.enabledPlugins = {};
|
|
4386
|
-
}
|
|
4387
|
-
const plugins = settings.enabledPlugins;
|
|
4388
|
-
for (const old of LEGACY_PLUGIN_IDS) {
|
|
4389
|
-
delete plugins[old];
|
|
4390
|
-
}
|
|
4391
|
-
plugins[PLUGIN_ID] = true;
|
|
4392
|
-
writeJsonFile(CLAUDE_SETTINGS_FILE, settings);
|
|
4393
|
-
console.error(ok(`Plugin enabled (${PLUGIN_ID})`));
|
|
4394
|
-
console.error("");
|
|
4395
|
-
console.error(" Elnora skills are now available in Claude Code.");
|
|
4396
|
-
console.error(" Restart Claude Code if it's currently running.");
|
|
4397
|
-
return true;
|
|
4398
|
-
}
|
|
4399
|
-
|
|
4400
|
-
// src/lib/setup/codex.ts
|
|
4401
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
4402
|
-
import { join as join5 } from "node:path";
|
|
4403
|
-
var CODEX_MCP_FILE = join5(CODEX_DIR, "mcp.json");
|
|
4404
|
-
function setupCodex(apiKey) {
|
|
4405
|
-
if (!existsSync6(CODEX_DIR)) {
|
|
4406
|
-
console.error(fail("Codex CLI not found (~/.codex/ does not exist)"));
|
|
4407
|
-
console.error("");
|
|
4408
|
-
console.error(" Install Codex first: https://github.com/openai/codex");
|
|
4409
|
-
return false;
|
|
4410
|
-
}
|
|
4411
|
-
console.error(ok("Codex CLI found"));
|
|
4412
|
-
const config = readJsonFile(CODEX_MCP_FILE);
|
|
4413
|
-
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
4414
|
-
config.mcpServers = {};
|
|
4415
|
-
}
|
|
4416
|
-
config.mcpServers.elnora = {
|
|
4417
|
-
url: "https://mcp.elnora.ai/mcp",
|
|
4418
|
-
headers: {
|
|
4419
|
-
"X-API-Key": apiKey
|
|
4420
|
-
}
|
|
4421
|
-
};
|
|
4422
|
-
writeJsonFile(CODEX_MCP_FILE, config);
|
|
4423
|
-
console.error(ok("MCP server configured in ~/.codex/mcp.json"));
|
|
4424
|
-
console.error("");
|
|
4425
|
-
console.error(" Elnora MCP server is now available in Codex.");
|
|
4426
|
-
return true;
|
|
4427
|
-
}
|
|
4428
|
-
|
|
4429
|
-
// src/lib/setup/cursor.ts
|
|
4430
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
4431
|
-
function setupCursor(apiKey) {
|
|
4432
|
-
if (!existsSync7(CURSOR_DIR)) {
|
|
4433
|
-
console.error(fail("Cursor not found (~/.cursor/ does not exist)"));
|
|
4434
|
-
console.error("");
|
|
4435
|
-
console.error(" Install Cursor first: https://cursor.com");
|
|
4436
|
-
return false;
|
|
4437
|
-
}
|
|
4438
|
-
console.error(ok("Cursor found"));
|
|
4439
|
-
const config = readJsonFile(CURSOR_MCP_GLOBAL);
|
|
4440
|
-
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
4441
|
-
config.mcpServers = {};
|
|
4442
|
-
}
|
|
4443
|
-
config.mcpServers.elnora = {
|
|
4444
|
-
url: "https://mcp.elnora.ai/mcp",
|
|
4445
|
-
headers: {
|
|
4446
|
-
"X-API-Key": apiKey
|
|
4447
|
-
}
|
|
4448
|
-
};
|
|
4449
|
-
writeJsonFile(CURSOR_MCP_GLOBAL, config);
|
|
4450
|
-
console.error(ok("MCP server configured in ~/.cursor/mcp.json"));
|
|
4451
|
-
console.error("");
|
|
4452
|
-
console.error(" Elnora MCP server is now available in Cursor.");
|
|
4453
|
-
console.error(" Restart Cursor if it's currently running.");
|
|
4454
|
-
return true;
|
|
4455
|
-
}
|
|
4456
|
-
|
|
4457
|
-
// src/lib/setup/vscode.ts
|
|
4458
|
-
import { existsSync as existsSync8 } from "node:fs";
|
|
4459
|
-
import { join as join6 } from "node:path";
|
|
4460
|
-
var VSCODE_MCP_GLOBAL = join6(HOME, ".vscode", "mcp.json");
|
|
4461
|
-
function setupVscode(apiKey) {
|
|
4462
|
-
if (!existsSync8(VSCODE_DIR)) {
|
|
4463
|
-
console.error(fail("VS Code not found (~/.vscode/ does not exist)"));
|
|
4464
|
-
console.error("");
|
|
4465
|
-
console.error(" Install VS Code first: https://code.visualstudio.com");
|
|
4466
|
-
return false;
|
|
4467
|
-
}
|
|
4468
|
-
console.error(ok("VS Code found"));
|
|
4469
|
-
const config = readJsonFile(VSCODE_MCP_GLOBAL);
|
|
4470
|
-
if (!config.servers || typeof config.servers !== "object") {
|
|
4471
|
-
config.servers = {};
|
|
4472
|
-
}
|
|
4473
|
-
config.servers.elnora = {
|
|
4474
|
-
type: "http",
|
|
4475
|
-
url: "https://mcp.elnora.ai/mcp",
|
|
4476
|
-
headers: {
|
|
4477
|
-
"X-API-Key": apiKey
|
|
4478
|
-
}
|
|
4479
|
-
};
|
|
4480
|
-
writeJsonFile(VSCODE_MCP_GLOBAL, config);
|
|
4481
|
-
console.error(ok("MCP server configured in ~/.vscode/mcp.json"));
|
|
4482
|
-
console.error("");
|
|
4483
|
-
console.error(" Elnora MCP server is now available in VS Code Copilot.");
|
|
4484
|
-
console.error(" Restart VS Code if it's currently running.");
|
|
4485
|
-
return true;
|
|
4486
|
-
}
|
|
4487
|
-
|
|
4488
|
-
// src/commands/setup.ts
|
|
4489
|
-
var PLATFORM_SETUP = {
|
|
4490
|
-
claude: () => setupClaude(),
|
|
4491
|
-
cursor: (key) => setupCursor(key),
|
|
4492
|
-
vscode: (key) => setupVscode(key),
|
|
4493
|
-
codex: (key) => setupCodex(key)
|
|
4494
|
-
};
|
|
4495
|
-
function runSetup(platforms, profileName) {
|
|
4496
|
-
let apiKey;
|
|
4497
|
-
const needsKey = platforms.some((p) => p !== "claude");
|
|
4498
|
-
if (needsKey) {
|
|
4499
|
-
try {
|
|
4500
|
-
apiKey = resolveApiKey(profileName);
|
|
4501
|
-
} catch {
|
|
4502
|
-
if (platforms.every((p) => p !== "claude")) {
|
|
4503
|
-
console.error(fail("No API key found. Run 'elnora auth login' first."));
|
|
4504
|
-
process.exitCode = 1;
|
|
4505
|
-
return;
|
|
4506
|
-
}
|
|
4507
|
-
}
|
|
4508
|
-
}
|
|
4509
|
-
let anyFailed = false;
|
|
4510
|
-
for (const platform of platforms) {
|
|
4511
|
-
const setupFn = PLATFORM_SETUP[platform];
|
|
4512
|
-
const success = setupFn(apiKey ?? "");
|
|
4513
|
-
if (!success) anyFailed = true;
|
|
4514
|
-
console.error("");
|
|
4515
|
-
}
|
|
4516
|
-
if (anyFailed) {
|
|
4517
|
-
process.exitCode = 1;
|
|
4518
|
-
}
|
|
4519
|
-
}
|
|
4520
|
-
function addSetupCommand(program2) {
|
|
4521
|
-
const setup = program2.command("setup").description("Configure AI coding tools to use Elnora").option("--profile <name>", "API key profile to use", "default");
|
|
4522
|
-
setup.command("claude").description("Set up Claude Code (skills + MCP server)").action(() => {
|
|
4523
|
-
const opts = setup.opts();
|
|
4524
|
-
console.error("");
|
|
4525
|
-
runSetup(["claude"], opts.profile);
|
|
4526
|
-
});
|
|
4527
|
-
setup.command("cursor").description("Set up Cursor (MCP server)").action(() => {
|
|
4528
|
-
const opts = setup.opts();
|
|
4529
|
-
console.error("");
|
|
4530
|
-
runSetup(["cursor"], opts.profile);
|
|
4531
|
-
});
|
|
4532
|
-
setup.command("vscode").description("Set up VS Code Copilot (MCP server)").action(() => {
|
|
4533
|
-
const opts = setup.opts();
|
|
4534
|
-
console.error("");
|
|
4535
|
-
runSetup(["vscode"], opts.profile);
|
|
4536
|
-
});
|
|
4537
|
-
setup.command("codex").description("Set up OpenAI Codex (MCP server)").action(() => {
|
|
4538
|
-
const opts = setup.opts();
|
|
4539
|
-
console.error("");
|
|
4540
|
-
runSetup(["codex"], opts.profile);
|
|
4541
|
-
});
|
|
4542
|
-
setup.action(() => {
|
|
4543
|
-
const opts = setup.opts();
|
|
4544
|
-
console.error("");
|
|
4545
|
-
const detected = detectPlatforms();
|
|
4546
|
-
const installed = detected.filter((p) => p.installed);
|
|
4547
|
-
if (installed.length === 0) {
|
|
4548
|
-
console.error(fail("No supported AI coding tools detected."));
|
|
4549
|
-
console.error("");
|
|
4550
|
-
console.error(" Supported: Claude Code, Cursor, VS Code, OpenAI Codex");
|
|
4551
|
-
console.error(" Install one first, then run 'elnora setup' again.");
|
|
4552
|
-
process.exitCode = 1;
|
|
4553
|
-
return;
|
|
4554
|
-
}
|
|
4555
|
-
console.error(ok(`Detected ${installed.length} platform(s):`));
|
|
4556
|
-
for (const p of installed) {
|
|
4557
|
-
console.error(info(`${p.label}`));
|
|
4558
|
-
}
|
|
4559
|
-
console.error("");
|
|
4560
|
-
const platforms = installed.map((p) => p.name);
|
|
4561
|
-
runSetup(platforms, opts.profile);
|
|
4562
|
-
});
|
|
4563
|
-
program2.command("setup-claude").description("Set up Claude Code (alias for 'setup claude')").option("--profile <name>", "API key profile to use", "default").action((opts) => {
|
|
4564
|
-
console.error("");
|
|
4565
|
-
runSetup(["claude"], opts.profile);
|
|
4566
|
-
});
|
|
4567
|
-
}
|
|
4568
|
-
|
|
4569
4879
|
// src/commands/tasks/archive.ts
|
|
4570
|
-
import { z as
|
|
4571
|
-
var
|
|
4572
|
-
taskId:
|
|
4880
|
+
import { z as z90 } from "zod";
|
|
4881
|
+
var inputSchema88 = z90.object({
|
|
4882
|
+
taskId: z90.string().uuid().describe("Task ID to archive")
|
|
4573
4883
|
});
|
|
4574
4884
|
var tasksArchive = {
|
|
4575
4885
|
name: "tasks.archive",
|
|
4576
4886
|
group: "tasks",
|
|
4577
4887
|
description: "Archive (delete) a task",
|
|
4578
|
-
inputSchema:
|
|
4579
|
-
outputSchema:
|
|
4888
|
+
inputSchema: inputSchema88,
|
|
4889
|
+
outputSchema: z90.any(),
|
|
4580
4890
|
annotations: { destructiveHint: true },
|
|
4581
4891
|
async execute(input, ctx) {
|
|
4582
4892
|
await ctx.client.del("task", {
|
|
@@ -4591,7 +4901,7 @@ var tasksArchive = {
|
|
|
4591
4901
|
};
|
|
4592
4902
|
|
|
4593
4903
|
// src/commands/tasks/create.ts
|
|
4594
|
-
import { z as
|
|
4904
|
+
import { z as z91 } from "zod";
|
|
4595
4905
|
|
|
4596
4906
|
// src/lib/poll.ts
|
|
4597
4907
|
init_errors();
|
|
@@ -4713,7 +5023,7 @@ async function collectStreamResponse(taskId, apiKey, options) {
|
|
|
4713
5023
|
}
|
|
4714
5024
|
|
|
4715
5025
|
// src/lib/stream-renderer.ts
|
|
4716
|
-
import
|
|
5026
|
+
import pc5 from "picocolors";
|
|
4717
5027
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4718
5028
|
var SPINNER_INTERVAL_MS = 80;
|
|
4719
5029
|
var StreamRenderer = class {
|
|
@@ -4750,14 +5060,14 @@ var StreamRenderer = class {
|
|
|
4750
5060
|
this.stopSpinner();
|
|
4751
5061
|
process.stderr.write(
|
|
4752
5062
|
`
|
|
4753
|
-
${this.useColor ?
|
|
5063
|
+
${this.useColor ? pc5.red("Error:") : "Error:"} ${event.content}
|
|
4754
5064
|
`
|
|
4755
5065
|
);
|
|
4756
5066
|
break;
|
|
4757
5067
|
case "timeout":
|
|
4758
5068
|
this.stopSpinner();
|
|
4759
5069
|
process.stderr.write(`
|
|
4760
|
-
${this.useColor ?
|
|
5070
|
+
${this.useColor ? pc5.yellow("Stream timed out.") : "Stream timed out."}
|
|
4761
5071
|
`);
|
|
4762
5072
|
break;
|
|
4763
5073
|
}
|
|
@@ -4779,7 +5089,7 @@ ${this.useColor ? pc4.yellow("Stream timed out.") : "Stream timed out."}
|
|
|
4779
5089
|
}
|
|
4780
5090
|
writeSpinnerFrame() {
|
|
4781
5091
|
const frame = SPINNER_FRAMES[this.spinnerFrame];
|
|
4782
|
-
const text = this.useColor ?
|
|
5092
|
+
const text = this.useColor ? pc5.dim(`${frame} ${this.currentLabel}`) : `${frame} ${this.currentLabel}`;
|
|
4783
5093
|
process.stderr.write(`\r ${text}`);
|
|
4784
5094
|
}
|
|
4785
5095
|
stopSpinner() {
|
|
@@ -4794,19 +5104,19 @@ ${this.useColor ? pc4.yellow("Stream timed out.") : "Stream timed out."}
|
|
|
4794
5104
|
};
|
|
4795
5105
|
|
|
4796
5106
|
// src/commands/tasks/create.ts
|
|
4797
|
-
var
|
|
4798
|
-
project:
|
|
4799
|
-
title:
|
|
4800
|
-
message:
|
|
4801
|
-
wait:
|
|
4802
|
-
stream:
|
|
5107
|
+
var inputSchema89 = z91.object({
|
|
5108
|
+
project: z91.string().uuid().describe("Project ID"),
|
|
5109
|
+
title: z91.string().optional().describe("Task title"),
|
|
5110
|
+
message: z91.string().optional().describe("Initial message"),
|
|
5111
|
+
wait: z91.boolean().default(false).describe("Wait for agent response (polling)"),
|
|
5112
|
+
stream: z91.boolean().default(false).describe("Stream agent response in real-time (SSE)")
|
|
4803
5113
|
});
|
|
4804
5114
|
var tasksCreate = {
|
|
4805
5115
|
name: "tasks.create",
|
|
4806
5116
|
group: "tasks",
|
|
4807
5117
|
description: "Create a new task in a project",
|
|
4808
|
-
inputSchema:
|
|
4809
|
-
outputSchema:
|
|
5118
|
+
inputSchema: inputSchema89,
|
|
5119
|
+
outputSchema: z91.any(),
|
|
4810
5120
|
async execute(input, ctx) {
|
|
4811
5121
|
const result = await ctx.client.post("tasks", {
|
|
4812
5122
|
projectId: input.project,
|
|
@@ -4855,17 +5165,17 @@ var tasksCreate = {
|
|
|
4855
5165
|
}
|
|
4856
5166
|
};
|
|
4857
5167
|
|
|
4858
|
-
// src/commands/tasks/get.ts
|
|
4859
|
-
import { z as
|
|
4860
|
-
var
|
|
4861
|
-
taskId:
|
|
5168
|
+
// src/commands/tasks/get.ts
|
|
5169
|
+
import { z as z92 } from "zod";
|
|
5170
|
+
var inputSchema90 = z92.object({
|
|
5171
|
+
taskId: z92.string().uuid().describe("Task ID")
|
|
4862
5172
|
});
|
|
4863
5173
|
var tasksGet = {
|
|
4864
5174
|
name: "tasks.get",
|
|
4865
5175
|
group: "tasks",
|
|
4866
5176
|
description: "Get details of a specific task",
|
|
4867
|
-
inputSchema:
|
|
4868
|
-
outputSchema:
|
|
5177
|
+
inputSchema: inputSchema90,
|
|
5178
|
+
outputSchema: z92.any(),
|
|
4869
5179
|
annotations: { readOnlyHint: true },
|
|
4870
5180
|
async execute(input, ctx) {
|
|
4871
5181
|
return ctx.client.get("task", {
|
|
@@ -4879,17 +5189,17 @@ var tasksGet = {
|
|
|
4879
5189
|
};
|
|
4880
5190
|
|
|
4881
5191
|
// src/commands/tasks/list.ts
|
|
4882
|
-
import { z as
|
|
4883
|
-
var
|
|
4884
|
-
project:
|
|
5192
|
+
import { z as z93 } from "zod";
|
|
5193
|
+
var inputSchema91 = z93.object({
|
|
5194
|
+
project: z93.string().uuid().optional().describe("Project ID to filter by"),
|
|
4885
5195
|
...paginationInput
|
|
4886
5196
|
});
|
|
4887
5197
|
var tasksList = {
|
|
4888
5198
|
name: "tasks.list",
|
|
4889
5199
|
group: "tasks",
|
|
4890
5200
|
description: "List tasks, optionally filtered by project",
|
|
4891
|
-
inputSchema:
|
|
4892
|
-
outputSchema:
|
|
5201
|
+
inputSchema: inputSchema91,
|
|
5202
|
+
outputSchema: z93.any(),
|
|
4893
5203
|
annotations: { readOnlyHint: true },
|
|
4894
5204
|
async execute(input, ctx) {
|
|
4895
5205
|
if (input.project) {
|
|
@@ -4909,18 +5219,18 @@ var tasksList = {
|
|
|
4909
5219
|
};
|
|
4910
5220
|
|
|
4911
5221
|
// src/commands/tasks/messages.ts
|
|
4912
|
-
import { z as
|
|
4913
|
-
var
|
|
4914
|
-
taskId:
|
|
4915
|
-
cursor:
|
|
4916
|
-
limit:
|
|
5222
|
+
import { z as z94 } from "zod";
|
|
5223
|
+
var inputSchema92 = z94.object({
|
|
5224
|
+
taskId: z94.string().uuid().describe("Task ID"),
|
|
5225
|
+
cursor: z94.string().optional().describe("Pagination cursor"),
|
|
5226
|
+
limit: z94.number().int().min(1).max(100).default(50).describe("Results per page")
|
|
4917
5227
|
});
|
|
4918
5228
|
var tasksMessages = {
|
|
4919
5229
|
name: "tasks.messages",
|
|
4920
5230
|
group: "tasks",
|
|
4921
5231
|
description: "List messages for a task",
|
|
4922
|
-
inputSchema:
|
|
4923
|
-
outputSchema:
|
|
5232
|
+
inputSchema: inputSchema92,
|
|
5233
|
+
outputSchema: z94.any(),
|
|
4924
5234
|
annotations: { readOnlyHint: true },
|
|
4925
5235
|
async execute(input, ctx) {
|
|
4926
5236
|
const queryParams = { limit: input.limit };
|
|
@@ -4939,22 +5249,22 @@ var tasksMessages = {
|
|
|
4939
5249
|
};
|
|
4940
5250
|
|
|
4941
5251
|
// src/commands/tasks/send.ts
|
|
4942
|
-
import { z as
|
|
5252
|
+
import { z as z95 } from "zod";
|
|
4943
5253
|
init_profiles();
|
|
4944
5254
|
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4945
|
-
var
|
|
4946
|
-
taskId:
|
|
4947
|
-
message:
|
|
4948
|
-
fileRefs:
|
|
4949
|
-
wait:
|
|
4950
|
-
stream:
|
|
5255
|
+
var inputSchema93 = z95.object({
|
|
5256
|
+
taskId: z95.string().uuid().describe("Task ID"),
|
|
5257
|
+
message: z95.string().min(1).describe("Message content"),
|
|
5258
|
+
fileRefs: z95.string().optional().describe("Comma-separated file reference UUIDs"),
|
|
5259
|
+
wait: z95.boolean().default(false).describe("Wait for agent response (polling)"),
|
|
5260
|
+
stream: z95.boolean().default(false).describe("Stream agent response in real-time (SSE)")
|
|
4951
5261
|
});
|
|
4952
5262
|
var tasksSend = {
|
|
4953
5263
|
name: "tasks.send",
|
|
4954
5264
|
group: "tasks",
|
|
4955
5265
|
description: "Send a message to a task",
|
|
4956
|
-
inputSchema:
|
|
4957
|
-
outputSchema:
|
|
5266
|
+
inputSchema: inputSchema93,
|
|
5267
|
+
outputSchema: z95.any(),
|
|
4958
5268
|
async execute(input, ctx) {
|
|
4959
5269
|
let referencedFileIds;
|
|
4960
5270
|
if (input.fileRefs) {
|
|
@@ -5011,18 +5321,18 @@ var tasksSend = {
|
|
|
5011
5321
|
|
|
5012
5322
|
// src/commands/tasks/update.ts
|
|
5013
5323
|
init_errors();
|
|
5014
|
-
import { z as
|
|
5015
|
-
var
|
|
5016
|
-
taskId:
|
|
5017
|
-
title:
|
|
5018
|
-
status:
|
|
5324
|
+
import { z as z96 } from "zod";
|
|
5325
|
+
var inputSchema94 = z96.object({
|
|
5326
|
+
taskId: z96.string().uuid().describe("Task ID"),
|
|
5327
|
+
title: z96.string().min(1).optional().describe("New task title"),
|
|
5328
|
+
status: z96.string().optional().describe("New task status")
|
|
5019
5329
|
});
|
|
5020
5330
|
var tasksUpdate = {
|
|
5021
5331
|
name: "tasks.update",
|
|
5022
5332
|
group: "tasks",
|
|
5023
5333
|
description: "Update an existing task",
|
|
5024
|
-
inputSchema:
|
|
5025
|
-
outputSchema:
|
|
5334
|
+
inputSchema: inputSchema94,
|
|
5335
|
+
outputSchema: z96.any(),
|
|
5026
5336
|
annotations: { idempotentHint: true },
|
|
5027
5337
|
async execute(input, ctx) {
|
|
5028
5338
|
const { taskId, ...fields } = input;
|
|
@@ -5047,220 +5357,6 @@ function registerTaskCommands() {
|
|
|
5047
5357
|
return [tasksList, tasksGet, tasksCreate, tasksSend, tasksMessages, tasksUpdate, tasksArchive];
|
|
5048
5358
|
}
|
|
5049
5359
|
|
|
5050
|
-
// src/commands/update.ts
|
|
5051
|
-
init_config();
|
|
5052
|
-
import { execFileSync } from "node:child_process";
|
|
5053
|
-
import { createHash } from "node:crypto";
|
|
5054
|
-
import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync7, rmSync } from "node:fs";
|
|
5055
|
-
import { tmpdir } from "node:os";
|
|
5056
|
-
import { join as join7 } from "node:path";
|
|
5057
|
-
import { Readable } from "node:stream";
|
|
5058
|
-
import { pipeline } from "node:stream/promises";
|
|
5059
|
-
import pc5 from "picocolors";
|
|
5060
|
-
|
|
5061
|
-
// src/lib/install-method.ts
|
|
5062
|
-
function detectInstallMethod() {
|
|
5063
|
-
const execPath = process.argv[1] ?? "";
|
|
5064
|
-
if (execPath.includes("node_modules")) {
|
|
5065
|
-
return "npm";
|
|
5066
|
-
}
|
|
5067
|
-
if (execPath.includes("/Cellar/") || execPath.includes("/homebrew/")) {
|
|
5068
|
-
return "homebrew";
|
|
5069
|
-
}
|
|
5070
|
-
return "binary";
|
|
5071
|
-
}
|
|
5072
|
-
function getUpdateInstruction(method) {
|
|
5073
|
-
switch (method) {
|
|
5074
|
-
case "npm":
|
|
5075
|
-
return "npm install -g @elnora-ai/cli@latest";
|
|
5076
|
-
case "homebrew":
|
|
5077
|
-
return "brew upgrade elnora";
|
|
5078
|
-
case "binary":
|
|
5079
|
-
return "elnora update --install";
|
|
5080
|
-
}
|
|
5081
|
-
}
|
|
5082
|
-
|
|
5083
|
-
// src/commands/update.ts
|
|
5084
|
-
var NPM_REGISTRY_URL2 = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
5085
|
-
var GITHUB_REPO = "Elnora-AI/elnora-cli";
|
|
5086
|
-
function getPlatformTarget() {
|
|
5087
|
-
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
5088
|
-
switch (process.platform) {
|
|
5089
|
-
case "darwin":
|
|
5090
|
-
return { target: `elnora-macos-${arch}`, ext: "tar.gz" };
|
|
5091
|
-
case "linux":
|
|
5092
|
-
return { target: `elnora-linux-${arch}`, ext: "tar.gz" };
|
|
5093
|
-
case "win32":
|
|
5094
|
-
return { target: `elnora-win-${arch}.exe`, ext: "zip" };
|
|
5095
|
-
default:
|
|
5096
|
-
return null;
|
|
5097
|
-
}
|
|
5098
|
-
}
|
|
5099
|
-
async function fetchLatestVersion() {
|
|
5100
|
-
const res = await fetch(NPM_REGISTRY_URL2, { signal: AbortSignal.timeout(5e3) });
|
|
5101
|
-
if (!res.ok) throw new Error(`Registry returned HTTP ${res.status}`);
|
|
5102
|
-
const data = await res.json();
|
|
5103
|
-
return data.version ?? "unknown";
|
|
5104
|
-
}
|
|
5105
|
-
async function downloadToFile(url, dest) {
|
|
5106
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(12e4), redirect: "follow" });
|
|
5107
|
-
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
|
5108
|
-
const nodeStream = Readable.fromWeb(res.body);
|
|
5109
|
-
await pipeline(nodeStream, createWriteStream(dest));
|
|
5110
|
-
}
|
|
5111
|
-
async function installBinaryUpdate(latest) {
|
|
5112
|
-
const color2 = isColorEnabled();
|
|
5113
|
-
const platformInfo = getPlatformTarget();
|
|
5114
|
-
if (!platformInfo) {
|
|
5115
|
-
console.error(`Unsupported platform: ${process.platform}.`);
|
|
5116
|
-
console.error("Try: npm install -g @elnora-ai/cli@latest");
|
|
5117
|
-
process.exitCode = 1;
|
|
5118
|
-
return;
|
|
5119
|
-
}
|
|
5120
|
-
const { target, ext } = platformInfo;
|
|
5121
|
-
const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
|
|
5122
|
-
const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
|
|
5123
|
-
const tmp = join7(tmpdir(), `elnora-update-${Date.now()}`);
|
|
5124
|
-
mkdirSync4(tmp, { recursive: true });
|
|
5125
|
-
const archivePath = join7(tmp, `${target}.${ext}`);
|
|
5126
|
-
try {
|
|
5127
|
-
console.error("Downloading...");
|
|
5128
|
-
await downloadToFile(binaryUrl, archivePath);
|
|
5129
|
-
let checksumVerified = false;
|
|
5130
|
-
try {
|
|
5131
|
-
const checksumRes = await fetch(checksumUrl, { signal: AbortSignal.timeout(15e3) });
|
|
5132
|
-
if (checksumRes.ok) {
|
|
5133
|
-
const checksumText = await checksumRes.text();
|
|
5134
|
-
const expected = checksumText.split(/\s/)[0].trim();
|
|
5135
|
-
if (expected.length === 64) {
|
|
5136
|
-
const actual = createHash("sha256").update(readFileSync7(archivePath)).digest("hex");
|
|
5137
|
-
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
5138
|
-
console.error("Checksum verification failed. The download may be corrupted.");
|
|
5139
|
-
process.exitCode = 1;
|
|
5140
|
-
return;
|
|
5141
|
-
}
|
|
5142
|
-
checksumVerified = true;
|
|
5143
|
-
}
|
|
5144
|
-
}
|
|
5145
|
-
} catch {
|
|
5146
|
-
}
|
|
5147
|
-
if (!checksumVerified) {
|
|
5148
|
-
console.error("Warning: Could not verify download checksum. Proceeding anyway.");
|
|
5149
|
-
}
|
|
5150
|
-
if (process.platform === "win32") {
|
|
5151
|
-
const extractDir = join7(tmp, "extracted");
|
|
5152
|
-
execFileSync("powershell", [
|
|
5153
|
-
"-NoProfile",
|
|
5154
|
-
"-Command",
|
|
5155
|
-
`Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
|
|
5156
|
-
]);
|
|
5157
|
-
const installDir = join7(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
5158
|
-
mkdirSync4(installDir, { recursive: true });
|
|
5159
|
-
copyFileSync(join7(extractDir, target), join7(installDir, "elnora.exe"));
|
|
5160
|
-
} else {
|
|
5161
|
-
execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
|
|
5162
|
-
const installDir = process.env.ELNORA_INSTALL_DIR ?? join7(process.env.HOME ?? "", ".local", "bin");
|
|
5163
|
-
mkdirSync4(installDir, { recursive: true });
|
|
5164
|
-
const dest = join7(installDir, "elnora");
|
|
5165
|
-
copyFileSync(join7(tmp, target), dest);
|
|
5166
|
-
chmodSync2(dest, 493);
|
|
5167
|
-
}
|
|
5168
|
-
const done = color2 ? `${pc5.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
|
|
5169
|
-
console.error(done);
|
|
5170
|
-
} finally {
|
|
5171
|
-
try {
|
|
5172
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
5173
|
-
} catch {
|
|
5174
|
-
}
|
|
5175
|
-
}
|
|
5176
|
-
}
|
|
5177
|
-
function addUpdateCommand(program2) {
|
|
5178
|
-
program2.command("update").description("Check for updates (use --install to apply)").option("--install", "Download and install the update").action(async (opts) => {
|
|
5179
|
-
const color2 = isColorEnabled();
|
|
5180
|
-
const method = detectInstallMethod();
|
|
5181
|
-
let latest;
|
|
5182
|
-
try {
|
|
5183
|
-
latest = await fetchLatestVersion();
|
|
5184
|
-
} catch {
|
|
5185
|
-
console.error("Failed to check for updates.");
|
|
5186
|
-
process.exitCode = 1;
|
|
5187
|
-
return;
|
|
5188
|
-
}
|
|
5189
|
-
if (latest === "unknown") {
|
|
5190
|
-
console.error("Could not determine latest version.");
|
|
5191
|
-
process.exitCode = 1;
|
|
5192
|
-
return;
|
|
5193
|
-
}
|
|
5194
|
-
if (latest === VERSION || !isNewerVersion(latest, VERSION)) {
|
|
5195
|
-
const msg2 = color2 ? `${pc5.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
|
|
5196
|
-
console.error(msg2);
|
|
5197
|
-
return;
|
|
5198
|
-
}
|
|
5199
|
-
const instruction = getUpdateInstruction(method);
|
|
5200
|
-
if (!opts.install) {
|
|
5201
|
-
const msg2 = color2 ? `${pc5.yellow("Update available:")} v${VERSION} \u2192 v${latest}` : `Update available: v${VERSION} \u2192 v${latest}`;
|
|
5202
|
-
console.error(msg2);
|
|
5203
|
-
console.error(`Run: ${instruction}`);
|
|
5204
|
-
return;
|
|
5205
|
-
}
|
|
5206
|
-
const msg = color2 ? `Updating Elnora CLI ${pc5.dim(`v${VERSION}`)} \u2192 ${pc5.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
|
|
5207
|
-
console.error(msg);
|
|
5208
|
-
if (method === "npm") {
|
|
5209
|
-
console.error("Installed via npm. Run:");
|
|
5210
|
-
console.error(` npm install -g @elnora-ai/cli@latest`);
|
|
5211
|
-
return;
|
|
5212
|
-
}
|
|
5213
|
-
if (method === "homebrew") {
|
|
5214
|
-
console.error("Installed via Homebrew. Run:");
|
|
5215
|
-
console.error(` brew upgrade elnora`);
|
|
5216
|
-
return;
|
|
5217
|
-
}
|
|
5218
|
-
try {
|
|
5219
|
-
await installBinaryUpdate(latest);
|
|
5220
|
-
} catch {
|
|
5221
|
-
console.error("Update failed. Try manually:");
|
|
5222
|
-
console.error(
|
|
5223
|
-
process.platform === "win32" ? " irm https://cli.elnora.ai/install.ps1 | iex" : " curl -fsSL https://cli.elnora.ai/install.sh | bash"
|
|
5224
|
-
);
|
|
5225
|
-
process.exitCode = 1;
|
|
5226
|
-
}
|
|
5227
|
-
});
|
|
5228
|
-
}
|
|
5229
|
-
|
|
5230
|
-
// src/commands/whoami.ts
|
|
5231
|
-
init_client();
|
|
5232
|
-
init_profiles();
|
|
5233
|
-
function addWhoamiCommand(program2) {
|
|
5234
|
-
program2.command("whoami").description("Show current authentication context").action(async () => {
|
|
5235
|
-
const parentOpts = program2.opts();
|
|
5236
|
-
const profileName = parentOpts.profile ?? "default";
|
|
5237
|
-
try {
|
|
5238
|
-
const hint = resolveCredentialHint(profileName);
|
|
5239
|
-
let orgName = "unknown";
|
|
5240
|
-
try {
|
|
5241
|
-
const client = new ElnoraApiClient(resolveApiKey(profileName));
|
|
5242
|
-
const orgs = await client.get("organizations");
|
|
5243
|
-
const orgList = Array.isArray(orgs) ? orgs : orgs?.items ?? [];
|
|
5244
|
-
if (orgList.length > 0) orgName = orgList[0]?.name ?? "unknown";
|
|
5245
|
-
} catch {
|
|
5246
|
-
}
|
|
5247
|
-
if (parentOpts.json) {
|
|
5248
|
-
console.log(JSON.stringify({ profile: profileName, credential: hint, orgName }, null, 2));
|
|
5249
|
-
} else {
|
|
5250
|
-
console.log(`
|
|
5251
|
-
Profile: ${profileName}`);
|
|
5252
|
-
console.log(`Credential: ${hint}`);
|
|
5253
|
-
console.log(`Org: ${orgName}`);
|
|
5254
|
-
console.log("");
|
|
5255
|
-
}
|
|
5256
|
-
} catch (e) {
|
|
5257
|
-
console.error(e instanceof Error ? e.message : "Not authenticated");
|
|
5258
|
-
console.error("Run: elnora auth login");
|
|
5259
|
-
process.exit(3);
|
|
5260
|
-
}
|
|
5261
|
-
});
|
|
5262
|
-
}
|
|
5263
|
-
|
|
5264
5360
|
// src/core/registry.ts
|
|
5265
5361
|
var CommandRegistry = class {
|
|
5266
5362
|
commands = /* @__PURE__ */ new Map();
|
|
@@ -5288,6 +5384,34 @@ var CommandRegistry = class {
|
|
|
5288
5384
|
}
|
|
5289
5385
|
};
|
|
5290
5386
|
|
|
5387
|
+
// src/core/build-registry.ts
|
|
5388
|
+
function buildRegistry() {
|
|
5389
|
+
const registry2 = new CommandRegistry();
|
|
5390
|
+
const commandGroups = [
|
|
5391
|
+
[healthCheck],
|
|
5392
|
+
registerAuthCommands(),
|
|
5393
|
+
registerProjectCommands(),
|
|
5394
|
+
registerTaskCommands(),
|
|
5395
|
+
registerFileCommands(),
|
|
5396
|
+
registerOrgCommands(),
|
|
5397
|
+
registerFolderCommands(),
|
|
5398
|
+
registerSearchCommands(),
|
|
5399
|
+
registerProtocolCommands(),
|
|
5400
|
+
registerLibraryCommands(),
|
|
5401
|
+
registerAccountCommands(),
|
|
5402
|
+
registerApiKeyCommands(),
|
|
5403
|
+
registerAuditCommands(),
|
|
5404
|
+
registerFeedbackCommands(),
|
|
5405
|
+
registerFlagCommands()
|
|
5406
|
+
];
|
|
5407
|
+
for (const commands of commandGroups) {
|
|
5408
|
+
for (const cmd of commands) {
|
|
5409
|
+
registry2.register(cmd);
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
return registry2;
|
|
5413
|
+
}
|
|
5414
|
+
|
|
5291
5415
|
// src/cli.ts
|
|
5292
5416
|
init_errors();
|
|
5293
5417
|
process.on("uncaughtException", (err) => {
|
|
@@ -5311,28 +5435,7 @@ process.on("unhandledRejection", (reason) => {
|
|
|
5311
5435
|
}
|
|
5312
5436
|
process.exitCode = 1;
|
|
5313
5437
|
});
|
|
5314
|
-
var registry =
|
|
5315
|
-
var commandGroups = [
|
|
5316
|
-
[healthCheck],
|
|
5317
|
-
registerAuthCommands(),
|
|
5318
|
-
registerProjectCommands(),
|
|
5319
|
-
registerTaskCommands(),
|
|
5320
|
-
registerFileCommands(),
|
|
5321
|
-
registerOrgCommands(),
|
|
5322
|
-
registerFolderCommands(),
|
|
5323
|
-
registerSearchCommands(),
|
|
5324
|
-
registerLibraryCommands(),
|
|
5325
|
-
registerAccountCommands(),
|
|
5326
|
-
registerApiKeyCommands(),
|
|
5327
|
-
registerAuditCommands(),
|
|
5328
|
-
registerFeedbackCommands(),
|
|
5329
|
-
registerFlagCommands()
|
|
5330
|
-
];
|
|
5331
|
-
for (const commands of commandGroups) {
|
|
5332
|
-
for (const cmd of commands) {
|
|
5333
|
-
registry.register(cmd);
|
|
5334
|
-
}
|
|
5335
|
-
}
|
|
5438
|
+
var registry = buildRegistry();
|
|
5336
5439
|
var program = buildProgram(registry);
|
|
5337
5440
|
addMcpCommands(program, registry);
|
|
5338
5441
|
addDoctorCommand(program);
|