pi-link 0.1.9 → 0.1.10
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 +171 -159
- package/README.md +589 -580
- package/bin/pi-link.mjs +75 -65
- package/index.ts +1480 -1424
- package/package.json +1 -1
- package/skills/pi-link-coordination/SKILL.md +16 -9
package/bin/pi-link.mjs
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// pi-link CLI —
|
|
3
|
+
// pi-link CLI — launch Pi with session resume by name
|
|
4
4
|
//
|
|
5
5
|
// Usage:
|
|
6
|
-
// pi-link
|
|
7
|
-
//
|
|
8
|
-
// If a session named <name> exists, resumes it.
|
|
9
|
-
// If not, creates a new session.
|
|
10
|
-
// Always connects to the link as <name>.
|
|
6
|
+
// pi-link <name> [flags...] Resume or create a named session, connected to link.
|
|
7
|
+
// pi-link resolve <name> Print just the session path (machine-readable).
|
|
11
8
|
|
|
12
9
|
import { readdir, stat } from "fs/promises";
|
|
13
10
|
import { createReadStream } from "fs";
|
|
@@ -18,9 +15,7 @@ import { spawn } from "child_process";
|
|
|
18
15
|
|
|
19
16
|
const SESSIONS_DIR = join(homedir(), ".pi", "agent", "sessions");
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
async function getSessionName(filePath) {
|
|
18
|
+
async function getSessionMeta(filePath) {
|
|
24
19
|
let name;
|
|
25
20
|
let cwd;
|
|
26
21
|
const rl = createInterface({ input: createReadStream(filePath, "utf-8"), crlfDelay: Infinity });
|
|
@@ -28,9 +23,9 @@ async function getSessionName(filePath) {
|
|
|
28
23
|
if (!line) continue;
|
|
29
24
|
try {
|
|
30
25
|
const entry = JSON.parse(line);
|
|
31
|
-
if (entry.type === "session" && entry.cwd) cwd = entry.cwd;
|
|
32
|
-
if (entry.type === "session_info" && entry.name
|
|
33
|
-
name = entry.name
|
|
26
|
+
if (entry.type === "session" && typeof entry.cwd === "string") cwd = entry.cwd;
|
|
27
|
+
if (entry.type === "session_info" && typeof entry.name === "string") {
|
|
28
|
+
name = entry.name.trim().replace(/\s+/g, " ") || undefined;
|
|
34
29
|
}
|
|
35
30
|
} catch {
|
|
36
31
|
// skip malformed lines
|
|
@@ -64,7 +59,7 @@ async function findSessionsByName(targetName) {
|
|
|
64
59
|
if (!file.endsWith(".jsonl")) continue;
|
|
65
60
|
const filePath = join(dirPath, file);
|
|
66
61
|
try {
|
|
67
|
-
const { name, cwd } = await
|
|
62
|
+
const { name, cwd } = await getSessionMeta(filePath);
|
|
68
63
|
if (name === targetName) {
|
|
69
64
|
const stats = await stat(filePath);
|
|
70
65
|
matches.push({ path: filePath, cwd: cwd || "?", modified: stats.mtime });
|
|
@@ -88,67 +83,82 @@ async function findSessionsByName(targetName) {
|
|
|
88
83
|
|
|
89
84
|
// ── CLI ────────────────────────────────────────────────────────────────────
|
|
90
85
|
|
|
91
|
-
const args = process.argv.slice(2);
|
|
92
|
-
const command = args[0];
|
|
93
|
-
|
|
94
|
-
if (command !== "start" || args.length < 2) {
|
|
95
|
-
console.log(`Usage: pi-link start <name> [pi-flags...]
|
|
86
|
+
const [command, ...args] = process.argv.slice(2);
|
|
96
87
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
pi
|
|
104
|
-
process.exit(command === "start" ? 1 : 0);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const name = args[1].trim().replace(/\s+/g, " ");
|
|
108
|
-
if (!name) {
|
|
109
|
-
console.error("Error: name cannot be empty.");
|
|
88
|
+
function printCandidates(name, matches) {
|
|
89
|
+
console.error(`Multiple sessions named "${name}":\n`);
|
|
90
|
+
for (const m of matches) {
|
|
91
|
+
console.error(` ${m.modified.toISOString().slice(0, 19)} cwd: ${m.cwd}`);
|
|
92
|
+
console.error(` ${m.path}\n`);
|
|
93
|
+
}
|
|
94
|
+
console.error(`Use: pi --session <path> --link`);
|
|
110
95
|
process.exit(1);
|
|
111
96
|
}
|
|
112
97
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
console.error(
|
|
98
|
+
if (command === "resolve") {
|
|
99
|
+
const name = args[0]?.trim().replace(/\s+/g, " ");
|
|
100
|
+
if (!name) {
|
|
101
|
+
console.error("Usage: pi-link resolve <name>");
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
const matches = await findSessionsByName(name);
|
|
105
|
+
if (matches.length === 1) {
|
|
106
|
+
process.stdout.write(matches[0].path);
|
|
107
|
+
} else if (matches.length > 1) {
|
|
108
|
+
printCandidates(name, matches);
|
|
109
|
+
}
|
|
110
|
+
} else if (command && command !== "--help" && command !== "-h") {
|
|
111
|
+
// pi-link <name> [flags...] — resolve and launch Pi
|
|
112
|
+
const name = command.trim().replace(/\s+/g, " ");
|
|
113
|
+
if (!name) {
|
|
114
|
+
console.error("Usage: pi-link <name> [pi flags...]");
|
|
117
115
|
process.exit(1);
|
|
118
116
|
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
console.log(`Searching for session "${name}"...`);
|
|
122
|
-
const matches = await findSessionsByName(name);
|
|
123
|
-
|
|
124
|
-
const piArgs = [];
|
|
125
117
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
118
|
+
// Reject conflicting flags
|
|
119
|
+
for (const flag of args) {
|
|
120
|
+
const key = flag.split("=")[0];
|
|
121
|
+
if (["--session", "--continue", "-c", "--resume", "-r", "--fork", "--no-session", "--session-dir"].includes(key)) {
|
|
122
|
+
console.error(`Error: ${key} is managed by pi-link. Remove it.`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
if (key === "--link-name") {
|
|
126
|
+
console.error("Error: --link-name was removed. Use: pi-link <name>");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
134
129
|
}
|
|
135
|
-
console.error(`Use pi --session <path> --link-name ${name} to pick one.`);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
} else {
|
|
138
|
-
console.log("No existing session found. Starting new session.");
|
|
139
|
-
}
|
|
140
130
|
|
|
141
|
-
|
|
131
|
+
const matches = await findSessionsByName(name);
|
|
132
|
+
if (matches.length > 1) {
|
|
133
|
+
printCandidates(name, matches);
|
|
134
|
+
}
|
|
142
135
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
136
|
+
const piArgs = [];
|
|
137
|
+
if (matches.length === 1) {
|
|
138
|
+
console.error(`Resuming session: ${matches[0].path}`);
|
|
139
|
+
piArgs.push("--session", matches[0].path);
|
|
140
|
+
} else {
|
|
141
|
+
console.error("No existing session found. Starting new session.");
|
|
142
|
+
}
|
|
143
|
+
piArgs.push("--link", ...args);
|
|
147
144
|
|
|
148
|
-
const
|
|
145
|
+
const isWin = process.platform === "win32";
|
|
146
|
+
const cmd = isWin ? "cmd.exe" : "pi";
|
|
147
|
+
const cmdArgs = isWin ? ["/d", "/c", "pi", ...piArgs] : piArgs;
|
|
149
148
|
|
|
150
|
-
child
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
const child = spawn(cmd, cmdArgs, {
|
|
150
|
+
stdio: "inherit",
|
|
151
|
+
env: { ...process.env, PI_LINK_NAME: name },
|
|
152
|
+
});
|
|
153
|
+
child.once("exit", (code, signal) => {
|
|
154
|
+
if (code !== null) process.exit(code);
|
|
155
|
+
process.exit(signal === "SIGINT" ? 130 : 1);
|
|
156
|
+
});
|
|
157
|
+
child.once("error", (err) => {
|
|
158
|
+
console.error(`Failed to start pi: ${err.message}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
console.error("Usage: pi-link <name> [pi flags...]\n pi-link resolve <name>");
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|