pi-link 0.1.4 → 0.1.5

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,17 @@
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-02` (current). Versions correspond to npm publishes.
6
+
7
+ ---
8
+
9
+ ## 0.1.5 — 2026-04-02
10
+
11
+ ### Added
12
+
13
+ - **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.
14
+
15
+ - **Header comment cleanup.** Simplified the top-of-file doc comment — removed feature bullet list and install instructions in favor of a concise summary.
6
16
 
7
17
  ---
8
18
 
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 and on session switch. `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,20 @@ 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:
389
-
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 |
400
+ The wire protocol consists of **10 message types**, all serialized as JSON over WebSocket frames. New cwd-related fields are optional for backward compatibility.
401
+
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
+ | `cwd_update` | Any → Hub → All | Terminal broadcasts a cwd change |
413
+ | `error` | Hub → Client | Error notification |
401
414
 
402
415
  ### Message Flow Examples
403
416
 
@@ -406,16 +419,17 @@ The wire protocol consists of **9 message types**, all serialized as JSON over W
406
419
  ```
407
420
  Client Hub
408
421
  | |
409
- | register {name:"builder"} |
422
+ | register {name:"builder", |
423
+ | cwd:"C:\\Users\\..."} |
410
424
  |---------------------------->|
411
425
  | |
412
426
  | welcome {name, terminals, |
413
- | statuses} |
427
+ | statuses, cwds} |
414
428
  |<----------------------------|
415
429
  | |
416
430
  ```
417
431
 
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).
432
+ 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, and mid-session cwd changes are distributed via `cwd_update`.
419
433
 
420
434
  **Sending a chat message:**
421
435
 
@@ -468,6 +482,7 @@ Default names are random 4-character hex IDs: `t-a1b2`, `t-c3d4`, etc.
468
482
  | `agentRunning` | `boolean` | Whether an agent run is active; blocks incoming remote prompts |
469
483
  | `activeToolName` | `string \| null` | Name of the currently executing tool (drives `tool:<name>` status) |
470
484
  | `stateSince` | `number` | Timestamp of last status change (used for duration display) |
485
+ | `currentCwd` | `string` | Current working directory reported to peers on connect and session switch |
471
486
  | `manuallyDisconnected` | `boolean` | Set by `/link-disconnect`; suppresses auto-reconnect |
472
487
  | `pendingRemotePrompt` | `object \| null` | Tracks the single in-flight remote prompt execution |
