pi-ssh-remote 0.1.6 → 0.1.7

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
@@ -18,6 +18,21 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Rel
18
18
 
19
19
  - None.
20
20
 
21
+ ## [0.1.7] - 2026-08-05
22
+
23
+ ### Added
24
+
25
+ - Added non-secret, session-scoped SSH workspace state for endpoint, remote directory, tool-routing mode, and port forwards.
26
+
27
+ ### Changed
28
+
29
+ - `/new` now inherits the active SSH workspace, forks and clones retain their source workspace, and `/resume` restores the selected historical session's recorded workspace.
30
+ - Session changes close the previous transport cleanly and reconnect the target workspace without storing passwords or other credentials in session files.
31
+
32
+ ### Fixed
33
+
34
+ - None.
35
+
21
36
  ## [0.1.6] - 2026-08-05
22
37
 
23
38
  ### Added
@@ -130,7 +145,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Rel
130
145
 
131
146
  - None.
132
147
 
133
- [Unreleased]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.6...HEAD
148
+ [Unreleased]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.7...HEAD
149
+ [0.1.7]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.6...v0.1.7
134
150
  [0.1.6]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.5...v0.1.6
135
151
  [0.1.5]: https://github.com/petrichor20211/pi-ssh-remote/compare/v0.1.4...v0.1.5
136
152
  [0.1.4]: https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.4
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -35,7 +35,7 @@ Each `user@host:port` endpoint remembers its remote working directory, note, ser
35
35
 
36
36
  ### Resilient and bounded by default
37
37
 
38
- Dropped connections are automatically re-established during the active session. Remote commands have a 30-second default timeout. Remote text reads fetch focused ranges instead of downloading complete files, command output is streamed through bounded buffers, and a 32 KB per-turn budget prevents parallel tools from flooding model context. Complete oversized command output is preserved in a permission-restricted temporary file.
38
+ Dropped connections are automatically re-established during the active session. SSH workspace state is also session-aware: `/new` inherits the current workspace, forks and clones retain their source workspace, and `/resume` restores the selected session's recorded endpoint, remote directory, routing mode, and forwards. Remote commands have a 30-second default timeout. Remote text reads fetch focused ranges instead of downloading complete files, command output is streamed through bounded buffers, and a 32 KB per-turn budget prevents parallel tools from flooding model context. Complete oversized command output is preserved in a permission-restricted temporary file.
39
39
 
40
40
  ### Hybrid local/remote workflows
41
41
 
@@ -195,7 +195,7 @@ Endpoint configuration is stored locally in:
195
195
  ~/.pi/agent/ssh-remote-config.json
