commonswarm 0.1.18 → 0.1.20

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/cswarm.cjs +643 -42
  2. package/package.json +1 -1
package/cswarm.cjs CHANGED
@@ -13513,7 +13513,7 @@ __export(cli_exports, {
13513
13513
  resolveDetachedCodexExecutable: () => resolveDetachedCodexExecutable
13514
13514
  });
13515
13515
  module.exports = __toCommonJS(cli_exports);
13516
- var import_node_crypto18 = require("node:crypto");
13516
+ var import_node_crypto19 = require("node:crypto");
13517
13517
  var import_node_fs7 = require("node:fs");
13518
13518
  var import_promises10 = require("node:fs/promises");
13519
13519
  var import_node_path17 = require("node:path");
@@ -22105,7 +22105,7 @@ registerUpcaster("TaskCreated", 0, (p) => ({ task_id: p.id, slug: p.name }));
22105
22105
  // src/protocol/workspace-commands.ts
22106
22106
  var INVITATION_MAX_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
22107
22107
  var AGENT_TOKEN_DEFAULT_TTL_MS = 60 * 60 * 1e3;
22108
- var AGENT_TOKEN_MAX_TTL_MS = 8 * 60 * 60 * 1e3;
22108
+ var AGENT_TOKEN_MAX_TTL_MS = 24 * 60 * 60 * 1e3;
22109
22109
  var RENEWAL_HORIZON_DEFAULT_MS = 30 * 24 * 60 * 60 * 1e3;
22110
22110
  var RENEWAL_HORIZON_MAX_MS = 90 * 24 * 60 * 60 * 1e3;
22111
22111
 
@@ -22380,6 +22380,41 @@ async function raceSignalDeadline(work, deadline, callerAbort) {
22380
22380
  }
22381
22381
  return await Promise.race(arms);
22382
22382
  }
