pi-anchor 0.1.0 → 0.1.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.
@@ -1191,22 +1191,32 @@ function updateStartupBanner(ctx, store) {
1191
1191
  }
1192
1192
  const groups = groupAnchorsByQuadrant(active);
1193
1193
  const sorted = [...groups.today, ...groups.upcoming, ...groups.habits, ...groups.backlog];
1194
- const lines = [
1195
- `\u2316 Anchor \xB7 ${active.length} active commitments:`
1196
- ];
1197
1194
  const topItems = sorted.slice(0, 3);
1198
- for (let i = 0; i < topItems.length; i++) {
1199
- const a = topItems[i];
1200
- const num = (i + 1).toString().padStart(2, "0");
1201
- const q = classifyAnchor(a);
1202
- const tgt = formatTargetDate(a);
1203
- const titlePadded = a.title.length > 34 ? a.title.slice(0, 32) + ".." : a.title;
1204
- lines.push(` \u2022 [${q}] ${num} ${titlePadded} (${tgt})`);
1205
- }
1206
- if (sorted.length > 3) {
1207
- lines.push(` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`);
1208
- }
1209
- ctx.ui.setWidget("anchor-startup", lines, { placement: "aboveEditor" });
1195
+ const factory = (_tui, theme) => ({
1196
+ render: (width) => {
1197
+ const maxTitleWidth = Math.max(16, width - 36);
1198
+ const fg = theme?.fg ? theme.fg.bind(theme) : (_c, text) => text;
1199
+ const lines = [
1200
+ fg("accent", `\u2316 Anchor \xB7 ${active.length} active commitments:`)
1201
+ ];
1202
+ for (let i = 0; i < topItems.length; i++) {
1203
+ const a = topItems[i];
1204
+ const num = (i + 1).toString().padStart(2, "0");
1205
+ const q = classifyAnchor(a);
1206
+ const tgt = formatTargetDate(a);
1207
+ const titlePadded = truncateToWidth(a.title, maxTitleWidth);
1208
+ lines.push(` \u2022 ${fg("muted", `[${q}]`)} ${fg("dim", num)} ${titlePadded} ${fg("dim", `(${tgt})`)}`);
1209
+ }
1210
+ if (sorted.length > 3) {
1211
+ lines.push(fg("dim", ` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`));
1212
+ }
1213
+ return lines;
1214
+ },
1215
+ invalidate: () => {
1216
+ }
1217
+ });
1218
+ const content = ctx.ui?.theme ? factory : factory().render(80);
1219
+ ctx.ui.setWidget("anchor-startup", content, { placement: "aboveEditor" });
1210
1220
  }
