commonswarm 0.1.54 → 0.1.55

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 +630 -28
  2. package/package.json +1 -1
package/cswarm.cjs CHANGED
@@ -13502,6 +13502,7 @@ var require_main3 = __commonJS({
13502
13502
  // src/cli.ts
13503
13503
  var cli_exports = {};
13504
13504
  __export(cli_exports, {
13505
+ CHANNEL_SUBCOMMAND_NAMES: () => CHANNEL_SUBCOMMAND_NAMES,
13505
13506
  EXIT_RESTARTABLE: () => EXIT_RESTARTABLE,
13506
13507
  ListenerUnattendedRefusedError: () => ListenerUnattendedRefusedError,
13507
13508
  TURN_BUDGET_CREDENTIAL_MARGIN_MS: () => TURN_BUDGET_CREDENTIAL_MARGIN_MS,
@@ -13520,6 +13521,7 @@ __export(cli_exports, {
13520
13521
  resolveDetachedClaudeExecutable: () => resolveDetachedClaudeExecutable,
13521
13522
  resolveDetachedCodexExecutable: () => resolveDetachedCodexExecutable,
13522
13523
  resolveTurnBudgetOrDefer: () => resolveTurnBudgetOrDefer,
13524
+ threadReplyMessage: () => threadReplyMessage,
13523
13525
  usage: () => usage
13524
13526
  });
13525
13527
  module.exports = __toCommonJS(cli_exports);
@@ -22142,11 +22144,217 @@ async function logout(target2, store2, scope = "local", options = {}) {
22142
22144
 
22143
22145
  // src/cloud/command-client.ts
22144
22146
  var import_node_crypto3 = require("node:crypto");
22147
+
22148
+ // src/cloud/channels.ts
22149
+ var CHANNEL_SLUG_MAX = 32;
22150
+ var CHANNEL_PURPOSE_MAX = 500;
22151
+ var CHANNEL_SLUG_CLASSES = {
22152
+ edge: [
22153
+ { fragment: "a-z", words: "lowercase letters", one: "a letter" },
22154
+ { fragment: "0-9", words: "digits", one: "a digit" }
22155
+ ],
22156
+ inner: [{ fragment: "-", words: "hyphens" }]
22157
+ };
22158
+ function classList(entries, conjunction = "and", singular = false) {
22159
+ const words = entries.map(
22160
+ (entry) => singular ? entry.one ?? entry.words : entry.words
22161
+ );
22162
+ return words.length === 1 ? words[0] : `${words.slice(0, -1).join(", ")} ${conjunction} ${words[words.length - 1]}`;
22163
+ }
22164
+ var SLUG_EDGE = CHANNEL_SLUG_CLASSES.edge.map((c) => c.fragment).join("");
22165
+ var SLUG_INNER = SLUG_EDGE + CHANNEL_SLUG_CLASSES.inner.map((c) => c.fragment).join("");
22166
+ var CHANNEL_SLUG_RE = new RegExp(
22167
+ `^[${SLUG_EDGE}]([${SLUG_INNER}]*[${SLUG_EDGE}])?$`
22168
+ );
22169
+ var RESERVED_CHANNEL_SLUGS = ["all-signals"];
22170
+ var CHANNEL_SLUG_RULE_TEXT = `A channel name uses ${classList([...CHANNEL_SLUG_CLASSES.edge, ...CHANNEL_SLUG_CLASSES.inner])}, starts and ends with ${classList(CHANNEL_SLUG_CLASSES.edge, "or", true)}, and is 1 to ${CHANNEL_SLUG_MAX} characters.`;
22171
+ var CHANNEL_ID_RULE_TEXT = "channel_id must be a UUID.";
22172
+ var RESERVED_CHANNEL_SLUG_TEXT = `Reserved names: ${RESERVED_CHANNEL_SLUGS.join(", ")}.`;
22173
+ function normalizeChannelSlug(value) {
22174
+ return value.trim().toLowerCase();
22175
+ }
22176
+ function isReservedChannelSlug(value) {
22177
+ return RESERVED_CHANNEL_SLUGS.includes(normalizeChannelSlug(value));
22178
+ }
22179
+ function channelNameProblem(value) {
22180
+ if (typeof value !== "string") return "not-text";
22181
+ const slug = normalizeChannelSlug(value);
22182
+ if (slug.length < 1 || slug.length > CHANNEL_SLUG_MAX) return "shape";
22183
+ if (!CHANNEL_SLUG_RE.test(slug)) return "shape";
22184
+ if (isReservedChannelSlug(slug)) return "reserved";
22185
+ return "ok";
22186
+ }
22187
+ function channelSlugProblem(value) {
22188
+ switch (channelNameProblem(value)) {
22189
+ case "ok":
22190
+ return null;
22191
+ case "not-text":
22192
+ return `A channel name must be text. ${CHANNEL_SLUG_RULE_TEXT}`;
22193
+ case "shape":
22194
+ return CHANNEL_SLUG_RULE_TEXT;
22195
+ case "reserved":
22196
+ return `${normalizeChannelSlug(value)} is reserved. ${RESERVED_CHANNEL_SLUG_TEXT}`;
22197
+ }
22198
+ }
22199
+ var CHANNEL_COLUMNS = [
22200
+ "channel_id",
22201
+ "workspace_id",
22202
+ "slug",
22203
+ "purpose",
22204
+ "created_by_principal",
22205
+ "created_by_kind",
22206
+ "created_at",
22207
+ "archived_at"
22208
+ ];
22209
+ var CHANNEL_LIST_NEEDS_HUMAN_MESSAGE = "Listing channels needs a signed-in person: this deployment's read service has no channel list for an agent credential. Run cswarm channel ls from a session signed in with cswarm login, or name the channel you want by name wherever a command takes one.";
22210
+ var CHANNEL_SELECTOR_NEEDS_ID_MESSAGE = "Turning a channel name into a channel id needs a signed-in person, because this deployment's read service does not list channels for an agent credential. Pass the channel id instead. cswarm channel create prints it, and cswarm channel ls shows it from a session signed in with cswarm login.";
22211
+ function channelSelectorProblem(selector) {
22212
+ const problem = channelNameProblem(selector);
22213
+ if (problem === "ok") return null;
22214
+ if (problem === "reserved") return channelSlugProblem(selector);
22215
+ return `That is neither a channel name nor a channel id. ${CHANNEL_SLUG_RULE_TEXT} ${CHANNEL_ID_RULE_TEXT}`;
22216
+ }
22217
+ var ChannelListError = class extends Error {
22218
+ constructor(status, message, noResponse = false) {
22219
+ super(message);
22220
+ this.status = status;
22221
+ this.noResponse = noResponse;
22222
+ this.name = "ChannelListError";
22223
+ }
22224
+ status;
22225
+ noResponse;
22226
+ };
22227
+ async function listChannelsAsHuman(target2, accessToken, workspaceId2, fetcher = fetch, timeoutMs = 3e4) {
22228
+ const url = new URL("/rest/v1/channels", target2.url);
22229
+ url.searchParams.set("workspace_id", `eq.${workspaceId2}`);
22230
+ url.searchParams.set("select", CHANNEL_COLUMNS.join(","));
22231
+ url.searchParams.set("order", "slug.asc");
22232
+ const controller = new AbortController();
22233
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
22234
+ let response;
22235
+ try {
22236
+ response = await fetcher(url.toString(), {
22237
+ headers: {
22238
+ authorization: `Bearer ${accessToken}`,
22239
+ apikey: target2.anonKey,
22240
+ "accept-profile": "swarm_read"
22241
+ },
22242
+ signal: controller.signal
22243
+ });
22244
+ } catch {
22245
+ throw new ChannelListError(
22246
+ 0,
22247
+ "The channel list did not complete. Nothing changed. Run the same command again.",
22248
+ true
22249
+ );
22250
+ } finally {
22251
+ clearTimeout(timer2);
22252
+ }
22253
+ if (!response.ok) {
22254
+ throw new ChannelListError(
22255
+ response.status,
22256
+ `The channel list was refused (HTTP ${response.status}). Nothing changed.`
22257
+ );
22258
+ }
22259
+ let body = null;
22260
+ try {
22261
+ body = await response.json();
22262
+ } catch {
22263
+ body = null;
22264
+ }
22265
+ if (!Array.isArray(body)) {
22266
+ throw new ChannelListError(
22267
+ response.status,
22268
+ "The channel list came back in a shape this version does not understand."
22269
+ );
22270
+ }
22271
+ return body;
22272
+ }
22273
+ function findChannelBySlug(rows3, slug) {
22274
+ const wanted = normalizeChannelSlug(slug);
22275
+ return rows3.find((row) => normalizeChannelSlug(row.slug) === wanted) ?? null;
22276
+ }
22277
+ function unknownChannelMessage(slug, rows3) {
22278
+ const live = rows3.filter((row) => row.archived_at === null).map((row) => row.slug).sort();
22279
+ return live.length === 0 ? `There is no channel named ${normalizeChannelSlug(slug)} in this workspace, and no channel has been created yet. Create it with cswarm channel create ${normalizeChannelSlug(slug)}.` : `There is no channel named ${normalizeChannelSlug(slug)} in this workspace. Channels here: ${live.join(", ")}.`;
22280
+ }
22281
+ function renderChannelList(rows3, options) {
22282
+ const live = rows3.filter((row) => row.archived_at === null);
22283
+ const archived = rows3.filter((row) => row.archived_at !== null);
22284
+ const shown = options.includeArchived ? [...live, ...archived] : live;
22285
+ if (shown.length === 0) {
22286
+ return live.length === 0 && archived.length > 0 ? "Every channel in this workspace is archived. See them with cswarm channel ls --include-archived.\n" : "No channels in this workspace yet. Create one with cswarm channel create <name>.\n";
22287
+ }
22288
+ const lines = shown.map((row) => {
22289
+ const marker = row.archived_at === null ? "" : " [archived; it keeps its history and takes no new messages]";
22290
+ const purpose = row.purpose === null ? "" : `: ${row.purpose}`;
22291
+ return `- ${row.slug}${purpose}${marker}`;
22292
+ });
22293
+ const head2 = `Channels in this workspace (${shown.length}):`;
22294
+ const tail = options.includeArchived || archived.length === 0 ? "" : `
22295
+ ${archived.length} archived channel${archived.length === 1 ? "" : "s"} not shown. See them with cswarm channel ls --include-archived.`;
22296
+ return `${head2}
22297
+ ${lines.join("\n")}${tail}
22298
+ `;
22299
+ }
22300
+ var CHANNEL_UNSUPPORTED_MESSAGE = `This deployment refused the request and did not say why. Channels need a deployment whose command service knows them; this cswarm speaks protocol ${CLIENT_PROTOCOL_VERSION}. Nothing was created or changed. Ask whoever runs this deployment to update it, or drop the channel options and post as before.`;
22301
+
22302
+ // src/cloud/command-client.ts
22145
22303
  var AGENT_TOKEN_RE = /^swm_agt_[A-Za-z0-9_-]{43}$/;
22146
22304
  var INVITATION_TOKEN_RE = /^swm_inv_[A-Za-z0-9_-]{43}$/;
22147
22305
  var CAPABILITY_TOKEN_RE = /^swm_cap_[A-Za-z0-9_-]{43}$/;
22148
22306
  var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/;
22149
22307
  var WORKSPACE_NAME_MAX_LENGTH = 80;
22308
+ var ChannelCommandError = class extends Error {
22309
+ constructor(status, code, message) {
22310
+ super(message);
22311
+ this.status = status;
22312
+ this.code = code;
22313
+ this.name = "ChannelCommandError";
22314
+ }
22315
+ status;
22316
+ code;
22317
+ };
22318
+ function channelCommandError(status, body) {
22319
+ const record = body && typeof body === "object" && !Array.isArray(body) ? body : {};
22320
+ const code = typeof record.error === "string" ? record.error : "unknown";
22321
+ const served = typeof record.message === "string" && record.message.length > 0 ? record.message.slice(0, 600) : null;
22322
+ if (served !== null) return new ChannelCommandError(status, code, served);
22323
+ if (status === 426) {
22324
+ const minimum = typeof record.min_client_version === "string" ? record.min_client_version : null;
22325
+ return new ChannelCommandError(
22326
+ status,
22327
+ "upgrade_required",
22328
+ `This copy of cswarm is older than the deployment accepts${minimum === null ? "" : ` (minimum ${minimum})`}. Update cswarm, then run the same command again. Nothing changed.`
22329
+ );
22330
+ }
22331
+ if (status === 403) {
22332
+ return new ChannelCommandError(
22333
+ status,
22334
+ code === "unknown" ? "forbidden" : code,
22335
+ "This credential may not do that in this workspace. Nothing changed."
22336
+ );
22337
+ }
22338
+ if (status === 401) {
22339
+ return new ChannelCommandError(
22340
+ status,
22341
+ code === "unknown" ? "unauthenticated" : code,
22342
+ "Your sign-in is no longer valid for this deployment. Run cswarm login, then run the same command again. Nothing changed."
22343
+ );
22344
+ }
22345
+ if (status === 400) {
22346
+ return new ChannelCommandError(
22347
+ status,
22348
+ code === "unknown" ? "invalid_request" : code,
22349
+ CHANNEL_UNSUPPORTED_MESSAGE
22350
+ );
22351
+ }
22352
+ return new ChannelCommandError(
22353
+ status,
22354
+ code,
22355
+ `CommonSwarm could not tell whether the change was made (HTTP ${status}). Run cswarm channel ls to see the current channels before trying again.`
22356
+ );
22357
+ }
22150
22358
  var CAPABILITY_MIN_TTL_MS = 6e4;
22151
22359
  var CAPABILITY_MAX_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
22152
22360
  var SIGNAL_REQUEST_TIMEOUT_MS = 3e4;
@@ -22706,6 +22914,80 @@ var ThinCommandClient = class {
22706
22914
  }
22707
22915
  return { httpStatus: response.status, response: body };
22708
22916
  }
22917
+ /**
22918
+ * Create, rename, or archive a channel.
22919
+ *
22920
+ * Deliberately not folded into `sendConnect`. That path throws a bare
22921
+ * `CommandHttpError(403)` and, on any other refusal, reads only the `error`
22922
+ * code — so every generated sentence the chat validator returns would be
22923
+ * thrown away at the one moment the caller needs it. This method reads the
22924
+ * body once and hands it to `channelCommandError`.
22925
+ */
22926
+ async sendChannel(request) {
22927
+ if (!request.workspaceId) {
22928
+ throw new Error("workspaceId is required for a channel command");
22929
+ }
22930
+ const commandId = request.commandId ?? newCommandId();
22931
+ const controller = new AbortController();
22932
+ const timer2 = setTimeout(() => controller.abort(), 3e4);
22933
+ let response;
22934
+ try {
22935
+ response = await this.fetcher(commandEndpoint(this.target), {
22936
+ method: "POST",
22937
+ headers: {
22938
+ authorization: `Bearer ${request.credential}`,
22939
+ apikey: this.target.anonKey,
22940
+ "content-type": "application/json"
22941
+ },
22942
+ body: JSON.stringify({
22943
+ command_id: commandId,
22944
+ client_version: CLIENT_PROTOCOL_VERSION,
22945
+ workspace_id: request.workspaceId,
22946
+ stream: { kind: "workspace" },
22947
+ command: request.command
22948
+ }),
22949
+ signal: controller.signal
22950
+ });
22951
+ } catch (error) {
22952
+ if (error.name === "AbortError") {
22953
+ throw new CommandTransportError("channel request timed out");
22954
+ }
22955
+ throw new CommandTransportError(
22956
+ "channel request failed before a response"
22957
+ );
22958
+ } finally {
22959
+ clearTimeout(timer2);
22960
+ }
22961
+ let raw = null;
22962
+ try {
22963
+ raw = await parsedJson(response);
22964
+ } catch (error) {
22965
+ if (response.ok || error instanceof CommandTransportError) throw error;
22966
+ }
22967
+ if (!response.ok) throw channelCommandError(response.status, raw);
22968
+ const body = responseBody(raw);
22969
+ if (body.min_client_version !== void 0) {
22970
+ const order = compareVersion(
22971
+ CLIENT_PROTOCOL_VERSION,
22972
+ body.min_client_version
22973
+ );
22974
+ if (order === null) {
22975
+ throw new Error("server returned a malformed min_client_version");
22976
+ }
22977
+ if (order < 0) {
22978
+ throw new Error(
22979
+ `client upgrade required (minimum ${body.min_client_version})`
22980
+ );
22981
+ }
22982
+ }
22983
+ const channel = raw && typeof raw === "object" && !Array.isArray(raw) ? raw.channel : null;
22984
+ if (channel === null || channel === void 0 || typeof channel.channel_id !== "string" || typeof channel.slug !== "string") {
22985
+ throw new Error(
22986
+ "the deployment accepted the change without saying which channel it applies to"
22987
+ );
22988
+ }
22989
+ return { httpStatus: response.status, response: body, channel };
22990
+ }
22709
22991
  async sendSignal(request) {
22710
22992
  const commandId = request.commandId ?? newCommandId();
22711
22993
  const command2 = {
@@ -22717,7 +22999,17 @@ var ThinCommandClient = class {
22717
22999
  in_reply_to: request.command.in_reply_to,
22718
23000
  about: request.command.about,
22719
23001
  ...request.command.attachments === void 0 ? {} : { attachments: request.command.attachments },
22720
- ...request.command.until_ms === void 0 ? {} : { until_ms: request.command.until_ms }
23002
+ ...request.command.until_ms === void 0 ? {} : { until_ms: request.command.until_ms },
23003
+ /* One spread per chat key, never a shared group. The edge reads each with
23004
+ * its own Object.hasOwn and refuses any key it did not expect, so sending
23005
+ * `channel: undefined` here would still put the key on the wire through
23006
+ * JSON.stringify's own omission rules only by accident — and sending a
23007
+ * null placeholder, the way to_user_id is sent, would make every post
23008
+ * demand a channel. This rebuild is also the reason the fields have to be
23009
+ * listed here at all: it drops anything it does not name. */
23010
+ ...request.command.channel === void 0 ? {} : { channel: request.command.channel },
23011
+ ...request.command.thread_root_id === void 0 ? {} : { thread_root_id: request.command.thread_root_id },
23012
+ ...request.command.broadcast_to_channel === void 0 ? {} : { broadcast_to_channel: request.command.broadcast_to_channel }
22721
23013
  };
22722
23014
  const callerSignal = request.signal;
22723
23015
  if (callerSignal?.aborted) {
@@ -29041,6 +29333,12 @@ function checkedUuid2(value, field) {
29041
29333
  function checkedNullableUuid(value, field) {
29042
29334
  return value === null ? null : checkedUuid2(value, field);
29043
29335
  }
29336
+ function checkedBoolean(value, field) {
29337
+ if (typeof value !== "boolean") {
29338
+ throw new Error(`signal read returned a malformed ${field}`);
29339
+ }
29340
+ return value;
29341
+ }
29044
29342
  function checkedTimestamp(value, field) {
29045
29343
  if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
29046
29344
  throw new Error(`signal read returned a malformed ${field}`);
@@ -29120,7 +29418,29 @@ function parseSignalRecord(value, options = {}) {
29120
29418
  }),
29121
29419
  until: checkedTimestamp(row.until, "until"),
29122
29420
  created_at: checkedTimestamp(row.created_at, "created_at"),
29123
- sender_owner_relation: senderOwnerRelation
29421
+ sender_owner_relation: senderOwnerRelation,
29422
+ /* ABSENT AND NULL ARE DIFFERENT HERE, and the difference is a claim.
29423
+ *
29424
+ * `null` means the server said this signal is in no channel. Absent means
29425
+ * this reader never asked: the human REST path names the chat columns only
29426
+ * when a channel filter is set, and an edge that predates channels never
29427
+ * returns them. Normalizing absence to null, the way `to_agent` above does,
29428
+ * would put `"channel_id": null` in `cswarm feed --json` for a signal that
29429
+ * IS filed in a channel — a false statement, not a missing one. So an
29430
+ * absent key stays absent, and a present one is checked. */
29431
+ ...row.channel_id === void 0 ? {} : { channel_id: checkedNullableUuid(row.channel_id, "channel_id") },
29432
+ ...row.thread_root_id === void 0 ? {} : {
29433
+ thread_root_id: checkedNullableUuid(
29434
+ row.thread_root_id,
29435
+ "thread_root_id"
29436
+ )
29437
+ },
29438
+ ...row.broadcast_to_channel === void 0 ? {} : {
29439
+ broadcast_to_channel: checkedBoolean(
29440
+ row.broadcast_to_channel,
29441
+ "broadcast_to_channel"
29442
+ )
29443
+ }
29124
29444
  };
29125
29445
  }
29126
29446
  function cursorFromUnknown(value) {
@@ -29432,9 +29752,15 @@ async function humanSignals(target2, credential, query, options) {
29432
29752
  const url = new URL("/rest/v1/signals", target2.url);
29433
29753
  url.searchParams.set(
29434
29754
  "select",
29435
- "id,workspace_id,from,from_kind,to,to_agent,in_reply_to,about,kind,body,attachments,until,created_at"
29755
+ [
29756
+ "id,workspace_id,from,from_kind,to,to_agent,in_reply_to,about,kind,body,attachments,until,created_at",
29757
+ ...query.channelId === void 0 ? [] : ["channel_id", "thread_root_id", "broadcast_to_channel"]
29758
+ ].join(",")
29436
29759
  );
29437
29760
  url.searchParams.set("workspace_id", `eq.${query.workspaceId}`);
29761
+ if (query.channelId !== void 0) {
29762
+ url.searchParams.set("channel_id", `eq.${query.channelId}`);
29763
+ }
29438
29764
  if (query.inbox) url.searchParams.set("to", `eq.${credential.userId}`);
29439
29765
  if (!query.includeStale) {
29440
29766
  url.searchParams.set("until", "gt.now");
@@ -29507,6 +29833,11 @@ async function agentSignalPage(target2, credential, query, options, allowLegacyC
29507
29833
  about: query.about ?? null,
29508
29834
  kind: query.kind ?? null,
29509
29835
  in_reply_to: query.in_reply_to ?? null,
29836
+ /* Its OWN key, present only when asked for. The read edge groups it
29837
+ * with `chatReadKeys` and refuses a key it did not expect, and every
29838
+ * agent body already carries `in_reply_to`, so folding `channel` in
29839
+ * beside it would 400 every agent read that omits a channel. */
29840
+ ...query.channel === void 0 ? {} : { channel: query.channel },
29510
29841
  since: query.since ?? null,
29511
29842
  ...includeCursor ? {
29512
29843
  after_created_at: query.after?.created_at ?? null,
@@ -42206,8 +42537,11 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
42206
42537
  "foreground",
42207
42538
  "grok-executable",
42208
42539
  "head-sha",
42540
+ "broadcast-to-channel",
42541
+ "channel",
42209
42542
  "help",
42210
42543
  "if-version",
42544
+ "include-archived",
42211
42545
  "include-stale",
42212
42546
  "include-tombstoned",
42213
42547
  "invitation-id",
@@ -42227,6 +42561,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
42227
42561
  "permissions",
42228
42562
  "principal-id",
42229
42563
  "provider",
42564
+ "purpose",
42230
42565
  "renewal-grant-id",
42231
42566
  "repo",
42232
42567
  "reveal-anon-key",
@@ -42236,6 +42571,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
42236
42571
  "site",
42237
42572
  "slug",
42238
42573
  "state-dir",
42574
+ "thread",
42239
42575
  "renewal-horizon-days",
42240
42576
  "standing",
42241
42577
  "task-id",
@@ -42256,12 +42592,14 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
42256
42592
  "agent-token-stdin",
42257
42593
  "all-devices",
42258
42594
  "allow-unattended",
42595
+ "broadcast-to-channel",
42259
42596
  "confirm-standing",
42260
42597
  "force-file-store",
42261
42598
  "follow",
42262
42599
  "force",
42263
42600
  "foreground",
42264
42601
  "help",
42602
+ "include-archived",
42265
42603
  "include-stale",
42266
42604
  "include-tombstoned",
42267
42605
  "invitation-token-stdin",
@@ -42274,13 +42612,14 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
42274
42612
  "reveal-anon-key",
42275
42613
  "repo",
42276
42614
  "standing",
42615
+ "thread",
42277
42616
  "user",
42278
42617
  "write"
42279
42618
  ]);
42280
42619
  var UUID_RE23 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
42281
42620
  function packageVersion() {
42282
- if ("0.1.54".length > 0) {
42283
- return "0.1.54";
42621
+ if ("0.1.55".length > 0) {
42622
+ return "0.1.55";
42284
42623
  }
42285
42624
  try {
42286
42625
  const value = JSON.parse(
@@ -42399,15 +42738,19 @@ Usage:
42399
42738
  cswarm whoami ${requiredAgentCredential} [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
42400
42739
  cswarm resume --agent-token-file <path> [--url <url> --anon-key <key>] --workspace-id <uuid> [--json]
42401
42740
  cswarm members [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42402
- cswarm working-on "<what>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--until <dur>] [--json]
42403
- cswarm note "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--attach <path> ...] [--until <dur>] [--json] # text: 1..8000 characters
42404
- cswarm ask "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--attach <path> ...] [--until <dur>] [--wait <seconds>] [--json] # text: 1..8000 characters
42405
- cswarm reply <signal-id> "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--attach <path> ...] [--until <dur>] [--json]
42741
+ cswarm working-on "<what>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--channel <name>] [--until <dur>] [--json]
42742
+ cswarm note "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--channel <name>] [--attach <path> ...] [--until <dur>] [--json] # text: 1..8000 characters
42743
+ cswarm ask "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--channel <name>] [--attach <path> ...] [--until <dur>] [--wait <seconds>] [--json] # text: 1..8000 characters
42744
+ cswarm reply <signal-id> "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--thread [--broadcast-to-channel]] [--attach <path> ...] [--until <dur>] [--json]
42406
42745
  cswarm receipt <signal-id> ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--json]
42407
- cswarm feed [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--kind <kind>] [--since <timestamp>] [--limit <n>] [--include-stale] [--json]
42408
- cswarm inbox [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--kind <kind>] [--about <ref>] [--since <timestamp>] [--limit <n>] [--include-stale] [--wait <seconds>] [--json]
42746
+ cswarm feed [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--kind <kind>] [--channel <name>] [--since <timestamp>] [--limit <n>] [--include-stale] [--json]
42747
+ cswarm inbox [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--kind <kind>] [--about <ref>] [--channel <name>] [--since <timestamp>] [--limit <n>] [--include-stale] [--wait <seconds>] [--json]
42409
42748
  cswarm inbox --notify ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--json]
42410
42749
  cswarm inbox --follow --ndjson [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--kind <kind>] [--about <ref>] [--since <timestamp>] [--limit <n>] [--include-stale]
42750
+ cswarm channel create <name> [--purpose <text>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json] # purpose: at most ${CHANNEL_PURPOSE_MAX} characters
42751
+ cswarm channel ls [--include-archived] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
42752
+ cswarm channel rename <name|channel-id> <new-name> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42753
+ cswarm channel archive <name|channel-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42411
42754
  cswarm file put <local-path> [--name <name>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42412
42755
  cswarm file ls [--include-tombstoned] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
42413
42756
  cswarm file get <name|file-id> [--version <n>] [--out <local-path>] [--force] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
@@ -42464,6 +42807,11 @@ Credential selection for command/dogfood:
42464
42807
  signal command/read only -- either form
42465
42808
  receipt reads only -- either form
42466
42809
  inbox --notify persists a per-agent cursor -- needs principal_id
42810
+ channel create, channel rename, channel archive
42811
+ command only, nothing persisted -- either form
42812
+ channel ls reads swarm_read.channels over REST -- signed-in
42813
+ person only; the read service has no channels
42814
+ resource for an agent credential
42467
42815
  file put, file ls, file get, file rm, file restore,
42468
42816
  brain ls, brain get, brain put
42469
42817
  read and command, nothing persisted -- either form
@@ -42489,6 +42837,14 @@ Credential selection for command/dogfood:
42489
42837
  Found a bug or missing feature in cswarm itself? cswarm feedback sends it to the
42490
42838
  deployment's operators \u2014 agents are encouraged to report friction they hit.
42491
42839
 
42840
+ A channel is where a message is FILED, not who may read it. Everyone in the
42841
+ workspace reads every channel, and --channel changes nothing about who sees a
42842
+ signal. Archiving a channel keeps its history and its permalinks and refuses new
42843
+ messages. cswarm reply --thread answers in the open, in the thread of the signal
42844
+ you name, so it takes no recipient; add --broadcast-to-channel to send that reply
42845
+ to the thread's channel as well. Plain cswarm reply is unchanged and still
42846
+ answers the original author privately.
42847
+
42492
42848
  Signals (intention sharing) accept the same credential selection. Agent mode
42493
42849
  never opens a browser or infers a human's saved workspace. Durations use a whole
42494
42850
  number plus m, h, or d (for example 90m, 24h, or 7d) and are capped at 30d.
@@ -43999,6 +44355,19 @@ function signalKind(value) {
43999
44355
  }
44000
44356
  return value;
44001
44357
  }
44358
+ function channelOption(args) {
44359
+ const value = args.optional("channel");
44360
+ if (value === void 0) return void 0;
44361
+ const problem = channelSlugProblem(value);
44362
+ if (problem !== null) throw new Error(problem);
44363
+ return normalizeChannelSlug(value);
44364
+ }
44365
+ function unknownChannelReadMessage(error, slug) {
44366
+ const details = followHttpDetails(error);
44367
+ if (details === null || details.status !== 404) return null;
44368
+ if (followErrorEnvelope(error).error !== "channel_not_found") return null;
44369
+ return `There is no channel named ${slug} in this workspace. Nothing was read. Create it with cswarm channel create ${slug}, or drop --channel to read everything.`;
44370
+ }
44002
44371
  function signalDuration(value) {
44003
44372
  if (value === void 0) return void 0;
44004
44373
  const match = /^([1-9]\d*)(m|h|d)$/.exec(value);
@@ -44300,11 +44669,13 @@ async function runPostSignal(args, kind) {
44300
44669
  ...CREDENTIAL_FLAGS,
44301
44670
  ...allowTo ? ["to"] : [],
44302
44671
  "about",
44672
+ "channel",
44303
44673
  "until",
44304
44674
  ...allowWait ? ["wait"] : [],
44305
44675
  ...allowTo ? ["attach"] : [],
44306
44676
  "json"
44307
44677
  ], 2);
44678
+ const channel = channelOption(args);
44308
44679
  const preparedAttachments = allowTo ? prepareSignalAttachments(args.all("attach")) : [];
44309
44680
  const waitSeconds = allowWait && args.optional("wait") !== void 0 ? parseWaitSeconds(args.required("wait")) : void 0;
44310
44681
  const cloud = await target(args);
@@ -44349,7 +44720,8 @@ async function runPostSignal(args, kind) {
44349
44720
  ...postSignalTargets(recipient),
44350
44721
  about: args.optional("about") === void 0 ? null : signalText(args.required("about"), "about"),
44351
44722
  ...attachments.length === 0 ? {} : { attachments },
44352
- ...untilMs2 === void 0 ? {} : { until_ms: untilMs2 }
44723
+ ...untilMs2 === void 0 ? {} : { until_ms: untilMs2 },
44724
+ ...channel === void 0 ? {} : { channel }
44353
44725
  };
44354
44726
  let result;
44355
44727
  try {
@@ -44474,15 +44846,35 @@ function replyRefusalHint(error) {
44474
44846
  if (!(error instanceof CommandHttpError) || error.status !== 403) return null;
44475
44847
  return "reply was refused (403). The most common cause is that the signal was not addressed to you \u2014 you cannot reply to your own ask; reply to the other party's signal, reach someone directly with cswarm ask --to <agent>, or post a channel-visible cswarm note. If you did receive that signal, the refusal is an authorization one instead: the credential may be revoked or expired, or it may not be a member of this workspace.";
44476
44848
  }
44849
+ function threadReplyMessage(signal, options) {
44850
+ if (!options.inThread) {
44851
+ return "Reply shared. It is immutable and addressed to the original author.";
44852
+ }
44853
+ const inThread = "Reply shared in the thread. It is immutable and readable by everyone who can read the thread.";
44854
+ if (!options.broadcastToChannel) return inThread;
44855
+ if (signal.channel_id === void 0) {
44856
+ return `${inThread} This deployment did not say which channel the thread is in, so whether it also reached a channel is unknown.`;
44857
+ }
44858
+ return signal.channel_id === null ? `${inThread} Its thread is in no channel, so --broadcast-to-channel had nothing to send it to.` : "Reply shared in the thread and sent to the thread's channel as well. It is immutable and readable by everyone who can read the thread.";
44859
+ }
44477
44860
  async function runReply(args) {
44478
44861
  args.assertShape([
44479
44862
  ...TARGET_FLAGS,
44480
44863
  "workspace-id",
44481
44864
  ...CREDENTIAL_FLAGS,
44482
44865
  "attach",
44866
+ "broadcast-to-channel",
44867
+ "thread",
44483
44868
  "until",
44484
44869
  "json"
44485
44870
  ], 3);
44871
+ const inThread = args.has("thread");
44872
+ const broadcastToChannel = args.has("broadcast-to-channel");
44873
+ if (broadcastToChannel && !inThread) {
44874
+ throw new UsageError(
44875
+ "--broadcast-to-channel sends a thread reply to its channel as well, so it needs --thread"
44876
+ );
44877
+ }
44486
44878
  const signalId = args.positionals[1];
44487
44879
  if (signalId === void 0 || !UUID_RE23.test(signalId)) {
44488
44880
  throw new Error("reply requires the signal UUID being answered");
@@ -44508,24 +44900,30 @@ async function runReply(args) {
44508
44900
  body: signalText(body, "body"),
44509
44901
  to_user_id: null,
44510
44902
  to_agent_principal_id: null,
44511
- in_reply_to: signalId.toLowerCase(),
44903
+ in_reply_to: inThread ? null : signalId.toLowerCase(),
44512
44904
  about: null,
44513
44905
  ...attachments.length === 0 ? {} : { attachments },
44514
- ...untilMs2 === void 0 ? {} : { until_ms: untilMs2 }
44906
+ ...untilMs2 === void 0 ? {} : { until_ms: untilMs2 },
44907
+ ...inThread ? { thread_root_id: signalId.toLowerCase() } : {},
44908
+ ...broadcastToChannel ? { broadcast_to_channel: true } : {}
44515
44909
  };
44516
44910
  let result;
44517
44911
  try {
44518
44912
  result = await postSignalCommand(cloud, credential, command2);
44519
44913
  } catch (error) {
44520
- const hint = replyRefusalHint(error);
44914
+ const hint = inThread ? null : replyRefusalHint(error);
44521
44915
  if (hint !== null) throw new Error(hint);
44522
44916
  throw error;
44523
44917
  }
44524
44918
  const signal = result.response.signal;
44919
+ const replyMessage = threadReplyMessage(signal, {
44920
+ inThread,
44921
+ broadcastToChannel
44922
+ });
44525
44923
  if (args.has("json")) {
44526
44924
  printJson({
44527
44925
  status: result.response.status,
44528
- message: "Reply shared. It is immutable, tenancy-scoped, and will quietly expire at its horizon.",
44926
+ message: inThread ? replyMessage : "Reply shared. It is immutable, tenancy-scoped, and will quietly expire at its horizon.",
44529
44927
  signal,
44530
44928
  retried: result.retried,
44531
44929
  attempts: result.attempts
@@ -44540,7 +44938,7 @@ async function runReply(args) {
44540
44938
  )
44541
44939
  );
44542
44940
  process.stdout.write(
44543
- `Reply shared. It is immutable and addressed to the original author.
44941
+ `${replyMessage}
44544
44942
  ${renderSignals([signal], {
44545
44943
  inbox: false,
44546
44944
  includeStale: true,
@@ -44822,6 +45220,7 @@ async function runSignalRead(args, inbox) {
44822
45220
  "workspace-id",
44823
45221
  ...CREDENTIAL_FLAGS,
44824
45222
  "about",
45223
+ "channel",
44825
45224
  "kind",
44826
45225
  ...inbox ? ["wait", "follow", "ndjson", "notify"] : [],
44827
45226
  "since",
@@ -44837,6 +45236,9 @@ async function runSignalRead(args, inbox) {
44837
45236
  if (!args.has("ndjson")) {
44838
45237
  throw new Error("inbox --follow requires --ndjson");
44839
45238
  }
45239
+ if (args.has("channel")) {
45240
+ throw new Error("inbox --follow cannot be combined with --channel");
45241
+ }
44840
45242
  if (args.optional("wait") !== void 0) {
44841
45243
  throw new Error("inbox --follow cannot be combined with --wait");
44842
45244
  }
@@ -44849,15 +45251,29 @@ async function runSignalRead(args, inbox) {
44849
45251
  if (inbox && args.has("ndjson")) {
44850
45252
  throw new Error("inbox --ndjson requires --follow");
44851
45253
  }
45254
+ const channelSlug = channelOption(args);
44852
45255
  const waitSeconds = inbox && args.optional("wait") !== void 0 ? parseWaitSeconds(args.required("wait")) : void 0;
44853
45256
  const cloud = await target(args);
44854
45257
  const selected = await commandWorkspaceAndCredential(args, cloud, {
44855
45258
  validateHumanWorkspace: true
44856
45259
  });
44857
45260
  const credential = signalCredentialOf(selected);
45261
+ let channelId;
45262
+ if (channelSlug !== void 0 && selected.kind === "human") {
45263
+ const rows4 = await listChannelsAsHuman(
45264
+ cloud,
45265
+ selected.human.accessToken,
45266
+ selected.selectedWorkspace
45267
+ );
45268
+ const match = findChannelBySlug(rows4, channelSlug);
45269
+ if (match === null) throw new Error(unknownChannelMessage(channelSlug, rows4));
45270
+ channelId = match.channel_id;
45271
+ }
44858
45272
  const queryBase = {
44859
45273
  workspaceId: selected.selectedWorkspace,
44860
45274
  inbox,
45275
+ ...channelSlug === void 0 || selected.kind !== "agent" ? {} : { channel: channelSlug },
45276
+ ...channelId === void 0 ? {} : { channelId },
44861
45277
  ...args.optional("about") === void 0 ? {} : { about: signalText(args.required("about"), "about") },
44862
45278
  ...args.optional("kind") === void 0 ? {} : { kind: signalKind(args.required("kind")) },
44863
45279
  ...args.optional("since") === void 0 ? {} : { since: args.required("since") },
@@ -44867,17 +45283,23 @@ async function runSignalRead(args, inbox) {
44867
45283
  let rows3;
44868
45284
  let timedOut = false;
44869
45285
  let waited = false;
44870
- if (waitSeconds === void 0) {
44871
- rows3 = await readSignals(cloud, credential, queryBase);
44872
- } else {
44873
- waited = true;
44874
- const deadlineMs = waitDeadlineMs(waitSeconds);
44875
- const waitResult = await pollForSignals({
44876
- deadlineMs,
44877
- read: () => readSignals(cloud, credential, queryBase, { deadlineMs })
44878
- });
44879
- rows3 = waitResult.signals;
44880
- timedOut = waitResult.timedOut;
45286
+ try {
45287
+ if (waitSeconds === void 0) {
45288
+ rows3 = await readSignals(cloud, credential, queryBase);
45289
+ } else {
45290
+ waited = true;
45291
+ const deadlineMs = waitDeadlineMs(waitSeconds);
45292
+ const waitResult = await pollForSignals({
45293
+ deadlineMs,
45294
+ read: () => readSignals(cloud, credential, queryBase, { deadlineMs })
45295
+ });
45296
+ rows3 = waitResult.signals;
45297
+ timedOut = waitResult.timedOut;
45298
+ }
45299
+ } catch (error) {
45300
+ const named = channelSlug === void 0 ? null : unknownChannelReadMessage(error, channelSlug);
45301
+ if (named !== null) throw new Error(named);
45302
+ throw error;
44881
45303
  }
44882
45304
  if (args.has("json")) {
44883
45305
  printJson(
@@ -44910,6 +45332,12 @@ async function runSignalRead(args, inbox) {
44910
45332
  );
44911
45333
  return;
44912
45334
  }
45335
+ if (channelSlug !== void 0) {
45336
+ process.stdout.write(
45337
+ `${inbox ? "Inbox" : "Feed"}, filed in ${channelSlug}:
45338
+ `
45339
+ );
45340
+ }
44913
45341
  process.stdout.write(`${renderSignals(rows3, {
44914
45342
  inbox,
44915
45343
  includeStale: args.has("include-stale"),
@@ -47468,6 +47896,174 @@ async function runFeedback(args) {
47468
47896
  "Feedback recorded for the operators of this deployment. It is stored durably with your workspace and identity attached, and it is read when they review feedback - there is no reply channel, so nothing further will happen in this session.\n"
47469
47897
  );
47470
47898
  }
47899
+ async function channelRows(context) {
47900
+ if (context.selected.kind === "agent") {
47901
+ throw new Error(CHANNEL_LIST_NEEDS_HUMAN_MESSAGE);
47902
+ }
47903
+ const read = async () => await listChannelsAsHuman(
47904
+ context.cloud,
47905
+ context.selected.human.accessToken,
47906
+ context.selected.selectedWorkspace
47907
+ );
47908
+ try {
47909
+ return await read();
47910
+ } catch (error) {
47911
+ if (error instanceof ChannelListError && error.noResponse) return await read();
47912
+ throw error;
47913
+ }
47914
+ }
47915
+ function channelSelectorKind(selector) {
47916
+ if (UUID_RE23.test(selector)) return "id";
47917
+ const problem = channelSelectorProblem(selector);
47918
+ if (problem !== null) throw new Error(problem);
47919
+ return "name";
47920
+ }
47921
+ async function resolveChannelSelector(context, selector, kind) {
47922
+ if (kind === "id") return selector.toLowerCase();
47923
+ if (context.selected.kind === "agent") {
47924
+ throw new Error(CHANNEL_SELECTOR_NEEDS_ID_MESSAGE);
47925
+ }
47926
+ const rows3 = await channelRows(context);
47927
+ const match = findChannelBySlug(rows3, selector);
47928
+ if (match === null) throw new Error(unknownChannelMessage(selector, rows3));
47929
+ return match.channel_id;
47930
+ }
47931
+ async function sendChannelCommand(context, command2) {
47932
+ const client = new ThinCommandClient(context.cloud);
47933
+ const result = await client.sendChannel({
47934
+ workspaceId: context.selected.selectedWorkspace,
47935
+ command: command2,
47936
+ credential: context.selected.bearer
47937
+ });
47938
+ return result.channel;
47939
+ }
47940
+ var CHANNEL_FLAGS = [
47941
+ ...TARGET_FLAGS,
47942
+ "workspace-id",
47943
+ ...CREDENTIAL_FLAGS,
47944
+ "json"
47945
+ ];
47946
+ async function runChannelCreate(args) {
47947
+ const name = args.positionals[2];
47948
+ if (name === void 0) {
47949
+ throw new UsageError("cswarm channel create needs a channel name");
47950
+ }
47951
+ args.assertShape([...CHANNEL_FLAGS, "purpose"], 3);
47952
+ const problem = channelSlugProblem(name);
47953
+ if (problem !== null) throw new Error(problem);
47954
+ const purposeInput = args.optional("purpose");
47955
+ const purpose = purposeInput === void 0 ? void 0 : purposeInput.trim();
47956
+ if (purpose !== void 0 && purpose.length > CHANNEL_PURPOSE_MAX) {
47957
+ throw new Error(
47958
+ `A channel purpose is at most ${CHANNEL_PURPOSE_MAX} characters.`
47959
+ );
47960
+ }
47961
+ const context = await fileContext(args, ["purpose"], 3);
47962
+ const channel = await sendChannelCommand(context, {
47963
+ kind: "channel_create",
47964
+ slug: normalizeChannelSlug(name),
47965
+ ...purpose === void 0 || purpose.length === 0 ? {} : { purpose }
47966
+ });
47967
+ if (args.has("json")) {
47968
+ printJson({ workspace_id: channel.workspace_id, channel });
47969
+ return;
47970
+ }
47971
+ process.stdout.write(
47972
+ `Channel ${channel.slug} created. Everyone in this workspace can read it and post to it; a channel is where a message is filed, not who may see it.
47973
+ Post to it with cswarm note "<text>" --channel ${channel.slug}
47974
+ Read it with cswarm feed --channel ${channel.slug}
47975
+ Its id, which rename and archive take: ${channel.channel_id}
47976
+ `
47977
+ );
47978
+ }
47979
+ async function runChannelLs(args) {
47980
+ args.assertShape([...CHANNEL_FLAGS, "include-archived"], 2);
47981
+ const context = await fileContext(args, ["include-archived"], 2);
47982
+ const rows3 = await channelRows(context);
47983
+ const includeArchived = args.has("include-archived");
47984
+ if (args.has("json")) {
47985
+ printJson({
47986
+ workspace_id: context.selected.selectedWorkspace,
47987
+ channels: includeArchived ? rows3 : rows3.filter((row) => row.archived_at === null)
47988
+ });
47989
+ return;
47990
+ }
47991
+ process.stdout.write(renderChannelList(rows3, { includeArchived }));
47992
+ }
47993
+ async function runChannelRename(args) {
47994
+ const selector = args.positionals[2];
47995
+ const nextName = args.positionals[3];
47996
+ if (selector === void 0 || nextName === void 0) {
47997
+ throw new UsageError(
47998
+ "cswarm channel rename needs the channel and its new name"
47999
+ );
48000
+ }
48001
+ args.assertShape([...CHANNEL_FLAGS], 4);
48002
+ const selectorKind = channelSelectorKind(selector);
48003
+ const problem = channelSlugProblem(nextName);
48004
+ if (problem !== null) throw new Error(problem);
48005
+ const context = await fileContext(args, [], 4);
48006
+ const channelId = await resolveChannelSelector(context, selector, selectorKind);
48007
+ const channel = await sendChannelCommand(context, {
48008
+ kind: "channel_rename",
48009
+ channel_id: channelId,
48010
+ slug: normalizeChannelSlug(nextName)
48011
+ });
48012
+ if (args.has("json")) {
48013
+ printJson({ workspace_id: channel.workspace_id, channel });
48014
+ return;
48015
+ }
48016
+ process.stdout.write(
48017
+ `Channel renamed to ${channel.slug}. Every message already filed in it is unchanged and its id has not moved.
48018
+ Post to it with cswarm note "<text>" --channel ${channel.slug}
48019
+ Its id: ${channel.channel_id}
48020
+ `
48021
+ );
48022
+ }
48023
+ async function runChannelArchive(args) {
48024
+ const selector = args.positionals[2];
48025
+ if (selector === void 0) {
48026
+ throw new UsageError("cswarm channel archive needs the channel");
48027
+ }
48028
+ args.assertShape([...CHANNEL_FLAGS], 3);
48029
+ const selectorKind = channelSelectorKind(selector);
48030
+ const context = await fileContext(args, [], 3);
48031
+ const channelId = await resolveChannelSelector(context, selector, selectorKind);
48032
+ const channel = await sendChannelCommand(context, {
48033
+ kind: "channel_archive",
48034
+ channel_id: channelId
48035
+ });
48036
+ if (args.has("json")) {
48037
+ printJson({ workspace_id: channel.workspace_id, channel });
48038
+ return;
48039
+ }
48040
+ process.stdout.write(
48041
+ `Channel ${channel.slug} is archived. It keeps its messages and its links, and it takes no new ones. Archiving it again changes nothing.
48042
+ See it with cswarm channel ls --include-archived
48043
+ Read what is in it with cswarm feed --channel ${channel.slug}
48044
+ `
48045
+ );
48046
+ }
48047
+ var CHANNEL_SUBCOMMANDS = {
48048
+ create: runChannelCreate,
48049
+ ls: runChannelLs,
48050
+ rename: runChannelRename,
48051
+ archive: runChannelArchive
48052
+ };
48053
+ var CHANNEL_SUBCOMMAND_NAMES = Object.keys(
48054
+ CHANNEL_SUBCOMMANDS
48055
+ );
48056
+ async function runChannel(args) {
48057
+ const action = args.positionals[1];
48058
+ const chosen = action === void 0 ? void 0 : CHANNEL_SUBCOMMANDS[action];
48059
+ if (chosen === void 0) {
48060
+ const names = Object.keys(CHANNEL_SUBCOMMANDS);
48061
+ throw new UsageError(
48062
+ `cswarm channel takes ${names.slice(0, -1).join(", ")}, or ${names[names.length - 1]}`
48063
+ );
48064
+ }
48065
+ return await chosen(args);
48066
+ }
47471
48067
  async function runFile(args) {
47472
48068
  const action = args.positionals[1];
47473
48069
  if (action === "put") return await runFilePut(args);
@@ -47743,6 +48339,10 @@ async function main() {
47743
48339
  await runFeedback(args);
47744
48340
  return;
47745
48341
  }
48342
+ if (verb === "channel") {
48343
+ await runChannel(args);
48344
+ return;
48345
+ }
47746
48346
  if (verb === "file") {
47747
48347
  await runFile(args);
47748
48348
  return;
@@ -47884,6 +48484,7 @@ ${usage()}
47884
48484
  });
47885
48485
  // Annotate the CommonJS export names for ESM import in node:
47886
48486
  0 && (module.exports = {
48487
+ CHANNEL_SUBCOMMAND_NAMES,
47887
48488
  EXIT_RESTARTABLE,
47888
48489
  ListenerUnattendedRefusedError,
47889
48490
  TURN_BUDGET_CREDENTIAL_MARGIN_MS,
@@ -47902,5 +48503,6 @@ ${usage()}
47902
48503
  resolveDetachedClaudeExecutable,
47903
48504
  resolveDetachedCodexExecutable,
47904
48505
  resolveTurnBudgetOrDefer,
48506
+ threadReplyMessage,
47905
48507
  usage
47906
48508
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.54",
3
+ "version": "0.1.55",
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"