pi-link 0.1.4 → 0.1.6

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 CHANGED
@@ -2,7 +2,31 @@
2
2
 
3
3
  All notable changes to pi-link are documented here.
4
4
 
5
- This changelog is based on the git history from `2026-03-21` (initial commit as `pi-mesh`) through `2026-03-30` (current). Versions correspond to npm publishes.
5
+ This changelog is based on the git history from `2026-03-21` through `2026-04-03` (current). Versions correspond to npm publishes.
6
+
7
+ ---
8
+
9
+ ## 0.1.6 — 2026-04-03
10
+
11
+ ### Added
12
+
13
+ - **Persistent connection intent.** `/link-connect` and `/link-disconnect` now save their state to the session via `pi.appendEntry("link-active", ...)`. On `session_start`, the saved preference is checked before falling back to `--link`. Connect once and it stays connected across session resumes without needing the flag.
14
+
15
+ ### Removed
16
+
17
+ - **`cwd_update` message type.** Working directories are now only reported on connect (via `register`/`welcome`), not mid-session. Protocol returns to 9 message types.
18
+
19
+ - **`session_switch` handler.** Removed the session-switch logic that handled name and cwd changes on `/resume`. Simplifies the lifecycle.
20
+
21
+ ---
22
+
23
+ ## 0.1.5 — 2026-04-02
24
+
25
+ ### Added
26
+
27
+ - **Working directory sharing.** Each terminal reports its `cwd` on connect and on session switch. New `cwd_update` protocol message (10th message type) broadcasts mid-session directory changes. `link_list` and `/link` now show per-terminal working directories — full absolute paths in tool output, `~/…` shortened in the TUI. Agents can use this to choose the right target, use explicit paths when terminals differ, and catch wrong-project mistakes early.
28
+
29
+ - **Header comment cleanup.** Simplified the top-of-file doc comment — removed feature bullet list and install instructions in favor of a concise summary.
6
30
 
7
31
  ---
8
32
 
package/README.md CHANGED
@@ -85,7 +85,9 @@ Here's a concrete example of two terminals collaborating. Open two separate `pi
85
85
  > /link
86
86
  ⚡ Link: builder (hub) · 2 online
87
87
  builder: idle (5s)
88
+ cwd: ~/my-project
88
89
  researcher: idle (12s)
90
+ cwd: ~/my-project
89
91
  ```
90
92
 
91
93
  **Terminal 2** — rename it too:
@@ -142,11 +144,11 @@ The extension registers three tools that the LLM can invoke during agent runs.
142
144
 
143
145
  ### Which tool should I use?
144
146
 
145
- | Tool | Behavior | Returns |
146
- | ------------- | ---------------------------------------------------- | ---------------------------------------- |
147
- | `link_send` | Send a message; optionally trigger the remote LLM | Send/delivery status only |
148
- | `link_prompt` | Run a prompt on a remote terminal and wait for reply | The remote terminal's assistant response |
149
- | `link_list` | List currently connected terminals | Terminal directory with roles and status |
147
+ | Tool | Behavior | Returns |
148
+ | ------------- | ---------------------------------------------------- | ----------------------------------------- |
149
+ | `link_send` | Send a message; optionally trigger the remote LLM | Send/delivery status only |
150
+ | `link_prompt` | Run a prompt on a remote terminal and wait for reply | The remote terminal's assistant response |
151
+ | `link_list` | List currently connected terminals | Terminal list with roles, status, and cwd |
150
152
 
151
153
  **If you need the other terminal's answer back, use `link_prompt`.** Use `link_send` to notify or steer without waiting.
152
154
 
@@ -187,7 +189,9 @@ Send a prompt to a remote terminal and **wait** for the LLM's response (synchron
187
189
 
188
190
  ### `link_list`
189
191
 
190
- Lists all connected terminals with role info, live agent status, and self-identification. Takes no parameters.
192
+ Lists all connected terminals with role info, live agent status, working directory, and self-identification. Takes no parameters.
193
+
194
+ Each terminal reports its current working directory on connect. `link_list` shows the full absolute path so agents can choose the right target, use explicit paths when terminals differ, and catch wrong-project mistakes early.
191
195
 
192
196
  Each terminal's status is derived automatically from Pi lifecycle events — agents can't set it manually. Three states:
193
197
 
@@ -199,13 +203,18 @@ Each terminal's status is derived automatically from Pi lifecycle events — age
199
203
 
200
204
  Durations are computed at render time from a `since` timestamp — no timer traffic over the wire. Terminals that just joined with no status data yet render as blank, not fake idle.
201
205
 
206
+ Working directories use full absolute paths in tool output. In the TUI (`/link`), paths are shortened to `~/...` when possible to keep the display compact.
207
+
202
208
  **Example output:**
203
209
 
204
210
  ```
