skydive-cli 0.1.0-beta.390 → 0.1.0-beta.393

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/js/bin.mjs CHANGED
@@ -19,7 +19,7 @@ import semver from "semver";
19
19
 
20
20
  //#region package.json
21
21
  var name = "skydive-cli";
22
- var version = "0.1.0-beta.390";
22
+ var version = "0.1.0-beta.393";
23
23
 
24
24
  //#endregion
25
25
  //#region src/types.ts
@@ -1423,7 +1423,7 @@ const chatCommand = {
1423
1423
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
1424
1424
  process.exit(1);
1425
1425
  }
1426
- const { runChat } = await import("./boot-Ce1QLo2p.mjs");
1426
+ const { runChat } = await import("./boot-B-AxTj84.mjs");
1427
1427
  await runChat({
1428
1428
  appUrl,
1429
1429
  sessionToken: session.value.sessionToken,
@@ -1735,7 +1735,7 @@ const switchCommand = {
1735
1735
  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.");
1736
1736
  process.exit(1);
1737
1737
  }
1738
- const { runWorkspacePicker } = await import("./boot-Ce1QLo2p.mjs");
1738
+ const { runWorkspacePicker } = await import("./boot-B-AxTj84.mjs");
1739
1739
  await runWorkspacePicker(session);
1740
1740
  return;
1741
1741
  }
@@ -3637,6 +3637,198 @@ function mapToolState(raw) {
3637
3637
  }
3638
3638
  }
3639
3639
 
