pi-anchor 0.4.0 → 0.5.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/README.md +35 -34
- package/README_zh.md +33 -37
- package/assets/hero-en.svg +47 -30
- package/assets/hero-zh.svg +45 -28
- package/bin/anchor.js +39 -8
- package/dist/{chunk-LKY6WIKH.js → chunk-VXEQDTK4.js} +270 -60
- package/dist/{extension-B1-fzCox.d.cts → extension-Cq1NH1jT.d.cts} +42 -4
- package/dist/{extension-B1-fzCox.d.ts → extension-Cq1NH1jT.d.ts} +42 -4
- package/dist/extension.cjs +269 -60
- package/dist/extension.d.cts +1 -1
- package/dist/extension.d.ts +1 -1
- package/dist/extension.js +3 -1
- package/dist/index.cjs +156 -30
- package/dist/index.d.cts +15 -10
- package/dist/index.d.ts +15 -10
- package/dist/index.js +7 -1
- package/package.json +2 -2
- package/src/extension.ts +169 -42
- package/src/pi-types.d.ts +5 -1
- package/src/store.ts +93 -5
- package/src/tui.ts +89 -35
- package/src/types.ts +14 -1
package/dist/index.cjs
CHANGED
|
@@ -37,8 +37,10 @@ __export(index_exports, {
|
|
|
37
37
|
acquireSyncLock: () => acquireSyncLock,
|
|
38
38
|
atomicRenameWithRetry: () => atomicRenameWithRetry,
|
|
39
39
|
extractPathsFromText: () => extractPathsFromText,
|
|
40
|
+
extractProgressFromText: () => extractProgressFromText,
|
|
40
41
|
findMatchedAnchors: () => findMatchedAnchors,
|
|
41
42
|
formatOrigin: () => formatOrigin,
|
|
43
|
+
formatProgressBar: () => formatProgressBar,
|
|
42
44
|
formatRelativeAge: () => formatRelativeAge,
|
|
43
45
|
getDefaultStorageDir: () => getDefaultStorageDir,
|
|
44
46
|
getDisplayWidth: () => getDisplayWidth,
|
|
@@ -53,6 +55,7 @@ __export(index_exports, {
|
|
|
53
55
|
openAnchorDashboard: () => openAnchorDashboard,
|
|
54
56
|
padToWidth: () => padToWidth,
|
|
55
57
|
parseAnchorCommandInput: () => parseAnchorCommandInput,
|
|
58
|
+
parseQuickNoteInput: () => parseQuickNoteInput,
|
|
56
59
|
renderActiveAnchorsContext: () => renderActiveAnchorsContext,
|
|
57
60
|
renderColdStartAnchorsContext: () => renderColdStartAnchorsContext,
|
|
58
61
|
resolveAnchorTarget: () => resolveAnchorTarget,
|
|
@@ -115,6 +118,32 @@ function extractPathsFromText(text) {
|
|
|
115
118
|
}
|
|
116
119
|
return Array.from(results);
|
|
117
120
|
}
|
|
121
|
+
function extractProgressFromText(rawTitle) {
|
|
122
|
+
if (!rawTitle) return { cleanTitle: "" };
|
|
123
|
+
const stepParts = Array.from(rawTitle.matchAll(/(?:^|\s)(\d+)\.\s*([^\d]+?)(?=\s+\d+\.\s|$)/g));
|
|
124
|
+
if (stepParts.length >= 2) {
|
|
125
|
+
const steps = stepParts.map((m) => m[2].trim().replace(/[;;,,]+$/, "")).filter(Boolean);
|
|
126
|
+
const firstMatchIdx = rawTitle.indexOf(stepParts[0][0]);
|
|
127
|
+
const prefix = rawTitle.slice(0, firstMatchIdx).replace(/[::\-—\s]+$/, "").trim();
|
|
128
|
+
return {
|
|
129
|
+
cleanTitle: prefix || steps.join(" \u2192 "),
|
|
130
|
+
progress: { done: 0, total: steps.length, steps }
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const ratioMatch = rawTitle.match(/(?:\[(\d{1,3})\/(\d{1,3})\]|\((\d{1,3})\/(\d{1,3})\)|(?:^|\s)(\d{1,3})\/(\d{1,3})(?=\s|$))/);
|
|
134
|
+
if (ratioMatch) {
|
|
135
|
+
const done = parseInt(ratioMatch[1] ?? ratioMatch[3] ?? ratioMatch[5], 10);
|
|
136
|
+
const total = parseInt(ratioMatch[2] ?? ratioMatch[4] ?? ratioMatch[6], 10);
|
|
137
|
+
if (!isNaN(done) && !isNaN(total) && total >= 2 && total <= 500 && done >= 0 && done <= total) {
|
|
138
|
+
const stripped = rawTitle.replace(ratioMatch[0], " ").replace(/\s+/g, " ").trim();
|
|
139
|
+
return {
|
|
140
|
+
cleanTitle: stripped || rawTitle.trim(),
|
|
141
|
+
progress: { done, total }
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { cleanTitle: rawTitle.trim() };
|
|
146
|
+
}
|
|
118
147
|
function isAnchorTargetingWorkspace(anchor, targetCwd) {
|
|
119
148
|
if (!targetCwd) return true;
|
|
120
149
|
const normTarget = targetCwd.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -257,8 +286,9 @@ function renderColdStartAnchorsContext(store, cwdOrNow, nowArg) {
|
|
|
257
286
|
const lines = activeNotes.map((a) => {
|
|
258
287
|
const num = a.id.replace(/^anc-/, "");
|
|
259
288
|
const files = a.files.length > 0 ? ` (${a.files.slice(0, 2).join(", ")})` : "";
|
|
289
|
+
const prog = a.progress ? ` [${a.progress.done}/${a.progress.total}${a.progress.steps && a.progress.steps[a.progress.done] ? ` \xB7 next: ${a.progress.steps[a.progress.done]}` : ""}]` : "";
|
|
260
290
|
const desc = a.description ? ` \u2014 ${a.description}` : "";
|
|
261
|
-
return `\u2022 #${num} [${a.project}] ${a.title}${files}${desc}`;
|
|
291
|
+
return `\u2022 #${num} [${a.project}] ${a.title}${prog}${files}${desc}`;
|
|
262
292
|
});
|
|
263
293
|
return [
|
|
264
294
|
`<sticky-notes count="${activeNotes.length}">`,
|
|
@@ -433,8 +463,9 @@ var AnchorStore = class {
|
|
|
433
463
|
const state = this.loadState();
|
|
434
464
|
const now = Date.now();
|
|
435
465
|
const id = this.generateId(state);
|
|
466
|
+
const { cleanTitle, progress: parsedProgress } = extractProgressFromText(input.title);
|
|
436
467
|
const explicitFiles = (input.files || []).map((f) => normalizePath(f)).filter(Boolean);
|
|
437
|
-
const inferredFiles = explicitFiles.length > 0 ? explicitFiles : extractPathsFromText(`${
|
|
468
|
+
const inferredFiles = explicitFiles.length > 0 ? explicitFiles : extractPathsFromText(`${cleanTitle} ${input.description || ""}`);
|
|
438
469
|
let cwd = input.cwd ? normalizePath(input.cwd) : "";
|
|
439
470
|
if (input.project && (/^[a-zA-Z]:[\\/]/.test(input.project.trim()) || input.project.trim().startsWith("/"))) {
|
|
440
471
|
cwd = normalizePath(input.project.trim());
|
|
@@ -445,7 +476,7 @@ var AnchorStore = class {
|
|
|
445
476
|
const projectName = input.project ? input.project.trim() : cwd ? import_node_path.default.basename(cwd) : "global";
|
|
446
477
|
const anchor = {
|
|
447
478
|
id,
|
|
448
|
-
title:
|
|
479
|
+
title: cleanTitle,
|
|
449
480
|
description: input.description?.trim(),
|
|
450
481
|
priority: input.priority || "p1",
|
|
451
482
|
status: "active",
|
|
@@ -455,7 +486,9 @@ var AnchorStore = class {
|
|
|
455
486
|
updatedAt: now,
|
|
456
487
|
lastTouchedAt: now,
|
|
457
488
|
files: inferredFiles,
|
|
458
|
-
tags: (input.tags || []).map((t) => t.trim()).filter(Boolean)
|
|
489
|
+
tags: (input.tags || []).map((t) => t.trim()).filter(Boolean),
|
|
490
|
+
...input.sessionFile ? { sessionFile: normalizePath(input.sessionFile) } : {},
|
|
491
|
+
...input.progress || parsedProgress ? { progress: input.progress || parsedProgress } : {}
|
|
459
492
|
};
|
|
460
493
|
state.anchors.push(anchor);
|
|
461
494
|
this.saveState(state);
|
|
@@ -504,7 +537,7 @@ var AnchorStore = class {
|
|
|
504
537
|
return updated;
|
|
505
538
|
});
|
|
506
539
|
}
|
|
507
|
-
touch(id, timestamp = Date.now()) {
|
|
540
|
+
touch(id, timestamp = Date.now(), sessionFile) {
|
|
508
541
|
return this.withLock(() => {
|
|
509
542
|
const state = this.loadState();
|
|
510
543
|
const idx = this.findAnchorIndex(state.anchors, id);
|
|
@@ -515,11 +548,44 @@ var AnchorStore = class {
|
|
|
515
548
|
if (current.status === "sleeping") {
|
|
516
549
|
current.status = "active";
|
|
517
550
|
}
|
|
551
|
+
if (sessionFile) {
|
|
552
|
+
current.sessionFile = normalizePath(sessionFile);
|
|
553
|
+
}
|
|
518
554
|
state.anchors[idx] = current;
|
|
519
555
|
this.saveState(state);
|
|
520
556
|
return current;
|
|
521
557
|
});
|
|
522
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* Advance (or set) progress on a sticky note:
|
|
561
|
+
* - If delta is provided (default +1), increments `done` up to `total`.
|
|
562
|
+
* - If note had no progress yet, initializes a 4-step bar (`1/4`).
|
|
563
|
+
*/
|
|
564
|
+
step(id, delta = 1, explicitTotal) {
|
|
565
|
+
return this.withLock(() => {
|
|
566
|
+
const state = this.loadState();
|
|
567
|
+
const idx = this.findAnchorIndex(state.anchors, id);
|
|
568
|
+
if (idx === -1) throw new Error(`Note not found: ${id}`);
|
|
569
|
+
const current = state.anchors[idx];
|
|
570
|
+
const now = Date.now();
|
|
571
|
+
const total = explicitTotal && explicitTotal >= 2 ? explicitTotal : current.progress?.total || 4;
|
|
572
|
+
const prevDone = current.progress?.done || 0;
|
|
573
|
+
const nextDone = Math.max(0, Math.min(total, prevDone + delta));
|
|
574
|
+
current.progress = {
|
|
575
|
+
...current.progress || {},
|
|
576
|
+
done: nextDone,
|
|
577
|
+
total
|
|
578
|
+
};
|
|
579
|
+
current.lastTouchedAt = now;
|
|
580
|
+
current.updatedAt = now;
|
|
581
|
+
if (current.status === "sleeping") {
|
|
582
|
+
current.status = "active";
|
|
583
|
+
}
|
|
584
|
+
state.anchors[idx] = current;
|
|
585
|
+
this.saveState(state);
|
|
586
|
+
return { anchor: current, completed: nextDone >= total };
|
|
587
|
+
});
|
|
588
|
+
}
|
|
523
589
|
/**
|
|
524
590
|
* Tear off (settle) a completed sticky note and archive it to archive.jsonl
|
|
525
591
|
*/
|
|
@@ -590,6 +656,7 @@ var AnchorStore = class {
|
|
|
590
656
|
};
|
|
591
657
|
|
|
592
658
|
// src/tui.ts
|
|
659
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
593
660
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
594
661
|
var ANSI_REGEX = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
595
662
|
function stripAnsi(str) {
|
|
@@ -656,6 +723,14 @@ function padToWidth(str, targetWidth) {
|
|
|
656
723
|
if (current >= targetWidth) return truncated;
|
|
657
724
|
return truncated + " ".repeat(targetWidth - current);
|
|
658
725
|
}
|
|
726
|
+
function formatProgressBar(progress, cells = 4) {
|
|
727
|
+
if (!progress || !progress.total || progress.total < 1) return "";
|
|
728
|
+
const done = Math.max(0, Math.min(progress.total, progress.done || 0));
|
|
729
|
+
const filled = Math.min(cells, Math.max(done > 0 ? 1 : 0, Math.round(done / progress.total * cells)));
|
|
730
|
+
const finalFilled = done >= progress.total ? cells : done === 0 ? 0 : filled;
|
|
731
|
+
const bar = "\u25A0".repeat(finalFilled) + "\u25A1".repeat(Math.max(0, cells - finalFilled));
|
|
732
|
+
return `[${bar} ${done}/${progress.total}]`;
|
|
733
|
+
}
|
|
659
734
|
function formatRelativeAge(timestamp, now = Date.now()) {
|
|
660
735
|
const diffSec = Math.max(0, Math.floor((now - timestamp) / 1e3));
|
|
661
736
|
if (diffSec < 60) return "just now";
|
|
@@ -709,6 +784,7 @@ function updateAnchorStatusBar(ctx, store) {
|
|
|
709
784
|
try {
|
|
710
785
|
const active = store.list({ status: "active", cwd: ctx.cwd });
|
|
711
786
|
ctx.ui.setStatus("anchor", active.length > 0 ? `\u2316 ${active.length}` : void 0);
|
|
787
|
+
updateStartupBanner(ctx, store);
|
|
712
788
|
} catch {
|
|
713
789
|
}
|
|
714
790
|
}
|
|
@@ -717,41 +793,41 @@ function updateStartupBanner(ctx, store) {
|
|
|
717
793
|
try {
|
|
718
794
|
const active = store.list({ status: "active", cwd: ctx.cwd });
|
|
719
795
|
if (active.length === 0) {
|
|
720
|
-
ctx.ui.setWidget("anchor-startup", void 0);
|
|
796
|
+
ctx.ui.setWidget("anchor-startup", void 0, { placement: "belowEditor" });
|
|
721
797
|
return;
|
|
722
798
|
}
|
|
723
799
|
const sorted = sortAnchorsForDisplay(active, ctx.cwd);
|
|
724
|
-
const topItems = sorted.slice(0, 3);
|
|
725
800
|
const factory = (_tui, theme) => ({
|
|
726
801
|
render: (width) => {
|
|
727
|
-
const maxTitleWidth = Math.max(14, width - 34);
|
|
728
802
|
const fg = theme?.fg ? theme.fg.bind(theme) : (_c, text) => text;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
const num = a.id.replace(/^anc-/, "").padStart(2, "0");
|
|
803
|
+
if (sorted.length === 1) {
|
|
804
|
+
const a = sorted[0];
|
|
805
|
+
const num = a.id.replace(/^anc-/, "");
|
|
806
|
+
const bar = a.progress ? `${fg("accent", formatProgressBar(a.progress))} ` : "";
|
|
807
|
+
const nextStep = a.progress?.steps && a.progress.steps[a.progress.done] ? fg("muted", ` \u25B8 ${a.progress.steps[a.progress.done]}`) : "";
|
|
735
808
|
const origin = formatOrigin(a);
|
|
736
|
-
const
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
)
|
|
743
|
-
);
|
|
809
|
+
const sessionIcon = a.sessionFile ? " \u21AA" : "";
|
|
810
|
+
const prefix = `${fg("accent", "\u2316")} ${fg("dim", `#${num}`)} ${bar}`;
|
|
811
|
+
const suffix = ` ${fg("muted", `(${origin}${sessionIcon})`)}`;
|
|
812
|
+
const availTitle = Math.max(12, width - getDisplayWidth(prefix) - getDisplayWidth(nextStep) - getDisplayWidth(suffix) - 2);
|
|
813
|
+
const titleClipped = truncateToWidth(a.title, availTitle);
|
|
814
|
+
return [truncateToWidth(`${prefix}${titleClipped}${nextStep}${suffix}`, width)];
|
|
744
815
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
816
|
+
const countTag = fg("accent", `\u2316 ${sorted.length} notes: `);
|
|
817
|
+
const segments = sorted.slice(0, 3).map((a) => {
|
|
818
|
+
const num = a.id.replace(/^anc-/, "");
|
|
819
|
+
const bar = a.progress ? `${formatProgressBar(a.progress)} ` : "";
|
|
820
|
+
const shortTitle = truncateToWidth(a.title, Math.max(14, Math.floor((width - 20) / Math.min(3, sorted.length)) - 12));
|
|
821
|
+
return `${fg("dim", `#${num}`)} ${bar}${shortTitle}`;
|
|
822
|
+
});
|
|
823
|
+
const more = sorted.length > 3 ? fg("dim", ` +${sorted.length - 3}`) : "";
|
|
824
|
+
return [truncateToWidth(`${countTag}${segments.join(fg("dim", " \xB7 "))}${more}`, width)];
|
|
749
825
|
},
|
|
750
826
|
invalidate: () => {
|
|
751
827
|
}
|
|
752
828
|
});
|
|
753
829
|
const content = ctx.ui?.theme ? factory : factory().render(80);
|
|
754
|
-
ctx.ui.setWidget("anchor-startup", content, { placement: "
|
|
830
|
+
ctx.ui.setWidget("anchor-startup", content, { placement: "belowEditor" });
|
|
755
831
|
} catch {
|
|
756
832
|
}
|
|
757
833
|
}
|
|
@@ -759,7 +835,7 @@ async function openAnchorDashboard(ctx, store) {
|
|
|
759
835
|
if (!hasActiveUI(ctx)) return;
|
|
760
836
|
const allNotes = store.list({ cwd: ctx.cwd, all: true }).filter((a) => a.status === "active" || a.status === "sleeping");
|
|
761
837
|
if (allNotes.length === 0) {
|
|
762
|
-
ctx.ui.notify("\u2316 No sticky notes.
|
|
838
|
+
ctx.ui.notify("\u2316 No sticky notes. Type `pin: <note>` or `/pin <note>` to stick one.", "info");
|
|
763
839
|
return;
|
|
764
840
|
}
|
|
765
841
|
const sorted = sortAnchorsForDisplay(allNotes, ctx.cwd);
|
|
@@ -770,10 +846,11 @@ async function openAnchorDashboard(ctx, store) {
|
|
|
770
846
|
const titleWidth = isNarrow ? Math.max(20, termCols - 30) : 42;
|
|
771
847
|
for (const a of sorted) {
|
|
772
848
|
const num = a.id.replace(/^anc-/, "").padStart(2, "0");
|
|
773
|
-
const
|
|
849
|
+
const bar = a.progress ? `${formatProgressBar(a.progress)} ` : "";
|
|
850
|
+
const origin = formatOrigin(a) + (a.sessionFile ? " \u21AA" : "");
|
|
774
851
|
const age = a.status === "sleeping" ? `${formatRelativeAge(a.createdAt)} \xB7 quiet` : formatRelativeAge(a.createdAt);
|
|
775
852
|
const colNum = `${num} `;
|
|
776
|
-
const colTitle = padToWidth(a.title
|
|
853
|
+
const colTitle = padToWidth(`${bar}${a.title}`, titleWidth);
|
|
777
854
|
const colOrigin = padToWidth(origin, 22);
|
|
778
855
|
const label = isNarrow ? `${colNum}${colTitle} ${age}`.trimEnd() : `${colNum}${colTitle} ${colOrigin} ${age}`.trimEnd();
|
|
779
856
|
optionMap.set(label, a);
|
|
@@ -783,8 +860,15 @@ async function openAnchorDashboard(ctx, store) {
|
|
|
783
860
|
if (!selected) return;
|
|
784
861
|
const anchor = optionMap.get(selected);
|
|
785
862
|
if (!anchor) return;
|
|
863
|
+
const currentSessionFile = ctx.sessionManager?.getSessionFile?.();
|
|
864
|
+
const canJumpSession = Boolean(
|
|
865
|
+
anchor.sessionFile && import_node_fs2.default.existsSync(anchor.sessionFile) && typeof ctx.switchSession === "function" && (!currentSessionFile || import_node_path2.default.resolve(anchor.sessionFile) !== import_node_path2.default.resolve(currentSessionFile))
|
|
866
|
+
);
|
|
867
|
+
const stepLabel = anchor.progress ? `\u25B6 Advance step (${anchor.progress.done}/${anchor.progress.total} \u2794 ${Math.min(anchor.progress.total, anchor.progress.done + 1)}/${anchor.progress.total})` : "\u25B6 Start progress bar (1/4)";
|
|
786
868
|
const actions = [
|
|
787
869
|
"\u2713 Done (tear off note)",
|
|
870
|
+
stepLabel,
|
|
871
|
+
...canJumpSession ? ["\u21AA Jump to session (resume linked conversation)"] : [],
|
|
788
872
|
...anchor.status === "sleeping" ? ["\u26A1 Wake up (mark active)"] : [],
|
|
789
873
|
"\u2139 Read full note",
|
|
790
874
|
"\u21A9 Back"
|
|
@@ -795,6 +879,23 @@ async function openAnchorDashboard(ctx, store) {
|
|
|
795
879
|
store.settle(anchor.id, { settledBy: "manual" });
|
|
796
880
|
ctx.ui.notify(`\u2316 Torn off #${anchor.id.replace(/^anc-/, "")}: "${anchor.title}" (/anchor undo to put back)`, "info");
|
|
797
881
|
updateAnchorStatusBar(ctx, store);
|
|
882
|
+
} else if (action.startsWith("\u25B6")) {
|
|
883
|
+
const { anchor: updated, completed } = store.step(anchor.id, 1);
|
|
884
|
+
updateAnchorStatusBar(ctx, store);
|
|
885
|
+
if (completed) {
|
|
886
|
+
ctx.ui.notify(
|
|
887
|
+
`\u2316 #${updated.id.replace(/^anc-/, "")} reached ${formatProgressBar(updated.progress)}! Run /done ${updated.id.replace(/^anc-/, "")} to tear off.`,
|
|
888
|
+
"info"
|
|
889
|
+
);
|
|
890
|
+
} else {
|
|
891
|
+
ctx.ui.notify(`\u2316 Advanced #${updated.id.replace(/^anc-/, "")} to ${formatProgressBar(updated.progress)}`, "info");
|
|
892
|
+
}
|
|
893
|
+
} else if (action.startsWith("\u21AA") && anchor.sessionFile) {
|
|
894
|
+
try {
|
|
895
|
+
await ctx.switchSession(anchor.sessionFile);
|
|
896
|
+
} catch (err) {
|
|
897
|
+
ctx.ui.notify(`Could not switch session: ${err.message}`, "error");
|
|
898
|
+
}
|
|
798
899
|
} else if (action.startsWith("\u26A1")) {
|
|
799
900
|
store.touch(anchor.id);
|
|
800
901
|
ctx.ui.notify(`\u2316 Woke up #${anchor.id.replace(/^anc-/, "")}: "${anchor.title}"`, "info");
|
|
@@ -802,8 +903,10 @@ async function openAnchorDashboard(ctx, store) {
|
|
|
802
903
|
} else if (action.startsWith("\u2139")) {
|
|
803
904
|
const details = [
|
|
804
905
|
`\u2316 Note #${anchor.id.replace(/^anc-/, "")}: ${anchor.title}`,
|
|
906
|
+
anchor.progress ? `Progress: ${formatProgressBar(anchor.progress)}${anchor.progress.steps ? ` (${anchor.progress.steps.join(" \u2192 ")})` : ""}` : null,
|
|
805
907
|
anchor.description ? `Details: ${anchor.description}` : null,
|
|
806
908
|
anchor.files.length > 0 ? `Files: ${anchor.files.join(", ")}` : null,
|
|
909
|
+
anchor.sessionFile ? `Session: ${import_node_path2.default.basename(anchor.sessionFile)}` : null,
|
|
807
910
|
`Workspace: ${anchor.project || "global"} \xB7 Pinned ${formatRelativeAge(anchor.createdAt)}`
|
|
808
911
|
].filter(Boolean).join("\n");
|
|
809
912
|
ctx.ui.notify(details, "info");
|
|
@@ -812,6 +915,7 @@ async function openAnchorDashboard(ctx, store) {
|
|
|
812
915
|
|
|
813
916
|
// src/extension.ts
|
|
814
917
|
var import_typebox = require("@sinclair/typebox");
|
|
918
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
815
919
|
function parseAnchorCommandInput(raw) {
|
|
816
920
|
const trimmed = (raw || "").trim();
|
|
817
921
|
if (!trimmed || trimmed === "list" || trimmed === "ls") {
|
|
@@ -827,11 +931,30 @@ function parseAnchorCommandInput(raw) {
|
|
|
827
931
|
targetId: settleMatch[1].trim().replace(/^#/, "")
|
|
828
932
|
};
|
|
829
933
|
}
|
|
934
|
+
const stepMatch = trimmed.match(/^(?:step|\+)\s*(\S+)(?:\s+([+-]?\d+))?$/i);
|
|
935
|
+
if (stepMatch) {
|
|
936
|
+
return {
|
|
937
|
+
mode: "step",
|
|
938
|
+
targetId: stepMatch[1].trim().replace(/^#/, ""),
|
|
939
|
+
delta: stepMatch[2] ? parseInt(stepMatch[2], 10) : 1
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
const jumpMatch = trimmed.match(/^(?:jump|resume|session)\s+(.+)$/i);
|
|
943
|
+
if (jumpMatch) {
|
|
944
|
+
return {
|
|
945
|
+
mode: "jump",
|
|
946
|
+
targetId: jumpMatch[1].trim().replace(/^#/, "")
|
|
947
|
+
};
|
|
948
|
+
}
|
|
830
949
|
return {
|
|
831
950
|
mode: "pin",
|
|
832
951
|
title: trimmed
|
|
833
952
|
};
|
|
834
953
|
}
|
|
954
|
+
function parseQuickNoteInput(text) {
|
|
955
|
+
const m = (text || "").trim().match(/^(?:pin|note|便签|备忘)\s*[::]\s*(.+)$/i);
|
|
956
|
+
return m && m[1].trim() ? m[1].trim() : null;
|
|
957
|
+
}
|
|
835
958
|
function resolveAnchorTarget(store, idOrIndex, cwd) {
|
|
836
959
|
const clean = idOrIndex.trim().replace(/^#/, "");
|
|
837
960
|
if (/^\d+$/.test(clean)) {
|
|
@@ -853,8 +976,10 @@ function resolveAnchorTarget(store, idOrIndex, cwd) {
|
|
|
853
976
|
acquireSyncLock,
|
|
854
977
|
atomicRenameWithRetry,
|
|
855
978
|
extractPathsFromText,
|
|
979
|
+
extractProgressFromText,
|
|
856
980
|
findMatchedAnchors,
|
|
857
981
|
formatOrigin,
|
|
982
|
+
formatProgressBar,
|
|
858
983
|
formatRelativeAge,
|
|
859
984
|
getDefaultStorageDir,
|
|
860
985
|
getDisplayWidth,
|
|
@@ -869,6 +994,7 @@ function resolveAnchorTarget(store, idOrIndex, cwd) {
|
|
|
869
994
|
openAnchorDashboard,
|
|
870
995
|
padToWidth,
|
|
871
996
|
parseAnchorCommandInput,
|
|
997
|
+
parseQuickNoteInput,
|
|
872
998
|
renderActiveAnchorsContext,
|
|
873
999
|
renderColdStartAnchorsContext,
|
|
874
1000
|
resolveAnchorTarget,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as Anchor, a as AnchorStore } from './extension-
|
|
2
|
-
export {
|
|
3
|
-
import { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
1
|
+
import { A as Anchor, a as AnchorProgress, b as AnchorStore } from './extension-Cq1NH1jT.cjs';
|
|
2
|
+
export { c as AnchorEvidence, d as AnchorPriority, e as AnchorStatus, f as AnchorStoreState, C as COMMENT_FORMATS, M as MS_PER_DAY, Q as QUIET_AFTER_DAYS, T as TouchMatchResult, g as acquireSyncLock, h as atomicRenameWithRetry, i as extractPathsFromText, j as extractProgressFromText, k as findMatchedAnchors, l as getDefaultStorageDir, m as globToRegExp, n as isAnchorTargetingWorkspace, o as isSameOrSubPath, p as makeSafeTaskAnnotation, q as matchAnchorAgainstTouchedFiles, r as matchesCommitMessage, s as normalizePath, t as parseAnchorCommandInput, u as parseQuickNoteInput, v as renderActiveAnchorsContext, w as renderColdStartAnchorsContext, x as resolveAnchorTarget, y as sweepQuietNotes } from './extension-Cq1NH1jT.cjs';
|
|
3
|
+
import { ExtensionContext, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
|
|
4
4
|
|
|
5
5
|
declare function stripAnsi(str: string): string;
|
|
6
6
|
/**
|
|
@@ -10,12 +10,16 @@ declare function getDisplayWidth(str: string): number;
|
|
|
10
10
|
declare function hasActiveUI(ctx: ExtensionContext | null | undefined): ctx is ExtensionContext;
|
|
11
11
|
declare function truncateToWidth(str: string, maxWidth: number, ellipsis?: string): string;
|
|
12
12
|
declare function padToWidth(str: string, targetWidth: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* Render a 4-cell micro progress bar: e.g. `[■■□□ 2/4]` or `[■□□□ 5/30]`
|
|
15
|
+
*/
|
|
16
|
+
declare function formatProgressBar(progress?: AnchorProgress, cells?: number): string;
|
|
13
17
|
/**
|
|
14
18
|
* Format relative sticky-note age cleanly: 'just now', '15m ago', '3h ago', '2d ago'
|
|
15
19
|
*/
|
|
16
20
|
declare function formatRelativeAge(timestamp: number, now?: number): string;
|
|
17
21
|
/**
|
|
18
|
-
* Format workspace & first file context concisely (e.g. 'anchor ·
|
|
22
|
+
* Format workspace & first file context concisely (e.g. 'anchor · auth.ts' or 'College/优化')
|
|
19
23
|
*/
|
|
20
24
|
declare function formatOrigin(anchor: Anchor | string): string;
|
|
21
25
|
/**
|
|
@@ -26,17 +30,18 @@ declare function formatOrigin(anchor: Anchor | string): string;
|
|
|
26
30
|
*/
|
|
27
31
|
declare function sortAnchorsForDisplay(anchors: Anchor[], cwd?: string): Anchor[];
|
|
28
32
|
/**
|
|
29
|
-
* Update status bar badge
|
|
33
|
+
* Update status bar badge (`⌖ 2`) AND the persistent below-editor sticky strip!
|
|
30
34
|
*/
|
|
31
35
|
declare function updateAnchorStatusBar(ctx: ExtensionContext, store: AnchorStore): void;
|
|
32
36
|
/**
|
|
33
|
-
* Render
|
|
34
|
-
*
|
|
37
|
+
* Render persistent 1-line sticky-note strip BELOW the editor (`placement: 'belowEditor'`).
|
|
38
|
+
* Never collides with `pi-lingual` (`aboveEditor`), stays visible while you work,
|
|
39
|
+
* and takes 0 lines when there are no active notes.
|
|
35
40
|
*/
|
|
36
41
|
declare function updateStartupBanner(ctx: ExtensionContext, store: AnchorStore): void;
|
|
37
42
|
/**
|
|
38
|
-
* Interactive `/anchor` sticky-note board
|
|
43
|
+
* Interactive `/anchor` sticky-note board with 1-click progress stepping and session teleport (`switchSession`)
|
|
39
44
|
*/
|
|
40
|
-
declare function openAnchorDashboard(ctx: ExtensionContext, store: AnchorStore): Promise<void>;
|
|
45
|
+
declare function openAnchorDashboard(ctx: ExtensionContext | ExtensionCommandContext, store: AnchorStore): Promise<void>;
|
|
41
46
|
|
|
42
|
-
export { Anchor, AnchorStore, formatOrigin, formatRelativeAge, getDisplayWidth, hasActiveUI, openAnchorDashboard, padToWidth, sortAnchorsForDisplay, stripAnsi, truncateToWidth, updateAnchorStatusBar, updateStartupBanner };
|
|
47
|
+
export { Anchor, AnchorProgress, AnchorStore, formatOrigin, formatProgressBar, formatRelativeAge, getDisplayWidth, hasActiveUI, openAnchorDashboard, padToWidth, sortAnchorsForDisplay, stripAnsi, truncateToWidth, updateAnchorStatusBar, updateStartupBanner };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as Anchor, a as AnchorStore } from './extension-
|
|
2
|
-
export {
|
|
3
|
-
import { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
1
|
+
import { A as Anchor, a as AnchorProgress, b as AnchorStore } from './extension-Cq1NH1jT.js';
|
|
2
|
+
export { c as AnchorEvidence, d as AnchorPriority, e as AnchorStatus, f as AnchorStoreState, C as COMMENT_FORMATS, M as MS_PER_DAY, Q as QUIET_AFTER_DAYS, T as TouchMatchResult, g as acquireSyncLock, h as atomicRenameWithRetry, i as extractPathsFromText, j as extractProgressFromText, k as findMatchedAnchors, l as getDefaultStorageDir, m as globToRegExp, n as isAnchorTargetingWorkspace, o as isSameOrSubPath, p as makeSafeTaskAnnotation, q as matchAnchorAgainstTouchedFiles, r as matchesCommitMessage, s as normalizePath, t as parseAnchorCommandInput, u as parseQuickNoteInput, v as renderActiveAnchorsContext, w as renderColdStartAnchorsContext, x as resolveAnchorTarget, y as sweepQuietNotes } from './extension-Cq1NH1jT.js';
|
|
3
|
+
import { ExtensionContext, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
|
|
4
4
|
|
|
5
5
|
declare function stripAnsi(str: string): string;
|
|
6
6
|
/**
|
|
@@ -10,12 +10,16 @@ declare function getDisplayWidth(str: string): number;
|
|
|
10
10
|
declare function hasActiveUI(ctx: ExtensionContext | null | undefined): ctx is ExtensionContext;
|
|
11
11
|
declare function truncateToWidth(str: string, maxWidth: number, ellipsis?: string): string;
|
|
12
12
|
declare function padToWidth(str: string, targetWidth: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* Render a 4-cell micro progress bar: e.g. `[■■□□ 2/4]` or `[■□□□ 5/30]`
|
|
15
|
+
*/
|
|
16
|
+
declare function formatProgressBar(progress?: AnchorProgress, cells?: number): string;
|
|
13
17
|
/**
|
|
14
18
|
* Format relative sticky-note age cleanly: 'just now', '15m ago', '3h ago', '2d ago'
|
|
15
19
|
*/
|
|
16
20
|
declare function formatRelativeAge(timestamp: number, now?: number): string;
|
|
17
21
|
/**
|
|
18
|
-
* Format workspace & first file context concisely (e.g. 'anchor ·
|
|
22
|
+
* Format workspace & first file context concisely (e.g. 'anchor · auth.ts' or 'College/优化')
|
|
19
23
|
*/
|
|
20
24
|
declare function formatOrigin(anchor: Anchor | string): string;
|
|
21
25
|
/**
|
|
@@ -26,17 +30,18 @@ declare function formatOrigin(anchor: Anchor | string): string;
|
|
|
26
30
|
*/
|
|
27
31
|
declare function sortAnchorsForDisplay(anchors: Anchor[], cwd?: string): Anchor[];
|
|
28
32
|
/**
|
|
29
|
-
* Update status bar badge
|
|
33
|
+
* Update status bar badge (`⌖ 2`) AND the persistent below-editor sticky strip!
|
|
30
34
|
*/
|
|
31
35
|
declare function updateAnchorStatusBar(ctx: ExtensionContext, store: AnchorStore): void;
|
|
32
36
|
/**
|
|
33
|
-
* Render
|
|
34
|
-
*
|
|
37
|
+
* Render persistent 1-line sticky-note strip BELOW the editor (`placement: 'belowEditor'`).
|
|
38
|
+
* Never collides with `pi-lingual` (`aboveEditor`), stays visible while you work,
|
|
39
|
+
* and takes 0 lines when there are no active notes.
|
|
35
40
|
*/
|
|
36
41
|
declare function updateStartupBanner(ctx: ExtensionContext, store: AnchorStore): void;
|
|
37
42
|
/**
|
|
38
|
-
* Interactive `/anchor` sticky-note board
|
|
43
|
+
* Interactive `/anchor` sticky-note board with 1-click progress stepping and session teleport (`switchSession`)
|
|
39
44
|
*/
|
|
40
|
-
declare function openAnchorDashboard(ctx: ExtensionContext, store: AnchorStore): Promise<void>;
|
|
45
|
+
declare function openAnchorDashboard(ctx: ExtensionContext | ExtensionCommandContext, store: AnchorStore): Promise<void>;
|
|
41
46
|
|
|
42
|
-
export { Anchor, AnchorStore, formatOrigin, formatRelativeAge, getDisplayWidth, hasActiveUI, openAnchorDashboard, padToWidth, sortAnchorsForDisplay, stripAnsi, truncateToWidth, updateAnchorStatusBar, updateStartupBanner };
|
|
47
|
+
export { Anchor, AnchorProgress, AnchorStore, formatOrigin, formatProgressBar, formatRelativeAge, getDisplayWidth, hasActiveUI, openAnchorDashboard, padToWidth, sortAnchorsForDisplay, stripAnsi, truncateToWidth, updateAnchorStatusBar, updateStartupBanner };
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
acquireSyncLock,
|
|
7
7
|
atomicRenameWithRetry,
|
|
8
8
|
extractPathsFromText,
|
|
9
|
+
extractProgressFromText,
|
|
9
10
|
findMatchedAnchors,
|
|
10
11
|
formatOrigin,
|
|
12
|
+
formatProgressBar,
|
|
11
13
|
formatRelativeAge,
|
|
12
14
|
getDefaultStorageDir,
|
|
13
15
|
getDisplayWidth,
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
openAnchorDashboard,
|
|
23
25
|
padToWidth,
|
|
24
26
|
parseAnchorCommandInput,
|
|
27
|
+
parseQuickNoteInput,
|
|
25
28
|
renderActiveAnchorsContext,
|
|
26
29
|
renderColdStartAnchorsContext,
|
|
27
30
|
resolveAnchorTarget,
|
|
@@ -31,7 +34,7 @@ import {
|
|
|
31
34
|
truncateToWidth,
|
|
32
35
|
updateAnchorStatusBar,
|
|
33
36
|
updateStartupBanner
|
|
34
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-VXEQDTK4.js";
|
|
35
38
|
export {
|
|
36
39
|
AnchorStore,
|
|
37
40
|
COMMENT_FORMATS,
|
|
@@ -40,8 +43,10 @@ export {
|
|
|
40
43
|
acquireSyncLock,
|
|
41
44
|
atomicRenameWithRetry,
|
|
42
45
|
extractPathsFromText,
|
|
46
|
+
extractProgressFromText,
|
|
43
47
|
findMatchedAnchors,
|
|
44
48
|
formatOrigin,
|
|
49
|
+
formatProgressBar,
|
|
45
50
|
formatRelativeAge,
|
|
46
51
|
getDefaultStorageDir,
|
|
47
52
|
getDisplayWidth,
|
|
@@ -56,6 +61,7 @@ export {
|
|
|
56
61
|
openAnchorDashboard,
|
|
57
62
|
padToWidth,
|
|
58
63
|
parseAnchorCommandInput,
|
|
64
|
+
parseQuickNoteInput,
|
|
59
65
|
renderActiveAnchorsContext,
|
|
60
66
|
renderColdStartAnchorsContext,
|
|
61
67
|
resolveAnchorTarget,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-anchor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A digital sticky note
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "A digital sticky note docked below your Pi editor — stick notes in 0ms (pin: ...), track long tasks with micro progress bars [■■□□], jump back to the original session (↪), and auto-tear off on git commit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|