@nickmeriano/task 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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -0
  3. package/dist/author.d.ts +17 -0
  4. package/dist/author.d.ts.map +1 -0
  5. package/dist/author.js +52 -0
  6. package/dist/author.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +415 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/index.d.ts +5 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +7 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/server.d.ts +14 -0
  16. package/dist/server.d.ts.map +1 -0
  17. package/dist/server.js +292 -0
  18. package/dist/server.js.map +1 -0
  19. package/dist/store.d.ts +63 -0
  20. package/dist/store.d.ts.map +1 -0
  21. package/dist/store.js +346 -0
  22. package/dist/store.js.map +1 -0
  23. package/dist/types.d.ts +61 -0
  24. package/dist/types.d.ts.map +1 -0
  25. package/dist/types.js +13 -0
  26. package/dist/types.js.map +1 -0
  27. package/package.json +81 -0
  28. package/skill/SKILL.md +109 -0
  29. package/src/author.ts +57 -0
  30. package/src/cli.ts +419 -0
  31. package/src/index.ts +6 -0
  32. package/src/server.ts +311 -0
  33. package/src/store.ts +418 -0
  34. package/src/types.ts +75 -0
  35. package/ui/dist/assets/index-BopXdeSy.js +229 -0
  36. package/ui/dist/assets/index-Cl_P2tLU.css +1 -0
  37. package/ui/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  38. package/ui/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  39. package/ui/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  40. package/ui/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  41. package/ui/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  42. package/ui/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  43. package/ui/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  44. package/ui/dist/icon.svg +4 -0
  45. package/ui/dist/index.html +21 -0
