zidane 6.2.20 → 6.2.22
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-BoQdQUwo.js → acp-Cwp_Y78H.js} +2 -2
- package/dist/{acp-BoQdQUwo.js.map → acp-Cwp_Y78H.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-CBK6ZUXs.js → agent-IPshmLIF.js} +15 -2
- package/dist/agent-IPshmLIF.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/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/{index-BRxJUFyo.d.ts → index-BDtxt4FY.d.ts} +26 -1
- package/dist/index-BDtxt4FY.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/{login-DreurRAq.js → login-BkZ_d5sT.js} +2 -2
- package/dist/{login-DreurRAq.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-HtxmBWs9.js → resolve-x5mlFtLX.js} +28 -13
- 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.d.ts +1 -1
- package/dist/{skills-DKWqoaYH.js → skills-C1vGKbqv.js} +2 -2
- package/dist/{skills-DKWqoaYH.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-CMrkqO4q.d.ts} +2 -2
- package/dist/{tool-formatters-Dokpl_jU.d.ts.map → tool-formatters-CMrkqO4q.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-Bvb8iMiY.d.ts} +8 -2
- package/dist/{transcript-anchors-5wRP7hln.d.ts.map → transcript-anchors-Bvb8iMiY.d.ts.map} +1 -1
- package/dist/{transcript-anchors-Bm3bYmOD.js → transcript-anchors-DvLIRYLz.js} +215 -21
- package/dist/transcript-anchors-DvLIRYLz.js.map +1 -0
- package/dist/tui.d.ts +11 -3
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +876 -95
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-D96guhSq.d.ts → turn-operations-BIw6n_H2.d.ts} +3 -3
- package/dist/{turn-operations-D96guhSq.d.ts.map → turn-operations-BIw6n_H2.d.ts.map} +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/agent-CBK6ZUXs.js.map +0 -1
- package/dist/index-BRxJUFyo.d.ts.map +0 -1
- package/dist/resolve-HtxmBWs9.js.map +0 -1
- package/dist/transcript-anchors-Bm3bYmOD.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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-DvLIRYLz.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-IPshmLIF.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";
|
|
@@ -14,8 +14,8 @@ import { a as getExtensionSessionState, i as demoteStaleRunningStatus, o as load
|
|
|
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
|
|
@@ -17238,6 +17980,8 @@ function AppShell() {
|
|
|
17238
17980
|
enabledSkillsRef.current = settings.enabledSkills;
|
|
17239
17981
|
const importForeignConfigsRef = useRef(settings.importForeignConfigs);
|
|
17240
17982
|
importForeignConfigsRef.current = settings.importForeignConfigs;
|
|
17983
|
+
const importRulerConfigsRef = useRef(settings.importRulerConfigs);
|
|
17984
|
+
importRulerConfigsRef.current = settings.importRulerConfigs;
|
|
17241
17985
|
const mcpsCatalogRef = useRef(mcpsCatalog);
|
|
17242
17986
|
mcpsCatalogRef.current = mcpsCatalog;
|
|
17243
17987
|
const enabledMcpsRef = useRef(settings.enabledMcps);
|
|
@@ -18127,7 +18871,8 @@ function AppShell() {
|
|
|
18127
18871
|
const skillsScan = defaultSkillScanPaths({
|
|
18128
18872
|
cwd: projectDir,
|
|
18129
18873
|
prefix: config.prefix,
|
|
18130
|
-
foreignAgents: importForeignConfigsRef.current === true
|
|
18874
|
+
foreignAgents: importForeignConfigsRef.current === true,
|
|
18875
|
+
ruler: importRulerConfigsRef.current !== false
|
|
18131
18876
|
});
|
|
18132
18877
|
const liveExtensionRegistry = filterExtensionRegistry(extensionRegistry, enabledExtensionsRef.current);
|
|
18133
18878
|
const extensionSkillNames = liveExtensionRegistry.skills.map((s) => s.name);
|
|
@@ -18147,7 +18892,8 @@ function AppShell() {
|
|
|
18147
18892
|
const agentsMd = discoverAgentsMd({
|
|
18148
18893
|
cwd: actualCwd,
|
|
18149
18894
|
prefix: config.prefix,
|
|
18150
|
-
scope: userInstructionsScopeRef.current
|
|
18895
|
+
scope: userInstructionsScopeRef.current,
|
|
18896
|
+
ruler: importRulerConfigsRef.current !== false
|
|
18151
18897
|
});
|
|
18152
18898
|
const envOpts = {
|
|
18153
18899
|
cwd: actualCwd,
|
|
@@ -18254,7 +19000,6 @@ function AppShell() {
|
|
|
18254
19000
|
tools: mergedTools,
|
|
18255
19001
|
...composedPreset.toolAliases ? { toolAliases: composedPreset.toolAliases } : {},
|
|
18256
19002
|
...effectiveSystem ? { system: effectiveSystem } : {},
|
|
18257
|
-
mcpServers: mergedMcpServers,
|
|
18258
19003
|
skills,
|
|
18259
19004
|
behavior: mergedBehavior,
|
|
18260
19005
|
spawnTool: seamCatalog.length === 0 ? seamSpawnBase : seamSpawnBase.withModels(seamCatalog)
|
|
@@ -19702,6 +20447,30 @@ function AppShell() {
|
|
|
19702
20447
|
modal,
|
|
19703
20448
|
setSetting
|
|
19704
20449
|
]);
|
|
20450
|
+
/**
|
|
20451
|
+
* "Import from Claude Code" — launched from the settings modal's Agent
|
|
20452
|
+
* tab (row only wired when `~/.claude` exists). The import modal
|
|
20453
|
+
* replaces the settings modal in the single modal slot; a successful
|
|
20454
|
+
* session import refreshes the sessions list through the live ref.
|
|
20455
|
+
*/
|
|
20456
|
+
const onImportClaudeCode = useCallback(() => {
|
|
20457
|
+
modal.open(/* @__PURE__ */ jsx$1(ClaudeImportModal, {
|
|
20458
|
+
store,
|
|
20459
|
+
cwd: cwdRef.current,
|
|
20460
|
+
...config.prefix ? { prefix: config.prefix } : {},
|
|
20461
|
+
onDone: (summary) => {
|
|
20462
|
+
if ((summary.sessions?.imported ?? 0) > 0) refreshSessionsRef.current().catch((err) => debugLog("claude-import: refreshSessions failed", err));
|
|
20463
|
+
if ((summary.mcps?.copied.length ?? 0) > 0) Promise.resolve(onRefreshMcps()).catch((err) => debugLog("claude-import: refreshMcps failed", err));
|
|
20464
|
+
if ((summary.skills?.copied.length ?? 0) > 0) Promise.resolve(onRefreshSkills()).catch((err) => debugLog("claude-import: refreshSkills failed", err));
|
|
20465
|
+
}
|
|
20466
|
+
}));
|
|
20467
|
+
}, [
|
|
20468
|
+
modal,
|
|
20469
|
+
store,
|
|
20470
|
+
config.prefix,
|
|
20471
|
+
onRefreshMcps,
|
|
20472
|
+
onRefreshSkills
|
|
20473
|
+
]);
|
|
19705
20474
|
const onPickEffort = useCallback((effort) => {
|
|
19706
20475
|
setPicked((prev) => {
|
|
19707
20476
|
if (!prev) return prev;
|
|
@@ -20134,11 +20903,22 @@ function AppShell() {
|
|
|
20134
20903
|
if (tasks.length === 0) return [];
|
|
20135
20904
|
await flushCommandTurn();
|
|
20136
20905
|
const handle = await agent.ensureExecution();
|
|
20906
|
+
let onAbort;
|
|
20137
20907
|
try {
|
|
20138
|
-
await agent.warmup()
|
|
20908
|
+
await Promise.race([agent.warmup(), new Promise((resolveAbort) => {
|
|
20909
|
+
if (controller.signal.aborted) {
|
|
20910
|
+
resolveAbort();
|
|
20911
|
+
return;
|
|
20912
|
+
}
|
|
20913
|
+
onAbort = () => resolveAbort();
|
|
20914
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
20915
|
+
})]);
|
|
20139
20916
|
} catch (err) {
|
|
20140
20917
|
debugLog("spawnAgents: warmup failed — children fall back to config bootstrap", err);
|
|
20918
|
+
} finally {
|
|
20919
|
+
if (onAbort) controller.signal.removeEventListener("abort", onAbort);
|
|
20141
20920
|
}
|
|
20921
|
+
if (controller.signal.aborted) throw new Error("spawnAgents: command aborted");
|
|
20142
20922
|
const parentMcp = agent.mcpConnection;
|
|
20143
20923
|
const results = tasks.map((task, i) => ({
|
|
20144
20924
|
label: task.label ?? `task-${i + 1}`,
|
|
@@ -20169,7 +20949,7 @@ function AppShell() {
|
|
|
20169
20949
|
...seam.system !== void 0 ? { system: seam.system } : {},
|
|
20170
20950
|
tools: seam.tools,
|
|
20171
20951
|
...seam.toolAliases !== void 0 ? { toolAliases: seam.toolAliases } : {},
|
|
20172
|
-
mcpServers:
|
|
20952
|
+
mcpServers: [...agent.mcpServers],
|
|
20173
20953
|
...parentMcp ? { mcpConnection: nonOwningMcpView(parentMcp) } : {},
|
|
20174
20954
|
skills: seam.skills,
|
|
20175
20955
|
behavior: seam.behavior,
|
|
@@ -21436,7 +22216,8 @@ function AppShell() {
|
|
|
21436
22216
|
onAddMcp,
|
|
21437
22217
|
onRemoveSkill,
|
|
21438
22218
|
onRemoveMcp,
|
|
21439
|
-
onPickCompactionModel
|
|
22219
|
+
onPickCompactionModel,
|
|
22220
|
+
...hasClaudeCodeSetup() ? { onImportClaudeCode } : {}
|
|
21440
22221
|
}
|
|
21441
22222
|
}));
|
|
21442
22223
|
return;
|