@elnora-ai/cli 1.2.1 → 1.3.1
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 +437 -204
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -398,7 +398,32 @@ function resolveApiKey(profileName) {
|
|
|
398
398
|
migrateConfigIfNeeded();
|
|
399
399
|
return getApiKey(profileName ?? "default");
|
|
400
400
|
}
|
|
401
|
-
|
|
401
|
+
function truncateForDisplay(value) {
|
|
402
|
+
if (value.length > 20) return `${value.slice(0, 16)}...${value.slice(-4)}`;
|
|
403
|
+
return `${value.slice(0, 4)}...`;
|
|
404
|
+
}
|
|
405
|
+
function resolveCredentialHint(profileName) {
|
|
406
|
+
for (const varName of CREDENTIAL_ENV_VARS) {
|
|
407
|
+
const val2 = process.env[varName];
|
|
408
|
+
if (val2) return truncateForDisplay(val2);
|
|
409
|
+
}
|
|
410
|
+
migrateConfigIfNeeded();
|
|
411
|
+
const profiles = loadProfiles();
|
|
412
|
+
const name = profileName ?? "default";
|
|
413
|
+
if (!(name in profiles)) {
|
|
414
|
+
throw new AuthError(`Profile '${name}' not found.`, {
|
|
415
|
+
suggestion: "Run 'elnora auth login' to set up a profile."
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
const val = profiles[name][PROFILE_CREDENTIAL_FIELD] ?? "";
|
|
419
|
+
if (!val) {
|
|
420
|
+
throw new AuthError(`Profile '${name}' has no credential.`, {
|
|
421
|
+
suggestion: `Run: elnora auth login --profile ${name}`
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return truncateForDisplay(val);
|
|
425
|
+
}
|
|
426
|
+
var CONFIG_DIR, PROFILES_FILE, LEGACY_CONFIG_FILE, PROFILE_NAME_RE, CREDENTIAL_ENV_VARS, PROFILE_CREDENTIAL_FIELD;
|
|
402
427
|
var init_profiles = __esm({
|
|
403
428
|
"src/lib/profiles.ts"() {
|
|
404
429
|
"use strict";
|
|
@@ -407,6 +432,8 @@ var init_profiles = __esm({
|
|
|
407
432
|
PROFILES_FILE = join(CONFIG_DIR, "profiles.toml");
|
|
408
433
|
LEGACY_CONFIG_FILE = join(CONFIG_DIR, "config.toml");
|
|
409
434
|
PROFILE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
435
|
+
CREDENTIAL_ENV_VARS = ["ELNORA_API_KEY", "ELNORA_MCP_API_KEY"];
|
|
436
|
+
PROFILE_CREDENTIAL_FIELD = "api_key";
|
|
410
437
|
}
|
|
411
438
|
});
|
|
412
439
|
|
|
@@ -1906,7 +1933,7 @@ function generateZsh(root) {
|
|
|
1906
1933
|
lines.push(" if (( CURRENT == 2 )); then");
|
|
1907
1934
|
lines.push(" commands=(");
|
|
1908
1935
|
for (const cmd of root.subcommands) {
|
|
1909
|
-
lines.push(` '${cmd.name}:${cmd.description.replace(/'/g, "")}'`);
|
|
1936
|
+
lines.push(` '${cmd.name}:${cmd.description.replace(/\\/g, "\\\\").replace(/'/g, "")}'`);
|
|
1910
1937
|
}
|
|
1911
1938
|
lines.push(" )");
|
|
1912
1939
|
lines.push(" _describe 'command' commands");
|
|
@@ -1920,7 +1947,7 @@ function generateZsh(root) {
|
|
|
1920
1947
|
lines.push(" local -a subcommands");
|
|
1921
1948
|
lines.push(" subcommands=(");
|
|
1922
1949
|
for (const sub of group.subcommands) {
|
|
1923
|
-
lines.push(` '${sub.name}:${sub.description.replace(/'/g, "")}'`);
|
|
1950
|
+
lines.push(` '${sub.name}:${sub.description.replace(/\\/g, "\\\\").replace(/'/g, "")}'`);
|
|
1924
1951
|
}
|
|
1925
1952
|
lines.push(" )");
|
|
1926
1953
|
lines.push(" _describe 'subcommand' subcommands");
|
|
@@ -1941,14 +1968,14 @@ function generateFish(root) {
|
|
|
1941
1968
|
];
|
|
1942
1969
|
for (const cmd of root.subcommands) {
|
|
1943
1970
|
lines.push(
|
|
1944
|
-
`complete -c elnora -n "__fish_use_subcommand" -a "${cmd.name}" -d "${cmd.description.replace(/"/g, '\\"')}"`
|
|
1971
|
+
`complete -c elnora -n "__fish_use_subcommand" -a "${cmd.name}" -d "${cmd.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
1945
1972
|
);
|
|
1946
1973
|
}
|
|
1947
1974
|
lines.push("");
|
|
1948
1975
|
for (const group of root.subcommands) {
|
|
1949
1976
|
for (const sub of group.subcommands) {
|
|
1950
1977
|
lines.push(
|
|
1951
|
-
`complete -c elnora -n "__fish_seen_subcommand_from ${group.name}" -a "${sub.name}" -d "${sub.description.replace(/"/g, '\\"')}"`
|
|
1978
|
+
`complete -c elnora -n "__fish_seen_subcommand_from ${group.name}" -a "${sub.name}" -d "${sub.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
1952
1979
|
);
|
|
1953
1980
|
}
|
|
1954
1981
|
}
|
|
@@ -1992,10 +2019,10 @@ var CACHE_DIR = join2(homedir2(), ".elnora");
|
|
|
1992
2019
|
var CACHE_FILE = join2(CACHE_DIR, ".update-check");
|
|
1993
2020
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1994
2021
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
1995
|
-
|
|
2022
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/;
|
|
2023
|
+
function shouldSkipEntirely() {
|
|
1996
2024
|
if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
|
|
1997
2025
|
if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
|
|
1998
|
-
if (!process.stderr.isTTY) return true;
|
|
1999
2026
|
return false;
|
|
2000
2027
|
}
|
|
2001
2028
|
function readCache() {
|
|
@@ -2015,13 +2042,14 @@ function writeCache(entry) {
|
|
|
2015
2042
|
}
|
|
2016
2043
|
}
|
|
2017
2044
|
function showUpdateNotice(latest) {
|
|
2018
|
-
|
|
2019
|
-
const
|
|
2045
|
+
if (!process.stderr.isTTY) return;
|
|
2046
|
+
const color2 = isColorEnabled();
|
|
2047
|
+
const msg = color2 ? `
|
|
2020
2048
|
${pc.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
|
|
2021
|
-
Run:
|
|
2049
|
+
Run: elnora update
|
|
2022
2050
|
` : `
|
|
2023
2051
|
Update available: v${VERSION} \u2192 v${latest}
|
|
2024
|
-
Run:
|
|
2052
|
+
Run: elnora update
|
|
2025
2053
|
`;
|
|
2026
2054
|
process.stderr.write(msg);
|
|
2027
2055
|
}
|
|
@@ -2037,7 +2065,7 @@ function isNewerVersion(a, b) {
|
|
|
2037
2065
|
return false;
|
|
2038
2066
|
}
|
|
2039
2067
|
function registerUpdateCheck() {
|
|
2040
|
-
if (
|
|
2068
|
+
if (shouldSkipEntirely()) return;
|
|
2041
2069
|
const cached = readCache();
|
|
2042
2070
|
if (cached) {
|
|
2043
2071
|
const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
|
|
@@ -2050,7 +2078,9 @@ function registerUpdateCheck() {
|
|
|
2050
2078
|
return;
|
|
2051
2079
|
}
|
|
2052
2080
|
}
|
|
2053
|
-
|
|
2081
|
+
const keepAlive = setInterval(() => {
|
|
2082
|
+
}, 6e4);
|
|
2083
|
+
(async () => {
|
|
2054
2084
|
try {
|
|
2055
2085
|
const response = await fetch(NPM_REGISTRY_URL, {
|
|
2056
2086
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
@@ -2058,22 +2088,25 @@ function registerUpdateCheck() {
|
|
|
2058
2088
|
if (!response.ok) return;
|
|
2059
2089
|
const data = await response.json();
|
|
2060
2090
|
const latest = data.version ?? "unknown";
|
|
2091
|
+
if (latest !== "unknown" && !SEMVER_RE.test(latest)) return;
|
|
2061
2092
|
writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest });
|
|
2062
2093
|
if (latest !== VERSION && latest !== "unknown" && isNewerVersion(latest, VERSION)) {
|
|
2063
2094
|
showUpdateNotice(latest);
|
|
2064
2095
|
}
|
|
2065
2096
|
} catch {
|
|
2097
|
+
} finally {
|
|
2098
|
+
clearInterval(keepAlive);
|
|
2066
2099
|
}
|
|
2067
|
-
}
|
|
2100
|
+
})();
|
|
2068
2101
|
}
|
|
2069
2102
|
|
|
2070
2103
|
// src/commands/doctor.ts
|
|
2071
2104
|
function addDoctorCommand(program2) {
|
|
2072
2105
|
program2.command("doctor").description("Check Elnora CLI configuration and connectivity").action(async () => {
|
|
2073
|
-
const
|
|
2074
|
-
const
|
|
2075
|
-
const
|
|
2076
|
-
const warn = (msg) =>
|
|
2106
|
+
const color2 = isColorEnabled();
|
|
2107
|
+
const ok2 = (msg) => color2 ? ` ${pc2.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
2108
|
+
const fail2 = (msg) => color2 ? ` ${pc2.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
2109
|
+
const warn = (msg) => color2 ? ` ${pc2.yellow("!")} ${msg}` : ` ! ${msg}`;
|
|
2077
2110
|
console.error(`
|
|
2078
2111
|
Elnora CLI v${VERSION}
|
|
2079
2112
|
`);
|
|
@@ -2085,12 +2118,12 @@ Elnora CLI v${VERSION}
|
|
|
2085
2118
|
const res = await fetch("https://platform.elnora.ai/health", { signal: AbortSignal.timeout(5e3) });
|
|
2086
2119
|
const ms = Date.now() - start;
|
|
2087
2120
|
if (res.ok) {
|
|
2088
|
-
console.error(
|
|
2121
|
+
console.error(ok2(`API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)`));
|
|
2089
2122
|
passed++;
|
|
2090
|
-
} else console.error(
|
|
2123
|
+
} else console.error(fail2(`API unreachable platform.elnora.ai (HTTP ${res.status})`));
|
|
2091
2124
|
} catch (e) {
|
|
2092
2125
|
console.error(
|
|
2093
|
-
|
|
2126
|
+
fail2(`API unreachable platform.elnora.ai (${e instanceof Error ? e.message : "network error"})`)
|
|
2094
2127
|
);
|
|
2095
2128
|
}
|
|
2096
2129
|
total++;
|
|
@@ -2101,10 +2134,10 @@ Elnora CLI v${VERSION}
|
|
|
2101
2134
|
queryParams: { page: 1, pageSize: 1 }
|
|
2102
2135
|
});
|
|
2103
2136
|
const count = result?.totalCount ?? result?.items?.length ?? 0;
|
|
2104
|
-
console.error(
|
|
2137
|
+
console.error(ok2(`Authenticated profile: default (${count} projects accessible)`));
|
|
2105
2138
|
passed++;
|
|
2106
2139
|
} catch (e) {
|
|
2107
|
-
console.error(
|
|
2140
|
+
console.error(fail2(`Authentication failed ${e instanceof Error ? e.message : "no API key"}`));
|
|
2108
2141
|
}
|
|
2109
2142
|
total++;
|
|
2110
2143
|
try {
|
|
@@ -2115,29 +2148,29 @@ Elnora CLI v${VERSION}
|
|
|
2115
2148
|
const data = await res.json();
|
|
2116
2149
|
const latest = data.version ?? "unknown";
|
|
2117
2150
|
if (latest === VERSION || latest === "unknown" || !isNewerVersion(latest, VERSION)) {
|
|
2118
|
-
console.error(
|
|
2151
|
+
console.error(ok2(`Version current v${VERSION} (latest: v${latest})`));
|
|
2119
2152
|
} else {
|
|
2120
2153
|
console.error(warn(`Update available v${VERSION} \u2192 v${latest}`));
|
|
2121
2154
|
}
|
|
2122
2155
|
passed++;
|
|
2123
2156
|
} else {
|
|
2124
|
-
console.error(
|
|
2157
|
+
console.error(ok2(`Version v${VERSION} (registry check failed)`));
|
|
2125
2158
|
passed++;
|
|
2126
2159
|
}
|
|
2127
2160
|
} catch {
|
|
2128
|
-
console.error(
|
|
2161
|
+
console.error(ok2(`Version v${VERSION} (registry unreachable)`));
|
|
2129
2162
|
passed++;
|
|
2130
2163
|
}
|
|
2131
2164
|
total++;
|
|
2132
2165
|
if (process.platform === "win32") {
|
|
2133
|
-
console.error(
|
|
2166
|
+
console.error(ok2("Config permissions (skipped on Windows)"));
|
|
2134
2167
|
passed++;
|
|
2135
2168
|
} else if (!existsSync3(PROFILES_FILE)) {
|
|
2136
2169
|
console.error(warn("Config permissions ~/.elnora/profiles.toml not found"));
|
|
2137
2170
|
} else {
|
|
2138
2171
|
const mode = statSync(PROFILES_FILE).mode & 511;
|
|
2139
2172
|
if (mode === 384) {
|
|
2140
|
-
console.error(
|
|
2173
|
+
console.error(ok2(`Config permissions ${PROFILES_FILE} (0600)`));
|
|
2141
2174
|
passed++;
|
|
2142
2175
|
} else {
|
|
2143
2176
|
console.error(warn(`Config permissions ${PROFILES_FILE} (0${mode.toString(8)}) \u2014 should be 0600`));
|
|
@@ -2149,11 +2182,11 @@ Elnora CLI v${VERSION}
|
|
|
2149
2182
|
const res = await fetch(`${AI_SERVER_URL}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
2150
2183
|
const ms = Date.now() - start;
|
|
2151
2184
|
if (res.ok) {
|
|
2152
|
-
console.error(
|
|
2185
|
+
console.error(ok2(`AI Server reachable ${new URL(AI_SERVER_URL).hostname} (${res.status} OK, ${ms}ms)`));
|
|
2153
2186
|
passed++;
|
|
2154
|
-
} else console.error(
|
|
2187
|
+
} else console.error(fail2(`AI Server unreachable ${new URL(AI_SERVER_URL).hostname} (HTTP ${res.status})`));
|
|
2155
2188
|
} catch {
|
|
2156
|
-
console.error(
|
|
2189
|
+
console.error(fail2(`AI Server unreachable ${AI_SERVER_URL}`));
|
|
2157
2190
|
}
|
|
2158
2191
|
console.error("");
|
|
2159
2192
|
if (passed === total) {
|
|
@@ -4094,42 +4127,31 @@ function registerSearchCommands() {
|
|
|
4094
4127
|
return [searchTasks, searchFiles, searchAll, searchFileContent];
|
|
4095
4128
|
}
|
|
4096
4129
|
|
|
4097
|
-
// src/commands/setup
|
|
4098
|
-
|
|
4099
|
-
|
|
4130
|
+
// src/commands/setup.ts
|
|
4131
|
+
init_profiles();
|
|
4132
|
+
|
|
4133
|
+
// src/lib/setup/claude.ts
|
|
4134
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
4135
|
+
|
|
4136
|
+
// src/lib/setup/common.ts
|
|
4137
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4100
4138
|
import { homedir as homedir3 } from "node:os";
|
|
4101
|
-
import {
|
|
4102
|
-
import { fileURLToPath } from "node:url";
|
|
4139
|
+
import { join as join3 } from "node:path";
|
|
4103
4140
|
import pc3 from "picocolors";
|
|
4104
|
-
var
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
var
|
|
4111
|
-
var
|
|
4112
|
-
var
|
|
4113
|
-
var
|
|
4114
|
-
var
|
|
4115
|
-
var MARKETPLACE_NAME = "elnora-local";
|
|
4141
|
+
var HOME = homedir3();
|
|
4142
|
+
var ELNORA_DIR = join3(HOME, ".elnora");
|
|
4143
|
+
var CLAUDE_DIR = join3(HOME, ".claude");
|
|
4144
|
+
var CLAUDE_MARKETPLACES_FILE = join3(CLAUDE_DIR, "plugins", "known_marketplaces.json");
|
|
4145
|
+
var CLAUDE_SETTINGS_FILE = join3(CLAUDE_DIR, "settings.json");
|
|
4146
|
+
var CURSOR_DIR = join3(HOME, ".cursor");
|
|
4147
|
+
var CURSOR_MCP_GLOBAL = join3(HOME, ".cursor", "mcp.json");
|
|
4148
|
+
var VSCODE_DIR = join3(HOME, ".vscode");
|
|
4149
|
+
var CODEX_DIR = join3(HOME, ".codex");
|
|
4150
|
+
var MARKETPLACE_NAME = "elnora-ai";
|
|
4151
|
+
var MARKETPLACE_REPO = "Elnora-AI/elnora-plugins";
|
|
4116
4152
|
var PLUGIN_ID = `elnora@${MARKETPLACE_NAME}`;
|
|
4117
|
-
|
|
4118
|
-
|
|
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
|
-
}
|
|
4153
|
+
var LEGACY_PLUGIN_IDS = ["elnora@elnora-local"];
|
|
4154
|
+
var LEGACY_MARKETPLACE_NAMES = ["elnora-local"];
|
|
4133
4155
|
function readJsonFile(path) {
|
|
4134
4156
|
try {
|
|
4135
4157
|
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
@@ -4142,63 +4164,225 @@ function writeJsonFile(path, data) {
|
|
|
4142
4164
|
writeFileSync3(path, `${JSON.stringify(data, null, 2)}
|
|
4143
4165
|
`, "utf-8");
|
|
4144
4166
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4167
|
+
var color = isColorEnabled();
|
|
4168
|
+
function ok(msg) {
|
|
4169
|
+
return color ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
4170
|
+
}
|
|
4171
|
+
function fail(msg) {
|
|
4172
|
+
return color ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
4173
|
+
}
|
|
4174
|
+
function info(msg) {
|
|
4175
|
+
return color ? ` ${pc3.dim(msg)}` : ` ${msg}`;
|
|
4176
|
+
}
|
|
4177
|
+
function detectPlatforms() {
|
|
4178
|
+
return [
|
|
4179
|
+
{ name: "claude", label: "Claude Code", installed: existsSync4(CLAUDE_DIR) },
|
|
4180
|
+
{ name: "cursor", label: "Cursor", installed: existsSync4(CURSOR_DIR) },
|
|
4181
|
+
{ name: "vscode", label: "VS Code", installed: existsSync4(VSCODE_DIR) },
|
|
4182
|
+
{ name: "codex", label: "OpenAI Codex", installed: existsSync4(CODEX_DIR) }
|
|
4183
|
+
];
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
// src/lib/setup/claude.ts
|
|
4187
|
+
function setupClaude() {
|
|
4188
|
+
if (!existsSync5(CLAUDE_DIR)) {
|
|
4189
|
+
console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
|
|
4150
4190
|
console.error("");
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4191
|
+
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
4192
|
+
return false;
|
|
4193
|
+
}
|
|
4194
|
+
console.error(ok("Claude Code found"));
|
|
4195
|
+
const marketplaces = readJsonFile(CLAUDE_MARKETPLACES_FILE);
|
|
4196
|
+
for (const old of LEGACY_MARKETPLACE_NAMES) {
|
|
4197
|
+
delete marketplaces[old];
|
|
4198
|
+
}
|
|
4199
|
+
marketplaces[MARKETPLACE_NAME] = {
|
|
4200
|
+
source: {
|
|
4201
|
+
source: "github",
|
|
4202
|
+
repo: MARKETPLACE_REPO
|
|
4156
4203
|
}
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4204
|
+
};
|
|
4205
|
+
writeJsonFile(CLAUDE_MARKETPLACES_FILE, marketplaces);
|
|
4206
|
+
console.error(ok(`Marketplace registered (GitHub: ${MARKETPLACE_REPO})`));
|
|
4207
|
+
const settings = readJsonFile(CLAUDE_SETTINGS_FILE);
|
|
4208
|
+
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
|
|
4209
|
+
settings.enabledPlugins = {};
|
|
4210
|
+
}
|
|
4211
|
+
const plugins = settings.enabledPlugins;
|
|
4212
|
+
for (const old of LEGACY_PLUGIN_IDS) {
|
|
4213
|
+
delete plugins[old];
|
|
4214
|
+
}
|
|
4215
|
+
plugins[PLUGIN_ID] = true;
|
|
4216
|
+
writeJsonFile(CLAUDE_SETTINGS_FILE, settings);
|
|
4217
|
+
console.error(ok(`Plugin enabled (${PLUGIN_ID})`));
|
|
4218
|
+
console.error("");
|
|
4219
|
+
console.error(" Elnora skills are now available in Claude Code.");
|
|
4220
|
+
console.error(" Restart Claude Code if it's currently running.");
|
|
4221
|
+
return true;
|
|
4222
|
+
}
|
|
4223
|
+
|
|
4224
|
+
// src/lib/setup/codex.ts
|
|
4225
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
4226
|
+
import { join as join4 } from "node:path";
|
|
4227
|
+
var CODEX_MCP_FILE = join4(CODEX_DIR, "mcp.json");
|
|
4228
|
+
function setupCodex(apiKey) {
|
|
4229
|
+
if (!existsSync6(CODEX_DIR)) {
|
|
4230
|
+
console.error(fail("Codex CLI not found (~/.codex/ does not exist)"));
|
|
4231
|
+
console.error("");
|
|
4232
|
+
console.error(" Install Codex first: https://github.com/openai/codex");
|
|
4233
|
+
return false;
|
|
4234
|
+
}
|
|
4235
|
+
console.error(ok("Codex CLI found"));
|
|
4236
|
+
const config = readJsonFile(CODEX_MCP_FILE);
|
|
4237
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
4238
|
+
config.mcpServers = {};
|
|
4239
|
+
}
|
|
4240
|
+
config.mcpServers.elnora = {
|
|
4241
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
4242
|
+
headers: {
|
|
4243
|
+
"X-API-Key": apiKey
|
|
4165
4244
|
}
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
console.error(
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
|
|
4193
|
-
settings.enabledPlugins = {};
|
|
4245
|
+
};
|
|
4246
|
+
writeJsonFile(CODEX_MCP_FILE, config);
|
|
4247
|
+
console.error(ok("MCP server configured in ~/.codex/mcp.json"));
|
|
4248
|
+
console.error("");
|
|
4249
|
+
console.error(" Elnora MCP server is now available in Codex.");
|
|
4250
|
+
return true;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4253
|
+
// src/lib/setup/cursor.ts
|
|
4254
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
4255
|
+
function setupCursor(apiKey) {
|
|
4256
|
+
if (!existsSync7(CURSOR_DIR)) {
|
|
4257
|
+
console.error(fail("Cursor not found (~/.cursor/ does not exist)"));
|
|
4258
|
+
console.error("");
|
|
4259
|
+
console.error(" Install Cursor first: https://cursor.com");
|
|
4260
|
+
return false;
|
|
4261
|
+
}
|
|
4262
|
+
console.error(ok("Cursor found"));
|
|
4263
|
+
const config = readJsonFile(CURSOR_MCP_GLOBAL);
|
|
4264
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
4265
|
+
config.mcpServers = {};
|
|
4266
|
+
}
|
|
4267
|
+
config.mcpServers.elnora = {
|
|
4268
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
4269
|
+
headers: {
|
|
4270
|
+
"X-API-Key": apiKey
|
|
4194
4271
|
}
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4272
|
+
};
|
|
4273
|
+
writeJsonFile(CURSOR_MCP_GLOBAL, config);
|
|
4274
|
+
console.error(ok("MCP server configured in ~/.cursor/mcp.json"));
|
|
4275
|
+
console.error("");
|
|
4276
|
+
console.error(" Elnora MCP server is now available in Cursor.");
|
|
4277
|
+
console.error(" Restart Cursor if it's currently running.");
|
|
4278
|
+
return true;
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
// src/lib/setup/vscode.ts
|
|
4282
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
4283
|
+
import { join as join5 } from "node:path";
|
|
4284
|
+
var VSCODE_MCP_GLOBAL = join5(HOME, ".vscode", "mcp.json");
|
|
4285
|
+
function setupVscode(apiKey) {
|
|
4286
|
+
if (!existsSync8(VSCODE_DIR)) {
|
|
4287
|
+
console.error(fail("VS Code not found (~/.vscode/ does not exist)"));
|
|
4198
4288
|
console.error("");
|
|
4199
|
-
console.error("
|
|
4200
|
-
|
|
4289
|
+
console.error(" Install VS Code first: https://code.visualstudio.com");
|
|
4290
|
+
return false;
|
|
4291
|
+
}
|
|
4292
|
+
console.error(ok("VS Code found"));
|
|
4293
|
+
const config = readJsonFile(VSCODE_MCP_GLOBAL);
|
|
4294
|
+
if (!config.servers || typeof config.servers !== "object") {
|
|
4295
|
+
config.servers = {};
|
|
4296
|
+
}
|
|
4297
|
+
config.servers.elnora = {
|
|
4298
|
+
type: "http",
|
|
4299
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
4300
|
+
headers: {
|
|
4301
|
+
"X-API-Key": apiKey
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
writeJsonFile(VSCODE_MCP_GLOBAL, config);
|
|
4305
|
+
console.error(ok("MCP server configured in ~/.vscode/mcp.json"));
|
|
4306
|
+
console.error("");
|
|
4307
|
+
console.error(" Elnora MCP server is now available in VS Code Copilot.");
|
|
4308
|
+
console.error(" Restart VS Code if it's currently running.");
|
|
4309
|
+
return true;
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
// src/commands/setup.ts
|
|
4313
|
+
var PLATFORM_SETUP = {
|
|
4314
|
+
claude: () => setupClaude(),
|
|
4315
|
+
cursor: (key) => setupCursor(key),
|
|
4316
|
+
vscode: (key) => setupVscode(key),
|
|
4317
|
+
codex: (key) => setupCodex(key)
|
|
4318
|
+
};
|
|
4319
|
+
function runSetup(platforms, profileName) {
|
|
4320
|
+
let apiKey;
|
|
4321
|
+
const needsKey = platforms.some((p) => p !== "claude");
|
|
4322
|
+
if (needsKey) {
|
|
4323
|
+
try {
|
|
4324
|
+
apiKey = resolveApiKey(profileName);
|
|
4325
|
+
} catch {
|
|
4326
|
+
if (platforms.every((p) => p !== "claude")) {
|
|
4327
|
+
console.error(fail("No API key found. Run 'elnora auth login' first."));
|
|
4328
|
+
process.exitCode = 1;
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
let anyFailed = false;
|
|
4334
|
+
for (const platform of platforms) {
|
|
4335
|
+
const setupFn = PLATFORM_SETUP[platform];
|
|
4336
|
+
const success = setupFn(apiKey ?? "");
|
|
4337
|
+
if (!success) anyFailed = true;
|
|
4338
|
+
console.error("");
|
|
4339
|
+
}
|
|
4340
|
+
if (anyFailed) {
|
|
4341
|
+
process.exitCode = 1;
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
function addSetupCommand(program2) {
|
|
4345
|
+
const setup = program2.command("setup").description("Configure AI coding tools to use Elnora").option("--profile <name>", "API key profile to use", "default");
|
|
4346
|
+
setup.command("claude").description("Set up Claude Code (skills + MCP server)").action(() => {
|
|
4347
|
+
const opts = setup.opts();
|
|
4201
4348
|
console.error("");
|
|
4349
|
+
runSetup(["claude"], opts.profile);
|
|
4350
|
+
});
|
|
4351
|
+
setup.command("cursor").description("Set up Cursor (MCP server)").action(() => {
|
|
4352
|
+
const opts = setup.opts();
|
|
4353
|
+
console.error("");
|
|
4354
|
+
runSetup(["cursor"], opts.profile);
|
|
4355
|
+
});
|
|
4356
|
+
setup.command("vscode").description("Set up VS Code Copilot (MCP server)").action(() => {
|
|
4357
|
+
const opts = setup.opts();
|
|
4358
|
+
console.error("");
|
|
4359
|
+
runSetup(["vscode"], opts.profile);
|
|
4360
|
+
});
|
|
4361
|
+
setup.command("codex").description("Set up OpenAI Codex (MCP server)").action(() => {
|
|
4362
|
+
const opts = setup.opts();
|
|
4363
|
+
console.error("");
|
|
4364
|
+
runSetup(["codex"], opts.profile);
|
|
4365
|
+
});
|
|
4366
|
+
setup.action(() => {
|
|
4367
|
+
const opts = setup.opts();
|
|
4368
|
+
console.error("");
|
|
4369
|
+
const detected = detectPlatforms();
|
|
4370
|
+
const installed = detected.filter((p) => p.installed);
|
|
4371
|
+
if (installed.length === 0) {
|
|
4372
|
+
console.error(fail("No supported AI coding tools detected."));
|
|
4373
|
+
console.error("");
|
|
4374
|
+
console.error(" Supported: Claude Code, Cursor, VS Code, OpenAI Codex");
|
|
4375
|
+
console.error(" Install one first, then run 'elnora setup' again.");
|
|
4376
|
+
process.exitCode = 1;
|
|
4377
|
+
return;
|
|
4378
|
+
}
|
|
4379
|
+
console.error(ok(`Detected ${installed.length} platform(s):`));
|
|
4380
|
+
for (const p of installed) {
|
|
4381
|
+
console.error(info(`${p.label}`));
|
|
4382
|
+
}
|
|
4383
|
+
console.error("");
|
|
4384
|
+
const platforms = installed.map((p) => p.name);
|
|
4385
|
+
runSetup(platforms, opts.profile);
|
|
4202
4386
|
});
|
|
4203
4387
|
}
|
|
4204
4388
|
|
|
@@ -4689,10 +4873,34 @@ import { execFileSync } from "node:child_process";
|
|
|
4689
4873
|
import { createHash } from "node:crypto";
|
|
4690
4874
|
import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
|
|
4691
4875
|
import { tmpdir } from "node:os";
|
|
4692
|
-
import { join as
|
|
4876
|
+
import { join as join6 } from "node:path";
|
|
4693
4877
|
import { Readable } from "node:stream";
|
|
4694
4878
|
import { pipeline } from "node:stream/promises";
|
|
4695
4879
|
import pc5 from "picocolors";
|
|
4880
|
+
|
|
4881
|
+
// src/lib/install-method.ts
|
|
4882
|
+
function detectInstallMethod() {
|
|
4883
|
+
const execPath = process.argv[1] ?? "";
|
|
4884
|
+
if (execPath.includes("node_modules")) {
|
|
4885
|
+
return "npm";
|
|
4886
|
+
}
|
|
4887
|
+
if (execPath.includes("/Cellar/") || execPath.includes("/homebrew/")) {
|
|
4888
|
+
return "homebrew";
|
|
4889
|
+
}
|
|
4890
|
+
return "binary";
|
|
4891
|
+
}
|
|
4892
|
+
function getUpdateInstruction(method) {
|
|
4893
|
+
switch (method) {
|
|
4894
|
+
case "npm":
|
|
4895
|
+
return "npm update -g @elnora-ai/cli";
|
|
4896
|
+
case "homebrew":
|
|
4897
|
+
return "brew upgrade elnora";
|
|
4898
|
+
case "binary":
|
|
4899
|
+
return "elnora update --install";
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4902
|
+
|
|
4903
|
+
// src/commands/update.ts
|
|
4696
4904
|
var NPM_REGISTRY_URL2 = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
4697
4905
|
var GITHUB_REPO = "Elnora-AI/elnora-cli";
|
|
4698
4906
|
function getPlatformTarget() {
|
|
@@ -4708,8 +4916,11 @@ function getPlatformTarget() {
|
|
|
4708
4916
|
return null;
|
|
4709
4917
|
}
|
|
4710
4918
|
}
|
|
4711
|
-
function
|
|
4712
|
-
|
|
4919
|
+
async function fetchLatestVersion() {
|
|
4920
|
+
const res = await fetch(NPM_REGISTRY_URL2, { signal: AbortSignal.timeout(5e3) });
|
|
4921
|
+
if (!res.ok) throw new Error(`Registry returned HTTP ${res.status}`);
|
|
4922
|
+
const data = await res.json();
|
|
4923
|
+
return data.version ?? "unknown";
|
|
4713
4924
|
}
|
|
4714
4925
|
async function downloadToFile(url, dest) {
|
|
4715
4926
|
const res = await fetch(url, { signal: AbortSignal.timeout(12e4), redirect: "follow" });
|
|
@@ -4717,98 +4928,121 @@ async function downloadToFile(url, dest) {
|
|
|
4717
4928
|
const nodeStream = Readable.fromWeb(res.body);
|
|
4718
4929
|
await pipeline(nodeStream, createWriteStream(dest));
|
|
4719
4930
|
}
|
|
4931
|
+
async function installBinaryUpdate(latest) {
|
|
4932
|
+
const color2 = isColorEnabled();
|
|
4933
|
+
const platformInfo = getPlatformTarget();
|
|
4934
|
+
if (!platformInfo) {
|
|
4935
|
+
console.error(`Unsupported platform: ${process.platform}.`);
|
|
4936
|
+
console.error("Try: npm update -g @elnora-ai/cli");
|
|
4937
|
+
process.exitCode = 1;
|
|
4938
|
+
return;
|
|
4939
|
+
}
|
|
4940
|
+
const { target, ext } = platformInfo;
|
|
4941
|
+
const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
|
|
4942
|
+
const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
|
|
4943
|
+
const tmp = join6(tmpdir(), `elnora-update-${Date.now()}`);
|
|
4944
|
+
mkdirSync4(tmp, { recursive: true });
|
|
4945
|
+
const archivePath = join6(tmp, `${target}.${ext}`);
|
|
4946
|
+
try {
|
|
4947
|
+
console.error("Downloading...");
|
|
4948
|
+
await downloadToFile(binaryUrl, archivePath);
|
|
4949
|
+
let checksumVerified = false;
|
|
4950
|
+
try {
|
|
4951
|
+
const checksumRes = await fetch(checksumUrl, { signal: AbortSignal.timeout(15e3) });
|
|
4952
|
+
if (checksumRes.ok) {
|
|
4953
|
+
const checksumText = await checksumRes.text();
|
|
4954
|
+
const expected = checksumText.split(/\s/)[0].trim();
|
|
4955
|
+
if (expected.length === 64) {
|
|
4956
|
+
const actual = createHash("sha256").update(readFileSync6(archivePath)).digest("hex");
|
|
4957
|
+
if (actual.toLowerCase() !== expected.toLowerCase()) {
|
|
4958
|
+
console.error("Checksum verification failed. The download may be corrupted.");
|
|
4959
|
+
process.exitCode = 1;
|
|
4960
|
+
return;
|
|
4961
|
+
}
|
|
4962
|
+
checksumVerified = true;
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
} catch {
|
|
4966
|
+
}
|
|
4967
|
+
if (!checksumVerified) {
|
|
4968
|
+
console.error("Warning: Could not verify download checksum. Proceeding anyway.");
|
|
4969
|
+
}
|
|
4970
|
+
if (process.platform === "win32") {
|
|
4971
|
+
const extractDir = join6(tmp, "extracted");
|
|
4972
|
+
execFileSync("powershell", [
|
|
4973
|
+
"-NoProfile",
|
|
4974
|
+
"-Command",
|
|
4975
|
+
`Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
|
|
4976
|
+
]);
|
|
4977
|
+
const installDir = join6(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
4978
|
+
mkdirSync4(installDir, { recursive: true });
|
|
4979
|
+
copyFileSync(join6(extractDir, target), join6(installDir, "elnora.exe"));
|
|
4980
|
+
} else {
|
|
4981
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
|
|
4982
|
+
const installDir = process.env.ELNORA_INSTALL_DIR ?? join6(process.env.HOME ?? "", ".local", "bin");
|
|
4983
|
+
mkdirSync4(installDir, { recursive: true });
|
|
4984
|
+
const dest = join6(installDir, "elnora");
|
|
4985
|
+
copyFileSync(join6(tmp, target), dest);
|
|
4986
|
+
chmodSync2(dest, 493);
|
|
4987
|
+
}
|
|
4988
|
+
const done = color2 ? `${pc5.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
|
|
4989
|
+
console.error(done);
|
|
4990
|
+
} finally {
|
|
4991
|
+
try {
|
|
4992
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
4993
|
+
} catch {
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4720
4997
|
function addUpdateCommand(program2) {
|
|
4721
|
-
program2.command("update").description("
|
|
4722
|
-
const
|
|
4723
|
-
const
|
|
4998
|
+
program2.command("update").description("Check for updates (use --install to apply)").option("--install", "Download and install the update").action(async (opts) => {
|
|
4999
|
+
const color2 = isColorEnabled();
|
|
5000
|
+
const method = detectInstallMethod();
|
|
4724
5001
|
let latest;
|
|
4725
5002
|
try {
|
|
4726
|
-
|
|
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";
|
|
5003
|
+
latest = await fetchLatestVersion();
|
|
4734
5004
|
} catch {
|
|
4735
|
-
console.error("Failed to
|
|
4736
|
-
|
|
4737
|
-
|
|
5005
|
+
console.error("Failed to check for updates.");
|
|
5006
|
+
process.exitCode = 1;
|
|
5007
|
+
return;
|
|
5008
|
+
}
|
|
5009
|
+
if (latest === "unknown") {
|
|
5010
|
+
console.error("Could not determine latest version.");
|
|
5011
|
+
process.exitCode = 1;
|
|
5012
|
+
return;
|
|
4738
5013
|
}
|
|
4739
5014
|
if (latest === VERSION || !isNewerVersion(latest, VERSION)) {
|
|
4740
|
-
const msg2 =
|
|
5015
|
+
const msg2 = color2 ? `${pc5.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
|
|
4741
5016
|
console.error(msg2);
|
|
4742
5017
|
return;
|
|
4743
5018
|
}
|
|
4744
|
-
const
|
|
5019
|
+
const instruction = getUpdateInstruction(method);
|
|
5020
|
+
if (!opts.install) {
|
|
5021
|
+
const msg2 = color2 ? `${pc5.yellow("Update available:")} v${VERSION} \u2192 v${latest}` : `Update available: v${VERSION} \u2192 v${latest}`;
|
|
5022
|
+
console.error(msg2);
|
|
5023
|
+
console.error(`Run: ${instruction}`);
|
|
5024
|
+
return;
|
|
5025
|
+
}
|
|
5026
|
+
const msg = color2 ? `Updating Elnora CLI ${pc5.dim(`v${VERSION}`)} \u2192 ${pc5.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
|
|
4745
5027
|
console.error(msg);
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
console.error(`
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
5028
|
+
if (method === "npm") {
|
|
5029
|
+
console.error("Installed via npm. Run:");
|
|
5030
|
+
console.error(` npm update -g @elnora-ai/cli`);
|
|
5031
|
+
return;
|
|
5032
|
+
}
|
|
5033
|
+
if (method === "homebrew") {
|
|
5034
|
+
console.error("Installed via Homebrew. Run:");
|
|
5035
|
+
console.error(` brew upgrade elnora`);
|
|
5036
|
+
return;
|
|
4752
5037
|
}
|
|
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
5038
|
try {
|
|
4760
|
-
await
|
|
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);
|
|
5039
|
+
await installBinaryUpdate(latest);
|
|
4803
5040
|
} catch {
|
|
4804
|
-
console.error("
|
|
4805
|
-
console.error(
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
4810
|
-
} catch {
|
|
4811
|
-
}
|
|
5041
|
+
console.error("Update failed. Try manually:");
|
|
5042
|
+
console.error(
|
|
5043
|
+
process.platform === "win32" ? " irm https://cli.elnora.ai/install.ps1 | iex" : " curl -fsSL https://cli.elnora.ai/install.sh | bash"
|
|
5044
|
+
);
|
|
5045
|
+
process.exitCode = 1;
|
|
4812
5046
|
}
|
|
4813
5047
|
});
|
|
4814
5048
|
}
|
|
@@ -4821,22 +5055,21 @@ function addWhoamiCommand(program2) {
|
|
|
4821
5055
|
const parentOpts = program2.opts();
|
|
4822
5056
|
const profileName = parentOpts.profile ?? "default";
|
|
4823
5057
|
try {
|
|
4824
|
-
const
|
|
4825
|
-
const masked = key.length > 20 ? `${key.slice(0, 16)}...${key.slice(-4)}` : `${key.slice(0, 4)}...`;
|
|
5058
|
+
const hint = resolveCredentialHint(profileName);
|
|
4826
5059
|
let orgName = "unknown";
|
|
4827
5060
|
try {
|
|
4828
|
-
const client = new ElnoraApiClient(
|
|
5061
|
+
const client = new ElnoraApiClient(resolveApiKey(profileName));
|
|
4829
5062
|
const orgs = await client.get("organizations");
|
|
4830
5063
|
const orgList = Array.isArray(orgs) ? orgs : orgs?.items ?? [];
|
|
4831
5064
|
if (orgList.length > 0) orgName = orgList[0]?.name ?? "unknown";
|
|
4832
5065
|
} catch {
|
|
4833
5066
|
}
|
|
4834
5067
|
if (parentOpts.json) {
|
|
4835
|
-
console.log(JSON.stringify({ profile: profileName,
|
|
5068
|
+
console.log(JSON.stringify({ profile: profileName, credential: hint, orgName }, null, 2));
|
|
4836
5069
|
} else {
|
|
4837
5070
|
console.log(`
|
|
4838
|
-
Profile:
|
|
4839
|
-
console.log(`
|
|
5071
|
+
Profile: ${profileName}`);
|
|
5072
|
+
console.log(`Credential: ${hint}`);
|
|
4840
5073
|
console.log(`Org: ${orgName}`);
|
|
4841
5074
|
console.log("");
|
|
4842
5075
|
}
|
|
@@ -4925,7 +5158,7 @@ addMcpCommands(program, registry);
|
|
|
4925
5158
|
addDoctorCommand(program);
|
|
4926
5159
|
addWhoamiCommand(program);
|
|
4927
5160
|
addOpenCommand(program);
|
|
4928
|
-
|
|
5161
|
+
addSetupCommand(program);
|
|
4929
5162
|
addUpdateCommand(program);
|
|
4930
5163
|
addCompletionCommand(program);
|
|
4931
5164
|
registerUpdateCheck();
|