@samva/cli 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/AGENTS.md +16 -7
  2. package/dist/bin.mjs +158 -79
  3. package/package.json +5 -5
package/AGENTS.md CHANGED
@@ -21,9 +21,15 @@ response writes its body to stderr and exits `4` for 401 and 403, otherwise `1`.
21
21
  is no built-in filter.
22
22
 
23
23
  `samva api-keys` manages the organization's keys: `list`, `get`, `create`, `update`, `rotate`, and
24
- `revoke`. `create` takes `--name` and exactly one of `--full-access` or repeatable `--grant
25
- <resource>=read|write` (neither or both exits `2`), plus optional `--description` and `--expires-at`;
26
- mailbox and namespace scope go through `samva api`. The API refuses a key with more access than the
24
+ `revoke`. `create` takes `--name` and either `--full-access` or restricted access built from repeatable
25
+ `--grant <resource>=read|write` and mailbox access: repeatable `--mailbox-permission
26
+ read|update|send|quarantine.read`, optionally narrowed to repeatable `--mailbox <id>` (default:
27
+ every mailbox the owner reaches) with `--send-mode approval` (each send waits for a person other
28
+ than the key). `--namespace <id>` confines a restricted key to one namespace. Passing no access, or both
29
+ `--full-access` and restricted access, exits `2`, as does `--mailbox` or `--send-mode` without a
30
+ `--mailbox-permission`. `--description` and `--expires-at` are optional. `update` renames a key,
31
+ sets its description, or changes a mailbox key's `--send-mode` (a person signed in with `samva
32
+ login`); every other part of a key's access is fixed at creation. The API refuses a key with more access than the
27
33
  caller holds. `create` and `rotate` return the secret once: `--json` carries `{apiKey, key,
28
34
  displayOnce, warning}`, so `samva api-keys create ... --json | jq -r .data.key` yields the key, and
29
35
  human mode prints it to stdout with the warning on stderr. `rotate` and `revoke` require
@@ -136,7 +142,10 @@ calls to you, rather than to an installation. Read what exists with `list`, `ver
136
142
  `run <id> <run-id>` (adds the handler's log lines); the list commands page with `--limit`,
137
143
  `--cursor`, and `--all --jsonl`.
138
144
 
139
- `samva mailboxes` reads and sends as a mailbox: `list` pages with `--limit`, `--cursor`, `--after`,
145
+ `samva mailboxes` reads and sends as a mailbox. Every command except `list` and `create`
146
+ takes the mailbox ID as its first argument and the message, action, or endpoint ID second,
147
+ as in `read <mailbox-id> <message-id>` and `endpoints update <mailbox-id> <endpoint-id>`; `events`
148
+ takes any number of mailbox IDs, or none for every mailbox the credential reads. `list` pages with `--limit`, `--cursor`, `--after`,
140
149
  and `--before`. `create --address <address> --name <name>` makes a mailbox with one email endpoint; the
141
150
  slug defaults to the address's local part, and a collision asks for `--slug` rather than suffixing.
142
151
  `get <id>` inspects a mailbox; `update <id> --data <JSON-or-@file>` changes
@@ -156,9 +165,9 @@ public SDK describes email only.
156
165
  Quarantine `list` pages the same way, bounded by `--after` and `--before` on when a message was
157
166
  held; `release` and `discard` take the held message's `id` and require a person signed in with
158
167
  `samva login`. Discard requires confirmation (`--yes` outside a terminal) and supports `--dry-run`.
159
- `actions list --mailbox <id>` filters by `--state` and pages with `--limit`, `--cursor`, and
160
- `--all --jsonl`. `actions approve <action-id> --mailbox <id>` and `actions deny <action-id>
161
- --mailbox <id> [--reason <text>]` require a person different from the proposer, confirmation
168
+ `actions list <mailbox-id>` filters by `--state` and pages with `--limit`, `--cursor`, and
169
+ `--all --jsonl`. `actions approve <mailbox-id> <action-id>` and `actions deny <mailbox-id>
170
+ <action-id> [--reason <text>]` require a person different from the proposer, confirmation
162
171
  (`--yes` outside a terminal), and offer `--dry-run`.
163
172
  Human mailbox and extension results retain the same facts as JSON: mailbox endpoints (channel, address,
164
173
  sender name, default), thread state and activity, message bodies, attachments, authentication and actor,
package/dist/bin.mjs CHANGED
@@ -14106,7 +14106,7 @@ var layer12 = /* @__PURE__ */ layer.pipe(/* @__PURE__ */ provideMerge(/* @__PURE
14106
14106
  // package.json
14107
14107
  var package_default = {
14108
14108
  name: "@samva/cli",
14109
- version: "0.16.0",
14109
+ version: "0.17.0",
14110
14110
  description: "Samva CLI \u2014 email and platform operations from your terminal",
14111
14111
  keywords: [
14112
14112
  "cli",
@@ -14168,10 +14168,10 @@ var package_default = {
14168
14168
  vitest: "catalog:"
14169
14169
  },
14170
14170
  optionalDependencies: {
14171
- "@samva/cli-darwin-arm64": "workspace:0.16.0",
14172
- "@samva/cli-darwin-x64": "workspace:0.16.0",
14173
- "@samva/cli-linux-arm64": "workspace:0.16.0",
14174
- "@samva/cli-linux-x64": "workspace:0.16.0"
14171
+ "@samva/cli-darwin-arm64": "workspace:0.17.0",
14172
+ "@samva/cli-darwin-x64": "workspace:0.17.0",
14173
+ "@samva/cli-linux-arm64": "workspace:0.17.0",
14174
+ "@samva/cli-linux-x64": "workspace:0.17.0"
14175
14175
  },
14176
14176
  engines: {
14177
14177
  node: ">=18"
@@ -19353,6 +19353,7 @@ var Literals4 = (name, choices) => Literals2(argumentKind, name, choices);
19353
19353
  var optional5 = (arg) => optional3(arg);
19354
19354
  var withDescription4 = /* @__PURE__ */ dual(2, (self, description) => withDescription(self, description));
19355
19355
  var withDefault6 = withDefault4;
19356
+ var atLeast3 = /* @__PURE__ */ dual(2, (self, min) => atLeast(self, min));
19356
19357
  var withSchema3 = /* @__PURE__ */ dual(2, (self, schema) => withSchema(self, schema));
19357
19358
  var filterMap3 = /* @__PURE__ */ dual(3, (self, f, onNone) => filterMap2(self, f, onNone));
19358
19359
 
@@ -26569,6 +26570,7 @@ var API_KEY_STATEMENT = {
26569
26570
  };
26570
26571
  var API_KEY_RESOURCES = Object.keys(API_KEY_STATEMENT);
26571
26572
  var API_KEY_LEVEL_RESOURCES = API_KEY_RESOURCES.filter((resource) => resource !== "mailboxes");
26573
+ var API_KEY_MAILBOX_ACTIONS = mailboxKeyActions;
26572
26574
  function apiKeyAccessLevels(resource) {
26573
26575
  return API_KEY_STATEMENT[resource].some((action) => action !== "read") ? ["read", "write"] : ["read"];
26574
26576
  }
@@ -29066,7 +29068,7 @@ var parseGrant = (raw) => {
29066
29068
  if (resource === "mailboxes") {
29067
29069
  return new CliUsageError({
29068
29070
  code: "invalid_grant",
29069
- message: "--grant does not cover mailboxes; mailbox and namespace access are set with `samva api`."
29071
+ message: "--grant does not cover mailboxes; use --mailbox-permission <action> (with optional --mailbox and --send-mode) instead."
29070
29072
  });
29071
29073
  }
29072
29074
  if (!isLevelResource(resource) || !isGrantLevel(level)) {
@@ -29083,19 +29085,52 @@ var parseGrant = (raw) => {
29083
29085
  }
29084
29086
  return { resource, level };
29085
29087
  };
29086
- var accessFromFlags = (grants, fullAccess) => gen2(function* () {
29087
- if (fullAccess && grants.length > 0) {
29088
+ var repeated = (values) => values.find((value, index) => values.indexOf(value) !== index);
29089
+ var mailboxAccessFromFlags = (flags) => gen2(function* () {
29090
+ if (flags.permissions.length === 0) {
29091
+ if (flags.mailboxIds.length > 0 || isSome2(flags.sendMode)) {
29092
+ return yield* new CliUsageError({
29093
+ code: "mailbox_permission_required",
29094
+ message: `--mailbox and --send-mode narrow mailbox access; also pass --mailbox-permission ${API_KEY_MAILBOX_ACTIONS.join("|")}.`
29095
+ });
29096
+ }
29097
+ return;
29098
+ }
29099
+ const duplicatePermission = repeated(flags.permissions);
29100
+ if (duplicatePermission !== undefined) {
29101
+ return yield* new CliUsageError({
29102
+ code: "invalid_mailbox_access",
29103
+ message: `--mailbox-permission names ${duplicatePermission} more than once.`
29104
+ });
29105
+ }
29106
+ const duplicateMailbox = repeated(flags.mailboxIds);
29107
+ if (duplicateMailbox !== undefined) {
29108
+ return yield* new CliUsageError({
29109
+ code: "invalid_mailbox_access",
29110
+ message: `--mailbox names ${duplicateMailbox} more than once.`
29111
+ });
29112
+ }
29113
+ return {
29114
+ permissions: flags.permissions,
29115
+ ...flags.mailboxIds.length > 0 ? { mailboxIds: flags.mailboxIds } : {},
29116
+ ...isSome2(flags.sendMode) ? { sendMode: flags.sendMode.value } : {}
29117
+ };
29118
+ });
29119
+ var accessFromFlags = (grants, fullAccess, mailbox, namespace) => gen2(function* () {
29120
+ const restrictedFlags = grants.length > 0 || mailbox.permissions.length > 0 || mailbox.mailboxIds.length > 0 || isSome2(mailbox.sendMode) || isSome2(namespace);
29121
+ if (fullAccess && restrictedFlags) {
29088
29122
  return yield* new CliUsageError({
29089
29123
  code: "access_conflict",
29090
- message: "Pass either --full-access or --grant <resource>=read|write, not both."
29124
+ message: "Pass either --full-access or restricted access (--grant, --mailbox-permission, --namespace), not both."
29091
29125
  });
29092
29126
  }
29093
29127
  if (fullAccess)
29094
29128
  return { mode: "full" };
29095
- if (grants.length === 0) {
29129
+ const mailboxes = yield* mailboxAccessFromFlags(mailbox);
29130
+ if (grants.length === 0 && mailboxes === undefined) {
29096
29131
  return yield* new CliUsageError({
29097
29132
  code: "access_required",
29098
- message: "Choose the key's access: --full-access, or one or more --grant <resource>=read|write."
29133
+ message: "Choose the key's access: --full-access, or one or more --grant <resource>=read|write and/or --mailbox-permission <action>."
29099
29134
  });
29100
29135
  }
29101
29136
  const resources = {};
@@ -29111,7 +29146,12 @@ var accessFromFlags = (grants, fullAccess) => gen2(function* () {
29111
29146
  }
29112
29147
  resources[grant.resource] = grant.level;
29113
29148
  }
29114
- return { mode: "restricted", resources };
29149
+ return {
29150
+ mode: "restricted",
29151
+ resources,
29152
+ ...mailboxes === undefined ? {} : { mailboxes },
29153
+ ...isSome2(namespace) ? { namespaceId: namespace.value } : {}
29154
+ };
29115
29155
  });
29116
29156
  var parseExpiresAt = (raw) => Number.isNaN(Date.parse(raw)) ? fail6(new CliUsageError({
29117
29157
  code: "invalid_expires_at",
@@ -29125,7 +29165,12 @@ var describeAccess = (access) => {
29125
29165
  return "full";
29126
29166
  const grants = Object.entries(access.resources).filter(([, level]) => level !== undefined).map(([resource, level]) => `${resource}:${level}`);
29127
29167
  if (access.mailboxes !== undefined && access.mailboxes !== null) {
29128
- grants.push(`mailboxes:${access.mailboxes.permissions.join("+")}`);
29168
+ const { permissions, mailboxIds, sendMode } = access.mailboxes;
29169
+ const scope = [
29170
+ ...mailboxIds === undefined || mailboxIds === null ? [] : [mailboxIds.join("+")],
29171
+ ...sendMode === undefined || sendMode === "send" ? [] : [`${sendMode} sends`]
29172
+ ];
29173
+ grants.push(`mailboxes:${permissions.join("+")}${scope.length === 0 ? "" : ` [${scope.join("; ")}]`}`);
29129
29174
  }
29130
29175
  if (access.namespaceId !== undefined && access.namespaceId !== null)
29131
29176
  grants.push(`namespace:${access.namespaceId}`);
@@ -29204,11 +29249,19 @@ var create2 = make29("create", {
29204
29249
  description: String9("description").pipe(withDescription2("What the key is for"), optional4),
29205
29250
  expiresAt: String9("expires-at").pipe(withDescription2("When the key stops working, an ISO 8601 instant"), optional4),
29206
29251
  grant: String9("grant").pipe(withDescription2(`Give a restricted key a resource at a level, <resource>=read|write (repeatable). Resources: ${GRANTABLE_RESOURCES.join(", ")}`), atLeast2(0)),
29207
- fullAccess: Boolean6("full-access").pipe(withDescription2("Give the key its owner's full access instead of --grant"), withDefault5(false)),
29252
+ mailboxPermission: Literals3("mailbox-permission", API_KEY_MAILBOX_ACTIONS).pipe(withDescription2(`Let a restricted key work in mailboxes with this action (repeatable): ${API_KEY_MAILBOX_ACTIONS.join(", ")}`), atLeast2(0)),
29253
+ mailbox: String9("mailbox").pipe(withSchema2(MailboxId), withDescription2("Confine the mailbox access to this mailbox (repeatable); omit for every mailbox the owner reaches"), atLeast2(0)),
29254
+ sendMode: Literals3("send-mode", mailboxSendModes).pipe(withDescription2("With approval, each send waits until a person other than the key approves it (default send)"), optional4),
29255
+ namespace: String9("namespace").pipe(withSchema2(NamespaceId), withDescription2("Confine a restricted key to one namespace (ns_...)"), optional4),
29256
+ fullAccess: Boolean6("full-access").pipe(withDescription2("Give the key its owner's full access instead of restricted access"), withDefault5(false)),
29208
29257
  dryRun: dryRunFlag,
29209
29258
  json: jsonFlag
29210
29259
  }).pipe(withDescription3("Create an API key. The secret is shown once; the API refuses access you do not hold."), withExamples([
29211
29260
  { command: "samva api-keys create --name ci --grant email=write --dry-run" },
29261
+ {
29262
+ command: "samva api-keys create --name agent --mailbox-permission read --mailbox-permission send --mailbox mbx_1234567890123456 --send-mode approval",
29263
+ description: "A key that works in one mailbox and needs a person to approve each send"
29264
+ },
29212
29265
  {
29213
29266
  command: "samva api-keys create --name ci --grant email=write --json | jq -r .data.key",
29214
29267
  description: "Pipe the secret into another tool"
@@ -29216,8 +29269,20 @@ var create2 = make29("create", {
29216
29269
  {
29217
29270
  command: "samva api-keys create --name deploy --full-access --expires-at 2027-01-01T00:00:00Z"
29218
29271
  }
29219
- ]), withHandler(({ name, description, expiresAt, grant, fullAccess, dryRun, json }) => withJsonQuiet(json, gen2(function* () {
29220
- const access = yield* accessFromFlags(grant, fullAccess);
29272
+ ]), withHandler(({
29273
+ name,
29274
+ description,
29275
+ expiresAt,
29276
+ grant,
29277
+ mailboxPermission,
29278
+ mailbox,
29279
+ sendMode,
29280
+ namespace,
29281
+ fullAccess,
29282
+ dryRun,
29283
+ json
29284
+ }) => withJsonQuiet(json, gen2(function* () {
29285
+ const access = yield* accessFromFlags(grant, fullAccess, { permissions: mailboxPermission, mailboxIds: mailbox, sendMode }, namespace);
29221
29286
  const expires = yield* match(expiresAt, {
29222
29287
  onNone: () => void_3,
29223
29288
  onSome: parseExpiresAt
@@ -29233,7 +29298,11 @@ var create2 = make29("create", {
29233
29298
  });
29234
29299
  if (dryRun) {
29235
29300
  if (json) {
29236
- return yield* printJson({ dryRun: true, action: "api-keys.create", request: payload });
29301
+ return yield* printJson({
29302
+ dryRun: true,
29303
+ action: "api-keys.create",
29304
+ request: payload
29305
+ });
29237
29306
  }
29238
29307
  return yield* printFields([
29239
29308
  ["Action", "api-keys.create"],
@@ -29256,13 +29325,17 @@ var update3 = make29("update", {
29256
29325
  id: idArgument,
29257
29326
  name: String9("name").pipe(withDescription2("New key name"), optional4),
29258
29327
  description: String9("description").pipe(withDescription2("New description"), optional4),
29328
+ sendMode: Literals3("send-mode", mailboxSendModes).pipe(withDescription2("Change a mailbox key's send mode: approval holds each send for a person other than the key, send releases them. Takes a person signed in with samva login"), optional4),
29259
29329
  dryRun: dryRunFlag,
29260
29330
  json: jsonFlag
29261
- }).pipe(withDescription3("Rename an API key or change its description. Access is fixed at creation; create a new key to change it."), withExamples([{ command: "samva api-keys update key_abc123 --name ci-prod --dry-run" }]), withHandler(({ id, name, description, dryRun, json }) => withJsonQuiet(json, gen2(function* () {
29262
- if (isNone2(name) && isNone2(description)) {
29331
+ }).pipe(withDescription3("Rename an API key, change its description, or change a mailbox key's send mode. Its other access is fixed at creation; create a new key to change it."), withExamples([
29332
+ { command: "samva api-keys update key_abc123 --name ci-prod --dry-run" },
29333
+ { command: "samva api-keys update key_abc123 --send-mode approval" }
29334
+ ]), withHandler(({ id, name, description, sendMode, dryRun, json }) => withJsonQuiet(json, gen2(function* () {
29335
+ if (isNone2(name) && isNone2(description) && isNone2(sendMode)) {
29263
29336
  return yield* new CliUsageError({
29264
29337
  code: "update_required",
29265
- message: "Pass --name, --description, or both."
29338
+ message: "Pass --name, --description, --send-mode, or a combination."
29266
29339
  });
29267
29340
  }
29268
29341
  const payload = yield* decodeRequest(ApiKeysUpdateRequestJson, {
@@ -29270,6 +29343,10 @@ var update3 = make29("update", {
29270
29343
  ...match(description, {
29271
29344
  onNone: () => ({}),
29272
29345
  onSome: (value) => ({ description: value })
29346
+ }),
29347
+ ...match(sendMode, {
29348
+ onNone: () => ({}),
29349
+ onSome: (value) => ({ mailboxes: { sendMode: value } })
29273
29350
  })
29274
29351
  });
29275
29352
  if (dryRun) {
@@ -29285,7 +29362,8 @@ var update3 = make29("update", {
29285
29362
  ["Action", "api-keys.update"],
29286
29363
  ["API key", id],
29287
29364
  ["Name", payload.name],
29288
- ["Description", payload.description]
29365
+ ["Description", payload.description],
29366
+ ["Send mode", payload.mailboxes?.sendMode]
29289
29367
  ]);
29290
29368
  }
29291
29369
  const client = yield* getClientProvider;
@@ -66402,7 +66480,7 @@ var refusalNext = (failure) => {
66402
66480
  case "ForbiddenError":
66403
66481
  return "Use a credential that may read mailboxes and follow the API message.";
66404
66482
  case "ResourceNotFoundError":
66405
- return "Run samva mailboxes list to find the mailboxes this credential can read, then retry with those --mailbox ids.";
66483
+ return "Run samva mailboxes list to find the mailboxes this credential can read, then retry with those mailbox ids.";
66406
66484
  case "CursorExpiredError":
66407
66485
  return "Events after your cursor expired. Read current state from the API and reconnect without --cursor.";
66408
66486
  case "ValidationError":
@@ -66529,12 +66607,16 @@ var tailEvents = (options) => gen2(function* () {
66529
66607
  })), mapError4(streamError), options.limit === undefined ? (stream) => stream : take3(options.limit), interruptWhen2(interrupted), runForEach2(options.onEvent), raceFirst2(interrupted.pipe(andThen2(sleep2(HANDLER_DRAIN)))))));
66530
66608
  });
66531
66609
  var mailboxesEventsCommand = make29("events", {
66610
+ mailbox: String10("mailbox-id").pipe(withDescription4("Mailboxes to follow; omit to follow every mailbox the credential reads"), withSchema3(MailboxId), atLeast3(0)),
66532
66611
  cursor: String9("cursor").pipe(withDescription2("Replay events after a cursor from an earlier event frame, then go live"), optional4),
66533
- mailbox: String9("mailbox").pipe(withSchema2(MailboxId), atLeast2(0)),
66534
66612
  limit: Int5("limit").pipe(optional4),
66535
66613
  noReconnect: Boolean6("no-reconnect").pipe(withDefault5(false), withDescription2("Exit non-zero with the last cursor when the connection drops, instead of reconnecting from it"))
66536
66614
  }).pipe(withDescription3("Tail mailbox events, one record per line; --jsonl wraps each in a result envelope; a dropped connection reconnects from the last cursor; Ctrl+C exits cleanly"), withExamples([
66537
- { command: "samva mailboxes events --mailbox mbx_1234567890123456 --limit 10" },
66615
+ { command: "samva mailboxes events mbx_1234567890123456 --limit 10" },
66616
+ {
66617
+ command: "samva mailboxes events mbx_1234567890123456 mbx_6543210987654321 --jsonl",
66618
+ description: "Follow several mailboxes"
66619
+ },
66538
66620
  { command: "samva mailboxes events --jsonl", description: "Stream envelopes for automation" },
66539
66621
  { command: "samva mailboxes events --cursor <cursor-from-an-event-frame>" },
66540
66622
  {
@@ -71573,7 +71655,7 @@ var decodeThreadPage = decoder3(WireThreadPage, "thread page");
71573
71655
  var decodeMessage = decoder3(WireMessage, "message");
71574
71656
 
71575
71657
  // src/commands/mailboxes.ts
71576
- var mailboxFlag = String9("mailbox").pipe(withDescription2("Mailbox ID"), withSchema2(MailboxId));
71658
+ var mailboxIdArgument = String10("mailbox-id").pipe(withSchema3(MailboxId));
71577
71659
  var messageIdArgument = String10("message-id").pipe(withSchema3(MessageId));
71578
71660
  var optional6 = (name) => String9(name).pipe(optional4);
71579
71661
  var pageFlags2 = {
@@ -71616,7 +71698,7 @@ var chatContent = (channel, flags) => gen2(function* () {
71616
71698
  }
71617
71699
  if (channel !== "whatsapp")
71618
71700
  return yield* usage2("mailbox_chat_template", "A template sends on WhatsApp only.");
71619
- const templateId = yield* decodeUnknownEffect2(TemplateId)(flags.template.value).pipe(mapError2(() => usage2("mailbox_chat_template", "--template must be an approved template id (tmpl_\u2026).")));
71701
+ const templateId = yield* decodeEffect2(TemplateId)(flags.template.value).pipe(mapError2(() => usage2("mailbox_chat_template", "--template must be an approved template id (tmpl_\u2026).")));
71620
71702
  const dataText = getOrUndefined(flags.data);
71621
71703
  if (dataText === undefined)
71622
71704
  return { templateId };
@@ -71734,7 +71816,6 @@ var create9 = make29("create", {
71734
71816
  return yield* printJson(data);
71735
71817
  yield* printMailbox(data);
71736
71818
  })));
71737
- var mailboxIdArgument = String10("mailbox-id").pipe(withSchema3(MailboxId));
71738
71819
  var get22 = make29("get", { id: mailboxIdArgument, json: jsonFlag }).pipe(withDescription3("Inspect a mailbox's endpoints and settings"), withHandler(({ id, json }) => gen2(function* () {
71739
71820
  const raw = yield* getMailboxJson("Could not read mailbox", `/v1/mailboxes/${id}`);
71740
71821
  const result = yield* decodeMailbox(raw);
@@ -71793,7 +71874,7 @@ var update9 = make29("update", {
71793
71874
  yield* json ? printJson(result) : printMailbox(result);
71794
71875
  })));
71795
71876
  var endpointAdd = make29("add", {
71796
- mailbox: mailboxFlag,
71877
+ mailbox: mailboxIdArgument,
71797
71878
  channel: channelFlag2,
71798
71879
  address: String9("address").pipe(withDescription2("The address to add: an email address such as billing@acme.samva.email, or on whatsapp and sms a number you connected, in E.164 form such as +14155550123")),
71799
71880
  displayName: String9("display-name").pipe(withDescription2("The sender name messages from this endpoint carry"), optional4),
@@ -71801,10 +71882,10 @@ var endpointAdd = make29("add", {
71801
71882
  json: jsonFlag
71802
71883
  }).pipe(withDescription3("Add an endpoint on a channel (email by default) to a mailbox"), withExamples([
71803
71884
  {
71804
- command: "samva mailboxes endpoints add --mailbox mbx_1234567890123456 --address billing@acme.samva.email"
71885
+ command: "samva mailboxes endpoints add mbx_1234567890123456 --address billing@acme.samva.email"
71805
71886
  },
71806
71887
  {
71807
- command: "samva mailboxes endpoints add --mailbox mbx_1234567890123456 --channel whatsapp --address +14155550123",
71888
+ command: "samva mailboxes endpoints add mbx_1234567890123456 --channel whatsapp --address +14155550123",
71808
71889
  description: "Add a connected WhatsApp number"
71809
71890
  }
71810
71891
  ]), withHandler(({ mailbox, channel, address, displayName, dryRun, json }) => gen2(function* () {
@@ -71836,8 +71917,8 @@ var endpointAdd = make29("add", {
71836
71917
  })));
71837
71918
  var endpointIdArgument = String10("endpoint-id").pipe(withSchema3(MailboxEndpointId));
71838
71919
  var endpointUpdate = make29("update", {
71920
+ mailbox: mailboxIdArgument,
71839
71921
  endpointId: endpointIdArgument,
71840
- mailbox: mailboxFlag,
71841
71922
  displayName: String9("display-name").pipe(withDescription2("The sender name messages from this endpoint carry"), optional4),
71842
71923
  clearDisplayName: Boolean6("clear-display-name").pipe(withDescription2("Send under the mailbox's name again"), withDefault5(false)),
71843
71924
  makeDefault: Boolean6("default").pipe(withDescription2("Start new conversations from this endpoint on its channel"), withDefault5(false)),
@@ -71845,7 +71926,7 @@ var endpointUpdate = make29("update", {
71845
71926
  json: jsonFlag
71846
71927
  }).pipe(withDescription3("Rename an endpoint's sender or make it the mailbox's default"), withExamples([
71847
71928
  {
71848
- command: 'samva mailboxes endpoints update mbxep_1234567890123456 --mailbox mbx_1234567890123456 --display-name "Acme Billing" --default'
71929
+ command: 'samva mailboxes endpoints update mbx_1234567890123456 mbxep_1234567890123456 --display-name "Acme Billing" --default'
71849
71930
  }
71850
71931
  ]), withHandler(({ endpointId, mailbox, displayName, clearDisplayName, makeDefault, dryRun, json }) => gen2(function* () {
71851
71932
  if (isSome2(displayName) && clearDisplayName)
@@ -71879,14 +71960,14 @@ var endpointUpdate = make29("update", {
71879
71960
  yield* json ? printJson(result) : printMailbox(result);
71880
71961
  })));
71881
71962
  var endpointRemove = make29("remove", {
71963
+ mailbox: mailboxIdArgument,
71882
71964
  endpointId: endpointIdArgument,
71883
- mailbox: mailboxFlag,
71884
71965
  dryRun: Boolean6("dry-run").pipe(withDefault5(false)),
71885
71966
  yes: Boolean6("yes").pipe(withDefault5(false)),
71886
71967
  json: jsonFlag
71887
71968
  }).pipe(withDescription3("Remove an endpoint from a mailbox; mail sent to it stops reaching the mailbox"), withExamples([
71888
71969
  {
71889
- command: "samva mailboxes endpoints remove mbxep_1234567890123456 --mailbox mbx_1234567890123456 --yes"
71970
+ command: "samva mailboxes endpoints remove mbx_1234567890123456 mbxep_1234567890123456 --yes"
71890
71971
  }
71891
71972
  ]), withHandler(({ endpointId, mailbox, dryRun, yes, json }) => gen2(function* () {
71892
71973
  if (dryRun)
@@ -71918,7 +71999,7 @@ var lifecycle = (verb) => make29(verb, {
71918
71999
  yield* json ? printJson(result) : printMailbox(result);
71919
72000
  })));
71920
72001
  var actionsList = make29("list", {
71921
- mailbox: mailboxFlag,
72002
+ mailbox: mailboxIdArgument,
71922
72003
  ...pageFlags2,
71923
72004
  state: Literals3("state", MailboxActionState.literals).pipe(optional4),
71924
72005
  after: instantFlag("after", "Only actions created after this ISO 8601 instant"),
@@ -71943,8 +72024,8 @@ var actionsList = make29("list", {
71943
72024
  });
71944
72025
  })));
71945
72026
  var actionDecision = (verb) => make29(verb, {
72027
+ mailbox: mailboxIdArgument,
71946
72028
  actionId: String10("action-id").pipe(withSchema3(MailboxActionId)),
71947
- mailbox: mailboxFlag,
71948
72029
  ...verb === "deny" ? {
71949
72030
  reason: optional6("reason").pipe(withDescription2("Denial reason; defaults to Denied by reviewer."))
71950
72031
  } : {},
@@ -71999,7 +72080,7 @@ var remove9 = make29("remove", {
71999
72080
  yield* log(`Removed mailbox ${id}. The messages it received stay with the organization.`);
72000
72081
  }).pipe(json ? provideService2(MinimumLogLevel2, "None") : identity)));
72001
72082
  var threads = make29("threads", {
72002
- mailbox: mailboxFlag,
72083
+ mailbox: mailboxIdArgument,
72003
72084
  ...pageFlags2,
72004
72085
  query: optional6("query"),
72005
72086
  folder: Literals3("folder", MailboxThreadFolder2.literals).pipe(optional4),
@@ -72010,37 +72091,35 @@ var threads = make29("threads", {
72010
72091
  before: instantFlag("before", "Only threads whose last activity is before this ISO 8601 instant"),
72011
72092
  json: jsonFlag
72012
72093
  }).pipe(withDescription3("Search and list mailbox threads"), withExamples([
72013
- { command: "samva mailboxes threads --mailbox mbx_1234567890123456 --query invoice --json" },
72094
+ { command: "samva mailboxes threads mbx_1234567890123456 --query invoice --json" },
72014
72095
  {
72015
- command: "samva mailboxes threads --mailbox mbx_1234567890123456 --starred --after 2026-10-01T00:00:00Z"
72096
+ command: "samva mailboxes threads mbx_1234567890123456 --starred --after 2026-10-01T00:00:00Z"
72016
72097
  },
72017
- { command: "samva mailboxes threads --mailbox mbx_1234567890123456 --all --jsonl" }
72018
- ]), withHandler(({ mailbox, limit, cursor, all, query, folder, label, unread, starred, after, before, json }) => gen2(function* () {
72019
- yield* followPages({
72020
- all,
72021
- json,
72022
- cursor,
72023
- page: (pageCursor) => getMailboxJson("Could not list threads", `/v1/mailboxes/${mailbox}/threads${searchParams({
72024
- cursor: pageCursor,
72025
- limit: pageLimit(limit),
72026
- q: getOrUndefined(query),
72027
- folder: getOrUndefined(folder),
72028
- label: getOrUndefined(label),
72029
- unread: unread ? "true" : undefined,
72030
- starred: starred ? "true" : undefined,
72031
- after: getOrUndefined(after),
72032
- before: getOrUndefined(before)
72033
- })}`).pipe(flatMap3(decodeThreadPage)),
72034
- empty: "No threads found.",
72035
- human: printThread
72036
- });
72098
+ { command: "samva mailboxes threads mbx_1234567890123456 --all --jsonl" }
72099
+ ]), withHandler(({ mailbox, limit, cursor, all, query, folder, label, unread, starred, after, before, json }) => followPages({
72100
+ all,
72101
+ json,
72102
+ cursor,
72103
+ page: (pageCursor) => getMailboxJson("Could not list threads", `/v1/mailboxes/${mailbox}/threads${searchParams({
72104
+ cursor: pageCursor,
72105
+ limit: pageLimit(limit),
72106
+ q: getOrUndefined(query),
72107
+ folder: getOrUndefined(folder),
72108
+ label: getOrUndefined(label),
72109
+ unread: unread ? "true" : undefined,
72110
+ starred: starred ? "true" : undefined,
72111
+ after: getOrUndefined(after),
72112
+ before: getOrUndefined(before)
72113
+ })}`).pipe(flatMap3(decodeThreadPage)),
72114
+ empty: "No threads found.",
72115
+ human: printThread
72037
72116
  })));
72038
72117
  var read2 = make29("read", {
72118
+ mailbox: mailboxIdArgument,
72039
72119
  messageId: messageIdArgument,
72040
- mailbox: mailboxFlag,
72041
72120
  json: jsonFlag
72042
72121
  }).pipe(withDescription3("Read a mailbox message"), withExamples([
72043
- { command: "samva mailboxes read msg_1234567890123456 --mailbox mbx_1234567890123456 --json" }
72122
+ { command: "samva mailboxes read mbx_1234567890123456 msg_1234567890123456 --json" }
72044
72123
  ]), withHandler(({ messageId, mailbox, json }) => gen2(function* () {
72045
72124
  const raw = yield* getMailboxJson("Could not read message", `/v1/mailboxes/${mailbox}/messages/${messageId}`);
72046
72125
  const data = yield* decodeMessage(raw);
@@ -72049,7 +72128,7 @@ var read2 = make29("read", {
72049
72128
  yield* printMessage2(data);
72050
72129
  })));
72051
72130
  var send7 = make29("send", {
72052
- mailbox: mailboxFlag,
72131
+ mailbox: mailboxIdArgument,
72053
72132
  channel: channelFlag2,
72054
72133
  to: String9("to").pipe(withDescription2("Recipient email address (repeatable), or one phone number in E.164 form"), atLeast2(1)),
72055
72134
  subject: String9("subject").pipe(withDescription2("Email only"), optional4),
@@ -72060,14 +72139,14 @@ var send7 = make29("send", {
72060
72139
  json: jsonFlag
72061
72140
  }).pipe(withDescription3("Send a message from a mailbox: email by default, or to one phone number with --channel whatsapp or sms"), withExamples([
72062
72141
  {
72063
- command: 'samva mailboxes send --mailbox mbx_1234567890123456 --to recipient@example.com --subject Hello --text "Hello"'
72142
+ command: 'samva mailboxes send mbx_1234567890123456 --to recipient@example.com --subject Hello --text "Hello"'
72064
72143
  },
72065
72144
  {
72066
- command: `samva mailboxes send --mailbox mbx_1234567890123456 --channel whatsapp --to +14155550123 --template tmpl_1234567890123456 --data '{"name":"Ada"}'`,
72145
+ command: `samva mailboxes send mbx_1234567890123456 --channel whatsapp --to +14155550123 --template tmpl_1234567890123456 --data '{"name":"Ada"}'`,
72067
72146
  description: "Open a WhatsApp conversation with an approved template"
72068
72147
  },
72069
72148
  {
72070
- command: 'samva mailboxes send --mailbox mbx_1234567890123456 --channel sms --to +14155550123 --text "Your order shipped."'
72149
+ command: 'samva mailboxes send mbx_1234567890123456 --channel sms --to +14155550123 --text "Your order shipped."'
72071
72150
  }
72072
72151
  ]), withHandler(({
72073
72152
  mailbox,
@@ -72119,18 +72198,18 @@ var send7 = make29("send", {
72119
72198
  })));
72120
72199
  var messageChannel = (mailbox, messageId) => getMailboxJson("Could not read message", `/v1/mailboxes/${mailbox}/messages/${messageId}`).pipe(flatMap3((raw) => decodeUnknownEffect2(Struct({ channel: Literals(["email", "whatsapp", "sms"]) }))(raw).pipe(map7(({ channel }) => channel), mapError2(() => new SamvaCliError({ message: "The API answered with an unreadable message." })))));
72121
72200
  var reply2 = make29("reply", {
72201
+ mailbox: mailboxIdArgument,
72122
72202
  messageId: messageIdArgument,
72123
- mailbox: mailboxFlag,
72124
72203
  ...bodyFlags,
72125
72204
  ...chatOnlyFlags,
72126
72205
  all: Boolean6("all").pipe(withDefault5(false)),
72127
72206
  json: jsonFlag
72128
72207
  }).pipe(withDescription3("Reply to a message; --all includes the other recipients. A WhatsApp or SMS message is answered on its thread, with --text or a WhatsApp --template"), withExamples([
72129
72208
  {
72130
- command: 'samva mailboxes reply msg_1234567890123456 --mailbox mbx_1234567890123456 --text "Thanks" --all'
72209
+ command: 'samva mailboxes reply mbx_1234567890123456 msg_1234567890123456 --text "Thanks" --all'
72131
72210
  },
72132
72211
  {
72133
- command: "samva mailboxes reply msg_1234567890123456 --mailbox mbx_1234567890123456 --template tmpl_1234567890123456",
72212
+ command: "samva mailboxes reply mbx_1234567890123456 msg_1234567890123456 --template tmpl_1234567890123456",
72134
72213
  description: "Answer a WhatsApp thread whose 24-hour service window has closed"
72135
72214
  }
72136
72215
  ]), withHandler(({ messageId, mailbox, text, html, template, data, idempotencyKey, all, json }) => gen2(function* () {
@@ -72158,8 +72237,8 @@ var reply2 = make29("reply", {
72158
72237
  })));
72159
72238
  var actionIdArgument = String10("action-id").pipe(withSchema3(MailboxActionId));
72160
72239
  var actionGet = make29("get", {
72240
+ mailbox: mailboxIdArgument,
72161
72241
  actionId: actionIdArgument,
72162
- mailbox: mailboxFlag,
72163
72242
  json: jsonFlag
72164
72243
  }).pipe(withDescription3("Read a mailbox action and its current send progress"), withHandler(({ actionId, mailbox, json }) => gen2(function* () {
72165
72244
  const client = yield* getClientProvider;
@@ -72169,8 +72248,8 @@ var actionGet = make29("get", {
72169
72248
  yield* printAction(data);
72170
72249
  })));
72171
72250
  var actionCancel = make29("cancel", {
72251
+ mailbox: mailboxIdArgument,
72172
72252
  actionId: actionIdArgument,
72173
- mailbox: mailboxFlag,
72174
72253
  json: jsonFlag
72175
72254
  }).pipe(withDescription3("Cancel an unaccepted send as a permitted person signed in with samva login"), withHandler(({ actionId, mailbox, json }) => gen2(function* () {
72176
72255
  const client = yield* getClientProvider;
@@ -72184,8 +72263,8 @@ var actionCancel = make29("cancel", {
72184
72263
  ]);
72185
72264
  })));
72186
72265
  var actionRetry = make29("retry", {
72266
+ mailbox: mailboxIdArgument,
72187
72267
  actionId: actionIdArgument,
72188
- mailbox: mailboxFlag,
72189
72268
  json: jsonFlag
72190
72269
  }).pipe(withDescription3("Resume an eligible ended execution as a permitted person; preserves content and deadline"), withHandler(({ actionId, mailbox, json }) => gen2(function* () {
72191
72270
  const client = yield* getClientProvider;
@@ -72199,14 +72278,14 @@ var actionRetry = make29("retry", {
72199
72278
  ]);
72200
72279
  })));
72201
72280
  var quarantineList = make29("list", {
72202
- mailbox: mailboxFlag,
72281
+ mailbox: mailboxIdArgument,
72203
72282
  ...pageFlags2,
72204
72283
  after: instantFlag("after", "Only messages held after this ISO 8601 instant"),
72205
72284
  before: instantFlag("before", "Only messages held before this ISO 8601 instant"),
72206
72285
  json: jsonFlag
72207
72286
  }).pipe(withDescription3("List held messages without their bodies"), withExamples([
72208
- { command: "samva mailboxes quarantine list --mailbox mbx_1234567890123456 --json" },
72209
- { command: "samva mailboxes quarantine list --mailbox mbx_1234567890123456 --all --jsonl" }
72287
+ { command: "samva mailboxes quarantine list mbx_1234567890123456 --json" },
72288
+ { command: "samva mailboxes quarantine list mbx_1234567890123456 --all --jsonl" }
72210
72289
  ]), withHandler(({ mailbox, limit, cursor, all, after, before, json }) => gen2(function* () {
72211
72290
  const client = yield* getClientProvider;
72212
72291
  yield* followPages({
@@ -72225,12 +72304,12 @@ var quarantineList = make29("list", {
72225
72304
  });
72226
72305
  })));
72227
72306
  var release2 = make29("release", {
72307
+ mailbox: mailboxIdArgument,
72228
72308
  messageId: messageIdArgument,
72229
- mailbox: mailboxFlag,
72230
72309
  json: jsonFlag
72231
72310
  }).pipe(withDescription3("Release a held message as a person signed in with samva login"), withExamples([
72232
72311
  {
72233
- command: "samva mailboxes quarantine release msg_1234567890123456 --mailbox mbx_1234567890123456"
72312
+ command: "samva mailboxes quarantine release mbx_1234567890123456 msg_1234567890123456"
72234
72313
  }
72235
72314
  ]), withHandler(({ messageId, mailbox, json }) => gen2(function* () {
72236
72315
  const client = yield* getClientProvider;
@@ -72242,12 +72321,12 @@ var release2 = make29("release", {
72242
72321
  var discard = make29("discard", {
72243
72322
  dryRun: Boolean6("dry-run").pipe(withDefault5(false)),
72244
72323
  yes: Boolean6("yes").pipe(withDefault5(false)),
72324
+ mailbox: mailboxIdArgument,
72245
72325
  messageId: messageIdArgument,
72246
- mailbox: mailboxFlag,
72247
72326
  json: jsonFlag
72248
72327
  }).pipe(withDescription3("Discard a held message for good as a person signed in with samva login"), withExamples([
72249
72328
  {
72250
- command: "samva mailboxes quarantine discard msg_1234567890123456 --mailbox mbx_1234567890123456"
72329
+ command: "samva mailboxes quarantine discard mbx_1234567890123456 msg_1234567890123456"
72251
72330
  }
72252
72331
  ]), withHandler(({ messageId, mailbox, dryRun, yes, json }) => gen2(function* () {
72253
72332
  if (dryRun)
@@ -75659,7 +75738,7 @@ var TemplatesGroup = class extends make37("Templates").add(get5("templatesList",
75659
75738
  TemplatesPublish500,
75660
75739
  TemplatesPublish504.pipe(status(504))
75661
75740
  ]
75662
- }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.publish").annotate(Summary, "Publish an exact template commit").annotate(Description, "Builds an exact Git commit and entry path, stores the immutable compiled IR and its assets, records the input contract it validates against, and makes that publication current."), get5("templatesGetPublication", "/v1/templates/:id/publications/:publicationId", {
75741
+ }).middleware(ApiKeySecurityMiddleware).annotate(Identifier, "templates.publish").annotate(Summary, "Publish an exact template commit").annotate(Description, "Builds an exact Git commit and entry path, stores the immutable compiled IR and its assets, records the input contract it validates against, and makes that publication current. A commit that compiles to the same build as an existing publication makes that publication current again instead of recording another."), get5("templatesGetPublication", "/v1/templates/:id/publications/:publicationId", {
75663
75742
  params: TemplatesGetPublicationPathParams,
75664
75743
  success: TemplatesGetPublication200,
75665
75744
  error: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samva/cli",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Samva CLI — email and platform operations from your terminal",
5
5
  "keywords": [
6
6
  "cli",
@@ -62,10 +62,10 @@
62
62
  "vitest": "^5.0.3"
63
63
  },
64
64
  "optionalDependencies": {
65
- "@samva/cli-darwin-arm64": "0.16.0",
66
- "@samva/cli-darwin-x64": "0.16.0",
67
- "@samva/cli-linux-arm64": "0.16.0",
68
- "@samva/cli-linux-x64": "0.16.0"
65
+ "@samva/cli-darwin-arm64": "0.17.0",
66
+ "@samva/cli-darwin-x64": "0.17.0",
67
+ "@samva/cli-linux-arm64": "0.17.0",
68
+ "@samva/cli-linux-x64": "0.17.0"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=18"