pi-anchor 0.3.0 → 0.4.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.
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 840 430" width="840" height="430">
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 840 390" width="840" height="390">
2
2
  <defs>
3
3
  <filter id="shadow" x="-5%" y="-5%" width="110%" height="115%" filterUnits="userSpaceOnUse">
4
4
  <feDropShadow dx="0" dy="12" stdDeviation="16" flood-color="#000000" flood-opacity="0.45" />
@@ -10,72 +10,56 @@
10
10
  </defs>
11
11
 
12
12
  <g filter="url(#shadow)">
13
- <rect x="20" y="15" width="800" height="390" rx="12" fill="#1e2326" stroke="#343f44" stroke-width="1.2" />
13
+ <rect x="20" y="15" width="800" height="350" rx="12" fill="#1e2326" stroke="#343f44" stroke-width="1.2" />
14
14
  <path d="M 20,27 A 12,12 0 0,1 32,15 L 808,15 A 12,12 0 0,1 820,27 L 820,52 L 20,52 Z" fill="url(#headerGrad)" stroke="#343f44" stroke-width="1" />
15
15
  <line x1="20" y1="52" x2="820" y2="52" stroke="#2d353b" stroke-width="1" />
16
16
  <circle cx="42" cy="33.5" r="5.5" fill="#e67e80" />
17
17
  <circle cx="60" cy="33.5" r="5.5" fill="#dbbc7f" />
18
18
  <circle cx="78" cy="33.5" r="5.5" fill="#a7c080" />
19
19
  <text x="420" y="38" text-anchor="middle" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" font-size="12" font-weight="500" fill="#859289">
20
- ⌖ anchor (v0.3.0) · 小巧 · 自主 · 持久
20
+ ⌖ anchor · 贴在终端边框上的电子便签
21
21
  </text>
22
22
  </g>
23
23
 
24
24
  <g font-family="'JetBrains Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace" font-size="13" xml:space="preserve">
25
- <text x="45" y="84">
26
- <tspan fill="#a7c080" font-weight="bold">~/Workspace</tspan>
25
+ <text x="45" y="86">
26
+ <tspan fill="#a7c080" font-weight="bold">~/Workspace/app</tspan>
27
27
  <tspan fill="#7fbbb3"> ❯ </tspan>
28
28
  <tspan fill="#d3c6aa" font-weight="bold">anchor</tspan>
29
29
  </text>
30
30
 
31
- <text x="45" y="116">
32
- <tspan fill="#7fbbb3" font-weight="bold">⌖ Anchors </tspan>
33
- <tspan fill="#859289">(3 active, 1 quiet · 0 仓库污染 · 0 闲置 Token):</tspan>
31
+ <text x="45" y="120">
32
+ <tspan fill="#7fbbb3" font-weight="bold">⌖ Notes (3):</tspan>
34
33
  </text>
35
34
 
36
- <text x="45" y="152">
35
+ <text x="45" y="154">
37
36
  <tspan fill="#859289"> 01 </tspan>
38
- <tspan fill="#e67e80" font-weight="bold">[P0] </tspan>
39
- <tspan fill="#d3c6aa">重构鉴权模块为 HttpOnly Cookie [src/auth.ts]</tspan>
40
- <tspan fill="#7fbbb3"> anchor </tspan>
41
- <tspan fill="#e67e80" font-weight="bold">Today </tspan>
42
- <tspan fill="#7a8478">Today 10:02</tspan>
37
+ <tspan fill="#d3c6aa">把登录 Token 改用 HttpOnly Cookie 存储 </tspan>
38
+ <tspan fill="#7fbbb3">app · auth.ts </tspan>
39
+ <tspan fill="#7a8478">25m ago</tspan>
43
40
  </text>
44
41
 
45
42
  <text x="45" y="182">
46
43
  <tspan fill="#859289"> 02 </tspan>
47
- <tspan fill="#dbbc7f" font-weight="bold">[P1] </tspan>
48
- <tspan fill="#d3c6aa">补充并发文件锁单元测试 </tspan>
49
- <tspan fill="#7fbbb3"> anchor </tspan>
50
- <tspan fill="#dbbc7f">Tomorrow </tspan>
51
- <tspan fill="#7a8478">Yesterday 21:34</tspan>
44
+ <tspan fill="#d3c6aa">检查网关超时后的降级重试边界 </tspan>
45
+ <tspan fill="#7fbbb3">app · gateway.ts </tspan>
46
+ <tspan fill="#7a8478">3h ago</tspan>
52
47
  </text>