package/dist/server.js ADDED
@@ -0,0 +1,292 @@
1
+ import { createServer } from "node:http";
2
+ import { watch, existsSync, readFileSync, statSync } from "node:fs";
3
+ import { extname, join, normalize } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { resolveAuthor } from "./author.js";
6
+ import { findBoards, TaskStore } from "./store.js";
7
+ import { STATUSES, isStatus } from "./types.js";
8
+ /** The prebuilt SPA, shipped inside the package next to dist/. */
9
+ const UI_DIR = fileURLToPath(new URL("../ui/dist", import.meta.url));
10
+ const MIME = {
11
+ ".html": "text/html; charset=utf-8",
12
+ ".js": "text/javascript",
13
+ ".css": "text/css",
14
+ ".svg": "image/svg+xml",
15
+ ".json": "application/json",
16
+ ".png": "image/png",
17
+ ".ico": "image/x-icon",
18
+ ".woff2": "font/woff2",
19
+ ".map": "application/json",
20
+ };
21
+ function json(res, status, body) {
22
+ const data = JSON.stringify(body);
23
+ res.writeHead(status, { "Content-Type": "application/json" });
24
+ res.end(data);
25
+ }
26
+ async function readBody(req) {
27
+ const chunks = [];
28
+ for await (const chunk of req)
29
+ chunks.push(chunk);
30
+ const text = Buffer.concat(chunks).toString("utf8");
31
+ if (!text)
32
+ return {};
33
+ return JSON.parse(text);
34
+ }
35
+ function parsePatch(body) {
36
+ const patch = {};
37
+ if (typeof body.title === "string")
38
+ patch.title = body.title;
39
+ if (typeof body.description === "string")
40
+ patch.description = body.description;
41
+ if (typeof body.status === "string") {
42
+ if (!isStatus(body.status))
43
+ throw new Error(`invalid status: ${body.status}`);
44
+ patch.status = body.status;
45
+ }
46
+ if (Array.isArray(body.tags))
47
+ patch.tags = body.tags.map(String);
48
+ if (typeof body.milestone === "string" || body.milestone === null) {
49
+ patch.milestone = body.milestone === "" ? null : body.milestone;
50
+ }
51
+ if (typeof body.needsHuman === "boolean")
52
+ patch.needsHuman = body.needsHuman;
53
+ if (typeof body.position === "number")
54
+ patch.position = body.position;
55
+ return patch;
56
+ }
57
+ /**
58
+ * `task serve` — one plain node:http server for the static UI, the JSON API,
59
+ * and an SSE stream that pings whenever anything writes to a `.task/` (the UI
60
+ * itself, another terminal, or an agent running the CLI). The UI refetches on
61
+ * every ping, so "realtime" is just fs.watch + a debounce — no sockets, no
62
+ * state to reconcile.
63
+ *
64
+ * Serves every board found at or below `serveRoot` (see `findBoards`): API
65
+ * requests pick one with `?board=<id>`, defaulting to the first. One board is
66
+ * the common case and behaves exactly as before.
67
+ */
68
+ export function createTaskServer(serveRoot) {
69
+ const clients = new Set();
70
+ // Identity is resolved here, server-side, once: the board runs on your own
71
+ // machine as you, so there is no login — and nothing a browser sends about
72
+ // who it is can be trusted or is worth trusting. All boards live in the
73
+ // same checkout, so one resolution covers them all.
74
+ const author = resolveAuthor(undefined, serveRoot).name;
75
+ const changed = new Set();
76
+ let pending = null;
77
+ const noteChange = (id) => {
78
+ changed.add(id);
79
+ if (pending)
80
+ return;
81
+ pending = setTimeout(() => {
82
+ pending = null;
83
+ const ids = [...changed];
84
+ changed.clear();
85
+ for (const id of ids) {
86
+ const message = `data: ${JSON.stringify({ type: "change", board: id })}\n\n`;
87
+ for (const client of clients)
88
+ client.write(message);
89
+ }
90
+ }, 80);
91
+ };
92
+ const boards = new Map();
93
+ let ordered = [];
94
+ const openBoard = (ref) => {
95
+ const store = new TaskStore(ref.root);
96
+ const watcher = watch(store.taskDir, () => noteChange(ref.id));
97
+ // A deleted board dir can make fs.watch emit; the next rescan cleans up.
98
+ watcher.on("error", () => { });
99
+ boards.set(ref.id, { store, watcher });
100
+ };
101
+ /** Reconcile open boards with what's on disk; cheap enough to run per request. */
102
+ const syncBoards = () => {
103
+ const found = findBoards(serveRoot);
104
+ const ids = new Set(found.map((ref) => ref.id));
105
+ for (const [id, board] of boards) {
106
+ if (!ids.has(id)) {
107
+ board.watcher.close();
108
+ board.store.close();
109
+ boards.delete(id);
110
+ }
111
+ }
112
+ for (const ref of found)
113
+ if (!boards.has(ref.id))
114
+ openBoard(ref);
115
+ ordered = found.map((ref) => ref.id);
116
+ return found;
117
+ };
118
+ syncBoards();
119
+ if (boards.size === 0) {
120
+ throw new Error(`no .task directory found at or below ${serveRoot} — run \`task init\` first`);
121
+ }
122
+ const heartbeat = setInterval(() => {
123
+ for (const client of clients)
124
+ client.write(`: ping\n\n`);
125
+ }, 30_000);
126
+ const server = createServer((req, res) => {
127
+ try {
128
+ route(req, res);
129
+ }
130
+ catch (error) {
131
+ json(res, 400, { error: error instanceof Error ? error.message : String(error) });
132
+ }
133
+ });
134
+ server.on("close", () => {
135
+ if (pending)
136
+ clearTimeout(pending);
137
+ clearInterval(heartbeat);
138
+ for (const board of boards.values()) {
139
+ board.watcher.close();
140
+ board.store.close();
141
+ }
142
+ for (const client of clients)
143
+ client.end();
144
+ });
145
+ function route(req, res) {
146
+ const url = new URL(req.url ?? "/", "http://localhost");
147
+ const path = url.pathname;
148
+ if (path === "/api/events") {
149
+ res.writeHead(200, {
150
+ "Content-Type": "text/event-stream",
151
+ "Cache-Control": "no-cache",
152
+ Connection: "keep-alive",
153
+ });
154
+ res.write(`retry: 1000\n\n`);
155
+ clients.add(res);
156
+ req.on("close", () => clients.delete(res));
157
+ return;
158
+ }
159
+ if (path === "/api/boards") {
160
+ const found = syncBoards();
161
+ json(res, 200, {
162
+ boards: found.map((ref) => {
163
+ const { config } = boards.get(ref.id).store;
164
+ return { id: ref.id, name: config.name, prefix: config.prefix };
165
+ }),
166
+ });
167
+ return;
168
+ }
169
+ if (path.startsWith("/api/")) {
170
+ const id = url.searchParams.get("board") ?? ordered[0];
171
+ const board = boards.get(id);
172
+ if (!board) {
173
+ json(res, 404, { error: `no such board: ${id}` });
174
+ return;
175
+ }
176
+ routeApi(board.store, req, res, path);
177
+ return;
178
+ }
179
+ serveStatic(path, res);
180
+ }
181
+ function routeApi(store, req, res, path) {
182
+ if (path === "/api/project") {
183
+ json(res, 200, {
184
+ name: store.config.name,
185
+ prefix: store.config.prefix,
186
+ statuses: STATUSES,
187
+ author,
188
+ });
189
+ return;
190
+ }
191
+ if (path === "/api/tasks" && req.method === "GET") {
192
+ const counts = store.commentCounts();
193
+ const tasks = store
194
+ .list()
195
+ .map((t) => ({ ...t, commentCount: counts.get(t.number) ?? 0 }));
196
+ json(res, 200, { tasks });
197
+ return;
198
+ }
199
+ if (path === "/api/tasks" && req.method === "POST") {
200
+ void readBody(req)
201
+ .then((body) => {
202
+ if (typeof body.title !== "string" || !body.title.trim()) {
203
+ throw new Error("title is required");
204
+ }
205
+ const patch = parsePatch(body);
206
+ const task = store.create({ title: body.title.trim(), ...patch });
207
+ json(res, 201, { task });
208
+ })
209
+ .catch((error) => json(res, 400, { error: error.message }));
210
+ return;
211
+ }
212
+ const taskMatch = /^\/api\/tasks\/([^/]+)(\/comments(?:\/(\d+))?)?$/.exec(path);
213
+ if (taskMatch) {
214
+ const number = store.parseId(decodeURIComponent(taskMatch[1]));
215
+ if (taskMatch[3] && req.method === "DELETE") {
216
+ const commentId = Number(taskMatch[3]);
217
+ const existing = store.comments(number).find((c) => c.id === commentId);
218
+ if (!existing) {
219
+ json(res, 404, { error: `no such comment: ${commentId}` });
220
+ return;
221
+ }
222
+ // Same identity rule as posting: the server's resolved author is the
223
+ // only author it trusts, so you can only delete what it says is yours.
224
+ if (existing.author !== author) {
225
+ json(res, 403, { error: "only your own comments can be deleted" });
226
+ return;
227
+ }
228
+ store.deleteComment(number, commentId);
229
+ json(res, 200, { ok: true });
230
+ return;
231
+ }
232
+ if (taskMatch[2] && !taskMatch[3] && req.method === "POST") {
233
+ void readBody(req)
234
+ .then((body) => {
235
+ if (typeof body.body !== "string" || !body.body.trim()) {
236
+ throw new Error("body is required");
237
+ }
238
+ // `body.author` is ignored on purpose — unauthenticated noise.
239
+ const comment = store.addComment(number, body.body.trim(), author);
240
+ json(res, 201, { comment });
241
+ })
242
+ .catch((error) => json(res, 400, { error: error.message }));
243
+ return;
244
+ }
245
+ if (!taskMatch[2]) {
246
+ if (req.method === "GET") {
247
+ const task = store.get(number);
248
+ if (!task) {
249
+ json(res, 404, { error: `no such task: ${store.displayId(number)}` });
250
+ return;
251
+ }
252
+ json(res, 200, { task, comments: store.comments(number) });
253
+ return;
254
+ }
255
+ if (req.method === "PATCH") {
256
+ void readBody(req)
257
+ .then((body) => {
258
+ const task = store.update(number, parsePatch(body));
259
+ json(res, 200, { task });
260
+ })
261
+ .catch((error) => json(res, 400, { error: error.message }));
262
+ return;
263
+ }
264
+ if (req.method === "DELETE") {
265
+ store.delete(number);
266
+ json(res, 200, { ok: true });
267
+ return;
268
+ }
269
+ }
270
+ }
271
+ json(res, 404, { error: "not found" });
272
+ }
273
+ function serveStatic(path, res) {
274
+ const safe = normalize(path).replace(/^(\.\.[/\\])+/, "");
275
+ let file = join(UI_DIR, safe);
276
+ if (!existsSync(file) || statSync(file).isDirectory()) {
277
+ file = join(UI_DIR, "index.html"); // SPA fallback
278
+ }
279
+ if (!existsSync(file)) {
280
+ res.writeHead(404, { "Content-Type": "text/plain" });
281
+ res.end("UI build not found — run the package build (vite build ui).");
282
+ return;
283
+ }
284
+ res.writeHead(200, {
285
+ "Content-Type": MIME[extname(file)] ?? "application/octet-stream",
286
+ "Cache-Control": "no-cache",
287
+ });
288
+ res.end(readFileSync(file));
289
+ }
290
+ return server;
291
+ }
292
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { Comment, ProjectConfig, Task, TaskFilter, TaskInput, TaskPatch } from "./types.js";
2
+ export declare const TASK_DIR = ".task";
3
+ export declare const DB_FILE = "tasks.db";
4
+ export declare const CONFIG_FILE = "config.json";
5
+ /**
6
+ * Walk up from `from` looking for a `.task/config.json`, the way git finds its
7
+ * `.git`. Returns the directory that *contains* `.task`, or null.
8
+ */
9
+ export declare function findRoot(from: string): string | null;
10
+ export interface BoardRef {
11
+ /** POSIX-style path relative to the serve root — "." for the root board. */
12
+ id: string;
13
+ /** Absolute directory containing `.task/`. */
14
+ root: string;
15
+ }
16
+ /**
17
+ * The multi-board complement of `findRoot`: walk *down* from `serveRoot`
18
+ * collecting every directory that holds a `.task/config.json`, so one
19
+ * `task serve` at a monorepo root can serve every nested board. Boards are
20
+ * identified by relative path — unique where names need not be. Dot-dirs,
21
+ * dependency/build dirs and symlinks are skipped; unreadable dirs are ignored.
22
+ */
23
+ export declare function findBoards(serveRoot: string, maxDepth?: number): BoardRef[];
24
+ /** Derive an id prefix from a project name: "phone agent" → "PHONE". */
25
+ export declare function derivePrefix(name: string): string;
26
+ /**
27
+ * The whole persistence layer: one SQLite database inside `.task/`, opened
28
+ * synchronously via node:sqlite (built into Node ≥22.13 — zero dependencies,
29
+ * nothing to compile, safe for `npx`).
30
+ */
31
+ export declare class TaskStore {
32
+ readonly root: string;
33
+ readonly taskDir: string;
34
+ readonly config: ProjectConfig;
35
+ private db;
36
+ constructor(root: string);
37
+ close(): void;
38
+ displayId(number: number): string;
39
+ /** Accepts "PHONE-12", "phone-12" or "12". */
40
+ parseId(ref: string): number;
41
+ private toTask;
42
+ private toComment;
43
+ list(filter?: TaskFilter): Task[];
44
+ get(number: number): Task | null;
45
+ create(input: TaskInput): Task;
46
+ update(number: number, patch: TaskPatch): Task;
47
+ delete(number: number): void;
48
+ comments(number: number): Comment[];
49
+ addComment(number: number, body: string, author?: string): Comment;
50
+ deleteComment(number: number, commentId: number): Comment;
51
+ commentCounts(): Map<number, number>;
52
+ }
53
+ export interface InitOptions {
54
+ name: string;
55
+ prefix?: string;
56
+ }
57
+ /**
58
+ * Create `.task/` in `root`: config, an empty database, and a .gitignore for
59
+ * SQLite's transient sidecar files (the database itself is meant to be
60
+ * committed — that's the point).
61
+ */
62
+ export declare function initProject(root: string, options: InitOptions): TaskStore;
63
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EAEb,IAAI,EACJ,UAAU,EACV,SAAS,EACT,SAAS,EACV,MAAM,YAAY,CAAA;AAEnB,eAAO,MAAM,QAAQ,UAAU,CAAA;AAC/B,eAAO,MAAM,OAAO,aAAa,CAAA;AACjC,eAAO,MAAM,WAAW,gBAAgB,CAAA;AAKxC;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQpD;AAED,MAAM,WAAW,QAAQ;IACvB,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAA;IACV,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAA;CACb;AAKD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,QAAQ,EAAE,CA4BtE;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AA2BD;;;;GAIG;AACH,qBAAa,SAAS;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,OAAO,CAAC,EAAE,CAAc;gBAEZ,IAAI,EAAE,MAAM;IAcxB,KAAK,IAAI,IAAI;IAIb,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAIjC,8CAA8C;IAC9C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAM5B,OAAO,CAAC,MAAM;IAgBd,OAAO,CAAC,SAAS;IAUjB,IAAI,CAAC,MAAM,GAAE,UAAe,GAAG,IAAI,EAAE;IAyBrC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAOhC,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IA0B9B,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAoC9C,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK5B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE;IAOnC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,OAAO;IAW9D,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IASzD,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAMrC;AAgFD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,SAAS,CAiBzE"}