196
196
  ```
197
197
 
198
- Saved values include endpoints, active endpoint, notes, server-specific memories, remote working directories, forwards, preview settings, and model-output budgets. Server memory is user-configured trusted context and is inserted into each model request only while that endpoint is the active remote workspace; it is not read from the remote server. Passwords are **never written to this file**: SSH agent authentication is preferred, and prompted passwords remain only in process memory.
198
+ Saved global values include endpoints, active endpoint, notes, server-specific memories, remote working directories, forwards, preview settings, and model-output budgets. Each Pi session also stores non-secret SSH workspace metadata so `/resume` can restore the server associated with that session. Server memory is user-configured trusted context and is inserted into each model request only while that endpoint is the active remote workspace; it is not read from the remote server. Passwords are **never written to this file**: SSH agent authentication is preferred, and prompted passwords remain only in process memory.
199
199
 
200
200
  New or changed host keys require interactive confirmation and are stored separately from OpenSSH. By default, remote text reads return at most 400 lines or 16 KB, remote command results return the last 200 lines or 8 KB, and all remote tools in one agent turn share a 32 KB output budget. Text reads support `offset`/`limit` continuation without downloading the complete remote file. Oversized command output is streamed to a permission-restricted temporary local file rather than accumulated in memory. Configured limits may be raised only to the extension's hard safety ceilings. Preview-line settings affect only the collapsed UI and never increase model output.
201
201
 
@@ -205,13 +205,13 @@ The extension currently supports direct SSH commands with `-p` and `-l`. It does
205
205
 
206
206
  ## Releases
207
207
 
208
- Latest release: [v0.1.6](https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.6)
208
+ Latest release: [v0.1.7](https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.7)
209
209
 
210
210
  | Version | Date | Highlights |
211
211
  |---|---|---|
212
+ | [0.1.7](CHANGELOG.md#017---2026-08-05) | 2026-08-05 | Session-aware SSH workspace restoration for new, forked, and resumed sessions |
212
213
  | [0.1.6](CHANGELOG.md#016---2026-08-05) | 2026-08-05 | Bounded remote reads, streamed command output, and per-turn output budgets |
213
214
  | [0.1.5](CHANGELOG.md#015---2026-08-04) | 2026-08-04 | Endpoint-specific server memory and automatic context injection |
214
- | [0.1.4](CHANGELOG.md#014---2026-07-31) | 2026-07-31 | Endpoint notes and enforced remote command timeouts |
215
215
 
216
216
  See [CHANGELOG.md](CHANGELOG.md) for the complete release history, including additions, behavior changes, and bug fixes.
217
217
 
package/README.zh-CN.md CHANGED
@@ -58,7 +58,7 @@ SSH remote H100 训练机 (root@gpu-box.example.com:2202):/srv/project
58
58
 
59
59
  ### 断线后可以自动恢复
60
60
 
61
- 当前会话中如果 SSH 连接意外断开,插件会尝试自动重连,不需要 Agent 从头建立工作环境。
61
+ 当前会话中如果 SSH 连接意外断开,插件会尝试自动重连,不需要 Agent 从头建立工作环境。SSH 工作区状态也会跟随 Pi session:`/new` 继承当前工作区,`/fork` 和 `/clone` 保留来源工作区,`/resume` 恢复目标历史 session 记录的服务器、远程目录、路由模式和端口转发。
62
62
 
63
63
  ### 对 Agent 上下文更友好
64
64
 
@@ -249,7 +249,7 @@ Pi 会通过插件提供的 `ssh_remote_control` 工具完成这些操作。
249
249
  ~/.pi/agent/ssh-remote-config.json
250
250
  ```
251
251
 
252
- 其中包括:
252
+ 全局配置包括:
253
253
 
254
254
  - 已保存的服务器;
255
255
  - 当前选中的服务器;
@@ -260,7 +260,7 @@ Pi 会通过插件提供的 `ssh_remote_control` 工具完成这些操作。
260
260
  - 命令预览设置;
261
261
  - 模型输出预算。
262
262
 
263
- 服务器记忆是由用户在本地配置的可信上下文,不会从远程服务器自动读取;仅当对应 endpoint 作为远端工作区启用时才会加入每次模型请求。
263
+ 此外,每个 Pi session 都会记录不含凭据的 SSH 工作区元数据,用于在 `/resume` 时恢复该历史 session 对应的服务器环境。服务器记忆是由用户在本地配置的可信上下文,不会从远程服务器自动读取;仅当对应 endpoint 作为远端工作区启用时才会加入每次模型请求。
264
264
 
265
265
  密码不会写入配置文件。插件会优先使用 SSH agent;如果需要手动输入密码,密码只会缓存在当前 Pi 进程的内存中。
266
266
 
@@ -283,13 +283,13 @@ Pi 会通过插件提供的 `ssh_remote_control` 工具完成这些操作。
283
283
 
284
284
  ## 版本发布
285
285
 
