@nomac/cli 0.11.2 → 0.11.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomac/cli",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "description": "Cloud Mac sessions over SSH and HTTPS, plus iOS builds and publishing — built for agents.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -4,8 +4,8 @@ import { join, resolve } from "node:path";
4
4
  import { parseArgs } from "node:util";
5
5
  import { api, CliError } from "./api.mjs";
6
6
  import { sessionStore } from "./session-store.mjs";
7
- import { forwardingArg, runProcess, sshArgs, workspaceCommand } from "./session-ssh.mjs";
8
- import { pullProject, syncProject } from "./session-transfer.mjs";
7
+ import { forwardingArg, isShellLine, runProcess, sshArgs, workspaceCommand } from "./session-ssh.mjs";
8
+ import { describePull, describeSync, pullProject, syncProject } from "./session-transfer.mjs";
9
9
  import { macRequest, sessionId, transportOptions } from "./mac-transport.mjs";
10
10
  import { copyToClipboard, desktop, DESKTOP_HELP, screenSharing } from "./desktop.mjs";
11
11
  import { screenshot, startRecording, stopRecording } from "./mac-screen.mjs";
@@ -386,7 +386,7 @@ export async function hostedSessionCommand(command, argv, { store = sessionStore
386
386
  // A command may run an hour (the gateway's limit); waiting can cover all of it.
387
387
  const waitFor = integer(values.wait, 30, 0, 3600);
388
388
  const timeout = integer(values.timeout, values["request-key"] === undefined ? Math.max(300, waitFor) : 600, 1, 3600);
389
- method = "POST"; path = `${base}/jobs`; params = { requestKey: values["request-key"] ?? `cli-${randomUUID()}`, body: { argv: positionals, timeout_seconds: timeout } };
389
+ method = "POST"; path = `${base}/jobs`; params = { requestKey: values["request-key"] ?? `cli-${randomUUID()}`, body: { argv: isShellLine(positionals) ? ["/bin/bash", "-lc", positionals[0]] : positionals, timeout_seconds: timeout } };
390
390
  } else {
391
391
  if (positionals.length !== 1 || !/^job_[a-f0-9]{40}$/.test(positionals[0])) throw new CliError("Supply the existing job ID.");
392
392
  path = `${base}/jobs/${positionals[0]}`;
@@ -583,11 +583,15 @@ export async function hostedSessionCommand(command, argv, { store = sessionStore
583
583
  await ready(local);
584
584
  if (command === "sync") {
585
585
  if (positionals.length > 1 || (positionals[0] && resolve(positionals[0]) !== store.project)) throw new CliError("Run nomac sync . from the attached project.");
586
- return output(await syncProject(local, store, { overwrite: values.overwrite, dryRun: values["dry-run"], run }));
586
+ const result = await syncProject(local, store, { overwrite: values.overwrite, dryRun: values["dry-run"], run });
587
+ if (!values.json) { print(describeSync(result)); return result; }
588
+ return output(result);
587
589
  }
588
590
  if (command === "pull") {
589
591
  if (positionals.length !== 1) throw new CliError("Usage: nomac pull LOCAL_PATH [--from REMOTE_PATH]");
590
- return output(await pullProject(local, store, positionals[0], { from: values.from ?? positionals[0], overwrite: values.overwrite, dryRun: values["dry-run"], run }));
592
+ const result = await pullProject(local, store, positionals[0], { from: values.from ?? positionals[0], overwrite: values.overwrite, dryRun: values["dry-run"], run });
593
+ if (!values.json) { print(describePull(result)); return result; }
594
+ return output(result);
591
595
  }
592
596
  throw new CliError("Unknown cloud session command.");
593
597
  });
@@ -127,8 +127,14 @@ export async function prepareWorkspace(session, options) {
127
127
  return { workspace, capabilities: { os: "macOS", version: osVersion, architecture, administrator: admin, available_disk_bytes: Number(availableKb) * 1024 } };
128
128
  }
129
129
 
130
+ /**
131
+ * One argument with spaces or shell syntax is a shell command line, as ssh host 'a; b' runs it;
132
+ * several are a program and its arguments, passed exactly.
133
+ */
134
+ export const isShellLine = (args) => args.length === 1 && /[\s;&|<>()$`*?{}~#'"\\]/.test(args[0]);
135
+
130
136
  export function workspaceCommand(session, args = []) {
131
- const command = args.length ? args.map(shellQuote).join(" ") : '"$SHELL" -l';
137
+ const command = !args.length ? '"$SHELL" -l' : isShellLine(args) ? `"$SHELL" -lc ${shellQuote(args[0])}` : args.map(shellQuote).join(" ");
132
138
  return `cd ${shellQuote(validateWorkspace(session.workspace))} && exec ${command}`;
133
139
  }
134
140
 
@@ -186,6 +186,9 @@ export function describeSync(result) {
186
186
  return lines.join("\n");
187
187
  }
188
188
 
189
+ /** The human summary of a pull. */
190
+ export const describePull = (result) => `${result.dry_run ? "Would retrieve" : "Retrieved"} ${result.changed.length} changed files (${result.files} considered).${result.recovery_directory ? `\nPrevious local files: ${result.recovery_directory}` : ""}`;
191
+
189
192
  const transferEnv = (session, remoteDirectory = ".") => ({ ...process.env, NOMAC_TRANSFER_SESSION: JSON.stringify({ ...session, remoteDirectory }) });
190
193
 
191
194
  function conflictError(paths, side) {
package/src/sessions.mjs CHANGED
@@ -5,7 +5,7 @@ import { parseArgs } from "node:util";
5
5
  import { CliError } from "./api.mjs";
6
6
  import { sessionStore } from "./session-store.mjs";
7
7
  import { forwardingArg, prepareWorkspace, runProcess, shellQuote, sshArgs, validateTarget, workspaceCommand } from "./session-ssh.mjs";
8
- import { describeSync, pullProject, syncProject } from "./session-transfer.mjs";
8
+ import { describePull, describeSync, pullProject, syncProject } from "./session-transfer.mjs";
9
9
 
10
10
  export const SESSION_HELP = `Nomac 2.0 — SSH workspace preview
11
11
 
@@ -137,7 +137,7 @@ export async function sessionCommand(command, argv, { store: suppliedStore, run
137
137
  if (command === "pull") {
138
138
  if (positionals.length !== 1) throw new CliError("pull usage", "Usage: nomac pull LOCAL_DIRECTORY [--from REMOTE_DIRECTORY] [--dry-run] [--overwrite]");
139
139
  const result = await pullProject(session, store, positionals[0], { from: values.from ?? positionals[0], overwrite: values.overwrite, dryRun: values["dry-run"], run });
140
- return output(result, `${result.dry_run ? "Would retrieve" : "Retrieved"} ${result.changed.length} changed files (${result.files} considered).${result.recovery_directory ? `\nPrevious local files: ${result.recovery_directory}` : ""}`);
140
+ return output(result, describePull(result));
141
141
  }
142
142
  throw new CliError("unknown session command");
143
143
  });