skydive-cli 0.4.0-beta.1029 → 0.4.0-beta.1050
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/js/bin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as setActiveWorkspace,
|
|
2
|
+
import { C as ensureActiveOrganization, D as setActiveWorkspace, E as listWorkspaces, O as name, T as getSessionIdentity, a as installCrashHandler, k as version, t as maybeStartProfiling, u as brandHelpArt, w as getActiveWorkspaceId, x as themes } from "./profiler-Dsf_RNXl.mjs";
|
|
3
3
|
import { A as getStoredApiKeyWorkspaceName, C as getLastSeenVersion, F as resolveManagementAuth, I as resolveSession, M as resolveAppUrl, N as resolveChatAuth, O as getShareMachineDefault, P as resolveConfig, R as saveConfig, S as getConfigPath, T as getPromptHistoryPath, V as setLastSeenVersion, _ as API_KEY_PREFIX, b as PREFERENCES, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as getUpdateCheckDisabled, k as getStoredApiKeyId, v as DEFAULT_API_URL, w as getPreference, x as deleteConfig, z as saveSession } from "./print-CiaU2ygh.mjs";
|
|
4
4
|
import { n as printError, r as printTable, t as output } from "./output-DYzzdXYV.mjs";
|
|
5
5
|
import { n as createRestClient, t as HttpError } from "./rest-CG7VpQ56.mjs";
|
|
@@ -1505,7 +1505,7 @@ const importCommand = {
|
|
|
1505
1505
|
process.exit(1);
|
|
1506
1506
|
}
|
|
1507
1507
|
}
|
|
1508
|
-
const { runChat } = await import("./boot-
|
|
1508
|
+
const { runChat } = await import("./boot-D7c-DOE9.mjs");
|
|
1509
1509
|
await runChat({
|
|
1510
1510
|
appUrl,
|
|
1511
1511
|
sessionToken: session.value.sessionToken,
|
|
@@ -1917,7 +1917,7 @@ const chatCommand = {
|
|
|
1917
1917
|
sessionToken: auth.value.token,
|
|
1918
1918
|
agentSelector: argv.agent ?? null
|
|
1919
1919
|
});
|
|
1920
|
-
const { runChat } = await import("./boot-
|
|
1920
|
+
const { runChat } = await import("./boot-D7c-DOE9.mjs");
|
|
1921
1921
|
await runChat({
|
|
1922
1922
|
appUrl: auth.value.appUrl,
|
|
1923
1923
|
sessionToken: auth.value.token,
|
|
@@ -2276,7 +2276,7 @@ const switchCommand = {
|
|
|
2276
2276
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
2277
2277
|
process.exit(1);
|
|
2278
2278
|
}
|
|
2279
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2279
|
+
const { runWorkspacePicker } = await import("./boot-D7c-DOE9.mjs");
|
|
2280
2280
|
await runWorkspacePicker(session);
|
|
2281
2281
|
return;
|
|
2282
2282
|
}
|
|
@@ -3881,6 +3881,7 @@ function maybePrintWhatsNew({ stderrIsTTY = process.stderr.isTTY ?? false, nonIn
|
|
|
3881
3881
|
//#endregion
|
|
3882
3882
|
//#region src/bin.ts
|
|
3883
3883
|
installCrashHandler();
|
|
3884
|
+
maybeStartProfiling(hideBin(process.argv), version);
|
|
3884
3885
|
if (process.argv.includes(UPDATE_WORKER_FLAG)) {
|
|
3885
3886
|
await runUpdateCheckWorker();
|
|
3886
3887
|
process.exit(0);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { D as setActiveWorkspace, E as listWorkspaces, S as themesForMode, _ as themeForMode, a as installCrashHandler, b as themeVersion, c as MARK_CELLS, d as DEFAULT_THEME_ID, f as applyTheme, g as theme, h as noColorRequested, i as writeArtifact, l as WORDMARK, m as monoTheme, n as profilingEnabled, o as buildCrashReport, p as findTheme, r as record, s as writeCrashReport, v as themeMode, w as getActiveWorkspaceId, y as themeModeFromColorFgBg } from "./profiler-Dsf_RNXl.mjs";
|
|
3
3
|
import { B as saveTheme, D as getSavedTheme, E as getReviewStateDir, L as resolveWebUrl, S as getConfigPath, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams, v as DEFAULT_API_URL, y as DEFAULT_APP_URL } from "./print-CiaU2ygh.mjs";
|
|
4
4
|
import { a as errorMessage, i as sendErrorMessage, n as createRestClient, o as isRecord, r as errorDetail, t as HttpError } from "./rest-CG7VpQ56.mjs";
|
|
5
5
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-CwfG1BTR.mjs";
|
|
@@ -19,7 +19,7 @@ import { connect, createConnection } from "node:net";
|
|
|
19
19
|
import { access, appendFile, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
20
20
|
import { MarkdownRenderable, RenderableEvents, StyledText, SyntaxStyle, addDefaultParsers, createCliRenderer, decodePasteBytes, detectLinks, fg, link } from "@opentui/core";
|
|
21
21
|
import { createRoot, extend, useKeyboard, usePaste, useRenderer, useTerminalDimensions } from "@opentui/react";
|
|
22
|
-
import { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
22
|
+
import { Profiler, createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
23
23
|
import { create } from "zustand";
|
|
24
24
|
import { Fragment, jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
25
25
|
import fuzzysort from "fuzzysort";
|
|
@@ -1842,6 +1842,9 @@ function FilterableAgentList({ agents, scope, onPick }) {
|
|
|
1842
1842
|
fg: theme.muted,
|
|
1843
1843
|
children: "search "
|
|
1844
1844
|
}), /* @__PURE__ */ jsx("input", {
|
|
1845
|
+
textColor: theme.fg,
|
|
1846
|
+
focusedTextColor: theme.fg,
|
|
1847
|
+
placeholderColor: theme.dim,
|
|
1845
1848
|
placeholder: "type to filter…",
|
|
1846
1849
|
focused: true,
|
|
1847
1850
|
width: Math.max(10, width - 12),
|
|
@@ -2040,6 +2043,9 @@ function AgentCreateScreen() {
|
|
|
2040
2043
|
flexShrink: 0
|
|
2041
2044
|
},
|
|
2042
2045
|
children: /* @__PURE__ */ jsx("input", {
|
|
2046
|
+
textColor: theme.fg,
|
|
2047
|
+
focusedTextColor: theme.fg,
|
|
2048
|
+
placeholderColor: theme.dim,
|
|
2043
2049
|
placeholder: "Agent name",
|
|
2044
2050
|
onInput: (value) => {
|
|
2045
2051
|
setName(value);
|
|
@@ -2742,6 +2748,9 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
|
|
|
2742
2748
|
fg: theme.muted,
|
|
2743
2749
|
children: "search "
|
|
2744
2750
|
}), /* @__PURE__ */ jsx("input", {
|
|
2751
|
+
textColor: theme.fg,
|
|
2752
|
+
focusedTextColor: theme.fg,
|
|
2753
|
+
placeholderColor: theme.dim,
|
|
2745
2754
|
placeholder: "type to filter…",
|
|
2746
2755
|
focused: confirmId === null,
|
|
2747
2756
|
width: Math.max(10, width - 12),
|
|
@@ -6055,6 +6064,9 @@ function HelpModal({ onClose }) {
|
|
|
6055
6064
|
fg: theme.muted,
|
|
6056
6065
|
children: "search "
|
|
6057
6066
|
}), /* @__PURE__ */ jsx("input", {
|
|
6067
|
+
textColor: theme.fg,
|
|
6068
|
+
focusedTextColor: theme.fg,
|
|
6069
|
+
placeholderColor: theme.dim,
|
|
6058
6070
|
placeholder: "type to filter…",
|
|
6059
6071
|
focused: true,
|
|
6060
6072
|
width: Math.max(10, width - 12),
|
|
@@ -6618,6 +6630,9 @@ function FilterableModelList({ models, currentModel, modelLocked, agentId, onSel
|
|
|
6618
6630
|
fg: theme.muted,
|
|
6619
6631
|
children: "search "
|
|
6620
6632
|
}), /* @__PURE__ */ jsx("input", {
|
|
6633
|
+
textColor: theme.fg,
|
|
6634
|
+
focusedTextColor: theme.fg,
|
|
6635
|
+
placeholderColor: theme.dim,
|
|
6621
6636
|
placeholder: "type to filter…",
|
|
6622
6637
|
focused: true,
|
|
6623
6638
|
width: Math.max(10, width - 12),
|
|
@@ -7562,6 +7577,9 @@ function ReviewFileSearch({ query, onQueryChange, placeholder = "filter files…
|
|
|
7562
7577
|
fg: theme.accent,
|
|
7563
7578
|
children: "/ "
|
|
7564
7579
|
}), /* @__PURE__ */ jsx("input", {
|
|
7580
|
+
textColor: theme.fg,
|
|
7581
|
+
focusedTextColor: theme.fg,
|
|
7582
|
+
placeholderColor: theme.dim,
|
|
7565
7583
|
placeholder,
|
|
7566
7584
|
focused: true,
|
|
7567
7585
|
width: "100%",
|
|
@@ -8281,6 +8299,9 @@ function WorkspaceFilesTab({ agentId, viewportWidth, viewportHeight, active, she
|
|
|
8281
8299
|
height: 3
|
|
8282
8300
|
},
|
|
8283
8301
|
children: /* @__PURE__ */ jsx("input", {
|
|
8302
|
+
textColor: theme.fg,
|
|
8303
|
+
focusedTextColor: theme.fg,
|
|
8304
|
+
placeholderColor: theme.dim,
|
|
8284
8305
|
placeholder: "Leave a comment…",
|
|
8285
8306
|
focused: true,
|
|
8286
8307
|
width: Math.max(8, width - 10),
|
|
@@ -8808,6 +8829,9 @@ function ReviewPane({ agentId, conversationId, items, viewportWidth, viewportHei
|
|
|
8808
8829
|
height: 3
|
|
8809
8830
|
},
|
|
8810
8831
|
children: /* @__PURE__ */ jsx("input", {
|
|
8832
|
+
textColor: theme.fg,
|
|
8833
|
+
focusedTextColor: theme.fg,
|
|
8834
|
+
placeholderColor: theme.dim,
|
|
8811
8835
|
placeholder: "Leave a comment…",
|
|
8812
8836
|
focused: true,
|
|
8813
8837
|
width: Math.max(8, sourceWidth - 8),
|
|
@@ -11439,6 +11463,9 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11439
11463
|
},
|
|
11440
11464
|
children: /* @__PURE__ */ jsx("textarea", {
|
|
11441
11465
|
ref: composerRef,
|
|
11466
|
+
textColor: theme.fg,
|
|
11467
|
+
focusedTextColor: theme.fg,
|
|
11468
|
+
placeholderColor: theme.dim,
|
|
11442
11469
|
placeholder: forking ? "forking into a new chat…" : `message ${agent.name}…`,
|
|
11443
11470
|
keyBindings: composerKeyBindings,
|
|
11444
11471
|
onContentChange: handleComposerChange,
|
|
@@ -11571,12 +11598,18 @@ function CredentialPromptBox({ prompt, height, onInput, onSubmit }) {
|
|
|
11571
11598
|
]
|
|
11572
11599
|
}),
|
|
11573
11600
|
field.secret ? /* @__PURE__ */ jsx("input", {
|
|
11601
|
+
textColor: theme.fg,
|
|
11602
|
+
focusedTextColor: theme.fg,
|
|
11603
|
+
placeholderColor: theme.dim,
|
|
11574
11604
|
value: MASK_CHAR.repeat(prompt.current.length),
|
|
11575
11605
|
placeholder: field.placeholder ?? "",
|
|
11576
11606
|
onInput,
|
|
11577
11607
|
onSubmit,
|
|
11578
11608
|
focused: !prompt.submitting
|
|
11579
11609
|
}, field.key) : /* @__PURE__ */ jsx("input", {
|
|
11610
|
+
textColor: theme.fg,
|
|
11611
|
+
focusedTextColor: theme.fg,
|
|
11612
|
+
placeholderColor: theme.dim,
|
|
11580
11613
|
placeholder: field.placeholder ?? "",
|
|
11581
11614
|
onInput,
|
|
11582
11615
|
onSubmit,
|
|
@@ -12284,6 +12317,97 @@ async function settleBootThemeMode({ source, applyThemeMode, graceMs = THEME_QUE
|
|
|
12284
12317
|
if (answered) applyThemeMode(answered);
|
|
12285
12318
|
}
|
|
12286
12319
|
|
|
12320
|
+
//#endregion
|
|
12321
|
+
//#region src/profiling/tui.ts
|
|
12322
|
+
/**
|
|
12323
|
+
* TUI-side profiling instrumentation. Everything here rides on the shared
|
|
12324
|
+
* profile directory created in bin.ts (see profiler.ts) and is inert unless
|
|
12325
|
+
* SKYDIVE_PROFILE is set.
|
|
12326
|
+
*/
|
|
12327
|
+
/**
|
|
12328
|
+
* Renderer options to merge into createCliRenderer when profiling:
|
|
12329
|
+
* gatherStats makes OpenTUI retain per-frame timings (frame time, cells
|
|
12330
|
+
* updated, native render + stdout write time) that getStats() exposes.
|
|
12331
|
+
*/
|
|
12332
|
+
function profilingRendererOptions() {
|
|
12333
|
+
return profilingEnabled() ? { gatherStats: true } : {};
|
|
12334
|
+
}
|
|
12335
|
+
/**
|
|
12336
|
+
* Samples renderer stats periodically and dumps the full series at exit.
|
|
12337
|
+
* The periodic samples give the timeline shape (fps over the session); the
|
|
12338
|
+
* final dump carries the complete frame-time distribution.
|
|
12339
|
+
*/
|
|
12340
|
+
function startRendererStatsCapture(renderer) {
|
|
12341
|
+
if (!profilingEnabled()) return;
|
|
12342
|
+
const sample = () => {
|
|
12343
|
+
const s = renderer.getStats();
|
|
12344
|
+
record("render", {
|
|
12345
|
+
fps: s.fps,
|
|
12346
|
+
frameCount: s.frameCount,
|
|
12347
|
+
averageFrameTime: s.averageFrameTime,
|
|
12348
|
+
maxFrameTime: s.maxFrameTime,
|
|
12349
|
+
cellsUpdated: s.cellsUpdated,
|
|
12350
|
+
nativeRenderTime: s.nativeRenderTime ?? null,
|
|
12351
|
+
nativeStdoutWriteTime: s.nativeStdoutWriteTime ?? null
|
|
12352
|
+
});
|
|
12353
|
+
};
|
|
12354
|
+
const interval = setInterval(sample, 1e3);
|
|
12355
|
+
interval.unref?.();
|
|
12356
|
+
process.on("exit", () => {
|
|
12357
|
+
clearInterval(interval);
|
|
12358
|
+
const s = renderer.getStats();
|
|
12359
|
+
writeArtifact("render.json", {
|
|
12360
|
+
fps: s.fps,
|
|
12361
|
+
frameCount: s.frameCount,
|
|
12362
|
+
frameTimes: s.frameTimes,
|
|
12363
|
+
averageFrameTime: s.averageFrameTime,
|
|
12364
|
+
minFrameTime: s.minFrameTime,
|
|
12365
|
+
maxFrameTime: s.maxFrameTime,
|
|
12366
|
+
cellsUpdated: s.cellsUpdated,
|
|
12367
|
+
averageCellsUpdated: s.averageCellsUpdated
|
|
12368
|
+
});
|
|
12369
|
+
});
|
|
12370
|
+
}
|
|
12371
|
+
/**
|
|
12372
|
+
* Records zustand store transitions. Subscribes to the whole store and
|
|
12373
|
+
* logs a compact diff-flavored event per change: the screen kind always,
|
|
12374
|
+
* plus which top-level keys changed. Gives an agent the "what was the UI
|
|
12375
|
+
* doing at t=X" axis without serializing message payloads.
|
|
12376
|
+
*/
|
|
12377
|
+
function startStateCapture(store) {
|
|
12378
|
+
if (!profilingEnabled()) return;
|
|
12379
|
+
store.subscribe((state, prev) => {
|
|
12380
|
+
const prevEntries = new Map(Object.entries(prev));
|
|
12381
|
+
const changed = [];
|
|
12382
|
+
let screenKind = null;
|
|
12383
|
+
for (const [key, value] of Object.entries(state)) {
|
|
12384
|
+
if (typeof value === "function") continue;
|
|
12385
|
+
if (value !== prevEntries.get(key)) changed.push(key);
|
|
12386
|
+
if (key === "screen" && typeof value === "object" && value !== null && "kind" in value && typeof value.kind === "string") screenKind = value.kind;
|
|
12387
|
+
}
|
|
12388
|
+
if (changed.length === 0) return;
|
|
12389
|
+
record("state", {
|
|
12390
|
+
screen: screenKind,
|
|
12391
|
+
changed
|
|
12392
|
+
});
|
|
12393
|
+
});
|
|
12394
|
+
}
|
|
12395
|
+
/**
|
|
12396
|
+
* React Profiler onRender callback for the <Profiler> wrapping <App>.
|
|
12397
|
+
* One event per commit: which subtree, mount/update, and actual vs base
|
|
12398
|
+
* duration (actualDuration >> baseDuration means memoization is losing).
|
|
12399
|
+
*/
|
|
12400
|
+
function onRenderCommit(id, phase, actualDuration, baseDuration, startTime, commitTime) {
|
|
12401
|
+
record("commits", {
|
|
12402
|
+
id,
|
|
12403
|
+
phase,
|
|
12404
|
+
actualDuration,
|
|
12405
|
+
baseDuration,
|
|
12406
|
+
startTime,
|
|
12407
|
+
commitTime
|
|
12408
|
+
});
|
|
12409
|
+
}
|
|
12410
|
+
|
|
12287
12411
|
//#endregion
|
|
12288
12412
|
//#region src/chat/boot.tsx
|
|
12289
12413
|
/**
|
|
@@ -12296,7 +12420,12 @@ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachin
|
|
|
12296
12420
|
await registerGrammars();
|
|
12297
12421
|
const watchdog = startMemoryWatchdog();
|
|
12298
12422
|
installCrashHandler({ memoryProvider: () => watchdog.snapshot() });
|
|
12299
|
-
const renderer = await createCliRenderer({
|
|
12423
|
+
const renderer = await createCliRenderer({
|
|
12424
|
+
exitOnCtrlC: false,
|
|
12425
|
+
...profilingRendererOptions()
|
|
12426
|
+
});
|
|
12427
|
+
startRendererStatsCapture(renderer);
|
|
12428
|
+
startStateCapture(useStore);
|
|
12300
12429
|
extend({ markdown: LinkifiedMarkdownRenderable });
|
|
12301
12430
|
useStore.setState({
|
|
12302
12431
|
authKind,
|
|
@@ -12316,16 +12445,20 @@ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachin
|
|
|
12316
12445
|
source: renderer,
|
|
12317
12446
|
applyThemeMode: (mode) => useStore.getState().setThemeMode(mode)
|
|
12318
12447
|
});
|
|
12319
|
-
createRoot(renderer).render(/* @__PURE__ */ jsx(
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12448
|
+
createRoot(renderer).render(/* @__PURE__ */ jsx(Profiler, {
|
|
12449
|
+
id: "app",
|
|
12450
|
+
onRender: onRenderCommit,
|
|
12451
|
+
children: /* @__PURE__ */ jsx(App, {
|
|
12452
|
+
appUrl,
|
|
12453
|
+
sessionToken,
|
|
12454
|
+
authKind,
|
|
12455
|
+
shareMachine,
|
|
12456
|
+
promptHistoryPath,
|
|
12457
|
+
notifications,
|
|
12458
|
+
agentSelector: agentSelector ?? null,
|
|
12459
|
+
conversationId: conversationId ?? null,
|
|
12460
|
+
newConversation
|
|
12461
|
+
})
|
|
12329
12462
|
}));
|
|
12330
12463
|
}
|
|
12331
12464
|
/** Open the same workspace picker used by the initial agent screen. */
|
|
@@ -8,7 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
|
|
9
9
|
//#region package.json
|
|
10
10
|
var name = "skydive-cli";
|
|
11
|
-
var version$1 = "0.4.0-beta.
|
|
11
|
+
var version$1 = "0.4.0-beta.1050";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/auth/organization.ts
|
|
@@ -1057,4 +1057,211 @@ function printFatalNotice(file, memory) {
|
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
1059
|
//#endregion
|
|
1060
|
-
|
|
1060
|
+
//#region src/profiling/profiler.ts
|
|
1061
|
+
/**
|
|
1062
|
+
* Opt-in session profiling. When SKYDIVE_PROFILE is set, an invocation
|
|
1063
|
+
* records everything needed to reconstruct what happened performance-wise —
|
|
1064
|
+
* designed so the whole directory can be handed to an agent (or a human)
|
|
1065
|
+
* and read without special tooling:
|
|
1066
|
+
*
|
|
1067
|
+
* network.ndjson every fetch: method, url, status, timing, content type
|
|
1068
|
+
* state.ndjson TUI state transitions (screen changes, store updates)
|
|
1069
|
+
* commits.ndjson React commits: which subtree rendered, when, how long
|
|
1070
|
+
* render.ndjson OpenTUI renderer samples: fps, frame times, cells drawn
|
|
1071
|
+
* render.json final renderer stats dump (full frame-time series)
|
|
1072
|
+
* cpu-<pid>.cpuprofile V8 CPU profile (Node-run commands only)
|
|
1073
|
+
* meta-<pid>.json argv, versions, runtime, exit code, wall time
|
|
1074
|
+
*
|
|
1075
|
+
* Every ndjson event carries a wall-clock `t` (epoch ms) and `pid`, so
|
|
1076
|
+
* records from the Node parent and the Bun-re-exec'd chat TUI land in the
|
|
1077
|
+
* same files and stay correlatable on one clock. The profile directory is
|
|
1078
|
+
* created by the first process and shared with children through
|
|
1079
|
+
* SKYDIVE_PROFILE_DIR (the chat re-exec inherits the environment).
|
|
1080
|
+
*
|
|
1081
|
+
* Inert unless SKYDIVE_PROFILE is set: no patched fetch, no subscriptions,
|
|
1082
|
+
* no inspector session.
|
|
1083
|
+
*/
|
|
1084
|
+
const ENV_FLAG = "SKYDIVE_PROFILE";
|
|
1085
|
+
const ENV_DIR = "SKYDIVE_PROFILE_DIR";
|
|
1086
|
+
let activeDir = null;
|
|
1087
|
+
let startedAtMs = 0;
|
|
1088
|
+
/**
|
|
1089
|
+
* Buffered event lines per stream, flushed asynchronously. Events are
|
|
1090
|
+
* appended to an in-memory buffer and written with fs.promises off the
|
|
1091
|
+
* hot path, so recording never blocks the TUI's event loop — high-rate
|
|
1092
|
+
* streams (React commits, renderer samples) stay cheap. Whatever is
|
|
1093
|
+
* still buffered when the process exits is drained synchronously in the
|
|
1094
|
+
* exit handler, where async I/O would never flush.
|
|
1095
|
+
*/
|
|
1096
|
+
const pendingLines = /* @__PURE__ */ new Map();
|
|
1097
|
+
let flushScheduled = false;
|
|
1098
|
+
let flushing = Promise.resolve();
|
|
1099
|
+
function drainBuffersSync() {
|
|
1100
|
+
if (activeDir === null) return;
|
|
1101
|
+
for (const [stream, lines] of pendingLines) {
|
|
1102
|
+
if (lines.length === 0) continue;
|
|
1103
|
+
pendingLines.set(stream, []);
|
|
1104
|
+
try {
|
|
1105
|
+
fs.appendFileSync(path.join(activeDir, `${stream}.ndjson`), lines.join("\n") + "\n");
|
|
1106
|
+
} catch (_error) {}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
function scheduleFlush() {
|
|
1110
|
+
if (flushScheduled || activeDir === null) return;
|
|
1111
|
+
flushScheduled = true;
|
|
1112
|
+
setTimeout(() => {
|
|
1113
|
+
flushScheduled = false;
|
|
1114
|
+
flushing = flushing.then(async () => {
|
|
1115
|
+
if (activeDir === null) return;
|
|
1116
|
+
for (const [stream, lines] of pendingLines) {
|
|
1117
|
+
if (lines.length === 0) continue;
|
|
1118
|
+
pendingLines.set(stream, []);
|
|
1119
|
+
try {
|
|
1120
|
+
await fs.promises.appendFile(path.join(activeDir, `${stream}.ndjson`), lines.join("\n") + "\n");
|
|
1121
|
+
} catch (_error) {}
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
}, 100).unref?.();
|
|
1125
|
+
}
|
|
1126
|
+
function profilingEnabled() {
|
|
1127
|
+
const v = process.env[ENV_FLAG];
|
|
1128
|
+
return v !== void 0 && v !== "" && v !== "0";
|
|
1129
|
+
}
|
|
1130
|
+
function sanitizeToken(token) {
|
|
1131
|
+
return token.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40);
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Appends one event to a stream file. Buffered and written asynchronously
|
|
1135
|
+
* so recording never blocks the event loop; the exit handler drains any
|
|
1136
|
+
* remainder synchronously so abrupt exits still keep their events.
|
|
1137
|
+
*/
|
|
1138
|
+
function record(stream, event) {
|
|
1139
|
+
if (activeDir === null) return;
|
|
1140
|
+
const line = JSON.stringify({
|
|
1141
|
+
t: Date.now(),
|
|
1142
|
+
pid: process.pid,
|
|
1143
|
+
...event
|
|
1144
|
+
});
|
|
1145
|
+
const lines = pendingLines.get(stream);
|
|
1146
|
+
if (lines === void 0) pendingLines.set(stream, [line]);
|
|
1147
|
+
else lines.push(line);
|
|
1148
|
+
scheduleFlush();
|
|
1149
|
+
}
|
|
1150
|
+
/** Writes a JSON artifact (non-append) into the profile directory. */
|
|
1151
|
+
function writeArtifact(name, data) {
|
|
1152
|
+
if (activeDir === null) return;
|
|
1153
|
+
try {
|
|
1154
|
+
fs.writeFileSync(path.join(activeDir, name), JSON.stringify(data, null, 2));
|
|
1155
|
+
} catch (_error) {}
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Patches globalThis.fetch to record request timing. A wrapper (not
|
|
1159
|
+
* undici's diagnostics_channel) because chat re-execs under Bun, where
|
|
1160
|
+
* fetch is Bun-native and undici events never fire; the wrapper behaves
|
|
1161
|
+
* identically in both runtimes.
|
|
1162
|
+
*/
|
|
1163
|
+
function installFetchRecorder() {
|
|
1164
|
+
const original = globalThis.fetch;
|
|
1165
|
+
const wrapped = async (...args) => {
|
|
1166
|
+
const [input, init] = args;
|
|
1167
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1168
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
1169
|
+
const started = Date.now();
|
|
1170
|
+
try {
|
|
1171
|
+
const res = await original(...args);
|
|
1172
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
1173
|
+
record("network", {
|
|
1174
|
+
method,
|
|
1175
|
+
url,
|
|
1176
|
+
status: res.status,
|
|
1177
|
+
durationMs: Date.now() - started,
|
|
1178
|
+
contentType,
|
|
1179
|
+
streamed: contentType.includes("text/event-stream")
|
|
1180
|
+
});
|
|
1181
|
+
return res;
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
record("network", {
|
|
1184
|
+
method,
|
|
1185
|
+
url,
|
|
1186
|
+
status: 0,
|
|
1187
|
+
durationMs: Date.now() - started,
|
|
1188
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1189
|
+
});
|
|
1190
|
+
throw err;
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
globalThis.fetch = Object.assign(wrapped, original);
|
|
1194
|
+
}
|
|
1195
|
+
function isBunRuntime() {
|
|
1196
|
+
return typeof process !== "undefined" && "bun" in process.versions;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Starts the V8 CPU profiler via node:inspector. Node-run commands only —
|
|
1200
|
+
* Bun does not implement the inspector Profiler domain, so the chat TUI
|
|
1201
|
+
* skips this artifact.
|
|
1202
|
+
*/
|
|
1203
|
+
function startCpuProfile() {
|
|
1204
|
+
if (isBunRuntime()) return;
|
|
1205
|
+
(async () => {
|
|
1206
|
+
try {
|
|
1207
|
+
const { Session } = await import("node:inspector/promises");
|
|
1208
|
+
const session = new Session();
|
|
1209
|
+
session.connect();
|
|
1210
|
+
await session.post("Profiler.enable");
|
|
1211
|
+
await session.post("Profiler.start");
|
|
1212
|
+
process.once("beforeExit", () => {
|
|
1213
|
+
(async () => {
|
|
1214
|
+
try {
|
|
1215
|
+
const { profile } = await session.post("Profiler.stop");
|
|
1216
|
+
writeArtifact(`cpu-${process.pid}.cpuprofile`, profile);
|
|
1217
|
+
session.disconnect();
|
|
1218
|
+
} catch (_error) {}
|
|
1219
|
+
})();
|
|
1220
|
+
});
|
|
1221
|
+
} catch (_error) {}
|
|
1222
|
+
})();
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Activates profiling for this process if SKYDIVE_PROFILE is set. Creates
|
|
1226
|
+
* the profile directory (or joins the one a parent process created),
|
|
1227
|
+
* patches fetch, starts the CPU profiler, and registers the exit-time
|
|
1228
|
+
* meta dump. Call once, as early as possible.
|
|
1229
|
+
*/
|
|
1230
|
+
function maybeStartProfiling(argv, cliVersion) {
|
|
1231
|
+
if (!profilingEnabled() || activeDir !== null) return;
|
|
1232
|
+
const inherited = process.env[ENV_DIR];
|
|
1233
|
+
if (inherited !== void 0 && inherited !== "") activeDir = inherited;
|
|
1234
|
+
else {
|
|
1235
|
+
const flagValue = process.env[ENV_FLAG] ?? "1";
|
|
1236
|
+
const command = argv.find((a) => !a.startsWith("-")) ?? "chat";
|
|
1237
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
1238
|
+
activeDir = flagValue === "1" || flagValue.toLowerCase() === "true" ? path.resolve(process.cwd(), `skydive-profile-${sanitizeToken(command)}-${stamp}`) : path.resolve(process.cwd(), flagValue);
|
|
1239
|
+
process.env[ENV_DIR] = activeDir;
|
|
1240
|
+
}
|
|
1241
|
+
try {
|
|
1242
|
+
fs.mkdirSync(activeDir, { recursive: true });
|
|
1243
|
+
} catch (_error) {
|
|
1244
|
+
activeDir = null;
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
startedAtMs = Date.now();
|
|
1248
|
+
installFetchRecorder();
|
|
1249
|
+
startCpuProfile();
|
|
1250
|
+
process.on("exit", (code) => {
|
|
1251
|
+
drainBuffersSync();
|
|
1252
|
+
writeArtifact(`meta-${process.pid}.json`, {
|
|
1253
|
+
argv,
|
|
1254
|
+
cliVersion,
|
|
1255
|
+
runtime: isBunRuntime() ? `bun ${process.versions["bun"]}` : `node ${process.version}`,
|
|
1256
|
+
platform: process.platform,
|
|
1257
|
+
pid: process.pid,
|
|
1258
|
+
exitCode: code,
|
|
1259
|
+
startedAt: new Date(startedAtMs).toISOString(),
|
|
1260
|
+
wallTimeMs: Date.now() - startedAtMs
|
|
1261
|
+
});
|
|
1262
|
+
if (process.env[ENV_DIR] === activeDir && inherited === void 0) process.stderr.write(`\nprofile written to ${activeDir}\n`);
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
//#endregion
|
|
1267
|
+
export { ensureActiveOrganization as C, setActiveWorkspace as D, listWorkspaces as E, name as O, themesForMode as S, getSessionIdentity as T, themeForMode as _, installCrashHandler as a, themeVersion as b, MARK_CELLS as c, DEFAULT_THEME_ID as d, applyTheme as f, theme as g, noColorRequested as h, writeArtifact as i, version$1 as k, WORDMARK as l, monoTheme as m, profilingEnabled as n, buildCrashReport as o, findTheme as p, record as r, writeCrashReport as s, maybeStartProfiling as t, brandHelpArt as u, themeMode as v, getActiveWorkspaceId as w, themes as x, themeModeFromColorFgBg as y };
|