@tryarcanist/cli 0.1.42 → 0.1.44

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 (2) hide show
  1. package/dist/index.js +144 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -273,7 +273,7 @@ async function readHiddenPrompt(prompt) {
273
273
  process.stdout.write("\n");
274
274
  resolve(inputChars.join(""));
275
275
  };
276
- const fail = (error) => {
276
+ const fail2 = (error) => {
277
277
  if (settled) return;
278
278
  settled = true;
279
279
  cleanup();
@@ -290,7 +290,7 @@ async function readHiddenPrompt(prompt) {
290
290
  return;
291
291
  }
292
292
  if (char === "") {
293
- fail(new CliError("user", "Interrupted.", { exitCode: 130 }));
293
+ fail2(new CliError("user", "Interrupted.", { exitCode: 130 }));
294
294
  return;
295
295
  }
296
296
  if (char === "\x7F" || char === "\b") {
@@ -306,7 +306,7 @@ async function readHiddenPrompt(prompt) {
306
306
  }
307
307
  };
308
308
  const onError = (error) => {
309
- fail(new CliError("user", `Failed to read input: ${error.message}`));
309
+ fail2(new CliError("user", `Failed to read input: ${error.message}`));
310
310
  };
311
311
  process.stdout.write(prompt);
312
312
  stdin.resume();
@@ -383,6 +383,128 @@ async function whoamiCommand(options, command) {
383
383
  if (payload.tokenScope) console.log(`Token scope: ${String(payload.tokenScope)}`);
384
384
  }
385
385
 
386
+ // src/uploads.ts
387
+ import { readFile } from "fs/promises";
388
+ import { basename, extname } from "path";
389
+
390
+ // ../../shared/constants/uploads.ts
391
+ var MAX_UPLOADED_FILES = 5;
392
+ var MAX_UPLOADED_FILE_SIZE_BYTES = 102400;
393
+ var UPLOADED_FILE_EXTENSIONS = [".md", ".txt", ".csv", ".json", ".yaml", ".yml", ".xml", ".html"];
394
+ var MAX_UPLOADED_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
395
+ var ALLOWED_IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
396
+
397
+ // ../../shared/utils/uploads.ts
398
+ var ALLOWED_IMAGE_MEDIA_TYPE_SET = new Set(ALLOWED_IMAGE_MEDIA_TYPES);
399
+ function ok(value) {
400
+ return { ok: true, value };
401
+ }
402
+ function fail(error) {
403
+ return { ok: false, error };
404
+ }
405
+ function validateUploadedName(name, kind) {
406
+ const trimmed = name.trim();
407
+ if (!trimmed) {
408
+ return `Uploaded ${kind} name must be a non-empty string`;
409
+ }
410
+ if (trimmed.includes("/") || trimmed.includes("\\")) {
411
+ return `Invalid uploaded ${kind} name: ${trimmed}`;
412
+ }
413
+ return null;
414
+ }
415
+ function trimNamedItems(items) {
416
+ return items.map((item) => ({ ...item, name: item.name.trim() }));
417
+ }
418
+ function hasNullBytes(content) {
419
+ return content.slice(0, 8192).includes("\0");
420
+ }
421
+ function checkUploadCapacity(currentCount, incomingCount, max, label) {
422
+ const remaining = max - currentCount;
423
+ if (incomingCount > remaining) {
424
+ return `Maximum ${max} ${label} (${remaining} remaining)`;
425
+ }
426
+ return null;
427
+ }
428
+ function deduplicateByName(items, existingNames = []) {
429
+ const seen = new Set(existingNames);
430
+ const deduped = [];
431
+ for (const item of items) {
432
+ if (seen.has(item.name)) continue;
433
+ seen.add(item.name);
434
+ deduped.push(item);
435
+ }
436
+ return deduped;
437
+ }
438
+ function validateUploadedFilePayload(files, existingNames = []) {
439
+ const trimmed = trimNamedItems(files);
440
+ const deduped = deduplicateByName(trimmed, existingNames);
441
+ const capacityError = checkUploadCapacity(existingNames.length, deduped.length, MAX_UPLOADED_FILES, "uploaded files");
442
+ if (capacityError) return fail(capacityError);
443
+ for (const file of deduped) {
444
+ const nameError = validateUploadedName(file.name, "file");
445
+ if (nameError) return fail(nameError);
446
+ const byteLength = new TextEncoder().encode(file.content).byteLength;
447
+ if (byteLength > MAX_UPLOADED_FILE_SIZE_BYTES) {
448
+ return fail(`Uploaded file too large: ${file.name} (${byteLength} bytes, max ${MAX_UPLOADED_FILE_SIZE_BYTES})`);
449
+ }
450
+ if (hasNullBytes(file.content)) {
451
+ return fail(`Binary files are not supported: ${file.name}`);
452
+ }
453
+ }
454
+ return ok(deduped);
455
+ }
456
+
457
+ // src/uploads.ts
458
+ async function resolveUploadedFileOptions(files) {
459
+ const paths = normalizeUploadedFileOptions(files);
460
+ if (paths.length === 0) return void 0;
461
+ const names = paths.map((path) => basename(path));
462
+ validateUploadedFileNames(names);
463
+ const uploadedFiles = await Promise.all(
464
+ paths.map(async (path) => {
465
+ const name = basename(path);
466
+ try {
467
+ return { name, content: await readFile(path, "utf8") };
468
+ } catch (err) {
469
+ const message = err instanceof Error ? err.message : String(err);
470
+ throw new CliError("user", `Failed to read uploaded file ${path}: ${message}`);
471
+ }
472
+ })
473
+ );
474
+ const validation = validateUploadedFilePayload(uploadedFiles);
475
+ if (!validation.ok) {
476
+ throw new CliError("user", validation.error);
477
+ }
478
+ return validation.value;
479
+ }
480
+ function collectUploadedFileOption(path, previous = []) {
481
+ return [...previous, path];
482
+ }
483
+ function validateUploadedFileNames(names) {
484
+ const seen = /* @__PURE__ */ new Set();
485
+ const duplicates = /* @__PURE__ */ new Set();
486
+ for (const name of names) {
487
+ if (seen.has(name)) duplicates.add(name);
488
+ seen.add(name);
489
+ }
490
+ if (duplicates.size > 0) {
491
+ throw new CliError(
492
+ "user",
493
+ `Duplicate uploaded-file basenames: ${[...duplicates].join(", ")}. Rename or move them so each basename is unique.`
494
+ );
495
+ }
496
+ for (const name of names) {
497
+ const ext = extname(name).toLowerCase();
498
+ if (!UPLOADED_FILE_EXTENSIONS.includes(ext)) {
499
+ throw new CliError("user", `Unsupported file type: ${name}. Supported: ${UPLOADED_FILE_EXTENSIONS.join(", ")}`);
500
+ }
501
+ }
502
+ }
503
+ function normalizeUploadedFileOptions(files) {
504
+ if (!files) return [];
505
+ return Array.isArray(files) ? files : [files];
506
+ }
507
+
386
508
  // ../../shared/utils/timing.ts
387
509
  function sleep(ms) {
388
510
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -577,7 +699,6 @@ function getRawSessionEventData(event) {
577
699
  case "idle":
578
700
  return withPromptId(event, {
579
701
  ...base,
580
- ...typeof payload.taskType === "string" ? { taskType: payload.taskType } : {},
581
702
  ...typeof payload.sessionEditCount === "number" ? { sessionEditCount: payload.sessionEditCount } : {},
582
703
  ...typeof payload.sessionPromptCount === "number" ? { sessionPromptCount: payload.sessionPromptCount } : {}
583
704
  });
@@ -1163,6 +1284,9 @@ ${event.answer ? `**Answer:** ${event.answer}
1163
1284
  `;
1164
1285
  case "session_resumed_cold":
1165
1286
  return `*[sandbox cold-restarted; in-sandbox state was lost${event.lostSnapshotImageId ? ` (snapshot ${event.lostSnapshotImageId} no longer usable)` : ""}]*
1287
+ `;
1288
+ case "customer_activity":
1289
+ return `**${event.title}:** ${event.summary}
1166
1290
  `;
1167
1291
  case "raw_opencode":
1168
1292
  return "";
@@ -1570,6 +1694,7 @@ async function createCommand(repoUrl, promptArg, options, command) {
1570
1694
  if (repoError) {
1571
1695
  throw new CliError("user", repoError);
1572
1696
  }
1697
+ const uploadedFiles = await resolveUploadedFileOptions(options.uploadedFile);
1573
1698
  const idempotencyKey = options.idempotencyKey ?? randomIdempotencyKey();
1574
1699
  const sessionIdempotencyKey = `${idempotencyKey}:session`;
1575
1700
  const promptIdempotencyKey = `${idempotencyKey}:prompt`;
@@ -1591,7 +1716,7 @@ async function createCommand(repoUrl, promptArg, options, command) {
1591
1716
  {
1592
1717
  method: "POST",
1593
1718
  headers: { "Idempotency-Key": promptIdempotencyKey },
1594
- body: JSON.stringify({ prompt })
1719
+ body: JSON.stringify({ prompt, ...uploadedFiles?.length ? { uploadedFiles } : {} })
1595
1720
  }
1596
1721
  );
1597
1722
  promptId = promptData.prompt?.promptId ?? promptData.prompt?.id;
@@ -1678,13 +1803,14 @@ async function messageCommand(sessionId, promptArg, options = {}, command) {
1678
1803
  const runtime = getRuntimeOptions(command, options);
1679
1804
  const config = requireConfig(runtime);
1680
1805
  const prompt = await resolvePromptInput(promptArg, options);
1806
+ const uploadedFiles = await resolveUploadedFileOptions(options.uploadedFile);
1681
1807
  const response = await apiFetch(
1682
1808
  config,
1683
1809
  `/api/sessions/${sessionId}/prompts`,
1684
1810
  {
1685
1811
  method: "POST",
1686
1812
  headers: { "Idempotency-Key": options.idempotencyKey ?? randomIdempotencyKey() },
1687
- body: JSON.stringify({ prompt })
1813
+ body: JSON.stringify({ prompt, ...uploadedFiles?.length ? { uploadedFiles } : {} })
1688
1814
  }
1689
1815
  );
1690
1816
  const promptId = response.prompt?.promptId ?? response.prompt?.id;
@@ -1930,7 +2056,11 @@ program.hook("preAction", (_thisCommand, actionCommand) => {
1930
2056
  applyColorEnvironment(getRuntimeOptions(actionCommand));
1931
2057
  });
1932
2058
  function addCreateOptions(cmd) {
1933
- return cmd.argument("<repo-url>", "Repository URL").argument("[prompt]", "Prompt to send, or '-' to read stdin").option("--model <model>", "Model to use").option("--reasoning-effort <effort>", "Reasoning effort to use for models that support it").option("--prompt-stdin", "Read prompt from stdin").option("--wait", "Wait for the created prompt to finish and exit non-zero if it fails").option(
2059
+ return cmd.argument("<repo-url>", "Repository URL").argument("[prompt]", "Prompt to send, or '-' to read stdin").option("--model <model>", "Model to use").option("--reasoning-effort <effort>", "Reasoning effort to use for models that support it").option("--prompt-stdin", "Read prompt from stdin").option(
2060
+ "--uploaded-file <path>",
2061
+ "Attach a local text file to the prompt as uploadedFiles; repeat for multiple files",
2062
+ collectUploadedFileOption
2063
+ ).option("--wait", "Wait for the created prompt to finish and exit non-zero if it fails").option(
1934
2064
  "--poll-interval <ms>",
1935
2065
  "Polling interval in milliseconds while waiting",
1936
2066
  String(DEFAULT_WATCH_POLL_INTERVAL_MS)
@@ -1939,6 +2069,7 @@ function addCreateOptions(cmd) {
1939
2069
  `
1940
2070
  Examples:
1941
2071
  arcanist sessions create https://github.com/org/repo "fix bug"
2072
+ arcanist sessions create https://github.com/org/repo "review this trace" --uploaded-file trace.txt
1942
2073
  printf "fix bug" | arcanist sessions create https://github.com/org/repo --prompt-stdin --json
1943
2074
  printf "fix bug" | arcanist sessions create https://github.com/org/repo --prompt-stdin --wait
1944
2075
  arcanist sessions create https://github.com/org/repo - --json | jq -r .sessionId | xargs -I{} arcanist sessions events {} --follow --json
@@ -1946,11 +2077,16 @@ Examples:
1946
2077
  );
1947
2078
  }
1948
2079
  function addSendOptions(cmd) {
1949
- return cmd.argument("<session-id>", "Session ID").argument("[prompt]", "Prompt to send, or '-' to read stdin").option("--prompt-stdin", "Read prompt from stdin").option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText(
2080
+ return cmd.argument("<session-id>", "Session ID").argument("[prompt]", "Prompt to send, or '-' to read stdin").option("--prompt-stdin", "Read prompt from stdin").option(
2081
+ "--uploaded-file <path>",
2082
+ "Attach a local text file to the prompt as uploadedFiles; repeat for multiple files",
2083
+ collectUploadedFileOption
2084
+ ).option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText(
1950
2085
  "after",
1951
2086
  `
1952
2087
  Examples:
1953
2088
  arcanist sessions send <session-id> "also update tests"
2089
+ arcanist sessions send <session-id> "use this log" --uploaded-file failing.log
1954
2090
  printf "also update tests" | arcanist sessions send <session-id> --prompt-stdin --json
1955
2091
  `
1956
2092
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {