pi-link 0.1.14-beta.0 → 0.1.15-beta.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/CHANGELOG.md +50 -2
- package/README.md +7 -5
- package/bin/pi-link.mjs +259 -84
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,55 @@ This changelog is based on the git history from `2026-03-21` (initial commit) th
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## 0.1.
|
|
9
|
+
## 0.1.15-beta.0 — 2026-05-17
|
|
10
|
+
|
|
11
|
+
Beta release for the 0.1.15 cycle. Install with `npm i -g pi-link@beta` for soak; `npm i -g pi-link` continues to install 0.1.14. Promotion to `latest` after smoke + at least one external user confirmation.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`--list` and `--resolve <name>` flag forms for the `pi-link` CLI wrapper.** Use instead of the `list` / `resolve` subcommands. `--resolve=<name>` joined form also accepted. This fixes the reserved-word collision that prevented sessions named `list` or `resolve`, and the silent-typo failure mode where `pi-link resolv foo` would create a session called `resolv` and pass `foo` as a prompt to Pi.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
pi-link --list [--global|-g]
|
|
19
|
+
pi-link --resolve <name> [--global|-g]
|
|
20
|
+
pi-link --resolve=<name>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`pi-link --resolve <missing-name>` now exits with code `2`** (was `0`). Single match still exits `0`; ambiguous still exits `1`; not found is now distinguishable from success in scripts. The legacy `pi-link resolve <missing-name>` form gets the same fix.
|
|
26
|
+
- **`pi-link <name> <extra-positional>` now errors** instead of silently passing the extra to Pi as a prompt. Catches typos like `pi-link resolv foo`. Tokens that follow a flag without `=` are still accepted as that flag's value (e.g. `pi-link worker --model opus` works). Use `--` to pass bare positionals through unchanged: `pi-link worker -- some-arg`.
|
|
27
|
+
- **`pi-link foo --help` now errors** with "cannot combine session name and --help" instead of silently passing `--help` to Pi. Run `pi --help` for Pi's own help.
|
|
28
|
+
|
|
29
|
+
### Deprecated
|
|
30
|
+
|
|
31
|
+
- **`pi-link list` and `pi-link resolve` subcommands.** Use `--list` / `--resolve` instead. Subcommands still work for one release with a stderr deprecation warning, then will be removed. The `--global` flag placement is more flexible in the deprecated `resolve` form than the canonical `--resolve` form: `pi-link resolve --global foo` is still accepted, while `pi-link --resolve --global foo` is an error (use `pi-link --resolve foo --global` or `--resolve=foo --global`).
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **Pi-bundled imports now declared as peer dependencies.** `package.json` adds `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`, and `typebox` as `peerDependencies` with `"*"` ranges, matching Pi's `docs/packages.md` convention for packages that import Pi-bundled modules. Previously only `ws` was declared, so consumers whose toolchain didn't auto-resolve modules through Pi's loader (e.g. some Docker setups) hit `ERR_MODULE_NOT_FOUND` on `typebox`. With modern npm/pnpm, peer deps auto-install alongside the package; older toolchains may need explicit `npm install`.
|
|
36
|
+
|
|
37
|
+
### Migration
|
|
38
|
+
|
|
39
|
+
All existing scripts and aliases continue to work — the deprecated subcommands print a stderr warning but produce identical output (and the new exit-code 2 on missing resolve). Scripts that depended on `pi-link resolve <name>` returning exit 0 for missing names need updating to handle 2. Most callers already treated empty stdout as "not found" and will be unaffected.
|
|
40
|
+
|
|
41
|
+
To silence the deprecation warning, switch to the flag form:
|
|
42
|
+
|
|
43
|
+
| Old | New |
|
|
44
|
+
| ------------------------------ | ------------------------------------------------------------------ |
|
|
45
|
+
| `pi-link list` | `pi-link --list` |
|
|
46
|
+
| `pi-link list -g` | `pi-link --list -g` |
|
|
47
|
+
| `pi-link resolve foo` | `pi-link --resolve foo` |
|
|
48
|
+
| `pi-link resolve foo -g` | `pi-link --resolve foo -g` |
|
|
49
|
+
| `pi-link resolve --global foo` | `pi-link --resolve foo --global` (order matters in canonical form) |
|
|
50
|
+
|
|
51
|
+
### Test coverage
|
|
52
|
+
|
|
53
|
+
40 automated cases in `test/cli-flags-test.mjs` covering: canonical forms (5), deprecation aliases (4), orphan-positional rejection (7), mode-selecting validation (11), help / unknown / managed-flag rejection (8), wrapper-vs-pi flag boundaries (4). Cases that exercise the launch path use a stubbed `pi` on PATH that records argv + `PI_LINK_NAME`. Run with `node test/cli-flags-test.mjs`.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 0.1.14 — 2026-05-04
|
|
10
58
|
|
|
11
59
|
### Added
|
|
12
60
|
|
|
@@ -31,7 +79,7 @@ This changelog is based on the git history from `2026-03-21` (initial commit) th
|
|
|
31
79
|
|
|
32
80
|
### Changed
|
|
33
81
|
|
|
34
|
-
- **TypeBox import migrated from `@sinclair/typebox` to `typebox`.** Pi 0.69.0
|
|
82
|
+
- **TypeBox import migrated from `@sinclair/typebox` to `typebox`.** Pi 0.69.0 migrated to typebox 1.x's bare-package name; `@sinclair/typebox` still resolves to the same module via Pi's legacy alias, so behavior is unchanged for our root `Type.*` imports. Aligns with Pi's preferred naming and avoids future churn if the alias is dropped. README's "Provided by Pi" table updated to match.
|
|
35
83
|
|
|
36
84
|
### Fixed
|
|
37
85
|
|
package/README.md
CHANGED
|
@@ -171,10 +171,10 @@ Lookup is **scoped to the current cwd by default**; pass `--global` (`-g`) to co
|
|
|
171
171
|
|
|
172
172
|
### Discovering sessions
|
|
173
173
|
|
|
174
|
-
`pi-link list` shows pi-link sessions in the current cwd; `pi-link list --global` (or `-g`) lists them across all directories. Sorted by last activity — starting a session with the same name it already has does not bump recency; only real activity (messages, tool calls, edits, name changes) does.
|
|
174
|
+
`pi-link --list` shows pi-link sessions in the current cwd; `pi-link --list --global` (or `-g`) lists them across all directories. Sorted by last activity — starting a session with the same name it already has does not bump recency; only real activity (messages, tool calls, edits, name changes) does.
|
|
175
175
|
|
|
176
176
|
```
|
|
177
|
-
$ pi-link list
|
|
177
|
+
$ pi-link --list
|
|
178
178
|
NAME MODIFIED MESSAGES ID
|
|
179
179
|
opus@pi-link 2m ago 4632 6332faab
|
|
180
180
|
gpt@pi-link 5m ago 1493 20d43841
|
|
@@ -185,7 +185,7 @@ Resume: pi-link <name>
|
|
|
185
185
|
With `--global`:
|
|
186
186
|
|
|
187
187
|
```
|
|
188
|
-
$ pi-link list --global
|
|
188
|
+
$ pi-link --list --global
|
|
189
189
|
NAME CWD MODIFIED MESSAGES ID
|
|
190
190
|
opus@pi-link ~/my-project 2m ago 4632 6332faab
|
|
191
191
|
gpt@pi-link ~/other-project 5m ago 1493 20d43841
|
|
@@ -195,9 +195,11 @@ Resume: pi-link <name>
|
|
|
195
195
|
|
|
196
196
|
`--global` adds a `CWD` column with `~` substituted for `$HOME`. Output is plain when piped (`NO_COLOR` honored).
|
|
197
197
|
|
|
198
|
-
`pi-link <name>` and `pi-link resolve <name>` follow the same scoping: local cwd by default, `--global` (or `-g`) widens. When `pi-link <name>` finds no local match but matches exist elsewhere, it warns and points at `--global` instead of silently jumping cwds.
|
|
198
|
+
`pi-link <name>` and `pi-link --resolve <name>` follow the same scoping: local cwd by default, `--global` (or `-g`) widens. When `pi-link <name>` finds no local match but matches exist elsewhere, it warns and points at `--global` instead of silently jumping cwds.
|
|
199
199
|
|
|
200
|
-
For scripting, `pi-link resolve <name>` prints just the session path (machine-readable, no other output).
|
|
200
|
+
For scripting, `pi-link --resolve <name>` prints just the session path (machine-readable, no other output). Exit codes: `0` on single match, `1` if ambiguous (multiple matches printed to stderr), `2` if not found.
|
|
201
|
+
|
|
202
|
+
> The legacy subcommand forms `pi-link list` and `pi-link resolve <name>` still work but print a stderr deprecation warning and will be removed in a future release. Switch to the flag forms above.
|
|
201
203
|
|
|
202
204
|
---
|
|
203
205
|
|
package/bin/pi-link.mjs
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
// Usage:
|
|
6
6
|
// pi-link <name> [--global|-g] [flags...]
|
|
7
7
|
// Resume or create a named session, connected to link.
|
|
8
|
-
// pi-link list [--global|-g]
|
|
9
|
-
// pi-link resolve <name> [--global|-g]
|
|
8
|
+
// pi-link --list [--global|-g] List pi-link sessions in current cwd (or everywhere).
|
|
9
|
+
// pi-link --resolve <name> [--global|-g]
|
|
10
10
|
// Print just the session path (machine-readable).
|
|
11
|
+
//
|
|
12
|
+
// Deprecated subcommand forms `pi-link list` / `pi-link resolve <name>` still
|
|
13
|
+
// work for one release with a stderr warning.
|
|
11
14
|
|
|
12
15
|
import { readdir, stat } from "fs/promises";
|
|
13
16
|
import { createReadStream, existsSync, readFileSync } from "fs";
|
|
@@ -232,7 +235,7 @@ function renderTable(rows, columns) {
|
|
|
232
235
|
|
|
233
236
|
// ── CLI ────────────────────────────────────────────────────────────────────
|
|
234
237
|
|
|
235
|
-
const
|
|
238
|
+
const rawArgs = process.argv.slice(2);
|
|
236
239
|
|
|
237
240
|
// Reject pi-link flags renamed in 0.1.12 with a clear pointer to the new name.
|
|
238
241
|
// Same intent as `rejectManagedFlag` (specific message > generic "Unknown argument")
|
|
@@ -247,9 +250,9 @@ function rejectRenamedFlag(token) {
|
|
|
247
250
|
|
|
248
251
|
// Reject Pi flags that pi-link manages, plus --link-name (which exists at the
|
|
249
252
|
// `pi` level for link-only naming, but the wrapper's combined-mode contract
|
|
250
|
-
// conflicts with it).
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
+
// conflicts with it). Called from Phase 6 (mode entry) and Phase 7 (after
|
|
254
|
+
// launcher name), so it fires on both `pi-link --session foo` and
|
|
255
|
+
// `pi-link foo --session bar` with the friendly message.
|
|
253
256
|
function rejectManagedFlag(token) {
|
|
254
257
|
const key = token.split("=")[0];
|
|
255
258
|
if (key === "--link-name") {
|
|
@@ -276,25 +279,243 @@ function printCandidates(name, matches) {
|
|
|
276
279
|
process.exit(1);
|
|
277
280
|
}
|
|
278
281
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
282
|
+
function fail(msg) {
|
|
283
|
+
console.error(`Error: ${msg}`);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function printHelp() {
|
|
288
|
+
console.error("Usage: pi-link <name> [--global|-g] [pi flags...]");
|
|
289
|
+
console.error(" pi-link --list [--global|-g]");
|
|
290
|
+
console.error(" pi-link --resolve <name> [--global|-g]");
|
|
291
|
+
console.error("");
|
|
292
|
+
console.error("By default, name lookup is scoped to the current cwd.");
|
|
293
|
+
console.error("--global / -g widens the search to sessions in any cwd.");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function printDeprecationWarning(form) {
|
|
297
|
+
const canonical = form === "list" ? "--list" : "--resolve";
|
|
298
|
+
console.error(
|
|
299
|
+
`Warning: 'pi-link ${form}' is deprecated. Use 'pi-link ${canonical}' instead. ` +
|
|
300
|
+
`(Subcommand form will be removed in a future release.)`,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function describeMode(mode) {
|
|
305
|
+
switch (mode) {
|
|
306
|
+
case "help": return "--help";
|
|
307
|
+
case "list": return "--list";
|
|
308
|
+
case "resolve": return "--resolve";
|
|
309
|
+
case "launcher": return "session name";
|
|
310
|
+
default: return mode;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── Parser ─────────────────────────────────────────────────────────────────
|
|
315
|
+
//
|
|
316
|
+
// Single sequential pass populates `state`; dispatcher reads it. Phases (per
|
|
317
|
+
// PLAN-cli-flags.md):
|
|
318
|
+
// 1. Renamed-flag rejection (always-on for --all/-a)
|
|
319
|
+
// 2. Global flags (--global, --help, --)
|
|
320
|
+
// 3. Mode-selecting flags (--list, --resolve, --resolve=<name>)
|
|
321
|
+
// 4. Deprecated subcommands (list, resolve <name>) — only at mode-null position
|
|
322
|
+
// 5. Mode-specific extra-token rejection (with deprecated-resolve leniency)
|
|
323
|
+
// 6. Launcher mode entry (mode null + bare positional)
|
|
324
|
+
// 7. Launcher passthrough (mode launcher) with orphan-positional rejection
|
|
325
|
+
|
|
326
|
+
const state = {
|
|
327
|
+
mode: null, // null | "help" | "list" | "resolve" | "launcher"
|
|
328
|
+
resolveName: null,
|
|
329
|
+
launcherName: null,
|
|
330
|
+
global: false,
|
|
331
|
+
piPassthrough: [],
|
|
332
|
+
deprecated: null, // null | "list" | "resolve"
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
function setMode(mode) {
|
|
336
|
+
if (state.mode !== null && state.mode !== mode) {
|
|
337
|
+
fail(`cannot combine ${describeMode(state.mode)} and ${describeMode(mode)}`);
|
|
338
|
+
}
|
|
339
|
+
state.mode = mode;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let lastWasFlag = false;
|
|
343
|
+
|
|
344
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
345
|
+
const a = rawArgs[i];
|
|
346
|
+
|
|
347
|
+
// Phase 1: renamed-flag rejection.
|
|
348
|
+
rejectRenamedFlag(a);
|
|
349
|
+
|
|
350
|
+
// Phase 2: global flags / scope-affecting tokens.
|
|
351
|
+
if (a === "--global" || a === "-g") {
|
|
352
|
+
state.global = true;
|
|
353
|
+
lastWasFlag = false;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (a === "--help" || a === "-h") {
|
|
357
|
+
setMode("help"); // errors if combined with another mode
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (a === "--") {
|
|
361
|
+
// `--` only meaningful in launcher mode (separates pi flags from positionals).
|
|
362
|
+
if (state.mode !== "launcher") {
|
|
363
|
+
fail(`-- is only valid after a session name`);
|
|
364
|
+
}
|
|
365
|
+
for (let j = i + 1; j < rawArgs.length; j++) {
|
|
366
|
+
state.piPassthrough.push(rawArgs[j]);
|
|
367
|
+
}
|
|
368
|
+
i = rawArgs.length;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Phase 3: mode-selecting flags.
|
|
373
|
+
if (a === "--list") {
|
|
374
|
+
setMode("list");
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (a.startsWith("--resolve=")) {
|
|
378
|
+
setMode("resolve");
|
|
379
|
+
if (state.resolveName !== null) fail(`--resolve specified more than once`);
|
|
380
|
+
state.resolveName = a.slice("--resolve=".length);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (a === "--resolve") {
|
|
384
|
+
setMode("resolve");
|
|
385
|
+
if (state.resolveName !== null) fail(`--resolve specified more than once`);
|
|
386
|
+
const next = rawArgs[i + 1];
|
|
387
|
+
if (next === undefined || next.startsWith("-")) {
|
|
388
|
+
fail(`--resolve requires a name argument.\n Usage: pi-link --resolve <name> [--global|-g]`);
|
|
389
|
+
}
|
|
390
|
+
state.resolveName = next;
|
|
391
|
+
i++; // consume the value
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Phase 4: deprecated subcommands (only at mode-null position).
|
|
396
|
+
if (state.mode === null && (a === "list" || a === "resolve")) {
|
|
397
|
+
state.deprecated = a;
|
|
398
|
+
if (a === "list") {
|
|
399
|
+
setMode("list");
|
|
400
|
+
} else {
|
|
401
|
+
setMode("resolve");
|
|
402
|
+
const next = rawArgs[i + 1];
|
|
403
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
404
|
+
state.resolveName = next;
|
|
405
|
+
i++;
|
|
406
|
+
}
|
|
407
|
+
// else: leave null; Phase 5 leniency or post-parse validation handles it.
|
|
408
|
+
}
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Phase 5: mode-specific extra-token rejection.
|
|
413
|
+
if (state.mode === "help") {
|
|
414
|
+
fail(`--help does not accept arguments: ${a}`);
|
|
415
|
+
}
|
|
416
|
+
if (state.mode === "list") {
|
|
417
|
+
fail(`--list does not accept argument: ${a}\n Usage: pi-link --list [--global|-g]`);
|
|
418
|
+
}
|
|
419
|
+
if (state.mode === "resolve") {
|
|
420
|
+
// Deprecated-form leniency: `pi-link resolve --global foo` was order-independent;
|
|
421
|
+
// if we entered via deprecated path and haven't bound a name yet, take this.
|
|
422
|
+
if (
|
|
423
|
+
state.deprecated === "resolve" &&
|
|
424
|
+
state.resolveName === null &&
|
|
425
|
+
!a.startsWith("-")
|
|
426
|
+
) {
|
|
427
|
+
state.resolveName = a;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
fail(`--resolve accepts exactly one name; got extra: ${a}`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Phase 6: launcher mode entry. state.mode === null here, no name set yet.
|
|
434
|
+
// (lastWasFlag is still false here — only Phase 7 sets it, and Phase 7 requires launcher mode.)
|
|
435
|
+
if (state.mode === null) {
|
|
436
|
+
rejectManagedFlag(a);
|
|
437
|
+
if (a.startsWith("-")) {
|
|
438
|
+
fail(`Unknown argument: ${a}\n Usage: pi-link <name> [--global|-g] [pi flags...]`);
|
|
288
439
|
}
|
|
440
|
+
state.mode = "launcher";
|
|
441
|
+
state.launcherName = a;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Phase 7: launcher mode, name set. Tokens go to passthrough or get rejected.
|
|
446
|
+
rejectManagedFlag(a);
|
|
447
|
+
if (a.startsWith("-")) {
|
|
448
|
+
state.piPassthrough.push(a);
|
|
449
|
+
// `--key=value` is self-contained; only `--key` (without `=`) might consume
|
|
450
|
+
// the next token as its value.
|
|
451
|
+
lastWasFlag = !a.includes("=");
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
// Bare positional: allowed only if it follows a flag without `=`.
|
|
455
|
+
if (lastWasFlag) {
|
|
456
|
+
state.piPassthrough.push(a);
|
|
457
|
+
lastWasFlag = false;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
fail(`Unexpected argument after session name: ${a}\n Use -- to pass positional arguments to pi.`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ── Post-parse validation ──────────────────────────────────────────────────
|
|
464
|
+
|
|
465
|
+
if (state.mode === "resolve") {
|
|
466
|
+
if (state.resolveName === null) {
|
|
467
|
+
fail(`--resolve requires a name argument.\n Usage: pi-link --resolve <name> [--global|-g]`);
|
|
289
468
|
}
|
|
469
|
+
const normalized = state.resolveName.trim().replace(/\s+/g, " ");
|
|
470
|
+
if (!normalized) {
|
|
471
|
+
fail(`--resolve requires a non-empty name argument.\n Usage: pi-link --resolve <name> [--global|-g]`);
|
|
472
|
+
}
|
|
473
|
+
state.resolveName = normalized;
|
|
474
|
+
}
|
|
475
|
+
if (state.mode === "launcher") {
|
|
476
|
+
const normalized = state.launcherName.trim().replace(/\s+/g, " ");
|
|
477
|
+
if (!normalized) {
|
|
478
|
+
fail(`session name cannot be empty.\n Usage: pi-link <name> [--global|-g] [pi flags...]`);
|
|
479
|
+
}
|
|
480
|
+
state.launcherName = normalized;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (state.deprecated) {
|
|
484
|
+
printDeprecationWarning(state.deprecated);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ── Dispatch ───────────────────────────────────────────────────────────────
|
|
488
|
+
|
|
489
|
+
switch (state.mode) {
|
|
490
|
+
case null:
|
|
491
|
+
case "help":
|
|
492
|
+
printHelp();
|
|
493
|
+
process.exit(0);
|
|
494
|
+
break; // unreachable; present to satisfy no-fallthrough lints
|
|
495
|
+
case "list":
|
|
496
|
+
await runList(state);
|
|
497
|
+
break;
|
|
498
|
+
case "resolve":
|
|
499
|
+
await runResolve(state);
|
|
500
|
+
break;
|
|
501
|
+
case "launcher":
|
|
502
|
+
await runLauncher(state);
|
|
503
|
+
break;
|
|
504
|
+
default:
|
|
505
|
+
fail(`internal error: unknown mode ${state.mode}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// ── Mode handlers ──────────────────────────────────────────────────────────
|
|
509
|
+
|
|
510
|
+
async function runList(state) {
|
|
290
511
|
const { dir, isCustom } = resolveSessionDir(process.cwd(), resolveAgentDir());
|
|
291
|
-
const sessions = await listSessions({ all: global, dir, isCustom });
|
|
512
|
+
const sessions = await listSessions({ all: state.global, dir, isCustom });
|
|
292
513
|
if (sessions.length === 0) {
|
|
293
|
-
console.log(global ? "No pi-link sessions found." : "No pi-link sessions found in this cwd.");
|
|
514
|
+
console.log(state.global ? "No pi-link sessions found." : "No pi-link sessions found in this cwd.");
|
|
294
515
|
console.log("Start one: pi-link <name>");
|
|
295
|
-
|
|
516
|
+
return;
|
|
296
517
|
}
|
|
297
|
-
const columns = global
|
|
518
|
+
const columns = state.global
|
|
298
519
|
? [
|
|
299
520
|
{ header: "NAME", get: (s) => s.name },
|
|
300
521
|
{ header: "CWD", get: (s) => displayPath(s.cwd) },
|
|
@@ -313,71 +534,33 @@ if (command === "list") {
|
|
|
313
534
|
console.log("");
|
|
314
535
|
console.log(dim("Resume: pi-link <name>"));
|
|
315
536
|
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
rejectRenamedFlag(a);
|
|
321
|
-
if (a === "--global" || a === "-g") global = true;
|
|
322
|
-
else if (a.startsWith("-")) {
|
|
323
|
-
console.error(`Unknown argument: ${a}`);
|
|
324
|
-
console.error("Usage: pi-link resolve <name> [--global|-g]");
|
|
325
|
-
process.exit(1);
|
|
326
|
-
} else positional.push(a);
|
|
327
|
-
}
|
|
328
|
-
if (positional.length !== 1) {
|
|
329
|
-
console.error("Usage: pi-link resolve <name> [--global|-g]");
|
|
330
|
-
process.exit(1);
|
|
331
|
-
}
|
|
332
|
-
const name = positional[0].trim().replace(/\s+/g, " ");
|
|
333
|
-
if (!name) {
|
|
334
|
-
console.error("Usage: pi-link resolve <name> [--global|-g]");
|
|
335
|
-
process.exit(1);
|
|
336
|
-
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function runResolve(state) {
|
|
540
|
+
const name = state.resolveName; // already normalized
|
|
337
541
|
const { dir, isCustom } = resolveSessionDir(process.cwd(), resolveAgentDir());
|
|
338
542
|
const { local, all } = await findSessionsByName(name, dir, isCustom);
|
|
339
|
-
const matches = global ? all : local;
|
|
543
|
+
const matches = state.global ? all : local;
|
|
340
544
|
if (matches.length === 1) {
|
|
341
545
|
process.stdout.write(matches[0].path);
|
|
342
|
-
|
|
343
|
-
printCandidates(name, matches);
|
|
546
|
+
return; // exit 0
|
|
344
547
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// Walk every token in one pass: pull out --global wherever it appears, treat
|
|
348
|
-
// the first non-flag token as the name, reject managed flags, forward the rest.
|
|
349
|
-
let global = false;
|
|
350
|
-
let name = null;
|
|
351
|
-
const piPassthrough = [];
|
|
352
|
-
for (const token of [command, ...args]) {
|
|
353
|
-
rejectRenamedFlag(token);
|
|
354
|
-
if (token === "--global" || token === "-g") { global = true; continue; }
|
|
355
|
-
rejectManagedFlag(token);
|
|
356
|
-
if (name === null) {
|
|
357
|
-
// Before the name is set, an unknown leading flag is almost certainly a
|
|
358
|
-
// user mistake (`pi-link --model gpt-4 foo`) — don't silently treat it
|
|
359
|
-
// as a session name. After the name is set, anything goes (forwarded to Pi).
|
|
360
|
-
if (token.startsWith("-")) {
|
|
361
|
-
console.error(`Unknown argument before name: ${token}`);
|
|
362
|
-
console.error("Usage: pi-link <name> [--global|-g] [pi flags...]");
|
|
363
|
-
process.exit(1);
|
|
364
|
-
}
|
|
365
|
-
name = token;
|
|
366
|
-
} else piPassthrough.push(token);
|
|
367
|
-
}
|
|
368
|
-
if (!name) {
|
|
369
|
-
console.error("Usage: pi-link <name> [--global|-g] [pi flags...]");
|
|
370
|
-
process.exit(1);
|
|
548
|
+
if (matches.length > 1) {
|
|
549
|
+
printCandidates(name, matches); // exits 1
|
|
371
550
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
551
|
+
// matches.length === 0 → not found; exit 2 to distinguish from ambiguous.
|
|
552
|
+
console.error(`No session named "${name}" found${state.global ? "" : " in this cwd"}.`);
|
|
553
|
+
if (!state.global && all.length > 0) {
|
|
554
|
+
console.error(`(${all.length} match${all.length === 1 ? "" : "es"} in other cwds — try --global to consider ${all.length === 1 ? "it" : "them"}.)`);
|
|
376
555
|
}
|
|
556
|
+
process.exit(2);
|
|
557
|
+
}
|
|
377
558
|
|
|
559
|
+
async function runLauncher(state) {
|
|
560
|
+
const name = state.launcherName; // already normalized
|
|
378
561
|
const { dir, isCustom } = resolveSessionDir(process.cwd(), resolveAgentDir());
|
|
379
562
|
const { local, all } = await findSessionsByName(name, dir, isCustom);
|
|
380
|
-
const matches = global ? all : local;
|
|
563
|
+
const matches = state.global ? all : local;
|
|
381
564
|
if (matches.length > 1) {
|
|
382
565
|
printCandidates(name, matches);
|
|
383
566
|
}
|
|
@@ -387,13 +570,13 @@ if (command === "list") {
|
|
|
387
570
|
console.error(`Resuming session: ${matches[0].path}`);
|
|
388
571
|
piArgs.push("--session", matches[0].path);
|
|
389
572
|
} else {
|
|
390
|
-
if (!global && all.length > local.length) {
|
|
573
|
+
if (!state.global && all.length > local.length) {
|
|
391
574
|
const elsewhere = all.length - local.length;
|
|
392
575
|
console.error(`No "${name}" in this cwd. (${elsewhere} match${elsewhere === 1 ? "" : "es"} in other cwds — use --global to consider ${elsewhere === 1 ? "it" : "them"}.)`);
|
|
393
576
|
}
|
|
394
577
|
console.error("Starting new session.");
|
|
395
578
|
}
|
|
396
|
-
piArgs.push("--link", ...piPassthrough);
|
|
579
|
+
piArgs.push("--link", ...state.piPassthrough);
|
|
397
580
|
|
|
398
581
|
const isWin = process.platform === "win32";
|
|
399
582
|
const cmd = isWin ? "cmd.exe" : "pi";
|
|
@@ -413,12 +596,4 @@ if (command === "list") {
|
|
|
413
596
|
console.error(`Failed to start pi: ${err.message}`);
|
|
414
597
|
process.exit(1);
|
|
415
598
|
});
|
|
416
|
-
} else {
|
|
417
|
-
console.error("Usage: pi-link <name> [--global|-g] [pi flags...]");
|
|
418
|
-
console.error(" pi-link list [--global|-g]");
|
|
419
|
-
console.error(" pi-link resolve <name> [--global|-g]");
|
|
420
|
-
console.error("");
|
|
421
|
-
console.error("By default, name lookup is scoped to the current cwd.");
|
|
422
|
-
console.error("--global / -g widens the search to sessions in any cwd.");
|
|
423
|
-
process.exit(0);
|
|
424
599
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-link",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15-beta.0",
|
|
4
4
|
"description": "WebSocket-based inter-terminal communication for Pi. Connect multiple Pi terminals over a local link network.",
|
|
5
5
|
"author": "alvivar",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"ws": "^8.20.0"
|
|
24
24
|
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
27
|
+
"@mariozechner/pi-tui": "*",
|
|
28
|
+
"typebox": "*"
|
|
29
|
+
},
|
|
25
30
|
"devDependencies": {
|
|
26
31
|
"@types/ws": "^8.18.1"
|
|
27
32
|
},
|