@elnora-ai/cli 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +155 -137
- package/dist/cli.js +809 -298
- package/package.json +66 -67
- 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}`);
|
|
@@ -37,7 +25,7 @@ var VERSION, 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 =
|
|
28
|
+
VERSION = typeof __APP_VERSION__ !== "undefined" ? __APP_VERSION__ : process.env.npm_package_version ?? "0.0.0-dev";
|
|
41
29
|
BASE_URL = "https://platform.elnora.ai/api/v1";
|
|
42
30
|
AI_SERVER_URL = process.env.ELNORA_AI_SERVER_URL ?? "https://platform.elnora.ai/ai-server";
|
|
43
31
|
ENDPOINTS = {
|
|
@@ -175,6 +163,23 @@ function formatErrorPayload(err) {
|
|
|
175
163
|
}
|
|
176
164
|
return payload;
|
|
177
165
|
}
|
|
166
|
+
function formatErrorForHuman(err) {
|
|
167
|
+
const lines = [];
|
|
168
|
+
const message = scrub(err.message);
|
|
169
|
+
lines.push(`Error: ${message}`);
|
|
170
|
+
if (err instanceof ElnoraError && err.suggestion) {
|
|
171
|
+
const steps = err.suggestion.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
172
|
+
if (steps.length === 1) {
|
|
173
|
+
lines.push("", ` ${steps[0]}`);
|
|
174
|
+
} else {
|
|
175
|
+
lines.push("", "Next steps:");
|
|
176
|
+
for (const step of steps) {
|
|
177
|
+
lines.push(` ${step}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return lines.join("\n");
|
|
182
|
+
}
|
|
178
183
|
var ElnoraError, AuthError, NotFoundError, RateLimitError, ValidationError, ServerError, EXIT_CODES, SCRUB_KEY_VALUE_RE, SCRUB_LONG_TOKEN_RE;
|
|
179
184
|
var init_errors = __esm({
|
|
180
185
|
"src/lib/errors.ts"() {
|
|
@@ -244,9 +249,9 @@ var init_errors = __esm({
|
|
|
244
249
|
});
|
|
245
250
|
|
|
246
251
|
// src/lib/profiles.ts
|
|
247
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync
|
|
252
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
248
253
|
import { homedir } from "node:os";
|
|
249
|
-
import { join
|
|
254
|
+
import { join } from "node:path";
|
|
250
255
|
function validateProfileName(name) {
|
|
251
256
|
if (!PROFILE_NAME_RE.test(name)) {
|
|
252
257
|
throw new ValidationError(
|
|
@@ -305,7 +310,7 @@ function serializeProfiles(profiles) {
|
|
|
305
310
|
return parts.join("\n");
|
|
306
311
|
}
|
|
307
312
|
function writeSecureFile(filePath, content) {
|
|
308
|
-
const dir =
|
|
313
|
+
const dir = join(filePath, "..");
|
|
309
314
|
mkdirSync(dir, { recursive: true });
|
|
310
315
|
if (process.platform !== "win32") {
|
|
311
316
|
try {
|
|
@@ -318,7 +323,7 @@ function writeSecureFile(filePath, content) {
|
|
|
318
323
|
function loadProfiles() {
|
|
319
324
|
if (!existsSync(PROFILES_FILE)) return {};
|
|
320
325
|
try {
|
|
321
|
-
const text =
|
|
326
|
+
const text = readFileSync(PROFILES_FILE, "utf-8");
|
|
322
327
|
return parseProfilesToml(text);
|
|
323
328
|
} catch {
|
|
324
329
|
return {};
|
|
@@ -368,7 +373,7 @@ function migrateConfigIfNeeded() {
|
|
|
368
373
|
if (!existsSync(LEGACY_CONFIG_FILE)) return false;
|
|
369
374
|
let apiKey = "";
|
|
370
375
|
try {
|
|
371
|
-
const text =
|
|
376
|
+
const text = readFileSync(LEGACY_CONFIG_FILE, "utf-8");
|
|
372
377
|
for (const line of text.split("\n")) {
|
|
373
378
|
const trimmed = line.trim();
|
|
374
379
|
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
@@ -396,9 +401,9 @@ var init_profiles = __esm({
|
|
|
396
401
|
"src/lib/profiles.ts"() {
|
|
397
402
|
"use strict";
|
|
398
403
|
init_errors();
|
|
399
|
-
CONFIG_DIR =
|
|
400
|
-
PROFILES_FILE =
|
|
401
|
-
LEGACY_CONFIG_FILE =
|
|
404
|
+
CONFIG_DIR = join(homedir(), ".elnora");
|
|
405
|
+
PROFILES_FILE = join(CONFIG_DIR, "profiles.toml");
|
|
406
|
+
LEGACY_CONFIG_FILE = join(CONFIG_DIR, "config.toml");
|
|
402
407
|
PROFILE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
403
408
|
}
|
|
404
409
|
});
|
|
@@ -544,14 +549,30 @@ var init_client = __esm({
|
|
|
544
549
|
bodyText = scrub(await response.text());
|
|
545
550
|
} catch {
|
|
546
551
|
}
|
|
547
|
-
|
|
548
|
-
|
|
552
|
+
let errorMessage = bodyText;
|
|
553
|
+
try {
|
|
554
|
+
const parsed = JSON.parse(bodyText);
|
|
555
|
+
if (parsed.messages?.length) {
|
|
556
|
+
errorMessage = parsed.messages.join(" ");
|
|
557
|
+
} else if (parsed.message) {
|
|
558
|
+
errorMessage = parsed.message;
|
|
559
|
+
}
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
if (response.status === 401) {
|
|
563
|
+
throw new AuthError(errorMessage.slice(0, 200) || "Authentication failed");
|
|
564
|
+
}
|
|
565
|
+
if (response.status === 403) {
|
|
566
|
+
throw new ElnoraError(errorMessage.slice(0, 200) || "Access denied", {
|
|
567
|
+
code: "FORBIDDEN",
|
|
568
|
+
suggestion: "You don't have permission to access this resource. Check the resource ID or your organization membership."
|
|
569
|
+
});
|
|
549
570
|
}
|
|
550
571
|
if (response.status === 404) {
|
|
551
|
-
throw new NotFoundError("resource",
|
|
572
|
+
throw new NotFoundError("resource", errorMessage.slice(0, 200) || "not found");
|
|
552
573
|
}
|
|
553
574
|
if (response.status === 422) {
|
|
554
|
-
throw new ValidationError(
|
|
575
|
+
throw new ValidationError(errorMessage.slice(0, 500) || "Validation error");
|
|
555
576
|
}
|
|
556
577
|
if (response.status === 429) {
|
|
557
578
|
if (attempt < MAX_RETRIES) {
|
|
@@ -824,6 +845,7 @@ var init_http_server = __esm({
|
|
|
824
845
|
|
|
825
846
|
// src/adapters/cli.ts
|
|
826
847
|
init_client();
|
|
848
|
+
init_config();
|
|
827
849
|
init_errors();
|
|
828
850
|
import { Command } from "commander";
|
|
829
851
|
|
|
@@ -979,12 +1001,12 @@ function kebabToCamel(str) {
|
|
|
979
1001
|
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
980
1002
|
}
|
|
981
1003
|
function buildProgram(registry2) {
|
|
982
|
-
const program2 = new Command().name("elnora").description("Elnora AI Platform CLI").version(
|
|
1004
|
+
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
1005
|
for (const group of registry2.groups()) {
|
|
984
1006
|
const groupCmd = new Command(group).description(`Manage ${group}`);
|
|
985
1007
|
const commands = registry2.byGroup(group);
|
|
986
1008
|
for (const cmd of commands) {
|
|
987
|
-
const subName = cmd.name.split(".").slice(1).join("-");
|
|
1009
|
+
const subName = camelToKebab(cmd.name.split(".").slice(1).join("-"));
|
|
988
1010
|
const sub = new Command(subName).description(cmd.description);
|
|
989
1011
|
const opts = zodToCommanderOptions(cmd.inputSchema);
|
|
990
1012
|
for (const opt of opts) {
|
|
@@ -1024,7 +1046,11 @@ function buildProgram(registry2) {
|
|
|
1024
1046
|
}
|
|
1025
1047
|
}
|
|
1026
1048
|
const profileName = parentOpts.profile ?? "default";
|
|
1027
|
-
|
|
1049
|
+
if (parentOpts.profile && !("profile" in input && input.profile !== "default")) {
|
|
1050
|
+
input.profile = parentOpts.profile;
|
|
1051
|
+
}
|
|
1052
|
+
const skipAuth = cmd.name.startsWith("auth.login") || cmd.name === "auth.logout" || cmd.name === "auth.profiles";
|
|
1053
|
+
const client = skipAuth ? new ElnoraApiClient("placeholder") : ElnoraApiClient.fromEnv(profileName);
|
|
1028
1054
|
const ctx = {
|
|
1029
1055
|
client,
|
|
1030
1056
|
profileName,
|
|
@@ -1037,19 +1063,37 @@ function buildProgram(registry2) {
|
|
|
1037
1063
|
};
|
|
1038
1064
|
const parsed = cmd.inputSchema.parse(input);
|
|
1039
1065
|
const result = await cmd.execute(parsed, ctx);
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1066
|
+
if (result != null) {
|
|
1067
|
+
const isStreamed = typeof result === "object" && result.streamed === true;
|
|
1068
|
+
if (!isStreamed) {
|
|
1069
|
+
const hasGlobalFlags = ctx.output.compact || ctx.output.format !== "json" || ctx.output.fields;
|
|
1070
|
+
let formatted;
|
|
1071
|
+
if (cmd.formatOutput && !hasGlobalFlags) {
|
|
1072
|
+
formatted = cmd.formatOutput(result, ctx.output.format);
|
|
1073
|
+
} else {
|
|
1074
|
+
formatted = formatOutput(result, {
|
|
1075
|
+
format: ctx.output.format,
|
|
1076
|
+
compact: ctx.output.compact,
|
|
1077
|
+
fields: ctx.output.fields
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
if (formatted) {
|
|
1081
|
+
process.stdout.write(`${formatted}
|
|
1046
1082
|
`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1047
1086
|
} catch (err) {
|
|
1048
1087
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
1049
|
-
|
|
1050
|
-
|
|
1088
|
+
if (process.stderr.isTTY) {
|
|
1089
|
+
process.stderr.write(`${formatErrorForHuman(error)}
|
|
1051
1090
|
`);
|
|
1052
|
-
|
|
1091
|
+
} else {
|
|
1092
|
+
const payload = formatErrorPayload(error);
|
|
1093
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}
|
|
1094
|
+
`);
|
|
1095
|
+
}
|
|
1096
|
+
process.exitCode = getExitCode(error);
|
|
1053
1097
|
}
|
|
1054
1098
|
});
|
|
1055
1099
|
groupCmd.addCommand(sub);
|
|
@@ -1454,7 +1498,7 @@ var paginationInput = {
|
|
|
1454
1498
|
|
|
1455
1499
|
// src/commands/audit/list.ts
|
|
1456
1500
|
var inputSchema15 = z16.object({
|
|
1457
|
-
|
|
1501
|
+
orgId: z16.string().uuid().describe("Organization ID"),
|
|
1458
1502
|
...paginationInput,
|
|
1459
1503
|
action: z16.string().optional().describe("Filter by action type"),
|
|
1460
1504
|
userId: z16.string().optional().describe("Filter by user ID")
|
|
@@ -1474,7 +1518,7 @@ var auditList = {
|
|
|
1474
1518
|
if (input.action) queryParams.action = input.action;
|
|
1475
1519
|
if (input.userId) queryParams.userId = input.userId;
|
|
1476
1520
|
return ctx.client.get("audit_log", {
|
|
1477
|
-
pathParams: { orgId: input.
|
|
1521
|
+
pathParams: { orgId: input.orgId },
|
|
1478
1522
|
queryParams
|
|
1479
1523
|
});
|
|
1480
1524
|
},
|
|
@@ -1494,6 +1538,78 @@ init_client();
|
|
|
1494
1538
|
init_errors();
|
|
1495
1539
|
init_profiles();
|
|
1496
1540
|
import { z as z17 } from "zod";
|
|
1541
|
+
|
|
1542
|
+
// src/lib/prompt.ts
|
|
1543
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
1544
|
+
function promptSecret(message) {
|
|
1545
|
+
return new Promise((resolve, reject) => {
|
|
1546
|
+
const { stdin, stdout } = process;
|
|
1547
|
+
if (!stdin.isTTY) {
|
|
1548
|
+
const rl = createInterface2({ input: stdin, output: stdout });
|
|
1549
|
+
rl.question(message, (answer) => {
|
|
1550
|
+
rl.close();
|
|
1551
|
+
resolve(answer.trim());
|
|
1552
|
+
});
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
stdout.write(message);
|
|
1556
|
+
let input = "";
|
|
1557
|
+
const wasRaw = stdin.isRaw;
|
|
1558
|
+
stdin.setRawMode(true);
|
|
1559
|
+
stdin.resume();
|
|
1560
|
+
stdin.setEncoding("utf8");
|
|
1561
|
+
const onData = (char) => {
|
|
1562
|
+
switch (char) {
|
|
1563
|
+
case "\n":
|
|
1564
|
+
case "\r":
|
|
1565
|
+
case "": {
|
|
1566
|
+
stdin.setRawMode(wasRaw);
|
|
1567
|
+
stdin.pause();
|
|
1568
|
+
stdin.removeListener("data", onData);
|
|
1569
|
+
stdout.write("\n");
|
|
1570
|
+
resolve(input.trim());
|
|
1571
|
+
break;
|
|
1572
|
+
}
|
|
1573
|
+
case "":
|
|
1574
|
+
// Ctrl+C
|
|
1575
|
+
case "\x1B": {
|
|
1576
|
+
stdin.setRawMode(wasRaw);
|
|
1577
|
+
stdin.pause();
|
|
1578
|
+
stdin.removeListener("data", onData);
|
|
1579
|
+
stdout.write("\n");
|
|
1580
|
+
reject(new Error("Cancelled"));
|
|
1581
|
+
break;
|
|
1582
|
+
}
|
|
1583
|
+
case "\x7F":
|
|
1584
|
+
case "\b": {
|
|
1585
|
+
if (input.length > 0) {
|
|
1586
|
+
input = input.slice(0, -1);
|
|
1587
|
+
stdout.write("\b \b");
|
|
1588
|
+
}
|
|
1589
|
+
break;
|
|
1590
|
+
}
|
|
1591
|
+
default: {
|
|
1592
|
+
input += char;
|
|
1593
|
+
stdout.write("*");
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
stdin.on("data", onData);
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// src/lib/tty.ts
|
|
1603
|
+
function isTTY() {
|
|
1604
|
+
return Boolean(process.stdout.isTTY);
|
|
1605
|
+
}
|
|
1606
|
+
function isColorEnabled() {
|
|
1607
|
+
if (process.env.NO_COLOR !== void 0) return false;
|
|
1608
|
+
if (process.env.FORCE_COLOR !== void 0) return true;
|
|
1609
|
+
return isTTY();
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// src/commands/auth/login.ts
|
|
1497
1613
|
var inputSchema16 = z17.object({
|
|
1498
1614
|
apiKey: z17.string().optional().describe("Elnora API key (elnora_live_...)"),
|
|
1499
1615
|
profile: z17.string().default("default").describe("Profile name to save key under")
|
|
@@ -1505,30 +1621,118 @@ var authLogin = {
|
|
|
1505
1621
|
inputSchema: inputSchema16,
|
|
1506
1622
|
outputSchema: z17.any(),
|
|
1507
1623
|
async execute(input, _ctx) {
|
|
1508
|
-
|
|
1624
|
+
let apiKey = input.apiKey;
|
|
1625
|
+
const profileName = input.profile;
|
|
1626
|
+
validateProfileName(profileName);
|
|
1627
|
+
const existing = listProfileNames();
|
|
1628
|
+
const profileExists = existing.includes(profileName);
|
|
1629
|
+
if (!apiKey) {
|
|
1630
|
+
if (profileExists) {
|
|
1631
|
+
let savedKey;
|
|
1632
|
+
try {
|
|
1633
|
+
savedKey = getApiKey(profileName);
|
|
1634
|
+
} catch {
|
|
1635
|
+
}
|
|
1636
|
+
if (savedKey && isTTY()) {
|
|
1637
|
+
process.stderr.write(`
|
|
1638
|
+
Checking profile "${profileName}"...`);
|
|
1639
|
+
try {
|
|
1640
|
+
const client = new ElnoraApiClient(savedKey);
|
|
1641
|
+
const result = await client.get("projects", {
|
|
1642
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
1643
|
+
});
|
|
1644
|
+
const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
|
|
1645
|
+
process.stderr.write(` authenticated (${projectCount} project${projectCount !== 1 ? "s" : ""}).
|
|
1646
|
+
|
|
1647
|
+
`);
|
|
1648
|
+
process.stderr.write(" To update your API key, run:\n");
|
|
1649
|
+
process.stderr.write(
|
|
1650
|
+
` elnora auth login --api-key <new-key>${profileName !== "default" ? ` --profile ${profileName}` : ""}
|
|
1651
|
+
|
|
1652
|
+
`
|
|
1653
|
+
);
|
|
1654
|
+
return null;
|
|
1655
|
+
} catch {
|
|
1656
|
+
process.stderr.write(" key is invalid or expired.\n\n");
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (isTTY()) {
|
|
1661
|
+
if (profileExists) {
|
|
1662
|
+
process.stderr.write(` Enter a new API key for profile "${profileName}"
|
|
1663
|
+
`);
|
|
1664
|
+
} else {
|
|
1665
|
+
process.stderr.write("\n Set up Elnora CLI authentication\n");
|
|
1666
|
+
}
|
|
1667
|
+
process.stderr.write(" Get your API key at: https://platform.elnora.ai > Settings > API Keys\n\n");
|
|
1668
|
+
try {
|
|
1669
|
+
apiKey = await promptSecret(" API key (Ctrl+C to cancel): ");
|
|
1670
|
+
} catch {
|
|
1671
|
+
process.stderr.write("\n Login cancelled.\n\n");
|
|
1672
|
+
process.exit(0);
|
|
1673
|
+
}
|
|
1674
|
+
process.stderr.write("\n");
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1509
1677
|
if (!apiKey) {
|
|
1510
1678
|
throw new ValidationError(
|
|
1511
|
-
"API key is required. Use --api-key flag
|
|
1679
|
+
"API key is required. Use --api-key flag or run interactively in a terminal.",
|
|
1512
1680
|
"Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1513
1681
|
);
|
|
1514
1682
|
}
|
|
1515
1683
|
const key = apiKey.trim();
|
|
1516
1684
|
if (!key.startsWith("elnora_live_")) {
|
|
1517
|
-
throw new AuthError("API key must start with 'elnora_live_'."
|
|
1685
|
+
throw new AuthError("API key must start with 'elnora_live_'. Check that you copied the full key.", {
|
|
1686
|
+
suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1687
|
+
});
|
|
1518
1688
|
}
|
|
1519
1689
|
if (key.length < 20) {
|
|
1520
|
-
throw new AuthError("API key looks too short.
|
|
1690
|
+
throw new AuthError("API key looks too short. Make sure you copied the full key.", {
|
|
1691
|
+
suggestion: "Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
if (isTTY()) {
|
|
1695
|
+
process.stderr.write(" Verifying...");
|
|
1696
|
+
}
|
|
1697
|
+
try {
|
|
1698
|
+
const tempClient = new ElnoraApiClient(key);
|
|
1699
|
+
const result = await tempClient.get("projects", {
|
|
1700
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
1701
|
+
});
|
|
1702
|
+
const projectCount = result?.totalCount ?? result?.items?.length ?? 0;
|
|
1703
|
+
const configPath = saveProfile(profileName, key);
|
|
1704
|
+
if (isTTY()) {
|
|
1705
|
+
process.stderr.write(` Done! ${projectCount} project${projectCount !== 1 ? "s" : ""} accessible.
|
|
1706
|
+
`);
|
|
1707
|
+
process.stderr.write(` Saved to profile "${profileName}" at ${configPath}
|
|
1708
|
+
|
|
1709
|
+
`);
|
|
1710
|
+
}
|
|
1711
|
+
return { profile: profileName, verified: true, configPath };
|
|
1712
|
+
} catch (err) {
|
|
1713
|
+
if (isTTY()) {
|
|
1714
|
+
process.stderr.write("\n");
|
|
1715
|
+
}
|
|
1716
|
+
if (err instanceof AuthError) {
|
|
1717
|
+
throw new AuthError("API key is invalid or has been revoked. Please check your key and try again.", {
|
|
1718
|
+
suggestion: "Get a new key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
throw err;
|
|
1521
1722
|
}
|
|
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
1723
|
},
|
|
1529
1724
|
formatOutput(output, format) {
|
|
1530
|
-
|
|
1531
|
-
return JSON.stringify(output
|
|
1725
|
+
const data = output;
|
|
1726
|
+
if (format === "compact") return data.profile ?? JSON.stringify(output);
|
|
1727
|
+
const lines = [
|
|
1728
|
+
`\u2713 Authenticated! API key saved to profile "${data.profile}".`,
|
|
1729
|
+
"",
|
|
1730
|
+
"Next steps:",
|
|
1731
|
+
" elnora projects list See your projects",
|
|
1732
|
+
" elnora tasks create --project <ID> --stream Start a conversation",
|
|
1733
|
+
" elnora doctor Verify your setup"
|
|
1734
|
+
];
|
|
1735
|
+
return lines.join("\n");
|
|
1532
1736
|
}
|
|
1533
1737
|
};
|
|
1534
1738
|
|
|
@@ -1772,26 +1976,102 @@ function addCompletionCommand(program2) {
|
|
|
1772
1976
|
init_client();
|
|
1773
1977
|
init_config();
|
|
1774
1978
|
init_profiles();
|
|
1775
|
-
import { existsSync as
|
|
1776
|
-
import
|
|
1979
|
+
import { existsSync as existsSync3, statSync } from "node:fs";
|
|
1980
|
+
import pc2 from "picocolors";
|
|
1777
1981
|
|
|
1778
|
-
// src/lib/
|
|
1779
|
-
|
|
1780
|
-
|
|
1982
|
+
// src/lib/update-check.ts
|
|
1983
|
+
init_config();
|
|
1984
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1985
|
+
import { homedir as homedir2 } from "node:os";
|
|
1986
|
+
import { join as join2 } from "node:path";
|
|
1987
|
+
import pc from "picocolors";
|
|
1988
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
1989
|
+
var CACHE_DIR = join2(homedir2(), ".elnora");
|
|
1990
|
+
var CACHE_FILE = join2(CACHE_DIR, ".update-check");
|
|
1991
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1992
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
1993
|
+
function shouldSkip() {
|
|
1994
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
|
|
1995
|
+
if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
|
|
1996
|
+
if (!process.stderr.isTTY) return true;
|
|
1997
|
+
return false;
|
|
1781
1998
|
}
|
|
1782
|
-
function
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1999
|
+
function readCache() {
|
|
2000
|
+
try {
|
|
2001
|
+
if (!existsSync2(CACHE_FILE)) return null;
|
|
2002
|
+
const raw = readFileSync2(CACHE_FILE, "utf-8");
|
|
2003
|
+
return JSON.parse(raw);
|
|
2004
|
+
} catch {
|
|
2005
|
+
return null;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
function writeCache(entry) {
|
|
2009
|
+
try {
|
|
2010
|
+
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
2011
|
+
writeFileSync2(CACHE_FILE, JSON.stringify(entry), "utf-8");
|
|
2012
|
+
} catch {
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
function showUpdateNotice(latest) {
|
|
2016
|
+
const color = isColorEnabled();
|
|
2017
|
+
const msg = color ? `
|
|
2018
|
+
${pc.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
|
|
2019
|
+
Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
|
|
2020
|
+
` : `
|
|
2021
|
+
Update available: v${VERSION} \u2192 v${latest}
|
|
2022
|
+
Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
|
|
2023
|
+
`;
|
|
2024
|
+
process.stderr.write(msg);
|
|
2025
|
+
}
|
|
2026
|
+
function isNewerVersion(a, b) {
|
|
2027
|
+
const pa = a.split(".").map(Number);
|
|
2028
|
+
const pb = b.split(".").map(Number);
|
|
2029
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
2030
|
+
const na = pa[i] ?? 0;
|
|
2031
|
+
const nb = pb[i] ?? 0;
|
|
2032
|
+
if (na > nb) return true;
|
|
2033
|
+
if (na < nb) return false;
|
|
2034
|
+
}
|
|
2035
|
+
return false;
|
|
2036
|
+
}
|
|
2037
|
+
function registerUpdateCheck() {
|
|
2038
|
+
if (shouldSkip()) return;
|
|
2039
|
+
const cached = readCache();
|
|
2040
|
+
if (cached) {
|
|
2041
|
+
const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
|
|
2042
|
+
if (elapsed < CHECK_INTERVAL_MS) {
|
|
2043
|
+
if (cached.latest && cached.latest !== VERSION && cached.latest !== "unknown") {
|
|
2044
|
+
if (isNewerVersion(cached.latest, VERSION)) {
|
|
2045
|
+
showUpdateNotice(cached.latest);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
setTimeout(async () => {
|
|
2052
|
+
try {
|
|
2053
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
2054
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
2055
|
+
});
|
|
2056
|
+
if (!response.ok) return;
|
|
2057
|
+
const data = await response.json();
|
|
2058
|
+
const latest = data.version ?? "unknown";
|
|
2059
|
+
writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest });
|
|
2060
|
+
if (latest !== VERSION && latest !== "unknown" && isNewerVersion(latest, VERSION)) {
|
|
2061
|
+
showUpdateNotice(latest);
|
|
2062
|
+
}
|
|
2063
|
+
} catch {
|
|
2064
|
+
}
|
|
2065
|
+
}, 0);
|
|
1786
2066
|
}
|
|
1787
2067
|
|
|
1788
2068
|
// src/commands/doctor.ts
|
|
1789
2069
|
function addDoctorCommand(program2) {
|
|
1790
2070
|
program2.command("doctor").description("Check Elnora CLI configuration and connectivity").action(async () => {
|
|
1791
2071
|
const color = isColorEnabled();
|
|
1792
|
-
const ok = (msg) => color ? ` ${
|
|
1793
|
-
const fail = (msg) => color ? ` ${
|
|
1794
|
-
const warn = (msg) => color ? ` ${
|
|
2072
|
+
const ok = (msg) => color ? ` ${pc2.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
2073
|
+
const fail = (msg) => color ? ` ${pc2.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
2074
|
+
const warn = (msg) => color ? ` ${pc2.yellow("!")} ${msg}` : ` ! ${msg}`;
|
|
1795
2075
|
console.error(`
|
|
1796
2076
|
Elnora CLI v${VERSION}
|
|
1797
2077
|
`);
|
|
@@ -1832,7 +2112,7 @@ Elnora CLI v${VERSION}
|
|
|
1832
2112
|
if (res.ok) {
|
|
1833
2113
|
const data = await res.json();
|
|
1834
2114
|
const latest = data.version ?? "unknown";
|
|
1835
|
-
if (latest === VERSION || latest === "unknown") {
|
|
2115
|
+
if (latest === VERSION || latest === "unknown" || !isNewerVersion(latest, VERSION)) {
|
|
1836
2116
|
console.error(ok(`Version current v${VERSION} (latest: v${latest})`));
|
|
1837
2117
|
} else {
|
|
1838
2118
|
console.error(warn(`Update available v${VERSION} \u2192 v${latest}`));
|
|
@@ -1850,7 +2130,7 @@ Elnora CLI v${VERSION}
|
|
|
1850
2130
|
if (process.platform === "win32") {
|
|
1851
2131
|
console.error(ok("Config permissions (skipped on Windows)"));
|
|
1852
2132
|
passed++;
|
|
1853
|
-
} else if (!
|
|
2133
|
+
} else if (!existsSync3(PROFILES_FILE)) {
|
|
1854
2134
|
console.error(warn("Config permissions ~/.elnora/profiles.toml not found"));
|
|
1855
2135
|
} else {
|
|
1856
2136
|
const mode = statSync(PROFILES_FILE).mode & 511;
|
|
@@ -1878,7 +2158,7 @@ Elnora CLI v${VERSION}
|
|
|
1878
2158
|
console.error("All checks passed.");
|
|
1879
2159
|
} else {
|
|
1880
2160
|
console.error(`${passed}/${total} checks passed.`);
|
|
1881
|
-
if (!
|
|
2161
|
+
if (!existsSync3(PROFILES_FILE)) console.error("\nRun: elnora auth login");
|
|
1882
2162
|
}
|
|
1883
2163
|
});
|
|
1884
2164
|
}
|
|
@@ -2752,7 +3032,7 @@ var healthCheck = {
|
|
|
2752
3032
|
// src/commands/library/create-folder.ts
|
|
2753
3033
|
import { z as z51 } from "zod";
|
|
2754
3034
|
var inputSchema49 = z51.object({
|
|
2755
|
-
|
|
3035
|
+
orgId: z51.string().uuid().describe("Organization ID"),
|
|
2756
3036
|
name: z51.string().min(1).describe("Folder name"),
|
|
2757
3037
|
parent: z51.string().uuid().optional().describe("Parent folder ID")
|
|
2758
3038
|
});
|
|
@@ -2766,7 +3046,7 @@ var libraryCreateFolder = {
|
|
|
2766
3046
|
const body = { name: input.name };
|
|
2767
3047
|
if (input.parent) body.parentId = input.parent;
|
|
2768
3048
|
return ctx.client.post("library_folders", body, {
|
|
2769
|
-
pathParams: { orgId: input.
|
|
3049
|
+
pathParams: { orgId: input.orgId }
|
|
2770
3050
|
});
|
|
2771
3051
|
},
|
|
2772
3052
|
formatOutput(output, format) {
|
|
@@ -2778,7 +3058,7 @@ var libraryCreateFolder = {
|
|
|
2778
3058
|
// src/commands/library/delete-folder.ts
|
|
2779
3059
|
import { z as z52 } from "zod";
|
|
2780
3060
|
var inputSchema50 = z52.object({
|
|
2781
|
-
|
|
3061
|
+
orgId: z52.string().uuid().describe("Organization ID"),
|
|
2782
3062
|
folderId: z52.string().uuid().describe("Folder ID to delete")
|
|
2783
3063
|
});
|
|
2784
3064
|
var libraryDeleteFolder = {
|
|
@@ -2790,7 +3070,7 @@ var libraryDeleteFolder = {
|
|
|
2790
3070
|
annotations: { destructiveHint: true },
|
|
2791
3071
|
async execute(input, ctx) {
|
|
2792
3072
|
await ctx.client.del("library_folder", {
|
|
2793
|
-
pathParams: { orgId: input.
|
|
3073
|
+
pathParams: { orgId: input.orgId, id: input.folderId }
|
|
2794
3074
|
});
|
|
2795
3075
|
return { deleted: true, folderId: input.folderId };
|
|
2796
3076
|
},
|
|
@@ -2803,7 +3083,7 @@ var libraryDeleteFolder = {
|
|
|
2803
3083
|
// src/commands/library/files.ts
|
|
2804
3084
|
import { z as z53 } from "zod";
|
|
2805
3085
|
var inputSchema51 = z53.object({
|
|
2806
|
-
|
|
3086
|
+
orgId: z53.string().uuid().describe("Organization ID"),
|
|
2807
3087
|
...paginationInput
|
|
2808
3088
|
});
|
|
2809
3089
|
var libraryFiles = {
|
|
@@ -2815,7 +3095,7 @@ var libraryFiles = {
|
|
|
2815
3095
|
annotations: { readOnlyHint: true },
|
|
2816
3096
|
async execute(input, ctx) {
|
|
2817
3097
|
return ctx.client.get("library_files", {
|
|
2818
|
-
pathParams: { orgId: input.
|
|
3098
|
+
pathParams: { orgId: input.orgId },
|
|
2819
3099
|
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
2820
3100
|
});
|
|
2821
3101
|
},
|
|
@@ -2828,7 +3108,7 @@ var libraryFiles = {
|
|
|
2828
3108
|
// src/commands/library/folders.ts
|
|
2829
3109
|
import { z as z54 } from "zod";
|
|
2830
3110
|
var inputSchema52 = z54.object({
|
|
2831
|
-
|
|
3111
|
+
orgId: z54.string().uuid().describe("Organization ID")
|
|
2832
3112
|
});
|
|
2833
3113
|
var libraryFolders = {
|
|
2834
3114
|
name: "library.folders",
|
|
@@ -2839,7 +3119,7 @@ var libraryFolders = {
|
|
|
2839
3119
|
annotations: { readOnlyHint: true },
|
|
2840
3120
|
async execute(input, ctx) {
|
|
2841
3121
|
return ctx.client.get("library_folders", {
|
|
2842
|
-
pathParams: { orgId: input.
|
|
3122
|
+
pathParams: { orgId: input.orgId }
|
|
2843
3123
|
});
|
|
2844
3124
|
},
|
|
2845
3125
|
formatOutput(output, format) {
|
|
@@ -2851,7 +3131,7 @@ var libraryFolders = {
|
|
|
2851
3131
|
// src/commands/library/rename-folder.ts
|
|
2852
3132
|
import { z as z55 } from "zod";
|
|
2853
3133
|
var inputSchema53 = z55.object({
|
|
2854
|
-
|
|
3134
|
+
orgId: z55.string().uuid().describe("Organization ID"),
|
|
2855
3135
|
folderId: z55.string().uuid().describe("Folder ID to rename"),
|
|
2856
3136
|
name: z55.string().min(1).describe("New folder name")
|
|
2857
3137
|
});
|
|
@@ -2866,7 +3146,7 @@ var libraryRenameFolder = {
|
|
|
2866
3146
|
"library_folder",
|
|
2867
3147
|
{ name: input.name },
|
|
2868
3148
|
{
|
|
2869
|
-
pathParams: { orgId: input.
|
|
3149
|
+
pathParams: { orgId: input.orgId, id: input.folderId }
|
|
2870
3150
|
}
|
|
2871
3151
|
);
|
|
2872
3152
|
},
|
|
@@ -3174,9 +3454,10 @@ var orgsInvite = {
|
|
|
3174
3454
|
pathParams: { orgId: input.orgId }
|
|
3175
3455
|
});
|
|
3176
3456
|
const items = Array.isArray(existing) ? existing : existing?.items ?? [];
|
|
3177
|
-
const match = items.find(
|
|
3178
|
-
|
|
3179
|
-
|
|
3457
|
+
const match = items.find((inv) => {
|
|
3458
|
+
const i = inv;
|
|
3459
|
+
return i.email?.toLowerCase() === input.email.toLowerCase() && i.status === "Pending";
|
|
3460
|
+
});
|
|
3180
3461
|
if (match) return match;
|
|
3181
3462
|
return ctx.client.post(
|
|
3182
3463
|
"org_invitations",
|
|
@@ -3781,6 +4062,114 @@ function registerSearchCommands() {
|
|
|
3781
4062
|
return [searchTasks, searchFiles, searchAll, searchFileContent];
|
|
3782
4063
|
}
|
|
3783
4064
|
|
|
4065
|
+
// src/commands/setup-claude.ts
|
|
4066
|
+
init_config();
|
|
4067
|
+
import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4068
|
+
import { homedir as homedir3 } from "node:os";
|
|
4069
|
+
import { dirname, join as join3 } from "node:path";
|
|
4070
|
+
import { fileURLToPath } from "node:url";
|
|
4071
|
+
import pc3 from "picocolors";
|
|
4072
|
+
var __script_dir;
|
|
4073
|
+
try {
|
|
4074
|
+
__script_dir = dirname(fileURLToPath(import.meta.url));
|
|
4075
|
+
} catch {
|
|
4076
|
+
__script_dir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
4077
|
+
}
|
|
4078
|
+
var ELNORA_DIR = join3(homedir3(), ".elnora");
|
|
4079
|
+
var PLUGIN_DIR = join3(ELNORA_DIR, "plugin", "elnora");
|
|
4080
|
+
var CLAUDE_DIR = join3(homedir3(), ".claude");
|
|
4081
|
+
var MARKETPLACES_FILE = join3(CLAUDE_DIR, "plugins", "known_marketplaces.json");
|
|
4082
|
+
var SETTINGS_FILE = join3(CLAUDE_DIR, "settings.json");
|
|
4083
|
+
var MARKETPLACE_NAME = "elnora-local";
|
|
4084
|
+
var PLUGIN_ID = `elnora@${MARKETPLACE_NAME}`;
|
|
4085
|
+
function findSkillsDir() {
|
|
4086
|
+
const candidates = [
|
|
4087
|
+
join3(__script_dir, "../../skills"),
|
|
4088
|
+
join3(__script_dir, "../skills"),
|
|
4089
|
+
// When running from source via tsx
|
|
4090
|
+
join3(__script_dir, "../../../skills"),
|
|
4091
|
+
// Standalone binary: skills extracted to ~/.elnora/skills/
|
|
4092
|
+
join3(ELNORA_DIR, "skills")
|
|
4093
|
+
];
|
|
4094
|
+
for (const dir of candidates) {
|
|
4095
|
+
if (existsSync4(join3(dir, "elnora-platform", "SKILL.md"))) {
|
|
4096
|
+
return dir;
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
return null;
|
|
4100
|
+
}
|
|
4101
|
+
function readJsonFile(path) {
|
|
4102
|
+
try {
|
|
4103
|
+
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
4104
|
+
} catch {
|
|
4105
|
+
return {};
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
function writeJsonFile(path, data) {
|
|
4109
|
+
mkdirSync3(join3(path, ".."), { recursive: true });
|
|
4110
|
+
writeFileSync3(path, `${JSON.stringify(data, null, 2)}
|
|
4111
|
+
`, "utf-8");
|
|
4112
|
+
}
|
|
4113
|
+
function addSetupClaudeCommand(program2) {
|
|
4114
|
+
program2.command("setup-claude").description("Install Elnora skills for Claude Code").action(() => {
|
|
4115
|
+
const color = isColorEnabled();
|
|
4116
|
+
const ok = (msg) => color ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
4117
|
+
const fail = (msg) => color ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
4118
|
+
console.error("");
|
|
4119
|
+
if (!existsSync4(CLAUDE_DIR)) {
|
|
4120
|
+
console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
|
|
4121
|
+
console.error("");
|
|
4122
|
+
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
4123
|
+
process.exit(1);
|
|
4124
|
+
}
|
|
4125
|
+
console.error(ok("Claude Code found"));
|
|
4126
|
+
const skillsDir = findSkillsDir();
|
|
4127
|
+
if (!skillsDir) {
|
|
4128
|
+
console.error(fail("Skills not found"));
|
|
4129
|
+
console.error("");
|
|
4130
|
+
console.error(" Skills may not be bundled with your install method.");
|
|
4131
|
+
console.error(" Try: npm install -g @elnora-ai/cli");
|
|
4132
|
+
process.exit(1);
|
|
4133
|
+
}
|
|
4134
|
+
console.error(ok(`Skills found at ${skillsDir}`));
|
|
4135
|
+
mkdirSync3(PLUGIN_DIR, { recursive: true });
|
|
4136
|
+
writeJsonFile(join3(PLUGIN_DIR, ".claude-plugin", "plugin.json"), {
|
|
4137
|
+
name: "elnora",
|
|
4138
|
+
version: VERSION,
|
|
4139
|
+
description: "AI-powered bioprotocol generation and lab workflow management. Connect to the Elnora AI Platform to generate, optimize, and manage bioprotocols.",
|
|
4140
|
+
author: { name: "Elnora AI", url: "https://elnora.ai" },
|
|
4141
|
+
repository: "https://github.com/Elnora-AI/elnora-cli",
|
|
4142
|
+
homepage: "https://elnora.ai",
|
|
4143
|
+
license: "Apache-2.0",
|
|
4144
|
+
keywords: ["bioprotocol", "lab", "pharma", "life-sciences"]
|
|
4145
|
+
});
|
|
4146
|
+
cpSync(skillsDir, join3(PLUGIN_DIR, "skills"), { recursive: true });
|
|
4147
|
+
console.error(ok("Skills installed to ~/.elnora/plugin/"));
|
|
4148
|
+
const marketplaces = readJsonFile(MARKETPLACES_FILE);
|
|
4149
|
+
marketplaces[MARKETPLACE_NAME] = {
|
|
4150
|
+
source: {
|
|
4151
|
+
source: "directory",
|
|
4152
|
+
path: join3(ELNORA_DIR, "plugin")
|
|
4153
|
+
},
|
|
4154
|
+
installLocation: join3(ELNORA_DIR, "plugin"),
|
|
4155
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
4156
|
+
};
|
|
4157
|
+
writeJsonFile(MARKETPLACES_FILE, marketplaces);
|
|
4158
|
+
console.error(ok("Marketplace registered in Claude Code"));
|
|
4159
|
+
const settings = readJsonFile(SETTINGS_FILE);
|
|
4160
|
+
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
|
|
4161
|
+
settings.enabledPlugins = {};
|
|
4162
|
+
}
|
|
4163
|
+
settings.enabledPlugins[PLUGIN_ID] = true;
|
|
4164
|
+
writeJsonFile(SETTINGS_FILE, settings);
|
|
4165
|
+
console.error(ok("Plugin enabled"));
|
|
4166
|
+
console.error("");
|
|
4167
|
+
console.error(" Elnora skills are now available in Claude Code.");
|
|
4168
|
+
console.error(" Restart Claude Code if it's currently running.");
|
|
4169
|
+
console.error("");
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
4172
|
+
|
|
3784
4173
|
// src/commands/tasks/archive.ts
|
|
3785
4174
|
import { z as z88 } from "zod";
|
|
3786
4175
|
var inputSchema86 = z88.object({
|
|
@@ -3807,128 +4196,69 @@ var tasksArchive = {
|
|
|
3807
4196
|
|
|
3808
4197
|
// src/commands/tasks/create.ts
|
|
3809
4198
|
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
4199
|
|
|
3834
|
-
// src/
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
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 }
|
|
4200
|
+
// src/lib/poll.ts
|
|
4201
|
+
init_errors();
|
|
4202
|
+
async function pollForResponse(client, taskId, afterSequence = 0, timeoutMs = 12e4) {
|
|
4203
|
+
const deadline = Date.now() + timeoutMs;
|
|
4204
|
+
while (Date.now() < deadline) {
|
|
4205
|
+
const messages = await client.get("task_messages", {
|
|
4206
|
+
pathParams: { id: taskId },
|
|
4207
|
+
queryParams: { limit: 10 }
|
|
3849
4208
|
});
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
4209
|
+
const items = Array.isArray(messages) ? messages : messages?.items ?? [];
|
|
4210
|
+
if (Array.isArray(items)) {
|
|
4211
|
+
const assistantMsg = items.find((m) => {
|
|
4212
|
+
const msg = m;
|
|
4213
|
+
return (msg.role === "assistant" || msg.role === "Assistant") && (msg.sequence ?? msg.sequenceNumber ?? 0) > afterSequence;
|
|
4214
|
+
});
|
|
4215
|
+
if (assistantMsg) return assistantMsg;
|
|
4216
|
+
}
|
|
4217
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
3854
4218
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
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 }
|
|
3879
|
-
});
|
|
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;
|
|
3905
|
-
}
|
|
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);
|
|
3914
|
-
}
|
|
3915
|
-
};
|
|
4219
|
+
throw new ElnoraError("Timed out waiting for agent response (120s)", {
|
|
4220
|
+
code: "RESPONSE_TIMEOUT",
|
|
4221
|
+
suggestion: "The agent may still be processing. Check with: elnora tasks messages <task-id>"
|
|
4222
|
+
});
|
|
4223
|
+
}
|
|
3916
4224
|
|
|
3917
|
-
// src/commands/tasks/
|
|
3918
|
-
init_errors();
|
|
4225
|
+
// src/commands/tasks/create.ts
|
|
3919
4226
|
init_profiles();
|
|
3920
|
-
import { z as z93 } from "zod";
|
|
3921
4227
|
|
|
3922
4228
|
// src/lib/stream.ts
|
|
3923
4229
|
init_config();
|
|
4230
|
+
async function getStreamToken(taskId, apiKey) {
|
|
4231
|
+
const url = `${BASE_URL}/auth/stream-token`;
|
|
4232
|
+
const response = await fetch(url, {
|
|
4233
|
+
method: "POST",
|
|
4234
|
+
headers: {
|
|
4235
|
+
...DEFAULT_HEADERS,
|
|
4236
|
+
"X-API-Key": apiKey
|
|
4237
|
+
},
|
|
4238
|
+
body: JSON.stringify({ taskId }),
|
|
4239
|
+
signal: AbortSignal.timeout(1e4)
|
|
4240
|
+
});
|
|
4241
|
+
if (!response.ok) {
|
|
4242
|
+
throw new Error(`Stream token exchange failed: HTTP ${response.status}`);
|
|
4243
|
+
}
|
|
4244
|
+
const data = await response.json();
|
|
4245
|
+
return data.token;
|
|
4246
|
+
}
|
|
3924
4247
|
var TERMINAL_EVENTS = /* @__PURE__ */ new Set(["completed", "error", "timeout"]);
|
|
3925
4248
|
var STREAM_TIMEOUT_MS = 3e5;
|
|
3926
4249
|
async function* streamTask(taskId, apiKey, options) {
|
|
4250
|
+
let streamToken;
|
|
4251
|
+
try {
|
|
4252
|
+
streamToken = await getStreamToken(taskId, apiKey);
|
|
4253
|
+
} catch (err) {
|
|
4254
|
+
yield { type: "error", content: `Failed to get stream token: ${err instanceof Error ? err.message : String(err)}` };
|
|
4255
|
+
return;
|
|
4256
|
+
}
|
|
3927
4257
|
const baseUrl = options?.aiServerBaseUrl ?? AI_SERVER_URL;
|
|
3928
4258
|
const url = `${baseUrl}/api/v1/tasks/${taskId}/stream`;
|
|
3929
4259
|
const response = await fetch(url, {
|
|
3930
4260
|
headers: {
|
|
3931
|
-
|
|
4261
|
+
Authorization: `Bearer ${streamToken}`,
|
|
3932
4262
|
Accept: "text/event-stream"
|
|
3933
4263
|
},
|
|
3934
4264
|
signal: options?.signal ?? AbortSignal.timeout(STREAM_TIMEOUT_MS)
|
|
@@ -3987,7 +4317,7 @@ async function collectStreamResponse(taskId, apiKey, options) {
|
|
|
3987
4317
|
}
|
|
3988
4318
|
|
|
3989
4319
|
// src/lib/stream-renderer.ts
|
|
3990
|
-
import
|
|
4320
|
+
import pc4 from "picocolors";
|
|
3991
4321
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3992
4322
|
var SPINNER_INTERVAL_MS = 80;
|
|
3993
4323
|
var StreamRenderer = class {
|
|
@@ -4010,7 +4340,7 @@ var StreamRenderer = class {
|
|
|
4010
4340
|
case "tool_end": {
|
|
4011
4341
|
this.stopSpinner();
|
|
4012
4342
|
const toolEvent = event;
|
|
4013
|
-
const icon = toolEvent.success ? this.useColor ?
|
|
4343
|
+
const icon = toolEvent.success ? this.useColor ? pc4.green("\u2713") : "\u2713" : this.useColor ? pc4.red("\u2717") : "\u2717";
|
|
4014
4344
|
const suffix = toolEvent.result ? ` (${toolEvent.result})` : "";
|
|
4015
4345
|
process.stderr.write(` ${icon} ${toolEvent.tool}${suffix}
|
|
4016
4346
|
`);
|
|
@@ -4019,7 +4349,7 @@ var StreamRenderer = class {
|
|
|
4019
4349
|
case "progress":
|
|
4020
4350
|
this.stopSpinner();
|
|
4021
4351
|
process.stderr.write(
|
|
4022
|
-
` ${this.useColor ?
|
|
4352
|
+
` ${this.useColor ? pc4.dim(event.content) : event.content}
|
|
4023
4353
|
`
|
|
4024
4354
|
);
|
|
4025
4355
|
break;
|
|
@@ -4035,14 +4365,14 @@ var StreamRenderer = class {
|
|
|
4035
4365
|
this.stopSpinner();
|
|
4036
4366
|
process.stderr.write(
|
|
4037
4367
|
`
|
|
4038
|
-
${this.useColor ?
|
|
4368
|
+
${this.useColor ? pc4.red("Error:") : "Error:"} ${event.content}
|
|
4039
4369
|
`
|
|
4040
4370
|
);
|
|
4041
4371
|
break;
|
|
4042
4372
|
case "timeout":
|
|
4043
4373
|
this.stopSpinner();
|
|
4044
4374
|
process.stderr.write(`
|
|
4045
|
-
${this.useColor ?
|
|
4375
|
+
${this.useColor ? pc4.yellow("Stream timed out.") : "Stream timed out."}
|
|
4046
4376
|
`);
|
|
4047
4377
|
break;
|
|
4048
4378
|
}
|
|
@@ -4064,7 +4394,7 @@ ${this.useColor ? pc2.yellow("Stream timed out.") : "Stream timed out."}
|
|
|
4064
4394
|
}
|
|
4065
4395
|
writeSpinnerFrame() {
|
|
4066
4396
|
const frame = SPINNER_FRAMES[this.spinnerFrame];
|
|
4067
|
-
const text = this.useColor ?
|
|
4397
|
+
const text = this.useColor ? pc4.dim(`${frame} ${this.currentLabel}`) : `${frame} ${this.currentLabel}`;
|
|
4068
4398
|
process.stderr.write(`\r ${text}`);
|
|
4069
4399
|
}
|
|
4070
4400
|
stopSpinner() {
|
|
@@ -4078,7 +4408,145 @@ ${this.useColor ? pc2.yellow("Stream timed out.") : "Stream timed out."}
|
|
|
4078
4408
|
}
|
|
4079
4409
|
};
|
|
4080
4410
|
|
|
4411
|
+
// src/commands/tasks/create.ts
|
|
4412
|
+
var inputSchema87 = z89.object({
|
|
4413
|
+
project: z89.string().uuid().describe("Project ID"),
|
|
4414
|
+
title: z89.string().optional().describe("Task title"),
|
|
4415
|
+
message: z89.string().optional().describe("Initial message"),
|
|
4416
|
+
wait: z89.boolean().default(false).describe("Wait for agent response (polling)"),
|
|
4417
|
+
stream: z89.boolean().default(false).describe("Stream agent response in real-time (SSE)")
|
|
4418
|
+
});
|
|
4419
|
+
var tasksCreate = {
|
|
4420
|
+
name: "tasks.create",
|
|
4421
|
+
group: "tasks",
|
|
4422
|
+
description: "Create a new task in a project",
|
|
4423
|
+
inputSchema: inputSchema87,
|
|
4424
|
+
outputSchema: z89.any(),
|
|
4425
|
+
async execute(input, ctx) {
|
|
4426
|
+
const result = await ctx.client.post("tasks", {
|
|
4427
|
+
projectId: input.project,
|
|
4428
|
+
title: input.title,
|
|
4429
|
+
initialMessage: input.message
|
|
4430
|
+
});
|
|
4431
|
+
const taskId = result.id;
|
|
4432
|
+
const sequence = result?.sequence ?? result?.sequenceNumber ?? 0;
|
|
4433
|
+
if (!input.message || !input.stream && !input.wait) {
|
|
4434
|
+
return result;
|
|
4435
|
+
}
|
|
4436
|
+
if (ctx.mode === "mcp") {
|
|
4437
|
+
try {
|
|
4438
|
+
const apiKey = resolveApiKey(ctx.profileName);
|
|
4439
|
+
const content = await collectStreamResponse(taskId, apiKey);
|
|
4440
|
+
return { ...result, response: content };
|
|
4441
|
+
} catch {
|
|
4442
|
+
return pollForResponse(ctx.client, taskId, sequence);
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
if (input.stream) {
|
|
4446
|
+
const apiKey = resolveApiKey(ctx.profileName);
|
|
4447
|
+
const renderer = new StreamRenderer();
|
|
4448
|
+
for await (const event of streamTask(taskId, apiKey)) {
|
|
4449
|
+
renderer.renderEvent(event);
|
|
4450
|
+
}
|
|
4451
|
+
renderer.stopSpinner();
|
|
4452
|
+
return { ...result, streamed: true };
|
|
4453
|
+
}
|
|
4454
|
+
return pollForResponse(ctx.client, taskId, sequence);
|
|
4455
|
+
},
|
|
4456
|
+
formatOutput(output, format) {
|
|
4457
|
+
const data = output;
|
|
4458
|
+
if (format === "compact") return data.id ?? JSON.stringify(output);
|
|
4459
|
+
if (data.streamed) return "";
|
|
4460
|
+
return JSON.stringify(output, null, 2);
|
|
4461
|
+
}
|
|
4462
|
+
};
|
|
4463
|
+
|
|
4464
|
+
// src/commands/tasks/get.ts
|
|
4465
|
+
import { z as z90 } from "zod";
|
|
4466
|
+
var inputSchema88 = z90.object({
|
|
4467
|
+
taskId: z90.string().uuid().describe("Task ID")
|
|
4468
|
+
});
|
|
4469
|
+
var tasksGet = {
|
|
4470
|
+
name: "tasks.get",
|
|
4471
|
+
group: "tasks",
|
|
4472
|
+
description: "Get details of a specific task",
|
|
4473
|
+
inputSchema: inputSchema88,
|
|
4474
|
+
outputSchema: z90.any(),
|
|
4475
|
+
annotations: { readOnlyHint: true },
|
|
4476
|
+
async execute(input, ctx) {
|
|
4477
|
+
return ctx.client.get("task", {
|
|
4478
|
+
pathParams: { id: input.taskId }
|
|
4479
|
+
});
|
|
4480
|
+
},
|
|
4481
|
+
formatOutput(output, format) {
|
|
4482
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
4483
|
+
return JSON.stringify(output, null, 2);
|
|
4484
|
+
}
|
|
4485
|
+
};
|
|
4486
|
+
|
|
4487
|
+
// src/commands/tasks/list.ts
|
|
4488
|
+
import { z as z91 } from "zod";
|
|
4489
|
+
var inputSchema89 = z91.object({
|
|
4490
|
+
project: z91.string().uuid().optional().describe("Project ID to filter by"),
|
|
4491
|
+
...paginationInput
|
|
4492
|
+
});
|
|
4493
|
+
var tasksList = {
|
|
4494
|
+
name: "tasks.list",
|
|
4495
|
+
group: "tasks",
|
|
4496
|
+
description: "List tasks, optionally filtered by project",
|
|
4497
|
+
inputSchema: inputSchema89,
|
|
4498
|
+
outputSchema: z91.any(),
|
|
4499
|
+
annotations: { readOnlyHint: true },
|
|
4500
|
+
async execute(input, ctx) {
|
|
4501
|
+
if (input.project) {
|
|
4502
|
+
return ctx.client.get("project_tasks", {
|
|
4503
|
+
pathParams: { id: input.project },
|
|
4504
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
4505
|
+
});
|
|
4506
|
+
}
|
|
4507
|
+
return ctx.client.get("tasks", {
|
|
4508
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
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/messages.ts
|
|
4518
|
+
import { z as z92 } from "zod";
|
|
4519
|
+
var inputSchema90 = z92.object({
|
|
4520
|
+
taskId: z92.string().uuid().describe("Task ID"),
|
|
4521
|
+
cursor: z92.string().optional().describe("Pagination cursor"),
|
|
4522
|
+
limit: z92.number().int().min(1).max(100).default(50).describe("Results per page")
|
|
4523
|
+
});
|
|
4524
|
+
var tasksMessages = {
|
|
4525
|
+
name: "tasks.messages",
|
|
4526
|
+
group: "tasks",
|
|
4527
|
+
description: "List messages for a task",
|
|
4528
|
+
inputSchema: inputSchema90,
|
|
4529
|
+
outputSchema: z92.any(),
|
|
4530
|
+
annotations: { readOnlyHint: true },
|
|
4531
|
+
async execute(input, ctx) {
|
|
4532
|
+
const queryParams = { limit: input.limit };
|
|
4533
|
+
if (input.cursor) {
|
|
4534
|
+
queryParams.cursor = input.cursor;
|
|
4535
|
+
}
|
|
4536
|
+
return ctx.client.get("task_messages", {
|
|
4537
|
+
pathParams: { id: input.taskId },
|
|
4538
|
+
queryParams
|
|
4539
|
+
});
|
|
4540
|
+
},
|
|
4541
|
+
formatOutput(output, format) {
|
|
4542
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
4543
|
+
return JSON.stringify(output, null, 2);
|
|
4544
|
+
}
|
|
4545
|
+
};
|
|
4546
|
+
|
|
4081
4547
|
// src/commands/tasks/send.ts
|
|
4548
|
+
import { z as z93 } from "zod";
|
|
4549
|
+
init_profiles();
|
|
4082
4550
|
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
4551
|
var inputSchema91 = z93.object({
|
|
4084
4552
|
taskId: z93.string().uuid().describe("Task ID"),
|
|
@@ -4087,28 +4555,6 @@ var inputSchema91 = z93.object({
|
|
|
4087
4555
|
wait: z93.boolean().default(false).describe("Wait for agent response (polling)"),
|
|
4088
4556
|
stream: z93.boolean().default(false).describe("Stream agent response in real-time (SSE)")
|
|
4089
4557
|
});
|
|
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 }
|
|
4096
|
-
});
|
|
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;
|
|
4102
|
-
});
|
|
4103
|
-
if (assistantMsg) return assistantMsg;
|
|
4104
|
-
}
|
|
4105
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
4106
|
-
}
|
|
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
|
-
}
|
|
4112
4558
|
var tasksSend = {
|
|
4113
4559
|
name: "tasks.send",
|
|
4114
4560
|
group: "tasks",
|
|
@@ -4154,7 +4600,9 @@ var tasksSend = {
|
|
|
4154
4600
|
return pollForResponse(ctx.client, input.taskId, sequence);
|
|
4155
4601
|
},
|
|
4156
4602
|
formatOutput(output, format) {
|
|
4157
|
-
|
|
4603
|
+
const data = output;
|
|
4604
|
+
if (format === "compact") return data.id ?? JSON.stringify(output);
|
|
4605
|
+
if (data.streamed) return "";
|
|
4158
4606
|
return JSON.stringify(output, null, 2);
|
|
4159
4607
|
}
|
|
4160
4608
|
};
|
|
@@ -4197,6 +4645,136 @@ function registerTaskCommands() {
|
|
|
4197
4645
|
return [tasksList, tasksGet, tasksCreate, tasksSend, tasksMessages, tasksUpdate, tasksArchive];
|
|
4198
4646
|
}
|
|
4199
4647
|
|
|
4648
|
+
// src/commands/update.ts
|
|
4649
|
+
init_config();
|
|
4650
|
+
import { execFileSync } from "node:child_process";
|
|
4651
|
+
import { createHash } from "node:crypto";
|
|
4652
|
+
import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
|
|
4653
|
+
import { tmpdir } from "node:os";
|
|
4654
|
+
import { join as join4 } from "node:path";
|
|
4655
|
+
import { Readable } from "node:stream";
|
|
4656
|
+
import { pipeline } from "node:stream/promises";
|
|
4657
|
+
import pc5 from "picocolors";
|
|
4658
|
+
var NPM_REGISTRY_URL2 = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
4659
|
+
var GITHUB_REPO = "Elnora-AI/elnora-cli";
|
|
4660
|
+
function getPlatformTarget() {
|
|
4661
|
+
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
4662
|
+
switch (process.platform) {
|
|
4663
|
+
case "darwin":
|
|
4664
|
+
return { target: `elnora-macos-${arch}`, ext: "tar.gz" };
|
|
4665
|
+
case "linux":
|
|
4666
|
+
return { target: `elnora-linux-${arch}`, ext: "tar.gz" };
|
|
4667
|
+
case "win32":
|
|
4668
|
+
return { target: `elnora-win-${arch}.exe`, ext: "zip" };
|
|
4669
|
+
default:
|
|
4670
|
+
return null;
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
function getManualHint() {
|
|
4674
|
+
return process.platform === "win32" ? " irm https://cli.elnora.ai/install.ps1 | iex" : " curl -fsSL https://cli.elnora.ai/install.sh | bash";
|
|
4675
|
+
}
|
|
4676
|
+
async function downloadToFile(url, dest) {
|
|
4677
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(12e4), redirect: "follow" });
|
|
4678
|
+
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
|
4679
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
4680
|
+
await pipeline(nodeStream, createWriteStream(dest));
|
|
4681
|
+
}
|
|
4682
|
+
function addUpdateCommand(program2) {
|
|
4683
|
+
program2.command("update").description("Update Elnora CLI to the latest version").action(async () => {
|
|
4684
|
+
const color = isColorEnabled();
|
|
4685
|
+
const hint = getManualHint();
|
|
4686
|
+
let latest;
|
|
4687
|
+
try {
|
|
4688
|
+
const res = await fetch(NPM_REGISTRY_URL2, { signal: AbortSignal.timeout(5e3) });
|
|
4689
|
+
if (!res.ok) {
|
|
4690
|
+
console.error("Failed to check for updates. Try manually:");
|
|
4691
|
+
console.error(hint);
|
|
4692
|
+
process.exit(1);
|
|
4693
|
+
}
|
|
4694
|
+
const data = await res.json();
|
|
4695
|
+
latest = data.version ?? "unknown";
|
|
4696
|
+
} catch {
|
|
4697
|
+
console.error("Failed to reach registry. Try manually:");
|
|
4698
|
+
console.error(hint);
|
|
4699
|
+
process.exit(1);
|
|
4700
|
+
}
|
|
4701
|
+
if (latest === VERSION || !isNewerVersion(latest, VERSION)) {
|
|
4702
|
+
const msg2 = color ? `${pc5.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
|
|
4703
|
+
console.error(msg2);
|
|
4704
|
+
return;
|
|
4705
|
+
}
|
|
4706
|
+
const msg = color ? `Updating Elnora CLI ${pc5.dim(`v${VERSION}`)} \u2192 ${pc5.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
|
|
4707
|
+
console.error(msg);
|
|
4708
|
+
const platformInfo = getPlatformTarget();
|
|
4709
|
+
if (!platformInfo) {
|
|
4710
|
+
console.error(`
|
|
4711
|
+
Unsupported platform: ${process.platform}. Try manually:`);
|
|
4712
|
+
console.error(" npm update -g @elnora-ai/cli");
|
|
4713
|
+
process.exit(1);
|
|
4714
|
+
}
|
|
4715
|
+
const { target, ext } = platformInfo;
|
|
4716
|
+
const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
|
|
4717
|
+
const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
|
|
4718
|
+
const tmp = join4(tmpdir(), `elnora-update-${Date.now()}`);
|
|
4719
|
+
mkdirSync4(tmp, { recursive: true });
|
|
4720
|
+
const archivePath = join4(tmp, `${target}.${ext}`);
|
|
4721
|
+
try {
|
|
4722
|
+
await downloadToFile(binaryUrl, archivePath);
|
|
4723
|
+
let checksumVerified = false;
|
|
4724
|
+
try {
|
|
4725
|
+
const checksumRes = await fetch(checksumUrl, { signal: AbortSignal.timeout(15e3) });
|
|
4726
|
+
if (checksumRes.ok) {
|
|
4727
|
+
const checksumText = await checksumRes.text();
|
|
4728
|
+
const expected = checksumText.split(/\s/)[0].trim();
|
|
4729
|
+
if (expected.length === 64) {
|
|
4730
|
+
const actual = createHash("sha256").update(readFileSync6(archivePath)).digest("hex");
|
|
4731
|
+
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
4732
|
+
console.error("\nChecksum verification failed. The download may be corrupted.");
|
|
4733
|
+
console.error("Try again, or install manually:");
|
|
4734
|
+
console.error(hint);
|
|
4735
|
+
process.exit(1);
|
|
4736
|
+
}
|
|
4737
|
+
checksumVerified = true;
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
} catch {
|
|
4741
|
+
}
|
|
4742
|
+
if (!checksumVerified) {
|
|
4743
|
+
console.error("Warning: Could not verify download checksum. Proceeding anyway.");
|
|
4744
|
+
}
|
|
4745
|
+
if (process.platform === "win32") {
|
|
4746
|
+
const extractDir = join4(tmp, "extracted");
|
|
4747
|
+
execFileSync("powershell", [
|
|
4748
|
+
"-NoProfile",
|
|
4749
|
+
"-Command",
|
|
4750
|
+
`Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
|
|
4751
|
+
]);
|
|
4752
|
+
const installDir = join4(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
4753
|
+
mkdirSync4(installDir, { recursive: true });
|
|
4754
|
+
copyFileSync(join4(extractDir, target), join4(installDir, "elnora.exe"));
|
|
4755
|
+
} else {
|
|
4756
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
|
|
4757
|
+
const installDir = process.env.ELNORA_INSTALL_DIR ?? join4(process.env.HOME ?? "", ".local", "bin");
|
|
4758
|
+
mkdirSync4(installDir, { recursive: true });
|
|
4759
|
+
const dest = join4(installDir, "elnora");
|
|
4760
|
+
copyFileSync(join4(tmp, target), dest);
|
|
4761
|
+
chmodSync2(dest, 493);
|
|
4762
|
+
}
|
|
4763
|
+
const done = color ? `${pc5.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
|
|
4764
|
+
console.error(done);
|
|
4765
|
+
} catch {
|
|
4766
|
+
console.error("\nUpdate failed. Try manually:");
|
|
4767
|
+
console.error(hint);
|
|
4768
|
+
process.exit(1);
|
|
4769
|
+
} finally {
|
|
4770
|
+
try {
|
|
4771
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
4772
|
+
} catch {
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4200
4778
|
// src/commands/whoami.ts
|
|
4201
4779
|
init_client();
|
|
4202
4780
|
init_profiles();
|
|
@@ -4261,95 +4839,26 @@ var CommandRegistry = class {
|
|
|
4261
4839
|
|
|
4262
4840
|
// src/cli.ts
|
|
4263
4841
|
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
4842
|
process.on("uncaughtException", (err) => {
|
|
4342
|
-
|
|
4343
|
-
|
|
4843
|
+
if (process.stderr.isTTY) {
|
|
4844
|
+
process.stderr.write(`${formatErrorForHuman(err)}
|
|
4845
|
+
`);
|
|
4846
|
+
} else {
|
|
4847
|
+
process.stderr.write(`${JSON.stringify(formatErrorPayload(err), null, 2)}
|
|
4344
4848
|
`);
|
|
4345
|
-
|
|
4849
|
+
}
|
|
4850
|
+
process.exitCode = getExitCode(err);
|
|
4346
4851
|
});
|
|
4347
4852
|
process.on("unhandledRejection", (reason) => {
|
|
4348
4853
|
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
4349
|
-
|
|
4350
|
-
|
|
4854
|
+
if (process.stderr.isTTY) {
|
|
4855
|
+
process.stderr.write(`${formatErrorForHuman(err)}
|
|
4351
4856
|
`);
|
|
4352
|
-
|
|
4857
|
+
} else {
|
|
4858
|
+
process.stderr.write(`${JSON.stringify(formatErrorPayload(err), null, 2)}
|
|
4859
|
+
`);
|
|
4860
|
+
}
|
|
4861
|
+
process.exitCode = 1;
|
|
4353
4862
|
});
|
|
4354
4863
|
var registry = new CommandRegistry();
|
|
4355
4864
|
var commandGroups = [
|
|
@@ -4378,6 +4887,8 @@ addMcpCommands(program, registry);
|
|
|
4378
4887
|
addDoctorCommand(program);
|
|
4379
4888
|
addWhoamiCommand(program);
|
|
4380
4889
|
addOpenCommand(program);
|
|
4890
|
+
addSetupClaudeCommand(program);
|
|
4891
|
+
addUpdateCommand(program);
|
|
4381
4892
|
addCompletionCommand(program);
|
|
4382
4893
|
registerUpdateCheck();
|
|
4383
4894
|
program.parseAsync(process.argv);
|