skydive-cli 0.5.0-beta.32 → 0.5.0-beta.33
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 +20 -0
- package/dist/js/bin.mjs +188 -29
- package/dist/js/{boot-yyXrIeVy.mjs → boot-C5HJfwRS.mjs} +5 -6
- package/dist/js/{daemon-CxFxH36O.mjs → daemon-BPGZufS1.mjs} +1 -1
- package/dist/js/{daemon-Btd2436i.mjs → daemon-WQ8QYrFt.mjs} +1 -1
- package/dist/js/daemon-client-DTfIza0O.mjs +8 -0
- package/dist/js/{daemon-client-Br1NlkRp.mjs → daemon-client-MpAGVkqP.mjs} +1 -1
- package/dist/js/{profiler-CJMnC90G.mjs → install-BPUAm8bS.mjs} +273 -209
- package/package.json +1 -1
- package/dist/js/daemon-client-1Qfc_Y3J.mjs +0 -8
package/README.md
CHANGED
|
@@ -483,6 +483,25 @@ The remaining keys (`apiKey`, `apiUrl`, `sessionToken`, `appUrl`, `themeDark`,
|
|
|
483
483
|
`themeLight`, …) are credentials or flow-managed state — leave them to the
|
|
484
484
|
CLI.
|
|
485
485
|
|
|
486
|
+
## Diagnostic logs
|
|
487
|
+
|
|
488
|
+
The CLI records local diagnostics by default. It keeps the latest 20 sessions
|
|
489
|
+
beside the CLI config. Nothing uploads automatically.
|
|
490
|
+
|
|
491
|
+
```sh
|
|
492
|
+
skydive logs # list recent sessions
|
|
493
|
+
skydive logs path # print the latest session directory
|
|
494
|
+
skydive logs export # archive the latest session for sharing
|
|
495
|
+
skydive logs export <session> # archive one session from `logs list`
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Automatic logs include request timing, TUI state changes, render performance,
|
|
499
|
+
and process metadata. Command values and URL query values are removed.
|
|
500
|
+
|
|
501
|
+
Set `SKYDIVE_PROFILE=<path>` to write a full profile to a chosen directory.
|
|
502
|
+
Full profiles also include CPU data. Set `SKYDIVE_PROFILE=0` to disable all
|
|
503
|
+
session diagnostics.
|
|
504
|
+
|
|
486
505
|
## Environment variables
|
|
487
506
|
|
|
488
507
|
| Variable | Effect |
|
|
@@ -493,6 +512,7 @@ CLI.
|
|
|
493
512
|
| `SKYDIVE_APP_URL` | Chat app/session origin (overrides stored value) |
|
|
494
513
|
| `SKYDIVE_THEME` | Chat colorscheme id (flag `--theme` overrides) |
|
|
495
514
|
| `SKYDIVE_CONFIG_NAME` | On-disk config profile name (default `skydive`) |
|
|
515
|
+
| `SKYDIVE_PROFILE` | Full profile path; `0` disables diagnostics |
|
|
496
516
|
|
|
497
517
|
## Testing the TUI
|
|
498
518
|
|
package/dist/js/bin.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as
|
|
3
|
-
import { A as getShareMachineDefault, C as getConfigPath, D as getPromptHistoryPath, E as getPreference, F as resolveAppUrl, H as saveSession, I as resolveChatAuth, L as resolveConfig, M as getStoredApiKeyWorkspaceName, N as getUpdateCheckDisabled, R as resolveManagementAuth, S as deleteConfig, T as getLastSeenVersion, V as saveConfig, W as setLastSeenVersion, _ as API_KEY_FAMILY_PREFIX, g as API_KEYS_URL, i as resolveAgent$1, j as getStoredApiKeyId, v as API_KEY_PREFIX, w as getDefaultAgent, x as PREFERENCES, y as DEFAULT_API_URL, z as resolveSession } from "./print-Clgq46GU.mjs";
|
|
2
|
+
import { A as installAgentAlias, B as resolveWorkspaceId, D as machineOsFromPlatform, E as buildImportSeedPrompt, F as detectShell, H as name, I as ensureActiveOrganization, L as getActiveWorkspaceId, M as takenAliasNames, N as SUPPORTED_SHELLS, O as aliasActivationHint, P as defaultInstallEnv, R as getSessionIdentity, U as version, V as setActiveWorkspace, c as getAutomaticLogsDir, j as slugifyAliasName, k as dedupeAliasName, l as maybeStartProfiling, o as brandHelpArt, t as installCrashHandler, u as profileDir, w as themes, z as listWorkspaces } from "./install-BPUAm8bS.mjs";
|
|
3
|
+
import { A as getShareMachineDefault, C as getConfigPath, D as getPromptHistoryPath, E as getPreference, F as resolveAppUrl, H as saveSession, I as resolveChatAuth, L as resolveConfig, M as getStoredApiKeyWorkspaceName, N as getUpdateCheckDisabled, R as resolveManagementAuth, S as deleteConfig, T as getLastSeenVersion, V as saveConfig, W as setLastSeenVersion, _ as API_KEY_FAMILY_PREFIX, g as API_KEYS_URL, i as resolveAgent$1, j as getStoredApiKeyId, v as API_KEY_PREFIX, w as getDefaultAgent, x as PREFERENCES, y as DEFAULT_API_URL, z as resolveSession$1 } from "./print-Clgq46GU.mjs";
|
|
4
4
|
import { n as printError, r as printTable, t as output } from "./output-wY0VQDea.mjs";
|
|
5
5
|
import { t as HttpError } from "./http-error-UHH3mVBF.mjs";
|
|
6
6
|
import { t as createRestClient } from "./rest-C6E4mS2A.mjs";
|
|
7
7
|
import "./billing-blocked-D3l5kJlX.mjs";
|
|
8
8
|
import { r as resolveMachineIdentity } from "./client-d6K9qm6B.mjs";
|
|
9
|
-
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-
|
|
9
|
+
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-BPGZufS1.mjs";
|
|
10
10
|
import "./tls-cert-CV-pwxVN.mjs";
|
|
11
11
|
import { i as grantPortalAccess, n as fetchPortalDevices, o as registerPortalDevice, r as findThisDevice, s as revokePortalAccess } from "./api-DQCaztBg.mjs";
|
|
12
12
|
import { t as SandboxStream } from "./client-BTQ1fwzM.mjs";
|
|
@@ -25,6 +25,7 @@ import fsp, { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
25
25
|
import { quote } from "shell-quote";
|
|
26
26
|
import semver from "semver";
|
|
27
27
|
import { createInterface } from "node:readline";
|
|
28
|
+
import { createInterface as createInterface$1 } from "node:readline/promises";
|
|
28
29
|
|
|
29
30
|
//#region src/types.ts
|
|
30
31
|
const NON_INTERACTIVE_ENV_VARS = [
|
|
@@ -620,7 +621,7 @@ const logoutCommand = {
|
|
|
620
621
|
const keyId = getStoredApiKeyId();
|
|
621
622
|
let keyRevoked = false;
|
|
622
623
|
if (keyId) {
|
|
623
|
-
const session = resolveSession({ appUrl: argv["api-url"] });
|
|
624
|
+
const session = resolveSession$1({ appUrl: argv["api-url"] });
|
|
624
625
|
const config = resolveConfig({ apiUrl: argv["api-url"] });
|
|
625
626
|
const credential = session.isOk() ? {
|
|
626
627
|
appUrl: session.value.appUrl,
|
|
@@ -651,7 +652,7 @@ const statusCommand$1 = {
|
|
|
651
652
|
describe: "Show current authentication state",
|
|
652
653
|
handler: async (argv) => {
|
|
653
654
|
const apiKey = resolveConfig({ apiUrl: argv["api-url"] });
|
|
654
|
-
const session = resolveSession({});
|
|
655
|
+
const session = resolveSession$1({});
|
|
655
656
|
const management = resolveManagementAuth({ apiUrl: argv["api-url"] });
|
|
656
657
|
const identity = session.isOk() ? (await getSessionIdentity({
|
|
657
658
|
appUrl: session.value.appUrl,
|
|
@@ -1001,7 +1002,7 @@ const completionCommand = {
|
|
|
1001
1002
|
* resolve-or-exit block isn't copy-pasted per command.
|
|
1002
1003
|
*/
|
|
1003
1004
|
function requireSession(argv) {
|
|
1004
|
-
const session = resolveSession({ appUrl: resolveAppUrl({ appUrl: argv["api-url"] }) });
|
|
1005
|
+
const session = resolveSession$1({ appUrl: resolveAppUrl({ appUrl: argv["api-url"] }) });
|
|
1005
1006
|
if (session.isErr()) {
|
|
1006
1007
|
printError(`${session.error.message} Run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
1007
1008
|
process.exit(1);
|
|
@@ -1066,7 +1067,7 @@ async function resolveAgent(client, selector) {
|
|
|
1066
1067
|
if (matches.length === 0) throw new Error(`No agent named "${selector}". Pass an id, or run \`skydive agents list\`.`);
|
|
1067
1068
|
throw new Error(`Multiple agents named "${selector}". Pass the id instead:\n${matches.map((a) => ` ${a.name} ${a.id}`).join("\n")}`);
|
|
1068
1069
|
}
|
|
1069
|
-
const listCommand$
|
|
1070
|
+
const listCommand$6 = {
|
|
1070
1071
|
command: "list",
|
|
1071
1072
|
describe: "List agents in the active workspace",
|
|
1072
1073
|
builder: (y) => y.option("limit", {
|
|
@@ -1290,7 +1291,7 @@ const aliasCommand = {
|
|
|
1290
1291
|
const agentsCommand = {
|
|
1291
1292
|
command: "agents",
|
|
1292
1293
|
describe: "Manage agents",
|
|
1293
|
-
builder: (y) => y.command(listCommand$
|
|
1294
|
+
builder: (y) => y.command(listCommand$6).command(getCommand$2).command(createCommand$1).command(aliasCommand).demandCommand(1, "Specify a subcommand: list, get, create, alias"),
|
|
1294
1295
|
handler: () => {}
|
|
1295
1296
|
};
|
|
1296
1297
|
|
|
@@ -1572,20 +1573,20 @@ const importCommand = {
|
|
|
1572
1573
|
printError(`import needs the Bun runtime and it couldn't be set up automatically. Install Bun (https://bun.sh) and run \`bun ${process.argv[1] ?? "skydive"} import\`, point SKYDIVE_BUN_PATH at an existing bun binary, or use the non-interactive \`import -p\` (runs under Node).`);
|
|
1573
1574
|
process.exit(1);
|
|
1574
1575
|
}
|
|
1575
|
-
let session = resolveSession({ appUrl });
|
|
1576
|
+
let session = resolveSession$1({ appUrl });
|
|
1576
1577
|
if (session.isErr()) {
|
|
1577
1578
|
const login = await loginWithDevice({ appUrl });
|
|
1578
1579
|
if (login.isErr()) {
|
|
1579
1580
|
printError(login.error.message);
|
|
1580
1581
|
process.exit(1);
|
|
1581
1582
|
}
|
|
1582
|
-
session = resolveSession({ appUrl });
|
|
1583
|
+
session = resolveSession$1({ appUrl });
|
|
1583
1584
|
if (session.isErr()) {
|
|
1584
1585
|
printError("Signed in, but no session was stored.");
|
|
1585
1586
|
process.exit(1);
|
|
1586
1587
|
}
|
|
1587
1588
|
}
|
|
1588
|
-
const { runChat } = await import("./boot-
|
|
1589
|
+
const { runChat } = await import("./boot-C5HJfwRS.mjs");
|
|
1589
1590
|
await runChat({
|
|
1590
1591
|
appUrl,
|
|
1591
1592
|
sessionToken: session.value.sessionToken,
|
|
@@ -1610,7 +1611,7 @@ const importCommand = {
|
|
|
1610
1611
|
* (`chat -p "go ahead" --resume <id> --share-machine`) to apply it.
|
|
1611
1612
|
*/
|
|
1612
1613
|
async function runImportPrintMode({ argv, appUrl }) {
|
|
1613
|
-
const session = resolveSession({ appUrl });
|
|
1614
|
+
const session = resolveSession$1({ appUrl });
|
|
1614
1615
|
if (session.isErr()) {
|
|
1615
1616
|
printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
1616
1617
|
process.exit(1);
|
|
@@ -1647,7 +1648,7 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1647
1648
|
|
|
1648
1649
|
//#endregion
|
|
1649
1650
|
//#region src/commands/keys.ts
|
|
1650
|
-
const listCommand$
|
|
1651
|
+
const listCommand$5 = {
|
|
1651
1652
|
command: "list",
|
|
1652
1653
|
describe: "List API keys for an agent",
|
|
1653
1654
|
handler: async (argv) => {
|
|
@@ -1736,7 +1737,7 @@ const keysCommand = {
|
|
|
1736
1737
|
type: "string",
|
|
1737
1738
|
demandOption: true,
|
|
1738
1739
|
describe: "Agent ID"
|
|
1739
|
-
}).command(listCommand$
|
|
1740
|
+
}).command(listCommand$5).command(createCommand).command(revokeCommand$1).demandCommand(1, "Specify a subcommand: list, create, revoke"),
|
|
1740
1741
|
handler: () => {}
|
|
1741
1742
|
};
|
|
1742
1743
|
|
|
@@ -1748,7 +1749,7 @@ async function readStdin() {
|
|
|
1748
1749
|
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1749
1750
|
return Buffer.concat(chunks).toString("utf8").replace(/\n$/, "");
|
|
1750
1751
|
}
|
|
1751
|
-
const listCommand$
|
|
1752
|
+
const listCommand$4 = {
|
|
1752
1753
|
command: "list",
|
|
1753
1754
|
describe: "List secret names for an agent (values are never shown)",
|
|
1754
1755
|
handler: async (argv) => {
|
|
@@ -1834,7 +1835,7 @@ const secretsCommand = {
|
|
|
1834
1835
|
type: "string",
|
|
1835
1836
|
demandOption: true,
|
|
1836
1837
|
describe: "Agent ID"
|
|
1837
|
-
}).command(listCommand$
|
|
1838
|
+
}).command(listCommand$4).command(setCommand$1).command(rmCommand).demandCommand(1, "Specify a subcommand: list, set, rm"),
|
|
1838
1839
|
handler: () => {}
|
|
1839
1840
|
};
|
|
1840
1841
|
|
|
@@ -2030,7 +2031,7 @@ const chatCommand = {
|
|
|
2030
2031
|
sessionToken: auth.value.token,
|
|
2031
2032
|
agentSelector: argv.agent ?? null
|
|
2032
2033
|
});
|
|
2033
|
-
const { runChat } = await import("./boot-
|
|
2034
|
+
const { runChat } = await import("./boot-C5HJfwRS.mjs");
|
|
2034
2035
|
await runChat({
|
|
2035
2036
|
appUrl: auth.value.appUrl,
|
|
2036
2037
|
sessionToken: auth.value.token,
|
|
@@ -2256,7 +2257,7 @@ function messageToText(message) {
|
|
|
2256
2257
|
}
|
|
2257
2258
|
return chunks.join("\n").trim();
|
|
2258
2259
|
}
|
|
2259
|
-
const listCommand$
|
|
2260
|
+
const listCommand$3 = {
|
|
2260
2261
|
command: "list",
|
|
2261
2262
|
describe: "List an agent's conversations",
|
|
2262
2263
|
builder: (y) => y.option("agent", {
|
|
@@ -2346,7 +2347,7 @@ const conversationsCommand = {
|
|
|
2346
2347
|
command: "conversations",
|
|
2347
2348
|
aliases: ["conv"],
|
|
2348
2349
|
describe: "List and read agent conversations",
|
|
2349
|
-
builder: (y) => y.command(listCommand$
|
|
2350
|
+
builder: (y) => y.command(listCommand$3).command(showCommand).demandCommand(1, "Specify a subcommand: list, show"),
|
|
2350
2351
|
handler: () => {}
|
|
2351
2352
|
};
|
|
2352
2353
|
|
|
@@ -2388,7 +2389,7 @@ async function runList(argv, { hint }) {
|
|
|
2388
2389
|
]));
|
|
2389
2390
|
if (hint && !argv.quiet) console.log("\nRun `skydive workspace switch` to pick a workspace, or pass its slug directly.");
|
|
2390
2391
|
}
|
|
2391
|
-
const listCommand$
|
|
2392
|
+
const listCommand$2 = {
|
|
2392
2393
|
command: "list",
|
|
2393
2394
|
describe: "List workspaces on your account",
|
|
2394
2395
|
handler: (argv) => runList(argv, { hint: false })
|
|
@@ -2412,7 +2413,7 @@ const switchCommand = {
|
|
|
2412
2413
|
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.");
|
|
2413
2414
|
process.exit(1);
|
|
2414
2415
|
}
|
|
2415
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2416
|
+
const { runWorkspacePicker } = await import("./boot-C5HJfwRS.mjs");
|
|
2416
2417
|
await runWorkspacePicker(session);
|
|
2417
2418
|
return;
|
|
2418
2419
|
}
|
|
@@ -2445,7 +2446,7 @@ const switchCommand = {
|
|
|
2445
2446
|
const workspaceCommand = {
|
|
2446
2447
|
command: "workspace",
|
|
2447
2448
|
describe: "List or switch the workspace all `skydive` commands act on",
|
|
2448
|
-
builder: (y) => y.command(listCommand$
|
|
2449
|
+
builder: (y) => y.command(listCommand$2).command(switchCommand),
|
|
2449
2450
|
handler: (argv) => runList(argv, { hint: true })
|
|
2450
2451
|
};
|
|
2451
2452
|
|
|
@@ -2491,7 +2492,7 @@ const openCommand = {
|
|
|
2491
2492
|
const agent = argv.agent ? resolveAgent$1((await fetchPortalDevices(session)).agents, argv.agent) : null;
|
|
2492
2493
|
const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
|
|
2493
2494
|
const { machineName } = await resolveMachineIdentity(null);
|
|
2494
|
-
const { PortalDaemonClient } = await import("./daemon-client-
|
|
2495
|
+
const { PortalDaemonClient } = await import("./daemon-client-DTfIza0O.mjs");
|
|
2495
2496
|
let lastLine = "";
|
|
2496
2497
|
let signalConnected;
|
|
2497
2498
|
const connected = new Promise((resolve) => {
|
|
@@ -3859,7 +3860,7 @@ function snapshot(pref) {
|
|
|
3859
3860
|
describe: pref.describe
|
|
3860
3861
|
};
|
|
3861
3862
|
}
|
|
3862
|
-
const listCommand = {
|
|
3863
|
+
const listCommand$1 = {
|
|
3863
3864
|
command: "list",
|
|
3864
3865
|
aliases: ["ls"],
|
|
3865
3866
|
describe: "Show every editable preference, its value, and its source",
|
|
@@ -3950,7 +3951,7 @@ const unsetCommand = {
|
|
|
3950
3951
|
}
|
|
3951
3952
|
}
|
|
3952
3953
|
};
|
|
3953
|
-
const pathCommand = {
|
|
3954
|
+
const pathCommand$1 = {
|
|
3954
3955
|
command: "path",
|
|
3955
3956
|
describe: "Print the on-disk config file path",
|
|
3956
3957
|
handler: (argv) => {
|
|
@@ -3964,14 +3965,172 @@ const pathCommand = {
|
|
|
3964
3965
|
const configCommand = {
|
|
3965
3966
|
command: "config",
|
|
3966
3967
|
describe: "View and set CLI preferences (safe, hand-editable keys only)",
|
|
3967
|
-
builder: (y) => y.command(listCommand).command(getCommand).command(setCommand).command(unsetCommand).command(pathCommand),
|
|
3968
|
+
builder: (y) => y.command(listCommand$1).command(getCommand).command(setCommand).command(unsetCommand).command(pathCommand$1),
|
|
3969
|
+
handler: (argv) => listCommand$1.handler(argv)
|
|
3970
|
+
};
|
|
3971
|
+
|
|
3972
|
+
//#endregion
|
|
3973
|
+
//#region src/commands/logs.ts
|
|
3974
|
+
const ISSUE_REPOSITORY_URL = "https://github.com/Create-Inc/skydive-cli";
|
|
3975
|
+
function issueUrl(archivePath) {
|
|
3976
|
+
const url = new URL(`${ISSUE_REPOSITORY_URL}/issues/new`);
|
|
3977
|
+
url.searchParams.set("title", "CLI issue: ");
|
|
3978
|
+
url.searchParams.set("body", [
|
|
3979
|
+
"## What happened?",
|
|
3980
|
+
"",
|
|
3981
|
+
"<!-- Tell us what you were doing and what went wrong. -->",
|
|
3982
|
+
"",
|
|
3983
|
+
"## Diagnostics",
|
|
3984
|
+
"",
|
|
3985
|
+
`Please attach \`${path.basename(archivePath)}\` to this issue.`
|
|
3986
|
+
].join("\n"));
|
|
3987
|
+
return url.toString();
|
|
3988
|
+
}
|
|
3989
|
+
async function listLogSessions(activeSessionPath = profileDir()) {
|
|
3990
|
+
const root = getAutomaticLogsDir();
|
|
3991
|
+
try {
|
|
3992
|
+
const entries = await fs.promises.readdir(root, { withFileTypes: true });
|
|
3993
|
+
return (await Promise.all(entries.filter((entry) => entry.isDirectory() && path.join(root, entry.name) !== activeSessionPath).map(async (entry) => {
|
|
3994
|
+
const sessionPath = path.join(root, entry.name);
|
|
3995
|
+
return {
|
|
3996
|
+
name: entry.name,
|
|
3997
|
+
path: sessionPath,
|
|
3998
|
+
modifiedAt: (await fs.promises.stat(sessionPath)).mtime.toISOString()
|
|
3999
|
+
};
|
|
4000
|
+
}))).sort((a, b) => b.name.localeCompare(a.name));
|
|
4001
|
+
} catch (_error) {
|
|
4002
|
+
return [];
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
async function resolveSession(name) {
|
|
4006
|
+
const sessions = await listLogSessions();
|
|
4007
|
+
const session = name ? sessions.find((candidate) => candidate.name === name) : sessions[0];
|
|
4008
|
+
if (session) return session;
|
|
4009
|
+
printError(name ? `Log session "${name}" was not found. Run \`skydive logs list\`.` : "No diagnostic logs found yet.");
|
|
4010
|
+
process.exit(1);
|
|
4011
|
+
}
|
|
4012
|
+
async function offerIssueReport(archivePath) {
|
|
4013
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
4014
|
+
console.log(`Report an issue: ${ISSUE_REPOSITORY_URL}`);
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
4017
|
+
const prompt = createInterface$1({
|
|
4018
|
+
input: process.stdin,
|
|
4019
|
+
output: process.stdout
|
|
4020
|
+
});
|
|
4021
|
+
try {
|
|
4022
|
+
if ((await prompt.question("Open a prefilled GitHub issue in your browser? [Y/n] ")).trim().toLowerCase() === "n") {
|
|
4023
|
+
console.log(`File an issue: ${ISSUE_REPOSITORY_URL}`);
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
await open(issueUrl(archivePath));
|
|
4027
|
+
console.log(`Attach the diagnostics archive: ${archivePath}`);
|
|
4028
|
+
} finally {
|
|
4029
|
+
prompt.close();
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
async function createArchive(source, destination) {
|
|
4033
|
+
await new Promise((resolve, reject) => {
|
|
4034
|
+
const child = spawn("tar", [
|
|
4035
|
+
"-czf",
|
|
4036
|
+
destination,
|
|
4037
|
+
"-C",
|
|
4038
|
+
path.dirname(source),
|
|
4039
|
+
path.basename(source)
|
|
4040
|
+
], { stdio: [
|
|
4041
|
+
"ignore",
|
|
4042
|
+
"ignore",
|
|
4043
|
+
"pipe"
|
|
4044
|
+
] });
|
|
4045
|
+
const stderr = [];
|
|
4046
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
4047
|
+
child.once("error", reject);
|
|
4048
|
+
child.once("close", (code) => {
|
|
4049
|
+
if (code === 0) {
|
|
4050
|
+
resolve();
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
reject(new Error(Buffer.concat(stderr).toString("utf8").trim() || "Could not archive logs."));
|
|
4054
|
+
});
|
|
4055
|
+
});
|
|
4056
|
+
}
|
|
4057
|
+
const listCommand = {
|
|
4058
|
+
command: "list",
|
|
4059
|
+
aliases: ["ls"],
|
|
4060
|
+
describe: "List recent diagnostic sessions",
|
|
4061
|
+
handler: async (argv) => {
|
|
4062
|
+
const sessions = await listLogSessions();
|
|
4063
|
+
if (argv.json) {
|
|
4064
|
+
output(argv, sessions);
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
if (sessions.length === 0) {
|
|
4068
|
+
console.log("No diagnostic logs found yet.");
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
printTable(["Session", "Modified"], sessions.map((session) => [session.name, session.modifiedAt]));
|
|
4072
|
+
}
|
|
4073
|
+
};
|
|
4074
|
+
const pathCommand = {
|
|
4075
|
+
command: "path [session]",
|
|
4076
|
+
describe: "Print a diagnostic session directory (latest by default)",
|
|
4077
|
+
builder: (y) => y.positional("session", {
|
|
4078
|
+
type: "string",
|
|
4079
|
+
describe: "Session name from `skydive logs list`"
|
|
4080
|
+
}),
|
|
4081
|
+
handler: async (argv) => {
|
|
4082
|
+
const session = await resolveSession(argv.session);
|
|
4083
|
+
if (argv.json) {
|
|
4084
|
+
output(argv, session);
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
console.log(session.path);
|
|
4088
|
+
}
|
|
4089
|
+
};
|
|
4090
|
+
const exportCommand = {
|
|
4091
|
+
command: "export [session]",
|
|
4092
|
+
describe: "Archive a diagnostic session for sharing (latest by default)",
|
|
4093
|
+
builder: (y) => y.positional("session", {
|
|
4094
|
+
type: "string",
|
|
4095
|
+
describe: "Session name from `skydive logs list`"
|
|
4096
|
+
}).option("output", {
|
|
4097
|
+
alias: "o",
|
|
4098
|
+
type: "string",
|
|
4099
|
+
describe: "Archive path (defaults to the current directory)"
|
|
4100
|
+
}),
|
|
4101
|
+
handler: async (argv) => {
|
|
4102
|
+
const session = await resolveSession(argv.session);
|
|
4103
|
+
const destination = path.resolve(argv.output ?? `skydive-logs-${session.name}.tar.gz`);
|
|
4104
|
+
try {
|
|
4105
|
+
await createArchive(session.path, destination);
|
|
4106
|
+
} catch (error) {
|
|
4107
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
4108
|
+
process.exit(1);
|
|
4109
|
+
}
|
|
4110
|
+
if (argv.json) {
|
|
4111
|
+
output(argv, {
|
|
4112
|
+
path: destination,
|
|
4113
|
+
session: session.name,
|
|
4114
|
+
issueUrl: issueUrl(destination),
|
|
4115
|
+
repositoryUrl: ISSUE_REPOSITORY_URL
|
|
4116
|
+
});
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
console.log(destination);
|
|
4120
|
+
await offerIssueReport(destination);
|
|
4121
|
+
}
|
|
4122
|
+
};
|
|
4123
|
+
const logsCommand = {
|
|
4124
|
+
command: "logs",
|
|
4125
|
+
describe: "Find and export recent local diagnostic logs",
|
|
4126
|
+
builder: (y) => y.command(listCommand).command(pathCommand).command(exportCommand),
|
|
3968
4127
|
handler: (argv) => listCommand.handler(argv)
|
|
3969
4128
|
};
|
|
3970
4129
|
|
|
3971
4130
|
//#endregion
|
|
3972
4131
|
//#region src/cli.ts
|
|
3973
4132
|
function createCli(argv) {
|
|
3974
|
-
return yargs(argv).scriptName("skydive").usage("$0 <command> [options]").parserConfiguration({ "populate--": true }).example("skydive auth login", "Store your API key").example("skydive chat", "Open the interactive chat TUI (prod)").example("skydive chat --api-url http://localhost:4500", "Chat against a local dev API").example("skydive agents list", "List your agents").example("skydive completion install", "Set up TAB completion for your shell").example("skydive keys create \"my-key\"", "Create an API key").example("skydive secrets set OPENAI_API_KEY --agent-id <id>", "Set a secret (value from stdin)").example("skydive workspace list", "List workspaces on your account").example("skydive config set updateCheck false", "Disable the daily update check").example("skydive workspace switch acme-team", "Switch the workspace all `skydive` commands act on").example("skydive portal open --agent grace", "Open the portal to this machine for an agent, headless (no TUI)").example("skydive fs edit --agent grace /tmp/notes.md", "Edit a file on the agent's computer with your local editor").option("json", {
|
|
4133
|
+
return yargs(argv).scriptName("skydive").usage("$0 <command> [options]").parserConfiguration({ "populate--": true }).example("skydive auth login", "Store your API key").example("skydive chat", "Open the interactive chat TUI (prod)").example("skydive chat --api-url http://localhost:4500", "Chat against a local dev API").example("skydive agents list", "List your agents").example("skydive completion install", "Set up TAB completion for your shell").example("skydive keys create \"my-key\"", "Create an API key").example("skydive secrets set OPENAI_API_KEY --agent-id <id>", "Set a secret (value from stdin)").example("skydive workspace list", "List workspaces on your account").example("skydive config set updateCheck false", "Disable the daily update check").example("skydive logs export", "Archive the latest local diagnostics for sharing").example("skydive workspace switch acme-team", "Switch the workspace all `skydive` commands act on").example("skydive portal open --agent grace", "Open the portal to this machine for an agent, headless (no TUI)").example("skydive fs edit --agent grace /tmp/notes.md", "Edit a file on the agent's computer with your local editor").option("json", {
|
|
3975
4134
|
type: "boolean",
|
|
3976
4135
|
default: false,
|
|
3977
4136
|
global: true,
|
|
@@ -3985,7 +4144,7 @@ function createCli(argv) {
|
|
|
3985
4144
|
type: "string",
|
|
3986
4145
|
global: true,
|
|
3987
4146
|
describe: "Override API base URL"
|
|
3988
|
-
}).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(importCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).command(portalCommand).command(sandboxCommand).command(fsCommand).command(updateCommand).command(configCommand).command(completionCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
|
|
4147
|
+
}).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(importCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).command(portalCommand).command(sandboxCommand).command(fsCommand).command(updateCommand).command(configCommand).command(logsCommand).command(completionCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
|
|
3989
4148
|
printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
|
|
3990
4149
|
process.exit(1);
|
|
3991
4150
|
});
|
|
@@ -4341,7 +4500,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
|
|
|
4341
4500
|
process.exit(0);
|
|
4342
4501
|
}
|
|
4343
4502
|
if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
|
|
4344
|
-
const { runPortalDaemon } = await import("./daemon-
|
|
4503
|
+
const { runPortalDaemon } = await import("./daemon-WQ8QYrFt.mjs");
|
|
4345
4504
|
runPortalDaemon(process.argv);
|
|
4346
4505
|
} else runCli();
|
|
4347
4506
|
function runCli() {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as installAgentAlias, C as themeVersion, D as machineOsFromPlatform, F as detectShell, L as getActiveWorkspaceId, M as takenAliasNames, O as aliasActivationHint, S as themeModeFromColorFgBg, T as themesForMode, V as setActiveWorkspace, _ as monoTheme, a as WORDMARK, b as themeForMode, d as profilingEnabled, f as record, g as findTheme, h as applyTheme, i as MARK_CELLS, j as slugifyAliasName, k as dedupeAliasName, m as DEFAULT_THEME_ID, n as buildCrashReport, p as writeArtifact, r as writeCrashReport, s as splashFitsWidth, t as installCrashHandler, v as noColorRequested, x as themeMode, y as theme, z as listWorkspaces } from "./install-BPUAm8bS.mjs";
|
|
3
3
|
import { B as resolveWebUrl, C as getConfigPath, O as getReviewStateDir, P as recordDefaultAgent, U as saveTheme, b as DEFAULT_APP_URL, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, h as specKeyFor, i as resolveAgent, k as getSavedTheme, l as parseExternalOauthConnectParams, m as parseConnectCard, p as computeSettledLabel, s as MASK_CHAR, u as parseOauthConnectParams, y as DEFAULT_API_URL } from "./print-Clgq46GU.mjs";
|
|
4
4
|
import { t as HttpError } from "./http-error-UHH3mVBF.mjs";
|
|
5
5
|
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-C6E4mS2A.mjs";
|
|
6
6
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-D3l5kJlX.mjs";
|
|
7
7
|
import { t as PortalClient } from "./client-d6K9qm6B.mjs";
|
|
8
|
-
import "./daemon-
|
|
8
|
+
import "./daemon-BPGZufS1.mjs";
|
|
9
9
|
import { t as defaultTlsCertSource } from "./tls-cert-CV-pwxVN.mjs";
|
|
10
10
|
import "./api-DQCaztBg.mjs";
|
|
11
11
|
import { t as SandboxStream } from "./client-BTQ1fwzM.mjs";
|
|
12
|
-
import { t as PortalDaemonClient } from "./daemon-client-
|
|
12
|
+
import { t as PortalDaemonClient } from "./daemon-client-MpAGVkqP.mjs";
|
|
13
13
|
import { t as runRawPtyPassthrough } from "./raw-pty-B9Bue8gg.mjs";
|
|
14
14
|
import * as os$1 from "node:os";
|
|
15
15
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
@@ -14144,9 +14144,8 @@ async function settleBootThemeMode({ source, applyThemeMode, graceMs = THEME_QUE
|
|
|
14144
14144
|
//#endregion
|
|
14145
14145
|
//#region src/profiling/tui.ts
|
|
14146
14146
|
/**
|
|
14147
|
-
* TUI-side
|
|
14148
|
-
*
|
|
14149
|
-
* SKYDIVE_PROFILE is set.
|
|
14147
|
+
* TUI-side diagnostics. Everything here rides on the shared session directory
|
|
14148
|
+
* created in bin.ts. SKYDIVE_PROFILE=0 makes these helpers inert.
|
|
14150
14149
|
*/
|
|
14151
14150
|
/**
|
|
14152
14151
|
* Renderer options to merge into createCliRenderer when profiling:
|
|
@@ -21,7 +21,7 @@ import { access, appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/
|
|
|
21
21
|
* the commit time of the built tree is one monotonic clock they share.
|
|
22
22
|
*/
|
|
23
23
|
function portalDaemonBuild() {
|
|
24
|
-
return "
|
|
24
|
+
return "1786769616";
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Whether a client carrying `mine` should replace a running daemon carrying
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./client-d6K9qm6B.mjs";
|
|
3
|
-
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-
|
|
3
|
+
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-BPGZufS1.mjs";
|
|
4
4
|
import "./tls-cert-CV-pwxVN.mjs";
|
|
5
5
|
import "./api-DQCaztBg.mjs";
|
|
6
6
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-
|
|
2
|
+
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-BPGZufS1.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
|
|
@@ -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.5.0-beta.
|
|
11
|
+
var version$1 = "0.5.0-beta.33";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/auth/organization.ts
|
|
@@ -1050,6 +1050,277 @@ function applyTheme(def) {
|
|
|
1050
1050
|
version++;
|
|
1051
1051
|
}
|
|
1052
1052
|
|
|
1053
|
+
//#endregion
|
|
1054
|
+
//#region src/profiling/profiler.ts
|
|
1055
|
+
/**
|
|
1056
|
+
* Session diagnostics. Every invocation records enough context to reconstruct
|
|
1057
|
+
* what happened after the fact. SKYDIVE_PROFILE can select a custom directory
|
|
1058
|
+
* or disable recording with `0`.
|
|
1059
|
+
*
|
|
1060
|
+
* Default sessions live under the CLI's system state directory. Explicit
|
|
1061
|
+
* profiles keep the original cwd/custom-path behavior and add a CPU profile.
|
|
1062
|
+
* Each directory is designed to be handed to an agent (or a human) and read
|
|
1063
|
+
* without special tooling:
|
|
1064
|
+
*
|
|
1065
|
+
* network.ndjson every fetch: method, url, status, timing, content type
|
|
1066
|
+
* state.ndjson TUI state transitions (screen changes, store updates)
|
|
1067
|
+
* commits.ndjson React commits: which subtree rendered, when, how long
|
|
1068
|
+
* render.ndjson OpenTUI renderer samples: fps, frame times, cells drawn
|
|
1069
|
+
* render.json final renderer stats dump (full frame-time series)
|
|
1070
|
+
* cpu-<pid>.cpuprofile V8 CPU profile (Node-run commands only)
|
|
1071
|
+
* meta-<pid>.json argv, versions, runtime, exit code, wall time
|
|
1072
|
+
*
|
|
1073
|
+
* Every ndjson event carries a wall-clock `t` (epoch ms) and `pid`, so
|
|
1074
|
+
* records from the Node parent and the Bun-re-exec'd chat TUI land in the
|
|
1075
|
+
* same files and stay correlatable on one clock. The profile directory is
|
|
1076
|
+
* created by the first process and shared with children through
|
|
1077
|
+
* SKYDIVE_PROFILE_DIR (the chat re-exec inherits the environment).
|
|
1078
|
+
*
|
|
1079
|
+
*/
|
|
1080
|
+
const ENV_FLAG = "SKYDIVE_PROFILE";
|
|
1081
|
+
const ENV_DIR = "SKYDIVE_PROFILE_DIR";
|
|
1082
|
+
const DEFAULT_SESSION_LIMIT = 20;
|
|
1083
|
+
let activeDir = null;
|
|
1084
|
+
let startedAtMs = 0;
|
|
1085
|
+
/**
|
|
1086
|
+
* Buffered event lines per stream, flushed asynchronously. Events are
|
|
1087
|
+
* appended to an in-memory buffer and written with fs.promises off the
|
|
1088
|
+
* hot path, so recording never blocks the TUI's event loop — high-rate
|
|
1089
|
+
* streams (React commits, renderer samples) stay cheap. Whatever is
|
|
1090
|
+
* still buffered when the process exits is drained synchronously in the
|
|
1091
|
+
* exit handler, where async I/O would never flush.
|
|
1092
|
+
*/
|
|
1093
|
+
const pendingLines = /* @__PURE__ */ new Map();
|
|
1094
|
+
let flushScheduled = false;
|
|
1095
|
+
let flushing = Promise.resolve();
|
|
1096
|
+
function drainBuffersSync() {
|
|
1097
|
+
if (activeDir === null) return;
|
|
1098
|
+
for (const [stream, lines] of pendingLines) {
|
|
1099
|
+
if (lines.length === 0) continue;
|
|
1100
|
+
pendingLines.set(stream, []);
|
|
1101
|
+
try {
|
|
1102
|
+
fs.appendFileSync(path.join(activeDir, `${stream}.ndjson`), lines.join("\n") + "\n");
|
|
1103
|
+
} catch (_error) {}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
function scheduleFlush() {
|
|
1107
|
+
if (flushScheduled || activeDir === null) return;
|
|
1108
|
+
flushScheduled = true;
|
|
1109
|
+
setTimeout(() => {
|
|
1110
|
+
flushScheduled = false;
|
|
1111
|
+
flushing = flushing.then(async () => {
|
|
1112
|
+
if (activeDir === null) return;
|
|
1113
|
+
for (const [stream, lines] of pendingLines) {
|
|
1114
|
+
if (lines.length === 0) continue;
|
|
1115
|
+
pendingLines.set(stream, []);
|
|
1116
|
+
try {
|
|
1117
|
+
await fs.promises.appendFile(path.join(activeDir, `${stream}.ndjson`), lines.join("\n") + "\n");
|
|
1118
|
+
} catch (_error) {}
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
}, 100).unref?.();
|
|
1122
|
+
}
|
|
1123
|
+
function profilingEnabled() {
|
|
1124
|
+
return process.env[ENV_FLAG] !== "0";
|
|
1125
|
+
}
|
|
1126
|
+
function explicitProfilingEnabled() {
|
|
1127
|
+
const value = process.env[ENV_FLAG];
|
|
1128
|
+
return value !== void 0 && value !== "" && value !== "0";
|
|
1129
|
+
}
|
|
1130
|
+
function profileDir() {
|
|
1131
|
+
return activeDir;
|
|
1132
|
+
}
|
|
1133
|
+
function automaticLogsDir(platform, env, home) {
|
|
1134
|
+
const appName = env["SKYDIVE_CONFIG_NAME"] ?? "skydive";
|
|
1135
|
+
if (platform === "darwin") return path.join(home, "Library", "Logs", appName);
|
|
1136
|
+
if (platform === "linux") {
|
|
1137
|
+
const stateHome = env["XDG_STATE_HOME"] || path.join(home, ".local", "state");
|
|
1138
|
+
return path.join(stateHome, appName);
|
|
1139
|
+
}
|
|
1140
|
+
return path.join(path.dirname(getConfigPath()), "logs");
|
|
1141
|
+
}
|
|
1142
|
+
function getAutomaticLogsDir() {
|
|
1143
|
+
return automaticLogsDir(process.platform, process.env, os.homedir());
|
|
1144
|
+
}
|
|
1145
|
+
function sanitizeToken(token) {
|
|
1146
|
+
return token.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40);
|
|
1147
|
+
}
|
|
1148
|
+
function commandName(argv) {
|
|
1149
|
+
return argv.find((arg) => !arg.startsWith("-")) ?? "chat";
|
|
1150
|
+
}
|
|
1151
|
+
function sessionName(argv) {
|
|
1152
|
+
const command = commandName(argv);
|
|
1153
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19)}-${sanitizeToken(command)}-${process.pid}`;
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Automatic logs retain command shape, not values. Positional arguments and
|
|
1157
|
+
* flag values can contain prompts, secrets, paths, and other private data.
|
|
1158
|
+
*/
|
|
1159
|
+
function safeAutomaticArgv(argv) {
|
|
1160
|
+
return [commandName(argv), ...argv.filter((arg) => arg.startsWith("-")).map((arg) => arg.split("=", 1)[0] ?? arg)];
|
|
1161
|
+
}
|
|
1162
|
+
/** Keeps automatic diagnostics bounded without touching explicit profiles. */
|
|
1163
|
+
function pruneAutomaticSessions(root, activeSession) {
|
|
1164
|
+
try {
|
|
1165
|
+
const sessions = fs.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== activeSession).map((entry) => entry.name).sort().reverse();
|
|
1166
|
+
for (const stale of sessions.slice(DEFAULT_SESSION_LIMIT - 1)) fs.rmSync(path.join(root, stale), {
|
|
1167
|
+
recursive: true,
|
|
1168
|
+
force: true
|
|
1169
|
+
});
|
|
1170
|
+
} catch (_error) {}
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Appends one event to a stream file. Buffered and written asynchronously
|
|
1174
|
+
* so recording never blocks the event loop; the exit handler drains any
|
|
1175
|
+
* remainder synchronously so abrupt exits still keep their events.
|
|
1176
|
+
*/
|
|
1177
|
+
function record(stream, event) {
|
|
1178
|
+
if (activeDir === null) return;
|
|
1179
|
+
const line = JSON.stringify({
|
|
1180
|
+
t: Date.now(),
|
|
1181
|
+
pid: process.pid,
|
|
1182
|
+
...event
|
|
1183
|
+
});
|
|
1184
|
+
const lines = pendingLines.get(stream);
|
|
1185
|
+
if (lines === void 0) pendingLines.set(stream, [line]);
|
|
1186
|
+
else lines.push(line);
|
|
1187
|
+
scheduleFlush();
|
|
1188
|
+
}
|
|
1189
|
+
/** Writes a JSON artifact (non-append) into the profile directory. */
|
|
1190
|
+
function writeArtifact(name, data) {
|
|
1191
|
+
if (activeDir === null) return;
|
|
1192
|
+
try {
|
|
1193
|
+
fs.writeFileSync(path.join(activeDir, name), JSON.stringify(data, null, 2));
|
|
1194
|
+
} catch (_error) {}
|
|
1195
|
+
}
|
|
1196
|
+
function safeAutomaticUrl(value) {
|
|
1197
|
+
try {
|
|
1198
|
+
const url = new URL(value);
|
|
1199
|
+
for (const key of url.searchParams.keys()) url.searchParams.set(key, "<redacted>");
|
|
1200
|
+
url.hash = "";
|
|
1201
|
+
return url.href;
|
|
1202
|
+
} catch (_error) {
|
|
1203
|
+
return "<invalid-url>";
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Patches globalThis.fetch to record request timing. A wrapper (not
|
|
1208
|
+
* undici's diagnostics_channel) because chat re-execs under Bun, where
|
|
1209
|
+
* fetch is Bun-native and undici events never fire; the wrapper behaves
|
|
1210
|
+
* identically in both runtimes.
|
|
1211
|
+
*/
|
|
1212
|
+
function installFetchRecorder(explicit) {
|
|
1213
|
+
const original = globalThis.fetch;
|
|
1214
|
+
const wrapped = async (...args) => {
|
|
1215
|
+
const [input, init] = args;
|
|
1216
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1217
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
1218
|
+
const started = Date.now();
|
|
1219
|
+
try {
|
|
1220
|
+
const res = await original(...args);
|
|
1221
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
1222
|
+
record("network", {
|
|
1223
|
+
method,
|
|
1224
|
+
url: explicit ? url : safeAutomaticUrl(url),
|
|
1225
|
+
status: res.status,
|
|
1226
|
+
durationMs: Date.now() - started,
|
|
1227
|
+
contentType,
|
|
1228
|
+
streamed: contentType.includes("text/event-stream")
|
|
1229
|
+
});
|
|
1230
|
+
return res;
|
|
1231
|
+
} catch (err) {
|
|
1232
|
+
record("network", {
|
|
1233
|
+
method,
|
|
1234
|
+
url: explicit ? url : safeAutomaticUrl(url),
|
|
1235
|
+
status: 0,
|
|
1236
|
+
durationMs: Date.now() - started,
|
|
1237
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1238
|
+
});
|
|
1239
|
+
throw err;
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
globalThis.fetch = Object.assign(wrapped, original);
|
|
1243
|
+
}
|
|
1244
|
+
function isBunRuntime() {
|
|
1245
|
+
return typeof process !== "undefined" && "bun" in process.versions;
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Starts the V8 CPU profiler via node:inspector. Node-run commands only —
|
|
1249
|
+
* Bun does not implement the inspector Profiler domain, so the chat TUI
|
|
1250
|
+
* skips this artifact.
|
|
1251
|
+
*/
|
|
1252
|
+
function startCpuProfile() {
|
|
1253
|
+
if (isBunRuntime()) return;
|
|
1254
|
+
(async () => {
|
|
1255
|
+
try {
|
|
1256
|
+
const { Session } = await import("node:inspector/promises");
|
|
1257
|
+
const session = new Session();
|
|
1258
|
+
session.connect();
|
|
1259
|
+
await session.post("Profiler.enable");
|
|
1260
|
+
await session.post("Profiler.start");
|
|
1261
|
+
process.once("beforeExit", () => {
|
|
1262
|
+
(async () => {
|
|
1263
|
+
try {
|
|
1264
|
+
const { profile } = await session.post("Profiler.stop");
|
|
1265
|
+
writeArtifact(`cpu-${process.pid}.cpuprofile`, profile);
|
|
1266
|
+
session.disconnect();
|
|
1267
|
+
} catch (_error) {}
|
|
1268
|
+
})();
|
|
1269
|
+
});
|
|
1270
|
+
} catch (_error) {}
|
|
1271
|
+
})();
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Starts diagnostics unless SKYDIVE_PROFILE=0. Creates a system-state session
|
|
1275
|
+
* directory (or joins the one a parent process created), patches fetch, and
|
|
1276
|
+
* registers the exit-time metadata dump. Explicit profiles also capture CPU.
|
|
1277
|
+
*/
|
|
1278
|
+
function maybeStartProfiling(argv, cliVersion) {
|
|
1279
|
+
if (!profilingEnabled() || activeDir !== null) return;
|
|
1280
|
+
const inherited = process.env[ENV_DIR];
|
|
1281
|
+
const explicit = explicitProfilingEnabled();
|
|
1282
|
+
let automaticRoot = null;
|
|
1283
|
+
let automaticSession = null;
|
|
1284
|
+
if (inherited !== void 0 && inherited !== "") activeDir = inherited;
|
|
1285
|
+
else if (explicit) {
|
|
1286
|
+
const flagValue = process.env[ENV_FLAG] ?? "1";
|
|
1287
|
+
activeDir = flagValue === "1" || flagValue.toLowerCase() === "true" ? path.resolve(process.cwd(), `skydive-profile-${sessionName(argv)}`) : path.resolve(process.cwd(), flagValue);
|
|
1288
|
+
process.env[ENV_DIR] = activeDir;
|
|
1289
|
+
} else {
|
|
1290
|
+
automaticRoot = getAutomaticLogsDir();
|
|
1291
|
+
automaticSession = sessionName(argv);
|
|
1292
|
+
activeDir = path.join(automaticRoot, automaticSession);
|
|
1293
|
+
process.env[ENV_DIR] = activeDir;
|
|
1294
|
+
}
|
|
1295
|
+
try {
|
|
1296
|
+
fs.mkdirSync(activeDir, {
|
|
1297
|
+
recursive: true,
|
|
1298
|
+
mode: 448
|
|
1299
|
+
});
|
|
1300
|
+
if (automaticRoot !== null && automaticSession !== null) pruneAutomaticSessions(automaticRoot, automaticSession);
|
|
1301
|
+
} catch (_error) {
|
|
1302
|
+
activeDir = null;
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
startedAtMs = Date.now();
|
|
1306
|
+
installFetchRecorder(explicit);
|
|
1307
|
+
if (explicit) startCpuProfile();
|
|
1308
|
+
process.on("exit", (code) => {
|
|
1309
|
+
drainBuffersSync();
|
|
1310
|
+
writeArtifact(`meta-${process.pid}.json`, {
|
|
1311
|
+
argv: explicit ? argv : safeAutomaticArgv(argv),
|
|
1312
|
+
cliVersion,
|
|
1313
|
+
runtime: isBunRuntime() ? `bun ${process.versions["bun"]}` : `node ${process.version}`,
|
|
1314
|
+
platform: process.platform,
|
|
1315
|
+
pid: process.pid,
|
|
1316
|
+
exitCode: code,
|
|
1317
|
+
startedAt: new Date(startedAtMs).toISOString(),
|
|
1318
|
+
wallTimeMs: Date.now() - startedAtMs
|
|
1319
|
+
});
|
|
1320
|
+
if (explicit && process.env[ENV_DIR] === activeDir && inherited === void 0) process.stderr.write(`\nprofile written to ${activeDir}\n`);
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1053
1324
|
//#endregion
|
|
1054
1325
|
//#region src/brand.ts
|
|
1055
1326
|
const MARK_CELLS = [
|
|
@@ -1311,211 +1582,4 @@ function printFatalNotice(file, memory) {
|
|
|
1311
1582
|
}
|
|
1312
1583
|
|
|
1313
1584
|
//#endregion
|
|
1314
|
-
|
|
1315
|
-
/**
|
|
1316
|
-
* Opt-in session profiling. When SKYDIVE_PROFILE is set, an invocation
|
|
1317
|
-
* records everything needed to reconstruct what happened performance-wise —
|
|
1318
|
-
* designed so the whole directory can be handed to an agent (or a human)
|
|
1319
|
-
* and read without special tooling:
|
|
1320
|
-
*
|
|
1321
|
-
* network.ndjson every fetch: method, url, status, timing, content type
|
|
1322
|
-
* state.ndjson TUI state transitions (screen changes, store updates)
|
|
1323
|
-
* commits.ndjson React commits: which subtree rendered, when, how long
|
|
1324
|
-
* render.ndjson OpenTUI renderer samples: fps, frame times, cells drawn
|
|
1325
|
-
* render.json final renderer stats dump (full frame-time series)
|
|
1326
|
-
* cpu-<pid>.cpuprofile V8 CPU profile (Node-run commands only)
|
|
1327
|
-
* meta-<pid>.json argv, versions, runtime, exit code, wall time
|
|
1328
|
-
*
|
|
1329
|
-
* Every ndjson event carries a wall-clock `t` (epoch ms) and `pid`, so
|
|
1330
|
-
* records from the Node parent and the Bun-re-exec'd chat TUI land in the
|
|
1331
|
-
* same files and stay correlatable on one clock. The profile directory is
|
|
1332
|
-
* created by the first process and shared with children through
|
|
1333
|
-
* SKYDIVE_PROFILE_DIR (the chat re-exec inherits the environment).
|
|
1334
|
-
*
|
|
1335
|
-
* Inert unless SKYDIVE_PROFILE is set: no patched fetch, no subscriptions,
|
|
1336
|
-
* no inspector session.
|
|
1337
|
-
*/
|
|
1338
|
-
const ENV_FLAG = "SKYDIVE_PROFILE";
|
|
1339
|
-
const ENV_DIR = "SKYDIVE_PROFILE_DIR";
|
|
1340
|
-
let activeDir = null;
|
|
1341
|
-
let startedAtMs = 0;
|
|
1342
|
-
/**
|
|
1343
|
-
* Buffered event lines per stream, flushed asynchronously. Events are
|
|
1344
|
-
* appended to an in-memory buffer and written with fs.promises off the
|
|
1345
|
-
* hot path, so recording never blocks the TUI's event loop — high-rate
|
|
1346
|
-
* streams (React commits, renderer samples) stay cheap. Whatever is
|
|
1347
|
-
* still buffered when the process exits is drained synchronously in the
|
|
1348
|
-
* exit handler, where async I/O would never flush.
|
|
1349
|
-
*/
|
|
1350
|
-
const pendingLines = /* @__PURE__ */ new Map();
|
|
1351
|
-
let flushScheduled = false;
|
|
1352
|
-
let flushing = Promise.resolve();
|
|
1353
|
-
function drainBuffersSync() {
|
|
1354
|
-
if (activeDir === null) return;
|
|
1355
|
-
for (const [stream, lines] of pendingLines) {
|
|
1356
|
-
if (lines.length === 0) continue;
|
|
1357
|
-
pendingLines.set(stream, []);
|
|
1358
|
-
try {
|
|
1359
|
-
fs.appendFileSync(path.join(activeDir, `${stream}.ndjson`), lines.join("\n") + "\n");
|
|
1360
|
-
} catch (_error) {}
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
function scheduleFlush() {
|
|
1364
|
-
if (flushScheduled || activeDir === null) return;
|
|
1365
|
-
flushScheduled = true;
|
|
1366
|
-
setTimeout(() => {
|
|
1367
|
-
flushScheduled = false;
|
|
1368
|
-
flushing = flushing.then(async () => {
|
|
1369
|
-
if (activeDir === null) return;
|
|
1370
|
-
for (const [stream, lines] of pendingLines) {
|
|
1371
|
-
if (lines.length === 0) continue;
|
|
1372
|
-
pendingLines.set(stream, []);
|
|
1373
|
-
try {
|
|
1374
|
-
await fs.promises.appendFile(path.join(activeDir, `${stream}.ndjson`), lines.join("\n") + "\n");
|
|
1375
|
-
} catch (_error) {}
|
|
1376
|
-
}
|
|
1377
|
-
});
|
|
1378
|
-
}, 100).unref?.();
|
|
1379
|
-
}
|
|
1380
|
-
function profilingEnabled() {
|
|
1381
|
-
const v = process.env[ENV_FLAG];
|
|
1382
|
-
return v !== void 0 && v !== "" && v !== "0";
|
|
1383
|
-
}
|
|
1384
|
-
function sanitizeToken(token) {
|
|
1385
|
-
return token.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40);
|
|
1386
|
-
}
|
|
1387
|
-
/**
|
|
1388
|
-
* Appends one event to a stream file. Buffered and written asynchronously
|
|
1389
|
-
* so recording never blocks the event loop; the exit handler drains any
|
|
1390
|
-
* remainder synchronously so abrupt exits still keep their events.
|
|
1391
|
-
*/
|
|
1392
|
-
function record(stream, event) {
|
|
1393
|
-
if (activeDir === null) return;
|
|
1394
|
-
const line = JSON.stringify({
|
|
1395
|
-
t: Date.now(),
|
|
1396
|
-
pid: process.pid,
|
|
1397
|
-
...event
|
|
1398
|
-
});
|
|
1399
|
-
const lines = pendingLines.get(stream);
|
|
1400
|
-
if (lines === void 0) pendingLines.set(stream, [line]);
|
|
1401
|
-
else lines.push(line);
|
|
1402
|
-
scheduleFlush();
|
|
1403
|
-
}
|
|
1404
|
-
/** Writes a JSON artifact (non-append) into the profile directory. */
|
|
1405
|
-
function writeArtifact(name, data) {
|
|
1406
|
-
if (activeDir === null) return;
|
|
1407
|
-
try {
|
|
1408
|
-
fs.writeFileSync(path.join(activeDir, name), JSON.stringify(data, null, 2));
|
|
1409
|
-
} catch (_error) {}
|
|
1410
|
-
}
|
|
1411
|
-
/**
|
|
1412
|
-
* Patches globalThis.fetch to record request timing. A wrapper (not
|
|
1413
|
-
* undici's diagnostics_channel) because chat re-execs under Bun, where
|
|
1414
|
-
* fetch is Bun-native and undici events never fire; the wrapper behaves
|
|
1415
|
-
* identically in both runtimes.
|
|
1416
|
-
*/
|
|
1417
|
-
function installFetchRecorder() {
|
|
1418
|
-
const original = globalThis.fetch;
|
|
1419
|
-
const wrapped = async (...args) => {
|
|
1420
|
-
const [input, init] = args;
|
|
1421
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1422
|
-
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
1423
|
-
const started = Date.now();
|
|
1424
|
-
try {
|
|
1425
|
-
const res = await original(...args);
|
|
1426
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
1427
|
-
record("network", {
|
|
1428
|
-
method,
|
|
1429
|
-
url,
|
|
1430
|
-
status: res.status,
|
|
1431
|
-
durationMs: Date.now() - started,
|
|
1432
|
-
contentType,
|
|
1433
|
-
streamed: contentType.includes("text/event-stream")
|
|
1434
|
-
});
|
|
1435
|
-
return res;
|
|
1436
|
-
} catch (err) {
|
|
1437
|
-
record("network", {
|
|
1438
|
-
method,
|
|
1439
|
-
url,
|
|
1440
|
-
status: 0,
|
|
1441
|
-
durationMs: Date.now() - started,
|
|
1442
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1443
|
-
});
|
|
1444
|
-
throw err;
|
|
1445
|
-
}
|
|
1446
|
-
};
|
|
1447
|
-
globalThis.fetch = Object.assign(wrapped, original);
|
|
1448
|
-
}
|
|
1449
|
-
function isBunRuntime() {
|
|
1450
|
-
return typeof process !== "undefined" && "bun" in process.versions;
|
|
1451
|
-
}
|
|
1452
|
-
/**
|
|
1453
|
-
* Starts the V8 CPU profiler via node:inspector. Node-run commands only —
|
|
1454
|
-
* Bun does not implement the inspector Profiler domain, so the chat TUI
|
|
1455
|
-
* skips this artifact.
|
|
1456
|
-
*/
|
|
1457
|
-
function startCpuProfile() {
|
|
1458
|
-
if (isBunRuntime()) return;
|
|
1459
|
-
(async () => {
|
|
1460
|
-
try {
|
|
1461
|
-
const { Session } = await import("node:inspector/promises");
|
|
1462
|
-
const session = new Session();
|
|
1463
|
-
session.connect();
|
|
1464
|
-
await session.post("Profiler.enable");
|
|
1465
|
-
await session.post("Profiler.start");
|
|
1466
|
-
process.once("beforeExit", () => {
|
|
1467
|
-
(async () => {
|
|
1468
|
-
try {
|
|
1469
|
-
const { profile } = await session.post("Profiler.stop");
|
|
1470
|
-
writeArtifact(`cpu-${process.pid}.cpuprofile`, profile);
|
|
1471
|
-
session.disconnect();
|
|
1472
|
-
} catch (_error) {}
|
|
1473
|
-
})();
|
|
1474
|
-
});
|
|
1475
|
-
} catch (_error) {}
|
|
1476
|
-
})();
|
|
1477
|
-
}
|
|
1478
|
-
/**
|
|
1479
|
-
* Activates profiling for this process if SKYDIVE_PROFILE is set. Creates
|
|
1480
|
-
* the profile directory (or joins the one a parent process created),
|
|
1481
|
-
* patches fetch, starts the CPU profiler, and registers the exit-time
|
|
1482
|
-
* meta dump. Call once, as early as possible.
|
|
1483
|
-
*/
|
|
1484
|
-
function maybeStartProfiling(argv, cliVersion) {
|
|
1485
|
-
if (!profilingEnabled() || activeDir !== null) return;
|
|
1486
|
-
const inherited = process.env[ENV_DIR];
|
|
1487
|
-
if (inherited !== void 0 && inherited !== "") activeDir = inherited;
|
|
1488
|
-
else {
|
|
1489
|
-
const flagValue = process.env[ENV_FLAG] ?? "1";
|
|
1490
|
-
const command = argv.find((a) => !a.startsWith("-")) ?? "chat";
|
|
1491
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
1492
|
-
activeDir = flagValue === "1" || flagValue.toLowerCase() === "true" ? path.resolve(process.cwd(), `skydive-profile-${sanitizeToken(command)}-${stamp}`) : path.resolve(process.cwd(), flagValue);
|
|
1493
|
-
process.env[ENV_DIR] = activeDir;
|
|
1494
|
-
}
|
|
1495
|
-
try {
|
|
1496
|
-
fs.mkdirSync(activeDir, { recursive: true });
|
|
1497
|
-
} catch (_error) {
|
|
1498
|
-
activeDir = null;
|
|
1499
|
-
return;
|
|
1500
|
-
}
|
|
1501
|
-
startedAtMs = Date.now();
|
|
1502
|
-
installFetchRecorder();
|
|
1503
|
-
startCpuProfile();
|
|
1504
|
-
process.on("exit", (code) => {
|
|
1505
|
-
drainBuffersSync();
|
|
1506
|
-
writeArtifact(`meta-${process.pid}.json`, {
|
|
1507
|
-
argv,
|
|
1508
|
-
cliVersion,
|
|
1509
|
-
runtime: isBunRuntime() ? `bun ${process.versions["bun"]}` : `node ${process.version}`,
|
|
1510
|
-
platform: process.platform,
|
|
1511
|
-
pid: process.pid,
|
|
1512
|
-
exitCode: code,
|
|
1513
|
-
startedAt: new Date(startedAtMs).toISOString(),
|
|
1514
|
-
wallTimeMs: Date.now() - startedAtMs
|
|
1515
|
-
});
|
|
1516
|
-
if (process.env[ENV_DIR] === activeDir && inherited === void 0) process.stderr.write(`\nprofile written to ${activeDir}\n`);
|
|
1517
|
-
});
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
//#endregion
|
|
1521
|
-
export { takenAliasNames as A, name as B, themesForMode as C, dedupeAliasName as D, aliasActivationHint as E, getActiveWorkspaceId as F, getSessionIdentity as I, listWorkspaces as L, defaultInstallEnv as M, detectShell as N, installAgentAlias as O, ensureActiveOrganization as P, resolveWorkspaceId as R, themes as S, machineOsFromPlatform as T, version$1 as V, theme as _, installCrashHandler as a, themeModeFromColorFgBg as b, MARK_CELLS as c, splashFitsWidth as d, DEFAULT_THEME_ID as f, noColorRequested as g, monoTheme as h, writeArtifact as i, SUPPORTED_SHELLS as j, slugifyAliasName as k, WORDMARK as l, findTheme as m, profilingEnabled as n, buildCrashReport as o, applyTheme as p, record as r, writeCrashReport as s, maybeStartProfiling as t, brandHelpArt as u, themeForMode as v, buildImportSeedPrompt as w, themeVersion as x, themeMode as y, setActiveWorkspace as z };
|
|
1585
|
+
export { installAgentAlias as A, resolveWorkspaceId as B, themeVersion as C, machineOsFromPlatform as D, buildImportSeedPrompt as E, detectShell as F, name as H, ensureActiveOrganization as I, getActiveWorkspaceId as L, takenAliasNames as M, SUPPORTED_SHELLS as N, aliasActivationHint as O, defaultInstallEnv as P, getSessionIdentity as R, themeModeFromColorFgBg as S, themesForMode as T, version$1 as U, setActiveWorkspace as V, monoTheme as _, WORDMARK as a, themeForMode as b, getAutomaticLogsDir as c, profilingEnabled as d, record as f, findTheme as g, applyTheme as h, MARK_CELLS as i, slugifyAliasName as j, dedupeAliasName as k, maybeStartProfiling as l, DEFAULT_THEME_ID as m, buildCrashReport as n, brandHelpArt as o, writeArtifact as p, writeCrashReport as r, splashFitsWidth as s, installCrashHandler as t, profileDir as u, noColorRequested as v, themes as w, themeMode as x, theme as y, listWorkspaces as z };
|
package/package.json
CHANGED