@frehilm/ordna-cli 0.1.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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -0
  3. package/dist/agent.d.ts +18 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +36 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/bin/ordna.d.ts +3 -0
  8. package/dist/bin/ordna.d.ts.map +1 -0
  9. package/dist/bin/ordna.js +11 -0
  10. package/dist/bin/ordna.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +80 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/colors.d.ts +12 -0
  16. package/dist/colors.d.ts.map +1 -0
  17. package/dist/colors.js +18 -0
  18. package/dist/colors.js.map +1 -0
  19. package/dist/commands/assign.d.ts +2 -0
  20. package/dist/commands/assign.d.ts.map +1 -0
  21. package/dist/commands/assign.js +18 -0
  22. package/dist/commands/assign.js.map +1 -0
  23. package/dist/commands/commit.d.ts +2 -0
  24. package/dist/commands/commit.d.ts.map +1 -0
  25. package/dist/commands/commit.js +14 -0
  26. package/dist/commands/commit.js.map +1 -0
  27. package/dist/commands/create.d.ts +10 -0
  28. package/dist/commands/create.d.ts.map +1 -0
  29. package/dist/commands/create.js +21 -0
  30. package/dist/commands/create.js.map +1 -0
  31. package/dist/commands/init.d.ts +2 -0
  32. package/dist/commands/init.d.ts.map +1 -0
  33. package/dist/commands/init.js +37 -0
  34. package/dist/commands/init.js.map +1 -0
  35. package/dist/commands/list.d.ts +7 -0
  36. package/dist/commands/list.d.ts.map +1 -0
  37. package/dist/commands/list.js +16 -0
  38. package/dist/commands/list.js.map +1 -0
  39. package/dist/commands/move.d.ts +2 -0
  40. package/dist/commands/move.d.ts.map +1 -0
  41. package/dist/commands/move.js +15 -0
  42. package/dist/commands/move.js.map +1 -0
  43. package/dist/commands/show.d.ts +2 -0
  44. package/dist/commands/show.d.ts.map +1 -0
  45. package/dist/commands/show.js +13 -0
  46. package/dist/commands/show.js.map +1 -0
  47. package/dist/commands/web.d.ts +7 -0
  48. package/dist/commands/web.d.ts.map +1 -0
  49. package/dist/commands/web.js +26 -0
  50. package/dist/commands/web.js.map +1 -0
  51. package/dist/format.d.ts +6 -0
  52. package/dist/format.d.ts.map +1 -0
  53. package/dist/format.js +55 -0
  54. package/dist/format.js.map +1 -0
  55. package/dist/index.d.ts +6 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +4 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/tui/App.d.ts +7 -0
  60. package/dist/tui/App.d.ts.map +1 -0
  61. package/dist/tui/App.js +484 -0
  62. package/dist/tui/App.js.map +1 -0
  63. package/dist/tui/Card.d.ts +12 -0
  64. package/dist/tui/Card.d.ts.map +1 -0
  65. package/dist/tui/Card.js +29 -0
  66. package/dist/tui/Card.js.map +1 -0
  67. package/dist/tui/Column.d.ts +18 -0
  68. package/dist/tui/Column.d.ts.map +1 -0
  69. package/dist/tui/Column.js +19 -0
  70. package/dist/tui/Column.js.map +1 -0
  71. package/dist/tui/SelectPrompt.d.ts +11 -0
  72. package/dist/tui/SelectPrompt.d.ts.map +1 -0
  73. package/dist/tui/SelectPrompt.js +25 -0
  74. package/dist/tui/SelectPrompt.js.map +1 -0
  75. package/dist/tui/Sidebar.d.ts +44 -0
  76. package/dist/tui/Sidebar.d.ts.map +1 -0
  77. package/dist/tui/Sidebar.js +84 -0
  78. package/dist/tui/Sidebar.js.map +1 -0
  79. package/dist/tui/Subbar.d.ts +11 -0
  80. package/dist/tui/Subbar.d.ts.map +1 -0
  81. package/dist/tui/Subbar.js +22 -0
  82. package/dist/tui/Subbar.js.map +1 -0
  83. package/dist/tui/TaskDetail.d.ts +12 -0
  84. package/dist/tui/TaskDetail.d.ts.map +1 -0
  85. package/dist/tui/TaskDetail.js +15 -0
  86. package/dist/tui/TaskDetail.js.map +1 -0
  87. package/dist/tui/TextPrompt.d.ts +10 -0
  88. package/dist/tui/TextPrompt.d.ts.map +1 -0
  89. package/dist/tui/TextPrompt.js +15 -0
  90. package/dist/tui/TextPrompt.js.map +1 -0
  91. package/dist/tui/hooks.d.ts +6 -0
  92. package/dist/tui/hooks.d.ts.map +1 -0
  93. package/dist/tui/hooks.js +21 -0
  94. package/dist/tui/hooks.js.map +1 -0
  95. package/dist/tui/index.d.ts +10 -0
  96. package/dist/tui/index.d.ts.map +1 -0
  97. package/dist/tui/index.js +25 -0
  98. package/dist/tui/index.js.map +1 -0
  99. package/dist/tui/theme.d.ts +17 -0
  100. package/dist/tui/theme.d.ts.map +1 -0
  101. package/dist/tui/theme.js +35 -0
  102. package/dist/tui/theme.js.map +1 -0
  103. package/package.json +58 -0
