@elnora-ai/cli 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +307 -128
- 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,6 +2019,7 @@ 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;
|
|
2022
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/;
|
|
1995
2023
|
function shouldSkip() {
|
|
1996
2024
|
if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
|
|
1997
2025
|
if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
|
|
@@ -2015,8 +2043,8 @@ function writeCache(entry) {
|
|
|
2015
2043
|
}
|
|
2016
2044
|
}
|
|
2017
2045
|
function showUpdateNotice(latest) {
|
|
2018
|
-
const
|
|
2019
|
-
const msg =
|
|
2046
|
+
const color2 = isColorEnabled();
|
|
2047
|
+
const msg = color2 ? `
|
|
2020
2048
|
${pc.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
|
|
2021
2049
|
Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
|
|
2022
2050
|
` : `
|
|
@@ -2058,6 +2086,7 @@ function registerUpdateCheck() {
|
|
|
2058
2086
|
if (!response.ok) return;
|
|
2059
2087
|
const data = await response.json();
|
|
2060
2088
|
const latest = data.version ?? "unknown";
|
|
2089
|
+
if (latest !== "unknown" && !SEMVER_RE.test(latest)) return;
|
|
2061
2090
|
writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest });
|
|
2062
2091
|
if (latest !== VERSION && latest !== "unknown" && isNewerVersion(latest, VERSION)) {
|
|
2063
2092
|
showUpdateNotice(latest);
|
|
@@ -2070,10 +2099,10 @@ function registerUpdateCheck() {
|
|
|
2070
2099
|
// src/commands/doctor.ts
|
|
2071
2100
|
function addDoctorCommand(program2) {
|
|
2072
2101
|
program2.command("doctor").description("Check Elnora CLI configuration and connectivity").action(async () => {
|
|
2073
|
-
const
|
|
2074
|
-
const
|
|
2075
|
-
const
|
|
2076
|
-
const warn = (msg) =>
|
|
2102
|
+
const color2 = isColorEnabled();
|
|
2103
|
+
const ok2 = (msg) => color2 ? ` ${pc2.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
2104
|
+
const fail2 = (msg) => color2 ? ` ${pc2.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
2105
|
+
const warn = (msg) => color2 ? ` ${pc2.yellow("!")} ${msg}` : ` ! ${msg}`;
|
|
2077
2106
|
console.error(`
|
|
2078
2107
|
Elnora CLI v${VERSION}
|
|
2079
2108
|
`);
|
|
@@ -2085,12 +2114,12 @@ Elnora CLI v${VERSION}
|
|
|
2085
2114
|
const res = await fetch("https://platform.elnora.ai/health", { signal: AbortSignal.timeout(5e3) });
|
|
2086
2115
|
const ms = Date.now() - start;
|
|
2087
2116
|
if (res.ok) {
|
|
2088
|
-
console.error(
|
|
2117
|
+
console.error(ok2(`API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)`));
|
|
2089
2118
|
passed++;
|
|
2090
|
-
} else console.error(
|
|
2119
|
+
} else console.error(fail2(`API unreachable platform.elnora.ai (HTTP ${res.status})`));
|
|
2091
2120
|
} catch (e) {
|
|
2092
2121
|
console.error(
|
|
2093
|
-
|
|
2122
|
+
fail2(`API unreachable platform.elnora.ai (${e instanceof Error ? e.message : "network error"})`)
|
|
2094
2123
|
);
|
|
2095
2124
|
}
|
|
2096
2125
|
total++;
|
|
@@ -2101,10 +2130,10 @@ Elnora CLI v${VERSION}
|
|
|
2101
2130
|
queryParams: { page: 1, pageSize: 1 }
|
|
2102
2131
|
});
|
|
2103
2132
|
const count = result?.totalCount ?? result?.items?.length ?? 0;
|
|
2104
|
-
console.error(
|
|
2133
|
+
console.error(ok2(`Authenticated profile: default (${count} projects accessible)`));
|
|
2105
2134
|
passed++;
|
|
2106
2135
|
} catch (e) {
|
|
2107
|
-
console.error(
|
|
2136
|
+
console.error(fail2(`Authentication failed ${e instanceof Error ? e.message : "no API key"}`));
|
|
2108
2137
|
}
|
|
2109
2138
|
total++;
|
|
2110
2139
|
try {
|
|
@@ -2115,29 +2144,29 @@ Elnora CLI v${VERSION}
|
|
|
2115
2144
|
const data = await res.json();
|
|
2116
2145
|
const latest = data.version ?? "unknown";
|
|
2117
2146
|
if (latest === VERSION || latest === "unknown" || !isNewerVersion(latest, VERSION)) {
|
|
2118
|
-
console.error(
|
|
2147
|
+
console.error(ok2(`Version current v${VERSION} (latest: v${latest})`));
|
|
2119
2148
|
} else {
|
|
2120
2149
|
console.error(warn(`Update available v${VERSION} \u2192 v${latest}`));
|
|
2121
2150
|
}
|
|
2122
2151
|
passed++;
|
|
2123
2152
|
} else {
|
|
2124
|
-
console.error(
|
|
2153
|
+
console.error(ok2(`Version v${VERSION} (registry check failed)`));
|
|
2125
2154
|
passed++;
|
|
2126
2155
|
}
|
|
2127
2156
|
} catch {
|
|
2128
|
-
console.error(
|
|
2157
|
+
console.error(ok2(`Version v${VERSION} (registry unreachable)`));
|
|
2129
2158
|
passed++;
|
|
2130
2159
|
}
|
|
2131
2160
|
total++;
|
|
2132
2161
|
if (process.platform === "win32") {
|
|
2133
|
-
console.error(
|
|
2162
|
+
console.error(ok2("Config permissions (skipped on Windows)"));
|
|
2134
2163
|
passed++;
|
|
2135
2164
|
} else if (!existsSync3(PROFILES_FILE)) {
|
|
2136
2165
|
console.error(warn("Config permissions ~/.elnora/profiles.toml not found"));
|
|
2137
2166
|
} else {
|
|
2138
2167
|
const mode = statSync(PROFILES_FILE).mode & 511;
|
|
2139
2168
|
if (mode === 384) {
|
|
2140
|
-
console.error(
|
|
2169
|
+
console.error(ok2(`Config permissions ${PROFILES_FILE} (0600)`));
|
|
2141
2170
|
passed++;
|
|
2142
2171
|
} else {
|
|
2143
2172
|
console.error(warn(`Config permissions ${PROFILES_FILE} (0${mode.toString(8)}) \u2014 should be 0600`));
|
|
@@ -2149,11 +2178,11 @@ Elnora CLI v${VERSION}
|
|
|
2149
2178
|
const res = await fetch(`${AI_SERVER_URL}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
2150
2179
|
const ms = Date.now() - start;
|
|
2151
2180
|
if (res.ok) {
|
|
2152
|
-
console.error(
|
|
2181
|
+
console.error(ok2(`AI Server reachable ${new URL(AI_SERVER_URL).hostname} (${res.status} OK, ${ms}ms)`));
|
|
2153
2182
|
passed++;
|
|
2154
|
-
} else console.error(
|
|
2183
|
+
} else console.error(fail2(`AI Server unreachable ${new URL(AI_SERVER_URL).hostname} (HTTP ${res.status})`));
|
|
2155
2184
|
} catch {
|
|
2156
|
-
console.error(
|
|
2185
|
+
console.error(fail2(`AI Server unreachable ${AI_SERVER_URL}`));
|
|
2157
2186
|
}
|
|
2158
2187
|
console.error("");
|
|
2159
2188
|
if (passed === total) {
|
|
@@ -4094,42 +4123,31 @@ function registerSearchCommands() {
|
|
|
4094
4123
|
return [searchTasks, searchFiles, searchAll, searchFileContent];
|
|
4095
4124
|
}
|
|
4096
4125
|
|
|
4097
|
-
// src/commands/setup
|
|
4098
|
-
|
|
4099
|
-
|
|
4126
|
+
// src/commands/setup.ts
|
|
4127
|
+
init_profiles();
|
|
4128
|
+
|
|
4129
|
+
// src/lib/setup/claude.ts
|
|
4130
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
4131
|
+
|
|
4132
|
+
// src/lib/setup/common.ts
|
|
4133
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4100
4134
|
import { homedir as homedir3 } from "node:os";
|
|
4101
|
-
import {
|
|
4102
|
-
import { fileURLToPath } from "node:url";
|
|
4135
|
+
import { join as join3 } from "node:path";
|
|
4103
4136
|
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";
|
|
4137
|
+
var HOME = homedir3();
|
|
4138
|
+
var ELNORA_DIR = join3(HOME, ".elnora");
|
|
4139
|
+
var CLAUDE_DIR = join3(HOME, ".claude");
|
|
4140
|
+
var CLAUDE_MARKETPLACES_FILE = join3(CLAUDE_DIR, "plugins", "known_marketplaces.json");
|
|
4141
|
+
var CLAUDE_SETTINGS_FILE = join3(CLAUDE_DIR, "settings.json");
|
|
4142
|
+
var CURSOR_DIR = join3(HOME, ".cursor");
|
|
4143
|
+
var CURSOR_MCP_GLOBAL = join3(HOME, ".cursor", "mcp.json");
|
|
4144
|
+
var VSCODE_DIR = join3(HOME, ".vscode");
|
|
4145
|
+
var CODEX_DIR = join3(HOME, ".codex");
|
|
4146
|
+
var MARKETPLACE_NAME = "elnora-ai";
|
|
4147
|
+
var MARKETPLACE_REPO = "Elnora-AI/elnora-plugins";
|
|
4116
4148
|
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
|
-
}
|
|
4149
|
+
var LEGACY_PLUGIN_IDS = ["elnora@elnora-local"];
|
|
4150
|
+
var LEGACY_MARKETPLACE_NAMES = ["elnora-local"];
|
|
4133
4151
|
function readJsonFile(path) {
|
|
4134
4152
|
try {
|
|
4135
4153
|
return JSON.parse(readFileSync5(path, "utf-8"));
|
|
@@ -4142,63 +4160,225 @@ function writeJsonFile(path, data) {
|
|
|
4142
4160
|
writeFileSync3(path, `${JSON.stringify(data, null, 2)}
|
|
4143
4161
|
`, "utf-8");
|
|
4144
4162
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4163
|
+
var color = isColorEnabled();
|
|
4164
|
+
function ok(msg) {
|
|
4165
|
+
return color ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
4166
|
+
}
|
|
4167
|
+
function fail(msg) {
|
|
4168
|
+
return color ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
4169
|
+
}
|
|
4170
|
+
function info(msg) {
|
|
4171
|
+
return color ? ` ${pc3.dim(msg)}` : ` ${msg}`;
|
|
4172
|
+
}
|
|
4173
|
+
function detectPlatforms() {
|
|
4174
|
+
return [
|
|
4175
|
+
{ name: "claude", label: "Claude Code", installed: existsSync4(CLAUDE_DIR) },
|
|
4176
|
+
{ name: "cursor", label: "Cursor", installed: existsSync4(CURSOR_DIR) },
|
|
4177
|
+
{ name: "vscode", label: "VS Code", installed: existsSync4(VSCODE_DIR) },
|
|
4178
|
+
{ name: "codex", label: "OpenAI Codex", installed: existsSync4(CODEX_DIR) }
|
|
4179
|
+
];
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
// src/lib/setup/claude.ts
|
|
4183
|
+
function setupClaude() {
|
|
4184
|
+
if (!existsSync5(CLAUDE_DIR)) {
|
|
4185
|
+
console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
|
|
4150
4186
|
console.error("");
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4187
|
+
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
4188
|
+
return false;
|
|
4189
|
+
}
|
|
4190
|
+
console.error(ok("Claude Code found"));
|
|
4191
|
+
const marketplaces = readJsonFile(CLAUDE_MARKETPLACES_FILE);
|
|
4192
|
+
for (const old of LEGACY_MARKETPLACE_NAMES) {
|
|
4193
|
+
delete marketplaces[old];
|
|
4194
|
+
}
|
|
4195
|
+
marketplaces[MARKETPLACE_NAME] = {
|
|
4196
|
+
source: {
|
|
4197
|
+
source: "github",
|
|
4198
|
+
repo: MARKETPLACE_REPO
|
|
4156
4199
|
}
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4200
|
+
};
|
|
4201
|
+
writeJsonFile(CLAUDE_MARKETPLACES_FILE, marketplaces);
|
|
4202
|
+
console.error(ok(`Marketplace registered (GitHub: ${MARKETPLACE_REPO})`));
|
|
4203
|
+
const settings = readJsonFile(CLAUDE_SETTINGS_FILE);
|
|
4204
|
+
if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
|
|
4205
|
+
settings.enabledPlugins = {};
|
|
4206
|
+
}
|
|
4207
|
+
const plugins = settings.enabledPlugins;
|
|
4208
|
+
for (const old of LEGACY_PLUGIN_IDS) {
|
|
4209
|
+
delete plugins[old];
|
|
4210
|
+
}
|
|
4211
|
+
plugins[PLUGIN_ID] = true;
|
|
4212
|
+
writeJsonFile(CLAUDE_SETTINGS_FILE, settings);
|
|
4213
|
+
console.error(ok(`Plugin enabled (${PLUGIN_ID})`));
|
|
4214
|
+
console.error("");
|
|
4215
|
+
console.error(" Elnora skills are now available in Claude Code.");
|
|
4216
|
+
console.error(" Restart Claude Code if it's currently running.");
|
|
4217
|
+
return true;
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4220
|
+
// src/lib/setup/codex.ts
|
|
4221
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
4222
|
+
import { join as join4 } from "node:path";
|
|
4223
|
+
var CODEX_MCP_FILE = join4(CODEX_DIR, "mcp.json");
|
|
4224
|
+
function setupCodex(apiKey) {
|
|
4225
|
+
if (!existsSync6(CODEX_DIR)) {
|
|
4226
|
+
console.error(fail("Codex CLI not found (~/.codex/ does not exist)"));
|
|
4227
|
+
console.error("");
|
|
4228
|
+
console.error(" Install Codex first: https://github.com/openai/codex");
|
|
4229
|
+
return false;
|
|
4230
|
+
}
|
|
4231
|
+
console.error(ok("Codex CLI found"));
|
|
4232
|
+
const config = readJsonFile(CODEX_MCP_FILE);
|
|
4233
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
4234
|
+
config.mcpServers = {};
|
|
4235
|
+
}
|
|
4236
|
+
config.mcpServers.elnora = {
|
|
4237
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
4238
|
+
headers: {
|
|
4239
|
+
"X-API-Key": apiKey
|
|
4165
4240
|
}
|
|
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 = {};
|
|
4241
|
+
};
|
|
4242
|
+
writeJsonFile(CODEX_MCP_FILE, config);
|
|
4243
|
+
console.error(ok("MCP server configured in ~/.codex/mcp.json"));
|
|
4244
|
+
console.error("");
|
|
4245
|
+
console.error(" Elnora MCP server is now available in Codex.");
|
|
4246
|
+
return true;
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
// src/lib/setup/cursor.ts
|
|
4250
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
4251
|
+
function setupCursor(apiKey) {
|
|
4252
|
+
if (!existsSync7(CURSOR_DIR)) {
|
|
4253
|
+
console.error(fail("Cursor not found (~/.cursor/ does not exist)"));
|
|
4254
|
+
console.error("");
|
|
4255
|
+
console.error(" Install Cursor first: https://cursor.com");
|
|
4256
|
+
return false;
|
|
4257
|
+
}
|
|
4258
|
+
console.error(ok("Cursor found"));
|
|
4259
|
+
const config = readJsonFile(CURSOR_MCP_GLOBAL);
|
|
4260
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
4261
|
+
config.mcpServers = {};
|
|
4262
|
+
}
|
|
4263
|
+
config.mcpServers.elnora = {
|
|
4264
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
4265
|
+
headers: {
|
|
4266
|
+
"X-API-Key": apiKey
|
|
4194
4267
|
}
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4268
|
+
};
|
|
4269
|
+
writeJsonFile(CURSOR_MCP_GLOBAL, config);
|
|
4270
|
+
console.error(ok("MCP server configured in ~/.cursor/mcp.json"));
|
|
4271
|
+
console.error("");
|
|
4272
|
+
console.error(" Elnora MCP server is now available in Cursor.");
|
|
4273
|
+
console.error(" Restart Cursor if it's currently running.");
|
|
4274
|
+
return true;
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
// src/lib/setup/vscode.ts
|
|
4278
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
4279
|
+
import { join as join5 } from "node:path";
|
|
4280
|
+
var VSCODE_MCP_GLOBAL = join5(HOME, ".vscode", "mcp.json");
|
|
4281
|
+
function setupVscode(apiKey) {
|
|
4282
|
+
if (!existsSync8(VSCODE_DIR)) {
|
|
4283
|
+
console.error(fail("VS Code not found (~/.vscode/ does not exist)"));
|
|
4198
4284
|
console.error("");
|
|
4199
|
-
console.error("
|
|
4200
|
-
|
|
4285
|
+
console.error(" Install VS Code first: https://code.visualstudio.com");
|
|
4286
|
+
return false;
|
|
4287
|
+
}
|
|
4288
|
+
console.error(ok("VS Code found"));
|
|
4289
|
+
const config = readJsonFile(VSCODE_MCP_GLOBAL);
|
|
4290
|
+
if (!config.servers || typeof config.servers !== "object") {
|
|
4291
|
+
config.servers = {};
|
|
4292
|
+
}
|
|
4293
|
+
config.servers.elnora = {
|
|
4294
|
+
type: "http",
|
|
4295
|
+
url: "https://mcp.elnora.ai/mcp",
|
|
4296
|
+
headers: {
|
|
4297
|
+
"X-API-Key": apiKey
|
|
4298
|
+
}
|
|
4299
|
+
};
|
|
4300
|
+
writeJsonFile(VSCODE_MCP_GLOBAL, config);
|
|
4301
|
+
console.error(ok("MCP server configured in ~/.vscode/mcp.json"));
|
|
4302
|
+
console.error("");
|
|
4303
|
+
console.error(" Elnora MCP server is now available in VS Code Copilot.");
|
|
4304
|
+
console.error(" Restart VS Code if it's currently running.");
|
|
4305
|
+
return true;
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
// src/commands/setup.ts
|
|
4309
|
+
var PLATFORM_SETUP = {
|
|
4310
|
+
claude: () => setupClaude(),
|
|
4311
|
+
cursor: (key) => setupCursor(key),
|
|
4312
|
+
vscode: (key) => setupVscode(key),
|
|
4313
|
+
codex: (key) => setupCodex(key)
|
|
4314
|
+
};
|
|
4315
|
+
function runSetup(platforms, profileName) {
|
|
4316
|
+
let apiKey;
|
|
4317
|
+
const needsKey = platforms.some((p) => p !== "claude");
|
|
4318
|
+
if (needsKey) {
|
|
4319
|
+
try {
|
|
4320
|
+
apiKey = resolveApiKey(profileName);
|
|
4321
|
+
} catch {
|
|
4322
|
+
if (platforms.every((p) => p !== "claude")) {
|
|
4323
|
+
console.error(fail("No API key found. Run 'elnora auth login' first."));
|
|
4324
|
+
process.exitCode = 1;
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
let anyFailed = false;
|
|
4330
|
+
for (const platform of platforms) {
|
|
4331
|
+
const setupFn = PLATFORM_SETUP[platform];
|
|
4332
|
+
const success = setupFn(apiKey ?? "");
|
|
4333
|
+
if (!success) anyFailed = true;
|
|
4334
|
+
console.error("");
|
|
4335
|
+
}
|
|
4336
|
+
if (anyFailed) {
|
|
4337
|
+
process.exitCode = 1;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
function addSetupCommand(program2) {
|
|
4341
|
+
const setup = program2.command("setup").description("Configure AI coding tools to use Elnora").option("--profile <name>", "API key profile to use", "default");
|
|
4342
|
+
setup.command("claude").description("Set up Claude Code (skills + MCP server)").action(() => {
|
|
4343
|
+
const opts = setup.opts();
|
|
4201
4344
|
console.error("");
|
|
4345
|
+
runSetup(["claude"], opts.profile);
|
|
4346
|
+
});
|
|
4347
|
+
setup.command("cursor").description("Set up Cursor (MCP server)").action(() => {
|
|
4348
|
+
const opts = setup.opts();
|
|
4349
|
+
console.error("");
|
|
4350
|
+
runSetup(["cursor"], opts.profile);
|
|
4351
|
+
});
|
|
4352
|
+
setup.command("vscode").description("Set up VS Code Copilot (MCP server)").action(() => {
|
|
4353
|
+
const opts = setup.opts();
|
|
4354
|
+
console.error("");
|
|
4355
|
+
runSetup(["vscode"], opts.profile);
|
|
4356
|
+
});
|
|
4357
|
+
setup.command("codex").description("Set up OpenAI Codex (MCP server)").action(() => {
|
|
4358
|
+
const opts = setup.opts();
|
|
4359
|
+
console.error("");
|
|
4360
|
+
runSetup(["codex"], opts.profile);
|
|
4361
|
+
});
|
|
4362
|
+
setup.action(() => {
|
|
4363
|
+
const opts = setup.opts();
|
|
4364
|
+
console.error("");
|
|
4365
|
+
const detected = detectPlatforms();
|
|
4366
|
+
const installed = detected.filter((p) => p.installed);
|
|
4367
|
+
if (installed.length === 0) {
|
|
4368
|
+
console.error(fail("No supported AI coding tools detected."));
|
|
4369
|
+
console.error("");
|
|
4370
|
+
console.error(" Supported: Claude Code, Cursor, VS Code, OpenAI Codex");
|
|
4371
|
+
console.error(" Install one first, then run 'elnora setup' again.");
|
|
4372
|
+
process.exitCode = 1;
|
|
4373
|
+
return;
|
|
4374
|
+
}
|
|
4375
|
+
console.error(ok(`Detected ${installed.length} platform(s):`));
|
|
4376
|
+
for (const p of installed) {
|
|
4377
|
+
console.error(info(`${p.label}`));
|
|
4378
|
+
}
|
|
4379
|
+
console.error("");
|
|
4380
|
+
const platforms = installed.map((p) => p.name);
|
|
4381
|
+
runSetup(platforms, opts.profile);
|
|
4202
4382
|
});
|
|
4203
4383
|
}
|
|
4204
4384
|
|
|
@@ -4689,7 +4869,7 @@ import { execFileSync } from "node:child_process";
|
|
|
4689
4869
|
import { createHash } from "node:crypto";
|
|
4690
4870
|
import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
|
|
4691
4871
|
import { tmpdir } from "node:os";
|
|
4692
|
-
import { join as
|
|
4872
|
+
import { join as join6 } from "node:path";
|
|
4693
4873
|
import { Readable } from "node:stream";
|
|
4694
4874
|
import { pipeline } from "node:stream/promises";
|
|
4695
4875
|
import pc5 from "picocolors";
|
|
@@ -4719,7 +4899,7 @@ async function downloadToFile(url, dest) {
|
|
|
4719
4899
|
}
|
|
4720
4900
|
function addUpdateCommand(program2) {
|
|
4721
4901
|
program2.command("update").description("Update Elnora CLI to the latest version").action(async () => {
|
|
4722
|
-
const
|
|
4902
|
+
const color2 = isColorEnabled();
|
|
4723
4903
|
const hint = getManualHint();
|
|
4724
4904
|
let latest;
|
|
4725
4905
|
try {
|
|
@@ -4737,11 +4917,11 @@ function addUpdateCommand(program2) {
|
|
|
4737
4917
|
process.exit(1);
|
|
4738
4918
|
}
|
|
4739
4919
|
if (latest === VERSION || !isNewerVersion(latest, VERSION)) {
|
|
4740
|
-
const msg2 =
|
|
4920
|
+
const msg2 = color2 ? `${pc5.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
|
|
4741
4921
|
console.error(msg2);
|
|
4742
4922
|
return;
|
|
4743
4923
|
}
|
|
4744
|
-
const msg =
|
|
4924
|
+
const msg = color2 ? `Updating Elnora CLI ${pc5.dim(`v${VERSION}`)} \u2192 ${pc5.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
|
|
4745
4925
|
console.error(msg);
|
|
4746
4926
|
const platformInfo = getPlatformTarget();
|
|
4747
4927
|
if (!platformInfo) {
|
|
@@ -4753,9 +4933,9 @@ Unsupported platform: ${process.platform}. Try manually:`);
|
|
|
4753
4933
|
const { target, ext } = platformInfo;
|
|
4754
4934
|
const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
|
|
4755
4935
|
const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
|
|
4756
|
-
const tmp =
|
|
4936
|
+
const tmp = join6(tmpdir(), `elnora-update-${Date.now()}`);
|
|
4757
4937
|
mkdirSync4(tmp, { recursive: true });
|
|
4758
|
-
const archivePath =
|
|
4938
|
+
const archivePath = join6(tmp, `${target}.${ext}`);
|
|
4759
4939
|
try {
|
|
4760
4940
|
await downloadToFile(binaryUrl, archivePath);
|
|
4761
4941
|
let checksumVerified = false;
|
|
@@ -4781,24 +4961,24 @@ Unsupported platform: ${process.platform}. Try manually:`);
|
|
|
4781
4961
|
console.error("Warning: Could not verify download checksum. Proceeding anyway.");
|
|
4782
4962
|
}
|
|
4783
4963
|
if (process.platform === "win32") {
|
|
4784
|
-
const extractDir =
|
|
4964
|
+
const extractDir = join6(tmp, "extracted");
|
|
4785
4965
|
execFileSync("powershell", [
|
|
4786
4966
|
"-NoProfile",
|
|
4787
4967
|
"-Command",
|
|
4788
4968
|
`Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
|
|
4789
4969
|
]);
|
|
4790
|
-
const installDir =
|
|
4970
|
+
const installDir = join6(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
4791
4971
|
mkdirSync4(installDir, { recursive: true });
|
|
4792
|
-
copyFileSync(
|
|
4972
|
+
copyFileSync(join6(extractDir, target), join6(installDir, "elnora.exe"));
|
|
4793
4973
|
} else {
|
|
4794
4974
|
execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
|
|
4795
|
-
const installDir = process.env.ELNORA_INSTALL_DIR ??
|
|
4975
|
+
const installDir = process.env.ELNORA_INSTALL_DIR ?? join6(process.env.HOME ?? "", ".local", "bin");
|
|
4796
4976
|
mkdirSync4(installDir, { recursive: true });
|
|
4797
|
-
const dest =
|
|
4798
|
-
copyFileSync(
|
|
4977
|
+
const dest = join6(installDir, "elnora");
|
|
4978
|
+
copyFileSync(join6(tmp, target), dest);
|
|
4799
4979
|
chmodSync2(dest, 493);
|
|
4800
4980
|
}
|
|
4801
|
-
const done =
|
|
4981
|
+
const done = color2 ? `${pc5.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
|
|
4802
4982
|
console.error(done);
|
|
4803
4983
|
} catch {
|
|
4804
4984
|
console.error("\nUpdate failed. Try manually:");
|
|
@@ -4821,22 +5001,21 @@ function addWhoamiCommand(program2) {
|
|
|
4821
5001
|
const parentOpts = program2.opts();
|
|
4822
5002
|
const profileName = parentOpts.profile ?? "default";
|
|
4823
5003
|
try {
|
|
4824
|
-
const
|
|
4825
|
-
const masked = key.length > 20 ? `${key.slice(0, 16)}...${key.slice(-4)}` : `${key.slice(0, 4)}...`;
|
|
5004
|
+
const hint = resolveCredentialHint(profileName);
|
|
4826
5005
|
let orgName = "unknown";
|
|
4827
5006
|
try {
|
|
4828
|
-
const client = new ElnoraApiClient(
|
|
5007
|
+
const client = new ElnoraApiClient(resolveApiKey(profileName));
|
|
4829
5008
|
const orgs = await client.get("organizations");
|
|
4830
5009
|
const orgList = Array.isArray(orgs) ? orgs : orgs?.items ?? [];
|
|
4831
5010
|
if (orgList.length > 0) orgName = orgList[0]?.name ?? "unknown";
|
|
4832
5011
|
} catch {
|
|
4833
5012
|
}
|
|
4834
5013
|
if (parentOpts.json) {
|
|
4835
|
-
console.log(JSON.stringify({ profile: profileName,
|
|
5014
|
+
console.log(JSON.stringify({ profile: profileName, credential: hint, orgName }, null, 2));
|
|
4836
5015
|
} else {
|
|
4837
5016
|
console.log(`
|
|
4838
|
-
Profile:
|
|
4839
|
-
console.log(`
|
|
5017
|
+
Profile: ${profileName}`);
|
|
5018
|
+
console.log(`Credential: ${hint}`);
|
|
4840
5019
|
console.log(`Org: ${orgName}`);
|
|
4841
5020
|
console.log("");
|
|
4842
5021
|
}
|
|
@@ -4925,7 +5104,7 @@ addMcpCommands(program, registry);
|
|
|
4925
5104
|
addDoctorCommand(program);
|
|
4926
5105
|
addWhoamiCommand(program);
|
|
4927
5106
|
addOpenCommand(program);
|
|
4928
|
-
|
|
5107
|
+
addSetupCommand(program);
|
|
4929
5108
|
addUpdateCommand(program);
|
|
4930
5109
|
addCompletionCommand(program);
|
|
4931
5110
|
registerUpdateCheck();
|