205
- link (hub) 3 terminal(s)
206
- builder@pi-link (you) thinking (3s)
207
- reviewer@pi-link idle (45s)
208
- docs@pi-link tool:read (2s)
211
+ Connected terminals:
212
+ opus@pi-link (you) idle (12s)
213
+ cwd: C:\Users\andre\.pi
214
+ gpt@pi-link thinking (3s)
215
+ cwd: C:\Users\andre\.pi
216
+ • docs@pi-link idle (1m)
217
+ cwd: C:\Users\andre\.pi
209
218
  ```
210
219
 
211
220
  ---
@@ -214,7 +223,7 @@ link (hub) 3 terminal(s)
214
223
 
215
224
  | Command | Purpose |
216
225
  | ----------------------- | ------------------------------------------------------------------------------------------------------------------------ |
217
- | `/link` | Show link status (name, role, online count, agent status per terminal) |
226
+ | `/link` | Show link status (name, role, online count, agent status, and cwd per terminal) |
218
227
  | `/link-name [name]` | Rename and save as this session's preferred link name. With no argument, adopts the Pi session name. Restored on resume. |
219
228
  | `/link-broadcast <msg>` | Broadcast a chat message to all other terminals |
220
229
  | `/link-connect` | Connect to Pi Link (works anytime, with or without `--link`) |
@@ -226,8 +235,11 @@ link (hub) 3 terminal(s)
226
235
  > /link
227
236
  ⚡ Link: builder (hub) · 3 online
228
237
  builder: idle (12s)
238
+ cwd: ~/my-project
229
239
  worker-1: thinking (3s)
240
+ cwd: ~/my-project
230
241
  worker-2: tool:bash (5s)
242
+ cwd: ~/other-project
231
243
 
232
244
  > /link-name orchestrator
233
245
  ✓ Renamed to "orchestrator"
@@ -385,19 +397,19 @@ The `pi.extensions` field tells Pi which files to load as extensions. Here it po
385
397
 
386
398
  ### Protocol
387
399
 
388
- The wire protocol consists of **9 message types**, all serialized as JSON over WebSocket frames:
400
+ The wire protocol consists of **9 message types**, all serialized as JSON over WebSocket frames. Cwd-related fields are optional for backward compatibility.
389
401
 
390
- | Type | Direction | Purpose |
391
- | ----------------- | --------------- | ------------------------------------------------------- |
392
- | `register` | Client → Hub | First message after connecting; requests a name |
393
- | `welcome` | Hub → Client | Confirms assigned name, terminal list + status snapshot |
394
- | `terminal_joined` | Hub → All | Broadcast when a terminal joins |
395
- | `terminal_left` | Hub → All | Broadcast when a terminal disconnects |
396
- | `chat` | Any → Any/All | Fire-and-forget message; optionally triggers LLM turn |
397
- | `prompt_request` | Any → Any | Request a remote terminal to execute a prompt |
398
- | `prompt_response` | Any → Any | Response carrying the remote prompt result |
399
- | `status_update` | Any → Hub → All | Terminal broadcasts its agent status change |
400
- | `error` | Hub → Client | Error notification |
402
+ | Type | Direction | Purpose |
403
+ | ----------------- | --------------- | ----------------------------------------------------------------------- |
404
+ | `register` | Client → Hub | First message after connecting; requests a name, optionally reports cwd |
405
+ | `welcome` | Hub → Client | Confirms assigned name, terminal list + status/cwd snapshots |
406
+ | `terminal_joined` | Hub → All | Broadcast when a terminal joins; may include cwd |
407
+ | `terminal_left` | Hub → All | Broadcast when a terminal disconnects |
408
+ | `chat` | Any → Any/All | Fire-and-forget message; optionally triggers LLM turn |
409
+ | `prompt_request` | Any → Any | Request a remote terminal to execute a prompt |
410
+ | `prompt_response` | Any → Any | Response carrying the remote prompt result |
411
+ | `status_update` | Any → Hub → All | Terminal broadcasts its agent status change |
412
+ | `error` | Hub → Client | Error notification |
401
413
 
402
414
  ### Message Flow Examples
403
415
 
@@ -406,16 +418,17 @@ The wire protocol consists of **9 message types**, all serialized as JSON over W
406
418
  ```
