ptywright 0.2.0 → 0.4.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.
@@ -1,11 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
- import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
4
+ import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
5
5
  import { createHash } from "node:crypto";
6
- import { spawn } from "node:child_process";
7
6
  import { chromium } from "playwright";
8
7
  import { createServer } from "node:http";
8
+ import { spawn } from "node:child_process";
9
9
  //#region src/agent/manifest.ts
10
10
  const AGENT_MANIFEST_SCHEMA_URL = "https://ptywright.local/schemas/ptywright-agent-manifest.schema.json";
11
11
  const AGENT_MANIFEST_FILE_NAME = "ptywright-agent.manifest.json";
@@ -171,134 +171,6 @@ function formatZodIssues$1(error) {
171
171
  }).join("; ");
172
172
  }
173
173
  //#endregion
174
- //#region src/agent/aitty.ts
175
- function buildAittyExecCommand(launch, options = {}) {
176
- if (!launch.command) throw new Error("launch.command is required for aitty mode");
177
- const rootDir = options.rootDir ?? process.cwd();
178
- const cwd = launch.cwd ? resolve(rootDir, launch.cwd) : rootDir;
179
- const aitty = launch.aitty ?? {};
180
- const cli = resolveAittyCliCommand(aitty.command, rootDir, options.env ?? process.env);
181
- const args = [
182
- ...cli.args,
183
- "exec",
184
- "--launch",
185
- "print"
186
- ];
187
- pushOption(args, "--cwd", cwd);
188
- pushOption(args, "--host", aitty.host);
189
- pushOption(args, "--port", aitty.port);
190
- pushOption(args, "--project", aitty.project);
191
- pushOption(args, "--label", aitty.label);
192
- pushOption(args, "--title", aitty.title);
193
- pushOption(args, "--subtitle", aitty.subtitle);
194
- pushOption(args, "--theme", aitty.theme && aitty.theme !== "auto" ? aitty.theme : void 0);
195
- pushOption(args, "--font-size", aitty.fontSize);
196
- pushOption(args, "--experimental-screen-mode", aitty.screenMode);
197
- args.push("--", launch.command, ...launch.args ?? []);
198
- return {
199
- file: cli.file,
200
- args,
201
- cwd,
202
- env: {
203
- ...options.env ?? process.env,
204
- ...launch.env
205
- }
206
- };
207
- }
208
- async function launchAittyBrowserSession(launch, options = {}) {
209
- const command = buildAittyExecCommand(launch, options);
210
- const timeoutMs = launch.aitty?.waitForUrlMs ?? 15e3;
211
- const child = spawn(command.file, command.args, {
212
- cwd: command.cwd,
213
- env: command.env,
214
- stdio: [
215
- "ignore",
216
- "pipe",
217
- "pipe"
218
- ]
219
- });
220
- const chunks = [];
221
- const stderrChunks = [];
222
- return {
223
- url: await new Promise((resolveUrl, reject) => {
224
- let settled = false;
225
- const timer = setTimeout(() => {
226
- finish(/* @__PURE__ */ new Error(`timed out after ${timeoutMs}ms waiting for aitty session URL\nstderr=${stderrChunks.join("").trim()}`));
227
- }, timeoutMs);
228
- const finish = (result) => {
229
- if (settled) return;
230
- settled = true;
231
- clearTimeout(timer);
232
- child.stdout.off("data", onStdout);
233
- child.stderr.off("data", onStderr);
234
- child.off("error", onError);
235
- child.off("exit", onExit);
236
- if (result instanceof Error) reject(result);
237
- else resolveUrl(result);
238
- };
239
- const onStdout = (chunk) => {
240
- const text = chunk.toString("utf8");
241
- chunks.push(text);
242
- const found = extractAittyUrlFromOutput(chunks.join(""));
243
- if (found) finish(found);
244
- };
245
- const onStderr = (chunk) => {
246
- stderrChunks.push(chunk.toString("utf8"));
247
- };
248
- const onError = (error) => finish(error);
249
- const onExit = (code, signal) => {
250
- finish(/* @__PURE__ */ new Error(`aitty exited before printing a session URL (code=${code ?? "null"} signal=${signal ?? "null"})\nstdout=${chunks.join("").trim()}\nstderr=${stderrChunks.join("").trim()}`));
251
- };
252
- child.stdout.on("data", onStdout);
253
- child.stderr.on("data", onStderr);
254
- child.once("error", onError);
255
- child.once("exit", onExit);
256
- }),
257
- process: child,
258
- close: () => closeChild(child)
259
- };
260
- }
261
- function extractAittyUrlFromOutput(output) {
262
- return output.match(/https?:\/\/[^\s"'<>]+/)?.[0] ?? null;
263
- }
264
- function resolveAittyCliCommand(explicitCommand, rootDir, env) {
265
- if (explicitCommand) return {
266
- file: explicitCommand,
267
- args: []
268
- };
269
- if (env.PTYWRIGHT_AITTY_CLI) return {
270
- file: env.PTYWRIGHT_AITTY_CLI,
271
- args: []
272
- };
273
- const siblingDist = resolve(rootDir, "../aitty/packages/cli/dist/cli.js");
274
- if (existsSync(siblingDist)) return {
275
- file: "node",
276
- args: [siblingDist]
277
- };
278
- return {
279
- file: "aitty",
280
- args: []
281
- };
282
- }
283
- function pushOption(args, name, value) {
284
- if (value === void 0 || value === "") return;
285
- args.push(name, String(value));
286
- }
287
- async function closeChild(child) {
288
- if (child.exitCode !== null || child.signalCode !== null) return;
289
- await new Promise((resolveClose) => {
290
- const timer = setTimeout(() => {
291
- if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
292
- resolveClose();
293
- }, 2e3);
294
- child.once("exit", () => {
295
- clearTimeout(timer);
296
- resolveClose();
297
- });
298
- child.kill("SIGTERM");
299
- });
300
- }
301
- //#endregion
302
174
  //#region src/agent/browser.ts
303
175
  async function launchAgentBrowser(args) {
304
176
  let lastError;
@@ -391,8 +263,14 @@ const agentViewportSchema = z.object({
391
263
  isMobile: z.boolean().optional(),
392
264
  hasTouch: z.boolean().optional()
393
265
  });
266
+ const agentLaunchModeSchema = z.enum(["command", "url"]);
267
+ function inferAgentLaunchMode(value) {
268
+ if (value.mode) return value.mode;
269
+ if (value.url) return "url";
270
+ return "command";
271
+ }
394
272
  const agentLaunchSchema = z.object({
395
- mode: z.enum(["aitty", "url"]).optional(),
273
+ mode: agentLaunchModeSchema.optional(),
396
274
  agentFlavor: z.enum([
397
275
  "codex",
398
276
  "claude",
@@ -404,33 +282,17 @@ const agentLaunchSchema = z.object({
404
282
  cwd: z.string().optional(),
405
283
  env: z.record(z.string()).optional(),
406
284
  url: z.string().url().optional(),
407
- aitty: z.object({
408
- command: z.string().min(1).optional(),
409
- args: z.array(z.string()).optional(),
410
- project: z.string().min(1).optional(),
411
- label: z.string().min(1).optional(),
412
- title: z.string().min(1).optional(),
413
- subtitle: z.string().min(1).optional(),
414
- theme: z.enum([
415
- "dark",
416
- "light",
417
- "auto"
418
- ]).optional(),
419
- fontSize: z.number().int().min(11).max(24).optional(),
420
- screenMode: z.enum(["termvision"]).optional(),
421
- port: z.number().int().min(0).max(65535).optional(),
422
- host: z.string().min(1).optional(),
423
- waitForUrlMs: z.number().int().positive().optional()
424
- }).optional()
425
- }).superRefine((value, ctx) => {
426
- const mode = value.mode ?? (value.url ? "url" : "aitty");
285
+ urlRegex: z.string().min(1).optional(),
286
+ waitForUrlMs: z.number().int().positive().optional()
287
+ }).strict().superRefine((value, ctx) => {
288
+ const mode = inferAgentLaunchMode(value);
427
289
  if (mode === "url" && !value.url) ctx.addIssue({
428
290
  code: z.ZodIssueCode.custom,
429
291
  message: "launch.url is required when launch.mode is 'url'"
430
292
  });
431
- if (mode === "aitty" && !value.command) ctx.addIssue({
293
+ if (mode === "command" && !value.command) ctx.addIssue({
432
294
  code: z.ZodIssueCode.custom,
433
- message: "launch.command is required when launch.mode is 'aitty'"
295
+ message: "launch.command is required when launch.mode is 'command'"
434
296
  });
435
297
  });
436
298
  const waitForTextStepSchema = z.object({
@@ -528,6 +390,9 @@ function normalizeAgentFlowSpec(input) {
528
390
  viewports: spec.viewports?.length ? spec.viewports : [...DEFAULT_AGENT_VIEWPORTS]
529
391
  };
530
392
  }
393
+ function resolveAgentLaunchMode(launch) {
394
+ return inferAgentLaunchMode(launch);
395
+ }
531
396
  //#endregion
532
397
  //#region src/agent/cassette.ts
533
398
  const AGENT_CASSETTE_SCHEMA_URL = "https://ptywright.local/schemas/ptywright-agent-cassette.schema.json";
@@ -759,6 +624,164 @@ function escapeHtml$1(input) {
759
624
  return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
760
625
  }
761
626
  //#endregion
627
+ //#region src/agent/config_defaults.ts
628
+ function normalizeAgentFlowSpecWithConfig(input, config) {
629
+ return normalizeAgentFlowSpec(applyAgentConfigDefaults(agentFlowSpecSchema.parse(input), config));
630
+ }
631
+ function applyAgentConfigDefaults(input, config) {
632
+ const agent = config?.agent;
633
+ if (!agent) return input;
634
+ const name = sanitizeArtifactName(input.name ?? "agent-flow");
635
+ const configDefaults = agent.defaults ?? {};
636
+ const specDefaults = input.defaults ?? {};
637
+ const viewports = input.viewports ? void 0 : cloneViewports(configDefaults.viewports);
638
+ return {
639
+ ...input,
640
+ artifactsDir: input.artifactsDir ?? resolveNamedDir(agent.artifactsRoot, name, config.rootDir),
641
+ snapshotDir: input.snapshotDir ?? resolveNamedDir(agent.snapshotDir, name, config.rootDir),
642
+ viewports: viewports ?? input.viewports,
643
+ defaults: {
644
+ ...specDefaults,
645
+ timeoutMs: specDefaults.timeoutMs ?? configDefaults.timeoutMs,
646
+ screenshot: specDefaults.screenshot ?? configDefaults.screenshot,
647
+ mask: mergeMaskRules(configDefaults.mask, specDefaults.mask)
648
+ }
649
+ };
650
+ }
651
+ function resolveNamedDir(root, name, configRoot) {
652
+ if (!root) return void 0;
653
+ const namedDir = join(root, name);
654
+ return isAbsolute(namedDir) ? namedDir : resolve(configRoot, namedDir);
655
+ }
656
+ function cloneViewports(viewports) {
657
+ return Array.isArray(viewports) && viewports.length > 0 ? viewports.map((viewport) => ({ ...viewport })) : void 0;
658
+ }
659
+ function mergeMaskRules(configMask, specMask) {
660
+ const merged = [...configMask ?? [], ...specMask ?? []];
661
+ return merged.length > 0 ? merged : void 0;
662
+ }
663
+ //#endregion
664
+ //#region src/agent/command_launch.ts
665
+ const DEFAULT_URL_REGEX = /https?:\/\/[^\s"'<>]+/;
666
+ function buildCommandLaunchCommand(launch, options = {}) {
667
+ if (!launch.command) throw new Error("launch.command is required when launch.mode is 'command'");
668
+ const rootDir = options.rootDir ?? process.cwd();
669
+ const cwd = launch.cwd ? resolve(rootDir, launch.cwd) : rootDir;
670
+ return {
671
+ file: launch.command,
672
+ args: launch.args ?? [],
673
+ cwd,
674
+ env: {
675
+ ...options.env ?? process.env,
676
+ ...launch.env
677
+ },
678
+ label: launch.command,
679
+ urlRegex: launch.urlRegex,
680
+ waitForUrlMs: launch.waitForUrlMs
681
+ };
682
+ }
683
+ async function launchBrowserSessionFromCommand(command) {
684
+ const timeoutMs = command.waitForUrlMs ?? 15e3;
685
+ const child = spawn(command.file, command.args, {
686
+ cwd: command.cwd,
687
+ env: command.env,
688
+ stdio: [
689
+ "ignore",
690
+ "pipe",
691
+ "pipe"
692
+ ]
693
+ });
694
+ const stdoutChunks = [];
695
+ const stderrChunks = [];
696
+ return {
697
+ url: await new Promise((resolveUrl, reject) => {
698
+ let settled = false;
699
+ const timer = setTimeout(() => {
700
+ finish(/* @__PURE__ */ new Error(`timed out after ${timeoutMs}ms waiting for ${command.label ?? command.file} session URL\nstdout=${stdoutChunks.join("").trim()}\nstderr=${stderrChunks.join("").trim()}`));
701
+ }, timeoutMs);
702
+ const finish = (result) => {
703
+ if (settled) return;
704
+ settled = true;
705
+ clearTimeout(timer);
706
+ child.stdout.off("data", onStdout);
707
+ child.stderr.off("data", onStderr);
708
+ child.off("error", onError);
709
+ child.off("exit", onExit);
710
+ if (result instanceof Error) reject(result);
711
+ else resolveUrl(result);
712
+ };
713
+ const readUrl = () => {
714
+ const found = extractUrlFromOutput(`${stdoutChunks.join("")}\n${stderrChunks.join("")}`, command.urlRegex);
715
+ if (found) finish(found);
716
+ };
717
+ const onStdout = (chunk) => {
718
+ stdoutChunks.push(chunk.toString("utf8"));
719
+ readUrl();
720
+ };
721
+ const onStderr = (chunk) => {
722
+ stderrChunks.push(chunk.toString("utf8"));
723
+ readUrl();
724
+ };
725
+ const onError = (error) => finish(error);
726
+ const onExit = (code, signal) => {
727
+ finish(/* @__PURE__ */ new Error(`${command.label ?? command.file} exited before printing a session URL (code=${code ?? "null"} signal=${signal ?? "null"})\nstdout=${stdoutChunks.join("").trim()}\nstderr=${stderrChunks.join("").trim()}`));
728
+ };
729
+ child.stdout.on("data", onStdout);
730
+ child.stderr.on("data", onStderr);
731
+ child.once("error", onError);
732
+ child.once("exit", onExit);
733
+ }),
734
+ process: child,
735
+ close: () => closeChild(child)
736
+ };
737
+ }
738
+ function extractUrlFromOutput(output, regexSource) {
739
+ if (!regexSource) return output.match(DEFAULT_URL_REGEX)?.[0] ?? null;
740
+ const match = output.match(new RegExp(regexSource, "m"));
741
+ return match?.[1] ?? match?.[0] ?? null;
742
+ }
743
+ function formatBrowserLaunchCommand(command) {
744
+ return [command.file, ...command.args].join(" ");
745
+ }
746
+ async function closeChild(child) {
747
+ if (child.exitCode !== null || child.signalCode !== null) return;
748
+ await new Promise((resolveClose) => {
749
+ const timer = setTimeout(() => {
750
+ if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
751
+ resolveClose();
752
+ }, 2e3);
753
+ child.once("exit", () => {
754
+ clearTimeout(timer);
755
+ resolveClose();
756
+ });
757
+ child.kill("SIGTERM");
758
+ });
759
+ }
760
+ //#endregion
761
+ //#region src/agent/launch.ts
762
+ function buildAgentLaunchCommand(launch, options = {}) {
763
+ if (resolveAgentLaunchMode(launch) === "url") return null;
764
+ return buildCommandLaunchCommand(launch, options);
765
+ }
766
+ async function resolveAgentLaunchTarget(launch, options = {}) {
767
+ const mode = resolveAgentLaunchMode(launch);
768
+ if (mode === "url") return {
769
+ mode,
770
+ url: launch.url,
771
+ session: null
772
+ };
773
+ const session = await launchBrowserSessionFromCommand(buildCommandLaunchCommand(launch, options));
774
+ return {
775
+ mode,
776
+ url: session.url,
777
+ session
778
+ };
779
+ }
780
+ function formatAgentLaunchCommand(launch) {
781
+ const command = buildAgentLaunchCommand(launch);
782
+ return command ? formatBrowserLaunchCommand(command) : "launch.mode=url";
783
+ }
784
+ //#endregion
762
785
  //#region src/agent/presets.ts
763
786
  const COMMON_AGENT_MASKS = [
764
787
  {
@@ -849,19 +872,15 @@ function createAgentTemplateSpec(flavor) {
849
872
  artifactsDir: `.tmp/agent/${name}`,
850
873
  snapshotDir: `tests/agent-snapshots/${name}`,
851
874
  launch: {
852
- mode: "aitty",
875
+ mode: "command",
853
876
  agentFlavor: flavor,
854
- command,
855
- args: [],
856
- aitty: {
857
- project: "ptywright",
858
- label: command,
859
- title: `${command} browser smoke`,
860
- subtitle: "browser-hosted terminal agent regression",
861
- theme: "light",
862
- fontSize: 14,
863
- waitForUrlMs: 15e3
864
- }
877
+ command: "your-browser-terminal-launcher",
878
+ args: [
879
+ "--agent",
880
+ command,
881
+ "--print-url"
882
+ ],
883
+ waitForUrlMs: 15e3
865
884
  },
866
885
  viewports: DEFAULT_VIEWPORTS.map((viewport) => ({ ...viewport })),
867
886
  defaults: {
@@ -1303,20 +1322,26 @@ function escapeAttribute(input) {
1303
1322
  //#region src/agent/spec_loader.ts
1304
1323
  async function loadAgentSpec(specPath) {
1305
1324
  const resolved = resolve(process.cwd(), specPath);
1306
- if (resolved.endsWith(".json")) return {
1307
- spec: normalizeAgentFlowSpec(JSON.parse(readFileSync(resolved, "utf8"))),
1308
- path: resolved
1309
- };
1325
+ if (resolved.endsWith(".json")) {
1326
+ const raw = JSON.parse(readFileSync(resolved, "utf8"));
1327
+ return {
1328
+ spec: normalizeAgentFlowSpec(raw),
1329
+ raw,
1330
+ path: resolved
1331
+ };
1332
+ }
1310
1333
  const mod = await import(`${pathToFileURL(resolved).href}?t=${Date.now()}`);
1334
+ const raw = mod.default ?? mod.spec;
1311
1335
  return {
1312
- spec: normalizeAgentFlowSpec(mod.default ?? mod.spec),
1336
+ spec: normalizeAgentFlowSpec(raw),
1337
+ raw,
1313
1338
  path: resolved
1314
1339
  };
1315
1340
  }
1316
1341
  //#endregion
1317
1342
  //#region src/agent/runner.ts
1318
1343
  async function runAgentSpecPath(specPath, options = {}) {
1319
- return runAgentSpec((await loadAgentSpec(specPath)).spec, options);
1344
+ return runAgentSpec((await loadAgentSpec(specPath)).raw, options);
1320
1345
  }
1321
1346
  async function replayAgentRecordPath(recordPath, options = {}) {
1322
1347
  const raw = JSON.parse(readFileSync(recordPath, "utf8"));
@@ -1329,14 +1354,20 @@ async function replayAgentRecordPath(recordPath, options = {}) {
1329
1354
  artifactsDir: options.artifactsDir ?? join(dirname(recordPath), "replay")
1330
1355
  });
1331
1356
  }
1332
- if (record.spec) return runAgentSpec(record.spec, options);
1357
+ if (record.spec) return runAgentSpec(record.spec, {
1358
+ ...options,
1359
+ config: void 0
1360
+ });
1333
1361
  if (!record.flowPath) throw new Error(`invalid agent run record: missing replay source in ${recordPath}`);
1334
- return runAgentSpecPath(isAbsolute(record.flowPath) ? record.flowPath : resolve(dirname(recordPath), record.flowPath), options);
1362
+ return runAgentSpecPath(isAbsolute(record.flowPath) ? record.flowPath : resolve(dirname(recordPath), record.flowPath), {
1363
+ ...options,
1364
+ config: void 0
1365
+ });
1335
1366
  }
1336
1367
  async function runAgentSpec(input, options = {}) {
1337
1368
  const startedAt = Date.now();
1338
1369
  const rootDir = options.rootDir ? resolve(process.cwd(), options.rootDir) : process.cwd();
1339
- const spec = normalizeAgentFlowSpec(input);
1370
+ const spec = normalizeAgentFlowSpecWithConfig(input, options.replayCassette ? void 0 : options.config);
1340
1371
  const name = sanitizeArtifactName(spec.name ?? "agent-flow");
1341
1372
  const artifactsDir = resolve(rootDir, options.artifactsDir ?? spec.artifactsDir ?? join(".tmp", "agent", name));
1342
1373
  const snapshotDir = resolve(rootDir, spec.snapshotDir ?? join("snapshots", name));
@@ -1412,9 +1443,7 @@ async function runAgentSpec(input, options = {}) {
1412
1443
  }
1413
1444
  async function runViewport(args) {
1414
1445
  const { browser, spec, viewport, rootDir, artifactsDir, snapshotDir, updateSnapshots, recordCassette, cassette, result } = args;
1415
- const launchMode = spec.launch.mode ?? (spec.launch.url ? "url" : "aitty");
1416
- const session = launchMode === "aitty" ? await launchAittyBrowserSession(spec.launch, { rootDir }) : null;
1417
- const url = launchMode === "url" ? spec.launch.url : session.url;
1446
+ const launchTarget = await resolveAgentLaunchTarget(spec.launch, { rootDir });
1418
1447
  const context = await browser.newContext({
1419
1448
  viewport: {
1420
1449
  width: viewport.width,
@@ -1440,7 +1469,7 @@ async function runViewport(args) {
1440
1469
  nextReplayPhase: 0
1441
1470
  };
1442
1471
  try {
1443
- await page.goto(url, {
1472
+ await page.goto(launchTarget.url, {
1444
1473
  waitUntil: "domcontentloaded",
1445
1474
  timeout: spec.defaults?.timeoutMs ?? 3e4
1446
1475
  });
@@ -1482,7 +1511,7 @@ async function runViewport(args) {
1482
1511
  }
1483
1512
  } finally {
1484
1513
  await withTimeout(context.close().catch(() => void 0), 5e3);
1485
- await session?.close();
1514
+ await launchTarget.session?.close();
1486
1515
  }
1487
1516
  }
1488
1517
  async function replayAgentCassette(cassette, cassettePath, options) {
@@ -1884,14 +1913,11 @@ function formatStepLabel(step) {
1884
1913
  function envTruthy(value) {
1885
1914
  return value === "1" || value === "true" || value === "yes";
1886
1915
  }
1887
- function printAittyLaunchPlan(input) {
1888
- const spec = normalizeAgentFlowSpec(input);
1889
- if ((spec.launch.mode ?? "aitty") !== "aitty") return "launch.mode=url";
1890
- const command = buildAittyExecCommand(spec.launch);
1891
- return [command.file, ...command.args].join(" ");
1916
+ function printAgentLaunchPlan(input) {
1917
+ return formatAgentLaunchCommand(normalizeAgentFlowSpec(input).launch);
1892
1918
  }
1893
1919
  function defaultSpecNameForPath(path) {
1894
1920
  return sanitizeArtifactName(basename(path, extname(path)));
1895
1921
  }
1896
1922
  //#endregion
1897
- export { isAgentManifestLike as C, writeAgentManifestPath as E, agentManifestPath as S, validateAgentManifestFiles as T, normalizeAgentFlowSpec as _, runAgentSpecPath as a, launchAittyBrowserSession as b, agentRunModeSchema as c, readAgentRunRecordPath as d, writeAgentRunRecordPath as f, readAgentCassettePath as g, isAgentCassetteLike as h, runAgentSpec as i, formatAgentArgv as l, createAgentTemplateSpec as m, printAittyLaunchPlan as n, loadAgentSpec as o, formatArgv as p, replayAgentRecordPath as r, AGENT_RUN_RECORD_SCHEMA_URL as s, defaultSpecNameForPath as t, isAgentRunRecordLike as u, sanitizeArtifactName as v, readAgentManifestPath as w, AGENT_MANIFEST_FILE_NAME as x, launchAgentBrowser as y };
1923
+ export { agentManifestPath as C, writeAgentManifestPath as D, validateAgentManifestFiles as E, AGENT_MANIFEST_FILE_NAME as S, readAgentManifestPath as T, isAgentCassetteLike as _, runAgentSpecPath as a, sanitizeArtifactName as b, agentRunModeSchema as c, readAgentRunRecordPath as d, writeAgentRunRecordPath as f, normalizeAgentFlowSpecWithConfig as g, resolveAgentLaunchTarget as h, runAgentSpec as i, formatAgentArgv as l, createAgentTemplateSpec as m, printAgentLaunchPlan as n, loadAgentSpec as o, formatArgv as p, replayAgentRecordPath as r, AGENT_RUN_RECORD_SCHEMA_URL as s, defaultSpecNameForPath as t, isAgentRunRecordLike as u, readAgentCassettePath as v, isAgentManifestLike as w, launchAgentBrowser as x, normalizeAgentFlowSpec as y };
@@ -2081,7 +2081,7 @@ function joinPosix(a, b) {
2081
2081
  }
2082
2082
  //#endregion
2083
2083
  //#region package.json
2084
- var version = "0.2.0";
2084
+ var version = "0.4.0";
2085
2085
  //#endregion
2086
2086
  //#region src/mcp/server.ts
2087
2087
  const textMaskRuleSchema = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ptywright",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Terminal/TUI automation driver over PTY + xterm, exposed as MCP tools",
5
5
  "keywords": [
6
6
  "agent",
@@ -37,6 +37,7 @@
37
37
  "exports": {
38
38
  ".": "./dist/cli.mjs",
39
39
  "./agent": "./dist/agent.mjs",
40
+ "./config": "./dist/config.mjs",
40
41
  "./mcp": "./dist/mcp.mjs",
41
42
  "./pty-cassette": "./dist/pty-cassette.mjs",
42
43
  "./session": "./dist/session.mjs",
@@ -14,31 +14,15 @@
14
14
  "type": "object",
15
15
  "additionalProperties": false,
16
16
  "properties": {
17
- "mode": { "type": "string", "enum": ["aitty", "url"] },
17
+ "mode": { "type": "string", "enum": ["command", "url"] },
18
18
  "agentFlavor": { "type": "string", "enum": ["codex", "claude", "droid", "generic"] },
19
19
  "command": { "type": "string", "minLength": 1 },
20
20
  "args": { "type": "array", "items": { "type": "string" } },
21
21
  "cwd": { "type": "string" },
22
22
  "env": { "type": "object", "additionalProperties": { "type": "string" } },
23
23
  "url": { "type": "string" },
24
- "aitty": {
25
- "type": "object",
26
- "additionalProperties": false,
27
- "properties": {
28
- "command": { "type": "string", "minLength": 1 },
29
- "args": { "type": "array", "items": { "type": "string" } },
30
- "project": { "type": "string", "minLength": 1 },
31
- "label": { "type": "string", "minLength": 1 },
32
- "title": { "type": "string", "minLength": 1 },
33
- "subtitle": { "type": "string", "minLength": 1 },
34
- "theme": { "type": "string", "enum": ["dark", "light", "auto"] },
35
- "fontSize": { "type": "integer", "minimum": 11, "maximum": 24 },
36
- "screenMode": { "type": "string", "enum": ["termvision"] },
37
- "port": { "type": "integer", "minimum": 0, "maximum": 65535 },
38
- "host": { "type": "string", "minLength": 1 },
39
- "waitForUrlMs": { "type": "integer", "minimum": 1 }
40
- }
41
- }
24
+ "urlRegex": { "type": "string", "minLength": 1 },
25
+ "waitForUrlMs": { "type": "integer", "minimum": 1 }
42
26
  }
43
27
  },
44
28
  "viewports": {