@elnora-ai/cli 1.4.0 → 1.5.0

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