@xynogen/pix-core 0.2.4 → 0.3.1

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 +24 -21
  2. package/package.json +11 -17
  3. package/skills/ask-user/SKILL.md +0 -48
  4. package/src/commands/agent-sop/agent-sop.ts +0 -58
  5. package/src/commands/clear/clear.ts +0 -32
  6. package/src/commands/diff/diff.ts +0 -32
  7. package/src/commands/models/models.test.ts +0 -95
  8. package/src/commands/models/models.ts +0 -367
  9. package/src/commands/models/patch-builtin.test.ts +0 -66
  10. package/src/commands/models/patch-builtin.ts +0 -120
  11. package/src/commands/tools.test.ts +0 -15
  12. package/src/commands/update/update.test.ts +0 -112
  13. package/src/commands/update/update.ts +0 -271
  14. package/src/index.ts +0 -45
  15. package/src/lib/data.ts +0 -33
  16. package/src/nudge/capability.test.ts +0 -258
  17. package/src/nudge/capability.ts +0 -189
  18. package/src/nudge/index.ts +0 -17
  19. package/src/nudge/tools.test.ts +0 -157
  20. package/src/nudge/tools.ts +0 -212
  21. package/src/tool/ask/ask.test.ts +0 -243
  22. package/src/tool/ask/components.ts +0 -55
  23. package/src/tool/ask/helpers.ts +0 -77
  24. package/src/tool/ask/index.ts +0 -130
  25. package/src/tool/ask/questionnaire.ts +0 -693
  26. package/src/tool/ask/rpc.ts +0 -84
  27. package/src/tool/ask/schema.ts +0 -69
  28. package/src/tool/ask/single-select-layout.test.ts +0 -124
  29. package/src/tool/ask/single-select-layout.ts +0 -237
  30. package/src/tool/ask/types.ts +0 -17
  31. package/src/tool/todo/todo.test.ts +0 -646
  32. package/src/tool/todo/todo.ts +0 -218
  33. package/src/tool/toolbox/toolbox.test.ts +0 -314
  34. package/src/tool/toolbox/toolbox.ts +0 -570
  35. package/src/ui/diagnostics.ts +0 -145
  36. package/src/ui/footer.ts +0 -513
  37. package/src/ui/welcome.test.ts +0 -124
  38. package/src/ui/welcome.ts +0 -369