3640
+ //#endregion
3641
+ //#region src/chat/tui/chat/todos.ts
3642
+ const todoItemSchema = z.object({
3643
+ id: z.string(),
3644
+ content: z.string(),
3645
+ status: z.enum([
3646
+ "pending",
3647
+ "in_progress",
3648
+ "completed",
3649
+ "failed"
3650
+ ]),
3651
+ position: z.number()
3652
+ });
3653
+ const todosPayloadSchema = z.object({ todos: z.array(todoItemSchema) });
3654
+ /**
3655
+ * The todos carried by a `data-anyone-todos-updated` chunk/part `data`
3656
+ * payload, or null when the payload isn't the documented shape. Used for both
3657
+ * the live stream (reducer-adjacent, in chat.tsx) and history parts, which
3658
+ * carry the identical `{ data: { todos } }` shape.
3659
+ */
3660
+ function todosFromPayload(data) {
3661
+ const parsed = todosPayloadSchema.safeParse(data);
3662
+ return parsed.success ? parsed.data.todos : null;
3663
+ }
3664
+ /**
3665
+ * The conversation's current todo list from loaded history: the newest
3666
+ * `data-anyone-todos-updated` part in the transcript. Every `platform todo
3667
+ * write` appends a fresh snapshot rather than editing one in place, so the
3668
+ * last part wins and earlier ones are history — matching the web client's
3669
+ * `latestTodoSnapshot`.
3670
+ *
3671
+ * Returns null when the agent never wrote a list, and when the newest snapshot
3672
+ * is empty: `platform todo dismiss` writes an empty list, so an empty newest
3673
+ * snapshot means "the plan is gone", not "fall back to the previous one".
3674
+ */
3675
+ function latestTodosFromHistory(messages) {
3676
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
3677
+ const parts = messages[i]?.parts ?? [];
3678
+ for (let j = parts.length - 1; j >= 0; j -= 1) {
3679
+ const part = parts[j];
3680
+ if (!part || part.type !== "data-anyone-todos-updated") continue;
3681
+ const data = isRecord(part) && "data" in part && isRecord(part.data) ? part.data : null;
3682
+ const todos = data ? todosFromPayload(data) : null;
3683
+ if (!todos) return null;
3684
+ return todos.length > 0 ? todos : null;
3685
+ }
3686
+ }
3687
+ return null;
3688
+ }
3689
+ /** How many todo rows the card shows before it caps the tail with "+N more". */
3690
+ const MAX_VISIBLE_ROWS = 6;
3691
+ function windowTodos(todos, cap = MAX_VISIBLE_ROWS) {
3692
+ const sorted = [...todos].sort((a, b) => a.position - b.position);
3693
+ let leadingDone = 0;
3694
+ while (leadingDone < sorted.length && sorted[leadingDone]?.status === "completed") leadingDone += 1;
3695
+ const head = leadingDone >= 2 || sorted.length > cap ? leadingDone : 0;
3696
+ const rest = sorted.slice(head);
3697
+ const rowBudget = Math.max(1, head > 0 ? cap - 1 : cap);
3698
+ if (rest.length <= rowBudget) return {
3699
+ doneSummary: head,
3700
+ rows: rest,
3701
+ moreCount: 0
3702
+ };
3703
+ const windowSize = Math.max(1, rowBudget - 1);
3704
+ const shown = rest.slice(0, windowSize);
3705
+ return {
3706
+ doneSummary: head,
3707
+ rows: shown,
3708
+ moreCount: rest.length - shown.length
3709
+ };
3710
+ }
3711
+ /**
3712
+ * Total terminal rows the card occupies for `todos`, so the chat screen can
3713
+ * reserve exactly this much and keep the composer pinned. Header + optional
3714
+ * done-summary line + rendered rows + optional "+N more" tail + top margin.
3715
+ * Must track `windowTodos` and `TodoCardView`'s layout.
3716
+ */
3717
+ function todoCardHeight(todos) {
3718
+ if (todos.length === 0) return 0;
3719
+ const w = windowTodos(todos);
3720
+ const summaryRow = w.doneSummary > 0 ? 1 : 0;
3721
+ const moreRow = w.moreCount > 0 ? 1 : 0;
3722
+ return 1 + summaryRow + w.rows.length + moreRow + 1;
3723
+ }
3724
+
3725
+ //#endregion
3726
+ //#region src/chat/tui/chat/todo-card.tsx
3727
+ function glyph(status) {
3728
+ switch (status) {
3729
+ case "completed": return {
3730
+ char: "✔",
3731
+ fg: theme.success
3732
+ };
3733
+ case "in_progress": return {
3734
+ char: "▶",
3735
+ fg: theme.accent
3736
+ };
3737
+ case "failed": return {
3738
+ char: "✗",
3739
+ fg: theme.error
3740
+ };
3741
+ default: return {
3742
+ char: "○",
3743
+ fg: theme.dim
3744
+ };
3745
+ }
3746
+ }
3747
+ function rowColor(status) {
3748
+ switch (status) {
3749
+ case "completed": return theme.dim;
3750
+ case "in_progress": return theme.fg;
3751
+ case "failed": return theme.muted;
3752
+ default: return theme.muted;
3753
+ }
3754
+ }
3755
+ /**
3756
+ * The agent's live todo list, pinned just above the composer — the TUI analog
3757
+ * of the web chat's todo card. Rendered only once the agent has actually
3758
+ * written a list (`platform todo write`) and until it clears one, so a
3759
+ * conversation the agent never wrote todos for grows no chrome for it.
3760
+ *
3761
+ * The card can't scroll like the web card, so it windows (see `windowTodos`):
3762
+ * a leading run of completed todos folds into a `✔ N done` line, the active
3763
+ * and upcoming rows fill the rest, and a `+N more` tail counts pending work
3764
+ * past the cap. The window follows the in-progress row down as todos complete.
3765
+ *
3766
+ * `isActive` dims the header while the run is idle: an in-progress row only
3767
+ * reads as "happening now" when a run is actually producing output.
3768
+ */
3769
+ function TodoCardView({ todos, isActive }) {
3770
+ if (todos.length === 0) return null;
3771
+ const completed = todos.filter((t) => t.status === "completed").length;
3772
+ const { doneSummary, rows, moreCount } = windowTodos(todos);
3773
+ return /* @__PURE__ */ jsxs("box", {
3774
+ style: {
3775
+ flexDirection: "column",
3776
+ flexShrink: 0,
3777
+ marginTop: 1,
3778
+ paddingLeft: 1,
3779
+ paddingRight: 1
3780
+ },
3781
+ children: [
3782
+ /* @__PURE__ */ jsxs("box", {
3783
+ style: { flexDirection: "row" },
3784
+ children: [/* @__PURE__ */ jsx("text", {
3785
+ fg: isActive ? theme.accent : theme.muted,
3786
+ children: /* @__PURE__ */ jsx("b", { children: "Plan" })
3787
+ }), /* @__PURE__ */ jsxs("text", {
3788
+ fg: theme.dim,
3789
+ children: [
3790
+ " ",
3791
+ completed,
3792
+ "/",
3793
+ todos.length
3794
+ ]
3795
+ })]
3796
+ }),
3797
+ doneSummary > 0 ? /* @__PURE__ */ jsxs("box", {
3798
+ style: { flexDirection: "row" },
3799
+ children: [/* @__PURE__ */ jsx("text", {
3800
+ fg: theme.success,
3801
+ children: "✔ "
3802
+ }), /* @__PURE__ */ jsxs("text", {
3803
+ fg: theme.dim,
3804
+ children: [doneSummary, " done"]
3805
+ })]
3806
+ }) : null,
3807
+ rows.map((todo) => {
3808
+ const g = glyph(todo.status);
3809
+ return /* @__PURE__ */ jsxs("box", {
3810
+ style: { flexDirection: "row" },
3811
+ children: [/* @__PURE__ */ jsxs("text", {
3812
+ fg: g.fg,
3813
+ children: [g.char, " "]
3814
+ }), /* @__PURE__ */ jsx("text", {
3815
+ fg: rowColor(todo.status),
3816
+ children: todo.content
3817
+ })]
3818
+ }, todo.id);
3819
+ }),
3820
+ moreCount > 0 ? /* @__PURE__ */ jsxs("text", {
3821
+ fg: theme.dim,
3822
+ children: [
3823
+ " … +",
3824
+ moreCount,
3825
+ " more"
3826
+ ]
3827
+ }) : null
3828
+ ]
3829
+ });
3830
+ }
3831
+
3640
3832
  //#endregion