286
- 最新版本:[v0.1.6](https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.6)
286
+ 最新版本:[v0.1.7](https://github.com/petrichor20211/pi-ssh-remote/releases/tag/v0.1.7)
287
287
 
288
288
  | 版本 | 日期 | 主要内容 |
289
289
  |---|---|---|
290
+ | [0.1.7](CHANGELOG.md#017---2026-08-05) | 2026-08-05 | 新建、分叉和恢复 session 时自动恢复对应 SSH 工作区 |
290
291
  | [0.1.6](CHANGELOG.md#016---2026-08-05) | 2026-08-05 | 有界远端读取、流式命令输出与每轮输出预算 |
291
292
  | [0.1.5](CHANGELOG.md#015---2026-08-04) | 2026-08-04 | endpoint 级服务器记忆与自动上下文注入 |
292
- | [0.1.4](CHANGELOG.md#014---2026-07-31) | 2026-07-31 | endpoint 备注与远程命令强制超时 |
293
293
 
294
294
  完整的新增内容、行为变更和 Bug 修复记录请查看 [CHANGELOG.md](CHANGELOG.md)。
295
295
 
package/index.ts CHANGED
@@ -48,7 +48,7 @@ interface RemoteState extends ParsedSsh {
48
48
 
49
49
  interface CredentialCache {
50
50
  passwords: Map<string, string>;
51
- resume?: { command: string; cwd: string };
51
+ resume?: { command: string; cwd: string; routeRemoteTools: boolean; forwards?: string[] };
52
52
  }
53
53
 
54
54
  interface RemoteEndpointConfig {
@@ -80,6 +80,15 @@ interface ForwardSpec {
80
80
  remotePort: number;
81
81
  }
82
82
 
83
+ interface SessionRemoteState {
84
+ version: 1;
85
+ connected: boolean;
86
+ command?: string;
87
+ cwd?: string;
88
+ routeRemoteTools?: boolean;
89
+ forwards?: string[];
90
+ }
91
+
83
92
  const AGENT_DIR = join(process.env.HOME || ".", CONFIG_DIR_NAME, "agent");
84
93
  const KNOWN_HOSTS_FILE = join(AGENT_DIR, "ssh-remote-known-hosts.json");
85
94
  const REMOTE_CONFIG_FILE = join(AGENT_DIR, "ssh-remote-config.json");
@@ -95,6 +104,7 @@ const MIN_MODEL_OUTPUT_BYTES = 1024;
95
104
  const OUTPUT_FOOTER_RESERVE_BYTES = 512;
96
105
  const DEFAULT_REMOTE_TIMEOUT_SECONDS = 30;
97
106
  const MAX_REMOTE_TIMEOUT_SECONDS = 2_147_483_647 / 1000;
107
+ const SESSION_STATE_ENTRY_TYPE = "pi-ssh-remote-state";
98
108
  const CACHE_KEY = "__piHpcCredentialCacheV1";
99
109
  const cacheHost = globalThis as typeof globalThis & { [CACHE_KEY]?: CredentialCache };
100
110
  const credentialCache = cacheHost[CACHE_KEY] ??= { passwords: new Map<string, string>() };
@@ -659,6 +669,9 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
659
669
  let currentCtx: any;
660
670
  let reconnectPromise: Promise<RemoteState> | null = null;
661
671
  const forwardServers = new Map<number, Server>();
672
+ const forwardSpecs = new Map<number, ForwardSpec>();
673
+ let sessionReady = false;
674
+ let restoringSessionState = false;
662
675
  let lastConnectionError: string | undefined;
663
676
  let lastCommand = credentialCache.resume?.command ?? activeSshCommand() ?? "";
664
677
  let turnOutputBytes = 0;
@@ -684,6 +697,42 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
684
697
  return posix.join(remote.cwd, normalized);
685
698
  };
686
699
 
700
+ const serializeForward = (spec: ForwardSpec): string => `${spec.localPort}:${spec.remoteHost}:${spec.remotePort}`;
701
+
702
+ const currentSessionRemoteState = (): SessionRemoteState => remote ? {
703
+ version: 1,
704
+ connected: true,
705
+ command: remote.command,
706
+ cwd: remote.cwd,
707
+ routeRemoteTools,
708
+ forwards: [...forwardSpecs.values()].map(serializeForward),
709
+ } : { version: 1, connected: false };
710
+
711
+ const persistSessionRemoteState = (): void => {
712
+ if (!sessionReady || restoringSessionState) return;
713
+ pi.appendEntry(SESSION_STATE_ENTRY_TYPE, currentSessionRemoteState());
714
+ };
715
+
716
+ const loadSessionRemoteState = (ctx: any): SessionRemoteState | undefined => {
717
+ const branch = ctx.sessionManager.getBranch();
718
+ for (let index = branch.length - 1; index >= 0; index--) {
719
+ const entry = branch[index] as any;
720
+ if (entry.type !== "custom" || entry.customType !== SESSION_STATE_ENTRY_TYPE) continue;
721
+ const data = entry.data as Partial<SessionRemoteState> | undefined;
722
+ if (!data || data.version !== 1 || typeof data.connected !== "boolean") return undefined;
723
+ if (data.connected && (typeof data.command !== "string" || typeof data.cwd !== "string")) return undefined;
724
+ return {
725
+ version: 1,
726
+ connected: data.connected,
727
+ ...(data.command ? { command: data.command } : {}),
728
+ ...(data.cwd ? { cwd: data.cwd } : {}),
729
+ ...(typeof data.routeRemoteTools === "boolean" ? { routeRemoteTools: data.routeRemoteTools } : {}),
730
+ ...(Array.isArray(data.forwards) ? { forwards: data.forwards.filter((value): value is string => typeof value === "string") } : {}),
731
+ };
732
+ }
733
+ return undefined;
734
+ };
735
+
687
736
  const limitRemoteToolResult = (result: any, kind: "read" | "exec", startLine = 1, requestedMaxLines?: number) => {
688
737
  const limits = configuredOutputLimits();
689
738
  const configuredMaxBytes = kind === "read" ? limits.readMaxBytes : limits.execMaxBytes;
@@ -772,6 +821,7 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
772
821
 
773
822
  async function reconnectRemote(): Promise<RemoteState> {
774
823
  if (reconnectPromise) return reconnectPromise;
824
+ const resumeRouting = remote ? routeRemoteTools : (credentialCache.resume?.routeRemoteTools ?? true);
775
825
  const source = remote ?? (credentialCache.resume ? { ...parseSshCommand(credentialCache.resume.command), cwd: credentialCache.resume.cwd } : null);
776
826
  if (!source) throw new Error("No SSH remote connection is available to reconnect");
777
827
  const parsed = parseSshCommand(source.command);
@@ -780,7 +830,8 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
780
830
  const oldClient = remote?.client;
781
831
  const next = await establish(parsed, password, source.cwd);
782
832
  remote = next;
783
- credentialCache.resume = { command: parsed.command, cwd: next.cwd };
833
+ routeRemoteTools = resumeRouting;
834
+ credentialCache.resume = { command: parsed.command, cwd: next.cwd, routeRemoteTools, forwards: credentialCache.resume?.forwards };
784
835
  oldClient?.end();
785
836
  if (currentCtx) {
786
837
  status(currentCtx);
@@ -810,9 +861,10 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
810
861
  `cd -- ${quote(remote!.cwd)} && ${targetCommand} && pwd -P`,
811
862
  ))).toString().trim();
812
863
  remote.cwd = resolved;
813
- credentialCache.resume = { command: remote.command, cwd: resolved };
864
+ credentialCache.resume = { command: remote.command, cwd: resolved, routeRemoteTools, forwards: [...forwardSpecs.values()].map(serializeForward) };
814
865
  saveEndpointConfig(remote.command, { remoteCwd: resolved }, true);
815
866
  status(ctx);
867
+ persistSessionRemoteState();
816
868
  return resolved;
817
869
  };
818
870
 
@@ -874,11 +926,12 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
874
926
  routeRemoteTools = true;
875
927
  previous?.end();
876
928
  if (password) setCachedPassword(parsed, password);
877
- credentialCache.resume = { command, cwd: next.cwd };
929
+ credentialCache.resume = { command, cwd: next.cwd, routeRemoteTools, forwards: [] };
878
930
  lastCommand = command;
879
931
  lastConnectionError = undefined;
880
932
  saveEndpointConfig(command, { remoteCwd: next.cwd }, true);
881
933
  status(ctx);
934
+ persistSessionRemoteState();
882
935
  ctx.ui.notify(`SSH remote connected: ${endpointDisplayLabel(next)}:${next.cwd}`, "info");
883
936
  return next;
884
937
  } catch (error) {
@@ -922,14 +975,32 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
922
975
  });
923
976
  });
924
977
  forwardServers.set(spec.localPort, server);
978
+ forwardSpecs.set(spec.localPort, spec);
925
979
  };
