skydive-cli 0.1.0-beta.363 → 0.1.0-beta.378

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 CHANGED
@@ -8,9 +8,45 @@ npx skydive-cli --help
8
8
 
9
9
  ## Authentication
10
10
 
11
- The CLI has two credentials, stored together in a single `config.json`. The
12
- location is platform-conventional (resolved via `env-paths`), so it is **not**
13
- `~/.config/skydive/` everywhere:
11
+ Sign in once via the browser the same shape as `doppler login`: the CLI
12
+ opens the approval page, shows a short code, waits for you to approve, and
13
+ then automatically mints an API key for this machine:
14
+
15
+ ```sh
16
+ skydive auth login
17
+ ```
18
+
19
+ That single flow yields both credentials the CLI uses, stored together in a
20
+ single `config.json`:
21
+
22
+ - an **API key** (`sky_live_…`) named after this machine and the workspace it
23
+ was minted in (`CLI (host) — Acme`) — the durable credential for the
24
+ management commands (`agents`, `keys`, `secrets`). Logging in again from the
25
+ same machine replaces it rather than accumulating rows, and
26
+ `skydive auth logout` revokes it, so signing out doesn't leave a live key
27
+ behind. The workspace is in the name because the key is pinned to it: unlike
28
+ the session, a key never follows `workspace switch`.
29
+ - a **user session** for `skydive chat`. Chat is user-level and multi-agent,
30
+ so it authenticates as you — and unlike the API key (which is pinned to the
31
+ workspace that minted it), the session follows `skydive workspace switch`.
32
+ The management commands prefer the session while it's valid and fall back
33
+ to the API key.
34
+
35
+ The flow doesn't require a TTY: in a non-interactive shell (an agent harness,
36
+ SSH without a display) `auth login` still prints the verification URL + code
37
+ and waits — open the link in any browser to approve. For unattended CI, skip
38
+ the wait entirely and pass an existing key (mint one at
39
+ `skydive.com/settings/account`):
40
+
41
+ ```sh
42
+ skydive auth login --api-key sky_live_… # or set SKYDIVE_API_KEY
43
+ ```
44
+
45
+ `skydive auth status` shows both credentials; `skydive auth logout` revokes
46
+ the auto-minted key and clears them.
47
+
48
+ The config location is platform-conventional (resolved via `env-paths`), so
49
+ it is **not** `~/.config/skydive/` everywhere:
14
50
 
15
51
  | Platform | Path |
16
52
  | -------- | ---------------------------------------------------------------- |
@@ -21,29 +57,6 @@ location is platform-conventional (resolved via `env-paths`), so it is **not**
21
57
  `skydive auth status` prints the real resolved path (`Config: …`) — trust that
22
58
  over this table if they ever disagree.
23
59
 
24
- - **API key** (`sky_live_…`). Mint one at `skydive.com/settings/account`, then:
25
-
26
- ```sh
27
- skydive auth login # paste the key
28
- ```
29
-
30
- - **User session** for `skydive chat`. Chat is user-level and multi-agent,
31
- so it signs you in via the browser (device flow) rather than a per-agent
32
- API key. `skydive chat` does this automatically on first run; you can also
33
- do it up front:
34
-
35
- ```sh
36
- skydive auth login --web # opens the browser, prompts for approval
37
- ```
38
-
39
- Either credential authenticates the management commands (`agents`, `keys`,
40
- `secrets`): the CLI prefers the API key and falls back to the `--web` chat
41
- session, so a `--web` login alone is enough to run them — you do **not** need a
42
- separate API key. The API key is only required when you have no chat session
43
- (e.g. CI). `skydive chat` requires the chat session specifically.
44
-
45
- `skydive auth status` shows both; `skydive auth logout` clears them.
46
-
47
60
  ## Commands
48
61
 
