@gamflaz/opencode-todolist 1.0.4 → 1.0.6

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.
Files changed (38) hide show
  1. package/README.md +32 -31
  2. package/dist/icons/{capabilities.d.ts → image-capabilities.d.ts} +1 -1
  3. package/dist/icons/{convert.js → svg-icon.js} +1 -1
  4. package/dist/icons/{icons.d.ts → svg-icons.d.ts} +1 -0
  5. package/dist/icons/{icons.js → svg-icons.js} +1 -0
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +6 -6
  8. package/dist/{schema/store.schema.d.ts → schemas/platform.d.ts} +1 -0
  9. package/dist/{schema/store.schema.js → schemas/platform.js} +1 -0
  10. package/dist/{schema/status.schema.d.ts → schemas/status.d.ts} +2 -0
  11. package/dist/{schema/status.schema.js → schemas/status.js} +8 -0
  12. package/dist/{schema/todo.schema.js → schemas/todo.js} +1 -1
  13. package/dist/{store → storage}/errors.d.ts +1 -0
  14. package/dist/{store → storage}/errors.js +1 -0
  15. package/dist/{store/store.d.ts → storage/todo-store.d.ts} +1 -1
  16. package/dist/{store/store.js → storage/todo-store.js} +2 -2
  17. package/dist/{todo → todos}/display.d.ts +2 -2
  18. package/dist/{todo → todos}/display.js +1 -1
  19. package/dist/{todo → todos}/hint.d.ts +1 -1
  20. package/dist/{todo → todos}/hint.js +2 -2
  21. package/dist/{todo/todo.d.ts → todos/todo-tree.d.ts} +2 -2
  22. package/dist/{todo/todo.js → todos/todo-tree.js} +1 -1
  23. package/dist/{todo → todos}/update.d.ts +2 -2
  24. package/dist/tui/layout.d.ts +18 -0
  25. package/dist/tui/layout.js +22 -0
  26. package/dist/tui/text.d.ts +2 -0
  27. package/dist/tui/text.js +4 -0
  28. package/dist/tui/todo-panel.d.ts +77 -0
  29. package/dist/tui/todo-panel.js +742 -0
  30. package/dist/tui.d.ts +0 -97
  31. package/dist/tui.js +16 -776
  32. package/package.json +7 -2
  33. /package/dist/icons/{capabilities.js → image-capabilities.js} +0 -0
  34. /package/dist/icons/{convert.d.ts → svg-icon.d.ts} +0 -0
  35. /package/dist/{schema/todo.schema.d.ts → schemas/todo.d.ts} +0 -0
  36. /package/dist/skills/{skill.d.ts → loader.d.ts} +0 -0
  37. /package/dist/skills/{skill.js → loader.js} +0 -0
  38. /package/dist/{todo → todos}/update.js +0 -0
package/README.md CHANGED
@@ -24,11 +24,27 @@ bun install
24
24
  bun run build
25
25
  ```
26
26
 
27
+ Or add it directly through the OpenCode CLI:
28
+
29
+ ```bash
30
+ opencode plugin add @gamflaz/opencode-todolist
31
+ ```
32
+
27
33
  OpenCode loads local plugins from the plugins directory, but it does not install
28
34
  their dependencies, so `bun install` is required before use.
29
35
 
30
36
  ## Tool: `manage_todo_list`
31
37
 
38
+ #### Operation fields
39
+
40
+ | Operation | Required fields | Optional fields |
41
+ | --------- | ------------------------------- | ------------------------------ |
42
+ | `read` | `operation` | None |
43
+ | `write` | `operation`, `todos` | None; use `todos: []` to clear |
44
+ | `update` | `operation`, `id`, `status` | `content`, `priority`, `note` |
45
+
46
+ For `update`, send `null` for `priority` or `note` to remove the existing value.
47
+
32
48
  ### Read
33
49
 
34
50
  ```json
@@ -77,6 +93,21 @@ Clear the current list:
77
93
  { "operation": "write", "todos": [] }