926
980
 
927
981
  const stopForwards = async (): Promise<void> => {
928
982
  const servers = [...forwardServers.values()];
929
983
  forwardServers.clear();
984
+ forwardSpecs.clear();
930
985
  await Promise.all(servers.map((server) => new Promise<void>((resolve) => server.close(() => resolve()))));
931
986
  };
932
987
 
988
+ const restoreSessionRemoteState = async (saved: SessionRemoteState, ctx: any): Promise<void> => {
989
+ if (!saved.connected) {
990
+ credentialCache.resume = undefined;
991
+ status(ctx);
992
+ return;
993
+ }
994
+ const command = saved.command!;
995
+ const next = await connectInteractive(command, ctx, saved.cwd);
996
+ if (!next) throw new Error(lastConnectionError || "SSH remote session restore was cancelled or failed");
997
+ routeRemoteTools = saved.routeRemoteTools ?? true;
998
+ const specs = (saved.forwards ?? []).map(parseForwardSpec);
999
+ for (const spec of specs) await startForward(spec);
1000
+ credentialCache.resume = { command, cwd: next.cwd, routeRemoteTools, forwards: [...forwardSpecs.values()].map(serializeForward) };
1001
+ status(ctx);
1002
+ };
1003
+
933
1004
  const disconnect = (ctx: any, forgetPassword = false) => {
934
1005
  const previous = remote;
935
1006
  remote = null;
@@ -948,6 +1019,7 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
948
1019
  }
