@clawops/cli 2.1.3 → 2.2.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.
Files changed (51) hide show
  1. package/README.md +120 -2
  2. package/dist/agent-activity-25FAWLAI.js +16 -0
  3. package/dist/{apply-JARPVA5Y.js → apply-F5SQ2ZOS.js} +7 -7
  4. package/dist/backup-flows-NDP5JTR3.js +29 -0
  5. package/dist/{bootstrap-3PBVY7MA.js → bootstrap-32TZXVDF.js} +3 -3
  6. package/dist/bootstrap.sh.tmpl +4 -0
  7. package/dist/chunk-27335SIH.js +51 -0
  8. package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
  9. package/dist/{chunk-PTCVFLZ3.js → chunk-66R52R7K.js} +7 -11
  10. package/dist/{chunk-7T4CIT7C.js → chunk-AX65JMDZ.js} +15 -15
  11. package/dist/{chunk-TYHTISI5.js → chunk-CQOYUWR7.js} +610 -104
  12. package/dist/chunk-E6BRQK67.js +289 -0
  13. package/dist/chunk-LV7UO5CG.js +82 -0
  14. package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
  15. package/dist/{chunk-CPCO6UJK.js → chunk-OUQVKIIA.js} +368 -30
  16. package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
  17. package/dist/chunk-PCSX6Y3U.js +297 -0
  18. package/dist/chunk-POYS3EVQ.js +14 -0
  19. package/dist/chunk-SX75L2T3.js +46 -0
  20. package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
  21. package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
  22. package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
  23. package/dist/{chunk-CHNSL3TC.js → chunk-YPVWF7IA.js} +1 -1
  24. package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
  25. package/dist/cli.js +303 -698
  26. package/dist/conn-M2V5HBPJ.js +9 -0
  27. package/dist/{context-WOKSKAIA.js → context-EHSLWKSH.js} +2 -2
  28. package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
  29. package/dist/{flows-HGJIA6EL.js → flows-6B2I6TRW.js} +5 -5
  30. package/dist/{generate-NE54BATS.js → generate-4YYNW3IU.js} +5 -4
  31. package/dist/{harden-RUOUA5BV.js → harden-UHAOVQMZ.js} +3 -3
  32. package/dist/{mcp-wire-FIO5DFBY.js → mcp-wire-BXF4DJFT.js} +21 -14
  33. package/dist/migrate-flow-24XA4GZL.js +25 -0
  34. package/dist/{package-MF5LPITL.js → package-HYGHRX35.js} +3 -1
  35. package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
  36. package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
  37. package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
  38. package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
  39. package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
  40. package/dist/{server-OXWHBTXK.js → server-E3YA32ND.js} +21 -14
  41. package/dist/ssh-HOM7GF3M.js +13 -0
  42. package/dist/stack-delete-NTIGKWUF.js +13 -0
  43. package/dist/{tailscale-cutover-EKAQSDXP.js → tailscale-cutover-V42HO5UM.js} +1 -1
  44. package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
  45. package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
  46. package/package.json +3 -1
  47. package/spec/mcp-tools.yaml +213 -7
  48. package/dist/logs-5A7YYA53.js +0 -15
  49. package/dist/ssh-VAI5DU5M.js +0 -11
  50. package/dist/upgrade-ULD4KFYC.js +0 -88
  51. /package/dist/{chunk-NUL5F6YV.js → chunk-SOZ62ZXW.js} +0 -0
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ execPrivileged,
4
+ execPrivilegedWithInput,
5
+ streamPrivileged
6
+ } from "./chunk-WPZIJONN.js";
7
+ import {
8
+ CONTAINER_UID,
9
+ stateDirForOS
10
+ } from "./chunk-N5ZYUTZS.js";
11
+
12
+ // src/openclaw/backup-flows.ts
13
+ import { createReadStream, createWriteStream, statSync } from "fs";
14
+ import { pipeline } from "stream/promises";
15
+
16
+ // src/openclaw/restore.ts
17
+ var SUPPORTED_MANIFEST_SCHEMA = 1;
18
+ function locateRestoredState(manifestJson, bundleRoot) {
19
+ let manifest;
20
+ try {
21
+ manifest = JSON.parse(manifestJson);
22
+ } catch {
23
+ return { ok: false, reason: "The restored archive has no readable manifest.json." };
24
+ }
25
+ if (manifest.schemaVersion !== SUPPORTED_MANIFEST_SCHEMA) {
26
+ return {
27
+ ok: false,
28
+ reason: `This archive declares manifest schemaVersion ${String(manifest.schemaVersion)}, and clawops has only been tested against ${SUPPORTED_MANIFEST_SCHEMA}. Its layout may have changed, so clawops will not move it into place. The archive is expanded and can be adopted by hand.`
29
+ };
30
+ }
31
+ const archiveRoot = typeof manifest.archiveRoot === "string" ? manifest.archiveRoot : "";
32
+ const stateDir = typeof manifest.paths?.stateDir === "string" ? manifest.paths.stateDir : "";
33
+ if (!archiveRoot || !stateDir) {
34
+ return { ok: false, reason: "The manifest names no archiveRoot or stateDir, so clawops cannot tell which directory to adopt." };
35
+ }
36
+ return {
37
+ ok: true,
38
+ stateDirInArchive: stateDir,
39
+ statePath: `${bundleRoot}/${archiveRoot}/payload/posix/${stateDir.replace(/^\/+/, "")}`
40
+ };
41
+ }
42
+ async function move(exec, from, to) {
43
+ const r = await exec(`mv ${shellArg(from)} ${shellArg(to)}`);
44
+ if (r.code === 0) return null;
45
+ return (r.stderr || r.stdout).trim().split("\n").slice(-1)[0] || `mv exited ${r.code}`;
46
+ }
47
+ async function activateRestored(opts) {
48
+ const { stateDir, staging, exec, restart, waitHealthy } = opts;
49
+ const stamp = new Date(opts.now?.() ?? Date.now()).toISOString().replace(/[:.]/g, "-");
50
+ const parent = stateDir.replace(/\/+$/, "").replace(/\/[^/]+$/, "") || "/";
51
+ const preserved = `${stateDir}.pre-restore-${stamp}`;
52
+ const holding = `${parent}/.clawops-restored-${stamp}`;
53
+ const exists = await exec(`test -d ${shellArg(staging)} && echo yes || echo no`);
54
+ if (exists.stdout.trim() !== "yes") {
55
+ return { ok: false, reason: `The restored archive is not at ${staging}.`, rolledBack: false };
56
+ }
57
+ const outFirst = await move(exec, staging, holding);
58
+ if (outFirst) {
59
+ return { ok: false, reason: `Could not move the restored state clear of the live directory: ${outFirst}`, rolledBack: false };
60
+ }
61
+ const asideErr = await move(exec, stateDir, preserved);
62
+ if (asideErr) {
63
+ await move(exec, holding, staging);
64
+ return { ok: false, reason: `Could not set the current state aside: ${asideErr}`, rolledBack: false };
65
+ }
66
+ const inErr = await move(exec, holding, stateDir);
67
+ if (inErr) {
68
+ await move(exec, preserved, stateDir);
69
+ return { ok: false, reason: `Could not move the restored state into place: ${inErr}`, rolledBack: true };
70
+ }
71
+ try {
72
+ await restart();
73
+ await waitHealthy();
74
+ return { ok: true, preserved };
75
+ } catch (err) {
76
+ const keptFailed = `${stateDir}.failed-restore-${stamp}`;
77
+ const reason = err instanceof Error ? err.message : String(err);
78
+ const parkErr = await move(exec, stateDir, keptFailed);
79
+ if (parkErr) {
80
+ return {
81
+ ok: false,
82
+ rolledBack: false,
83
+ preserved,
84
+ reason: `The gateway did not come up (${reason}), and the restored state could not be moved aside to roll back (${parkErr}). The previous state is at ${preserved}.`
85
+ };
86
+ }
87
+ const backErr = await move(exec, preserved, stateDir);
88
+ if (backErr) {
89
+ return {
90
+ ok: false,
91
+ rolledBack: false,
92
+ keptFailed,
93
+ reason: `The gateway did not come up (${reason}), and the previous state could not be put back (${backErr}). It is at ${preserved}; the restored state is at ${keptFailed}.`
94
+ };
95
+ }
96
+ await restart().catch(() => void 0);
97
+ return {
98
+ ok: false,
99
+ rolledBack: true,
100
+ keptFailed,
101
+ reason: `The gateway did not come up with the restored state (${reason}). The previous state has been put back.`
102
+ };
103
+ }
104
+ }
105
+ async function hasRoomFor(exec, dir, bytes) {
106
+ const r = await exec(`df -Pk ${shellArg(dir)} | tail -1 | awk '{print $4}'`);
107
+ const availableKb = Number.parseInt(r.stdout.trim(), 10);
108
+ if (!Number.isFinite(availableKb)) return { ok: true };
109
+ const availableBytes = availableKb * 1024;
110
+ return availableBytes >= bytes ? { ok: true } : { ok: false, availableBytes, neededBytes: bytes };
111
+ }
112
+ function shellArg(value) {
113
+ return `'${value.replace(/'/g, `'\\''`)}'`;
114
+ }
115
+
116
+ // src/openclaw/backup-flows.ts
117
+ function defaultBackupFilename(now = /* @__PURE__ */ new Date()) {
118
+ return `openclaw-backup-${now.toISOString().replace(/[:.]/g, "-")}.tar.gz`;
119
+ }
120
+ var ARCHIVE_IS_A_CREDENTIAL = [
121
+ "Saved with mode 0600. The archive contains the state database \u2014 OAuth tokens,",
122
+ "secrets and device credentials, unencrypted. Treat it as a credential."
123
+ ];
124
+ var PLUGINS_NOT_CARRIED = [
125
+ "Provider plugins are not carried in the archive; re-run `clawops apply` to",
126
+ "reinstall them, or the gateway starts without its model providers."
127
+ ];
128
+ function nothingChanged(stagingOnHost) {
129
+ return `The expanded archive is at ${stagingOnHost}; nothing has been changed.`;
130
+ }
131
+ function mib(bytes) {
132
+ return `${Math.round(bytes / 1024 / 1024)} MiB`;
133
+ }
134
+ async function createBackup(session, opts) {
135
+ const { outPath, signal } = opts;
136
+ opts.onPhase?.("creating");
137
+ const remoteArchive = "/tmp/clawops-backup.tar.gz";
138
+ const createResult = await execPrivileged(
139
+ session,
140
+ `docker exec openclaw sh -lc 'rm -f ${remoteArchive} && openclaw backup create --output ${remoteArchive} --verify --json'`,
141
+ signal
142
+ );
143
+ if (createResult.code !== 0) {
144
+ return { ok: false, reason: `Backup failed on the remote host: ${createResult.stderr.slice(0, 300)}` };
145
+ }
146
+ const backupStream = await streamPrivileged(session, `docker exec openclaw cat ${remoteArchive}`, signal);
147
+ opts.onPhase?.("downloading");
148
+ const fileStream = createWriteStream(outPath, { mode: 384 });
149
+ await pipeline(backupStream, fileStream);
150
+ await execPrivileged(session, `docker exec openclaw rm -f ${remoteArchive}`, signal);
151
+ return { ok: true, path: outPath };
152
+ }
153
+ async function stageRestore(session, opts) {
154
+ const { file, signal } = opts;
155
+ const remoteArchive = "/tmp/clawops-restore.tar.gz";
156
+ const stamp = (opts.now ?? Date.now)();
157
+ const stagingInContainer = `/tmp/clawops-restored-${stamp}`;
158
+ const hostExec = (command) => execPrivileged(session, command, signal);
159
+ const osProbe = await session.exec("uname -s", signal);
160
+ const stateDir = stateDirForOS(osProbe.stdout.trim() === "Darwin" ? "Darwin" : "Linux");
161
+ const stateParent = stateDir.replace(/\/+$/, "").replace(/\/[^/]+$/, "") || "/";
162
+ const stagingOnHost = `${stateParent}/.clawops-restored-${stamp}`;
163
+ const archiveBytes = statSync(file).size;
164
+ const room = await hasRoomFor(hostExec, stateDir, archiveBytes * 3);
165
+ if (!room.ok) {
166
+ return {
167
+ ok: false,
168
+ kind: "refused",
169
+ reason: `Not enough free space on the host to expand this archive: ${mib(room.availableBytes)} available where the state lives, and the restore needs about ${mib(room.neededBytes)}. Free some space and run this again; nothing has been changed.`
170
+ };
171
+ }
172
+ opts.onPhase?.("uploading");
173
+ const uploadResult = await execPrivilegedWithInput(
174
+ session,
175
+ `docker exec -i openclaw sh -c 'cat > ${remoteArchive}'`,
176
+ createReadStream(file),
177
+ signal
178
+ );
179
+ if (uploadResult.code !== 0) {
180
+ return { ok: false, kind: "failed", reason: `Could not upload the archive: ${uploadResult.stderr.slice(0, 300)}` };
181
+ }
182
+ opts.onPhase?.("restoring");
183
+ const restore = await execPrivileged(
184
+ session,
185
+ `docker exec openclaw openclaw backup restore ${remoteArchive} --target ${stagingInContainer} --json`,
186
+ signal
187
+ );
188
+ await execPrivileged(session, `docker exec openclaw rm -f ${remoteArchive}`, signal);
189
+ if (restore.code !== 0) {
190
+ return { ok: false, kind: "failed", reason: `Restore failed: ${(restore.stderr || restore.stdout).slice(0, 400)}` };
191
+ }
192
+ const copyOut = await hostExec(`docker cp openclaw:${stagingInContainer} ${stagingOnHost}`);
193
+ await execPrivileged(session, `docker exec openclaw rm -rf ${stagingInContainer}`, signal);
194
+ if (copyOut.code !== 0) {
195
+ return {
196
+ ok: false,
197
+ kind: "failed",
198
+ reason: `The archive was restored inside the container but could not be copied to the host: ${(copyOut.stderr || copyOut.stdout).slice(0, 300)}`
199
+ };
200
+ }
201
+ let report = {};
202
+ let unparsedReport;
203
+ try {
204
+ report = JSON.parse(restore.stdout.trim());
205
+ } catch {
206
+ unparsedReport = restore.stdout;
207
+ }
208
+ const manifestRead = await hostExec(`cat ${stagingOnHost}/*/manifest.json`);
209
+ const located = locateRestoredState(manifestRead.stdout, stagingOnHost);
210
+ if (!located.ok) {
211
+ return {
212
+ ok: false,
213
+ kind: "unadoptable",
214
+ reason: located.reason,
215
+ stagingOnHost,
216
+ ...unparsedReport !== void 0 ? { unparsedReport } : {}
217
+ };
218
+ }
219
+ return {
220
+ ok: true,
221
+ stateDir,
222
+ stagingOnHost,
223
+ statePath: located.statePath,
224
+ stateDirInArchive: located.stateDirInArchive,
225
+ ...report.entryCount !== void 0 ? { entryCount: report.entryCount } : {},
226
+ warnings: report.warnings ?? [],
227
+ ...unparsedReport !== void 0 ? { unparsedReport } : {}
228
+ };
229
+ }
230
+ async function activateStaged(session, staged, opts = {}) {
231
+ const { signal } = opts;
232
+ const hostExec = (command) => execPrivileged(session, command, signal);
233
+ const { restartGateway } = await import("./remote-config-OZYBMBSK.js");
234
+ const { waitForGateway } = await import("./ready-IJEUBFVX.js");
235
+ opts.onPhase?.("activating");
236
+ const chown = await hostExec(`chown -R ${CONTAINER_UID}:${CONTAINER_UID} ${staged.statePath}`);
237
+ if (chown.code !== 0) {
238
+ opts.onPhase?.("done");
239
+ return {
240
+ ok: false,
241
+ reason: `Could not give the restored state to the gateway's user: ${(chown.stderr || chown.stdout).slice(0, 200)}`,
242
+ stagingOnHost: staged.stagingOnHost
243
+ };
244
+ }
245
+ const outcome = await activateRestored({
246
+ stateDir: staged.stateDir,
247
+ staging: staged.statePath,
248
+ exec: hostExec,
249
+ restart: async () => {
250
+ opts.onPhase?.("restarting");
251
+ await restartGateway(session, signal);
252
+ },
253
+ waitHealthy: async () => {
254
+ opts.onPhase?.("waiting");
255
+ await waitForGateway(session, { ...signal ? { signal } : {}, timeoutMs: 18e4 });
256
+ }
257
+ });
258
+ opts.onPhase?.("done");
259
+ if (!outcome.ok) {
260
+ return {
261
+ ok: false,
262
+ reason: outcome.reason,
263
+ ...outcome.keptFailed ? { keptFailed: outcome.keptFailed } : {},
264
+ ...outcome.preserved ? { preserved: outcome.preserved } : {}
265
+ };
266
+ }
267
+ await hostExec(`rm -rf ${staged.stagingOnHost}`);
268
+ return { ok: true, preserved: outcome.preserved };
269
+ }
270
+ function activationFailureDetail(outcome) {
271
+ const lines = [];
272
+ if (outcome.stagingOnHost) lines.push(nothingChanged(outcome.stagingOnHost));
273
+ if (outcome.keptFailed) lines.push(`The restored state was kept at ${outcome.keptFailed}.`);
274
+ if (outcome.preserved) lines.push(`The previous state is at ${outcome.preserved}.`);
275
+ return lines;
276
+ }
277
+ function activatedLines(preserved) {
278
+ return [
279
+ `The state it replaced is at ${preserved}.`,
280
+ "Remove that directory once you are satisfied; clawops will not.",
281
+ ...PLUGINS_NOT_CARRIED
282
+ ];
283
+ }
284
+ var ACTIVATED = "The restored state is live and the gateway is answering.";
285
+
286
+ export {
287
+ defaultBackupFilename,
288
+ ARCHIVE_IS_A_CREDENTIAL,
289
+ PLUGINS_NOT_CARRIED,
290
+ nothingChanged,
291
+ createBackup,
292
+ stageRestore,
293
+ activateStaged,
294
+ activationFailureDetail,
295
+ activatedLines,
296
+ ACTIVATED
297
+ };
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/errors/pulumi.ts
4
+ function pulumiCause(err) {
5
+ const raw = err instanceof Error ? err.message : String(err);
6
+ const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l !== "");
7
+ const explained = lines.find((l) => /(^|\s)error:/i.test(l));
8
+ if (explained) return explained.replace(/^.*?error:\s*/i, "");
9
+ return lines[0] ?? raw;
10
+ }
11
+
12
+ export {
13
+ pulumiCause
14
+ };
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ pulumiCause
4
+ } from "./chunk-POYS3EVQ.js";
5
+ import {
6
+ StateError
7
+ } from "./chunk-KGXPLI7W.js";
8
+
9
+ // src/transport/conn.ts
10
+ async function resolveConn(ctx) {
11
+ if (ctx.adapter.name === "local") {
12
+ const state = ctx.localState;
13
+ if (!state) throw new StateError("Stack has no local state \u2014 run `clawops up` first.");
14
+ return {
15
+ host: state.sshHost,
16
+ port: state.sshPort,
17
+ user: state.sshUser,
18
+ privateKeyPath: state.privateKeyPath,
19
+ knownHostsPath: state.knownHostsPath
20
+ };
21
+ }
22
+ const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
23
+ let outputs;
24
+ try {
25
+ const stack = await ctx.getStack();
26
+ const outputMap = await stack.outputs();
27
+ outputs = Object.fromEntries(Object.entries(outputMap).map(([k, v]) => [k, v.value]));
28
+ } catch (err) {
29
+ throw new StateError(
30
+ `Could not read the state of stack "${ctx.stackName}": ${pulumiCause(err)}. Check that the credentials for its state backend are in this process's environment (for an MCP server, the env block of its client config).`
31
+ );
32
+ }
33
+ if (!outputs["publicIp"]) {
34
+ throw new StateError("Stack has no outputs \u2014 run `clawops up` first.");
35
+ }
36
+ const base = extractBaseOutputs(outputs);
37
+ return ctx.adapter.getConnectionInfo({
38
+ ...base,
39
+ privateKeyPath: ctx.config.ssh.keyPath,
40
+ knownHostsPath: ctx.config.ssh.knownHostsPath
41
+ });
42
+ }
43
+
44
+ export {
45
+ resolveConn
46
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  connect
4
- } from "./chunk-E4NJSJ54.js";
4
+ } from "./chunk-4PY6PN4G.js";
5
5
  import {
6
6
  NetworkError
7
7
  } from "./chunk-KGXPLI7W.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  execPrivileged
