@swmansion/argent 0.16.2-next.9 → 0.17.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.
@@ -109,7 +109,9 @@ var init_artifacts = __esm({
109
109
  ".xml": "application/xml",
110
110
  ".txt": "text/plain",
111
111
  ".md": "text/markdown",
112
- ".html": "text/html"
112
+ ".html": "text/html",
113
+ ".mp4": "video/mp4",
114
+ ".mov": "video/quicktime"
113
115
  };
114
116
  ArtifactStore = class {
115
117
  entries = /* @__PURE__ */ new Map();
@@ -138,6 +140,7 @@ var init_artifacts = __esm({
138
140
  };
139
141
  if (mtimeMs != null) handle.mtimeMs = mtimeMs;
140
142
  if (isDirectory2) handle.archive = "tar.gz";
143
+ if (opts?.saveDir) handle.saveDir = opts.saveDir;
141
144
  return handle;
142
145
  }
143
146
  get(id) {
@@ -382,6 +385,7 @@ var init_failure_codes = __esm({
382
385
  SCREEN_RECORDING_NO_ACTIVE_SESSION: "SCREEN_RECORDING_NO_ACTIVE_SESSION",
383
386
  SCREEN_RECORDING_STOP_IN_PROGRESS: "SCREEN_RECORDING_STOP_IN_PROGRESS",
384
387
  SCREEN_RECORDING_START_EXITED: "SCREEN_RECORDING_START_EXITED",
388
+ SCREEN_RECORDING_START_TIMEOUT: "SCREEN_RECORDING_START_TIMEOUT",
385
389
  SCREEN_RECORDING_PROCESS_ERROR: "SCREEN_RECORDING_PROCESS_ERROR",
386
390
  SCREEN_RECORDING_OUTPUT_MISSING: "SCREEN_RECORDING_OUTPUT_MISSING",
387
391
  SCREEN_RECORDING_SERVER_SHUTTING_DOWN: "SCREEN_RECORDING_SERVER_SHUTTING_DOWN",
@@ -34931,7 +34935,7 @@ var require_view = __commonJS({
34931
34935
  var path38 = require("node:path");
34932
34936
  var fs50 = require("node:fs");
34933
34937
  var dirname14 = path38.dirname;
34934
- var basename9 = path38.basename;
34938
+ var basename10 = path38.basename;
34935
34939
  var extname4 = path38.extname;
34936
34940
  var join46 = path38.join;
34937
34941
  var resolve7 = path38.resolve;
@@ -34970,7 +34974,7 @@ var require_view = __commonJS({
34970
34974
  var root2 = roots[i];
34971
34975
  var loc = resolve7(root2, name);
34972
34976
  var dir = dirname14(loc);
34973
- var file2 = basename9(loc);
34977
+ var file2 = basename10(loc);
34974
34978
  path39 = this.resolve(dir, file2);
34975
34979
  }
34976
34980
  return path39;
@@ -35000,7 +35004,7 @@ var require_view = __commonJS({
35000
35004
  if (stat4 && stat4.isFile()) {
35001
35005
  return path39;
35002
35006
  }
35003
- path39 = join46(dir, basename9(file2, ext), "index" + ext);
35007
+ path39 = join46(dir, basename10(file2, ext), "index" + ext);
35004
35008
  stat4 = tryStat(path39);
35005
35009
  if (stat4 && stat4.isFile()) {
35006
35010
  return path39;
@@ -57445,9 +57449,9 @@ var require_content_disposition = __commonJS({
57445
57449
  if (typeof fallback === "string" && NON_LATIN1_REGEXP.test(fallback)) {
57446
57450
  throw new TypeError("fallback must be ISO-8859-1 string");
57447
57451
  }
57448
- var name = basename9(filename);
57452
+ var name = basename10(filename);
57449
57453
  var isQuotedString = TEXT_REGEXP.test(name);
57450
- var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename9(fallback);
57454
+ var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename10(fallback);
57451
57455
  var hasFallback = typeof fallbackName === "string" && fallbackName !== name;
57452
57456
  if (hasFallback || !isQuotedString || hasHexEscape(name)) {
57453
57457
  params["filename*"] = name;
@@ -57567,7 +57571,7 @@ var require_content_disposition = __commonJS({
57567
57571
  this.type = type;
57568
57572
  this.parameters = parameters;
57569
57573
  }
57570
- function basename9(path38) {
57574
+ function basename10(path38) {
57571
57575
  const normalized = path38.replaceAll("\\", "/");
57572
57576
  let end = normalized.length;
57573
57577
  while (end > 0 && normalized[end - 1] === "/") {
@@ -104489,7 +104493,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
104489
104493
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
104490
104494
  function readCliVersion() {
104491
104495
  if (true) {
104492
- return "0.16.1";
104496
+ return "0.17.0";
104493
104497
  }
104494
104498
  return "0.0.0";
104495
104499
  }
@@ -105877,7 +105881,7 @@ var import_node_path5 = __toESM(require("node:path"));
105877
105881
  var import_semver = __toESM(require_semver2());
105878
105882
 
105879
105883
  // ../tool-server/package.json
105880
- var version3 = "0.16.1";
105884
+ var version3 = "0.17.0";
105881
105885
 
105882
105886
  // ../tool-server/src/utils/update-checker.ts
105883
105887
  var import_update_core = __toESM(require_dist4());
@@ -114038,6 +114042,27 @@ function sendCommand(api, cmd) {
114038
114042
  ws.once("open", () => ws.send(payload));
114039
114043
  }
114040
114044
  }
114045
+ function setPointerVisible(api, show, signal) {
114046
+ return pointerPost(api, { show }, signal);
114047
+ }
114048
+ function setPointerTrail(api, trail, signal) {
114049
+ return pointerPost(api, { trail }, signal);
114050
+ }
114051
+ async function pointerPost(api, body, signal) {
114052
+ try {
114053
+ const res = await fetch(`${api.apiUrl}/api/pointer`, {
114054
+ method: "POST",
114055
+ headers: { "Content-Type": "application/json" },
114056
+ body: JSON.stringify(body),
114057
+ signal
114058
+ });
114059
+ if (!res.ok) return false;
114060
+ const parsed = await res.json().catch(() => null);
114061
+ return parsed?.status === "ok";
114062
+ } catch {
114063
+ return false;
114064
+ }
114065
+ }
114041
114066
  async function simulatorPost(toolLabel, api, endpoint, reqBody, signal, fallbackHint) {
114042
114067
  let res;
114043
114068
  try {
@@ -142986,6 +143011,8 @@ function clearLiveState2(state3) {
142986
143011
  state3.pendingChild = null;
142987
143012
  state3.frameStream = null;
142988
143013
  state3.lastFrameStreamError = null;
143014
+ state3.pointerDisable = null;
143015
+ state3.pointerFailed = false;
142989
143016
  state3.recordingTimedOut = false;
142990
143017
  state3.recordingExitedUnexpectedly = false;
142991
143018
  state3.lastExitInfo = null;
@@ -143039,6 +143066,8 @@ var screenRecordingSessionBlueprint = {
143039
143066
  trimStatic: true,
143040
143067
  framesWritten: 0,
143041
143068
  trimmedAnyFrames: false,
143069
+ pointerDisable: null,
143070
+ pointerFailed: false,
143042
143071
  wallClockStartMs: null,
143043
143072
  wallClockEndMs: null,
143044
143073
  timeLimitSeconds: null,
@@ -143061,6 +143090,12 @@ var screenRecordingSessionBlueprint = {
143061
143090
  state3.pumpTimer = null;
143062
143091
  }
143063
143092
  state3.frameStream?.close();
143093
+ if (state3.pointerDisable) {
143094
+ const disable = state3.pointerDisable;
143095
+ state3.pointerDisable = null;
143096
+ await disable().catch(() => {
143097
+ });
143098
+ }
143064
143099
  if (state3.pendingChild) {
143065
143100
  try {
143066
143101
  state3.pendingChild.kill("SIGKILL");
@@ -143593,6 +143628,12 @@ function startPump(api, stream) {
143593
143628
  }
143594
143629
  }, FRAME_INTERVAL_MS);
143595
143630
  }
143631
+ async function disablePointer(api) {
143632
+ const disable = api.pointerDisable;
143633
+ api.pointerDisable = null;
143634
+ if (disable) await disable().catch(() => {
143635
+ });
143636
+ }
143596
143637
  function stopPump(api) {
143597
143638
  if (api.pumpTimer) {
143598
143639
  clearInterval(api.pumpTimer);
@@ -143687,6 +143728,7 @@ async function startCaptureLocked(api, params) {
143687
143728
  api.recordingTimedOut = false;
143688
143729
  api.recordingExitedUnexpectedly = false;
143689
143730
  api.pendingRetrieval = false;
143731
+ api.pointerFailed = false;
143690
143732
  api.lastExitInfo = null;
143691
143733
  api.lastFrameStreamError = null;
143692
143734
  api.outputFile = outputFile;
@@ -143702,16 +143744,6 @@ async function startCaptureLocked(api, params) {
143702
143744
  api.timeLimitSeconds = params.timeLimitSeconds;
143703
143745
  registerActiveScreenRecording(api.deviceId, api.wallClockStartMs, params.timeLimitSeconds);
143704
143746
  startPump(api, stream);
143705
- api.recordingTimeout = setTimeout(() => {
143706
- api.recordingTimeout = null;
143707
- if (api.captureProcess !== child) return;
143708
- api.recordingTimedOut = true;
143709
- api.recordingActive = false;
143710
- api.wallClockEndMs = Date.now();
143711
- api.pendingRetrieval = true;
143712
- markScreenRecordingFinalized(api.deviceId, `it hit its ${params.timeLimitSeconds}s time limit`);
143713
- finalizeCapture(api);
143714
- }, params.timeLimitSeconds * 1e3);
143715
143747
  child.on("exit", (code, signal) => {
143716
143748
  if (api.captureProcess !== child) return;
143717
143749
  api.lastExitInfo = { code, signal };
@@ -143726,9 +143758,25 @@ async function startCaptureLocked(api, params) {
143726
143758
  api.wallClockEndMs = Date.now();
143727
143759
  api.pendingRetrieval = true;
143728
143760
  stopPump(api);
143761
+ void disablePointer(api);
143729
143762
  markScreenRecordingFinalized(api.deviceId, "the recording process exited unexpectedly");
143730
143763
  }
143731
143764
  });
143765
+ if (params.pointer) {
143766
+ api.pointerDisable = params.pointer.disable;
143767
+ api.pointerFailed = !await params.pointer.enable();
143768
+ }
143769
+ api.recordingTimeout = setTimeout(() => {
143770
+ api.recordingTimeout = null;
143771
+ if (api.captureProcess !== child) return;
143772
+ api.recordingTimedOut = true;
143773
+ api.recordingActive = false;
143774
+ api.wallClockEndMs = Date.now();
143775
+ api.pendingRetrieval = true;
143776
+ markScreenRecordingFinalized(api.deviceId, `it hit its ${params.timeLimitSeconds}s time limit`);
143777
+ finalizeCapture(api);
143778
+ void disablePointer(api);
143779
+ }, params.timeLimitSeconds * 1e3);
143732
143780
  return {
143733
143781
  status: "recording",
143734
143782
  timeLimitSeconds: params.timeLimitSeconds,
@@ -143795,6 +143843,7 @@ async function stopCapture(api) {
143795
143843
  const endedEarly = api.recordingTimedOut || api.recordingExitedUnexpectedly;
143796
143844
  const streamError = api.frameStream?.error ?? api.lastFrameStreamError ?? null;
143797
143845
  const watermarkSkipped = api.watermarkSkipped;
143846
+ const pointerFailed = api.pointerFailed;
143798
143847
  let warning;
143799
143848
  try {
143800
143849
  const child = api.captureProcess;
@@ -143828,6 +143877,12 @@ async function stopCapture(api) {
143828
143877
  if (watermarkSkipped) {
143829
143878
  warning = [warning, `The watermark was not applied (${watermarkSkipped}).`].filter(Boolean).join(" ");
143830
143879
  }
143880
+ if (pointerFailed) {
143881
+ warning = [
143882
+ warning,
143883
+ "The touch visualizer could not be enabled on simulator-server, so touches are not shown in this video."
143884
+ ].filter(Boolean).join(" ");
143885
+ }
143831
143886
  const size = await statNonEmptyOutput(outputFile, "screen_recording_stop");
143832
143887
  const wallClockMs = startedAtMs === null ? null : (api.wallClockEndMs ?? Date.now()) - startedAtMs;
143833
143888
  const durationMs = trimStatic ? Math.round(api.framesWritten / OUTPUT_FPS2 * 1e3) : wallClockMs;
@@ -143846,6 +143901,7 @@ async function stopCapture(api) {
143846
143901
  throw err;
143847
143902
  } finally {
143848
143903
  stopPump(api);
143904
+ await disablePointer(api);
143849
143905
  api.recordingActive = false;
143850
143906
  api.stopPending = false;
143851
143907
  api.pendingRetrieval = false;
@@ -143853,6 +143909,7 @@ async function stopCapture(api) {
143853
143909
  api.outputFile = null;
143854
143910
  api.logoFile = null;
143855
143911
  api.watermarkSkipped = null;
143912
+ api.pointerFailed = false;
143856
143913
  api.framesWritten = 0;
143857
143914
  api.trimmedAnyFrames = false;
143858
143915
  api.wallClockStartMs = null;
@@ -143871,6 +143928,8 @@ async function stopCapture(api) {
143871
143928
  // ../tool-server/src/tools/screen-recording/screen-recording-start.ts
143872
143929
  var DEFAULT_TIME_LIMIT_SECONDS = 180;
143873
143930
  var MAX_TIME_LIMIT_SECONDS = 600;
143931
+ var POINTER_TRAIL_LENGTH = 8;
143932
+ var POINTER_REQUEST_TIMEOUT_MS = 2e3;
143874
143933
  var zodSchema51 = external_exports.object({
143875
143934
  udid: external_exports.string().describe("Target device id from `list-devices` (iOS Simulator UDID or Android serial)."),
143876
143935
  timeLimitSeconds: external_exports.number().int().min(1).max(MAX_TIME_LIMIT_SECONDS).optional().describe(
@@ -143878,6 +143937,9 @@ var zodSchema51 = external_exports.object({
143878
143937
  ),
143879
143938
  trimStatic: external_exports.boolean().optional().describe(
143880
143939
  "Default true. Collapse stretches where the screen does not change: the first second of each still stretch is kept, then unchanged frames are dropped until something moves again, so a long recording with brief activity comes back short instead of full of dead air. The returned durationMs is the trimmed length; wallClockMs/trimmedMs report what was removed. Set false to keep a faithful real-time recording."
143940
+ ),
143941
+ showTouches: external_exports.boolean().optional().describe(
143942
+ "Default true. Draw simulator-server's touch visualizer into the recording: a pulse marks each tap, a comet trail follows swipes and drags, and paired markers show two-finger pinch/rotate, so the video makes clear where every interaction landed. Set false to record the raw screen with no overlay."
143881
143943
  )
143882
143944
  });
143883
143945
  var capability26 = {
@@ -143890,6 +143952,7 @@ function createScreenRecordingStartTool(registry2) {
143890
143952
  capability: capability26,
143891
143953
  description: `Start recording the device screen to a video file (h264 mp4, 30fps at the device's native resolution).
143892
143954
  By default stretches where the screen does not change are trimmed out (see trimStatic), so a long session with only brief activity comes back as a short clip instead of minutes of dead air.
143955
+ By default every tap, swipe, drag, pinch and rotate is drawn into the video as an on-screen touch marker (see showTouches), so the recording shows where each interaction landed.
143893
143956
  The recording keeps running across other tool calls (every result carries a reminder) until \`screen-recording-stop\` is called or timeLimitSeconds elapses \u2014 immediately after starting, set yourself a reminder/wakeup for the expected end of the recording so it is never left running.
143894
143957
  Use when the user wants a video of an interaction, animation, or app behavior \u2014 for a single still frame use \`screenshot\` instead.
143895
143958
  Returns { status: "recording", timeLimitSeconds, outputFile } \u2014 the video is retrieved later by \`screen-recording-stop\`, not by reading outputFile directly.
@@ -143934,21 +143997,53 @@ Fails if a recording is already running on the device, the device is not booted,
143934
143997
  }
143935
143998
  );
143936
143999
  }
144000
+ const showTouches = params.showTouches ?? true;
144001
+ const pointer = showTouches ? makePointerControl(simulator) : void 0;
143937
144002
  return startCapture(api, {
143938
144003
  streamUrl,
143939
144004
  timeLimitSeconds,
143940
144005
  watermark: isFeatureEnabled("video-watermark"),
143941
- trimStatic: params.trimStatic ?? true
144006
+ trimStatic: params.trimStatic ?? true,
144007
+ pointer
143942
144008
  });
143943
144009
  }
143944
144010
  };
143945
144011
  }
144012
+ function makePointerControl(simulator) {
144013
+ let enabling = null;
144014
+ return {
144015
+ async enable() {
144016
+ const run2 = (async () => {
144017
+ await setPointerTrail(
144018
+ simulator,
144019
+ POINTER_TRAIL_LENGTH,
144020
+ AbortSignal.timeout(POINTER_REQUEST_TIMEOUT_MS)
144021
+ );
144022
+ return setPointerVisible(simulator, true, AbortSignal.timeout(POINTER_REQUEST_TIMEOUT_MS));
144023
+ })();
144024
+ enabling = run2;
144025
+ try {
144026
+ return await run2;
144027
+ } finally {
144028
+ if (enabling === run2) enabling = null;
144029
+ }
144030
+ },
144031
+ async disable() {
144032
+ const pending = enabling;
144033
+ if (pending) await pending.catch(() => {
144034
+ });
144035
+ await setPointerVisible(simulator, false, AbortSignal.timeout(POINTER_REQUEST_TIMEOUT_MS));
144036
+ }
144037
+ };
144038
+ }
143946
144039
 
143947
144040
  // ../tool-server/src/tools/screen-recording/screen-recording-stop.ts
144041
+ var import_node_path20 = require("node:path");
143948
144042
  init_zod();
143949
144043
  var zodSchema52 = external_exports.object({
143950
144044
  udid: external_exports.string().describe("Target device id from `list-devices` (iOS Simulator UDID or Android serial).")
143951
144045
  });
144046
+ var RECORDINGS_DIR = ".argent/recordings";
143952
144047
  var capability27 = {
143953
144048
  apple: { simulator: true },
143954
144049
  android: { emulator: true, device: true, unknown: true }
@@ -143972,7 +144067,13 @@ Fails if no recording (running or finished-but-unretrieved) exists for the given
143972
144067
  assertSupported("screen-recording-stop", capability27, device);
143973
144068
  const stopped = await stopCapture(api);
143974
144069
  const artifacts = requireArtifacts(ctx);
143975
- const video = await artifacts.register(stopped.outputFile, { mimeType: "video/mp4" });
144070
+ const video = await artifacts.register(stopped.outputFile, {
144071
+ mimeType: "video/mp4",
144072
+ // Drop the internal `argent-` temp prefix so the saved file reads cleanly
144073
+ // as `.argent/recordings/screen-recording-<device>-<ts>.mp4`.
144074
+ filename: (0, import_node_path20.basename)(stopped.outputFile).replace(/^argent-/, ""),
144075
+ saveDir: RECORDINGS_DIR
144076
+ });
143976
144077
  const result = { video, durationMs: stopped.durationMs };
143977
144078
  if (stopped.wallClockMs !== void 0) result.wallClockMs = stopped.wallClockMs;
143978
144079
  if (stopped.trimmedMs !== void 0) result.trimmedMs = stopped.trimmedMs;
@@ -150994,7 +151095,7 @@ init_zod();
150994
151095
 
150995
151096
  // ../tool-server/src/utils/workspace-reader.ts
150996
151097
  var import_promises12 = require("node:fs/promises");
150997
- var import_node_path20 = require("node:path");
151098
+ var import_node_path21 = require("node:path");
150998
151099
  var import_node_child_process27 = require("node:child_process");
150999
151100
  var import_dotenv = __toESM(require_main());
151000
151101
  async function exists(path38) {
@@ -151082,7 +151183,7 @@ async function detectLockfile(workspacePath) {
151082
151183
  const checks = await Promise.all(
151083
151184
  LOCKFILES.map(async (name) => ({
151084
151185
  name,
151085
- exists: await exists((0, import_node_path20.join)(workspacePath, name))
151186
+ exists: await exists((0, import_node_path21.join)(workspacePath, name))
151086
151187
  }))
151087
151188
  );
151088
151189
  return checks.find((c) => c.exists)?.name ?? null;
@@ -151101,7 +151202,7 @@ var CI_CONFIGS = [
151101
151202
  ];
151102
151203
  async function detectCiConfig(workspacePath) {
151103
151204
  for (const ci of CI_CONFIGS) {
151104
- if (await exists((0, import_node_path20.join)(workspacePath, ci.path))) return ci.label;
151205
+ if (await exists((0, import_node_path21.join)(workspacePath, ci.path))) return ci.label;
151105
151206
  }
151106
151207
  return null;
151107
151208
  }
@@ -151122,7 +151223,7 @@ async function detectLintStagedConfig(workspacePath, packageJson) {
151122
151223
  return packageJson["lint-staged"];
151123
151224
  }
151124
151225
  for (const file2 of LINT_STAGED_FILES) {
151125
- const full = (0, import_node_path20.join)(workspacePath, file2);
151226
+ const full = (0, import_node_path21.join)(workspacePath, file2);
151126
151227
  if (await exists(full)) {
151127
151228
  if (file2.endsWith(".json") || file2 === ".lintstagedrc") {
151128
151229
  return readJsonFile(full);
@@ -151164,7 +151265,7 @@ var CONFIG_FILES = [
151164
151265
  async function detectConfigFiles(workspacePath) {
151165
151266
  const results = await Promise.allSettled(
151166
151267
  CONFIG_FILES.map(async (file2) => {
151167
- if (await exists((0, import_node_path20.join)(workspacePath, file2))) return file2;
151268
+ if (await exists((0, import_node_path21.join)(workspacePath, file2))) return file2;
151168
151269
  return null;
151169
151270
  })
151170
151271
  );
@@ -151203,7 +151304,7 @@ async function detectEnvFiles(workspacePath) {
151203
151304
  const envFileNames = entries.filter((e) => e === ".env" || e.startsWith(".env."));
151204
151305
  const results = await Promise.all(
151205
151306
  envFileNames.map(async (name) => {
151206
- const content = await readTextFile((0, import_node_path20.join)(workspacePath, name));
151307
+ const content = await readTextFile((0, import_node_path21.join)(workspacePath, name));
151207
151308
  return {
151208
151309
  name,
151209
151310
  keys: content ? extractEnvKeys(content) : []
@@ -151230,34 +151331,34 @@ async function readWorkspaceSnapshot(workspacePath) {
151230
151331
  ciConfig,
151231
151332
  configFilesFound
151232
151333
  ] = await Promise.all([
151233
- readJsonFile((0, import_node_path20.join)(workspacePath, "package.json")),
151234
- readJsonFile((0, import_node_path20.join)(workspacePath, "app.json")),
151235
- readJsonFile((0, import_node_path20.join)(workspacePath, "eas.json")),
151236
- readJsonFile((0, import_node_path20.join)(workspacePath, "tsconfig.json")),
151237
- readTextFile((0, import_node_path20.join)(workspacePath, "metro.config.js")).then(
151238
- (r) => r ?? readTextFile((0, import_node_path20.join)(workspacePath, "metro.config.ts"))
151334
+ readJsonFile((0, import_node_path21.join)(workspacePath, "package.json")),
151335
+ readJsonFile((0, import_node_path21.join)(workspacePath, "app.json")),
151336
+ readJsonFile((0, import_node_path21.join)(workspacePath, "eas.json")),
151337
+ readJsonFile((0, import_node_path21.join)(workspacePath, "tsconfig.json")),
151338
+ readTextFile((0, import_node_path21.join)(workspacePath, "metro.config.js")).then(
151339
+ (r) => r ?? readTextFile((0, import_node_path21.join)(workspacePath, "metro.config.ts"))
151239
151340
  ),
151240
- readTextFile((0, import_node_path20.join)(workspacePath, "babel.config.js")).then(
151241
- (r) => r ?? readTextFile((0, import_node_path20.join)(workspacePath, "babel.config.cjs"))
151341
+ readTextFile((0, import_node_path21.join)(workspacePath, "babel.config.js")).then(
151342
+ (r) => r ?? readTextFile((0, import_node_path21.join)(workspacePath, "babel.config.cjs"))
151242
151343
  ),
151243
- isDirectory((0, import_node_path20.join)(workspacePath, "ios")),
151244
- isDirectory((0, import_node_path20.join)(workspacePath, "android")),
151344
+ isDirectory((0, import_node_path21.join)(workspacePath, "ios")),
151345
+ isDirectory((0, import_node_path21.join)(workspacePath, "android")),
151245
151346
  detectLockfile(workspacePath),
151246
151347
  detectEnvFiles(workspacePath),
151247
151348
  detectToolVersions(workspacePath),
151248
- listDir((0, import_node_path20.join)(workspacePath, "scripts")),
151249
- listDir((0, import_node_path20.join)(workspacePath, ".husky")),
151349
+ listDir((0, import_node_path21.join)(workspacePath, "scripts")),
151350
+ listDir((0, import_node_path21.join)(workspacePath, ".husky")),
151250
151351
  detectCiConfig(workspacePath),
151251
151352
  detectConfigFiles(workspacePath)
151252
151353
  ]);
151253
- const iosDir = (0, import_node_path20.join)(workspacePath, "ios");
151254
- const androidDir = (0, import_node_path20.join)(workspacePath, "android");
151354
+ const iosDir = (0, import_node_path21.join)(workspacePath, "ios");
151355
+ const androidDir = (0, import_node_path21.join)(workspacePath, "android");
151255
151356
  const [iosWorkspace, iosHasPodfile, makefileText, lintStagedConfig, androidHasGradle] = await Promise.all([
151256
151357
  hasIosDir ? findIosWorkspace(iosDir) : Promise.resolve(null),
151257
- exists((0, import_node_path20.join)(workspacePath, "ios", "Podfile")),
151258
- readTextFile((0, import_node_path20.join)(workspacePath, "Makefile")),
151358
+ exists((0, import_node_path21.join)(workspacePath, "ios", "Podfile")),
151359
+ readTextFile((0, import_node_path21.join)(workspacePath, "Makefile")),
151259
151360
  detectLintStagedConfig(workspacePath, packageJson),
151260
- hasAndroidDir ? exists((0, import_node_path20.join)(androidDir, "gradlew")) : Promise.resolve(false)
151361
+ hasAndroidDir ? exists((0, import_node_path21.join)(androidDir, "gradlew")) : Promise.resolve(false)
151261
151362
  ]);
151262
151363
  const metroPort = metroConfigRaw ? extractMetroPort(metroConfigRaw) : null;
151263
151364
  const makefileTargets = makefileText ? extractMakefileTargets(makefileText) : null;
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.16.2-next.9",
3
+ "version": "0.17.0",
4
4
  "description": "MCP server for iOS Simulator and Android Emulator control",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -24,14 +24,18 @@ A recording does not stop itself before its `timeLimitSeconds` cap, so a forgott
24
24
  ## 3. Workflow
25
25
 
26
26
  1. Ensure the target device is booted and the app is in the state you want the video to open on (`list-devices`, `launch-app`, `argent-device-interact`).
27
- 2. Call `screen-recording-start` with `udid` and a `timeLimitSeconds` slightly above the expected interaction length (default 180, max 600).
27
+ 2. Call `screen-recording-start` with `udid` and a `timeLimitSeconds` slightly above the expected interaction length (default 180, max 600). Taps and swipes are drawn into the video by default (see the touch-visualizer note below); pass `showTouches: false` for a clean raw-screen capture.
28
28
  3. Set the end-of-recording reminder described in §2 — this step is not optional.
29
29
  4. Drive the interaction to capture: gestures, navigation, typing (`argent-device-interact`). Prefer `run-sequence` for tight multi-step interactions so tool-call latency does not pad the video.
30
- 5. Call `screen-recording-stop` with the same `udid`. It returns `{ video, durationMs, wallClockMs?, trimmedMs?, warning? }`; `video` is an artifact use its `hostPath` locally or download it via the artifacts endpoint. The video is already final when stop returns (the watermark is stamped during capture, not in a second pass), so stop takes well under a second.
30
+ 5. Call `screen-recording-stop` with the same `udid`. It returns `{ video, durationMs, wallClockMs?, trimmedMs?, warning? }`; `video` is an artifact whose resolved path points at the durably-saved file (see below). The video is already final when stop returns (the watermark is stamped during capture, not in a second pass), so stop takes well under a second.
31
31
  6. Check `warning`: it reports cap-triggered stops, early encoder exits, a dropped frame stream, and possibly-truncated containers. Verify the file plays (or at least has a sane size) before presenting it to the user.
32
32
 
33
+ **Where the file lands (durable, not scratch).** Unlike most argent artifacts (which live in a disposable temp cache the OS reclaims), a finished recording is saved durably on the **client** into `<project>/.argent/recordings/` — the project being the nearest ancestor of the client's working directory with a `.git`/`package.json`/`.argent`, or `~/.argent/recordings/` when the client isn't inside a project. This holds even for a remote `argent link` tool-server: the mp4 is written on the client host, not the server. The saved name is `screen-recording-<device>-<timestamp>.mp4`; if that name is already taken the new file lands beside it as `… (2).mp4` rather than overwriting. Because these files persist in the working tree, mention the path to the user (and note that they are untracked — add them to `.gitignore` or clean them up if they shouldn't be committed).
34
+
33
35
  **Static-frame trimming (on by default).** Stretches where the screen does not change are collapsed: the first second of each still stretch is kept so pauses read naturally, then unchanged frames are dropped until something moves again (a change of even a couple of pixels counts). So you can leave a recording running across slow steps, waits, or thinking time without padding the clip with dead air — a 40-second session with 5 seconds of real activity comes back as a ~5-7 second video. When trimming removed anything, stop also returns `wallClockMs` (real elapsed time) and `trimmedMs` (how much was cut); `durationMs` is always the length of the video you actually get. Pass `trimStatic: false` to `screen-recording-start` when you want a faithful real-time recording (e.g. to measure how long something took on screen).
34
36
 
37
+ **Touch visualizer (on by default).** Every interaction argent drives is drawn straight into the video: a pulse marks each tap, a fading comet trail follows swipes and drags, and paired markers show the two contact points of a pinch or rotate. This makes a recording self-explanatory — a viewer can see _where_ each gesture landed rather than watching the UI react to an invisible finger. It is rendered by simulator-server into the frame stream (nothing is composited host-side), so it costs nothing extra and never appears in a `screenshot`. Pass `showTouches: false` to `screen-recording-start` to record the raw screen with no overlay (e.g. when capturing exactly what the user would see). If simulator-server cannot enable it, the recording still succeeds and stop returns a `warning` saying touches are not shown.
38
+
35
39
  ---
36
40
 
37
41
  ## 4. Platform notes and limits