1211
1221
  async function openAnchorDashboard(ctx, store) {
1212
1222
  if (!ctx.hasUI || !ctx.ui) return;
@@ -1205,22 +1205,32 @@ function updateStartupBanner(ctx, store) {
1205
1205
  }
1206
1206
  const groups = groupAnchorsByQuadrant(active);
1207
1207
  const sorted = [...groups.today, ...groups.upcoming, ...groups.habits, ...groups.backlog];
1208
- const lines = [
1209
- `\u2316 Anchor \xB7 ${active.length} active commitments:`
1210
- ];
1211
1208
  const topItems = sorted.slice(0, 3);
1212
- for (let i = 0; i < topItems.length; i++) {
1213
- const a = topItems[i];
1214
- const num = (i + 1).toString().padStart(2, "0");
1215
- const q = classifyAnchor(a);
1216
- const tgt = formatTargetDate(a);
1217
- const titlePadded = a.title.length > 34 ? a.title.slice(0, 32) + ".." : a.title;
1218
- lines.push(` \u2022 [${q}] ${num} ${titlePadded} (${tgt})`);
1219
- }
1220
- if (sorted.length > 3) {
1221
- lines.push(` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`);
1222
- }
1223
- ctx.ui.setWidget("anchor-startup", lines, { placement: "aboveEditor" });
1209
+ const factory = (_tui, theme) => ({
1210
+ render: (width) => {
1211
+ const maxTitleWidth = Math.max(16, width - 36);
1212
+ const fg = theme?.fg ? theme.fg.bind(theme) : (_c, text) => text;
1213
+ const lines = [
1214
+ fg("accent", `\u2316 Anchor \xB7 ${active.length} active commitments:`)
1215
+ ];
1216
+ for (let i = 0; i < topItems.length; i++) {
1217
+ const a = topItems[i];
1218
+ const num = (i + 1).toString().padStart(2, "0");
1219
+ const q = classifyAnchor(a);
1220
+ const tgt = formatTargetDate(a);
1221
+ const titlePadded = truncateToWidth(a.title, maxTitleWidth);
1222
+ lines.push(` \u2022 ${fg("muted", `[${q}]`)} ${fg("dim", num)} ${titlePadded} ${fg("dim", `(${tgt})`)}`);
1223
+ }
1224
+ if (sorted.length > 3) {
1225
+ lines.push(fg("dim", ` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`));
1226
+ }
1227
+ return lines;
1228
+ },
1229
+ invalidate: () => {
1230
+ }
1231
+ });
1232
+ const content = ctx.ui?.theme ? factory : factory().render(80);
1233
+ ctx.ui.setWidget("anchor-startup", content, { placement: "aboveEditor" });
1224
1234
  }
1225
1235
  async function openAnchorDashboard(ctx, store) {
1226
1236
  if (!ctx.hasUI || !ctx.ui) return;
@@ -1426,6 +1436,42 @@ Mark as completed and archive?`
1426
1436
  verifyCommand: import_typebox.Type.Optional(import_typebox.Type.String({ description: "Optional shell command for automated physical verification" })),
1427
1437
  id: import_typebox.Type.Optional(import_typebox.Type.String({ description: "Anchor ID, e.g. anc-1 (for settle or touch)" }))
1428
1438
  }),
1439
+ renderCall(args, theme) {
1440
+ const action = args.action || "list";
1441
+ let title = theme.fg("toolTitle", theme.bold("\u2316 anchor ")) + theme.fg("accent", action);
1442
+ if (action === "pin" && args.title) {
1443
+ const due = args.targetDate ? ` ${theme.fg("dim", `[${args.targetDate}]`)}` : "";
1444
+ const files = args.files && args.files.length > 0 ? ` ${theme.fg("muted", `(${args.files.slice(0, 2).join(", ")})`)}` : "";
1445
+ title += ` "${args.title}"${due}${files}`;
1446
+ } else if (action === "settle" && args.id) {
1447
+ title += ` ${theme.fg("dim", `#${args.id}`)}`;
1448
+ } else if (action === "touch" && args.id) {
1449
+ title += ` ${theme.fg("dim", `#${args.id}`)}`;
1450
+ } else if (action === "list") {
1451
+ if (args.priority) title += ` ${theme.fg("muted", `[${args.priority}]`)}`;
1452
+ }
1453
+ return {
1454
+ render: () => [title],
1455
+ invalidate: () => {
1456
+ }
1457
+ };
1458
+ },
1459
+ renderResult(result, _options, theme) {
1460
+ if (result.isError) {
1461
+ const errText = result.content?.[0]?.text || "Execution failed";
1462
+ return {
1463
+ render: () => [theme.fg("error", `\u2717 ${errText}`)],
1464
+ invalidate: () => {
1465
+ }
1466
+ };
1467
+ }
1468
+ const text = result.content?.[0]?.text || "OK";
1469
+ return {
1470
+ render: () => [theme.fg("success", `\u2714 ${text}`)],
1471
+ invalidate: () => {
1472
+ }
1473
+ };
1474
+ },
1429
1475
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1430
1476
  if (!ctx) return { content: [{ type: "text", text: "Error: context required" }], isError: true };
1431
1477
  if (params.action === "pin") {
package/dist/extension.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  sweepStore,
12
12
  updateAnchorStatusBar,
13
13
  updateStartupBanner
14
- } from "./chunk-NNYUD5O7.js";
14
+ } from "./chunk-QKBUATPO.js";
15
15
 
16
16
  // src/extension.ts
17
17
  import { Type } from "@sinclair/typebox";
@@ -175,6 +175,42 @@ Mark as completed and archive?`
175
175
  verifyCommand: Type.Optional(Type.String({ description: "Optional shell command for automated physical verification" })),
176
176
  id: Type.Optional(Type.String({ description: "Anchor ID, e.g. anc-1 (for settle or touch)" }))
177
177
  }),