3641
3833
  //#region src/chat/tui/chat/footer-hints.ts
3642
3834
  /** Build the chat footer copy independently of its colors and layout. */
@@ -7481,6 +7673,7 @@ function ChatScreen({ agent, conversation }) {
7481
7673
  const agentHost = useAgentHost();
7482
7674
  const [conversationId, setConversationId] = useState(initialConversationId);
7483
7675
  const [items, setItems] = useState([]);
7676
+ const [todos, setTodos] = useState(null);
7484
7677
  const [historyLoaded, setHistoryLoaded] = useState(initialConversationId === null);
7485
7678
  const [input, setInput] = useState("");
7486
7679
  const [run, setRun] = useState({ kind: "idle" });
@@ -7564,7 +7757,9 @@ function ChatScreen({ agent, conversation }) {
7564
7757
  const chatHeight = bodyHeight;
7565
7758
  const inlineReviewHeight = reviewOpen && reviewPlacement === "below" ? reviewHeight : 0;
7566
7759
  const menuRows = menuOpen ? completionMenuHeight(menuCommands.length) : 0;
7567
- const scrollHeight = Math.max(3, chatHeight - 2 - bottomBoxHeight - pendingRows - inlineReviewHeight - menuRows);
7760
+ const todoCardVisible = !credPrompt && !!todos && todos.length > 0;
7761
+ const todoRows = todoCardVisible ? todoCardHeight(todos) : 0;
7762
+ const scrollHeight = Math.max(3, chatHeight - 2 - bottomBoxHeight - pendingRows - inlineReviewHeight - menuRows - todoRows);
7568
7763
  useEffect(() => {
7569
7764
  if (reviewOpen && reviewFocused) {
7570
7765
  const composer = composerRef.current;
@@ -7612,6 +7807,7 @@ function ChatScreen({ agent, conversation }) {
7612
7807
  text: recapText
7613
7808
  });
7614
7809
  setItems(loaded);
7810
+ setTodos(latestTodosFromHistory(messages));
7615
7811
  } catch (err) {
7616
7812
  if (cancelled) return;
7617
7813
  setItems((prev) => [...prev, {
@@ -7644,6 +7840,11 @@ function ChatScreen({ agent, conversation }) {
7644
7840
  onEvent: (event) => {
7645
7841
  if (event.kind === "chunk") {
7646
7842
  responsePreview.addChunk(event.chunk);
7843
+ if (event.chunk.type === "data-anyone-todos-updated") {
7844
+ const next = todosFromPayload(event.chunk.data);
7845
+ if (next) setTodos(next.length > 0 ? next : null);
7846
+ return;
7847
+ }
7647
7848
  setItems((prev) => applyChunk(prev, event.chunk, streamAgentName));
7648
7849
  return;
7649
7850
  }
@@ -8751,6 +8952,10 @@ function ChatScreen({ agent, conversation }) {
8751
8952
  }) }), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, { label: run.kind === "sending" ? "sending" : "working" }) : null]
8752
8953
  })
8753
8954
  }),
8955
+ todoCardVisible && todos ? /* @__PURE__ */ jsx(TodoCardView, {
8956
+ todos,
8957
+ isActive: run.kind !== "idle"
8958
+ }) : null,
8754
8959
  pendingVisible ? /* @__PURE__ */ jsxs("box", {
8755
8960
  style: {
8756
8961
  paddingLeft: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.1.0-beta.390",
3
+ "version": "0.1.0-beta.393",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",