@@ -1,646 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import registerTodo, { renderTodoLines, type TodoItem } from "./todo.ts";
3
-
4
- // Stub theme tags each fragment with its color/bold so assertions can verify
5
- // which status got which tint, without depending on real ANSI codes.
6
- const tagTheme = {
7
- fg: (color: string, text: string) => `[${color}]${text}[/]`,
8
- bold: (text: string) => `<b>${text}</b>`,
9
- };
10
-
11
- // ─── Helpers ────────────────────────────────────────────────────────────────
12
-
13
- /** Create a mock ExtensionAPI that captures the registered tool's execute fn. */
14
- function makeHost(
15
- initialEntries: Array<{
16
- type: string;
17
- customType?: string;
18
- data?: unknown;
19
- }> = [],
20
- ) {
21
- let capturedExecute:
22
- | ((
23
- id: string,
24
- params: Record<string, unknown>,
25
- ) => Promise<{
26
- content: Array<{ type: string; text: string }>;
27
- isError?: boolean;
28
- }>)
29
- | null = null;
30
- const appendCalls: Array<{ type: string; data: unknown }> = [];
31
- const handlers: Record<
32
- string,
33
- Array<(event: unknown, ctx?: unknown) => unknown>
34
- > = {};
35
-
36
- const pi = {
37
- registerTool(def: { name: string; execute: typeof capturedExecute }) {
38
- capturedExecute = def.execute;
39
- },
40
- appendEntry(type: string, data: unknown) {
41
- appendCalls.push({ type, data });
42
- },
43
- on(ev: string, fn: (event: unknown, ctx?: unknown) => unknown) {
44
- (handlers[ev] ??= []).push(fn);
45
- },
46
- async emit(ev: string, event?: unknown, ctx?: unknown) {
47
- for (const fn of handlers[ev] ?? []) await fn(event, ctx);
48
- },
49
- } as never;
50
-
51
- const sessionManager = {
52
- getEntries() {
53
- return initialEntries;
54
- },
55
- };
56
-
57
- return {
58
- pi,
59
- sessionManager,
60
- get execute() {
61
- return capturedExecute!;
62
- },
63
- appendCalls,
64
- async emit(ev: string, event?: unknown, ctx?: unknown) {
65
- for (const fn of handlers[ev] ?? []) await fn(event, ctx);
66
- },
67
- };
68
- }
69
-
70
- async function run(
71
- execute: (
72
- id: string,
73
- params: Record<string, unknown>,
74
- ) => Promise<{
75
- content: Array<{ type: string; text: string }>;
76
- isError?: boolean;
77
- }>,
78
- params: Record<string, unknown>,
79
- ) {
80
- return execute("call-1", params);
81
- }
82
-
83
- function text(result: { content: Array<{ type: string; text: string }> }) {
84
- return result.content.map((c) => c.text).join("\n");
85
- }
86
-
87
- // ─── parseItems (via set/add) ───────────────────────────────────────────────
88
-
89
- describe("todo actions", () => {
90
- test("list on empty returns (no todos)", async () => {
91
- const host = makeHost();
92
- registerTodo(host.pi);
93
- await host.emit(
94
- "session_start",
95
- {},
96
- { sessionManager: host.sessionManager },
97
- );
98
- const result = await run(host.execute, { action: "list" });
99
- expect(text(result)).toBe("(no todos)");
100
- });
101
-
102
- test("set creates items from newline text", async () => {
103
- const host = makeHost();
104
- registerTodo(host.pi);
105
- await host.emit(
106
- "session_start",
107
- {},
108
- { sessionManager: host.sessionManager },
109
- );
110
- const result = await run(host.execute, {
111
- action: "set",
112
- items: "alpha\nbravo\ncharlie",
113
- });
114
- const out = text(result);
115
- expect(out).toContain("Todos 0/3 done");
116
- expect(out).toContain("○ 1. alpha");
117
- expect(out).toContain("○ 2. bravo");
118
- expect(out).toContain("○ 3. charlie");
119
- });
120
-
121
- test("set creates items from numbered list", async () => {
122
- const host = makeHost();
123
- registerTodo(host.pi);
124
- await host.emit(
125
- "session_start",
126
- {},
127
- { sessionManager: host.sessionManager },
128
- );
129
- const result = await run(host.execute, {
130
- action: "set",
131
- items: "1. alpha\n2. bravo",
132
- });
133
- expect(text(result)).toContain("○ 1. alpha");
134
- });
135
-
136
- test("set creates items from bullet list", async () => {
137
- const host = makeHost();
138
- registerTodo(host.pi);
139
- await host.emit(
140
- "session_start",
141
- {},
142
- { sessionManager: host.sessionManager },
143
- );
144
- const result = await run(host.execute, {
145
- action: "set",
146
- items: "- alpha\n* bravo",
147
- });
148
- expect(text(result)).toContain("○ 1. alpha");
149
- expect(text(result)).toContain("○ 2. bravo");
150
- });
151
-
152
- test("set ignores empty lines", async () => {
153
- const host = makeHost();
154
- registerTodo(host.pi);
155
- await host.emit(
156
- "session_start",
157
- {},
158
- { sessionManager: host.sessionManager },
159
- );
160
- const result = await run(host.execute, {
161
- action: "set",
162
- items: "alpha\n\nbravo\n \ncharlie",
163
- });
164
- expect(text(result)).toContain("Todos 0/3 done");
165
- });
166
-
167
- test("set with empty items returns error", async () => {
168
- const host = makeHost();
169
- registerTodo(host.pi);
170
- await host.emit(
171
- "session_start",
172
- {},
173
- { sessionManager: host.sessionManager },
174
- );
175
- const result = await run(host.execute, { action: "set", items: "" });
176
- expect(result.isError).toBe(true);
177
- expect(text(result)).toContain("non-empty");
178
- });
179
-
180
- test("set with only whitespace returns error", async () => {
181
- const host = makeHost();
182
- registerTodo(host.pi);
183
- await host.emit(
184
- "session_start",
185
- {},
186
- { sessionManager: host.sessionManager },
187
- );
188
- const result = await run(host.execute, { action: "set", items: " \n " });
189
- expect(result.isError).toBe(true);
190
- });
191
-
192
- test("set resets ids on re-set", async () => {
193
- const host = makeHost();
194
- registerTodo(host.pi);
195
- await host.emit(
196
- "session_start",
197
- {},
198
- { sessionManager: host.sessionManager },
199
- );
200
- await run(host.execute, { action: "set", items: "first\nsecond" });
201
- const result = await run(host.execute, { action: "set", items: "new" });
202
- expect(text(result)).toContain("○ 1. new");
203
- expect(text(result)).toContain("Todos 0/1 done");
204
- });
205
-
206
- test("add appends items", async () => {
207
- const host = makeHost();
208
- registerTodo(host.pi);
209
- await host.emit(
210
- "session_start",
211
- {},
212
- { sessionManager: host.sessionManager },
213
- );
214
- await run(host.execute, { action: "set", items: "alpha" });
215
- const result = await run(host.execute, {
216
- action: "add",
217
- items: "bravo\ncharlie",
218
- });
219
- const out = text(result);
220
- expect(out).toContain("○ 1. alpha");
221
- expect(out).toContain("○ 2. bravo");
222
- expect(out).toContain("○ 3. charlie");
223
- expect(out).toContain("Todos 0/3 done");
224
- });
225
-
226
- test("add with ids continuing sequence", async () => {
227
- const host = makeHost();
228
- registerTodo(host.pi);
229
- await host.emit(
230
- "session_start",
231
- {},
232
- { sessionManager: host.sessionManager },
233
- );
234
- await run(host.execute, { action: "set", items: "first\nsecond\nthird" });
235
- const result = await run(host.execute, { action: "add", items: "fourth" });
236
- expect(text(result)).toContain("○ 4. fourth");
237
- });
238
-
239
- test("add with empty items returns error", async () => {
240
- const host = makeHost();
241
- registerTodo(host.pi);
242
- await host.emit(
243
- "session_start",
244
- {},
245
- { sessionManager: host.sessionManager },
246
- );
247
- const result = await run(host.execute, { action: "add", items: "" });
248
- expect(result.isError).toBe(true);
249
- expect(text(result)).toContain("non-empty");
250
- });
251
-
252
- test("update changes status", async () => {
253
- const host = makeHost();
254
- registerTodo(host.pi);
255
- await host.emit(
256
- "session_start",
257
- {},
258
- { sessionManager: host.sessionManager },
259
- );
260
- await run(host.execute, { action: "set", items: "alpha\nbravo" });
261
- const result = await run(host.execute, {
262
- action: "update",
263
- id: 1,
264
- status: "done",
265
- });
266
- const out = text(result);
267
- expect(out).toContain("● 1. alpha");
268
- expect(out).toContain("○ 2. bravo");
269
- expect(out).toContain("Todos 1/2 done");
270
- });
271
-
272
- test("update changes text", async () => {
273
- const host = makeHost();
274
- registerTodo(host.pi);
275
- await host.emit(
276
- "session_start",
277
- {},
278
- { sessionManager: host.sessionManager },
279
- );
280
- await run(host.execute, { action: "set", items: "old name" });
281
- const result = await run(host.execute, {
282
- action: "update",
283
- id: 1,
284
- text: "new name",
285
- });
286
- expect(text(result)).toContain("○ 1. new name");
287
- });
288
-
289
- test("update changes status and text together", async () => {
290
- const host = makeHost();
291
- registerTodo(host.pi);
292
- await host.emit(
293
- "session_start",
294
- {},
295
- { sessionManager: host.sessionManager },
296
- );
297
- await run(host.execute, { action: "set", items: "alpha" });
298
- const result = await run(host.execute, {
299
- action: "update",
300
- id: 1,
301
- status: "blocked",
302
- text: "alpha (waiting)",
303
- });
304
- const out = text(result);
305
- expect(out).toContain("⊘ 1. alpha (waiting)");
306
- expect(out).toContain("Todos 0/1 done");
307
- });
308
-
309
- test("update unknown id returns error", async () => {
310
- const host = makeHost();
311
- registerTodo(host.pi);
312
- await host.emit(
313
- "session_start",
314
- {},
315
- { sessionManager: host.sessionManager },
316
- );
317
- const result = await run(host.execute, {
318
- action: "update",
319
- id: 999,
320
- status: "done",
321
- });
322
- expect(result.isError).toBe(true);
323
- expect(text(result)).toContain("999");
324
- });
325
-
326
- test("update without status or text does nothing", async () => {
327
- const host = makeHost();
328
- registerTodo(host.pi);
329
- await host.emit(
330
- "session_start",
331
- {},
332
- { sessionManager: host.sessionManager },
333
- );
334
- await run(host.execute, { action: "set", items: "unchanged" });
335
- const result = await run(host.execute, { action: "update", id: 1 });
336
- expect(text(result)).toContain("○ 1. unchanged");
337
- });
338
-
339
- test("clear empties list", async () => {
340
- const host = makeHost();
341
- registerTodo(host.pi);
342
- await host.emit(
343
- "session_start",
344
- {},
345
- { sessionManager: host.sessionManager },
346
- );
347
- await run(host.execute, { action: "set", items: "alpha\nbravo" });
348
- const result = await run(host.execute, { action: "clear" });
349
- expect(text(result)).toContain("Todos cleared");
350
- // next list should show empty
351
- const list = await run(host.execute, { action: "list" });
352
- expect(text(list)).toBe("(no todos)");
353
- });
354
-
355
- test("clear resets id counter", async () => {
356
- const host = makeHost();
357
- registerTodo(host.pi);
358
- await host.emit(
359
- "session_start",
360
- {},
361
- { sessionManager: host.sessionManager },
362
- );
363
- await run(host.execute, { action: "set", items: "alpha\nbravo\ncharlie" });
364
- await run(host.execute, { action: "clear" });
365
- const result = await run(host.execute, { action: "set", items: "new" });
366
- expect(text(result)).toContain("○ 1. new");
367
- });
368
-
369
- test("unknown action returns error", async () => {
370
- const host = makeHost();
371
- registerTodo(host.pi);
372
- await host.emit(
373
- "session_start",
374
- {},
375
- { sessionManager: host.sessionManager },
376
- );
377
- const result = await run(host.execute, { action: "bogus" });
378
- expect(result.isError).toBe(true);
379
- expect(text(result)).toContain("Unknown action");
380
- });
381
-
382
- test("all status glyphs render correctly", async () => {
383
- const host = makeHost();
384
- registerTodo(host.pi);
385
- await host.emit(
386
- "session_start",
387
- {},
388
- { sessionManager: host.sessionManager },
389
- );
390
- await run(host.execute, { action: "set", items: "a\nb\nc\nd" });
391
- await run(host.execute, { action: "update", id: 1, status: "pending" });
392
- await run(host.execute, { action: "update", id: 2, status: "in_progress" });
393
- await run(host.execute, { action: "update", id: 3, status: "done" });
394
- await run(host.execute, { action: "update", id: 4, status: "blocked" });
395
- const out = text(await run(host.execute, { action: "list" }));
396
- expect(out).toContain("○ 1. a");
397
- expect(out).toContain("◐ 2. b");
398
- expect(out).toContain("● 3. c");
399
- expect(out).toContain("⊘ 4. d");
400
- expect(out).toContain("Todos 1/4 done");
401
- });
402
- });
403
-
404
- // ─── Persistence ────────────────────────────────────────────────────────────
405
-
406
- describe("persistence", () => {
407
- test("set persists todos", async () => {
408
- const host = makeHost();
409
- registerTodo(host.pi);
410
- await host.emit(
411
- "session_start",
412
- {},
413
- { sessionManager: host.sessionManager },
414
- );
415
- host.appendCalls.length = 0;
416
- await run(host.execute, { action: "set", items: "alpha\nbravo" });
417
- expect(host.appendCalls.length).toBe(1);
418
- expect(host.appendCalls[0].type).toBe("todo-state");
419
- const data = host.appendCalls[0].data as {
420
- todos: Array<{ id: number; text: string; status: string }>;
421
- nextTodoId: number;
422
- };
423
- expect(data.todos).toHaveLength(2);
424
- expect(data.nextTodoId).toBe(3);
425
- });
426
-
427
- test("add persists todos", async () => {
428
- const host = makeHost();
429
- registerTodo(host.pi);
430
- await host.emit(
431
- "session_start",
432
- {},
433
- { sessionManager: host.sessionManager },
434
- );
435
- await run(host.execute, { action: "set", items: "alpha" });
436
- host.appendCalls.length = 0;
437
- await run(host.execute, { action: "add", items: "bravo" });
438
- expect(host.appendCalls.length).toBe(1);
439
- });
440
-
441
- test("update persists todos", async () => {
442
- const host = makeHost();
443
- registerTodo(host.pi);
444
- await host.emit(
445
- "session_start",
446
- {},
447
- { sessionManager: host.sessionManager },
448
- );
449
- await run(host.execute, { action: "set", items: "alpha" });
450
- host.appendCalls.length = 0;
451
- await run(host.execute, { action: "update", id: 1, status: "done" });
452
- expect(host.appendCalls.length).toBe(1);
453
- });
454
-
455
- test("clear persists", async () => {
456
- const host = makeHost();
457
- registerTodo(host.pi);
458
- await host.emit(
459
- "session_start",
460
- {},
461
- { sessionManager: host.sessionManager },
462
- );
463
- await run(host.execute, { action: "set", items: "alpha" });
464
- host.appendCalls.length = 0;
465
- await run(host.execute, { action: "clear" });
466
- expect(host.appendCalls.length).toBe(1);
467
- const data = host.appendCalls[0].data as {
468
- todos: Array<unknown>;
469
- nextTodoId: number;
470
- };
471
- expect(data.todos).toEqual([]);
472
- expect(data.nextTodoId).toBe(1);
473
- });
474
- });
475
-
476
- // ─── Session restore ────────────────────────────────────────────────────────
477
-
478
- describe("restore", () => {
479
- test("restores todos from last todo-state entry", async () => {
480
- const host = makeHost([
481
- {
482
- type: "custom",
483
- customType: "todo-state",
484
- data: {
485
- todos: [{ id: 1, text: "restored", status: "done" }],
486
- nextTodoId: 2,
487
- },
488
- },
489
- ]);
490
- registerTodo(host.pi);
491
- await host.emit(
492
- "session_start",
493
- {},
494
- { sessionManager: host.sessionManager },
495
- );
496
- const result = await run(host.execute, { action: "list" });
497
- expect(text(result)).toContain("● 1. restored");
498
- expect(text(result)).toContain("Todos 1/1 done");
499
- });
500
-
501
- test("restores nextTodoId so new items continue sequence", async () => {
502
- const host = makeHost([
503
- {
504
- type: "custom",
505
- customType: "todo-state",
506
- data: {
507
- todos: [{ id: 5, text: "existing", status: "pending" }],
508
- nextTodoId: 6,
509
- },
510
- },
511
- ]);
512
- registerTodo(host.pi);
513
- await host.emit(
514
- "session_start",
515
- {},
516
- { sessionManager: host.sessionManager },
517
- );
518
- const result = await run(host.execute, { action: "add", items: "new" });
519
- expect(text(result)).toContain("○ 6. new");
520
- });
521
-
522
- test("restores nextTodoId from max id when nextTodoId missing", async () => {
523
- const host = makeHost([
524
- {
525
- type: "custom",
526
- customType: "todo-state",
527
- data: {
528
- todos: [
529
- { id: 3, text: "old", status: "done" },
530
- { id: 7, text: "newer", status: "pending" },
531
- ],
532
- },
533
- },
534
- ]);
535
- registerTodo(host.pi);
536
- await host.emit(
537
- "session_start",
538
- {},
539
- { sessionManager: host.sessionManager },
540
- );
541
- const result = await run(host.execute, { action: "add", items: "next" });
542
- expect(text(result)).toContain("○ 8. next");
543
- });
544
-
545
- test("ignores non-todo-state entries", async () => {
546
- const host = makeHost([
547
- { type: "message", data: "hello" },
548
- { type: "custom", customType: "other-thing", data: {} },
549
- {
550
- type: "custom",
551
- customType: "todo-state",
552
- data: {
553
- todos: [{ id: 1, text: "real", status: "pending" }],
554
- nextTodoId: 2,
555
- },
556
- },
557
- ]);
558
- registerTodo(host.pi);
559
- await host.emit(
560
- "session_start",
561
- {},
562
- { sessionManager: host.sessionManager },
563
- );
564
- const result = await run(host.execute, { action: "list" });
565
- expect(text(result)).toContain("○ 1. real");
566
- });
567
-
568
- test("no todo-state entries starts empty", async () => {
569
- const host = makeHost([{ type: "message", data: "hello" }]);
570
- registerTodo(host.pi);
571
- await host.emit(
572
- "session_start",
573
- {},
574
- { sessionManager: host.sessionManager },
575
- );
576
- const result = await run(host.execute, { action: "list" });
577
- expect(text(result)).toBe("(no todos)");
578
- });
579
-
580
- test("empty entries list starts empty", async () => {
581
- const host = makeHost([]);
582
- registerTodo(host.pi);
583
- await host.emit(
584
- "session_start",
585
- {},
586
- { sessionManager: host.sessionManager },
587
- );
588
- const result = await run(host.execute, { action: "list" });
589
- expect(text(result)).toBe("(no todos)");
590
- });
591
-
592
- test("restore with empty todos array works", async () => {
593
- const host = makeHost([
594
- {
595
- type: "custom",
596
- customType: "todo-state",
597
- data: { todos: [], nextTodoId: 1 },
598
- },
599
- ]);
600
- registerTodo(host.pi);
601
- await host.emit(
602
- "session_start",
603
- {},
604
- { sessionManager: host.sessionManager },
605
- );
606
- const result = await run(host.execute, { action: "list" });
607
- expect(text(result)).toBe("(no todos)");
608
- });
609
- });
610
-
611
- describe("renderTodoLines (colored TUI render)", () => {
612
- const items: TodoItem[] = [
613
- { id: 1, text: "alpha", status: "done" },
614
- { id: 2, text: "bravo", status: "in_progress" },
615
- { id: 3, text: "charlie", status: "pending" },
616
- { id: 4, text: "delta", status: "blocked" },
617
- ];
618
-
619
- test("empty list renders muted placeholder", () => {
620
- expect(renderTodoLines([], tagTheme)).toBe("[muted](no todos)[/]");
621
- });
622
-
623
- test("tints each glyph by status", () => {
624
- const out = renderTodoLines(items, tagTheme);
625
- expect(out).toContain("[success]●[/]"); // done
626
- expect(out).toContain("[accent]◐[/]"); // in_progress
627
- expect(out).toContain("[muted]○[/]"); // pending
628
- expect(out).toContain("[error]⊘[/]"); // blocked
629
- });
630
-
631
- test("highlights the in-progress row bold + accent", () => {
632
- const out = renderTodoLines(items, tagTheme);
633
- expect(out).toContain("<b>[accent]2. bravo[/]</b>");
634
- });
635
-
636
- test("dims completed rows and uses text color for active-but-not-running", () => {
637
- const out = renderTodoLines(items, tagTheme);
638
- expect(out).toContain("[muted]1. alpha[/]"); // done body muted
639
- expect(out).toContain("[text]3. charlie[/]"); // pending body text
640
- });
641
-
642
- test("shows the done/total count header", () => {
643
- const out = renderTodoLines(items, tagTheme);
644
- expect(out).toContain("[muted]Todos 1/4 done:[/]");
645
- });
646
- });