4
- } from "./chunk-NUDIHZEH.js";
4
+ } from "./chunk-WPZIJONN.js";
5
5
  import {
6
6
  CONTAINER_UID,
7
7
  PUBLISH_INSPECT_CMD,
@@ -9,7 +9,7 @@ import {
9
9
  gatewayRunCommand,
10
10
  publishForRestart,
11
11
  stateDirForOS
12
- } from "./chunk-GIEVAGEK.js";
12
+ } from "./chunk-N5ZYUTZS.js";
13
13
  import {
14
14
  GATEWAY_PORT,
15
15
  IMAGE_INSPECT_CMD,
@@ -13,8 +13,19 @@ function looksLikePermissionDenied(result) {
13
13
  return text.includes("permission denied") || text.includes("docker.sock") || text.includes("operation not permitted");
14
14
  }
15
15
  var sudoNeeded = /* @__PURE__ */ new WeakMap();
16
+ var dockerSudoNeeded = /* @__PURE__ */ new WeakMap();
16
17
  function resetPrivilegeCache(session) {
17
18
  sudoNeeded.delete(session);
19
+ dockerSudoNeeded.delete(session);
20
+ }
21
+ async function dockerNeedsSudo(session, signal) {
22
+ const key = session;
23
+ const known = dockerSudoNeeded.get(key);
24
+ if (known !== void 0) return known;
25
+ const probe = await session.exec('docker version --format "{{.Server.Version}}"', signal);
26
+ const needed = looksLikePermissionDenied(probe);
27
+ dockerSudoNeeded.set(key, needed);
28
+ return needed;
18
29
  }
19
30
  async function execPrivileged(session, cmd, signal) {
20
31
  const key = session;
@@ -29,20 +40,11 @@ async function execPrivileged(session, cmd, signal) {
29
40
  return escalated;
30
41
  }
31
42
  async function streamPrivileged(session, cmd, signal) {
32
- const key = session;
33
- if (sudoNeeded.get(key) === void 0) {
34
- const probe = await session.exec('docker version --format "{{.Server.Version}}"', signal);
35
- sudoNeeded.set(key, looksLikePermissionDenied(probe));
36
- }
37
- return session.stream(sudoNeeded.get(key) ? sudoWrap(cmd) : cmd, signal);
43
+ return session.stream(await dockerNeedsSudo(session, signal) ? sudoWrap(cmd) : cmd, signal);
38
44
  }
39
45
  async function execPrivilegedWithInput(session, cmd, input, signal) {
40
- const key = session;
41
- if (sudoNeeded.get(key) === void 0) {
42
- const probe = await session.exec('docker version --format "{{.Server.Version}}"', signal);
43
- sudoNeeded.set(key, looksLikePermissionDenied(probe));
44
- }
45
- return session.execWithInput(sudoNeeded.get(key) ? sudoWrap(cmd) : cmd, input, signal);
46
+ const escalate = await dockerNeedsSudo(session, signal);
47
+ return session.execWithInput(escalate ? sudoWrap(cmd) : cmd, input, signal);
46
48
  }
47
49
 
48
50
  export {
@@ -7,7 +7,7 @@ import {
7
7
  CONTAINER_UID,
8
8
  STATE_DIR_HOST_LINUX,
9
9
  gatewayRunCommand
10
- } from "./chunk-GIEVAGEK.js";
10
+ } from "./chunk-N5ZYUTZS.js";
11
11
  import {
12
12
  GATEWAY_PORT
13
13
  } from "./chunk-VOEJYRIK.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  execPrivileged
4
- } from "./chunk-NUDIHZEH.js";
4
+ } from "./chunk-WPZIJONN.js";
5
5
 
6
6
  // src/openclaw/docker.ts
7
7
  function looksLikeStillBooting(detail) {