@sunasteriskrnd/takumi 0.13.0 → 0.14.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.js +1369 -144
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19815,7 +19815,7 @@ var package_default;
|
|
|
19815
19815
|
var init_package = __esm(() => {
|
|
19816
19816
|
package_default = {
|
|
19817
19817
|
name: "@sunasteriskrnd/takumi",
|
|
19818
|
-
version: "0.
|
|
19818
|
+
version: "0.14.0",
|
|
19819
19819
|
description: "CLI tool for bootstrapping and managing Takumi projects",
|
|
19820
19820
|
type: "module",
|
|
19821
19821
|
repository: {
|
|
@@ -48344,7 +48344,12 @@ async function runPkceFlow(options2) {
|
|
|
48344
48344
|
codeChallenge: run.pair.challenge,
|
|
48345
48345
|
port: run.port
|
|
48346
48346
|
});
|
|
48347
|
-
|
|
48347
|
+
options2.onAuthUrl?.(url);
|
|
48348
|
+
try {
|
|
48349
|
+
await openBrowser(url);
|
|
48350
|
+
} catch (err) {
|
|
48351
|
+
options2.onOpenBrowserError?.(err instanceof Error ? err : new Error(String(err)));
|
|
48352
|
+
}
|
|
48348
48353
|
const callback = await Promise.race([received, timeoutPromise]);
|
|
48349
48354
|
const code = callback.searchParams.get("code");
|
|
48350
48355
|
const callbackState = callback.searchParams.get("state");
|
|
@@ -48364,7 +48369,9 @@ var init_pkce_flow = __esm(() => {
|
|
|
48364
48369
|
PkceTimeoutError = class PkceTimeoutError extends Error {
|
|
48365
48370
|
constructor() {
|
|
48366
48371
|
super(`Login timed out after 90s waiting for browser callback.
|
|
48367
|
-
` + " - Re-run `tkm auth login` if the browser opened but you didn't complete sign-in.\n" +
|
|
48372
|
+
` + " - Re-run `tkm auth login` if the browser opened but you didn't complete sign-in.\n" + ` - If the browser didn't open, copy the sign-in link printed above and open it
|
|
48373
|
+
` + ` manually in a browser on this machine.
|
|
48374
|
+
` + " - On a headless / WSL2 box without a browser, run `tkm auth login` from your\n" + " laptop and copy the token file to `<home>/.takumi/auth/token.json`.");
|
|
48368
48375
|
this.name = "PkceTimeoutError";
|
|
48369
48376
|
}
|
|
48370
48377
|
};
|
|
@@ -48397,7 +48404,7 @@ async function exchangeCodeForSession(webUrl, code, codeVerifier) {
|
|
|
48397
48404
|
}
|
|
48398
48405
|
return await res.json();
|
|
48399
48406
|
}
|
|
48400
|
-
async function runLoginFlow() {
|
|
48407
|
+
async function runLoginFlow(hooks = {}) {
|
|
48401
48408
|
const serverUrl = getServerUrl();
|
|
48402
48409
|
const pair = generatePkcePair();
|
|
48403
48410
|
const state = generateState();
|
|
@@ -48405,6 +48412,8 @@ async function runLoginFlow() {
|
|
|
48405
48412
|
const callback = await runPkceFlow({
|
|
48406
48413
|
state,
|
|
48407
48414
|
pair,
|
|
48415
|
+
onAuthUrl: hooks.onAuthUrl,
|
|
48416
|
+
onOpenBrowserError: hooks.onOpenBrowserError,
|
|
48408
48417
|
authUrl: ({ state: s, codeChallenge, port }) => `${serverUrl}/cli/auth?state=${encodeURIComponent(s)}` + `&code_challenge=${encodeURIComponent(codeChallenge)}` + `&port=${port}&prompt=1` + `&cli_version=${encodeURIComponent(cliVersion)}`
|
|
48409
48418
|
});
|
|
48410
48419
|
const tokens = await exchangeCodeForSession(serverUrl, callback.code, pair.verifier);
|
|
@@ -48439,15 +48448,29 @@ function reportLoginError(err) {
|
|
|
48439
48448
|
async function login() {
|
|
48440
48449
|
oe("Takumi login");
|
|
48441
48450
|
const spinner = de();
|
|
48442
|
-
|
|
48451
|
+
let spinning = false;
|
|
48443
48452
|
try {
|
|
48444
|
-
const result = await runLoginFlow(
|
|
48445
|
-
|
|
48453
|
+
const result = await runLoginFlow({
|
|
48454
|
+
onAuthUrl: (url) => {
|
|
48455
|
+
f2.step("Open this link to sign in (copy it if the browser doesn't open):");
|
|
48456
|
+
console.log(`
|
|
48457
|
+
${url}
|
|
48458
|
+
`);
|
|
48459
|
+
spinner.start("Waiting for sign-in in browser (90s timeout)…");
|
|
48460
|
+
spinning = true;
|
|
48461
|
+
},
|
|
48462
|
+
onOpenBrowserError: () => {
|
|
48463
|
+
spinner.message("Browser didn't open — copy the link above to sign in (90s timeout)…");
|
|
48464
|
+
}
|
|
48465
|
+
});
|
|
48466
|
+
if (spinning)
|
|
48467
|
+
spinner.stop("Signed in.");
|
|
48446
48468
|
if (result.email)
|
|
48447
48469
|
f2.success(`Signed in as ${result.email}`);
|
|
48448
48470
|
$e("You're ready to go.");
|
|
48449
48471
|
} catch (err) {
|
|
48450
|
-
|
|
48472
|
+
if (spinning)
|
|
48473
|
+
spinner.stop("Login failed.", 1);
|
|
48451
48474
|
reportLoginError(err);
|
|
48452
48475
|
process.exitCode = 1;
|
|
48453
48476
|
}
|
|
@@ -48631,7 +48654,18 @@ async function ensureAuthenticated(options2) {
|
|
|
48631
48654
|
if (!mod.runLoginFlow) {
|
|
48632
48655
|
throw new Error("Login command is not available in this build. Run `tkm auth login` manually.");
|
|
48633
48656
|
}
|
|
48634
|
-
await
|
|
48657
|
+
const { log: log2 } = await Promise.resolve().then(() => (init_dist2(), exports_dist));
|
|
48658
|
+
await mod.runLoginFlow({
|
|
48659
|
+
onAuthUrl: (url) => {
|
|
48660
|
+
log2.step("Open this link to sign in (copy it if the browser doesn't open):");
|
|
48661
|
+
console.log(`
|
|
48662
|
+
${url}
|
|
48663
|
+
`);
|
|
48664
|
+
},
|
|
48665
|
+
onOpenBrowserError: () => {
|
|
48666
|
+
log2.warn("Browser didn't open — copy the link above to sign in.");
|
|
48667
|
+
}
|
|
48668
|
+
});
|
|
48635
48669
|
});
|
|
48636
48670
|
await runLogin();
|
|
48637
48671
|
const token = await getToken({ ...options2, forceConfigRefresh: false });
|
|
@@ -49807,6 +49841,73 @@ var init_artifact_command_help = __esm(() => {
|
|
|
49807
49841
|
};
|
|
49808
49842
|
});
|
|
49809
49843
|
|
|
49844
|
+
// src/domains/help/commands/mcp-command-help.ts
|
|
49845
|
+
var mcpCommandHelp;
|
|
49846
|
+
var init_mcp_command_help = __esm(() => {
|
|
49847
|
+
mcpCommandHelp = {
|
|
49848
|
+
name: "mcp",
|
|
49849
|
+
description: "Manage internal MCP connectors for coding agents (add|list|remove)",
|
|
49850
|
+
usage: "tkm mcp <add|list|remove> [service] [options] [-- <extra args...>]",
|
|
49851
|
+
examples: [
|
|
49852
|
+
{
|
|
49853
|
+
command: "tkm mcp list",
|
|
49854
|
+
description: "Show available internal MCP services and per-agent configured state"
|
|
49855
|
+
},
|
|
49856
|
+
{
|
|
49857
|
+
command: "tkm mcp add meet-plus",
|
|
49858
|
+
description: "Register the 'meet-plus' MCP with the current project's coding agent(s)"
|
|
49859
|
+
},
|
|
49860
|
+
{
|
|
49861
|
+
command: "tkm mcp add playwright -- --browser msedge --headless",
|
|
49862
|
+
description: "Add a stdio MCP with extra launch args appended after the registry defaults"
|
|
49863
|
+
}
|
|
49864
|
+
],
|
|
49865
|
+
optionGroups: [
|
|
49866
|
+
{
|
|
49867
|
+
title: "Actions",
|
|
49868
|
+
options: [
|
|
49869
|
+
{
|
|
49870
|
+
flags: "add <service> [-- <extra args...>]",
|
|
49871
|
+
description: "Add a registry service to detected agent(s); argv after -- is appended to a stdio service's args (e.g. -- --browser msedge)"
|
|
49872
|
+
},
|
|
49873
|
+
{
|
|
49874
|
+
flags: "list",
|
|
49875
|
+
description: "List registry services with per-agent configured status"
|
|
49876
|
+
},
|
|
49877
|
+
{
|
|
49878
|
+
flags: "remove <service>",
|
|
49879
|
+
description: "Remove a service from detected agent(s); absent entries are skipped"
|
|
49880
|
+
}
|
|
49881
|
+
]
|
|
49882
|
+
},
|
|
49883
|
+
{
|
|
49884
|
+
title: "Options",
|
|
49885
|
+
options: [
|
|
49886
|
+
{
|
|
49887
|
+
flags: "-a, --agent <agents...>",
|
|
49888
|
+
description: "Target specific agent(s): claude-code | codex (repeatable)"
|
|
49889
|
+
},
|
|
49890
|
+
{
|
|
49891
|
+
flags: "-s, --scope <scope>",
|
|
49892
|
+
description: "Config scope: local | user | project (default: user; agent-dependent)"
|
|
49893
|
+
},
|
|
49894
|
+
{ flags: "-y, --yes", description: "Non-interactive mode: skip selection prompts" },
|
|
49895
|
+
{ flags: "--json", description: "Machine-readable JSON output (list only)" }
|
|
49896
|
+
]
|
|
49897
|
+
}
|
|
49898
|
+
],
|
|
49899
|
+
sections: [
|
|
49900
|
+
{
|
|
49901
|
+
title: "Registry source",
|
|
49902
|
+
content: `Cache-first: an existing local cache (<TAKUMI_HOME>/mcp/registry-cache.json) is
|
|
49903
|
+
` + `served immediately while a background request refreshes it for the next run.
|
|
49904
|
+
` + `With no cache yet, the CLI fetches the Takumi server directly and reports an
|
|
49905
|
+
` + "error when the server is unreachable (no stale in-package fallback)."
|
|
49906
|
+
}
|
|
49907
|
+
]
|
|
49908
|
+
};
|
|
49909
|
+
});
|
|
49910
|
+
|
|
49810
49911
|
// src/domains/help/commands/index.ts
|
|
49811
49912
|
var init_commands2 = __esm(() => {
|
|
49812
49913
|
init_init_command_help();
|
|
@@ -49817,6 +49918,7 @@ var init_commands2 = __esm(() => {
|
|
|
49817
49918
|
init_config_command_help();
|
|
49818
49919
|
init_auth_command_help();
|
|
49819
49920
|
init_artifact_command_help();
|
|
49921
|
+
init_mcp_command_help();
|
|
49820
49922
|
init_common_options();
|
|
49821
49923
|
});
|
|
49822
49924
|
|
|
@@ -49836,7 +49938,8 @@ var init_help_commands = __esm(() => {
|
|
|
49836
49938
|
doctor: doctorCommandHelp,
|
|
49837
49939
|
uninstall: uninstallCommandHelp,
|
|
49838
49940
|
auth: authCommandHelp,
|
|
49839
|
-
artifact: artifactCommandHelp
|
|
49941
|
+
artifact: artifactCommandHelp,
|
|
49942
|
+
mcp: mcpCommandHelp
|
|
49840
49943
|
};
|
|
49841
49944
|
});
|
|
49842
49945
|
|
|
@@ -53485,8 +53588,47 @@ function tryOpenBrowser(target) {
|
|
|
53485
53588
|
}
|
|
53486
53589
|
|
|
53487
53590
|
// src/domains/sessions/server.ts
|
|
53591
|
+
init_logger();
|
|
53488
53592
|
import * as http from "node:http";
|
|
53489
53593
|
|
|
53594
|
+
// src/domains/sessions/analytics-daily-by-model.ts
|
|
53595
|
+
function buildDailyByModel(aggregates, oldestAllowedMs) {
|
|
53596
|
+
const byDayModel = new Map;
|
|
53597
|
+
for (const agg of aggregates) {
|
|
53598
|
+
const ms = startedMs(agg?.startedAt);
|
|
53599
|
+
if (ms === null)
|
|
53600
|
+
continue;
|
|
53601
|
+
const perModel = agg?.tokensByModel;
|
|
53602
|
+
if (!perModel || typeof perModel !== "object")
|
|
53603
|
+
continue;
|
|
53604
|
+
const date = dayKey(ms);
|
|
53605
|
+
if (Date.parse(`${date}T00:00:00.000Z`) < oldestAllowedMs)
|
|
53606
|
+
continue;
|
|
53607
|
+
let byModel = byDayModel.get(date);
|
|
53608
|
+
if (!byModel) {
|
|
53609
|
+
byModel = new Map;
|
|
53610
|
+
byDayModel.set(date, byModel);
|
|
53611
|
+
}
|
|
53612
|
+
for (const [model, mtok] of Object.entries(perModel)) {
|
|
53613
|
+
let acc = byModel.get(model);
|
|
53614
|
+
if (!acc) {
|
|
53615
|
+
acc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
53616
|
+
byModel.set(model, acc);
|
|
53617
|
+
}
|
|
53618
|
+
accumulateTokens(acc, normalizeTokens(mtok));
|
|
53619
|
+
}
|
|
53620
|
+
}
|
|
53621
|
+
const rows = [];
|
|
53622
|
+
for (const [date, byModel] of byDayModel) {
|
|
53623
|
+
for (const [model, tokens] of byModel) {
|
|
53624
|
+
if (sumTokens(tokens) > 0)
|
|
53625
|
+
rows.push({ date, model, tokens });
|
|
53626
|
+
}
|
|
53627
|
+
}
|
|
53628
|
+
rows.sort((a3, b3) => a3.date.localeCompare(b3.date) || a3.model.localeCompare(b3.model));
|
|
53629
|
+
return rows;
|
|
53630
|
+
}
|
|
53631
|
+
|
|
53490
53632
|
// src/domains/sessions/analytics.ts
|
|
53491
53633
|
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
53492
53634
|
var PER_DAY_MAX = 371;
|
|
@@ -53521,16 +53663,26 @@ function accumulateTokens(a3, b3) {
|
|
|
53521
53663
|
a3.cacheRead += b3.cacheRead;
|
|
53522
53664
|
a3.cacheWrite += b3.cacheWrite;
|
|
53523
53665
|
}
|
|
53524
|
-
function
|
|
53666
|
+
function buildDenseWindow(byDay, startDayMs, endDayMs) {
|
|
53525
53667
|
const out = [];
|
|
53526
|
-
|
|
53527
|
-
|
|
53528
|
-
const date = dayKey(todayMs - i * DAY_MS);
|
|
53668
|
+
for (let ms = startDayMs;ms <= endDayMs; ms += DAY_MS) {
|
|
53669
|
+
const date = dayKey(ms);
|
|
53529
53670
|
out.push({ date, count: byDay.get(date) ?? 0 });
|
|
53530
53671
|
}
|
|
53531
53672
|
return out;
|
|
53532
53673
|
}
|
|
53533
|
-
function
|
|
53674
|
+
function windowBounds(now, range) {
|
|
53675
|
+
if (!range) {
|
|
53676
|
+
const todayMs = Date.parse(`${dayKey(now)}T00:00:00.000Z`);
|
|
53677
|
+
return { start: todayMs - (LAST_N_DAYS - 1) * DAY_MS, end: todayMs };
|
|
53678
|
+
}
|
|
53679
|
+
const lo = Math.min(range.from, range.to);
|
|
53680
|
+
const hi = Math.max(range.from, range.to);
|
|
53681
|
+
const end = Date.parse(`${dayKey(hi)}T00:00:00.000Z`);
|
|
53682
|
+
const start = Math.max(Date.parse(`${dayKey(lo)}T00:00:00.000Z`), end - (PER_DAY_MAX - 1) * DAY_MS);
|
|
53683
|
+
return { start, end };
|
|
53684
|
+
}
|
|
53685
|
+
function foldAnalytics(aggregates, now, range) {
|
|
53534
53686
|
const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
53535
53687
|
const eventsByDay = new Map;
|
|
53536
53688
|
const tokensByDow = new Map;
|
|
@@ -53585,10 +53737,12 @@ function foldAnalytics(aggregates, now) {
|
|
|
53585
53737
|
const perDay = [...eventsByDay.entries()].filter(([date]) => Date.parse(`${date}T00:00:00.000Z`) >= oldestAllowedMs).map(([date, count]) => ({ date, count })).sort((a3, b3) => a3.date.localeCompare(b3.date));
|
|
53586
53738
|
const byProjectAll = [...tokensByProject.entries()].map(([label, tokens]) => ({ label, tokens })).sort((a3, b3) => b3.tokens - a3.tokens || a3.label.localeCompare(b3.label));
|
|
53587
53739
|
const byProject = byProjectAll.slice(0, TOP_PROJECTS);
|
|
53588
|
-
const
|
|
53740
|
+
const { start: windowStart, end: windowEnd } = windowBounds(now, range);
|
|
53741
|
+
const daily = buildDenseWindow(tokensByDay, windowStart, windowEnd);
|
|
53589
53742
|
const rankedModels = [...tokensByModel.entries()].map(([label, tokens]) => ({ label, tokens, total: sumTokens(tokens) })).filter((m2) => m2.total > 0).sort((a3, b3) => b3.total - a3.total || a3.label.localeCompare(b3.label)).slice(0, TOP_PROJECTS);
|
|
53590
53743
|
const byModel = rankedModels.map(({ label, total }) => ({ label, tokens: total }));
|
|
53591
53744
|
const byModelDetailed = rankedModels.map(({ label, tokens }) => ({ label, tokens }));
|
|
53745
|
+
const dailyByModel = buildDailyByModel(aggregates, oldestAllowedMs);
|
|
53592
53746
|
const cacheDenom = totals.cacheRead + totals.input;
|
|
53593
53747
|
const cacheHitRate = cacheDenom > 0 ? totals.cacheRead / cacheDenom : 0;
|
|
53594
53748
|
return {
|
|
@@ -53602,7 +53756,7 @@ function foldAnalytics(aggregates, now) {
|
|
|
53602
53756
|
events: {
|
|
53603
53757
|
total: eventTotal,
|
|
53604
53758
|
perDay,
|
|
53605
|
-
last30:
|
|
53759
|
+
last30: buildDenseWindow(eventsByDay, windowStart, windowEnd)
|
|
53606
53760
|
},
|
|
53607
53761
|
tokens: {
|
|
53608
53762
|
byType: { ...totals },
|
|
@@ -53610,7 +53764,8 @@ function foldAnalytics(aggregates, now) {
|
|
|
53610
53764
|
byProject,
|
|
53611
53765
|
daily,
|
|
53612
53766
|
byModel,
|
|
53613
|
-
byModelDetailed
|
|
53767
|
+
byModelDetailed,
|
|
53768
|
+
dailyByModel
|
|
53614
53769
|
},
|
|
53615
53770
|
window: {
|
|
53616
53771
|
earliest: earliestMs === null ? null : new Date(earliestMs).toISOString(),
|
|
@@ -54912,6 +55067,24 @@ function invalidate() {
|
|
|
54912
55067
|
}
|
|
54913
55068
|
}
|
|
54914
55069
|
|
|
55070
|
+
// src/domains/sessions/session-meta-filter.ts
|
|
55071
|
+
function filterSessionMetas(metas, filter) {
|
|
55072
|
+
return metas.filter((m2) => {
|
|
55073
|
+
if (filter.project !== undefined && m2.project !== filter.project)
|
|
55074
|
+
return false;
|
|
55075
|
+
if (filter.from === undefined && filter.to === undefined)
|
|
55076
|
+
return true;
|
|
55077
|
+
const ms = m2.startedAt === null ? Number.NaN : Date.parse(m2.startedAt);
|
|
55078
|
+
if (Number.isNaN(ms))
|
|
55079
|
+
return false;
|
|
55080
|
+
if (filter.from !== undefined && ms < filter.from)
|
|
55081
|
+
return false;
|
|
55082
|
+
if (filter.to !== undefined && ms > filter.to)
|
|
55083
|
+
return false;
|
|
55084
|
+
return true;
|
|
55085
|
+
});
|
|
55086
|
+
}
|
|
55087
|
+
|
|
54915
55088
|
// src/domains/sessions/store/ingest.ts
|
|
54916
55089
|
import { createHash as createHash9 } from "node:crypto";
|
|
54917
55090
|
import { closeSync as closeSync4, openSync as openSync4, readSync as readSync4, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
|
|
@@ -64676,6 +64849,32 @@ async function readStoreAggregates(db) {
|
|
|
64676
64849
|
get2(r2.session_id).agentCount += Number(r2.n ?? 0);
|
|
64677
64850
|
return map;
|
|
64678
64851
|
}
|
|
64852
|
+
async function attachSessionTokenTotals(db, list2) {
|
|
64853
|
+
const totals = await readSessionTokenTotals(db);
|
|
64854
|
+
return list2.map((s) => {
|
|
64855
|
+
const tokens = totals.get(s.id);
|
|
64856
|
+
return tokens ? { ...s, tokens } : s;
|
|
64857
|
+
});
|
|
64858
|
+
}
|
|
64859
|
+
async function readSessionTokenTotals(db) {
|
|
64860
|
+
const rows = await db.selectFrom("event").where("type", "=", "tokens").select((eb) => [
|
|
64861
|
+
"session_id",
|
|
64862
|
+
eb.fn.sum("input").as("input"),
|
|
64863
|
+
eb.fn.sum("output").as("output"),
|
|
64864
|
+
eb.fn.sum("cache_read").as("cache_read"),
|
|
64865
|
+
eb.fn.sum("cache_write").as("cache_write")
|
|
64866
|
+
]).groupBy("session_id").execute();
|
|
64867
|
+
const map = new Map;
|
|
64868
|
+
for (const r2 of rows) {
|
|
64869
|
+
map.set(r2.session_id, {
|
|
64870
|
+
input: Number(r2.input ?? 0),
|
|
64871
|
+
output: Number(r2.output ?? 0),
|
|
64872
|
+
cacheCreate: Number(r2.cache_write ?? 0),
|
|
64873
|
+
cacheRead: Number(r2.cache_read ?? 0)
|
|
64874
|
+
});
|
|
64875
|
+
}
|
|
64876
|
+
return map;
|
|
64877
|
+
}
|
|
64679
64878
|
function buildAggregatesFromStore(sessions, store) {
|
|
64680
64879
|
return sessions.map((s) => {
|
|
64681
64880
|
const a3 = store.get(s.id) ?? emptyStoreAggregate();
|
|
@@ -64692,30 +64891,52 @@ function buildAggregatesFromStore(sessions, store) {
|
|
|
64692
64891
|
}
|
|
64693
64892
|
|
|
64694
64893
|
// src/domains/sessions/analytics-source.ts
|
|
64695
|
-
var
|
|
64894
|
+
var DAY_MS2 = 24 * 60 * 60 * 1000;
|
|
64895
|
+
var DEFAULT_WINDOW_DAYS = 30;
|
|
64896
|
+
var cache2 = new Map;
|
|
64696
64897
|
var storeHandle = null;
|
|
64697
64898
|
function setStore(handle) {
|
|
64698
64899
|
storeHandle = handle;
|
|
64699
64900
|
}
|
|
64700
64901
|
function invalidate2() {
|
|
64701
|
-
cache2 =
|
|
64902
|
+
cache2 = new Map;
|
|
64702
64903
|
}
|
|
64703
|
-
|
|
64904
|
+
var cacheKey = (o2) => `${o2.from ?? ""}|${o2.to ?? ""}|${o2.project === undefined ? "" : `p:${o2.project}`}`;
|
|
64905
|
+
function rangeOf(o2, now) {
|
|
64906
|
+
if (o2.from === undefined && o2.to === undefined)
|
|
64907
|
+
return;
|
|
64908
|
+
const to = o2.to ?? now;
|
|
64909
|
+
const from = o2.from ?? to - (DEFAULT_WINDOW_DAYS - 1) * DAY_MS2;
|
|
64910
|
+
return { from, to };
|
|
64911
|
+
}
|
|
64912
|
+
async function computeFromStore(handle, opts, now) {
|
|
64913
|
+
const refresh2 = opts.refresh ?? false;
|
|
64704
64914
|
if (refresh2)
|
|
64705
64915
|
await ingestAll(handle, { refresh: true });
|
|
64706
64916
|
const sessions = await list({ refresh: refresh2 });
|
|
64917
|
+
const metas = filterSessionMetas(sessions.map((s) => ({ id: s.id, startedAt: s.startedAt, project: s.project })), { from: opts.from, to: opts.to, project: opts.project });
|
|
64707
64918
|
const storeAggs = await readStoreAggregates(handle.db);
|
|
64708
|
-
const
|
|
64709
|
-
return
|
|
64919
|
+
const payload = foldAnalytics(buildAggregatesFromStore(metas, storeAggs), now, rangeOf(opts, now));
|
|
64920
|
+
return {
|
|
64921
|
+
...payload,
|
|
64922
|
+
filters: {
|
|
64923
|
+
from: opts.from ?? null,
|
|
64924
|
+
to: opts.to ?? null,
|
|
64925
|
+
project: opts.project ?? null
|
|
64926
|
+
}
|
|
64927
|
+
};
|
|
64710
64928
|
}
|
|
64711
64929
|
async function getAnalytics(opts = {}) {
|
|
64712
|
-
|
|
64713
|
-
|
|
64930
|
+
const key = cacheKey(opts);
|
|
64931
|
+
const hit = cache2.get(key);
|
|
64932
|
+
if (hit && !opts.refresh)
|
|
64933
|
+
return hit;
|
|
64714
64934
|
if (!storeHandle)
|
|
64715
64935
|
throw new StoreUnavailableError;
|
|
64716
64936
|
const now = Date.now();
|
|
64717
|
-
|
|
64718
|
-
|
|
64937
|
+
const payload = await computeFromStore(storeHandle, opts, now);
|
|
64938
|
+
cache2.set(key, payload);
|
|
64939
|
+
return payload;
|
|
64719
64940
|
}
|
|
64720
64941
|
|
|
64721
64942
|
// src/domains/sessions/origin-allowlist.ts
|
|
@@ -64743,13 +64964,33 @@ function isAllowedOrigin(origin) {
|
|
|
64743
64964
|
return LOOPBACK_HOST_PATTERN.test(origin);
|
|
64744
64965
|
}
|
|
64745
64966
|
|
|
64967
|
+
// src/domains/sessions/range-params.ts
|
|
64968
|
+
function intParam(search, key) {
|
|
64969
|
+
const raw = search.get(key);
|
|
64970
|
+
if (raw === null || raw === "")
|
|
64971
|
+
return;
|
|
64972
|
+
const n = Number(raw);
|
|
64973
|
+
if (!Number.isInteger(n) || n < 0)
|
|
64974
|
+
return { error: `invalid ${key}: ${raw}` };
|
|
64975
|
+
return n;
|
|
64976
|
+
}
|
|
64977
|
+
function parseRangeProjectParams(search) {
|
|
64978
|
+
const from = intParam(search, "from");
|
|
64979
|
+
if (typeof from === "object")
|
|
64980
|
+
return from;
|
|
64981
|
+
const to = intParam(search, "to");
|
|
64982
|
+
if (typeof to === "object")
|
|
64983
|
+
return to;
|
|
64984
|
+
return { from, to, project: search.get("project") || undefined };
|
|
64985
|
+
}
|
|
64986
|
+
|
|
64746
64987
|
// src/domains/sessions/store/bridge-store.ts
|
|
64747
64988
|
init_logger();
|
|
64748
64989
|
|
|
64749
64990
|
// src/domains/sessions/store/prune.ts
|
|
64750
64991
|
import { existsSync as existsSync34, statSync as statSync6 } from "node:fs";
|
|
64751
64992
|
init_logger();
|
|
64752
|
-
var
|
|
64993
|
+
var DAY_MS3 = 24 * 60 * 60 * 1000;
|
|
64753
64994
|
var DEFAULT_RETENTION_DAYS = 180;
|
|
64754
64995
|
var SIZE_DELETE_BATCH = 500;
|
|
64755
64996
|
var SIZE_PRUNE_MAX_ITERS = 200;
|
|
@@ -64775,7 +65016,7 @@ async function runPrune(handle, opts = {}) {
|
|
|
64775
65016
|
const now = opts.now ?? Date.now();
|
|
64776
65017
|
const retentionDays = opts.retentionDays ?? DEFAULT_RETENTION_DAYS;
|
|
64777
65018
|
const dbPath = opts.dbPath ?? getConsoleStorePath();
|
|
64778
|
-
const cutoff = now - retentionDays *
|
|
65019
|
+
const cutoff = now - retentionDays * DAY_MS3;
|
|
64779
65020
|
await db.deleteFrom("event").where("ts", "<", cutoff).execute();
|
|
64780
65021
|
if (opts.maxSizeMb && opts.maxSizeMb > 0) {
|
|
64781
65022
|
const cap = opts.maxSizeMb * 1024 * 1024;
|
|
@@ -64953,6 +65194,17 @@ async function attachStore(deps = {}) {
|
|
|
64953
65194
|
}
|
|
64954
65195
|
|
|
64955
65196
|
// src/domains/sessions/server.ts
|
|
65197
|
+
var tokenStore = null;
|
|
65198
|
+
async function withTokenTotals(list2) {
|
|
65199
|
+
if (!tokenStore)
|
|
65200
|
+
return list2;
|
|
65201
|
+
try {
|
|
65202
|
+
return await attachSessionTokenTotals(tokenStore.db, list2);
|
|
65203
|
+
} catch (err) {
|
|
65204
|
+
logger.verbose(`console bridge: token totals enrichment failed (${String(err)})`);
|
|
65205
|
+
return list2;
|
|
65206
|
+
}
|
|
65207
|
+
}
|
|
64956
65208
|
var PORT_BUMP_MAX = 9;
|
|
64957
65209
|
function applyCorsHeaders(req, res) {
|
|
64958
65210
|
const origin = req.headers.origin;
|
|
@@ -65010,15 +65262,20 @@ async function route(req, res, version3) {
|
|
|
65010
65262
|
if (refresh2)
|
|
65011
65263
|
invalidate();
|
|
65012
65264
|
const list2 = await list({ refresh: refresh2 });
|
|
65013
|
-
sendJSON(res, 200, list2);
|
|
65265
|
+
sendJSON(res, 200, await withTokenTotals(list2));
|
|
65014
65266
|
return;
|
|
65015
65267
|
}
|
|
65016
65268
|
if (pathname === "/api/analytics") {
|
|
65017
65269
|
const refresh2 = url.searchParams.get("refresh") === "1";
|
|
65270
|
+
const rp = parseRangeProjectParams(url.searchParams);
|
|
65271
|
+
if ("error" in rp) {
|
|
65272
|
+
sendJSON(res, 400, { error: rp.error });
|
|
65273
|
+
return;
|
|
65274
|
+
}
|
|
65018
65275
|
if (refresh2)
|
|
65019
65276
|
invalidate2();
|
|
65020
65277
|
try {
|
|
65021
|
-
const payload = await getAnalytics({ refresh: refresh2 });
|
|
65278
|
+
const payload = await getAnalytics({ refresh: refresh2, ...rp });
|
|
65022
65279
|
sendJSON(res, 200, payload);
|
|
65023
65280
|
} catch (err) {
|
|
65024
65281
|
if (err instanceof StoreUnavailableError) {
|
|
@@ -65091,6 +65348,7 @@ function start(options2 = {}) {
|
|
|
65091
65348
|
close: async () => {
|
|
65092
65349
|
if (attached) {
|
|
65093
65350
|
setStore(null);
|
|
65351
|
+
tokenStore = null;
|
|
65094
65352
|
await attached.stop().catch(() => {});
|
|
65095
65353
|
}
|
|
65096
65354
|
await closeServer();
|
|
@@ -65101,8 +65359,10 @@ function start(options2 = {}) {
|
|
|
65101
65359
|
};
|
|
65102
65360
|
const doAttach = options2.attachStore ?? attachStore;
|
|
65103
65361
|
doAttach({ onInvalidate: () => invalidate2() }).then((attached) => {
|
|
65104
|
-
if (attached)
|
|
65362
|
+
if (attached) {
|
|
65105
65363
|
setStore(attached.handle);
|
|
65364
|
+
tokenStore = attached.handle;
|
|
65365
|
+
}
|
|
65106
65366
|
finish(attached);
|
|
65107
65367
|
}).catch(() => finish(null));
|
|
65108
65368
|
});
|
|
@@ -84144,22 +84404,984 @@ async function initCommand(options2) {
|
|
|
84144
84404
|
throw error;
|
|
84145
84405
|
}
|
|
84146
84406
|
}
|
|
84407
|
+
// src/domains/mcp/mcp-service.ts
|
|
84408
|
+
init_logger();
|
|
84409
|
+
|
|
84410
|
+
// src/domains/mcp/mcp-service-core.ts
|
|
84411
|
+
init_environment();
|
|
84412
|
+
init_logger();
|
|
84413
|
+
init_dist2();
|
|
84414
|
+
|
|
84415
|
+
// src/domains/mcp/agent-detector.ts
|
|
84416
|
+
init_environment();
|
|
84417
|
+
import { existsSync as existsSync58 } from "node:fs";
|
|
84418
|
+
import { join as join126 } from "node:path";
|
|
84419
|
+
|
|
84420
|
+
// src/domains/mcp/shell-out.ts
|
|
84421
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
84422
|
+
var DEFAULT_TIMEOUT_MS7 = 60000;
|
|
84423
|
+
var PROBE_TIMEOUT_MS = 5000;
|
|
84424
|
+
var SERVICE_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
84425
|
+
function isValidServiceName(name2) {
|
|
84426
|
+
return SERVICE_NAME_PATTERN.test(name2);
|
|
84427
|
+
}
|
|
84428
|
+
function runAgentCli(bin, args, timeoutMs = DEFAULT_TIMEOUT_MS7) {
|
|
84429
|
+
const result = spawnSync5(bin, args, {
|
|
84430
|
+
timeout: timeoutMs,
|
|
84431
|
+
encoding: "utf-8",
|
|
84432
|
+
shell: false,
|
|
84433
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
84434
|
+
});
|
|
84435
|
+
if (result.error) {
|
|
84436
|
+
return {
|
|
84437
|
+
ok: false,
|
|
84438
|
+
code: null,
|
|
84439
|
+
stdout: "",
|
|
84440
|
+
stderr: result.error.message
|
|
84441
|
+
};
|
|
84442
|
+
}
|
|
84443
|
+
return {
|
|
84444
|
+
ok: result.status === 0,
|
|
84445
|
+
code: result.status,
|
|
84446
|
+
stdout: result.stdout ?? "",
|
|
84447
|
+
stderr: result.stderr ?? ""
|
|
84448
|
+
};
|
|
84449
|
+
}
|
|
84450
|
+
function isBinaryOnPath(bin) {
|
|
84451
|
+
const probeCmd = process.platform === "win32" ? "where" : "which";
|
|
84452
|
+
const result = spawnSync5(probeCmd, [bin], {
|
|
84453
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
84454
|
+
encoding: "utf-8",
|
|
84455
|
+
shell: false
|
|
84456
|
+
});
|
|
84457
|
+
if (result.error || result.status !== 0)
|
|
84458
|
+
return false;
|
|
84459
|
+
if (process.platform !== "win32")
|
|
84460
|
+
return true;
|
|
84461
|
+
return (result.stdout ?? "").split(/\r?\n/).some((line) => /\.(exe|com)$/i.test(line.trim()));
|
|
84462
|
+
}
|
|
84463
|
+
function isAlreadyExistsError(result) {
|
|
84464
|
+
return /already exists/i.test(`${result.stderr}
|
|
84465
|
+
${result.stdout}`);
|
|
84466
|
+
}
|
|
84467
|
+
|
|
84468
|
+
// src/domains/mcp/types.ts
|
|
84469
|
+
init_zod();
|
|
84470
|
+
var McpAgentSchema = exports_external.enum(["claude-code", "codex"]);
|
|
84471
|
+
var ALL_MCP_AGENTS = ["claude-code", "codex"];
|
|
84472
|
+
var McpScopeSchema = exports_external.enum(["local", "user", "project"]);
|
|
84473
|
+
var ALL_MCP_SCOPES = ["local", "user", "project"];
|
|
84474
|
+
var DEFAULT_MCP_SCOPE = "user";
|
|
84475
|
+
var McpTransportSchema = exports_external.enum(["http", "sse", "stdio"]);
|
|
84476
|
+
var McpRemoteServiceEntrySchema = exports_external.object({
|
|
84477
|
+
name: exports_external.string(),
|
|
84478
|
+
description: exports_external.string(),
|
|
84479
|
+
transport: exports_external.enum(["http", "sse"]),
|
|
84480
|
+
url: exports_external.string().url()
|
|
84481
|
+
});
|
|
84482
|
+
var McpStdioServiceEntrySchema = exports_external.object({
|
|
84483
|
+
name: exports_external.string(),
|
|
84484
|
+
description: exports_external.string(),
|
|
84485
|
+
transport: exports_external.literal("stdio"),
|
|
84486
|
+
command: exports_external.string().min(1),
|
|
84487
|
+
args: exports_external.array(exports_external.string()).nullish()
|
|
84488
|
+
});
|
|
84489
|
+
var McpServiceEntrySchema = exports_external.union([
|
|
84490
|
+
McpRemoteServiceEntrySchema,
|
|
84491
|
+
McpStdioServiceEntrySchema
|
|
84492
|
+
]);
|
|
84493
|
+
var McpRegistrySchema = exports_external.object({
|
|
84494
|
+
services: exports_external.array(McpServiceEntrySchema)
|
|
84495
|
+
});
|
|
84496
|
+
|
|
84497
|
+
// src/domains/mcp/agent-detector.ts
|
|
84498
|
+
var AGENT_BINARY = {
|
|
84499
|
+
"claude-code": "claude",
|
|
84500
|
+
codex: "codex"
|
|
84501
|
+
};
|
|
84502
|
+
function getAgentConfigPath(agent) {
|
|
84503
|
+
const home6 = getHomeDirectoryFromEnv();
|
|
84504
|
+
if (!home6)
|
|
84505
|
+
return null;
|
|
84506
|
+
switch (agent) {
|
|
84507
|
+
case "claude-code":
|
|
84508
|
+
return join126(home6, ".claude.json");
|
|
84509
|
+
case "codex":
|
|
84510
|
+
return join126(home6, ".codex", "config.toml");
|
|
84511
|
+
default: {
|
|
84512
|
+
const _exhaustive = agent;
|
|
84513
|
+
return _exhaustive;
|
|
84514
|
+
}
|
|
84515
|
+
}
|
|
84516
|
+
}
|
|
84517
|
+
var defaultCheckers = {
|
|
84518
|
+
binaryOnPath: isBinaryOnPath,
|
|
84519
|
+
configExists: existsSync58
|
|
84520
|
+
};
|
|
84521
|
+
function isAgentPresent(agent, checkers = defaultCheckers) {
|
|
84522
|
+
if (checkers.binaryOnPath(AGENT_BINARY[agent])) {
|
|
84523
|
+
return true;
|
|
84524
|
+
}
|
|
84525
|
+
const configPath = getAgentConfigPath(agent);
|
|
84526
|
+
return configPath !== null && checkers.configExists(configPath);
|
|
84527
|
+
}
|
|
84528
|
+
function detectAgents(checkers = defaultCheckers) {
|
|
84529
|
+
return ALL_MCP_AGENTS.filter((agent) => isAgentPresent(agent, checkers));
|
|
84530
|
+
}
|
|
84531
|
+
var PROJECT_MARKERS = {
|
|
84532
|
+
"claude-code": [".claude", "CLAUDE.md"],
|
|
84533
|
+
codex: [".codex", "AGENTS.md"]
|
|
84534
|
+
};
|
|
84535
|
+
function detectProjectAgents(cwd2 = process.cwd(), exists2 = existsSync58) {
|
|
84536
|
+
return ALL_MCP_AGENTS.filter((agent) => PROJECT_MARKERS[agent].some((marker) => exists2(join126(cwd2, marker))));
|
|
84537
|
+
}
|
|
84538
|
+
|
|
84539
|
+
// src/domains/mcp/registry-client.ts
|
|
84540
|
+
init_zod();
|
|
84541
|
+
init_logger();
|
|
84542
|
+
init_auth_client();
|
|
84543
|
+
|
|
84544
|
+
// src/domains/mcp/registry-cache.ts
|
|
84545
|
+
init_logger();
|
|
84546
|
+
init_paths2();
|
|
84547
|
+
import { promises as fs33 } from "node:fs";
|
|
84548
|
+
import { join as join127 } from "node:path";
|
|
84549
|
+
function getCacheDir() {
|
|
84550
|
+
return join127(getConfigDir(), "mcp");
|
|
84551
|
+
}
|
|
84552
|
+
function getCachePath2() {
|
|
84553
|
+
return join127(getCacheDir(), "registry-cache.json");
|
|
84554
|
+
}
|
|
84555
|
+
async function readCachedRegistry() {
|
|
84556
|
+
try {
|
|
84557
|
+
const raw = await fs33.readFile(getCachePath2(), "utf8");
|
|
84558
|
+
const parsed = JSON.parse(raw);
|
|
84559
|
+
const result = McpRegistrySchema.safeParse({ services: parsed.services });
|
|
84560
|
+
if (!result.success)
|
|
84561
|
+
return null;
|
|
84562
|
+
return {
|
|
84563
|
+
registry: result.data,
|
|
84564
|
+
cachedAt: typeof parsed.cachedAt === "number" ? parsed.cachedAt : 0
|
|
84565
|
+
};
|
|
84566
|
+
} catch (err) {
|
|
84567
|
+
if (err?.code === "ENOENT")
|
|
84568
|
+
return null;
|
|
84569
|
+
logger.verbose(`registry-cache: read failed (${err instanceof Error ? err.message : String(err)})`);
|
|
84570
|
+
return null;
|
|
84571
|
+
}
|
|
84572
|
+
}
|
|
84573
|
+
async function writeCachedRegistry(registry) {
|
|
84574
|
+
const dir = getCacheDir();
|
|
84575
|
+
await fs33.mkdir(dir, { recursive: true, mode: 448 });
|
|
84576
|
+
const body = { services: registry.services, cachedAt: Date.now() };
|
|
84577
|
+
await fs33.writeFile(getCachePath2(), JSON.stringify(body, null, 2), {
|
|
84578
|
+
mode: 384,
|
|
84579
|
+
encoding: "utf8"
|
|
84580
|
+
});
|
|
84581
|
+
}
|
|
84582
|
+
|
|
84583
|
+
// src/domains/mcp/writers/json-config-file.ts
|
|
84584
|
+
import { readFile as readFile44 } from "node:fs/promises";
|
|
84585
|
+
function toMessage(error) {
|
|
84586
|
+
return error instanceof Error ? error.message : String(error);
|
|
84587
|
+
}
|
|
84588
|
+
function isEnoent(error) {
|
|
84589
|
+
return error?.code === "ENOENT";
|
|
84590
|
+
}
|
|
84591
|
+
async function readJsonConfigFile(path11) {
|
|
84592
|
+
let content;
|
|
84593
|
+
try {
|
|
84594
|
+
content = await readFile44(path11, "utf-8");
|
|
84595
|
+
} catch (error) {
|
|
84596
|
+
if (isEnoent(error))
|
|
84597
|
+
return { ok: true, raw: {} };
|
|
84598
|
+
return { ok: false, detail: toMessage(error) };
|
|
84599
|
+
}
|
|
84600
|
+
try {
|
|
84601
|
+
const parsed = JSON.parse(content);
|
|
84602
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
84603
|
+
return { ok: true, raw: parsed };
|
|
84604
|
+
}
|
|
84605
|
+
return { ok: false, detail: `Refusing to overwrite malformed ${path11}: not a JSON object` };
|
|
84606
|
+
} catch (error) {
|
|
84607
|
+
return { ok: false, detail: `Refusing to overwrite malformed ${path11}: ${toMessage(error)}` };
|
|
84608
|
+
}
|
|
84609
|
+
}
|
|
84610
|
+
function extractMcpServers(raw) {
|
|
84611
|
+
const servers = raw.mcpServers;
|
|
84612
|
+
return servers && typeof servers === "object" && !Array.isArray(servers) ? servers : {};
|
|
84613
|
+
}
|
|
84614
|
+
|
|
84615
|
+
// src/domains/mcp/registry-client.ts
|
|
84616
|
+
class RegistryUnavailableError extends Error {
|
|
84617
|
+
constructor(cause) {
|
|
84618
|
+
super(`Cannot load the MCP registry: ${cause}. Check your network connection (the registry is fetched from the Takumi server) and retry.`);
|
|
84619
|
+
this.name = "RegistryUnavailableError";
|
|
84620
|
+
}
|
|
84621
|
+
}
|
|
84622
|
+
var REGISTRY_PATH2 = "/api/v1/mcp/registry";
|
|
84623
|
+
var REGISTRY_FETCH_TIMEOUT_MS = 3000;
|
|
84624
|
+
var LooseRegistrySchema = exports_external.object({ services: exports_external.array(exports_external.unknown()) });
|
|
84625
|
+
function parseRegistryLenient(data) {
|
|
84626
|
+
const loose = LooseRegistrySchema.parse(data);
|
|
84627
|
+
const services = [];
|
|
84628
|
+
for (const raw of loose.services) {
|
|
84629
|
+
const parsed = McpServiceEntrySchema.safeParse(raw);
|
|
84630
|
+
if (parsed.success) {
|
|
84631
|
+
services.push(parsed.data);
|
|
84632
|
+
} else {
|
|
84633
|
+
const name2 = raw?.name;
|
|
84634
|
+
logger.verbose(`registry: dropped invalid entry ${typeof name2 === "string" ? `"${name2}"` : "(unnamed)"}`);
|
|
84635
|
+
}
|
|
84636
|
+
}
|
|
84637
|
+
if (loose.services.length > 0 && services.length === 0) {
|
|
84638
|
+
throw new Error("registry: every entry failed validation (schema drift?)");
|
|
84639
|
+
}
|
|
84640
|
+
return { services };
|
|
84641
|
+
}
|
|
84642
|
+
async function fetchRemoteRegistry() {
|
|
84643
|
+
const res = await fetch(`${getServerUrl()}${REGISTRY_PATH2}`, {
|
|
84644
|
+
headers: { "X-TKM-Client": "takumi-cli/mcp" },
|
|
84645
|
+
signal: AbortSignal.timeout(REGISTRY_FETCH_TIMEOUT_MS)
|
|
84646
|
+
});
|
|
84647
|
+
if (!res.ok)
|
|
84648
|
+
throw new Error(`registry: HTTP ${res.status}`);
|
|
84649
|
+
return parseRegistryLenient(await res.json());
|
|
84650
|
+
}
|
|
84651
|
+
async function refreshCache() {
|
|
84652
|
+
try {
|
|
84653
|
+
await writeCachedRegistry(await fetchRemoteRegistry());
|
|
84654
|
+
return true;
|
|
84655
|
+
} catch (err) {
|
|
84656
|
+
logger.verbose(`registry: revalidate skipped (${toMessage(err)})`);
|
|
84657
|
+
return false;
|
|
84658
|
+
}
|
|
84659
|
+
}
|
|
84660
|
+
async function getRegistryWithSource() {
|
|
84661
|
+
const cached = await readCachedRegistry();
|
|
84662
|
+
if (cached) {
|
|
84663
|
+
return { registry: cached.registry, source: "cache", revalidated: refreshCache() };
|
|
84664
|
+
}
|
|
84665
|
+
try {
|
|
84666
|
+
const remote = await fetchRemoteRegistry();
|
|
84667
|
+
try {
|
|
84668
|
+
await writeCachedRegistry(remote);
|
|
84669
|
+
} catch (err) {
|
|
84670
|
+
logger.verbose(`registry: cache write skipped (${toMessage(err)})`);
|
|
84671
|
+
}
|
|
84672
|
+
return { registry: remote, source: "server" };
|
|
84673
|
+
} catch (err) {
|
|
84674
|
+
throw new RegistryUnavailableError(toMessage(err));
|
|
84675
|
+
}
|
|
84676
|
+
}
|
|
84677
|
+
function resolveService(registry, name2) {
|
|
84678
|
+
return registry.services.find((service) => service.name === name2);
|
|
84679
|
+
}
|
|
84680
|
+
|
|
84681
|
+
// src/domains/mcp/writers/claude-config-file.ts
|
|
84682
|
+
import { existsSync as existsSync59 } from "node:fs";
|
|
84683
|
+
import { mkdir as mkdir29, writeFile as writeFile31 } from "node:fs/promises";
|
|
84684
|
+
import { dirname as dirname36, join as join128 } from "node:path";
|
|
84685
|
+
var AGENT = "claude-code";
|
|
84686
|
+
var LOCAL_SCOPE_FALLBACK_WARNING = "claude CLI not found; local scope isn't representable via direct file write, wrote to user-level ~/.claude.json instead";
|
|
84687
|
+
function userConfigPath() {
|
|
84688
|
+
return getAgentConfigPath(AGENT);
|
|
84689
|
+
}
|
|
84690
|
+
function projectConfigPath() {
|
|
84691
|
+
return join128(process.cwd(), ".mcp.json");
|
|
84692
|
+
}
|
|
84693
|
+
function configPathFor(scope) {
|
|
84694
|
+
return scope === "project" ? projectConfigPath() : userConfigPath();
|
|
84695
|
+
}
|
|
84696
|
+
function withLocalScopeFallbackWarning(detail, scope) {
|
|
84697
|
+
if (scope !== "local")
|
|
84698
|
+
return detail;
|
|
84699
|
+
return detail ? `${detail} (${LOCAL_SCOPE_FALLBACK_WARNING})` : LOCAL_SCOPE_FALLBACK_WARNING;
|
|
84700
|
+
}
|
|
84701
|
+
async function fileAdd(entry, scope) {
|
|
84702
|
+
const path11 = configPathFor(scope);
|
|
84703
|
+
if (!path11) {
|
|
84704
|
+
return { agent: AGENT, status: "failed", detail: "Could not resolve home directory" };
|
|
84705
|
+
}
|
|
84706
|
+
const existing = await readJsonConfigFile(path11);
|
|
84707
|
+
if (!existing.ok) {
|
|
84708
|
+
return { agent: AGENT, status: "failed", detail: existing.detail };
|
|
84709
|
+
}
|
|
84710
|
+
try {
|
|
84711
|
+
const servers = extractMcpServers(existing.raw);
|
|
84712
|
+
servers[entry.name] = entry.transport === "stdio" ? {
|
|
84713
|
+
type: "stdio",
|
|
84714
|
+
command: entry.command,
|
|
84715
|
+
...entry.args?.length ? { args: entry.args } : {}
|
|
84716
|
+
} : { type: entry.transport, url: entry.url };
|
|
84717
|
+
await mkdir29(dirname36(path11), { recursive: true });
|
|
84718
|
+
await writeFile31(path11, JSON.stringify({ ...existing.raw, mcpServers: servers }, null, 2), "utf-8");
|
|
84719
|
+
return {
|
|
84720
|
+
agent: AGENT,
|
|
84721
|
+
status: "added",
|
|
84722
|
+
detail: withLocalScopeFallbackWarning(`Wrote ${path11} (file fallback)`, scope)
|
|
84723
|
+
};
|
|
84724
|
+
} catch (error) {
|
|
84725
|
+
return { agent: AGENT, status: "failed", detail: toMessage(error) };
|
|
84726
|
+
}
|
|
84727
|
+
}
|
|
84728
|
+
async function fileRemove(name2, scope) {
|
|
84729
|
+
const path11 = configPathFor(scope);
|
|
84730
|
+
if (!path11 || !existsSync59(path11)) {
|
|
84731
|
+
return { agent: AGENT, status: "skipped", detail: "No config file present" };
|
|
84732
|
+
}
|
|
84733
|
+
const existing = await readJsonConfigFile(path11);
|
|
84734
|
+
if (!existing.ok) {
|
|
84735
|
+
return { agent: AGENT, status: "failed", detail: existing.detail };
|
|
84736
|
+
}
|
|
84737
|
+
try {
|
|
84738
|
+
const servers = extractMcpServers(existing.raw);
|
|
84739
|
+
if (!(name2 in servers)) {
|
|
84740
|
+
return { agent: AGENT, status: "skipped", detail: "Not configured" };
|
|
84741
|
+
}
|
|
84742
|
+
delete servers[name2];
|
|
84743
|
+
await writeFile31(path11, JSON.stringify({ ...existing.raw, mcpServers: servers }, null, 2), "utf-8");
|
|
84744
|
+
return {
|
|
84745
|
+
agent: AGENT,
|
|
84746
|
+
status: "removed",
|
|
84747
|
+
detail: withLocalScopeFallbackWarning(`Updated ${path11} (file fallback)`, scope)
|
|
84748
|
+
};
|
|
84749
|
+
} catch (error) {
|
|
84750
|
+
return { agent: AGENT, status: "failed", detail: toMessage(error) };
|
|
84751
|
+
}
|
|
84752
|
+
}
|
|
84753
|
+
async function fileConfigured(name2, scope) {
|
|
84754
|
+
const path11 = configPathFor(scope);
|
|
84755
|
+
if (!path11)
|
|
84756
|
+
return false;
|
|
84757
|
+
const existing = await readJsonConfigFile(path11);
|
|
84758
|
+
return existing.ok && name2 in extractMcpServers(existing.raw);
|
|
84759
|
+
}
|
|
84760
|
+
async function listUserConfigured() {
|
|
84761
|
+
const path11 = userConfigPath();
|
|
84762
|
+
if (!path11)
|
|
84763
|
+
return { has: () => false };
|
|
84764
|
+
const existing = await readJsonConfigFile(path11);
|
|
84765
|
+
if (!existing.ok)
|
|
84766
|
+
return { has: () => false };
|
|
84767
|
+
const servers = extractMcpServers(existing.raw);
|
|
84768
|
+
return { has: (name2) => (name2 in servers) };
|
|
84769
|
+
}
|
|
84770
|
+
|
|
84771
|
+
// src/domains/mcp/writers/claude-writer.ts
|
|
84772
|
+
var AGENT2 = "claude-code";
|
|
84773
|
+
var BIN = "claude";
|
|
84774
|
+
async function add(entry, opts) {
|
|
84775
|
+
if (!isValidServiceName(entry.name)) {
|
|
84776
|
+
return { agent: AGENT2, status: "failed", detail: `Invalid service name: ${entry.name}` };
|
|
84777
|
+
}
|
|
84778
|
+
if (opts.scope !== "local" && await fileConfigured(entry.name, opts.scope)) {
|
|
84779
|
+
return { agent: AGENT2, status: "skipped", detail: "already configured — no change" };
|
|
84780
|
+
}
|
|
84781
|
+
if (isBinaryOnPath(BIN)) {
|
|
84782
|
+
const argv = entry.transport === "stdio" ? [
|
|
84783
|
+
"mcp",
|
|
84784
|
+
"add",
|
|
84785
|
+
"--scope",
|
|
84786
|
+
opts.scope,
|
|
84787
|
+
entry.name,
|
|
84788
|
+
"--",
|
|
84789
|
+
entry.command,
|
|
84790
|
+
...entry.args ?? []
|
|
84791
|
+
] : [
|
|
84792
|
+
"mcp",
|
|
84793
|
+
"add",
|
|
84794
|
+
"--transport",
|
|
84795
|
+
entry.transport,
|
|
84796
|
+
entry.name,
|
|
84797
|
+
entry.url,
|
|
84798
|
+
"--scope",
|
|
84799
|
+
opts.scope
|
|
84800
|
+
];
|
|
84801
|
+
const result = runAgentCli(BIN, argv);
|
|
84802
|
+
if (result.ok) {
|
|
84803
|
+
return { agent: AGENT2, status: "added", detail: result.stdout.trim() || undefined };
|
|
84804
|
+
}
|
|
84805
|
+
if (isAlreadyExistsError(result)) {
|
|
84806
|
+
return { agent: AGENT2, status: "skipped", detail: "already configured — no change" };
|
|
84807
|
+
}
|
|
84808
|
+
return {
|
|
84809
|
+
agent: AGENT2,
|
|
84810
|
+
status: "failed",
|
|
84811
|
+
detail: result.stderr.trim() || `exit code ${result.code}`
|
|
84812
|
+
};
|
|
84813
|
+
}
|
|
84814
|
+
return fileAdd(entry, opts.scope);
|
|
84815
|
+
}
|
|
84816
|
+
async function remove10(name2, opts) {
|
|
84817
|
+
if (!isValidServiceName(name2)) {
|
|
84818
|
+
return { agent: AGENT2, status: "failed", detail: `Invalid service name: ${name2}` };
|
|
84819
|
+
}
|
|
84820
|
+
if (isBinaryOnPath(BIN)) {
|
|
84821
|
+
if (opts.scope !== "local" && !await fileConfigured(name2, opts.scope)) {
|
|
84822
|
+
return { agent: AGENT2, status: "skipped", detail: "Not configured" };
|
|
84823
|
+
}
|
|
84824
|
+
const result = runAgentCli(BIN, ["mcp", "remove", name2, "--scope", opts.scope]);
|
|
84825
|
+
if (result.ok) {
|
|
84826
|
+
return { agent: AGENT2, status: "removed", detail: result.stdout.trim() || undefined };
|
|
84827
|
+
}
|
|
84828
|
+
return {
|
|
84829
|
+
agent: AGENT2,
|
|
84830
|
+
status: "failed",
|
|
84831
|
+
detail: result.stderr.trim() || `exit code ${result.code}`
|
|
84832
|
+
};
|
|
84833
|
+
}
|
|
84834
|
+
return fileRemove(name2, opts.scope);
|
|
84835
|
+
}
|
|
84836
|
+
var claudeWriter = {
|
|
84837
|
+
agent: AGENT2,
|
|
84838
|
+
add,
|
|
84839
|
+
remove: remove10,
|
|
84840
|
+
listConfigured: listUserConfigured
|
|
84841
|
+
};
|
|
84842
|
+
|
|
84843
|
+
// src/domains/mcp/writers/codex-writer.ts
|
|
84844
|
+
init_path_safety();
|
|
84845
|
+
import { existsSync as existsSync60 } from "node:fs";
|
|
84846
|
+
import { readFile as readFile45, writeFile as writeFile32 } from "node:fs/promises";
|
|
84847
|
+
|
|
84848
|
+
// src/domains/mcp/writers/codex-config-file.ts
|
|
84849
|
+
function escapeRegex2(value) {
|
|
84850
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
84851
|
+
}
|
|
84852
|
+
function sectionRegex(header) {
|
|
84853
|
+
const escaped = escapeRegex2(header);
|
|
84854
|
+
return new RegExp(`\\n?^\\[${escaped}\\]\\s*\\r?\\n(?:(?!\\[)[^\\r\\n]*\\r?\\n?)*`, "gm");
|
|
84855
|
+
}
|
|
84856
|
+
function tomlString(value) {
|
|
84857
|
+
return JSON.stringify(value).replace(/\u007f/g, "\\u007F");
|
|
84858
|
+
}
|
|
84859
|
+
function buildMcpServerSection(name2, entry) {
|
|
84860
|
+
if (!isValidServiceName(name2)) {
|
|
84861
|
+
throw new Error(`Refusing to build TOML section for invalid service name: ${name2}`);
|
|
84862
|
+
}
|
|
84863
|
+
if (entry.transport === "stdio") {
|
|
84864
|
+
const argsLine = entry.args?.length ? `args = [${entry.args.map(tomlString).join(", ")}]
|
|
84865
|
+
` : "";
|
|
84866
|
+
return `[mcp_servers.${name2}]
|
|
84867
|
+
command = ${tomlString(entry.command)}
|
|
84868
|
+
${argsLine}`;
|
|
84869
|
+
}
|
|
84870
|
+
return `[mcp_servers.${name2}]
|
|
84871
|
+
url = ${tomlString(entry.url)}
|
|
84872
|
+
`;
|
|
84873
|
+
}
|
|
84874
|
+
function upsertMcpServerSection(content, name2, section) {
|
|
84875
|
+
const regex2 = sectionRegex(`mcp_servers.${name2}`);
|
|
84876
|
+
if (regex2.test(content)) {
|
|
84877
|
+
return content.replace(regex2, () => `
|
|
84878
|
+
${section}`).trimStart();
|
|
84879
|
+
}
|
|
84880
|
+
const separator = content.trim().length > 0 ? `
|
|
84881
|
+
|
|
84882
|
+
` : "";
|
|
84883
|
+
return `${content.trimEnd()}${separator}${section}`;
|
|
84884
|
+
}
|
|
84885
|
+
function removeMcpServerSection(content, name2) {
|
|
84886
|
+
const regex2 = sectionRegex(`mcp_servers.${name2}`);
|
|
84887
|
+
if (!regex2.test(content)) {
|
|
84888
|
+
return { content, existed: false };
|
|
84889
|
+
}
|
|
84890
|
+
return { content: content.replace(regex2, ""), existed: true };
|
|
84891
|
+
}
|
|
84892
|
+
function hasMcpServerSection(content, name2) {
|
|
84893
|
+
return sectionRegex(`mcp_servers.${name2}`).test(content);
|
|
84894
|
+
}
|
|
84895
|
+
|
|
84896
|
+
// src/domains/mcp/writers/codex-writer.ts
|
|
84897
|
+
var AGENT3 = "codex";
|
|
84898
|
+
var BIN2 = "codex";
|
|
84899
|
+
var SCOPE_WARNING = "codex has no local/project scope; wrote to user-level ~/.codex/config.toml";
|
|
84900
|
+
function configPath() {
|
|
84901
|
+
return getAgentConfigPath(AGENT3);
|
|
84902
|
+
}
|
|
84903
|
+
function withScopeWarning(detail, scope) {
|
|
84904
|
+
if (scope === "user")
|
|
84905
|
+
return detail;
|
|
84906
|
+
return detail ? `${detail} (${SCOPE_WARNING})` : SCOPE_WARNING;
|
|
84907
|
+
}
|
|
84908
|
+
async function fileAdd2(entry, scope) {
|
|
84909
|
+
const path11 = configPath();
|
|
84910
|
+
if (!path11) {
|
|
84911
|
+
return { agent: AGENT3, status: "failed", detail: "Could not resolve home directory" };
|
|
84912
|
+
}
|
|
84913
|
+
try {
|
|
84914
|
+
return await withCodexTargetLock2(path11, async () => {
|
|
84915
|
+
let existing = "";
|
|
84916
|
+
try {
|
|
84917
|
+
existing = await readFile45(path11, "utf-8");
|
|
84918
|
+
} catch {
|
|
84919
|
+
existing = "";
|
|
84920
|
+
}
|
|
84921
|
+
const section = buildMcpServerSection(entry.name, entry);
|
|
84922
|
+
const updated = upsertMcpServerSection(existing, entry.name, section);
|
|
84923
|
+
await writeFile32(path11, updated, "utf-8");
|
|
84924
|
+
return {
|
|
84925
|
+
agent: AGENT3,
|
|
84926
|
+
status: "added",
|
|
84927
|
+
detail: withScopeWarning(`Wrote ${path11} (file fallback)`, scope)
|
|
84928
|
+
};
|
|
84929
|
+
});
|
|
84930
|
+
} catch (error) {
|
|
84931
|
+
return { agent: AGENT3, status: "failed", detail: toMessage(error) };
|
|
84932
|
+
}
|
|
84933
|
+
}
|
|
84934
|
+
async function fileRemove2(name2) {
|
|
84935
|
+
const path11 = configPath();
|
|
84936
|
+
if (!path11 || !existsSync60(path11)) {
|
|
84937
|
+
return { agent: AGENT3, status: "skipped", detail: "No config file present" };
|
|
84938
|
+
}
|
|
84939
|
+
try {
|
|
84940
|
+
return await withCodexTargetLock2(path11, async () => {
|
|
84941
|
+
const existing = await readFile45(path11, "utf-8");
|
|
84942
|
+
const { content, existed } = removeMcpServerSection(existing, name2);
|
|
84943
|
+
if (!existed) {
|
|
84944
|
+
return {
|
|
84945
|
+
agent: AGENT3,
|
|
84946
|
+
status: "skipped",
|
|
84947
|
+
detail: "Not configured"
|
|
84948
|
+
};
|
|
84949
|
+
}
|
|
84950
|
+
await writeFile32(path11, content, "utf-8");
|
|
84951
|
+
return {
|
|
84952
|
+
agent: AGENT3,
|
|
84953
|
+
status: "removed",
|
|
84954
|
+
detail: `Updated ${path11} (file fallback)`
|
|
84955
|
+
};
|
|
84956
|
+
});
|
|
84957
|
+
} catch (error) {
|
|
84958
|
+
return { agent: AGENT3, status: "failed", detail: toMessage(error) };
|
|
84959
|
+
}
|
|
84960
|
+
}
|
|
84961
|
+
async function add2(entry, opts) {
|
|
84962
|
+
if (!isValidServiceName(entry.name)) {
|
|
84963
|
+
return { agent: AGENT3, status: "failed", detail: `Invalid service name: ${entry.name}` };
|
|
84964
|
+
}
|
|
84965
|
+
if (await isConfigured(entry.name)) {
|
|
84966
|
+
return { agent: AGENT3, status: "skipped", detail: "already configured — no change" };
|
|
84967
|
+
}
|
|
84968
|
+
if (isBinaryOnPath(BIN2)) {
|
|
84969
|
+
const argv = entry.transport === "stdio" ? ["mcp", "add", entry.name, "--", entry.command, ...entry.args ?? []] : ["mcp", "add", entry.name, "--url", entry.url];
|
|
84970
|
+
const result = runAgentCli(BIN2, argv, 300000);
|
|
84971
|
+
if (result.ok) {
|
|
84972
|
+
return {
|
|
84973
|
+
agent: AGENT3,
|
|
84974
|
+
status: "added",
|
|
84975
|
+
detail: withScopeWarning(result.stdout.trim() || undefined, opts.scope)
|
|
84976
|
+
};
|
|
84977
|
+
}
|
|
84978
|
+
if (isAlreadyExistsError(result)) {
|
|
84979
|
+
return { agent: AGENT3, status: "skipped", detail: "already configured — no change" };
|
|
84980
|
+
}
|
|
84981
|
+
return {
|
|
84982
|
+
agent: AGENT3,
|
|
84983
|
+
status: "failed",
|
|
84984
|
+
detail: result.stderr.trim() || `exit code ${result.code}`
|
|
84985
|
+
};
|
|
84986
|
+
}
|
|
84987
|
+
return fileAdd2(entry, opts.scope);
|
|
84988
|
+
}
|
|
84989
|
+
async function remove11(name2, opts) {
|
|
84990
|
+
if (!isValidServiceName(name2)) {
|
|
84991
|
+
return { agent: AGENT3, status: "failed", detail: `Invalid service name: ${name2}` };
|
|
84992
|
+
}
|
|
84993
|
+
if (isBinaryOnPath(BIN2)) {
|
|
84994
|
+
if (!(await listConfigured()).has(name2)) {
|
|
84995
|
+
return { agent: AGENT3, status: "skipped", detail: "Not configured" };
|
|
84996
|
+
}
|
|
84997
|
+
const result = runAgentCli(BIN2, ["mcp", "remove", name2]);
|
|
84998
|
+
if (result.ok) {
|
|
84999
|
+
return {
|
|
85000
|
+
agent: AGENT3,
|
|
85001
|
+
status: "removed",
|
|
85002
|
+
detail: withScopeWarning(result.stdout.trim() || undefined, opts.scope)
|
|
85003
|
+
};
|
|
85004
|
+
}
|
|
85005
|
+
return {
|
|
85006
|
+
agent: AGENT3,
|
|
85007
|
+
status: "failed",
|
|
85008
|
+
detail: result.stderr.trim() || `exit code ${result.code}`
|
|
85009
|
+
};
|
|
85010
|
+
}
|
|
85011
|
+
return fileRemove2(name2);
|
|
85012
|
+
}
|
|
85013
|
+
async function listConfigured() {
|
|
85014
|
+
const path11 = configPath();
|
|
85015
|
+
if (!path11 || !existsSync60(path11))
|
|
85016
|
+
return { has: () => false };
|
|
85017
|
+
try {
|
|
85018
|
+
const content = await readFile45(path11, "utf-8");
|
|
85019
|
+
return { has: (name2) => hasMcpServerSection(content, name2) };
|
|
85020
|
+
} catch {
|
|
85021
|
+
return { has: () => false };
|
|
85022
|
+
}
|
|
85023
|
+
}
|
|
85024
|
+
async function isConfigured(name2) {
|
|
85025
|
+
return (await listConfigured()).has(name2);
|
|
85026
|
+
}
|
|
85027
|
+
var codexWriter = {
|
|
85028
|
+
agent: AGENT3,
|
|
85029
|
+
add: add2,
|
|
85030
|
+
remove: remove11,
|
|
85031
|
+
listConfigured
|
|
85032
|
+
};
|
|
85033
|
+
|
|
85034
|
+
// src/domains/mcp/writers/writer-registry.ts
|
|
85035
|
+
var writers = {
|
|
85036
|
+
"claude-code": claudeWriter,
|
|
85037
|
+
codex: codexWriter
|
|
85038
|
+
};
|
|
85039
|
+
function getWriter(agent) {
|
|
85040
|
+
return writers[agent];
|
|
85041
|
+
}
|
|
85042
|
+
|
|
85043
|
+
// src/domains/mcp/mcp-service-core.ts
|
|
85044
|
+
var defaultDeps2 = {
|
|
85045
|
+
getRegistry: getRegistryWithSource,
|
|
85046
|
+
detectAgents,
|
|
85047
|
+
detectProjectAgents,
|
|
85048
|
+
getWriter
|
|
85049
|
+
};
|
|
85050
|
+
function renderResults(results) {
|
|
85051
|
+
for (const result of results) {
|
|
85052
|
+
const line = `${result.agent}: ${result.status}${result.detail ? ` — ${result.detail}` : ""}`;
|
|
85053
|
+
switch (result.status) {
|
|
85054
|
+
case "added":
|
|
85055
|
+
case "removed":
|
|
85056
|
+
logger.success(line);
|
|
85057
|
+
break;
|
|
85058
|
+
case "skipped":
|
|
85059
|
+
logger.warning(line);
|
|
85060
|
+
break;
|
|
85061
|
+
case "failed":
|
|
85062
|
+
logger.error(line);
|
|
85063
|
+
break;
|
|
85064
|
+
default: {
|
|
85065
|
+
const _exhaustive = result.status;
|
|
85066
|
+
}
|
|
85067
|
+
}
|
|
85068
|
+
}
|
|
85069
|
+
}
|
|
85070
|
+
async function selectAgents2(verb, entryName, candidates, opts) {
|
|
85071
|
+
if (opts.yes || isNonInteractive() || candidates.length <= 1) {
|
|
85072
|
+
return candidates;
|
|
85073
|
+
}
|
|
85074
|
+
const preposition = verb === "add" ? "to" : "from";
|
|
85075
|
+
const selected = await ae({
|
|
85076
|
+
message: `Select agents to ${verb} "${entryName}" ${preposition}:`,
|
|
85077
|
+
options: candidates.map((agent) => ({ value: agent, label: agent })),
|
|
85078
|
+
initialValues: candidates,
|
|
85079
|
+
required: false
|
|
85080
|
+
});
|
|
85081
|
+
if (lD(selected))
|
|
85082
|
+
return null;
|
|
85083
|
+
return selected;
|
|
85084
|
+
}
|
|
85085
|
+
async function resolveAgentTargets(verb, service, agentFilter, deps) {
|
|
85086
|
+
const { registry, revalidated } = await deps.getRegistry();
|
|
85087
|
+
const entry = resolveService(registry, service);
|
|
85088
|
+
if (!entry) {
|
|
85089
|
+
logger.error(`Unknown MCP service: ${service}`);
|
|
85090
|
+
logger.info(`Known services: ${registry.services.map((s3) => s3.name).join(", ") || "(none)"}`);
|
|
85091
|
+
return { ok: false, result: { results: [], exitCode: 1 }, revalidated };
|
|
85092
|
+
}
|
|
85093
|
+
if (agentFilter && agentFilter.length > 0) {
|
|
85094
|
+
return { ok: true, entry, targets: agentFilter, prompt: false, revalidated };
|
|
85095
|
+
}
|
|
85096
|
+
const installed = deps.detectAgents();
|
|
85097
|
+
const projectTargets = deps.detectProjectAgents().filter((agent) => installed.includes(agent));
|
|
85098
|
+
if (projectTargets.length > 0) {
|
|
85099
|
+
const action = verb === "add" ? "adding to" : "removing from";
|
|
85100
|
+
logger.info(`Detected ${projectTargets.join(" + ")} project → ${action} ${projectTargets.join(", ")}.`);
|
|
85101
|
+
return { ok: true, entry, targets: projectTargets, prompt: false, revalidated };
|
|
85102
|
+
}
|
|
85103
|
+
if (installed.length === 0) {
|
|
85104
|
+
logger.warning("No coding agents detected (claude-code / codex).");
|
|
85105
|
+
return { ok: false, result: { results: [], exitCode: 1 }, revalidated };
|
|
85106
|
+
}
|
|
85107
|
+
return { ok: true, entry, targets: installed, prompt: true, revalidated };
|
|
85108
|
+
}
|
|
85109
|
+
|
|
85110
|
+
// src/domains/mcp/mcp-service.ts
|
|
85111
|
+
async function runMutation(verb, opts, deps) {
|
|
85112
|
+
const resolved = await resolveAgentTargets(verb, opts.service, opts.agents, deps);
|
|
85113
|
+
try {
|
|
85114
|
+
if (!resolved.ok)
|
|
85115
|
+
return resolved.result;
|
|
85116
|
+
const { prompt } = resolved;
|
|
85117
|
+
let { entry } = resolved;
|
|
85118
|
+
let targets = resolved.targets;
|
|
85119
|
+
if (verb === "add" && opts.extraArgs && opts.extraArgs.length > 0) {
|
|
85120
|
+
if (entry.transport !== "stdio") {
|
|
85121
|
+
logger.error(`Extra args after "--" only apply to stdio MCP services; "${entry.name}" is ${entry.transport} (remote).`);
|
|
85122
|
+
return { results: [], exitCode: 1 };
|
|
85123
|
+
}
|
|
85124
|
+
entry = { ...entry, args: [...entry.args ?? [], ...opts.extraArgs] };
|
|
85125
|
+
}
|
|
85126
|
+
if (prompt) {
|
|
85127
|
+
const chosen = await selectAgents2(verb, entry.name, targets, { yes: opts.yes });
|
|
85128
|
+
if (chosen === null) {
|
|
85129
|
+
logger.info("Cancelled.");
|
|
85130
|
+
return { results: [], exitCode: 1 };
|
|
85131
|
+
}
|
|
85132
|
+
if (chosen.length === 0) {
|
|
85133
|
+
logger.warning(`No agents selected — nothing to ${verb}.`);
|
|
85134
|
+
return { results: [], exitCode: 1 };
|
|
85135
|
+
}
|
|
85136
|
+
targets = chosen;
|
|
85137
|
+
}
|
|
85138
|
+
const writerOpts = { scope: opts.scope };
|
|
85139
|
+
const results = [];
|
|
85140
|
+
for (const agent of targets) {
|
|
85141
|
+
const writer = deps.getWriter(agent);
|
|
85142
|
+
results.push(verb === "add" ? await writer.add(entry, writerOpts) : await writer.remove(entry.name, writerOpts));
|
|
85143
|
+
}
|
|
85144
|
+
renderResults(results);
|
|
85145
|
+
const allFailed = results.length > 0 && results.every((r2) => r2.status === "failed");
|
|
85146
|
+
return { results, exitCode: allFailed ? 1 : 0 };
|
|
85147
|
+
} finally {
|
|
85148
|
+
await resolved.revalidated;
|
|
85149
|
+
}
|
|
85150
|
+
}
|
|
85151
|
+
async function runAdd(opts, deps = defaultDeps2) {
|
|
85152
|
+
return runMutation("add", opts, deps);
|
|
85153
|
+
}
|
|
85154
|
+
async function runRemove(opts, deps = defaultDeps2) {
|
|
85155
|
+
return runMutation("remove", opts, deps);
|
|
85156
|
+
}
|
|
85157
|
+
async function runList(deps = defaultDeps2) {
|
|
85158
|
+
const { registry, source, revalidated } = await deps.getRegistry();
|
|
85159
|
+
const projectAgents = deps.detectProjectAgents();
|
|
85160
|
+
const detected = projectAgents.length > 0 ? projectAgents : deps.detectAgents();
|
|
85161
|
+
const lookups = await Promise.all(detected.map(async (agent) => ({
|
|
85162
|
+
agent,
|
|
85163
|
+
lookup: await deps.getWriter(agent).listConfigured()
|
|
85164
|
+
})));
|
|
85165
|
+
const entries = registry.services.map((entry) => ({
|
|
85166
|
+
entry,
|
|
85167
|
+
perAgent: lookups.map(({ agent, lookup }) => ({
|
|
85168
|
+
agent,
|
|
85169
|
+
configured: lookup.has(entry.name)
|
|
85170
|
+
}))
|
|
85171
|
+
}));
|
|
85172
|
+
return { entries, source, revalidated };
|
|
85173
|
+
}
|
|
85174
|
+
|
|
85175
|
+
// src/commands/mcp/mutation-command.ts
|
|
85176
|
+
init_logger();
|
|
85177
|
+
|
|
85178
|
+
// src/commands/mcp/agent-option.ts
|
|
85179
|
+
class InvalidAgentOptionError extends Error {
|
|
85180
|
+
invalidValues;
|
|
85181
|
+
constructor(invalidValues) {
|
|
85182
|
+
super(`Invalid --agent value(s): ${invalidValues.join(", ")}. Accepted agents: ${ALL_MCP_AGENTS.join(", ")}.`);
|
|
85183
|
+
this.invalidValues = invalidValues;
|
|
85184
|
+
this.name = "InvalidAgentOptionError";
|
|
85185
|
+
}
|
|
85186
|
+
}
|
|
85187
|
+
function normalizeAgentOption(agent) {
|
|
85188
|
+
if (agent === undefined)
|
|
85189
|
+
return;
|
|
85190
|
+
const values = Array.isArray(agent) ? agent : [agent];
|
|
85191
|
+
const invalid = values.filter((value) => !McpAgentSchema.safeParse(value).success);
|
|
85192
|
+
if (invalid.length > 0) {
|
|
85193
|
+
throw new InvalidAgentOptionError(invalid);
|
|
85194
|
+
}
|
|
85195
|
+
return values;
|
|
85196
|
+
}
|
|
85197
|
+
|
|
85198
|
+
// src/commands/mcp/scope-option.ts
|
|
85199
|
+
class InvalidScopeOptionError extends Error {
|
|
85200
|
+
invalidValue;
|
|
85201
|
+
constructor(invalidValue) {
|
|
85202
|
+
super(`Invalid --scope value: ${invalidValue}. Accepted scopes: ${ALL_MCP_SCOPES.join(", ")}.`);
|
|
85203
|
+
this.invalidValue = invalidValue;
|
|
85204
|
+
this.name = "InvalidScopeOptionError";
|
|
85205
|
+
}
|
|
85206
|
+
}
|
|
85207
|
+
function normalizeScopeOption(scope) {
|
|
85208
|
+
if (scope === undefined)
|
|
85209
|
+
return DEFAULT_MCP_SCOPE;
|
|
85210
|
+
const parsed = McpScopeSchema.safeParse(scope);
|
|
85211
|
+
if (!parsed.success) {
|
|
85212
|
+
throw new InvalidScopeOptionError(scope);
|
|
85213
|
+
}
|
|
85214
|
+
return parsed.data;
|
|
85215
|
+
}
|
|
85216
|
+
|
|
85217
|
+
// src/commands/mcp/mutation-command.ts
|
|
85218
|
+
async function mutationCommand(verb, service, options2 = {}) {
|
|
85219
|
+
if (!service) {
|
|
85220
|
+
logger.error(`Usage: tkm mcp ${verb} <service> [--agent <name>...] [-s, --scope local|user|project] [--yes]${verb === "add" ? " [-- <extra args...>]" : ""}`);
|
|
85221
|
+
process.exitCode = 1;
|
|
85222
|
+
return;
|
|
85223
|
+
}
|
|
85224
|
+
const extraArgs = options2["--"] ?? [];
|
|
85225
|
+
if (verb === "remove" && extraArgs.length > 0) {
|
|
85226
|
+
logger.warning(`Extra args after "--" only apply to add — ignored for remove.`);
|
|
85227
|
+
}
|
|
85228
|
+
let agents;
|
|
85229
|
+
try {
|
|
85230
|
+
agents = normalizeAgentOption(options2.agent);
|
|
85231
|
+
} catch (error) {
|
|
85232
|
+
if (error instanceof InvalidAgentOptionError) {
|
|
85233
|
+
logger.error(error.message);
|
|
85234
|
+
process.exitCode = 1;
|
|
85235
|
+
return;
|
|
85236
|
+
}
|
|
85237
|
+
throw error;
|
|
85238
|
+
}
|
|
85239
|
+
let scope;
|
|
85240
|
+
try {
|
|
85241
|
+
scope = normalizeScopeOption(options2.scope);
|
|
85242
|
+
} catch (error) {
|
|
85243
|
+
if (error instanceof InvalidScopeOptionError) {
|
|
85244
|
+
logger.error(error.message);
|
|
85245
|
+
process.exitCode = 1;
|
|
85246
|
+
return;
|
|
85247
|
+
}
|
|
85248
|
+
throw error;
|
|
85249
|
+
}
|
|
85250
|
+
const run2 = verb === "add" ? runAdd : runRemove;
|
|
85251
|
+
try {
|
|
85252
|
+
const { exitCode } = await run2({
|
|
85253
|
+
service,
|
|
85254
|
+
agents,
|
|
85255
|
+
scope,
|
|
85256
|
+
yes: options2.yes,
|
|
85257
|
+
extraArgs: verb === "add" ? extraArgs : undefined
|
|
85258
|
+
});
|
|
85259
|
+
process.exitCode = exitCode;
|
|
85260
|
+
} catch (error) {
|
|
85261
|
+
if (error instanceof RegistryUnavailableError) {
|
|
85262
|
+
logger.error(error.message);
|
|
85263
|
+
process.exitCode = 1;
|
|
85264
|
+
return;
|
|
85265
|
+
}
|
|
85266
|
+
throw error;
|
|
85267
|
+
}
|
|
85268
|
+
}
|
|
85269
|
+
|
|
85270
|
+
// src/commands/mcp/add-command.ts
|
|
85271
|
+
async function addCommand(service, options2 = {}) {
|
|
85272
|
+
await mutationCommand("add", service, options2);
|
|
85273
|
+
}
|
|
85274
|
+
// src/commands/mcp/list-command.ts
|
|
85275
|
+
init_logger();
|
|
85276
|
+
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
85277
|
+
function statusCell(configured) {
|
|
85278
|
+
return configured ? { value: "● configured", paint: import_picocolors25.default.green } : { value: "○ not set", paint: import_picocolors25.default.dim };
|
|
85279
|
+
}
|
|
85280
|
+
function renderTable(header, rows) {
|
|
85281
|
+
const lastCol = header.length - 1;
|
|
85282
|
+
const widths = header.map((h2, col) => Math.max(h2.length, ...rows.map((r2) => r2[col]?.value.length ?? 0)));
|
|
85283
|
+
const padPlain = (text, col) => col === lastCol ? text : text.padEnd(widths[col]);
|
|
85284
|
+
const headerLine = import_picocolors25.default.bold(header.map((h2, col) => padPlain(h2, col)).join(" ").trimEnd());
|
|
85285
|
+
const rowLine = (cells) => cells.map((cell, col) => {
|
|
85286
|
+
const padded = padPlain(cell.value, col);
|
|
85287
|
+
return cell.paint ? cell.paint(padded) : padded;
|
|
85288
|
+
}).join(" ").trimEnd();
|
|
85289
|
+
return [headerLine, ...rows.map(rowLine)].join(`
|
|
85290
|
+
`);
|
|
85291
|
+
}
|
|
85292
|
+
async function listCommand(options2 = {}) {
|
|
85293
|
+
let listed;
|
|
85294
|
+
try {
|
|
85295
|
+
listed = await runList();
|
|
85296
|
+
} catch (error) {
|
|
85297
|
+
if (error instanceof RegistryUnavailableError) {
|
|
85298
|
+
logger.error(error.message);
|
|
85299
|
+
process.exitCode = 1;
|
|
85300
|
+
return;
|
|
85301
|
+
}
|
|
85302
|
+
throw error;
|
|
85303
|
+
}
|
|
85304
|
+
const { entries, source, revalidated } = listed;
|
|
85305
|
+
if (options2.json) {
|
|
85306
|
+
const services = entries.map(({ entry, perAgent }) => ({
|
|
85307
|
+
name: entry.name,
|
|
85308
|
+
description: entry.description,
|
|
85309
|
+
transport: entry.transport,
|
|
85310
|
+
configured: Object.fromEntries(perAgent.map((p2) => [p2.agent, p2.configured]))
|
|
85311
|
+
}));
|
|
85312
|
+
process.stdout.write(`${JSON.stringify({ source, services })}
|
|
85313
|
+
`);
|
|
85314
|
+
await revalidated;
|
|
85315
|
+
return;
|
|
85316
|
+
}
|
|
85317
|
+
if (entries.length === 0) {
|
|
85318
|
+
logger.info("No MCP services in the registry.");
|
|
85319
|
+
return;
|
|
85320
|
+
}
|
|
85321
|
+
const agents = entries[0].perAgent.map((p2) => p2.agent);
|
|
85322
|
+
const header = ["NAME", "TRANSPORT", ...agents.map((a3) => a3.toUpperCase()), "DESCRIPTION"];
|
|
85323
|
+
const rows = entries.map(({ entry, perAgent }) => [
|
|
85324
|
+
{ value: entry.name },
|
|
85325
|
+
{ value: entry.transport },
|
|
85326
|
+
...perAgent.map((p2) => statusCell(p2.configured)),
|
|
85327
|
+
{ value: entry.description }
|
|
85328
|
+
]);
|
|
85329
|
+
logger.info(`MCP registry — ${entries.length} services (source: ${source})`);
|
|
85330
|
+
if (agents.length === 0) {
|
|
85331
|
+
logger.info("No coding agents detected (claude-code / codex).");
|
|
85332
|
+
}
|
|
85333
|
+
process.stdout.write(`${renderTable(header, rows)}
|
|
85334
|
+
`);
|
|
85335
|
+
await revalidated;
|
|
85336
|
+
}
|
|
85337
|
+
// src/commands/mcp/mcp-command.ts
|
|
85338
|
+
init_logger();
|
|
85339
|
+
|
|
85340
|
+
// src/commands/mcp/remove-command.ts
|
|
85341
|
+
async function removeCommand(service, options2 = {}) {
|
|
85342
|
+
await mutationCommand("remove", service, options2);
|
|
85343
|
+
}
|
|
85344
|
+
|
|
85345
|
+
// src/commands/mcp/mcp-command.ts
|
|
85346
|
+
function printUsage() {
|
|
85347
|
+
logger.error("Usage: tkm mcp <add|list|remove> [service] [options] — see `tkm mcp --help`");
|
|
85348
|
+
}
|
|
85349
|
+
async function mcpCommand(action, service, options2 = {}) {
|
|
85350
|
+
switch (action) {
|
|
85351
|
+
case "add":
|
|
85352
|
+
await addCommand(service, options2);
|
|
85353
|
+
break;
|
|
85354
|
+
case "list":
|
|
85355
|
+
await listCommand(options2);
|
|
85356
|
+
break;
|
|
85357
|
+
case "remove":
|
|
85358
|
+
await removeCommand(service, options2);
|
|
85359
|
+
break;
|
|
85360
|
+
case undefined:
|
|
85361
|
+
printUsage();
|
|
85362
|
+
process.exitCode = 1;
|
|
85363
|
+
break;
|
|
85364
|
+
default:
|
|
85365
|
+
logger.error(`Unknown mcp action: ${action}. Available: add, list, remove`);
|
|
85366
|
+
process.exitCode = 1;
|
|
85367
|
+
}
|
|
85368
|
+
}
|
|
84147
85369
|
// src/commands/plan/plan-command.ts
|
|
84148
85370
|
init_output_manager();
|
|
84149
|
-
import { existsSync as
|
|
84150
|
-
import { dirname as
|
|
85371
|
+
import { existsSync as existsSync65, statSync as statSync11 } from "node:fs";
|
|
85372
|
+
import { dirname as dirname42, join as join132, parse as parse4, resolve as resolve33 } from "node:path";
|
|
84151
85373
|
|
|
84152
85374
|
// src/commands/plan/plan-read-handlers.ts
|
|
84153
|
-
import { existsSync as
|
|
84154
|
-
import { basename as basename21, dirname as
|
|
85375
|
+
import { existsSync as existsSync64, statSync as statSync10 } from "node:fs";
|
|
85376
|
+
import { basename as basename21, dirname as dirname41, join as join131, relative as relative20, resolve as resolve31 } from "node:path";
|
|
84155
85377
|
|
|
84156
85378
|
// src/domains/plan-parser/index.ts
|
|
84157
|
-
import { dirname as
|
|
85379
|
+
import { dirname as dirname40 } from "node:path";
|
|
84158
85380
|
|
|
84159
85381
|
// src/domains/plan-parser/plan-table-parser.ts
|
|
84160
85382
|
var import_gray_matter5 = __toESM(require_gray_matter(), 1);
|
|
84161
85383
|
import { readFileSync as readFileSync23 } from "node:fs";
|
|
84162
|
-
import { dirname as
|
|
85384
|
+
import { dirname as dirname37, resolve as resolve30 } from "node:path";
|
|
84163
85385
|
function normalizeStatus(raw) {
|
|
84164
85386
|
const s3 = raw.toLowerCase().trim();
|
|
84165
85387
|
if (s3.includes("complete") || s3.includes("done") || s3.includes("✓") || s3.includes("✅")) {
|
|
@@ -84420,7 +85642,7 @@ function parseFormat4(content, planFilePath, options2) {
|
|
|
84420
85642
|
const hasCheck = /[✅✓]/.test(line);
|
|
84421
85643
|
current = { name: name2, status: hasCheck ? "completed" : "pending" };
|
|
84422
85644
|
} else if (fileMatch && current) {
|
|
84423
|
-
const planDir =
|
|
85645
|
+
const planDir = dirname37(planFilePath);
|
|
84424
85646
|
current.file = resolve30(planDir, fileMatch[1].trim());
|
|
84425
85647
|
} else if (statusMatch && current) {
|
|
84426
85648
|
current.status = normalizeStatus(statusMatch[2]);
|
|
@@ -84525,30 +85747,30 @@ function parsePhasesFromBody(body, dir, options2) {
|
|
|
84525
85747
|
}
|
|
84526
85748
|
function parsePlanFile(planFilePath, options2) {
|
|
84527
85749
|
const content = readFileSync23(planFilePath, "utf8");
|
|
84528
|
-
const dir =
|
|
85750
|
+
const dir = dirname37(planFilePath);
|
|
84529
85751
|
const { data: frontmatter, content: body } = import_gray_matter5.default(content);
|
|
84530
85752
|
const phases = parsePhasesFromBody(body, dir, options2);
|
|
84531
85753
|
return { frontmatter, phases };
|
|
84532
85754
|
}
|
|
84533
85755
|
// src/domains/plan-parser/plan-scanner.ts
|
|
84534
|
-
import { existsSync as
|
|
84535
|
-
import { join as
|
|
85756
|
+
import { existsSync as existsSync61, readdirSync as readdirSync10 } from "node:fs";
|
|
85757
|
+
import { join as join129 } from "node:path";
|
|
84536
85758
|
function scanPlanDir(dir) {
|
|
84537
|
-
if (!
|
|
85759
|
+
if (!existsSync61(dir))
|
|
84538
85760
|
return [];
|
|
84539
85761
|
try {
|
|
84540
|
-
return readdirSync10(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
85762
|
+
return readdirSync10(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join129(dir, entry.name, "plan.md")).filter(existsSync61);
|
|
84541
85763
|
} catch {
|
|
84542
85764
|
return [];
|
|
84543
85765
|
}
|
|
84544
85766
|
}
|
|
84545
85767
|
// src/domains/plan-parser/plan-validator.ts
|
|
84546
85768
|
var import_gray_matter6 = __toESM(require_gray_matter(), 1);
|
|
84547
|
-
import { existsSync as
|
|
84548
|
-
import { basename as basename19, dirname as
|
|
85769
|
+
import { existsSync as existsSync62, readFileSync as readFileSync24 } from "node:fs";
|
|
85770
|
+
import { basename as basename19, dirname as dirname38 } from "node:path";
|
|
84549
85771
|
function validatePlanFile(filePath, strict = false) {
|
|
84550
85772
|
const content = readFileSync24(filePath, "utf8");
|
|
84551
|
-
const dir =
|
|
85773
|
+
const dir = dirname38(filePath);
|
|
84552
85774
|
const issues = [];
|
|
84553
85775
|
const lines = content.split(`
|
|
84554
85776
|
`);
|
|
@@ -84584,7 +85806,7 @@ function validatePlanFile(filePath, strict = false) {
|
|
|
84584
85806
|
});
|
|
84585
85807
|
}
|
|
84586
85808
|
for (const phase of phases) {
|
|
84587
|
-
if (phase.file && !
|
|
85809
|
+
if (phase.file && !existsSync62(phase.file)) {
|
|
84588
85810
|
const fileBasename = basename19(phase.file);
|
|
84589
85811
|
const refLine = lines.findIndex((l2) => l2.includes(fileBasename));
|
|
84590
85812
|
issues.push({
|
|
@@ -84605,8 +85827,8 @@ function validatePlanFile(filePath, strict = false) {
|
|
|
84605
85827
|
// src/domains/plan-parser/plan-writer.ts
|
|
84606
85828
|
var import_gray_matter7 = __toESM(require_gray_matter(), 1);
|
|
84607
85829
|
import { mkdirSync as mkdirSync9, readFileSync as readFileSync25, writeFileSync as writeFileSync12 } from "node:fs";
|
|
84608
|
-
import { existsSync as
|
|
84609
|
-
import { basename as basename20, dirname as
|
|
85830
|
+
import { existsSync as existsSync63 } from "node:fs";
|
|
85831
|
+
import { basename as basename20, dirname as dirname39, join as join130 } from "node:path";
|
|
84610
85832
|
function phaseNameToFilename(id, name2) {
|
|
84611
85833
|
const numMatch = /^(\d+)([a-z]*)$/i.exec(id);
|
|
84612
85834
|
const num4 = numMatch ? numMatch[1] : id;
|
|
@@ -84714,12 +85936,12 @@ function scaffoldPlan(options2) {
|
|
|
84714
85936
|
mkdirSync9(dir, { recursive: true });
|
|
84715
85937
|
const resolvedPhases = resolvePhaseIds(options2.phases);
|
|
84716
85938
|
const optionsWithResolved = { ...options2, phases: resolvedPhases };
|
|
84717
|
-
const planFile =
|
|
85939
|
+
const planFile = join130(dir, "plan.md");
|
|
84718
85940
|
writeFileSync12(planFile, generatePlanMd(optionsWithResolved), "utf8");
|
|
84719
85941
|
const phaseFiles = [];
|
|
84720
85942
|
for (const phase of resolvedPhases) {
|
|
84721
85943
|
const filename = phaseNameToFilename(phase.id, phase.name);
|
|
84722
|
-
const phaseFile =
|
|
85944
|
+
const phaseFile = join130(dir, filename);
|
|
84723
85945
|
writeFileSync12(phaseFile, generatePhaseTemplate(phase), "utf8");
|
|
84724
85946
|
phaseFiles.push(phaseFile);
|
|
84725
85947
|
}
|
|
@@ -84785,9 +86007,9 @@ function updatePhaseStatus(planFile, phaseId, newStatus) {
|
|
|
84785
86007
|
const updatedFrontmatter = { ...frontmatter, status: planStatus };
|
|
84786
86008
|
const updatedContent = import_gray_matter7.default.stringify(updatedBody, updatedFrontmatter);
|
|
84787
86009
|
writeFileSync12(planFile, updatedContent, "utf8");
|
|
84788
|
-
const planDir =
|
|
86010
|
+
const planDir = dirname39(planFile);
|
|
84789
86011
|
const phaseFilename = phaseNameFilenameFromTableRow(updatedBody, phaseId, planDir);
|
|
84790
|
-
if (phaseFilename &&
|
|
86012
|
+
if (phaseFilename && existsSync63(phaseFilename)) {
|
|
84791
86013
|
updatePhaseFileFrontmatter(phaseFilename, newStatus);
|
|
84792
86014
|
}
|
|
84793
86015
|
}
|
|
@@ -84799,7 +86021,7 @@ function phaseNameFilenameFromTableRow(body, phaseId, planDir) {
|
|
|
84799
86021
|
continue;
|
|
84800
86022
|
const linkMatch = /\[([^\]]+)\]\(\.\/([^)]+)\)/.exec(row);
|
|
84801
86023
|
if (linkMatch)
|
|
84802
|
-
return
|
|
86024
|
+
return join130(planDir, linkMatch[2]);
|
|
84803
86025
|
}
|
|
84804
86026
|
return null;
|
|
84805
86027
|
}
|
|
@@ -84817,7 +86039,7 @@ function addPhase(planFile, name2, afterId) {
|
|
|
84817
86039
|
throw new Error("Non-canonical plan.md — cannot add phase");
|
|
84818
86040
|
}
|
|
84819
86041
|
const { data: frontmatter, content: body } = import_gray_matter7.default(raw);
|
|
84820
|
-
const planDir =
|
|
86042
|
+
const planDir = dirname39(planFile);
|
|
84821
86043
|
const existingIds = [];
|
|
84822
86044
|
for (const match of body.matchAll(/^\|\s*(\d+[a-z]?)\s*\|/gim)) {
|
|
84823
86045
|
existingIds.push(match[1].toLowerCase());
|
|
@@ -84880,7 +86102,7 @@ function addPhase(planFile, name2, afterId) {
|
|
|
84880
86102
|
`);
|
|
84881
86103
|
}
|
|
84882
86104
|
writeFileSync12(planFile, import_gray_matter7.default.stringify(updatedBody, frontmatter), "utf8");
|
|
84883
|
-
const phaseFilePath =
|
|
86105
|
+
const phaseFilePath = join130(planDir, filename);
|
|
84884
86106
|
writeFileSync12(phaseFilePath, generatePhaseTemplate({ id: phaseId, name: name2 }), "utf8");
|
|
84885
86107
|
return { phaseId, phaseFile: phaseFilePath };
|
|
84886
86108
|
}
|
|
@@ -84892,7 +86114,7 @@ function buildPlanSummary(planFile) {
|
|
|
84892
86114
|
const inProgress = phases.filter((p2) => p2.status === "in-progress").length;
|
|
84893
86115
|
const pending = phases.filter((p2) => p2.status === "pending").length;
|
|
84894
86116
|
return {
|
|
84895
|
-
planDir:
|
|
86117
|
+
planDir: dirname40(planFile),
|
|
84896
86118
|
planFile,
|
|
84897
86119
|
title: typeof frontmatter.title === "string" ? frontmatter.title : undefined,
|
|
84898
86120
|
description: typeof frontmatter.description === "string" ? frontmatter.description : undefined,
|
|
@@ -84908,7 +86130,7 @@ function buildPlanSummary(planFile) {
|
|
|
84908
86130
|
// src/commands/plan/plan-read-handlers.ts
|
|
84909
86131
|
init_logger();
|
|
84910
86132
|
init_output_manager();
|
|
84911
|
-
var
|
|
86133
|
+
var import_picocolors26 = __toESM(require_picocolors(), 1);
|
|
84912
86134
|
async function handleParse(target, options2) {
|
|
84913
86135
|
const planFile = resolvePlanFile(target);
|
|
84914
86136
|
if (!planFile) {
|
|
@@ -84929,9 +86151,9 @@ async function handleParse(target, options2) {
|
|
|
84929
86151
|
console.log(JSON.stringify({ file: relative20(process.cwd(), planFile), frontmatter, phases }, null, 2));
|
|
84930
86152
|
return;
|
|
84931
86153
|
}
|
|
84932
|
-
const title = typeof frontmatter.title === "string" ? frontmatter.title : basename21(
|
|
86154
|
+
const title = typeof frontmatter.title === "string" ? frontmatter.title : basename21(dirname41(planFile));
|
|
84933
86155
|
console.log();
|
|
84934
|
-
console.log(
|
|
86156
|
+
console.log(import_picocolors26.default.bold(` Plan: ${title}`));
|
|
84935
86157
|
console.log(` File: ${planFile}`);
|
|
84936
86158
|
console.log(` Phases found: ${phases.length}`);
|
|
84937
86159
|
console.log();
|
|
@@ -84962,7 +86184,7 @@ async function handleValidate(target, options2) {
|
|
|
84962
86184
|
return;
|
|
84963
86185
|
}
|
|
84964
86186
|
console.log();
|
|
84965
|
-
console.log(
|
|
86187
|
+
console.log(import_picocolors26.default.bold(` Validating: ${planFile}`));
|
|
84966
86188
|
console.log();
|
|
84967
86189
|
if (result.issues.length === 0) {
|
|
84968
86190
|
console.log(` [OK] No issues found — ${result.phases.length} phases detected`);
|
|
@@ -84976,7 +86198,7 @@ async function handleValidate(target, options2) {
|
|
|
84976
86198
|
}
|
|
84977
86199
|
}
|
|
84978
86200
|
console.log();
|
|
84979
|
-
const validStr = result.valid ?
|
|
86201
|
+
const validStr = result.valid ? import_picocolors26.default.green("[OK] Valid") : import_picocolors26.default.red("[X] Invalid");
|
|
84980
86202
|
console.log(` ${validStr} — ${result.issues.filter((i) => i.severity === "error").length} errors, ${result.issues.filter((i) => i.severity === "warning").length} warnings`);
|
|
84981
86203
|
console.log();
|
|
84982
86204
|
if (!result.valid)
|
|
@@ -84984,7 +86206,7 @@ async function handleValidate(target, options2) {
|
|
|
84984
86206
|
}
|
|
84985
86207
|
async function handleStatus(target, options2) {
|
|
84986
86208
|
const t = target ? resolve31(target) : null;
|
|
84987
|
-
const plansDir = t &&
|
|
86209
|
+
const plansDir = t && existsSync64(t) && statSync10(t).isDirectory() && !existsSync64(join131(t, "plan.md")) ? t : null;
|
|
84988
86210
|
if (plansDir) {
|
|
84989
86211
|
const planFiles = scanPlanDir(plansDir);
|
|
84990
86212
|
if (planFiles.length === 0) {
|
|
@@ -85003,20 +86225,20 @@ async function handleStatus(target, options2) {
|
|
|
85003
86225
|
return;
|
|
85004
86226
|
}
|
|
85005
86227
|
console.log();
|
|
85006
|
-
console.log(
|
|
86228
|
+
console.log(import_picocolors26.default.bold(` Plans in: ${plansDir}`));
|
|
85007
86229
|
console.log();
|
|
85008
86230
|
for (const pf of planFiles) {
|
|
85009
86231
|
try {
|
|
85010
86232
|
const s3 = buildPlanSummary(pf);
|
|
85011
86233
|
const bar = progressBar(s3.completed, s3.totalPhases);
|
|
85012
|
-
const title2 = s3.title ?? basename21(
|
|
85013
|
-
console.log(` ${
|
|
86234
|
+
const title2 = s3.title ?? basename21(dirname41(pf));
|
|
86235
|
+
console.log(` ${import_picocolors26.default.bold(title2)}`);
|
|
85014
86236
|
console.log(` ${bar}`);
|
|
85015
86237
|
if (s3.inProgress > 0)
|
|
85016
86238
|
console.log(` [~] ${s3.inProgress} in progress`);
|
|
85017
86239
|
console.log();
|
|
85018
86240
|
} catch {
|
|
85019
|
-
console.log(` [X] Failed to read: ${basename21(
|
|
86241
|
+
console.log(` [X] Failed to read: ${basename21(dirname41(pf))}`);
|
|
85020
86242
|
console.log();
|
|
85021
86243
|
}
|
|
85022
86244
|
}
|
|
@@ -85040,9 +86262,9 @@ async function handleStatus(target, options2) {
|
|
|
85040
86262
|
console.log(JSON.stringify(summary, null, 2));
|
|
85041
86263
|
return;
|
|
85042
86264
|
}
|
|
85043
|
-
const title = summary.title ?? basename21(
|
|
86265
|
+
const title = summary.title ?? basename21(dirname41(planFile));
|
|
85044
86266
|
console.log();
|
|
85045
|
-
console.log(
|
|
86267
|
+
console.log(import_picocolors26.default.bold(` ${title}`));
|
|
85046
86268
|
if (summary.status)
|
|
85047
86269
|
console.log(` Status: ${summary.status}`);
|
|
85048
86270
|
console.log();
|
|
@@ -85068,7 +86290,7 @@ async function handleKanban(target, _options) {
|
|
|
85068
86290
|
// src/commands/plan/plan-write-handlers.ts
|
|
85069
86291
|
import { basename as basename22, relative as relative21, resolve as resolve32 } from "node:path";
|
|
85070
86292
|
init_output_manager();
|
|
85071
|
-
var
|
|
86293
|
+
var import_picocolors27 = __toESM(require_picocolors(), 1);
|
|
85072
86294
|
async function handleCreate(target, options2) {
|
|
85073
86295
|
if (!options2.title) {
|
|
85074
86296
|
output.error("[X] --title is required for create");
|
|
@@ -85115,7 +86337,7 @@ async function handleCreate(target, options2) {
|
|
|
85115
86337
|
return;
|
|
85116
86338
|
}
|
|
85117
86339
|
console.log();
|
|
85118
|
-
console.log(
|
|
86340
|
+
console.log(import_picocolors27.default.bold(` [OK] Plan created: ${options2.title}`));
|
|
85119
86341
|
console.log(` Directory: ${resolve32(dir)}`);
|
|
85120
86342
|
console.log(` Phases: ${result.phaseFiles.length}`);
|
|
85121
86343
|
for (const f4 of result.phaseFiles) {
|
|
@@ -85215,22 +86437,22 @@ async function handleAddPhase(target, options2) {
|
|
|
85215
86437
|
// src/commands/plan/plan-command.ts
|
|
85216
86438
|
function resolvePlanFile(target) {
|
|
85217
86439
|
const t = target ? resolve33(target) : process.cwd();
|
|
85218
|
-
if (
|
|
86440
|
+
if (existsSync65(t)) {
|
|
85219
86441
|
const stat14 = statSync11(t);
|
|
85220
86442
|
if (stat14.isFile())
|
|
85221
86443
|
return t;
|
|
85222
|
-
const candidate =
|
|
85223
|
-
if (
|
|
86444
|
+
const candidate = join132(t, "plan.md");
|
|
86445
|
+
if (existsSync65(candidate))
|
|
85224
86446
|
return candidate;
|
|
85225
86447
|
}
|
|
85226
86448
|
if (!target) {
|
|
85227
86449
|
let dir = process.cwd();
|
|
85228
86450
|
const root = parse4(dir).root;
|
|
85229
86451
|
while (dir !== root) {
|
|
85230
|
-
const candidate =
|
|
85231
|
-
if (
|
|
86452
|
+
const candidate = join132(dir, "plan.md");
|
|
86453
|
+
if (existsSync65(candidate))
|
|
85232
86454
|
return candidate;
|
|
85233
|
-
dir =
|
|
86455
|
+
dir = dirname42(dir);
|
|
85234
86456
|
}
|
|
85235
86457
|
}
|
|
85236
86458
|
return null;
|
|
@@ -85278,7 +86500,7 @@ async function planCommand(action, target, options2) {
|
|
|
85278
86500
|
let resolvedTarget = target;
|
|
85279
86501
|
if (resolvedAction && !knownActions.has(resolvedAction)) {
|
|
85280
86502
|
const looksLikePath = resolvedAction.includes("/") || resolvedAction.includes("\\") || resolvedAction.endsWith(".md") || resolvedAction === "." || resolvedAction === "..";
|
|
85281
|
-
const existsOnDisk = !looksLikePath &&
|
|
86503
|
+
const existsOnDisk = !looksLikePath && existsSync65(resolve33(resolvedAction));
|
|
85282
86504
|
if (looksLikePath || existsOnDisk) {
|
|
85283
86505
|
resolvedTarget = resolvedAction;
|
|
85284
86506
|
resolvedAction = undefined;
|
|
@@ -85322,22 +86544,22 @@ init_logger();
|
|
|
85322
86544
|
init_logger();
|
|
85323
86545
|
|
|
85324
86546
|
// src/commands/telemetry/shared.ts
|
|
85325
|
-
import { existsSync as
|
|
86547
|
+
import { existsSync as existsSync66, readFileSync as readFileSync26, readdirSync as readdirSync11 } from "node:fs";
|
|
85326
86548
|
import { homedir as homedir29 } from "node:os";
|
|
85327
|
-
import { join as
|
|
86549
|
+
import { join as join133 } from "node:path";
|
|
85328
86550
|
init_token_store();
|
|
85329
86551
|
init_manifest_path_resolver();
|
|
85330
86552
|
init_takumi_constants();
|
|
85331
|
-
var USER_CACHE_PATH =
|
|
85332
|
-
var EVENT_BUFFER_DIR =
|
|
85333
|
-
var RATE_STATE_PATH =
|
|
85334
|
-
var TAKUMI_MANIFEST_PATH =
|
|
85335
|
-
var LEGACY_METADATA_PATH =
|
|
86553
|
+
var USER_CACHE_PATH = join133(homedir29(), ".claude", "sk-user.json");
|
|
86554
|
+
var EVENT_BUFFER_DIR = join133(homedir29(), ".claude", "sk-events");
|
|
86555
|
+
var RATE_STATE_PATH = join133(homedir29(), ".claude", "sk-rate-state.json");
|
|
86556
|
+
var TAKUMI_MANIFEST_PATH = join133(homedir29(), ".claude", MANIFEST_FILENAME);
|
|
86557
|
+
var LEGACY_METADATA_PATH = join133(homedir29(), ".claude", LEGACY_MANIFEST_FILENAME);
|
|
85336
86558
|
var TELEMETRY_HOOK_FIELD = "hooks.telemetry";
|
|
85337
86559
|
var TOKEN_PLACEHOLDER = "__INJECT_AT_RELEASE__";
|
|
85338
86560
|
function readUserCache() {
|
|
85339
86561
|
try {
|
|
85340
|
-
if (!
|
|
86562
|
+
if (!existsSync66(USER_CACHE_PATH))
|
|
85341
86563
|
return null;
|
|
85342
86564
|
const parsed = JSON.parse(readFileSync26(USER_CACHE_PATH, "utf8"));
|
|
85343
86565
|
if (!parsed || typeof parsed !== "object")
|
|
@@ -85349,7 +86571,7 @@ function readUserCache() {
|
|
|
85349
86571
|
}
|
|
85350
86572
|
function countBufferFiles() {
|
|
85351
86573
|
try {
|
|
85352
|
-
if (!
|
|
86574
|
+
if (!existsSync66(EVENT_BUFFER_DIR))
|
|
85353
86575
|
return 0;
|
|
85354
86576
|
return readdirSync11(EVENT_BUFFER_DIR).filter((f4) => f4.endsWith(".jsonl")).length;
|
|
85355
86577
|
} catch {
|
|
@@ -85361,7 +86583,7 @@ function readTelemetryConfig() {
|
|
|
85361
86583
|
const envToken = process.env.TAKUMI_TELEMETRY_TOKEN;
|
|
85362
86584
|
let metadata = null;
|
|
85363
86585
|
try {
|
|
85364
|
-
const resolved = findManifestPathSync(
|
|
86586
|
+
const resolved = findManifestPathSync(join133(homedir29(), ".claude"));
|
|
85365
86587
|
if (resolved) {
|
|
85366
86588
|
metadata = JSON.parse(readFileSync26(resolved.path, "utf8"));
|
|
85367
86589
|
}
|
|
@@ -85387,8 +86609,8 @@ function collectRuntimeContext() {
|
|
|
85387
86609
|
cacheSource: cache3?.source === "gh" || cache3?.source === "manual" ? cache3.source : null,
|
|
85388
86610
|
bufferFileCount: countBufferFiles(),
|
|
85389
86611
|
bufferDir: EVENT_BUFFER_DIR,
|
|
85390
|
-
rateStateExists:
|
|
85391
|
-
userCacheExists:
|
|
86612
|
+
rateStateExists: existsSync66(RATE_STATE_PATH),
|
|
86613
|
+
userCacheExists: existsSync66(USER_CACHE_PATH),
|
|
85392
86614
|
endpoint,
|
|
85393
86615
|
tokenConfigured: Boolean(token)
|
|
85394
86616
|
};
|
|
@@ -85489,7 +86711,7 @@ init_manifest_writer();
|
|
|
85489
86711
|
init_logger();
|
|
85490
86712
|
init_safe_prompts();
|
|
85491
86713
|
init_types2();
|
|
85492
|
-
var
|
|
86714
|
+
var import_picocolors29 = __toESM(require_picocolors(), 1);
|
|
85493
86715
|
|
|
85494
86716
|
// src/commands/uninstall/installation-detector.ts
|
|
85495
86717
|
init_paths();
|
|
@@ -85539,7 +86761,7 @@ init_safe_prompts();
|
|
|
85539
86761
|
init_safe_spinner();
|
|
85540
86762
|
var import_fs_extra37 = __toESM(require_lib(), 1);
|
|
85541
86763
|
import { readdirSync as readdirSync13, rmSync as rmSync9 } from "node:fs";
|
|
85542
|
-
import { join as
|
|
86764
|
+
import { join as join135, resolve as resolve34, sep as sep9 } from "node:path";
|
|
85543
86765
|
|
|
85544
86766
|
// src/commands/uninstall/analysis-handler.ts
|
|
85545
86767
|
init_metadata_migration();
|
|
@@ -85548,14 +86770,14 @@ init_ownership_checker();
|
|
|
85548
86770
|
init_logger();
|
|
85549
86771
|
init_safe_prompts();
|
|
85550
86772
|
init_takumi_constants();
|
|
85551
|
-
var
|
|
85552
|
-
import { existsSync as
|
|
85553
|
-
import { dirname as
|
|
86773
|
+
var import_picocolors28 = __toESM(require_picocolors(), 1);
|
|
86774
|
+
import { existsSync as existsSync67, readdirSync as readdirSync12, rmSync as rmSync8 } from "node:fs";
|
|
86775
|
+
import { dirname as dirname43, join as join134 } from "node:path";
|
|
85554
86776
|
function listPresentManifestNames(installPath) {
|
|
85555
86777
|
const present = [];
|
|
85556
|
-
if (
|
|
86778
|
+
if (existsSync67(getManifestPath(installPath)))
|
|
85557
86779
|
present.push(MANIFEST_FILENAME);
|
|
85558
|
-
if (
|
|
86780
|
+
if (existsSync67(getLegacyManifestPath(installPath)))
|
|
85559
86781
|
present.push(LEGACY_MANIFEST_FILENAME);
|
|
85560
86782
|
return present;
|
|
85561
86783
|
}
|
|
@@ -85573,7 +86795,7 @@ function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
|
|
|
85573
86795
|
}
|
|
85574
86796
|
async function cleanupEmptyDirectories3(filePath, installationRoot) {
|
|
85575
86797
|
let cleaned = 0;
|
|
85576
|
-
let currentDir =
|
|
86798
|
+
let currentDir = dirname43(filePath);
|
|
85577
86799
|
while (currentDir !== installationRoot && currentDir.startsWith(installationRoot)) {
|
|
85578
86800
|
try {
|
|
85579
86801
|
const entries = readdirSync12(currentDir);
|
|
@@ -85581,7 +86803,7 @@ async function cleanupEmptyDirectories3(filePath, installationRoot) {
|
|
|
85581
86803
|
rmSync8(currentDir, { recursive: true });
|
|
85582
86804
|
cleaned++;
|
|
85583
86805
|
logger.debug(`Removed empty directory: ${currentDir}`);
|
|
85584
|
-
currentDir =
|
|
86806
|
+
currentDir = dirname43(currentDir);
|
|
85585
86807
|
} else {
|
|
85586
86808
|
break;
|
|
85587
86809
|
}
|
|
@@ -85603,7 +86825,7 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
|
|
|
85603
86825
|
if (uninstallManifest.isMultiKit && kit && metadata?.kits?.[kit]) {
|
|
85604
86826
|
const kitFiles = metadata.kits[kit].files || [];
|
|
85605
86827
|
for (const trackedFile of kitFiles) {
|
|
85606
|
-
const filePath =
|
|
86828
|
+
const filePath = join134(installation.path, trackedFile.path);
|
|
85607
86829
|
if (uninstallManifest.filesToPreserve.includes(trackedFile.path)) {
|
|
85608
86830
|
result.toPreserve.push({ path: trackedFile.path, reason: "shared with other kit" });
|
|
85609
86831
|
continue;
|
|
@@ -85635,7 +86857,7 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
|
|
|
85635
86857
|
return result;
|
|
85636
86858
|
}
|
|
85637
86859
|
for (const trackedFile of allTrackedFiles) {
|
|
85638
|
-
const filePath =
|
|
86860
|
+
const filePath = join134(installation.path, trackedFile.path);
|
|
85639
86861
|
const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
|
|
85640
86862
|
if (!ownershipResult.exists)
|
|
85641
86863
|
continue;
|
|
@@ -85653,27 +86875,27 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
|
|
|
85653
86875
|
}
|
|
85654
86876
|
function displayDryRunPreview(analysis, installationType) {
|
|
85655
86877
|
console.log("");
|
|
85656
|
-
log.info(
|
|
86878
|
+
log.info(import_picocolors28.default.bold(`DRY RUN - Preview for ${installationType} installation:`));
|
|
85657
86879
|
console.log("");
|
|
85658
86880
|
if (analysis.toDelete.length > 0) {
|
|
85659
|
-
console.log(
|
|
86881
|
+
console.log(import_picocolors28.default.red(import_picocolors28.default.bold(`Files to DELETE (${analysis.toDelete.length}):`)));
|
|
85660
86882
|
const showDelete = analysis.toDelete.slice(0, 10);
|
|
85661
86883
|
for (const item of showDelete) {
|
|
85662
|
-
console.log(` ${
|
|
86884
|
+
console.log(` ${import_picocolors28.default.red("✖")} ${item.path}`);
|
|
85663
86885
|
}
|
|
85664
86886
|
if (analysis.toDelete.length > 10) {
|
|
85665
|
-
console.log(
|
|
86887
|
+
console.log(import_picocolors28.default.gray(` ... and ${analysis.toDelete.length - 10} more`));
|
|
85666
86888
|
}
|
|
85667
86889
|
console.log("");
|
|
85668
86890
|
}
|
|
85669
86891
|
if (analysis.toPreserve.length > 0) {
|
|
85670
|
-
console.log(
|
|
86892
|
+
console.log(import_picocolors28.default.green(import_picocolors28.default.bold(`Files to PRESERVE (${analysis.toPreserve.length}):`)));
|
|
85671
86893
|
const showPreserve = analysis.toPreserve.slice(0, 10);
|
|
85672
86894
|
for (const item of showPreserve) {
|
|
85673
|
-
console.log(` ${
|
|
86895
|
+
console.log(` ${import_picocolors28.default.green("✓")} ${item.path} ${import_picocolors28.default.gray(`(${item.reason})`)}`);
|
|
85674
86896
|
}
|
|
85675
86897
|
if (analysis.toPreserve.length > 10) {
|
|
85676
|
-
console.log(
|
|
86898
|
+
console.log(import_picocolors28.default.gray(` ... and ${analysis.toPreserve.length - 10} more`));
|
|
85677
86899
|
}
|
|
85678
86900
|
console.log("");
|
|
85679
86901
|
}
|
|
@@ -85734,7 +86956,7 @@ async function removeInstallations(installations, options2) {
|
|
|
85734
86956
|
let removedCount = 0;
|
|
85735
86957
|
let cleanedDirs = 0;
|
|
85736
86958
|
for (const item of analysis.toDelete) {
|
|
85737
|
-
const filePath =
|
|
86959
|
+
const filePath = join135(installation.path, item.path);
|
|
85738
86960
|
if (!await import_fs_extra37.pathExists(filePath))
|
|
85739
86961
|
continue;
|
|
85740
86962
|
if (!await isPathSafeToRemove(filePath, installation.path)) {
|
|
@@ -85795,15 +87017,15 @@ function displayInstallations(installations, scope) {
|
|
|
85795
87017
|
const hasLegacy = installations.some((i) => !i.hasMetadata);
|
|
85796
87018
|
const lines = installations.map((i) => {
|
|
85797
87019
|
const typeLabel = i.type === "local" ? "Local " : "Global";
|
|
85798
|
-
const legacyTag = !i.hasMetadata ?
|
|
87020
|
+
const legacyTag = !i.hasMetadata ? import_picocolors29.default.yellow(" [legacy]") : "";
|
|
85799
87021
|
const components = formatComponentSummary(i);
|
|
85800
87022
|
return ` ${typeLabel}: ${i.path}${legacyTag}${components}`;
|
|
85801
87023
|
});
|
|
85802
87024
|
prompts.note(lines.join(`
|
|
85803
87025
|
`), `Detected Takumi installations (${scopeLabel})`);
|
|
85804
87026
|
if (hasLegacy) {
|
|
85805
|
-
log.warn(
|
|
85806
|
-
`) +
|
|
87027
|
+
log.warn(import_picocolors29.default.yellow(`[!] Legacy installation(s) detected without metadata.json.
|
|
87028
|
+
`) + import_picocolors29.default.yellow(" These files cannot be selectively removed. Full directory cleanup will be performed."));
|
|
85807
87029
|
}
|
|
85808
87030
|
log.warn("[!] This will permanently delete Takumi files from the above paths.");
|
|
85809
87031
|
}
|
|
@@ -85863,7 +87085,7 @@ async function uninstallCommand(options2) {
|
|
|
85863
87085
|
}
|
|
85864
87086
|
const isAtHome = isLocalSameAsGlobal();
|
|
85865
87087
|
if (validOptions.local && !validOptions.global && isAtHome) {
|
|
85866
|
-
log.warn(
|
|
87088
|
+
log.warn(import_picocolors29.default.yellow("Cannot use --local at HOME directory (local path equals global path)."));
|
|
85867
87089
|
log.info("Use -g/--global or run from a project directory.");
|
|
85868
87090
|
return;
|
|
85869
87091
|
}
|
|
@@ -85875,7 +87097,7 @@ async function uninstallCommand(options2) {
|
|
|
85875
87097
|
} else if (validOptions.global) {
|
|
85876
87098
|
scope = "global";
|
|
85877
87099
|
} else if (isAtHome) {
|
|
85878
|
-
log.info(
|
|
87100
|
+
log.info(import_picocolors29.default.cyan("Running at HOME directory - targeting global installation"));
|
|
85879
87101
|
scope = "global";
|
|
85880
87102
|
} else {
|
|
85881
87103
|
const promptedScope = await promptScope(allInstallations);
|
|
@@ -85897,10 +87119,10 @@ async function uninstallCommand(options2) {
|
|
|
85897
87119
|
}
|
|
85898
87120
|
displayInstallations(installations, scope);
|
|
85899
87121
|
if (validOptions.kit) {
|
|
85900
|
-
log.info(
|
|
87122
|
+
log.info(import_picocolors29.default.cyan(`Kit-scoped uninstall: ${validOptions.kit} kit only`));
|
|
85901
87123
|
}
|
|
85902
87124
|
if (validOptions.dryRun) {
|
|
85903
|
-
log.info(
|
|
87125
|
+
log.info(import_picocolors29.default.yellow("DRY RUN MODE - No files will be deleted"));
|
|
85904
87126
|
await removeInstallations(installations, {
|
|
85905
87127
|
dryRun: true,
|
|
85906
87128
|
forceOverwrite: validOptions.forceOverwrite,
|
|
@@ -85910,8 +87132,8 @@ async function uninstallCommand(options2) {
|
|
|
85910
87132
|
return;
|
|
85911
87133
|
}
|
|
85912
87134
|
if (validOptions.forceOverwrite) {
|
|
85913
|
-
log.warn(`${
|
|
85914
|
-
${
|
|
87135
|
+
log.warn(`${import_picocolors29.default.yellow(import_picocolors29.default.bold("FORCE MODE ENABLED"))}
|
|
87136
|
+
${import_picocolors29.default.yellow("User modifications will be permanently deleted!")}`);
|
|
85915
87137
|
}
|
|
85916
87138
|
if (!validOptions.yes) {
|
|
85917
87139
|
const kitLabel = validOptions.kit ? ` (${validOptions.kit} kit only)` : "";
|
|
@@ -86364,7 +87586,7 @@ init_auth_client();
|
|
|
86364
87586
|
init_github_client();
|
|
86365
87587
|
init_logger();
|
|
86366
87588
|
init_types2();
|
|
86367
|
-
var
|
|
87589
|
+
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
86368
87590
|
function formatRelativeTime(dateString) {
|
|
86369
87591
|
if (!dateString)
|
|
86370
87592
|
return "Unknown";
|
|
@@ -86386,21 +87608,21 @@ function formatRelativeTime(dateString) {
|
|
|
86386
87608
|
}
|
|
86387
87609
|
function displayKitReleases(kitName, releases) {
|
|
86388
87610
|
console.log(`
|
|
86389
|
-
${
|
|
87611
|
+
${import_picocolors30.default.bold(import_picocolors30.default.cyan(kitName))} - Available Versions:
|
|
86390
87612
|
`);
|
|
86391
87613
|
if (releases.length === 0) {
|
|
86392
|
-
console.log(
|
|
87614
|
+
console.log(import_picocolors30.default.dim(" No releases found"));
|
|
86393
87615
|
return;
|
|
86394
87616
|
}
|
|
86395
87617
|
for (const release of releases) {
|
|
86396
|
-
const version3 =
|
|
87618
|
+
const version3 = import_picocolors30.default.green(release.tag);
|
|
86397
87619
|
const publishedAt = formatRelativeTime(release.publishedAt);
|
|
86398
|
-
const badge = release.prerelease ? ` ${
|
|
87620
|
+
const badge = release.prerelease ? ` ${import_picocolors30.default.yellow("[prerelease]")}` : "";
|
|
86399
87621
|
const versionPart = version3.padEnd(20);
|
|
86400
|
-
const timePart =
|
|
87622
|
+
const timePart = import_picocolors30.default.dim(publishedAt.padEnd(20));
|
|
86401
87623
|
console.log(` ${versionPart} ${timePart}${badge}`);
|
|
86402
87624
|
}
|
|
86403
|
-
console.log(
|
|
87625
|
+
console.log(import_picocolors30.default.dim(`
|
|
86404
87626
|
Showing ${releases.length} ${releases.length === 1 ? "release" : "releases"}`));
|
|
86405
87627
|
}
|
|
86406
87628
|
async function fetchReleasesForKit(kitType, options2) {
|
|
@@ -86445,8 +87667,8 @@ async function versionCommand(options2) {
|
|
|
86445
87667
|
for (const result of results) {
|
|
86446
87668
|
if (result.error) {
|
|
86447
87669
|
console.log(`
|
|
86448
|
-
${
|
|
86449
|
-
console.log(
|
|
87670
|
+
${import_picocolors30.default.bold(import_picocolors30.default.cyan(result.kitConfig.name))} - ${import_picocolors30.default.red("Error")}`);
|
|
87671
|
+
console.log(import_picocolors30.default.dim(` ${result.error}`));
|
|
86450
87672
|
} else {
|
|
86451
87673
|
displayKitReleases(result.kitConfig.name, result.releases);
|
|
86452
87674
|
}
|
|
@@ -86592,6 +87814,9 @@ function registerCommands(cli) {
|
|
|
86592
87814
|
process.exitCode = 1;
|
|
86593
87815
|
}
|
|
86594
87816
|
});
|
|
87817
|
+
cli.command("mcp [action] [service]", "Manage internal MCP connectors for coding agents (add|list|remove)").option("-a, --agent <agents...>", "Target agent(s): claude-code, codex").option("-s, --scope <scope>", "Config scope: local | user | project (default: user)").option("-y, --yes", "Non-interactive mode: skip confirmation prompts").option("--json", "Machine-readable JSON output (list only; ignored by add/remove)").action(async (action, service, options2 = {}) => {
|
|
87818
|
+
await mcpCommand(action, service, options2);
|
|
87819
|
+
});
|
|
86595
87820
|
}
|
|
86596
87821
|
|
|
86597
87822
|
// src/cli/version-display.ts
|
|
@@ -86603,7 +87828,7 @@ init_manifest_path_resolver();
|
|
|
86603
87828
|
init_logger();
|
|
86604
87829
|
init_types2();
|
|
86605
87830
|
import { readFileSync as readFileSync27 } from "node:fs";
|
|
86606
|
-
import { join as
|
|
87831
|
+
import { join as join136 } from "node:path";
|
|
86607
87832
|
var PROVIDER_LOCAL_SUBDIRS = {
|
|
86608
87833
|
"claude-code": ".claude",
|
|
86609
87834
|
codex: ".codex"
|
|
@@ -86658,7 +87883,7 @@ async function displayVersion() {
|
|
|
86658
87883
|
const localSubdir = PROVIDER_LOCAL_SUBDIRS[provider];
|
|
86659
87884
|
if (!localSubdir)
|
|
86660
87885
|
continue;
|
|
86661
|
-
const localRoot =
|
|
87886
|
+
const localRoot = join136(process.cwd(), localSubdir);
|
|
86662
87887
|
if (localRoot === inst.globalRoot())
|
|
86663
87888
|
continue;
|
|
86664
87889
|
const resolved = findManifestPathSync(localRoot);
|
|
@@ -86739,7 +87964,7 @@ function getPackageVersion3() {
|
|
|
86739
87964
|
|
|
86740
87965
|
// src/shared/logger.ts
|
|
86741
87966
|
init_output_manager();
|
|
86742
|
-
var
|
|
87967
|
+
var import_picocolors31 = __toESM(require_picocolors(), 1);
|
|
86743
87968
|
import { createWriteStream as createWriteStream4 } from "node:fs";
|
|
86744
87969
|
|
|
86745
87970
|
class Logger2 {
|
|
@@ -86748,23 +87973,23 @@ class Logger2 {
|
|
|
86748
87973
|
exitHandlerRegistered = false;
|
|
86749
87974
|
info(message) {
|
|
86750
87975
|
const symbols = output.getSymbols();
|
|
86751
|
-
console.log(
|
|
87976
|
+
console.log(import_picocolors31.default.blue(symbols.info), message);
|
|
86752
87977
|
}
|
|
86753
87978
|
success(message) {
|
|
86754
87979
|
const symbols = output.getSymbols();
|
|
86755
|
-
console.log(
|
|
87980
|
+
console.log(import_picocolors31.default.green(symbols.success), message);
|
|
86756
87981
|
}
|
|
86757
87982
|
warning(message) {
|
|
86758
87983
|
const symbols = output.getSymbols();
|
|
86759
|
-
console.log(
|
|
87984
|
+
console.log(import_picocolors31.default.yellow(symbols.warning), message);
|
|
86760
87985
|
}
|
|
86761
87986
|
error(message) {
|
|
86762
87987
|
const symbols = output.getSymbols();
|
|
86763
|
-
console.error(
|
|
87988
|
+
console.error(import_picocolors31.default.red(symbols.error), message);
|
|
86764
87989
|
}
|
|
86765
87990
|
debug(message) {
|
|
86766
87991
|
if (process.env.DEBUG) {
|
|
86767
|
-
console.log(
|
|
87992
|
+
console.log(import_picocolors31.default.gray("[DEBUG]"), message);
|
|
86768
87993
|
}
|
|
86769
87994
|
}
|
|
86770
87995
|
verbose(message, context) {
|
|
@@ -86773,7 +87998,7 @@ class Logger2 {
|
|
|
86773
87998
|
const timestamp = this.getTimestamp();
|
|
86774
87999
|
const sanitizedMessage = this.sanitize(message);
|
|
86775
88000
|
const formattedContext = context ? this.formatContext(context) : "";
|
|
86776
|
-
const logLine = `${timestamp} ${
|
|
88001
|
+
const logLine = `${timestamp} ${import_picocolors31.default.gray("[VERBOSE]")} ${sanitizedMessage}${formattedContext}`;
|
|
86777
88002
|
console.error(logLine);
|
|
86778
88003
|
if (this.logFileStream) {
|
|
86779
88004
|
const plainLogLine = `${timestamp} [VERBOSE] ${sanitizedMessage}${formattedContext}`;
|
|
@@ -86876,7 +88101,7 @@ var logger3 = new Logger2;
|
|
|
86876
88101
|
|
|
86877
88102
|
// src/shared/output-manager.ts
|
|
86878
88103
|
init_terminal_utils();
|
|
86879
|
-
var
|
|
88104
|
+
var import_picocolors32 = __toESM(require_picocolors(), 1);
|
|
86880
88105
|
var SYMBOLS2 = {
|
|
86881
88106
|
unicode: {
|
|
86882
88107
|
prompt: "◇",
|
|
@@ -86957,7 +88182,7 @@ class OutputManager2 {
|
|
|
86957
88182
|
if (this.config.quiet)
|
|
86958
88183
|
return;
|
|
86959
88184
|
const symbol = this.getSymbols().success;
|
|
86960
|
-
console.log(
|
|
88185
|
+
console.log(import_picocolors32.default.green(`${symbol} ${message}`));
|
|
86961
88186
|
}
|
|
86962
88187
|
error(message, data) {
|
|
86963
88188
|
if (this.config.json) {
|
|
@@ -86965,7 +88190,7 @@ class OutputManager2 {
|
|
|
86965
88190
|
return;
|
|
86966
88191
|
}
|
|
86967
88192
|
const symbol = this.getSymbols().error;
|
|
86968
|
-
console.error(
|
|
88193
|
+
console.error(import_picocolors32.default.red(`${symbol} ${message}`));
|
|
86969
88194
|
}
|
|
86970
88195
|
warning(message, data) {
|
|
86971
88196
|
if (this.config.json) {
|
|
@@ -86975,7 +88200,7 @@ class OutputManager2 {
|
|
|
86975
88200
|
if (this.config.quiet)
|
|
86976
88201
|
return;
|
|
86977
88202
|
const symbol = this.getSymbols().warning;
|
|
86978
|
-
console.log(
|
|
88203
|
+
console.log(import_picocolors32.default.yellow(`${symbol} ${message}`));
|
|
86979
88204
|
}
|
|
86980
88205
|
info(message, data) {
|
|
86981
88206
|
if (this.config.json) {
|
|
@@ -86985,7 +88210,7 @@ class OutputManager2 {
|
|
|
86985
88210
|
if (this.config.quiet)
|
|
86986
88211
|
return;
|
|
86987
88212
|
const symbol = this.getSymbols().info;
|
|
86988
|
-
console.log(
|
|
88213
|
+
console.log(import_picocolors32.default.blue(`${symbol} ${message}`));
|
|
86989
88214
|
}
|
|
86990
88215
|
verbose(message, data) {
|
|
86991
88216
|
if (!this.config.verbose)
|
|
@@ -86994,7 +88219,7 @@ class OutputManager2 {
|
|
|
86994
88219
|
this.addJsonEntry({ type: "info", message, data });
|
|
86995
88220
|
return;
|
|
86996
88221
|
}
|
|
86997
|
-
console.log(
|
|
88222
|
+
console.log(import_picocolors32.default.dim(` ${message}`));
|
|
86998
88223
|
}
|
|
86999
88224
|
indent(message) {
|
|
87000
88225
|
if (this.config.json)
|
|
@@ -87019,7 +88244,7 @@ class OutputManager2 {
|
|
|
87019
88244
|
return;
|
|
87020
88245
|
const symbols = this.getSymbols();
|
|
87021
88246
|
console.log();
|
|
87022
|
-
console.log(
|
|
88247
|
+
console.log(import_picocolors32.default.bold(import_picocolors32.default.cyan(`${symbols.line} ${title}`)));
|
|
87023
88248
|
}
|
|
87024
88249
|
addJsonEntry(entry) {
|
|
87025
88250
|
this.jsonBuffer.push({
|