opencode-backlog 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sacha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # opencode-backlog
2
+
3
+ A persistent project backlog for OpenCode V2 agents and the TUI.
4
+
5
+ `opencode-backlog` gives the agent tools to manage project tasks. It also adds an interactive backlog to the OpenCode sidebar and command palette.
6
+
7
+ ![Backlog sidebar in OpenCode](docs/images/opencode-backlog.jpg)
8
+
9
+ ## What It Does
10
+
11
+ The plugin stores tasks in `BACKLOG.json` at the project root. Each task has a title, optional notes, and one Kanban state:
12
+
13
+ - `todo`
14
+ - `doing`
15
+ - `done`
16
+
17
+ The server plugin lets the agent list, add, edit, move, reorder, and remove tasks. The TUI plugin provides:
18
+
19
+ - A live backlog summary below the sidebar context.
20
+ - A `Browse backlog` command in the command palette.
21
+ - `/backlog` and `/tasks` slash commands.
22
+ - Task details and status changes from the TUI.
23
+
24
+ ## Requirements
25
+
26
+ - OpenCode V2 with the beta plugin API.
27
+ - Node.js 24 and npm, or Nix with flakes enabled.
28
+
29
+ The Nix development shell supplies Node.js 24 and npm when you use Nix.
30
+
31
+ The plugin currently targets `@opencode-ai/plugin@0.0.0-beta-17927`.
32
+
33
+ ## Install With Node.js
34
+
35
+ Clone the repository and build the plugin:
36
+
37
+ ```sh
38
+ git clone https://github.com/sachahjkl/opencode-backlog.git
39
+ cd opencode-backlog
40
+ npm ci
41
+ npm run build
42
+ pwd
43
+ ```
44
+
45
+ Add the server entrypoint to `opencode.jsonc`. Replace `/absolute/path/to/opencode-backlog` with the path printed by `pwd`.
46
+
47
+ ```jsonc
48
+ {
49
+ "$schema": "https://opencode.ai/config.json",
50
+ "plugins": [
51
+ "/absolute/path/to/opencode-backlog/dist/index.js"
52
+ ]
53
+ }
54
+ ```
55
+
56
+ Add the TUI entrypoint to `~/.config/opencode/cli.json`:
57
+
58
+ ```json
59
+ {
60
+ "plugins": [
61
+ "/absolute/path/to/opencode-backlog/dist/tui.js"
62
+ ]
63
+ }
64
+ ```
65
+
66
+ Restart the OpenCode service and reopen the TUI:
67
+
68
+ ```sh
69
+ opencode2 service restart
70
+ ```
71
+
72
+ ## Install With Nix
73
+
74
+ Build the package:
75
+
76
+ ```sh
77
+ nix build github:sachahjkl/opencode-backlog
78
+ realpath result
79
+ ```
80
+
81
+ The result contains two plugin entrypoints:
82
+
83
+ ```text
84
+ result/lib/opencode-backlog/dist/index.js
85
+ result/lib/opencode-backlog/dist/tui.js
86
+ ```
87
+
88
+ Add the server entrypoint to `opencode.jsonc`. Replace `/nix/store/...` with the path printed by `realpath result`.
89
+
90
+ ```jsonc
91
+ {
92
+ "$schema": "https://opencode.ai/config.json",
93
+ "plugins": [
94
+ "/nix/store/...-opencode-backlog-0.1.0/lib/opencode-backlog/dist/index.js"
95
+ ]
96
+ }
97
+ ```
98
+
99
+ Add the TUI entrypoint to `~/.config/opencode/cli.json`:
100
+
101
+ ```json
102
+ {
103
+ "plugins": [
104
+ "/nix/store/...-opencode-backlog-0.1.0/lib/opencode-backlog/dist/tui.js"
105
+ ]
106
+ }
107
+ ```
108
+
109
+ Restart the OpenCode service after the first installation:
110
+
111
+ ```sh
112
+ opencode2 service restart
113
+ ```
114
+
115
+ Reopen the TUI to load the TUI plugin.
116
+
117
+ ## Use The Backlog
118
+
119
+ Ask the agent to manage tasks in normal language:
120
+
121
+ ```text
122
+ Add a todo task to document the release process.
123
+ Move the release task to doing.
124
+ List only the todo tasks.
125
+ Mark the current task as done.
126
+ ```
127
+
128
+ Open the command palette and select `Browse backlog` to inspect tasks. Select a task to view its ID, notes, and current state. Then select its new state.
129
+
130
+ Run `/backlog` or `/tasks` to open the same browser directly.
131
+
132
+ ## Agent Tools
133
+
134
+ | Tool | Purpose |
135
+ | --- | --- |
136
+ | `backlog_list` | List all tasks or filter by `todo`, `doing`, or `done`. |
137
+ | `backlog_add` | Add a task at an optional state and position. |
138
+ | `backlog_update` | Change a task title or notes. |
139
+ | `backlog_move` | Change a task state or position. |
140
+ | `backlog_remove` | Permanently remove a task. |
141
+
142
+ The agent receives task IDs from `backlog_list` and from each mutation result.
143
+
144
+ ## Backlog File
145
+
146
+ The plugin creates `BACKLOG.json` when the first task is added:
147
+
148
+ ```json
149
+ {
150
+ "version": 1,
151
+ "items": [
152
+ {
153
+ "id": "c50437e3-2a57-424d-9257-32ec432145a9",
154
+ "title": "Add invoice search",
155
+ "notes": "Search by customer name and invoice number.",
156
+ "status": "doing"
157
+ }
158
+ ]
159
+ }
160
+ ```
161
+
162
+ Items stay grouped by state. Their array order defines their position inside each state.
163
+
164
+ Commit `BACKLOG.json` when the backlog must follow the project. Ignore it when the backlog must remain local.
165
+
166
+ ## Verify The Installation
167
+
168
+ List active server plugins:
169
+
170
+ ```sh
171
+ opencode2 api get /api/plugin
172
+ ```
173
+
174
+ The response must include `opencode.backlog`.
175
+
176
+ Open the command palette in the TUI. The `Browse backlog` command confirms that the TUI entrypoint loaded.
177
+
178
+ ## Development
179
+
180
+ Run all development commands through Nix:
181
+
182
+ ```sh
183
+ nix develop
184
+ nix develop -c pre-commit run --all-files
185
+ nix develop -c npm run check
186
+ nix develop -c npm test
187
+ nix flake check --print-build-logs
188
+ ```
189
+
190
+ Entering `nix develop` installs the repository pre-commit hook. The hook checks Nix formatting, GitHub Actions, JSON, merge conflicts, file sizes, and whitespace.
191
+
192
+ `BACKLOG.json` is not required by the test suite.
193
+
194
+ ## Release
195
+
196
+ Bootstrap the new package from an authenticated workstation:
197
+
198
+ ```sh
199
+ npm login
200
+ npm publish
201
+ ```
202
+
203
+ Staged publishing cannot create a new package. After `opencode-backlog` exists on npm, configure trusted publishing with these exact values:
204
+
205
+ ```text
206
+ Organization or user: sachahjkl
207
+ Repository: opencode-backlog
208
+ Workflow filename: publish.yml
209
+ Environment name: <empty>
210
+ Allowed action: npm publish
211
+ ```
212
+
213
+ For later releases, update the package version and push its commit and tag:
214
+
215
+ ```sh
216
+ npm version patch
217
+ git push origin master --follow-tags
218
+ ```
219
+
220
+ Use `minor` or `major` instead of `patch` when the release requires it. The workflow rejects a tag that does not match `package.json`.
221
+
222
+ The workflow publishes through npm trusted publishing. It uses OIDC and does not store an npm token.
223
+
224
+ ## Compatibility
225
+
226
+ This project targets OpenCode V2. The V2 plugin API is beta and can change between OpenCode releases.
227
+
228
+ Match the plugin API version in `package.json` with the OpenCode release that loads the plugin.
@@ -0,0 +1,19 @@
1
+ export declare const BACKLOG_FILE = "BACKLOG.json";
2
+ export declare const STATUSES: readonly ["todo", "doing", "done"];
3
+ export type Status = (typeof STATUSES)[number];
4
+ export interface BacklogItem {
5
+ readonly id: string;
6
+ readonly title: string;
7
+ readonly notes?: string;
8
+ readonly status: Status;
9
+ }
10
+ export interface Backlog {
11
+ readonly version: 1;
12
+ readonly items: readonly BacklogItem[];
13
+ }
14
+ export declare const EMPTY_BACKLOG: Backlog;
15
+ export declare function isStatus(value: unknown): value is Status;
16
+ export declare function parseBacklog(value: unknown): Backlog;
17
+ export declare function sortByStatus(items: readonly BacklogItem[]): BacklogItem[];
18
+ export declare function moveItem(items: readonly BacklogItem[], id: string, status: Status | undefined, position: number | undefined): BacklogItem[];
19
+ export declare function describeBacklog(backlog: Backlog, statuses?: readonly Status[]): string;
@@ -0,0 +1,70 @@
1
+ export const BACKLOG_FILE = "BACKLOG.json";
2
+ export const STATUSES = ["todo", "doing", "done"];
3
+ export const EMPTY_BACKLOG = { version: 1, items: [] };
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ export function isStatus(value) {
8
+ return typeof value === "string" && STATUSES.includes(value);
9
+ }
10
+ export function parseBacklog(value) {
11
+ if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.items)) {
12
+ throw new Error("BACKLOG.json must contain a version 1 backlog");
13
+ }
14
+ const ids = new Set();
15
+ const items = value.items.map((item, index) => {
16
+ if (!isRecord(item))
17
+ throw new Error(`Backlog item ${index} must be an object`);
18
+ if (typeof item.id !== "string" || item.id.length === 0) {
19
+ throw new Error(`Backlog item ${index} must have a non-empty id`);
20
+ }
21
+ if (ids.has(item.id))
22
+ throw new Error(`Backlog item id ${item.id} is duplicated`);
23
+ if (typeof item.title !== "string" || item.title.trim().length === 0) {
24
+ throw new Error(`Backlog item ${item.id} must have a non-empty title`);
25
+ }
26
+ if (item.notes !== undefined && typeof item.notes !== "string") {
27
+ throw new Error(`Backlog item ${item.id} has invalid notes`);
28
+ }
29
+ if (!isStatus(item.status))
30
+ throw new Error(`Backlog item ${item.id} has an invalid status`);
31
+ ids.add(item.id);
32
+ return {
33
+ id: item.id,
34
+ title: item.title,
35
+ ...(item.notes === undefined ? {} : { notes: item.notes }),
36
+ status: item.status,
37
+ };
38
+ });
39
+ return { version: 1, items };
40
+ }
41
+ export function sortByStatus(items) {
42
+ return STATUSES.flatMap((status) => items.filter((item) => item.status === status));
43
+ }
44
+ export function moveItem(items, id, status, position) {
45
+ const item = items.find((candidate) => candidate.id === id);
46
+ if (!item)
47
+ throw new Error(`Backlog item ${id} does not exist`);
48
+ const targetStatus = status ?? item.status;
49
+ const remaining = items.filter((candidate) => candidate.id !== id);
50
+ const target = remaining.filter((candidate) => candidate.status === targetStatus);
51
+ const targetPosition = Math.min(position ?? target.length, target.length);
52
+ target.splice(targetPosition, 0, { ...item, status: targetStatus });
53
+ return STATUSES.flatMap((candidateStatus) => candidateStatus === targetStatus
54
+ ? target
55
+ : remaining.filter((candidate) => candidate.status === candidateStatus));
56
+ }
57
+ export function describeBacklog(backlog, statuses = STATUSES) {
58
+ if (backlog.items.length === 0 && statuses.length === STATUSES.length) {
59
+ return "The project backlog is empty.";
60
+ }
61
+ return statuses.map((status) => {
62
+ const items = backlog.items.filter((item) => item.status === status);
63
+ const lines = items.map((item, index) => {
64
+ const notes = item.notes ? `\n Notes: ${item.notes}` : "";
65
+ return `${index}. ${item.id}: ${item.title}${notes}`;
66
+ });
67
+ return `${status.toUpperCase()} (${items.length})\n${lines.length > 0 ? lines.join("\n") : "Empty"}`;
68
+ }).join("\n\n");
69
+ }
70
+ //# sourceMappingURL=backlog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backlog.js","sourceRoot":"","sources":["../src/backlog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Plugin } from "@opencode-ai/plugin";
2
+ declare const _default: Plugin.Plugin;
3
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,169 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import { Plugin } from "@opencode-ai/plugin";
4
+ import { BACKLOG_FILE, describeBacklog, moveItem, sortByStatus, } from "./backlog.js";
5
+ import { optionalNullableString, optionalPosition, optionalStatus, record, requiredString, } from "./input.js";
6
+ import { readBacklog, updateBacklog } from "./store.js";
7
+ const idInput = {
8
+ type: "object",
9
+ properties: { id: { type: "string", minLength: 1 } },
10
+ required: ["id"],
11
+ additionalProperties: false,
12
+ };
13
+ async function backlogPath(context, sessionID) {
14
+ const session = await context.session.get({ sessionID });
15
+ return join(session.location.directory, BACKLOG_FILE);
16
+ }
17
+ async function readForSession(context, sessionID) {
18
+ return readBacklog(await backlogPath(context, sessionID));
19
+ }
20
+ async function updateForSession(context, sessionID, update) {
21
+ return updateBacklog(await backlogPath(context, sessionID), update);
22
+ }
23
+ function replaceItem(items, replacement) {
24
+ if (!items.some((item) => item.id === replacement.id)) {
25
+ throw new Error(`Backlog item ${replacement.id} does not exist`);
26
+ }
27
+ return items.map((item) => (item.id === replacement.id ? replacement : item));
28
+ }
29
+ export default Plugin.define({
30
+ id: "opencode.backlog",
31
+ tui: true,
32
+ async setup(context) {
33
+ await context.tool.transform((tools) => {
34
+ tools.add({
35
+ name: "backlog_list",
36
+ description: "List the ordered project backlog, optionally filtered by Kanban state.",
37
+ input: {
38
+ type: "object",
39
+ properties: { status: { type: "string", enum: ["todo", "doing", "done"] } },
40
+ additionalProperties: false,
41
+ },
42
+ options: { codemode: false },
43
+ execute: async (input, toolContext) => {
44
+ const status = optionalStatus(record(input));
45
+ return {
46
+ content: describeBacklog(await readForSession(context, toolContext.sessionID), status === undefined ? undefined : [status]),
47
+ };
48
+ },
49
+ });
50
+ tools.add({
51
+ name: "backlog_add",
52
+ description: "Add a task to the project backlog at a position within a Kanban state.",
53
+ input: {
54
+ type: "object",
55
+ properties: {
56
+ title: { type: "string", minLength: 1 },
57
+ notes: { type: "string" },
58
+ status: { type: "string", enum: ["todo", "doing", "done"] },
59
+ position: { type: "integer", minimum: 0 },
60
+ },
61
+ required: ["title"],
62
+ additionalProperties: false,
63
+ },
64
+ options: { codemode: false },
65
+ execute: async (input, toolContext) => {
66
+ const values = record(input);
67
+ const title = requiredString(values, "title");
68
+ const notes = optionalNullableString(values, "notes") ?? undefined;
69
+ const status = optionalStatus(values) ?? "todo";
70
+ const position = optionalPosition(values);
71
+ const item = {
72
+ id: randomUUID(),
73
+ title,
74
+ ...(notes === undefined ? {} : { notes }),
75
+ status,
76
+ };
77
+ const backlog = await updateForSession(context, toolContext.sessionID, (current) => ({
78
+ version: 1,
79
+ items: moveItem([...current.items, item], item.id, status, position),
80
+ }));
81
+ return { content: `Added ${item.id}.\n\n${describeBacklog(backlog)}` };
82
+ },
83
+ });
84
+ tools.add({
85
+ name: "backlog_update",
86
+ description: "Change a backlog task title or notes. Use null notes to remove them.",
87
+ input: {
88
+ type: "object",
89
+ properties: {
90
+ id: { type: "string", minLength: 1 },
91
+ title: { type: "string", minLength: 1 },
92
+ notes: { type: ["string", "null"] },
93
+ },
94
+ required: ["id"],
95
+ additionalProperties: false,
96
+ },
97
+ options: { codemode: false },
98
+ execute: async (input, toolContext) => {
99
+ const values = record(input);
100
+ const id = requiredString(values, "id");
101
+ const title = values.title === undefined ? undefined : requiredString(values, "title");
102
+ const notes = optionalNullableString(values, "notes");
103
+ if (title === undefined && notes === undefined) {
104
+ throw new Error("backlog_update requires a title or notes change");
105
+ }
106
+ const backlog = await updateForSession(context, toolContext.sessionID, (current) => {
107
+ const item = current.items.find((candidate) => candidate.id === id);
108
+ if (!item)
109
+ throw new Error(`Backlog item ${id} does not exist`);
110
+ const { notes: _currentNotes, ...itemWithoutNotes } = item;
111
+ const replacement = {
112
+ ...(notes === null ? itemWithoutNotes : item),
113
+ ...(title === undefined ? {} : { title }),
114
+ ...(notes === undefined || notes === null ? {} : { notes }),
115
+ };
116
+ return { version: 1, items: sortByStatus(replaceItem(current.items, replacement)) };
117
+ });
118
+ return { content: `Updated ${id}.\n\n${describeBacklog(backlog)}` };
119
+ },
120
+ });
121
+ tools.add({
122
+ name: "backlog_move",
123
+ description: "Change a task Kanban state or its zero-based position within that state.",
124
+ input: {
125
+ type: "object",
126
+ properties: {
127
+ id: { type: "string", minLength: 1 },
128
+ status: { type: "string", enum: ["todo", "doing", "done"] },
129
+ position: { type: "integer", minimum: 0 },
130
+ },
131
+ required: ["id"],
132
+ additionalProperties: false,
133
+ },
134
+ options: { codemode: false },
135
+ execute: async (input, toolContext) => {
136
+ const values = record(input);
137
+ const id = requiredString(values, "id");
138
+ const status = optionalStatus(values);
139
+ const position = optionalPosition(values);
140
+ if (status === undefined && position === undefined) {
141
+ throw new Error("backlog_move requires a status or position change");
142
+ }
143
+ const backlog = await updateForSession(context, toolContext.sessionID, (current) => ({
144
+ version: 1,
145
+ items: moveItem(current.items, id, status, position),
146
+ }));
147
+ return { content: `Moved ${id}.\n\n${describeBacklog(backlog)}` };
148
+ },
149
+ });
150
+ tools.add({
151
+ name: "backlog_remove",
152
+ description: "Permanently remove a task from the project backlog.",
153
+ input: idInput,
154
+ options: { codemode: false },
155
+ execute: async (input, toolContext) => {
156
+ const id = requiredString(record(input), "id");
157
+ const backlog = await updateForSession(context, toolContext.sessionID, (current) => {
158
+ if (!current.items.some((item) => item.id === id)) {
159
+ throw new Error(`Backlog item ${id} does not exist`);
160
+ }
161
+ return { version: 1, items: current.items.filter((item) => item.id !== id) };
162
+ });
163
+ return { content: `Removed ${id}.\n\n${describeBacklog(backlog)}` };
164
+ },
165
+ });
166
+ });
167
+ },
168
+ });
169
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type Status } from "./backlog.js";
2
+ export declare function record(input: unknown): Record<string, unknown>;
3
+ export declare function requiredString(input: Record<string, unknown>, key: string): string;
4
+ export declare function optionalString(input: Record<string, unknown>, key: string): string | undefined;
5
+ export declare function optionalNullableString(input: Record<string, unknown>, key: string): string | null | undefined;
6
+ export declare function optionalStatus(input: Record<string, unknown>): Status | undefined;
7
+ export declare function optionalPosition(input: Record<string, unknown>): number | undefined;
package/dist/input.js ADDED
@@ -0,0 +1,46 @@
1
+ import { isStatus } from "./backlog.js";
2
+ export function record(input) {
3
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
4
+ throw new Error("Tool input must be an object");
5
+ }
6
+ return input;
7
+ }
8
+ export function requiredString(input, key) {
9
+ const value = input[key];
10
+ if (typeof value !== "string" || value.trim().length === 0) {
11
+ throw new Error(`${key} must be a non-empty string`);
12
+ }
13
+ return value;
14
+ }
15
+ export function optionalString(input, key) {
16
+ const value = input[key];
17
+ if (value === undefined)
18
+ return undefined;
19
+ if (typeof value !== "string")
20
+ throw new Error(`${key} must be a string`);
21
+ return value;
22
+ }
23
+ export function optionalNullableString(input, key) {
24
+ const value = input[key];
25
+ if (value === undefined || value === null || typeof value === "string")
26
+ return value;
27
+ throw new Error(`${key} must be a string or null`);
28
+ }
29
+ export function optionalStatus(input) {
30
+ const value = input.status;
31
+ if (value === undefined)
32
+ return undefined;
33
+ if (!isStatus(value))
34
+ throw new Error("status must be todo, doing, or done");
35
+ return value;
36
+ }
37
+ export function optionalPosition(input) {
38
+ const value = input.position;
39
+ if (value === undefined)
40
+ return undefined;
41
+ if (!Number.isInteger(value) || typeof value !== "number" || value < 0) {
42
+ throw new Error("position must be a non-negative integer");
43
+ }
44
+ return value;
45
+ }
46
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../src/input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAe,MAAM,cAAc,CAAA;AAEpD,MAAM,UAAU,MAAM,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;IACjD,CAAC;IACD,OAAO,KAAgC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA8B,EAAE,GAAW;IACxE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,6BAA6B,CAAC,CAAA;IACtD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA8B,EAAE,GAAW;IACxE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,mBAAmB,CAAC,CAAA;IACzE,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,KAA8B,EAC9B,GAAW;IAEX,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACpF,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,2BAA2B,CAAC,CAAA;AACpD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAA8B;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAA;IAC1B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IAC5E,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAA8B;IAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAA;IAC5B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type Backlog } from "./backlog.js";
2
+ export declare function readBacklog(path: string): Promise<Backlog>;
3
+ export declare function readBacklogSync(path: string): Backlog;
4
+ export declare function updateBacklog(path: string, update: (backlog: Backlog) => Backlog): Promise<Backlog>;
package/dist/store.js ADDED
@@ -0,0 +1,51 @@
1
+ import { readFile, rename, rm, writeFile } from "node:fs/promises";
2
+ import { readFileSync } from "node:fs";
3
+ import { randomUUID } from "node:crypto";
4
+ import lockfile from "proper-lockfile";
5
+ import { EMPTY_BACKLOG, parseBacklog } from "./backlog.js";
6
+ export async function readBacklog(path) {
7
+ try {
8
+ return parseBacklog(JSON.parse(await readFile(path, "utf8")));
9
+ }
10
+ catch (error) {
11
+ if (isMissingFile(error))
12
+ return EMPTY_BACKLOG;
13
+ throw error;
14
+ }
15
+ }
16
+ export function readBacklogSync(path) {
17
+ try {
18
+ return parseBacklog(JSON.parse(readFileSync(path, "utf8")));
19
+ }
20
+ catch (error) {
21
+ if (isMissingFile(error))
22
+ return EMPTY_BACKLOG;
23
+ throw error;
24
+ }
25
+ }
26
+ export async function updateBacklog(path, update) {
27
+ const release = await lockfile.lock(path, {
28
+ realpath: false,
29
+ retries: { retries: 10, minTimeout: 10, maxTimeout: 250 },
30
+ stale: 10_000,
31
+ });
32
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
33
+ try {
34
+ const result = parseBacklog(update(await readBacklog(path)));
35
+ await writeFile(temporary, `${JSON.stringify(result, null, 2)}\n`, "utf8");
36
+ await rename(temporary, path);
37
+ return result;
38
+ }
39
+ finally {
40
+ try {
41
+ await rm(temporary, { force: true });
42
+ }
43
+ finally {
44
+ await release();
45
+ }
46
+ }
47
+ }
48
+ function isMissingFile(error) {
49
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
50
+ }
51
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,QAAQ,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAgB,MAAM,cAAc,CAAA;AAExE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IAC/D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,aAAa,CAAC,KAAK,CAAC;YAAE,OAAO,aAAa,CAAA;QAC9C,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IAC7D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,aAAa,CAAC,KAAK,CAAC;YAAE,OAAO,aAAa,CAAA;QAC9C,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAY,EACZ,MAAqC;IAErC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;QACxC,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;QACzD,KAAK,EAAE,MAAM;KACd,CAAC,CAAA;IACF,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,UAAU,EAAE,MAAM,CAAA;IAE9D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAC5D,MAAM,SAAS,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC1E,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAC7B,OAAO,MAAM,CAAA;IACf,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACtC,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,EAAE,CAAA;QACjB,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAA;AAC7E,CAAC"}
package/dist/tui.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Plugin } from "@opencode-ai/plugin/tui";
2
+ declare const _default: Plugin.Definition;
3
+ export default _default;
package/dist/tui.js ADDED
@@ -0,0 +1,162 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@opentui/solid/jsx-runtime";
2
+ import { watch } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Plugin } from "@opencode-ai/plugin/tui";
5
+ import { createMemo, For, Show } from "solid-js";
6
+ import { BACKLOG_FILE, EMPTY_BACKLOG, STATUSES, moveItem, } from "./backlog.js";
7
+ import { readBacklog, readBacklogSync, updateBacklog } from "./store.js";
8
+ const EMPTY_SNAPSHOT = { backlog: EMPTY_BACKLOG };
9
+ function statusLabel(status) {
10
+ if (status === "todo")
11
+ return "Todo";
12
+ if (status === "doing")
13
+ return "Doing";
14
+ return "Done";
15
+ }
16
+ function BacklogView(props) {
17
+ const theme = props.context.theme;
18
+ const snapshot = createMemo(() => props.view.directories[props.directory] ?? EMPTY_SNAPSHOT);
19
+ const backlog = createMemo(() => snapshot().backlog);
20
+ const error = createMemo(() => snapshot().error);
21
+ const color = (status) => {
22
+ if (status === "doing")
23
+ return theme.text.feedback.warning.default;
24
+ if (status === "done")
25
+ return theme.text.feedback.success.default;
26
+ return theme.text.subdued;
27
+ };
28
+ return (_jsxs("box", { children: [_jsx("text", { fg: theme.text.default, children: _jsx("b", { children: "Backlog" }) }), _jsx(Show, { when: error(), children: (message) => _jsx("text", { fg: theme.text.feedback.error.default, children: message() }) }), _jsx(Show, { when: !error() && backlog().items.length === 0, children: _jsx("text", { fg: theme.text.subdued, children: "No tasks" }) }), _jsx(For, { each: STATUSES, children: (status) => {
29
+ const items = createMemo(() => backlog().items.filter((item) => item.status === status));
30
+ return (_jsx(Show, { when: items().length > 0, children: _jsxs("box", { marginTop: 1, children: [_jsxs("text", { fg: color(status), children: [_jsx("b", { children: statusLabel(status) }), " (", items().length, ")"] }), _jsx(For, { each: items(), children: (item) => (_jsxs("box", { flexDirection: "row", gap: 1, minWidth: 0, children: [_jsx("text", { fg: color(status), flexShrink: 0, children: status === "done" ? "✓" : status === "doing" ? "●" : "○" }), _jsx("text", { fg: theme.text.default, wrapMode: "none", truncate: true, flexGrow: 1, minWidth: 0, children: item.title })] })) })] }) }));
31
+ } })] }));
32
+ }
33
+ function taskDetails(item) {
34
+ return [`Status: ${statusLabel(item.status)}`, `ID: ${item.id}`, "", item.notes ?? "No notes"].join("\n");
35
+ }
36
+ async function browseBacklog(context) {
37
+ const route = context.ui.router.current();
38
+ const location = route.type === "session" ? context.data.session.get(route.sessionID)?.location : context.location;
39
+ const directory = location?.directory ?? context.data.location.default().directory;
40
+ const path = join(directory, BACKLOG_FILE);
41
+ const backlog = await readBacklog(path);
42
+ if (backlog.items.length === 0) {
43
+ await context.ui.dialog.alert({ title: "Backlog", message: "No tasks" });
44
+ return;
45
+ }
46
+ const id = await context.ui.dialog.select({
47
+ title: "Backlog",
48
+ placeholder: "Select a task",
49
+ options: backlog.items.map((item) => ({
50
+ title: item.title,
51
+ value: item.id,
52
+ ...(item.notes === undefined ? {} : { description: item.notes }),
53
+ category: statusLabel(item.status),
54
+ })),
55
+ });
56
+ if (!id)
57
+ return;
58
+ const item = backlog.items.find((candidate) => candidate.id === id);
59
+ if (!item)
60
+ return;
61
+ await context.ui.dialog.alert({ title: item.title, message: taskDetails(item) });
62
+ const status = await context.ui.dialog.select({
63
+ title: `Change status: ${item.title}`,
64
+ current: item.status,
65
+ options: STATUSES.map((candidate) => ({
66
+ title: statusLabel(candidate),
67
+ value: candidate,
68
+ ...(candidate === item.status ? { description: "Current status" } : {}),
69
+ })),
70
+ });
71
+ if (!status || status === item.status)
72
+ return;
73
+ await updateBacklog(path, (current) => ({
74
+ version: 1,
75
+ items: moveItem(current.items, item.id, status, undefined),
76
+ }));
77
+ context.ui.toast.show({ message: `Moved "${item.title}" to ${statusLabel(status)}.`, variant: "success" });
78
+ }
79
+ function Commands(props) {
80
+ props.context.keymap.layer(() => ({
81
+ mode: "global",
82
+ commands: [
83
+ {
84
+ id: "backlog.browse",
85
+ title: "Browse backlog",
86
+ description: "View backlog tasks and change their status",
87
+ group: "Backlog",
88
+ palette: true,
89
+ slash: { name: "backlog", aliases: ["tasks"] },
90
+ async run() {
91
+ try {
92
+ await browseBacklog(props.context);
93
+ }
94
+ catch (cause) {
95
+ props.context.ui.toast.show({
96
+ message: cause instanceof Error ? cause.message : String(cause),
97
+ variant: "error",
98
+ });
99
+ }
100
+ },
101
+ },
102
+ ],
103
+ }));
104
+ return null;
105
+ }
106
+ export default Plugin.define({
107
+ id: "opencode.backlog.tui",
108
+ setup(context) {
109
+ const watchers = new Map();
110
+ const [view, updateView] = context.storage.memory("view", {
111
+ initial: { directories: {} },
112
+ });
113
+ const releaseCommands = context.ui.slot({
114
+ append: "app",
115
+ render: () => _jsx(Commands, { context: context }),
116
+ });
117
+ const refreshDirectory = (directory) => {
118
+ try {
119
+ const backlog = readBacklogSync(join(directory, BACKLOG_FILE));
120
+ updateView((draft) => {
121
+ draft.directories[directory] = { backlog };
122
+ });
123
+ }
124
+ catch (cause) {
125
+ updateView((draft) => {
126
+ draft.directories[directory] = {
127
+ backlog: EMPTY_BACKLOG,
128
+ error: cause instanceof Error ? cause.message : String(cause),
129
+ };
130
+ });
131
+ }
132
+ };
133
+ const watchDirectory = (directory) => {
134
+ if (watchers.has(directory))
135
+ return;
136
+ refreshDirectory(directory);
137
+ const watcher = watch(directory, { persistent: false }, (_event, filename) => {
138
+ if (filename?.toString() === BACKLOG_FILE)
139
+ refreshDirectory(directory);
140
+ });
141
+ watchers.set(directory, watcher);
142
+ };
143
+ const releaseSlot = context.ui.slot({
144
+ append: "sidebar.content",
145
+ render: (props) => {
146
+ const directory = context.data.session.get(props.sessionID)?.location.directory;
147
+ if (!directory)
148
+ return null;
149
+ watchDirectory(directory);
150
+ return _jsx(BacklogView, { context: context, directory: directory, view: view });
151
+ },
152
+ });
153
+ return () => {
154
+ releaseSlot();
155
+ releaseCommands();
156
+ for (const watcher of watchers.values())
157
+ watcher.close();
158
+ watchers.clear();
159
+ };
160
+ },
161
+ });
162
+ //# sourceMappingURL=tui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.js","sourceRoot":"","sources":["../src/tui.tsx"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "opencode-backlog",
3
+ "version": "0.1.0",
4
+ "description": "Project backlog tools and sidebar for OpenCode 2",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/sachahjkl/opencode-backlog.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/sachahjkl/opencode-backlog/issues"
12
+ },
13
+ "homepage": "https://github.com/sachahjkl/opencode-backlog#readme",
14
+ "engines": {
15
+ "node": ">=24"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "exports": {
21
+ ".": "./dist/index.js",
22
+ "./tui": "./dist/tui.js"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.build.json",
31
+ "check": "tsc -p tsconfig.json --noEmit",
32
+ "prepack": "npm run build",
33
+ "test": "npm run build && tsc -p tsconfig.test.json && node --test .test-dist/test/*.test.js"
34
+ },
35
+ "keywords": [
36
+ "opencode",
37
+ "opencode-plugin",
38
+ "backlog",
39
+ "kanban"
40
+ ],
41
+ "license": "MIT",
42
+ "dependencies": {
43
+ "@opencode-ai/plugin": "0.0.0-beta-17927",
44
+ "proper-lockfile": "^4.1.2"
45
+ },
46
+ "peerDependencies": {
47
+ "@opentui/solid": ">=0.5.6",
48
+ "solid-js": ">=1.9.0"
49
+ },
50
+ "devDependencies": {
51
+ "@opentui/solid": "0.5.6",
52
+ "@types/node": "24.3.0",
53
+ "@types/proper-lockfile": "^4.1.4",
54
+ "solid-js": "1.9.12",
55
+ "typescript": "5.8.2"
56
+ }
57
+ }