949
1020
  previous?.client.end();
950
1021
  status(ctx);
1022
+ persistSessionRemoteState();
951
1023
  ctx.ui.notify(forgetPassword ? "SSH remote disconnected and cached password cleared" : "SSH remote mode disabled (password remains cached in memory only)", "info");
952
1024
  };
953
1025
 
@@ -1104,6 +1176,7 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1104
1176
  }
1105
1177
  if (params.action === "unforward") {
1106
1178
  await stopForwards();
1179
+ persistSessionRemoteState();
1107
1180
  return { content: [{ type: "text", text: "Closed all extension-managed SSH port forwards." }], details: { forwardedPorts: [] } };
1108
1181
  }
1109
1182
  if (params.action === "forward") {
@@ -1113,7 +1186,9 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1113
1186
  const specs = values.map(parseForwardSpec);
1114
1187
  for (const spec of specs) await startForward(spec);
1115
1188
  routeRemoteTools = false;
1189
+ credentialCache.resume = { command: state.command, cwd: state.cwd, routeRemoteTools, forwards: [...forwardSpecs.values()].map(serializeForward) };
1116
1190
  if (currentCtx) status(currentCtx);
1191
+ persistSessionRemoteState();
1117
1192
  const ports = specs.map((spec) => spec.localPort);
1118
1193
  return { content: [{ type: "text", text: `Forwarded local ports: ${ports.join(", ")}; tools remain local.` }], details: { toolRouting: "local", forwardedPorts: ports } };
1119
1194
  }
@@ -1231,6 +1306,7 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1231
1306
  previous.client.end();
1232
1307
  await stopForwards();
1233
1308
  status(ctx);
1309
+ persistSessionRemoteState();
1234
1310
  }
1235
1311
  saveRemoteConfig({ ...config, activeEndpoint: key });
1236
1312
  lastCommand = command;
@@ -1309,13 +1385,16 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1309
1385
  const specs = values.map(parseForwardSpec);
1310
1386
  for (const spec of specs) await startForward(spec);
1311
1387
  routeRemoteTools = false;
