@selesai/code 0.13.1 → 0.13.2
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/CHANGELOG.md +10 -0
- package/dist/extensions/pi-rewind-hook/README.md +4 -1
- package/dist/extensions/pi-rewind-hook/index.ts +151 -21
- package/dist/extensions/pi-rewind-hook/package.json +5 -3
- package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +0 -4
- package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +4 -6
- package/package.json +1 -1
- package/dist/extensions/ascii.txt +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@selesai/code` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.13.2] - 2026-08-30
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Rewind submodule support.** `pi-rewind-hook` now snapshots gitlink commits and restores submodule worktrees to their target commits during exact rewind, with safety checks: submodule paths must stay unchanged, worktrees must be clean and initialized, and target commits must be available locally. Unsupported submodule states (dirty, uninitialized, added/removed paths, nested submodules) are refused with a clear error instead of being reported as exact restores.
|
|
9
|
+
- **Rewind retention sweep concurrency safety.** The retention sweep now uses a compare-and-swap on the store ref with retries, so concurrent sessions adding snapshots mid-sweep are preserved instead of being clobbered; the startup sweep is reused across session replacement instead of racing a second sweep.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **Herdr status bridge drops agent scoping.** `pi-subagents` no longer passes `--agent pi` / `--applies-to-source herdr:pi` to Herdr `report-metadata` calls; state labels are reported under the `pi-subagents:herdr` source directly.
|
|
13
|
+
- **Rewind extension packaging.** `pi-rewind-hook` declares `@selesai/code` as a peer dependency (was devDependency) and bumps to 1.8.6.
|
|
14
|
+
|
|
5
15
|
## [0.13.1] - 2026-08-30
|
|
6
16
|
|
|
7
17
|
### Fixed
|
|
@@ -12,7 +12,7 @@ Rewind metadata lives in the session itself as hidden entries, so rewind history
|
|
|
12
12
|
|
|
13
13
|
## Requirements
|
|
14
14
|
|
|
15
|
-
- Pi agent v0.
|
|
15
|
+
- Pi agent v0.74.0+
|
|
16
16
|
- Node.js (for installation)
|
|
17
17
|
- Git repository
|
|
18
18
|
|
|
@@ -181,6 +181,8 @@ Rewind restores the exact file state for the snapshot domain it owns:
|
|
|
181
181
|
|
|
182
182
|
Before restoring target contents, it deletes paths present in the current snapshot but absent from the target snapshot, then restores the target snapshot into the worktree only.
|
|
183
183
|
|
|
184
|
+
Submodules are supported only when their paths stay unchanged, their worktrees are clean and initialized, and each target commit is already available locally. Rewind checks out those target commits without fetching. Exact restore is refused with a clear error for dirty or unavailable submodule state, added or removed submodule paths, and nested submodules; uncommitted files inside submodules are not part of snapshots.
|
|
185
|
+
|
|
184
186
|
Out of scope:
|
|
185
187
|
|
|
186
188
|
- ignored files
|
|
@@ -240,6 +242,7 @@ grep '"customType":"rewind-' ~/.pi/agent/sessions/**/*.jsonl
|
|
|
240
242
|
## Limitations
|
|
241
243
|
|
|
242
244
|
- Only works in git repositories
|
|
245
|
+
- Submodule snapshots contain gitlink commits, not uncommitted nested files; unsupported submodule states are refused instead of reported as exact restores
|
|
243
246
|
- Session metadata grows append-only; retention only trims git reachability
|
|
244
247
|
- Discovery for retention is best-effort across discovered Pi session roots and explicit `parentSession` ancestors
|
|
245
248
|
- Ignored files and empty directories are outside the snapshot model
|
|
@@ -17,6 +17,8 @@ const RETENTION_SWEEP_THRESHOLD = 50;
|
|
|
17
17
|
const RETENTION_VERSION = 2;
|
|
18
18
|
const EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
19
19
|
|
|
20
|
+
class UnsupportedSubmoduleStateError extends Error {}
|
|
21
|
+
|
|
20
22
|
type ExecFn = (cmd: string, args: string[]) => Promise<{ stdout: string; stderr: string; code: number }>;
|
|
21
23
|
|
|
22
24
|
type GitExecResult = Awaited<ReturnType<ExecFn>>;
|
|
@@ -70,6 +72,11 @@ interface ExactState {
|
|
|
70
72
|
treeSha: string;
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
interface GitlinkEntry {
|
|
76
|
+
path: string;
|
|
77
|
+
commitSha: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
interface ActiveBranchState {
|
|
74
81
|
currentCommitSha?: string;
|
|
75
82
|
currentTreeSha?: string;
|
|
@@ -387,7 +394,7 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
387
394
|
let pendingTreeState: PendingResultingState | null = null;
|
|
388
395
|
let activePromptText: string | null = null;
|
|
389
396
|
let newSnapshotsSinceSweep = 0;
|
|
390
|
-
let
|
|
397
|
+
let retentionSweep: Promise<void> | undefined;
|
|
391
398
|
let sweepCompletedThisSession = false;
|
|
392
399
|
let forceConversationOnlyOnNextFork = false;
|
|
393
400
|
let forceConversationOnlySource: string | null = null;
|
|
@@ -461,6 +468,87 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
461
468
|
return repoRoot;
|
|
462
469
|
}
|
|
463
470
|
|
|
471
|
+
async function getGitlinkEntries(treeSha: string, gitPrefix: string[] = []): Promise<GitlinkEntry[]> {
|
|
472
|
+
const result = await execGitChecked([...gitPrefix, "ls-tree", "-r", "-z", treeSha, "--"]);
|
|
473
|
+
return parseGitlinkEntries(result.stdout);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function parseGitlinkEntries(output: string): GitlinkEntry[] {
|
|
477
|
+
return output.split("\0").flatMap((record) => {
|
|
478
|
+
const separator = record.indexOf("\t");
|
|
479
|
+
if (separator < 0) return [];
|
|
480
|
+
|
|
481
|
+
const [mode, type, commitSha] = record.slice(0, separator).split(/\s+/);
|
|
482
|
+
if (mode !== "160000" || type !== "commit" || !commitSha) return [];
|
|
483
|
+
return [{ path: record.slice(separator + 1), commitSha }];
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function getIndexedGitlinkEntries(): Promise<GitlinkEntry[]> {
|
|
488
|
+
const result = await execGitChecked(["ls-files", "--stage", "-z"]);
|
|
489
|
+
return parseGitlinkEntries(result.stdout);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function assertSubmoduleWorktreesClean(root: string, entries: GitlinkEntry[], action: string) {
|
|
493
|
+
for (const entry of entries) {
|
|
494
|
+
const submodulePath = resolve(root, entry.path);
|
|
495
|
+
if (!isInsidePath(submodulePath, root)) {
|
|
496
|
+
throw new UnsupportedSubmoduleStateError(`refusing ${action} for submodule path outside repo root: ${entry.path}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const status = await pi.exec("git", ["-C", submodulePath, "status", "--porcelain", "--untracked-files=all"]);
|
|
500
|
+
if (status.code !== 0) {
|
|
501
|
+
throw new UnsupportedSubmoduleStateError(`cannot ${action} with uninitialized submodule: ${entry.path}`);
|
|
502
|
+
}
|
|
503
|
+
if (status.stdout.trim()) {
|
|
504
|
+
throw new UnsupportedSubmoduleStateError(`cannot ${action} with dirty submodule: ${entry.path}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function prepareSubmoduleRestore(root: string, currentEntries: GitlinkEntry[], targetEntries: GitlinkEntry[]) {
|
|
510
|
+
const currentPaths = new Set(currentEntries.map((entry) => entry.path));
|
|
511
|
+
const targetPaths = new Set(targetEntries.map((entry) => entry.path));
|
|
512
|
+
const changedPaths = [...new Set([...currentPaths, ...targetPaths])]
|
|
513
|
+
.filter((entryPath) => !currentPaths.has(entryPath) || !targetPaths.has(entryPath));
|
|
514
|
+
if (changedPaths.length > 0) {
|
|
515
|
+
throw new UnsupportedSubmoduleStateError(
|
|
516
|
+
`exact rewind does not support adding or removing submodules: ${changedPaths.join(", ")}`,
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
await assertSubmoduleWorktreesClean(root, currentEntries, "restore exact files");
|
|
521
|
+
|
|
522
|
+
const commitsToInspect = new Map<string, GitlinkEntry>();
|
|
523
|
+
for (const entry of [...currentEntries, ...targetEntries]) {
|
|
524
|
+
commitsToInspect.set(`${entry.path}\0${entry.commitSha}`, entry);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
for (const entry of commitsToInspect.values()) {
|
|
528
|
+
const entryPath = entry.path;
|
|
529
|
+
const commitSha = entry.commitSha;
|
|
530
|
+
const submodulePath = resolve(root, entryPath);
|
|
531
|
+
const commit = await pi.exec("git", ["-C", submodulePath, "cat-file", "-e", `${commitSha}^{commit}`]);
|
|
532
|
+
if (commit.code !== 0) {
|
|
533
|
+
throw new UnsupportedSubmoduleStateError(
|
|
534
|
+
`cannot inspect submodule ${entryPath}: commit ${commitSha.slice(0, 12)} is unavailable locally`,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const targetTreeSha = (await execGitChecked(["-C", submodulePath, "show", "-s", "--format=%T", commitSha])).stdout.trim();
|
|
539
|
+
const nestedEntries = await getGitlinkEntries(targetTreeSha, ["-C", submodulePath]);
|
|
540
|
+
if (nestedEntries.length > 0) {
|
|
541
|
+
throw new UnsupportedSubmoduleStateError(`cannot restore nested submodules: ${entryPath}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function restoreSubmodules(root: string, entries: GitlinkEntry[]) {
|
|
547
|
+
for (const entry of entries) {
|
|
548
|
+
await execGitChecked(["-C", resolve(root, entry.path), "checkout", "--detach", entry.commitSha]);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
464
552
|
async function captureWorktreeTree(): Promise<{ treeSha: string }> {
|
|
465
553
|
const root = await getRepoRoot(pi.exec);
|
|
466
554
|
const tempDir = await mkdtemp(join(tmpdir(), "pi-rewind-"));
|
|
@@ -470,7 +558,12 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
470
558
|
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
471
559
|
await execAsync("git add -A", { cwd: root, env });
|
|
472
560
|
const { stdout } = await execAsync("git write-tree", { cwd: root, env });
|
|
473
|
-
|
|
561
|
+
const treeSha = stdout.trim();
|
|
562
|
+
const entriesByPath = new Map<string, GitlinkEntry>();
|
|
563
|
+
for (const entry of await getIndexedGitlinkEntries()) entriesByPath.set(entry.path, entry);
|
|
564
|
+
for (const entry of await getGitlinkEntries(treeSha)) entriesByPath.set(entry.path, entry);
|
|
565
|
+
await assertSubmoduleWorktreesClean(root, [...entriesByPath.values()], "capture exact files");
|
|
566
|
+
return { treeSha };
|
|
474
567
|
} finally {
|
|
475
568
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
476
569
|
// Best effort cleanup for temporary index directory.
|
|
@@ -542,19 +635,38 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
542
635
|
return "preserved-empty";
|
|
543
636
|
}
|
|
544
637
|
|
|
545
|
-
let
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
638
|
+
let attempts = 0;
|
|
639
|
+
let lastError: unknown;
|
|
640
|
+
let preserveConcurrentHead = false;
|
|
641
|
+
|
|
642
|
+
while (attempts < 5) {
|
|
643
|
+
attempts += 1;
|
|
644
|
+
const oldHead = await getStoreHead();
|
|
645
|
+
// A failed compare-and-swap means another session added snapshots while
|
|
646
|
+
// this sweep was running. Preserve that new reachability and let a later
|
|
647
|
+
// uncontended sweep prune anything no longer retained.
|
|
648
|
+
let head = preserveConcurrentHead ? oldHead : undefined;
|
|
649
|
+
for (const commitSha of uniqueLiveCommits) {
|
|
650
|
+
head = await createStoreKeepaliveCommit(commitSha, head);
|
|
651
|
+
}
|
|
549
652
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
653
|
+
try {
|
|
654
|
+
if (oldHead) {
|
|
655
|
+
await execGitChecked(["update-ref", STORE_REF, head!, oldHead]);
|
|
656
|
+
} else {
|
|
657
|
+
await execGitChecked(["update-ref", STORE_REF, head!, LEGACY_ZERO_SHA]);
|
|
658
|
+
}
|
|
659
|
+
return "rewritten";
|
|
660
|
+
} catch (error) {
|
|
661
|
+
// Retry if another process updated the store ref concurrently.
|
|
662
|
+
// Keep the most recent error for actionable failure context.
|
|
663
|
+
lastError = error;
|
|
664
|
+
preserveConcurrentHead = true;
|
|
665
|
+
}
|
|
554
666
|
}
|
|
555
667
|
|
|
556
|
-
|
|
557
|
-
|
|
668
|
+
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
669
|
+
throw new Error(`failed to rewrite rewind store ref: ${detail}`);
|
|
558
670
|
}
|
|
559
671
|
|
|
560
672
|
async function ensureSnapshotForTree(treeSha: string): Promise<string> {
|
|
@@ -606,6 +718,12 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
606
718
|
const { treeSha: currentTreeSha } = await captureWorktreeTree();
|
|
607
719
|
const targetTreeSha = await getCommitTreeSha(targetCommitSha);
|
|
608
720
|
|
|
721
|
+
const currentSubmodules = await getGitlinkEntries(currentTreeSha);
|
|
722
|
+
const targetSubmodules = await getGitlinkEntries(targetTreeSha);
|
|
723
|
+
if (currentSubmodules.length > 0 || targetSubmodules.length > 0) {
|
|
724
|
+
await prepareSubmoduleRestore(repoRoot!, currentSubmodules, targetSubmodules);
|
|
725
|
+
}
|
|
726
|
+
|
|
609
727
|
if (currentTreeSha === targetTreeSha) {
|
|
610
728
|
lastExact = { commitSha: targetCommitSha, treeSha: targetTreeSha };
|
|
611
729
|
return { changed: false, targetTreeSha };
|
|
@@ -615,6 +733,13 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
615
733
|
const pathsToDelete = await getDeletedPaths(currentTreeSha, targetTreeSha);
|
|
616
734
|
await deletePathsFromWorkingTree(pathsToDelete);
|
|
617
735
|
await execGitChecked(["restore", `--source=${targetCommitSha}`, "--worktree", "--", "."]);
|
|
736
|
+
if (targetSubmodules.length > 0) {
|
|
737
|
+
await restoreSubmodules(repoRoot!, targetSubmodules);
|
|
738
|
+
const { treeSha: restoredTreeSha } = await captureWorktreeTree();
|
|
739
|
+
if (restoredTreeSha !== targetTreeSha) {
|
|
740
|
+
throw new Error(`submodule restore did not produce target tree ${targetTreeSha}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
618
743
|
lastExact = { commitSha: targetCommitSha, treeSha: targetTreeSha };
|
|
619
744
|
return { changed: true, undoCommitSha, targetTreeSha };
|
|
620
745
|
}
|
|
@@ -964,13 +1089,13 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
964
1089
|
if (reason === "new-snapshots" && newSnapshotsSinceSweep < RETENTION_SWEEP_THRESHOLD) return;
|
|
965
1090
|
if (reason === "shutdown" && sweepCompletedThisSession && newSnapshotsSinceSweep < RETENTION_SWEEP_THRESHOLD) return;
|
|
966
1091
|
if (!repoRoot) return;
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1092
|
+
// Session replacement invalidates ctx after session_shutdown resolves, so reuse
|
|
1093
|
+
// the in-flight startup sweep and let shutdown await it before returning.
|
|
1094
|
+
if (retentionSweep) return retentionSweep;
|
|
1095
|
+
retentionSweep = runRetentionSweep(ctx, reason).finally(() => {
|
|
1096
|
+
retentionSweep = undefined;
|
|
1097
|
+
});
|
|
1098
|
+
return retentionSweep;
|
|
974
1099
|
}
|
|
975
1100
|
|
|
976
1101
|
async function runRetentionSweep(ctx: ExtensionContext, reason: "startup" | "new-snapshots" | "shutdown") {
|
|
@@ -1127,7 +1252,12 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
1127
1252
|
}
|
|
1128
1253
|
|
|
1129
1254
|
await getRepoRoot(pi.exec);
|
|
1130
|
-
|
|
1255
|
+
try {
|
|
1256
|
+
await reconstructState(ctx);
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
if (!(error instanceof UnsupportedSubmoduleStateError)) throw error;
|
|
1259
|
+
notify(ctx, `Rewind unavailable: ${error.message}`, "warning");
|
|
1260
|
+
}
|
|
1131
1261
|
updateStatus(ctx);
|
|
1132
1262
|
maybeSweepRetention(ctx, "startup").catch((error) => {
|
|
1133
1263
|
notify(ctx, `Rewind retention startup sweep failed: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
@@ -1151,7 +1281,7 @@ export default function rewindExtension(pi: ExtensionAPI) {
|
|
|
1151
1281
|
const ctx = activeContext;
|
|
1152
1282
|
if (!ctx) return;
|
|
1153
1283
|
|
|
1154
|
-
checkpointEntry(ctx, data.entryId).catch((error) => {
|
|
1284
|
+
return checkpointEntry(ctx, data.entryId).catch((error) => {
|
|
1155
1285
|
notify(ctx, `Rewind: failed to checkpoint ${data.entryId} (${error instanceof Error ? error.message : String(error)})`, "warning");
|
|
1156
1286
|
});
|
|
1157
1287
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-rewind-hook",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.6",
|
|
4
4
|
"description": "Rewind extension for Pi agent - automatic git checkpoints with file/conversation restore",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
"author": "nicobailon",
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"pi": {
|
|
21
|
-
"extensions": [
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./index.ts"
|
|
23
|
+
]
|
|
22
24
|
},
|
|
23
25
|
"scripts": {
|
|
24
26
|
"test": "npx --yes tsx --tsconfig tsconfig.test.json --test index.test.ts"
|
|
25
27
|
},
|
|
26
|
-
"
|
|
28
|
+
"peerDependencies": {
|
|
27
29
|
"@selesai/code": "*"
|
|
28
30
|
}
|
|
29
31
|
}
|
|
@@ -205,8 +205,6 @@ export function registerHerdrStatusBridge(options: HerdrStatusBridgeOptions): He
|
|
|
205
205
|
enqueue([
|
|
206
206
|
"pane", "report-metadata", paneId,
|
|
207
207
|
"--source", DEFAULT_SOURCE,
|
|
208
|
-
"--agent", "pi",
|
|
209
|
-
"--applies-to-source", "herdr:pi",
|
|
210
208
|
"--clear-state-labels",
|
|
211
209
|
"--clear-token", "summary",
|
|
212
210
|
"--clear-token", "title-suffix",
|
|
@@ -220,8 +218,6 @@ export function registerHerdrStatusBridge(options: HerdrStatusBridgeOptions): He
|
|
|
220
218
|
enqueue([
|
|
221
219
|
"pane", "report-metadata", paneId,
|
|
222
220
|
"--source", DEFAULT_SOURCE,
|
|
223
|
-
"--agent", "pi",
|
|
224
|
-
"--applies-to-source", "herdr:pi",
|
|
225
221
|
"--state-label", `idle=${text}`,
|
|
226
222
|
"--state-label", `done=${text}`,
|
|
227
223
|
"--state-label", `working=${text}`,
|
|
@@ -159,17 +159,15 @@ describe("Herdr status bridge", () => {
|
|
|
159
159
|
label: "⏳ 1 subagent (worker)",
|
|
160
160
|
}]);
|
|
161
161
|
assert.equal(commands.length, 1);
|
|
162
|
-
assert.deepEqual(commands[0]?.slice(0,
|
|
162
|
+
assert.deepEqual(commands[0]?.slice(0, 4), [
|
|
163
163
|
"pane",
|
|
164
164
|
"report-metadata",
|
|
165
165
|
"w1:p1",
|
|
166
166
|
"--source",
|
|
167
|
-
"pi-subagents:herdr",
|
|
168
|
-
"--agent",
|
|
169
|
-
"pi",
|
|
170
|
-
"--applies-to-source",
|
|
171
167
|
]);
|
|
172
|
-
assert.ok(commands[0]?.includes("herdr
|
|
168
|
+
assert.ok(commands[0]?.includes("pi-subagents:herdr"));
|
|
169
|
+
assert.ok(!commands[0]?.includes("--agent"));
|
|
170
|
+
assert.ok(!commands[0]?.includes("--applies-to-source"));
|
|
173
171
|
assert.ok(commands[0]?.includes("--state-label"));
|
|
174
172
|
assert.ok(commands[0]?.includes("working=⏳ 1 subagent (worker)"));
|
|
175
173
|
assert.ok(commands[0]?.includes("idle=⏳ 1 subagent (worker)"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selesai/code",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
____ ____ __ ____ ____ ______ ______
|
|
2
|
-
/\ _`\ /\ _`\ /\ \ /\ _`\ /\ _`\ /\ _ \/\__ _\
|
|
3
|
-
\ \,\L\_\ \ \L\_\ \ \ \ \ \L\_\ \,\L\_\ \ \ \L\ \/_/\ \/
|
|
4
|
-
\/_\__ \\ \ _\L\ \ \ __\ \ _\L\/_\__ \ \ \ __ \ \ \ \
|
|
5
|
-
/\ \L\ \ \ \L\ \ \ \L\ \\ \ \L\ \/\ \L\ \ \ \ \/\ \ \_\ \__
|
|
6
|
-
\ `\____\ \____/\ \____/ \ \____/\ `\____\ \ \_\ \_\/\_____\
|
|
7
|
-
\/_____/\/___/ \/___/ \/___/ \/_____/ \/_/\/_/\/_____/
|
|
8
|
-
|
|
9
|
-
|