78
94
  ```
79
95
 
96
+ ### Fields
97
+
98
+ #### Todo item fields
99
+
100
+ | Field | Type | Required | Description |
101
+ | ---------- | --------------------------------- | --------------- | ------------------------------------------------------------------------- |
102
+ | `id` | number | Yes | Root: positive integer. Child: `<root>.<n>`, such as `1.1`. |
103
+ | `content` | string | Yes | Task summary. |
104
+ | `status` | enum | Yes | `pending`, `in_progress`, `completed`, `cancelled`, or `error`. |
105
+ | `priority` | `high` \| `medium` \| `low` | No | Optional priority label. |
106
+ | `note` | string | No | Technical context or evidence. |
107
+ | `children` | array of child items | No, root only | Up to 9 subtasks. Child items cannot contain more children. |
108
+
109
+ Root IDs must be unique. A child ID must start with its parent root ID.
110
+
80
111
  ### Update
81
112
 
82
113
  `update` changes one existing root or subtask. Send only the fields that change;
@@ -91,7 +122,7 @@ Clear the current list:
91
122
  }
92
123
  ```
93
124
 
94
- Field values:
125
+ ### Fields
95
126
 
96
127
  | Field | Required | Notes |
97
128
  | ---------- | -------- | ------------------------------------------------------------ |
@@ -101,36 +132,6 @@ Field values:
101
132
  | `note` | No | Technical context or evidence. Pass `null` to remove. |
102
133
  | `priority` | No | `high`, `medium`, or `low`. Pass `null` to remove. |
103
134
 
104
- ## Rules
105
-
106
- 1. **One root at a time, in order.** Root `2` cannot start while root `1` is
107
- still open. Finish or cancel root `1` first.
108
- 2. **One active subtask per root.** Starting a second `in_progress` subtask is
109
- rejected until the first one finishes or is cancelled.
110
- 3. **Forward-only progress.** `in_progress` never returns to `pending`, and
111
- `completed` or `cancelled` items are locked.
112
- 4. **Error exception.** A root that reports `error` may be fixed early, but only
113
- that root and its own subtasks; every other root still waits its turn.
114
- 5. **Evidence in `note`.** Record what was verified before marking an item
115
- `completed`.
116
-
117
- Every response reports progress, the next step, and any status transitions, for
118
- example:
119
-
120
- ```text
121
- Todos (2/4) updated. Next: 2 "Write tests". Continue immediately.
122
- ```
123
-
124
- ## TUI widget
125
-
126
- The widget sits above the session composer:
127
-
128
- - Collapsed view shows the progress count and the active item.
129
- - Expand it to see every task; long lists scroll.
130
- - `↻ Continue` asks the session to resume the next open item with a short
131
- summary of what remains.
132
- - `✕` clears the list.
133
-
134
135
  ## Skills
135
136
 
136
137
  `skills/todolist` and `skills/continue-todolist` ship with the plugin and are
@@ -1,5 +1,5 @@
1
1
  import { type TerminalCapabilities } from "@opentui/core";
2
- /** Minimal capability view needed to decide between image and text icons. */
2
+ /** Minimal image capability view needed to decide between image and text icons. */
3
3
  export type ImageCapabilitySnapshot = Pick<TerminalCapabilities, "kitty_graphics" | "sixel"> & Partial<Pick<TerminalCapabilities, "image_protocol" | "multiplexer">>;