53
48
 
54
- <text x="45" y="212">
49
+ <text x="45" y="210">
55
50
  <tspan fill="#859289"> 03 </tspan>
56
- <tspan fill="#dbbc7f" font-weight="bold">[P1] </tspan>
57
- <tspan fill="#d3c6aa">打磨 CLI 交互式极简工程列表 </tspan>
58
- <tspan fill="#7fbbb3"> Desktop </tspan>
59
- <tspan fill="#a7c080">In 2d </tspan>
60
- <tspan fill="#7a8478">Today 10:02</tspan>
51
+ <tspan fill="#859289">College 重构前置查阅外部架构参考库 </tspan>
52
+ <tspan fill="#7a8478">College/优化 · notes.md </tspan>
53
+ <tspan fill="#56635f">8d ago · quiet</tspan>
61
54
  </text>
62
55
 
63
- <text x="45" y="242">
64
- <tspan fill="#859289"> 04 </tspan>
65
- <tspan fill="#7a8478" font-weight="bold">[Quiet] </tspan>
66
- <tspan fill="#859289">College 重构前置查阅外部架构参考库 </tspan>
67
- <tspan fill="#7a8478"> College/优化 </tspan>
68
- <tspan fill="#7a8478">Quiet (0 tok) </tspan>
69
- <tspan fill="#56635f">09-28 14:20</tspan>
70
- </text>
71
-
72
- <rect x="45" y="274" width="750" height="58" rx="6" fill="#232a2e" stroke="#3a464c" stroke-width="1" />
73
- <text x="62" y="298" fill="#a7c080" font-weight="bold">⚡ 会话内实时自主核销(执行 git commit 或测试通过当场自动结案):</text>
74
- <text x="62" y="319" fill="#d3c6aa"> git commit -m "feat(auth): 重构鉴权模块为 HttpOnly Cookie" ➔ ⌖ Auto-settled #anc-1</text>
56
+ <rect x="45" y="242" width="750" height="58" rx="6" fill="#232a2e" stroke="#3a464c" stroke-width="1" />
57
+ <text x="62" y="266" fill="#dbbc7f">1. 随手贴一张: /pin 把 src/auth.ts 的登录改用 HttpOnly Cookie</text>
58
+ <text x="62" y="287" fill="#a7c080">2. 提交自动撕: git commit -m "feat: 登录改用 HttpOnly Cookie" ➔ ⌖ Torn off #1</text>
75
59
 
76
- <text x="45" y="366" fill="#7a8478">
60
+ <text x="45" y="334" fill="#7a8478">
77
61
  <tspan> 常用命令: </tspan>
78
- <tspan fill="#7fbbb3" font-weight="bold">`/pin &lt;任务&gt;`</tspan>
62
+ <tspan fill="#7fbbb3" font-weight="bold">`/pin &lt;便签&gt;`</tspan>
79
63
  <tspan> · </tspan>
80
64
  <tspan fill="#7fbbb3" font-weight="bold">`/anchor`</tspan>
81
65
  <tspan> · </tspan>
package/bin/anchor.js CHANGED
@@ -1,15 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Anchor CLI - Minimalist task protocol for AI coding workflows
5
- * Production-ready executable with dual-timeline & 4-quadrant cognitive grouping
4
+ * Anchor CLI — Minimalist terminal sticky notes for AI coding
6
5
  */
7
6
 
8
7
  import {
9
8
  AnchorStore,
10
9
  formatOrigin,
11
- formatTargetDate,
12
- formatCreationTime,
10
+ formatRelativeAge,
13
11
  sortAnchorsForDisplay,
14
12
  padToWidth
15
13
  } from '../dist/index.js';