@@ -0,0 +1,26 @@
1
+ import { runWeb } from "@frehilm/ordna-web";
2
+ import { c } from "../colors.js";
3
+ export async function runWebCommand(options = {}) {
4
+ const handle = await runWeb({
5
+ port: options.port,
6
+ host: options.host,
7
+ openBrowser: !options.noOpen,
8
+ });
9
+ const url = `http://${options.host ?? "127.0.0.1"}:${handle.port}`;
10
+ console.log(c.green(`Ordna web running at ${c.bold(url)}`));
11
+ console.log(c.dim("Press Ctrl+C to stop."));
12
+ let shuttingDown = false;
13
+ const shutdown = async () => {
14
+ if (shuttingDown) {
15
+ console.log(c.dim("Forcing exit."));
16
+ process.exit(1);
17
+ }
18
+ shuttingDown = true;
19
+ console.log(c.dim("Shutting down…"));
20
+ await handle.close();
21
+ process.exit(0);
22
+ };
23
+ process.on("SIGINT", shutdown);
24
+ process.on("SIGTERM", shutdown);
25
+ }
26
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/commands/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAQjC,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAsB,EAAE;IAC3D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC;QAC3B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM;KAC5B,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,UAAU,OAAO,CAAC,IAAI,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAE5C,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,MAAM,QAAQ,GAAG,KAAK,IAAmB,EAAE;QAC1C,IAAI,YAAY,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QACD,YAAY,GAAG,IAAI,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACrC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { OrdnaConfig, Task } from "@frehilm/ordna-core";
2
+ export declare function colorStatus(status: string): string;
3
+ export declare function formatListRow(task: Task): string;
4
+ export declare function formatTask(task: Task): string;
5
+ export declare function summarizeStatuses(config: OrdnaConfig, tasks: Task[]): string;
6
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAS7D,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAShD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAyB7C;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAK5E"}
package/dist/format.js ADDED
@@ -0,0 +1,55 @@
1
+ import { c } from "./colors.js";
2
+ const STATUS_COLORS = {
3
+ todo: c.gray,
4
+ doing: c.yellow,
5
+ done: c.green,
6
+ };
7
+ export function colorStatus(status) {
8
+ const fn = STATUS_COLORS[status] ?? c.cyan;
9
+ return fn(status);
10
+ }
11
+ export function formatListRow(task) {
12
+ const pieces = [
13
+ c.bold(task.id.padEnd(8)),
14
+ colorStatus(task.status.padEnd(8)),
15
+ task.assignee ? c.magenta(`@${task.assignee}`.padEnd(14)) : " ".repeat(14),
16
+ task.priority ? c.yellow(`!${task.priority}`.padEnd(8)) : " ".repeat(8),
17
+ task.title,
18
+ ];
19
+ return pieces.join(" ");
20
+ }
21
+ export function formatTask(task) {
22
+ const lines = [];
23
+ lines.push(`${c.bold(task.id)} ${task.title}`);
24
+ lines.push([
25
+ ` status: ${colorStatus(task.status)}`,
26
+ `assignee: ${task.assignee ?? c.dim("—")}`,
27
+ `priority: ${task.priority ?? c.dim("—")}`,
28
+ ].join(" "));
29
+ if (task.tags.length > 0)
30
+ lines.push(` tags: ${task.tags.map((t) => c.cyan(t)).join(", ")}`);
31
+ if (task.depends_on.length > 0)
32
+ lines.push(` depends_on: ${task.depends_on.map((d) => c.blue(d)).join(", ")}`);
33
+ lines.push(c.dim(` created_at: ${task.created_at} updated_at: ${task.updated_at}`));
34
+ lines.push("");
35
+ for (const section of task.sections) {
36
+ if (section.heading === "") {
37
+ lines.push(section.content);
38
+ continue;
39
+ }
40
+ lines.push(c.bold(`## ${section.heading}`));
41
+ if (section.content.length > 0)
42
+ lines.push(section.content);
43
+ lines.push("");
44
+ }
45
+ return lines.join("\n");
46
+ }
47
+ export function summarizeStatuses(config, tasks) {
48
+ const counts = {};
49
+ for (const s of config.statuses)
50
+ counts[s] = 0;
51
+ for (const t of tasks)
52
+ counts[t.status] = (counts[t.status] ?? 0) + 1;
53
+ return config.statuses.map((s) => `${colorStatus(s)}: ${counts[s] ?? 0}`).join(" ");
54
+ }
55
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from "@frehilm/ordna-core";
2
+ export { runBoard } from "./tui/index.js";
3
+ export type { RunBoardOptions } from "./tui/index.js";
4
+ export type { AgentHookConfig, AgentContext } from "./agent.js";
5
+ export { loadAgentHook, sendAgent } from "./agent.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "@frehilm/ordna-core";
2
+ export { runBoard } from "./tui/index.js";
3
+ export { loadAgentHook, sendAgent } from "./agent.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ import { type AgentHookConfig } from "../agent.js";
3
+ export interface AppProps {
4
+ agentHook?: AgentHookConfig | null;
5
+ }
6
+ export declare function App({ agentHook: agentHookProp }?: AppProps): React.JSX.Element;
7
+ //# sourceMappingURL=App.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/tui/App.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAoD,MAAM,OAAO,CAAC;AACzE,OAAO,EAAE,KAAK,eAAe,EAA4B,MAAM,aAAa,CAAC;AA2C7E,MAAM,WAAW,QAAQ;IACxB,SAAS,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CACnC;AAED,wBAAgB,GAAG,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,GAAE,QAAa,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAgmBlF"}
@@ -0,0 +1,484 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { spawn } from "node:child_process";
3
+ import { ARCHIVED_STATUS, createContext as createStoreContext, createTask, listTasks, moveTask, updateTask, watchTasks, } from "@frehilm/ordna-core";
4
+ import { Box, Text, useApp, useInput, useStdin } from "ink";
5
+ import React, { useCallback, useEffect, useMemo, useState } from "react";
6
+ import { loadAgentHook, sendAgent } from "../agent.js";
7
+ import { Column } from "./Column.js";
8
+ import { useTerminalSize } from "./hooks.js";
9
+ import { SelectPrompt } from "./SelectPrompt.js";
10
+ import { Sidebar, buildSidebarRows, matchesFilter, rowKey, } from "./Sidebar.js";
11
+ import { Subbar } from "./Subbar.js";
12
+ import { TaskDetail } from "./TaskDetail.js";
13
+ import { TextPrompt } from "./TextPrompt.js";
14
+ import { theme } from "./theme.js";
15
+ const SIDEBAR_WIDTH = 22;
16
+ function groupByStatus(tasks, statuses) {
17
+ const groups = new Map();
18
+ for (const status of statuses)
19
+ groups.set(status, []);
20
+ for (const task of tasks) {
21
+ const bucket = groups.get(task.status);
22
+ if (bucket)
23
+ bucket.push(task);
24
+ }
25
+ return groups;
26
+ }
27
+ function flattenSidebarRows(rows) {
28
+ return [...rows.views, ...rows.priorities, ...rows.tags];
29
+ }
30
+ export function App({ agentHook: agentHookProp } = {}) {
31
+ const { exit } = useApp();
32
+ const { setRawMode } = useStdin();
33
+ const { rows: termRows, columns: termCols } = useTerminalSize();
34
+ const [ctx] = useState(() => createStoreContext());
35
+ const [tasks, setTasks] = useState([]);
36
+ const [loaded, setLoaded] = useState(false);
37
+ const [columnIndex, setColumnIndex] = useState(0);
38
+ const [rowIndex, setRowIndex] = useState(0);
39
+ const [mode, setMode] = useState({ kind: "browse" });
40
+ const [searchQuery, setSearchQuery] = useState("");
41
+ const [toast, setToast] = useState(null);
42
+ const [grabbedId, setGrabbedId] = useState(null);
43
+ const [filter, setFilter] = useState({ kind: "all" });
44
+ const [focus, setFocus] = useState("board");
45
+ const [sidebarFocusedKey, setSidebarFocusedKey] = useState(null);
46
+ const [scrollOffsets, setScrollOffsets] = useState({});
47
+ const [agentHook] = useState(() => {
48
+ if (agentHookProp !== undefined)
49
+ return agentHookProp;
50
+ return loadAgentHook();
51
+ });
52
+ const statuses = ctx.config.statuses;
53
+ const reload = useCallback(async () => {
54
+ const fresh = await listTasks(ctx);
55
+ setTasks(fresh);
56
+ setLoaded(true);
57
+ }, [ctx]);
58
+ useEffect(() => {
59
+ void reload();
60
+ const unsubscribe = watchTasks(ctx, () => {
61
+ void reload();
62
+ });
63
+ return () => {
64
+ void unsubscribe();
65
+ };
66
+ }, [ctx, reload]);
67
+ const sidebarRows = useMemo(() => buildSidebarRows(tasks, statuses), [tasks, statuses]);
68
+ const flatRows = useMemo(() => flattenSidebarRows(sidebarRows), [sidebarRows]);
69
+ useEffect(() => {
70
+ if (focus !== "sidebar")
71
+ return;
72
+ if (sidebarFocusedKey === null && flatRows.length > 0) {
73
+ const active = flatRows.find((r) => rowKey(r.item) === rowKey(filter));
74
+ setSidebarFocusedKey(rowKey(active?.item ?? flatRows[0]?.item ?? { kind: "all" }));
75
+ }
76
+ }, [focus, flatRows, sidebarFocusedKey, filter]);
77
+ const showingBoardColumns = filter.kind !== "archived";
78
+ const boardStatuses = useMemo(() => {
79
+ if (filter.kind === "archived")
80
+ return [ARCHIVED_STATUS];
81
+ if (filter.kind === "status")
82
+ return [filter.status];
83
+ return statuses;
84
+ }, [filter, statuses]);
85
+ const filteredTasks = useMemo(() => {
86
+ const q = searchQuery.toLowerCase();
87
+ return tasks.filter((t) => {
88
+ if (!matchesFilter(t, filter))
89
+ return false;
90
+ if (!q)
91
+ return true;
92
+ return (t.title.toLowerCase().includes(q) ||
93
+ t.id.toLowerCase().includes(q) ||
94
+ t.tags.some((tag) => tag.toLowerCase().includes(q)));
95
+ });
96
+ }, [tasks, filter, searchQuery]);
97
+ const groups = useMemo(() => groupByStatus(filteredTasks, boardStatuses), [filteredTasks, boardStatuses]);
98
+ const activeStatus = boardStatuses[columnIndex] ?? boardStatuses[0] ?? "";
99
+ const activeColumn = groups.get(activeStatus) ?? [];
100
+ const selectedTask = activeColumn[rowIndex];
101
+ useEffect(() => {
102
+ if (columnIndex >= boardStatuses.length)
103
+ setColumnIndex(0);
104
+ }, [boardStatuses.length, columnIndex]);
105
+ useEffect(() => {
106
+ if (rowIndex >= activeColumn.length) {
107
+ setRowIndex(Math.max(0, activeColumn.length - 1));
108
+ }
109
+ }, [activeColumn.length, rowIndex]);
110
+ const visibleCount = filteredTasks.length;
111
+ const totalCount = useMemo(() => tasks.filter((t) => t.status !== ARCHIVED_STATUS).length, [tasks]);
112
+ const launchEditor = useCallback((task) => {
113
+ const editor = process.env.EDITOR || process.env.VISUAL || "vi";
114
+ setRawMode?.(false);
115
+ const proc = spawn(editor, [task.filePath], { stdio: "inherit" });
116
+ proc.on("exit", () => {
117
+ setRawMode?.(true);
118
+ void reload();
119
+ });
120
+ }, [reload, setRawMode]);
121
+ const flashToast = (message) => {
122
+ setToast(message);
123
+ setTimeout(() => setToast((t) => (t === message ? null : t)), 2500);
124
+ };
125
+ const moveGrabbedTo = async (targetIndex) => {
126
+ if (grabbedId === null)
127
+ return;
128
+ const targetStatus = boardStatuses[targetIndex];
129
+ if (!targetStatus)
130
+ return;
131
+ const task = tasks.find((t) => t.id === grabbedId);
132
+ if (!task || task.status === targetStatus) {
133
+ setColumnIndex(targetIndex);
134
+ return;
135
+ }
136
+ try {
137
+ await moveTask(grabbedId, targetStatus, ctx);
138
+ await reload();
139
+ setColumnIndex(targetIndex);
140
+ const fresh = await listTasks(ctx);
141
+ const group = fresh.filter((t) => t.status === targetStatus);
142
+ const nextIdx = group.findIndex((t) => t.id === grabbedId);
143
+ const clampedIdx = Math.max(0, nextIdx);
144
+ setRowIndex(clampedIdx);
145
+ ensureRowVisible(targetStatus, clampedIdx, group);
146
+ }
147
+ catch (error) {
148
+ flashToast(error.message);
149
+ }
150
+ };
151
+ const archiveSelected = async (task) => {
152
+ try {
153
+ await updateTask(task.id, { status: ARCHIVED_STATUS }, ctx);
154
+ flashToast(`${task.id} archived`);
155
+ await reload();
156
+ }
157
+ catch (error) {
158
+ flashToast(error.message);
159
+ }
160
+ };
161
+ const triggerAgent = async (task) => {
162
+ if (!agentHook)
163
+ return;
164
+ try {
165
+ const result = await sendAgent(agentHook, task, {
166
+ tasksDir: ctx.config.tasksDir,
167
+ cwd: ctx.cwd,
168
+ schema: ctx.config.schema,
169
+ });
170
+ if (!result.ok) {
171
+ flashToast(`${agentHook.label} hook failed (${result.status})`);
172
+ return;
173
+ }
174
+ flashToast(`Sent ${task.id} to ${agentHook.label}`);
175
+ }
176
+ catch (error) {
177
+ flashToast(error.message);
178
+ }
179
+ };
180
+ const applySidebarFocused = () => {
181
+ if (sidebarFocusedKey === null)
182
+ return;
183
+ const row = flatRows.find((r) => rowKey(r.item) === sidebarFocusedKey);
184
+ if (row) {
185
+ setFilter(row.item);
186
+ setColumnIndex(0);
187
+ setRowIndex(0);
188
+ setFocus("board");
189
+ }
190
+ };
191
+ useInput(async (input, key) => {
192
+ if (mode.kind !== "browse")
193
+ return;
194
+ if (key.tab) {
195
+ if (focus === "board") {
196
+ setFocus("sidebar");
197
+ setSidebarFocusedKey(rowKey(filter));
198
+ }
199
+ else {
200
+ setFocus("board");
201
+ }
202
+ return;
203
+ }
204
+ if (focus === "sidebar") {
205
+ if (key.escape) {
206
+ setFocus("board");
207
+ return;
208
+ }
209
+ if (key.upArrow || input === "k") {
210
+ const idx = flatRows.findIndex((r) => rowKey(r.item) === sidebarFocusedKey);
211
+ if (idx > 0)
212
+ setSidebarFocusedKey(rowKey(flatRows[idx - 1]?.item ?? flatRows[0]?.item ?? { kind: "all" }));
213
+ return;
214
+ }
215
+ if (key.downArrow || input === "j") {
216
+ const idx = flatRows.findIndex((r) => rowKey(r.item) === sidebarFocusedKey);
217
+ if (idx >= 0 && idx < flatRows.length - 1) {
218
+ setSidebarFocusedKey(rowKey(flatRows[idx + 1]?.item ?? flatRows[0]?.item ?? { kind: "all" }));
219
+ }
220
+ return;
221
+ }
222
+ if (key.return) {
223
+ applySidebarFocused();
224
+ return;
225
+ }
226
+ if (input === "q") {
227
+ exit();
228
+ }
229
+ return;
230
+ }
231
+ if (grabbedId !== null) {
232
+ if (input === " " || key.return) {
233
+ flashToast(`Dropped ${grabbedId}`);
234
+ setGrabbedId(null);
235
+ return;
236
+ }
237
+ if (key.escape) {
238
+ setGrabbedId(null);
239
+ return;
240
+ }
241
+ if (key.leftArrow || input === "h") {
242
+ await moveGrabbedTo(Math.max(0, columnIndex - 1));
243
+ return;
244
+ }
245
+ if (key.rightArrow || input === "l") {
246
+ await moveGrabbedTo(Math.min(boardStatuses.length - 1, columnIndex + 1));
247
+ return;
248
+ }
249
+ return;
250
+ }
251
+ if (input === "q") {
252
+ exit();
253
+ return;
254
+ }
255
+ if (key.leftArrow || input === "h") {
256
+ setColumnIndex((i) => Math.max(0, i - 1));
257
+ setRowIndex(0);
258
+ }
259
+ else if (key.rightArrow || input === "l") {
260
+ setColumnIndex((i) => Math.min(boardStatuses.length - 1, i + 1));
261
+ setRowIndex(0);
262
+ }
263
+ else if (key.upArrow || input === "k") {
264
+ const nextRow = Math.max(0, rowIndex - 1);
265
+ setRowIndex(nextRow);
266
+ ensureRowVisible(activeStatus, nextRow, activeColumn);
267
+ }
268
+ else if (key.downArrow || input === "j") {
269
+ const nextRow = Math.min(Math.max(0, activeColumn.length - 1), rowIndex + 1);
270
+ setRowIndex(nextRow);
271
+ ensureRowVisible(activeStatus, nextRow, activeColumn);
272
+ }
273
+ else if (input === " " && selectedTask) {
274
+ setGrabbedId(selectedTask.id);
275
+ flashToast(`Grabbed ${selectedTask.id} — ← → to move, space to drop`);
276
+ }
277
+ else if (key.return && selectedTask) {
278
+ setMode({ kind: "detail", task: selectedTask });
279
+ }
280
+ else if (input === "c") {
281
+ setMode({ kind: "create" });
282
+ }
283
+ else if (input === "m" && selectedTask) {
284
+ setMode({ kind: "move", task: selectedTask });
285
+ }
286
+ else if (input === "a" && selectedTask) {
287
+ setMode({ kind: "assign", task: selectedTask });
288
+ }
289
+ else if (input === "e" && selectedTask) {
290
+ launchEditor(selectedTask);
291
+ }
292
+ else if (input === "x" && selectedTask) {
293
+ void archiveSelected(selectedTask);
294
+ }
295
+ else if (input === "g" && selectedTask && agentHook) {
296
+ void triggerAgent(selectedTask);
297
+ }
298
+ else if (input === "/") {
299
+ setMode({ kind: "search" });
300
+ }
301
+ else if (key.escape && searchQuery) {
302
+ setSearchQuery("");
303
+ }
304
+ }, { isActive: mode.kind === "browse" });
305
+ const onCreate = async (title) => {
306
+ try {
307
+ const task = await createTask({ title }, ctx);
308
+ flashToast(`Created ${task.id}`);
309
+ await reload();
310
+ }
311
+ catch (error) {
312
+ flashToast(error.message);
313
+ }
314
+ setMode({ kind: "browse" });
315
+ };
316
+ const onMove = async (targetStatus) => {
317
+ if (mode.kind !== "move")
318
+ return;
319
+ try {
320
+ await moveTask(mode.task.id, targetStatus, ctx);
321
+ flashToast(`${mode.task.id} → ${targetStatus}`);
322
+ await reload();
323
+ const fresh = await listTasks(ctx);
324
+ const group = fresh.filter((t) => t.status === targetStatus);
325
+ const nextIdx = group.findIndex((t) => t.id === mode.task.id);
326
+ if (nextIdx >= 0) {
327
+ const targetColIdx = boardStatuses.indexOf(targetStatus);
328
+ if (targetColIdx >= 0)
329
+ setColumnIndex(targetColIdx);
330
+ setRowIndex(nextIdx);
331
+ ensureRowVisible(targetStatus, nextIdx, group);
332
+ }
333
+ }
334
+ catch (error) {
335
+ flashToast(error.message);
336
+ }
337
+ setMode({ kind: "browse" });
338
+ };
339
+ const onAssign = async (name) => {
340
+ if (mode.kind !== "assign")
341
+ return;
342
+ try {
343
+ const value = name.trim().length === 0 ? null : name.trim();
344
+ await updateTask(mode.task.id, { assignee: value }, ctx);
345
+ flashToast(`${mode.task.id} ${value ? `→ @${value}` : "unassigned"}`);
346
+ await reload();
347
+ }
348
+ catch (error) {
349
+ flashToast(error.message);
350
+ }
351
+ setMode({ kind: "browse" });
352
+ };
353
+ const moveOptions = useMemo(() => [...statuses, ARCHIVED_STATUS], [statuses]);
354
+ const sidebarWidth = SIDEBAR_WIDTH;
355
+ const boardAreaWidth = Math.max(30, termCols - sidebarWidth);
356
+ const subbarHeight = 1;
357
+ const bodyHeight = Math.max(5, termRows - 3);
358
+ const boardHeight = Math.max(5, bodyHeight - subbarHeight - 1);
359
+ // Normal board column width = boardAreaWidth evenly split across configured statuses.
360
+ // Archive view reuses this width (one column, same size as a regular lane).
361
+ // Other views (all / status / tag) stretch columns to fill the board area evenly.
362
+ const normalColCount = Math.max(1, statuses.length);
363
+ const normalColWidth = Math.max(24, Math.floor(boardAreaWidth / normalColCount));
364
+ const isArchive = filter.kind === "archived";
365
+ const stretchedColCount = Math.max(1, boardStatuses.length);
366
+ const stretchedColWidth = Math.max(24, Math.floor(boardAreaWidth / stretchedColCount));
367
+ // Each column body has (boardHeight - 2) usable rows after title + bottom border.
368
+ const columnBodyLines = Math.max(1, boardHeight - 2);
369
+ const columnLayout = useCallback((status, all) => {
370
+ const total = all.length;
371
+ const needsScroll = total > columnBodyLines;
372
+ // Reserve 1 row top + 1 row bottom for scroll indicators when needed.
373
+ const visibleCount = needsScroll
374
+ ? Math.max(1, columnBodyLines - 2)
375
+ : columnBodyLines;
376
+ const maxOffset = Math.max(0, total - visibleCount);
377
+ const storedOffset = scrollOffsets[status] ?? 0;
378
+ const offset = Math.max(0, Math.min(maxOffset, storedOffset));
379
+ const visible = all.slice(offset, offset + visibleCount);
380
+ return {
381
+ visible,
382
+ offset,
383
+ visibleCount,
384
+ aboveCount: offset,
385
+ belowCount: Math.max(0, total - offset - visibleCount),
386
+ total,
387
+ };
388
+ }, [columnBodyLines, scrollOffsets]);
389
+ const ensureRowVisible = useCallback((status, nextRow, all) => {
390
+ const total = all.length;
391
+ const needsScroll = total > columnBodyLines;
392
+ const visibleCount = needsScroll
393
+ ? Math.max(1, columnBodyLines - 2)
394
+ : columnBodyLines;
395
+ setScrollOffsets((prev) => {
396
+ const cur = prev[status] ?? 0;
397
+ let next = cur;
398
+ if (nextRow < cur)
399
+ next = nextRow;
400
+ else if (nextRow >= cur + visibleCount)
401
+ next = nextRow - visibleCount + 1;
402
+ const maxOffset = Math.max(0, total - visibleCount);
403
+ next = Math.max(0, Math.min(maxOffset, next));
404
+ if (next === cur)
405
+ return prev;
406
+ return { ...prev, [status]: next };
407
+ });
408
+ }, [columnBodyLines]);
409
+ const popupWidth = Math.min(boardAreaWidth - 4, Math.max(40, Math.floor(boardAreaWidth * 0.75)));
410
+ const popupHeight = Math.min(boardHeight - 2, Math.max(10, Math.floor(boardHeight * 0.85)));
411
+ const renderBoard = () => (_jsx(Box, { flexDirection: "row", height: boardHeight, width: boardAreaWidth, children: boardStatuses.map((s, idx) => {
412
+ const all = groups.get(s) ?? [];
413
+ const layout = columnLayout(s, all);
414
+ const isFocusedCol = idx === columnIndex && mode.kind === "browse" && focus === "board";
415
+ const selectedRelativeIndex = isFocusedCol
416
+ ? rowIndex - layout.offset
417
+ : -1;
418
+ let w;
419
+ if (isArchive) {
420
+ w = normalColWidth;
421
+ }
422
+ else if (idx === boardStatuses.length - 1) {
423
+ w = boardAreaWidth - stretchedColWidth * (boardStatuses.length - 1);
424
+ }
425
+ else {
426
+ w = stretchedColWidth;
427
+ }
428
+ return (_jsx(Column, { status: s, statusIndex: statuses.indexOf(s) >= 0 ? statuses.indexOf(s) : idx, visibleTasks: layout.visible, totalTasks: layout.total, aboveCount: layout.aboveCount, belowCount: layout.belowCount, focused: isFocusedCol, selectedRelativeIndex: selectedRelativeIndex, grabbedId: grabbedId, width: w, height: boardHeight }, s));
429
+ }) }));
430
+ const renderOverlay = () => {
431
+ if (mode.kind === "create") {
432
+ return (_jsx(TextPrompt, { label: "New task title", onSubmit: onCreate, onCancel: () => setMode({ kind: "browse" }) }));
433
+ }
434
+ if (mode.kind === "move") {
435
+ return (_jsx(SelectPrompt, { label: `Move ${mode.task.id} to…`, options: moveOptions, initialIndex: Math.max(0, moveOptions.indexOf(mode.task.status)), onSubmit: onMove, onCancel: () => setMode({ kind: "browse" }) }));
436
+ }
437
+ if (mode.kind === "assign") {
438
+ return (_jsx(TextPrompt, { label: `Assign ${mode.task.id} (blank to unassign)`, initialValue: mode.task.assignee ?? "", onSubmit: onAssign, onCancel: () => setMode({ kind: "browse" }) }));
439
+ }
440
+ if (mode.kind === "search") {
441
+ return (_jsx(TextPrompt, { label: "Search", initialValue: searchQuery, onSubmit: (v) => {
442
+ setSearchQuery(v);
443
+ setMode({ kind: "browse" });
444
+ }, onCancel: () => setMode({ kind: "browse" }) }));
445
+ }
446
+ return null;
447
+ };
448
+ const overlay = renderOverlay();
449
+ const showDetail = mode.kind === "detail";
450
+ const sidebarHints = [
451
+ { keys: "Tab", label: "board" },
452
+ { keys: "↑/↓", label: "select" },
453
+ { keys: "Enter", label: "apply" },
454
+ { keys: "Esc", label: "back" },
455
+ ];
456
+ const grabHints = [
457
+ { keys: "←/→", label: "move" },
458
+ { keys: "Space", label: "drop" },
459
+ { keys: "Esc", label: "cancel" },
460
+ ];
461
+ const browseHints = [
462
+ { keys: "Tab", label: "sidebar" },
463
+ { keys: "←/→", label: "cols" },
464
+ { keys: "↑/↓", label: "tasks" },
465
+ { keys: "Space", label: "grab" },
466
+ { keys: "Enter", label: "open" },
467
+ { keys: "c", label: "new" },
468
+ { keys: "m", label: "move" },
469
+ { keys: "a", label: "assign" },
470
+ { keys: "e", label: "edit" },
471
+ { keys: "x", label: "archive" },
472
+ ...(agentHook ? [{ keys: "g", label: agentHook.label.toLowerCase() }] : []),
473
+ { keys: "/", label: "find" },
474
+ { keys: "q", label: "quit" },
475
+ ];
476
+ const footerHints = focus === "sidebar" ? sidebarHints : grabbedId ? grabHints : browseHints;
477
+ const footerPrefix = focus === "board" && grabbedId ? `moving ${grabbedId} · ` : null;
478
+ return (_jsxs(Box, { flexDirection: "column", width: termCols, height: termRows, children: [_jsxs(Box, { paddingX: 1, width: termCols, children: [_jsx(Text, { bold: true, color: theme.accent, children: "Ordna" }), _jsx(Text, { color: theme.textMuted, children: ` ${ctx.tasksDir}` }), _jsx(Box, { flexGrow: 1 }), toast ? _jsx(Text, { color: theme.accent2, children: toast }) : null] }), _jsxs(Box, { flexDirection: "row", width: termCols, height: bodyHeight, children: [_jsx(Box, { width: sidebarWidth, height: bodyHeight, children: _jsx(Sidebar, { rows: sidebarRows, active: filter, focusedKey: focus === "sidebar" ? sidebarFocusedKey : null, focused: focus === "sidebar", width: sidebarWidth, height: bodyHeight }) }), _jsxs(Box, { flexDirection: "column", width: boardAreaWidth, height: bodyHeight, children: [_jsx(Subbar, { filter: filter, visible: visibleCount, total: totalCount, searchQuery: searchQuery }), overlay ? (_jsx(Box, { height: boardHeight, width: boardAreaWidth, paddingX: 1, paddingY: 1, children: overlay })) : showDetail && mode.kind === "detail" ? (_jsx(Box, { height: boardHeight, width: boardAreaWidth, alignItems: "center", justifyContent: "center", children: _jsx(TaskDetail, { task: mode.task, onClose: () => setMode({ kind: "browse" }), onEdit: () => {
479
+ const target = mode.task;
480
+ setMode({ kind: "browse" });
481
+ launchEditor(target);
482
+ }, width: popupWidth, height: popupHeight }) })) : !loaded ? (_jsx(Box, { height: boardHeight, paddingX: 1, children: _jsx(Text, { color: theme.textMuted, children: "Loading\u2026" }) })) : showingBoardColumns || filter.kind === "archived" ? (renderBoard()) : (renderBoard())] })] }), _jsx(Box, { paddingX: 1, width: termCols, children: _jsxs(Text, { wrap: "truncate-end", children: [footerPrefix ? (_jsx(Text, { color: theme.textMuted, children: footerPrefix })) : null, footerHints.map((hint, idx) => (_jsxs(Text, { children: [idx > 0 ? (_jsx(Text, { color: theme.textFaint, children: " · " })) : null, _jsx(Text, { color: theme.textDim, bold: true, children: hint.keys }), _jsx(Text, { color: theme.textMuted, children: ` ${hint.label}` })] }, `${hint.keys}-${hint.label}`)))] }) })] }));
483
+ }
484
+ //# sourceMappingURL=App.js.map