skydive-cli 0.2.0 → 0.3.0

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.
@@ -1,193 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import { x as getConfigPath } from "./print-Cd-bVSEJ.mjs";
3
+ import os from "node:os";
2
4
  import path from "node:path";
3
- import Conf from "conf";
4
5
  import { err, ok } from "neverthrow";
5
6
  import { z } from "zod";
7
+ import fs from "node:fs";
6
8
 
7
- //#region src/config.ts
8
- /** Default host for the public management API (`/v1`, API-key auth). */
9
- const DEFAULT_API_URL = "https://api.skydive.com";
10
- /**
11
- * Default origin for the interactive chat client (`skydive chat`).
12
- *
13
- * The API host that serves better-auth (`/api/auth/*`) and the internal tRPC
14
- * API (`/api/v1/trpc`) that chat streams over. We target the API host
15
- * directly (not the web front door) because chat opens a WebSocket and
16
- * authenticates with a bearer token on the upgrade request. Same host as
17
- * `DEFAULT_API_URL`; override with `--api-url` / `SKYDIVE_APP_URL` for local
18
- * dev or while the DNS record is still being provisioned.
19
- */
20
- const DEFAULT_APP_URL = "https://api.skydive.com";
21
- /** Web front door, for pages opened in the user's browser. */
22
- const DEFAULT_WEB_URL = "https://skydive.com";
23
- /**
24
- * Origin for browser-facing links (e.g. opening a conversation's web page).
25
- * The app origin is the API host, which serves no web UI in production, so
26
- * map the default to the web front door. Overridden origins (local dev,
27
- * previews) serve both and pass through unchanged.
28
- */
29
- function resolveWebUrl(appUrl) {
30
- return appUrl === DEFAULT_APP_URL ? DEFAULT_WEB_URL : appUrl;
31
- }
32
- /** Prefix on workspace-scoped Skydive API keys. Kept in sync with the API's
33
- * `API_KEY_PREFIX` (`apps/anyone/api/src/lib/api-key.ts`); the CLI is a
34
- * standalone published package so it can't import the backend constant. */
35
- const API_KEY_PREFIX = "sky_live_";
36
- /**
37
- * Common prefix across all Skydive API key kinds — `sky_live_…` workspace
38
- * keys today, `sky_user_…` account keys when ANY-5105 lands. The CLI only
39
- * sanity-checks the family on `--api-key`; the server authoritatively rejects
40
- * a kind that can't drive a given route, with a clearer message than the
41
- * client could produce.
42
- */
43
- const API_KEY_FAMILY_PREFIX = "sky_";
44
- /** Where users mint and copy API keys. Shown in the login prompt. */
45
- const API_KEYS_URL = "skydive.com/settings/account";
46
- const store = new Conf({
47
- projectName: process.env["SKYDIVE_CONFIG_NAME"] ?? "skydive",
48
- projectSuffix: "",
49
- configFileMode: 384
50
- });
51
- function resolveConfig(opts) {
52
- const apiKey = process.env["SKYDIVE_API_KEY"] ?? store.get("apiKey");
53
- const apiUrl = process.env["SKYDIVE_API_URL"] ?? opts.apiUrl ?? store.get("apiUrl") ?? DEFAULT_API_URL;
54
- if (!apiKey) return err({ message: "Not authenticated. Run `skydive auth login` first." });
55
- return ok({
56
- apiKey,
57
- apiUrl
58
- });
59
- }
60
- /**
61
- * Resolve the bearer credential for the management API (`agents` / `keys` /
62
- * `secrets`). The server's `/v1` gate accepts either an API key or the
63
- * device-flow session bearer, so both work — but only one of them tracks the
64
- * active workspace.
65
- *
66
- * An API key is pinned server-side to the organization that minted it and
67
- * ignores the workspace header by design, so it can never follow `skydive
68
- * workspace switch`. A key is also a strictly narrower credential than its
69
- * owner's session. So the session wins whenever there is one, and a key is
70
- * what's left for machines that never ran an interactive login.
71
- *
72
- * `SKYDIVE_SESSION_TOKEN=` (empty) suppresses session auth for one
73
- * invocation, to drive a specific organization's key while signed in.
74
- */
75
- function resolveManagementAuth(opts) {
76
- const session = resolveSession({ appUrl: opts.apiUrl });
77
- if (session.isOk()) return ok({
78
- token: session.value.sessionToken,
79
- apiUrl: session.value.appUrl,
80
- kind: "session",
81
- pinnedWorkspaceName: null
82
- });
83
- const envKey = process.env["SKYDIVE_API_KEY"];
84
- const storedKey = store.get("apiKey");
85
- const apiKey = envKey ?? storedKey;
86
- if (apiKey) return ok({
87
- token: apiKey,
88
- apiUrl: process.env["SKYDIVE_API_URL"] ?? opts.apiUrl ?? store.get("apiUrl") ?? DEFAULT_API_URL,
89
- kind: "api-key",
90
- pinnedWorkspaceName: !envKey && storedKey ? store.get("apiKeyWorkspaceName") ?? null : null
91
- });
92
- return err({ message: "Not authenticated. Run `skydive auth login`." });
93
- }
94
- function saveConfig(config) {
95
- store.set("apiKey", config.apiKey);
96
- store.set("apiUrl", config.apiUrl);
97
- if (config.apiKeyId) store.set("apiKeyId", config.apiKeyId);
98
- else store.delete("apiKeyId");
99
- if (config.workspaceName) store.set("apiKeyWorkspaceName", config.workspaceName);
100
- else store.delete("apiKeyWorkspaceName");
101
- }
102
- /** Server-side id of the auto-minted key, if login minted one. */
103
- function getStoredApiKeyId() {
104
- return store.get("apiKeyId") ?? null;
105
- }
106
- /** Workspace the auto-minted key is pinned to, if login recorded one. */
107
- function getStoredApiKeyWorkspaceName() {
108
- return store.get("apiKeyWorkspaceName") ?? null;
109
- }
110
- function deleteConfig() {
111
- store.clear();
112
- }
113
- function getConfigPath() {
114
- return store.path;
115
- }
116
- /**
117
- * Where the chat TUI persists its prompt history (up-arrow recall). Kept
118
- * beside the config file so all CLI state lives in one directory.
119
- */
120
- function getLastSeenVersion() {
121
- return store.get("lastSeenVersion");
122
- }
123
- function setLastSeenVersion(version) {
124
- store.set("lastSeenVersion", version);
125
- }
126
- function getPromptHistoryPath() {
127
- return path.join(path.dirname(store.path), "prompt-history.jsonl");
128
- }
129
- /**
130
- * Where the chat TUI persists pending review comments — one JSON file per
131
- * conversation, so a pending comment survives conversation switches and
132
- * process death. Kept beside the config file like prompt history.
133
- */
134
- function getReviewStateDir() {
135
- return path.join(path.dirname(store.path), "review");
136
- }
137
- /**
138
- * Resolve the chat/auth origin. Precedence: `SKYDIVE_APP_URL` env > explicit
139
- * `--api-url` style override > stored value > `SKYDIVE_API_URL` env >
140
- * `DEFAULT_APP_URL`.
141
- *
142
- * The `SKYDIVE_API_URL` fallback matters for previews: the device/`--web`
143
- * flow and chat hit the same api service as the management API, so pointing
144
- * `SKYDIVE_API_URL` at a preview stack is enough — you don't also have to set
145
- * `SKYDIVE_APP_URL`. Otherwise auth would silently fall through to prod
146
- * (`DEFAULT_APP_URL`) and hand back a prod verification URL.
147
- */
148
- function resolveAppUrl(opts) {
149
- return process.env["SKYDIVE_APP_URL"] ?? opts.appUrl ?? store.get("appUrl") ?? process.env["SKYDIVE_API_URL"] ?? DEFAULT_APP_URL;
150
- }
151
- function resolveSession(opts) {
152
- const sessionToken = process.env["SKYDIVE_SESSION_TOKEN"] ?? store.get("sessionToken");
153
- const appUrl = resolveAppUrl(opts);
154
- if (!sessionToken) return err({ message: "Not signed in for chat. Run `skydive chat` to sign in." });
155
- return ok({
156
- sessionToken,
157
- appUrl
158
- });
159
- }
160
- function saveSession(session) {
161
- store.set("sessionToken", session.sessionToken);
162
- store.set("sessionObtainedAt", (/* @__PURE__ */ new Date()).toISOString());
163
- store.set("appUrl", session.appUrl);
164
- }
165
- function getSavedTheme(mode) {
166
- return store.get(mode === "dark" ? "themeDark" : "themeLight");
167
- }
168
- function saveTheme(mode, themeId) {
169
- store.set(mode === "dark" ? "themeDark" : "themeLight", themeId);
170
- }
171
- /**
172
- * Whether `skydive chat` should enable portal machine sharing on launch
173
- * without `--share-machine`. Set by hand-editing `shareMachineDefault` in
174
- * config.json — deliberately no CLI command (kept off the API surface).
175
- * Sharing only makes the machine reachable — agents still need a
176
- * (persistent) grant to run anything, so this default skips the per-session
177
- * enable step, not the consent step.
178
- */
179
- function getShareMachineDefault() {
180
- return store.get("shareMachineDefault") ?? false;
181
- }
182
- /**
183
- * Whether the update check is disabled via config.json (`"updateCheck":
184
- * false`). The persistent counterpart to the SKYDIVE_NO_UPDATE_CHECK /
185
- * NO_UPDATE_NOTIFIER env opt-outs; like shareMachineDefault, set by editing
186
- * config.json — deliberately no CLI command.
187
- */
188
- function getUpdateCheckDisabled() {
189
- return store.get("updateCheck") === false;
190
- }
9
+ //#region package.json
10
+ var name = "skydive-cli";
11
+ var version$1 = "0.3.0";
191
12
 