178
+ renderCall(args, theme) {
179
+ const action = args.action || "list";
180
+ let title = theme.fg("toolTitle", theme.bold("\u2316 anchor ")) + theme.fg("accent", action);
181
+ if (action === "pin" && args.title) {
182
+ const due = args.targetDate ? ` ${theme.fg("dim", `[${args.targetDate}]`)}` : "";
183
+ const files = args.files && args.files.length > 0 ? ` ${theme.fg("muted", `(${args.files.slice(0, 2).join(", ")})`)}` : "";
184
+ title += ` "${args.title}"${due}${files}`;
185
+ } else if (action === "settle" && args.id) {
186
+ title += ` ${theme.fg("dim", `#${args.id}`)}`;
187
+ } else if (action === "touch" && args.id) {
188
+ title += ` ${theme.fg("dim", `#${args.id}`)}`;
189
+ } else if (action === "list") {
190
+ if (args.priority) title += ` ${theme.fg("muted", `[${args.priority}]`)}`;
191
+ }
192
+ return {
193
+ render: () => [title],
194
+ invalidate: () => {
195
+ }
196
+ };
197
+ },
198
+ renderResult(result, _options, theme) {
199
+ if (result.isError) {
200
+ const errText = result.content?.[0]?.text || "Execution failed";
201
+ return {
202
+ render: () => [theme.fg("error", `\u2717 ${errText}`)],
203
+ invalidate: () => {
204
+ }
205
+ };
206
+ }
207
+ const text = result.content?.[0]?.text || "OK";
208
+ return {
209
+ render: () => [theme.fg("success", `\u2714 ${text}`)],
210
+ invalidate: () => {
211
+ }
212
+ };
213
+ },
178
214
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
179
215
  if (!ctx) return { content: [{ type: "text", text: "Error: context required" }], isError: true };