4
4
  /**
5
5
  * Whether the terminal can render raster icons. Terminals without Kitty or
@@ -11,7 +11,7 @@ import { usePlugin } from "@opencode/plugin/tui";
11
11
  import { rgbToHex } from "@opentui/core";
12
12
  import { Resvg } from "@resvg/resvg-js";
13
13
  import { createMemo, createSignal, onCleanup, Show } from "solid-js";
14
- import { supportsImageRendering } from "./capabilities";
14
+ import { supportsImageRendering } from "./image-capabilities";
15
15
  export function SvgIcon(props) {
16
16
  const {
17
17
  source: _,
@@ -1,3 +1,4 @@
1
+ /** Inline SVG icons used by the TUI. */
1
2
  export declare const removeSvgIcon = "\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-mop-sparkles preview-icon\"><path d=\"M10 22a3 3 0 01-3-3\"/><path d=\"M10 22c2.761 0 5-1.79 5-4-4.42 0-4.08-5-8.5-5a4.501 4.501 0 000 9z\"/><path d=\"M10 3H8\"/><path d=\"M12.5 11.5 22 2\"/><path d=\"M20 13v4\"/><path d=\"M22 15h-4\"/><path d=\"M4 5v4\"/><path d=\"M6 7H2\"/><path d=\"m6.98 13.02 2.665-2.664a1.21 1.21 0 011.71 0l2.29 2.288a1.21 1.21 0 010 1.712l-2.088 2.087\"/><path d=\"M9 2v2\"/></svg>\n";
2
3
  export declare const arrowDownSvgIcon = "\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\"/>\n</svg>\n";
3
4
  export declare const arrowRightSvgIcon = "\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\"/>\n</svg>\n";
@@ -1,3 +1,4 @@
1
+ /** Inline SVG icons used by the TUI. */
1
2
  export const removeSvgIcon = `
2
3
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mop-sparkles preview-icon"><path d="M10 22a3 3 0 01-3-3"/><path d="M10 22c2.761 0 5-1.79 5-4-4.42 0-4.08-5-8.5-5a4.501 4.501 0 000 9z"/><path d="M10 3H8"/><path d="M12.5 11.5 22 2"/><path d="M20 13v4"/><path d="M22 15h-4"/><path d="M4 5v4"/><path d="M6 7H2"/><path d="m6.98 13.02 2.665-2.664a1.21 1.21 0 011.71 0l2.29 2.288a1.21 1.21 0 010 1.712l-2.088 2.087"/><path d="M9 2v2"/></svg>
3
4
  `;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Plugin } from "@opencode/plugin";
2
- import { type TodoRootItem } from "./schema/todo.schema";
2
+ import { type TodoRootItem } from "./schemas/todo";
3
3
  export declare function formatReadOutput(todos: readonly TodoRootItem[]): string;
4
4
  export declare function formatTransitionLines(before: readonly TodoRootItem[], after: readonly TodoRootItem[]): string[];
