@elnora-ai/cli 1.3.5 → 1.4.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 +337 -161
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ var VERSION, PRODUCTION_BASE_URL, BASE_URL, AI_SERVER_URL, ENDPOINTS, DEFAULT_HE
25
25
  var init_config = __esm({
26
26
  "src/lib/config.ts"() {
27
27
  "use strict";
28
- VERSION = true ? "1.3.5" : process.env.npm_package_version ?? "0.0.0-dev";
28
+ VERSION = true ? "1.4.0" : process.env.npm_package_version ?? "0.0.0-dev";
29
29
  PRODUCTION_BASE_URL = "https://platform.elnora.ai/api/v1";
30
30
  BASE_URL = process.env.ELNORA_API_URL ?? PRODUCTION_BASE_URL;
31
31
  AI_SERVER_URL = process.env.ELNORA_AI_SERVER_URL ?? "https://platform.elnora.ai/ai-server";
@@ -2005,18 +2005,70 @@ function addCompletionCommand(program2) {
2005
2005
  init_client();
2006
2006
  init_config();
2007
2007
  init_profiles();
2008
- import { existsSync as existsSync3, statSync } from "node:fs";
2009
- import pc2 from "picocolors";
2008
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "node:fs";
2009
+ import { homedir as homedir4 } from "node:os";
2010
+ import { delimiter, join as join4 } from "node:path";
2011
+ import pc3 from "picocolors";
2010
2012
 
2011
- // src/lib/update-check.ts
2012
- init_config();
2013
+ // src/lib/setup/common.ts
2013
2014
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
2014
2015
  import { homedir as homedir2 } from "node:os";
2015
2016
  import { join as join2 } from "node:path";
2016
2017
  import pc from "picocolors";
2018
+ var HOME = homedir2();
2019
+ var ELNORA_DIR = join2(HOME, ".elnora");
2020
+ var CLAUDE_DIR = join2(HOME, ".claude");
2021
+ var CLAUDE_MARKETPLACES_FILE = join2(CLAUDE_DIR, "plugins", "known_marketplaces.json");
2022
+ var CLAUDE_SETTINGS_FILE = join2(CLAUDE_DIR, "settings.json");
2023
+ var CURSOR_DIR = join2(HOME, ".cursor");
2024
+ var CURSOR_MCP_GLOBAL = join2(HOME, ".cursor", "mcp.json");
2025
+ var VSCODE_DIR = join2(HOME, ".vscode");
2026
+ var CODEX_DIR = join2(HOME, ".codex");
2027
+ var MARKETPLACE_NAME = "elnora-plugins";
2028
+ var MARKETPLACE_REPO = "Elnora-AI/elnora-plugins";
2029
+ var PLUGIN_ID = `elnora@${MARKETPLACE_NAME}`;
2030
+ var LEGACY_PLUGIN_IDS = ["elnora@elnora-local", "elnora@elnora-ai"];
2031
+ var LEGACY_MARKETPLACE_NAMES = ["elnora-local", "elnora-ai"];
2032
+ function readJsonFile(path) {
2033
+ try {
2034
+ return JSON.parse(readFileSync2(path, "utf-8"));
2035
+ } catch {
2036
+ return {};
2037
+ }
2038
+ }
2039
+ function writeJsonFile(path, data) {
2040
+ mkdirSync2(join2(path, ".."), { recursive: true });
2041
+ writeFileSync2(path, `${JSON.stringify(data, null, 2)}
2042
+ `, "utf-8");
2043
+ }
2044
+ var color = isColorEnabled();
2045
+ function ok(msg) {
2046
+ return color ? ` ${pc.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
2047
+ }
2048
+ function fail(msg) {
2049
+ return color ? ` ${pc.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
2050
+ }
2051
+ function info(msg) {
2052
+ return color ? ` ${pc.dim(msg)}` : ` ${msg}`;
2053
+ }
2054
+ function detectPlatforms() {
2055
+ return [
2056
+ { name: "claude", label: "Claude Code", installed: existsSync2(CLAUDE_DIR) },
2057
+ { name: "cursor", label: "Cursor", installed: existsSync2(CURSOR_DIR) },
2058
+ { name: "vscode", label: "VS Code", installed: existsSync2(VSCODE_DIR) },
2059
+ { name: "codex", label: "OpenAI Codex", installed: existsSync2(CODEX_DIR) }
2060
+ ];
2061
+ }
2062
+
2063
+ // src/lib/update-check.ts
2064
+ init_config();
2065
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
2066
+ import { homedir as homedir3 } from "node:os";
2067
+ import { join as join3 } from "node:path";
2068
+ import pc2 from "picocolors";
2017
2069
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/@elnora-ai/cli/latest";
2018
- var CACHE_DIR = join2(homedir2(), ".elnora");
2019
- var CACHE_FILE = join2(CACHE_DIR, ".update-check");
2070
+ var CACHE_DIR = join3(homedir3(), ".elnora");
2071
+ var CACHE_FILE = join3(CACHE_DIR, ".update-check");
2020
2072
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
2021
2073
  var FETCH_TIMEOUT_MS = 3e3;
2022
2074
  var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/;
@@ -2027,8 +2079,8 @@ function shouldSkipEntirely() {
2027
2079
  }
2028
2080
  function readCache() {
2029
2081
  try {
2030
- if (!existsSync2(CACHE_FILE)) return null;
2031
- const raw = readFileSync2(CACHE_FILE, "utf-8");
2082
+ if (!existsSync3(CACHE_FILE)) return null;
2083
+ const raw = readFileSync3(CACHE_FILE, "utf-8");
2032
2084
  return JSON.parse(raw);
2033
2085
  } catch {
2034
2086
  return null;
@@ -2036,8 +2088,8 @@ function readCache() {
2036
2088
  }
2037
2089
  function writeCache(entry) {
2038
2090
  try {
2039
- mkdirSync2(CACHE_DIR, { recursive: true });
2040
- writeFileSync2(CACHE_FILE, JSON.stringify(entry), "utf-8");
2091
+ mkdirSync3(CACHE_DIR, { recursive: true });
2092
+ writeFileSync3(CACHE_FILE, JSON.stringify(entry), "utf-8");
2041
2093
  } catch {
2042
2094
  }
2043
2095
  }
@@ -2045,7 +2097,7 @@ function showUpdateNotice(latest) {
2045
2097
  if (!process.stderr.isTTY) return;
2046
2098
  const color2 = isColorEnabled();
2047
2099
  const msg = color2 ? `
2048
- ${pc.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
2100
+ ${pc2.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
2049
2101
  Run: elnora update
2050
2102
  ` : `
2051
2103
  Update available: v${VERSION} \u2192 v${latest}
@@ -2101,99 +2153,275 @@ function registerUpdateCheck() {
2101
2153
  }
2102
2154
 
2103
2155
  // src/commands/doctor.ts
2156
+ var EXPECTED_SKILLS = [
2157
+ "elnora-admin",
2158
+ "elnora-agent",
2159
+ "elnora-files",
2160
+ "elnora-folders",
2161
+ "elnora-orgs",
2162
+ "elnora-platform",
2163
+ "elnora-projects",
2164
+ "elnora-search",
2165
+ "elnora-tasks"
2166
+ ];
2167
+ async function runCheck(fn) {
2168
+ try {
2169
+ return await fn();
2170
+ } catch (e) {
2171
+ return { status: "fail", msg: `${e instanceof Error ? e.message : String(e)}` };
2172
+ }
2173
+ }
2174
+ async function checkApiReachable() {
2175
+ const start = Date.now();
2176
+ const res = await fetch("https://platform.elnora.ai/health", { signal: AbortSignal.timeout(5e3) });
2177
+ const ms = Date.now() - start;
2178
+ if (res.ok) return { status: "pass", msg: `API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)` };
2179
+ return { status: "fail", msg: `API unreachable platform.elnora.ai (HTTP ${res.status})` };
2180
+ }
2181
+ async function checkAuthenticated() {
2182
+ const key = resolveApiKey();
2183
+ const client = new ElnoraApiClient(key);
2184
+ const result = await client.get("projects", {
2185
+ queryParams: { page: 1, pageSize: 1 }
2186
+ });
2187
+ const count = result?.totalCount ?? result?.items?.length ?? 0;
2188
+ return { status: "pass", msg: `Authenticated profile: default (${count} projects accessible)` };
2189
+ }
2190
+ async function checkVersionCurrent() {
2191
+ try {
2192
+ const res = await fetch("https://registry.npmjs.org/@elnora-ai/cli/latest", {
2193
+ signal: AbortSignal.timeout(3e3)
2194
+ });
2195
+ if (!res.ok) return { status: "pass", msg: `Version v${VERSION} (registry check failed)` };
2196
+ const data = await res.json();
2197
+ const latest = data.version ?? "unknown";
2198
+ if (latest === VERSION || latest === "unknown" || !isNewerVersion(latest, VERSION)) {
2199
+ return { status: "pass", msg: `Version current v${VERSION} (latest: v${latest})` };
2200
+ }
2201
+ return { status: "warn", msg: `Update available v${VERSION} \u2192 v${latest}` };
2202
+ } catch {
2203
+ return { status: "pass", msg: `Version v${VERSION} (registry unreachable)` };
2204
+ }
2205
+ }
2206
+ function checkConfigPermissions() {
2207
+ if (process.platform === "win32") {
2208
+ return { status: "pass", msg: "Config permissions (skipped on Windows)" };
2209
+ }
2210
+ if (!existsSync4(PROFILES_FILE)) {
2211
+ return { status: "warn", msg: "Config permissions ~/.elnora/profiles.toml not found" };
2212
+ }
2213
+ const mode = statSync(PROFILES_FILE).mode & 511;
2214
+ if (mode === 384) {
2215
+ return { status: "pass", msg: `Config permissions ${PROFILES_FILE} (0600)` };
2216
+ }
2217
+ return { status: "warn", msg: `Config permissions ${PROFILES_FILE} (0${mode.toString(8)}) \u2014 should be 0600` };
2218
+ }
2219
+ async function checkAiServerReachable() {
2220
+ const start = Date.now();
2221
+ const res = await fetch(`${AI_SERVER_URL}/health`, { signal: AbortSignal.timeout(5e3) });
2222
+ const ms = Date.now() - start;
2223
+ const host = new URL(AI_SERVER_URL).hostname;
2224
+ if (res.ok) return { status: "pass", msg: `AI Server reachable ${host} (${res.status} OK, ${ms}ms)` };
2225
+ return { status: "fail", msg: `AI Server unreachable ${host} (HTTP ${res.status})` };
2226
+ }
2227
+ function checkPathConfigured() {
2228
+ const installDirs = [join4(homedir4(), ".local", "bin"), join4(homedir4(), ".elnora", "bin")];
2229
+ const pathVar = process.env.PATH ?? "";
2230
+ const entries = pathVar.split(delimiter).filter(Boolean);
2231
+ const matched = installDirs.find((d) => entries.includes(d));
2232
+ if (matched) {
2233
+ return { status: "pass", msg: `PATH configured ${matched}` };
2234
+ }
2235
+ return {
2236
+ status: "warn",
2237
+ msg: "PATH configured standard install dirs not in PATH (OK if installed via npm/brew)"
2238
+ };
2239
+ }
2240
+ function checkClaudePlugin() {
2241
+ if (!existsSync4(CLAUDE_DIR)) {
2242
+ return { status: "skip", msg: "Plugin enabled (Claude Code not installed)" };
2243
+ }
2244
+ if (!existsSync4(CLAUDE_SETTINGS_FILE)) {
2245
+ return { status: "fail", msg: "Plugin enabled settings.json not found \u2014 run 'elnora setup claude'" };
2246
+ }
2247
+ let settings;
2248
+ try {
2249
+ settings = JSON.parse(readFileSync4(CLAUDE_SETTINGS_FILE, "utf-8"));
2250
+ } catch (e) {
2251
+ return {
2252
+ status: "fail",
2253
+ msg: `Plugin enabled settings.json unreadable (${e instanceof Error ? e.message : "parse error"})`
2254
+ };
2255
+ }
2256
+ const raw = settings.enabledPlugins;
2257
+ const enabledPlugins = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
2258
+ const isEnabled = enabledPlugins[PLUGIN_ID] === true;
2259
+ const legacyKeys = Object.keys(enabledPlugins).filter((k) => k.startsWith("elnora@") && k !== PLUGIN_ID);
2260
+ if (isEnabled && legacyKeys.length === 0) {
2261
+ return { status: "pass", msg: `Plugin enabled ${PLUGIN_ID}` };
2262
+ }
2263
+ if (isEnabled && legacyKeys.length > 0) {
2264
+ return {
2265
+ status: "warn",
2266
+ msg: `Plugin enabled ${PLUGIN_ID} \u2014 but stale legacy entries also present: ${legacyKeys.join(", ")}`
2267
+ };
2268
+ }
2269
+ if (legacyKeys.length > 0) {
2270
+ return {
2271
+ status: "fail",
2272
+ msg: `Plugin enabled found legacy entries ${legacyKeys.join(", ")} but not ${PLUGIN_ID} \u2014 run 'elnora setup claude'`
2273
+ };
2274
+ }
2275
+ return { status: "fail", msg: "Plugin enabled not found \u2014 run 'elnora setup claude'" };
2276
+ }
2277
+ function checkSkillsInstalled() {
2278
+ if (!existsSync4(CLAUDE_DIR)) {
2279
+ return { status: "skip", msg: "Skills installed (Claude Code not installed)" };
2280
+ }
2281
+ const skillsDir = join4(CLAUDE_DIR, "plugins", "marketplaces", MARKETPLACE_NAME, "elnora", "skills");
2282
+ if (!existsSync4(skillsDir)) {
2283
+ return {
2284
+ status: "warn",
2285
+ msg: "Skills installed marketplace not cloned yet \u2014 restart Claude Code to trigger clone"
2286
+ };
2287
+ }
2288
+ const found = EXPECTED_SKILLS.filter((s) => existsSync4(join4(skillsDir, s, "SKILL.md")));
2289
+ if (found.length === EXPECTED_SKILLS.length) {
2290
+ return { status: "pass", msg: `Skills installed ${found.length} skills` };
2291
+ }
2292
+ const missing = EXPECTED_SKILLS.filter((s) => !found.includes(s));
2293
+ return {
2294
+ status: "fail",
2295
+ msg: `Skills installed ${found.length}/${EXPECTED_SKILLS.length} \u2014 missing: ${missing.join(", ")}`
2296
+ };
2297
+ }
2298
+ function checkPluginVersion() {
2299
+ if (!existsSync4(CLAUDE_DIR)) {
2300
+ return { status: "skip", msg: "Plugin version (Claude Code not installed)" };
2301
+ }
2302
+ const pluginJsonPath = join4(
2303
+ CLAUDE_DIR,
2304
+ "plugins",
2305
+ "marketplaces",
2306
+ MARKETPLACE_NAME,
2307
+ "elnora",
2308
+ ".claude-plugin",
2309
+ "plugin.json"
2310
+ );
2311
+ if (!existsSync4(pluginJsonPath)) {
2312
+ return { status: "skip", msg: "Plugin version (plugin not cloned yet)" };
2313
+ }
2314
+ try {
2315
+ const pj = JSON.parse(readFileSync4(pluginJsonPath, "utf-8"));
2316
+ const pluginVersion = pj.version ?? "unknown";
2317
+ if (pluginVersion === VERSION) {
2318
+ return { status: "pass", msg: `Plugin version v${pluginVersion} (matches CLI)` };
2319
+ }
2320
+ return {
2321
+ status: "warn",
2322
+ msg: `Plugin version v${pluginVersion} (CLI is v${VERSION}) \u2014 restart Claude Code to refresh`
2323
+ };
2324
+ } catch (e) {
2325
+ return {
2326
+ status: "fail",
2327
+ msg: `Plugin version plugin.json unreadable (${e instanceof Error ? e.message : "parse error"})`
2328
+ };
2329
+ }
2330
+ }
2331
+ async function checkMcpReachable() {
2332
+ const start = Date.now();
2333
+ try {
2334
+ const res = await fetch("https://mcp.elnora.ai/mcp", {
2335
+ method: "HEAD",
2336
+ signal: AbortSignal.timeout(5e3)
2337
+ });
2338
+ const ms = Date.now() - start;
2339
+ if (res.status < 500) {
2340
+ return { status: "pass", msg: `Server reachable mcp.elnora.ai (${res.status}, ${ms}ms)` };
2341
+ }
2342
+ return { status: "fail", msg: `Server unreachable mcp.elnora.ai (HTTP ${res.status})` };
2343
+ } catch (e) {
2344
+ return {
2345
+ status: "fail",
2346
+ msg: `Server unreachable mcp.elnora.ai (${e instanceof Error ? e.message : "network error"})`
2347
+ };
2348
+ }
2349
+ }
2104
2350
  function addDoctorCommand(program2) {
2105
2351
  program2.command("doctor").description("Check Elnora CLI configuration and connectivity").action(async () => {
2106
2352
  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}`;
2353
+ const ok2 = (msg) => color2 ? ` ${pc3.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
2354
+ const fail2 = (msg) => color2 ? ` ${pc3.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
2355
+ const warn = (msg) => color2 ? ` ${pc3.yellow("!")} ${msg}` : ` ! ${msg}`;
2356
+ const skip = (msg) => color2 ? ` ${pc3.dim("\u25CB")} ${msg}` : ` \u25CB ${msg}`;
2357
+ const section = (name) => color2 ? pc3.bold(` ${name}`) : ` ${name}`;
2358
+ const render = (r) => {
2359
+ switch (r.status) {
2360
+ case "pass":
2361
+ return ok2(r.msg);
2362
+ case "fail":
2363
+ return fail2(r.msg);
2364
+ case "warn":
2365
+ return warn(r.msg);
2366
+ case "skip":
2367
+ return skip(r.msg);
2368
+ }
2369
+ };
2110
2370
  console.error(`
2111
2371
  Elnora CLI v${VERSION}
2112
2372
  `);
2113
2373
  let passed = 0;
2374
+ let failed = 0;
2375
+ let warnings = 0;
2114
2376
  let total = 0;
2115
- total++;
2116
- try {
2117
- const start = Date.now();
2118
- const res = await fetch("https://platform.elnora.ai/health", { signal: AbortSignal.timeout(5e3) });
2119
- const ms = Date.now() - start;
2120
- if (res.ok) {
2121
- console.error(ok2(`API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)`));
2122
- passed++;
2123
- } else console.error(fail2(`API unreachable platform.elnora.ai (HTTP ${res.status})`));
2124
- } catch (e) {
2125
- console.error(
2126
- fail2(`API unreachable platform.elnora.ai (${e instanceof Error ? e.message : "network error"})`)
2127
- );
2128
- }
2129
- total++;
2130
- try {
2131
- const key = resolveApiKey();
2132
- const client = new ElnoraApiClient(key);
2133
- const result = await client.get("projects", {
2134
- queryParams: { page: 1, pageSize: 1 }
2135
- });
2136
- const count = result?.totalCount ?? result?.items?.length ?? 0;
2137
- console.error(ok2(`Authenticated profile: default (${count} projects accessible)`));
2138
- passed++;
2139
- } catch (e) {
2140
- console.error(fail2(`Authentication failed ${e instanceof Error ? e.message : "no API key"}`));
2141
- }
2142
- total++;
2143
- try {
2144
- const res = await fetch("https://registry.npmjs.org/@elnora-ai/cli/latest", {
2145
- signal: AbortSignal.timeout(3e3)
2146
- });
2147
- if (res.ok) {
2148
- const data = await res.json();
2149
- const latest = data.version ?? "unknown";
2150
- if (latest === VERSION || latest === "unknown" || !isNewerVersion(latest, VERSION)) {
2151
- console.error(ok2(`Version current v${VERSION} (latest: v${latest})`));
2152
- } else {
2153
- console.error(warn(`Update available v${VERSION} \u2192 v${latest}`));
2154
- }
2155
- passed++;
2156
- } else {
2157
- console.error(ok2(`Version v${VERSION} (registry check failed)`));
2377
+ const tally = (r) => {
2378
+ total++;
2379
+ if (r.status === "pass" || r.status === "skip") passed++;
2380
+ else if (r.status === "warn") {
2158
2381
  passed++;
2159
- }
2160
- } catch {
2161
- console.error(ok2(`Version v${VERSION} (registry unreachable)`));
2162
- passed++;
2382
+ warnings++;
2383
+ } else failed++;
2384
+ };
2385
+ console.error(section("CLI"));
2386
+ const cliChecks = [
2387
+ await runCheck(() => checkApiReachable()),
2388
+ await runCheck(() => checkAuthenticated()),
2389
+ await runCheck(() => checkVersionCurrent()),
2390
+ await runCheck(() => checkConfigPermissions()),
2391
+ await runCheck(() => checkAiServerReachable()),
2392
+ await runCheck(() => checkPathConfigured())
2393
+ ];
2394
+ for (const r of cliChecks) {
2395
+ console.error(render(r));
2396
+ tally(r);
2163
2397
  }
2164
- total++;
2165
- if (process.platform === "win32") {
2166
- console.error(ok2("Config permissions (skipped on Windows)"));
2167
- passed++;
2168
- } else if (!existsSync3(PROFILES_FILE)) {
2169
- console.error(warn("Config permissions ~/.elnora/profiles.toml not found"));
2170
- } else {
2171
- const mode = statSync(PROFILES_FILE).mode & 511;
2172
- if (mode === 384) {
2173
- console.error(ok2(`Config permissions ${PROFILES_FILE} (0600)`));
2174
- passed++;
2175
- } else {
2176
- console.error(warn(`Config permissions ${PROFILES_FILE} (0${mode.toString(8)}) \u2014 should be 0600`));
2177
- }
2398
+ console.error("");
2399
+ console.error(section("Claude Code"));
2400
+ const claudeChecks = [
2401
+ await runCheck(() => checkClaudePlugin()),
2402
+ await runCheck(() => checkSkillsInstalled()),
2403
+ await runCheck(() => checkPluginVersion())
2404
+ ];
2405
+ for (const r of claudeChecks) {
2406
+ console.error(render(r));
2407
+ tally(r);
2178
2408
  }
2179
- total++;
2180
- try {
2181
- const start = Date.now();
2182
- const res = await fetch(`${AI_SERVER_URL}/health`, { signal: AbortSignal.timeout(5e3) });
2183
- const ms = Date.now() - start;
2184
- if (res.ok) {
2185
- console.error(ok2(`AI Server reachable ${new URL(AI_SERVER_URL).hostname} (${res.status} OK, ${ms}ms)`));
2186
- passed++;
2187
- } else console.error(fail2(`AI Server unreachable ${new URL(AI_SERVER_URL).hostname} (HTTP ${res.status})`));
2188
- } catch {
2189
- console.error(fail2(`AI Server unreachable ${AI_SERVER_URL}`));
2409
+ console.error("");
2410
+ console.error(section("MCP"));
2411
+ const mcpChecks = [await runCheck(() => checkMcpReachable())];
2412
+ for (const r of mcpChecks) {
2413
+ console.error(render(r));
2414
+ tally(r);
2190
2415
  }
2191
2416
  console.error("");
2192
- if (passed === total) {
2417
+ if (failed === 0 && warnings === 0) {
2193
2418
  console.error("All checks passed.");
2419
+ } else if (failed === 0) {
2420
+ console.error(`${passed}/${total} checks passed (${warnings} warning${warnings === 1 ? "" : "s"}).`);
2194
2421
  } else {
2195
- console.error(`${passed}/${total} checks passed.`);
2196
- if (!existsSync3(PROFILES_FILE)) console.error("\nRun: elnora auth login");
2422
+ const warnSuffix = warnings > 0 ? ` (${warnings} warning${warnings === 1 ? "" : "s"})` : "";
2423
+ console.error(`${passed}/${total} checks passed \u2014 ${failed} failed${warnSuffix}.`);
2424
+ if (!existsSync4(PROFILES_FILE)) console.error("\nRun: elnora auth login");
2197
2425
  }
2198
2426
  });
2199
2427
  }
@@ -2586,7 +2814,7 @@ var filesUpdate = {
2586
2814
 
2587
2815
  // src/commands/files/upload.ts
2588
2816
  init_client();
2589
- import { readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
2817
+ import { readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
2590
2818
  import { basename } from "node:path";
2591
2819
  import { z as z37 } from "zod";
2592
2820
  var inputSchema36 = z37.object({
@@ -2602,7 +2830,7 @@ var filesUpload = {
2602
2830
  inputSchema: inputSchema36,
2603
2831
  outputSchema: z37.any(),
2604
2832
  async execute(input, ctx) {
2605
- const fileBuffer = readFileSync3(input.filePath);
2833
+ const fileBuffer = readFileSync5(input.filePath);
2606
2834
  const fileName = input.fileName ?? basename(input.filePath);
2607
2835
  const contentType = input.contentType ?? "application/octet-stream";
2608
2836
  const fileSize = statSync2(input.filePath).size;
@@ -2633,7 +2861,7 @@ var filesUpload = {
2633
2861
  // src/commands/files/upload-batch.ts
2634
2862
  init_client();
2635
2863
  init_errors();
2636
- import { readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
2864
+ import { readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
2637
2865
  import { basename as basename2 } from "node:path";
2638
2866
  import { z as z38 } from "zod";
2639
2867
  var inputSchema37 = z38.object({
@@ -2662,7 +2890,7 @@ var filesUploadBatch = {
2662
2890
  for (const filePath of paths) {
2663
2891
  const fileName = basename2(filePath);
2664
2892
  try {
2665
- const fileBuffer = readFileSync4(filePath);
2893
+ const fileBuffer = readFileSync6(filePath);
2666
2894
  const contentType = "application/octet-stream";
2667
2895
  const fileSize = statSync3(filePath).size;
2668
2896
  const uploadInfo = await ctx.client.post("file_upload", {
@@ -4132,58 +4360,6 @@ init_profiles();
4132
4360
 
4133
4361
  // src/lib/setup/claude.ts
4134
4362
  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";
4138
- import { homedir as homedir3 } from "node:os";
4139
- import { join as join3 } from "node:path";
4140
- import pc3 from "picocolors";
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-plugins";
4151
- var MARKETPLACE_REPO = "Elnora-AI/elnora-plugins";
4152
- var PLUGIN_ID = `elnora@${MARKETPLACE_NAME}`;
4153
- var LEGACY_PLUGIN_IDS = ["elnora@elnora-local", "elnora@elnora-ai"];
4154
- var LEGACY_MARKETPLACE_NAMES = ["elnora-local", "elnora-ai"];
4155
- function readJsonFile(path) {
4156
- try {
4157
- return JSON.parse(readFileSync5(path, "utf-8"));
4158
- } catch {
4159
- return {};
4160
- }
4161
- }
4162
- function writeJsonFile(path, data) {
4163
- mkdirSync3(join3(path, ".."), { recursive: true });
4164
- writeFileSync3(path, `${JSON.stringify(data, null, 2)}
4165
- `, "utf-8");
4166
- }
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
4363
  function setupClaude() {
4188
4364
  if (!existsSync5(CLAUDE_DIR)) {
4189
4365
  console.error(fail("Claude Code not found (~/.claude/ does not exist)"));
@@ -4223,8 +4399,8 @@ function setupClaude() {
4223
4399
 
4224
4400
  // src/lib/setup/codex.ts
4225
4401
  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");
4402
+ import { join as join5 } from "node:path";
4403
+ var CODEX_MCP_FILE = join5(CODEX_DIR, "mcp.json");
4228
4404
  function setupCodex(apiKey) {
4229
4405
  if (!existsSync6(CODEX_DIR)) {
4230
4406
  console.error(fail("Codex CLI not found (~/.codex/ does not exist)"));
@@ -4280,8 +4456,8 @@ function setupCursor(apiKey) {
4280
4456
 
4281
4457
  // src/lib/setup/vscode.ts
4282
4458
  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");
4459
+ import { join as join6 } from "node:path";
4460
+ var VSCODE_MCP_GLOBAL = join6(HOME, ".vscode", "mcp.json");
4285
4461
  function setupVscode(apiKey) {
4286
4462
  if (!existsSync8(VSCODE_DIR)) {
4287
4463
  console.error(fail("VS Code not found (~/.vscode/ does not exist)"));
@@ -4875,9 +5051,9 @@ function registerTaskCommands() {
4875
5051
  init_config();
4876
5052
  import { execFileSync } from "node:child_process";
4877
5053
  import { createHash } from "node:crypto";
4878
- import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
5054
+ import { chmodSync as chmodSync2, copyFileSync, createWriteStream, mkdirSync as mkdirSync4, readFileSync as readFileSync7, rmSync } from "node:fs";
4879
5055
  import { tmpdir } from "node:os";
4880
- import { join as join6 } from "node:path";
5056
+ import { join as join7 } from "node:path";
4881
5057
  import { Readable } from "node:stream";
4882
5058
  import { pipeline } from "node:stream/promises";
4883
5059
  import pc5 from "picocolors";
@@ -4944,9 +5120,9 @@ async function installBinaryUpdate(latest) {
4944
5120
  const { target, ext } = platformInfo;
4945
5121
  const binaryUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.${ext}`;
4946
5122
  const checksumUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${latest}/${target}.sha256`;
4947
- const tmp = join6(tmpdir(), `elnora-update-${Date.now()}`);
5123
+ const tmp = join7(tmpdir(), `elnora-update-${Date.now()}`);
4948
5124
  mkdirSync4(tmp, { recursive: true });
4949
- const archivePath = join6(tmp, `${target}.${ext}`);
5125
+ const archivePath = join7(tmp, `${target}.${ext}`);
4950
5126
  try {
4951
5127
  console.error("Downloading...");
4952
5128
  await downloadToFile(binaryUrl, archivePath);
@@ -4957,7 +5133,7 @@ async function installBinaryUpdate(latest) {
4957
5133
  const checksumText = await checksumRes.text();
4958
5134
  const expected = checksumText.split(/\s/)[0].trim();
4959
5135
  if (expected.length === 64) {
4960
- const actual = createHash("sha256").update(readFileSync6(archivePath)).digest("hex");
5136
+ const actual = createHash("sha256").update(readFileSync7(archivePath)).digest("hex");
4961
5137
  if (actual.toLowerCase() !== expected.toLowerCase()) {
4962
5138
  console.error("Checksum verification failed. The download may be corrupted.");
4963
5139
  process.exitCode = 1;
@@ -4972,21 +5148,21 @@ async function installBinaryUpdate(latest) {
4972
5148
  console.error("Warning: Could not verify download checksum. Proceeding anyway.");
4973
5149
  }
4974
5150
  if (process.platform === "win32") {
4975
- const extractDir = join6(tmp, "extracted");
5151
+ const extractDir = join7(tmp, "extracted");
4976
5152
  execFileSync("powershell", [
4977
5153
  "-NoProfile",
4978
5154
  "-Command",
4979
5155
  `Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force`
4980
5156
  ]);
4981
- const installDir = join6(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
5157
+ const installDir = join7(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
4982
5158
  mkdirSync4(installDir, { recursive: true });
4983
- copyFileSync(join6(extractDir, target), join6(installDir, "elnora.exe"));
5159
+ copyFileSync(join7(extractDir, target), join7(installDir, "elnora.exe"));
4984
5160
  } else {
4985
5161
  execFileSync("tar", ["-xzf", archivePath, "-C", tmp]);
4986
- const installDir = process.env.ELNORA_INSTALL_DIR ?? join6(process.env.HOME ?? "", ".local", "bin");
5162
+ const installDir = process.env.ELNORA_INSTALL_DIR ?? join7(process.env.HOME ?? "", ".local", "bin");
4987
5163
  mkdirSync4(installDir, { recursive: true });
4988
- const dest = join6(installDir, "elnora");
4989
- copyFileSync(join6(tmp, target), dest);
5164
+ const dest = join7(installDir, "elnora");
5165
+ copyFileSync(join7(tmp, target), dest);
4990
5166
  chmodSync2(dest, 493);
4991
5167
  }
4992
5168
  const done = color2 ? `${pc5.green("\u2713")} Updated to v${latest}` : `\u2713 Updated to v${latest}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elnora-ai/cli",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "description": "Elnora AI Platform CLI",
5
5
  "type": "module",
6
6
  "bin": {