@codelia/cli 0.1.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/index.cjs ADDED
@@ -0,0 +1,918 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/args.ts
27
+ var import_cac = require("cac");
28
+ var parseWithCac = (values) => {
29
+ const cli = (0, import_cac.cac)("codelia-args");
30
+ cli.command("[...positionals]").allowUnknownOptions();
31
+ const parsed = cli.parse(["node", "codelia-args", ...values], {
32
+ run: false
33
+ });
34
+ return {
35
+ args: [...parsed.args],
36
+ options: parsed.options
37
+ };
38
+ };
39
+ var parseBoolean = (value, flagName) => {
40
+ const normalized = value.trim().toLowerCase();
41
+ if (normalized === "true") return true;
42
+ if (normalized === "false") return false;
43
+ throw new Error(`${flagName} must be true|false`);
44
+ };
45
+ var parseTimeout = (value) => {
46
+ const parsed = Number.parseInt(value, 10);
47
+ if (!Number.isFinite(parsed) || parsed <= 0) {
48
+ throw new Error("--request-timeout-ms must be a positive integer");
49
+ }
50
+ return parsed;
51
+ };
52
+ var parseEpochMs = (value, flagName) => {
53
+ const parsed = Number.parseInt(value, 10);
54
+ if (!Number.isFinite(parsed) || parsed <= 0) {
55
+ throw new Error(`${flagName} must be a positive integer (epoch ms)`);
56
+ }
57
+ return parsed;
58
+ };
59
+ var parseKeyValue = (raw, flagName) => {
60
+ const [key, ...rest] = raw.split("=");
61
+ const value = rest.join("=");
62
+ if (!key || !value) {
63
+ throw new Error(`${flagName} must be key=value`);
64
+ }
65
+ return [key, value];
66
+ };
67
+ var parseCliArgs = (values) => {
68
+ const parsed = parseWithCac(values);
69
+ return {
70
+ positionals: parsed.args,
71
+ options: parsed.options
72
+ };
73
+ };
74
+ var readOptionValues = (parsed, flagName) => {
75
+ const value = parsed.options[flagName];
76
+ if (value == null || value === false || value === true) return [];
77
+ const entries = Array.isArray(value) ? value : [value];
78
+ return entries.map((entry) => String(entry));
79
+ };
80
+ var getLastFlagValue = (parsed, flagName) => {
81
+ const values = readOptionValues(parsed, flagName);
82
+ if (!values || values.length === 0) return void 0;
83
+ return values[values.length - 1];
84
+ };
85
+ var getAllFlagValues = (parsed, flagName) => readOptionValues(parsed, flagName);
86
+ var hasBooleanFlag = (parsed, flagName) => parsed.options[flagName] === true;
87
+
88
+ // src/mcp/auth-file.ts
89
+ var import_storage = require("@codelia/storage");
90
+ var authStore = new import_storage.McpAuthStore();
91
+ var readMcpAuth = async () => {
92
+ return authStore.load();
93
+ };
94
+ var writeMcpAuth = async (auth) => {
95
+ await authStore.save(auth);
96
+ };
97
+
98
+ // src/commands/mcp-auth.ts
99
+ var runMcpAuthCommand = async (values) => {
100
+ const [subcommand, ...rest] = values;
101
+ const parsed = parseCliArgs(rest);
102
+ if (!subcommand) {
103
+ console.error("usage: codelia mcp auth <list|set|clear> ...");
104
+ return 1;
105
+ }
106
+ if (subcommand === "list") {
107
+ const auth = await readMcpAuth();
108
+ const entries = Object.entries(auth.servers).sort(
109
+ ([a], [b]) => a.localeCompare(b)
110
+ );
111
+ if (!entries.length) {
112
+ console.log("no MCP auth tokens configured");
113
+ return 0;
114
+ }
115
+ console.log("server_id has_refresh expires_at");
116
+ for (const [serverId, tokens] of entries) {
117
+ console.log(
118
+ `${serverId} ${tokens.refresh_token ? "yes" : "no"} ${tokens.expires_at ?? "-"}`
119
+ );
120
+ }
121
+ return 0;
122
+ }
123
+ if (subcommand === "set") {
124
+ const serverId = parsed.positionals[0];
125
+ if (!serverId) {
126
+ console.error(
127
+ "usage: codelia mcp auth set <server-id> --access-token <token> [--refresh-token <token>] [--expires-at <epoch_ms>] [--expires-in <sec>] [--scope <scope>] [--token-type <type>]"
128
+ );
129
+ return 1;
130
+ }
131
+ const accessToken = getLastFlagValue(parsed, "access-token");
132
+ if (!accessToken) {
133
+ console.error("--access-token is required");
134
+ return 1;
135
+ }
136
+ const refreshToken = getLastFlagValue(parsed, "refresh-token");
137
+ const expiresAtRaw = getLastFlagValue(parsed, "expires-at");
138
+ const expiresInRaw = getLastFlagValue(parsed, "expires-in");
139
+ let expiresAt;
140
+ if (expiresAtRaw) {
141
+ expiresAt = parseEpochMs(expiresAtRaw, "--expires-at");
142
+ } else if (expiresInRaw) {
143
+ expiresAt = Date.now() + parseTimeout(expiresInRaw) * 1e3;
144
+ }
145
+ const scope = getLastFlagValue(parsed, "scope");
146
+ const tokenType = getLastFlagValue(parsed, "token-type");
147
+ const auth = await readMcpAuth();
148
+ auth.servers[serverId] = {
149
+ access_token: accessToken,
150
+ ...refreshToken ? { refresh_token: refreshToken } : {},
151
+ ...expiresAt ? { expires_at: expiresAt } : {},
152
+ ...scope ? { scope } : {},
153
+ ...tokenType ? { token_type: tokenType } : {}
154
+ };
155
+ await writeMcpAuth(auth);
156
+ console.log(`stored MCP auth token for '${serverId}'`);
157
+ return 0;
158
+ }
159
+ if (subcommand === "clear") {
160
+ const serverId = parsed.positionals[0];
161
+ if (!serverId) {
162
+ console.error("usage: codelia mcp auth clear <server-id>");
163
+ return 1;
164
+ }
165
+ const auth = await readMcpAuth();
166
+ if (!auth.servers[serverId]) {
167
+ console.error(`no token found for server: ${serverId}`);
168
+ return 1;
169
+ }
170
+ delete auth.servers[serverId];
171
+ await writeMcpAuth(auth);
172
+ console.log(`cleared MCP auth token for '${serverId}'`);
173
+ return 0;
174
+ }
175
+ console.error(`unknown auth subcommand: ${subcommand}`);
176
+ return 1;
177
+ };
178
+
179
+ // src/commands/mcp-config.ts
180
+ var import_node_path = __toESM(require("path"), 1);
181
+ var import_config_loader = require("@codelia/config-loader");
182
+ var import_storage2 = require("@codelia/storage");
183
+ var import_zod = require("zod");
184
+
185
+ // src/mcp/probe.ts
186
+ var import_node_child_process = require("child_process");
187
+ var import_node_readline = require("readline");
188
+
189
+ // src/mcp/protocol.ts
190
+ var import_protocol = require("@codelia/protocol");
191
+ var assertSupportedProtocolVersion = (value) => {
192
+ (0, import_protocol.assertSupportedMcpProtocolVersion)(value);
193
+ };
194
+
195
+ // src/mcp/probe.ts
196
+ var DEFAULT_MCP_TIMEOUT_MS = 3e4;
197
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
198
+ var describeError = (error) => error instanceof Error ? error.message : String(error);
199
+ var postHttpJson = async (url, body, headers, timeoutMs) => {
200
+ const controller = new AbortController();
201
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
202
+ try {
203
+ const response = await fetch(url, {
204
+ method: "POST",
205
+ headers,
206
+ body: JSON.stringify(body),
207
+ signal: controller.signal
208
+ });
209
+ if (!response.ok) {
210
+ const bodyText = await response.text().catch(() => "");
211
+ throw new Error(`HTTP ${response.status}: ${bodyText.slice(0, 300)}`);
212
+ }
213
+ const text = await response.text();
214
+ const sessionId = response.headers.get("MCP-Session-Id") ?? void 0;
215
+ if (!text.trim()) return { body: null, sessionId };
216
+ return { body: JSON.parse(text), sessionId };
217
+ } catch (error) {
218
+ if (controller.signal.aborted) {
219
+ throw new Error(`request timed out after ${timeoutMs}ms`);
220
+ }
221
+ throw error;
222
+ } finally {
223
+ clearTimeout(timer);
224
+ }
225
+ };
226
+ var parseRpcResult = (raw, requestId) => {
227
+ if (Array.isArray(raw)) {
228
+ const found = raw.find(
229
+ (entry) => isRecord(entry) && String(entry.id ?? "") === requestId
230
+ );
231
+ if (!found) {
232
+ throw new Error(`missing response id=${requestId}`);
233
+ }
234
+ return parseRpcResult(found, requestId);
235
+ }
236
+ if (!isRecord(raw)) {
237
+ throw new Error("invalid JSON-RPC response");
238
+ }
239
+ if (isRecord(raw.error)) {
240
+ throw new Error(
241
+ `RPC ${String(raw.error.code)}: ${String(raw.error.message)}`
242
+ );
243
+ }
244
+ return raw.result;
245
+ };
246
+ var parseToolsResponse = (raw) => {
247
+ if (!isRecord(raw)) return { count: 0 };
248
+ const tools = Array.isArray(raw.tools) ? raw.tools.length : 0;
249
+ const nextCursor = typeof raw.nextCursor === "string" ? raw.nextCursor : typeof raw.next_cursor === "string" ? raw.next_cursor : void 0;
250
+ return { count: tools, nextCursor };
251
+ };
252
+ var probeHttpServer = async (config) => {
253
+ let seq = 0;
254
+ let sessionId;
255
+ const timeoutMs = config.request_timeout_ms ?? DEFAULT_MCP_TIMEOUT_MS;
256
+ const protocolVersion = (0, import_protocol.getMcpProtocolVersion)();
257
+ const baseHeaders = {
258
+ "Content-Type": "application/json",
259
+ Accept: "application/json, text/event-stream",
260
+ "MCP-Protocol-Version": protocolVersion,
261
+ ...config.headers ?? {}
262
+ };
263
+ const request = async (method, params) => {
264
+ seq += 1;
265
+ const requestId = `cli-${seq}`;
266
+ const headers = {
267
+ ...baseHeaders,
268
+ ...sessionId ? { "MCP-Session-Id": sessionId } : {}
269
+ };
270
+ const raw = await postHttpJson(
271
+ config.url ?? "",
272
+ {
273
+ jsonrpc: "2.0",
274
+ id: requestId,
275
+ method,
276
+ params
277
+ },
278
+ headers,
279
+ timeoutMs
280
+ );
281
+ if (raw.sessionId) {
282
+ sessionId = raw.sessionId;
283
+ }
284
+ return parseRpcResult(raw.body, requestId);
285
+ };
286
+ const notify = async (method, params) => {
287
+ const headers = {
288
+ ...baseHeaders,
289
+ ...sessionId ? { "MCP-Session-Id": sessionId } : {}
290
+ };
291
+ const raw = await postHttpJson(
292
+ config.url ?? "",
293
+ {
294
+ jsonrpc: "2.0",
295
+ method,
296
+ params
297
+ },
298
+ headers,
299
+ timeoutMs
300
+ );
301
+ if (raw.sessionId) {
302
+ sessionId = raw.sessionId;
303
+ }
304
+ };
305
+ const initResult = await request("initialize", {
306
+ protocolVersion,
307
+ clientInfo: { name: "codelia-cli", version: "0.1.0" },
308
+ capabilities: {}
309
+ });
310
+ assertSupportedProtocolVersion(initResult);
311
+ await notify("notifications/initialized", {});
312
+ let total = 0;
313
+ let cursor;
314
+ for (let page = 0; page < 100; page += 1) {
315
+ const result = await request("tools/list", cursor ? { cursor } : {});
316
+ const parsed = parseToolsResponse(result);
317
+ total += parsed.count;
318
+ if (!parsed.nextCursor) break;
319
+ cursor = parsed.nextCursor;
320
+ }
321
+ return total;
322
+ };
323
+ var probeStdioServer = async (config) => {
324
+ const timeoutMs = config.request_timeout_ms ?? DEFAULT_MCP_TIMEOUT_MS;
325
+ const child = (0, import_node_child_process.spawn)(config.command ?? "", config.args ?? [], {
326
+ cwd: config.cwd,
327
+ env: {
328
+ ...process.env,
329
+ ...config.env ?? {}
330
+ },
331
+ stdio: ["pipe", "pipe", "pipe"]
332
+ });
333
+ let seq = 0;
334
+ const pending = /* @__PURE__ */ new Map();
335
+ const stdoutReader = (0, import_node_readline.createInterface)({ input: child.stdout });
336
+ stdoutReader.on("line", (line) => {
337
+ const trimmed = line.trim();
338
+ if (!trimmed) return;
339
+ let parsed;
340
+ try {
341
+ parsed = JSON.parse(trimmed);
342
+ } catch {
343
+ return;
344
+ }
345
+ if (!isRecord(parsed) || parsed.id === void 0 || parsed.id === null) {
346
+ return;
347
+ }
348
+ const id = String(parsed.id);
349
+ const entry = pending.get(id);
350
+ if (!entry) return;
351
+ pending.delete(id);
352
+ clearTimeout(entry.timeout);
353
+ if (isRecord(parsed.error)) {
354
+ entry.reject(
355
+ new Error(
356
+ `RPC ${String(parsed.error.code)}: ${String(parsed.error.message)}`
357
+ )
358
+ );
359
+ return;
360
+ }
361
+ entry.resolve(parsed.result);
362
+ });
363
+ const stderrReader = (0, import_node_readline.createInterface)({ input: child.stderr });
364
+ stderrReader.on("line", () => void 0);
365
+ const failPending = (error) => {
366
+ for (const [id, entry] of pending) {
367
+ pending.delete(id);
368
+ clearTimeout(entry.timeout);
369
+ entry.reject(error);
370
+ }
371
+ };
372
+ child.once("error", (error) => {
373
+ failPending(new Error(`stdio process error: ${describeError(error)}`));
374
+ });
375
+ child.once("exit", (code, signal) => {
376
+ failPending(
377
+ new Error(`stdio exited: code=${String(code)} signal=${String(signal)}`)
378
+ );
379
+ });
380
+ const send = (payload) => new Promise((resolve, reject) => {
381
+ child.stdin.write(`${JSON.stringify(payload)}
382
+ `, (error) => {
383
+ if (error) {
384
+ reject(error);
385
+ return;
386
+ }
387
+ resolve();
388
+ });
389
+ });
390
+ const request = (method, params) => {
391
+ seq += 1;
392
+ const requestId = `cli-${seq}`;
393
+ return new Promise((resolve, reject) => {
394
+ const timeout = setTimeout(() => {
395
+ pending.delete(requestId);
396
+ reject(new Error(`request timed out after ${timeoutMs}ms`));
397
+ }, timeoutMs);
398
+ pending.set(requestId, { resolve, reject, timeout });
399
+ void send({
400
+ jsonrpc: "2.0",
401
+ id: requestId,
402
+ method,
403
+ params
404
+ }).catch((error) => {
405
+ const entry = pending.get(requestId);
406
+ if (!entry) return;
407
+ pending.delete(requestId);
408
+ clearTimeout(entry.timeout);
409
+ reject(new Error(`failed to send request: ${describeError(error)}`));
410
+ });
411
+ });
412
+ };
413
+ const notify = async (method, params) => {
414
+ await send({ jsonrpc: "2.0", method, params });
415
+ };
416
+ try {
417
+ const initResult = await request("initialize", {
418
+ protocolVersion: (0, import_protocol.getMcpProtocolVersion)(),
419
+ clientInfo: { name: "codelia-cli", version: "0.1.0" },
420
+ capabilities: {}
421
+ });
422
+ assertSupportedProtocolVersion(initResult);
423
+ await notify("notifications/initialized", {});
424
+ let total = 0;
425
+ let cursor;
426
+ for (let page = 0; page < 100; page += 1) {
427
+ const result = await request("tools/list", cursor ? { cursor } : {});
428
+ const parsed = parseToolsResponse(result);
429
+ total += parsed.count;
430
+ if (!parsed.nextCursor) break;
431
+ cursor = parsed.nextCursor;
432
+ }
433
+ return total;
434
+ } finally {
435
+ child.kill("SIGTERM");
436
+ stdoutReader.close();
437
+ stderrReader.close();
438
+ }
439
+ };
440
+ var probeServer = async (config) => {
441
+ if (config.transport === "http") {
442
+ return probeHttpServer(config);
443
+ }
444
+ return probeStdioServer(config);
445
+ };
446
+
447
+ // src/commands/mcp-config.ts
448
+ var TIMEOUT_SCHEMA = import_zod.z.number().positive().transform((value) => Math.round(value));
449
+ var OAUTH_SCHEMA = import_zod.z.object({
450
+ authorization_url: import_zod.z.string(),
451
+ token_url: import_zod.z.string(),
452
+ registration_url: import_zod.z.string(),
453
+ client_id: import_zod.z.string(),
454
+ client_secret: import_zod.z.string(),
455
+ scope: import_zod.z.string()
456
+ }).partial();
457
+ var HTTP_SERVER_CONFIG_SCHEMA = import_zod.z.object({
458
+ transport: import_zod.z.literal("http"),
459
+ enabled: import_zod.z.boolean().optional(),
460
+ url: import_zod.z.string(),
461
+ headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
462
+ env: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
463
+ request_timeout_ms: TIMEOUT_SCHEMA.optional(),
464
+ oauth: OAUTH_SCHEMA.optional()
465
+ });
466
+ var STDIO_SERVER_CONFIG_SCHEMA = import_zod.z.object({
467
+ transport: import_zod.z.literal("stdio"),
468
+ enabled: import_zod.z.boolean().optional(),
469
+ command: import_zod.z.string(),
470
+ args: import_zod.z.array(import_zod.z.string()).optional(),
471
+ cwd: import_zod.z.string().optional(),
472
+ env: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
473
+ request_timeout_ms: TIMEOUT_SCHEMA.optional(),
474
+ oauth: OAUTH_SCHEMA.optional()
475
+ });
476
+ var MCP_SERVER_CONFIG_SCHEMA = import_zod.z.union([
477
+ HTTP_SERVER_CONFIG_SCHEMA,
478
+ STDIO_SERVER_CONFIG_SCHEMA
479
+ ]);
480
+ var describeError2 = (error) => error instanceof Error ? error.message : String(error);
481
+ var readEnvValue = (key) => {
482
+ const value = process.env[key];
483
+ if (!value) return void 0;
484
+ const trimmed = value.trim();
485
+ return trimmed ? trimmed : void 0;
486
+ };
487
+ var resolveGlobalConfigPath = () => {
488
+ const envPath = readEnvValue("CODELIA_CONFIG_PATH");
489
+ if (envPath) return import_node_path.default.resolve(envPath);
490
+ const storage = (0, import_storage2.resolveStoragePaths)();
491
+ return storage.configFile;
492
+ };
493
+ var resolveProjectConfigPath = () => import_node_path.default.resolve(process.cwd(), ".codelia", "config.json");
494
+ var normalizeServerConfig = (value) => {
495
+ const parsed = MCP_SERVER_CONFIG_SCHEMA.safeParse(value);
496
+ return parsed.success ? parsed.data : null;
497
+ };
498
+ var normalizeEntries = (servers, source) => {
499
+ const entries = [];
500
+ for (const [id, rawConfig] of Object.entries(servers)) {
501
+ const config = normalizeServerConfig(rawConfig);
502
+ if (!config) continue;
503
+ entries.push({ id, config, source });
504
+ }
505
+ return entries.sort((left, right) => left.id.localeCompare(right.id));
506
+ };
507
+ var loadEntriesByScope = async (scope) => {
508
+ const globalServers = await (0, import_config_loader.loadMcpServers)(resolveGlobalConfigPath());
509
+ const projectServers = await (0, import_config_loader.loadMcpServers)(resolveProjectConfigPath());
510
+ if (scope === "global") {
511
+ return normalizeEntries(globalServers, "global");
512
+ }
513
+ if (scope === "project") {
514
+ return normalizeEntries(projectServers, "project");
515
+ }
516
+ const merged = /* @__PURE__ */ new Map();
517
+ for (const entry of normalizeEntries(globalServers, "global")) {
518
+ merged.set(entry.id, entry);
519
+ }
520
+ for (const entry of normalizeEntries(projectServers, "project")) {
521
+ merged.set(entry.id, entry);
522
+ }
523
+ return Array.from(merged.values()).sort(
524
+ (left, right) => left.id.localeCompare(right.id)
525
+ );
526
+ };
527
+ var resolveMutableScope = (parsed) => {
528
+ const scopeRaw = getLastFlagValue(parsed, "scope") ?? "project";
529
+ if (scopeRaw === "project" || scopeRaw === "global") {
530
+ return scopeRaw;
531
+ }
532
+ throw new Error("--scope must be project|global");
533
+ };
534
+ var resolveListScope = (parsed) => {
535
+ const scopeRaw = getLastFlagValue(parsed, "scope") ?? "effective";
536
+ if (scopeRaw === "project" || scopeRaw === "global" || scopeRaw === "effective") {
537
+ return scopeRaw;
538
+ }
539
+ throw new Error("--scope must be effective|project|global");
540
+ };
541
+ var buildServerConfigFromAdd = (parsed) => {
542
+ const transport = getLastFlagValue(parsed, "transport");
543
+ if (transport !== "http" && transport !== "stdio") {
544
+ throw new Error("--transport must be http|stdio");
545
+ }
546
+ const enabledRaw = getLastFlagValue(parsed, "enabled");
547
+ const enabled = enabledRaw ? parseBoolean(enabledRaw, "--enabled") : true;
548
+ const timeoutRaw = getLastFlagValue(parsed, "request-timeout-ms");
549
+ const requestTimeoutMs = timeoutRaw ? parseTimeout(timeoutRaw) : void 0;
550
+ const oauth = {
551
+ authorization_url: getLastFlagValue(parsed, "oauth-authorization-url"),
552
+ token_url: getLastFlagValue(parsed, "oauth-token-url"),
553
+ registration_url: getLastFlagValue(parsed, "oauth-registration-url"),
554
+ client_id: getLastFlagValue(parsed, "oauth-client-id"),
555
+ client_secret: getLastFlagValue(parsed, "oauth-client-secret"),
556
+ scope: getLastFlagValue(parsed, "oauth-scope")
557
+ };
558
+ const oauthConfig = Object.fromEntries(
559
+ Object.entries(oauth).filter(([, value]) => !!value)
560
+ );
561
+ if (transport === "http") {
562
+ const url = getLastFlagValue(parsed, "url");
563
+ if (!url) throw new Error("--url is required when --transport=http");
564
+ const headers = Object.fromEntries(
565
+ getAllFlagValues(parsed, "header").map(
566
+ (value) => parseKeyValue(value, "--header")
567
+ )
568
+ );
569
+ return {
570
+ transport,
571
+ ...enabled !== true ? { enabled } : {},
572
+ url,
573
+ ...Object.keys(headers).length ? { headers } : {},
574
+ ...requestTimeoutMs ? { request_timeout_ms: requestTimeoutMs } : {},
575
+ ...oauthConfig && Object.keys(oauthConfig).length ? { oauth: oauthConfig } : {}
576
+ };
577
+ }
578
+ const command = getLastFlagValue(parsed, "command");
579
+ if (!command) {
580
+ throw new Error("--command is required when --transport=stdio");
581
+ }
582
+ const env = Object.fromEntries(
583
+ getAllFlagValues(parsed, "env").map(
584
+ (value) => parseKeyValue(value, "--env")
585
+ )
586
+ );
587
+ const args2 = getAllFlagValues(parsed, "arg");
588
+ const cwd = getLastFlagValue(parsed, "cwd");
589
+ return {
590
+ transport,
591
+ ...enabled !== true ? { enabled } : {},
592
+ command,
593
+ ...args2.length ? { args: args2 } : {},
594
+ ...cwd ? { cwd } : {},
595
+ ...Object.keys(env).length ? { env } : {},
596
+ ...requestTimeoutMs ? { request_timeout_ms: requestTimeoutMs } : {},
597
+ ...oauthConfig && Object.keys(oauthConfig).length ? { oauth: oauthConfig } : {}
598
+ };
599
+ };
600
+ var printStaticList = (entries) => {
601
+ if (!entries.length) {
602
+ console.log("no MCP servers configured");
603
+ return;
604
+ }
605
+ console.log("id transport source enabled");
606
+ for (const entry of entries) {
607
+ const enabled = entry.config.enabled !== false;
608
+ console.log(
609
+ `${entry.id} ${entry.config.transport} ${entry.source} ${enabled}`
610
+ );
611
+ }
612
+ };
613
+ var runMcpConfigCommand = async (subcommand, rest) => {
614
+ const parsed = parseCliArgs(rest);
615
+ if (subcommand === "list") {
616
+ const scope = resolveListScope(parsed);
617
+ const entries = await loadEntriesByScope(scope);
618
+ printStaticList(entries);
619
+ return 0;
620
+ }
621
+ if (subcommand === "add") {
622
+ const serverId = parsed.positionals[0];
623
+ if (!serverId) {
624
+ console.error(
625
+ "usage: codelia mcp add <server-id> --transport <http|stdio> ..."
626
+ );
627
+ return 1;
628
+ }
629
+ const scope = resolveMutableScope(parsed);
630
+ const configPath = scope === "global" ? resolveGlobalConfigPath() : resolveProjectConfigPath();
631
+ const servers = await (0, import_config_loader.loadMcpServers)(configPath);
632
+ const replace = hasBooleanFlag(parsed, "replace");
633
+ if (servers[serverId] !== void 0 && !replace) {
634
+ console.error(`server already exists in ${scope} scope: ${serverId}`);
635
+ console.error("pass --replace to overwrite");
636
+ return 1;
637
+ }
638
+ const serverConfig = buildServerConfigFromAdd(parsed);
639
+ await (0, import_config_loader.upsertMcpServerConfig)(configPath, serverId, serverConfig);
640
+ console.log(`added MCP server '${serverId}' (${scope})`);
641
+ return 0;
642
+ }
643
+ if (subcommand === "remove" || subcommand === "enable" || subcommand === "disable") {
644
+ const serverId = parsed.positionals[0];
645
+ if (!serverId) {
646
+ console.error(
647
+ `usage: codelia mcp ${subcommand} <server-id> [--scope project|global]`
648
+ );
649
+ return 1;
650
+ }
651
+ const scope = resolveMutableScope(parsed);
652
+ const configPath = scope === "global" ? resolveGlobalConfigPath() : resolveProjectConfigPath();
653
+ if (subcommand === "remove") {
654
+ const removed = await (0, import_config_loader.removeMcpServerConfig)(configPath, serverId);
655
+ if (!removed) {
656
+ console.error(`server not found in ${scope} scope: ${serverId}`);
657
+ return 1;
658
+ }
659
+ } else {
660
+ const updated = await (0, import_config_loader.setMcpServerEnabled)(
661
+ configPath,
662
+ serverId,
663
+ subcommand === "enable"
664
+ );
665
+ if (!updated) {
666
+ console.error(`server not found in ${scope} scope: ${serverId}`);
667
+ return 1;
668
+ }
669
+ }
670
+ console.log(`${subcommand}d MCP server '${serverId}' (${scope})`);
671
+ return 0;
672
+ }
673
+ if (subcommand === "test") {
674
+ const serverId = parsed.positionals[0];
675
+ if (!serverId) {
676
+ console.error(
677
+ "usage: codelia mcp test <server-id> [--scope effective|project|global]"
678
+ );
679
+ return 1;
680
+ }
681
+ const scope = resolveListScope(parsed);
682
+ const entries = await loadEntriesByScope(scope);
683
+ const target = entries.find((entry) => entry.id === serverId);
684
+ if (!target) {
685
+ console.error(`server not found: ${serverId} (scope=${scope})`);
686
+ return 1;
687
+ }
688
+ try {
689
+ const auth = await readMcpAuth();
690
+ const accessToken = auth.servers[target.id]?.access_token;
691
+ const configForTest = target.config.transport === "http" && accessToken ? {
692
+ ...target.config,
693
+ headers: {
694
+ ...target.config.headers ?? {},
695
+ Authorization: `Bearer ${accessToken}`
696
+ }
697
+ } : target.config;
698
+ const tools = await probeServer(configForTest);
699
+ console.log(
700
+ `MCP test succeeded: id=${target.id} transport=${target.config.transport} source=${target.source} tools=${tools}`
701
+ );
702
+ return 0;
703
+ } catch (error) {
704
+ console.error(
705
+ `MCP test failed: id=${target.id} transport=${target.config.transport}: ${describeError2(error)}`
706
+ );
707
+ return 1;
708
+ }
709
+ }
710
+ return -1;
711
+ };
712
+
713
+ // src/commands/mcp.ts
714
+ var runMcpCommand = async (values) => {
715
+ const [subcommand, ...rest] = values;
716
+ if (!subcommand) {
717
+ console.error(
718
+ "usage: codelia mcp <add|list|remove|enable|disable|test|auth> ..."
719
+ );
720
+ return 1;
721
+ }
722
+ if (subcommand === "auth") {
723
+ return runMcpAuthCommand(rest);
724
+ }
725
+ const handled = await runMcpConfigCommand(subcommand, rest);
726
+ if (handled >= 0) {
727
+ return handled;
728
+ }
729
+ console.error(`unknown subcommand: ${subcommand}`);
730
+ return 1;
731
+ };
732
+
733
+ // src/tui/launcher.ts
734
+ var import_node_child_process2 = require("child_process");
735
+ var import_node_fs = __toESM(require("fs"), 1);
736
+ var import_node_module = require("module");
737
+ var import_node_path2 = __toESM(require("path"), 1);
738
+ var import_storage3 = require("@codelia/storage");
739
+ var resolveTuiBinaryName = (platform) => platform === "win32" ? "codelia-tui.exe" : "codelia-tui";
740
+ var TUI_PACKAGE_BY_TARGET = {
741
+ "darwin-arm64": "@codelia/tui-darwin-arm64",
742
+ "darwin-x64": "@codelia/tui-darwin-x64",
743
+ "linux-arm64": "@codelia/tui-linux-arm64",
744
+ "linux-x64": "@codelia/tui-linux-x64",
745
+ "win32-x64": "@codelia/tui-win32-x64"
746
+ };
747
+ var describeError3 = (error) => error instanceof Error ? error.message : String(error);
748
+ var splitArgs = (value) => value.split(/\s+/).map((part) => part.trim()).filter((part) => part.length > 0);
749
+ var quoteShellWord = (value) => `'${value.replace(/'/g, "'\\''")}'`;
750
+ var isExecutable = (candidate) => {
751
+ try {
752
+ import_node_fs.default.accessSync(candidate, import_node_fs.default.constants.X_OK);
753
+ return true;
754
+ } catch {
755
+ return false;
756
+ }
757
+ };
758
+ var initStorage = async () => {
759
+ try {
760
+ const paths = (0, import_storage3.resolveStoragePaths)();
761
+ await (0, import_storage3.ensureStorageDirs)(paths);
762
+ } catch (error) {
763
+ console.warn(
764
+ `Failed to initialize storage directories: ${describeError3(error)}`
765
+ );
766
+ }
767
+ };
768
+ var resolveCliPackageRoot = () => {
769
+ const scriptPath = process.argv[1];
770
+ if (!scriptPath) return null;
771
+ try {
772
+ const resolvedScriptPath = import_node_fs.default.realpathSync(scriptPath);
773
+ const scriptDir = import_node_path2.default.dirname(resolvedScriptPath);
774
+ if (import_node_path2.default.basename(scriptDir) === "dist") {
775
+ return import_node_path2.default.dirname(scriptDir);
776
+ }
777
+ return scriptDir;
778
+ } catch {
779
+ return null;
780
+ }
781
+ };
782
+ var resolveRuntimeRequireBase = () => {
783
+ const cliPackageRoot = resolveCliPackageRoot();
784
+ if (cliPackageRoot) {
785
+ return import_node_path2.default.resolve(cliPackageRoot, "package.json");
786
+ }
787
+ return import_node_path2.default.resolve(process.cwd(), "package.json");
788
+ };
789
+ var runtimeRequire = (0, import_node_module.createRequire)(resolveRuntimeRequireBase());
790
+ var resolveBundledRuntimeEntry = () => {
791
+ try {
792
+ return runtimeRequire.resolve("@codelia/runtime");
793
+ } catch {
794
+ return null;
795
+ }
796
+ };
797
+ var resolveInstalledPackageJson = (packageName) => {
798
+ try {
799
+ return runtimeRequire.resolve(`${packageName}/package.json`);
800
+ } catch {
801
+ return null;
802
+ }
803
+ };
804
+ var resolvePlatformTuiPackageName = (platform = process.platform, arch = process.arch) => TUI_PACKAGE_BY_TARGET[`${platform}-${arch}`] ?? null;
805
+ var resolveOptionalTuiBinaryPath = (platform = process.platform, arch = process.arch, resolvePackageJson = resolveInstalledPackageJson) => {
806
+ const packageName = resolvePlatformTuiPackageName(platform, arch);
807
+ if (!packageName) {
808
+ return null;
809
+ }
810
+ const packageJsonPath = resolvePackageJson(packageName);
811
+ if (!packageJsonPath) {
812
+ return null;
813
+ }
814
+ return import_node_path2.default.resolve(
815
+ import_node_path2.default.dirname(packageJsonPath),
816
+ "bin",
817
+ resolveTuiBinaryName(platform)
818
+ );
819
+ };
820
+ var resolveRuntimeEnvForTui = (env, resolveRuntimeEntry = resolveBundledRuntimeEntry) => {
821
+ if (env.CODELIA_RUNTIME_CMD || env.CODELIA_RUNTIME_ARGS) {
822
+ return env;
823
+ }
824
+ const runtimeEntry = resolveRuntimeEntry();
825
+ if (!runtimeEntry) {
826
+ return env;
827
+ }
828
+ return {
829
+ ...env,
830
+ CODELIA_RUNTIME_CMD: process.execPath,
831
+ CODELIA_RUNTIME_ARGS: quoteShellWord(runtimeEntry)
832
+ };
833
+ };
834
+ var resolveTuiCommand = (options = {}) => {
835
+ const env = options.env ?? process.env;
836
+ if (env.CODELIA_TUI_CMD) {
837
+ return env.CODELIA_TUI_CMD;
838
+ }
839
+ const platform = options.platform ?? process.platform;
840
+ const arch = options.arch ?? process.arch;
841
+ const resolveOptionalBinary = options.resolveOptionalTuiBinary ?? resolveOptionalTuiBinaryPath;
842
+ const isExecutableCandidate = options.isExecutableCandidate ?? isExecutable;
843
+ const tuiBinaryName = resolveTuiBinaryName(platform);
844
+ const cwd = options.cwd ?? process.cwd();
845
+ const cliPackageRoot = options.cliPackageRoot === void 0 ? resolveCliPackageRoot() : options.cliPackageRoot;
846
+ const seen = /* @__PURE__ */ new Set();
847
+ const optionalTuiBinary = resolveOptionalBinary(platform, arch);
848
+ const candidates = [
849
+ ...optionalTuiBinary ? [optionalTuiBinary] : [],
850
+ ...cliPackageRoot ? [
851
+ import_node_path2.default.resolve(cliPackageRoot, "dist/bin", tuiBinaryName),
852
+ import_node_path2.default.resolve(
853
+ cliPackageRoot,
854
+ "../../crates/tui/target/release",
855
+ tuiBinaryName
856
+ ),
857
+ import_node_path2.default.resolve(
858
+ cliPackageRoot,
859
+ "../../crates/tui/target/debug",
860
+ tuiBinaryName
861
+ )
862
+ ] : [],
863
+ import_node_path2.default.resolve(cwd, "target/release", tuiBinaryName),
864
+ import_node_path2.default.resolve(cwd, "target/debug", tuiBinaryName),
865
+ import_node_path2.default.resolve(cwd, "crates/tui/target/release", tuiBinaryName),
866
+ import_node_path2.default.resolve(cwd, "crates/tui/target/debug", tuiBinaryName)
867
+ ];
868
+ for (const candidate of candidates) {
869
+ if (seen.has(candidate)) continue;
870
+ seen.add(candidate);
871
+ if (isExecutableCandidate(candidate)) {
872
+ return candidate;
873
+ }
874
+ }
875
+ return tuiBinaryName;
876
+ };
877
+ var runTui = (args2) => {
878
+ void initStorage();
879
+ const tuiCmd = resolveTuiCommand();
880
+ const tuiArgs = process.env.CODELIA_TUI_ARGS ? splitArgs(process.env.CODELIA_TUI_ARGS) : [];
881
+ const child = (0, import_node_child_process2.spawn)(tuiCmd, [...tuiArgs, ...args2], {
882
+ stdio: "inherit",
883
+ env: resolveRuntimeEnvForTui(process.env)
884
+ });
885
+ child.on("exit", (code) => {
886
+ process.exitCode = code ?? 0;
887
+ });
888
+ child.on("error", (error) => {
889
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code ?? "") : "";
890
+ const expectedPackage = resolvePlatformTuiPackageName();
891
+ console.error(`Failed to launch TUI (${tuiCmd}): ${describeError3(error)}`);
892
+ if (code === "EACCES") {
893
+ console.error(
894
+ "Permission denied while launching TUI. Verify execute permission and PATH entries."
895
+ );
896
+ } else if (code === "ENOENT") {
897
+ console.error(
898
+ expectedPackage ? `TUI binary not found. Ensure \`${expectedPackage}\` is installed, or run \`bun run build\` (or \`bun run tui:build\`) and retry.` : "TUI binary not found. Run `bun run build` (or `bun run tui:build`) and retry."
899
+ );
900
+ }
901
+ console.error(
902
+ "Set CODELIA_TUI_CMD/CODELIA_TUI_ARGS to use a custom TUI command."
903
+ );
904
+ process.exitCode = 1;
905
+ });
906
+ };
907
+
908
+ // src/index.ts
909
+ var args = process.argv.slice(2);
910
+ var main = async () => {
911
+ if (args[0] === "mcp") {
912
+ const exitCode = await runMcpCommand(args.slice(1));
913
+ process.exitCode = exitCode;
914
+ return;
915
+ }
916
+ runTui(args);
917
+ };
918
+ void main();