473
488
  | `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,25 @@ 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;
51
+ }
52
+ interface CwdUpdateMsg {
53
+ type: "cwd_update";
54
+ name: string;
55
+ cwd: string;
56
56
  }
57
57
  interface TerminalLeftMsg {
58
58
  type: "terminal_left";
@@ -105,6 +105,7 @@ type LinkMessage =
105
105
  | PromptRequestMsg
106
106
  | PromptResponseMsg
107
107
  | StatusUpdateMsg
108
+ | CwdUpdateMsg
108
109
  | ErrorMsg;
109
110
 
110
111
  // ─── Extension ───────────────────────────────────────────────────────────────
@@ -134,11 +135,14 @@ export default function (pi: ExtensionAPI) {
134
135
  let lastPushedKind: string | null = null;
135
136
  let lastPushedTool: string | null = null;
136
137
  const terminalStatuses = new Map<string, LinkStatus>(); // other terminals
138
+ let currentCwd = "";
139
+ const terminalCwds = new Map<string, string>(); // other terminals' cwds
137
140
 
138
141
  // Hub state
139
142
  let wss: WebSocketServer | null = null;
140
143
  const hubClients = new Map<WebSocket, string>(); // ws → terminal name
141
144
  const hubTerminalStatuses = new Map<string, LinkStatus>(); // hub-authoritative
145
+ const hubTerminalCwds = new Map<string, string>(); // hub-authoritative (excludes self)
142
146
 
143
147
  // Client state
144
148
  let ws: WebSocket | null = null;
@@ -221,6 +225,34 @@ export default function (pi: ExtensionAPI) {
221
225
  return map.get(name) ?? null;
222
226
  }
223
227
 
228
+ function getCwdFor(name: string): string | null {
229
+ if (name === terminalName) return currentCwd || null;
230
+ if (role === "hub") return hubTerminalCwds.get(name) ?? null;
231
+ return terminalCwds.get(name) ?? null;
232
+ }
233
+
234
+ function shortenPath(cwd: string): string {
235
+ const home = os.homedir().replace(/\\/g, "/");
236
+ const normalized = cwd.replace(/\\/g, "/");
237
+ if (normalized === home) return "~";
238
+ if (normalized.startsWith(home + "/"))
239
+ return "~" + normalized.slice(home.length);
240
+ return normalized;
241
+ }
242
+
243
+ function pushCwdUpdate() {
244
+ const msg: CwdUpdateMsg = {
245
+ type: "cwd_update",
246
+ name: terminalName,
247
+ cwd: currentCwd,
248
+ };
249
+ if (role === "hub") {
250
+ hubBroadcast(msg, terminalName);
251
+ } else if (role === "client" && ws?.readyState === WebSocket.OPEN) {
252
+ ws.send(JSON.stringify(msg));
253
+ }
254
+ }
255
+
224
256
  // ── Pending prompt helpers ───────────────────────────────────────────────
225
257
 
226
258
  function cleanupPending(requestId: string) {
@@ -365,11 +397,17 @@ export default function (pi: ExtensionAPI) {
365
397
  terminalName = msg.name;
366
398
  connectedTerminals = msg.terminals;
367
399
  terminalStatuses.clear();
400
+ terminalCwds.clear();
368
401
  if (msg.statuses) {
369
402
  for (const [name, status] of Object.entries(msg.statuses)) {
370
403
  terminalStatuses.set(name, status);
371
404
  }
372
405
  }
406
+ if (msg.cwds) {
407
+ for (const [name, cwd] of Object.entries(msg.cwds)) {
408
+ terminalCwds.set(name, cwd);
409
+ }
410
+ }
373
411
  updateStatus();
374
412
  ctx?.ui.notify(
375
413
  `Joined link as "${terminalName}" (${connectedTerminals.length} online)`,
@@ -378,9 +416,10 @@ export default function (pi: ExtensionAPI) {
378
416
  pushStatus(true);
379
417
  break;
380
418
 
381
- // ── Directory updates ──
419
+ // ── Membership updates ──
382
420
  case "terminal_joined":
383
421
  connectedTerminals = msg.terminals;
422
+ if (role !== "hub" && msg.cwd) terminalCwds.set(msg.name, msg.cwd);
384
423
  updateStatus();
385
424
  ctx?.ui.notify(`"${msg.name}" joined the link`, "info");
386
425
  break;
@@ -388,6 +427,7 @@ export default function (pi: ExtensionAPI) {
388
427
  case "terminal_left":
389
428
  connectedTerminals = msg.terminals;
390
429
  terminalStatuses.delete(msg.name);
430
+ if (role !== "hub") terminalCwds.delete(msg.name);
391
431
  // Fail any pending prompts to the departed terminal immediately
392
432
  for (const [id, pending] of pendingPromptResponses) {
393
433
  if (pending.targetName === msg.name) {
@@ -412,6 +452,10 @@ export default function (pi: ExtensionAPI) {
412
452
  resetInactivityFor(msg.name);
413
453
  break;
414
454
 
455
+ case "cwd_update":
456
+ terminalCwds.set(msg.name, msg.cwd);
457
+ break;
458
+
415
459
  // ── Chat message ──
416
460
  case "chat":
417
461
  pi.sendMessage(
@@ -488,30 +532,38 @@ export default function (pi: ExtensionAPI) {
488
532
  if (msg.type === "register") {
489
533
  clientName = uniqueName(msg.name);
490
534
  hubClients.set(clientWs, clientName);
535
+ if (msg.cwd) hubTerminalCwds.set(clientName, msg.cwd);
491
536
  const list = terminalList();
492
537
  connectedTerminals = list;
493
538
  updateStatus();
494
539
 
495
- // Confirm to the new client (include status snapshot)
540
+ // Confirm to the new client (include status + cwd snapshots)
496
541
  const statuses: Record<string, LinkStatus> = {};
497
542
  statuses[terminalName] = deriveStatus(); // hub's own status
498
543
  for (const [name, status] of hubTerminalStatuses) {
499
544
  if (name !== clientName) statuses[name] = status;
500
545
  }
546
+ const cwds: Record<string, string> = {};
547
+ if (currentCwd) cwds[terminalName] = currentCwd; // hub's own cwd
548
+ for (const [name, cwd] of hubTerminalCwds) {
549
+ if (name !== clientName) cwds[name] = cwd;
550
+ }
501
551
  clientWs.send(
502
552
  JSON.stringify({
503
553
  type: "welcome",
504
554
  name: clientName,
505
555
  terminals: list,
506
556
  statuses,
557
+ cwds,
507
558
  } satisfies WelcomeMsg),
508
559
  );
509
560
 
510
- // Notify everyone else
561
+ // Notify everyone else (include joiner's cwd)
511
562
  const joined: TerminalJoinedMsg = {
512
563
  type: "terminal_joined",
513
564
  name: clientName,
514
565
  terminals: list,
566
+ cwd: msg.cwd,
515
567
  };
516
568
  hubBroadcast(joined, clientName);
517
569
  return;
@@ -536,6 +588,21 @@ export default function (pi: ExtensionAPI) {
536
588
  return;
537
589
  }
538
590
 
591
+ // Cwd update — store and relay to other clients only
592
+ if (msg.type === "cwd_update") {
593
+ hubTerminalCwds.set(clientName, msg.cwd);
594
+ const normalized: CwdUpdateMsg = {
595
+ type: "cwd_update",
596
+ name: clientName,
597
+ cwd: msg.cwd,
598
+ };
599
+ const json = JSON.stringify(normalized);
600
+ for (const [otherWs, name] of hubClients) {
601
+ if (name !== clientName) otherWs.send(json);
602
+ }
603
+ return;
604
+ }
605
+
539
606
  // Route chat / prompt messages
540
607
  if (
541
608
  msg.type === "chat" ||
@@ -550,6 +617,7 @@ export default function (pi: ExtensionAPI) {
550
617
  if (clientName) {
551
618
  hubClients.delete(clientWs);
552
619
  hubTerminalStatuses.delete(clientName);
620
+ hubTerminalCwds.delete(clientName);
553
621
  const list = terminalList();
554
622
  connectedTerminals = list;
555
623
  updateStatus();
@@ -614,6 +682,7 @@ export default function (pi: ExtensionAPI) {
614
682
  JSON.stringify({
615
683
  type: "register",
616
684
  name: preferredName ?? terminalName,
685
+ cwd: currentCwd || undefined,
617
686
  } satisfies RegisterMsg),
618
687
  );
619
688
  resolve(true);
@@ -717,6 +786,8 @@ export default function (pi: ExtensionAPI) {
717
786
  connectedTerminals = [];
718
787
  terminalStatuses.clear();
719
788
  hubTerminalStatuses.clear();
789
+ terminalCwds.clear();
790
+ hubTerminalCwds.clear();
720
791
  lastPushedKind = null;
721
792
  lastPushedTool = null;
722
793
  updateStatus();
@@ -731,6 +802,7 @@ export default function (pi: ExtensionAPI) {
731
802
 
732
803
  pi.on("session_start", async (_event, _ctx) => {
733
804
  ctx = _ctx;
805
+ currentCwd = _ctx.cwd;
734
806
 
735
807
  // Restore preferred link name from session
736
808
  const saved = _ctx.sessionManager
@@ -755,7 +827,12 @@ export default function (pi: ExtensionAPI) {
755
827
  pi.on("session_switch", async (_event, _ctx) => {
756
828
  ctx = _ctx;
757
829
 
758
- // Restore preferred name from the new session
830
+ // 1. Cwd change detection (always, before any name logic)
831
+ const newCwd = _ctx.cwd;
832
+ const cwdChanged = newCwd !== currentCwd;
833
+ if (cwdChanged) currentCwd = newCwd;
834
+
835
+ // 2. Restore preferred name from the new session
759
836
  const saved = _ctx.sessionManager
760
837
  .getEntries()
761
838
  .filter(
@@ -766,9 +843,17 @@ export default function (pi: ExtensionAPI) {
766
843
 
767
844
  preferredName = saved?.data?.name ?? null;
768
845
  const desiredName = preferredName ?? `t-${crypto.randomUUID().slice(0, 4)}`;
846
+ const nameChanged = desiredName !== terminalName;
769
847
 
770
- if (desiredName === terminalName) return; // no identity change needed
848
+ if (!nameChanged && !cwdChanged) return; // nothing to do
771
849
 
850
+ if (!nameChanged) {
851
+ // Name stayed the same, but cwd changed — push cwd update
852
+ pushCwdUpdate();
853
+ return;
854
+ }
855
+
856
+ // Name changed (cwd may or may not have changed too)
772
857
  if (role === "hub") {
773
858
  // Hub rename in-place — avoid tearing down the server
774
859
  const takenByOther = Array.from(hubClients.values()).includes(
@@ -780,6 +865,8 @@ export default function (pi: ExtensionAPI) {
780
865
  `Session preferred name "${desiredName}" is taken, keeping "${terminalName}"`,
781
866
  "warning",
782
867
  );
868
+ // Still push cwd update under current name if cwd changed
869
+ if (cwdChanged) pushCwdUpdate();
783
870
  return;
784
871
  }
785
872
  const old = terminalName;
@@ -793,12 +880,17 @@ export default function (pi: ExtensionAPI) {
793
880
  terminalName,
794
881
  );
795
882
  hubBroadcast(
796
- { type: "terminal_joined", name: desiredName, terminals: list },
883
+ {
884
+ type: "terminal_joined",
885
+ name: desiredName,
886
+ terminals: list,
887
+ cwd: currentCwd,
888
+ },
797
889
  terminalName,
798
890
  );
799
891
  pushStatus(true);
800
892
  } else if (role === "client") {
801
- // Client — disconnect and reconnect with new name
893
+ // Client — disconnect and reconnect with new name (register includes cwd)
802
894
  terminalName = desiredName;
803
895
  disconnect();
804
896
  manuallyDisconnected = false;
@@ -1099,19 +1191,25 @@ export default function (pi: ExtensionAPI) {
1099
1191
  if (role === "disconnected") return notConnectedResult();
1100
1192
 
1101
1193
  const statuses: Record<string, string> = {};
1194
+ const cwds: Record<string, string> = {};
1102
1195
  const list = connectedTerminals
1103
1196
  .map((name) => {
1104
1197
  const status = getStatusFor(name);
1105
1198
  const statusStr = status ? formatStatus(status) : "";
1106
1199
  if (statusStr) statuses[name] = statusStr;
1200
+ const cwd = getCwdFor(name);
1201
+ if (cwd) cwds[name] = cwd;
1107
1202
  const marker = name === terminalName ? " (you)" : "";
1108
- return ` ${name}${marker}${statusStr ? " " + statusStr : ""}`;
1203
+ let line = ` \u2022 ${name}${marker}${statusStr ? " " + statusStr : ""}`;
1204
+ if (cwd) line += `\n cwd: ${cwd}`;
1205
+ return line;
1109
1206
  })
1110
1207
  .join("\n");
1111
1208
 
1112
1209
  return textResult(`Connected terminals:\n${list}`, {
1113
1210
  terminals: connectedTerminals,
1114
1211
  statuses,
1212
+ cwds,
1115
1213
  self: terminalName,
1116
1214
  role,
1117
1215
  });
@@ -1122,6 +1220,7 @@ export default function (pi: ExtensionAPI) {
1122
1220
  | {
1123
1221
  terminals?: string[];
1124
1222
  statuses?: Record<string, string>;
1223
+ cwds?: Record<string, string>;
1125
1224
  self?: string;
1126
1225
  role?: string;
1127
1226
  }
@@ -1137,11 +1236,13 @@ export default function (pi: ExtensionAPI) {
1137
1236
  for (const name of details.terminals) {
1138
1237
  const isSelf = name === details.self;
1139
1238
  const status = details.statuses?.[name] ?? "";
1140
- const nameStr = isSelf ? `• ${name} (you)` : `• ${name}`;
1239
+ const cwd = details.cwds?.[name];
1240
+ const nameStr = isSelf ? `\u2022 ${name} (you)` : `\u2022 ${name}`;
1141
1241
  text +=
1142
1242
  "\n " +
1143
1243
  (isSelf ? theme.fg("accent", nameStr) : theme.fg("text", nameStr)) +
1144
1244
  (status ? " " + theme.fg("dim", status) : "");
1245
+ if (cwd) text += "\n " + theme.fg("dim", `cwd: ${shortenPath(cwd)}`);
1145
1246
  }
1146
1247
  return new Text(text, 0, 0);
1147
1248
  },
@@ -1159,8 +1260,11 @@ export default function (pi: ExtensionAPI) {
1159
1260
  const lines = connectedTerminals.map((name) => {
1160
1261
  const status = getStatusFor(name);
1161
1262
  const statusStr = status ? formatStatus(status) : "";
1263
+ const cwd = getCwdFor(name);
1162
1264
  const marker = name === terminalName ? " (you)" : "";
1163
- return `${name}${marker}${statusStr ? ": " + statusStr : ""}`;
1265
+ let line = `${name}${marker}${statusStr ? ": " + statusStr : ""}`;
1266
+ if (cwd) line += `\n cwd: ${shortenPath(cwd)}`;
1267
+ return line;
1164
1268
  });
1165
1269
  _ctx.ui.notify(
1166
1270
  `Link: ${terminalName} (${role}) · ${connectedTerminals.length} online\n${lines.join("\n")}`,
@@ -1225,7 +1329,12 @@ export default function (pi: ExtensionAPI) {
1225
1329
  terminalName,
1226
1330
  );
1227
1331
  hubBroadcast(
1228
- { type: "terminal_joined", name: newName, terminals: list },
1332
+ {
1333
+ type: "terminal_joined",
1334
+ name: newName,
1335
+ terminals: list,
1336
+ cwd: currentCwd,
1337
+ },
1229
1338
  terminalName,
1230
1339
  );
1231
1340
  pushStatus(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-link",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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