skydive-cli 0.1.0-beta.163 → 0.1.0-beta.186

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
@@ -142,18 +142,19 @@ skydive chat -p "status?" --agent grace --json # structured envelo
142
142
  browser login — sign in first with `skydive auth login --web` or set
143
143
  `SKYDIVE_SESSION_TOKEN`.
144
144
  - `--json` prints `{ agentId, agentName, conversationId, isNewConversation,
145
- runId, text }` instead of streaming the raw text.
145
+ messageId, text }` instead of streaming the raw text.
146
146
  - **Recovering a dropped run.** A long run's stream can be cut off at the edge
147
- (a gateway 502/504) after the run was created. The run keeps going
148
- server-side, so a blind retry would re-execute the agent. Instead, note the
149
- `runId` (always in `--json` output) and fetch the result once it's done:
147
+ (a gateway 502/504) after the message was accepted. The agent keeps working
148
+ server-side, so a blind retry would re-execute it. Instead, note the
149
+ `messageId` (always in `--json` output) and fetch the result once it's done:
150
150
 
151
151
  ```sh
152
- skydive runs get <runId> # prints the reply; --json for the envelope
152
+ skydive messages get <messageId> # prints the reply; --json for the envelope
153
153
  ```
154
154
 
155
- `runs get` re-attaches to the run and replays its full reply whether it's
156
- still streaming or already finished.
155
+ `messages get` re-attaches to the exchange behind that message id and replays
156
+ the full reply whether it's still streaming or already finished. Message ids
157
+ are the handle this API works in; run ids stay server-side.
157
158
 
158
159
  It defaults to the production API (`https://api.skydive.com`). For local
159
160
  dev, point it at your stack:
package/dist/js/bin.mjs CHANGED
@@ -31,7 +31,7 @@ var __exportAll = (all, no_symbols) => {
31
31
 
32
32
  //#endregion
33
33
  //#region package.json
34
- var version$1 = "0.1.0-beta.163";
34
+ var version$1 = "0.1.0-beta.186";
35
35
 
36
36
  //#endregion
37
37
  //#region src/types.ts
@@ -886,7 +886,7 @@ function truncate(value, max) {
886
886
  if (trimmed.length <= max) return trimmed || "-";
887
887
  return `${trimmed.slice(0, max - 1)}\u2026`;
888
888
  }
889
- const getCommand = {
889
+ const getCommand$1 = {
890
890
  command: "get <id>",
891
891
  describe: "Get agent details",
892
892
  builder: (y) => y.positional("id", {
@@ -956,7 +956,7 @@ const createCommand$1 = {
956
956
  const agentsCommand = {
957
957
  command: "agents",
958
958
  describe: "Manage agents",
959
- builder: (y) => y.command(listCommand$4).command(getCommand).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
959
+ builder: (y) => y.command(listCommand$4).command(getCommand$1).command(createCommand$1).demandCommand(1, "Specify a subcommand: list, get, create"),
960
960
  handler: () => {}
961
961
  };
962
962
 
@@ -2152,7 +2152,7 @@ const chatCommand = {
2152
2152
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
2153
2153
  process.exit(1);
2154
2154
  }
2155
- const { runChat } = await import("./boot-B-eA8M6t.mjs");
2155
+ const { runChat } = await import("./boot-BEMmVR8C.mjs");
2156
2156
  await runChat({
2157
2157
  appUrl,
2158
2158
  sessionToken: session.value.sessionToken,
@@ -2199,6 +2199,60 @@ async function runPrintMode({ argv, appUrl }) {
2199
2199
  }
2200
2200
  }
2201
2201
 
2202
+ //#endregion
2203
+ //#region src/commands/messages.ts
2204
+ /**
2205
+ * `skydive messages get <messageId>` re-attaches to an exchange by message id
2206
+ * and prints the agent's reply.
2207
+ *
2208
+ * This is the recovery path for `chat -p`: a long run's stream can drop at the
2209
+ * edge (Cloudflare 502/504) after the message was accepted, leaving the caller
2210
+ * with a messageId but no result. The agent keeps working server-side, so a
2211
+ * blind retry would double-execute an agent that may have write access.
2212
+ * Instead, fetch the finished result by message id here — the server resolves
2213
+ * the run behind the message and replays it from its persisted event log, so
2214
+ * this works whether the run is still live or already done.
2215
+ *
2216
+ * Message ids are the currency of this API: `chat -p --json` reports the
2217
+ * `messageId` and this command consumes it. Run ids stay server-side.
2218
+ */
2219
+ const getCommand = {
2220
+ command: "get <message-id>",
2221
+ describe: "Fetch a message by id and print the reply (recovers a timed-out -p)",
2222
+ builder: (y) => y.positional("message-id", {
2223
+ type: "string",
2224
+ demandOption: true,
2225
+ describe: "Message id (the `messageId` from a prior `chat -p --json`)"
2226
+ }),
2227
+ handler: async (argv) => {
2228
+ const appUrl = resolveAppUrl({ appUrl: argv["api-url"] });
2229
+ const session = resolveSession({ appUrl });
2230
+ if (session.isErr()) {
2231
+ printError(`${session.error.message} Run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
2232
+ process.exit(1);
2233
+ }
2234
+ const { messageGet } = await Promise.resolve().then(() => print_exports);
2235
+ try {
2236
+ const result = await messageGet({
2237
+ appUrl,
2238
+ sessionToken: session.value.sessionToken,
2239
+ messageId: argv["message-id"],
2240
+ json: argv.json
2241
+ });
2242
+ if (argv.json) output(argv, result);
2243
+ } catch (error) {
2244
+ printError(error instanceof Error ? error.message : String(error));
2245
+ process.exit(1);
2246
+ }
2247
+ }
2248
+ };
2249
+ const messagesCommand = {
2250
+ command: "messages",
2251
+ describe: "Inspect chat messages and re-fetch agent replies",
2252
+ builder: (y) => y.command(getCommand).demandCommand(1, "Specify a subcommand: get"),
2253
+ handler: () => {}
2254
+ };
2255
+
2202
2256
  //#endregion
2203
2257
  //#region src/chat/api/rest.ts
2204
2258
  var HttpError = class extends Error {
@@ -2239,6 +2293,65 @@ function createRestClient({ appUrl, sessionToken }) {
2239
2293
  });
2240
2294
  if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
2241
2295
  }
2296
+ const streamEvents = async ({ path, label, signal, onEvent }) => {
2297
+ let lastEventId = null;
2298
+ let finished = false;
2299
+ let reconnects = 0;
2300
+ for (;;) {
2301
+ if (signal.aborted) return;
2302
+ try {
2303
+ const headers = {
2304
+ authorization: `Bearer ${sessionToken}`,
2305
+ accept: "text/event-stream"
2306
+ };
2307
+ if (lastEventId) headers["last-event-id"] = lastEventId;
2308
+ const res = await fetch(`${appUrl}${path}`, {
2309
+ headers,
2310
+ signal
2311
+ });
2312
+ if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
2313
+ reconnects = 0;
2314
+ const parser = createParser({ onEvent: (message) => {
2315
+ if (message.id) lastEventId = message.id;
2316
+ if (message.event === "error") {
2317
+ const { error } = streamErrorSchema.parse(JSON.parse(message.data));
2318
+ throw new Error(error);
2319
+ }
2320
+ const event = runStreamEventSchema.parse(JSON.parse(message.data));
2321
+ if (event.kind === "finished") finished = true;
2322
+ onEvent(event.kind === "finished" ? {
2323
+ ...event,
2324
+ error: event.error ?? null
2325
+ } : event);
2326
+ } });
2327
+ const decoder = new TextDecoder();
2328
+ const reader = res.body.getReader();
2329
+ try {
2330
+ for (;;) {
2331
+ const { done, value } = await reader.read();
2332
+ if (done) break;
2333
+ parser.feed(decoder.decode(value, { stream: true }));
2334
+ if (finished) return;
2335
+ }
2336
+ } finally {
2337
+ try {
2338
+ await reader.cancel();
2339
+ } catch (_error) {}
2340
+ }
2341
+ } catch (err) {
2342
+ if (signal.aborted) return;
2343
+ if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
2344
+ reconnects += 1;
2345
+ if (reconnects > MAX_STREAM_RECONNECTS) throw err;
2346
+ await sleep(Math.min(500 * 2 ** reconnects, 5e3));
2347
+ continue;
2348
+ }
2349
+ if (finished) return;
2350
+ reconnects += 1;
2351
+ if (reconnects > MAX_STREAM_RECONNECTS) throw new Error(`${label} stream ended unexpectedly`);
2352
+ await sleep(Math.min(500 * 2 ** reconnects, 5e3));
2353
+ }
2354
+ };
2242
2355
  return {
2243
2356
  listAgents: async ({ scope, onPage }) => {
2244
2357
  const all = [];
@@ -2365,65 +2478,18 @@ function createRestClient({ appUrl, sessionToken }) {
2365
2478
  });
2366
2479
  if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
2367
2480
  },
2368
- streamRun: async ({ runId, signal, onEvent }) => {
2369
- let lastEventId = null;
2370
- let finished = false;
2371
- let reconnects = 0;
2372
- for (;;) {
2373
- if (signal.aborted) return;
2374
- try {
2375
- const headers = {
2376
- authorization: `Bearer ${sessionToken}`,
2377
- accept: "text/event-stream"
2378
- };
2379
- if (lastEventId) headers["last-event-id"] = lastEventId;
2380
- const res = await fetch(`${appUrl}/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`, {
2381
- headers,
2382
- signal
2383
- });
2384
- if (!res.ok || !res.body) throw new HttpError(res.status, await res.text().catch(() => ""));
2385
- reconnects = 0;
2386
- const parser = createParser({ onEvent: (message) => {
2387
- if (message.id) lastEventId = message.id;
2388
- if (message.event === "error") {
2389
- const { error } = streamErrorSchema.parse(JSON.parse(message.data));
2390
- throw new Error(error);
2391
- }
2392
- const event = runStreamEventSchema.parse(JSON.parse(message.data));
2393
- if (event.kind === "finished") finished = true;
2394
- onEvent(event.kind === "finished" ? {
2395
- ...event,
2396
- error: event.error ?? null
2397
- } : event);
2398
- } });
2399
- const decoder = new TextDecoder();
2400
- const reader = res.body.getReader();
2401
- try {
2402
- for (;;) {
2403
- const { done, value } = await reader.read();
2404
- if (done) break;
2405
- parser.feed(decoder.decode(value, { stream: true }));
2406
- if (finished) return;
2407
- }
2408
- } finally {
2409
- try {
2410
- await reader.cancel();
2411
- } catch (_error) {}
2412
- }
2413
- } catch (err) {
2414
- if (signal.aborted) return;
2415
- if (err instanceof HttpError && err.status >= 400 && err.status < 500) throw err;
2416
- reconnects += 1;
2417
- if (reconnects > MAX_STREAM_RECONNECTS) throw err;
2418
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
2419
- continue;
2420
- }
2421
- if (finished) return;
2422
- reconnects += 1;
2423
- if (reconnects > MAX_STREAM_RECONNECTS) throw new Error("run stream ended unexpectedly");
2424
- await sleep(Math.min(500 * 2 ** reconnects, 5e3));
2425
- }
2426
- },
2481
+ streamRun: async ({ runId, signal, onEvent }) => streamEvents({
2482
+ path: `/api/v1/chat/runs/${encodeURIComponent(runId)}/stream`,
2483
+ label: "run",
2484
+ signal,
2485
+ onEvent
2486
+ }),
2487
+ streamMessage: async ({ messageId, signal, onEvent }) => streamEvents({
2488
+ path: `/api/v1/chat/messages/${encodeURIComponent(messageId)}/stream`,
2489
+ label: "message",
2490
+ signal,
2491
+ onEvent
2492
+ }),
2427
2493
  streamConversation: async ({ conversationId, signal, onEvent }) => {
2428
2494
  let reconnects = 0;
2429
2495
  for (;;) {
@@ -2554,6 +2620,7 @@ const recapResponseSchema = z.object({ recap: z.object({ text: z.string() }).nul
2554
2620
  const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
2555
2621
  const sendResultSchema = z.object({
2556
2622
  runId: z.string(),
2623
+ messageId: z.string().uuid().nullish(),
2557
2624
  conversationId: z.string().uuid(),
2558
2625
  isNewConversation: z.boolean(),
2559
2626
  steered: z.boolean().optional(),
@@ -2878,9 +2945,12 @@ function formatConnectCard(card) {
2878
2945
  //#endregion
2879
2946
  //#region src/chat/print.ts
2880
2947
  var print_exports = /* @__PURE__ */ __exportAll({
2948
+ collectRunText: () => collectRunText,
2949
+ messageGet: () => messageGet,
2881
2950
  readStdin: () => readStdin,
2882
2951
  resolveAgent: () => resolveAgent,
2883
- runPrint: () => runPrint
2952
+ runPrint: () => runPrint,
2953
+ toPrintError: () => toPrintError
2884
2954
  });
2885
2955
  /**
2886
2956
  * Non-interactive chat, à la `claude -p`. Sends a single prompt to an
@@ -2902,48 +2972,129 @@ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversat
2902
2972
  scope: "org",
2903
2973
  onPage: null
2904
2974
  }), agentSelector);
2905
- const send = await client.sendMessage({
2906
- agentId: agent.id,
2907
- conversationId,
2908
- content: prompt,
2909
- attachmentIds: [],
2910
- clientSurface: "cli"
2975
+ let send;
2976
+ try {
2977
+ send = await client.sendMessage({
2978
+ agentId: agent.id,
2979
+ conversationId,
2980
+ content: prompt,
2981
+ attachmentIds: [],
2982
+ clientSurface: "cli"
2983
+ });
2984
+ } catch (err) {
2985
+ throw toPrintError(err);
2986
+ }
2987
+ const { text, connectCards } = await collectRunText({
2988
+ client,
2989
+ appUrl,
2990
+ target: {
2991
+ kind: "run",
2992
+ id: send.runId
2993
+ },
2994
+ onText: json ? null : (delta) => process.stdout.write(delta),
2995
+ messageIdForHint: send.messageId ?? null
2911
2996
  });
2997
+ if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
2998
+ if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
2999
+ return {
3000
+ agentId: agent.id,
3001
+ agentName: agent.name,
3002
+ conversationId: send.conversationId,
3003
+ isNewConversation: send.isNewConversation,
3004
+ messageId: send.messageId ?? null,
3005
+ text,
3006
+ connectCards
3007
+ };
3008
+ }
3009
+ /**
3010
+ * Turn a transport failure into an actionable CLI error. A Cloudflare edge
3011
+ * 5xx (502/504) on a long run otherwise leaks a raw HTML/JSON error page to
3012
+ * stdout, which is impossible to act on. When a messageId is known and
3013
+ * recoverable we point the user at `skydive messages get <messageId>` rather
3014
+ * than a blind retry (a retry re-executes an agent that may have write access).
3015
+ */
3016
+ function toPrintError(err, messageId) {
3017
+ if (err instanceof HttpError && err.status >= 500) {
3018
+ const recovery = messageId ? ` The run may still be completing server-side. Do NOT blindly retry (it would re-run the agent). Fetch the result with: skydive messages get ${messageId}` : "";
3019
+ return /* @__PURE__ */ new Error(`The request to Skydive timed out at the edge (HTTP ${err.status}).${recovery}`);
3020
+ }
3021
+ return err instanceof Error ? err : new Error(String(err));
3022
+ }
3023
+ /**
3024
+ * Stream a run to completion, folding text-delta chunks into the reply and
3025
+ * collecting any connect cards (OAuth / MCP / credential requests) the run
3026
+ * posts. Shared by `chat -p` (streaming the run it just created) and `messages
3027
+ * get` (re-attaching by message id — the server replays a finished run from its
3028
+ * persisted log, so this works whether the run is live or already done).
3029
+ */
3030
+ async function collectRunText({ client, appUrl, target, onText, messageIdForHint }) {
2912
3031
  let text = "";
2913
3032
  const controller = new AbortController();
2914
3033
  let streamError = null;
2915
3034
  const connectCards = [];
2916
- await client.streamRun({
2917
- runId: send.runId,
2918
- signal: controller.signal,
2919
- onEvent: (event) => {
2920
- if (event.kind === "finished") {
2921
- if (event.error) streamError = event.error;
2922
- return;
2923
- }
2924
- const chunk = event.chunk;
2925
- if (chunk["type"] === "text-delta") {
2926
- const delta = typeof chunk["delta"] === "string" ? chunk["delta"] : typeof chunk["text"] === "string" ? chunk["text"] : "";
2927
- if (delta) {
2928
- text += delta;
2929
- if (!json) process.stdout.write(delta);
2930
- }
2931
- } else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
2932
- else {
2933
- const card = connectCardFromChunk(chunk, appUrl);
2934
- if (card) connectCards.push(card);
3035
+ const onEvent = (event) => {
3036
+ if (event.kind === "finished") {
3037
+ if (event.error) streamError = event.error;
3038
+ return;
3039
+ }
3040
+ const chunk = event.chunk;
3041
+ if (chunk["type"] === "text-delta") {
3042
+ const delta = typeof chunk["delta"] === "string" ? chunk["delta"] : typeof chunk["text"] === "string" ? chunk["text"] : "";
3043
+ if (delta) {
3044
+ text += delta;
3045
+ if (onText) onText(delta);
2935
3046
  }
3047
+ } else if (chunk["type"] === "error") streamError = typeof chunk["errorText"] === "string" ? chunk["errorText"] : "unknown error";
3048
+ else {
3049
+ const card = connectCardFromChunk(chunk, appUrl);
3050
+ if (card) connectCards.push(card);
2936
3051
  }
2937
- });
3052
+ };
3053
+ try {
3054
+ if (target.kind === "message") await client.streamMessage({
3055
+ messageId: target.id,
3056
+ signal: controller.signal,
3057
+ onEvent
3058
+ });
3059
+ else await client.streamRun({
3060
+ runId: target.id,
3061
+ signal: controller.signal,
3062
+ onEvent
3063
+ });
3064
+ } catch (err) {
3065
+ throw toPrintError(err, messageIdForHint);
3066
+ }
2938
3067
  if (streamError) throw new Error(streamError);
3068
+ return {
3069
+ text,
3070
+ connectCards
3071
+ };
3072
+ }
3073
+ /**
3074
+ * Re-attach to an exchange by message id and print its reply. Backs `skydive
3075
+ * messages get <messageId>` — the recovery path when a `chat -p` stream dropped
3076
+ * at the edge after the message was accepted. The server resolves the run
3077
+ * behind the message and replays it from its persisted event log, so this
3078
+ * returns the full reply whether the run is still live or already done.
3079
+ */
3080
+ async function messageGet({ appUrl, sessionToken, messageId, json }) {
3081
+ const { text, connectCards } = await collectRunText({
3082
+ client: createRestClient({
3083
+ appUrl,
3084
+ sessionToken
3085
+ }),
3086
+ appUrl,
3087
+ target: {
3088
+ kind: "message",
3089
+ id: messageId
3090
+ },
3091
+ onText: json ? null : (delta) => process.stdout.write(delta),
3092
+ messageIdForHint: null
3093
+ });
2939
3094
  if (!json && text && !text.endsWith("\n")) process.stdout.write("\n");
2940
3095
  if (!json) for (const card of connectCards) process.stdout.write(`${formatConnectCard(card)}\n`);
2941
3096
  return {
2942
- agentId: agent.id,
2943
- agentName: agent.name,
2944
- conversationId: send.conversationId,
2945
- isNewConversation: send.isNewConversation,
2946
- runId: send.runId,
3097
+ messageId,
2947
3098
  text,
2948
3099
  connectCards
2949
3100
  };
@@ -3179,7 +3330,7 @@ const switchCommand = {
3179
3330
  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.");
3180
3331
  process.exit(1);
3181
3332
  }
3182
- const { runWorkspacePicker } = await import("./boot-B-eA8M6t.mjs");
3333
+ const { runWorkspacePicker } = await import("./boot-BEMmVR8C.mjs");
3183
3334
  await runWorkspacePicker(session);
3184
3335
  return;
3185
3336
  }
@@ -3233,7 +3384,7 @@ function createCli(argv) {
3233
3384
  type: "string",
3234
3385
  global: true,
3235
3386
  describe: "Override API base URL"
3236
- }).command(authCommand).command(chatCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
3387
+ }).command(authCommand).command(chatCommand).command(messagesCommand).command(conversationsCommand).command(agentsCommand).command(keysCommand).command(secretsCommand).command(workspaceCommand).demandCommand(1, "Specify a command. Run --help for usage.").strict().wrap(null).version(version$1).alias("v", "version").alias("h", "help").help().fail((msg, err) => {
3237
3388
  printError(err ? err instanceof Error ? err.message : String(err) : msg ?? "Unknown error");
3238
3389
  process.exit(1);
3239
3390
  });
@@ -1953,6 +1953,30 @@ function parseDroppedPaths(text) {
1953
1953
  }
1954
1954
  return paths;
1955
1955
  }
1956
+ /** Decide how to handle a paste. `kind: 'binary'` events carry the mime of
1957
+ * bytes the terminal forwarded; text events carry the decoded paste text.
1958
+ *
1959
+ * The empty-text case is the fix for "can't paste an image": on macOS the
1960
+ * reflex is Cmd+V, which the terminal turns into a bracketed *text* paste, so
1961
+ * a screenshot on the clipboard arrives as empty bytes. We route that to an OS
1962
+ * clipboard read instead of dropping it, matching the explicit ctrl+v path. */
1963
+ function routePaste(input) {
1964
+ if (input.kind === "binary") {
1965
+ if (input.mimeType?.startsWith("image/")) return {
1966
+ kind: "binary-image",
1967
+ mediaType: input.mimeType
1968
+ };
1969
+ return { kind: "text" };
1970
+ }
1971
+ const paths = parseDroppedPaths(input.text);
1972
+ if (paths) return {
1973
+ kind: "dropped-paths",
1974
+ paths,
1975
+ text: input.text
1976
+ };
1977
+ if (input.text.trim() === "") return { kind: "clipboard-image" };
1978
+ return { kind: "text" };
1979
+ }
1956
1980
  /**
1957
1981
  * Resolve path candidates into images using the filesystem and file magic,
1958
1982
  * not extensions. The whole batch must be regular image files; otherwise the
@@ -5171,31 +5195,40 @@ function ChatScreen({ agent, conversation }) {
5171
5195
  }, [stageImages]);
5172
5196
  usePaste((event) => {
5173
5197
  if (modelPickerOpen || themePickerOpen || helpOpen || credPromptOpen || grantPrompt) return;
5174
- const mime = event.metadata?.mimeType;
5175
- if (event.metadata?.kind === "binary") {
5176
- if (mime?.startsWith("image/")) {
5198
+ const text = event.metadata?.kind === "binary" ? "" : decodePasteBytes(event.bytes);
5199
+ const route = routePaste({
5200
+ kind: event.metadata?.kind,
5201
+ mimeType: event.metadata?.mimeType,
5202
+ text
5203
+ });
5204
+ switch (route.kind) {
5205
+ case "binary-image": {
5177
5206
  event.preventDefault();
5178
- const ext = mime.split("/")[1]?.split("+")[0] ?? "png";
5207
+ const ext = route.mediaType.split("/")[1]?.split("+")[0] ?? "png";
5179
5208
  stageImages([{
5180
5209
  fileName: `pasted-image-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.${ext}`,
5181
- mediaType: mime,
5210
+ mediaType: route.mediaType,
5182
5211
  data: event.bytes
5183
5212
  }]);
5184
- }
5185
- return;
5186
- }
5187
- const text = decodePasteBytes(event.bytes);
5188
- const paths = parseDroppedPaths(text);
5189
- if (!paths) return;
5190
- event.preventDefault();
5191
- resolveDroppedImages(paths).then((images) => {
5192
- if (images) {
5193
- stageImages(images);
5194
5213
  return;
5195
5214
  }
5196
- composerRef.current?.insertText(text);
5197
- handleComposerChange();
5198
- });
5215
+ case "clipboard-image":
5216
+ event.preventDefault();
5217
+ pasteImage();
5218
+ return;
5219
+ case "dropped-paths":
5220
+ event.preventDefault();
5221
+ resolveDroppedImages(route.paths).then((images) => {
5222
+ if (images) {
5223
+ stageImages(images);
5224
+ return;
5225
+ }
5226
+ composerRef.current?.insertText(route.text);
5227
+ handleComposerChange();
5228
+ });
5229
+ return;
5230
+ case "text": return;
5231
+ }
5199
5232
  });
5200
5233
  const applyComposerText = useCallback((text, caret) => {
5201
5234
  const composer = composerRef.current;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.1.0-beta.163",
3
+ "version": "0.1.0-beta.186",
4
4
  "description": "Skydive CLI — manage AI agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",