180
216
  if (params.action === "pin") {
package/dist/index.cjs CHANGED
@@ -1038,22 +1038,32 @@ function updateStartupBanner(ctx, store) {
1038
1038
  }
1039
1039
  const groups = groupAnchorsByQuadrant(active);
1040
1040
  const sorted = [...groups.today, ...groups.upcoming, ...groups.habits, ...groups.backlog];
1041
- const lines = [
1042
- `\u2316 Anchor \xB7 ${active.length} active commitments:`
1043
- ];
1044
1041
  const topItems = sorted.slice(0, 3);
1045
- for (let i = 0; i < topItems.length; i++) {
1046
- const a = topItems[i];
1047
- const num = (i + 1).toString().padStart(2, "0");
1048
- const q = classifyAnchor(a);
1049
- const tgt = formatTargetDate(a);
1050
- const titlePadded = a.title.length > 34 ? a.title.slice(0, 32) + ".." : a.title;
1051
- lines.push(` \u2022 [${q}] ${num} ${titlePadded} (${tgt})`);
1052
- }
1053
- if (sorted.length > 3) {
1054
- lines.push(` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`);
1055
- }
1056
- ctx.ui.setWidget("anchor-startup", lines, { placement: "aboveEditor" });
1042
+ const factory = (_tui, theme) => ({
1043
+ render: (width) => {
1044
+ const maxTitleWidth = Math.max(16, width - 36);
1045
+ const fg = theme?.fg ? theme.fg.bind(theme) : (_c, text) => text;
1046
+ const lines = [
1047
+ fg("accent", `\u2316 Anchor \xB7 ${active.length} active commitments:`)
1048
+ ];
1049
+ for (let i = 0; i < topItems.length; i++) {
1050
+ const a = topItems[i];
1051
+ const num = (i + 1).toString().padStart(2, "0");
1052
+ const q = classifyAnchor(a);
1053
+ const tgt = formatTargetDate(a);
1054
+ const titlePadded = truncateToWidth(a.title, maxTitleWidth);
1055
+ lines.push(` \u2022 ${fg("muted", `[${q}]`)} ${fg("dim", num)} ${titlePadded} ${fg("dim", `(${tgt})`)}`);
1056
+ }
1057
+ if (sorted.length > 3) {
1058
+ lines.push(fg("dim", ` (+${sorted.length - 3} more \xB7 run /anchor to inspect)`));
1059
+ }
1060
+ return lines;
1061
+ },
1062
+ invalidate: () => {
1063
+ }
1064
+ });
1065
+ const content = ctx.ui?.theme ? factory : factory().render(80);
1066
+ ctx.ui.setWidget("anchor-startup", content, { placement: "aboveEditor" });
1057
1067
  }
1058
1068
  async function openAnchorDashboard(ctx, store) {
1059
1069
  if (!ctx.hasUI || !ctx.ui) return;
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  truncateToWidth,
40
40
  updateAnchorStatusBar,
41
41
  updateStartupBanner
42
- } from "./chunk-NNYUD5O7.js";
42
+ } from "./chunk-QKBUATPO.js";
43
43
 
44
44
  // src/protocol.ts
45
45
  var AnchorProtocol = class {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-anchor",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Zero-pollution, 0-idle-token task memory for AI coding agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/extension.ts CHANGED
@@ -212,6 +212,44 @@ export default function (pi: ExtensionAPI) {
212
212
  verifyCommand: Type.Optional(Type.String({ description: 'Optional shell command for automated physical verification' })),
213
213
  id: Type.Optional(Type.String({ description: 'Anchor ID, e.g. anc-1 (for settle or touch)' }))
214
214
  }),
