@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.
- package/dist/cli.js +337 -161
- 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.
|
|
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
|
|
2009
|
-
import
|
|
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/
|
|
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 =
|
|
2019
|
-
var CACHE_FILE =
|
|
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 (!
|
|
2031
|
-
const raw =
|
|
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
|
-
|
|
2040
|
-
|
|
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
|
-
${
|
|
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 ? ` ${
|
|
2108
|
-
const fail2 = (msg) => color2 ? ` ${
|
|
2109
|
-
const warn = (msg) => color2 ? ` ${
|
|
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
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
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
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
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
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
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
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
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 (
|
|
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
|
-
|
|
2196
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
4227
|
-
var CODEX_MCP_FILE =
|
|
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
|
|
4284
|
-
var VSCODE_MCP_GLOBAL =
|
|
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
|
|
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
|
|
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 =
|
|
5123
|
+
const tmp = join7(tmpdir(), `elnora-update-${Date.now()}`);
|
|
4948
5124
|
mkdirSync4(tmp, { recursive: true });
|
|
4949
|
-
const archivePath =
|
|
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(
|
|
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 =
|
|
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 =
|
|
5157
|
+
const installDir = join7(process.env.USERPROFILE ?? process.env.HOME ?? "", ".elnora", "bin");
|
|
4982
5158
|
mkdirSync4(installDir, { recursive: true });
|
|
4983
|
-
copyFileSync(
|
|
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 ??
|
|
5162
|
+
const installDir = process.env.ELNORA_INSTALL_DIR ?? join7(process.env.HOME ?? "", ".local", "bin");
|
|
4987
5163
|
mkdirSync4(installDir, { recursive: true });
|
|
4988
|
-
const dest =
|
|
4989
|
-
copyFileSync(
|
|
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}`;
|