5
5
  export declare function formatWriteOutput(todos: readonly TodoRootItem[], before?: readonly TodoRootItem[]): string;
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Plugin } from "@opencode/plugin";
2
- import { TodoInputToolSchema, TodoOutputToolSchema, } from "./schema/todo.schema";
3
- import { registerTodoSkills } from "./skills/skill";
4
- import { readTodoData, readTodos, updateTodos } from "./store/store";
5
- import { buildTodoHint, consumeTodoHint, findNextTodo, findStuckParent, flattenLeaves, isAnyTodoActive, } from "./todo/hint";
6
- import { TodoTree } from "./todo/todo";
7
- import { updateTodoById } from "./todo/update";
2
+ import { TodoInputToolSchema, TodoOutputToolSchema, } from "./schemas/todo";
3
+ import { registerTodoSkills } from "./skills/loader";
4
+ import { readTodoData, readTodos, updateTodos } from "./storage/todo-store";
5
+ import { buildTodoHint, consumeTodoHint, findNextTodo, findStuckParent, flattenLeaves, isAnyTodoActive, } from "./todos/hint";
6
+ import { TodoTree } from "./todos/todo-tree";
7
+ import { updateTodoById } from "./todos/update";
8
8
  export function formatReadOutput(todos) {
9
9
  if (todos.length === 0)
10
10
  return "No todos recorded.";
@@ -1,4 +1,5 @@
1
1
  import z from "zod";
2
+ /** Platform fields used to resolve the todo storage location. */
2
3
  export declare const PlatformContextSchema: z.ZodObject<{
3
4
  platform: z.ZodOptional<z.ZodEnum<{
4
5
  aix: "aix";
@@ -1,4 +1,5 @@
1
1
  import z from "zod";
2
+ /** Platform fields used to resolve the todo storage location. */
2
3
  export const PlatformContextSchema = z.object({
3
4
  platform: z
4
5
  .enum([
@@ -14,3 +14,5 @@ export declare const PrioritySchema: z.ZodEnum<{
14
14
  }>;
15
15
  export type Priority = z.infer<typeof PrioritySchema>;
16
16
  export declare const OPEN_STATUSES: readonly TodoStatus[];
17
+ /** Human-readable labels used in the resume prompt. */
18
+ export declare const StatusLabel: Record<TodoStatus, string>;
@@ -12,3 +12,11 @@ export const OPEN_STATUSES = [
12
12
  "in_progress",
13
13
  "error",
14
14
  ];
15
+ /** Human-readable labels used in the resume prompt. */
16
+ export const StatusLabel = {
17
+ pending: "not started",
18
+ in_progress: "currently being worked on",
19
+ completed: "completed",
20
+ cancelled: "cancelled",
21
+ error: "needs fixing",
22
+ };
@@ -1,5 +1,5 @@
1
1
  import z from "zod";
2
- import { PrioritySchema, TodoStatusSchema } from "./status.schema";
2
+ import { PrioritySchema, TodoStatusSchema } from "./status";
3
3
  // Child id format: <root_id>.<n> as a number -> 1.1, 2.3, etc.
4
4
  // Only a single decimal digit (1-9) is allowed, see the note below.
5
5
  const ChildIdSchema = z
@@ -1,3 +1,4 @@
1
+ /** Error raised when persisted todo data cannot be read safely. */
1
2
  export declare class InvalidTodoDataError extends Error {
2
3
  constructor(message: string);
3
4
  }
@@ -1,3 +1,4 @@
1
+ /** Error raised when persisted todo data cannot be read safely. */
1
2
  export class InvalidTodoDataError extends Error {
2
3
  constructor(message) {
3
4
  super(message);
@@ -1,4 +1,4 @@
1
- import { type TodoRootItem } from "../schema/todo.schema";
1
+ import { type TodoRootItem } from "../schemas/todo";
2
2
  /**
3
3
  * Environment/OS context used to resolve storage locations. All fields are
4
4
  * optional so callers (and tests) can simulate any platform on any host.
@@ -4,8 +4,8 @@ import { chmod, mkdir, readFile, rename, rm, writeFile, } from "node:fs/promises
4
4
  import { homedir, tmpdir } from "node:os";
5
5
  import { join, posix, win32 } from "node:path";
6
6
  import * as lockfile from "proper-lockfile";
7
- import { TodoRootItemSchema } from "../schema/todo.schema";
8
- import { TodoTree } from "../todo/todo";
7
+ import { TodoRootItemSchema } from "../schemas/todo";
8
+ import { TodoTree } from "../todos/todo-tree";
9
9
  import { InvalidTodoDataError } from "./errors";
10
10
  /**
11
11
  * Per-session todo persistence.
@@ -1,5 +1,5 @@
1
- import type { TodoStatus } from "../schema/status.schema";
2
- import type { TodoRootItem } from "../schema/todo.schema";
1
+ import type { TodoStatus } from "../schemas/status";
2
+ import type { TodoRootItem } from "../schemas/todo";
3
3
  export interface TodoDisplayItem {
4
4
  id: number;
5
5
  content: string;
@@ -1,4 +1,4 @@
1
- import { TodoTree } from "./todo";
1
+ import { TodoTree } from "./todo-tree";
2
2
  export function getStatusIcon(status) {
3
3
  switch (status) {
4
4
  case "completed":
@@ -1,4 +1,4 @@
1
- import type { TodoChildern, TodoRootItem } from "../schema/todo.schema";
1
+ import type { TodoChildern, TodoRootItem } from "../schemas/todo";
2
2
  export declare const DEFAULT_STALE_MS: number;
3
3
  export declare const CONTEXT_STALE_MS: number;
4
4
  export declare const TODO_HINT: string;
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, renameSync, writeFileSync } from "node:fs";
2
- import { getHintMarkerPath, readTodoData, TodoStore } from "../store/store";
3
- import { TodoTree } from "./todo";
2
+ import { getHintMarkerPath, readTodoData, TodoStore } from "../storage/todo-store";
3
+ import { TodoTree } from "./todo-tree";
4
4
  export const DEFAULT_STALE_MS = 30 * 60 * 1000; // 30 minutes (prompt fallback)
5
5
  export const CONTEXT_STALE_MS = 6 * 60 * 60 * 1000; // 6 hours
6
6
  const UPCOMING_LIMIT = 3;
@@ -1,5 +1,5 @@
1
- import { type TodoStatus } from "../schema/status.schema.js";
2
- import type { TodoChildern, TodoRootItem } from "../schema/todo.schema";
1
+ import { type TodoStatus } from "../schemas/status.js";
2
+ import type { TodoChildern, TodoRootItem } from "../schemas/todo";
3
3
  export interface TodoProgress {
4
4
  completed: number;
5
5
  total: number;
@@ -1,4 +1,4 @@
1
- import { OPEN_STATUSES } from "../schema/status.schema.js";
1
+ import { OPEN_STATUSES } from "../schemas/status.js";
2
2
  /**
3
3
  * A todo list (roots + children) with validation, statistics and rendering.
4
4
  *
@@ -1,5 +1,5 @@
1
- import type { Priority, TodoStatus } from "../schema/status.schema";
2
- import type { TodoRootItem, TodoUpdateInput } from "../schema/todo.schema";
1
+ import type { Priority, TodoStatus } from "../schemas/status";
2
+ import type { TodoRootItem, TodoUpdateInput } from "../schemas/todo";
3
3
  type UpdatableTodo = {
4
4
  content: string;
5
5
  status: TodoStatus;
@@ -0,0 +1,18 @@
1
+ /** Rendered prefix of an error detail row. */
2
+ export declare const ERROR_PREFIX = "\u21B3 error: ";
3
+ /**
4
+ * Rendered width of a row: the indentation, the icon cell and the title.
5
+ *
6
+ * Rows must be sized explicitly, otherwise the scroll content never grows
7
+ * wider than the viewport and horizontal scrolling has nothing to move.
8
+ */
9
+ export declare function RowWidth(row: {
10
+ id?: number;
11
+ content: string;
12
+ depth: number;
13
+ }): number;
14
+ /** Rendered width of an expanded error line under its row. */
15
+ export declare function ErrorRowWidth(row: {
16
+ error: string;
17
+ depth: number;
18
+ }): number;
@@ -0,0 +1,22 @@
1
+ import { TodoTree } from "../todos/todo-tree";
2
+ /** Rendered prefix of an error detail row. */
3
+ export const ERROR_PREFIX = "↳ error: ";
4
+ /**
5
+ * Rendered width of a row: the indentation, the icon cell and the title.
6
+ *
7
+ * Rows must be sized explicitly, otherwise the scroll content never grows
8
+ * wider than the viewport and horizontal scrolling has nothing to move.
9
+ */
10
+ export function RowWidth(row) {
11
+ const indent = row.depth > 0 ? 2 : 0;
12
+ const label = row.id !== undefined
13
+ ? `${TodoTree.formatIdPrefix(row.id, row.depth)}${row.content}`
14
+ : row.content;
15
+ // icon glyph + 1 space, then the title.
16
+ return indent + 1 + 1 + [...label].length;
17
+ }
18
+ /** Rendered width of an expanded error line under its row. */
19
+ export function ErrorRowWidth(row) {
20
+ const indent = row.depth > 0 ? 4 : 2;
21
+ return indent + [...(ERROR_PREFIX + row.error)].length;
22
+ }
@@ -0,0 +1,2 @@
1
+ /** Shorten prompt text without cutting the trailing ellipsis. */
2
+ export declare function shorten(text: string, max: number): string;
@@ -0,0 +1,4 @@
1
+ /** Shorten prompt text without cutting the trailing ellipsis. */
2
+ export function shorten(text, max) {
3
+ return text.length <= max ? text : `${text.slice(0, max - 3).trimEnd()}...`;
4
+ }
@@ -0,0 +1,77 @@
1
+ import { Plugin } from "@opencode/plugin/tui";
2
+ import { ColorInput, ScrollBoxRenderable } from "@opentui/core";
3
+ import { TodoStatus } from "../schemas/status";
4
+ /** How many open items the Continue prompt lists before collapsing the rest. */
5
+ export declare const CONTINUE_PROMPT_MAX_ITEMS = 3;
6
+ /** Per-field caps so one long note cannot flood the prompt. */
7
+ export declare const CONTINUE_PROMPT_MAX_CONTENT = 60;
8
+ export declare const CONTINUE_PROMPT_MAX_NOTE = 60;
9
+ /**
10
+ * Maximum number of todo rows the panel shows before it starts scrolling.
11
+ * Keeping the panel compact protects the composer; scrolling guarantees no
12
+ * row is ever silently dropped.
13
+ */
14
+ export declare const TODO_PANEL_MAX_ROWS = 8;
15
+ export interface TodoListRow {
16
+ id: number;
17
+ content: string;
18
+ icon: string;
19
+ iconColor: ColorInput;
20
+ textColor: ColorInput;
21
+ depth: number;
22
+ status: TodoStatus;
23
+ }
24
+ /** A renderable line: either a task row or its on-demand error row. */
25
+ export type TodoListEntry = {
26
+ kind: "row";
27
+ row: TodoListRow;
28
+ } | {
29
+ kind: "error";
30
+ row: TodoListRow;
31
+ };
32
+ export interface FollowScrollInput {
33
+ currentTop: number;
34
+ activeIndex: number;
35
+ rowCount: number;
36
+ viewportHeight: number;
37
+ }
38
+ /**
39
+ * Flattens rows into renderable lines, inserting an error detail right below
40
+ * an expanded error row (click to toggle).
41
+ */
42
+ export declare function TodoListEntries(rows: TodoListRow[], expandedIds: readonly number[]): TodoListEntry[];
43
+ /**
44
+ * Follow the most advanced active row: the last `in_progress` entry is the
45
+ * task currently being executed. Error lines are skipped.
46
+ */
47
+ export declare function FindActiveEntryIndex(entries: TodoListEntry[]): number;
48
+ /**
49
+ * Scroll offset needed to reveal the active row, or `undefined` when nothing
50
+ * should move (no active row, already visible, or the list fits).
51
+ *
52
+ * Kept pure so the follow behaviour is testable without a live renderer.
53
+ */
54
+ export declare function NextFollowScrollTop(input: FollowScrollInput): number | undefined;
55
+ /**
56
+ * Presentational, context-free todo list.
57
+ *
58
+ * The panel is deliberately chrome-free: no indicator row, and the horizontal
59
+ * scrollbar stays hidden. Long titles never wrap — they are reached by
60
+ * scrolling left/right, and the vertical list scrolls when it exceeds
61
+ * `TODO_PANEL_MAX_ROWS`. When a row is `in_progress` the panel follows it.
62
+ *
63
+ * `activeIcon` is an accessor so the spinner updates only the icon text node
64
+ * instead of forcing every row object to be rebuilt on each animation frame.
65
+ */
66
+ export declare function TodoList(props: {
67
+ rows: TodoListRow[];
68
+ expandedIds?: readonly number[];
69
+ scrollbarColor?: ColorInput;
70
+ activeIcon?: () => string;
71
+ onToggleRow?: (id: number) => void;
72
+ ref?: (el: ScrollBoxRenderable) => void;
73
+ }): import("solid-js").JSX.Element;
74
+ export declare function TodoProgress(props: {
75
+ context: Plugin.Context;
76
+ sessionID: string;
77
+ }): import("solid-js").JSX.Element;