zidane 6.2.21 → 6.2.23
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/{acp-BqnSn-gl.js → acp-B7wH6LhE.js} +3 -3
- package/dist/{acp-BqnSn-gl.js.map → acp-B7wH6LhE.js.map} +1 -1
- package/dist/acp-cli.js +2 -2
- package/dist/acp.d.ts +1 -1
- package/dist/acp.js +1 -1
- package/dist/{agent-BJLSgHAS.js → agent-BSIMsEtD.js} +126 -15
- package/dist/agent-BSIMsEtD.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat.d.ts +21 -5
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +3 -3
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +1 -1
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +2 -2
- package/dist/{index-BRxJUFyo.d.ts → index-Bv9qhj4n.d.ts} +69 -1
- package/dist/index-Bv9qhj4n.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/{login-BvGRBGaY.js → login-BkZ_d5sT.js} +2 -2
- package/dist/{login-BvGRBGaY.js.map → login-BkZ_d5sT.js.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/output/stream-json.d.ts +1 -1
- package/dist/output/terminal.d.ts +1 -1
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/{resolve-R3VDQMT3.js → resolve-x5mlFtLX.js} +4 -3
- package/dist/resolve-x5mlFtLX.js.map +1 -0
- package/dist/restate.d.ts +1 -1
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-DzrXKLw-.js → session-Dbvz1ZUE.js} +3 -2
- package/dist/session-Dbvz1ZUE.js.map +1 -0
- package/dist/session.d.ts +1 -1
- package/dist/session.js +1 -1
- package/dist/{skills-6_Z49T6p.js → skills-C1vGKbqv.js} +2 -2
- package/dist/{skills-6_Z49T6p.js.map → skills-C1vGKbqv.js.map} +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -2
- package/dist/{tool-formatters-Dokpl_jU.d.ts → tool-formatters-D9P6Znip.d.ts} +2 -2
- package/dist/{tool-formatters-Dokpl_jU.d.ts.map → tool-formatters-D9P6Znip.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-5wRP7hln.d.ts → transcript-anchors-Ci5888xS.d.ts} +18 -4
- package/dist/{transcript-anchors-5wRP7hln.d.ts.map → transcript-anchors-Ci5888xS.d.ts.map} +1 -1
- package/dist/{transcript-anchors-QeMjbphJ.js → transcript-anchors-CqzIr74w.js} +260 -22
- package/dist/transcript-anchors-CqzIr74w.js.map +1 -0
- package/dist/tui.d.ts +11 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +892 -106
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-D96guhSq.d.ts → turn-operations-C3y6A82G.d.ts} +3 -3
- package/dist/{turn-operations-D96guhSq.d.ts.map → turn-operations-C3y6A82G.d.ts.map} +1 -1
- package/dist/types-BIarq1qE.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/agent-BJLSgHAS.js.map +0 -1
- package/dist/index-BRxJUFyo.d.ts.map +0 -1
- package/dist/resolve-R3VDQMT3.js.map +0 -1
- package/dist/session-DzrXKLw-.js.map +0 -1
- package/dist/transcript-anchors-QeMjbphJ.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { $ as composeExtensionPreset, B as ExtensionInstallError, Bt as ensureKeybindingsFile, Et as createModelSearchTool, H as installExtensionFromSpec, Ht as groupBindings, J as createExtensionHud, K as removeInstalledExtension, P as summaryToTurn, St as tailTruncate, Ut as keybindingsPath, Vt as formatBindingForDisplay, Wt as matchesBinding, X as discoverAndBuildExtensionRegistry, Y as registerExtensionHostModules, Yt as resolveExtensionKeybindings, _ as buildPostCompactAttachments, c as applyCompactSummaryCutoff, dn as sanitizeExtensionConfigActionPatch, en as inlineLoadedExtension, et as disposeExtensionRegistry, f as cleanupPersistedSession, g as resolveTasksDir, h as resolvePersistDir, hn as effectiveContextWindow, ht as isSpawnTool, it as createExtensionHostRuntimeBridge, lt as basic_default, m as resolveMcpWarningsDir, mn as OUTPUT_RESERVE_TOKENS, nn as parseExtensionScanPathEnv, on as coerceExtensionConfigInput, q as updateInstalledExtension, rn as scanExtensionDirectories, rt as partitionExtensionCatalog, t as createAgent, tt as filterExtensionRegistry, un as resolveExtensionConfigValue, wt as looksBinary, x as compactConversation, y as selectFilesFromSession, z as createExtensionPromptBridge, zt as KEYBINDING_KEY_COL_WIDTH } from "./agent-
|
|
1
|
+
import { $ as McpAuthProvider, $t as filterChainFallbacks, A as runOAuthLogin, At as useSettings, B as mcpToolsCachePath, Ct as DEFAULT_SETTINGS, D as formatPathForCwd, Dn as accentColor, Dt as SETTINGS_TOGGLES, Et as SETTINGS_INPUTS, F as parseJsonTolerant, G as useMcpToolToggleMap, Gt as useDiscoveryOptional, Ht as createDiscoverySlot, I as parseMcpsFile, J as indexOfServerRow, Jt as resolveConfig, Kt as ConfigProvider, L as parseClaudeState, M as buildMcpServers, N as defaultMcpsConfigPaths, Nn as TODO_STATUS_GLYPHS, Nt as resolveChipColor, O as fetchOAuthRedirect, Ot as SettingsProvider, P as discoverProjectMcps, Pt as resolveTheme, Qt as chainsEqual, Sr as usageByModel, St as useEnabledToggleSet, T as suggestSafelistEntries, Tt as SETTINGS_CHOICES, U as subscribeMcpToolsCache, Un as useActiveTodos, Ut as DiscoveryProvider, V as refreshMcpToolsCatalog, Wt as useDiscovery, X as createFileMcpCredentialStore, Xt as buildRunModel, Y as parentServerName, Yt as resolveStoragePaths, Zt as chainPositionOf, _ as useSafeModeQueue, _r as sumRunCosts, _t as hintsLength, a as useSurfaces, an as tryOpenBrowser, at as InteractionsProvider, b as getSafelist, br as toolResultText, c as useStreamBuffer, cn as buildUpdateHint, cr as deriveSessionTitle, ct as createInteractionTools, d as discoverProjectSkills, dr as listSessionMeta, dt as pendingInteractionsFromTurns, en as providerKeySetChanged, er as buildBuildSystem, et as useMcpAuthDispatch, fr as loadState, g as useSafeModeActions, gr as stripSpawnTokensLine, gt as clipHintsToWidth, h as SafeModeProvider, hr as serverToolResultSummary, ht as EMPTY_HINTS, i as useSelectStyle, in as useCompletion, ir as createChatTurnsReadTool, it as ASK_USER_TOOL, j as supportsOAuth, k as oauthUsesManualCodePaste, kt as clampFps, l as buildSkillsConfig, ln as useUpdateCheck, lr as eventsFromTurns, m as writeSessionExport, mt as useInteractionsQueue, n as ThemeProvider, nr as envSection, nt as getMcpAuthStatus, or as compactSummaryEvent, ot as PRESENT_PLAN_TOOL, pr as marginTopFor, pt as useInteractionsActions, q as buildVisibleMcpRows, qt as useConfig, r as useColors, rn as toggleChainEntry, rr as chatReferenceTokensForPrompt, s as useTheme, sn as bootTick, st as buildResumedToolResultsTurn, t as computeTurnAnchors, tn as restoreChainFallbacks, tr as buildPlanSystem, tt as useMcpAuthState, u as defaultSkillScanPaths, ur as lastContextSizeFromTurns, ut as makeRequestInteraction, vn as chatAutoCompactBehavior, vt as truncateTrailing, wt as SETTINGS_CATEGORIES, x as isOnSafelist, xr as updateToolEventOutcomes, xt as listProjectFiles, y as addToSafelist, yn as discoverAgentsMd, yr as toolCallPreview, yt as buildHints, z as loadMcpToolsCache } from "./transcript-anchors-CqzIr74w.js";
|
|
2
|
+
import { $ as composeExtensionPreset, B as ExtensionInstallError, Bt as ensureKeybindingsFile, Et as createModelSearchTool, H as installExtensionFromSpec, Ht as groupBindings, J as createExtensionHud, K as removeInstalledExtension, P as summaryToTurn, St as tailTruncate, Ut as keybindingsPath, Vt as formatBindingForDisplay, Wt as matchesBinding, X as discoverAndBuildExtensionRegistry, Y as registerExtensionHostModules, Yt as resolveExtensionKeybindings, _ as buildPostCompactAttachments, c as applyCompactSummaryCutoff, dn as sanitizeExtensionConfigActionPatch, en as inlineLoadedExtension, et as disposeExtensionRegistry, f as cleanupPersistedSession, g as resolveTasksDir, h as resolvePersistDir, hn as effectiveContextWindow, ht as isSpawnTool, it as createExtensionHostRuntimeBridge, lt as basic_default, m as resolveMcpWarningsDir, mn as OUTPUT_RESERVE_TOKENS, nn as parseExtensionScanPathEnv, on as coerceExtensionConfigInput, q as updateInstalledExtension, rn as scanExtensionDirectories, rt as partitionExtensionCatalog, t as createAgent, tt as filterExtensionRegistry, un as resolveExtensionConfigValue, wt as looksBinary, x as compactConversation, y as selectFilesFromSession, z as createExtensionPromptBridge, zt as KEYBINDING_KEY_COL_WIDTH } from "./agent-BSIMsEtD.js";
|
|
3
3
|
import { a as formatDuration, c as previewLine, i as fmtTokens, l as shortId, n as compactPath, o as formatTaskStatus, r as fmtCost, s as formatTaskSummary, t as ageString } from "./format-BF745pLL.js";
|
|
4
4
|
import { n as appendCapped } from "./output-cap-kBUHBlnq.js";
|
|
5
5
|
import { i as killProcessGroup, n as SUPPORTS_PROCESS_GROUPS, r as createProcessContext } from "./contexts-A4ep-iJs.js";
|
|
6
6
|
import { l as errorMessage } from "./errors-DJUxZg9b.js";
|
|
7
|
-
import { M as findGitRoot, j as projectUserPaths } from "./resolve-
|
|
7
|
+
import { M as findGitRoot, j as projectUserPaths } from "./resolve-x5mlFtLX.js";
|
|
8
8
|
import { a as createRingBufferSink, i as createLoggingHooks, l as logFileTimestamp, n as createFileSink, o as fanoutSink, r as createLogger, s as formatLogLine, t as consoleSink } from "./logger-DBX9uYOw.js";
|
|
9
9
|
import { i as sniffImageMediaType } from "./media-sniff-Bn76JxAu.js";
|
|
10
10
|
import { d as McpOAuthProvider, i as nonOwningMcpView, n as connectMcpServers, p as hasAuthorizationHeader } from "./mcp-H6bqyM94.js";
|
|
11
11
|
import { n as flattenTurns, r as formatTokenUsage, t as effectiveInputFromTurn } from "./stats-DAKBEKjc.js";
|
|
12
12
|
import { replaceDynamicSection } from "./prompt.js";
|
|
13
|
-
import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, s as setExtensionSessionState, t as createSession } from "./session-
|
|
13
|
+
import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as loadSession, s as setExtensionSessionState, t as createSession } from "./session-Dbvz1ZUE.js";
|
|
14
14
|
import { o as withTimeout } from "./timeout-BDetbuN6.js";
|
|
15
15
|
import { a as generateStructuredData, c as shouldAutoGenerateTitle, f as commandBlockToEvent, g as parseCommandInvocation, h as executeCommand, n as patchInlineGhost, p as commandBlocksToContent, r as resolveInlineSuggestion, s as generateSessionTitle, t as createPathAutocompleteProvider } from "./inline-autocomplete-BAwBcZtD.js";
|
|
16
16
|
import { s as resolveCompletionSubmit } from "./completion-core-BRo86R_L.js";
|
|
17
|
-
import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-
|
|
18
|
-
import { i as ZIDANE_EXTENSION_SKILL_NAME, l as withZidaneExtensionSkill, o as extensionDirForCwd, r as EXTENSION_EDIT_METADATA_KEY, s as extensionScaffold, t as loginMcpServer } from "./login-
|
|
17
|
+
import { i as installSkillFromSpec, r as SkillInstallError } from "./skills-C1vGKbqv.js";
|
|
18
|
+
import { i as ZIDANE_EXTENSION_SKILL_NAME, l as withZidaneExtensionSkill, o as extensionDirForCwd, r as EXTENSION_EDIT_METADATA_KEY, s as extensionScaffold, t as loginMcpServer } from "./login-BkZ_d5sT.js";
|
|
19
19
|
import { C as restoreModelOptions, S as piIdOf, _ as modelOptionsFor, m as getContextWindow, p as enabledModelOptions, s as setProviderCredential, t as detectAuth, v as modelSupportsReasoning } from "./auth-CpdQebUy.js";
|
|
20
20
|
import { createDockerContext } from "./contexts/docker.js";
|
|
21
21
|
import { createTuiStore } from "./session/sqlite.js";
|
|
@@ -24,7 +24,7 @@ import { a as searchHighlightRanges, c as searchSessionHistoryIndexAsync, i as h
|
|
|
24
24
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
25
25
|
import { Buffer } from "node:buffer";
|
|
26
26
|
import * as fs from "node:fs";
|
|
27
|
-
import { createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { closeSync, cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
28
28
|
import { readFile, unlink } from "node:fs/promises";
|
|
29
29
|
import { spawn, spawnSync } from "node:child_process";
|
|
30
30
|
import { homedir, tmpdir } from "node:os";
|
|
@@ -41,84 +41,6 @@ import * as OpenTuiReactJsxDevRuntime from "@opentui/react/jsx-dev-runtime";
|
|
|
41
41
|
import * as OpenTuiReactJsxRuntime from "@opentui/react/jsx-runtime";
|
|
42
42
|
import { Fragment, jsx as jsx$1, jsxs } from "@opentui/react/jsx-runtime";
|
|
43
43
|
import * as ReactJsxDevRuntime from "react/jsx-dev-runtime";
|
|
44
|
-
/** Filter + rank commands against a query (name-priority, then description, then fuzzy on name). */
|
|
45
|
-
function scoreCommands(catalog, query) {
|
|
46
|
-
const q = query.trim().toLowerCase();
|
|
47
|
-
const fuzzyHits = q.length > 0 ? new Set(new Fzf([...catalog], {
|
|
48
|
-
selector: (c) => c.name,
|
|
49
|
-
casing: "case-insensitive"
|
|
50
|
-
}).find(q).map((r) => r.item)) : null;
|
|
51
|
-
const scored = [];
|
|
52
|
-
for (const command of catalog) {
|
|
53
|
-
const name = command.name.toLowerCase();
|
|
54
|
-
const aliases = (command.aliases ?? []).map((a) => a.toLowerCase());
|
|
55
|
-
const group = command.group?.toLowerCase();
|
|
56
|
-
const desc = command.description.toLowerCase();
|
|
57
|
-
if (q.length === 0) scored.push({
|
|
58
|
-
command,
|
|
59
|
-
rank: 4
|
|
60
|
-
});
|
|
61
|
-
else if (name.startsWith(q) || aliases.some((a) => a.startsWith(q))) scored.push({
|
|
62
|
-
command,
|
|
63
|
-
rank: 0
|
|
64
|
-
});
|
|
65
|
-
else if (name.includes(q) || aliases.some((a) => a.includes(q)) || group !== void 0 && q.length >= 2 && group.startsWith(q)) scored.push({
|
|
66
|
-
command,
|
|
67
|
-
rank: 1
|
|
68
|
-
});
|
|
69
|
-
else if (desc.includes(q)) scored.push({
|
|
70
|
-
command,
|
|
71
|
-
rank: 2
|
|
72
|
-
});
|
|
73
|
-
else if (fuzzyHits?.has(command)) scored.push({
|
|
74
|
-
command,
|
|
75
|
-
rank: 3
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
scored.sort((a, b) => a.rank !== b.rank ? a.rank - b.rank : a.command.name.localeCompare(b.command.name));
|
|
79
|
-
return scored.map(({ command }) => ({
|
|
80
|
-
id: command.name,
|
|
81
|
-
label: command.name,
|
|
82
|
-
description: command.aliases?.length ? `${command.description} (alias: ${command.aliases.map((a) => `/${a}`).join(", ")})` : command.description,
|
|
83
|
-
insertText: `/${command.name} `,
|
|
84
|
-
data: command
|
|
85
|
-
}));
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Build a commands completion provider against a *live* command list. The
|
|
89
|
-
* getter is re-evaluated per `suggest` so registry toggles / cwd rebuilds
|
|
90
|
-
* take effect without re-instantiating the provider.
|
|
91
|
-
*/
|
|
92
|
-
function createCommandsCompletionProvider(opts) {
|
|
93
|
-
return {
|
|
94
|
-
id: "commands",
|
|
95
|
-
trigger: "/",
|
|
96
|
-
label: "Commands",
|
|
97
|
-
allowSpacesInQuery: true,
|
|
98
|
-
suggest(query, ctx) {
|
|
99
|
-
const spaceIdx = query.search(/\s/);
|
|
100
|
-
if (spaceIdx !== -1) {
|
|
101
|
-
if (ctx.text.trimStart()[0] !== "/") return [];
|
|
102
|
-
const typed = query.slice(0, spaceIdx).toLowerCase();
|
|
103
|
-
const command = opts.getCommands().find((c) => c.name.toLowerCase() === typed || c.aliases?.some((a) => a.toLowerCase() === typed));
|
|
104
|
-
if (!command?.completeArgs) return [];
|
|
105
|
-
const argsPrefix = query.slice(spaceIdx + 1).replace(/^\s+/, "");
|
|
106
|
-
return Promise.resolve(command.completeArgs(argsPrefix)).then((suggestions) => (suggestions ?? []).map((s) => ({
|
|
107
|
-
id: `${command.name}:${s.value}`,
|
|
108
|
-
label: s.label ?? s.value,
|
|
109
|
-
...s.description !== void 0 ? { description: s.description } : {},
|
|
110
|
-
insertText: `/${command.name} ${s.value} `,
|
|
111
|
-
data: command
|
|
112
|
-
})));
|
|
113
|
-
}
|
|
114
|
-
return scoreCommands(opts.getCommands(), query);
|
|
115
|
-
},
|
|
116
|
-
parseReferences() {
|
|
117
|
-
return [];
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
//#endregion
|
|
122
44
|
//#region src/chat/mcps-write.ts
|
|
123
45
|
/**
|
|
124
46
|
* Resolve the CANONICAL write target for a scope — always the
|
|
@@ -240,6 +162,601 @@ function isRecord(value) {
|
|
|
240
162
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
241
163
|
}
|
|
242
164
|
//#endregion
|
|
165
|
+
//#region src/chat/claude-import.ts
|
|
166
|
+
function roots(opts) {
|
|
167
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
168
|
+
return {
|
|
169
|
+
projectRoot: findGitRoot(cwd) ?? cwd,
|
|
170
|
+
home: opts.home ?? homedir()
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** `~/.claude` — presence gates the settings action row. */
|
|
174
|
+
function hasClaudeCodeSetup(opts = {}) {
|
|
175
|
+
return existsSync(resolve(roots(opts).home, ".claude"));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Claude Code names each `~/.claude/projects/` subdirectory after the
|
|
179
|
+
* project path with every non-alphanumeric character flattened to `-`
|
|
180
|
+
* (`/Users/me/.claude/wt` → `-Users-me--claude-wt`). Lossy, so matching
|
|
181
|
+
* goes slug → slug rather than trying to reverse it.
|
|
182
|
+
*/
|
|
183
|
+
function claudeProjectSlug(path) {
|
|
184
|
+
return path.replace(/[^a-z0-9]/gi, "-");
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Session transcripts for the current project, or every project when
|
|
188
|
+
* `allProjects` is set.
|
|
189
|
+
*
|
|
190
|
+
* Claude Code slugs by LAUNCH directory, so a session started in a
|
|
191
|
+
* monorepo subdir (`repo/packages/foo`) lands in a different dir than
|
|
192
|
+
* one started at the repo root. Scoped listing therefore accepts both
|
|
193
|
+
* the exact slug of the resolved git root (realpath variants included)
|
|
194
|
+
* AND any dir whose slug extends it — but because slugs are lossy
|
|
195
|
+
* (`repo/foo` and a sibling `repo-foo` collide), prefix-matched dirs
|
|
196
|
+
* are confirmed per-file against the transcript's recorded `cwd`
|
|
197
|
+
* (resolved to its git root), mirroring how the converted session's
|
|
198
|
+
* `projectRoot` is derived.
|
|
199
|
+
*/
|
|
200
|
+
function listClaudeSessionFiles(opts = {}) {
|
|
201
|
+
const { projectRoot, home } = roots(opts);
|
|
202
|
+
const projectsDir = resolve(home, ".claude/projects");
|
|
203
|
+
if (!existsSync(projectsDir)) return [];
|
|
204
|
+
const slugs = [claudeProjectSlug(projectRoot), claudeProjectSlug(realpathOr(projectRoot))];
|
|
205
|
+
const exact = new Set(slugs);
|
|
206
|
+
const prefixes = [...new Set(slugs.map((s) => `${s}-`))];
|
|
207
|
+
const wantedRoots = /* @__PURE__ */ new Set([projectRoot, realpathOr(projectRoot)]);
|
|
208
|
+
const out = [];
|
|
209
|
+
for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
|
|
210
|
+
if (!entry.isDirectory()) continue;
|
|
211
|
+
const exactMatch = exact.has(entry.name);
|
|
212
|
+
if (!opts.allProjects && !exactMatch && !prefixes.some((p) => entry.name.startsWith(p))) continue;
|
|
213
|
+
const projectDir = join(projectsDir, entry.name);
|
|
214
|
+
for (const file of readdirSync(projectDir)) {
|
|
215
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
216
|
+
const path = join(projectDir, file);
|
|
217
|
+
if (!opts.allProjects) {
|
|
218
|
+
const root = transcriptProjectRoot(path);
|
|
219
|
+
if (root === null ? !exactMatch : !(wantedRoots.has(root) || wantedRoots.has(realpathOr(root)))) continue;
|
|
220
|
+
}
|
|
221
|
+
out.push({
|
|
222
|
+
path,
|
|
223
|
+
projectDir
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Peek a transcript's project root: scan the first chunk for the first
|
|
231
|
+
* record carrying a `cwd` and resolve it the same way
|
|
232
|
+
* `convertClaudeSession` does. When the chunk was truncated (giant first
|
|
233
|
+
* records — e.g. pasted base64 images) and held no cwd, fall back to the
|
|
234
|
+
* full file: the import reads it in full anyway, so correctness beats the
|
|
235
|
+
* shortcut. Null when no cwd exists at all (empty/torn file) or the file
|
|
236
|
+
* is unreadable.
|
|
237
|
+
*/
|
|
238
|
+
function transcriptProjectRoot(path) {
|
|
239
|
+
let text;
|
|
240
|
+
let truncated = false;
|
|
241
|
+
try {
|
|
242
|
+
const fd = openSync(path, "r");
|
|
243
|
+
try {
|
|
244
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
245
|
+
const n = readSync(fd, buf, 0, buf.length, 0);
|
|
246
|
+
text = buf.subarray(0, n).toString("utf8");
|
|
247
|
+
truncated = n === buf.length;
|
|
248
|
+
} finally {
|
|
249
|
+
closeSync(fd);
|
|
250
|
+
}
|
|
251
|
+
} catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const fromChunk = scanForCwd(text);
|
|
255
|
+
if (fromChunk !== null || !truncated) return fromChunk;
|
|
256
|
+
try {
|
|
257
|
+
return scanForCwd(readFileSync(path, "utf8"));
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function scanForCwd(text) {
|
|
263
|
+
for (const line of text.split("\n")) {
|
|
264
|
+
if (!line.trim()) continue;
|
|
265
|
+
try {
|
|
266
|
+
const record = JSON.parse(line);
|
|
267
|
+
if (typeof record.cwd === "string") return findGitRoot(record.cwd) ?? record.cwd;
|
|
268
|
+
} catch {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
function realpathOr(path) {
|
|
275
|
+
try {
|
|
276
|
+
return realpathSync(path);
|
|
277
|
+
} catch {
|
|
278
|
+
return path;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Convert one Claude Code session transcript (JSONL text) to a zidane
|
|
283
|
+
* `SessionData`. Returns null when no conversational turns survive the
|
|
284
|
+
* filter (hook noise, sidechains, queue bookkeeping).
|
|
285
|
+
*
|
|
286
|
+
* Kept-vs-dropped:
|
|
287
|
+
* - `user` / `assistant` records with a `message` → turns. Consecutive
|
|
288
|
+
* assistant records sharing a `message.id` merge into ONE turn (Claude
|
|
289
|
+
* writes text and tool_use as separate records of the same API message).
|
|
290
|
+
* - Consecutive tool_result-only user records merge into ONE user turn:
|
|
291
|
+
* Claude writes one record per result of a parallel tool batch, but
|
|
292
|
+
* zidane persists a batch as a single user turn and
|
|
293
|
+
* `ensureToolResultPairing` enforces strict adjacency — split turns
|
|
294
|
+
* would be "repaired" into orphan markers + error placeholders on resume.
|
|
295
|
+
* - `custom-title` / `summary` records → `metadata.title`.
|
|
296
|
+
* - Sidechains (subagent transcripts), `isMeta` records, `attachment`,
|
|
297
|
+
* `system`, `queue-operation`, `last-prompt` → dropped.
|
|
298
|
+
* - Thinking blocks → dropped (provider-bound signatures, not replayable).
|
|
299
|
+
*/
|
|
300
|
+
function convertClaudeSession(jsonl, opts = {}) {
|
|
301
|
+
const turns = [];
|
|
302
|
+
let title;
|
|
303
|
+
let claudeSessionId;
|
|
304
|
+
let cwd;
|
|
305
|
+
let lastAssistantMessageId;
|
|
306
|
+
for (const line of jsonl.split("\n")) {
|
|
307
|
+
if (!line.trim()) continue;
|
|
308
|
+
let record;
|
|
309
|
+
try {
|
|
310
|
+
record = JSON.parse(line);
|
|
311
|
+
} catch {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (record.type === "custom-title" && typeof record.customTitle === "string") {
|
|
315
|
+
title = record.customTitle;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (record.type === "summary" && typeof record.summary === "string") {
|
|
319
|
+
title ??= record.summary;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (record.type !== "user" && record.type !== "assistant") continue;
|
|
323
|
+
if (record.isSidechain || record.isMeta || !record.message) continue;
|
|
324
|
+
claudeSessionId ??= record.sessionId;
|
|
325
|
+
cwd ??= record.cwd;
|
|
326
|
+
const parsedTs = record.timestamp ? Date.parse(record.timestamp) : NaN;
|
|
327
|
+
const createdAt = Number.isNaN(parsedTs) ? Date.now() : parsedTs;
|
|
328
|
+
if (record.type === "assistant") {
|
|
329
|
+
const content = claudeBlocksToContent(record.message.content);
|
|
330
|
+
if (content.length === 0) continue;
|
|
331
|
+
const messageId = record.message.id;
|
|
332
|
+
const previous = turns[turns.length - 1];
|
|
333
|
+
if (messageId && messageId === lastAssistantMessageId && previous?.role === "assistant") {
|
|
334
|
+
previous.content.push(...content);
|
|
335
|
+
const usage = claudeUsage(record.message.usage, record.message.model);
|
|
336
|
+
if (usage) previous.usage = usage;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
lastAssistantMessageId = messageId;
|
|
340
|
+
const usage = claudeUsage(record.message.usage, record.message.model);
|
|
341
|
+
turns.push({
|
|
342
|
+
id: record.uuid ?? crypto.randomUUID(),
|
|
343
|
+
role: "assistant",
|
|
344
|
+
content,
|
|
345
|
+
createdAt,
|
|
346
|
+
...usage ? { usage } : {}
|
|
347
|
+
});
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
lastAssistantMessageId = void 0;
|
|
351
|
+
const content = claudeBlocksToContent(record.message.content);
|
|
352
|
+
if (content.length === 0) continue;
|
|
353
|
+
const previous = turns[turns.length - 1];
|
|
354
|
+
if (previous?.role === "user" && isToolResultOnly(content) && isToolResultOnly(previous.content)) {
|
|
355
|
+
previous.content.push(...content);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
turns.push({
|
|
359
|
+
id: record.uuid ?? crypto.randomUUID(),
|
|
360
|
+
role: "user",
|
|
361
|
+
content,
|
|
362
|
+
createdAt
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
if (turns.length === 0) return null;
|
|
366
|
+
const id = claudeSessionId ?? opts.fallbackId ?? crypto.randomUUID();
|
|
367
|
+
const projectRoot = cwd ? findGitRoot(cwd) ?? cwd : void 0;
|
|
368
|
+
return {
|
|
369
|
+
session: {
|
|
370
|
+
id,
|
|
371
|
+
...projectRoot ? { projectRoot } : {},
|
|
372
|
+
turns,
|
|
373
|
+
runs: [],
|
|
374
|
+
status: "idle",
|
|
375
|
+
metadata: {
|
|
376
|
+
...title ? { title } : {},
|
|
377
|
+
importedFrom: "claude-code",
|
|
378
|
+
claudeSessionId: id
|
|
379
|
+
},
|
|
380
|
+
createdAt: turns[0].createdAt,
|
|
381
|
+
updatedAt: turns[turns.length - 1].createdAt
|
|
382
|
+
},
|
|
383
|
+
claudeSessionId: id
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/** Map Claude/Anthropic content blocks to zidane `SessionContentBlock`s. */
|
|
387
|
+
function claudeBlocksToContent(content) {
|
|
388
|
+
if (typeof content === "string") return content.trim() ? [{
|
|
389
|
+
type: "text",
|
|
390
|
+
text: content
|
|
391
|
+
}] : [];
|
|
392
|
+
if (!Array.isArray(content)) return [];
|
|
393
|
+
const out = [];
|
|
394
|
+
for (const raw of content) {
|
|
395
|
+
if (!raw || typeof raw !== "object") continue;
|
|
396
|
+
const block = raw;
|
|
397
|
+
switch (block.type) {
|
|
398
|
+
case "text":
|
|
399
|
+
if (typeof block.text === "string" && block.text.trim()) out.push({
|
|
400
|
+
type: "text",
|
|
401
|
+
text: block.text
|
|
402
|
+
});
|
|
403
|
+
break;
|
|
404
|
+
case "tool_use":
|
|
405
|
+
if (typeof block.id === "string" && typeof block.name === "string") {
|
|
406
|
+
const input = block.input && typeof block.input === "object" && !Array.isArray(block.input) ? block.input : {};
|
|
407
|
+
out.push({
|
|
408
|
+
type: "tool_call",
|
|
409
|
+
id: block.id,
|
|
410
|
+
name: block.name,
|
|
411
|
+
input
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
break;
|
|
415
|
+
case "tool_result":
|
|
416
|
+
if (typeof block.tool_use_id === "string") out.push({
|
|
417
|
+
type: "tool_result",
|
|
418
|
+
callId: block.tool_use_id,
|
|
419
|
+
output: claudeToolResultOutput(block.content),
|
|
420
|
+
...block.is_error === true ? { isError: true } : {}
|
|
421
|
+
});
|
|
422
|
+
break;
|
|
423
|
+
case "image": {
|
|
424
|
+
const image = claudeImage(block);
|
|
425
|
+
if (image) out.push(image);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
function claudeImage(block) {
|
|
433
|
+
const source = block.source;
|
|
434
|
+
if (!source || source.type !== "base64" || typeof source.media_type !== "string" || typeof source.data !== "string") return null;
|
|
435
|
+
return {
|
|
436
|
+
type: "image",
|
|
437
|
+
mediaType: source.media_type,
|
|
438
|
+
data: source.data
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function claudeToolResultOutput(content) {
|
|
442
|
+
if (typeof content === "string") return content;
|
|
443
|
+
if (!Array.isArray(content)) return "";
|
|
444
|
+
const parts = [];
|
|
445
|
+
for (const raw of content) {
|
|
446
|
+
if (!raw || typeof raw !== "object") continue;
|
|
447
|
+
const block = raw;
|
|
448
|
+
if (block.type === "text" && typeof block.text === "string") parts.push({
|
|
449
|
+
type: "text",
|
|
450
|
+
text: block.text
|
|
451
|
+
});
|
|
452
|
+
else if (block.type === "image") {
|
|
453
|
+
const image = claudeImage(block);
|
|
454
|
+
if (image?.type === "image" && image.data) parts.push({
|
|
455
|
+
type: "image",
|
|
456
|
+
mediaType: image.mediaType,
|
|
457
|
+
data: image.data
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (parts.every((p) => p.type === "text")) return parts.map((p) => p.text).join("\n");
|
|
462
|
+
return parts;
|
|
463
|
+
}
|
|
464
|
+
function claudeUsage(usage, model) {
|
|
465
|
+
if (!usage || typeof usage !== "object") return void 0;
|
|
466
|
+
return {
|
|
467
|
+
input: usage.input_tokens ?? 0,
|
|
468
|
+
output: usage.output_tokens ?? 0,
|
|
469
|
+
...usage.cache_creation_input_tokens ? { cacheCreation: usage.cache_creation_input_tokens } : {},
|
|
470
|
+
...usage.cache_read_input_tokens ? { cacheRead: usage.cache_read_input_tokens } : {},
|
|
471
|
+
...model ? { modelId: model } : {}
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function isToolResultOnly(blocks) {
|
|
475
|
+
return blocks.length > 0 && blocks.every((b) => b.type === "tool_result");
|
|
476
|
+
}
|
|
477
|
+
/** Import Claude Code session transcripts into `store`. */
|
|
478
|
+
async function importClaudeSessions(store, opts = {}) {
|
|
479
|
+
const result = {
|
|
480
|
+
imported: 0,
|
|
481
|
+
skipped: 0,
|
|
482
|
+
empty: 0,
|
|
483
|
+
failed: []
|
|
484
|
+
};
|
|
485
|
+
for (const file of listClaudeSessionFiles(opts)) try {
|
|
486
|
+
const converted = convertClaudeSession(readFileSync(file.path, "utf8"), { fallbackId: basename(file.path, ".jsonl") });
|
|
487
|
+
if (!converted) {
|
|
488
|
+
result.empty++;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (await store.load(converted.session.id)) {
|
|
492
|
+
result.skipped++;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
await store.save(converted.session);
|
|
496
|
+
result.imported++;
|
|
497
|
+
} catch (err) {
|
|
498
|
+
result.failed.push({
|
|
499
|
+
path: file.path,
|
|
500
|
+
error: err instanceof Error ? err.message : String(err)
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Copy Claude Code's MCP servers into zidane-owned `mcps.json` files.
|
|
507
|
+
* The repo-shared `.mcp.json` writes to `<root>/.{prefix}/mcps.json`;
|
|
508
|
+
* BOTH `~/.claude.json` scopes write to `~/.{prefix}/mcps.json` —
|
|
509
|
+
* Claude's "local" scope (the per-project map inside the user state
|
|
510
|
+
* file) is private-by-design and routinely carries tokens in
|
|
511
|
+
* `env`/`headers`, so materializing it into a committable project file
|
|
512
|
+
* would leak credentials. User scope loses the per-project activation
|
|
513
|
+
* (zidane has no user-private per-project config), which is the safer
|
|
514
|
+
* direction to err. Collisions with names in ANY native config path are
|
|
515
|
+
* skipped.
|
|
516
|
+
*/
|
|
517
|
+
function importClaudeMcps(opts = {}) {
|
|
518
|
+
const { projectRoot, home } = roots(opts);
|
|
519
|
+
const result = {
|
|
520
|
+
copied: [],
|
|
521
|
+
skipped: [],
|
|
522
|
+
failed: []
|
|
523
|
+
};
|
|
524
|
+
const nativeNames = /* @__PURE__ */ new Set();
|
|
525
|
+
for (const candidate of defaultMcpsConfigPaths({
|
|
526
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
527
|
+
home,
|
|
528
|
+
...opts.prefix ? { prefix: opts.prefix } : {}
|
|
529
|
+
})) {
|
|
530
|
+
if (!existsSync(candidate.path)) continue;
|
|
531
|
+
for (const name of rawMcpNames(readFileSync(candidate.path, "utf8"))) nativeNames.add(name);
|
|
532
|
+
}
|
|
533
|
+
const sources = [];
|
|
534
|
+
const mcpJson = resolve(projectRoot, ".mcp.json");
|
|
535
|
+
if (existsSync(mcpJson)) try {
|
|
536
|
+
sources.push({
|
|
537
|
+
servers: parseMcpsFile(readFileSync(mcpJson, "utf8")),
|
|
538
|
+
scope: "project"
|
|
539
|
+
});
|
|
540
|
+
} catch (err) {
|
|
541
|
+
result.failed.push({
|
|
542
|
+
name: mcpJson,
|
|
543
|
+
error: err instanceof Error ? err.message : String(err)
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
const claudeState = resolve(home, ".claude.json");
|
|
547
|
+
if (existsSync(claudeState)) try {
|
|
548
|
+
const state = JSON.parse(readFileSync(claudeState, "utf8"));
|
|
549
|
+
sources.push({
|
|
550
|
+
servers: parseClaudeState(state, "claude-project-state", projectRoot),
|
|
551
|
+
scope: "user"
|
|
552
|
+
});
|
|
553
|
+
sources.push({
|
|
554
|
+
servers: parseClaudeState(state, "claude-user-state"),
|
|
555
|
+
scope: "user"
|
|
556
|
+
});
|
|
557
|
+
} catch (err) {
|
|
558
|
+
result.failed.push({
|
|
559
|
+
name: claudeState,
|
|
560
|
+
error: err instanceof Error ? err.message : String(err)
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
const seen = /* @__PURE__ */ new Set();
|
|
564
|
+
for (const { servers, scope } of sources) for (const server of servers) {
|
|
565
|
+
if (seen.has(server.name)) continue;
|
|
566
|
+
seen.add(server.name);
|
|
567
|
+
if (nativeNames.has(server.name)) {
|
|
568
|
+
result.skipped.push(server.name);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
try {
|
|
572
|
+
const target = mcpsWritePath(scope, {
|
|
573
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
574
|
+
home,
|
|
575
|
+
...opts.prefix ? { prefix: opts.prefix } : {}
|
|
576
|
+
});
|
|
577
|
+
const { action } = upsertMcpServer(target, server);
|
|
578
|
+
if (action === "updated") {
|
|
579
|
+
result.failed.push({
|
|
580
|
+
name: server.name,
|
|
581
|
+
error: `overwrote an existing entry in ${target} — restore it from version control if unintended`
|
|
582
|
+
});
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
result.copied.push(server.name);
|
|
586
|
+
} catch (err) {
|
|
587
|
+
result.failed.push({
|
|
588
|
+
name: server.name,
|
|
589
|
+
error: err instanceof Error ? err.message : String(err)
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Names physically present in an mcps config file, from the raw parsed
|
|
597
|
+
* shape — mirrors `upsertMcpServer`'s walk (flat array `name`s, wrapper
|
|
598
|
+
* `mcpServers` keys, bare-map keys) with none of discovery's filtering.
|
|
599
|
+
* Unreadable/malformed files yield no names; `upsertMcpServer` throws on
|
|
600
|
+
* those before writing, so they still can't be overwritten.
|
|
601
|
+
*/
|
|
602
|
+
function rawMcpNames(text) {
|
|
603
|
+
let raw;
|
|
604
|
+
try {
|
|
605
|
+
raw = parseJsonTolerant(text);
|
|
606
|
+
} catch {
|
|
607
|
+
return [];
|
|
608
|
+
}
|
|
609
|
+
if (Array.isArray(raw)) return raw.flatMap((entry) => entry && typeof entry === "object" && typeof entry.name === "string" ? [entry.name] : []);
|
|
610
|
+
if (raw && typeof raw === "object") {
|
|
611
|
+
const record = raw;
|
|
612
|
+
const map = record.mcpServers && typeof record.mcpServers === "object" && !Array.isArray(record.mcpServers) ? record.mcpServers : record;
|
|
613
|
+
return Object.keys(map);
|
|
614
|
+
}
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Copy `.claude/skills/<name>` directories (project + user) into the
|
|
619
|
+
* corresponding `.{prefix}/skills`. A skill "exists natively" when a
|
|
620
|
+
* directory of the same name sits in any native scan path — those are
|
|
621
|
+
* skipped, not overwritten.
|
|
622
|
+
*/
|
|
623
|
+
function importClaudeSkills(opts = {}) {
|
|
624
|
+
const { projectRoot, home } = roots(opts);
|
|
625
|
+
const prefix = (opts.prefix ?? ".zidane").replace(/^\./, "");
|
|
626
|
+
const result = {
|
|
627
|
+
copied: [],
|
|
628
|
+
skipped: [],
|
|
629
|
+
failed: []
|
|
630
|
+
};
|
|
631
|
+
const nativeNames = /* @__PURE__ */ new Set();
|
|
632
|
+
for (const scan of projectUserPaths({
|
|
633
|
+
subPath: "skills",
|
|
634
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
635
|
+
home,
|
|
636
|
+
...opts.prefix ? { prefix: opts.prefix } : {}
|
|
637
|
+
})) {
|
|
638
|
+
if (!existsSync(scan.path)) continue;
|
|
639
|
+
for (const entry of readdirSync(scan.path, { withFileTypes: true })) if (entry.isDirectory()) nativeNames.add(entry.name);
|
|
640
|
+
}
|
|
641
|
+
const pairs = [{
|
|
642
|
+
from: resolve(projectRoot, ".claude/skills"),
|
|
643
|
+
to: resolve(projectRoot, `.${prefix}/skills`)
|
|
644
|
+
}, {
|
|
645
|
+
from: resolve(home, ".claude/skills"),
|
|
646
|
+
to: resolve(home, `.${prefix}/skills`)
|
|
647
|
+
}];
|
|
648
|
+
for (const { from, to } of pairs) {
|
|
649
|
+
if (!existsSync(from)) continue;
|
|
650
|
+
for (const entry of readdirSync(from, { withFileTypes: true })) {
|
|
651
|
+
if (!entry.isDirectory()) continue;
|
|
652
|
+
if (!existsSync(join(from, entry.name, "SKILL.md"))) continue;
|
|
653
|
+
if (nativeNames.has(entry.name)) {
|
|
654
|
+
result.skipped.push(entry.name);
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
cpSync(join(from, entry.name), join(to, entry.name), {
|
|
659
|
+
recursive: true,
|
|
660
|
+
errorOnExist: true,
|
|
661
|
+
force: false
|
|
662
|
+
});
|
|
663
|
+
nativeNames.add(entry.name);
|
|
664
|
+
result.copied.push(entry.name);
|
|
665
|
+
} catch (err) {
|
|
666
|
+
result.failed.push({
|
|
667
|
+
name: entry.name,
|
|
668
|
+
error: err instanceof Error ? err.message : String(err)
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
async function importFromClaudeCode(store, opts = {}) {
|
|
676
|
+
const summary = { notes: ["CLAUDE.md is already read natively — nothing to import.", "Claude permissions/hooks/plugins have no zidane equivalent — not imported."] };
|
|
677
|
+
if (opts.sessions !== false) summary.sessions = await importClaudeSessions(store, opts);
|
|
678
|
+
if (opts.mcps !== false) summary.mcps = importClaudeMcps(opts);
|
|
679
|
+
if (opts.skills !== false) summary.skills = importClaudeSkills(opts);
|
|
680
|
+
return summary;
|
|
681
|
+
}
|
|
682
|
+
/** Filter + rank commands against a query (name-priority, then description, then fuzzy on name). */
|
|
683
|
+
function scoreCommands(catalog, query) {
|
|
684
|
+
const q = query.trim().toLowerCase();
|
|
685
|
+
const fuzzyHits = q.length > 0 ? new Set(new Fzf([...catalog], {
|
|
686
|
+
selector: (c) => c.name,
|
|
687
|
+
casing: "case-insensitive"
|
|
688
|
+
}).find(q).map((r) => r.item)) : null;
|
|
689
|
+
const scored = [];
|
|
690
|
+
for (const command of catalog) {
|
|
691
|
+
const name = command.name.toLowerCase();
|
|
692
|
+
const aliases = (command.aliases ?? []).map((a) => a.toLowerCase());
|
|
693
|
+
const group = command.group?.toLowerCase();
|
|
694
|
+
const desc = command.description.toLowerCase();
|
|
695
|
+
if (q.length === 0) scored.push({
|
|
696
|
+
command,
|
|
697
|
+
rank: 4
|
|
698
|
+
});
|
|
699
|
+
else if (name.startsWith(q) || aliases.some((a) => a.startsWith(q))) scored.push({
|
|
700
|
+
command,
|
|
701
|
+
rank: 0
|
|
702
|
+
});
|
|
703
|
+
else if (name.includes(q) || aliases.some((a) => a.includes(q)) || group !== void 0 && q.length >= 2 && group.startsWith(q)) scored.push({
|
|
704
|
+
command,
|
|
705
|
+
rank: 1
|
|
706
|
+
});
|
|
707
|
+
else if (desc.includes(q)) scored.push({
|
|
708
|
+
command,
|
|
709
|
+
rank: 2
|
|
710
|
+
});
|
|
711
|
+
else if (fuzzyHits?.has(command)) scored.push({
|
|
712
|
+
command,
|
|
713
|
+
rank: 3
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
scored.sort((a, b) => a.rank !== b.rank ? a.rank - b.rank : a.command.name.localeCompare(b.command.name));
|
|
717
|
+
return scored.map(({ command }) => ({
|
|
718
|
+
id: command.name,
|
|
719
|
+
label: command.name,
|
|
720
|
+
description: command.aliases?.length ? `${command.description} (alias: ${command.aliases.map((a) => `/${a}`).join(", ")})` : command.description,
|
|
721
|
+
insertText: `/${command.name} `,
|
|
722
|
+
data: command
|
|
723
|
+
}));
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Build a commands completion provider against a *live* command list. The
|
|
727
|
+
* getter is re-evaluated per `suggest` so registry toggles / cwd rebuilds
|
|
728
|
+
* take effect without re-instantiating the provider.
|
|
729
|
+
*/
|
|
730
|
+
function createCommandsCompletionProvider(opts) {
|
|
731
|
+
return {
|
|
732
|
+
id: "commands",
|
|
733
|
+
trigger: "/",
|
|
734
|
+
label: "Commands",
|
|
735
|
+
allowSpacesInQuery: true,
|
|
736
|
+
suggest(query, ctx) {
|
|
737
|
+
const spaceIdx = query.search(/\s/);
|
|
738
|
+
if (spaceIdx !== -1) {
|
|
739
|
+
if (ctx.text.trimStart()[0] !== "/") return [];
|
|
740
|
+
const typed = query.slice(0, spaceIdx).toLowerCase();
|
|
741
|
+
const command = opts.getCommands().find((c) => c.name.toLowerCase() === typed || c.aliases?.some((a) => a.toLowerCase() === typed));
|
|
742
|
+
if (!command?.completeArgs) return [];
|
|
743
|
+
const argsPrefix = query.slice(spaceIdx + 1).replace(/^\s+/, "");
|
|
744
|
+
return Promise.resolve(command.completeArgs(argsPrefix)).then((suggestions) => (suggestions ?? []).map((s) => ({
|
|
745
|
+
id: `${command.name}:${s.value}`,
|
|
746
|
+
label: s.label ?? s.value,
|
|
747
|
+
...s.description !== void 0 ? { description: s.description } : {},
|
|
748
|
+
insertText: `/${command.name} ${s.value} `,
|
|
749
|
+
data: command
|
|
750
|
+
})));
|
|
751
|
+
}
|
|
752
|
+
return scoreCommands(opts.getCommands(), query);
|
|
753
|
+
},
|
|
754
|
+
parseReferences() {
|
|
755
|
+
return [];
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
//#endregion
|
|
243
760
|
//#region src/mcp/warn-log.ts
|
|
244
761
|
/**
|
|
245
762
|
* Subscribe to `mcp:warn`
|
|
@@ -806,6 +1323,217 @@ function truncate(s, max) {
|
|
|
806
1323
|
return `${s.slice(0, max - 1)}…`;
|
|
807
1324
|
}
|
|
808
1325
|
//#endregion
|
|
1326
|
+
//#region src/tui/claude-import-modal.tsx
|
|
1327
|
+
/**
|
|
1328
|
+
* "Import from Claude Code" — launched from the settings modal's Agent
|
|
1329
|
+
* tab. Two phases:
|
|
1330
|
+
*
|
|
1331
|
+
* 1. Pick — toggle which pieces to import (sessions / MCP servers /
|
|
1332
|
+
* skills) and the session scope (this project vs every Claude
|
|
1333
|
+
* project), then ↵ on the Import row runs it.
|
|
1334
|
+
* 2. Done — the summary of what was imported / skipped / failed, plus
|
|
1335
|
+
* the standing notes about what is deliberately NOT imported
|
|
1336
|
+
* (CLAUDE.md is read natively; permissions/hooks have no zidane
|
|
1337
|
+
* equivalent).
|
|
1338
|
+
*
|
|
1339
|
+
* All writes go to zidane-owned locations (session store, `.{prefix}/`
|
|
1340
|
+
* files); the `.claude` setup itself is never touched. Idempotent — the
|
|
1341
|
+
* import skips whatever a previous run already brought over, so re-running
|
|
1342
|
+
* is always safe.
|
|
1343
|
+
*/
|
|
1344
|
+
function ClaudeImportModal({ store, cwd, prefix, onDone }) {
|
|
1345
|
+
const COLOR = useColors();
|
|
1346
|
+
const [options, setOptions] = useState({
|
|
1347
|
+
sessions: true,
|
|
1348
|
+
mcps: true,
|
|
1349
|
+
skills: true,
|
|
1350
|
+
allProjects: false
|
|
1351
|
+
});
|
|
1352
|
+
const [cursor, setCursor] = useState(0);
|
|
1353
|
+
const [phase, setPhase] = useState("pick");
|
|
1354
|
+
const [summary, setSummary] = useState(null);
|
|
1355
|
+
const [error, setError] = useState(null);
|
|
1356
|
+
const runningRef = useRef(false);
|
|
1357
|
+
const counts = useMemo(() => ({
|
|
1358
|
+
project: listClaudeSessionFiles({ cwd }).length,
|
|
1359
|
+
all: listClaudeSessionFiles({
|
|
1360
|
+
cwd,
|
|
1361
|
+
allProjects: true
|
|
1362
|
+
}).length
|
|
1363
|
+
}), [cwd]);
|
|
1364
|
+
const rows = [
|
|
1365
|
+
{
|
|
1366
|
+
key: "sessions",
|
|
1367
|
+
label: "Conversations",
|
|
1368
|
+
detail: `${options.allProjects ? counts.all : counts.project} transcript file(s) found`
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
key: "allProjects",
|
|
1372
|
+
label: "… from all projects",
|
|
1373
|
+
detail: `off = current project only (${counts.project} here, ${counts.all} total)`
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
key: "mcps",
|
|
1377
|
+
label: "MCP servers",
|
|
1378
|
+
detail: "copy into zidane-owned mcps.json (native names never overwritten)"
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
key: "skills",
|
|
1382
|
+
label: "Skills",
|
|
1383
|
+
detail: `copy .claude/skills into ${prefix ?? ".zidane"}/skills`
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
key: "run",
|
|
1387
|
+
label: "Import",
|
|
1388
|
+
detail: "run with the selection above"
|
|
1389
|
+
}
|
|
1390
|
+
];
|
|
1391
|
+
const runImport = async () => {
|
|
1392
|
+
if (runningRef.current) return;
|
|
1393
|
+
runningRef.current = true;
|
|
1394
|
+
setPhase("running");
|
|
1395
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1396
|
+
let result;
|
|
1397
|
+
try {
|
|
1398
|
+
result = await importFromClaudeCode(store, {
|
|
1399
|
+
cwd,
|
|
1400
|
+
...prefix ? { prefix } : {},
|
|
1401
|
+
sessions: options.sessions,
|
|
1402
|
+
mcps: options.mcps,
|
|
1403
|
+
skills: options.skills,
|
|
1404
|
+
allProjects: options.allProjects
|
|
1405
|
+
});
|
|
1406
|
+
setSummary(result);
|
|
1407
|
+
setPhase("done");
|
|
1408
|
+
} catch (err) {
|
|
1409
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1410
|
+
setPhase("done");
|
|
1411
|
+
} finally {
|
|
1412
|
+
runningRef.current = false;
|
|
1413
|
+
}
|
|
1414
|
+
if (result) try {
|
|
1415
|
+
await onDone?.(result);
|
|
1416
|
+
} catch {}
|
|
1417
|
+
};
|
|
1418
|
+
useKeyboard((key) => {
|
|
1419
|
+
if (phase !== "pick") return;
|
|
1420
|
+
if (key.name === "up" || key.ctrl && key.name === "p") setCursor((c) => (c - 1 + rows.length) % rows.length);
|
|
1421
|
+
else if (key.name === "down" || key.ctrl && key.name === "n") setCursor((c) => (c + 1) % rows.length);
|
|
1422
|
+
else if (key.name === "return" || key.name === "space") {
|
|
1423
|
+
const row = rows[cursor];
|
|
1424
|
+
if (row.key === "run") runImport();
|
|
1425
|
+
else setOptions((prev) => ({
|
|
1426
|
+
...prev,
|
|
1427
|
+
[row.key]: !prev[row.key]
|
|
1428
|
+
}));
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
if (phase === "running") return /* @__PURE__ */ jsx$1(Modal, {
|
|
1432
|
+
title: "Import from Claude Code",
|
|
1433
|
+
disableEscape: true,
|
|
1434
|
+
children: /* @__PURE__ */ jsx$1("text", {
|
|
1435
|
+
fg: COLOR.mute,
|
|
1436
|
+
children: "importing…"
|
|
1437
|
+
})
|
|
1438
|
+
});
|
|
1439
|
+
if (phase === "done") return /* @__PURE__ */ jsxs(Modal, {
|
|
1440
|
+
title: "Import from Claude Code",
|
|
1441
|
+
children: [/* @__PURE__ */ jsxs("box", {
|
|
1442
|
+
style: { flexDirection: "column" },
|
|
1443
|
+
children: [
|
|
1444
|
+
error && /* @__PURE__ */ jsx$1("text", {
|
|
1445
|
+
fg: COLOR.error,
|
|
1446
|
+
children: `import failed: ${error}`
|
|
1447
|
+
}),
|
|
1448
|
+
summary?.sessions && /* @__PURE__ */ jsxs("text", {
|
|
1449
|
+
fg: COLOR.dim,
|
|
1450
|
+
children: [
|
|
1451
|
+
`conversations: ${summary.sessions.imported} imported, ${summary.sessions.skipped} already present`,
|
|
1452
|
+
summary.sessions.empty > 0 ? `, ${summary.sessions.empty} empty` : "",
|
|
1453
|
+
summary.sessions.failed.length > 0 ? `, ${summary.sessions.failed.length} failed` : ""
|
|
1454
|
+
]
|
|
1455
|
+
}),
|
|
1456
|
+
summary?.mcps && /* @__PURE__ */ jsx$1("text", {
|
|
1457
|
+
fg: COLOR.dim,
|
|
1458
|
+
children: copyLine("mcp servers", summary.mcps)
|
|
1459
|
+
}),
|
|
1460
|
+
summary?.skills && /* @__PURE__ */ jsx$1("text", {
|
|
1461
|
+
fg: COLOR.dim,
|
|
1462
|
+
children: copyLine("skills", summary.skills)
|
|
1463
|
+
}),
|
|
1464
|
+
summary?.notes.map((note) => /* @__PURE__ */ jsx$1("text", {
|
|
1465
|
+
fg: COLOR.mute,
|
|
1466
|
+
children: `· ${note}`
|
|
1467
|
+
}, note))
|
|
1468
|
+
]
|
|
1469
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
1470
|
+
fg: COLOR.mute,
|
|
1471
|
+
children: [/* @__PURE__ */ jsx$1("span", {
|
|
1472
|
+
fg: COLOR.warn,
|
|
1473
|
+
children: "esc"
|
|
1474
|
+
}), " close"]
|
|
1475
|
+
})]
|
|
1476
|
+
});
|
|
1477
|
+
const enabledFor = (key) => key === "run" ? void 0 : options[key];
|
|
1478
|
+
return /* @__PURE__ */ jsxs(Modal, {
|
|
1479
|
+
title: "Import from Claude Code",
|
|
1480
|
+
children: [/* @__PURE__ */ jsx$1("box", {
|
|
1481
|
+
style: { flexDirection: "column" },
|
|
1482
|
+
children: rows.map((row, i) => {
|
|
1483
|
+
const focused = i === cursor;
|
|
1484
|
+
const enabled = enabledFor(row.key);
|
|
1485
|
+
return /* @__PURE__ */ jsxs("text", {
|
|
1486
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
1487
|
+
children: [
|
|
1488
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
1489
|
+
fg: focused ? COLOR.brand : COLOR.mute,
|
|
1490
|
+
children: focused ? "▶ " : " "
|
|
1491
|
+
}),
|
|
1492
|
+
enabled !== void 0 && /* @__PURE__ */ jsx$1("span", {
|
|
1493
|
+
fg: enabled ? COLOR.accent : COLOR.mute,
|
|
1494
|
+
children: enabled ? "[✓] " : "[ ] "
|
|
1495
|
+
}),
|
|
1496
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
1497
|
+
fg: focused ? COLOR.brand : COLOR.dim,
|
|
1498
|
+
children: row.label
|
|
1499
|
+
}),
|
|
1500
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1501
|
+
fg: COLOR.mute,
|
|
1502
|
+
children: [" ", row.detail]
|
|
1503
|
+
})
|
|
1504
|
+
]
|
|
1505
|
+
}, row.key);
|
|
1506
|
+
})
|
|
1507
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
1508
|
+
fg: COLOR.mute,
|
|
1509
|
+
children: [
|
|
1510
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
1511
|
+
fg: COLOR.warn,
|
|
1512
|
+
children: "↑↓"
|
|
1513
|
+
}),
|
|
1514
|
+
" navigate · ",
|
|
1515
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
1516
|
+
fg: COLOR.warn,
|
|
1517
|
+
children: "↵"
|
|
1518
|
+
}),
|
|
1519
|
+
" toggle / run · ",
|
|
1520
|
+
/* @__PURE__ */ jsx$1("span", {
|
|
1521
|
+
fg: COLOR.warn,
|
|
1522
|
+
children: "esc"
|
|
1523
|
+
}),
|
|
1524
|
+
" close"
|
|
1525
|
+
]
|
|
1526
|
+
})]
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
function copyLine(label, result) {
|
|
1530
|
+
const parts = [];
|
|
1531
|
+
parts.push(result.copied.length > 0 ? `${result.copied.length} copied (${result.copied.join(", ")})` : "none copied");
|
|
1532
|
+
if (result.skipped.length > 0) parts.push(`${result.skipped.length} skipped as native (${result.skipped.join(", ")})`);
|
|
1533
|
+
if (result.failed.length > 0) parts.push(`${result.failed.length} failed`);
|
|
1534
|
+
return `${label}: ${parts.join(" · ")}`;
|
|
1535
|
+
}
|
|
1536
|
+
//#endregion
|
|
809
1537
|
//#region src/tui/clipboard.ts
|
|
810
1538
|
/**
|
|
811
1539
|
* Two-pronged clipboard write.
|
|
@@ -4939,6 +5667,7 @@ function DiscoveryShell({ children }) {
|
|
|
4939
5667
|
const config = useConfig();
|
|
4940
5668
|
const { settings } = useSettings();
|
|
4941
5669
|
const foreignAgents = settings.importForeignConfigs === true;
|
|
5670
|
+
const rulerConfigs = settings.importRulerConfigs !== false;
|
|
4942
5671
|
const dispatchAuth = useMcpAuthDispatch();
|
|
4943
5672
|
const dispatchAuthRef = useRef(dispatchAuth);
|
|
4944
5673
|
dispatchAuthRef.current = dispatchAuth;
|
|
@@ -5010,7 +5739,8 @@ function DiscoveryShell({ children }) {
|
|
|
5010
5739
|
walk: () => discoverProjectSkills({
|
|
5011
5740
|
cwd: projectDir,
|
|
5012
5741
|
prefix: config.prefix,
|
|
5013
|
-
foreignAgents
|
|
5742
|
+
foreignAgents,
|
|
5743
|
+
ruler: rulerConfigs
|
|
5014
5744
|
}),
|
|
5015
5745
|
onLoad: (items) => {
|
|
5016
5746
|
if (skillsSlotRef.current === skillsSlot) setSkillsCatalog(items);
|
|
@@ -5026,7 +5756,8 @@ function DiscoveryShell({ children }) {
|
|
|
5026
5756
|
const { servers, errors } = discoverProjectMcps({
|
|
5027
5757
|
cwd: projectDir,
|
|
5028
5758
|
prefix: config.prefix,
|
|
5029
|
-
foreignAgents
|
|
5759
|
+
foreignAgents,
|
|
5760
|
+
ruler: rulerConfigs
|
|
5030
5761
|
});
|
|
5031
5762
|
mcpsFingerprintRef.current = JSON.stringify({
|
|
5032
5763
|
servers,
|
|
@@ -5047,7 +5778,8 @@ function DiscoveryShell({ children }) {
|
|
|
5047
5778
|
projectDir,
|
|
5048
5779
|
config.prefix,
|
|
5049
5780
|
seedMcpAuthBadges,
|
|
5050
|
-
foreignAgents
|
|
5781
|
+
foreignAgents,
|
|
5782
|
+
rulerConfigs
|
|
5051
5783
|
]);
|
|
5052
5784
|
const ensureFiles = useCallback(() => filesSlotRef.current?.ensure() ?? Promise.resolve([]), []);
|
|
5053
5785
|
const ensureSkills = useCallback(() => skillsSlotRef.current?.ensure() ?? Promise.resolve([]), []);
|
|
@@ -5058,7 +5790,8 @@ function DiscoveryShell({ children }) {
|
|
|
5058
5790
|
const { servers, errors } = discoverProjectMcps({
|
|
5059
5791
|
cwd: projectDir,
|
|
5060
5792
|
prefix: config.prefix,
|
|
5061
|
-
foreignAgents
|
|
5793
|
+
foreignAgents,
|
|
5794
|
+
ruler: rulerConfigs
|
|
5062
5795
|
});
|
|
5063
5796
|
const fingerprint = JSON.stringify({
|
|
5064
5797
|
servers,
|
|
@@ -5078,7 +5811,8 @@ function DiscoveryShell({ children }) {
|
|
|
5078
5811
|
projectDir,
|
|
5079
5812
|
config.prefix,
|
|
5080
5813
|
seedMcpAuthBadges,
|
|
5081
|
-
foreignAgents
|
|
5814
|
+
foreignAgents,
|
|
5815
|
+
rulerConfigs
|
|
5082
5816
|
]);
|
|
5083
5817
|
useEffect(() => {
|
|
5084
5818
|
let lastSignature = cacheFileSignature(dataDir);
|
|
@@ -13606,6 +14340,14 @@ function SettingsModal({ skillsCatalog: skillsCatalogProp, mcpsCatalog: mcpsCata
|
|
|
13606
14340
|
description: `current: ${settings.compactionModel || "main model"} — pick any provider's model for compaction summaries (pick the main model to clear)`,
|
|
13607
14341
|
onPick: actions.onPickCompactionModel
|
|
13608
14342
|
});
|
|
14343
|
+
if (actions?.onImportClaudeCode) acts.push({
|
|
14344
|
+
kind: "action",
|
|
14345
|
+
category: "agent",
|
|
14346
|
+
id: "import-claude-code",
|
|
14347
|
+
label: "Import from Claude Code",
|
|
14348
|
+
description: "copy conversations, MCP servers, and skills from your ~/.claude setup into zidane",
|
|
14349
|
+
onPick: actions.onImportClaudeCode
|
|
14350
|
+
});
|
|
13609
14351
|
const inputs = SETTINGS_INPUTS.filter((inp) => inp.key !== "sessionIcon" && inp.key !== "attentionIcon" && inp.key !== "activeIcon" || settings.terminalTitle === "icon-session").map((inp) => ({
|
|
13610
14352
|
kind: "input",
|
|
13611
14353
|
...inp
|
|
@@ -15091,7 +15833,7 @@ function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServe
|
|
|
15091
15833
|
if (totalCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
15092
15834
|
style: { flexDirection: "column" },
|
|
15093
15835
|
children: [
|
|
15094
|
-
renderMcpErrors(errors, home, COLOR.warn),
|
|
15836
|
+
renderMcpErrors(errors, home, COLOR.warn, COLOR.dim),
|
|
15095
15837
|
/* @__PURE__ */ jsx$1("text", {
|
|
15096
15838
|
fg: COLOR.dim,
|
|
15097
15839
|
children: "No MCP servers discovered."
|
|
@@ -15131,7 +15873,7 @@ function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServe
|
|
|
15131
15873
|
});
|
|
15132
15874
|
if (filteredCount === 0) return /* @__PURE__ */ jsxs("box", {
|
|
15133
15875
|
style: { flexDirection: "column" },
|
|
15134
|
-
children: [renderMcpErrors(errors, home, COLOR.warn), /* @__PURE__ */ jsx$1(EmptyRow, { label: `no servers match "${query}"` })]
|
|
15876
|
+
children: [renderMcpErrors(errors, home, COLOR.warn, COLOR.dim), /* @__PURE__ */ jsx$1(EmptyRow, { label: `no servers match "${query}"` })]
|
|
15135
15877
|
});
|
|
15136
15878
|
const groups = [];
|
|
15137
15879
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -15152,7 +15894,7 @@ function McpsList({ rows, filteredCount, enabledSet, toolToggleMap, toolsByServe
|
|
|
15152
15894
|
}
|
|
15153
15895
|
return /* @__PURE__ */ jsxs("box", {
|
|
15154
15896
|
style: { flexDirection: "column" },
|
|
15155
|
-
children: [renderMcpErrors(errors, home, COLOR.warn), groups.map((group) => /* @__PURE__ */ jsxs("box", {
|
|
15897
|
+
children: [renderMcpErrors(errors, home, COLOR.warn, COLOR.dim), groups.map((group) => /* @__PURE__ */ jsxs("box", {
|
|
15156
15898
|
style: { flexDirection: "column" },
|
|
15157
15899
|
children: [renderServerListRow({
|
|
15158
15900
|
entry: group.server.entry,
|
|
@@ -15749,13 +16491,13 @@ function renderInlineMcpBadge(status, COLOR) {
|
|
|
15749
16491
|
});
|
|
15750
16492
|
}
|
|
15751
16493
|
}
|
|
15752
|
-
function renderMcpErrors(errors, home, warnColor) {
|
|
16494
|
+
function renderMcpErrors(errors, home, warnColor, dimColor) {
|
|
15753
16495
|
if (!errors || errors.length === 0) return null;
|
|
15754
16496
|
return /* @__PURE__ */ jsx$1("box", {
|
|
15755
16497
|
style: { flexDirection: "column" },
|
|
15756
16498
|
children: errors.map((err) => /* @__PURE__ */ jsx$1("text", {
|
|
15757
|
-
fg: warnColor,
|
|
15758
|
-
children:
|
|
16499
|
+
fg: err.informational ? dimColor : warnColor,
|
|
16500
|
+
children: `${err.informational ? "i" : "!"} ${displayPath$1(err.path, home)}: ${err.message}`
|
|
15759
16501
|
}, err.path))
|
|
15760
16502
|
});
|
|
15761
16503
|
}
|
|
@@ -17149,6 +17891,7 @@ function AppShell() {
|
|
|
17149
17891
|
if (settings.maxCostUsd > 0) caps.maxCostUsd = settings.maxCostUsd;
|
|
17150
17892
|
if (settings.maxTotalTokens > 0) caps.maxTotalTokens = settings.maxTotalTokens;
|
|
17151
17893
|
if (settings.maxWallMinutes > 0) caps.maxWallMs = settings.maxWallMinutes * 6e4;
|
|
17894
|
+
if (settings.retryMaxAttempts > 0) caps.retry = { maxAttempts: settings.retryMaxAttempts };
|
|
17152
17895
|
return caps;
|
|
17153
17896
|
}, [
|
|
17154
17897
|
settings.resumeOnMaxTokens,
|
|
@@ -17156,7 +17899,8 @@ function AppShell() {
|
|
|
17156
17899
|
settings.maxTurns,
|
|
17157
17900
|
settings.maxCostUsd,
|
|
17158
17901
|
settings.maxTotalTokens,
|
|
17159
|
-
settings.maxWallMinutes
|
|
17902
|
+
settings.maxWallMinutes,
|
|
17903
|
+
settings.retryMaxAttempts
|
|
17160
17904
|
]);
|
|
17161
17905
|
const runCapsRef = useRef(runCaps);
|
|
17162
17906
|
runCapsRef.current = runCaps;
|
|
@@ -17238,6 +17982,8 @@ function AppShell() {
|
|
|
17238
17982
|
enabledSkillsRef.current = settings.enabledSkills;
|
|
17239
17983
|
const importForeignConfigsRef = useRef(settings.importForeignConfigs);
|
|
17240
17984
|
importForeignConfigsRef.current = settings.importForeignConfigs;
|
|
17985
|
+
const importRulerConfigsRef = useRef(settings.importRulerConfigs);
|
|
17986
|
+
importRulerConfigsRef.current = settings.importRulerConfigs;
|
|
17241
17987
|
const mcpsCatalogRef = useRef(mcpsCatalog);
|
|
17242
17988
|
mcpsCatalogRef.current = mcpsCatalog;
|
|
17243
17989
|
const enabledMcpsRef = useRef(settings.enabledMcps);
|
|
@@ -18127,7 +18873,8 @@ function AppShell() {
|
|
|
18127
18873
|
const skillsScan = defaultSkillScanPaths({
|
|
18128
18874
|
cwd: projectDir,
|
|
18129
18875
|
prefix: config.prefix,
|
|
18130
|
-
foreignAgents: importForeignConfigsRef.current === true
|
|
18876
|
+
foreignAgents: importForeignConfigsRef.current === true,
|
|
18877
|
+
ruler: importRulerConfigsRef.current !== false
|
|
18131
18878
|
});
|
|
18132
18879
|
const liveExtensionRegistry = filterExtensionRegistry(extensionRegistry, enabledExtensionsRef.current);
|
|
18133
18880
|
const extensionSkillNames = liveExtensionRegistry.skills.map((s) => s.name);
|
|
@@ -18147,7 +18894,8 @@ function AppShell() {
|
|
|
18147
18894
|
const agentsMd = discoverAgentsMd({
|
|
18148
18895
|
cwd: actualCwd,
|
|
18149
18896
|
prefix: config.prefix,
|
|
18150
|
-
scope: userInstructionsScopeRef.current
|
|
18897
|
+
scope: userInstructionsScopeRef.current,
|
|
18898
|
+
ruler: importRulerConfigsRef.current !== false
|
|
18151
18899
|
});
|
|
18152
18900
|
const envOpts = {
|
|
18153
18901
|
cwd: actualCwd,
|
|
@@ -18254,7 +19002,6 @@ function AppShell() {
|
|
|
18254
19002
|
tools: mergedTools,
|
|
18255
19003
|
...composedPreset.toolAliases ? { toolAliases: composedPreset.toolAliases } : {},
|
|
18256
19004
|
...effectiveSystem ? { system: effectiveSystem } : {},
|
|
18257
|
-
mcpServers: mergedMcpServers,
|
|
18258
19005
|
skills,
|
|
18259
19006
|
behavior: mergedBehavior,
|
|
18260
19007
|
spawnTool: seamCatalog.length === 0 ? seamSpawnBase : seamSpawnBase.withModels(seamCatalog)
|
|
@@ -18638,7 +19385,7 @@ function AppShell() {
|
|
|
18638
19385
|
lastInputTokensRef.current = ctx.effectiveTokens;
|
|
18639
19386
|
});
|
|
18640
19387
|
const spawnStartedAt = /* @__PURE__ */ new Map();
|
|
18641
|
-
agent.hooks.hook("spawn:before", ({ id, task, depth, model }) => {
|
|
19388
|
+
agent.hooks.hook("spawn:before", ({ id, task, depth, model, resumesChildId }) => {
|
|
18642
19389
|
const startedAt = Date.now();
|
|
18643
19390
|
const taskPreview = previewLine(task, 80);
|
|
18644
19391
|
spawnStartedAt.set(id, {
|
|
@@ -18648,7 +19395,7 @@ function AppShell() {
|
|
|
18648
19395
|
});
|
|
18649
19396
|
stream.appendImmediate({
|
|
18650
19397
|
kind: "spawn-start",
|
|
18651
|
-
text: taskPreview,
|
|
19398
|
+
text: resumesChildId ? `resumes ${resumesChildId} · ${taskPreview}` : taskPreview,
|
|
18652
19399
|
childId: id,
|
|
18653
19400
|
depth: depth ?? 1,
|
|
18654
19401
|
spawn: { startedAt },
|
|
@@ -19702,6 +20449,30 @@ function AppShell() {
|
|
|
19702
20449
|
modal,
|
|
19703
20450
|
setSetting
|
|
19704
20451
|
]);
|
|
20452
|
+
/**
|
|
20453
|
+
* "Import from Claude Code" — launched from the settings modal's Agent
|
|
20454
|
+
* tab (row only wired when `~/.claude` exists). The import modal
|
|
20455
|
+
* replaces the settings modal in the single modal slot; a successful
|
|
20456
|
+
* session import refreshes the sessions list through the live ref.
|
|
20457
|
+
*/
|
|
20458
|
+
const onImportClaudeCode = useCallback(() => {
|
|
20459
|
+
modal.open(/* @__PURE__ */ jsx$1(ClaudeImportModal, {
|
|
20460
|
+
store,
|
|
20461
|
+
cwd: cwdRef.current,
|
|
20462
|
+
...config.prefix ? { prefix: config.prefix } : {},
|
|
20463
|
+
onDone: (summary) => {
|
|
20464
|
+
if ((summary.sessions?.imported ?? 0) > 0) refreshSessionsRef.current().catch((err) => debugLog("claude-import: refreshSessions failed", err));
|
|
20465
|
+
if ((summary.mcps?.copied.length ?? 0) > 0) Promise.resolve(onRefreshMcps()).catch((err) => debugLog("claude-import: refreshMcps failed", err));
|
|
20466
|
+
if ((summary.skills?.copied.length ?? 0) > 0) Promise.resolve(onRefreshSkills()).catch((err) => debugLog("claude-import: refreshSkills failed", err));
|
|
20467
|
+
}
|
|
20468
|
+
}));
|
|
20469
|
+
}, [
|
|
20470
|
+
modal,
|
|
20471
|
+
store,
|
|
20472
|
+
config.prefix,
|
|
20473
|
+
onRefreshMcps,
|
|
20474
|
+
onRefreshSkills
|
|
20475
|
+
]);
|
|
19705
20476
|
const onPickEffort = useCallback((effort) => {
|
|
19706
20477
|
setPicked((prev) => {
|
|
19707
20478
|
if (!prev) return prev;
|
|
@@ -20134,11 +20905,22 @@ function AppShell() {
|
|
|
20134
20905
|
if (tasks.length === 0) return [];
|
|
20135
20906
|
await flushCommandTurn();
|
|
20136
20907
|
const handle = await agent.ensureExecution();
|
|
20908
|
+
let onAbort;
|
|
20137
20909
|
try {
|
|
20138
|
-
await agent.warmup()
|
|
20910
|
+
await Promise.race([agent.warmup(), new Promise((resolveAbort) => {
|
|
20911
|
+
if (controller.signal.aborted) {
|
|
20912
|
+
resolveAbort();
|
|
20913
|
+
return;
|
|
20914
|
+
}
|
|
20915
|
+
onAbort = () => resolveAbort();
|
|
20916
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
20917
|
+
})]);
|
|
20139
20918
|
} catch (err) {
|
|
20140
20919
|
debugLog("spawnAgents: warmup failed — children fall back to config bootstrap", err);
|
|
20920
|
+
} finally {
|
|
20921
|
+
if (onAbort) controller.signal.removeEventListener("abort", onAbort);
|
|
20141
20922
|
}
|
|
20923
|
+
if (controller.signal.aborted) throw new Error("spawnAgents: command aborted");
|
|
20142
20924
|
const parentMcp = agent.mcpConnection;
|
|
20143
20925
|
const results = tasks.map((task, i) => ({
|
|
20144
20926
|
label: task.label ?? `task-${i + 1}`,
|
|
@@ -20169,10 +20951,13 @@ function AppShell() {
|
|
|
20169
20951
|
...seam.system !== void 0 ? { system: seam.system } : {},
|
|
20170
20952
|
tools: seam.tools,
|
|
20171
20953
|
...seam.toolAliases !== void 0 ? { toolAliases: seam.toolAliases } : {},
|
|
20172
|
-
mcpServers:
|
|
20954
|
+
mcpServers: [...agent.mcpServers],
|
|
20173
20955
|
...parentMcp ? { mcpConnection: nonOwningMcpView(parentMcp) } : {},
|
|
20174
20956
|
skills: seam.skills,
|
|
20175
|
-
behavior:
|
|
20957
|
+
behavior: {
|
|
20958
|
+
...seam.behavior,
|
|
20959
|
+
...runCapsRef.current
|
|
20960
|
+
},
|
|
20176
20961
|
turnId: `command_${invocation.name}`,
|
|
20177
20962
|
callId: `command_spawn_${crypto.randomUUID()}`,
|
|
20178
20963
|
session
|
|
@@ -21436,7 +22221,8 @@ function AppShell() {
|
|
|
21436
22221
|
onAddMcp,
|
|
21437
22222
|
onRemoveSkill,
|
|
21438
22223
|
onRemoveMcp,
|
|
21439
|
-
onPickCompactionModel
|
|
22224
|
+
onPickCompactionModel,
|
|
22225
|
+
...hasClaudeCodeSetup() ? { onImportClaudeCode } : {}
|
|
21440
22226
|
}
|
|
21441
22227
|
}));
|
|
21442
22228
|
return;
|