@elnora-ai/cli 1.1.1 → 1.2.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 (5) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +155 -137
  3. package/dist/cli.js +996 -447
  4. package/package.json +66 -67
  5. package/dist/cli.cjs +0 -442
package/dist/cli.js CHANGED
@@ -10,18 +10,6 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/lib/config.ts
13
- import { readFileSync } from "node:fs";
14
- import { dirname, join } from "node:path";
15
- import { fileURLToPath } from "node:url";
16
- function getVersion() {
17
- try {
18
- const dir = dirname(fileURLToPath(import.meta.url));
19
- const pkg = JSON.parse(readFileSync(join(dir, "../../package.json"), "utf-8"));
20
- return pkg.version ?? "0.0.0-dev";
21
- } catch {
22
- return "0.0.0-dev";
23
- }
24
- }
25
13
  function buildUrl(endpointKey, params, baseUrl) {
26
14
  const path = ENDPOINTS[endpointKey];
27
15
  if (!path) throw new Error(`Unknown endpoint: ${endpointKey}`);
@@ -33,12 +21,13 @@ function buildUrl(endpointKey, params, baseUrl) {
33
21
  }
34
22
  return `${baseUrl ?? BASE_URL}${resolved}`;
35
23
  }
36
- var VERSION, BASE_URL, AI_SERVER_URL, ENDPOINTS, DEFAULT_HEADERS;
24
+ var VERSION, PRODUCTION_BASE_URL, BASE_URL, AI_SERVER_URL, ENDPOINTS, DEFAULT_HEADERS;
37
25
  var init_config = __esm({
38
26
  "src/lib/config.ts"() {
39
27
  "use strict";
40
- VERSION = getVersion();
41
- BASE_URL = "https://platform.elnora.ai/api/v1";
28
+ VERSION = typeof __APP_VERSION__ !== "undefined" ? __APP_VERSION__ : process.env.npm_package_version ?? "0.0.0-dev";
29
+ PRODUCTION_BASE_URL = "https://platform.elnora.ai/api/v1";
30
+ BASE_URL = process.env.ELNORA_API_URL ?? PRODUCTION_BASE_URL;
42
31
  AI_SERVER_URL = process.env.ELNORA_AI_SERVER_URL ?? "https://platform.elnora.ai/ai-server";
43
32
  ENDPOINTS = {
44
33
  // Projects
@@ -87,6 +76,7 @@ var init_config = __esm({
87
76
  // Organization invitations
88
77
  org_invitations: "/organizations/{orgId}/invitations",
89
78
  org_invitation: "/organizations/{orgId}/invitations/{invId}",
79
+ org_invitation_resend: "/organizations/{orgId}/invitations/{invId}/resend",
90
80
  // Public invitations
91
81
  invitation_info: "/invitations/{token}",
92
82
  invitation_accept: "/invitations/{token}/accept",
@@ -175,6 +165,23 @@ function formatErrorPayload(err) {
175
165
  }
176
166
  return payload;
177
167
  }
168
+ function formatErrorForHuman(err) {
169
+ const lines = [];
170
+ const message = scrub(err.message);
171
+ lines.push(`Error: ${message}`);
172
+ if (err instanceof ElnoraError && err.suggestion) {
173
+ const steps = err.suggestion.split("\n").map((s) => s.trim()).filter(Boolean);
174
+ if (steps.length === 1) {
175
+ lines.push("", ` ${steps[0]}`);
176
+ } else {
177
+ lines.push("", "Next steps:");
178
+ for (const step of steps) {
179
+ lines.push(` ${step}`);
180
+ }
181
+ }
182
+ }
183
+ return lines.join("\n");
184
+ }
178
185
  var ElnoraError, AuthError, NotFoundError, RateLimitError, ValidationError, ServerError, EXIT_CODES, SCRUB_KEY_VALUE_RE, SCRUB_LONG_TOKEN_RE;
179
186
  var init_errors = __esm({
180
187
  "src/lib/errors.ts"() {
@@ -244,9 +251,9 @@ var init_errors = __esm({
244
251
  });
245
252
 
246
253
  // src/lib/profiles.ts
247
- import { chmodSync, existsSync, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "node:fs";
254
+ import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
248
255
  import { homedir } from "node:os";
249
- import { join as join2 } from "node:path";
256
+ import { join } from "node:path";
250
257
  function validateProfileName(name) {
251
258
  if (!PROFILE_NAME_RE.test(name)) {
252
259
  throw new ValidationError(
@@ -305,7 +312,7 @@ function serializeProfiles(profiles) {
305
312
  return parts.join("\n");
306
313
  }
307
314
  function writeSecureFile(filePath, content) {
308
- const dir = join2(filePath, "..");
315
+ const dir = join(filePath, "..");
309
316
  mkdirSync(dir, { recursive: true });
310
317
  if (process.platform !== "win32") {
311
318
  try {
@@ -318,7 +325,7 @@ function writeSecureFile(filePath, content) {
318
325
  function loadProfiles() {
319
326
  if (!existsSync(PROFILES_FILE)) return {};
320
327
  try {
321
- const text = readFileSync2(PROFILES_FILE, "utf-8");
328
+ const text = readFileSync(PROFILES_FILE, "utf-8");
322
329
  return parseProfilesToml(text);
323
330
  } catch {
324
331
  return {};
@@ -368,7 +375,7 @@ function migrateConfigIfNeeded() {
368
375
  if (!existsSync(LEGACY_CONFIG_FILE)) return false;
369
376
  let apiKey = "";
370
377
  try {
371
- const text = readFileSync2(LEGACY_CONFIG_FILE, "utf-8");
378
+ const text = readFileSync(LEGACY_CONFIG_FILE, "utf-8");
372
379
  for (const line of text.split("\n")) {
373
380
  const trimmed = line.trim();
374
381
  if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -396,9 +403,9 @@ var init_profiles = __esm({
396
403
  "src/lib/profiles.ts"() {
397
404
  "use strict";
398
405
  init_errors();
399
- CONFIG_DIR = join2(homedir(), ".elnora");
400
- PROFILES_FILE = join2(CONFIG_DIR, "profiles.toml");
401
- LEGACY_CONFIG_FILE = join2(CONFIG_DIR, "config.toml");
406
+ CONFIG_DIR = join(homedir(), ".elnora");
407
+ PROFILES_FILE = join(CONFIG_DIR, "profiles.toml");
408
+ LEGACY_CONFIG_FILE = join(CONFIG_DIR, "config.toml");
402
409
  PROFILE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
403
410
  }
404
411
  });
@@ -492,7 +499,7 @@ var init_client = __esm({
492
499
  }
493
500
  url = `${url}?${qs.toString()}`;
494
501
  }
495
- if (this.baseUrl === BASE_URL) {
502
+ if (this.baseUrl === PRODUCTION_BASE_URL) {
496
503
  validateApiUrl(url);
497
504
  }
498
505
  const now = Date.now();
@@ -544,14 +551,30 @@ var init_client = __esm({
544
551
  bodyText = scrub(await response.text());
545
552
  } catch {
546
553
  }
547
- if (response.status === 401 || response.status === 403) {
548
- throw new AuthError(bodyText.slice(0, 200) || "Authentication failed");
554
+ let errorMessage = bodyText;
555
+ try {
556
+ const parsed = JSON.parse(bodyText);
557
+ if (parsed.messages?.length) {
558
+ errorMessage = parsed.messages.join(" ");
559
+ } else if (parsed.message) {
560
+ errorMessage = parsed.message;
561
+ }
562
+ } catch {
563
+ }
564
+ if (response.status === 401) {
565
+ throw new AuthError(errorMessage.slice(0, 200) || "Authentication failed");
566
+ }
567
+ if (response.status === 403) {
568
+ throw new ElnoraError(errorMessage.slice(0, 200) || "Access denied", {
569
+ code: "FORBIDDEN",
570
+ suggestion: "You don't have permission to access this resource. Check the resource ID or your organization membership."
571
+ });
549
572
  }
550
573
  if (response.status === 404) {
551
- throw new NotFoundError("resource", bodyText.slice(0, 200) || "not found");
574
+ throw new NotFoundError("resource", errorMessage.slice(0, 200) || "not found");
552
575
  }
553
576
  if (response.status === 422) {
554
- throw new ValidationError(bodyText.slice(0, 500) || "Validation error");
577
+ throw new ValidationError(errorMessage.slice(0, 500) || "Validation error");
555
578
  }
556
579
  if (response.status === 429) {
557
580
  if (attempt < MAX_RETRIES) {
@@ -824,6 +847,7 @@ var init_http_server = __esm({
824
847
 
825
848
  // src/adapters/cli.ts
826
849
  init_client();
850
+ init_config();
827
851
  init_errors();
828
852
  import { Command } from "commander";
829
853
 
@@ -979,12 +1003,12 @@ function kebabToCamel(str) {
979
1003
  return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
980
1004
  }
981
1005
  function buildProgram(registry2) {
982
- const program2 = new Command().name("elnora").description("Elnora AI Platform CLI").version("0.0.1").option("--compact", "Token-efficient minimal output", false).option("--output <format>", "Output format (json, csv)", "json").option("--fields <fields>", "Comma-separated fields to include").option("--profile <name>", "Named profile to use").option("--json", "Force JSON output", false);
1006
+ const program2 = new Command().name("elnora").description("Elnora AI Platform CLI").version(VERSION).option("--compact", "Token-efficient minimal output", false).option("--output <format>", "Output format (json, csv)", "json").option("--fields <fields>", "Comma-separated fields to include").option("--profile <name>", "Named profile to use").option("--json", "Force JSON output", false);
983
1007
  for (const group of registry2.groups()) {
984
1008
  const groupCmd = new Command(group).description(`Manage ${group}`);
985
1009
  const commands = registry2.byGroup(group);
986
1010
  for (const cmd of commands) {
987
- const subName = cmd.name.split(".").slice(1).join("-");
1011
+ const subName = camelToKebab(cmd.name.split(".").slice(1).join("-"));
988
1012
  const sub = new Command(subName).description(cmd.description);
989
1013
  const opts = zodToCommanderOptions(cmd.inputSchema);
990
1014
  for (const opt of opts) {
@@ -1024,7 +1048,11 @@ function buildProgram(registry2) {
1024
1048
  }
1025
1049
  }
1026
1050
  const profileName = parentOpts.profile ?? "default";
1027
- const client = ElnoraApiClient.fromEnv(profileName);
1051
+ if (parentOpts.profile && !("profile" in input && input.profile !== "default")) {
1052
+ input.profile = parentOpts.profile;
1053
+ }
1054
+ const skipAuth = cmd.name.startsWith("auth.login") || cmd.name === "auth.logout" || cmd.name === "auth.profiles";
1055
+ const client = skipAuth ? new ElnoraApiClient("placeholder") : ElnoraApiClient.fromEnv(profileName);
1028
1056
  const ctx = {
1029
1057
  client,
1030
1058
  profileName,
@@ -1037,19 +1065,37 @@ function buildProgram(registry2) {
1037
1065
  };
1038
1066
  const parsed = cmd.inputSchema.parse(input);
1039
1067
  const result = await cmd.execute(parsed, ctx);
1040
- const formatted = formatOutput(result, {
1041
- format: ctx.output.format,
1042
- compact: ctx.output.compact,
1043
- fields: ctx.output.fields
1044
- });
1045
- process.stdout.write(`${formatted}
1068
+ if (result != null) {
1069
+ const isStreamed = typeof result === "object" && result.streamed === true;
1070
+ if (!isStreamed) {
1071
+ const hasGlobalFlags = ctx.output.compact || ctx.output.format !== "json" || ctx.output.fields;
1072
+ let formatted;
1073
+ if (cmd.formatOutput && !hasGlobalFlags) {
1074
+ formatted = cmd.formatOutput(result, ctx.output.format);
1075
+ } else {
1076
+ formatted = formatOutput(result, {
1077
+ format: ctx.output.format,
1078
+ compact: ctx.output.compact,
1079
+ fields: ctx.output.fields
1080
+ });
1081
+ }
1082
+ if (formatted) {
1083
+ process.stdout.write(`${formatted}
1046
1084
  `);
1085
+ }
1086
+ }
1087
+ }
1047
1088
  } catch (err) {
1048
1089
  const error = err instanceof Error ? err : new Error(String(err));
1049
- const payload = formatErrorPayload(error);
1050
- process.stderr.write(`${JSON.stringify(payload, null, 2)}
1090
+ if (process.stderr.isTTY) {
1091
+ process.stderr.write(`${formatErrorForHuman(error)}
1051
1092
  `);
1052
- process.exit(getExitCode(error));
1093
+ } else {
1094
+ const payload = formatErrorPayload(error);
1095
+ process.stderr.write(`${JSON.stringify(payload, null, 2)}
1096
+ `);
1097
+ }
1098
+ process.exitCode = getExitCode(error);
1053
1099
  }
1054
1100
  });
1055
1101
  groupCmd.addCommand(sub);
@@ -1454,7 +1500,7 @@ var paginationInput = {
1454
1500
 
1455
1501
  // src/commands/audit/list.ts
1456
1502
  var inputSchema15 = z16.object({
1457
- org: z16.string().uuid().describe("Organization ID"),
1503
+ orgId: z16.string().uuid().describe("Organization ID"),
1458
1504
  ...paginationInput,
1459
1505
  action: z16.string().optional().describe("Filter by action type"),
1460
1506
  userId: z16.string().optional().describe("Filter by user ID")
@@ -1474,7 +1520,7 @@ var auditList = {
1474
1520
  if (input.action) queryParams.action = input.action;
1475
1521
  if (input.userId) queryParams.userId = input.userId;
1476
1522
  return ctx.client.get("audit_log", {
1477
- pathParams: { orgId: input.org },
1523
+ pathParams: { orgId: input.orgId },
1478
1524
  queryParams
1479
1525
  });
1480
1526
  },
@@ -1494,6 +1540,78 @@ init_client();
1494
1540
  init_errors();
1495
1541
  init_profiles();
1496
1542
  import { z as z17 } from "zod";
1543
+
1544
+ // src/lib/prompt.ts
1545
+ import { createInterface as createInterface2 } from "node:readline";
1546
+ function promptSecret(message) {
1547
+ return new Promise((resolve, reject) => {
1548
+ const { stdin, stdout } = process;
1549
+ if (!stdin.isTTY) {
1550
+ const rl = createInterface2({ input: stdin, output: stdout });
1551
+ rl.question(message, (answer) => {
1552
+ rl.close();
1553
+ resolve(answer.trim());
1554
+ });
1555
+ return;
1556
+ }
1557
+ stdout.write(message);
1558
+ let input = "";
1559
+ const wasRaw = stdin.isRaw;
1560
+ stdin.setRawMode(true);
1561
+ stdin.resume();
1562
+ stdin.setEncoding("utf8");
1563
+ const onData = (char) => {
1564
+ switch (char) {
1565
+ case "\n":
1566
+ case "\r":
1567
+ case "": {
1568
+ stdin.setRawMode(wasRaw);
1569
+ stdin.pause();
1570
+ stdin.removeListener("data", onData);
1571
+ stdout.write("\n");
1572
+ resolve(input.trim());
1573
+ break;
1574
+ }
1575
+ case "":
1576
+ // Ctrl+C
1577
+ case "\x1B": {
1578
+ stdin.setRawMode(wasRaw);
1579
+ stdin.pause();
1580
+ stdin.removeListener("data", onData);
1581
+ stdout.write("\n");
1582
+ reject(new Error("Cancelled"));
1583
+ break;
1584
+ }
1585
+ case "\x7F":
1586
+ case "\b": {
1587
+ if (input.length > 0) {
1588
+ input = input.slice(0, -1);
1589
+ stdout.write("\b \b");
1590
+ }
1591
+ break;
1592
+ }
1593
+ default: {
1594
+ input += char;
1595
+ stdout.write("*");
1596
+ break;
1597
+ }
1598
+ }
1599
+ };
1600
+ stdin.on("data", onData);
1601
+ });
1602
+ }
1603
+
1604
+ // src/lib/tty.ts
1605
+ function isTTY() {
1606
+ return Boolean(process.stdout.isTTY);
1607
+ }
1608
+ function isColorEnabled() {
1609
+ if (process.env.NO_COLOR !== void 0) return false;
1610
+ if (process.env.FORCE_COLOR !== void 0) return true;
1611
+ return isTTY();
1612
+ }
1613
+
1614
+ // src/commands/auth/login.ts
1497
1615
  var inputSchema16 = z17.object({
1498
1616
  apiKey: z17.string().optional().describe("Elnora API key (elnora_live_...)"),
1499
1617
  profile: z17.string().default("default").describe("Profile name to save key under")
@@ -1505,30 +1623,118 @@ var authLogin = {
1505
1623
  inputSchema: inputSchema16,
1506
1624
  outputSchema: z17.any(),
1507
1625
  async execute(input, _ctx) {
1508
- const apiKey = input.apiKey;
1626
+ let apiKey = input.apiKey;
1627
+ const profileName = input.profile;
1628
+ validateProfileName(profileName);
1629
+ const existing = listProfileNames();
1630
+ const profileExists = existing.includes(profileName);
1631
+ if (!apiKey) {
1632
+ if (profileExists) {
1633
+ let savedKey;
1634
+ try {
1635
+ savedKey = getApiKey(profileName);
1636
+ } catch {
1637
+ }
1638
+ if (savedKey && isTTY()) {
1639
+ process.stderr.write(`
1640
+ Checking profile "${profileName}"...`);
1641
+ try {
1642
+ const client = new ElnoraApiClient(savedKey);
1643
+ const result = await client.get("projects", {
1644
+ queryParams: { page: 1, pageSize: 1 }
1645
+ });
1646
+ const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
1647
+ process.stderr.write(` authenticated (${projectCount} project${projectCount !== 1 ? "s" : ""}).
1648
+
1649
+ `);
1650
+ process.stderr.write(" To update your API key, run:\n");
1651
+ process.stderr.write(
1652
+ ` elnora auth login --api-key <new-key>${profileName !== "default" ? ` --profile ${profileName}` : ""}
1653
+
1654
+ `
1655
+ );
1656
+ return null;
1657
+ } catch {
1658
+ process.stderr.write(" key is invalid or expired.\n\n");
1659
+ }
1660
+ }
1661
+ }
1662
+ if (isTTY()) {
1663
+ if (profileExists) {
1664
+ process.stderr.write(` Enter a new API key for profile "${profileName}"
1665
+ `);
1666
+ } else {
1667
+ process.stderr.write("\n Set up Elnora CLI authentication\n");
1668
+ }
1669
+ process.stderr.write(" Get your API key at: https://platform.elnora.ai > Settings > API Keys\n\n");
1670
+ try {
1671
+ apiKey = await promptSecret(" API key (Ctrl+C to cancel): ");
1672
+ } catch {
1673
+ process.stderr.write("\n Login cancelled.\n\n");
1674
+ process.exit(0);
1675
+ }
1676
+ process.stderr.write("\n");
1677
+ }
1678
+ }
1509
1679
  if (!apiKey) {
1510
1680
  throw new ValidationError(
1511
- "API key is required. Use --api-key flag to provide it.",
1681
+ "API key is required. Use --api-key flag or run interactively in a terminal.",
1512
1682
  "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
1513
1683
  );
1514
1684
  }
1515
1685
  const key = apiKey.trim();
1516
1686
  if (!key.startsWith("elnora_live_")) {
1517
- throw new AuthError("API key must start with 'elnora_live_'.");
1687
+ throw new AuthError("API key must start with 'elnora_live_'. Check that you copied the full key.", {
1688
+ suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
1689
+ });
1518
1690
  }
1519
1691
  if (key.length < 20) {
1520
- throw new AuthError("API key looks too short. Check your key and try again.");
1692
+ throw new AuthError("API key looks too short. Make sure you copied the full key.", {
1693
+ suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
1694
+ });
1695
+ }
1696
+ if (isTTY()) {
1697
+ process.stderr.write(" Verifying...");
1698
+ }
1699
+ try {
1700
+ const tempClient = new ElnoraApiClient(key);
1701
+ const result = await tempClient.get("projects", {
1702
+ queryParams: { page: 1, pageSize: 1 }
1703
+ });
1704
+ const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
1705
+ const configPath = saveProfile(profileName, key);
1706
+ if (isTTY()) {
1707
+ process.stderr.write(` Done! ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.
1708
+ `);
1709
+ process.stderr.write(` Saved to profile "${profileName}" at ${configPath}
1710
+
1711
+ `);
1712
+ }
1713
+ return { profile: profileName, verified: true, configPath };
1714
+ } catch (err) {
1715
+ if (isTTY()) {
1716
+ process.stderr.write("\n");
1717
+ }
1718
+ if (err instanceof AuthError) {
1719
+ throw new AuthError("API key is invalid or has been revoked. Please check your key and try again.", {
1720
+ suggestion: "Get a new key from: https://platform.elnora.ai > Settings > API Keys"
1721
+ });
1722
+ }
1723
+ throw err;
1521
1724
  }
1522
- const profileName = input.profile;
1523
- validateProfileName(profileName);
1524
- const tempClient = new ElnoraApiClient(key);
1525
- await tempClient.get("projects", { queryParams: { page: 1, pageSize: 1 } });
1526
- const configPath = saveProfile(profileName, key);
1527
- return { profile: profileName, verified: true, configPath };
1528
1725
  },
1529
1726
  formatOutput(output, format) {
1530
- if (format === "compact") return output.profile ?? JSON.stringify(output);
1531
- return JSON.stringify(output, null, 2);
1727
+ const data = output;
1728
+ if (format === "compact") return data.profile ?? JSON.stringify(output);
1729
+ const lines = [
1730
+ `\u2713 Authenticated! API key saved to profile "${data.profile}".`,
1731
+ "",
1732
+ "Next steps:",
1733
+ " elnora projects list See your projects",
1734
+ " elnora tasks create --project <ID> --stream Start a conversation",
1735
+ " elnora doctor Verify your setup"
1736
+ ];
1737
+ return lines.join("\n");
1532
1738
  }
1533
1739
  };
1534
1740
 
@@ -1772,26 +1978,102 @@ function addCompletionCommand(program2) {
1772
1978
  init_client();
1773
1979
  init_config();
1774
1980
  init_profiles();
1775
- import { existsSync as existsSync2, statSync } from "node:fs";
1776
- import pc from "picocolors";
1981
+ import { existsSync as existsSync3, statSync } from "node:fs";
1982
+ import pc2 from "picocolors";
1777
1983
 
1778
- // src/lib/tty.ts
1779
- function isTTY() {
1780
- return Boolean(process.stdout.isTTY);
1984
+ // src/lib/update-check.ts
1985
+ init_config();
1986
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
1987
+ import { homedir as homedir2 } from "node:os";
1988
+ import { join as join2 } from "node:path";
1989
+ import pc from "picocolors";
1990
+ var NPM_REGISTRY_URL = "https://registry.npmjs.org/@elnora-ai/cli/latest";
1991
+ var CACHE_DIR = join2(homedir2(), ".elnora");
1992
+ var CACHE_FILE = join2(CACHE_DIR, ".update-check");
1993
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1994
+ var FETCH_TIMEOUT_MS = 3e3;
1995
+ function shouldSkip() {
1996
+ if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
1997
+ if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
1998
+ if (!process.stderr.isTTY) return true;
1999
+ return false;
1781
2000
  }
1782
- function isColorEnabled() {
1783
- if (process.env.NO_COLOR !== void 0) return false;
1784
- if (process.env.FORCE_COLOR !== void 0) return true;
1785
- return isTTY();
2001
+ function readCache() {
2002
+ try {
2003
+ if (!existsSync2(CACHE_FILE)) return null;
2004
+ const raw = readFileSync2(CACHE_FILE, "utf-8");
2005
+ return JSON.parse(raw);
2006
+ } catch {
2007
+ return null;
2008
+ }
2009
+ }
2010
+ function writeCache(entry) {
2011
+ try {
2012
+ mkdirSync2(CACHE_DIR, { recursive: true });
2013
+ writeFileSync2(CACHE_FILE, JSON.stringify(entry), "utf-8");
2014
+ } catch {
2015
+ }
2016
+ }
2017
+ function showUpdateNotice(latest) {
2018
+ const color = isColorEnabled();
2019
+ const msg = color ? `
2020
+ ${pc.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
2021
+ Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
2022
+ ` : `
2023
+ Update available: v${VERSION} \u2192 v${latest}
2024
+ Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
2025
+ `;
2026
+ process.stderr.write(msg);
2027
+ }
2028
+ function isNewerVersion(a, b) {
2029
+ const pa = a.split(".").map(Number);
2030
+ const pb = b.split(".").map(Number);
2031
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
2032
+ const na = pa[i] ?? 0;
2033
+ const nb = pb[i] ?? 0;
2034
+ if (na > nb) return true;
2035
+ if (na < nb) return false;
2036
+ }
2037
+ return false;
2038
+ }
2039
+ function registerUpdateCheck() {
2040
+ if (shouldSkip()) return;
2041
+ const cached = readCache();
2042
+ if (cached) {
2043
+ const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
2044
+ if (elapsed < CHECK_INTERVAL_MS) {
2045
+ if (cached.latest && cached.latest !== VERSION && cached.latest !== "unknown") {
2046
+ if (isNewerVersion(cached.latest, VERSION)) {
2047
+ showUpdateNotice(cached.latest);
2048
+ }
2049
+ }
2050
+ return;
2051
+ }
2052
+ }
2053
+ setTimeout(async () => {
2054
+ try {
2055
+ const response = await fetch(NPM_REGISTRY_URL, {
2056
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
2057
+ });
2058
+ if (!response.ok) return;
2059
+ const data = await response.json();
2060
+ const latest = data.version ?? "unknown";
2061
+ writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest });
2062
+ if (latest !== VERSION && latest !== "unknown" && isNewerVersion(latest, VERSION)) {
2063
+ showUpdateNotice(latest);
2064
+ }
2065
+ } catch {
2066
+ }
2067
+ }, 0);
1786
2068
  }
1787
2069
 
1788
2070
  // src/commands/doctor.ts
1789
2071
  function addDoctorCommand(program2) {
1790
2072
  program2.command("doctor").description("Check Elnora CLI configuration and connectivity").action(async () => {
1791
2073
  const color = isColorEnabled();
1792
- const ok = (msg) => color ? ` ${pc.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
1793
- const fail = (msg) => color ? ` ${pc.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
1794
- const warn = (msg) => color ? ` ${pc.yellow("!")} ${msg}` : ` ! ${msg}`;
2074
+ const ok = (msg) => color ? ` ${pc2.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
2075
+ const fail = (msg) => color ? ` ${pc2.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
2076
+ const warn = (msg) => color ? ` ${pc2.yellow("!")} ${msg}` : ` ! ${msg}`;
1795
2077
  console.error(`
1796
2078
  Elnora CLI v${VERSION}
1797
2079
  `);
@@ -1832,7 +2114,7 @@ Elnora CLI v${VERSION}
1832
2114
  if (res.ok) {
1833
2115
  const data = await res.json();
1834
2116
  const latest = data.version ?? "unknown";
1835
- if (latest === VERSION || latest === "unknown") {
2117
+ if (latest === VERSION || latest === "unknown" || !isNewerVersion(latest, VERSION)) {
1836
2118
  console.error(ok(`Version current v${VERSION} (latest: v${latest})`));
1837
2119
  } else {
1838
2120
  console.error(warn(`Update available v${VERSION} \u2192 v${latest}`));
@@ -1850,7 +2132,7 @@ Elnora CLI v${VERSION}
1850
2132
  if (process.platform === "win32") {
1851
2133
  console.error(ok("Config permissions (skipped on Windows)"));
1852
2134
  passed++;
1853
- } else if (!existsSync2(PROFILES_FILE)) {
2135
+ } else if (!existsSync3(PROFILES_FILE)) {
1854
2136
  console.error(warn("Config permissions ~/.elnora/profiles.toml not found"));
1855
2137
  } else {
1856
2138
  const mode = statSync(PROFILES_FILE).mode & 511;
@@ -1878,7 +2160,7 @@ Elnora CLI v${VERSION}
1878
2160
  console.error("All checks passed.");
1879
2161
  } else {
1880
2162
  console.error(`${passed}/${total} checks passed.`);
1881
- if (!existsSync2(PROFILES_FILE)) console.error("\nRun: elnora auth login");
2163
+ if (!existsSync3(PROFILES_FILE)) console.error("\nRun: elnora auth login");
1882
2164
  }
1883
2165
  });
1884
2166
  }
@@ -2752,7 +3034,7 @@ var healthCheck = {
2752
3034
  // src/commands/library/create-folder.ts
2753
3035
  import { z as z51 } from "zod";
2754
3036
  var inputSchema49 = z51.object({
2755
- org: z51.string().uuid().describe("Organization ID"),
3037
+ orgId: z51.string().uuid().describe("Organization ID"),
2756
3038
  name: z51.string().min(1).describe("Folder name"),
2757
3039
  parent: z51.string().uuid().optional().describe("Parent folder ID")
2758
3040
  });
@@ -2766,7 +3048,7 @@ var libraryCreateFolder = {
2766
3048
  const body = { name: input.name };
2767
3049
  if (input.parent) body.parentId = input.parent;
2768
3050
  return ctx.client.post("library_folders", body, {
2769
- pathParams: { orgId: input.org }
3051
+ pathParams: { orgId: input.orgId }
2770
3052
  });
2771
3053
  },
2772
3054
  formatOutput(output, format) {
@@ -2778,7 +3060,7 @@ var libraryCreateFolder = {
2778
3060
  // src/commands/library/delete-folder.ts
2779
3061
  import { z as z52 } from "zod";
2780
3062
  var inputSchema50 = z52.object({
2781
- org: z52.string().uuid().describe("Organization ID"),
3063
+ orgId: z52.string().uuid().describe("Organization ID"),
2782
3064
  folderId: z52.string().uuid().describe("Folder ID to delete")
2783
3065
  });
2784
3066
  var libraryDeleteFolder = {
@@ -2790,7 +3072,7 @@ var libraryDeleteFolder = {
2790
3072
  annotations: { destructiveHint: true },
2791
3073
  async execute(input, ctx) {
2792
3074
  await ctx.client.del("library_folder", {
2793
- pathParams: { orgId: input.org, id: input.folderId }
3075
+ pathParams: { orgId: input.orgId, id: input.folderId }
2794
3076
  });
2795
3077
  return { deleted: true, folderId: input.folderId };
2796
3078
  },
@@ -2803,7 +3085,7 @@ var libraryDeleteFolder = {
2803
3085
  // src/commands/library/files.ts
2804
3086
  import { z as z53 } from "zod";
2805
3087
  var inputSchema51 = z53.object({
2806
- org: z53.string().uuid().describe("Organization ID"),
3088
+ orgId: z53.string().uuid().describe("Organization ID"),
2807
3089
  ...paginationInput
2808
3090
  });
2809
3091
  var libraryFiles = {
@@ -2815,7 +3097,7 @@ var libraryFiles = {
2815
3097
  annotations: { readOnlyHint: true },
2816
3098
  async execute(input, ctx) {
2817
3099
  return ctx.client.get("library_files", {
2818
- pathParams: { orgId: input.org },
3100
+ pathParams: { orgId: input.orgId },
2819
3101
  queryParams: { page: input.page, pageSize: input.pageSize }
2820
3102
  });
2821
3103
  },
@@ -2828,7 +3110,7 @@ var libraryFiles = {
2828
3110
  // src/commands/library/folders.ts
2829
3111
  import { z as z54 } from "zod";
2830
3112
  var inputSchema52 = z54.object({
2831
- org: z54.string().uuid().describe("Organization ID")
3113
+ orgId: z54.string().uuid().describe("Organization ID")
2832
3114
  });
2833
3115
  var libraryFolders = {
2834
3116
  name: "library.folders",
@@ -2839,7 +3121,7 @@ var libraryFolders = {
2839
3121
  annotations: { readOnlyHint: true },
2840
3122
  async execute(input, ctx) {
2841
3123
  return ctx.client.get("library_folders", {
2842
- pathParams: { orgId: input.org }
3124
+ pathParams: { orgId: input.orgId }
2843
3125
  });
2844
3126
  },
2845
3127
  formatOutput(output, format) {
@@ -2851,7 +3133,7 @@ var libraryFolders = {
2851
3133
  // src/commands/library/rename-folder.ts
2852
3134
  import { z as z55 } from "zod";
2853
3135
  var inputSchema53 = z55.object({
2854
- org: z55.string().uuid().describe("Organization ID"),
3136
+ orgId: z55.string().uuid().describe("Organization ID"),
2855
3137
  folderId: z55.string().uuid().describe("Folder ID to rename"),
2856
3138
  name: z55.string().min(1).describe("New folder name")
2857
3139
  });
@@ -2866,7 +3148,7 @@ var libraryRenameFolder = {
2866
3148
  "library_folder",
2867
3149
  { name: input.name },
2868
3150
  {
2869
- pathParams: { orgId: input.org, id: input.folderId }
3151
+ pathParams: { orgId: input.orgId, id: input.folderId }
2870
3152
  }
2871
3153
  );
2872
3154
  },
@@ -3174,10 +3456,14 @@ var orgsInvite = {
3174
3456
  pathParams: { orgId: input.orgId }
3175
3457
  });
3176
3458
  const items = Array.isArray(existing) ? existing : existing?.items ?? [];
3177
- const match = items.find(
3178
- (inv) => inv.email?.toLowerCase() === input.email.toLowerCase() && inv.status === "Pending"
3179
- );
3180
- if (match) return match;
3459
+ const match = items.find((inv) => {
3460
+ const i = inv;
3461
+ return i.email?.toLowerCase() === input.email.toLowerCase();
3462
+ });
3463
+ if (match) {
3464
+ const matchId = match.id;
3465
+ return ctx.client.post("org_invitation_resend", {}, { pathParams: { orgId: input.orgId, invId: matchId } });
3466
+ }
3181
3467
  return ctx.client.post(
3182
3468
  "org_invitations",
3183
3469
  { email: input.email, role: input.role ?? "Member" },
@@ -3276,17 +3562,43 @@ var orgsRemoveMember = {
3276
3562
  }
3277
3563
  };
3278
3564
 
3279
- // src/commands/orgs/set-default.ts
3565
+ // src/commands/orgs/resend-invite.ts
3280
3566
  import { z as z70 } from "zod";
3281
3567
  var inputSchema68 = z70.object({
3282
- orgId: z70.string().uuid().describe("Organization ID to set as default")
3568
+ orgId: z70.string().uuid().describe("Organization ID"),
3569
+ invitationId: z70.string().uuid().describe("Invitation ID to resend")
3570
+ });
3571
+ var orgsResendInvite = {
3572
+ name: "orgs.resendInvite",
3573
+ group: "orgs",
3574
+ description: "Resend an organization invitation email. Regenerates the token and extends the expiry by 7 days. Works on both pending and expired invitations, preserves the invitation ID.",
3575
+ inputSchema: inputSchema68,
3576
+ outputSchema: z70.any(),
3577
+ annotations: { idempotentHint: true },
3578
+ async execute(input, ctx) {
3579
+ return ctx.client.post(
3580
+ "org_invitation_resend",
3581
+ {},
3582
+ { pathParams: { orgId: input.orgId, invId: input.invitationId } }
3583
+ );
3584
+ },
3585
+ formatOutput(output, format) {
3586
+ if (format === "compact") return output.id ?? JSON.stringify(output);
3587
+ return JSON.stringify(output, null, 2);
3588
+ }
3589
+ };
3590
+
3591
+ // src/commands/orgs/set-default.ts
3592
+ import { z as z71 } from "zod";
3593
+ var inputSchema69 = z71.object({
3594
+ orgId: z71.string().uuid().describe("Organization ID to set as default")
3283
3595
  });
3284
3596
  var orgsSetDefault = {
3285
3597
  name: "orgs.setDefault",
3286
3598
  group: "orgs",
3287
3599
  description: "Set an organization as the default",
3288
- inputSchema: inputSchema68,
3289
- outputSchema: z70.any(),
3600
+ inputSchema: inputSchema69,
3601
+ outputSchema: z71.any(),
3290
3602
  annotations: { idempotentHint: true },
3291
3603
  async execute(input, ctx) {
3292
3604
  return ctx.client.put("organization_set_default", void 0, {
@@ -3300,17 +3612,17 @@ var orgsSetDefault = {
3300
3612
  };
3301
3613
 
3302
3614
  // src/commands/orgs/set-stripe.ts
3303
- import { z as z71 } from "zod";
3304
- var inputSchema69 = z71.object({
3305
- orgId: z71.string().uuid().describe("Organization ID"),
3306
- customerId: z71.string().min(1).describe("Stripe customer ID (e.g. cus_xxx)")
3615
+ import { z as z72 } from "zod";
3616
+ var inputSchema70 = z72.object({
3617
+ orgId: z72.string().uuid().describe("Organization ID"),
3618
+ customerId: z72.string().min(1).describe("Stripe customer ID (e.g. cus_xxx)")
3307
3619
  });
3308
3620
  var orgsSetStripe = {
3309
3621
  name: "orgs.setStripe",
3310
3622
  group: "orgs",
3311
3623
  description: "Set the Stripe customer ID for an organization",
3312
- inputSchema: inputSchema69,
3313
- outputSchema: z71.any(),
3624
+ inputSchema: inputSchema70,
3625
+ outputSchema: z72.any(),
3314
3626
  annotations: { idempotentHint: true },
3315
3627
  async execute(input, ctx) {
3316
3628
  return ctx.client.put(
@@ -3327,18 +3639,18 @@ var orgsSetStripe = {
3327
3639
 
3328
3640
  // src/commands/orgs/update.ts
3329
3641
  init_errors();
3330
- import { z as z72 } from "zod";
3331
- var inputSchema70 = z72.object({
3332
- orgId: z72.string().uuid().describe("Organization ID"),
3333
- name: z72.string().min(1).optional().describe("New organization name"),
3334
- description: z72.string().optional().describe("New organization description")
3642
+ import { z as z73 } from "zod";
3643
+ var inputSchema71 = z73.object({
3644
+ orgId: z73.string().uuid().describe("Organization ID"),
3645
+ name: z73.string().min(1).optional().describe("New organization name"),
3646
+ description: z73.string().optional().describe("New organization description")
3335
3647
  });
3336
3648
  var orgsUpdate = {
3337
3649
  name: "orgs.update",
3338
3650
  group: "orgs",
3339
3651
  description: "Update an existing organization",
3340
- inputSchema: inputSchema70,
3341
- outputSchema: z72.any(),
3652
+ inputSchema: inputSchema71,
3653
+ outputSchema: z73.any(),
3342
3654
  annotations: { idempotentHint: true },
3343
3655
  async execute(input, ctx) {
3344
3656
  const { orgId, ...fields } = input;
@@ -3359,18 +3671,18 @@ var orgsUpdate = {
3359
3671
  };
3360
3672
 
3361
3673
  // src/commands/orgs/update-role.ts
3362
- import { z as z73 } from "zod";
3363
- var inputSchema71 = z73.object({
3364
- orgId: z73.string().uuid().describe("Organization ID"),
3365
- membershipId: z73.string().uuid().describe("Membership ID of the member"),
3366
- role: z73.string().min(1).describe("New role to assign")
3674
+ import { z as z74 } from "zod";
3675
+ var inputSchema72 = z74.object({
3676
+ orgId: z74.string().uuid().describe("Organization ID"),
3677
+ membershipId: z74.string().uuid().describe("Membership ID of the member"),
3678
+ role: z74.string().min(1).describe("New role to assign")
3367
3679
  });
3368
3680
  var orgsUpdateRole = {
3369
3681
  name: "orgs.updateRole",
3370
3682
  group: "orgs",
3371
3683
  description: "Update an organization member's role",
3372
- inputSchema: inputSchema71,
3373
- outputSchema: z73.any(),
3684
+ inputSchema: inputSchema72,
3685
+ outputSchema: z74.any(),
3374
3686
  annotations: { idempotentHint: true },
3375
3687
  async execute(input, ctx) {
3376
3688
  return ctx.client.put(
@@ -3402,6 +3714,7 @@ function registerOrgCommands() {
3402
3714
  orgsInvite,
3403
3715
  orgsInvitations,
3404
3716
  orgsCancelInvite,
3717
+ orgsResendInvite,
3405
3718
  orgsInvitationInfo,
3406
3719
  orgsAcceptInvite,
3407
3720
  orgsFiles,
@@ -3410,18 +3723,18 @@ function registerOrgCommands() {
3410
3723
  }
3411
3724
 
3412
3725
  // src/commands/projects/add-member.ts
3413
- import { z as z74 } from "zod";
3414
- var inputSchema72 = z74.object({
3415
- projectId: z74.string().uuid().describe("Project ID"),
3416
- userId: z74.string().uuid().describe("User ID to add"),
3417
- role: z74.string().default("Member").describe("Role to assign (default: Member)")
3726
+ import { z as z75 } from "zod";
3727
+ var inputSchema73 = z75.object({
3728
+ projectId: z75.string().uuid().describe("Project ID"),
3729
+ userId: z75.string().uuid().describe("User ID to add"),
3730
+ role: z75.string().default("Member").describe("Role to assign (default: Member)")
3418
3731
  });
3419
3732
  var projectsAddMember = {
3420
3733
  name: "projects.addMember",
3421
3734
  group: "projects",
3422
3735
  description: "Add a member to a project",
3423
- inputSchema: inputSchema72,
3424
- outputSchema: z74.any(),
3736
+ inputSchema: inputSchema73,
3737
+ outputSchema: z75.any(),
3425
3738
  async execute(input, ctx) {
3426
3739
  return ctx.client.post(
3427
3740
  "project_members",
@@ -3436,16 +3749,16 @@ var projectsAddMember = {
3436
3749
  };
3437
3750
 
3438
3751
  // src/commands/projects/archive.ts
3439
- import { z as z75 } from "zod";
3440
- var inputSchema73 = z75.object({
3441
- projectId: z75.string().uuid().describe("Project ID to archive")
3752
+ import { z as z76 } from "zod";
3753
+ var inputSchema74 = z76.object({
3754
+ projectId: z76.string().uuid().describe("Project ID to archive")
3442
3755
  });
3443
3756
  var projectsArchive = {
3444
3757
  name: "projects.archive",
3445
3758
  group: "projects",
3446
3759
  description: "Archive (delete) a project",
3447
- inputSchema: inputSchema73,
3448
- outputSchema: z75.any(),
3760
+ inputSchema: inputSchema74,
3761
+ outputSchema: z76.any(),
3449
3762
  annotations: { destructiveHint: true },
3450
3763
  async execute(input, ctx) {
3451
3764
  await ctx.client.del("project", {
@@ -3460,18 +3773,18 @@ var projectsArchive = {
3460
3773
  };
3461
3774
 
3462
3775
  // src/commands/projects/create.ts
3463
- import { z as z76 } from "zod";
3464
- var inputSchema74 = z76.object({
3465
- name: z76.string().min(1).describe("Project name"),
3466
- description: z76.string().optional().describe("Project description"),
3467
- icon: z76.string().optional().describe("Project icon")
3776
+ import { z as z77 } from "zod";
3777
+ var inputSchema75 = z77.object({
3778
+ name: z77.string().min(1).describe("Project name"),
3779
+ description: z77.string().optional().describe("Project description"),
3780
+ icon: z77.string().optional().describe("Project icon")
3468
3781
  });
3469
3782
  var projectsCreate = {
3470
3783
  name: "projects.create",
3471
3784
  group: "projects",
3472
3785
  description: "Create a new project",
3473
- inputSchema: inputSchema74,
3474
- outputSchema: z76.any(),
3786
+ inputSchema: inputSchema75,
3787
+ outputSchema: z77.any(),
3475
3788
  async execute(input, ctx) {
3476
3789
  return ctx.client.post("projects", input);
3477
3790
  },
@@ -3482,17 +3795,17 @@ var projectsCreate = {
3482
3795
  };
3483
3796
 
3484
3797
  // src/commands/projects/get.ts
3485
- import { z as z77 } from "zod";
3486
- var inputSchema75 = z77.object({
3487
- projectId: z77.string().uuid().describe("Project ID")
3798
+ import { z as z78 } from "zod";
3799
+ var inputSchema76 = z78.object({
3800
+ projectId: z78.string().uuid().describe("Project ID")
3488
3801
  });
3489
3802
  var projectsGet = {
3490
3803
  name: "projects.get",
3491
3804
  group: "projects",
3492
3805
  description: "Get details of a specific project",
3493
- inputSchema: inputSchema75,
3494
- outputSchema: z77.any(),
3495
- annotations: { readOnlyHint: true },
3806
+ inputSchema: inputSchema76,
3807
+ outputSchema: z78.any(),
3808
+ annotations: { readOnlyHint: true },
3496
3809
  async execute(input, ctx) {
3497
3810
  return ctx.client.get("project", {
3498
3811
  pathParams: { id: input.projectId }
@@ -3505,16 +3818,16 @@ var projectsGet = {
3505
3818
  };
3506
3819
 
3507
3820
  // src/commands/projects/leave.ts
3508
- import { z as z78 } from "zod";
3509
- var inputSchema76 = z78.object({
3510
- projectId: z78.string().uuid().describe("Project ID to leave")
3821
+ import { z as z79 } from "zod";
3822
+ var inputSchema77 = z79.object({
3823
+ projectId: z79.string().uuid().describe("Project ID to leave")
3511
3824
  });
3512
3825
  var projectsLeave = {
3513
3826
  name: "projects.leave",
3514
3827
  group: "projects",
3515
3828
  description: "Leave a project",
3516
- inputSchema: inputSchema76,
3517
- outputSchema: z78.any(),
3829
+ inputSchema: inputSchema77,
3830
+ outputSchema: z79.any(),
3518
3831
  annotations: { destructiveHint: true },
3519
3832
  async execute(input, ctx) {
3520
3833
  return ctx.client.post("project_leave", void 0, {
@@ -3528,16 +3841,16 @@ var projectsLeave = {
3528
3841
  };
3529
3842
 
3530
3843
  // src/commands/projects/list.ts
3531
- import { z as z79 } from "zod";
3532
- var inputSchema77 = z79.object({
3844
+ import { z as z80 } from "zod";
3845
+ var inputSchema78 = z80.object({
3533
3846
  ...paginationInput
3534
3847
  });
3535
3848
  var projectsList = {
3536
3849
  name: "projects.list",
3537
3850
  group: "projects",
3538
3851
  description: "List all projects accessible to the current user",
3539
- inputSchema: inputSchema77,
3540
- outputSchema: z79.any(),
3852
+ inputSchema: inputSchema78,
3853
+ outputSchema: z80.any(),
3541
3854
  annotations: { readOnlyHint: true },
3542
3855
  async execute(input, ctx) {
3543
3856
  return ctx.client.get("projects", {
@@ -3551,16 +3864,16 @@ var projectsList = {
3551
3864
  };
3552
3865
 
3553
3866
  // src/commands/projects/members.ts
3554
- import { z as z80 } from "zod";
3555
- var inputSchema78 = z80.object({
3556
- projectId: z80.string().uuid().describe("Project ID")
3867
+ import { z as z81 } from "zod";
3868
+ var inputSchema79 = z81.object({
3869
+ projectId: z81.string().uuid().describe("Project ID")
3557
3870
  });
3558
3871
  var projectsMembers = {
3559
3872
  name: "projects.members",
3560
3873
  group: "projects",
3561
3874
  description: "List members of a project",
3562
- inputSchema: inputSchema78,
3563
- outputSchema: z80.any(),
3875
+ inputSchema: inputSchema79,
3876
+ outputSchema: z81.any(),
3564
3877
  annotations: { readOnlyHint: true },
3565
3878
  async execute(input, ctx) {
3566
3879
  return ctx.client.get("project_members", {
@@ -3574,17 +3887,17 @@ var projectsMembers = {
3574
3887
  };
3575
3888
 
3576
3889
  // src/commands/projects/remove-member.ts
3577
- import { z as z81 } from "zod";
3578
- var inputSchema79 = z81.object({
3579
- projectId: z81.string().uuid().describe("Project ID"),
3580
- userId: z81.string().uuid().describe("User ID to remove")
3890
+ import { z as z82 } from "zod";
3891
+ var inputSchema80 = z82.object({
3892
+ projectId: z82.string().uuid().describe("Project ID"),
3893
+ userId: z82.string().uuid().describe("User ID to remove")
3581
3894
  });
3582
3895
  var projectsRemoveMember = {
3583
3896
  name: "projects.removeMember",
3584
3897
  group: "projects",
3585
3898
  description: "Remove a member from a project",
3586
- inputSchema: inputSchema79,
3587
- outputSchema: z81.any(),
3899
+ inputSchema: inputSchema80,
3900
+ outputSchema: z82.any(),
3588
3901
  annotations: { destructiveHint: true },
3589
3902
  async execute(input, ctx) {
3590
3903
  await ctx.client.del("project_member", {
@@ -3600,19 +3913,19 @@ var projectsRemoveMember = {
3600
3913
 
3601
3914
  // src/commands/projects/update.ts
3602
3915
  init_errors();
3603
- import { z as z82 } from "zod";
3604
- var inputSchema80 = z82.object({
3605
- projectId: z82.string().uuid().describe("Project ID"),
3606
- name: z82.string().min(1).optional().describe("New project name"),
3607
- description: z82.string().optional().describe("New project description"),
3608
- icon: z82.string().optional().describe("New project icon")
3916
+ import { z as z83 } from "zod";
3917
+ var inputSchema81 = z83.object({
3918
+ projectId: z83.string().uuid().describe("Project ID"),
3919
+ name: z83.string().min(1).optional().describe("New project name"),
3920
+ description: z83.string().optional().describe("New project description"),
3921
+ icon: z83.string().optional().describe("New project icon")
3609
3922
  });
3610
3923
  var projectsUpdate = {
3611
3924
  name: "projects.update",
3612
3925
  group: "projects",
3613
3926
  description: "Update an existing project",
3614
- inputSchema: inputSchema80,
3615
- outputSchema: z82.any(),
3927
+ inputSchema: inputSchema81,
3928
+ outputSchema: z83.any(),
3616
3929
  annotations: { idempotentHint: true },
3617
3930
  async execute(input, ctx) {
3618
3931
  const { projectId, ...fields } = input;
@@ -3633,18 +3946,18 @@ var projectsUpdate = {
3633
3946
  };
3634
3947
 
3635
3948
  // src/commands/projects/update-role.ts
3636
- import { z as z83 } from "zod";
3637
- var inputSchema81 = z83.object({
3638
- projectId: z83.string().uuid().describe("Project ID"),
3639
- userId: z83.string().uuid().describe("User ID of the member"),
3640
- role: z83.string().min(1).describe("New role to assign")
3949
+ import { z as z84 } from "zod";
3950
+ var inputSchema82 = z84.object({
3951
+ projectId: z84.string().uuid().describe("Project ID"),
3952
+ userId: z84.string().uuid().describe("User ID of the member"),
3953
+ role: z84.string().min(1).describe("New role to assign")
3641
3954
  });
3642
3955
  var projectsUpdateRole = {
3643
3956
  name: "projects.updateRole",
3644
3957
  group: "projects",
3645
3958
  description: "Update a project member's role",
3646
- inputSchema: inputSchema81,
3647
- outputSchema: z83.any(),
3959
+ inputSchema: inputSchema82,
3960
+ outputSchema: z84.any(),
3648
3961
  annotations: { idempotentHint: true },
3649
3962
  async execute(input, ctx) {
3650
3963
  return ctx.client.put(
@@ -3676,17 +3989,17 @@ function registerProjectCommands() {
3676
3989
  }
3677
3990
 
3678
3991
  // src/commands/search/all.ts
3679
- import { z as z84 } from "zod";
3680
- var inputSchema82 = z84.object({
3681
- query: z84.string().min(1).describe("Search query"),
3992
+ import { z as z85 } from "zod";
3993
+ var inputSchema83 = z85.object({
3994
+ query: z85.string().min(1).describe("Search query"),
3682
3995
  ...paginationInput
3683
3996
  });
3684
3997
  var searchAll = {
3685
3998
  name: "search.all",
3686
3999
  group: "search",
3687
4000
  description: "Search across all entities",
3688
- inputSchema: inputSchema82,
3689
- outputSchema: z84.any(),
4001
+ inputSchema: inputSchema83,
4002
+ outputSchema: z85.any(),
3690
4003
  annotations: { readOnlyHint: true },
3691
4004
  async execute(input, ctx) {
3692
4005
  return ctx.client.get("search_all", {
@@ -3700,18 +4013,18 @@ var searchAll = {
3700
4013
  };
3701
4014
 
3702
4015
  // src/commands/search/file-content.ts
3703
- import { z as z85 } from "zod";
3704
- var inputSchema83 = z85.object({
3705
- query: z85.string().min(1).describe("Search query"),
3706
- projectId: z85.string().uuid().optional().describe("Filter by project ID"),
4016
+ import { z as z86 } from "zod";
4017
+ var inputSchema84 = z86.object({
4018
+ query: z86.string().min(1).describe("Search query"),
4019
+ projectId: z86.string().uuid().optional().describe("Filter by project ID"),
3707
4020
  ...paginationInput
3708
4021
  });
3709
4022
  var searchFileContent = {
3710
4023
  name: "search.fileContent",
3711
4024
  group: "search",
3712
4025
  description: "Search within file contents",
3713
- inputSchema: inputSchema83,
3714
- outputSchema: z85.any(),
4026
+ inputSchema: inputSchema84,
4027
+ outputSchema: z86.any(),
3715
4028
  annotations: { readOnlyHint: true },
3716
4029
  async execute(input, ctx) {
3717
4030
  const queryParams = {
@@ -3729,17 +4042,17 @@ var searchFileContent = {
3729
4042
  };
3730
4043
 
3731
4044
  // src/commands/search/files.ts
3732
- import { z as z86 } from "zod";
3733
- var inputSchema84 = z86.object({
3734
- query: z86.string().min(1).describe("Search query"),
4045
+ import { z as z87 } from "zod";
4046
+ var inputSchema85 = z87.object({
4047
+ query: z87.string().min(1).describe("Search query"),
3735
4048
  ...paginationInput
3736
4049
  });
3737
4050
  var searchFiles = {
3738
4051
  name: "search.files",
3739
4052
  group: "search",
3740
4053
  description: "Search files by query",
3741
- inputSchema: inputSchema84,
3742
- outputSchema: z86.any(),
4054
+ inputSchema: inputSchema85,
4055
+ outputSchema: z87.any(),
3743
4056
  annotations: { readOnlyHint: true },
3744
4057
  async execute(input, ctx) {
3745
4058
  return ctx.client.get("search_files", {
@@ -3753,17 +4066,17 @@ var searchFiles = {
3753
4066
  };
3754
4067
 
3755
4068
  // src/commands/search/tasks.ts
3756
- import { z as z87 } from "zod";
3757
- var inputSchema85 = z87.object({
3758
- query: z87.string().min(1).describe("Search query"),
4069
+ import { z as z88 } from "zod";
4070
+ var inputSchema86 = z88.object({
4071
+ query: z88.string().min(1).describe("Search query"),
3759
4072
  ...paginationInput
3760
4073
  });
3761
4074
  var searchTasks = {
3762
4075
  name: "search.tasks",
3763
4076
  group: "search",
3764
4077
  description: "Search tasks by query",
3765
- inputSchema: inputSchema85,
3766
- outputSchema: z87.any(),
4078
+ inputSchema: inputSchema86,
4079
+ outputSchema: z88.any(),
3767
4080
  annotations: { readOnlyHint: true },
3768
4081
  async execute(input, ctx) {
3769
4082
  return ctx.client.get("search_tasks", {
@@ -3781,17 +4094,125 @@ function registerSearchCommands() {
3781
4094
  return [searchTasks, searchFiles, searchAll, searchFileContent];
3782
4095
  }
3783
4096
 
4097
+ // src/commands/setup-claude.ts
4098
+ init_config();
4099
+ import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
4100
+ import { homedir as homedir3 } from "node:os";
4101
+ import { dirname, join as join3 } from "node:path";
4102
+ import { fileURLToPath } from "node:url";
4103
+ import pc3 from "picocolors";
4104
+ var __script_dir;
4105
+ try {
4106
+ __script_dir = dirname(fileURLToPath(import.meta.url));
4107
+ } catch {
4108
+ __script_dir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
4109
+ }
4110
+ var ELNORA_DIR = join3(homedir3(), ".elnora");
4111
+ var PLUGIN_DIR = join3(ELNORA_DIR, "plugin", "elnora");
4112
+ var CLAUDE_DIR = join3(homedir3(), ".claude");
4113
+ var MARKETPLACES_FILE = join3(CLAUDE_DIR, "plugins", "known_marketplaces.json");
4114
+ var SETTINGS_FILE = join3(CLAUDE_DIR, "settings.json");
4115
+ var MARKETPLACE_NAME = "elnora-local";
4116
+ var PLUGIN_ID = `elnora@${MARKETPLACE_NAME}`;
4117
+ function findSkillsDir() {
4118
+ const candidates = [
4119
+ join3(__script_dir, "../../skills"),
4120
+ join3(__script_dir, "../skills"),
4121
+ // When running from source via tsx
4122
+ join3(__script_dir, "../../../skills"),
4123
+ // Standalone binary: skills extracted to ~/.elnora/skills/
4124
+ join3(ELNORA_DIR, "skills")
4125
+ ];
4126
+ for (const dir of candidates) {
4127
+ if (existsSync4(join3(dir, "elnora-platform", "SKILL.md"))) {
4128
+ return dir;
4129
+ }
4130
+ }
4131
+ return null;
4132
+ }
4133
+ function readJsonFile(path) {
4134
+ try {
4135
+ return JSON.parse(readFileSync5(path, "utf-8"));
4136
+ } catch {
4137
+ return {};
4138
+ }
4139
+ }
4140
+ function writeJsonFile(path, data) {
4141
+ mkdirSync3(join3(path, ".."), { recursive: true });
4142
+ writeFileSync3(path, `${JSON.stringify(data, null, 2)}
4143
+ `, "utf-8");
4144
+ }
4145
+ function addSetupClaudeCommand(program2) {
4146
+ program2.command("setup-claude").description("Install Elnora skills for Claude Code").action(() => {
4147
+ const color = isColorEnabled();
4148
+ const ok = (msg) => color ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
4149
+ const fail = (msg) => color ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
4150
+ console.error("");
4151
+ if (!existsSync4(CLAUDE_DIR)) {
4152
+ console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
4153
+ console.error("");
4154
+ console.error(" Install Claude Code first: https://claude.ai/code");
4155
+ process.exit(1);
4156
+ }
4157
+ console.error(ok("Claude Code found"));
4158
+ const skillsDir = findSkillsDir();
4159
+ if (!skillsDir) {
4160
+ console.error(fail("Skills not found"));
4161
+ console.error("");
4162
+ console.error(" Skills may not be bundled with your install method.");
4163
+ console.error(" Try: npm install -g @elnora-ai/cli");
4164
+ process.exit(1);
4165
+ }
4166
+ console.error(ok(`Skills found at ${skillsDir}`));
4167
+ mkdirSync3(PLUGIN_DIR, { recursive: true });
4168
+ writeJsonFile(join3(PLUGIN_DIR, ".claude-plugin", "plugin.json"), {
4169
+ name: "elnora",
4170
+ version: VERSION,
4171
+ description: "AI-powered bioprotocol generation and lab workflow management. Connect to the Elnora AI Platform to generate, optimize, and manage bioprotocols.",
4172
+ author: { name: "Elnora AI", url: "https://elnora.ai" },
4173
+ repository: "https://github.com/Elnora-AI/elnora-cli",
4174
+ homepage: "https://elnora.ai",
4175
+ license: "Apache-2.0",
4176
+ keywords: ["bioprotocol", "lab", "pharma", "life-sciences"]
4177
+ });
4178
+ cpSync(skillsDir, join3(PLUGIN_DIR, "skills"), { recursive: true });
4179
+ console.error(ok("Skills installed to ~/.elnora/plugin/"));
4180
+ const marketplaces = readJsonFile(MARKETPLACES_FILE);
4181
+ marketplaces[MARKETPLACE_NAME] = {
4182
+ source: {
4183
+ source: "directory",
4184
+ path: join3(ELNORA_DIR, "plugin")
4185
+ },
4186
+ installLocation: join3(ELNORA_DIR, "plugin"),
4187
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
4188
+ };
4189
+ writeJsonFile(MARKETPLACES_FILE, marketplaces);
4190
+ console.error(ok("Marketplace registered in Claude Code"));
4191
+ const settings = readJsonFile(SETTINGS_FILE);
4192
+ if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
4193
+ settings.enabledPlugins = {};
4194
+ }
4195
+ settings.enabledPlugins[PLUGIN_ID] = true;
4196
+ writeJsonFile(SETTINGS_FILE, settings);
4197
+ console.error(ok("Plugin enabled"));
4198
+ console.error("");
4199
+ console.error(" Elnora skills are now available in Claude Code.");
4200
+ console.error(" Restart Claude Code if it's currently running.");
4201
+ console.error("");
4202
+ });
4203
+ }
4204
+
3784
4205
  // src/commands/tasks/archive.ts
3785
- import { z as z88 } from "zod";
3786
- var inputSchema86 = z88.object({
3787
- taskId: z88.string().uuid().describe("Task ID to archive")
4206
+ import { z as z89 } from "zod";
4207
+ var inputSchema87 = z89.object({
4208
+ taskId: z89.string().uuid().describe("Task ID to archive")
3788
4209
  });
3789
4210
  var tasksArchive = {
3790
4211
  name: "tasks.archive",
3791
4212
  group: "tasks",
3792
4213
  description: "Archive (delete) a task",
3793
- inputSchema: inputSchema86,
3794
- outputSchema: z88.any(),
4214
+ inputSchema: inputSchema87,
4215
+ outputSchema: z89.any(),
3795
4216
  annotations: { destructiveHint: true },
3796
4217
  async execute(input, ctx) {
3797
4218
  await ctx.client.del("task", {
@@ -3806,129 +4227,70 @@ var tasksArchive = {
3806
4227
  };
3807
4228
 
3808
4229
  // src/commands/tasks/create.ts
3809
- import { z as z89 } from "zod";
3810
- var inputSchema87 = z89.object({
3811
- project: z89.string().uuid().describe("Project ID"),
3812
- title: z89.string().optional().describe("Task title"),
3813
- message: z89.string().optional().describe("Initial message")
3814
- });
3815
- var tasksCreate = {
3816
- name: "tasks.create",
3817
- group: "tasks",
3818
- description: "Create a new task in a project",
3819
- inputSchema: inputSchema87,
3820
- outputSchema: z89.any(),
3821
- async execute(input, ctx) {
3822
- return ctx.client.post("tasks", {
3823
- projectId: input.project,
3824
- title: input.title,
3825
- initialMessage: input.message
3826
- });
3827
- },
3828
- formatOutput(output, format) {
3829
- if (format === "compact") return output.id ?? JSON.stringify(output);
3830
- return JSON.stringify(output, null, 2);
3831
- }
3832
- };
3833
-
3834
- // src/commands/tasks/get.ts
3835
4230
  import { z as z90 } from "zod";
3836
- var inputSchema88 = z90.object({
3837
- taskId: z90.string().uuid().describe("Task ID")
3838
- });
3839
- var tasksGet = {
3840
- name: "tasks.get",
3841
- group: "tasks",
3842
- description: "Get details of a specific task",
3843
- inputSchema: inputSchema88,
3844
- outputSchema: z90.any(),
3845
- annotations: { readOnlyHint: true },
3846
- async execute(input, ctx) {
3847
- return ctx.client.get("task", {
3848
- pathParams: { id: input.taskId }
3849
- });
3850
- },
3851
- formatOutput(output, format) {
3852
- if (format === "compact") return output.id ?? JSON.stringify(output);
3853
- return JSON.stringify(output, null, 2);
3854
- }
3855
- };
3856
4231
 
3857
- // src/commands/tasks/list.ts
3858
- import { z as z91 } from "zod";
3859
- var inputSchema89 = z91.object({
3860
- project: z91.string().uuid().optional().describe("Project ID to filter by"),
3861
- ...paginationInput
3862
- });
3863
- var tasksList = {
3864
- name: "tasks.list",
3865
- group: "tasks",
3866
- description: "List tasks, optionally filtered by project",
3867
- inputSchema: inputSchema89,
3868
- outputSchema: z91.any(),
3869
- annotations: { readOnlyHint: true },
3870
- async execute(input, ctx) {
3871
- if (input.project) {
3872
- return ctx.client.get("project_tasks", {
3873
- pathParams: { id: input.project },
3874
- queryParams: { page: input.page, pageSize: input.pageSize }
3875
- });
3876
- }
3877
- return ctx.client.get("tasks", {
3878
- queryParams: { page: input.page, pageSize: input.pageSize }
4232
+ // src/lib/poll.ts
4233
+ init_errors();
4234
+ async function pollForResponse(client, taskId, afterSequence = 0, timeoutMs = 12e4) {
4235
+ const deadline = Date.now() + timeoutMs;
4236
+ while (Date.now() < deadline) {
4237
+ const messages = await client.get("task_messages", {
4238
+ pathParams: { id: taskId },
4239
+ queryParams: { limit: 10 }
3879
4240
  });
3880
- },
3881
- formatOutput(output, format) {
3882
- if (format === "compact") return output.id ?? JSON.stringify(output);
3883
- return JSON.stringify(output, null, 2);
3884
- }
3885
- };
3886
-
3887
- // src/commands/tasks/messages.ts
3888
- import { z as z92 } from "zod";
3889
- var inputSchema90 = z92.object({
3890
- taskId: z92.string().uuid().describe("Task ID"),
3891
- cursor: z92.string().optional().describe("Pagination cursor"),
3892
- limit: z92.number().int().min(1).max(100).default(50).describe("Results per page")
3893
- });
3894
- var tasksMessages = {
3895
- name: "tasks.messages",
3896
- group: "tasks",
3897
- description: "List messages for a task",
3898
- inputSchema: inputSchema90,
3899
- outputSchema: z92.any(),
3900
- annotations: { readOnlyHint: true },
3901
- async execute(input, ctx) {
3902
- const queryParams = { limit: input.limit };
3903
- if (input.cursor) {
3904
- queryParams.cursor = input.cursor;
4241
+ const items = Array.isArray(messages) ? messages : messages?.items ?? [];
4242
+ if (Array.isArray(items)) {
4243
+ const assistantMsg = items.find((m) => {
4244
+ const msg = m;
4245
+ return (msg.role === "assistant" || msg.role === "Assistant") && (msg.sequence ?? msg.sequenceNumber ?? 0) > afterSequence;
4246
+ });
4247
+ if (assistantMsg) return assistantMsg;
3905
4248
  }
3906
- return ctx.client.get("task_messages", {
3907
- pathParams: { id: input.taskId },
3908
- queryParams
3909
- });
3910
- },
3911
- formatOutput(output, format) {
3912
- if (format === "compact") return output.id ?? JSON.stringify(output);
3913
- return JSON.stringify(output, null, 2);
4249
+ await new Promise((r) => setTimeout(r, 2e3));
3914
4250
  }
3915
- };
4251
+ throw new ElnoraError("Timed out waiting for agent response (120s)", {
4252
+ code: "RESPONSE_TIMEOUT",
4253
+ suggestion: "The agent may still be processing. Check with: elnora tasks messages <task-id>"
4254
+ });
4255
+ }
3916
4256
 
3917
- // src/commands/tasks/send.ts
3918
- init_errors();
4257
+ // src/commands/tasks/create.ts
3919
4258
  init_profiles();
3920
- import { z as z93 } from "zod";
3921
4259
 
3922
4260
  // src/lib/stream.ts
3923
4261
  init_config();
4262
+ async function getStreamToken(taskId, apiKey) {
4263
+ const url = `${BASE_URL}/auth/stream-token`;
4264
+ const response = await fetch(url, {
4265
+ method: "POST",
4266
+ headers: {
4267
+ ...DEFAULT_HEADERS,
4268
+ "X-API-Key": apiKey
4269
+ },
4270
+ body: JSON.stringify({ taskId }),
4271
+ signal: AbortSignal.timeout(1e4)
4272
+ });
4273
+ if (!response.ok) {
4274
+ throw new Error(`Stream token exchange failed: HTTP ${response.status}`);
4275
+ }
4276
+ const data = await response.json();
4277
+ return data.token;
4278
+ }
3924
4279
  var TERMINAL_EVENTS = /* @__PURE__ */ new Set(["completed", "error", "timeout"]);
3925
4280
  var STREAM_TIMEOUT_MS = 3e5;
3926
4281
  async function* streamTask(taskId, apiKey, options) {
4282
+ let streamToken;
4283
+ try {
4284
+ streamToken = await getStreamToken(taskId, apiKey);
4285
+ } catch (err) {
4286
+ yield { type: "error", content: `Failed to get stream token: ${err instanceof Error ? err.message : String(err)}` };
4287
+ return;
4288
+ }
3927
4289
  const baseUrl = options?.aiServerBaseUrl ?? AI_SERVER_URL;
3928
4290
  const url = `${baseUrl}/api/v1/tasks/${taskId}/stream`;
3929
4291
  const response = await fetch(url, {
3930
4292
  headers: {
3931
- "X-API-Key": apiKey,
4293
+ Authorization: `Bearer ${streamToken}`,
3932
4294
  Accept: "text/event-stream"
3933
4295
  },
3934
4296
  signal: options?.signal ?? AbortSignal.timeout(STREAM_TIMEOUT_MS)
@@ -3987,7 +4349,7 @@ async function collectStreamResponse(taskId, apiKey, options) {
3987
4349
  }
3988
4350
 
3989
4351
  // src/lib/stream-renderer.ts
3990
- import pc2 from "picocolors";
4352
+ import pc4 from "picocolors";
3991
4353
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3992
4354
  var SPINNER_INTERVAL_MS = 80;
3993
4355
  var StreamRenderer = class {
@@ -4001,27 +4363,16 @@ var StreamRenderer = class {
4001
4363
  renderEvent(event) {
4002
4364
  switch (event.type) {
4003
4365
  case "think":
4004
- this.startSpinner("Thinking...");
4366
+ this.startSpinner(event.content);
4005
4367
  break;
4006
4368
  case "tool_start":
4007
- this.stopSpinner();
4008
- this.startSpinner(`Using ${event.tool}...`);
4369
+ this.startSpinner(`Using ${event.tool}\u2026`);
4009
4370
  break;
4010
- case "tool_end": {
4371
+ case "tool_end":
4011
4372
  this.stopSpinner();
4012
- const toolEvent = event;
4013
- const icon = toolEvent.success ? this.useColor ? pc2.green("\u2713") : "\u2713" : this.useColor ? pc2.red("\u2717") : "\u2717";
4014
- const suffix = toolEvent.result ? ` (${toolEvent.result})` : "";
4015
- process.stderr.write(` ${icon} ${toolEvent.tool}${suffix}
4016
- `);
4017
4373
  break;
4018
- }
4019
4374
  case "progress":
4020
- this.stopSpinner();
4021
- process.stderr.write(
4022
- ` ${this.useColor ? pc2.dim(event.content) : event.content}
4023
- `
4024
- );
4375
+ this.startSpinner(event.content);
4025
4376
  break;
4026
4377
  case "token":
4027
4378
  this.stopSpinner();
@@ -4035,14 +4386,14 @@ var StreamRenderer = class {
4035
4386
  this.stopSpinner();
4036
4387
  process.stderr.write(
4037
4388
  `
4038
- ${this.useColor ? pc2.red("Error:") : "Error:"} ${event.content}
4389
+ ${this.useColor ? pc4.red("Error:") : "Error:"} ${event.content}
4039
4390
  `
4040
4391
  );
4041
4392
  break;
4042
4393
  case "timeout":
4043
4394
  this.stopSpinner();
4044
4395
  process.stderr.write(`
4045
- ${this.useColor ? pc2.yellow("Stream timed out.") : "Stream timed out."}
4396
+ ${this.useColor ? pc4.yellow("Stream timed out.") : "Stream timed out."}
4046
4397
  `);
4047
4398
  break;
4048
4399
  }
@@ -4064,7 +4415,7 @@ ${this.useColor ? pc2.yellow("Stream timed out.") : "Stream timed out."}
4064
4415
  }
4065
4416
  writeSpinnerFrame() {
4066
4417
  const frame = SPINNER_FRAMES[this.spinnerFrame];
4067
- const text = this.useColor ? pc2.dim(`${frame} ${this.currentLabel}`) : `${frame} ${this.currentLabel}`;
4418
+ const text = this.useColor ? pc4.dim(`${frame} ${this.currentLabel}`) : `${frame} ${this.currentLabel}`;
4068
4419
  process.stderr.write(`\r ${text}`);
4069
4420
  }
4070
4421
  stopSpinner() {
@@ -4078,43 +4429,168 @@ ${this.useColor ? pc2.yellow("Stream timed out.") : "Stream timed out."}
4078
4429
  }
4079
4430
  };
4080
4431
 
4081
- // src/commands/tasks/send.ts
4082
- var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4083
- var inputSchema91 = z93.object({
4084
- taskId: z93.string().uuid().describe("Task ID"),
4085
- message: z93.string().min(1).describe("Message content"),
4086
- fileRefs: z93.string().optional().describe("Comma-separated file reference UUIDs"),
4087
- wait: z93.boolean().default(false).describe("Wait for agent response (polling)"),
4088
- stream: z93.boolean().default(false).describe("Stream agent response in real-time (SSE)")
4432
+ // src/commands/tasks/create.ts
4433
+ var inputSchema88 = z90.object({
4434
+ project: z90.string().uuid().describe("Project ID"),
4435
+ title: z90.string().optional().describe("Task title"),
4436
+ message: z90.string().optional().describe("Initial message"),
4437
+ wait: z90.boolean().default(false).describe("Wait for agent response (polling)"),
4438
+ stream: z90.boolean().default(false).describe("Stream agent response in real-time (SSE)")
4089
4439
  });
4090
- async function pollForResponse(client, taskId, userMessageSequence, timeoutMs = 12e4) {
4091
- const deadline = Date.now() + timeoutMs;
4092
- while (Date.now() < deadline) {
4093
- const messages = await client.get("task_messages", {
4094
- pathParams: { id: taskId },
4095
- queryParams: { limit: 10 }
4440
+ var tasksCreate = {
4441
+ name: "tasks.create",
4442
+ group: "tasks",
4443
+ description: "Create a new task in a project",
4444
+ inputSchema: inputSchema88,
4445
+ outputSchema: z90.any(),
4446
+ async execute(input, ctx) {
4447
+ const result = await ctx.client.post("tasks", {
4448
+ projectId: input.project,
4449
+ title: input.title,
4450
+ initialMessage: input.message
4096
4451
  });
4097
- const items = Array.isArray(messages) ? messages : messages?.items ?? [];
4098
- if (Array.isArray(items)) {
4099
- const assistantMsg = items.find((m) => {
4100
- const msg = m;
4101
- return (msg.role === "assistant" || msg.role === "Assistant") && (msg.sequence ?? msg.sequenceNumber ?? 0) > userMessageSequence;
4452
+ const taskId = result.id;
4453
+ const sequence = result?.sequence ?? result?.sequenceNumber ?? 0;
4454
+ if (!input.message || !input.stream && !input.wait) {
4455
+ return result;
4456
+ }
4457
+ if (ctx.mode === "mcp") {
4458
+ try {
4459
+ const apiKey = resolveApiKey(ctx.profileName);
4460
+ const content = await collectStreamResponse(taskId, apiKey);
4461
+ return { ...result, response: content };
4462
+ } catch (err) {
4463
+ process.stderr.write(
4464
+ `Stream failed (${err instanceof Error ? err.message : String(err)}), polling fallback.
4465
+ `
4466
+ );
4467
+ return pollForResponse(ctx.client, taskId, sequence);
4468
+ }
4469
+ }
4470
+ if (input.stream) {
4471
+ try {
4472
+ const apiKey = resolveApiKey(ctx.profileName);
4473
+ const renderer = new StreamRenderer();
4474
+ for await (const event of streamTask(taskId, apiKey)) {
4475
+ renderer.renderEvent(event);
4476
+ }
4477
+ renderer.stopSpinner();
4478
+ return { ...result, streamed: true };
4479
+ } catch {
4480
+ process.stderr.write("Streaming failed \u2014 falling back to polling.\n");
4481
+ return pollForResponse(ctx.client, taskId, sequence);
4482
+ }
4483
+ }
4484
+ return pollForResponse(ctx.client, taskId, sequence);
4485
+ },
4486
+ formatOutput(output, format) {
4487
+ const data = output;
4488
+ if (format === "compact") return data.id ?? JSON.stringify(output);
4489
+ if (data.streamed) return "";
4490
+ return JSON.stringify(output, null, 2);
4491
+ }
4492
+ };
4493
+
4494
+ // src/commands/tasks/get.ts
4495
+ import { z as z91 } from "zod";
4496
+ var inputSchema89 = z91.object({
4497
+ taskId: z91.string().uuid().describe("Task ID")
4498
+ });
4499
+ var tasksGet = {
4500
+ name: "tasks.get",
4501
+ group: "tasks",
4502
+ description: "Get details of a specific task",
4503
+ inputSchema: inputSchema89,
4504
+ outputSchema: z91.any(),
4505
+ annotations: { readOnlyHint: true },
4506
+ async execute(input, ctx) {
4507
+ return ctx.client.get("task", {
4508
+ pathParams: { id: input.taskId }
4509
+ });
4510
+ },
4511
+ formatOutput(output, format) {
4512
+ if (format === "compact") return output.id ?? JSON.stringify(output);
4513
+ return JSON.stringify(output, null, 2);
4514
+ }
4515
+ };
4516
+
4517
+ // src/commands/tasks/list.ts
4518
+ import { z as z92 } from "zod";
4519
+ var inputSchema90 = z92.object({
4520
+ project: z92.string().uuid().optional().describe("Project ID to filter by"),
4521
+ ...paginationInput
4522
+ });
4523
+ var tasksList = {
4524
+ name: "tasks.list",
4525
+ group: "tasks",
4526
+ description: "List tasks, optionally filtered by project",
4527
+ inputSchema: inputSchema90,
4528
+ outputSchema: z92.any(),
4529
+ annotations: { readOnlyHint: true },
4530
+ async execute(input, ctx) {
4531
+ if (input.project) {
4532
+ return ctx.client.get("project_tasks", {
4533
+ pathParams: { id: input.project },
4534
+ queryParams: { page: input.page, pageSize: input.pageSize }
4102
4535
  });
4103
- if (assistantMsg) return assistantMsg;
4104
4536
  }
4105
- await new Promise((r) => setTimeout(r, 2e3));
4537
+ return ctx.client.get("tasks", {
4538
+ queryParams: { page: input.page, pageSize: input.pageSize }
4539
+ });
4540
+ },
4541
+ formatOutput(output, format) {
4542
+ if (format === "compact") return output.id ?? JSON.stringify(output);
4543
+ return JSON.stringify(output, null, 2);
4106
4544
  }
4107
- throw new ElnoraError("Timed out waiting for agent response (120s)", {
4108
- code: "RESPONSE_TIMEOUT",
4109
- suggestion: "The agent may still be processing. Check with: elnora tasks messages <task-id>"
4110
- });
4111
- }
4545
+ };
4546
+
4547
+ // src/commands/tasks/messages.ts
4548
+ import { z as z93 } from "zod";
4549
+ var inputSchema91 = z93.object({
4550
+ taskId: z93.string().uuid().describe("Task ID"),
4551
+ cursor: z93.string().optional().describe("Pagination cursor"),
4552
+ limit: z93.number().int().min(1).max(100).default(50).describe("Results per page")
4553
+ });
4554
+ var tasksMessages = {
4555
+ name: "tasks.messages",
4556
+ group: "tasks",
4557
+ description: "List messages for a task",
4558
+ inputSchema: inputSchema91,
4559
+ outputSchema: z93.any(),
4560
+ annotations: { readOnlyHint: true },
4561
+ async execute(input, ctx) {
4562
+ const queryParams = { limit: input.limit };
4563
+ if (input.cursor) {
4564
+ queryParams.cursor = input.cursor;
4565
+ }
4566
+ return ctx.client.get("task_messages", {
4567
+ pathParams: { id: input.taskId },
4568
+ queryParams
4569
+ });
4570
+ },
4571
+ formatOutput(output, format) {
4572
+ if (format === "compact") return output.id ?? JSON.stringify(output);
4573
+ return JSON.stringify(output, null, 2);
4574
+ }
4575
+ };
4576
+
4577
+ // src/commands/tasks/send.ts
4578
+ import { z as z94 } from "zod";
4579
+ init_profiles();
4580
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4581
+ var inputSchema92 = z94.object({
4582
+ taskId: z94.string().uuid().describe("Task ID"),
4583
+ message: z94.string().min(1).describe("Message content"),
4584
+ fileRefs: z94.string().optional().describe("Comma-separated file reference UUIDs"),
4585
+ wait: z94.boolean().default(false).describe("Wait for agent response (polling)"),
4586
+ stream: z94.boolean().default(false).describe("Stream agent response in real-time (SSE)")
4587
+ });
4112
4588
  var tasksSend = {
4113
4589
  name: "tasks.send",
4114
4590
  group: "tasks",
4115
4591
  description: "Send a message to a task",
4116
- inputSchema: inputSchema91,
4117
- outputSchema: z93.any(),
4592
+ inputSchema: inputSchema92,
4593
+ outputSchema: z94.any(),
4118
4594
  async execute(input, ctx) {
4119
4595
  let referencedFileIds;
4120
4596
  if (input.fileRefs) {
@@ -4128,51 +4604,61 @@ var tasksSend = {
4128
4604
  },
4129
4605
  { pathParams: { id: input.taskId } }
4130
4606
  );
4607
+ const sequence = result?.sequence ?? result?.sequenceNumber ?? 0;
4131
4608
  if (ctx.mode === "mcp") {
4132
4609
  try {
4133
4610
  const apiKey = resolveApiKey(ctx.profileName);
4134
4611
  const content = await collectStreamResponse(input.taskId, apiKey);
4135
4612
  return { sent: true, taskId: input.taskId, response: content };
4136
- } catch {
4137
- const seq = result?.sequence ?? 0;
4138
- return pollForResponse(ctx.client, input.taskId, seq);
4613
+ } catch (err) {
4614
+ process.stderr.write(
4615
+ `Stream failed (${err instanceof Error ? err.message : String(err)}), polling fallback.
4616
+ `
4617
+ );
4618
+ return pollForResponse(ctx.client, input.taskId, sequence);
4139
4619
  }
4140
4620
  }
4141
4621
  if (!input.wait && !input.stream) {
4142
4622
  return result;
4143
4623
  }
4144
4624
  if (input.stream) {
4145
- const apiKey = resolveApiKey(ctx.profileName);
4146
- const renderer = new StreamRenderer();
4147
- for await (const event of streamTask(input.taskId, apiKey)) {
4148
- renderer.renderEvent(event);
4625
+ try {
4626
+ const apiKey = resolveApiKey(ctx.profileName);
4627
+ const renderer = new StreamRenderer();
4628
+ for await (const event of streamTask(input.taskId, apiKey)) {
4629
+ renderer.renderEvent(event);
4630
+ }
4631
+ renderer.stopSpinner();
4632
+ return { sent: true, taskId: input.taskId, streamed: true };
4633
+ } catch {
4634
+ process.stderr.write("Streaming failed \u2014 falling back to polling.\n");
4635
+ return pollForResponse(ctx.client, input.taskId, sequence);
4149
4636
  }
4150
- renderer.stopSpinner();
4151
- return { sent: true, taskId: input.taskId, streamed: true };
4152
4637
  }
4153
- const sequence = result?.sequence ?? result?.sequenceNumber ?? 0;
4154
4638
  return pollForResponse(ctx.client, input.taskId, sequence);
4155
4639
  },
4156
4640
  formatOutput(output, format) {
4157
- if (format === "compact") return output.id ?? JSON.stringify(output);
4641
+ const data = output;
4642
+ if (format === "compact") return data.id ?? JSON.stringify(output);
4643
+ if (data.streamed) return "";
4158
4644
  return JSON.stringify(output, null, 2);
4159
4645
  }
4160
4646
  };
4161
4647
 
4162
4648
  // src/commands/tasks/update.ts
4163
4649
  init_errors();
4164
- import { z as z94 } from "zod";
4165
- var inputSchema92 = z94.object({
4166
- taskId: z94.string().uuid().describe("Task ID"),
4167
- title: z94.string().min(1).optional().describe("New task title"),
4168
- status: z94.string().optional().describe("New task status")
4650
+ import { z as z95 } from "zod";
4651
+ var inputSchema93 = z95.object({
4652
+ taskId: z95.string().uuid().describe("Task ID"),
4653
+ title: z95.string().min(1).optional().describe("New task title"),
4654
+ status: z95.string().optional().describe("New task status")
4169
4655
  });
4170
4656
  var tasksUpdate = {
4171
4657
  name: "tasks.update",
4172
4658
  group: "tasks",
4173
4659
  description: "Update an existing task",
4174
- inputSchema: inputSchema92,
4175
- outputSchema: z94.any(),
4660
+ inputSchema: inputSchema93,
4661
+ outputSchema: z95.any(),
4176
4662
  annotations: { idempotentHint: true },
4177
4663
  async execute(input, ctx) {
4178
4664
  const { taskId, ...fields } = input;
@@ -4197,6 +4683,136 @@ function registerTaskCommands() {
4197
4683
  return [tasksList, tasksGet, tasksCreate, tasksSend, tasksMessages, tasksUpdate, tasksArchive];
4198
4684
  }
4199
4685
 
4686
+ // src/commands/update.ts
4687
+ init_config();
4688
+ import { execFileSync } from "node:child_process";
4689
+ import { createHash } from "node:crypto";
4690
+ import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
4691
+ import { tmpdir } from "node:os";
4692
+ import { join as join4 } from "node:path";
4693
+ import { Readable } from "node:stream";
4694
+ import { pipeline } from "node:stream/promises";
4695
+ import pc5 from "picocolors";
4696
+ var NPM_REGISTRY_URL2 = "https://registry.npmjs.org/@elnora-ai/cli/latest";
4697
+ var GITHUB_REPO = "Elnora-AI/elnora-cli";
4698
+ function getPlatformTarget() {
4699
+ const arch = process.arch === "arm64" ? "arm64" : "x64";
4700
+ switch (process.platform) {
4701
+ case "darwin":
4702
+ return { target: `elnora-macos-${arch}`, ext: "tar.gz" };
4703
+ case "linux":
4704
+ return { target: `elnora-linux-${arch}`, ext: "tar.gz" };
4705
+ case "win32":
4706
+ return { target: `elnora-win-${arch}.exe`, ext: "zip" };
4707
+ default:
4708
+ return null;
4709
+ }
4710
+ }
4711
+ function getManualHint() {
4712
+ return process.platform === "win32" ? " irm https://cli.elnora.ai/install.ps1 | iex" : " curl -fsSL https://cli.elnora.ai/install.sh | bash";
4713
+ }
4714
+ async function downloadToFile(url, dest) {
4715
+ const res = await fetch(url, { signal: AbortSignal.timeout(12e4), redirect: "follow" });
4716
+ if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
4717
+ const nodeStream = Readable.fromWeb(res.body);
4718
+ await pipeline(nodeStream, createWriteStream(dest));
4719
+ }
4720
+ function addUpdateCommand(program2) {
4721
+ program2.command("update").description("Update Elnora CLI to the latest version").action(async () => {
4722
+ const color = isColorEnabled();
4723
+ const hint = getManualHint();
4724
+ let latest;
4725
+ try {
4726
+ const res = await fetch(NPM_REGISTRY_URL2, { signal: AbortSignal.timeout(5e3) });
4727
+ if (!res.ok) {
4728
+ console.error("Failed to check for updates. Try manually:");
4729
+ console.error(hint);
4730
+ process.exit(1);
4731
+ }
4732
+ const data = await res.json();
4733
+ latest = data.version ?? "unknown";
4734
+ } catch {
4735
+ console.error("Failed to reach registry. Try manually:");
4736
+ console.error(hint);
4737
+ process.exit(1);
4738
+ }
4739
+ if (latest === VERSION || !isNewerVersion(latest, VERSION)) {
4740
+ const msg2 = color ? `${pc5.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
4741
+ console.error(msg2);
4742
+ return;
4743
+ }
4744
+ const msg = color ? `Updating Elnora CLI ${pc5.dim(`v${VERSION}`)} \u2192 ${pc5.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
4745
+ console.error(msg);
4746
+ const platformInfo = getPlatformTarget();
4747
+ if (!platformInfo) {
4748
+ console.error(`
4749
+ Unsupported platform: ${process.platform}. Try manually:`);
4750
+ console.error(" npm update -g @elnora-ai/cli");
4751
+ process.exit(1);
4752
+ }
4753
+ const { target, ext } = platformInfo;
4754
+ const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
4755
+ const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
4756
+ const tmp = join4(tmpdir(), `elnora-update-${Date.now()}`);
4757
+ mkdirSync4(tmp, { recursive: true });
4758
+ const archivePath = join4(tmp, `${target}.${ext}`);
4759
+ try {
4760
+ await downloadToFile(binaryUrl, archivePath);
4761
+ let checksumVerified = false;
4762
+ try {
4763
+ const checksumRes = await fetch(checksumUrl, { signal: AbortSignal.timeout(15e3) });
4764
+ if (checksumRes.ok) {
4765
+ const checksumText = await checksumRes.text();
4766
+ const expected = checksumText.split(/\s/)[0].trim();
4767
+ if (expected.length === 64) {
4768
+ const actual = createHash("sha256").update(readFileSync6(archivePath)).digest("hex");
4769
+ if (actual.toLowerCase() !== expected.toLowerCase()) {
4770
+ console.error("\nChecksum verification failed. The download may be corrupted.");
4771
+ console.error("Try again, or install manually:");
4772
+ console.error(hint);
4773
+ process.exit(1);
4774
+ }
4775
+ checksumVerified = true;
4776
+ }
4777
+ }
4778
+ } catch {
4779
+ }
4780
+ if (!checksumVerified) {
4781
+ console.error("Warning: Could not verify download checksum. Proceeding anyway.");
4782
+ }
4783
+ if (process.platform === "win32") {
4784
+ const extractDir = join4(tmp, "extracted");
4785
+ execFileSync("powershell", [
4786
+ "-NoProfile",
4787
+ "-Command",
4788
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
4789
+ ]);
4790
+ const installDir = join4(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
4791
+ mkdirSync4(installDir, { recursive: true });
4792
+ copyFileSync(join4(extractDir, target), join4(installDir, "elnora.exe"));
4793
+ } else {
4794
+ execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
4795
+ const installDir = process.env.ELNORA_INSTALL_DIR ?? join4(process.env.HOME ?? "", ".local", "bin");
4796
+ mkdirSync4(installDir, { recursive: true });
4797
+ const dest = join4(installDir, "elnora");
4798
+ copyFileSync(join4(tmp, target), dest);
4799
+ chmodSync2(dest, 493);
4800
+ }
4801
+ const done = color ? `${pc5.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
4802
+ console.error(done);
4803
+ } catch {
4804
+ console.error("\nUpdate failed. Try manually:");
4805
+ console.error(hint);
4806
+ process.exit(1);
4807
+ } finally {
4808
+ try {
4809
+ rmSync(tmp, { recursive: true, force: true });
4810
+ } catch {
4811
+ }
4812
+ }
4813
+ });
4814
+ }
4815
+
4200
4816
  // src/commands/whoami.ts
4201
4817
  init_client();
4202
4818
  init_profiles();
@@ -4261,95 +4877,26 @@ var CommandRegistry = class {
4261
4877
 
4262
4878
  // src/cli.ts
4263
4879
  init_errors();
4264
-
4265
- // src/lib/update-check.ts
4266
- init_config();
4267
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
4268
- import { homedir as homedir2 } from "node:os";
4269
- import { join as join3 } from "node:path";
4270
- import pc3 from "picocolors";
4271
- var NPM_REGISTRY_URL = "https://registry.npmjs.org/@elnora-ai/cli/latest";
4272
- var CACHE_DIR = join3(homedir2(), ".elnora");
4273
- var CACHE_FILE = join3(CACHE_DIR, ".update-check");
4274
- var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
4275
- var FETCH_TIMEOUT_MS = 3e3;
4276
- function shouldSkip() {
4277
- if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
4278
- if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
4279
- if (!process.stderr.isTTY) return true;
4280
- return false;
4281
- }
4282
- function readCache() {
4283
- try {
4284
- if (!existsSync3(CACHE_FILE)) return null;
4285
- const raw = readFileSync5(CACHE_FILE, "utf-8");
4286
- return JSON.parse(raw);
4287
- } catch {
4288
- return null;
4289
- }
4290
- }
4291
- function writeCache(entry) {
4292
- try {
4293
- mkdirSync2(CACHE_DIR, { recursive: true });
4294
- writeFileSync2(CACHE_FILE, JSON.stringify(entry), "utf-8");
4295
- } catch {
4296
- }
4297
- }
4298
- function showUpdateNotice(latest) {
4299
- const color = isColorEnabled();
4300
- const msg = color ? `
4301
- ${pc3.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
4302
- Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
4303
- ` : `
4304
- Update available: v${VERSION} \u2192 v${latest}
4305
- Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
4306
- `;
4307
- process.stderr.write(msg);
4308
- }
4309
- function registerUpdateCheck() {
4310
- if (shouldSkip()) return;
4311
- const cached = readCache();
4312
- if (cached) {
4313
- const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
4314
- if (elapsed < CHECK_INTERVAL_MS) {
4315
- if (cached.latest && cached.latest !== VERSION && cached.latest !== "unknown") {
4316
- if (cached.latest > VERSION) {
4317
- showUpdateNotice(cached.latest);
4318
- }
4319
- }
4320
- return;
4321
- }
4322
- }
4323
- setTimeout(async () => {
4324
- try {
4325
- const response = await fetch(NPM_REGISTRY_URL, {
4326
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
4327
- });
4328
- if (!response.ok) return;
4329
- const data = await response.json();
4330
- const latest = data.version ?? "unknown";
4331
- writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest });
4332
- if (latest !== VERSION && latest !== "unknown" && latest > VERSION) {
4333
- showUpdateNotice(latest);
4334
- }
4335
- } catch {
4336
- }
4337
- }, 0);
4338
- }
4339
-
4340
- // src/cli.ts
4341
4880
  process.on("uncaughtException", (err) => {
4342
- const payload = formatErrorPayload(err);
4343
- process.stderr.write(`${JSON.stringify(payload, null, 2)}
4881
+ if (process.stderr.isTTY) {
4882
+ process.stderr.write(`${formatErrorForHuman(err)}
4883
+ `);
4884
+ } else {
4885
+ process.stderr.write(`${JSON.stringify(formatErrorPayload(err), null, 2)}
4344
4886
  `);
4345
- process.exit(getExitCode(err));
4887
+ }
4888
+ process.exitCode = getExitCode(err);
4346
4889
  });
4347
4890
  process.on("unhandledRejection", (reason) => {
4348
4891
  const err = reason instanceof Error ? reason : new Error(String(reason));
4349
- const payload = formatErrorPayload(err);
4350
- process.stderr.write(`${JSON.stringify(payload, null, 2)}
4892
+ if (process.stderr.isTTY) {
4893
+ process.stderr.write(`${formatErrorForHuman(err)}
4894
+ `);
4895
+ } else {
4896
+ process.stderr.write(`${JSON.stringify(formatErrorPayload(err), null, 2)}
4351
4897
  `);
4352
- process.exit(1);
4898
+ }
4899
+ process.exitCode = 1;
4353
4900
  });
4354
4901
  var registry = new CommandRegistry();
4355
4902
  var commandGroups = [
@@ -4378,6 +4925,8 @@ addMcpCommands(program, registry);
4378
4925
  addDoctorCommand(program);
4379
4926
  addWhoamiCommand(program);
4380
4927
  addOpenCommand(program);
4928
+ addSetupClaudeCommand(program);
4929
+ addUpdateCommand(program);
4381
4930
  addCompletionCommand(program);
4382
4931
  registerUpdateCheck();
4383
4932
  program.parseAsync(process.argv);