192
13
  //#endregion
193
14
  //#region src/auth/organization.ts
@@ -987,4 +808,253 @@ function applyTheme(def) {
987
808
  }
988
809
 
989
810
  //#endregion
990
- export { getShareMachineDefault as A, saveSession as B, DEFAULT_WEB_URL as C, getPromptHistoryPath as D, getLastSeenVersion as E, resolveConfig as F, setLastSeenVersion as H, resolveManagementAuth as I, resolveSession as L, getStoredApiKeyWorkspaceName as M, getUpdateCheckDisabled as N, getReviewStateDir as O, resolveAppUrl as P, resolveWebUrl as R, DEFAULT_APP_URL as S, getConfigPath as T, saveTheme as V, setActiveWorkspace as _, noColorRequested as a, API_KEY_PREFIX as b, themeMode as c, themes as d, themesForMode as f, listWorkspaces as g, getSessionIdentity as h, monoTheme as i, getStoredApiKeyId as j, getSavedTheme as k, themeModeFromColorFgBg as l, getActiveWorkspaceId as m, applyTheme as n, theme as o, ensureActiveOrganization as p, findTheme as r, themeForMode as s, DEFAULT_THEME_ID as t, themeVersion as u, API_KEYS_URL as v, deleteConfig as w, DEFAULT_API_URL as x, API_KEY_FAMILY_PREFIX as y, saveConfig as z };
811
+ //#region src/brand.ts
812
+ const MARK_CELLS = [
813
+ [
814
+ " ",
815
+ " ",
816
+ ["▆", "#517a9d"],
817
+ ["█", "#6192bb"],
818
+ ["█", "#6191ba"],
819
+ ["▌", "#4d7494"],
820
+ ["▆", "#90572e"],
821
+ ["█", "#e18948"],
822
+ ["█", "#df8747"],
823
+ ["▅", "#bd733c"],
824
+ " ",
825
+ " "
826
+ ],
827
+ [
828
+ " ",
829
+ ["▂", "#cb5556"],
830
+ ["▝", "#6699c4"],
831
+ ["█", "#446683"],
832
+ ["█", "#527c9f"],
833
+ ["▘", "#436582"],
834
+ ["▝", "#ba713b"],
835
+ ["█", "#be733c"],
836
+ ["█", "#9c5e31"],
837
+ ["▚", "#9d6345"],
838
+ ["▂", "#c1859b"],
839
+ " "
840
+ ],
841
+ [
842
+ ["█", "#c05152"],
843
+ ["█", "#e15f60"],
844
+ ["█", "#d65a5b"],
845
+ ["▅", "#a24445"],
846
+ " ",
847
+ " ",
848
+ " ",
849
+ " ",
850
+ ["▅", "#b17a8e"],
851
+ ["█", "#d895ad"],
852
+ ["█", "#df9ab3"],
853
+ ["▉", "#c3869c"]
854
+ ],
855
+ [
856
+ ["▝", "#d25859"],
857
+ ["█", "#953f3f"],
858
+ ["▘", "#df5e5f"],
859
+ ["▂", "#4d8d6f"],
860
+ ["▃", "#539877"],
861
+ " ",
862
+ " ",
863
+ ["▃", "#e5aa47"],
864
+ ["▂", "#d29d41"],
865
+ ["█", "#7f585e"],
866
+ ["█", "#916475"],
867
+ ["▘", "#c6899f"]
868
+ ],
869
+ [
870
+ " ",
871
+ " ",
872
+ ["█", "#4e8f70"],
873
+ ["█", "#569d7b"],
874
+ ["█", "#569d7b"],
875
+ ["▌", "#4a876a"],
876
+ ["▆", "#8f6a2c"],
877
+ ["█", "#efb24a"],
878
+ ["█", "#efb24a"],
879
+ ["▉", "#e2a845"],
880
+ " ",
881
+ " "
882
+ ],
883
+ [
884
+ " ",
885
+ " ",
886
+ " ",
887
+ ["▝", "#488467"],
888
+ ["▘", "#488367"],
889
+ " ",
890
+ " ",
891
+ ["▝", "#ca963e"],
892
+ ["▘", "#c8953d"],
893
+ " ",
894
+ " ",
895
+ " "
896
+ ]
897
+ ];
898
+ const MARK_WIDTH = 12;
899
+ /** Accent hex used for the compact one-liner star (matches the blue petal). */
900
+ const BRAND_ACCENT = "#5b8fc7";
901
+ /** "Skydive" in an ANSI-shadow block font. */
902
+ const WORDMARK = [
903
+ "███████╗██╗ ██╗██╗ ██╗██████╗ ██╗██╗ ██╗███████╗",
904
+ "██╔════╝██║ ██╔╝╚██╗ ██╔╝██╔══██╗██║██║ ██║██╔════╝",
905
+ "███████╗█████╔╝ ╚████╔╝ ██║ ██║██║██║ ██║█████╗ ",
906
+ "╚════██║██╔═██╗ ╚██╔╝ ██║ ██║██║╚██╗ ██╔╝██╔══╝ ",
907
+ "███████║██║ ██╗ ██║ ██████╔╝██║ ╚████╔╝ ███████╗",
908
+ "╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚══════╝"
909
+ ];
910
+ const RESET = "\x1B[0m";
911
+ function hexToRgb(hex) {
912
+ const n = Number.parseInt(hex.slice(1), 16);
913
+ return [
914
+ n >> 16 & 255,
915
+ n >> 8 & 255,
916
+ n & 255
917
+ ];
918
+ }
919
+ function sgr(text, hex) {
920
+ const [r, g, b] = hexToRgb(hex);
921
+ return `\x1b[38;2;${r};${g};${b}m${text}${RESET}`;
922
+ }
923
+ function markLinesAnsi() {
924
+ return MARK_CELLS.map((row) => row.map((cell) => cell === " " ? " " : sgr(cell[0], cell[1])).join(""));
925
+ }
926
+ /**
927
+ * Build the splash for the `skydive --help` screen as a string, gated on the
928
+ * terminal's capabilities. Returns '' when it should be suppressed (non-TTY
929
+ * target such as a pipe or CI). The wordmark is left uncolored (terminal default
930
+ * fg = neutral on both light and dark); only the mark uses brand petal hexes.
931
+ * A narrow terminal collapses to a one-liner.
932
+ */
933
+ function brandHelpArt(stream = process.stdout) {
934
+ if (!stream.isTTY) return "";
935
+ const truecolor = (typeof stream.getColorDepth === "function" ? stream.getColorDepth() : 1) >= 24;
936
+ if ((stream.columns ?? 80) < 66) return truecolor ? `\n ${sgr("✦", BRAND_ACCENT)} Skydive\n` : "\n ✦ Skydive\n";
937
+ if (!truecolor) return `\n${WORDMARK.map((l) => ` ${l}`).join("\n")}\n`;
938
+ const mark = markLinesAnsi();
939
+ const word = [...WORDMARK];
940
+ const height = Math.max(mark.length, word.length);
941
+ const topPad = Math.max(0, Math.floor((word.length - mark.length) / 2));
942
+ const blank = " ".repeat(MARK_WIDTH);
943
+ const lines = [];
944
+ for (let i = 0; i < height; i++) {
945
+ const mi = i - topPad;
946
+ const m = mi >= 0 && mi < mark.length ? mark[mi] : blank;
947
+ const w = i < word.length ? word[i] : "";
948
+ lines.push(` ${m} ${w}`);
949
+ }
950
+ return `\n${lines.join("\n")}\n`;
951
+ }
952
+
953
+ //#endregion
954
+ //#region src/crash/report.ts
955
+ const MB = 1024 * 1024;
956
+ function currentRuntime() {
957
+ const bun = process.versions["bun"];
958
+ return bun ? `bun ${bun}` : `node ${process.version}`;
959
+ }
960
+ function serializeError(error) {
961
+ if (error == null) return null;
962
+ if (error instanceof Error) return {
963
+ name: error.name,
964
+ message: error.message,
965
+ ...error.stack ? { stack: error.stack } : {}
966
+ };
967
+ return {
968
+ name: "NonError",
969
+ message: String(error)
970
+ };
971
+ }
972
+ /** Directory where crash/diagnostic reports are written (beside config). */
973
+ function getCrashDir() {
974
+ return path.join(path.dirname(getConfigPath()), "crash");
975
+ }
976
+ /** Build a report object without touching disk. */
977
+ function buildCrashReport(args) {
978
+ return {
979
+ reason: args.reason,
980
+ cliVersion: version$1,
981
+ runtime: currentRuntime(),
982
+ platform: process.platform,
983
+ arch: process.arch,
984
+ nodeVersion: process.version,
985
+ totalSystemMemory: os.totalmem(),
986
+ error: serializeError(args.error),
987
+ memory: args.memory ?? null,
988
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
989
+ };
990
+ }
991
+ /**
992
+ * Write a report to disk and return its path (or null if writing failed).
993
+ * Safe to call from a fatal handler: every failure is swallowed so the
994
+ * diagnostic can never mask the original crash.
995
+ */
996
+ function writeCrashReport(report) {
997
+ try {
998
+ const dir = getCrashDir();
999
+ fs.mkdirSync(dir, { recursive: true });
1000
+ const stamp = report.timestamp.replace(/[:.]/g, "-");
1001
+ const file = path.join(dir, `crash-${stamp}.json`);
1002
+ fs.writeFileSync(file, `${JSON.stringify(report, null, 2)}\n`, { mode: 384 });
1003
+ return file;
1004
+ } catch (_error) {
1005
+ return null;
1006
+ }
1007
+ }
1008
+ /** Human-readable MB, one decimal. */
1009
+ function formatMb(bytes) {
1010
+ return `${(bytes / MB).toFixed(1)} MB`;
1011
+ }
1012
+
1013
+ //#endregion
1014
+ //#region src/crash/install.ts
1015
+ let installed = false;
1016
+ /**
1017
+ * Install the process-wide fatal crash handler. Idempotent: a second call is
1018
+ * a no-op, and later options (e.g. a memory provider registered once chat
1019
+ * boots) update the live handler in place.
1020
+ *
1021
+ * Disabled entirely by `SKYDIVE_NO_CRASH_REPORT` — the escape hatch, and how
1022
+ * tests keep a real handler off the shared process.
1023
+ */
1024
+ function installCrashHandler(opts = {}) {
1025
+ if (process.env["SKYDIVE_NO_CRASH_REPORT"]) return;
1026
+ memoryProvider = opts.memoryProvider ?? memoryProvider;
1027
+ writeNotice = opts.writeNotice ?? writeNotice;
1028
+ exitProcess = opts.exit ?? exitProcess;
1029
+ if (installed) return;
1030
+ installed = true;
1031
+ process.on("uncaughtException", (error) => {
1032
+ const memory = safeMemory();
1033
+ printFatalNotice(writeCrashReport(buildCrashReport({
1034
+ reason: "uncaughtException",
1035
+ error,
1036
+ memory
1037
+ })), memory);
1038
+ exitProcess(1);
1039
+ });
1040
+ }
1041
+ const defaultWriteNotice = (text) => process.stderr.write(text);
1042
+ const defaultExit = (code) => process.exit(code);
1043
+ let memoryProvider = null;
1044
+ let writeNotice = defaultWriteNotice;
1045
+ let exitProcess = defaultExit;
1046
+ function safeMemory() {
1047
+ try {
1048
+ return memoryProvider?.() ?? void 0;
1049
+ } catch (_error) {
1050
+ return;
1051
+ }
1052
+ }
1053
+ function printFatalNotice(file, memory) {
1054
+ const peak = memory ? ` (peak memory ${formatMb(memory.peakRss)})` : "";
1055
+ if (file) writeNotice(`\nskydive: the CLI hit a fatal error${peak}. A diagnostic report was written to:\n ${file}\nPlease send this file to the Skydive team so we can investigate.\n`);
1056
+ else writeNotice(`\nskydive: the CLI hit a fatal error${peak}.\n`);
1057
+ }
1058
+
1059
+ //#endregion
1060
+ export { setActiveWorkspace as C, listWorkspaces as S, version$1 as T, themes as _, WORDMARK as a, getActiveWorkspaceId as b, applyTheme as c, noColorRequested as d, theme as f, themeVersion as g, themeModeFromColorFgBg as h, MARK_CELLS as i, findTheme as l, themeMode as m, buildCrashReport as n, brandHelpArt as o, themeForMode as p, writeCrashReport as r, DEFAULT_THEME_ID as s, installCrashHandler as t, monoTheme as u, themesForMode as v, name as w, getSessionIdentity as x, ensureActiveOrganization as y };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./rest-CamHVOce.mjs";
3
- import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-Bin4u16d.mjs";
2
+ import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-Cd-bVSEJ.mjs";
3
+ import "./rest-BlN_uWmL.mjs";
4
4
 
5
5
  export { messageGet, readStdin, resolveAgent, runPrint };