49
62
  ```sh
@@ -164,7 +177,7 @@ skydive chat -p "status?" --agent grace --json # structured envelo
164
177
  - `--conversation <id>` continues an existing thread; omitted, it starts a new
165
178
  one. The conversation id is included in `--json` output for chaining.
166
179
  - `-p` needs a user session just like `chat`, but never opens the interactive
167
- browser login — sign in first with `skydive auth login --web` or set
180
+ browser login — sign in first with `skydive auth login` or set
168
181
  `SKYDIVE_SESSION_TOKEN`.
169
182
  - `--json` prints `{ agentId, agentName, conversationId, isNewConversation,
170
183
  messageId, text }` instead of streaming the raw text.
package/dist/js/bin.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { t as __exportAll } from "./rolldown-runtime-Cz4Tg37Z.mjs";
3
3
  import { hideBin } from "yargs/helpers";
4
4
  import yargs from "yargs";
5
- import { createInterface } from "node:readline";
5
+ import os, { hostname } from "node:os";
6
6
  import path from "node:path";
7
7
  import Conf from "conf";
8
8
  import { err, ok } from "neverthrow";
@@ -13,11 +13,10 @@ import { spawnSync } from "node:child_process";
13
13
  import { createHash } from "node:crypto";
14
14
  import fs from "node:fs";
15
15
  import zlib from "node:zlib";
16
- import os from "node:os";
17
16
  import { WebSocket } from "ws";
18
17
 
19
18
  //#region package.json
20
- var version$1 = "0.1.0-beta.363";
19
+ var version$1 = "0.1.0-beta.378";
21
20
 
22
21
  //#endregion
23
22
  //#region src/types.ts
@@ -57,10 +56,18 @@ const DEFAULT_WEB_URL = "https://skydive.com";
57
56
  function resolveWebUrl(appUrl) {
58
57
  return appUrl === DEFAULT_APP_URL ? DEFAULT_WEB_URL : appUrl;
59
58
  }
60
- /** Prefix on every Skydive API key. Kept in sync with the API's
59
+ /** Prefix on workspace-scoped Skydive API keys. Kept in sync with the API's
61
60
  * `API_KEY_PREFIX` (`apps/anyone/api/src/lib/api-key.ts`); the CLI is a
62
61
  * standalone published package so it can't import the backend constant. */
63
62
  const API_KEY_PREFIX = "sky_live_";
63
+ /**
64
+ * Common prefix across all Skydive API key kinds — `sky_live_…` workspace
65
+ * keys today, `sky_user_…` account keys when ANY-5105 lands. The CLI only
66
+ * sanity-checks the family on `--api-key`; the server authoritatively rejects
67
+ * a kind that can't drive a given route, with a clearer message than the
68
+ * client could produce.
69
+ */
70
+ const API_KEY_FAMILY_PREFIX = "sky_";
64
71
  /** Where users mint and copy API keys. Shown in the login prompt. */
65
72
  const API_KEYS_URL = "skydive.com/settings/account";
66
73
  const store = new Conf({
@@ -79,9 +86,9 @@ function resolveConfig(opts) {
79
86
  }
80
87
  /**
81
88
  * Resolve the bearer credential for the management API (`agents` / `keys` /
82
- * `secrets`). The server's `/v1` gate accepts either an API key or the `--web`
83
- * session bearer, so both work — but only one of them tracks the active
84
- * workspace.
89
+ * `secrets`). The server's `/v1` gate accepts either an API key or the
90
+ * device-flow session bearer, so both work — but only one of them tracks the
91
+ * active workspace.
85
92
  *
86
93
  * An API key is pinned server-side to the organization that minted it and
87
94
  * ignores the workspace header by design, so it can never follow `skydive
@@ -105,11 +112,17 @@ function resolveManagementAuth(opts) {
105
112
  apiUrl: process.env["SKYDIVE_API_URL"] ?? opts.apiUrl ?? store.get("apiUrl") ?? DEFAULT_API_URL,
106
113
  kind: "api-key"
107
114
  });
108
- return err({ message: "Not authenticated. Run `skydive auth login` (API key) or `skydive auth login --web`." });
115
+ return err({ message: "Not authenticated. Run `skydive auth login`." });
109
116
  }
110
117
  function saveConfig(config) {
111
118
  store.set("apiKey", config.apiKey);
112
119
  store.set("apiUrl", config.apiUrl);
120
+ if (config.apiKeyId) store.set("apiKeyId", config.apiKeyId);
121
+ else store.delete("apiKeyId");
122
+ }
123
+ /** Server-side id of the auto-minted key, if login minted one. */
124
+ function getStoredApiKeyId() {
125
+ return store.get("apiKeyId") ?? null;
113
126
  }