@@ -17,55 +15,43 @@ import {
17
15
  const store = new AnchorStore();
18
16
  const args = process.argv.slice(2);
19
17
 
20
- // 1. View pending tasks: `anchor` or `anchor list`
18
+ // 1. View sticky notes: `anchor` or `anchor list`
21
19
  if (args.length === 0 || args[0] === 'list' || args[0] === 'ls') {
22
- const allUnfinished = store
20
+ const allNotes = store
23
21
  .list({ cwd: process.cwd(), all: true })
24
22
  .filter(a => a.status === 'active' || a.status === 'sleeping');
25
23
 
26
- if (allUnfinished.length === 0) {
27
- console.log('\n⌖ No active anchors. Use `anchor <task>` to record.\n');
24
+ if (allNotes.length === 0) {
25
+ console.log('\n⌖ No sticky notes. Run `anchor "<note>"` to stick one.\n');
28
26
  process.exit(0);
29
27
  }
30
28
 
31
- const sorted = sortAnchorsForDisplay(allUnfinished, process.cwd());
32
- const activeCount = sorted.filter(a => a.status === 'active').length;
33
- const quietCount = sorted.length - activeCount;
34
- const headerSuffix = quietCount > 0 ? `${activeCount} active, ${quietCount} quiet` : `${activeCount} active`;
35
-
36
- console.log(`\n⌖ Anchors (${headerSuffix}):\n`);
29
+ const sorted = sortAnchorsForDisplay(allNotes, process.cwd());
30
+ console.log(`\n⌖ Notes (${sorted.length}):\n`);
37
31
 
38
32
  sorted.forEach((a, idx) => {
39
33
  const num = String(idx + 1).padStart(2, '0');
40
- const badge = a.status === 'sleeping'
41
- ? padToWidth('[Quiet]', 10)
42
- : padToWidth(`[${a.priority.toUpperCase()}]`, 10);
43
34
  const origin = formatOrigin(a);
44
- const target = a.status === 'sleeping' ? 'Quiet (0 tok)' : formatTargetDate(a);
45
- const created = formatCreationTime(a.createdAt);
46
-
47
- const titleWithFiles = (a.files && a.files.length > 0)
48
- ? `${a.title} [${a.files.slice(0, 1).join(', ')}]`
49
- : a.title;
35
+ const age = a.status === 'sleeping'
36
+ ? `${formatRelativeAge(a.createdAt)} · quiet`
37
+ : formatRelativeAge(a.createdAt);
50
38
 
51
39
  const colNum = ` ${num} `;
52
- const colTitle = padToWidth(titleWithFiles, 36);
53
- const colOrigin = padToWidth(origin, 14);
54
- const colTarget = padToWidth(target, 14);
55
- const colCreated = created;
40
+ const colTitle = padToWidth(a.title, 42);
41
+ const colOrigin = padToWidth(origin, 22);
56
42
 
57
- console.log(`${colNum}${badge}${colTitle} ${colOrigin} ${colTarget} ${colCreated}`);
43
+ console.log(`${colNum}${colTitle} ${colOrigin} ${age}`);
58
44
  });
59
45
 
60
- console.log('\n Use `anchor done <id>` to complete, or `anchor undo` to restore.\n');
46
+ console.log('\n Run `anchor done <num>` to tear off, or `anchor undo` to put back.\n');
61
47
  process.exit(0);
62
48
  }
63
49
 
64
- // 2. Undo settlement: `anchor undo`
50
+ // 2. Put back last torn-off note: `anchor undo`
65
51
  if (args[0] === 'undo') {
66
52
  try {
67
53
  const restored = store.undoSettle();
68
- console.log(`⌖ Restored #${restored.id}: "${restored.title}"`);
54
+ console.log(`⌖ Put back #${restored.id.replace(/^anc-/, '')}: "${restored.title}"`);
69
55
  } catch (err) {
70
56
  console.error(`Error: ${err.message}`);
71
57
  process.exit(1);
@@ -73,53 +59,43 @@ if (args[0] === 'undo') {
73
59
  process.exit(0);
74
60
  }
75
61
 
76
- // 3. Help: `anchor --help` or `anchor -h`
62
+ // 3. Help: `anchor --help`
77
63
  if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
78
64
  console.log(`
79
- ⌖ Anchor CLI - Cross-session task protocol for AI coding agents
65
+ ⌖ Anchor — Digital sticky notes for AI coding sessions
80
66
 
81
67
  Usage:
82
- anchor List active anchors
83
- anchor <task> Pin a new task across sessions
84
- anchor done <id> Settle and evict a task (e.g. anchor done 1)
85
- anchor undo Restore the last settled task
86
-
87
- Options:
88
- -h, --help Show this help message
89
- -v, --version Show version
90
- -d, --due <date> Specify expected completion date (e.g. tomorrow, friday, 2026-09-28)
68
+ anchor View all sticky notes
69
+ anchor "<note>" Stick a new note
70
+ anchor done <num> Tear off a completed note (e.g. anchor done 1)
71
+ anchor undo Put back the last torn-off note
91
72
  `);
92
73
  process.exit(0);
93
74
  }
94
75
 
95
- // 4. Close task: `anchor done <id>` or `anchor rm <id>`
76
+ // 4. Tear off note: `anchor done <num>`
96
77
  if (args[0] === 'done' || args[0] === 'rm' || args[0] === 'close') {
97
78
  const idInput = args[1];
98
79
  if (!idInput) {
99
- console.error('Usage: anchor done <id>');
80
+ console.error('Usage: anchor done <num>');
100
81
  process.exit(1);
101
82
  }
102
83
  try {
103
- // 1. Check if idInput is a 1-based sequential display number (e.g. '1', '01')
104
- const allUnfinished = store
84
+ const allNotes = store
105
85
  .list({ cwd: process.cwd(), all: true })
106
86
  .filter(a => a.status === 'active' || a.status === 'sleeping');
107
- const sortedActive = sortAnchorsForDisplay(allUnfinished, process.cwd());
87
+ const sorted = sortAnchorsForDisplay(allNotes, process.cwd());
108
88
 
109
89
  let targetId = idInput;
110
- const numMatch = idInput.match(/^\d+$/);
111
- if (numMatch) {
90
+ if (/^\d+$/.test(idInput)) {
112
91
  const seqIndex = parseInt(idInput, 10);
113
- if (seqIndex >= 1 && seqIndex <= sortedActive.length) {
114
- targetId = sortedActive[seqIndex - 1].id;
92
+ if (seqIndex >= 1 && seqIndex <= sorted.length) {
93
+ targetId = sorted[seqIndex - 1].id;
115
94
  }
116
95
  }
117
96
 
118
- const settled = store.settle(targetId, { settledBy: 'manual-command' });
119
- const successMsg = settled.recurrence === 'daily'
120
- ? `⌖ Completed for today: "${settled.title}" (resets tomorrow)`
121
- : `⌖ Settled: "${settled.title}"`;
122
- console.log(successMsg);
97
+ const settled = store.settle(targetId, { settledBy: 'manual' });
98
+ console.log(`⌖ Torn off: "${settled.title}"`);
123
99
  } catch (err) {
124
100
  console.error(`Error: ${err.message}`);
125
101
  process.exit(1);
@@ -127,24 +103,15 @@ if (args[0] === 'done' || args[0] === 'rm' || args[0] === 'close') {
127
103
  process.exit(0);
128
104
  }
129
105
 
130
- // 5. Pin new task: `anchor <task description> [--due <date>]`
131
- let targetDateFlag;
132
- const dueIdx = args.findIndex(a => a === '--due' || a === '-d');
133
- if (dueIdx !== -1 && args[dueIdx + 1]) {
134
- targetDateFlag = args[dueIdx + 1];
135
- args.splice(dueIdx, 2);
136
- }
137
-
106
+ // 5. Stick a new note: `anchor <note text>`
138
107
  const title = args.join(' ').trim();
139
108
  if (!title) {
140
- console.error('Error: task description cannot be empty.');
109
+ console.error('Error: Note text cannot be empty');
141
110
  process.exit(1);
142
111
  }
143
112
 
144
- const anc = store.create({
113
+ const created = store.create({
145
114
  title,
146
- targetDate: targetDateFlag,
147
115
  cwd: process.cwd()
148
116
  });
149
- const targetBadge = anc.targetDate ? ` · ${formatTargetDate(anc)}` : '';
150
- console.log(`⌖ Pinned #${anc.id}: "${anc.title}" [${anc.project}]${targetBadge}`);
117
+ console.log(`⌖ Pinned #${created.id.replace(/^anc-/, '')}: "${created.title}"`);