@elnora-ai/cli 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +169 -72
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ var VERSION, PRODUCTION_BASE_URL, BASE_URL, AI_SERVER_URL, ENDPOINTS, DEFAULT_HE
|
|
|
25
25
|
var init_config = __esm({
|
|
26
26
|
"src/lib/config.ts"() {
|
|
27
27
|
"use strict";
|
|
28
|
-
VERSION = true ? "2.0.
|
|
28
|
+
VERSION = true ? "2.0.5" : process.env.npm_package_version ?? "0.0.0-dev";
|
|
29
29
|
PRODUCTION_BASE_URL = "https://platform.elnora.ai/api/v1";
|
|
30
30
|
BASE_URL = process.env.ELNORA_API_URL ?? PRODUCTION_BASE_URL;
|
|
31
31
|
AI_SERVER_URL = process.env.ELNORA_AI_SERVER_URL ?? "https://platform.elnora.ai/ai-server";
|
|
@@ -185,7 +185,7 @@ function formatErrorForHuman(err) {
|
|
|
185
185
|
}
|
|
186
186
|
return lines.join("\n");
|
|
187
187
|
}
|
|
188
|
-
var ElnoraError, AuthError, NotFoundError, RateLimitError, ValidationError, ServerError, EXIT_CODES, SCRUB_KEY_VALUE_RE, SCRUB_LONG_TOKEN_RE;
|
|
188
|
+
var ElnoraError, AuthError, NotFoundError, RateLimitError, ValidationError, ServerError, NetworkError, EXIT_CODES, SCRUB_KEY_VALUE_RE, SCRUB_LONG_TOKEN_RE;
|
|
189
189
|
var init_errors = __esm({
|
|
190
190
|
"src/lib/errors.ts"() {
|
|
191
191
|
"use strict";
|
|
@@ -241,6 +241,19 @@ var init_errors = __esm({
|
|
|
241
241
|
this.name = "ServerError";
|
|
242
242
|
}
|
|
243
243
|
};
|
|
244
|
+
NetworkError = class extends ElnoraError {
|
|
245
|
+
host;
|
|
246
|
+
constructor(host, cause) {
|
|
247
|
+
const target = host ? ` reaching ${host}` : "";
|
|
248
|
+
const reason = cause ? ` (${cause})` : "";
|
|
249
|
+
super(`Network error${target}${reason}`, {
|
|
250
|
+
code: "NETWORK_ERROR",
|
|
251
|
+
suggestion: host ? `Run 'elnora doctor' to diagnose connectivity, then confirm ${host} is reachable from this machine.` : "Run 'elnora doctor' to diagnose connectivity."
|
|
252
|
+
});
|
|
253
|
+
this.name = "NetworkError";
|
|
254
|
+
this.host = host;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
244
257
|
EXIT_CODES = /* @__PURE__ */ new Map([
|
|
245
258
|
[ValidationError, 2],
|
|
246
259
|
[AuthError, 3],
|
|
@@ -624,16 +637,17 @@ var init_client = __esm({
|
|
|
624
637
|
});
|
|
625
638
|
} catch (err) {
|
|
626
639
|
if (err instanceof ElnoraError) throw err;
|
|
640
|
+
let host;
|
|
641
|
+
try {
|
|
642
|
+
host = new URL(url).hostname;
|
|
643
|
+
} catch {
|
|
644
|
+
host = void 0;
|
|
645
|
+
}
|
|
627
646
|
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
628
|
-
throw new
|
|
629
|
-
code: "TIMEOUT",
|
|
630
|
-
suggestion: "Check your internet connection and try again."
|
|
631
|
-
});
|
|
647
|
+
throw new NetworkError(host, `timed out after ${this.timeout}ms`);
|
|
632
648
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
suggestion: "Check your internet connection and try again."
|
|
636
|
-
});
|
|
649
|
+
const causeCode = err instanceof Error && err.cause && typeof err.cause === "object" && "code" in err.cause ? String(err.cause.code) : err instanceof Error ? err.message : String(err);
|
|
650
|
+
throw new NetworkError(host, scrub(causeCode));
|
|
637
651
|
}
|
|
638
652
|
}
|
|
639
653
|
throw new RateLimitError("Rate limit exceeded after retries");
|
|
@@ -1337,6 +1351,30 @@ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as rea
|
|
|
1337
1351
|
import { homedir as homedir3 } from "node:os";
|
|
1338
1352
|
import { join as join3 } from "node:path";
|
|
1339
1353
|
import pc2 from "picocolors";
|
|
1354
|
+
|
|
1355
|
+
// src/lib/install-method.ts
|
|
1356
|
+
function detectInstallMethod() {
|
|
1357
|
+
const execPath = process.argv[1] ?? "";
|
|
1358
|
+
if (execPath.includes("node_modules")) {
|
|
1359
|
+
return "npm";
|
|
1360
|
+
}
|
|
1361
|
+
if (execPath.includes("/Cellar/") || execPath.includes("/homebrew/")) {
|
|
1362
|
+
return "homebrew";
|
|
1363
|
+
}
|
|
1364
|
+
return "binary";
|
|
1365
|
+
}
|
|
1366
|
+
function getUpdateInstruction(method) {
|
|
1367
|
+
switch (method) {
|
|
1368
|
+
case "npm":
|
|
1369
|
+
return "npm install -g @elnora-ai/cli@latest";
|
|
1370
|
+
case "homebrew":
|
|
1371
|
+
return "brew upgrade elnora";
|
|
1372
|
+
case "binary":
|
|
1373
|
+
return "elnora update --install";
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// src/lib/update-check.ts
|
|
1340
1378
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
1341
1379
|
var CACHE_DIR = join3(homedir3(), ".elnora");
|
|
1342
1380
|
var CACHE_FILE = join3(CACHE_DIR, ".update-check");
|
|
@@ -1366,13 +1404,15 @@ function writeCache(entry) {
|
|
|
1366
1404
|
}
|
|
1367
1405
|
function showUpdateNotice(latest) {
|
|
1368
1406
|
if (!process.stderr.isTTY) return;
|
|
1407
|
+
const command = getUpdateInstruction(detectInstallMethod());
|
|
1369
1408
|
const color2 = isColorEnabled();
|
|
1409
|
+
const head = `Update available: v${VERSION} \u2192 v${latest}`;
|
|
1370
1410
|
const msg = color2 ? `
|
|
1371
|
-
${pc2.yellow(
|
|
1372
|
-
Run:
|
|
1411
|
+
${pc2.yellow(head)}
|
|
1412
|
+
Run: ${command}
|
|
1373
1413
|
` : `
|
|
1374
|
-
|
|
1375
|
-
Run:
|
|
1414
|
+
${head}
|
|
1415
|
+
Run: ${command}
|
|
1376
1416
|
`;
|
|
1377
1417
|
process.stderr.write(msg);
|
|
1378
1418
|
}
|
|
@@ -1508,33 +1548,44 @@ function checkPathConfigured() {
|
|
|
1508
1548
|
msg: "PATH configured standard install dirs not in PATH (OK if installed via npm/brew)"
|
|
1509
1549
|
};
|
|
1510
1550
|
}
|
|
1551
|
+
function readEnabledPlugins(file) {
|
|
1552
|
+
if (!existsSync4(file)) return null;
|
|
1553
|
+
try {
|
|
1554
|
+
const settings = JSON.parse(readFileSync4(file, "utf-8"));
|
|
1555
|
+
const raw = settings.enabledPlugins;
|
|
1556
|
+
const enabledPlugins = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
1557
|
+
return { enabledPlugins };
|
|
1558
|
+
} catch (e) {
|
|
1559
|
+
return { enabledPlugins: {}, unreadable: e instanceof Error ? e.message : "parse error" };
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1511
1562
|
function checkClaudePlugin() {
|
|
1512
1563
|
if (!existsSync4(CLAUDE_DIR)) {
|
|
1513
1564
|
return { status: "skip", msg: "Plugin enabled (Claude Code not installed)" };
|
|
1514
1565
|
}
|
|
1515
|
-
|
|
1566
|
+
const projectSettingsFile = join4(process.cwd(), ".claude", "settings.json");
|
|
1567
|
+
const user = readEnabledPlugins(CLAUDE_SETTINGS_FILE);
|
|
1568
|
+
const project = readEnabledPlugins(projectSettingsFile);
|
|
1569
|
+
if (!user && !project) {
|
|
1516
1570
|
return { status: "fail", msg: "Plugin enabled settings.json not found \u2014 run 'elnora setup claude'" };
|
|
1517
1571
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
settings = JSON.parse(readFileSync4(CLAUDE_SETTINGS_FILE, "utf-8"));
|
|
1521
|
-
} catch (e) {
|
|
1522
|
-
return {
|
|
1523
|
-
status: "fail",
|
|
1524
|
-
msg: `Plugin enabled settings.json unreadable (${e instanceof Error ? e.message : "parse error"})`
|
|
1525
|
-
};
|
|
1572
|
+
if (user?.unreadable && !project?.enabledPlugins[PLUGIN_ID]) {
|
|
1573
|
+
return { status: "fail", msg: `Plugin enabled settings.json unreadable (${user.unreadable})` };
|
|
1526
1574
|
}
|
|
1527
|
-
const
|
|
1528
|
-
const
|
|
1529
|
-
const isEnabled =
|
|
1530
|
-
const
|
|
1575
|
+
const userEnabled = user?.enabledPlugins[PLUGIN_ID] === true;
|
|
1576
|
+
const projectEnabled = project?.enabledPlugins[PLUGIN_ID] === true;
|
|
1577
|
+
const isEnabled = userEnabled || projectEnabled;
|
|
1578
|
+
const scope = projectEnabled ? userEnabled ? "user + project" : "project" : "user";
|
|
1579
|
+
const legacyKeys = [
|
|
1580
|
+
.../* @__PURE__ */ new Set([...Object.keys(user?.enabledPlugins ?? {}), ...Object.keys(project?.enabledPlugins ?? {})])
|
|
1581
|
+
].filter((k) => k.startsWith("elnora@") && k !== PLUGIN_ID);
|
|
1531
1582
|
if (isEnabled && legacyKeys.length === 0) {
|
|
1532
|
-
return { status: "pass", msg: `Plugin enabled ${PLUGIN_ID}` };
|
|
1583
|
+
return { status: "pass", msg: `Plugin enabled ${PLUGIN_ID} (${scope} scope)` };
|
|
1533
1584
|
}
|
|
1534
1585
|
if (isEnabled && legacyKeys.length > 0) {
|
|
1535
1586
|
return {
|
|
1536
1587
|
status: "warn",
|
|
1537
|
-
msg: `Plugin enabled ${PLUGIN_ID} \u2014 but stale legacy entries also present: ${legacyKeys.join(", ")}`
|
|
1588
|
+
msg: `Plugin enabled ${PLUGIN_ID} (${scope} scope) \u2014 but stale legacy entries also present: ${legacyKeys.join(", ")}`
|
|
1538
1589
|
};
|
|
1539
1590
|
}
|
|
1540
1591
|
if (legacyKeys.length > 0) {
|
|
@@ -1545,15 +1596,46 @@ function checkClaudePlugin() {
|
|
|
1545
1596
|
}
|
|
1546
1597
|
return { status: "fail", msg: "Plugin enabled not found \u2014 run 'elnora setup claude'" };
|
|
1547
1598
|
}
|
|
1599
|
+
function isMarketplaceRegistered() {
|
|
1600
|
+
for (const file of [CLAUDE_SETTINGS_FILE, join4(process.cwd(), ".claude", "settings.json")]) {
|
|
1601
|
+
if (!existsSync4(file)) continue;
|
|
1602
|
+
try {
|
|
1603
|
+
const settings = JSON.parse(readFileSync4(file, "utf-8"));
|
|
1604
|
+
const enabled = settings.enabledPlugins;
|
|
1605
|
+
if (typeof enabled === "object" && enabled !== null && enabled[PLUGIN_ID] === true) {
|
|
1606
|
+
return true;
|
|
1607
|
+
}
|
|
1608
|
+
const extra = settings.extraKnownMarketplaces;
|
|
1609
|
+
if (extra != null && JSON.stringify(extra).includes(MARKETPLACE_NAME)) return true;
|
|
1610
|
+
} catch {
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
const knownMarketplaces = join4(CLAUDE_DIR, "plugins", "known_marketplaces.json");
|
|
1614
|
+
if (existsSync4(knownMarketplaces)) {
|
|
1615
|
+
try {
|
|
1616
|
+
if (JSON.stringify(JSON.parse(readFileSync4(knownMarketplaces, "utf-8"))).includes(MARKETPLACE_NAME)) {
|
|
1617
|
+
return true;
|
|
1618
|
+
}
|
|
1619
|
+
} catch {
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1548
1624
|
function checkSkillsInstalled() {
|
|
1549
1625
|
if (!existsSync4(CLAUDE_DIR)) {
|
|
1550
1626
|
return { status: "skip", msg: "Skills installed (Claude Code not installed)" };
|
|
1551
1627
|
}
|
|
1552
1628
|
const skillsDir = join4(CLAUDE_DIR, "plugins", "marketplaces", MARKETPLACE_NAME, "elnora", "skills");
|
|
1553
1629
|
if (!existsSync4(skillsDir)) {
|
|
1630
|
+
if (isMarketplaceRegistered()) {
|
|
1631
|
+
return {
|
|
1632
|
+
status: "warn",
|
|
1633
|
+
msg: "Skills installed registered but not cloned yet \u2014 open this folder in Claude Code once to clone"
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1554
1636
|
return {
|
|
1555
1637
|
status: "warn",
|
|
1556
|
-
msg: "Skills installed marketplace not
|
|
1638
|
+
msg: "Skills installed marketplace not registered \u2014 run 'elnora setup claude'"
|
|
1557
1639
|
};
|
|
1558
1640
|
}
|
|
1559
1641
|
const found = EXPECTED_SKILLS.filter((s) => existsSync4(join4(skillsDir, s, "SKILL.md")));
|
|
@@ -1642,16 +1724,25 @@ function addDoctorCommand(program2) {
|
|
|
1642
1724
|
Elnora CLI v${VERSION}
|
|
1643
1725
|
`);
|
|
1644
1726
|
let passed = 0;
|
|
1727
|
+
let skipped = 0;
|
|
1645
1728
|
let failed = 0;
|
|
1646
1729
|
let warnings = 0;
|
|
1647
|
-
let total = 0;
|
|
1648
1730
|
const tally = (r) => {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1731
|
+
switch (r.status) {
|
|
1732
|
+
case "pass":
|
|
1733
|
+
passed++;
|
|
1734
|
+
break;
|
|
1735
|
+
case "warn":
|
|
1736
|
+
passed++;
|
|
1737
|
+
warnings++;
|
|
1738
|
+
break;
|
|
1739
|
+
case "skip":
|
|
1740
|
+
skipped++;
|
|
1741
|
+
break;
|
|
1742
|
+
case "fail":
|
|
1743
|
+
failed++;
|
|
1744
|
+
break;
|
|
1745
|
+
}
|
|
1655
1746
|
};
|
|
1656
1747
|
console.error(section("CLI"));
|
|
1657
1748
|
const cliChecks = [
|
|
@@ -1685,14 +1776,15 @@ Elnora CLI v${VERSION}
|
|
|
1685
1776
|
tally(r);
|
|
1686
1777
|
}
|
|
1687
1778
|
console.error("");
|
|
1688
|
-
if (failed === 0 && warnings === 0) {
|
|
1779
|
+
if (failed === 0 && warnings === 0 && skipped === 0) {
|
|
1689
1780
|
console.error("All checks passed.");
|
|
1690
|
-
} else if (failed === 0) {
|
|
1691
|
-
console.error(`${passed}/${total} checks passed (${warnings} warning${warnings === 1 ? "" : "s"}).`);
|
|
1692
1781
|
} else {
|
|
1693
|
-
const
|
|
1694
|
-
|
|
1695
|
-
if (
|
|
1782
|
+
const parts = [`${passed} passed`];
|
|
1783
|
+
if (skipped > 0) parts.push(`${skipped} skipped`);
|
|
1784
|
+
if (warnings > 0) parts.push(`${warnings} warning${warnings === 1 ? "" : "s"}`);
|
|
1785
|
+
if (failed > 0) parts.push(`${failed} failed`);
|
|
1786
|
+
console.error(`${parts.join(", ")}.`);
|
|
1787
|
+
if (failed > 0 && !existsSync4(PROFILES_FILE)) console.error("\nRun: elnora auth login");
|
|
1696
1788
|
}
|
|
1697
1789
|
});
|
|
1698
1790
|
}
|
|
@@ -2017,36 +2109,21 @@ function addSetupCommand(program2) {
|
|
|
2017
2109
|
init_config();
|
|
2018
2110
|
import { execFileSync } from "node:child_process";
|
|
2019
2111
|
import { createHash } from "node:crypto";
|
|
2020
|
-
import {
|
|
2112
|
+
import {
|
|
2113
|
+
chmodSync as chmodSync2,
|
|
2114
|
+
copyFileSync,
|
|
2115
|
+
createWriteStream,
|
|
2116
|
+
mkdirSync as mkdirSync4,
|
|
2117
|
+
readdirSync,
|
|
2118
|
+
readFileSync as readFileSync5,
|
|
2119
|
+
renameSync,
|
|
2120
|
+
rmSync
|
|
2121
|
+
} from "node:fs";
|
|
2021
2122
|
import { tmpdir } from "node:os";
|
|
2022
2123
|
import { join as join7 } from "node:path";
|
|
2023
2124
|
import { Readable } from "node:stream";
|
|
2024
2125
|
import { pipeline } from "node:stream/promises";
|
|
2025
2126
|
import pc4 from "picocolors";
|
|
2026
|
-
|
|
2027
|
-
// src/lib/install-method.ts
|
|
2028
|
-
function detectInstallMethod() {
|
|
2029
|
-
const execPath = process.argv[1] ?? "";
|
|
2030
|
-
if (execPath.includes("node_modules")) {
|
|
2031
|
-
return "npm";
|
|
2032
|
-
}
|
|
2033
|
-
if (execPath.includes("/Cellar/") || execPath.includes("/homebrew/")) {
|
|
2034
|
-
return "homebrew";
|
|
2035
|
-
}
|
|
2036
|
-
return "binary";
|
|
2037
|
-
}
|
|
2038
|
-
function getUpdateInstruction(method) {
|
|
2039
|
-
switch (method) {
|
|
2040
|
-
case "npm":
|
|
2041
|
-
return "npm install -g @elnora-ai/cli@latest";
|
|
2042
|
-
case "homebrew":
|
|
2043
|
-
return "brew upgrade elnora";
|
|
2044
|
-
case "binary":
|
|
2045
|
-
return "elnora update --install";
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
// src/commands/update.ts
|
|
2050
2127
|
var NPM_REGISTRY_URL2 = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
2051
2128
|
var GITHUB_REPO = "Elnora-AI/elnora-cli";
|
|
2052
2129
|
function getPlatformTarget() {
|
|
@@ -2074,6 +2151,25 @@ async function downloadToFile(url, dest) {
|
|
|
2074
2151
|
const nodeStream = Readable.fromWeb(res.body);
|
|
2075
2152
|
await pipeline(nodeStream, createWriteStream(dest));
|
|
2076
2153
|
}
|
|
2154
|
+
function installWindowsBinary(srcPath, installDir) {
|
|
2155
|
+
mkdirSync4(installDir, { recursive: true });
|
|
2156
|
+
const dest = join7(installDir, "elnora.exe");
|
|
2157
|
+
try {
|
|
2158
|
+
renameSync(dest, `${dest}.old-${Date.now()}`);
|
|
2159
|
+
} catch (e) {
|
|
2160
|
+
if (e.code !== "ENOENT") throw e;
|
|
2161
|
+
}
|
|
2162
|
+
copyFileSync(srcPath, dest);
|
|
2163
|
+
for (const name of readdirSync(installDir)) {
|
|
2164
|
+
if (name.startsWith("elnora.exe.old-")) {
|
|
2165
|
+
try {
|
|
2166
|
+
rmSync(join7(installDir, name), { force: true });
|
|
2167
|
+
} catch {
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return dest;
|
|
2172
|
+
}
|
|
2077
2173
|
async function installBinaryUpdate(latest) {
|
|
2078
2174
|
const color2 = isColorEnabled();
|
|
2079
2175
|
const platformInfo = getPlatformTarget();
|
|
@@ -2121,8 +2217,7 @@ async function installBinaryUpdate(latest) {
|
|
|
2121
2217
|
`Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
|
|
2122
2218
|
]);
|
|
2123
2219
|
const installDir = join7(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
2124
|
-
|
|
2125
|
-
copyFileSync(join7(extractDir, target), join7(installDir, "elnora.exe"));
|
|
2220
|
+
installWindowsBinary(join7(extractDir, target), installDir);
|
|
2126
2221
|
} else {
|
|
2127
2222
|
execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
|
|
2128
2223
|
const installDir = process.env.ELNORA_INSTALL_DIR ?? join7(process.env.HOME ?? "", ".local", "bin");
|
|
@@ -2183,8 +2278,10 @@ function addUpdateCommand(program2) {
|
|
|
2183
2278
|
}
|
|
2184
2279
|
try {
|
|
2185
2280
|
await installBinaryUpdate(latest);
|
|
2186
|
-
} catch {
|
|
2187
|
-
|
|
2281
|
+
} catch (err) {
|
|
2282
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
2283
|
+
console.error(`Update failed: ${reason}`);
|
|
2284
|
+
console.error("Try manually:");
|
|
2188
2285
|
console.error(
|
|
2189
2286
|
process.platform === "win32" ? " irm https://cli.elnora.ai/install.ps1 | iex" : " curl -fsSL https://cli.elnora.ai/install.sh | bash"
|
|
2190
2287
|
);
|