@kernelonpanic/kitcode 1.2.0 → 1.2.4
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/README.md +282 -281
- package/README.ru.md +276 -276
- package/dist/index.js +951 -414
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -69,9 +69,6 @@ var diagnosticsSchema = z.object({
|
|
|
69
69
|
autoRun: z.boolean().default(true),
|
|
70
70
|
commands: z.array(z.string().trim().min(1).max(2e4)).max(8).default([])
|
|
71
71
|
});
|
|
72
|
-
var updatesSchema = z.object({
|
|
73
|
-
checkOnStart: z.boolean().default(true)
|
|
74
|
-
});
|
|
75
72
|
var configSchema = z.object({
|
|
76
73
|
version: z.literal(1).default(1),
|
|
77
74
|
model: z.string().optional(),
|
|
@@ -86,7 +83,6 @@ var configSchema = z.object({
|
|
|
86
83
|
maxSubagentsPerTurn: 3
|
|
87
84
|
}),
|
|
88
85
|
diagnostics: diagnosticsSchema.default({ autoRun: true, commands: [] }),
|
|
89
|
-
updates: updatesSchema.default({ checkOnStart: false }),
|
|
90
86
|
providers: z.record(providerIdSchema, providerConfigSchema).default({}),
|
|
91
87
|
permissions: z.record(safeRecordKeySchema, permissionModeSchema).default({}),
|
|
92
88
|
mcp: z.record(safeRecordKeySchema, mcpServerSchema).default({})
|
|
@@ -153,19 +149,19 @@ var CONNECTION_REASONS = {
|
|
|
153
149
|
DEPTH_ZERO_SELF_SIGNED_CERT: "the TLS certificate is self-signed",
|
|
154
150
|
UNABLE_TO_VERIFY_LEAF_SIGNATURE: "the TLS certificate chain cannot be verified"
|
|
155
151
|
};
|
|
156
|
-
function describeProviderFailure(error, providerId) {
|
|
152
|
+
function describeProviderFailure(error, providerId, knownSecrets = []) {
|
|
157
153
|
const abort = abortKind(error);
|
|
158
154
|
if (abort === "user") return `Request to "${providerId}" was cancelled.`;
|
|
159
155
|
if (abort === "timeout") {
|
|
160
156
|
return `Request to "${providerId}" ran out of time before the provider answered \u2014 it may be overloaded, try again.`;
|
|
161
157
|
}
|
|
162
158
|
const status = statusOf(error);
|
|
163
|
-
if (status !== void 0) return describeStatus(status, providerId, error);
|
|
164
|
-
const reason = connectionReason(error);
|
|
159
|
+
if (status !== void 0) return describeStatus(status, providerId, error, knownSecrets);
|
|
160
|
+
const reason = connectionReason(error, knownSecrets);
|
|
165
161
|
if (reason) {
|
|
166
162
|
return `Cannot reach "${providerId}": ${reason}. Check the base URL and your network.`;
|
|
167
163
|
}
|
|
168
|
-
return `Request to "${providerId}" failed \u2014 ${snippet(messageOf(error))}`;
|
|
164
|
+
return `Request to "${providerId}" failed \u2014 ${snippet(messageOf(error), 160, knownSecrets)}`;
|
|
169
165
|
}
|
|
170
166
|
function statusOf(error) {
|
|
171
167
|
const value = prop(error, "status") ?? prop(error, "statusCode");
|
|
@@ -177,8 +173,8 @@ function isUserAbort(error, signal) {
|
|
|
177
173
|
function isConnectionFailure(error) {
|
|
178
174
|
return connectionReason(error) !== void 0;
|
|
179
175
|
}
|
|
180
|
-
function toProviderError(error, providerId) {
|
|
181
|
-
return new ProviderError(describeProviderFailure(error, providerId), providerId, statusOf(error), {
|
|
176
|
+
function toProviderError(error, providerId, knownSecrets = []) {
|
|
177
|
+
return new ProviderError(describeProviderFailure(error, providerId, knownSecrets), providerId, statusOf(error), {
|
|
182
178
|
cause: error
|
|
183
179
|
});
|
|
184
180
|
}
|
|
@@ -226,8 +222,8 @@ async function readHead(stream) {
|
|
|
226
222
|
}
|
|
227
223
|
return text;
|
|
228
224
|
}
|
|
229
|
-
async function invalidStreamError(providerId, capture, cause) {
|
|
230
|
-
const excerpt = snippet(await capture.head(), EXCERPT_CHARS);
|
|
225
|
+
async function invalidStreamError(providerId, capture, cause, knownSecrets = []) {
|
|
226
|
+
const excerpt = snippet(await capture.head(), EXCERPT_CHARS, knownSecrets);
|
|
231
227
|
const detail = excerpt === "" ? "the body was empty" : `the body began: ${excerpt}`;
|
|
232
228
|
return new ProviderError(
|
|
233
229
|
`"${providerId}" returned a response that is not a valid streaming chat completion \u2014 no stream events arrived and ${detail}. The endpoint may speak a different protocol than the one configured for it.`,
|
|
@@ -236,8 +232,8 @@ async function invalidStreamError(providerId, capture, cause) {
|
|
|
236
232
|
cause === void 0 ? void 0 : { cause }
|
|
237
233
|
);
|
|
238
234
|
}
|
|
239
|
-
function describeStatus(status, providerId, error) {
|
|
240
|
-
const detail = detailOf(error, status);
|
|
235
|
+
function describeStatus(status, providerId, error, knownSecrets) {
|
|
236
|
+
const detail = detailOf(error, status, knownSecrets);
|
|
241
237
|
const wait = retryAfterSeconds(error);
|
|
242
238
|
const after = wait === void 0 ? void 0 : `retry after ${wait}s`;
|
|
243
239
|
if (status === 401) {
|
|
@@ -272,7 +268,7 @@ function abortKind(error) {
|
|
|
272
268
|
if (name === "AbortError") return "user";
|
|
273
269
|
return void 0;
|
|
274
270
|
}
|
|
275
|
-
function connectionReason(error) {
|
|
271
|
+
function connectionReason(error, knownSecrets = []) {
|
|
276
272
|
const links = chain(error);
|
|
277
273
|
for (const link of links) {
|
|
278
274
|
for (const candidate of [link, ...siblings(link)]) {
|
|
@@ -282,9 +278,11 @@ function connectionReason(error) {
|
|
|
282
278
|
}
|
|
283
279
|
}
|
|
284
280
|
const deepest = links[links.length - 1];
|
|
285
|
-
if (links.length > 1 && deepest instanceof Error)
|
|
281
|
+
if (links.length > 1 && deepest instanceof Error) {
|
|
282
|
+
return snippet(deepest.message, 160, knownSecrets);
|
|
283
|
+
}
|
|
286
284
|
const message = messageOf(error);
|
|
287
|
-
return /fetch failed|connection error|socket|network/i.test(message) ? snippet(message) : void 0;
|
|
285
|
+
return /fetch failed|connection error|socket|network/i.test(message) ? snippet(message, 160, knownSecrets) : void 0;
|
|
288
286
|
}
|
|
289
287
|
function chain(error) {
|
|
290
288
|
const links = [];
|
|
@@ -317,9 +315,9 @@ function header(error, name) {
|
|
|
317
315
|
const value = headers[name];
|
|
318
316
|
return typeof value === "string" ? value : void 0;
|
|
319
317
|
}
|
|
320
|
-
function detailOf(error, status) {
|
|
318
|
+
function detailOf(error, status, knownSecrets) {
|
|
321
319
|
const raw = bodyMessage(error) ?? messageOf(error).replace(new RegExp(`^${status}\\s+`), "");
|
|
322
|
-
const text = snippet(raw);
|
|
320
|
+
const text = snippet(raw, 160, knownSecrets);
|
|
323
321
|
if (text === "" || text === String(status) || text === "status code (no body)") return "";
|
|
324
322
|
return ` \u2014 ${text}`;
|
|
325
323
|
}
|
|
@@ -336,8 +334,8 @@ function messageOf(error) {
|
|
|
336
334
|
if (error === null || error === void 0) return "unknown error";
|
|
337
335
|
return String(error);
|
|
338
336
|
}
|
|
339
|
-
function snippet(text, limit = 160) {
|
|
340
|
-
return redactSecrets(oneLine(text)).slice(0, limit);
|
|
337
|
+
function snippet(text, limit = 160, knownSecrets = []) {
|
|
338
|
+
return redactSecrets(oneLine(text), knownSecrets).slice(0, limit);
|
|
341
339
|
}
|
|
342
340
|
function oneLine(text) {
|
|
343
341
|
return text.replace(/\s+/g, " ").trim();
|
|
@@ -454,7 +452,7 @@ async function detectProvider(rawUrl, apiKey2, opts = {}) {
|
|
|
454
452
|
}
|
|
455
453
|
if (!attempt.ok && attempt.status !== 404 && attempt.status !== 0) break;
|
|
456
454
|
}
|
|
457
|
-
throw new Error(describeFailure(last));
|
|
455
|
+
throw new Error(describeFailure(last, apiKey2));
|
|
458
456
|
}
|
|
459
457
|
function normaliseBaseUrl(rawUrl) {
|
|
460
458
|
return rawUrl.trim().replace(/\/+$/, "");
|
|
@@ -591,9 +589,9 @@ function stringList(value) {
|
|
|
591
589
|
if (!Array.isArray(value)) return [];
|
|
592
590
|
return value.filter((item) => typeof item === "string");
|
|
593
591
|
}
|
|
594
|
-
function describeFailure(attempt) {
|
|
592
|
+
function describeFailure(attempt, apiKey2) {
|
|
595
593
|
const status = attempt.status === 0 ? "request failed" : `HTTP ${attempt.status}`;
|
|
596
|
-
const snippet2 = redactSecrets(attempt.text.trim()).slice(0, 200);
|
|
594
|
+
const snippet2 = redactSecrets(attempt.text.trim(), [apiKey2]).slice(0, 200);
|
|
597
595
|
const detail = snippet2 === "" ? "" : `: ${snippet2}`;
|
|
598
596
|
return `No OpenAI- or Anthropic-compatible API found at ${attempt.url} (${status})${detail}`;
|
|
599
597
|
}
|
|
@@ -757,9 +755,16 @@ async function configLocation() {
|
|
|
757
755
|
active ??= await resolveConfigLocation();
|
|
758
756
|
return active;
|
|
759
757
|
}
|
|
758
|
+
async function loadConfig(cwd) {
|
|
759
|
+
const location = await resolveConfigLocation(cwd);
|
|
760
|
+
return loadConfigAt(location);
|
|
761
|
+
}
|
|
760
762
|
async function loadProjectConfig(dir) {
|
|
761
763
|
return loadConfigAt({ path: projectConfigPath(dir), scope: "project" });
|
|
762
764
|
}
|
|
765
|
+
async function loadGlobalConfig() {
|
|
766
|
+
return loadConfigAt({ path: configPath, scope: "global" });
|
|
767
|
+
}
|
|
763
768
|
async function loadRuntimeConfig(cwd) {
|
|
764
769
|
const location = await resolveConfigLocation(cwd);
|
|
765
770
|
if (location.scope === "project" && !await isWorkspaceTrusted(cwd)) {
|
|
@@ -886,7 +891,7 @@ async function addProvider(url, key, options = {}) {
|
|
|
886
891
|
await initProjectConfig(cwd);
|
|
887
892
|
await trustWorkspace(cwd);
|
|
888
893
|
}
|
|
889
|
-
const config = options.local ? await loadProjectConfig(cwd) :
|
|
894
|
+
const config = options.local ? await loadProjectConfig(cwd) : process.env.KITCODE_CONFIG ? await loadConfig(cwd) : await loadGlobalConfig();
|
|
890
895
|
const auth = await loadAuth();
|
|
891
896
|
const configBefore = structuredClone(config);
|
|
892
897
|
const authBefore = { ...auth };
|
|
@@ -1299,12 +1304,12 @@ function exportFileName(state) {
|
|
|
1299
1304
|
}
|
|
1300
1305
|
function renderSessionMarkdown(state) {
|
|
1301
1306
|
const lines = [
|
|
1302
|
-
`# ${state.title || `KitCode session ${shortSessionId(state.id)}`}`,
|
|
1307
|
+
`# ${escapeMarkdownInline(state.title || `KitCode session ${shortSessionId(state.id)}`)}`,
|
|
1303
1308
|
"",
|
|
1304
|
-
`- Session: ${state.id}`,
|
|
1305
|
-
`- Workspace: ${state.cwd}`,
|
|
1306
|
-
`- Model: ${state.model || "unknown"}`,
|
|
1307
|
-
`- Updated: ${state.updatedAt}`,
|
|
1309
|
+
`- Session: ${escapeMarkdownInline(state.id)}`,
|
|
1310
|
+
`- Workspace: ${escapeMarkdownInline(state.cwd)}`,
|
|
1311
|
+
`- Model: ${escapeMarkdownInline(state.model || "unknown")}`,
|
|
1312
|
+
`- Updated: ${escapeMarkdownInline(state.updatedAt)}`,
|
|
1308
1313
|
""
|
|
1309
1314
|
];
|
|
1310
1315
|
for (const message of state.messages) {
|
|
@@ -1319,18 +1324,41 @@ function renderMessage(content) {
|
|
|
1319
1324
|
const sections = [];
|
|
1320
1325
|
for (const block of content) {
|
|
1321
1326
|
if (block.type === "text") sections.push(block.text);
|
|
1322
|
-
else if (block.type === "image")
|
|
1323
|
-
|
|
1327
|
+
else if (block.type === "image") {
|
|
1328
|
+
sections.push(`[Image attached: ${escapeMarkdownInline(block.name)}]`);
|
|
1329
|
+
} else if (block.type === "file") {
|
|
1330
|
+
sections.push(
|
|
1331
|
+
`[File attached: ${escapeMarkdownInline(block.name)}]
|
|
1324
1332
|
|
|
1325
|
-
${block.text}`
|
|
1326
|
-
|
|
1327
|
-
else if (block.type === "
|
|
1333
|
+
${markdownCodeFence(block.text)}`
|
|
1334
|
+
);
|
|
1335
|
+
} else if (block.type === "tool_use") {
|
|
1336
|
+
sections.push(`> Tool: ${escapeMarkdownInline(block.name)}`);
|
|
1337
|
+
} else if (block.type === "tool_result") {
|
|
1338
|
+
const result = block.content.replace(/\r\n?/g, "\n").split("\n").map((line) => `> ${line}`).join("\n");
|
|
1328
1339
|
sections.push(`> Tool result${block.isError ? " (error)" : ""}:
|
|
1329
|
-
|
|
1340
|
+
${result}`);
|
|
1330
1341
|
}
|
|
1331
1342
|
}
|
|
1332
1343
|
return sections.join("\n\n").trim();
|
|
1333
1344
|
}
|
|
1345
|
+
function escapeMarkdownInline(value) {
|
|
1346
|
+
return value.replace(/[\r\n\t]+/g, " ").replace(/([\\`*_[\]<>])/g, "\\$1");
|
|
1347
|
+
}
|
|
1348
|
+
function markdownCodeFence(value) {
|
|
1349
|
+
const normalized = value.replace(/\r\n?/g, "\n");
|
|
1350
|
+
const backtickLength = longestMarkerRun(normalized, /`+/g) + 1;
|
|
1351
|
+
const tildeLength = longestMarkerRun(normalized, /~+/g) + 1;
|
|
1352
|
+
const marker = backtickLength <= tildeLength ? "`" : "~";
|
|
1353
|
+
const fence = marker.repeat(Math.max(3, Math.min(backtickLength, tildeLength)));
|
|
1354
|
+
return `${fence}
|
|
1355
|
+
${normalized}${normalized.endsWith("\n") ? "" : "\n"}${fence}`;
|
|
1356
|
+
}
|
|
1357
|
+
function longestMarkerRun(value, pattern) {
|
|
1358
|
+
let longest = 0;
|
|
1359
|
+
for (const match of value.matchAll(pattern)) longest = Math.max(longest, match[0].length);
|
|
1360
|
+
return longest;
|
|
1361
|
+
}
|
|
1334
1362
|
function readContextUsage(value) {
|
|
1335
1363
|
if (typeof value !== "object" || value === null) return void 0;
|
|
1336
1364
|
const candidate = value;
|
|
@@ -2509,8 +2537,8 @@ function looksLikeAttachmentPath(value) {
|
|
|
2509
2537
|
if (path7.isAbsolute(candidate)) return true;
|
|
2510
2538
|
if (/^(?:~|\.{1,2})[\\/]/.test(candidate)) return true;
|
|
2511
2539
|
if (candidate.includes("/") || candidate.includes("\\")) return true;
|
|
2512
|
-
const
|
|
2513
|
-
return path7.extname(
|
|
2540
|
+
const basename3 = path7.basename(candidate).toLowerCase();
|
|
2541
|
+
return path7.extname(basename3) !== "" || AUTO_PATH_NAMES.has(basename3);
|
|
2514
2542
|
}
|
|
2515
2543
|
async function loadClipboardImage(platform = process.platform, runner = runClipboardCommand) {
|
|
2516
2544
|
const commands = clipboardCommands(platform);
|
|
@@ -2586,8 +2614,8 @@ function resolveAttachmentPath(cwd, requestedPath) {
|
|
|
2586
2614
|
return path7.isAbsolute(expanded) ? path7.normalize(expanded) : path7.resolve(cwd, expanded);
|
|
2587
2615
|
}
|
|
2588
2616
|
function isSensitiveAutomaticPath(file) {
|
|
2589
|
-
const
|
|
2590
|
-
return
|
|
2617
|
+
const basename3 = path7.basename(file).toLowerCase();
|
|
2618
|
+
return basename3.startsWith(".env.") || SENSITIVE_AUTO_NAMES.has(basename3) || SENSITIVE_AUTO_EXTENSIONS.has(path7.extname(basename3));
|
|
2591
2619
|
}
|
|
2592
2620
|
function attachmentLabel(block) {
|
|
2593
2621
|
if (block.type === "image") return `image: ${block.name}`;
|
|
@@ -3424,7 +3452,7 @@ function clip(value) {
|
|
|
3424
3452
|
// package.json
|
|
3425
3453
|
var package_default = {
|
|
3426
3454
|
name: "@kernelonpanic/kitcode",
|
|
3427
|
-
version: "1.2.
|
|
3455
|
+
version: "1.2.4",
|
|
3428
3456
|
description: "Terminal coding agent with a config you never have to write by hand",
|
|
3429
3457
|
type: "module",
|
|
3430
3458
|
license: "MIT",
|
|
@@ -3465,6 +3493,7 @@ var package_default = {
|
|
|
3465
3493
|
"ink-text-input": "^6.0.0",
|
|
3466
3494
|
openai: "^7.3.0",
|
|
3467
3495
|
react: "^19.2.8",
|
|
3496
|
+
"string-width": "^8.2.2",
|
|
3468
3497
|
zod: "^4.4.3"
|
|
3469
3498
|
},
|
|
3470
3499
|
devDependencies: {
|
|
@@ -3491,8 +3520,7 @@ var package_default = {
|
|
|
3491
3520
|
|
|
3492
3521
|
// src/version.ts
|
|
3493
3522
|
var KITCODE_VERSION = package_default.version;
|
|
3494
|
-
var
|
|
3495
|
-
var KITCODE_COMMIT = true ? "c93b03ff85112af7b4c28f543aff6b9377f84ad6" : "development";
|
|
3523
|
+
var KITCODE_COMMIT = true ? "84441dfba8c67477cf72b76552e72ffedc8d41a8" : "development";
|
|
3496
3524
|
|
|
3497
3525
|
// src/mcp/client.ts
|
|
3498
3526
|
var clientInfo = { name: "kitcode", version: KITCODE_VERSION };
|
|
@@ -3633,9 +3661,9 @@ function createMcpManager(servers) {
|
|
|
3633
3661
|
serverStates.delete(name);
|
|
3634
3662
|
},
|
|
3635
3663
|
async close() {
|
|
3636
|
-
const
|
|
3664
|
+
const open3 = [...sessions.values()];
|
|
3637
3665
|
sessions.clear();
|
|
3638
|
-
await Promise.all(
|
|
3666
|
+
await Promise.all(open3.map((session) => session.client.close().catch(() => {
|
|
3639
3667
|
})));
|
|
3640
3668
|
},
|
|
3641
3669
|
states() {
|
|
@@ -4071,12 +4099,12 @@ function createAnthropicProvider(args) {
|
|
|
4071
4099
|
return {
|
|
4072
4100
|
id: args.id,
|
|
4073
4101
|
kind: "anthropic",
|
|
4074
|
-
stream: (req) => streamTurn(client, args.id, req),
|
|
4075
|
-
listModels: () => listModels(client, args.id),
|
|
4102
|
+
stream: (req) => streamTurn(client, args.id, args.apiKey, req),
|
|
4103
|
+
listModels: () => listModels(client, args.id, args.apiKey),
|
|
4076
4104
|
knownModels: () => KNOWN_MODELS
|
|
4077
4105
|
};
|
|
4078
4106
|
}
|
|
4079
|
-
async function* streamTurn(client, providerId, req) {
|
|
4107
|
+
async function* streamTurn(client, providerId, apiKey2, req) {
|
|
4080
4108
|
const params = {
|
|
4081
4109
|
model: req.model,
|
|
4082
4110
|
max_tokens: req.maxTokens,
|
|
@@ -4117,9 +4145,9 @@ async function* streamTurn(client, providerId, req) {
|
|
|
4117
4145
|
const limits2 = parseRateLimits(capture.headers());
|
|
4118
4146
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4119
4147
|
if (events === 0 && capture.succeeded() && !isConnectionFailure(error)) {
|
|
4120
|
-
throw await invalidStreamError(providerId, capture, error);
|
|
4148
|
+
throw await invalidStreamError(providerId, capture, error, [apiKey2]);
|
|
4121
4149
|
}
|
|
4122
|
-
throw toProviderError(error, providerId);
|
|
4150
|
+
throw toProviderError(error, providerId, [apiKey2]);
|
|
4123
4151
|
}
|
|
4124
4152
|
const stopReason = STOP_REASONS[final.stop_reason ?? ""] ?? "end_turn";
|
|
4125
4153
|
const content = toContentBlocks(final.content);
|
|
@@ -4138,7 +4166,7 @@ async function* streamTurn(client, providerId, req) {
|
|
|
4138
4166
|
...stopReason === "refusal" ? { refusal: toRefusal(final.stop_details) } : {}
|
|
4139
4167
|
};
|
|
4140
4168
|
}
|
|
4141
|
-
async function listModels(client, providerId) {
|
|
4169
|
+
async function listModels(client, providerId, apiKey2) {
|
|
4142
4170
|
try {
|
|
4143
4171
|
const models = [];
|
|
4144
4172
|
for await (const model of client.models.list({ limit: 100 })) {
|
|
@@ -4152,7 +4180,7 @@ async function listModels(client, providerId) {
|
|
|
4152
4180
|
}
|
|
4153
4181
|
return models;
|
|
4154
4182
|
} catch (error) {
|
|
4155
|
-
throw toProviderError(error, providerId);
|
|
4183
|
+
throw toProviderError(error, providerId, [apiKey2]);
|
|
4156
4184
|
}
|
|
4157
4185
|
}
|
|
4158
4186
|
function toMessageParams(messages) {
|
|
@@ -4256,12 +4284,12 @@ function createOpenAiProvider(args) {
|
|
|
4256
4284
|
return {
|
|
4257
4285
|
id: args.id,
|
|
4258
4286
|
kind: "openai",
|
|
4259
|
-
stream: (req) => streamTurn2(client, args.id, req),
|
|
4260
|
-
listModels: () => listModels2(client, args.id),
|
|
4287
|
+
stream: (req) => streamTurn2(client, args.id, args.apiKey, req),
|
|
4288
|
+
listModels: () => listModels2(client, args.id, args.apiKey),
|
|
4261
4289
|
knownModels: () => []
|
|
4262
4290
|
};
|
|
4263
4291
|
}
|
|
4264
|
-
async function* streamTurn2(client, providerId, req) {
|
|
4292
|
+
async function* streamTurn2(client, providerId, apiKey2, req) {
|
|
4265
4293
|
let text = "";
|
|
4266
4294
|
let thinking = "";
|
|
4267
4295
|
let finishReason = null;
|
|
@@ -4309,19 +4337,20 @@ async function* streamTurn2(client, providerId, req) {
|
|
|
4309
4337
|
if (!isUserAbort(error, req.signal)) {
|
|
4310
4338
|
const limits2 = parseRateLimits(capture.headers());
|
|
4311
4339
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4312
|
-
throw toProviderError(error, providerId);
|
|
4340
|
+
throw toProviderError(error, providerId, [apiKey2]);
|
|
4313
4341
|
}
|
|
4314
4342
|
aborted = true;
|
|
4315
4343
|
}
|
|
4316
|
-
const content = toContentBlocks2(thinking, text, calls);
|
|
4317
4344
|
if (aborted || req.signal?.aborted) {
|
|
4345
|
+
const content2 = toTextContentBlocks(thinking, text);
|
|
4318
4346
|
if (sawUsage) yield { type: "usage", usage };
|
|
4319
4347
|
const limits2 = parseRateLimits(capture.headers());
|
|
4320
4348
|
if (limits2) yield { type: "rate_limits", limits: limits2 };
|
|
4321
|
-
yield { type: "done", stopReason: "aborted", content };
|
|
4349
|
+
yield { type: "done", stopReason: "aborted", content: content2 };
|
|
4322
4350
|
return;
|
|
4323
4351
|
}
|
|
4324
|
-
if (recognised === 0) throw await invalidStreamError(providerId, capture);
|
|
4352
|
+
if (recognised === 0) throw await invalidStreamError(providerId, capture, void 0, [apiKey2]);
|
|
4353
|
+
const content = toContentBlocks2(providerId, thinking, text, calls);
|
|
4325
4354
|
for (const block of content) {
|
|
4326
4355
|
if (block.type === "tool_use") {
|
|
4327
4356
|
yield { type: "tool_call", id: block.id, name: block.name, input: block.input };
|
|
@@ -4338,7 +4367,7 @@ function resolveStop(finishReason, hasCalls) {
|
|
|
4338
4367
|
if (hasCalls) return "tool_use";
|
|
4339
4368
|
return mapped ?? "end_turn";
|
|
4340
4369
|
}
|
|
4341
|
-
async function listModels2(client, providerId) {
|
|
4370
|
+
async function listModels2(client, providerId, apiKey2) {
|
|
4342
4371
|
try {
|
|
4343
4372
|
const models = [];
|
|
4344
4373
|
for await (const model of client.models.list()) {
|
|
@@ -4347,7 +4376,7 @@ async function listModels2(client, providerId) {
|
|
|
4347
4376
|
}
|
|
4348
4377
|
return models;
|
|
4349
4378
|
} catch (error) {
|
|
4350
|
-
throw toProviderError(error, providerId);
|
|
4379
|
+
throw toProviderError(error, providerId, [apiKey2]);
|
|
4351
4380
|
}
|
|
4352
4381
|
}
|
|
4353
4382
|
function accumulate(calls, delta) {
|
|
@@ -4357,10 +4386,8 @@ function accumulate(calls, delta) {
|
|
|
4357
4386
|
if (delta.function?.arguments) call.args += delta.function.arguments;
|
|
4358
4387
|
calls.set(delta.index, call);
|
|
4359
4388
|
}
|
|
4360
|
-
function toContentBlocks2(thinking, text, calls) {
|
|
4361
|
-
const content =
|
|
4362
|
-
if (thinking) content.push({ type: "thinking", text: thinking });
|
|
4363
|
-
if (text) content.push({ type: "text", text });
|
|
4389
|
+
function toContentBlocks2(providerId, thinking, text, calls) {
|
|
4390
|
+
const content = toTextContentBlocks(thinking, text);
|
|
4364
4391
|
const sorted = [...calls.entries()].sort((a, b) => a[0] - b[0]);
|
|
4365
4392
|
const tmpId = (position) => `call_${position}`;
|
|
4366
4393
|
for (const [position, [, call]] of sorted.entries()) {
|
|
@@ -4368,18 +4395,34 @@ function toContentBlocks2(thinking, text, calls) {
|
|
|
4368
4395
|
type: "tool_use",
|
|
4369
4396
|
id: call.id || tmpId(position),
|
|
4370
4397
|
name: call.name,
|
|
4371
|
-
input: parseArguments(call.args)
|
|
4398
|
+
input: parseArguments(providerId, call.name, call.args)
|
|
4372
4399
|
});
|
|
4373
4400
|
}
|
|
4374
4401
|
return content;
|
|
4375
4402
|
}
|
|
4376
|
-
function
|
|
4377
|
-
|
|
4403
|
+
function toTextContentBlocks(thinking, text) {
|
|
4404
|
+
const content = [];
|
|
4405
|
+
if (thinking) content.push({ type: "thinking", text: thinking });
|
|
4406
|
+
if (text) content.push({ type: "text", text });
|
|
4407
|
+
return content;
|
|
4408
|
+
}
|
|
4409
|
+
function parseArguments(providerId, toolName, args) {
|
|
4410
|
+
let parsed;
|
|
4378
4411
|
try {
|
|
4379
|
-
|
|
4412
|
+
parsed = JSON.parse(args);
|
|
4380
4413
|
} catch {
|
|
4381
|
-
|
|
4414
|
+
throw new ProviderError(
|
|
4415
|
+
`Tool "${toolName || "unknown"}" returned malformed JSON arguments; the tool was not run.`,
|
|
4416
|
+
providerId
|
|
4417
|
+
);
|
|
4418
|
+
}
|
|
4419
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
4420
|
+
throw new ProviderError(
|
|
4421
|
+
`Tool "${toolName || "unknown"}" arguments must be a JSON object; the tool was not run.`,
|
|
4422
|
+
providerId
|
|
4423
|
+
);
|
|
4382
4424
|
}
|
|
4425
|
+
return parsed;
|
|
4383
4426
|
}
|
|
4384
4427
|
function toChatMessages(system, messages) {
|
|
4385
4428
|
const out = [{ role: "system", content: system }];
|
|
@@ -4512,7 +4555,7 @@ function createPermissionEngine(configPermissions) {
|
|
|
4512
4555
|
decide(tool, requested) {
|
|
4513
4556
|
const configured = resolve3(tool, requested);
|
|
4514
4557
|
if (configured === "deny") return "deny";
|
|
4515
|
-
if (current === "plan" &&
|
|
4558
|
+
if (current === "plan" && tool.readOnly !== true) return "deny";
|
|
4516
4559
|
if (configured === "allow") return "allow";
|
|
4517
4560
|
if (bypassEnabled) return "allow";
|
|
4518
4561
|
if (current === "accept" && isFileEdit(tool)) return "allow";
|
|
@@ -4524,8 +4567,9 @@ function createPermissionEngine(configPermissions) {
|
|
|
4524
4567
|
},
|
|
4525
4568
|
denyReason(tool, requested) {
|
|
4526
4569
|
const configured = resolve3(tool, requested);
|
|
4570
|
+
if (current === "plan" && tool.readOnly !== true) return PLAN_REFUSAL;
|
|
4527
4571
|
if (configured === "deny" || configured === "allow") return void 0;
|
|
4528
|
-
return
|
|
4572
|
+
return void 0;
|
|
4529
4573
|
},
|
|
4530
4574
|
bypass: {
|
|
4531
4575
|
enable() {
|
|
@@ -4555,7 +4599,133 @@ function isFileEdit(tool) {
|
|
|
4555
4599
|
}
|
|
4556
4600
|
|
|
4557
4601
|
// src/tools/edit.ts
|
|
4558
|
-
import {
|
|
4602
|
+
import { readFile as readFile8, stat as stat6 } from "fs/promises";
|
|
4603
|
+
|
|
4604
|
+
// src/tools/safe-write.ts
|
|
4605
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4606
|
+
import { chmod as chmod5, lstat as lstat3, open, rename as rename5, rm as rm3 } from "fs/promises";
|
|
4607
|
+
import { basename as basename2, dirname as dirname2, join } from "path";
|
|
4608
|
+
var UnsafeFileChangeError = class extends Error {
|
|
4609
|
+
constructor(message) {
|
|
4610
|
+
super(message);
|
|
4611
|
+
this.name = "UnsafeFileChangeError";
|
|
4612
|
+
}
|
|
4613
|
+
};
|
|
4614
|
+
async function readSafeFileSnapshot(file, maxBytes) {
|
|
4615
|
+
const parent = await lstat3(dirname2(file));
|
|
4616
|
+
if (parent.isSymbolicLink() || !parent.isDirectory()) {
|
|
4617
|
+
throw new UnsafeFileChangeError("the parent path is not a real directory");
|
|
4618
|
+
}
|
|
4619
|
+
const parentIdentity = directoryIdentity(parent);
|
|
4620
|
+
const pathInfo = await lstatMaybe(file);
|
|
4621
|
+
if (!pathInfo) return { exists: false, data: null, mode: 438, parent: parentIdentity };
|
|
4622
|
+
if (pathInfo.isSymbolicLink()) {
|
|
4623
|
+
throw new UnsafeFileChangeError("the path is a symbolic link");
|
|
4624
|
+
}
|
|
4625
|
+
if (!pathInfo.isFile()) throw new UnsafeFileChangeError("the path is not a regular file");
|
|
4626
|
+
if (pathInfo.size > maxBytes) throw new UnsafeFileChangeError("the file is too large");
|
|
4627
|
+
const handle = await open(file, "r");
|
|
4628
|
+
try {
|
|
4629
|
+
const opened = await handle.stat();
|
|
4630
|
+
if (!opened.isFile() || !sameObject(pathInfo, opened)) {
|
|
4631
|
+
throw new UnsafeFileChangeError("the file changed while it was being opened");
|
|
4632
|
+
}
|
|
4633
|
+
const data = await readBounded(handle, maxBytes);
|
|
4634
|
+
const afterRead = await handle.stat();
|
|
4635
|
+
if (!sameVersion(opened, afterRead)) {
|
|
4636
|
+
throw new UnsafeFileChangeError("the file changed while it was being read");
|
|
4637
|
+
}
|
|
4638
|
+
return {
|
|
4639
|
+
exists: true,
|
|
4640
|
+
data,
|
|
4641
|
+
mode: opened.mode & 511,
|
|
4642
|
+
identity: fileIdentity(opened),
|
|
4643
|
+
parent: parentIdentity
|
|
4644
|
+
};
|
|
4645
|
+
} finally {
|
|
4646
|
+
await handle.close();
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
async function atomicWriteSafeFile(file, data, snapshot) {
|
|
4650
|
+
const parent = dirname2(file);
|
|
4651
|
+
await assertSameParent(parent, snapshot.parent);
|
|
4652
|
+
const temp = join(parent, `.${basename2(file)}.${process.pid}.${randomUUID3()}.tmp`);
|
|
4653
|
+
let handle;
|
|
4654
|
+
try {
|
|
4655
|
+
handle = await open(temp, "wx", snapshot.mode);
|
|
4656
|
+
await handle.writeFile(data, typeof data === "string" ? { encoding: "utf8" } : void 0);
|
|
4657
|
+
await handle.sync();
|
|
4658
|
+
await handle.close();
|
|
4659
|
+
handle = void 0;
|
|
4660
|
+
await chmod5(temp, snapshot.mode);
|
|
4661
|
+
await assertSameParent(parent, snapshot.parent);
|
|
4662
|
+
const current = await lstatMaybe(file);
|
|
4663
|
+
if (!matchesSnapshot2(current, snapshot)) {
|
|
4664
|
+
throw new UnsafeFileChangeError("the destination changed before it could be replaced");
|
|
4665
|
+
}
|
|
4666
|
+
await rename5(temp, file);
|
|
4667
|
+
} catch (error) {
|
|
4668
|
+
await handle?.close().catch(() => void 0);
|
|
4669
|
+
await rm3(temp, { force: true }).catch(() => void 0);
|
|
4670
|
+
throw error;
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
async function readBounded(handle, maxBytes) {
|
|
4674
|
+
const chunks = [];
|
|
4675
|
+
let total = 0;
|
|
4676
|
+
for (; ; ) {
|
|
4677
|
+
const remaining = maxBytes + 1 - total;
|
|
4678
|
+
if (remaining <= 0) throw new UnsafeFileChangeError("the file is too large");
|
|
4679
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining));
|
|
4680
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
|
|
4681
|
+
if (bytesRead === 0) return Buffer.concat(chunks, total);
|
|
4682
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
4683
|
+
total += bytesRead;
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
async function assertSameParent(parent, expected) {
|
|
4687
|
+
const current = await lstat3(parent);
|
|
4688
|
+
if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) {
|
|
4689
|
+
throw new UnsafeFileChangeError("the parent directory changed during the write");
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
function matchesSnapshot2(current, snapshot) {
|
|
4693
|
+
if (!snapshot.exists) return current === null;
|
|
4694
|
+
return Boolean(
|
|
4695
|
+
current && !current.isSymbolicLink() && current.isFile() && sameIdentity(current, snapshot.identity)
|
|
4696
|
+
);
|
|
4697
|
+
}
|
|
4698
|
+
function sameObject(left, right) {
|
|
4699
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
4700
|
+
}
|
|
4701
|
+
function sameVersion(left, right) {
|
|
4702
|
+
return sameObject(left, right) && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
|
|
4703
|
+
}
|
|
4704
|
+
function sameIdentity(info, identity2) {
|
|
4705
|
+
return info.dev === identity2.dev && info.ino === identity2.ino && info.size === identity2.size && info.mtimeMs === identity2.mtimeMs && info.ctimeMs === identity2.ctimeMs;
|
|
4706
|
+
}
|
|
4707
|
+
function fileIdentity(info) {
|
|
4708
|
+
return {
|
|
4709
|
+
dev: info.dev,
|
|
4710
|
+
ino: info.ino,
|
|
4711
|
+
size: info.size,
|
|
4712
|
+
mtimeMs: info.mtimeMs,
|
|
4713
|
+
ctimeMs: info.ctimeMs
|
|
4714
|
+
};
|
|
4715
|
+
}
|
|
4716
|
+
function directoryIdentity(info) {
|
|
4717
|
+
return { dev: info.dev, ino: info.ino };
|
|
4718
|
+
}
|
|
4719
|
+
async function lstatMaybe(file) {
|
|
4720
|
+
try {
|
|
4721
|
+
return await lstat3(file);
|
|
4722
|
+
} catch (error) {
|
|
4723
|
+
if (error.code === "ENOENT") return null;
|
|
4724
|
+
throw error;
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4728
|
+
// src/tools/edit.ts
|
|
4559
4729
|
var MAX_FILE_BYTES2 = 5e6;
|
|
4560
4730
|
function countOccurrences(haystack, needle) {
|
|
4561
4731
|
if (needle === "") return 0;
|
|
@@ -4611,19 +4781,14 @@ var editTool = {
|
|
|
4611
4781
|
const { path: path14, oldString, newString, replaceAll = false } = input;
|
|
4612
4782
|
const safe = resolveInside(ctx.cwd, path14);
|
|
4613
4783
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
}
|
|
4618
|
-
|
|
4619
|
-
if (info && info.size > MAX_FILE_BYTES2) {
|
|
4620
|
-
return {
|
|
4621
|
-
content: `Cannot edit ${path14}: the file exceeds the ${MAX_FILE_BYTES2 / 1e6} MB limit.`,
|
|
4622
|
-
isError: true
|
|
4623
|
-
};
|
|
4784
|
+
let snapshot;
|
|
4785
|
+
try {
|
|
4786
|
+
snapshot = await readSafeFileSnapshot(safe.path, MAX_FILE_BYTES2);
|
|
4787
|
+
} catch (error) {
|
|
4788
|
+
return { content: `Cannot edit ${path14}: ${error.message}.`, isError: true };
|
|
4624
4789
|
}
|
|
4625
|
-
|
|
4626
|
-
|
|
4790
|
+
if (!snapshot.exists) return { content: `Cannot read ${path14}.`, isError: true };
|
|
4791
|
+
const beforeBuffer = snapshot.data;
|
|
4627
4792
|
if (beforeBuffer.subarray(0, 8192).includes(0)) {
|
|
4628
4793
|
return { content: `Cannot edit ${path14}: it is a binary file, not text.`, isError: true };
|
|
4629
4794
|
}
|
|
@@ -4655,7 +4820,7 @@ var editTool = {
|
|
|
4655
4820
|
return { content: `Checkpoint capture failed: ${error.message}`, isError: true };
|
|
4656
4821
|
}
|
|
4657
4822
|
try {
|
|
4658
|
-
await
|
|
4823
|
+
await atomicWriteSafeFile(safe.path, after, snapshot);
|
|
4659
4824
|
ctx.checkpoint?.markChanged(safe.path);
|
|
4660
4825
|
} catch (error) {
|
|
4661
4826
|
return { content: `Failed to write ${path14}: ${error.message}`, isError: true };
|
|
@@ -4675,7 +4840,7 @@ function replacement(before, oldString, newString, replaceAll) {
|
|
|
4675
4840
|
}
|
|
4676
4841
|
|
|
4677
4842
|
// src/tools/glob.ts
|
|
4678
|
-
import { join } from "path";
|
|
4843
|
+
import { join as join2 } from "path";
|
|
4679
4844
|
import fg from "fast-glob";
|
|
4680
4845
|
var MAX_RESULTS = 500;
|
|
4681
4846
|
var IGNORED_DIRECTORIES = ["**/node_modules/**", "**/.git/**", "**/dist/**"];
|
|
@@ -4692,6 +4857,7 @@ var globTool = {
|
|
|
4692
4857
|
additionalProperties: false
|
|
4693
4858
|
},
|
|
4694
4859
|
defaultPermission: "allow",
|
|
4860
|
+
readOnly: true,
|
|
4695
4861
|
summarize(input) {
|
|
4696
4862
|
return `glob(${brief(input.pattern)})`;
|
|
4697
4863
|
},
|
|
@@ -4713,7 +4879,7 @@ var globTool = {
|
|
|
4713
4879
|
});
|
|
4714
4880
|
if (entries.length === 0) return { content: `No files matched ${pattern}` };
|
|
4715
4881
|
entries.sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0));
|
|
4716
|
-
const paths = entries.slice(0, MAX_RESULTS).map((entry) =>
|
|
4882
|
+
const paths = entries.slice(0, MAX_RESULTS).map((entry) => join2(safe.relative, entry.path));
|
|
4717
4883
|
if (entries.length > MAX_RESULTS) {
|
|
4718
4884
|
paths.push(`... truncated: ${entries.length - MAX_RESULTS} more files matched.`);
|
|
4719
4885
|
}
|
|
@@ -4723,7 +4889,7 @@ var globTool = {
|
|
|
4723
4889
|
|
|
4724
4890
|
// src/tools/grep.ts
|
|
4725
4891
|
import { readFile as readFile9, stat as stat7 } from "fs/promises";
|
|
4726
|
-
import { join as
|
|
4892
|
+
import { join as join3 } from "path";
|
|
4727
4893
|
import fg2 from "fast-glob";
|
|
4728
4894
|
|
|
4729
4895
|
// src/tools/sensitive.ts
|
|
@@ -4872,6 +5038,7 @@ var grepTool = {
|
|
|
4872
5038
|
additionalProperties: false
|
|
4873
5039
|
},
|
|
4874
5040
|
defaultPermission: "allow",
|
|
5041
|
+
readOnly: true,
|
|
4875
5042
|
permission(input, ctx) {
|
|
4876
5043
|
const { path: path14, glob } = input ?? {};
|
|
4877
5044
|
if (isSensitivePath(path14) || mentionsSensitivePattern(glob)) return "ask";
|
|
@@ -4910,8 +5077,8 @@ var grepTool = {
|
|
|
4910
5077
|
ignore: IGNORED_DIRECTORIES,
|
|
4911
5078
|
stats: true
|
|
4912
5079
|
})).sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0)).map((entry) => ({
|
|
4913
|
-
absolute:
|
|
4914
|
-
relative:
|
|
5080
|
+
absolute: join3(safe.path, entry.path),
|
|
5081
|
+
relative: join3(safe.relative, entry.path),
|
|
4915
5082
|
size: entry.stats?.size ?? 0
|
|
4916
5083
|
})) : [{ absolute: safe.path, relative: safe.relative, size: target.size }];
|
|
4917
5084
|
const files = discovered.slice(0, MAX_FILES);
|
|
@@ -4979,6 +5146,7 @@ var readTool = {
|
|
|
4979
5146
|
additionalProperties: false
|
|
4980
5147
|
},
|
|
4981
5148
|
defaultPermission: "allow",
|
|
5149
|
+
readOnly: true,
|
|
4982
5150
|
permission(input, ctx) {
|
|
4983
5151
|
const target = input?.path;
|
|
4984
5152
|
if (isSensitivePath(target)) return "ask";
|
|
@@ -5039,8 +5207,8 @@ function toToolSchema(tool) {
|
|
|
5039
5207
|
}
|
|
5040
5208
|
|
|
5041
5209
|
// src/tools/write.ts
|
|
5042
|
-
import {
|
|
5043
|
-
import { dirname as
|
|
5210
|
+
import { mkdir as mkdir4, readFile as readFile11, stat as stat9 } from "fs/promises";
|
|
5211
|
+
import { dirname as dirname3 } from "path";
|
|
5044
5212
|
var MAX_FILE_BYTES5 = 5e6;
|
|
5045
5213
|
var writeTool = {
|
|
5046
5214
|
name: "write",
|
|
@@ -5090,31 +5258,29 @@ var writeTool = {
|
|
|
5090
5258
|
}
|
|
5091
5259
|
const safe = resolveInside(ctx.cwd, path14);
|
|
5092
5260
|
if (!safe.ok) return { content: safe.reason, isError: true };
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
return { content: `Cannot write ${path14}: the path is a symbolic link. Remove the symlink first.`, isError: true };
|
|
5096
|
-
}
|
|
5097
|
-
const beforeInfo = await stat9(safe.path).catch(() => null);
|
|
5098
|
-
if (beforeInfo && beforeInfo.size > MAX_FILE_BYTES5) {
|
|
5099
|
-
return {
|
|
5100
|
-
content: `Cannot replace ${path14}: the existing file exceeds the ${MAX_FILE_BYTES5 / 1e6} MB limit.`,
|
|
5101
|
-
isError: true
|
|
5102
|
-
};
|
|
5103
|
-
}
|
|
5104
|
-
const beforeBuffer = await readFile11(safe.path).catch(() => null);
|
|
5105
|
-
if (beforeBuffer instanceof Buffer && beforeBuffer.subarray(0, 8192).includes(0)) {
|
|
5106
|
-
return { content: `Cannot write ${path14}: the existing file is binary, not text.`, isError: true };
|
|
5107
|
-
}
|
|
5108
|
-
let before = "";
|
|
5109
|
-
if (beforeBuffer) before = beforeBuffer.toString("utf8");
|
|
5261
|
+
let target = safe.path;
|
|
5262
|
+
let snapshot;
|
|
5110
5263
|
try {
|
|
5111
|
-
await mkdir4(
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5264
|
+
await mkdir4(dirname3(target), { recursive: true });
|
|
5265
|
+
const rechecked = resolveInside(ctx.cwd, path14);
|
|
5266
|
+
if (!rechecked.ok || rechecked.path !== target) {
|
|
5267
|
+
return {
|
|
5268
|
+
content: `Cannot write ${path14}: the path changed while its parent was created.`,
|
|
5269
|
+
isError: true
|
|
5270
|
+
};
|
|
5271
|
+
}
|
|
5272
|
+
target = rechecked.path;
|
|
5273
|
+
snapshot = await readSafeFileSnapshot(target, MAX_FILE_BYTES5);
|
|
5274
|
+
if (snapshot.exists && snapshot.data.subarray(0, 8192).includes(0)) {
|
|
5275
|
+
return { content: `Cannot write ${path14}: the existing file is binary, not text.`, isError: true };
|
|
5276
|
+
}
|
|
5277
|
+
await ctx.checkpoint?.capture(target);
|
|
5278
|
+
await atomicWriteSafeFile(target, content, snapshot);
|
|
5279
|
+
ctx.checkpoint?.markChanged(target);
|
|
5115
5280
|
} catch (error) {
|
|
5116
5281
|
return { content: `Failed to write ${path14}: ${error.message}`, isError: true };
|
|
5117
5282
|
}
|
|
5283
|
+
const before = snapshot.exists ? snapshot.data.toString("utf8") : "";
|
|
5118
5284
|
const lines = content === "" ? 0 : content.replace(/\n$/, "").split("\n").length;
|
|
5119
5285
|
return {
|
|
5120
5286
|
content: `${before === "" ? "Created" : "Updated"} ${path14} (${lines} ${lines === 1 ? "line" : "lines"})`,
|
|
@@ -5147,7 +5313,7 @@ function createToolRegistry(tools) {
|
|
|
5147
5313
|
}
|
|
5148
5314
|
|
|
5149
5315
|
// src/prompts/library.ts
|
|
5150
|
-
import { chmod as
|
|
5316
|
+
import { chmod as chmod6, readFile as readFile12, readdir as readdir3, unlink as unlink3, writeFile as writeFile6 } from "fs/promises";
|
|
5151
5317
|
import path10 from "path";
|
|
5152
5318
|
async function savePrompt(input) {
|
|
5153
5319
|
const slug = slugify(input.name);
|
|
@@ -5161,11 +5327,11 @@ async function savePrompt(input) {
|
|
|
5161
5327
|
};
|
|
5162
5328
|
await ensureDir(promptsDir);
|
|
5163
5329
|
const file = path10.join(promptsDir, `${slug}.md`);
|
|
5164
|
-
await
|
|
5330
|
+
await writeFile6(file, serialize(prompt), {
|
|
5165
5331
|
encoding: "utf8",
|
|
5166
5332
|
mode: 384
|
|
5167
5333
|
});
|
|
5168
|
-
await
|
|
5334
|
+
await chmod6(file, 384);
|
|
5169
5335
|
return prompt;
|
|
5170
5336
|
}
|
|
5171
5337
|
async function getPrompt(slug) {
|
|
@@ -5252,18 +5418,21 @@ function parse(slug, text) {
|
|
|
5252
5418
|
}
|
|
5253
5419
|
|
|
5254
5420
|
// src/skills/library.ts
|
|
5255
|
-
import {
|
|
5421
|
+
import { lstat as lstat4, open as open2, readdir as readdir4 } from "fs/promises";
|
|
5256
5422
|
import path11 from "path";
|
|
5257
5423
|
var SKILL_FILE = "SKILL.md";
|
|
5258
5424
|
var FRONTMATTER_BYTES = 8192;
|
|
5259
5425
|
var MAX_SKILL_BYTES = 5e6;
|
|
5260
5426
|
var MAX_SKILLS_PER_ROOT = 500;
|
|
5427
|
+
var guards = /* @__PURE__ */ new WeakMap();
|
|
5261
5428
|
async function discoverSkills(dirs) {
|
|
5262
5429
|
const byName = /* @__PURE__ */ new Map();
|
|
5263
5430
|
for (const root of dirs) {
|
|
5264
|
-
const
|
|
5431
|
+
const rootInfo = await lstat4(root).catch(() => null);
|
|
5432
|
+
if (!rootInfo?.isDirectory() || rootInfo.isSymbolicLink()) continue;
|
|
5433
|
+
const entries = await readdir4(root, { withFileTypes: true }).catch(() => []);
|
|
5265
5434
|
const found = await Promise.all(
|
|
5266
|
-
entries.slice(0, MAX_SKILLS_PER_ROOT).map((entry) => readMeta(path11.join(root, entry)))
|
|
5435
|
+
entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).slice(0, MAX_SKILLS_PER_ROOT).map((entry) => readMeta(root, rootInfo, path11.join(root, entry.name)))
|
|
5267
5436
|
);
|
|
5268
5437
|
for (const meta of found) {
|
|
5269
5438
|
if (meta && !byName.has(meta.name)) byName.set(meta.name, meta);
|
|
@@ -5272,11 +5441,25 @@ async function discoverSkills(dirs) {
|
|
|
5272
5441
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
5273
5442
|
}
|
|
5274
5443
|
async function loadSkill(meta) {
|
|
5275
|
-
const
|
|
5276
|
-
if (
|
|
5444
|
+
const guard = guards.get(meta);
|
|
5445
|
+
if (!guard) throw new Error(`Skill was not discovered through a protected skill root: ${meta.file}`);
|
|
5446
|
+
await assertIdentity(guard.root, guard.rootIdentity, "skill root");
|
|
5447
|
+
await assertIdentity(meta.dir, guard.dirIdentity, "skill directory");
|
|
5448
|
+
const opened = await openVerified(meta.file, guard.fileIdentity);
|
|
5449
|
+
if (opened.info.size > MAX_SKILL_BYTES) {
|
|
5450
|
+
await opened.handle.close();
|
|
5277
5451
|
throw new Error(`Skill file exceeds the ${MAX_SKILL_BYTES / 1e6} MB limit: ${meta.file}`);
|
|
5278
5452
|
}
|
|
5279
|
-
|
|
5453
|
+
let text;
|
|
5454
|
+
try {
|
|
5455
|
+
text = (await readBounded2(opened.handle, MAX_SKILL_BYTES)).toString("utf8");
|
|
5456
|
+
} finally {
|
|
5457
|
+
await opened.handle.close();
|
|
5458
|
+
}
|
|
5459
|
+
await assertIdentity(guard.root, guard.rootIdentity, "skill root");
|
|
5460
|
+
await assertIdentity(meta.dir, guard.dirIdentity, "skill directory");
|
|
5461
|
+
await assertIdentity(meta.file, guard.fileIdentity, "skill file");
|
|
5462
|
+
const { body } = parseFrontmatter(text);
|
|
5280
5463
|
return { ...meta, body };
|
|
5281
5464
|
}
|
|
5282
5465
|
function formatSkillCatalogue(skills) {
|
|
@@ -5286,20 +5469,33 @@ function formatSkillCatalogue(skills) {
|
|
|
5286
5469
|
"\n"
|
|
5287
5470
|
);
|
|
5288
5471
|
}
|
|
5289
|
-
async function readMeta(dir) {
|
|
5472
|
+
async function readMeta(root, rootInfo, dir) {
|
|
5473
|
+
const dirInfo = await lstat4(dir).catch(() => null);
|
|
5474
|
+
if (!dirInfo?.isDirectory() || dirInfo.isSymbolicLink()) return null;
|
|
5290
5475
|
const file = path11.join(dir, SKILL_FILE);
|
|
5291
5476
|
const head = await readFrontmatterBytes(file);
|
|
5292
|
-
if (head
|
|
5293
|
-
const { fields } = parseFrontmatter(head);
|
|
5294
|
-
|
|
5477
|
+
if (!head) return null;
|
|
5478
|
+
const { fields } = parseFrontmatter(head.text);
|
|
5479
|
+
const meta = { name: fields.name || path11.basename(dir), description: fields.description ?? "", dir, file };
|
|
5480
|
+
guards.set(meta, {
|
|
5481
|
+
root,
|
|
5482
|
+
rootIdentity: identity(rootInfo),
|
|
5483
|
+
dirIdentity: identity(dirInfo),
|
|
5484
|
+
fileIdentity: head.identity
|
|
5485
|
+
});
|
|
5486
|
+
return meta;
|
|
5295
5487
|
}
|
|
5296
5488
|
async function readFrontmatterBytes(file) {
|
|
5297
|
-
const
|
|
5489
|
+
const before = await lstat4(file).catch(() => null);
|
|
5490
|
+
if (!before?.isFile() || before.isSymbolicLink()) return null;
|
|
5491
|
+
const handle = await open2(file, "r").catch(() => null);
|
|
5298
5492
|
if (!handle) return null;
|
|
5299
5493
|
try {
|
|
5494
|
+
const opened = await handle.stat();
|
|
5495
|
+
if (!opened.isFile() || !sameIdentity2(opened, identity(before))) return null;
|
|
5300
5496
|
const buffer = Buffer.alloc(FRONTMATTER_BYTES);
|
|
5301
5497
|
const { bytesRead } = await handle.read(buffer, 0, FRONTMATTER_BYTES, 0);
|
|
5302
|
-
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
5498
|
+
return { text: buffer.subarray(0, bytesRead).toString("utf8"), identity: identity(opened) };
|
|
5303
5499
|
} catch {
|
|
5304
5500
|
return null;
|
|
5305
5501
|
} finally {
|
|
@@ -5307,6 +5503,44 @@ async function readFrontmatterBytes(file) {
|
|
|
5307
5503
|
});
|
|
5308
5504
|
}
|
|
5309
5505
|
}
|
|
5506
|
+
async function openVerified(file, expected) {
|
|
5507
|
+
const before = await lstat4(file);
|
|
5508
|
+
if (!before.isFile() || before.isSymbolicLink() || !sameIdentity2(before, expected)) {
|
|
5509
|
+
throw new Error(`Refusing changed or symlinked skill file: ${file}`);
|
|
5510
|
+
}
|
|
5511
|
+
const handle = await open2(file, "r");
|
|
5512
|
+
const info = await handle.stat();
|
|
5513
|
+
if (!info.isFile() || !sameIdentity2(info, expected)) {
|
|
5514
|
+
await handle.close();
|
|
5515
|
+
throw new Error(`Refusing skill file changed while opening: ${file}`);
|
|
5516
|
+
}
|
|
5517
|
+
return { handle, info };
|
|
5518
|
+
}
|
|
5519
|
+
async function assertIdentity(file, expected, label) {
|
|
5520
|
+
const info = await lstat4(file);
|
|
5521
|
+
if (info.isSymbolicLink() || !sameIdentity2(info, expected)) {
|
|
5522
|
+
throw new Error(`Refusing changed or symlinked ${label}: ${file}`);
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
async function readBounded2(handle, maxBytes) {
|
|
5526
|
+
const chunks = [];
|
|
5527
|
+
let total = 0;
|
|
5528
|
+
for (; ; ) {
|
|
5529
|
+
const remaining = maxBytes + 1 - total;
|
|
5530
|
+
if (remaining <= 0) throw new Error(`Skill file exceeds the ${maxBytes / 1e6} MB limit`);
|
|
5531
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining));
|
|
5532
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
|
|
5533
|
+
if (bytesRead === 0) return Buffer.concat(chunks, total);
|
|
5534
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
5535
|
+
total += bytesRead;
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
function identity(info) {
|
|
5539
|
+
return { dev: info.dev, ino: info.ino };
|
|
5540
|
+
}
|
|
5541
|
+
function sameIdentity2(info, expected) {
|
|
5542
|
+
return info.dev === expected.dev && info.ino === expected.ino;
|
|
5543
|
+
}
|
|
5310
5544
|
function parseFrontmatter(text) {
|
|
5311
5545
|
const lines = text.split("\n");
|
|
5312
5546
|
const fields = {};
|
|
@@ -5325,7 +5559,7 @@ function parseFrontmatter(text) {
|
|
|
5325
5559
|
}
|
|
5326
5560
|
|
|
5327
5561
|
// src/skills/install.ts
|
|
5328
|
-
import { randomUUID as
|
|
5562
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
5329
5563
|
import { execFileSync } from "child_process";
|
|
5330
5564
|
import {
|
|
5331
5565
|
existsSync as existsSync2,
|
|
@@ -5337,7 +5571,7 @@ import {
|
|
|
5337
5571
|
rmSync,
|
|
5338
5572
|
writeFileSync
|
|
5339
5573
|
} from "fs";
|
|
5340
|
-
import { chmod as
|
|
5574
|
+
import { chmod as chmod7, mkdir as mkdir5 } from "fs/promises";
|
|
5341
5575
|
import path12 from "path";
|
|
5342
5576
|
var TMP_DIR = path12.join(skillsDir, ".tmp");
|
|
5343
5577
|
var MAX_SKILL_BYTES2 = 5e6;
|
|
@@ -5366,7 +5600,7 @@ async function installFromGitHub(url) {
|
|
|
5366
5600
|
const { owner, repo, subdir, branch } = parseGitHubUrl(url);
|
|
5367
5601
|
const name = safeSkillName(subdir ? path12.posix.basename(subdir) : repo);
|
|
5368
5602
|
const skillDir = safeChildPath(skillsDir, name);
|
|
5369
|
-
const tmpDir = safeChildPath(TMP_DIR, `${name}-${
|
|
5603
|
+
const tmpDir = safeChildPath(TMP_DIR, `${name}-${randomUUID4()}`);
|
|
5370
5604
|
try {
|
|
5371
5605
|
mkdirSync(tmpDir, { recursive: true });
|
|
5372
5606
|
const cloneUrl = `https://github.com/${owner}/${repo}.git`;
|
|
@@ -5388,7 +5622,7 @@ async function installFromNpm(packageName) {
|
|
|
5388
5622
|
if (!parsedName) throw new Error(`Invalid npm package name: ${packageName}`);
|
|
5389
5623
|
const name = safeSkillName(parsedName);
|
|
5390
5624
|
const skillDir = safeChildPath(skillsDir, name);
|
|
5391
|
-
const tmpDir = safeChildPath(TMP_DIR, `${name}-${
|
|
5625
|
+
const tmpDir = safeChildPath(TMP_DIR, `${name}-${randomUUID4()}`);
|
|
5392
5626
|
try {
|
|
5393
5627
|
mkdirSync(tmpDir, { recursive: true });
|
|
5394
5628
|
execFileSync("npm", ["pack", packageName, "--prefix", tmpDir], {
|
|
@@ -5492,7 +5726,7 @@ async function writeSkillFile(dir, body) {
|
|
|
5492
5726
|
throw new Error(`Refusing to replace symlinked skill file: ${file}`);
|
|
5493
5727
|
}
|
|
5494
5728
|
writeFileSync(file, body, { encoding: "utf8", mode: 384 });
|
|
5495
|
-
await
|
|
5729
|
+
await chmod7(file, 384);
|
|
5496
5730
|
}
|
|
5497
5731
|
function parseGitHubUrl(url) {
|
|
5498
5732
|
let parsed;
|
|
@@ -5586,6 +5820,7 @@ function createSkillTool(skills) {
|
|
|
5586
5820
|
additionalProperties: false
|
|
5587
5821
|
},
|
|
5588
5822
|
defaultPermission: "allow",
|
|
5823
|
+
readOnly: true,
|
|
5589
5824
|
summarize(input) {
|
|
5590
5825
|
return `skill(${brief(input.name)})`;
|
|
5591
5826
|
},
|
|
@@ -5691,50 +5926,94 @@ function field(input, key) {
|
|
|
5691
5926
|
}
|
|
5692
5927
|
|
|
5693
5928
|
// src/core/update.ts
|
|
5694
|
-
var UPDATE_URL =
|
|
5929
|
+
var UPDATE_URL = "https://registry.npmjs.org/%40kernelonpanic%2Fkitcode/latest";
|
|
5930
|
+
var PACKAGE_URL = "https://www.npmjs.com/package/@kernelonpanic/kitcode";
|
|
5695
5931
|
var TIMEOUT_MS = 4e3;
|
|
5696
5932
|
var MAX_RESPONSE_BYTES2 = 128e3;
|
|
5697
|
-
async function checkForUpdates(fetcher = fetch,
|
|
5698
|
-
if (
|
|
5699
|
-
return { status: "unknown", reason: "
|
|
5933
|
+
async function checkForUpdates(fetcher = fetch, currentVersion = KITCODE_VERSION) {
|
|
5934
|
+
if (!parseSemver(currentVersion)) {
|
|
5935
|
+
return { status: "unknown", reason: "installed KitCode version is not valid semver" };
|
|
5700
5936
|
}
|
|
5701
5937
|
const controller = new AbortController();
|
|
5702
5938
|
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
5703
5939
|
try {
|
|
5704
5940
|
const response = await fetcher(UPDATE_URL, {
|
|
5705
5941
|
headers: {
|
|
5706
|
-
accept: "application/
|
|
5707
|
-
"user-agent": `KitCode/${
|
|
5708
|
-
"x-github-api-version": "2022-11-28"
|
|
5942
|
+
accept: "application/json",
|
|
5943
|
+
"user-agent": `KitCode/${currentVersion}`
|
|
5709
5944
|
},
|
|
5710
5945
|
redirect: "error",
|
|
5711
5946
|
signal: controller.signal
|
|
5712
5947
|
});
|
|
5713
5948
|
if (!response.ok) {
|
|
5714
5949
|
await response.body?.cancel().catch(() => void 0);
|
|
5715
|
-
return { status: "unknown", reason: `
|
|
5950
|
+
return { status: "unknown", reason: `npm registry returned ${response.status}` };
|
|
5716
5951
|
}
|
|
5717
5952
|
const payload = await readJsonBounded(response);
|
|
5718
|
-
if (!payload) return { status: "unknown", reason: "
|
|
5719
|
-
if (typeof payload.
|
|
5720
|
-
return { status: "unknown", reason: "
|
|
5953
|
+
if (!payload) return { status: "unknown", reason: "npm response was too large or invalid" };
|
|
5954
|
+
if (typeof payload.version !== "string" || !parseSemver(payload.version)) {
|
|
5955
|
+
return { status: "unknown", reason: "npm response did not contain a valid version" };
|
|
5721
5956
|
}
|
|
5722
|
-
const
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
return { status: "current", current, latest };
|
|
5957
|
+
const latest = payload.version;
|
|
5958
|
+
if (compareSemver(latest, currentVersion) <= 0) {
|
|
5959
|
+
return { status: "current", current: currentVersion, latest };
|
|
5726
5960
|
}
|
|
5727
|
-
|
|
5728
|
-
|
|
5961
|
+
return {
|
|
5962
|
+
status: "available",
|
|
5963
|
+
current: currentVersion,
|
|
5964
|
+
latest,
|
|
5965
|
+
url: `${PACKAGE_URL}/v/${encodeURIComponent(latest)}`
|
|
5966
|
+
};
|
|
5729
5967
|
} catch (error) {
|
|
5730
5968
|
return {
|
|
5731
5969
|
status: "unknown",
|
|
5732
|
-
reason: error instanceof Error && error.name === "AbortError" ? "
|
|
5970
|
+
reason: error instanceof Error && error.name === "AbortError" ? "npm update check timed out" : "npm update check failed"
|
|
5733
5971
|
};
|
|
5734
5972
|
} finally {
|
|
5735
5973
|
clearTimeout(timer);
|
|
5736
5974
|
}
|
|
5737
5975
|
}
|
|
5976
|
+
function compareSemver(left, right) {
|
|
5977
|
+
const a = parseSemver(left);
|
|
5978
|
+
const b = parseSemver(right);
|
|
5979
|
+
if (!a || !b) throw new Error("Cannot compare invalid semantic versions");
|
|
5980
|
+
for (let index = 0; index < 3; index += 1) {
|
|
5981
|
+
const x = a.core[index] ?? 0n;
|
|
5982
|
+
const y = b.core[index] ?? 0n;
|
|
5983
|
+
if (x !== y) return x > y ? 1 : -1;
|
|
5984
|
+
}
|
|
5985
|
+
if (a.prerelease.length === 0 || b.prerelease.length === 0) {
|
|
5986
|
+
if (a.prerelease.length === b.prerelease.length) return 0;
|
|
5987
|
+
return a.prerelease.length === 0 ? 1 : -1;
|
|
5988
|
+
}
|
|
5989
|
+
const length = Math.max(a.prerelease.length, b.prerelease.length);
|
|
5990
|
+
for (let index = 0; index < length; index += 1) {
|
|
5991
|
+
const x = a.prerelease[index];
|
|
5992
|
+
const y = b.prerelease[index];
|
|
5993
|
+
if (x === void 0 || y === void 0) return x === void 0 ? -1 : 1;
|
|
5994
|
+
if (x === y) continue;
|
|
5995
|
+
const xNumeric = /^\d+$/.test(x);
|
|
5996
|
+
const yNumeric = /^\d+$/.test(y);
|
|
5997
|
+
if (xNumeric && yNumeric) return BigInt(x) > BigInt(y) ? 1 : -1;
|
|
5998
|
+
if (xNumeric !== yNumeric) return xNumeric ? -1 : 1;
|
|
5999
|
+
return x > y ? 1 : -1;
|
|
6000
|
+
}
|
|
6001
|
+
return 0;
|
|
6002
|
+
}
|
|
6003
|
+
function parseSemver(value) {
|
|
6004
|
+
const match = value.match(
|
|
6005
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/
|
|
6006
|
+
);
|
|
6007
|
+
if (!match) return null;
|
|
6008
|
+
const prerelease = match[4]?.split(".") ?? [];
|
|
6009
|
+
if (prerelease.some((part) => /^\d+$/.test(part) && part.length > 1 && part.startsWith("0"))) {
|
|
6010
|
+
return null;
|
|
6011
|
+
}
|
|
6012
|
+
return {
|
|
6013
|
+
core: [BigInt(match[1]), BigInt(match[2]), BigInt(match[3])],
|
|
6014
|
+
prerelease
|
|
6015
|
+
};
|
|
6016
|
+
}
|
|
5738
6017
|
async function readJsonBounded(response) {
|
|
5739
6018
|
const declared = Number(response.headers.get("content-length"));
|
|
5740
6019
|
if (Number.isFinite(declared) && declared > MAX_RESPONSE_BYTES2) {
|
|
@@ -5782,7 +6061,7 @@ async function boot(options) {
|
|
|
5782
6061
|
const warnings = [];
|
|
5783
6062
|
const workspaceRoot2 = await canonicalWorkspace(options.cwd);
|
|
5784
6063
|
const workspaceTrusted = await isWorkspaceTrusted(options.cwd);
|
|
5785
|
-
|
|
6064
|
+
let startupUpdate;
|
|
5786
6065
|
if (loadedConfig.ignoredProject) {
|
|
5787
6066
|
warnings.push(
|
|
5788
6067
|
`Project config ignored until this workspace is trusted: ${loadedConfig.ignoredProject.path}. Review it, then run: kitcode trust`
|
|
@@ -6395,7 +6674,7 @@ ${lines.join("\n")}` : null;
|
|
|
6395
6674
|
const states = mcp.states();
|
|
6396
6675
|
const lines = [
|
|
6397
6676
|
`KitCode ${KITCODE_VERSION} \xB7 ${KITCODE_COMMIT.slice(0, 12)}`,
|
|
6398
|
-
|
|
6677
|
+
"updates: npm check runs on every app start",
|
|
6399
6678
|
`runtime: Node ${process.versions.node} \xB7 ${process.platform}/${process.arch}`,
|
|
6400
6679
|
`workspace: ${options.cwd}`,
|
|
6401
6680
|
`config: ${location.path}`,
|
|
@@ -6423,7 +6702,8 @@ ${lines.join("\n")}` : null;
|
|
|
6423
6702
|
);
|
|
6424
6703
|
return lines.join("\n");
|
|
6425
6704
|
},
|
|
6426
|
-
startupUpdateCheck: () => startupUpdate,
|
|
6705
|
+
startupUpdateCheck: () => startupUpdate ??= checkForUpdates(),
|
|
6706
|
+
checkForUpdates: () => checkForUpdates(),
|
|
6427
6707
|
async run(history, hooks, signal) {
|
|
6428
6708
|
syncMcpTools();
|
|
6429
6709
|
const runProviderId = parseModelRef(modelRef)?.provider;
|
|
@@ -6561,6 +6841,13 @@ async function ask(text, options = {}) {
|
|
|
6561
6841
|
mode: options.mode
|
|
6562
6842
|
});
|
|
6563
6843
|
for (const warning of warnings) console.error(sanitizeTerminalText(warning));
|
|
6844
|
+
const update = await runtime.startupUpdateCheck();
|
|
6845
|
+
if (update.status === "available") {
|
|
6846
|
+
console.error(
|
|
6847
|
+
`A newer KitCode version is available (${update.latest}). Update with: npm install -g @kernelonpanic/kitcode@latest
|
|
6848
|
+
${update.url}`
|
|
6849
|
+
);
|
|
6850
|
+
}
|
|
6564
6851
|
const controller = new AbortController();
|
|
6565
6852
|
const onInterrupt = () => controller.abort();
|
|
6566
6853
|
process.on("SIGINT", onInterrupt);
|
|
@@ -6730,7 +7017,7 @@ function validateKey(value) {
|
|
|
6730
7017
|
import { render } from "ink";
|
|
6731
7018
|
|
|
6732
7019
|
// src/ui/App.tsx
|
|
6733
|
-
import { Box as Box13, Text as Text12, useApp, useInput as useInput2, useWindowSize as
|
|
7020
|
+
import { Box as Box13, Text as Text12, useApp, useInput as useInput2, useWindowSize as useWindowSize4 } from "ink";
|
|
6734
7021
|
import { useCallback, useEffect as useEffect2, useMemo as useMemo4, useRef as useRef4, useState as useState5 } from "react";
|
|
6735
7022
|
|
|
6736
7023
|
// src/mcp/add.ts
|
|
@@ -6784,6 +7071,7 @@ var COMMANDS = [
|
|
|
6784
7071
|
{ name: "mcp", args: "[add|list|delete|enable|disable]" },
|
|
6785
7072
|
{ name: "attach", args: "<path|clipboard|clear>" },
|
|
6786
7073
|
{ name: "compact" },
|
|
7074
|
+
{ name: "update" },
|
|
6787
7075
|
{ name: "checker" },
|
|
6788
7076
|
{ name: "sessions", args: "[list|rename|delete [all]|export]" },
|
|
6789
7077
|
{ name: "config" },
|
|
@@ -7026,7 +7314,11 @@ var en = {
|
|
|
7026
7314
|
sessionActionRename: "rename",
|
|
7027
7315
|
sessionActionDelete: "delete",
|
|
7028
7316
|
sessionActionExport: "export Markdown",
|
|
7029
|
-
updateAvailable: (version, url) => `A newer KitCode
|
|
7317
|
+
updateAvailable: (version, url) => `A newer KitCode version is available (${version}). Update with:
|
|
7318
|
+
npm install -g @kernelonpanic/kitcode@latest
|
|
7319
|
+
${url}`,
|
|
7320
|
+
updateCurrent: (version) => `KitCode ${version} is up to date.`,
|
|
7321
|
+
updateFailed: (reason) => `Could not check for updates: ${reason}.`,
|
|
7030
7322
|
accentSet: (name, hex) => `Accent: ${name} (${hex})`,
|
|
7031
7323
|
reasoning: (on) => `Reasoning ${on ? "on" : "off"}`,
|
|
7032
7324
|
effortSet: (value) => `Effort: ${value}`,
|
|
@@ -7101,6 +7393,7 @@ var en = {
|
|
|
7101
7393
|
"mcp disable": "disconnect without removing an MCP server",
|
|
7102
7394
|
attach: "attach an image or text file to the next message",
|
|
7103
7395
|
compact: "summarize older context and keep recent turns",
|
|
7396
|
+
update: "check npm for a newer KitCode version",
|
|
7104
7397
|
checker: "check local setup without spending model tokens",
|
|
7105
7398
|
sessions: "search and manage saved sessions",
|
|
7106
7399
|
"sessions list": "list saved sessions",
|
|
@@ -7209,7 +7502,11 @@ var ru = {
|
|
|
7209
7502
|
sessionActionRename: "\u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C",
|
|
7210
7503
|
sessionActionDelete: "\u0443\u0434\u0430\u043B\u0438\u0442\u044C",
|
|
7211
7504
|
sessionActionExport: "\u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C Markdown",
|
|
7212
|
-
updateAvailable: (version, url) => `\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u0430 \u043D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F KitCode (${version}):
|
|
7505
|
+
updateAvailable: (version, url) => `\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u0430 \u043D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F KitCode (${version}). \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C:
|
|
7506
|
+
npm install -g @kernelonpanic/kitcode@latest
|
|
7507
|
+
${url}`,
|
|
7508
|
+
updateCurrent: (version) => `\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u0430\u043A\u0442\u0443\u0430\u043B\u044C\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F KitCode ${version}.`,
|
|
7509
|
+
updateFailed: (reason) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F: ${reason}.`,
|
|
7213
7510
|
accentSet: (name, hex) => `\u0426\u0432\u0435\u0442: ${name} (${hex})`,
|
|
7214
7511
|
reasoning: (on) => `\u0420\u0430\u0437\u043C\u044B\u0448\u043B\u0435\u043D\u0438\u044F ${on ? "\u0432\u043A\u043B\u044E\u0447\u0435\u043D\u044B" : "\u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u044B"}`,
|
|
7215
7512
|
effortSet: (value) => `\u0413\u043B\u0443\u0431\u0438\u043D\u0430: ${value}`,
|
|
@@ -7282,6 +7579,7 @@ var ru = {
|
|
|
7282
7579
|
"mcp disable": "\u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C MCP \u0431\u0435\u0437 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F",
|
|
7283
7580
|
attach: "\u043F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443 \u0438\u043B\u0438 \u0442\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0439 \u0444\u0430\u0439\u043B \u043A \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044E",
|
|
7284
7581
|
compact: "\u0441\u0436\u0430\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0439 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442, \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0432 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 \u0445\u043E\u0434\u044B",
|
|
7582
|
+
update: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E KitCode \u0432 npm",
|
|
7285
7583
|
checker: "\u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0443 \u0431\u0435\u0437 \u0442\u0440\u0430\u0442\u044B \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u043C\u043E\u0434\u0435\u043B\u0438",
|
|
7286
7584
|
sessions: "\u043F\u043E\u0438\u0441\u043A \u0438 \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u043C\u0438 \u0441\u0435\u0441\u0441\u0438\u044F\u043C\u0438",
|
|
7287
7585
|
"sessions list": "\u0441\u043F\u0438\u0441\u043E\u043A \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0445 \u0441\u0435\u0441\u0441\u0438\u0439",
|
|
@@ -7764,7 +8062,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
7764
8062
|
valueRef.current = safeValue;
|
|
7765
8063
|
cursorRef.current = inputCursor;
|
|
7766
8064
|
const suggestions = matchCommands(safeValue);
|
|
7767
|
-
const
|
|
8065
|
+
const open3 = suggestions.length > 0;
|
|
7768
8066
|
const active2 = Math.min(selectionCursor, Math.max(0, suggestions.length - 1));
|
|
7769
8067
|
useEffect(() => {
|
|
7770
8068
|
setSelectionCursor(0);
|
|
@@ -7817,25 +8115,25 @@ var PromptInput = memo(function PromptInput2({
|
|
|
7817
8115
|
onPasteImage();
|
|
7818
8116
|
return;
|
|
7819
8117
|
}
|
|
7820
|
-
if (
|
|
8118
|
+
if (open3 && key.upArrow) {
|
|
7821
8119
|
setSelectionCursor(Math.max(0, active2 - 1));
|
|
7822
8120
|
return;
|
|
7823
8121
|
}
|
|
7824
|
-
if (
|
|
8122
|
+
if (open3 && key.downArrow) {
|
|
7825
8123
|
setSelectionCursor(Math.min(suggestions.length - 1, active2 + 1));
|
|
7826
8124
|
return;
|
|
7827
8125
|
}
|
|
7828
|
-
if (
|
|
8126
|
+
if (open3 && key.tab && !key.shift) {
|
|
7829
8127
|
const chosen = suggestions[active2];
|
|
7830
8128
|
if (chosen) change(`/${chosen.name} `);
|
|
7831
8129
|
return;
|
|
7832
8130
|
}
|
|
7833
|
-
if (
|
|
8131
|
+
if (open3 && key.return) {
|
|
7834
8132
|
const chosen = suggestions[active2];
|
|
7835
8133
|
if (chosen) submit(`/${chosen.name}`);
|
|
7836
8134
|
return;
|
|
7837
8135
|
}
|
|
7838
|
-
if (!
|
|
8136
|
+
if (!open3 && (key.upArrow || key.downArrow)) {
|
|
7839
8137
|
const moved = moveInputHistory(
|
|
7840
8138
|
history,
|
|
7841
8139
|
historyIndex,
|
|
@@ -7887,7 +8185,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
7887
8185
|
});
|
|
7888
8186
|
const start = Math.max(0, Math.min(active2 - WINDOW2 + 2, suggestions.length - WINDOW2));
|
|
7889
8187
|
const visible = suggestions.slice(start, start + WINDOW2);
|
|
7890
|
-
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
8188
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, flexShrink: 0, children: [
|
|
7891
8189
|
/* @__PURE__ */ jsxs8(
|
|
7892
8190
|
Box8,
|
|
7893
8191
|
{
|
|
@@ -7915,7 +8213,7 @@ var PromptInput = memo(function PromptInput2({
|
|
|
7915
8213
|
" ",
|
|
7916
8214
|
sanitizeTerminalText(hint)
|
|
7917
8215
|
] }),
|
|
7918
|
-
|
|
8216
|
+
open3 && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginLeft: 2, children: [
|
|
7919
8217
|
visible.map((command, index) => {
|
|
7920
8218
|
const selected = start + index === active2;
|
|
7921
8219
|
return /* @__PURE__ */ jsxs8(Text8, { color: selected ? theme.accent : void 0, dimColor: !selected, children: [
|
|
@@ -8051,6 +8349,7 @@ function StatusBar({ status }) {
|
|
|
8051
8349
|
Box9,
|
|
8052
8350
|
{
|
|
8053
8351
|
width: "100%",
|
|
8352
|
+
flexShrink: 0,
|
|
8054
8353
|
marginTop: 1,
|
|
8055
8354
|
paddingX: 1,
|
|
8056
8355
|
borderStyle: "single",
|
|
@@ -8182,8 +8481,12 @@ function ContextMeter({
|
|
|
8182
8481
|
import { Box as Box10 } from "ink";
|
|
8183
8482
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
8184
8483
|
function interactiveViewportRows(rows) {
|
|
8185
|
-
if (!Number.isFinite(rows)) return
|
|
8186
|
-
return Math.max(1, Math.floor(rows) -
|
|
8484
|
+
if (!Number.isFinite(rows)) return 22;
|
|
8485
|
+
return Math.max(1, Math.floor(rows) - 2);
|
|
8486
|
+
}
|
|
8487
|
+
var INTERACTIVE_CHROME_ROWS = 14;
|
|
8488
|
+
function liveTranscriptRows(rows) {
|
|
8489
|
+
return Math.max(1, interactiveViewportRows(rows) - INTERACTIVE_CHROME_ROWS);
|
|
8187
8490
|
}
|
|
8188
8491
|
function TerminalViewport({ children, rows }) {
|
|
8189
8492
|
return /* @__PURE__ */ jsx10(
|
|
@@ -8198,14 +8501,16 @@ function TerminalViewport({ children, rows }) {
|
|
|
8198
8501
|
}
|
|
8199
8502
|
|
|
8200
8503
|
// src/ui/components/Transcript.tsx
|
|
8201
|
-
import { Box as Box12, Static, Text as Text11 } from "ink";
|
|
8504
|
+
import { Box as Box12, Static, Text as Text11, useWindowSize as useWindowSize3 } from "ink";
|
|
8202
8505
|
import { memo as memo2, useMemo as useMemo3, useRef as useRef3 } from "react";
|
|
8203
8506
|
import Spinner2 from "ink-spinner";
|
|
8507
|
+
import stringWidth2 from "string-width";
|
|
8204
8508
|
|
|
8205
8509
|
// src/ui/markdown.tsx
|
|
8206
|
-
import { Box as Box11, Text as Text10 } from "ink";
|
|
8207
|
-
import { useMemo as useMemo2 } from "react";
|
|
8208
|
-
import
|
|
8510
|
+
import { Box as Box11, Text as Text10, useWindowSize as useWindowSize2 } from "ink";
|
|
8511
|
+
import { Fragment as Fragment4, useMemo as useMemo2 } from "react";
|
|
8512
|
+
import stringWidth from "string-width";
|
|
8513
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
8209
8514
|
function Markdown({ children }) {
|
|
8210
8515
|
const blocks = useMemo2(() => extractBlocks(children), [children]);
|
|
8211
8516
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx11(BlockView, { block }, i)) });
|
|
@@ -8213,45 +8518,43 @@ function Markdown({ children }) {
|
|
|
8213
8518
|
function BlockView({ block }) {
|
|
8214
8519
|
switch (block.type) {
|
|
8215
8520
|
case "heading": {
|
|
8216
|
-
|
|
8217
|
-
const size = sizes[(block.level ?? 1) - 1] ?? 14;
|
|
8218
|
-
return /* @__PURE__ */ jsx11(Box11, { marginTop: block.level === 1 ? 1 : 0, children: /* @__PURE__ */ jsx11(Text10, { bold: true, children: truncateBySize(block.text ?? "", size) }) });
|
|
8521
|
+
return /* @__PURE__ */ jsx11(Box11, { marginTop: block.level === 1 ? 1 : 0, children: /* @__PURE__ */ jsx11(Text10, { bold: true, children: inline(block.text ?? "") }) });
|
|
8219
8522
|
}
|
|
8220
8523
|
case "blockquote":
|
|
8221
|
-
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column",
|
|
8222
|
-
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\u2502 " }),
|
|
8223
|
-
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: line })
|
|
8224
|
-
] }, i)) });
|
|
8524
|
+
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: (block.text ?? "").split("\n").map((line, i) => /* @__PURE__ */ jsx11(Text10, { dimColor: true, italic: true, children: inline(line) }, i)) });
|
|
8225
8525
|
case "ul":
|
|
8226
8526
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box11, { children: [
|
|
8227
|
-
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\u2022 " }),
|
|
8228
|
-
/* @__PURE__ */ jsx11(Box11, {
|
|
8527
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: item.text?.match(/^[☑☐] /) ? "" : "\u2022 " }),
|
|
8528
|
+
/* @__PURE__ */ jsx11(Box11, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx11(BlockView, { block: item }) })
|
|
8229
8529
|
] }, i)) });
|
|
8230
8530
|
case "ol":
|
|
8231
8531
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: (block.items ?? []).map((item, i) => /* @__PURE__ */ jsxs10(Box11, { children: [
|
|
8232
|
-
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: `${
|
|
8233
|
-
/* @__PURE__ */ jsx11(Box11, {
|
|
8532
|
+
/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: `${(block.start ?? 1) + i}. ` }),
|
|
8533
|
+
/* @__PURE__ */ jsx11(Box11, { flexGrow: 1, flexShrink: 1, minWidth: 0, children: /* @__PURE__ */ jsx11(BlockView, { block: item }) })
|
|
8234
8534
|
] }, i)) });
|
|
8235
8535
|
case "paragraph":
|
|
8236
|
-
default:
|
|
8237
|
-
|
|
8536
|
+
default: {
|
|
8537
|
+
const content = /* @__PURE__ */ jsx11(Text10, { children: inline(block.text ?? "") });
|
|
8538
|
+
if (!block.children?.length) return content;
|
|
8539
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
8540
|
+
content,
|
|
8541
|
+
/* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: block.children.map((child, index) => /* @__PURE__ */ jsx11(BlockView, { block: child }, index)) })
|
|
8542
|
+
] });
|
|
8543
|
+
}
|
|
8238
8544
|
case "table":
|
|
8239
8545
|
return /* @__PURE__ */ jsx11(TableView, { block });
|
|
8240
8546
|
case "code":
|
|
8241
|
-
return /* @__PURE__ */ jsx11(CodeBlock, {
|
|
8547
|
+
return /* @__PURE__ */ jsx11(CodeBlock, { code: block.text ?? "" });
|
|
8242
8548
|
case "hr":
|
|
8243
|
-
return /* @__PURE__ */ jsx11(Box11, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "\u2500".repeat(60) }) });
|
|
8549
|
+
return /* @__PURE__ */ jsx11(Box11, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx11(Text10, { dimColor: true, wrap: "truncate-end", children: "\u2500".repeat(60) }) });
|
|
8244
8550
|
}
|
|
8245
8551
|
}
|
|
8246
|
-
function CodeBlock({
|
|
8247
|
-
const theme = useTheme();
|
|
8552
|
+
function CodeBlock({ code }) {
|
|
8248
8553
|
const lines = code.split("\n");
|
|
8249
|
-
return /* @__PURE__ */
|
|
8250
|
-
lang && /* @__PURE__ */ jsx11(Text10, { dimColor: true, color: theme.accent, children: lang }),
|
|
8251
|
-
/* @__PURE__ */ jsx11(Box11, { borderColor: "gray", borderStyle: "round", paddingX: 1, children: /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { color: "cyan", children: line }, i)) }) })
|
|
8252
|
-
] });
|
|
8554
|
+
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", marginTop: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx11(Text10, { color: "cyan", children: line }, i)) });
|
|
8253
8555
|
}
|
|
8254
8556
|
function TableView({ block }) {
|
|
8557
|
+
const { columns } = useWindowSize2();
|
|
8255
8558
|
const headers = block.headers ?? [];
|
|
8256
8559
|
const rows = block.rows ?? [];
|
|
8257
8560
|
if (headers.length === 0 && rows.length === 0) {
|
|
@@ -8261,77 +8564,104 @@ function TableView({ block }) {
|
|
|
8261
8564
|
if (colCount === 0) {
|
|
8262
8565
|
return /* @__PURE__ */ jsx11(Text10, { dimColor: true, children: "(empty table)" });
|
|
8263
8566
|
}
|
|
8264
|
-
const
|
|
8265
|
-
|
|
8567
|
+
const allRows = [headers, ...rows].map(
|
|
8568
|
+
(row) => Array.from({ length: colCount }, (_, index) => inlineDisplayText(row[index] ?? ""))
|
|
8569
|
+
);
|
|
8570
|
+
const naturalWidths = new Array(colCount).fill(1);
|
|
8266
8571
|
for (const row of allRows) {
|
|
8267
8572
|
for (let i = 0; i < colCount; i++) {
|
|
8268
8573
|
const cell = row[i] ?? "";
|
|
8269
|
-
|
|
8574
|
+
naturalWidths[i] = Math.max(naturalWidths[i] ?? 1, stringWidth(cell));
|
|
8270
8575
|
}
|
|
8271
8576
|
}
|
|
8272
|
-
const
|
|
8577
|
+
const colWidths = fitColumnWidths(naturalWidths, Math.max(1, columns));
|
|
8578
|
+
const separator = colWidths.map((width, index) => {
|
|
8579
|
+
const edge = index === 0 || index === colWidths.length - 1;
|
|
8580
|
+
return "\u2500".repeat(width + (edge ? 1 : 2));
|
|
8581
|
+
}).join("\u253C");
|
|
8273
8582
|
const lines = [];
|
|
8274
8583
|
allRows.forEach((row, rowIdx) => {
|
|
8275
8584
|
const cells = [];
|
|
8276
8585
|
for (let i = 0; i < colCount; i++) {
|
|
8277
8586
|
const cell = row[i] ?? "";
|
|
8278
8587
|
const align = block.colAligns?.[i] ?? "left";
|
|
8279
|
-
const
|
|
8280
|
-
const
|
|
8588
|
+
const width = colWidths[i] ?? 1;
|
|
8589
|
+
const fitted = truncateToWidth(cell, width);
|
|
8590
|
+
const visualWidth = stringWidth(fitted);
|
|
8591
|
+
const totalPad = Math.max(0, width - visualWidth);
|
|
8281
8592
|
let padded;
|
|
8282
8593
|
if (align === "right") {
|
|
8283
|
-
padded =
|
|
8594
|
+
padded = `${" ".repeat(totalPad)}${fitted}`;
|
|
8284
8595
|
} else if (align === "center") {
|
|
8285
|
-
const totalPad = padWidth - visualWidth;
|
|
8286
8596
|
const left = Math.floor(totalPad / 2);
|
|
8287
|
-
padded = " ".repeat(left)
|
|
8597
|
+
padded = `${" ".repeat(left)}${fitted}${" ".repeat(totalPad - left)}`;
|
|
8288
8598
|
} else {
|
|
8289
|
-
padded =
|
|
8599
|
+
padded = `${fitted}${" ".repeat(totalPad)}`;
|
|
8290
8600
|
}
|
|
8291
8601
|
cells.push(padded);
|
|
8292
8602
|
}
|
|
8293
8603
|
lines.push(
|
|
8294
|
-
/* @__PURE__ */ jsx11(Text10, { bold: rowIdx === 0, children: cells.join(" \u2502 ") }, `row-${rowIdx}`)
|
|
8604
|
+
/* @__PURE__ */ jsx11(Text10, { bold: rowIdx === 0, wrap: "truncate-end", children: cells.join(" \u2502 ") }, `row-${rowIdx}`)
|
|
8295
8605
|
);
|
|
8296
8606
|
if (rowIdx === 0) {
|
|
8297
|
-
lines.push(/* @__PURE__ */ jsx11(Text10, { dimColor: true, children: separator }, `sep-${rowIdx}`));
|
|
8607
|
+
lines.push(/* @__PURE__ */ jsx11(Text10, { dimColor: true, wrap: "truncate-end", children: separator }, `sep-${rowIdx}`));
|
|
8298
8608
|
}
|
|
8299
8609
|
});
|
|
8300
8610
|
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: lines });
|
|
8301
8611
|
}
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
const
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8612
|
+
function fitColumnWidths(natural, maxLineWidth) {
|
|
8613
|
+
const widths = natural.map((width) => Math.max(1, width));
|
|
8614
|
+
const separatorWidth = Math.max(0, widths.length - 1) * 3;
|
|
8615
|
+
const available = Math.max(widths.length, maxLineWidth - separatorWidth);
|
|
8616
|
+
let excess = widths.reduce((sum, width) => sum + width, 0) - available;
|
|
8617
|
+
while (excess > 0) {
|
|
8618
|
+
const shrinkable = widths.map((width, index) => ({ width, index })).filter(({ width }) => width > 1);
|
|
8619
|
+
if (shrinkable.length === 0) break;
|
|
8620
|
+
const share = Math.max(1, Math.ceil(excess / shrinkable.length));
|
|
8621
|
+
for (const { index } of shrinkable) {
|
|
8622
|
+
const current = widths[index] ?? 1;
|
|
8623
|
+
const amount = Math.min(current - 1, share, excess);
|
|
8624
|
+
widths[index] = current - amount;
|
|
8625
|
+
excess -= amount;
|
|
8626
|
+
if (excess === 0) break;
|
|
8627
|
+
}
|
|
8628
|
+
}
|
|
8629
|
+
return widths;
|
|
8630
|
+
}
|
|
8631
|
+
var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
8632
|
+
function truncateToWidth(text, maxWidth) {
|
|
8633
|
+
if (stringWidth(text) <= maxWidth) return text;
|
|
8634
|
+
if (maxWidth <= 1) return "\u2026";
|
|
8635
|
+
let result = "";
|
|
8636
|
+
for (const { segment } of graphemeSegmenter.segment(text)) {
|
|
8637
|
+
if (stringWidth(result + segment) > maxWidth - 1) break;
|
|
8638
|
+
result += segment;
|
|
8639
|
+
}
|
|
8640
|
+
return `${result}\u2026`;
|
|
8641
|
+
}
|
|
8642
|
+
function inlineDisplayText(text) {
|
|
8643
|
+
return text.replace(/!\[([^\]]*)\]\(([^)]*)\)/g, "[$1]").replace(/\[([^\]]*)\]\(([^)]*)\)/g, "$1($2)").replace(/(`+)(.*?)\1/g, "$2").replace(/(\*\*|__|~~)(?=\S)(.*?\S)\1/g, "$2").replace(/(?<!\\)(\*|_)(?=\S)(.*?\S)\1/g, "$2").replace(/\\([\\`*_[\]{}()#+\-.!|>])/g, "$1");
|
|
8644
|
+
}
|
|
8645
|
+
var HEADING_RE = /^ {0,3}(#{1,6})(?:[ \t]+(.*)|[ \t]*)$/;
|
|
8308
8646
|
var UL_RE = /^([-*+])\s+(.*)$/;
|
|
8309
|
-
var OL_RE = /^(\d
|
|
8647
|
+
var OL_RE = /^(\d{1,9})[.)]\s+(.*)$/;
|
|
8310
8648
|
var QUOTE_RE = /^>\s?(.*)$/;
|
|
8311
|
-
var
|
|
8312
|
-
var
|
|
8313
|
-
var
|
|
8314
|
-
var
|
|
8649
|
+
var TASK_CONTENT_RE = /^\[([ xX])\]\s+(.*)$/;
|
|
8650
|
+
var SETEXT_RE = /^ {0,3}(=+|-+)[ \t]*$/;
|
|
8651
|
+
var MAX_TABLE_COLUMNS = 20;
|
|
8652
|
+
var MAX_LIST_DEPTH = 20;
|
|
8315
8653
|
function extractBlocks(src) {
|
|
8316
|
-
const lines = src.replace(/\r\n
|
|
8654
|
+
const lines = src.replace(/\r\n?/g, "\n").split("\n");
|
|
8317
8655
|
const blocks = [];
|
|
8318
8656
|
let paragraph = [];
|
|
8319
|
-
let list = null;
|
|
8320
8657
|
let quote = [];
|
|
8321
8658
|
let code = null;
|
|
8322
|
-
let table = null;
|
|
8323
8659
|
const flushParagraph = () => {
|
|
8324
8660
|
if (paragraph.length) {
|
|
8325
|
-
blocks.push({ type: "paragraph", text: paragraph
|
|
8661
|
+
blocks.push({ type: "paragraph", text: joinParagraphLines(paragraph) });
|
|
8326
8662
|
paragraph = [];
|
|
8327
8663
|
}
|
|
8328
8664
|
};
|
|
8329
|
-
const flushList = () => {
|
|
8330
|
-
if (list && list.items.length) {
|
|
8331
|
-
blocks.push({ type: list.ordered ? "ol" : "ul", items: list.items });
|
|
8332
|
-
list = null;
|
|
8333
|
-
}
|
|
8334
|
-
};
|
|
8335
8665
|
const flushQuote = () => {
|
|
8336
8666
|
if (quote.length) {
|
|
8337
8667
|
blocks.push({ type: "blockquote", text: quote.join("\n") });
|
|
@@ -8346,238 +8676,360 @@ function extractBlocks(src) {
|
|
|
8346
8676
|
};
|
|
8347
8677
|
const flushAll = () => {
|
|
8348
8678
|
flushParagraph();
|
|
8349
|
-
flushList();
|
|
8350
8679
|
flushQuote();
|
|
8351
8680
|
flushCode();
|
|
8352
8681
|
};
|
|
8353
|
-
const flushTable = () => {
|
|
8354
|
-
if (table && table.headers.length > 0) {
|
|
8355
|
-
blocks.push({
|
|
8356
|
-
type: "table",
|
|
8357
|
-
headers: table.headers,
|
|
8358
|
-
rows: table.rows,
|
|
8359
|
-
colAligns: table.colAligns
|
|
8360
|
-
});
|
|
8361
|
-
}
|
|
8362
|
-
table = null;
|
|
8363
|
-
};
|
|
8364
|
-
const parseListItem = (text, indent) => {
|
|
8365
|
-
const task = text.match(TASK_RE);
|
|
8366
|
-
if (task) {
|
|
8367
|
-
const done = task[1].toLowerCase() === "x";
|
|
8368
|
-
return {
|
|
8369
|
-
type: "paragraph",
|
|
8370
|
-
text: `${done ? "\u2611" : "\u2610"} ${task[2]}`
|
|
8371
|
-
};
|
|
8372
|
-
}
|
|
8373
|
-
return { type: "paragraph", text };
|
|
8374
|
-
};
|
|
8375
8682
|
for (let i = 0; i < lines.length; i++) {
|
|
8376
|
-
const line = lines[i];
|
|
8683
|
+
const line = lines[i] ?? "";
|
|
8377
8684
|
const trimmed = line.trim();
|
|
8378
8685
|
if (code) {
|
|
8379
|
-
|
|
8380
|
-
if (endMatch && endMatch[1][0] === code.fence[0] && endMatch[1].length >= code.fence.length) {
|
|
8686
|
+
if (isClosingFence(line, code.fence)) {
|
|
8381
8687
|
flushCode();
|
|
8382
8688
|
continue;
|
|
8383
8689
|
}
|
|
8384
8690
|
code.lines.push(line);
|
|
8385
8691
|
continue;
|
|
8386
8692
|
}
|
|
8387
|
-
const
|
|
8388
|
-
if (
|
|
8693
|
+
const fence = parseOpeningFence(line);
|
|
8694
|
+
if (fence) {
|
|
8389
8695
|
flushAll();
|
|
8390
|
-
|
|
8391
|
-
code = { fence: fenceMatch[1], lang: fenceMatch[2], lines: [] };
|
|
8696
|
+
code = { fence: fence.fence, lang: fence.lang, lines: [] };
|
|
8392
8697
|
continue;
|
|
8393
8698
|
}
|
|
8394
8699
|
if (trimmed === "") {
|
|
8395
|
-
flushTable();
|
|
8396
8700
|
flushAll();
|
|
8397
8701
|
continue;
|
|
8398
8702
|
}
|
|
8399
|
-
const
|
|
8400
|
-
if (
|
|
8401
|
-
flushTable();
|
|
8703
|
+
const heading = line.match(HEADING_RE);
|
|
8704
|
+
if (heading) {
|
|
8402
8705
|
flushAll();
|
|
8403
|
-
|
|
8706
|
+
const text = (heading[2] ?? "").replace(/[ \t]+#+[ \t]*$/, "").trim();
|
|
8707
|
+
blocks.push({ type: "heading", level: heading[1].length, text });
|
|
8404
8708
|
continue;
|
|
8405
8709
|
}
|
|
8406
|
-
const
|
|
8407
|
-
if (
|
|
8408
|
-
|
|
8710
|
+
const setext = lines[i + 1]?.match(SETEXT_RE);
|
|
8711
|
+
if (setext && isSetextHeadingText(line)) {
|
|
8712
|
+
flushAll();
|
|
8713
|
+
blocks.push({ type: "heading", level: setext[1][0] === "=" ? 1 : 2, text: trimmed });
|
|
8714
|
+
i += 1;
|
|
8715
|
+
continue;
|
|
8716
|
+
}
|
|
8717
|
+
if (isHorizontalRule(trimmed)) {
|
|
8409
8718
|
flushAll();
|
|
8410
|
-
blocks.push({ type: "
|
|
8719
|
+
blocks.push({ type: "hr" });
|
|
8411
8720
|
continue;
|
|
8412
8721
|
}
|
|
8413
8722
|
const quoteMatch = trimmed.match(QUOTE_RE);
|
|
8414
8723
|
if (quoteMatch) {
|
|
8415
|
-
flushTable();
|
|
8416
8724
|
flushParagraph();
|
|
8417
|
-
flushList();
|
|
8418
8725
|
flushCode();
|
|
8419
8726
|
quote.push(quoteMatch[1]);
|
|
8420
8727
|
continue;
|
|
8421
8728
|
}
|
|
8422
|
-
const
|
|
8423
|
-
|
|
8424
|
-
const taskMatch = trimmed.match(TASK_RE);
|
|
8425
|
-
if (taskMatch) {
|
|
8426
|
-
flushTable();
|
|
8729
|
+
const parsedList = parseListBlock(lines, i);
|
|
8730
|
+
if (parsedList) {
|
|
8427
8731
|
flushParagraph();
|
|
8428
8732
|
flushQuote();
|
|
8429
8733
|
flushCode();
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
list = { ordered: false, items: [], indent };
|
|
8433
|
-
}
|
|
8434
|
-
list.items.push(parseListItem(trimmed, indent));
|
|
8734
|
+
blocks.push(parsedList.block);
|
|
8735
|
+
i = parsedList.nextIndex - 1;
|
|
8435
8736
|
continue;
|
|
8436
8737
|
}
|
|
8437
|
-
const
|
|
8438
|
-
if (
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
flushCode();
|
|
8443
|
-
if (!list || list.ordered || indent !== list.indent) {
|
|
8444
|
-
flushList();
|
|
8445
|
-
list = { ordered: false, items: [], indent };
|
|
8446
|
-
}
|
|
8447
|
-
list.items.push(parseListItem(ulMatch[2], indent));
|
|
8738
|
+
const markdownTable = parseMarkdownTable(lines, i);
|
|
8739
|
+
if (markdownTable) {
|
|
8740
|
+
flushAll();
|
|
8741
|
+
blocks.push(markdownTable.block);
|
|
8742
|
+
i = markdownTable.nextIndex - 1;
|
|
8448
8743
|
continue;
|
|
8449
8744
|
}
|
|
8450
|
-
const
|
|
8451
|
-
if (
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
flushCode();
|
|
8456
|
-
if (!list || !list.ordered || indent !== list.indent) {
|
|
8457
|
-
flushList();
|
|
8458
|
-
list = { ordered: true, items: [], indent };
|
|
8459
|
-
}
|
|
8460
|
-
list.items.push(parseListItem(olMatch[2], indent));
|
|
8745
|
+
const asciiTable = parseAsciiTable(lines, i);
|
|
8746
|
+
if (asciiTable) {
|
|
8747
|
+
flushAll();
|
|
8748
|
+
blocks.push(asciiTable.block);
|
|
8749
|
+
i = asciiTable.nextIndex - 1;
|
|
8461
8750
|
continue;
|
|
8462
8751
|
}
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
if (cells.length >= 2 && cells.every((c) => /^:?-+:?$/.test(c))) {
|
|
8467
|
-
if (table) {
|
|
8468
|
-
table.colAligns = cells.map((c) => {
|
|
8469
|
-
if (c.startsWith(":") && c.endsWith(":")) return "center";
|
|
8470
|
-
if (c.endsWith(":")) return "right";
|
|
8471
|
-
return "left";
|
|
8472
|
-
});
|
|
8473
|
-
continue;
|
|
8474
|
-
}
|
|
8475
|
-
}
|
|
8752
|
+
if (quote.length > 0) {
|
|
8753
|
+
quote.push(trimmed);
|
|
8754
|
+
continue;
|
|
8476
8755
|
}
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8756
|
+
flushQuote();
|
|
8757
|
+
flushCode();
|
|
8758
|
+
paragraph.push(paragraphLine(line));
|
|
8759
|
+
}
|
|
8760
|
+
flushAll();
|
|
8761
|
+
return blocks;
|
|
8762
|
+
}
|
|
8763
|
+
function paragraphLine(line) {
|
|
8764
|
+
const hardBreak = /(?: {2,}|\\)$/.test(line);
|
|
8765
|
+
const text = line.trim().replace(/\\$/, "");
|
|
8766
|
+
return hardBreak ? `${text}
|
|
8767
|
+
` : text;
|
|
8768
|
+
}
|
|
8769
|
+
function joinParagraphLines(lines) {
|
|
8770
|
+
let result = "";
|
|
8771
|
+
for (const line of lines) {
|
|
8772
|
+
if (result && !result.endsWith("\n")) result += " ";
|
|
8773
|
+
result += line;
|
|
8774
|
+
}
|
|
8775
|
+
return result.trim();
|
|
8776
|
+
}
|
|
8777
|
+
function parseListMarker(line) {
|
|
8778
|
+
const match = line.match(/^(\s*)(?:(\d{1,9})[.)]|([-+*]))\s+(.*)$/);
|
|
8779
|
+
if (!match) return null;
|
|
8780
|
+
return {
|
|
8781
|
+
indent: match[1].replace(/\t/g, " ").length,
|
|
8782
|
+
ordered: Boolean(match[2]),
|
|
8783
|
+
start: match[2] ? Number.parseInt(match[2], 10) : 1,
|
|
8784
|
+
text: match[4]
|
|
8785
|
+
};
|
|
8786
|
+
}
|
|
8787
|
+
function parseListItem(text) {
|
|
8788
|
+
const task = text.match(TASK_CONTENT_RE);
|
|
8789
|
+
return {
|
|
8790
|
+
type: "paragraph",
|
|
8791
|
+
text: task ? `${task[1].toLowerCase() === "x" ? "\u2611" : "\u2610"} ${task[2]}` : text
|
|
8792
|
+
};
|
|
8793
|
+
}
|
|
8794
|
+
function parseListBlock(lines, start, depth = 0) {
|
|
8795
|
+
const first2 = parseListMarker(lines[start] ?? "");
|
|
8796
|
+
if (!first2) return null;
|
|
8797
|
+
const items = [];
|
|
8798
|
+
let nextIndex = start;
|
|
8799
|
+
while (nextIndex < lines.length) {
|
|
8800
|
+
const marker = parseListMarker(lines[nextIndex] ?? "");
|
|
8801
|
+
if (!marker || marker.indent !== first2.indent || marker.ordered !== first2.ordered) break;
|
|
8802
|
+
const item = parseListItem(marker.text);
|
|
8803
|
+
nextIndex += 1;
|
|
8804
|
+
while (nextIndex < lines.length) {
|
|
8805
|
+
if ((lines[nextIndex] ?? "").trim() === "") {
|
|
8806
|
+
let afterBlank = nextIndex;
|
|
8807
|
+
while (afterBlank < lines.length && (lines[afterBlank] ?? "").trim() === "") {
|
|
8808
|
+
afterBlank += 1;
|
|
8491
8809
|
}
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
if (asciiSepMatch) {
|
|
8497
|
-
if (table && table.headers.length > 0 && table.colAligns.length === 0) {
|
|
8498
|
-
table.colAligns = table.headers.map(() => "left");
|
|
8499
|
-
continue;
|
|
8500
|
-
}
|
|
8501
|
-
if (paragraph.length === 1) {
|
|
8502
|
-
const prevLine = paragraph[0].trim();
|
|
8503
|
-
const tokens3 = prevLine.split(/\s{2,}/).map((c) => c.trim()).filter((c) => c.length > 0);
|
|
8504
|
-
if (tokens3.length >= 2 && tokens3.length <= 10) {
|
|
8505
|
-
paragraph = [];
|
|
8506
|
-
flushList();
|
|
8507
|
-
flushQuote();
|
|
8508
|
-
flushCode();
|
|
8509
|
-
table = { headers: tokens3, rows: [], colAligns: tokens3.map(() => "left") };
|
|
8510
|
-
continue;
|
|
8810
|
+
const followingMarker2 = parseListMarker(lines[afterBlank] ?? "");
|
|
8811
|
+
if (!followingMarker2 || followingMarker2.indent < first2.indent) {
|
|
8812
|
+
nextIndex = afterBlank;
|
|
8813
|
+
break;
|
|
8511
8814
|
}
|
|
8815
|
+
nextIndex = afterBlank;
|
|
8816
|
+
if (followingMarker2.indent === first2.indent) break;
|
|
8512
8817
|
}
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
if (!looksLikeCode && !looksLikePath) {
|
|
8520
|
-
table.rows.push(tokens3);
|
|
8818
|
+
const followingMarker = parseListMarker(lines[nextIndex] ?? "");
|
|
8819
|
+
if (followingMarker) {
|
|
8820
|
+
if (followingMarker.indent <= first2.indent) break;
|
|
8821
|
+
if (depth >= MAX_LIST_DEPTH) {
|
|
8822
|
+
item.text = `${item.text ?? ""} ${followingMarker.text}`.trim();
|
|
8823
|
+
nextIndex += 1;
|
|
8521
8824
|
continue;
|
|
8522
8825
|
}
|
|
8826
|
+
const nested = parseListBlock(lines, nextIndex, depth + 1);
|
|
8827
|
+
if (!nested) break;
|
|
8828
|
+
item.children ??= [];
|
|
8829
|
+
item.children.push(nested.block);
|
|
8830
|
+
nextIndex = nested.nextIndex;
|
|
8831
|
+
continue;
|
|
8523
8832
|
}
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8833
|
+
if (interruptsList(lines, nextIndex)) break;
|
|
8834
|
+
const continuation = (lines[nextIndex] ?? "").trim();
|
|
8835
|
+
item.text = `${item.text ?? ""} ${continuation}`.trim();
|
|
8836
|
+
nextIndex += 1;
|
|
8837
|
+
}
|
|
8838
|
+
items.push(item);
|
|
8839
|
+
}
|
|
8840
|
+
const block = { type: first2.ordered ? "ol" : "ul", items };
|
|
8841
|
+
if (first2.ordered && first2.start !== 1) block.start = first2.start;
|
|
8842
|
+
return { block, nextIndex };
|
|
8843
|
+
}
|
|
8844
|
+
function interruptsList(lines, index) {
|
|
8845
|
+
const line = lines[index] ?? "";
|
|
8846
|
+
const trimmed = line.trim();
|
|
8847
|
+
return Boolean(
|
|
8848
|
+
line.match(HEADING_RE) || trimmed.match(QUOTE_RE) || parseOpeningFence(line) || isHorizontalRule(trimmed) || parseMarkdownTable(lines, index) || parseAsciiTable(lines, index)
|
|
8849
|
+
);
|
|
8850
|
+
}
|
|
8851
|
+
function parseOpeningFence(line) {
|
|
8852
|
+
const match = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
8853
|
+
if (!match) return null;
|
|
8854
|
+
const info = match[2].trim();
|
|
8855
|
+
if (match[1][0] === "`" && info.includes("`")) return null;
|
|
8856
|
+
return { fence: match[1], lang: info.split(/\s+/, 1)[0] ?? "" };
|
|
8857
|
+
}
|
|
8858
|
+
function isClosingFence(line, opening) {
|
|
8859
|
+
const match = line.match(/^ {0,3}(`+|~+)[ \t]*$/);
|
|
8860
|
+
return Boolean(
|
|
8861
|
+
match && match[1][0] === opening[0] && match[1].length >= opening.length
|
|
8862
|
+
);
|
|
8863
|
+
}
|
|
8864
|
+
function isHorizontalRule(line) {
|
|
8865
|
+
return /^(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/.test(line);
|
|
8866
|
+
}
|
|
8867
|
+
function isSetextHeadingText(line) {
|
|
8868
|
+
const trimmed = line.trim();
|
|
8869
|
+
return trimmed !== "" && !HEADING_RE.test(line) && !QUOTE_RE.test(trimmed) && !UL_RE.test(trimmed) && !OL_RE.test(trimmed) && !parseOpeningFence(line);
|
|
8870
|
+
}
|
|
8871
|
+
function parseMarkdownTable(lines, start) {
|
|
8872
|
+
const headers = splitTableRow(lines[start] ?? "");
|
|
8873
|
+
const delimiter = splitTableRow(lines[start + 1] ?? "");
|
|
8874
|
+
if (!headers || !delimiter || headers.length < 2 || headers.length > MAX_TABLE_COLUMNS || delimiter.length !== headers.length || !delimiter.every((cell) => /^:?-{3,}:?$/.test(cell))) {
|
|
8875
|
+
return null;
|
|
8530
8876
|
}
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8877
|
+
const colAligns = delimiter.map((cell) => {
|
|
8878
|
+
if (cell.startsWith(":") && cell.endsWith(":")) return "center";
|
|
8879
|
+
if (cell.endsWith(":")) return "right";
|
|
8880
|
+
return "left";
|
|
8881
|
+
});
|
|
8882
|
+
const rows = [];
|
|
8883
|
+
let nextIndex = start + 2;
|
|
8884
|
+
while (nextIndex < lines.length) {
|
|
8885
|
+
const cells = splitTableRow(lines[nextIndex] ?? "");
|
|
8886
|
+
if (!cells) break;
|
|
8887
|
+
rows.push(headers.map((_, index) => cells[index] ?? ""));
|
|
8888
|
+
nextIndex += 1;
|
|
8889
|
+
}
|
|
8890
|
+
return {
|
|
8891
|
+
block: { type: "table", headers, rows, colAligns },
|
|
8892
|
+
nextIndex
|
|
8893
|
+
};
|
|
8894
|
+
}
|
|
8895
|
+
function splitTableRow(line) {
|
|
8896
|
+
const source = line.trim();
|
|
8897
|
+
if (!source.includes("|")) return null;
|
|
8898
|
+
const cells = [];
|
|
8899
|
+
let cell = "";
|
|
8900
|
+
let codeFenceLength = 0;
|
|
8901
|
+
let foundSeparator = false;
|
|
8902
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
8903
|
+
const character = source[index] ?? "";
|
|
8904
|
+
if (character === "\\" && source[index + 1] === "|") {
|
|
8905
|
+
cell += "|";
|
|
8906
|
+
index += 1;
|
|
8907
|
+
continue;
|
|
8908
|
+
}
|
|
8909
|
+
if (character === "`") {
|
|
8910
|
+
let end = index + 1;
|
|
8911
|
+
while (source[end] === "`") end += 1;
|
|
8912
|
+
const runLength = end - index;
|
|
8913
|
+
if (codeFenceLength === 0) codeFenceLength = runLength;
|
|
8914
|
+
else if (codeFenceLength === runLength) codeFenceLength = 0;
|
|
8915
|
+
cell += source.slice(index, end);
|
|
8916
|
+
index = end - 1;
|
|
8917
|
+
continue;
|
|
8918
|
+
}
|
|
8919
|
+
if (character === "|" && codeFenceLength === 0) {
|
|
8920
|
+
foundSeparator = true;
|
|
8921
|
+
cells.push(cell.trim());
|
|
8922
|
+
cell = "";
|
|
8923
|
+
continue;
|
|
8924
|
+
}
|
|
8925
|
+
cell += character;
|
|
8926
|
+
}
|
|
8927
|
+
cells.push(cell.trim());
|
|
8928
|
+
if (!foundSeparator) return null;
|
|
8929
|
+
if (cells[0] === "") cells.shift();
|
|
8930
|
+
if (cells[cells.length - 1] === "") cells.pop();
|
|
8931
|
+
return cells.length >= 2 ? cells : null;
|
|
8932
|
+
}
|
|
8933
|
+
function parseAsciiTable(lines, start) {
|
|
8934
|
+
const headers = splitAsciiCells(lines[start] ?? "");
|
|
8935
|
+
if (headers.length < 2 || headers.length > MAX_TABLE_COLUMNS) return null;
|
|
8936
|
+
const separator = (lines[start + 1] ?? "").trim();
|
|
8937
|
+
const separatorCells = splitAsciiCells(separator);
|
|
8938
|
+
const validSeparator = /^[─┄┈━┅-]{3,}$/.test(separator) || separatorCells.length === headers.length && separatorCells.every((cell) => /^[─┄┈━┅-]{3,}$/.test(cell));
|
|
8939
|
+
if (!validSeparator) return null;
|
|
8940
|
+
const rows = [];
|
|
8941
|
+
let nextIndex = start + 2;
|
|
8942
|
+
while (nextIndex < lines.length) {
|
|
8943
|
+
const cells = splitAsciiCells(lines[nextIndex] ?? "");
|
|
8944
|
+
if (cells.length !== headers.length) break;
|
|
8945
|
+
rows.push(cells);
|
|
8946
|
+
nextIndex += 1;
|
|
8947
|
+
}
|
|
8948
|
+
if (rows.length === 0) return null;
|
|
8949
|
+
return {
|
|
8950
|
+
block: {
|
|
8951
|
+
type: "table",
|
|
8952
|
+
headers,
|
|
8953
|
+
rows,
|
|
8954
|
+
colAligns: headers.map(() => "left")
|
|
8955
|
+
},
|
|
8956
|
+
nextIndex
|
|
8957
|
+
};
|
|
8958
|
+
}
|
|
8959
|
+
function splitAsciiCells(line) {
|
|
8960
|
+
return line.trim().split(/\s{2,}/).map((cell) => cell.trim()).filter(Boolean);
|
|
8534
8961
|
}
|
|
8535
8962
|
var MAX_INLINE_DEPTH = 10;
|
|
8963
|
+
var INLINE_ESCAPE_BASE = 57344;
|
|
8964
|
+
var ESCAPABLE_MARKDOWN = ["\\", "`", "*", "_", "[", "]", "{", "}", "(", ")", "#", "+", "-", ".", "!", "|", ">"];
|
|
8965
|
+
function protectInlineEscapes(text) {
|
|
8966
|
+
return text.replace(/\\([\\`*_[\]{}()#+\-.!|>])/g, (_match, character) => {
|
|
8967
|
+
const index = ESCAPABLE_MARKDOWN.indexOf(character);
|
|
8968
|
+
return String.fromCodePoint(INLINE_ESCAPE_BASE + index);
|
|
8969
|
+
});
|
|
8970
|
+
}
|
|
8971
|
+
function restoreInlineEscapes(text) {
|
|
8972
|
+
return [...text].map((character) => {
|
|
8973
|
+
const index = character.codePointAt(0) - INLINE_ESCAPE_BASE;
|
|
8974
|
+
return index >= 0 && index < ESCAPABLE_MARKDOWN.length ? ESCAPABLE_MARKDOWN[index] : character;
|
|
8975
|
+
}).join("");
|
|
8976
|
+
}
|
|
8536
8977
|
function inline(text, depth = 0) {
|
|
8537
8978
|
if (depth > MAX_INLINE_DEPTH) {
|
|
8538
|
-
return text;
|
|
8979
|
+
return restoreInlineEscapes(text);
|
|
8539
8980
|
}
|
|
8540
8981
|
const nodes = [];
|
|
8541
|
-
let rest = text;
|
|
8982
|
+
let rest = protectInlineEscapes(text);
|
|
8542
8983
|
let key = 0;
|
|
8543
8984
|
const matchers = [
|
|
8544
8985
|
{
|
|
8545
|
-
re: /(!\[)([^\]]*)\]\(([^)]
|
|
8986
|
+
re: /(!\[)([^\]]*)\]\(((?:[^()\\]|\\.|\([^()]*\))+?)\)/,
|
|
8546
8987
|
handler: (m) => /* @__PURE__ */ jsxs10(Text10, { color: "cyan", bold: true, children: [
|
|
8547
8988
|
"[img: ",
|
|
8548
|
-
m[2],
|
|
8989
|
+
restoreInlineEscapes(m[2]),
|
|
8549
8990
|
"]"
|
|
8550
8991
|
] }, key++)
|
|
8551
8992
|
},
|
|
8552
8993
|
{
|
|
8553
|
-
re: /(\[)([^\]]*)\]\(([^)]
|
|
8554
|
-
handler: (m) =>
|
|
8555
|
-
|
|
8556
|
-
/* @__PURE__ */ jsxs10(
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8994
|
+
re: /(\[)([^\]]*)\]\(((?:[^()\\]|\\.|\([^()]*\))+?)\)/,
|
|
8995
|
+
handler: (m) => {
|
|
8996
|
+
const fragmentKey = key++;
|
|
8997
|
+
return /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
8998
|
+
/* @__PURE__ */ jsx11(Text10, { color: "cyan", underline: true, children: inline(m[2], depth + 1) }),
|
|
8999
|
+
/* @__PURE__ */ jsxs10(Text10, { dimColor: true, color: "gray", children: [
|
|
9000
|
+
"(",
|
|
9001
|
+
restoreInlineEscapes(m[3]),
|
|
9002
|
+
")"
|
|
9003
|
+
] })
|
|
9004
|
+
] }, fragmentKey);
|
|
9005
|
+
}
|
|
8562
9006
|
},
|
|
8563
9007
|
{
|
|
8564
9008
|
re: /(`+)([^`]+?)\1/,
|
|
8565
|
-
handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, color: "cyan", children: m[2] }, key++)
|
|
9009
|
+
handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, color: "cyan", children: restoreInlineEscapes(m[2]) }, key++)
|
|
8566
9010
|
},
|
|
8567
9011
|
{
|
|
8568
|
-
re: /(
|
|
9012
|
+
re: /(\*\*\*|___)(?=\S)(.*?\S)\1/,
|
|
9013
|
+
handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, italic: true, children: inline(m[2], depth + 1) }, key++)
|
|
9014
|
+
},
|
|
9015
|
+
{
|
|
9016
|
+
re: /(\*\*)(?=\S)(.*?\S)\1/,
|
|
8569
9017
|
handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, children: inline(m[2], depth + 1) }, key++)
|
|
8570
9018
|
},
|
|
8571
9019
|
{
|
|
8572
|
-
re: /(__)(
|
|
9020
|
+
re: /(__)(?=\S)(.*?\S)\1/,
|
|
8573
9021
|
handler: (m) => /* @__PURE__ */ jsx11(Text10, { bold: true, children: inline(m[2], depth + 1) }, key++)
|
|
8574
9022
|
},
|
|
8575
9023
|
{
|
|
8576
|
-
re: /(~~)(
|
|
8577
|
-
handler: (m) => /* @__PURE__ */ jsx11(Text10, { strikethrough: true, children: m[2] }, key++)
|
|
9024
|
+
re: /(~~)(?=\S)(.*?\S)\1/,
|
|
9025
|
+
handler: (m) => /* @__PURE__ */ jsx11(Text10, { strikethrough: true, children: inline(m[2], depth + 1) }, key++)
|
|
9026
|
+
},
|
|
9027
|
+
{
|
|
9028
|
+
re: /(\*)(?=\S)(.*?\S)\1/,
|
|
9029
|
+
handler: (m) => /* @__PURE__ */ jsx11(Text10, { italic: true, children: inline(m[2], depth + 1) }, key++)
|
|
8578
9030
|
},
|
|
8579
9031
|
{
|
|
8580
|
-
re: /(
|
|
9032
|
+
re: /(?<!\w)(_)(?=\S)(.*?\S)\1(?!\w)/,
|
|
8581
9033
|
handler: (m) => /* @__PURE__ */ jsx11(Text10, { italic: true, children: inline(m[2], depth + 1) }, key++)
|
|
8582
9034
|
}
|
|
8583
9035
|
];
|
|
@@ -8590,11 +9042,11 @@ function inline(text, depth = 0) {
|
|
|
8590
9042
|
}
|
|
8591
9043
|
}
|
|
8592
9044
|
if (!best) {
|
|
8593
|
-
nodes.push(rest);
|
|
9045
|
+
nodes.push(restoreInlineEscapes(rest));
|
|
8594
9046
|
break;
|
|
8595
9047
|
}
|
|
8596
9048
|
const idx = best.match.index ?? 0;
|
|
8597
|
-
if (idx > 0) nodes.push(rest.slice(0, idx));
|
|
9049
|
+
if (idx > 0) nodes.push(restoreInlineEscapes(rest.slice(0, idx)));
|
|
8598
9050
|
nodes.push(best.handler(best.match));
|
|
8599
9051
|
rest = rest.slice(idx + best.match[0].length);
|
|
8600
9052
|
}
|
|
@@ -8604,12 +9056,19 @@ function inline(text, depth = 0) {
|
|
|
8604
9056
|
// src/ui/components/Transcript.tsx
|
|
8605
9057
|
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
8606
9058
|
var HEADER = { kind: "header" };
|
|
9059
|
+
function assistantThinkingForFrame(bubble, frozenThinking) {
|
|
9060
|
+
return bubble.streaming && bubble.text !== "" ? frozenThinking ?? bubble.thinking : bubble.thinking;
|
|
9061
|
+
}
|
|
8607
9062
|
function firstMutableBubbleIndex(bubbles) {
|
|
8608
9063
|
return bubbles.findIndex(
|
|
8609
9064
|
(bubble) => bubble.kind === "assistant" && bubble.streaming || bubble.kind === "tool" && bubble.state === "running" || bubble.kind === "subagent" && bubble.state === "running"
|
|
8610
9065
|
);
|
|
8611
9066
|
}
|
|
8612
|
-
var Transcript = memo2(function Transcript2({
|
|
9067
|
+
var Transcript = memo2(function Transcript2({
|
|
9068
|
+
bubbles,
|
|
9069
|
+
workspace,
|
|
9070
|
+
maxLiveRows = 12
|
|
9071
|
+
}) {
|
|
8613
9072
|
const liveAt = firstMutableBubbleIndex(bubbles);
|
|
8614
9073
|
const stableCount = liveAt === -1 ? bubbles.length : liveAt;
|
|
8615
9074
|
const stableRef = useRef3([]);
|
|
@@ -8623,21 +9082,36 @@ var Transcript = memo2(function Transcript2({ bubbles, workspace }) {
|
|
|
8623
9082
|
}
|
|
8624
9083
|
const live = liveAt === -1 ? [] : bubbles.slice(liveAt);
|
|
8625
9084
|
const staticItems = [HEADER, ...stableRef.current];
|
|
8626
|
-
return /* @__PURE__ */ jsxs11(
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
children:
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
9085
|
+
return /* @__PURE__ */ jsxs11(
|
|
9086
|
+
Box12,
|
|
9087
|
+
{
|
|
9088
|
+
flexDirection: "column",
|
|
9089
|
+
marginBottom: 1,
|
|
9090
|
+
flexShrink: 1,
|
|
9091
|
+
minHeight: 0,
|
|
9092
|
+
overflowY: "hidden",
|
|
9093
|
+
children: [
|
|
9094
|
+
/* @__PURE__ */ jsx12(Static, { items: staticItems, children: (item) => item.kind === "header" ? /* @__PURE__ */ jsx12(Logo, { workspace }, "kitcode-header") : /* @__PURE__ */ jsx12(BubbleView, { bubble: item }, item.id) }),
|
|
9095
|
+
/* @__PURE__ */ jsx12(
|
|
9096
|
+
Box12,
|
|
9097
|
+
{
|
|
9098
|
+
flexDirection: "column",
|
|
9099
|
+
flexShrink: 1,
|
|
9100
|
+
minHeight: 0,
|
|
9101
|
+
maxHeight: maxLiveRows,
|
|
9102
|
+
overflowY: "hidden",
|
|
9103
|
+
justifyContent: "flex-end",
|
|
9104
|
+
children: live.map((bubble) => /* @__PURE__ */ jsx12(BubbleView, { bubble, maxRows: maxLiveRows }, bubble.id))
|
|
9105
|
+
}
|
|
9106
|
+
)
|
|
9107
|
+
]
|
|
9108
|
+
}
|
|
9109
|
+
);
|
|
8639
9110
|
});
|
|
8640
|
-
var BubbleView = memo2(function BubbleView2({
|
|
9111
|
+
var BubbleView = memo2(function BubbleView2({
|
|
9112
|
+
bubble,
|
|
9113
|
+
maxRows
|
|
9114
|
+
}) {
|
|
8641
9115
|
const theme = useTheme();
|
|
8642
9116
|
if (bubble.kind === "user") {
|
|
8643
9117
|
return /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text11, { color: theme.accent, bold: true, children: [
|
|
@@ -8650,20 +9124,62 @@ var BubbleView = memo2(function BubbleView2({ bubble }) {
|
|
|
8650
9124
|
return /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text11, { color, children: bubble.text }) });
|
|
8651
9125
|
}
|
|
8652
9126
|
if (bubble.kind === "assistant") {
|
|
8653
|
-
return /* @__PURE__ */
|
|
8654
|
-
bubble.thinking.trim() !== "" && /* @__PURE__ */ jsx12(Text11, { dimColor: true, italic: true, children: bubble.thinking.trim() }),
|
|
8655
|
-
bubble.streaming ? /* @__PURE__ */ jsx12(Text11, { children: bubble.text }) : /* @__PURE__ */ jsx12(Markdown, { children: bubble.text }),
|
|
8656
|
-
bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
8657
|
-
/* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
|
|
8658
|
-
" thinking"
|
|
8659
|
-
] })
|
|
8660
|
-
] });
|
|
9127
|
+
return /* @__PURE__ */ jsx12(AssistantView, { bubble, maxRows });
|
|
8661
9128
|
}
|
|
8662
9129
|
if (bubble.kind === "subagent") {
|
|
8663
9130
|
return /* @__PURE__ */ jsx12(SubagentView, { bubble });
|
|
8664
9131
|
}
|
|
8665
9132
|
return /* @__PURE__ */ jsx12(ToolView, { bubble });
|
|
8666
9133
|
});
|
|
9134
|
+
function AssistantView({ bubble, maxRows }) {
|
|
9135
|
+
const { columns } = useWindowSize3();
|
|
9136
|
+
const frozenThinking = useRef3(void 0);
|
|
9137
|
+
const answering = bubble.streaming && bubble.text !== "";
|
|
9138
|
+
if (!answering) frozenThinking.current = void 0;
|
|
9139
|
+
if (answering && frozenThinking.current === void 0) {
|
|
9140
|
+
frozenThinking.current = bubble.thinking;
|
|
9141
|
+
}
|
|
9142
|
+
const visibleThinking = assistantThinkingForFrame(bubble, frozenThinking.current);
|
|
9143
|
+
const liveBudget = bubble.streaming && maxRows !== void 0 ? Math.max(1, maxRows - 1) : void 0;
|
|
9144
|
+
const thinkingBudget = liveBudget === void 0 ? void 0 : bubble.text !== "" ? Math.min(3, Math.max(0, liveBudget - 1)) : Math.max(0, liveBudget - 1);
|
|
9145
|
+
const frameThinking = thinkingBudget === void 0 ? visibleThinking.trim() : clipTextToRows(visibleThinking.trim(), thinkingBudget, columns);
|
|
9146
|
+
const usedThinkingRows = frameThinking === "" ? 0 : frameThinking.split("\n").length;
|
|
9147
|
+
const frameText = liveBudget === void 0 ? bubble.text : clipTextToRows(bubble.text, Math.max(1, liveBudget - usedThinkingRows), columns);
|
|
9148
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
9149
|
+
frameThinking !== "" && /* @__PURE__ */ jsx12(Text11, { dimColor: true, italic: true, children: frameThinking }),
|
|
9150
|
+
bubble.streaming ? /* @__PURE__ */ jsx12(Text11, { children: frameText }) : /* @__PURE__ */ jsx12(Markdown, { children: bubble.text }),
|
|
9151
|
+
bubble.streaming && bubble.text === "" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
9152
|
+
/* @__PURE__ */ jsx12(Spinner2, { type: "dots" }),
|
|
9153
|
+
" thinking"
|
|
9154
|
+
] })
|
|
9155
|
+
] });
|
|
9156
|
+
}
|
|
9157
|
+
function clipTextToRows(text, maxRows, columns) {
|
|
9158
|
+
if (text === "" || maxRows <= 0) return "";
|
|
9159
|
+
const width = Number.isFinite(columns) ? Math.max(1, Math.floor(columns)) : 80;
|
|
9160
|
+
const rows = text.replace(/\r\n?/g, "\n").split("\n").flatMap((line) => wrapVisualLine(line, width));
|
|
9161
|
+
if (rows.length <= maxRows) return rows.join("\n");
|
|
9162
|
+
const tail2 = rows.slice(-Math.max(1, maxRows - 1));
|
|
9163
|
+
return maxRows === 1 ? tail2.at(-1) ?? "" : ["\u2026", ...tail2].join("\n");
|
|
9164
|
+
}
|
|
9165
|
+
function wrapVisualLine(line, columns) {
|
|
9166
|
+
if (line === "") return [""];
|
|
9167
|
+
const rows = [];
|
|
9168
|
+
let current = "";
|
|
9169
|
+
let currentWidth = 0;
|
|
9170
|
+
for (const character of line) {
|
|
9171
|
+
const width = Math.max(1, stringWidth2(character));
|
|
9172
|
+
if (current !== "" && currentWidth + width > columns) {
|
|
9173
|
+
rows.push(current);
|
|
9174
|
+
current = "";
|
|
9175
|
+
currentWidth = 0;
|
|
9176
|
+
}
|
|
9177
|
+
current += character;
|
|
9178
|
+
currentWidth += width;
|
|
9179
|
+
}
|
|
9180
|
+
if (current !== "" || rows.length === 0) rows.push(current);
|
|
9181
|
+
return rows;
|
|
9182
|
+
}
|
|
8667
9183
|
function ToolView({ bubble }) {
|
|
8668
9184
|
const theme = useTheme();
|
|
8669
9185
|
const mark = bubble.state === "running" ? "\u25CC" : bubble.state === "ok" ? "\u25CF" : "\u2717";
|
|
@@ -8971,7 +9487,7 @@ function App({
|
|
|
8971
9487
|
warnings = []
|
|
8972
9488
|
}) {
|
|
8973
9489
|
const { exit } = useApp();
|
|
8974
|
-
const { rows } =
|
|
9490
|
+
const { rows } = useWindowSize4();
|
|
8975
9491
|
const [transcript, setTranscript] = useState5(
|
|
8976
9492
|
() => warnings.reduce((state, text) => pushNotice(state, "warn", text), fromHistory(initialHistory))
|
|
8977
9493
|
);
|
|
@@ -9064,11 +9580,10 @@ function App({
|
|
|
9064
9580
|
);
|
|
9065
9581
|
useEffect2(() => {
|
|
9066
9582
|
const check = runtime.startupUpdateCheck();
|
|
9067
|
-
if (!check) return;
|
|
9068
9583
|
let active2 = true;
|
|
9069
9584
|
void check.then((result) => {
|
|
9070
9585
|
if (active2 && result.status === "available") {
|
|
9071
|
-
notice("info", strings.updateAvailable(result.latest
|
|
9586
|
+
notice("info", strings.updateAvailable(result.latest, result.url));
|
|
9072
9587
|
}
|
|
9073
9588
|
});
|
|
9074
9589
|
return () => {
|
|
@@ -9364,6 +9879,17 @@ function App({
|
|
|
9364
9879
|
case "config":
|
|
9365
9880
|
notice("info", strings.configAt(runtime.configPath()));
|
|
9366
9881
|
return;
|
|
9882
|
+
case "update": {
|
|
9883
|
+
const result = await runtime.checkForUpdates();
|
|
9884
|
+
if (result.status === "available") {
|
|
9885
|
+
notice("info", strings.updateAvailable(result.latest, result.url));
|
|
9886
|
+
} else if (result.status === "current") {
|
|
9887
|
+
notice("info", strings.updateCurrent(result.current));
|
|
9888
|
+
} else {
|
|
9889
|
+
notice("warn", strings.updateFailed(result.reason));
|
|
9890
|
+
}
|
|
9891
|
+
return;
|
|
9892
|
+
}
|
|
9367
9893
|
case "login":
|
|
9368
9894
|
setSetup(true);
|
|
9369
9895
|
return;
|
|
@@ -10091,6 +10617,10 @@ Rename the file if you want a different name.`);
|
|
|
10091
10617
|
return;
|
|
10092
10618
|
}
|
|
10093
10619
|
if (!key.escape) return;
|
|
10620
|
+
if (input !== "") {
|
|
10621
|
+
setInput("");
|
|
10622
|
+
return;
|
|
10623
|
+
}
|
|
10094
10624
|
if (busy && abort.current) {
|
|
10095
10625
|
abort.current.abort();
|
|
10096
10626
|
queueRef.current = [];
|
|
@@ -10151,7 +10681,8 @@ ${strings.configAt(runtime.configPath())}`);
|
|
|
10151
10681
|
Transcript,
|
|
10152
10682
|
{
|
|
10153
10683
|
bubbles: transcript.bubbles,
|
|
10154
|
-
workspace: runtime.cwd
|
|
10684
|
+
workspace: runtime.cwd,
|
|
10685
|
+
maxLiveRows: liveTranscriptRows(rows)
|
|
10155
10686
|
},
|
|
10156
10687
|
transcriptRevision
|
|
10157
10688
|
),
|
|
@@ -10349,6 +10880,12 @@ import { jsx as jsx14 } from "react/jsx-runtime";
|
|
|
10349
10880
|
function clearTerminal() {
|
|
10350
10881
|
if (process.stdout.isTTY) process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
10351
10882
|
}
|
|
10883
|
+
var TUI_RENDER_OPTIONS = {
|
|
10884
|
+
// Keep unchanged streamed lines in place instead of clearing and redrawing
|
|
10885
|
+
// the whole live region on every frame.
|
|
10886
|
+
incrementalRendering: true,
|
|
10887
|
+
maxFps: 30
|
|
10888
|
+
};
|
|
10352
10889
|
async function startTui(options) {
|
|
10353
10890
|
const { runtime, history, warnings, shutdown } = await boot({
|
|
10354
10891
|
cwd: options.cwd ?? process.cwd(),
|
|
@@ -10358,10 +10895,10 @@ async function startTui(options) {
|
|
|
10358
10895
|
mode: options.mode
|
|
10359
10896
|
});
|
|
10360
10897
|
clearTerminal();
|
|
10361
|
-
const instance = render(
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10898
|
+
const instance = render(
|
|
10899
|
+
/* @__PURE__ */ jsx14(App, { runtime, initialHistory: history, warnings }),
|
|
10900
|
+
TUI_RENDER_OPTIONS
|
|
10901
|
+
);
|
|
10365
10902
|
try {
|
|
10366
10903
|
await instance.waitUntilExit();
|
|
10367
10904
|
} finally {
|