22383
+ async function declareAgentModel(target2, request, fetcher = fetch) {
22384
+ const controller = new AbortController();
22385
+ const timer2 = setTimeout(() => controller.abort(), 3e4);
22386
+ const onCallerAbort = () => controller.abort();
22387
+ request.signal?.addEventListener("abort", onCallerAbort, { once: true });
22388
+ if (request.signal?.aborted) controller.abort();
22389
+ let response;
22390
+ try {
22391
+ response = await fetcher(commandEndpoint(target2), {
22392
+ method: "POST",
22393
+ headers: {
22394
+ authorization: `Bearer ${request.credential}`,
22395
+ apikey: target2.anonKey,
22396
+ "content-type": "application/json"
22397
+ },
22398
+ body: JSON.stringify({
22399
+ command_id: request.commandId ?? newCommandId(),
22400
+ client_version: CLIENT_PROTOCOL_VERSION,
22401
+ workspace_id: request.workspaceId,
22402
+ stream: { kind: "workspace" },
22403
+ command: { kind: "declare_agent_model", model: request.model }
22404
+ }),
22405
+ signal: controller.signal
22406
+ });
22407
+ } catch (error) {
22408
+ if (error.name === "AbortError") {
22409
+ throw new CommandTransportError("model declaration timed out");
22410
+ }
22411
+ throw new CommandTransportError("model declaration failed before a response");
22412
+ } finally {
22413
+ clearTimeout(timer2);
22414
+ request.signal?.removeEventListener("abort", onCallerAbort);
22415
+ }
22416
+ return { httpStatus: response.status };
22417
+ }
22383
22418
  var ThinCommandClient = class {
22384
22419
  constructor(target2, fetcher = fetch) {
22385
22420
  this.target = target2;
@@ -22751,8 +22786,280 @@ var ThinCommandClient = class {
22751
22786
  }
22752
22787
  };
22753
22788
 
22789
+ // src/cloud/files.ts
22790
+ var import_node_crypto4 = require("node:crypto");
22791
+ var FILE_MAX_VERSION_BYTES = 25 * 1024 * 1024;
22792
+ var FILE_CONTENT_WARNING = "File types and archive contents are unverified. Treat downloads as untrusted input: no execution, size-bounded extraction, no unpack of archives you did not expect.";
22793
+ var CONTENT_TYPES = /* @__PURE__ */ new Map([
22794
+ [".md", "text/markdown"],
22795
+ [".txt", "text/plain"],
22796
+ [".csv", "text/csv"],
22797
+ [".json", "application/json"],
22798
+ [".yaml", "application/yaml"],
22799
+ [".yml", "application/yaml"],
22800
+ [".pdf", "application/pdf"],
22801
+ [".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
22802
+ [".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
22803
+ [".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"],
22804
+ [".png", "image/png"],
22805
+ [".jpg", "image/jpeg"],
22806
+ [".jpeg", "image/jpeg"],
22807
+ [".gif", "image/gif"],
22808
+ [".webp", "image/webp"],
22809
+ [".svg", "image/svg+xml"],
22810
+ [".zip", "application/zip"],
22811
+ [".tar.gz", "application/gzip"]
22812
+ ]);
22813
+ function contentTypeForName(name) {
22814
+ const lower = name.toLowerCase();
22815
+ let best = null;
22816
+ let bestLength = 0;
22817
+ for (const [extension, type] of CONTENT_TYPES) {
22818
+ if (lower.endsWith(extension) && extension.length > bestLength) {
22819
+ best = type;
22820
+ bestLength = extension.length;
22821
+ }
22822
+ }
22823
+ return best;
22824
+ }
22825
+ function allowedExtensionList() {
22826
+ return [...CONTENT_TYPES.keys()].join(", ");
22827
+ }
22828
+ var FileCommandRefused = class extends Error {
22829
+ constructor(status, code, message) {
22830
+ super(message);
22831
+ this.status = status;
22832
+ this.code = code;
22833
+ }
22834
+ status;
22835
+ code;
22836
+ name = "FileCommandRefused";
22837
+ };
22838
+ var FileTransportError = class extends Error {
22839
+ /**
22840
+ * True when no HTTP response arrived (connection failure, timeout), so the
22841
+ * outcome is UNKNOWN and one same-id retry is safe under the server's
22842
+ * command-id replay. A received refusal is a known outcome: never retried.
22843
+ */
22844
+ constructor(message, noResponse = false) {
22845
+ super(message);
22846
+ this.noResponse = noResponse;
22847
+ }
22848
+ noResponse;
22849
+ name = "FileTransportError";
22850
+ };
22851
+ var REQUEST_TIMEOUT_MS = 3e4;
22852
+ async function sendFileCommand(options, command2) {
22853
+ const fetcher = options.fetcher ?? fetch;
22854
+ const controller = new AbortController();
22855
+ const timer2 = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
22856
+ let response;
22857
+ try {
22858
+ response = await fetcher(commandEndpoint(options.target), {
22859
+ method: "POST",
22860
+ headers: {
22861
+ authorization: `Bearer ${options.credential}`,
22862
+ apikey: options.target.anonKey,
22863
+ "content-type": "application/json"
22864
+ },
22865
+ body: JSON.stringify({
22866
+ command_id: options.commandId ?? newCommandId(),
22867
+ client_version: "0.1.0",
22868
+ workspace_id: options.workspaceId,
22869
+ stream: { kind: "workspace" },
22870
+ command: command2
22871
+ }),
22872
+ signal: controller.signal
22873
+ });
22874
+ } catch (error) {
22875
+ if (error.name === "AbortError") {
22876
+ throw new FileTransportError("file command timed out", true);
22877
+ }
22878
+ throw new FileTransportError("file command failed before a response", true);
22879
+ } finally {
22880
+ clearTimeout(timer2);
22881
+ }
22882
+ const body = await response.json().catch(() => null);
22883
+ if (!response.ok) {
22884
+ const code = typeof body?.error === "string" ? body.error : "http_error";
22885
+ const message = typeof body?.message === "string" ? body.message : `file command failed (HTTP ${response.status})`;
22886
+ throw new FileCommandRefused(response.status, code, message);
22887
+ }
22888
+ if (!body || typeof body !== "object") {
22889
+ throw new FileTransportError("file command returned a malformed response");
22890
+ }
22891
+ return body;
22892
+ }
22893
+ function fileVersionCreate(options, input) {
22894
+ return sendFileCommand(options, {
22895
+ kind: "file_version_create",
22896
+ file_id: input.fileId,
22897
+ version_id: input.versionId,
22898
+ name: input.name,
22899
+ declared_size_bytes: input.declaredSizeBytes,
22900
+ content_type: input.contentType
22901
+ });
22902
+ }
22903
+ function fileVersionCommit(options, input) {
22904
+ return sendFileCommand(options, {
22905
+ kind: "file_version_commit",
22906
+ file_id: input.fileId,
22907
+ version_id: input.versionId,
22908
+ sha256: input.sha256
22909
+ });
22910
+ }
22911
+ function fileDownloadUrl(options, input) {
22912
+ return sendFileCommand(options, {
22913
+ kind: "file_download_url",
22914
+ file_id: input.fileId,
22915
+ version_n: input.versionN
22916
+ });
22917
+ }
22918
+ function fileTombstone(options, input) {
22919
+ return sendFileCommand(options, {
22920
+ kind: "file_tombstone",
22921
+ file_id: input.fileId
22922
+ });
22923
+ }
22924
+ function fileRestore(options, input) {
22925
+ return sendFileCommand(options, {
22926
+ kind: "file_restore",
22927
+ file_id: input.fileId
22928
+ });
22929
+ }
22930
+ function absoluteStorageUrl(target2, path) {
22931
+ if (!path.startsWith("/")) {
22932
+ throw new FileTransportError(
22933
+ "the server returned a storage path that is not relative; refusing to compose a URL from it"
22934
+ );
22935
+ }
22936
+ return `${target2.url}${path}`;
22937
+ }
22938
+ async function putObject(target2, uploadPath, bytes, contentType, fetcher = fetch) {
22939
+ let response;
22940
+ try {
22941
+ response = await fetcher(absoluteStorageUrl(target2, uploadPath), {
22942
+ method: "PUT",
22943
+ headers: { "content-type": contentType },
22944
+ /* Node's Buffer types as Uint8Array<ArrayBufferLike>, which the DOM-lib
22945
+ * BodyInit rejects since TS 5.7; the runtime accepts it. */
22946
+ body: bytes
22947
+ });
22948
+ } catch {
22949
+ throw new FileTransportError("the upload PUT failed before a response", true);
22950
+ }
22951
+ if (!response.ok) {
22952
+ throw new FileTransportError(
22953
+ `the upload PUT was refused (HTTP ${response.status}). Nothing went live, and this attempt's pending slot expires on its own within three hours. Check cswarm file ls, then re-run cswarm file put \u2014 a re-run is a new upload attempt with fresh ids`
22954
+ );
22955
+ }
22956
+ }
22957
+ async function onceRetried(step) {
22958
+ try {
22959
+ return await step();
22960
+ } catch (error) {
22961
+ if (error instanceof FileTransportError && error.noResponse) {
22962
+ return await step();
22963
+ }
22964
+ throw error;
22965
+ }
22966
+ }
22967
+ async function getObject(target2, downloadPath, fetcher = fetch) {
22968
+ let response;
22969
+ try {
22970
+ response = await fetcher(absoluteStorageUrl(target2, downloadPath));
22971
+ } catch {
22972
+ throw new FileTransportError("the download failed before a response");
22973
+ }
22974
+ if (!response.ok) {
22975
+ throw new FileTransportError(
22976
+ `the download was refused (HTTP ${response.status}); the signed URL lasts five minutes \u2014 request a fresh one with cswarm file get`
22977
+ );
22978
+ }
22979
+ return new Uint8Array(await response.arrayBuffer());
22980
+ }
22981
+ var LocalFileExists = class extends Error {
22982
+ name = "LocalFileExists";
22983
+ };
22984
+ function writeDestination(destination, bytes, force, writer) {
22985
+ try {
22986
+ writer(destination, bytes, { flag: force ? "w" : "wx" });
22987
+ } catch (error) {
22988
+ if (error.code === "EEXIST") {
22989
+ throw new LocalFileExists(
22990
+ `${destination} already exists locally; nothing was written. Pass --force to overwrite it, or --out <path> to write elsewhere`
22991
+ );
22992
+ }
22993
+ throw error;
22994
+ }
22995
+ }
22996
+ function sha256Hex(bytes) {
22997
+ return (0, import_node_crypto4.createHash)("sha256").update(bytes).digest("hex");
22998
+ }
22999
+ async function listFilesAsAgent(target2, credential, workspaceId2, fetcher = fetch) {
23000
+ let response;
23001
+ try {
23002
+ response = await fetcher(readEndpoint(target2), {
23003
+ method: "POST",
23004
+ headers: {
23005
+ authorization: `Bearer ${credential}`,
23006
+ apikey: target2.anonKey,
23007
+ "content-type": "application/json"
23008
+ },
23009
+ body: JSON.stringify({ resource: "files", workspace_id: workspaceId2 })
23010
+ });
23011
+ } catch {
23012
+ throw new FileTransportError("file list could not reach the cloud service");
23013
+ }
23014
+ if (!response.ok) {
23015
+ throw new FileCommandRefused(
23016
+ response.status,
23017
+ "http_error",
23018
+ `file list failed (HTTP ${response.status})`
23019
+ );
23020
+ }
23021
+ const body = await response.json().catch(() => null);
23022
+ if (!body || !Array.isArray(body.files)) {
23023
+ throw new FileTransportError("file list returned a malformed response");
23024
+ }
23025
+ return body.files;
23026
+ }
23027
+ async function listFilesAsHuman(target2, accessToken, workspaceId2, fetcher = fetch) {
23028
+ const url = new URL("/rest/v1/files", target2.url);
23029
+ url.searchParams.set("workspace_id", `eq.${workspaceId2}`);
23030
+ url.searchParams.set(
23031
+ "select",
23032
+ "file_id,name,current_version,size_bytes,content_type,sha256,created_by_kind,created_by,uploaded_by_kind,uploaded_by,created_at,committed_at,tombstoned_at"
23033
+ );
23034
+ url.searchParams.set("order", "name.asc");
23035
+ let response;
23036
+ try {
23037
+ response = await fetcher(url, {
23038
+ headers: {
23039
+ authorization: `Bearer ${accessToken}`,
23040
+ apikey: target2.anonKey,
23041
+ "accept-profile": "swarm_read"
23042
+ }
23043
+ });
23044
+ } catch {
23045
+ throw new FileTransportError("file list could not reach the cloud service");
23046
+ }
23047
+ if (!response.ok) {
23048
+ throw new FileCommandRefused(
23049
+ response.status,
23050
+ "http_error",
23051
+ `file list failed (HTTP ${response.status})`
23052
+ );
23053
+ }
23054
+ const body = await response.json().catch(() => null);
23055
+ if (!Array.isArray(body)) {
23056
+ throw new FileTransportError("file list returned a malformed response");
23057
+ }
23058
+ return body;
23059
+ }
23060
+
22754
23061
  // src/cloud/current-target.ts
22755
- var import_node_crypto5 = require("node:crypto");
23062
+ var import_node_crypto6 = require("node:crypto");
22756
23063
  var import_promises3 = require("node:fs/promises");
22757
23064
  var import_node_path2 = require("node:path");
22758
23065
 
@@ -22761,7 +23068,7 @@ var import_node_fs = require("node:fs");
22761
23068
  var import_promises = require("node:fs/promises");
22762
23069
  var import_node_os = require("node:os");
22763
23070
  var import_node_path = require("node:path");
22764
- var import_node_crypto4 = require("node:crypto");
23071
+ var import_node_crypto5 = require("node:crypto");
22765
23072
  var import_node_child_process2 = require("node:child_process");
22766
23073
  var import_promises2 = require("node:timers/promises");
22767
23074
  var KEYCHAIN_SERVICE = "com.commonswarm.cli";
@@ -22924,7 +23231,7 @@ async function withFileLock(stateDirectory2, lockName, work) {
22924
23231
  if (Date.now() >= deadline) {
22925
23232
  throw new Error("timed out waiting for the credential refresh lock");
22926
23233
  }
22927
- await (0, import_promises2.setTimeout)(25 + (0, import_node_crypto4.randomBytes)(1)[0] % 75);
23234
+ await (0, import_promises2.setTimeout)(25 + (0, import_node_crypto5.randomBytes)(1)[0] % 75);
22928
23235
  }
22929
23236
  }
22930
23237
  try {
@@ -22941,7 +23248,7 @@ async function writeSecureJsonFile(path, serialized) {
22941
23248
  } catch (error) {
22942
23249
  if (error.code !== "ENOENT") throw error;
22943
23250
  }
22944
- const temporary = `${path}.${process.pid}.${(0, import_node_crypto4.randomBytes)(6).toString("hex")}.tmp`;
23251
+ const temporary = `${path}.${process.pid}.${(0, import_node_crypto5.randomBytes)(6).toString("hex")}.tmp`;
22945
23252
  const handle = await (0, import_promises.open)(temporary, "wx", 384);
22946
23253
  try {
22947
23254
  await handle.writeFile(serialized, "utf8");
@@ -23291,7 +23598,7 @@ async function writeCurrentTarget(target2, options = {}) {
23291
23598
  anonKey: validated.anonKey
23292
23599
  };
23293
23600
  const serialized = JSON.stringify(record);
23294
- const temporary = `${path}.${process.pid}.${(0, import_node_crypto5.randomBytes)(6).toString("hex")}.tmp`;
23601
+ const temporary = `${path}.${process.pid}.${(0, import_node_crypto6.randomBytes)(6).toString("hex")}.tmp`;
23295
23602
  const handle = await (0, import_promises3.open)(temporary, "wx", 384);
23296
23603
  try {
23297
23604
  await handle.writeFile(serialized, "utf8");
@@ -23319,7 +23626,7 @@ async function clearCurrentTarget(options = {}) {
23319
23626
  }
23320
23627
  }
23321
23628
  function targetFingerprint(target2) {
23322
- return (0, import_node_crypto5.createHash)("sha256").update(target2.anonKey).digest("hex").slice(0, 12);
23629
+ return (0, import_node_crypto6.createHash)("sha256").update(target2.anonKey).digest("hex").slice(0, 12);
23323
23630
  }
23324
23631
  function currentTargetSummary(target2, reveal = false) {
23325
23632
  return {
@@ -23414,7 +23721,7 @@ async function resolveCloudTarget(options) {
23414
23721
  }
23415
23722
 
23416
23723
  // src/cloud/seed.ts
23417
- var import_node_crypto6 = require("node:crypto");
23724
+ var import_node_crypto7 = require("node:crypto");
23418
23725
 
23419
23726
  // node_modules/postgres/src/index.js
23420
23727
  var import_os = __toESM(require("os"), 1);
@@ -25552,7 +25859,7 @@ var P0_SCOPES = [
25552
25859
  "post_signal"
25553
25860
  ];
25554
25861
  function deterministicUuid(label) {
25555
- const bytes = (0, import_node_crypto6.createHash)("sha256").update(label).digest().subarray(0, 16);
25862
+ const bytes = (0, import_node_crypto7.createHash)("sha256").update(label).digest().subarray(0, 16);
25556
25863
  bytes[6] = bytes[6] & 15 | 80;
25557
25864
  bytes[8] = bytes[8] & 63 | 128;
25558
25865
  const hex = bytes.toString("hex");
@@ -25725,10 +26032,10 @@ async function seedDogfood(options) {
25725
26032
  tokenExpiresAt: existing[0].expires_at.toISOString()
25726
26033
  };
25727
26034
  }
25728
- const agentToken = `swm_agt_${(0, import_node_crypto6.randomBytes)(32).toString("base64url")}`;
25729
- const tokenHash = (0, import_node_crypto6.createHash)("sha256").update(agentToken).digest();
25730
- const tokenId = (0, import_node_crypto6.randomUUID)();
25731
- const lineageId = (0, import_node_crypto6.randomUUID)();
26035
+ const agentToken = `swm_agt_${(0, import_node_crypto7.randomBytes)(32).toString("base64url")}`;
26036
+ const tokenHash = (0, import_node_crypto7.createHash)("sha256").update(agentToken).digest();
26037
+ const tokenId = (0, import_node_crypto7.randomUUID)();
26038
+ const lineageId = (0, import_node_crypto7.randomUUID)();
25732
26039
  const inserted = await tx`
25733
26040
  INSERT INTO swarm.agent_tokens (
25734
26041
  token_id, principal_id, run_id, task_id, epoch,
@@ -25768,14 +26075,14 @@ async function seedDogfood(options) {
25768
26075
  }
25769
26076
 
25770
26077
  // src/cloud/agent-credential.ts
25771
- var import_node_crypto7 = require("node:crypto");
26078
+ var import_node_crypto8 = require("node:crypto");
25772
26079
  var import_node_os2 = require("node:os");
25773
26080
  var import_node_path3 = require("node:path");
25774
26081
  var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
25775
26082
  var AGENT_TOKEN_RE2 = /^swm_agt_[A-Za-z0-9_-]{43}$/;
25776
26083
  var MAX_RECORD_BYTES = 4 * 1024;
25777
26084
  function credentialLineageKey(rootToken) {
25778
- return (0, import_node_crypto7.createHash)("sha256").update(rootToken).digest("hex").slice(0, 32);
26085
+ return (0, import_node_crypto8.createHash)("sha256").update(rootToken).digest("hex").slice(0, 32);
25779
26086
  }
25780
26087
  function defaultAgentCredentialDirectory() {
25781
26088
  const configured = process.env.SWARM_AGENT_STATE_DIR;
@@ -25835,7 +26142,7 @@ async function agentCredentialStore(options) {
25835
26142
  }
25836
26143
 
25837
26144
  // src/cloud/renewal.ts
25838
- var import_node_crypto8 = require("node:crypto");
26145
+ var import_node_crypto9 = require("node:crypto");
25839
26146
  var AGENT_TOKEN_DEFAULT_TTL_MS2 = 60 * 60 * 1e3;
25840
26147
  var AGENT_TOKEN_MAX_TTL_MS2 = 8 * 60 * 60 * 1e3;
25841
26148
  var RENEWAL_HORIZON_DEFAULT_MS2 = 30 * 24 * 60 * 60 * 1e3;
@@ -25920,7 +26227,7 @@ function renewalCommand() {
25920
26227
  return command2;
25921
26228
  }
25922
26229
  function newRenewalCommandId() {
25923
- return `ren_${(0, import_node_crypto8.randomBytes)(18).toString("base64url")}`;
26230
+ return `ren_${(0, import_node_crypto9.randomBytes)(18).toString("base64url")}`;
25924
26231
  }
25925
26232
  function timestamp(value) {
25926
26233
  if (typeof value === "number" && Number.isFinite(value) && value > 0) {
@@ -26406,11 +26713,11 @@ var AgentCredentialSession = class _AgentCredentialSession {
26406
26713
  };
26407
26714
 
26408
26715
  // src/cloud/pending-command.ts
26409
- var import_node_crypto9 = require("node:crypto");
26716
+ var import_node_crypto10 = require("node:crypto");
26410
26717
  var MAX_PENDING_COMMANDS2 = 32;
26411
26718
  var SIGNAL_PENDING_RECOVERY_MS = 60 * 60 * 1e3;
26412
26719
  function intentHash(workspace, command2) {
26413
- return (0, import_node_crypto9.createHash)("sha256").update(canonicalJson({ workspace_id: workspace ?? null, command: command2 })).digest("hex");
26720
+ return (0, import_node_crypto10.createHash)("sha256").update(canonicalJson({ workspace_id: workspace ?? null, command: command2 })).digest("hex");
26414
26721
  }
26415
26722
  async function pendingCommandId(credentials, userId, workspace, command2) {
26416
26723
  const intent = intentHash(workspace, command2);
@@ -26510,7 +26817,7 @@ async function sendCapabilityWithPending(client, session, workspace, command2) {
26510
26817
  }
26511
26818
  }
26512
26819
  function signalIntentHash(workspace, command2, credentialIdentity) {
26513
- return (0, import_node_crypto9.createHash)("sha256").update(canonicalJson({
26820
+ return (0, import_node_crypto10.createHash)("sha256").update(canonicalJson({
26514
26821
  workspace_id: workspace,
26515
26822
  command: command2,
26516
26823
  credential_identity: credentialIdentity
@@ -26592,7 +26899,7 @@ async function sendSignalWithPending(client, session, workspace, command2) {
26592
26899
  var import_node_os3 = require("node:os");
26593
26900
 
26594
26901
  // src/cloud/invite-link.ts
26595
- var import_node_crypto10 = require("node:crypto");
26902
+ var import_node_crypto11 = require("node:crypto");
26596
26903
  var MAX_LINK_PAYLOAD_BYTES = 8 * 1024;
26597
26904
  var MAX_LABEL_INPUT_LENGTH = 1024;
26598
26905
  var CONTROL_GLOBAL_RE = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g;
@@ -26763,7 +27070,7 @@ function equalExact(actual, expected) {
26763
27070
  const paddedRight = Buffer.alloc(length);
26764
27071
  left.copy(paddedLeft);
26765
27072
  right.copy(paddedRight);
26766
- const equal = (0, import_node_crypto10.timingSafeEqual)(paddedLeft, paddedRight);
27073
+ const equal = (0, import_node_crypto11.timingSafeEqual)(paddedLeft, paddedRight);
26767
27074
  return equal && left.length === right.length;
26768
27075
  }
26769
27076
  async function requirePinnedOrigin(target2, options) {
@@ -29065,7 +29372,7 @@ async function runInboxFollow(options) {
29065
29372
 
29066
29373
  // src/host/opencode.ts
29067
29374
  var import_node_child_process3 = require("node:child_process");
29068
- var import_node_crypto11 = require("node:crypto");
29375
+ var import_node_crypto12 = require("node:crypto");
29069
29376
  var import_node_fs3 = require("node:fs");
29070
29377
  var import_promises4 = require("node:fs/promises");
29071
29378
  var import_node_os4 = require("node:os");
@@ -30250,7 +30557,7 @@ function buildOpenCodeHomeOwner(options) {
30250
30557
  version: 1,
30251
30558
  pid: options.pid ?? process.pid,
30252
30559
  uid: uid2,
30253
- instanceId: options.instanceId ?? (0, import_node_crypto11.randomUUID)(),
30560
+ instanceId: options.instanceId ?? (0, import_node_crypto12.randomUUID)(),
30254
30561
  role: options.role,
30255
30562
  createdAt: new Date((options.now ?? Date.now)()).toISOString()
30256
30563
  };
@@ -31798,7 +32105,7 @@ var ListenerEngine = class {
31798
32105
  };
31799
32106
 
31800
32107
  // src/listener/file-store.ts
31801
- var import_node_crypto12 = require("node:crypto");
32108
+ var import_node_crypto13 = require("node:crypto");
31802
32109
  var import_node_os5 = require("node:os");
31803
32110
  var import_node_path8 = require("node:path");
31804
32111
  var import_node_util = require("node:util");
@@ -31845,7 +32152,7 @@ function listenerInstanceKey(input) {
31845
32152
  if (!input.profileId || input.profileId.includes("\0")) {
31846
32153
  throw new Error("listener profile id is invalid");
31847
32154
  }
31848
- return (0, import_node_crypto12.createHash)("sha256").update(input.profileId).update("\0").update(input.workspaceId.toLowerCase()).update("\0").update(input.principalId.toLowerCase()).digest("hex");
32155
+ return (0, import_node_crypto13.createHash)("sha256").update(input.profileId).update("\0").update(input.workspaceId.toLowerCase()).update("\0").update(input.principalId.toLowerCase()).digest("hex");
31849
32156
  }
31850
32157
  function integer(value) {
31851
32158
  return Number.isSafeInteger(value) && value >= 0;
@@ -32380,7 +32687,7 @@ var GrokListenerModel = class {
32380
32687
  };
32381
32688
 
32382
32689
  // src/listener/opencode-model.ts
32383
- var import_node_crypto13 = require("node:crypto");
32690
+ var import_node_crypto14 = require("node:crypto");
32384
32691
  var import_promises6 = require("node:fs/promises");
32385
32692
  var import_node_path11 = require("node:path");
32386
32693
  function asError(error) {
@@ -32406,7 +32713,7 @@ var OpenCodeListenerModel = class {
32406
32713
  prepareWorkerCwd;
32407
32714
  permissionMode;
32408
32715
  pendingOpenWaitMs;
32409
- instanceId = (0, import_node_crypto13.randomUUID)();
32716
+ instanceId = (0, import_node_crypto14.randomUUID)();
32410
32717
  worker = null;
32411
32718
  workerHome = null;
32412
32719
  /** Worker homes retained after failed close or unsettled open. */
@@ -32810,7 +33117,7 @@ var OpenCodeListenerModel = class {
32810
33117
  };
32811
33118
 
32812
33119
  // src/listener/claude-model.ts
32813
- var import_node_crypto14 = require("node:crypto");
33120
+ var import_node_crypto15 = require("node:crypto");
32814
33121
  var import_promises7 = require("node:fs/promises");
32815
33122
  var import_node_os7 = require("node:os");
32816
33123
  var import_node_path12 = require("node:path");
@@ -32950,7 +33257,7 @@ var ClaudeListenerModel = class {
32950
33257
  async enablePromptsAfterClaudeCanary(handle) {
32951
33258
  const sentinelPath = (0, import_node_path12.join)(
32952
33259
  (0, import_node_os7.tmpdir)(),
32953
- `cswarm-claude-permission-canary-${process.pid}-${(0, import_node_crypto14.randomUUID)()}`
33260
+ `cswarm-claude-permission-canary-${process.pid}-${(0, import_node_crypto15.randomUUID)()}`
32954
33261
  );
32955
33262
  let sentinelCreated = false;
32956
33263
  try {
@@ -32976,7 +33283,7 @@ var ClaudeListenerModel = class {
32976
33283
  };
32977
33284
 
32978
33285
  // src/listener/codex-model.ts
32979
- var import_node_crypto15 = require("node:crypto");
33286
+ var import_node_crypto16 = require("node:crypto");
32980
33287
  var import_promises8 = require("node:fs/promises");
32981
33288
  var import_node_os8 = require("node:os");
32982
33289
  var import_node_path13 = require("node:path");
@@ -33116,7 +33423,7 @@ var CodexListenerModel = class {
33116
33423
  async enablePromptsAfterCodexCanary(handle) {
33117
33424
  const sentinelPath = (0, import_node_path13.join)(
33118
33425
  (0, import_node_os8.tmpdir)(),
33119
- `cswarm-codex-permission-canary-${process.pid}-${(0, import_node_crypto15.randomUUID)()}`
33426
+ `cswarm-codex-permission-canary-${process.pid}-${(0, import_node_crypto16.randomUUID)()}`
33120
33427
  );
33121
33428
  let sentinelCreated = false;
33122
33429
  try {
@@ -33142,7 +33449,7 @@ var CodexListenerModel = class {
33142
33449
  };
33143
33450
 
33144
33451
  // src/listener/runtime.ts
33145
- var import_node_crypto16 = require("node:crypto");
33452
+ var import_node_crypto17 = require("node:crypto");
33146
33453
 
33147
33454
  // src/cloud/delivery.ts
33148
33455
  var UUID_RE10 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -33873,7 +34180,7 @@ function sameEffectSignal(record, signal) {
33873
34180
  return record.signalId === signal.id.toLowerCase() && record.signalKind === signal.kind && record.askBody === signal.body && record.askUntil === signal.until && record.senderOwnerRelation === (signal.sender_owner_relation ?? "unknown");
33874
34181
  }
33875
34182
  function immutableSignalFingerprint(signalId, signalKind2, body, until, senderOwnerRelation) {
33876
- return (0, import_node_crypto16.createHash)("sha256").update(JSON.stringify([
34183
+ return (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify([
33877
34184
  signalId,
33878
34185
  signalKind2,
33879
34186
  body,
@@ -34214,6 +34521,30 @@ async function runListenerRuntime(options) {
34214
34521
  principalId: options.principalId,
34215
34522
  ts: eventTime(now)
34216
34523
  });
34524
+ if (options.declareModel !== void 0) {
34525
+ const declaredLabel = options.declareModel;
34526
+ void (async () => {
34527
+ let declared = false;
34528
+ try {
34529
+ const credential = await options.credentialSession.bearer();
34530
+ const outcome = await declareAgentModel(options.target, {
34531
+ workspaceId: options.workspaceId,
34532
+ model: declaredLabel,
34533
+ credential,
34534
+ ...abort ? { signal: abort } : {}
34535
+ }, options.fetcher);
34536
+ declared = outcome.httpStatus === 200;
34537
+ } catch {
34538
+ declared = false;
34539
+ }
34540
+ options.onEvent?.({
34541
+ type: "model_declared",
34542
+ ok: declared,
34543
+ model: declaredLabel,
34544
+ ts: eventTime(now)
34545
+ });
34546
+ })();
34547
+ }
34217
34548
  }
34218
34549
  let currentJournalRecord = null;
34219
34550
  if (durableConfigured) {
@@ -35068,7 +35399,7 @@ async function queryListenerControl(paths, command2, timeoutMs = CONTROL_TIMEOUT
35068
35399
  }
35069
35400
 
35070
35401
  // src/listener/supervisor.ts
35071
- var import_node_crypto17 = require("node:crypto");
35402
+ var import_node_crypto18 = require("node:crypto");
35072
35403
  var UUID_RE13 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
35073
35404
  var LISTENER_RESTART_MAX_ATTEMPTS = 5;
35074
35405
  var LISTENER_RESTART_INITIAL_MS = 1e3;
@@ -35116,7 +35447,7 @@ async function runListenerSupervisor(options) {
35116
35447
  const now = options.now ?? Date.now;
35117
35448
  const startedAt = iso2(now);
35118
35449
  const controller = new AbortController();
35119
- const proposedInstanceId = (0, import_node_crypto17.randomUUID)();
35450
+ const proposedInstanceId = (0, import_node_crypto18.randomUUID)();
35120
35451
  let status = {
35121
35452
  version: 1,
35122
35453
  instanceId: proposedInstanceId,
@@ -35227,6 +35558,15 @@ async function runListenerSupervisor(options) {
35227
35558
  });
35228
35559
  return;
35229
35560
  }
35561
+ if (event.type === "model_declared") {
35562
+ log({
35563
+ ts: event.ts,
35564
+ event: "listener_model_declared",
35565
+ ok: event.ok,
35566
+ model: event.model
35567
+ });
35568
+ return;
35569
+ }
35230
35570
  if (event.type === "delivery_mode") {
35231
35571
  status = {
35232
35572
  ...status,
@@ -36276,12 +36616,14 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
36276
36616
  "epoch",
36277
36617
  "evidence",
36278
36618
  "follow",
36619
+ "force",
36279
36620
  "force-file-store",
36280
36621
  "foreground",
36281
36622
  "grok-executable",
36282
36623
  "head-sha",
36283
36624
  "help",
36284
36625
  "include-stale",
36626
+ "include-tombstoned",
36285
36627
  "invitation-id",
36286
36628
  "invitation-token-stdin",
36287
36629
  "json",
@@ -36294,6 +36636,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
36294
36636
  "ndjson",
36295
36637
  "no-browser",
36296
36638
  "opencode-executable",
36639
+ "out",
36297
36640
  "permissions",
36298
36641
  "principal-id",
36299
36642
  "provider",
@@ -36318,9 +36661,11 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
36318
36661
  "all-devices",
36319
36662
  "force-file-store",
36320
36663
  "follow",
36664
+ "force",
36321
36665
  "foreground",
36322
36666
  "help",
36323
36667
  "include-stale",
36668
+ "include-tombstoned",
36324
36669
  "invitation-token-stdin",
36325
36670
  "json",
36326
36671
  "link-stdin",
@@ -36337,8 +36682,8 @@ var ACCEPTED_AGENT_CREDENTIAL_MESSAGES = [
36337
36682
  AGENT_CREDENTIAL_MESSAGE_D088
36338
36683
  ];
36339
36684
  function packageVersion() {
36340
- if ("0.1.18".length > 0) {
36341
- return "0.1.18";
36685
+ if ("0.1.20".length > 0) {
36686
+ return "0.1.20";
36342
36687
  }
36343
36688
  try {
36344
36689
  const value = JSON.parse(
@@ -36460,6 +36805,11 @@ Usage:
36460
36805
  cswarm feed [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--about <ref>] [--kind <kind>] [--since <timestamp>] [--limit <n>] [--include-stale] [--json]
36461
36806
  cswarm inbox [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--kind <kind>] [--about <ref>] [--since <timestamp>] [--limit <n>] [--include-stale] [--wait <seconds>] [--json]
36462
36807
  cswarm inbox --follow --ndjson [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--kind <kind>] [--about <ref>] [--since <timestamp>] [--limit <n>] [--include-stale]
36808
+ cswarm file put <local-path> [--name <name>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
36809
+ cswarm file ls [--include-tombstoned] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
36810
+ cswarm file get <name|file-id> [--version <n>] [--out <local-path>] [--force] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
36811
+ cswarm file rm <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
36812
+ cswarm file restore <name|file-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--agent-token-stdin] [--json]
36463
36813
  cswarm listen start --agent-token-stdin [--url <url> --anon-key <key>] --workspace-id <uuid> --provider grok|opencode|claude|codex [--cwd <absolute-path>] [--model <model>] [--effort <level>] [--permissions deny|allow] [--grok-executable <path>] [--opencode-executable <path>] [--claude-executable <path>] [--codex-executable <path>] [--foreground] [--json]
36464
36814
  cswarm listen status [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
36465
36815
  cswarm listen stop [--url <url> --anon-key <key>] --workspace-id <uuid> --principal-id <uuid> [--json]
@@ -36493,6 +36843,8 @@ Credential selection for command/dogfood:
36493
36843
  One that persists or references the credential needs the complete
36494
36844
  JSON artifact, because it needs a field a bare secret does not carry:
36495
36845
  members reads only -- either form
36846
+ file put, file ls, file get, file rm, file restore
36847
+ read and command, nothing persisted -- either form
36496
36848
  listen start persists durable state, rotates -- needs expires_at
36497
36849
  token revoke names what it revokes -- needs token_id
36498
36850
 
@@ -36835,7 +37187,7 @@ async function runNew(args) {
36835
37187
  assertWorkspaceName(name);
36836
37188
  const cloud = await target(args);
36837
37189
  const human = await humanCredential(args, cloud);
36838
- const proposedId = (0, import_node_crypto18.randomUUID)();
37190
+ const proposedId = (0, import_node_crypto19.randomUUID)();
36839
37191
  let result;
36840
37192
  try {
36841
37193
  result = await new ThinCommandClient(cloud).sendConnect({
@@ -37442,7 +37794,7 @@ async function runToken(args) {
37442
37794
  ...ttl === void 0 ? {} : {
37443
37795
  ttl_ms: integer2(args, "ttl-ms", {
37444
37796
  minimum: 1,
37445
- maximum: 288e5
37797
+ maximum: 864e5
37446
37798
  })
37447
37799
  }
37448
37800
  }
@@ -38464,6 +38816,18 @@ function listenerStateDirectory(args) {
38464
38816
  }
38465
38817
  return value;
38466
38818
  }
38819
+ function listenerModelLabel(provider) {
38820
+ switch (provider) {
38821
+ case "claude":
38822
+ return "claude (claude-agent-acp 0.64.2)";
38823
+ case "codex":
38824
+ return "codex (codex-acp 1.1.9)";
38825
+ case "opencode":
38826
+ return "opencode";
38827
+ case "grok":
38828
+ return "grok";
38829
+ }
38830
+ }
38467
38831
  function listenerProvider(args) {
38468
38832
  const provider = args.optional("provider");
38469
38833
  const hints = "supported providers: grok \u2014 install Grok CLI 0.2.117 and run grok login; opencode \u2014 install OpenCode 1.18.10 and authenticate it; claude \u2014 npm install -g @agentclientprotocol/claude-agent-acp@0.64.2; codex \u2014 npm install -g @agentclientprotocol/codex-acp@1.1.9. You can use working-on, note, ask, and feed now; detached live receipt needs one of these adapters";
@@ -38867,6 +39231,7 @@ async function runConfiguredListener(options) {
38867
39231
  model: newModel(),
38868
39232
  signal,
38869
39233
  onEvent,
39234
+ declareModel: listenerModelLabel(options.provider),
38870
39235
  listenerInstanceId,
38871
39236
  deliveryJournal: selectedJournal,
38872
39237
  resolveSenderProvenance
@@ -39123,6 +39488,238 @@ async function runListen(args) {
39123
39488
  }
39124
39489
  throw new UsageError("listen requires start, status, or stop");
39125
39490
  }
39491
+ function formatFileSize(value) {
39492
+ const bytes = Number(value ?? 0);
39493
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
39494
+ if (bytes < 1024) return `${bytes} B`;
39495
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
39496
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
39497
+ }
39498
+ async function fileContext(args, extraFlags, positionalCount) {
39499
+ args.assertShape(
39500
+ [...TARGET_FLAGS, "workspace-id", ...CREDENTIAL_FLAGS, "json", ...extraFlags],
39501
+ positionalCount
39502
+ );
39503
+ const cloud = await target(args);
39504
+ const selected = await commandWorkspaceAndCredential(args, cloud, {
39505
+ validateHumanWorkspace: true
39506
+ });
39507
+ return { cloud, selected };
39508
+ }
39509
+ async function fileRows(context) {
39510
+ const { cloud, selected } = context;
39511
+ if (selected.kind === "agent") {
39512
+ return await listFilesAsAgent(
39513
+ cloud,
39514
+ selected.bearer,
39515
+ selected.selectedWorkspace
39516
+ );
39517
+ }
39518
+ return await listFilesAsHuman(
39519
+ cloud,
39520
+ selected.human.accessToken,
39521
+ selected.selectedWorkspace
39522
+ );
39523
+ }
39524
+ async function resolveFileSelector(context, selector) {
39525
+ if (UUID_RE15.test(selector)) return selector.toLowerCase();
39526
+ const rows3 = await fileRows(context);
39527
+ const match = rows3.find(
39528
+ (row) => row.name.toLowerCase() === selector.toLowerCase()
39529
+ );
39530
+ if (match === void 0) {
39531
+ throw new Error(
39532
+ `no file named "${sanitizeDisplayLabel(selector, "that name")}" exists in this workspace; run cswarm file ls to see what does, or pass a file id`
39533
+ );
39534
+ }
39535
+ return match.file_id;
39536
+ }
39537
+ async function runFilePut(args) {
39538
+ const localPath = args.positionals[2];
39539
+ if (!localPath) throw new UsageError("cswarm file put needs a local path");
39540
+ const context = await fileContext(args, ["name"], 3);
39541
+ let bytes;
39542
+ try {
39543
+ bytes = (0, import_node_fs7.readFileSync)(localPath);
39544
+ } catch {
39545
+ throw new Error(`could not read ${localPath}; check the path and permissions`);
39546
+ }
39547
+ const name = args.optional("name") ?? (0, import_node_path17.basename)(localPath);
39548
+ if (bytes.byteLength > FILE_MAX_VERSION_BYTES) {
39549
+ throw new Error(
39550
+ `this file is ${formatFileSize(bytes.byteLength)}; the per-file limit is ${formatFileSize(FILE_MAX_VERSION_BYTES)}, so the upload was not started`
39551
+ );
39552
+ }
39553
+ const contentType = contentTypeForName(name);
39554
+ if (contentType === null) {
39555
+ throw new Error(
39556
+ `"${sanitizeDisplayLabel(name, "that name")}" has no allowed file extension; the workspace accepts ${allowedExtensionList()}`
39557
+ );
39558
+ }
39559
+ const send = {
39560
+ target: context.cloud,
39561
+ workspaceId: context.selected.selectedWorkspace,
39562
+ credential: context.selected.bearer
39563
+ };
39564
+ const fileId = (0, import_node_crypto19.randomUUID)();
39565
+ const versionId = (0, import_node_crypto19.randomUUID)();
39566
+ const createCommandId = newCommandId();
39567
+ const commitCommandId = newCommandId();
39568
+ const created = await onceRetried(
39569
+ () => fileVersionCreate({ ...send, commandId: createCommandId }, {
39570
+ fileId,
39571
+ versionId,
39572
+ name,
39573
+ declaredSizeBytes: bytes.byteLength,
39574
+ contentType
39575
+ })
39576
+ );
39577
+ await onceRetried(
39578
+ () => putObject(context.cloud, created.upload_path, bytes, contentType)
39579
+ );
39580
+ const committed = await onceRetried(
39581
+ () => fileVersionCommit({ ...send, commandId: commitCommandId }, {
39582
+ fileId: created.file_id,
39583
+ versionId: created.version_id,
39584
+ sha256: sha256Hex(bytes)
39585
+ })
39586
+ );
39587
+ if (args.has("json")) {
39588
+ process.stdout.write(`${JSON.stringify(committed, null, 2)}
39589
+ `);
39590
+ return;
39591
+ }
39592
+ process.stdout.write(
39593
+ `Uploaded ${committed.name} \u2014 version ${committed.version_n}, ${formatFileSize(committed.size_bytes)}, visible to everyone in this workspace.
39594
+ Reference for signals, pinned to this version: --about ${committed.reference}
39595
+ The recorded sha256 is an unverified client attestation.
39596
+ `
39597
+ );
39598
+ }
39599
+ async function runFileLs(args) {
39600
+ const context = await fileContext(args, ["include-tombstoned"], 2);
39601
+ const rows3 = await fileRows(context);
39602
+ const visible = args.has("include-tombstoned") ? rows3 : rows3.filter((row) => row.tombstoned_at === null);
39603
+ if (args.has("json")) {
39604
+ process.stdout.write(
39605
+ `${JSON.stringify(
39606
+ {
39607
+ workspace_id: context.selected.selectedWorkspace,
39608
+ files: visible,
39609
+ sha256_note: "unverified client attestation",
39610
+ content_warning: FILE_CONTENT_WARNING
39611
+ },
39612
+ null,
39613
+ 2
39614
+ )}
39615
+ `
39616
+ );
39617
+ return;
39618
+ }
39619
+ if (visible.length === 0) {
39620
+ process.stdout.write(
39621
+ rows3.length === 0 ? "No files in this workspace yet. Upload one with cswarm file put <path>.\n" : "No live files; tombstoned ones exist. See them with cswarm file ls --include-tombstoned.\n"
39622
+ );
39623
+ return;
39624
+ }
39625
+ process.stdout.write(`Files in this workspace (${visible.length}):
39626
+ `);
39627
+ for (const row of visible) {
39628
+ const marker = row.tombstoned_at === null ? "" : " [tombstoned; restorable with cswarm file restore]";
39629
+ process.stdout.write(
39630
+ `- ${sanitizeDisplayLabel(row.name, "unnamed file")} v${row.current_version} \xB7 ${formatFileSize(row.size_bytes)} \xB7 ${sanitizeDisplayLabel(row.content_type ?? "unknown type", "unknown type")} \xB7 by ${row.uploaded_by_kind ?? row.created_by_kind}${marker}
39631
+ `
39632
+ );
39633
+ }
39634
+ process.stdout.write(`${FILE_CONTENT_WARNING}
39635
+ `);
39636
+ }
39637
+ async function runFileGet(args) {
39638
+ const selector = args.positionals[2];
39639
+ if (!selector) throw new UsageError("cswarm file get needs a file name or id");
39640
+ const context = await fileContext(args, ["version", "out", "force"], 3);
39641
+ const versionN = args.has("version") ? integer2(args, "version", { minimum: 1 }) : null;
39642
+ const fileId = await resolveFileSelector(context, selector);
39643
+ const send = {
39644
+ target: context.cloud,
39645
+ workspaceId: context.selected.selectedWorkspace,
39646
+ credential: context.selected.bearer
39647
+ };
39648
+ const grant = await fileDownloadUrl(send, { fileId, versionN });
39649
+ const destination = args.optional("out") ?? (0, import_node_path17.basename)(grant.name);
39650
+ const bytes = await getObject(context.cloud, grant.download_path);
39651
+ writeDestination(destination, bytes, args.has("force"), import_node_fs7.writeFileSync);
39652
+ if (args.has("json")) {
39653
+ process.stdout.write(
39654
+ `${JSON.stringify(
39655
+ { ...grant, written_to: destination, written_bytes: bytes.byteLength },
39656
+ null,
39657
+ 2
39658
+ )}
39659
+ `
39660
+ );
39661
+ return;
39662
+ }
39663
+ process.stdout.write(
39664
+ `Downloaded ${grant.name} version ${grant.version_n} (${formatFileSize(bytes.byteLength)}, ${grant.content_type}) to ${destination}.
39665
+ ${grant.content_warning}
39666
+ `
39667
+ );
39668
+ }
39669
+ async function runFileRm(args) {
39670
+ const selector = args.positionals[2];
39671
+ if (!selector) throw new UsageError("cswarm file rm needs a file name or id");
39672
+ const context = await fileContext(args, [], 3);
39673
+ const fileId = await resolveFileSelector(context, selector);
39674
+ const result = await fileTombstone({
39675
+ target: context.cloud,
39676
+ workspaceId: context.selected.selectedWorkspace,
39677
+ credential: context.selected.bearer
39678
+ }, { fileId });
39679
+ if (args.has("json")) {
39680
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
39681
+ `);
39682
+ return;
39683
+ }
39684
+ process.stdout.write(
39685
+ `Tombstoned ${result.name}. It is hidden from listings and downloads now, and stays restorable with cswarm file restore until ${result.restorable_until ?? "the 30-day window ends"}.
39686
+ After that the purge permanently deletes the bytes; existing download URLs expire on their own 5-minute clock.
39687
+ `
39688
+ );
39689
+ }
39690
+ async function runFileRestore(args) {
39691
+ const selector = args.positionals[2];
39692
+ if (!selector) {
39693
+ throw new UsageError("cswarm file restore needs a file name or id");
39694
+ }
39695
+ const context = await fileContext(args, [], 3);
39696
+ const fileId = await resolveFileSelector(context, selector);
39697
+ const result = await fileRestore({
39698
+ target: context.cloud,
39699
+ workspaceId: context.selected.selectedWorkspace,
39700
+ credential: context.selected.bearer
39701
+ }, { fileId });
39702
+ if (args.has("json")) {
39703
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
39704
+ `);
39705
+ return;
39706
+ }
39707
+ process.stdout.write(
39708
+ `Restored ${result.name}. It is listed and downloadable again; nothing else changed.
39709
+ `
39710
+ );
39711
+ }
39712
+ async function runFile(args) {
39713
+ const action = args.positionals[1];
39714
+ if (action === "put") return await runFilePut(args);
39715
+ if (action === "ls") return await runFileLs(args);
39716
+ if (action === "get") return await runFileGet(args);
39717
+ if (action === "rm") return await runFileRm(args);
39718
+ if (action === "restore") return await runFileRestore(args);
39719
+ throw new UsageError(
39720
+ "cswarm file takes put, ls, get, rm, or restore"
39721
+ );
39722
+ }
39126
39723
  async function runTaskCommand(args) {
39127
39724
  args.assertShape(
39128
39725
  [...TARGET_FLAGS, ...ROUTE_FLAGS, ...CREDENTIAL_FLAGS, ...TASK_FLAGS],
@@ -39160,7 +39757,7 @@ async function runDogfood(args) {
39160
39757
  const { selectedWorkspace, bearer } = await commandWorkspaceAndCredential(args, cloud);
39161
39758
  const client = new ThinCommandClient(cloud);
39162
39759
  const route = stream(args);
39163
- const taskId = args.optional("task-id") ?? (0, import_node_crypto18.randomUUID)();
39760
+ const taskId = args.optional("task-id") ?? (0, import_node_crypto19.randomUUID)();
39164
39761
  const ttl = Number(args.optional("ttl-ms") ?? "3600000");
39165
39762
  if (!Number.isSafeInteger(ttl) || ttl <= 0 || ttl > 144e5) {
39166
39763
  throw new Error("--ttl-ms must be an integer in 1..14400000");
@@ -39367,6 +39964,10 @@ async function main() {
39367
39964
  await runStatus(args);
39368
39965
  return;
39369
39966
  }
39967
+ if (verb === "file") {
39968
+ await runFile(args);
39969
+ return;
39970
+ }
39370
39971
  if (verb === "members") {
39371
39972
  await runMembers(args);
39372
39973
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "CommonSwarm CLI — coordination for teams where people and AI agents work side by side.",
5
5
  "bin": {
6
6
  "cswarm": "cswarm.cjs"