1388
+ credentialCache.resume = { command: state.command, cwd: state.cwd, routeRemoteTools, forwards: [...forwardSpecs.values()].map(serializeForward) };
1312
1389
  status(ctx);
1390
+ persistSessionRemoteState();
1313
1391
  ctx.ui.notify(`SSH remote port forwarding started; tools remain local in ${localCwd}: ${specs.map((spec) => `127.0.0.1:${spec.localPort}`).join(", ")}`, "info");
1314
1392
  } catch (error) { ctx.ui.notify(`SSH remote port forwarding failed: ${(error as Error).message}`, "error"); }
1315
1393
  return;
1316
1394
  }
1317
1395
  if (action === "unforward") {
1318
1396
  await stopForwards();
1397
+ persistSessionRemoteState();
1319
1398
  ctx.ui.notify("Closed all extension-managed SSH remote port forwards", "info");
1320
1399
  return;
1321
1400
  }
@@ -1378,19 +1457,50 @@ export default function sshRemoteExtension(pi: ExtensionAPI) {
1378
1457
  pi.on("turn_start", () => { turnOutputBytes = 0; });
1379
1458
  pi.on("session_start", async (event, ctx) => {
1380
1459
  currentCtx = ctx;
1460
+ sessionReady = true;
1381
1461
  status(ctx);
1382
- if (event.reason === "reload" && credentialCache.resume) {
1383
- try { await reconnectRemote(); }
1384
- catch (error) { ctx.ui.notify(`SSH remote automatic login after reload failed: ${(error as Error).message}`, "error"); }
1462
+ const saved = loadSessionRemoteState(ctx);
1463
+ const canInheritCurrent = event.reason === "reload" || event.reason === "new" || event.reason === "fork";
1464
+ const inherited = !saved && canInheritCurrent && credentialCache.resume ? credentialCache.resume : undefined;
1465
+ const target: SessionRemoteState | undefined = saved ?? (inherited ? {
1466
+ version: 1,
1467
+ connected: true,
1468
+ command: inherited.command,
1469
+ cwd: inherited.cwd,
1470
+ routeRemoteTools: inherited.routeRemoteTools,
1471
+ forwards: inherited.forwards ?? [],
1472
+ } : undefined);
1473
+ if (!target) {
1474
+ credentialCache.resume = undefined;
1475
+ return;
1385
1476
  }
1477
+ restoringSessionState = true;
1478
+ try {
1479
+ await restoreSessionRemoteState(target, ctx);
1480
+ } catch (error) {
1481
+ ctx.ui.notify(`SSH remote session restore failed: ${(error as Error).message}`, "error");
1482
+ } finally {
1483
+ restoringSessionState = false;
1484
+ }
1485
+ if (inherited && remote) persistSessionRemoteState();
1386
1486
  });
1387
- pi.on("session_shutdown", (event) => {
1487
+ pi.on("session_shutdown", async (event) => {
1488
+ sessionReady = false;
1388
1489
  const previous = remote;
1490
+ const preserveConnection = event.reason === "reload" || event.reason === "new" || event.reason === "fork";
1491
+ if (previous && preserveConnection) {
1492
+ credentialCache.resume = {
1493
+ command: previous.command,
1494
+ cwd: previous.cwd,
1495
+ routeRemoteTools,
1496
+ forwards: [...forwardSpecs.values()].map(serializeForward),
1497
+ };
1498
+ }
1389
1499
  remote = null;
1390
1500
  routeRemoteTools = false;
1391
- void stopForwards();
1501
+ await stopForwards();
1392
1502
  previous?.client.end();
1393
- if (event.reason !== "reload") credentialCache.resume = undefined;
1503
+ if (!preserveConnection) credentialCache.resume = undefined;
1394
1504
  });
1395
1505
  pi.on("user_bash", async (event, ctx) => {
1396
1506
  if (!remote || !routeRemoteTools) return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ssh-remote",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Persistent remote SSH workspaces for Pi.",
5
5
  "type": "module",
6
6
  "author": "Yutong Bian",