@elnora-ai/cli 1.2.0 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +307 -128
  2. 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
- var CONFIG_DIR, PROFILES_FILE, LEGACY_CONFIG_FILE, PROFILE_NAME_RE;
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 color = isColorEnabled();
2019
- const msg = color ? `
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 color = isColorEnabled();
2074
- const ok = (msg) => color ? ` ${pc2.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
2075
- const fail = (msg) => color ? ` ${pc2.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
2076
- const warn = (msg) => color ? ` ${pc2.yellow("!")} ${msg}` : ` ! ${msg}`;
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(ok(`API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)`));
2117
+ console.error(ok2(`API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)`));
2089
2118
  passed++;
2090
- } else console.error(fail(`API unreachable platform.elnora.ai (HTTP ${res.status})`));
2119
+ } else console.error(fail2(`API unreachable platform.elnora.ai (HTTP ${res.status})`));
2091
2120
  } catch (e) {
2092
2121
  console.error(
2093
- fail(`API unreachable platform.elnora.ai (${e instanceof Error ? e.message : "network error"})`)
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(ok(`Authenticated profile: default (${count} projects accessible)`));
2133
+ console.error(ok2(`Authenticated profile: default (${count} projects accessible)`));
2105
2134
  passed++;
2106
2135
  } catch (e) {
2107
- console.error(fail(`Authentication failed ${e instanceof Error ? e.message : "no API key"}`));
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(ok(`Version current v${VERSION} (latest: v${latest})`));
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(ok(`Version v${VERSION} (registry check failed)`));
2153
+ console.error(ok2(`Version v${VERSION} (registry check failed)`));
2125
2154
  passed++;
2126
2155
  }
2127
2156
  } catch {
2128
- console.error(ok(`Version v${VERSION} (registry unreachable)`));
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(ok("Config permissions (skipped on Windows)"));
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(ok(`Config permissions ${PROFILES_FILE} (0600)`));
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(ok(`AI Server reachable ${new URL(AI_SERVER_URL).hostname} (${res.status} OK, ${ms}ms)`));
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(fail(`AI Server unreachable ${new URL(AI_SERVER_URL).hostname} (HTTP ${res.status})`));
2183
+ } else console.error(fail2(`AI Server unreachable ${new URL(AI_SERVER_URL).hostname} (HTTP ${res.status})`));
2155
2184
  } catch {
2156
- console.error(fail(`AI Server unreachable ${AI_SERVER_URL}`));
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-claude.ts
4098
- init_config();
4099
- import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
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 { dirname, join as join3 } from "node:path";
4102
- import { fileURLToPath } from "node:url";
4135
+ import { join as join3 } from "node:path";
4103
4136
  import pc3 from "picocolors";
4104
- var __script_dir;
4105
- try {
4106
- __script_dir = dirname(fileURLToPath(import.meta.url));
4107
- } catch {
4108
- __script_dir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
4109
- }
4110
- var ELNORA_DIR = join3(homedir3(), ".elnora");
4111
- var PLUGIN_DIR = join3(ELNORA_DIR, "plugin", "elnora");
4112
- var CLAUDE_DIR = join3(homedir3(), ".claude");
4113
- var MARKETPLACES_FILE = join3(CLAUDE_DIR, "plugins", "known_marketplaces.json");
4114
- var SETTINGS_FILE = join3(CLAUDE_DIR, "settings.json");
4115
- var MARKETPLACE_NAME = "elnora-local";
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
- function findSkillsDir() {
4118
- const candidates = [
4119
- join3(__script_dir, "../../skills"),
4120
- join3(__script_dir, "../skills"),
4121
- // When running from source via tsx
4122
- join3(__script_dir, "../../../skills"),
4123
- // Standalone binary: skills extracted to ~/.elnora/skills/
4124
- join3(ELNORA_DIR, "skills")
4125
- ];
4126
- for (const dir of candidates) {
4127
- if (existsSync4(join3(dir, "elnora-platform", "SKILL.md"))) {
4128
- return dir;
4129
- }
4130
- }
4131
- return null;
4132
- }
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
- function addSetupClaudeCommand(program2) {
4146
- program2.command("setup-claude").description("Install Elnora skills for Claude Code").action(() => {
4147
- const color = isColorEnabled();
4148
- const ok = (msg) => color ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
4149
- const fail = (msg) => color ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
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
- if (!existsSync4(CLAUDE_DIR)) {
4152
- console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
4153
- console.error("");
4154
- console.error(" Install Claude Code first: https://claude.ai/code");
4155
- process.exit(1);
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
- console.error(ok("Claude Code found"));
4158
- const skillsDir = findSkillsDir();
4159
- if (!skillsDir) {
4160
- console.error(fail("Skills not found"));
4161
- console.error("");
4162
- console.error(" Skills may not be bundled with your install method.");
4163
- console.error(" Try: npm install -g @elnora-ai/cli");
4164
- process.exit(1);
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
- console.error(ok(`Skills found at ${skillsDir}`));
4167
- mkdirSync3(PLUGIN_DIR, { recursive: true });
4168
- writeJsonFile(join3(PLUGIN_DIR, ".claude-plugin", "plugin.json"), {
4169
- name: "elnora",
4170
- version: VERSION,
4171
- description: "AI-powered bioprotocol generation and lab workflow management. Connect to the Elnora AI Platform to generate, optimize, and manage bioprotocols.",
4172
- author: { name: "Elnora AI", url: "https://elnora.ai" },
4173
- repository: "https://github.com/Elnora-AI/elnora-cli",
4174
- homepage: "https://elnora.ai",
4175
- license: "Apache-2.0",
4176
- keywords: ["bioprotocol", "lab", "pharma", "life-sciences"]
4177
- });
4178
- cpSync(skillsDir, join3(PLUGIN_DIR, "skills"), { recursive: true });
4179
- console.error(ok("Skills installed to ~/.elnora/plugin/"));
4180
- const marketplaces = readJsonFile(MARKETPLACES_FILE);
4181
- marketplaces[MARKETPLACE_NAME] = {
4182
- source: {
4183
- source: "directory",
4184
- path: join3(ELNORA_DIR, "plugin")
4185
- },
4186
- installLocation: join3(ELNORA_DIR, "plugin"),
4187
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
4188
- };
4189
- writeJsonFile(MARKETPLACES_FILE, marketplaces);
4190
- console.error(ok("Marketplace registered in Claude Code"));
4191
- const settings = readJsonFile(SETTINGS_FILE);
4192
- if (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object") {
4193
- settings.enabledPlugins = {};
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
- settings.enabledPlugins[PLUGIN_ID] = true;
4196
- writeJsonFile(SETTINGS_FILE, settings);
4197
- console.error(ok("Plugin enabled"));
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(" Elnora skills are now available in Claude Code.");
4200
- console.error(" Restart Claude Code if it's currently running.");
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 join4 } from "node:path";
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 color = isColorEnabled();
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 = color ? `${pc5.green("\u2713")} Already on the latest version (v${VERSION})` : `\u2713 Already on the latest version (v${VERSION})`;
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 = color ? `Updating Elnora CLI ${pc5.dim(`v${VERSION}`)} \u2192 ${pc5.green(`v${latest}`)}...` : `Updating Elnora CLI v${VERSION} \u2192 v${latest}...`;
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 = join4(tmpdir(), `elnora-update-${Date.now()}`);
4936
+ const tmp = join6(tmpdir(), `elnora-update-${Date.now()}`);
4757
4937
  mkdirSync4(tmp, { recursive: true });
4758
- const archivePath = join4(tmp, `${target}.${ext}`);
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 = join4(tmp, "extracted");
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 = join4(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
4970
+ const installDir = join6(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
4791
4971
  mkdirSync4(installDir, { recursive: true });
4792
- copyFileSync(join4(extractDir, target), join4(installDir, "elnora.exe"));
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 ?? join4(process.env.HOME ?? "", ".local", "bin");
4975
+ const installDir = process.env.ELNORA_INSTALL_DIR ?? join6(process.env.HOME ?? "", ".local", "bin");
4796
4976
  mkdirSync4(installDir, { recursive: true });
4797
- const dest = join4(installDir, "elnora");
4798
- copyFileSync(join4(tmp, target), dest);
4977
+ const dest = join6(installDir, "elnora");
4978
+ copyFileSync(join6(tmp, target), dest);
4799
4979
  chmodSync2(dest, 493);
4800
4980
  }
4801
- const done = color ? `${pc5.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
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 key = resolveApiKey(profileName);
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(key);
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, apiKey: masked, orgName }, null, 2));
5014
+ console.log(JSON.stringify({ profile: profileName, credential: hint, orgName }, null, 2));
4836
5015
  } else {
4837
5016
  console.log(`
4838
- Profile: ${profileName}`);
4839
- console.log(`API Key: ${masked}`);
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
- addSetupClaudeCommand(program);
5107
+ addSetupCommand(program);
4929
5108
  addUpdateCommand(program);
4930
5109
  addCompletionCommand(program);
4931
5110
  registerUpdateCheck();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elnora-ai/cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Elnora AI Platform CLI",
5
5
  "type": "module",
6
6
  "bin": {