215
+ renderCall(args: any, theme: any) {
216
+ const action = args.action || 'list';
217
+ let title = theme.fg('toolTitle', theme.bold('⌖ anchor ')) + theme.fg('accent', action);
218
+
219
+ if (action === 'pin' && args.title) {
220
+ const due = args.targetDate ? ` ${theme.fg('dim', `[${args.targetDate}]`)}` : '';
221
+ const files = (args.files && args.files.length > 0)
222
+ ? ` ${theme.fg('muted', `(${args.files.slice(0, 2).join(', ')})`)}`
223
+ : '';
224
+ title += ` "${args.title}"${due}${files}`;
225
+ } else if (action === 'settle' && args.id) {
226
+ title += ` ${theme.fg('dim', `#${args.id}`)}`;
227
+ } else if (action === 'touch' && args.id) {
228
+ title += ` ${theme.fg('dim', `#${args.id}`)}`;
229
+ } else if (action === 'list') {
230
+ if (args.priority) title += ` ${theme.fg('muted', `[${args.priority}]`)}`;
231
+ }
232
+
233
+ return {
234
+ render: () => [title],
235
+ invalidate: () => {}
236
+ };
237
+ },
238
+ renderResult(result: any, _options: any, theme: any) {
239
+ if (result.isError) {
240
+ const errText = result.content?.[0]?.text || 'Execution failed';
241
+ return {
242
+ render: () => [theme.fg('error', `✗ ${errText}`)],
243
+ invalidate: () => {}
244
+ };
245
+ }
246
+
247
+ const text = result.content?.[0]?.text || 'OK';
248
+ return {
249
+ render: () => [theme.fg('success', `✔ ${text}`)],
250
+ invalidate: () => {}
251
+ };
252
+ },
215
253
  async execute(_toolCallId: string, params: any, _signal?: AbortSignal, _onUpdate?: (partial: any) => void, ctx?: ExtensionContext) {
216
254
  if (!ctx) return { content: [{ type: 'text', text: 'Error: context required' }], isError: true };
217
255
  if (params.action === 'pin') {
package/src/pi-types.d.ts CHANGED
@@ -18,6 +18,8 @@ declare module '@earendil-works/pi-coding-agent' {
18
18
  promptSnippet?: string;
19
19
  promptGuidelines?: string[];
20
20
  parameters: unknown;
21
+ renderCall?: (args: any, theme: any, context?: any) => any;
22
+ renderResult?: (result: any, options: any, theme: any, context?: any) => any;
21
23
  execute: (
22
24
  toolCallId: string,
23
25
  params: any,
package/src/tui.ts CHANGED
@@ -298,26 +298,39 @@ export function updateStartupBanner(ctx: ExtensionContext, store: AnchorStore):
298
298
 
299
299
  const groups = groupAnchorsByQuadrant(active);
300
300
  const sorted = [...groups.today, ...groups.upcoming, ...groups.habits, ...groups.backlog];
301
-
302
- const lines: string[] = [
303
- `⌖ Anchor · ${active.length} active commitments:`
304
- ];
305
-
306
301
  const topItems = sorted.slice(0, 3);
307
- for (let i = 0; i < topItems.length; i++) {
308
- const a = topItems[i];
309
- const num = (i + 1).toString().padStart(2, '0');
310
- const q = classifyAnchor(a);
311
- const tgt = formatTargetDate(a);
312
- const titlePadded = a.title.length > 34 ? a.title.slice(0, 32) + '..' : a.title;
313
- lines.push(` • [${q}] ${num} ${titlePadded} (${tgt})`);
314
- }
315
-
316
- if (sorted.length > 3) {
317
- lines.push(` (+${sorted.length - 3} more · run /anchor to inspect)`);
318
- }
319
302
 
320
- ctx.ui.setWidget('anchor-startup', lines, { placement: 'aboveEditor' });
303
+ const factory = (_tui?: any, theme?: any) => ({
304
+ render: (width: number) => {
305
+ const maxTitleWidth = Math.max(16, width - 36);
306
+ const fg = theme?.fg ? theme.fg.bind(theme) : (_c: string, text: string) => text;
307
+
308
+ const lines: string[] = [
309
+ fg('accent', `⌖ Anchor · ${active.length} active commitments:`)
310
+ ];
311
+
312
+ for (let i = 0; i < topItems.length; i++) {
313
+ const a = topItems[i];
314
+ const num = (i + 1).toString().padStart(2, '0');
315
+ const q = classifyAnchor(a);
316
+ const tgt = formatTargetDate(a);
317
+ const titlePadded = truncateToWidth(a.title, maxTitleWidth);
318
+ lines.push(` • ${fg('muted', `[${q}]`)} ${fg('dim', num)} ${titlePadded} ${fg('dim', `(${tgt})`)}`);
319
+ }
320
+
321
+ if (sorted.length > 3) {
322
+ lines.push(fg('dim', ` (+${sorted.length - 3} more · run /anchor to inspect)`));
323
+ }
324
+
325
+ return lines;
326
+ },
327
+ invalidate: () => {}
328
+ });
329
+
330
+ // Reactive component factory in real TUI mode, static string[] in mock tests
331
+ const content = (ctx as any).ui?.theme ? factory : factory().render(80);
332
+
333
+ ctx.ui.setWidget('anchor-startup', content as any, { placement: 'aboveEditor' });
321
334
  }
322
335
 
323
336
  /**