114
127
  function deleteConfig() {
115
128
  store.clear();
@@ -321,7 +334,7 @@ var SkydiveApiClient = class {
321
334
  if (body.error && typeof body.error === "string") message = body.error;
322
335
  else if (body.error?.message) message = body.error.message;
323
336
  } catch {}
324
- if (response.status === 401 && this.authKind === "session") message = `${message} — your chat session may have expired. Run \`skydive auth login --web\`.`;
337
+ if (response.status === 401 && this.authKind === "session") message = `${message} — your chat session may have expired. Run \`skydive auth login\`.`;
325
338
  return err({
326
339
  message,
327
340
  status: response.status
@@ -648,6 +661,94 @@ function sleep$1(ms) {
648
661
  return new Promise((resolve) => setTimeout(resolve, ms));
649
662
  }
650
663
 
664
+ //#endregion
665
+ //#region src/auth/mint-key.ts
666
+ const mintedKeySchema = z.object({
667
+ id: z.string(),
668
+ name: z.string(),
669
+ prefix: z.string(),
670
+ key: z.string()
671
+ });
672
+ /**
673
+ * Mint an org-scoped (account-level) API key off a fresh device-flow session,
674
+ * so `auth login` ends with a durable machine credential the way
675
+ * `doppler login` does. Calls the same internal endpoint the web settings page
676
+ * uses; no `agentId` is sent, so the key is account-level, not agent-bound.
677
+ *
678
+ * Scopes are `use` + `edit` because the management commands the key powers
679
+ * (`agents create`, `keys create`, `secrets set`) are edit-tier operations.
680
+ */
681
+ async function mintCliApiKey({ appUrl, sessionToken, name }) {
682
+ try {
683
+ const res = await fetch(`${appUrl}/api/v1/api-keys`, {
684
+ method: "POST",
685
+ headers: {
686
+ authorization: `Bearer ${sessionToken}`,
687
+ "content-type": "application/json"
688
+ },
689
+ body: JSON.stringify({
690
+ name,
691
+ scopes: ["use", "edit"]
692
+ })
693
+ });
694
+ if (!res.ok) return err({ message: `failed to mint API key (${res.status})` });
695
+ const parsed = mintedKeySchema.safeParse(await res.json());
696
+ if (!parsed.success) return err({ message: "unexpected mint response" });
697
+ return ok(parsed.data);
698
+ } catch (e) {
699
+ return err({ message: e instanceof Error ? e.message : String(e) });
700
+ }
701
+ }
702
+ const keyListSchema = z.object({ keys: z.array(z.object({
703
+ id: z.string(),
704
+ name: z.string(),
705
+ agentId: z.string().nullish()
706
+ })) });
707
+ /**
708
+ * Revoke every active account-level key with the given name. Login calls this
709
+ * before minting so repeated logins from the same machine + workspace replace
710
+ * their key instead of piling up indistinguishable rows (`logout`'s revoke is
711
+ * best-effort, and login-without-logout is the normal case — a wiped
712
+ * container, an expired session). Agent-scoped keys are never touched even on
713
+ * a name collision.
714
+ */
715
+ async function revokeKeysByName({ appUrl, sessionToken, name }) {
716
+ try {
717
+ const res = await fetch(`${appUrl}/api/v1/api-keys`, { headers: { authorization: `Bearer ${sessionToken}` } });
718
+ if (!res.ok) return err({ message: `failed to list API keys (${res.status})` });
719
+ const parsed = keyListSchema.safeParse(await res.json());
720
+ if (!parsed.success) return err({ message: "unexpected key list response" });
721
+ const matches = parsed.data.keys.filter((key) => key.name === name && !key.agentId);
722
+ const failures = (await Promise.all(matches.map((key) => revokeApiKey({
723
+ appUrl,
724
+ token: sessionToken,
725
+ id: key.id
726
+ })))).filter((result) => result.isErr());
727
+ if (failures.length > 0) return err({ message: `failed to revoke ${failures.length} of ${matches.length} existing keys` });
728
+ return ok(matches.length);
729
+ } catch (e) {
730
+ return err({ message: e instanceof Error ? e.message : String(e) });
731
+ }
732
+ }
733
+ /**
734
+ * Revoke an API key by id. Used by `auth logout` to clean up the key that
735
+ * login auto-minted, so signing out on a machine doesn't leave a live
736
+ * credential behind. `token` may be a session bearer or an API key — the
737
+ * server accepts both on this route.
738
+ */
739
+ async function revokeApiKey({ appUrl, token, id }) {
740
+ try {
741
+ const res = await fetch(`${appUrl}/api/v1/api-keys/${id}`, {
742
+ method: "DELETE",
743
+ headers: { authorization: `Bearer ${token}` }
744
+ });
745
+ if (!res.ok && res.status !== 404) return err({ message: `failed to revoke API key (${res.status})` });
746
+ return ok(void 0);
747
+ } catch (e) {
748
+ return err({ message: e instanceof Error ? e.message : String(e) });
749
+ }
750
+ }
751
+
651
752
  //#endregion
652
753
  //#region src/output.ts
653
754
  function output(argv, data) {
@@ -677,99 +778,160 @@ function printError(message) {
677
778
  //#region src/commands/auth.ts
678
779
  const loginCommand = {
679
780
  command: "login",
680
- describe: "Authenticate with a Skydive API key (or --web for chat)",
781
+ describe: "Sign in via the browser (use --api-key for CI / headless)",
681
782
  builder: (y) => y.option("api-key", {
682
783
  type: "string",
683
- describe: `API key (${API_KEY_PREFIX}...)`
784
+ describe: `Skip the browser and authenticate with an existing API key (${API_KEY_PREFIX}...)`
684
785
  }).option("web", {
685
786
  type: "boolean",
686
787
  default: false,
687
- describe: "Sign in for `skydive chat` via the browser (device flow) instead of an API key"
788
+ hidden: true,
789
+ describe: "Deprecated: browser sign-in is now the default"
688
790
  }),
689
791
  handler: async (argv) => {
690
- if (argv.web) {
691
- await runWebLogin(argv);
792
+ if (argv["api-key"]) {
793
+ await runApiKeyLogin(argv, argv["api-key"]);
692
794
  return;
693
795
  }
694
- let apiKey = argv["api-key"] ?? process.env["SKYDIVE_API_KEY"];
695
- if (!apiKey) {
696
- const rl = createInterface({
697
- input: process.stdin,
698
- output: process.stderr
699
- });
700
- apiKey = await new Promise((resolve) => {
701
- rl.question(`Enter your API key (from ${API_KEYS_URL}): `, (answer) => {
702
- rl.close();
703
- resolve(answer.trim());
704
- });
705
- });
706
- }
707
- if (!apiKey) {
708
- printError("No API key provided.");
709
- process.exit(1);
710
- }
711
- if (!apiKey.startsWith(API_KEY_PREFIX)) {
712
- printError(`API key must start with ${API_KEY_PREFIX}`);
713
- process.exit(1);
714
- }
715
- const apiUrl = argv["api-url"] ?? DEFAULT_API_URL;
716
- const result = await new SkydiveApiClient({
717
- token: apiKey,
718
- apiUrl,
719
- kind: "api-key"
720
- }).listAgents({
721
- limit: 1,
722
- scope: null
723
- });
724
- if (result.isErr()) {
725
- printError(`Invalid API key or unreachable server: ${result.error.message}`);
726
- process.exit(1);
796
+ if (isNonInteractive()) {
797
+ const envKey = process.env["SKYDIVE_API_KEY"];
798
+ if (envKey) {
799
+ await runApiKeyLogin(argv, envKey);
800
+ return;
801
+ }
727
802
  }
728
- saveConfig({
729
- apiKey,
730
- apiUrl
731
- });
732
- if (argv.json) output(argv, {
803
+ await runBrowserLogin(argv);
804
+ }
805
+ };
806
+ /**
807
+ * Browser sign-in, shaped like `doppler login`: open the approval page with a
808
+ * short code, wait for the user to approve, then automatically mint an API
809
+ * key for this machine. One flow yields both credentials — the session (chat,
810
+ * workspace-following management calls) and a durable `sky_live_…` key that
811
+ * keeps headless/management use working after the session expires.
812
+ *
813
+ * Deliberately no TTY requirement: the flow only prints the verification URL
814
+ * + code and polls, so it also works from a non-interactive shell — an agent
815
+ * driving the CLI relays the URL to its human, who approves in any browser
816
+ * (the auto-open is best-effort). The cost is that an unattended run with no
817
+ * key configured waits out the device code's expiry instead of failing fast.
818
+ */
819
+ async function runBrowserLogin(argv) {
820
+ const appUrl = resolveAppUrl({ appUrl: argv["api-url"] });
821
+ const login = await loginWithDevice({ appUrl });
822
+ if (login.isErr()) {
823
+ printError(login.error.message);
824
+ process.exit(1);
825
+ }
826
+ const { sessionToken } = login.value;
827
+ const identity = (await getSessionIdentity({
828
+ appUrl,
829
+ sessionToken
830
+ })).unwrapOr(null);
831
+ const keyName = identity?.activeWorkspaceName ? `CLI (${hostname()}) — ${identity.activeWorkspaceName}` : `CLI (${hostname()})`;
832
+ const replaced = await revokeKeysByName({
833
+ appUrl,
834
+ sessionToken,
835
+ name: keyName
836
+ });
837
+ if (replaced.isErr()) process.stderr.write(`Warning: could not revoke this machine's previous API key (${replaced.error.message}); minting a new one anyway.\n`);
838
+ const minted = await mintCliApiKey({
839
+ appUrl,
840
+ sessionToken,
841
+ name: keyName
842
+ });
843
+ if (minted.isOk()) saveConfig({
844
+ apiKey: minted.value.key,
845
+ apiUrl: appUrl,
846
+ apiKeyId: minted.value.id
847
+ });
848
+ else process.stderr.write(`Warning: signed in, but could not mint an API key (${minted.error.message}). Management commands will use your session; run \`skydive auth login\` again to retry.
849
+ `);
850
+ if (argv.json) {
851
+ output(argv, {
733
852
  authenticated: true,
734
- prefix: apiKey.slice(0, 12),
853
+ mode: "browser",
854
+ appUrl,
855
+ prefix: minted.isOk() ? minted.value.prefix : null,
856
+ email: identity?.email ?? null,
857
+ workspaceName: identity?.activeWorkspaceName ?? null,
735
858
  configPath: getConfigPath()
736
859
  });
737
- else if (!argv.quiet) {
738
- console.log(`Authenticated successfully.`);
739
- console.log(` Key: ${apiKey.slice(0, 12)}...`);
740
- console.log(` Config: ${getConfigPath()}`);
741
- }
860
+ return;
742
861
  }
743
- };
744
- async function runWebLogin(argv) {
745
- if (isNonInteractive()) {
746
- printError("`auth login --web` needs an interactive terminal. Set SKYDIVE_SESSION_TOKEN for non-interactive use.");
862
+ if (argv.quiet) return;
863
+ const who = identity?.name ?? identity?.email;
864
+ console.log(who ? `Welcome, ${who}!` : "Signed in.");
865
+ if (identity?.activeWorkspaceName) console.log(` Workspace: ${identity.activeWorkspaceName}`);
866
+ if (minted.isOk()) console.log(` API key: ${minted.value.prefix}… (${minted.value.name})`);
867
+ console.log(` Config: ${getConfigPath()}`);
868
+ }
869
+ /** CI / headless path: validate and persist an existing key, no browser. */
870
+ async function runApiKeyLogin(argv, apiKey) {
871
+ if (!apiKey.startsWith(API_KEY_FAMILY_PREFIX)) {
872
+ printError(`API key must start with ${API_KEY_FAMILY_PREFIX}`);
747
873
  process.exit(1);
748
874
  }
749
- const result = await loginWithDevice({ appUrl: resolveAppUrl({ appUrl: argv["api-url"] }) });
875
+ const apiUrl = argv["api-url"] ?? DEFAULT_API_URL;
876
+ const result = await new SkydiveApiClient({
877
+ token: apiKey,
878
+ apiUrl,
879
+ kind: "api-key"
880
+ }).listAgents({
881
+ limit: 1,
882
+ scope: null
883
+ });
750
884
  if (result.isErr()) {
751
- printError(result.error.message);
885
+ printError(`Invalid API key or unreachable server: ${result.error.message}`);
752
886
  process.exit(1);
753
887
  }
888
+ saveConfig({
889
+ apiKey,
890
+ apiUrl,
891
+ apiKeyId: null
892
+ });
754
893
  if (argv.json) output(argv, {
755
894
  authenticated: true,
756
- mode: "session",
757
- appUrl: result.value.appUrl,
895
+ prefix: apiKey.slice(0, 12),
758
896
  configPath: getConfigPath()
759
897
  });
760
898
  else if (!argv.quiet) {
761
- console.log("Signed in for chat.");
762
- console.log(` App: ${result.value.appUrl}`);
899
+ console.log(`Authenticated successfully.`);
900
+ console.log(` Key: ${apiKey.slice(0, 12)}...`);
763
901
  console.log(` Config: ${getConfigPath()}`);
764
902
  }
765
903
  }
766
904
  const logoutCommand = {
767
905
  command: "logout",
768
- describe: "Clear stored credentials (API key and chat session)",
906
+ describe: "Revoke the auto-minted API key and clear stored credentials",
769
907
  handler: async (argv) => {
908
+ const keyId = getStoredApiKeyId();
909
+ let keyRevoked = false;
910
+ if (keyId) {
911
+ const session = resolveSession({ appUrl: argv["api-url"] });
912
+ const config = resolveConfig({ apiUrl: argv["api-url"] });
913
+ const credential = session.isOk() ? {
914
+ appUrl: session.value.appUrl,
915
+ token: session.value.sessionToken
916
+ } : config.isOk() ? {
917
+ appUrl: config.value.apiUrl,
918
+ token: config.value.apiKey
919
+ } : null;
920
+ if (credential) {
921
+ const revoked = await revokeApiKey({
922
+ ...credential,
923
+ id: keyId
924
+ });
925
+ keyRevoked = revoked.isOk();
926
+ if (revoked.isErr()) process.stderr.write(`Warning: could not revoke this machine's API key (${revoked.error.message}). Revoke it manually at ${API_KEYS_URL}.\n`);
927
+ }
928
+ }
770
929
  deleteConfig();
771
- if (argv.json) output(argv, { authenticated: false });
772
- else if (!argv.quiet) console.log("Logged out.");
930
+ if (argv.json) output(argv, {
931
+ authenticated: false,
932
+ keyRevoked
933
+ });
934
+ else if (!argv.quiet) console.log(keyRevoked ? "Logged out. API key revoked." : "Logged out.");
773
935
  }
774
936
  };
775
937
  const statusCommand$1 = {
@@ -802,7 +964,7 @@ const statusCommand$1 = {
802
964
  return;
803
965
  }
804
966
  if (apiKey.isErr() && session.isErr()) {
805
- console.log("Not authenticated. Run `skydive auth login` (API key) or `skydive auth login --web` (chat).");
967
+ console.log("Not authenticated. Run `skydive auth login`.");
806
968
  return;
807
969
  }
808
970
  if (apiKey.isOk()) {
@@ -1262,7 +1424,7 @@ const conversationStreamEventSchema = z.discriminatedUnion("kind", [z.object({
1262
1424
  function requireSession(argv) {
1263
1425
  const session = resolveSession({ appUrl: resolveAppUrl({ appUrl: argv["api-url"] }) });
1264
1426
  if (session.isErr()) {
1265
- printError(`${session.error.message} Run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
1427
+ printError(`${session.error.message} Run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
1266
1428
  process.exit(1);
1267
1429
  }
1268
1430
  return session.value;
@@ -2597,7 +2759,7 @@ const chatCommand = {
2597
2759
  let session = resolveSession({ appUrl });
2598
2760
  if (session.isErr()) {
2599
2761
  if (isNonInteractive()) {
2600
- printError("Not signed in for chat and no interactive terminal. Run `skydive auth login --web`, or set SKYDIVE_SESSION_TOKEN.");
2762
+ printError("Not signed in for chat and no interactive terminal. Run `skydive auth login`, or set SKYDIVE_SESSION_TOKEN.");
2601
2763
  process.exit(1);
2602
2764
  }
2603
2765
  const login = await loginWithDevice({ appUrl });
@@ -2616,7 +2778,7 @@ const chatCommand = {
2616
2778
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
2617
2779
  process.exit(1);
2618
2780
  }
2619
- const { runChat } = await import("./boot-C1WUzzUo.mjs");
2781
+ const { runChat } = await import("./boot-QnNiy1cA.mjs");
2620
2782
  await runChat({
2621
2783
  appUrl,
2622
2784
  sessionToken: session.value.sessionToken,
@@ -2640,7 +2802,7 @@ function resolveShareMachine(argv) {
2640
2802
  async function runPrintMode({ argv, appUrl }) {
2641
2803
  const session = resolveSession({ appUrl });
2642
2804
  if (session.isErr()) {
2643
- printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
2805
+ printError(`${session.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
2644
2806
  process.exit(1);
2645
2807
  }
2646
2808
  const { runPrint, readStdin } = await Promise.resolve().then(() => print_exports);
@@ -2728,7 +2890,7 @@ const getCommand = {
2728
2890
  const appUrl = resolveAppUrl({ appUrl: argv["api-url"] });
2729
2891
  const session = resolveSession({ appUrl });
2730
2892
  if (session.isErr()) {
2731
- printError(`${session.error.message} Run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
2893
+ printError(`${session.error.message} Run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
2732
2894
  process.exit(1);
2733
2895
  }
2734
2896
  const { messageGet } = await Promise.resolve().then(() => print_exports);
@@ -3410,7 +3572,7 @@ const switchCommand = {
3410
3572
  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.");
3411
3573
  process.exit(1);
3412
3574
  }
3413
- const { runWorkspacePicker } = await import("./boot-C1WUzzUo.mjs");
3575
+ const { runWorkspacePicker } = await import("./boot-QnNiy1cA.mjs");
3414
3576
  await runWorkspacePicker(session);
3415
3577
  return;
3416
3578
  }
@@ -2,14 +2,14 @@
2
2
  import { A as HttpError, B as saveTheme, C as noColorRequested, D as themeModeFromColorFgBg, E as themeMode, F as DEFAULT_API_URL, I as DEFAULT_APP_URL, L as getConfigPath, M as getActiveWorkspaceId, N as listWorkspaces, O as themeVersion, P as setActiveWorkspace, R as getSavedTheme, S as monoTheme, T as themeForMode, _ as errorMessage, b as applyTheme, d as cardActionErrorMessage, f as parseExternalOauthConnectParams, g as parseConnectCard, h as resolveConnectUrl, j as createRestClient, k as themesForMode, l as resolveAgent, m as reconcileMaskedInput, p as parseOauthConnectParams, t as SandboxStream, u as MASK_CHAR, v as isRecord, w as theme, x as findTheme, y as DEFAULT_THEME_ID, z as resolveWebUrl } from "./bin.mjs";
3
3
  import { t as PortalClient } from "./client-XFsd0Wy9.mjs";
4
4
  import { n as runRawPtyPassthrough } from "./raw-pty-C1DXKms6.mjs";
5
+ import * as os$1 from "node:os";
6
+ import { homedir, platform, release, tmpdir } from "node:os";
5
7
  import path, { basename, extname, isAbsolute, join, win32 } from "node:path";
6
8
  import { z } from "zod";
7
9
  import open from "open";
8
10
  import { execFile, spawn } from "node:child_process";
9
11
  import { createHash } from "node:crypto";
10
12
  import { constants } from "node:fs";
11
- import * as os$1 from "node:os";
12
- import { homedir, platform, release, tmpdir } from "node:os";
13
13
  import { MarkdownRenderable, RenderableEvents, SyntaxStyle, createCliRenderer, decodePasteBytes, detectLinks } from "@opentui/core";
14
14
  import { createRoot, extend, useKeyboard, usePaste, useRenderer, useTerminalDimensions } from "@opentui/react";
15
15
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.1.0-beta.363",
3
+ "version": "0.1.0-beta.378",
4
4
  "description": "Skydive CLI — manage AI agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",