407
419
  Client Hub
408
420
  | |
409
- | register {name:"builder"} |
421
+ | register {name:"builder", |
422
+ | cwd:"C:\\Users\\..."} |
410
423
  |---------------------------->|
411
424
  | |
412
425
  | welcome {name, terminals, |
413
- | statuses} |
426
+ | statuses, cwds} |
414
427
  |<----------------------------|
415
428
  | |
416
429
  ```
417
430
 
418
- Hub then broadcasts `terminal_joined` to the other connected terminals. The `welcome` message includes a status snapshot for all connected terminals (fields omitted above for brevity).
431
+ Hub then broadcasts `terminal_joined` to the other connected terminals. The `welcome` message includes status and cwd snapshots for all connected terminals (fields omitted above for brevity). `terminal_joined` also includes the new terminal's optional cwd.
419
432
 
420
433
  **Sending a chat message:**
421
434
 
@@ -468,6 +481,7 @@ Default names are random 4-character hex IDs: `t-a1b2`, `t-c3d4`, etc.
468
481
  | `agentRunning` | `boolean` | Whether an agent run is active; blocks incoming remote prompts |
469
482
  | `activeToolName` | `string \| null` | Name of the currently executing tool (drives `tool:<name>` status) |
470
483
  | `stateSince` | `number` | Timestamp of last status change (used for duration display) |
484
+ | `currentCwd` | `string` | Current working directory reported to peers on connect |
471
485
  | `manuallyDisconnected` | `boolean` | Set by `/link-disconnect`; suppresses auto-reconnect |
472
486
  | `pendingRemotePrompt` | `object \| null` | Tracks the single in-flight remote prompt execution |
473
487
  | `pendingPromptResponses` | `Map` | Outstanding prompt RPCs awaiting responses (includes inactivity + ceiling timers per entry) |
package/index.ts CHANGED
@@ -2,21 +2,12 @@
2
2
  * Pi Link — WebSocket-based inter-terminal communication
3
3
  *
4
4
  * Connects multiple Pi terminals over a local WebSocket link.
5
- * The first terminal becomes the hub (server); others join as clients.
6
- * If the hub exits, a surviving terminal promotes itself automatically.
5
+ * Opt-in via --link flag or /link-connect command.
6
+ * First terminal to connect becomes the hub; others join as clients.
7
+ * Hub loss triggers automatic promotion of a surviving client.
7
8
  *
8
- * Features:
9
- * - Auto-discovery: try to connect → fall back to becoming the hub
10
- * - Named terminals with uniqueness enforcement
11
- * - LLM tools: link_send (chat), link_prompt (remote prompt + response), link_list
12
- * - Commands: /link, /link-name, /link-broadcast, /link-connect, /link-disconnect
13
- * - Custom message renderer for incoming link messages
14
- * - Auto-reconnect with hub promotion on disconnect
15
- *
16
- * Install:
17
- * cd ~/.pi/agent/extensions/pi-link && npm install
18
- *
19
- * Then just start two or more `pi` terminals — they discover each other.
9
+ * Tools: link_send, link_prompt, link_list
10
+ * Commands: /link, /link-name, /link-broadcast, /link-connect, /link-disconnect
20
11
  */
21
12
 
22
13
  import type {
@@ -26,6 +17,7 @@ import type {
26
17
  import { Text } from "@mariozechner/pi-tui";
27
18
  import { Type } from "@sinclair/typebox";
28
19
  import * as crypto from "node:crypto";
20
+ import * as os from "node:os";
29
21
 
30
22
  import { WebSocket, WebSocketServer } from "ws";
31
23
 
@@ -42,17 +34,20 @@ const KEEPALIVE_INTERVAL_MS = 30_000;
42
34
  interface RegisterMsg {
43
35
  type: "register";
44
36
  name: string;
37
+ cwd?: string;
45
38
  }
46
39
  interface WelcomeMsg {
47
40
  type: "welcome";
48
41
  name: string;
49
42
  terminals: string[];
50
43
  statuses?: Record<string, LinkStatus>;
44
+ cwds?: Record<string, string>;
51
45
  }
52
46
  interface TerminalJoinedMsg {
53
47
  type: "terminal_joined";
54
48
  name: string;
55
49
  terminals: string[];
50
+ cwd?: string;
56
51
  }
57
52
  interface TerminalLeftMsg {
58
53
  type: "terminal_left";
@@ -134,11 +129,14 @@ export default function (pi: ExtensionAPI) {
134
129
  let lastPushedKind: string | null = null;
135
130
  let lastPushedTool: string | null = null;
136
131
  const terminalStatuses = new Map<string, LinkStatus>(); // other terminals
132
+ let currentCwd = "";
133
+ const terminalCwds = new Map<string, string>(); // other terminals' cwds
137
134
 
138
135
  // Hub state
139
136
  let wss: WebSocketServer | null = null;
140
137
  const hubClients = new Map<WebSocket, string>(); // ws → terminal name
141
138
  const hubTerminalStatuses = new Map<string, LinkStatus>(); // hub-authoritative
139
+ const hubTerminalCwds = new Map<string, string>(); // hub-authoritative (excludes self)
142
140
 
143
141
  // Client state
144
142
  let ws: WebSocket | null = null;
@@ -221,6 +219,35 @@ export default function (pi: ExtensionAPI) {
221
219
  return map.get(name) ?? null;
222
220
  }
223
221
 
222
+ function getCwdFor(name: string): string | null {
223
+ if (name === terminalName) return currentCwd || null;
224
+ if (role === "hub") return hubTerminalCwds.get(name) ?? null;
225
+ return terminalCwds.get(name) ?? null;
226
+ }
227
+
228
+ function shortenPath(cwd: string): string {
229
+ const home = os.homedir().replace(/\\/g, "/");
230
+ const normalized = cwd.replace(/\\/g, "/");
231
+ if (normalized === home) return "~";
232
+ if (normalized.startsWith(home + "/"))
233
+ return "~" + normalized.slice(home.length);
234
+ return normalized;
235
+ }
236
+
237
+ // ── Connection intent ──────────────────────────────────────────────────
238
+
239
+ function shouldConnect(_ctx: ExtensionContext): boolean {
240
+ const saved = _ctx.sessionManager
241
+ .getEntries()
242
+ .filter(
243
+ (e: { type: string; customType?: string }) =>
244
+ e.type === "custom" && e.customType === "link-active",
245
+ )
246
+ .pop() as { data?: { active?: boolean } } | undefined;
247
+ if (saved?.data?.active !== undefined) return saved.data.active;
248
+ return pi.getFlag("link") === true;
249
+ }
250
+
224
251
  // ── Pending prompt helpers ───────────────────────────────────────────────
225
252
 
226
253
  function cleanupPending(requestId: string) {
@@ -365,11 +392,17 @@ export default function (pi: ExtensionAPI) {
365
392
  terminalName = msg.name;
366
393
  connectedTerminals = msg.terminals;
367
394
  terminalStatuses.clear();
395
+ terminalCwds.clear();
368
396
  if (msg.statuses) {
369
397
  for (const [name, status] of Object.entries(msg.statuses)) {
370
398
  terminalStatuses.set(name, status);
371
399
  }
372
400
  }
401
+ if (msg.cwds) {
402
+ for (const [name, cwd] of Object.entries(msg.cwds)) {
403
+ terminalCwds.set(name, cwd);
404
+ }
405
+ }
373
406
  updateStatus();
374
407
  ctx?.ui.notify(
375
408
  `Joined link as "${terminalName}" (${connectedTerminals.length} online)`,
@@ -378,9 +411,10 @@ export default function (pi: ExtensionAPI) {
378
411
  pushStatus(true);
379
412
  break;
380
413
 
381
- // ── Directory updates ──
414
+ // ── Membership updates ──
382
415
  case "terminal_joined":
383
416
  connectedTerminals = msg.terminals;
417
+ if (role !== "hub" && msg.cwd) terminalCwds.set(msg.name, msg.cwd);
384
418
  updateStatus();
385
419
  ctx?.ui.notify(`"${msg.name}" joined the link`, "info");
386
420
  break;
@@ -388,6 +422,7 @@ export default function (pi: ExtensionAPI) {
388
422
  case "terminal_left":
389
423
  connectedTerminals = msg.terminals;
390
424
  terminalStatuses.delete(msg.name);
425
+ if (role !== "hub") terminalCwds.delete(msg.name);
391
426
  // Fail any pending prompts to the departed terminal immediately
392
427
  for (const [id, pending] of pendingPromptResponses) {
393
428
  if (pending.targetName === msg.name) {
@@ -488,30 +523,38 @@ export default function (pi: ExtensionAPI) {
488
523
  if (msg.type === "register") {
489
524
  clientName = uniqueName(msg.name);
490
525
  hubClients.set(clientWs, clientName);
526
+ if (msg.cwd) hubTerminalCwds.set(clientName, msg.cwd);
491
527
  const list = terminalList();
492
528
  connectedTerminals = list;
493
529
  updateStatus();
494
530
 
495
- // Confirm to the new client (include status snapshot)
531
+ // Confirm to the new client (include status + cwd snapshots)
496
532
  const statuses: Record<string, LinkStatus> = {};
497
533
  statuses[terminalName] = deriveStatus(); // hub's own status
498
534
  for (const [name, status] of hubTerminalStatuses) {
499
535
  if (name !== clientName) statuses[name] = status;
500
536
  }
537
+ const cwds: Record<string, string> = {};
538
+ if (currentCwd) cwds[terminalName] = currentCwd; // hub's own cwd
539
+ for (const [name, cwd] of hubTerminalCwds) {
540
+ if (name !== clientName) cwds[name] = cwd;
541
+ }
501
542
  clientWs.send(
502
543
  JSON.stringify({
503
544
  type: "welcome",
504
545
  name: clientName,
505
546
  terminals: list,
506
547
  statuses,
548
+ cwds,
507
549
  } satisfies WelcomeMsg),
508
550
  );
509
551
 
510
- // Notify everyone else
552
+ // Notify everyone else (include joiner's cwd)
511
553
  const joined: TerminalJoinedMsg = {
512
554
  type: "terminal_joined",
513
555
  name: clientName,
514
556
  terminals: list,
557
+ cwd: msg.cwd,
515
558
  };
516
559
  hubBroadcast(joined, clientName);
517
560
  return;
@@ -550,6 +593,7 @@ export default function (pi: ExtensionAPI) {
550
593
  if (clientName) {
551
594
  hubClients.delete(clientWs);
552
595
  hubTerminalStatuses.delete(clientName);
596
+ hubTerminalCwds.delete(clientName);
553
597
  const list = terminalList();
554
598
  connectedTerminals = list;
555
599
  updateStatus();
@@ -614,6 +658,7 @@ export default function (pi: ExtensionAPI) {
614
658
  JSON.stringify({
615
659
  type: "register",
616
660
  name: preferredName ?? terminalName,
661
+ cwd: currentCwd || undefined,
617
662
  } satisfies RegisterMsg),
618
663
  );
619
664
  resolve(true);
@@ -717,6 +762,8 @@ export default function (pi: ExtensionAPI) {
717
762
  connectedTerminals = [];
718
763
  terminalStatuses.clear();
719
764
  hubTerminalStatuses.clear();
765
+ terminalCwds.clear();
766
+ hubTerminalCwds.clear();
720
767
  lastPushedKind = null;
721
768
  lastPushedTool = null;
722
769
  updateStatus();
@@ -731,6 +778,7 @@ export default function (pi: ExtensionAPI) {
731
778
 
732
779
  pi.on("session_start", async (_event, _ctx) => {
733
780
  ctx = _ctx;
781
+ currentCwd = _ctx.cwd;
734
782
 
735
783
  // Restore preferred link name from session
736
784
  const saved = _ctx.sessionManager
@@ -745,70 +793,13 @@ export default function (pi: ExtensionAPI) {
745
793
  terminalName = preferredName;
746
794
  }
747
795
 
748
- if (pi.getFlag("link") === true) await initialize();
796
+ if (shouldConnect(_ctx)) await initialize();
749
797
  });
750
798
 
751
799
  pi.on("session_shutdown", async () => {
752
800
  cleanup();
753
801
  });
754
802
 
755
- pi.on("session_switch", async (_event, _ctx) => {
756
- ctx = _ctx;
757
-
758
- // Restore preferred name from the new session
759
- const saved = _ctx.sessionManager
760
- .getEntries()
761
- .filter(
762
- (e: { type: string; customType?: string }) =>
763
- e.type === "custom" && e.customType === "link-name",
764
- )
765
- .pop() as { data?: { name?: string } } | undefined;
766
-
767
- preferredName = saved?.data?.name ?? null;
768
- const desiredName = preferredName ?? `t-${crypto.randomUUID().slice(0, 4)}`;
769
-
770
- if (desiredName === terminalName) return; // no identity change needed
771
-
772
- if (role === "hub") {
773
- // Hub rename in-place — avoid tearing down the server
774
- const takenByOther = Array.from(hubClients.values()).includes(
775
- desiredName,
776
- );
777
- if (takenByOther) {
778
- // Can't use preferred name — keep current identity
779
- ctx?.ui.notify(
780
- `Session preferred name "${desiredName}" is taken, keeping "${terminalName}"`,
781
- "warning",
782
- );
783
- return;
784
- }
785
- const old = terminalName;
786
- terminalName = desiredName;
787
- const list = terminalList();
788
- connectedTerminals = list;
789
- updateStatus();
790
- // Notify clients only — hub already updated local state
791
- hubBroadcast(
792
- { type: "terminal_left", name: old, terminals: list },
793
- terminalName,
794
- );
795
- hubBroadcast(
796
- { type: "terminal_joined", name: desiredName, terminals: list },
797
- terminalName,
798
- );
799
- pushStatus(true);
800
- } else if (role === "client") {
801
- // Client — disconnect and reconnect with new name
802
- terminalName = desiredName;
803
- disconnect();
804
- manuallyDisconnected = false;
805
- await initialize();
806
- } else {
807
- // Disconnected — just update local name
808
- terminalName = desiredName;
809
- }
810
- });
811
-
812
803
  pi.on("agent_start", async () => {
813
804
  agentRunning = true;
814
805
  activeToolName = null;
@@ -1099,19 +1090,25 @@ export default function (pi: ExtensionAPI) {
1099
1090
  if (role === "disconnected") return notConnectedResult();
1100
1091
 
1101
1092
  const statuses: Record<string, string> = {};
1093
+ const cwds: Record<string, string> = {};
1102
1094
  const list = connectedTerminals
1103
1095
  .map((name) => {
1104
1096
  const status = getStatusFor(name);
1105
1097
  const statusStr = status ? formatStatus(status) : "";
1106
1098
  if (statusStr) statuses[name] = statusStr;
1099
+ const cwd = getCwdFor(name);
1100
+ if (cwd) cwds[name] = cwd;
1107
1101
  const marker = name === terminalName ? " (you)" : "";
1108
- return ` ${name}${marker}${statusStr ? " " + statusStr : ""}`;
1102
+ let line = ` \u2022 ${name}${marker}${statusStr ? " " + statusStr : ""}`;
1103
+ if (cwd) line += `\n cwd: ${cwd}`;
1104
+ return line;
1109
1105
  })
1110
1106
  .join("\n");
1111
1107
 
1112
1108
  return textResult(`Connected terminals:\n${list}`, {
1113
1109
  terminals: connectedTerminals,
1114
1110
  statuses,
1111
+ cwds,
1115
1112
  self: terminalName,
1116
1113
  role,
1117
1114
  });
@@ -1122,6 +1119,7 @@ export default function (pi: ExtensionAPI) {
1122
1119
  | {
1123
1120
  terminals?: string[];
1124
1121
  statuses?: Record<string, string>;
1122
+ cwds?: Record<string, string>;
1125
1123
  self?: string;
1126
1124
  role?: string;
1127
1125
  }
@@ -1137,11 +1135,13 @@ export default function (pi: ExtensionAPI) {
1137
1135
  for (const name of details.terminals) {
1138
1136
  const isSelf = name === details.self;
1139
1137
  const status = details.statuses?.[name] ?? "";
1140
- const nameStr = isSelf ? `• ${name} (you)` : `• ${name}`;
1138
+ const cwd = details.cwds?.[name];
1139
+ const nameStr = isSelf ? `\u2022 ${name} (you)` : `\u2022 ${name}`;
1141
1140
  text +=
1142
1141
  "\n " +
1143
1142
  (isSelf ? theme.fg("accent", nameStr) : theme.fg("text", nameStr)) +
1144
1143
  (status ? " " + theme.fg("dim", status) : "");
1144
+ if (cwd) text += "\n " + theme.fg("dim", `cwd: ${shortenPath(cwd)}`);
1145
1145
  }
1146
1146
  return new Text(text, 0, 0);
1147
1147
  },
@@ -1159,8 +1159,11 @@ export default function (pi: ExtensionAPI) {
1159
1159
  const lines = connectedTerminals.map((name) => {
1160
1160
  const status = getStatusFor(name);
1161
1161
  const statusStr = status ? formatStatus(status) : "";
1162
+ const cwd = getCwdFor(name);
1162
1163
  const marker = name === terminalName ? " (you)" : "";
1163
- return `${name}${marker}${statusStr ? ": " + statusStr : ""}`;
1164
+ let line = `${name}${marker}${statusStr ? ": " + statusStr : ""}`;
1165
+ if (cwd) line += `\n cwd: ${shortenPath(cwd)}`;
1166
+ return line;
1164
1167
  });
1165
1168
  _ctx.ui.notify(
1166
1169
  `Link: ${terminalName} (${role}) · ${connectedTerminals.length} online\n${lines.join("\n")}`,
@@ -1225,7 +1228,12 @@ export default function (pi: ExtensionAPI) {
1225
1228
  terminalName,
1226
1229
  );
1227
1230
  hubBroadcast(
1228
- { type: "terminal_joined", name: newName, terminals: list },
1231
+ {
1232
+ type: "terminal_joined",
1233
+ name: newName,
1234
+ terminals: list,
1235
+ cwd: currentCwd,
1236
+ },
1229
1237
  terminalName,
1230
1238
  );
1231
1239
  pushStatus(true);
@@ -1275,18 +1283,23 @@ export default function (pi: ExtensionAPI) {
1275
1283
  pi.registerCommand("link-disconnect", {
1276
1284
  description: "Disconnect from the link",
1277
1285
  handler: async (_args, _ctx) => {
1286
+ pi.appendEntry("link-active", { active: false });
1287
+ manuallyDisconnected = true;
1278
1288
  if (role === "disconnected") {
1279
- _ctx.ui.notify("Already disconnected", "info");
1289
+ if (reconnectTimer) {
1290
+ clearTimeout(reconnectTimer);
1291
+ reconnectTimer = null;
1292
+ }
1293
+ _ctx.ui.notify("Link disconnected", "info");
1280
1294
  return;
1281
1295
  }
1282
- manuallyDisconnected = true;
1283
1296
  disconnect();
1284
1297
  _ctx.ui.notify("Disconnected from link", "info");
1285
1298
  },
1286
1299
  });
1287
1300
 
1288
1301
  pi.registerCommand("link-connect", {
1289
- description: "Connect to the link (after manual disconnect)",
1302
+ description: "Connect to the link",
1290
1303
  handler: async (_args, _ctx) => {
1291
1304
  if (role !== "disconnected") {
1292
1305
  _ctx.ui.notify(
@@ -1295,6 +1308,7 @@ export default function (pi: ExtensionAPI) {
1295
1308
  );
1296
1309
  return;
1297
1310
  }
1311
+ pi.appendEntry("link-active", { active: true });
1298
1312
  manuallyDisconnected = false;
1299
1313
  await initialize();
1300
1314
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-link",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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",
package/sync.ffs_db ADDED
Binary file