primcli 3.0.0-preview.3 → 3.0.0-preview.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,13 @@
1
- import { $ as AutomatedFilterUnsupportedError, F as assertAwaitingFilterRows, G as WEBHOOK_EVENT_TYPES, I as awaitingRejectedError, Jt as getThread, L as ensureSearchReportsReplyState, Lt as getEmail, P as ReplyStateUnsupportedError, Pn as sdk_gen_exports, U as replyStateSurfaceForOperation, V as isAwaitingRejectedError, W as SENT_EMAIL_EVENT_TYPES, et as assertAutomatedVerdict, it as expectedAutomatedVerdicts, nt as automatedSurfaceForOperation, ot as isAutomatedRejectedError, rt as ensureSearchReportsAutomated, sn as listEndpoints, tt as automatedRejectedError, v as parseRetryAfter } from "./repeats-Dj_o6c-X-BKHiZsYv.js";
1
+ import { F as ReplyStateUnsupportedError, G as SENT_EMAIL_EVENT_TYPES, H as isAwaitingRejectedError, I as assertAwaitingFilterRows, K as WEBHOOK_EVENT_TYPES, L as awaitingRejectedError, Ln as sdk_gen_exports, R as ensureSearchReportsReplyState, Rt as getEmail, W as replyStateSurfaceForOperation, Yt as getThread, at as expectedAutomatedVerdicts, et as AutomatedFilterUnsupportedError, it as ensureSearchReportsAutomated, ln as listEndpoints, nt as automatedRejectedError, rt as automatedSurfaceForOperation, st as isAutomatedRejectedError, tt as assertAutomatedVerdict, v as parseRetryAfter } from "./repeats-Dj_o6c-X-BwGkVE7e.js";
2
2
  import { C as saveCliCredentials, F as agentProfilesDirectory, H as requireDefaultLoginProfile, N as agentProfileDirectory, R as loadConnectedAgentProfile, S as resolveCliAuth, W as PrimitiveApiClient, _ as deleteCliCredentials, b as loadCliCredentials, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, i as loadCliConfig, j as AGENT_PROFILE_ENV, m as cliAccessTokenExpiresAt, p as acquireCliCredentialsLock, w as saveSignupCredentials, x as normalizeApiBaseUrl, y as detectPrimitiveKeyEnvMisname } from "./cli-config-Ms_0vNjq.js";
3
3
  import "./oclif/global-org-flag.js";
4
4
  import { d as readMailJson, h as writeMailJson, m as withMailLock, p as syncMailDirectory, u as privateMailPermissions } from "./shared-mail-files-BAQSGxxz.js";
5
- import { Command, Errors, Flags } from "@oclif/core";
5
+ import { Args, Command, Errors, Flags } from "@oclif/core";
6
6
  import { existsSync, lstatSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
7
7
  import { createHash } from "node:crypto";
8
- import { join } from "node:path";
8
+ import { join, win32 } from "node:path";
9
+ import { spawn } from "node:child_process";
10
+ import { createInterface } from "node:readline/promises";
9
11
  // src/oclif/user-grant.ts
10
12
  /**
11
13
  * CLI user logins: a login as the person, not bound to one organization,
@@ -51,7 +53,7 @@ var CliOrgRefusedError = class extends Errors.CLIError {
51
53
  this.orgId = params.orgId;
52
54
  }
53
55
  };
54
- function isRecord(value) {
56
+ function isRecord$1(value) {
55
57
  return value !== null && typeof value === "object" && !Array.isArray(value);
56
58
  }
57
59
  function apiOrigin(apiBaseUrl) {
@@ -78,7 +80,7 @@ function isUuid(value) {
78
80
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
79
81
  }
80
82
  function isOrgAccessToken(value) {
81
- return isRecord(value) && typeof value.access_token === "string" && value.access_token.length > 0 && value.token_type === "Bearer" && typeof value.expires_in === "number" && value.expires_in > 0 && typeof value.org_id === "string" && (value.org_name === null || typeof value.org_name === "string") && typeof value.oauth_grant_id === "string";
83
+ return isRecord$1(value) && typeof value.access_token === "string" && value.access_token.length > 0 && value.token_type === "Bearer" && typeof value.expires_in === "number" && value.expires_in > 0 && typeof value.org_id === "string" && (value.org_name === null || typeof value.org_name === "string") && typeof value.oauth_grant_id === "string";
82
84
  }
83
85
  function orgTokenFromResponse(token, now = Date.now) {
84
86
  return {
@@ -125,7 +127,7 @@ async function postTokenEndpoint(params) {
125
127
  throw new Errors.CLIError(`Could not reach the Primitive OAuth token endpoint: ${detail}`, { exit: 1 });
126
128
  }
127
129
  const payload = await response.json().catch(() => null);
128
- if (response.ok && isRecord(payload)) return {
130
+ if (response.ok && isRecord$1(payload)) return {
129
131
  ok: true,
130
132
  payload
131
133
  };
@@ -133,7 +135,7 @@ async function postTokenEndpoint(params) {
133
135
  ok: true,
134
136
  payload: {}
135
137
  };
136
- const record = isRecord(payload) ? payload : {};
138
+ const record = isRecord$1(payload) ? payload : {};
137
139
  return {
138
140
  ok: false,
139
141
  status: response.status,
@@ -336,12 +338,12 @@ async function fetchCliOrgs(params) {
336
338
  }
337
339
  const payload = await response.json().catch(() => null);
338
340
  if (!response.ok) {
339
- const error = isRecord(payload) && isRecord(payload.error) ? payload.error : {};
341
+ const error = isRecord$1(payload) && isRecord$1(payload.error) ? payload.error : {};
340
342
  if (response.status === 404 && error.code === "feature_disabled") throw new Errors.CLIError("This Primitive server does not support switching organizations for CLI logins.", { exit: 1 });
341
343
  throw new Errors.CLIError(`Could not list organizations${typeof error.message === "string" ? `: ${error.message}` : ` (HTTP ${response.status}).`}`, { exit: 1 });
342
344
  }
343
- const data = isRecord(payload) ? payload.data : null;
344
- if (!isRecord(data) || !Array.isArray(data.orgs)) throw new Errors.CLIError("Primitive API returned an unexpected organization list.", { exit: 1 });
345
+ const data = isRecord$1(payload) ? payload.data : null;
346
+ if (!isRecord$1(data) || !Array.isArray(data.orgs)) throw new Errors.CLIError("Primitive API returned an unexpected organization list.", { exit: 1 });
345
347
  return data;
346
348
  }
347
349
  /** How long a command waits for another CLI process to finish with the saved login. */
@@ -914,6 +916,470 @@ async function withAttachmentWait(call, options) {
914
916
  }
915
917
  return result;
916
918
  }
919
+ // src/oclif/email-rule-authoring.ts
920
+ const EMAIL_ID = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
921
+ /** Whether `id` has the shape of an email id (a UUID). */
922
+ function isEmailId(id) {
923
+ return EMAIL_ID.test(id);
924
+ }
925
+ function usageError(message) {
926
+ return new Errors.CLIError(message, { exit: 2 });
927
+ }
928
+ function isRecord(value) {
929
+ return value !== null && typeof value === "object" && !Array.isArray(value);
930
+ }
931
+ function errorDetail(error) {
932
+ return error instanceof Error ? error.message : String(error);
933
+ }
934
+ function checkEmailId(id, label) {
935
+ if (!isEmailId(id)) throw usageError(`${label} takes email ids (UUIDs, from \`primitive emails list\`); "${id}" is not one.`);
936
+ return id;
937
+ }
938
+ /**
939
+ * The email ids in a repeatable, comma-separated flag, in the order given and
940
+ * without repeats.
941
+ */
942
+ function parseEmailIdList(values, flag) {
943
+ const ids = [];
944
+ const seen = /* @__PURE__ */ new Set();
945
+ for (const value of values) for (const raw of value.split(",")) {
946
+ const id = raw.trim();
947
+ if (id === "") continue;
948
+ checkEmailId(id, flag);
949
+ if (seen.has(id.toLowerCase())) continue;
950
+ seen.add(id.toLowerCase());
951
+ ids.push(id);
952
+ }
953
+ if (ids.length === 0) throw usageError(`${flag} needs at least one email id.`);
954
+ return ids;
955
+ }
956
+ function assertDisjoint(expectations, label) {
957
+ const match = new Set((expectations.match ?? []).map((id) => id.toLowerCase()));
958
+ const both = (expectations.no_match ?? []).find((id) => match.has(id.toLowerCase()));
959
+ if (both) throw usageError(`${label}: ${both} is expected both to match and not to match; give each email one way.`);
960
+ }
961
+ /**
962
+ * Check a JSON value in the shape of a rule's expectations or a test's
963
+ * `expect`: an object with `match` and `no_match`, each a list of email ids,
964
+ * at least one id in all, and no id in both. Repeats are removed.
965
+ */
966
+ function checkExpectations(value, label) {
967
+ if (!isRecord(value)) throw usageError(`${label} must hold a JSON object such as {"match": ["<email-id>"], "no_match": ["<email-id>"]}.`);
968
+ const expectations = {};
969
+ for (const [key, list] of Object.entries(value)) {
970
+ if (key !== "match" && key !== "no_match") throw usageError(`${label} takes only "match" and "no_match"; "${key}" is not one of them.`);
971
+ if (!Array.isArray(list) || list.some((id) => typeof id !== "string")) throw usageError(`${label}: "${key}" must be a list of email ids.`);
972
+ const ids = [];
973
+ const seen = /* @__PURE__ */ new Set();
974
+ for (const id of list) {
975
+ checkEmailId(id, label);
976
+ if (seen.has(id.toLowerCase())) continue;
977
+ seen.add(id.toLowerCase());
978
+ ids.push(id);
979
+ }
980
+ expectations[key] = ids;
981
+ }
982
+ if ((expectations.match?.length ?? 0) + (expectations.no_match?.length ?? 0) === 0) throw usageError(`${label} needs at least one email id in match or no_match.`);
983
+ assertDisjoint(expectations, label);
984
+ return expectations;
985
+ }
986
+ /** Read and parse a JSON file a flag names, failing like --body-file does. */
987
+ function readJsonFileFlag(path, flag) {
988
+ let text;
989
+ try {
990
+ text = readFileSync(path, "utf8");
991
+ } catch (error) {
992
+ throw new Errors.CLIError(`Could not read ${flag} ${path}: ${errorDetail(error)}`, { exit: 1 });
993
+ }
994
+ try {
995
+ return JSON.parse(text);
996
+ } catch (error) {
997
+ throw new Errors.CLIError(`${flag} ${path} is not valid JSON: ${errorDetail(error)}`, { exit: 1 });
998
+ }
999
+ }
1000
+ /** `expect` for test-email-rule, from --expect-file or the id flags. */
1001
+ function expectBodyFields(flags) {
1002
+ const file = flags["expect-file"];
1003
+ if (typeof file === "string") {
1004
+ const label = `--expect-file ${file}`;
1005
+ return [{
1006
+ flag: "--expect-file",
1007
+ property: "expect",
1008
+ value: checkExpectations(readJsonFileFlag(file, "--expect-file"), label)
1009
+ }];
1010
+ }
1011
+ const expect = {};
1012
+ const given = [];
1013
+ const match = flags["expect-match"];
1014
+ if (Array.isArray(match)) {
1015
+ expect.match = parseEmailIdList(match, "--expect-match");
1016
+ given.push("--expect-match");
1017
+ }
1018
+ const noMatch = flags["expect-no-match"];
1019
+ if (Array.isArray(noMatch)) {
1020
+ expect.no_match = parseEmailIdList(noMatch, "--expect-no-match");
1021
+ given.push("--expect-no-match");
1022
+ }
1023
+ if (given.length === 0) return [];
1024
+ assertDisjoint(expect, given.join(" and "));
1025
+ return [{
1026
+ flag: given.join(", "),
1027
+ property: "expect",
1028
+ value: expect
1029
+ }];
1030
+ }
1031
+ /**
1032
+ * `expectations` for create-email-rule and update-email-rule, from
1033
+ * --expectations-file. A file holding `null` removes the saved expectations,
1034
+ * which only update accepts.
1035
+ */
1036
+ function expectationsBodyFields(allowNull) {
1037
+ return (flags) => {
1038
+ const file = flags["expectations-file"];
1039
+ if (typeof file !== "string") return [];
1040
+ const value = readJsonFileFlag(file, "--expectations-file");
1041
+ if (value === null && !allowNull) throw usageError(`--expectations-file ${file} holds null, which only update-email-rule takes (to remove a rule's saved expectations). A new rule takes an object such as {"match": ["<email-id>"], "no_match": ["<email-id>"]}, or no --expectations-file.`);
1042
+ return [{
1043
+ flag: "--expectations-file",
1044
+ property: "expectations",
1045
+ value: value === null ? null : checkExpectations(value, `--expectations-file ${file}`)
1046
+ }];
1047
+ };
1048
+ }
1049
+ function confidence(value) {
1050
+ return value.toFixed(2);
1051
+ }
1052
+ /**
1053
+ * A fact or column value as rule conditions read it, with its confidence:
1054
+ * `true at 0.69`, or for a multi fact with per-value confidences
1055
+ * `[billing 0.91, invoice 0.62] at 0.80`.
1056
+ */
1057
+ function formatFactValue(fact) {
1058
+ const value = fact?.value;
1059
+ let text;
1060
+ if (Array.isArray(value)) {
1061
+ const per = isRecord(fact?.value_confidences) ? fact.value_confidences : void 0;
1062
+ text = `[${value.map((item) => {
1063
+ const name = String(item);
1064
+ const score = per?.[name];
1065
+ return typeof score === "number" ? `${name} ${confidence(score)}` : name;
1066
+ }).join(", ")}]`;
1067
+ } else if (value === null || value === void 0) text = "null";
1068
+ else if (typeof value === "object") text = JSON.stringify(value);
1069
+ else text = String(value);
1070
+ return typeof fact?.confidence === "number" ? `${text} at ${confidence(fact.confidence)}` : text;
1071
+ }
1072
+ /** One line for a fact: `is_cold_outreach: true at 0.69`. */
1073
+ function formatFact(name, fact) {
1074
+ return `${name}: ${formatFactValue(fact)}`;
1075
+ }
1076
+ function scopeWarningLine(warning) {
1077
+ const message = warning.message?.trim();
1078
+ if (!message) return `Scope warning: ${warning.name} (${warning.reason})`;
1079
+ return message.includes(warning.name) ? `Scope warning: ${message}` : `Scope warning: ${warning.name}: ${message}`;
1080
+ }
1081
+ function scopeWarningLines(warnings) {
1082
+ return Array.isArray(warnings) ? warnings.filter(isRecord).map(scopeWarningLine) : [];
1083
+ }
1084
+ const UNCLASSIFIED_REASONS = {
1085
+ pending: {
1086
+ why: "not classified yet",
1087
+ resolvable: true
1088
+ },
1089
+ classifier_unavailable: {
1090
+ why: "the classifier was unavailable",
1091
+ resolvable: true
1092
+ },
1093
+ classification_failed: {
1094
+ why: "its classification failed",
1095
+ resolvable: false,
1096
+ may: true
1097
+ },
1098
+ not_classifiable: {
1099
+ why: "it cannot be classified",
1100
+ resolvable: false
1101
+ }
1102
+ };
1103
+ function sameId(a, b) {
1104
+ return a.toLowerCase() === b.toLowerCase();
1105
+ }
1106
+ /**
1107
+ * Why an expectation is undecided, from its email's result, and whether a
1108
+ * later test can decide it: not for an email the rule cannot find or read, or
1109
+ * one that cannot be classified; yes for one not classified yet, or whose
1110
+ * question went unanswered.
1111
+ */
1112
+ function undecidedReason(expectation, results) {
1113
+ const result = results.find((entry) => sameId(entry.email_id, expectation.email_id));
1114
+ const outcome = result?.outcome ?? expectation.outcome;
1115
+ if (outcome === "not_found") return {
1116
+ why: "no such email the rule can read",
1117
+ resolvable: false
1118
+ };
1119
+ if (outcome === "unclassified") {
1120
+ const reason = result?.unclassified_reason;
1121
+ return (reason ? UNCLASSIFIED_REASONS[reason] : void 0) ?? {
1122
+ why: reason ? `unclassified (${reason})` : "unclassified",
1123
+ resolvable: false
1124
+ };
1125
+ }
1126
+ return {
1127
+ why: "a question went unanswered",
1128
+ resolvable: true
1129
+ };
1130
+ }
1131
+ function undecidedNote(reason) {
1132
+ return `${reason.why}; ${reason.resolvable ? "test again later" : reason.may ? "a later test may not decide it" : "testing again will not decide it"}`;
1133
+ }
1134
+ function answerText(answer) {
1135
+ const verdict = answer.answer === true ? "yes" : answer.answer === false ? "no" : "unanswered";
1136
+ const probability = typeof answer.probability === "number" ? ` at ${confidence(answer.probability)}` : "";
1137
+ const error = answer.error_class ? ` (${answer.error_class})` : "";
1138
+ return `"${answer.question}" ${verdict}${probability}${error}`;
1139
+ }
1140
+ function conditionLines(summary) {
1141
+ const conditions = Array.isArray(summary?.conditions) ? summary.conditions : [];
1142
+ if (conditions.length === 0) return [];
1143
+ return [`Conditions (${summary?.decided ?? 0} decided):`, ...conditions.map((condition) => ` ${condition.index}. ${condition.text}: held on ${condition.held}, failed on ${condition.failed} (the only failure on ${condition.only_failure})`)];
1144
+ }
1145
+ function questionLines(summary, results) {
1146
+ const questions = Array.isArray(summary?.questions) ? summary.questions : [];
1147
+ if (questions.length === 0) return [];
1148
+ return ["Questions (on emails whose conditions held):", ...questions.map((question) => {
1149
+ const text = results.find((result) => result.question_answers?.[question.index])?.question_answers?.[question.index]?.question;
1150
+ return ` ${question.index}. ${text ? `"${text}": ` : ""}yes ${question.yes}, no ${question.no}, unanswered ${question.unanswered}`;
1151
+ })];
1152
+ }
1153
+ function expectationLine(result, results) {
1154
+ const note = result.status === "undecided" ? undecidedNote(undecidedReason(result, results)) : void 0;
1155
+ return ` ${result.status}: ${result.email_id} expected ${result.expected}, got ${result.outcome}${note ? ` (${note})` : ""}`;
1156
+ }
1157
+ function expectationsLines(expectations, testResults) {
1158
+ if (!expectations) return [];
1159
+ const results = Array.isArray(expectations.results) ? expectations.results : [];
1160
+ const passed = expectations.counts?.passed ?? 0;
1161
+ const failed = expectations.counts?.failed ?? 0;
1162
+ const undecided = expectations.counts?.undecided ?? 0;
1163
+ if (expectations.passed && failed === 0 && undecided === 0) return [`Expectations: all ${passed} passed.`];
1164
+ return [`Expectations: ${[
1165
+ failed > 0 ? `${failed} failed` : "",
1166
+ undecided > 0 ? `${undecided} undecided` : "",
1167
+ `${passed} passed`
1168
+ ].filter((part) => part !== "").join(", ")}.`, ...results.filter((result) => result.status !== "passed").map((result) => expectationLine(result, testResults))];
1169
+ }
1170
+ const NOT_MATCHED_ORDER = {
1171
+ near_miss: 1,
1172
+ no_match: 2,
1173
+ unclassified: 3,
1174
+ not_found: 4
1175
+ };
1176
+ function notMatchedLine(result) {
1177
+ if (result.outcome === "unclassified") {
1178
+ const reason = result.unclassified_reason;
1179
+ const note = !reason ? "" : UNCLASSIFIED_REASONS[reason]?.resolvable ? `${reason}: test again later` : reason;
1180
+ return [` ${result.email_id} unclassified${note ? ` (${note})` : ""}`];
1181
+ }
1182
+ if (result.outcome === "not_found") return [` ${result.email_id} not_found`];
1183
+ const failedParts = (result.parts ?? []).filter((part) => !part.matched).map((part) => part.index);
1184
+ const values = Object.entries(result.values ?? {}).map(([column, value]) => formatFact(column, value));
1185
+ const missing = result.missing_facts ?? [];
1186
+ if (missing.length > 0) values.push(`missing: ${missing.join(", ")}`);
1187
+ const head = ` ${result.email_id} ${result.outcome}${failedParts.length > 0 ? ` (failed part ${failedParts.join(", ")})` : ""}`;
1188
+ const lines = [values.length > 0 ? `${head}: ${values.join("; ")}` : head];
1189
+ const answers = result.question_answers ?? [];
1190
+ if (answers.length > 0) {
1191
+ const source = result.question_answers_source ? ` (${result.question_answers_source})` : "";
1192
+ lines.push(` questions${source}: ${answers.map(answerText).join("; ")}`);
1193
+ }
1194
+ return lines;
1195
+ }
1196
+ function notMatchedLines(results, expectations) {
1197
+ const flagged = new Set((expectations?.results ?? []).filter((result) => result.status !== "passed").map((result) => result.email_id));
1198
+ const rank = (result) => flagged.has(result.email_id) ? 0 : NOT_MATCHED_ORDER[result.outcome] ?? 5;
1199
+ const notMatched = results.filter((result) => result.outcome !== "matched").map((result, position) => ({
1200
+ result,
1201
+ position
1202
+ })).sort((a, b) => rank(a.result) - rank(b.result) || a.position - b.position).map(({ result }) => result);
1203
+ if (notMatched.length === 0) return [];
1204
+ const shown = notMatched.slice(0, 25);
1205
+ return [shown.length < notMatched.length ? `Not matched (${shown.length} of ${notMatched.length} shown; the JSON has every result):` : `Not matched (${notMatched.length}):`, ...shown.flatMap(notMatchedLine)];
1206
+ }
1207
+ /**
1208
+ * The exit code a test's expectations call for, and the error a --json
1209
+ * document carries with it:
1210
+ * - 1 and `expectations_failed` when one failed;
1211
+ * - else 1 and `expectations_unresolvable` when one is undecided and a later
1212
+ * test cannot decide it (its email cannot be found or read, or cannot be
1213
+ * classified);
1214
+ * - else 3 and `expectations_undecided` when one is undecided and a later
1215
+ * test can decide it;
1216
+ * - else nothing: the exit code stays 0.
1217
+ */
1218
+ function expectationsOutcome(expectations, testResults = []) {
1219
+ if (!expectations) return {};
1220
+ const results = Array.isArray(expectations.results) ? expectations.results : [];
1221
+ const failed = results.filter((result) => result.status === "failed").map((result) => result.email_id);
1222
+ const undecided = results.filter((result) => result.status === "undecided").map((result) => ({
1223
+ id: result.email_id,
1224
+ reason: undecidedReason(result, testResults)
1225
+ }));
1226
+ const unresolvable = undecided.filter(({ reason }) => !reason.resolvable);
1227
+ const failedCount = Math.max(expectations.counts?.failed ?? 0, failed.length);
1228
+ const undecidedCount = Math.max(expectations.counts?.undecided ?? 0, undecided.length);
1229
+ const plural = (count, one, many) => `${count} ${count === 1 ? one : many}`;
1230
+ const why = (list) => list.map(({ id, reason }) => `${id} (${reason.why})`).join(", ");
1231
+ if (failedCount > 0) return {
1232
+ exitCode: 1,
1233
+ error: {
1234
+ code: "expectations_failed",
1235
+ message: `${plural(failedCount, "expectation", "expectations")} failed${failed.length > 0 ? `: ${failed.join(", ")}` : ""}.`
1236
+ }
1237
+ };
1238
+ if (unresolvable.length > 0) return {
1239
+ exitCode: 1,
1240
+ error: {
1241
+ code: "expectations_unresolvable",
1242
+ message: `${plural(unresolvable.length, "expectation is", "expectations are")} undecided, and testing again will not decide ${unresolvable.length === 1 ? "it" : "them"}: ${why(unresolvable)}.`
1243
+ }
1244
+ };
1245
+ if (undecidedCount > 0) return {
1246
+ exitCode: 3,
1247
+ error: {
1248
+ code: "expectations_undecided",
1249
+ message: `${plural(undecidedCount, "expectation is", "expectations are")} undecided${undecided.length > 0 ? `: ${why(undecided)}` : ""}. Test again later.`
1250
+ }
1251
+ };
1252
+ return {};
1253
+ }
1254
+ /** The stderr report and exit code of a test-email-rule answer. */
1255
+ function testEmailRuleReport(data) {
1256
+ if (!isRecord(data)) return { lines: [] };
1257
+ const result = data;
1258
+ const results = Array.isArray(result.results) ? result.results : [];
1259
+ const summary = isRecord(result.summary) ? result.summary : void 0;
1260
+ return {
1261
+ lines: [
1262
+ ...(Array.isArray(summary?.warnings) ? summary.warnings : []).map((warning) => `Warning: ${warning}`),
1263
+ ...scopeWarningLines(result.scope_warnings),
1264
+ ...conditionLines(summary),
1265
+ ...questionLines(summary, results),
1266
+ ...expectationsLines(result.expectations, results),
1267
+ ...notMatchedLines(results, result.expectations)
1268
+ ],
1269
+ ...expectationsOutcome(result.expectations, results)
1270
+ };
1271
+ }
1272
+ function expectationsCheckLine(check, ruleId) {
1273
+ if (!isRecord(check)) return void 0;
1274
+ const on = typeof check.version === "number" ? `version ${check.version}` : "the current version";
1275
+ const failed = Array.isArray(check.failed) ? check.failed : [];
1276
+ const undecided = Array.isArray(check.undecided) ? check.undecided : [];
1277
+ if (check.passed && failed.length === 0 && undecided.length === 0) return `Expectations on ${on}: all passed.`;
1278
+ const parts = [];
1279
+ if (failed.length > 0) parts.push(`${failed.length} failed (${failed.join(", ")})`);
1280
+ if (undecided.length > 0) parts.push(`${undecided.length} undecided (${undecided.join(", ")})`);
1281
+ const rest = undecided.length > 0 ? ` To decide the rest, run \`primitive email-rules test-email-rule --rule-id ${ruleId}\`.` : "";
1282
+ return `Expectations on ${on}: ${parts.length > 0 ? parts.join(", ") : "not passed"}.${rest}`;
1283
+ }
1284
+ /** The stderr report of a rule written by create-email-rule or update-email-rule. */
1285
+ function writtenEmailRuleReport(data, request) {
1286
+ if (!isRecord(data)) return { lines: [] };
1287
+ const rule = data;
1288
+ const ruleId = typeof rule.id === "string" ? rule.id : "<rule-id>";
1289
+ const lines = scopeWarningLines(rule.scope_warnings);
1290
+ const checked = expectationsCheckLine(rule.expectations_check, ruleId);
1291
+ if (checked) lines.push(checked);
1292
+ else if (isRecord(request.body) && isRecord(request.body.expectations)) lines.push(`Expectations were not checked on this write. Test them with \`primitive email-rules test-email-rule --rule-id ${ruleId}\`.`);
1293
+ return { lines };
1294
+ }
1295
+ /** The stderr report of get-email-rule-scope. */
1296
+ function emailRuleScopeReport(data) {
1297
+ if (!isRecord(data)) return { lines: [] };
1298
+ const scope = data;
1299
+ const lines = scopeWarningLines(scope.scope_warnings);
1300
+ if (scope.reads_nothing === true) {
1301
+ const stopped = typeof scope.stopped_reason === "string" && scope.stopped_reason ? ` Stopped: ${scope.stopped_reason}.` : "";
1302
+ lines.push(`This rule reads no mail now: it cannot run as its author, or everything its scope names is at none of the organization's domains or no longer read.${stopped}`);
1303
+ }
1304
+ return { lines };
1305
+ }
1306
+ function errorDetails(payload) {
1307
+ if (!isRecord(payload)) return {};
1308
+ if (isRecord(payload.details)) return payload.details;
1309
+ if (isRecord(payload.error) && isRecord(payload.error.details)) return payload.error.details;
1310
+ return {};
1311
+ }
1312
+ /**
1313
+ * The hint for update-email-rule's 409 `email_rule_conflict` with
1314
+ * `details.reason` `version_moved`: the rule's version is no longer the
1315
+ * if_version that was sent, so nothing was saved.
1316
+ */
1317
+ function versionMovedHint(payload, ruleId) {
1318
+ const details = errorDetails(payload);
1319
+ if (details.reason !== "version_moved") return void 0;
1320
+ const id = typeof ruleId === "string" && ruleId ? ruleId : "<rule-id>";
1321
+ const current = typeof details.current_version === "number" ? details.current_version : void 0;
1322
+ const read = `Read it again with \`primitive email-rules get-email-rule ${id}\` and reapply your change to what it shows`;
1323
+ return current === void 0 ? `Hint: the rule's version is not the if_version you sent, so nothing was saved, and the answer did not give its current version. ${read}.` : `Hint: the rule's current version is ${current}, not the if_version you sent, so nothing was saved. ${read}, then retry with --if-version ${current}.`;
1324
+ }
1325
+ const EXPECTATION_FLAG_NOTE = "Replaces any expect in --body-file or --raw-body. The exit code follows the answer's expectations; see --help-full.";
1326
+ const EMAIL_RULE_OPERATION_EXTENSIONS = {
1327
+ testEmailRule: {
1328
+ flags: {
1329
+ "expect-match": Flags.string({
1330
+ description: `An email the rule must match, by id: repeat the flag or give a comma list. Sets expect.match. ${EXPECTATION_FLAG_NOTE}`,
1331
+ multiple: true,
1332
+ exclusive: ["expect-file"]
1333
+ }),
1334
+ "expect-no-match": Flags.string({
1335
+ description: `An email the rule must not match, by id: repeat the flag or give a comma list. Sets expect.no_match. ${EXPECTATION_FLAG_NOTE}`,
1336
+ multiple: true,
1337
+ exclusive: ["expect-file"]
1338
+ }),
1339
+ "expect-file": Flags.string({
1340
+ description: "A JSON file holding the request's expect, such as {\"match\": [\"<email-id>\"], \"no_match\": [\"<email-id>\"]}. Not with --expect-match or --expect-no-match.",
1341
+ exclusive: ["expect-match", "expect-no-match"]
1342
+ })
1343
+ },
1344
+ bodyFields: expectBodyFields,
1345
+ bodyProperties: ["expect"],
1346
+ fallbackExitCode: (data) => {
1347
+ const expectations = data?.expectations;
1348
+ return expectations && expectations.passed !== true ? 1 : void 0;
1349
+ },
1350
+ report: testEmailRuleReport
1351
+ },
1352
+ createEmailRule: {
1353
+ flags: { "expectations-file": Flags.string({ description: "A JSON file holding the emails the rule must match and must not, such as {\"match\": [\"<email-id>\"], \"no_match\": [\"<email-id>\"]}. Sets expectations, over any in --body-file or --raw-body; the answer judges them in expectations_check." }) },
1354
+ bodyFields: expectationsBodyFields(false),
1355
+ bodyProperties: ["expectations"],
1356
+ report: writtenEmailRuleReport
1357
+ },
1358
+ updateEmailRule: {
1359
+ flags: { "expectations-file": Flags.string({ description: "A JSON file holding the emails the rule must match and must not, such as {\"match\": [\"<email-id>\"], \"no_match\": [\"<email-id>\"]}, replacing its saved expectations; a file holding null removes them. Sets expectations, over any in --body-file or --raw-body; the answer judges them in expectations_check." }) },
1360
+ bodyFields: expectationsBodyFields(true),
1361
+ bodyProperties: ["expectations"],
1362
+ report: writtenEmailRuleReport
1363
+ },
1364
+ getEmailRuleScope: {
1365
+ flags: {},
1366
+ report: emailRuleScopeReport
1367
+ }
1368
+ };
1369
+ /** The classification in a listEmailRuleResults answer, or null. */
1370
+ function classificationOf(data) {
1371
+ if (!isRecord(data) || !isRecord(data.classification)) return null;
1372
+ return data.classification;
1373
+ }
1374
+ /** One line per fact: name, value(s) and confidence. */
1375
+ function classificationFactLines(classification) {
1376
+ return Object.entries(isRecord(classification.facts) ? classification.facts : {}).map(([name, fact]) => formatFact(name, isRecord(fact) ? fact : {}));
1377
+ }
1378
+ /** Why there are no facts to print for an email. */
1379
+ function noFactsMessage(data) {
1380
+ if (classificationOf(data)) return "This email's classification has no facts.";
1381
+ return (isRecord(data) && Array.isArray(data.rules) ? data.rules : []).some((rule) => isRecord(rule) && isRecord(rule.decision) && rule.decision.detail_withheld === true) ? "No classification to show: this email's content cannot be read (withheld, failed to parse or discarded)." : "No classification for this email yet. It has not been classified (recent mail can take a while, so try again later), it will not be, or its content cannot be read.";
1382
+ }
917
1383
  // src/oclif/endpoint-event-types.ts
918
1384
  const EVENT_TYPES_FLAG_OPERATIONS = /* @__PURE__ */ new Set(["createEndpoint", "updateEndpoint"]);
919
1385
  const EVENT_TYPES_FLAG_DESCRIPTION = "Event types this endpoint subscribes to, comma separated or repeated (sets rules.event_types). Omitting the subscription means every event type except the opt-in sent_email.* events, which an endpoint receives only when it lists them; sent_email.* expands to all four. Keep email.received in the list if the endpoint also handles inbound mail. On update, the endpoint's other rules are kept.";
@@ -2735,7 +3201,7 @@ function savedConnectedProfiles(configDir, exclude) {
2735
3201
  */
2736
3202
  async function otherProfiles(configDir, session, current) {
2737
3203
  if (session) try {
2738
- const { connectedProfilesForSession } = await import("./machine-session-BG4getHB.js").then((n) => n.s);
3204
+ const { connectedProfilesForSession } = await import("./machine-session-DD35Pfgd.js").then((n) => n.s);
2739
3205
  return {
2740
3206
  inSession: true,
2741
3207
  profiles: connectedProfilesForSession(configDir, session, current)
@@ -2842,6 +3308,256 @@ function sentListOtherSendersNote(rows, self) {
2842
3308
  const named = senders.length > 3 ? `${senders.slice(0, 3).join(", ")} and ${senders.length - 3} more` : senders.join(", ");
2843
3309
  return `${count} of ${rows.length} rows were sent by another address in this organization (${named}) to ${own}; they are mail ${own} received, not its own sends. Rows with from_address ${own} are this address's sends; pass --from ${own} to list only those.`;
2844
3310
  }
3311
+ // src/oclif/browser-login-prompt.ts
3312
+ const { join: join$1 } = win32;
3313
+ /**
3314
+ * The program and arguments that open `url` in the default browser. On
3315
+ * Windows the link goes to the URL protocol handler directly: through `cmd
3316
+ * /c start` an unquoted `&` would end the command, cutting a link such as an
3317
+ * approval page's `?purpose=...&client=...` short.
3318
+ */
3319
+ function browserCommand(url, platform = process.platform) {
3320
+ if (platform === "darwin") return {
3321
+ command: "open",
3322
+ args: [url]
3323
+ };
3324
+ if (platform === "win32") return {
3325
+ command: join$1(process.env.SystemRoot ?? "C:\\Windows", "System32", "rundll32.exe"),
3326
+ args: ["url.dll,FileProtocolHandler", url]
3327
+ };
3328
+ return {
3329
+ command: "xdg-open",
3330
+ args: [url]
3331
+ };
3332
+ }
3333
+ /** The link as an http or https URL, or null for anything else (a path, file:, a share). */
3334
+ function browsableUrl(url) {
3335
+ try {
3336
+ const parsed = new URL(url);
3337
+ return parsed.protocol === "https:" || parsed.protocol === "http:" ? parsed.href : null;
3338
+ } catch {
3339
+ return null;
3340
+ }
3341
+ }
3342
+ /** Open an http or https link in the default browser; anything else is left to the printed link. */
3343
+ function openBrowser(url) {
3344
+ const link = browsableUrl(url);
3345
+ if (!link) return;
3346
+ const { command, args } = browserCommand(link);
3347
+ const child = spawn(command, args, {
3348
+ detached: true,
3349
+ stdio: "ignore"
3350
+ });
3351
+ child.on("error", () => void 0);
3352
+ child.unref();
3353
+ }
3354
+ /**
3355
+ * The copy-URL hotkey needs raw keypresses from a terminal and a terminal
3356
+ * to show the hint on. Anything else (CI, agents, piped output) gets the
3357
+ * plain URL and never blocks on input.
3358
+ */
3359
+ function canOfferCopyHotkey(input, output) {
3360
+ return Boolean(input.isTTY && output.isTTY && typeof input.setRawMode === "function");
3361
+ }
3362
+ function formatBrowserLoginInstructions(params) {
3363
+ const { copyHotkey, openedBrowser, start } = params;
3364
+ const copyHint = copyHotkey ? " (c to copy)" : "";
3365
+ const lines = [`Your sign-in code is: ${start.user_code}`];
3366
+ if (openedBrowser) lines.push("Opening Primitive in your browser...", "", `Browser didn't open, or want a different one? Use this URL${copyHint}:`);
3367
+ else lines.push("", `Open this URL in any browser to sign in${copyHint}:`);
3368
+ lines.push(` ${start.verification_uri_complete}`);
3369
+ if (start.verification_uri !== start.verification_uri_complete) lines.push(`On another device, go to ${start.verification_uri} and enter ${start.user_code}.`);
3370
+ lines.push("");
3371
+ return `${lines.join("\n")}\n`;
3372
+ }
3373
+ /**
3374
+ * OSC 52 asks the terminal emulator itself to set the clipboard, so it
3375
+ * works over SSH, where a local clipboard command would copy on the
3376
+ * remote host instead of the machine the user is sitting at.
3377
+ */
3378
+ function osc52Sequence(text) {
3379
+ return `\u001b]52;c;${Buffer.from(text, "utf8").toString("base64")}\u0007`;
3380
+ }
3381
+ function nativeClipboardCommand() {
3382
+ if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return null;
3383
+ if (process.platform === "darwin") return ["pbcopy", []];
3384
+ if (process.platform === "win32") return ["clip", []];
3385
+ if (process.env.WAYLAND_DISPLAY) return ["wl-copy", []];
3386
+ if (process.env.DISPLAY) return ["xclip", ["-selection", "clipboard"]];
3387
+ return null;
3388
+ }
3389
+ /**
3390
+ * Resolves false on any failure, including a tool that exits before
3391
+ * reading its input (EPIPE on stdin), so the caller falls back to OSC 52
3392
+ * instead of the error taking down login.
3393
+ */
3394
+ function runClipboardCommand(command, args, text) {
3395
+ return new Promise((resolve) => {
3396
+ let inputFailed = false;
3397
+ const child = spawn(command, args, { stdio: [
3398
+ "pipe",
3399
+ "ignore",
3400
+ "ignore"
3401
+ ] });
3402
+ child.on("error", () => resolve(false));
3403
+ child.stdin.on("error", () => {
3404
+ inputFailed = true;
3405
+ });
3406
+ child.on("close", (code) => resolve(code === 0 && !inputFailed));
3407
+ child.stdin.end(text);
3408
+ });
3409
+ }
3410
+ function copyWithNativeClipboard(text) {
3411
+ const command = nativeClipboardCommand();
3412
+ if (!command) return Promise.resolve(false);
3413
+ return runClipboardCommand(command[0], command[1], text);
3414
+ }
3415
+ /**
3416
+ * Copies via the native clipboard when running locally and always emits
3417
+ * OSC 52 as well. Terminals without OSC 52 support (macOS Terminal.app)
3418
+ * are covered by the native path; SSH sessions are covered by OSC 52.
3419
+ */
3420
+ async function copyToClipboard(text, output, native = copyWithNativeClipboard) {
3421
+ output.write(osc52Sequence(text));
3422
+ return await native(text) ? "native" : "terminal";
3423
+ }
3424
+ const CTRL_C = "";
3425
+ const CTRL_Z = "";
3426
+ /**
3427
+ * Listens for `c` while the CLI polls for browser approval. Raw mode turns
3428
+ * Ctrl+C and Ctrl+Z into plain input, so they are handled here: Ctrl+C is
3429
+ * re-raised as SIGINT, and Ctrl+Z restores the terminal, suspends with
3430
+ * SIGTSTP, and re-enters raw mode on SIGCONT. Several keys can arrive in
3431
+ * one chunk (pastes, SSH), so every character is checked. Returns a
3432
+ * disposer that restores the terminal.
3433
+ */
3434
+ function listenForCopyHotkey(params) {
3435
+ const { input, output, url } = params;
3436
+ const copy = params.copy ?? copyToClipboard;
3437
+ let disposed = false;
3438
+ const copyUrl = () => {
3439
+ copy(url, output).then((how) => {
3440
+ if (disposed) return;
3441
+ output.write(how === "native" ? "Copied the sign-in URL to your clipboard.\n" : "Sent the sign-in URL to your terminal's clipboard. If nothing was copied, select the URL above instead.\n");
3442
+ });
3443
+ };
3444
+ const onContinue = () => {
3445
+ if (!disposed) input.setRawMode?.(true);
3446
+ };
3447
+ const suspend = () => {
3448
+ input.setRawMode?.(false);
3449
+ process.once("SIGCONT", onContinue);
3450
+ process.kill(process.pid, "SIGTSTP");
3451
+ };
3452
+ const onData = (chunk) => {
3453
+ let wantsCopy = false;
3454
+ for (const key of chunk.toString()) {
3455
+ if (key === CTRL_C) {
3456
+ dispose();
3457
+ process.kill(process.pid, "SIGINT");
3458
+ return;
3459
+ }
3460
+ if (key === CTRL_Z && process.platform !== "win32") {
3461
+ if (wantsCopy) copyUrl();
3462
+ suspend();
3463
+ return;
3464
+ }
3465
+ if (key === "c" || key === "C") wantsCopy = true;
3466
+ }
3467
+ if (wantsCopy) copyUrl();
3468
+ };
3469
+ const dispose = () => {
3470
+ if (disposed) return;
3471
+ disposed = true;
3472
+ input.off("data", onData);
3473
+ process.off("SIGCONT", onContinue);
3474
+ input.setRawMode?.(false);
3475
+ input.pause();
3476
+ };
3477
+ input.setRawMode?.(true);
3478
+ input.on("data", onData);
3479
+ input.resume();
3480
+ return dispose;
3481
+ }
3482
+ // src/oclif/step-up-approval.ts
3483
+ const STEP_UP_HEADER = "Primitive-Step-Up";
3484
+ const CONTROL_CHARACTERS = new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}]`);
3485
+ const STEP_UP_TOKEN_FLAG_DESCRIPTION = "One-time token from the approval page, for a request you already approved in the browser. Sent in the Primitive-Step-Up header.";
3486
+ const BROWSER_FLAG_DESCRIPTION = "Open the approval page in your browser. Use --no-browser to only print its link.";
3487
+ /** The error code of a refusal, whether or not it is still in its envelope. */
3488
+ function errorCode(payload) {
3489
+ if (!payload || typeof payload !== "object") return void 0;
3490
+ const inner = payload.error;
3491
+ if (inner && typeof inner === "object" && typeof inner.code === "string") return inner.code;
3492
+ const code = payload.code;
3493
+ return typeof code === "string" ? code : void 0;
3494
+ }
3495
+ /** The approval link of a `step_up_required` refusal, if that is what it is. */
3496
+ function stepUpUrlOf(payload) {
3497
+ if (errorCode(payload) !== "step_up_required") return void 0;
3498
+ if (!payload || typeof payload !== "object") return void 0;
3499
+ const details = payload.details;
3500
+ if (!details || typeof details !== "object") return void 0;
3501
+ const url = details.step_up_url;
3502
+ return typeof url === "string" && url.length > 0 ? url : void 0;
3503
+ }
3504
+ /**
3505
+ * Send a request, and when the server asks the person to approve it, show
3506
+ * the approval link, open it where possible, wait for the token the page
3507
+ * shows, and send the request once more with it. Without a terminal to wait
3508
+ * in, the refusal is returned with its link, to rerun with --step-up-token.
3509
+ */
3510
+ async function withApproval(attempt, io) {
3511
+ const first = await attempt(io.stepUpToken);
3512
+ if (first.ok) return first;
3513
+ const stepUpUrl = stepUpUrlOf(first.payload);
3514
+ if (!stepUpUrl || io.stepUpToken || !io.interactive) return {
3515
+ ...first,
3516
+ ...stepUpUrl ? { stepUpUrl } : {}
3517
+ };
3518
+ io.write(`Approve this request in your browser: ${stepUpUrl}\n`);
3519
+ if (io.openBrowser) io.open(stepUpUrl);
3520
+ io.write("After you confirm, the page shows a one-time token. Paste it here to finish.\n");
3521
+ const token = (await io.prompt("Token: ")).trim();
3522
+ if (!token) return {
3523
+ ...first,
3524
+ stepUpUrl
3525
+ };
3526
+ if (CONTROL_CHARACTERS.test(token)) {
3527
+ io.write("That token holds a line break or another control character, so it cannot be sent. Copy it again from the page.\n");
3528
+ return {
3529
+ ...first,
3530
+ stepUpUrl
3531
+ };
3532
+ }
3533
+ return attempt(token);
3534
+ }
3535
+ /** The line to print when a refusal asks for an approval the command could not wait for. */
3536
+ function approveThenRerunHint(stepUpUrl) {
3537
+ return `Approve this request in your browser: ${stepUpUrl}\nThen run the same command again with --step-up-token <token from the page>.\n`;
3538
+ }
3539
+ async function promptLine(question) {
3540
+ const rl = createInterface({
3541
+ input: process.stdin,
3542
+ output: process.stderr
3543
+ });
3544
+ try {
3545
+ return await rl.question(question);
3546
+ } finally {
3547
+ rl.close();
3548
+ }
3549
+ }
3550
+ /** Approval in the terminal the command runs in: never under --json, and only with a terminal on both ends. */
3551
+ function terminalApprovalIo(options) {
3552
+ return {
3553
+ ...options.stepUpToken ? { stepUpToken: options.stepUpToken } : {},
3554
+ interactive: !options.json && Boolean(process.stdin.isTTY && process.stderr.isTTY),
3555
+ openBrowser: options.openBrowser,
3556
+ open: openBrowser,
3557
+ prompt: promptLine,
3558
+ write: (text) => process.stderr.write(text)
3559
+ };
3560
+ }
2845
3561
  // src/oclif/api-command.ts
2846
3562
  const API_ERROR_CODES = {
2847
3563
  accessDenied: "access_denied",
@@ -2896,6 +3612,11 @@ function collectHeaderFlags(parsedFlags, headerFlagToName) {
2896
3612
  function flagDescription(parameter) {
2897
3613
  return parameter.description ?? parameter.name;
2898
3614
  }
3615
+ const STEP_UP_OPERATIONS = /* @__PURE__ */ new Set([]);
3616
+ /** Whether an operation takes the browser approval token. */
3617
+ function takesStepUp(operation) {
3618
+ return STEP_UP_OPERATIONS.has(operation.sdkName) || operation.headerParams.some((p) => p.name === "Primitive-Step-Up");
3619
+ }
2899
3620
  const EMAIL_ID_NOT_SENT = "The id of the received email (from `primitive emails list`), not a sent email id. --id is accepted too.";
2900
3621
  const OPERATION_PATH_FLAGS = {
2901
3622
  listEmailRuleResults: { id: {
@@ -2915,6 +3636,34 @@ const OPERATION_PATH_FLAGS = {
2915
3636
  description: EMAIL_ID_NOT_SENT
2916
3637
  } }
2917
3638
  };
3639
+ /** End a description with a full stop when it has no closing punctuation. */
3640
+ function sentence(text) {
3641
+ const trimmed = text.trim();
3642
+ return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
3643
+ }
3644
+ /**
3645
+ * The path parameter a command also takes as its first argument: the only
3646
+ * one, when the operation has exactly one (`emails get <id>`). Its flag stays
3647
+ * and is no longer required on its own.
3648
+ */
3649
+ function positionalPathParameter(operation) {
3650
+ return operation.pathParams.length === 1 ? operation.pathParams[0] : void 0;
3651
+ }
3652
+ /** The most rows a paged --limit fetches, in pages of the server's own cap. */
3653
+ const PAGED_LIMIT_MAX = 1e3;
3654
+ /**
3655
+ * The server's per-request cap on --limit for a list the CLI can page itself
3656
+ * (array data, a cursor in meta.cursor, a limit with a maximum), else
3657
+ * undefined. A larger --limit, up to PAGED_LIMIT_MAX, is then fetched in
3658
+ * pages of that size.
3659
+ */
3660
+ function pagedLimitMaximum(operation) {
3661
+ if (takesStepUp(operation)) return void 0;
3662
+ const limit = operation.queryParams.find((p) => p.name === "limit");
3663
+ if (!limit || typeof limit.maximum !== "number") return void 0;
3664
+ if (!pagingFlagOverride(operation, limit)) return void 0;
3665
+ return limit.maximum < 1e3 ? limit.maximum : void 0;
3666
+ }
2918
3667
  /** The flag a path or query parameter is read from. */
2919
3668
  function parameterFlagName(sdkName, parameterName) {
2920
3669
  return (sdkName ? OPERATION_PATH_FLAGS[sdkName]?.[parameterName]?.flag : void 0) ?? flagName(parameterName);
@@ -2931,7 +3680,13 @@ function pagingFlagOverride(operation, parameter) {
2931
3680
  if (operation.responseSchema?.type !== "array") return void 0;
2932
3681
  const described = flagDescription(parameter).trim().replace(/\.$/, "");
2933
3682
  if (parameter.name === "cursor") return { description: `${described}. The next page's cursor is meta.cursor, null on the last page: without --json it is printed on stderr as \`next cursor: <value>\`; with --json, add --envelope to get it, since the bare --json output is the data array alone.` };
2934
- if (parameter.name === "limit" && typeof parameter.maximum === "number") return { description: `${described}.${described.includes(String(parameter.maximum)) ? "" : ` At most ${parameter.maximum} per page;`} for more, page with --cursor.`.replace(". for more", ". For more") };
3683
+ if (parameter.name === "limit" && typeof parameter.maximum === "number") {
3684
+ if (parameter.maximum < 1e3) {
3685
+ const single = operation.queryParams.some((p) => p.name === "wait") ? " (not with --wait or --since)" : "";
3686
+ return { description: `${described}. The API returns at most ${parameter.maximum} per request; a larger --limit, up to ${PAGED_LIMIT_MAX}, is fetched in pages of ${parameter.maximum}${single}. For more, page with --cursor.` };
3687
+ }
3688
+ return { description: `${described}.${described.includes(String(parameter.maximum)) ? "" : ` At most ${parameter.maximum} per page;`} for more, page with --cursor.`.replace(". for more", ". For more") };
3689
+ }
2935
3690
  }
2936
3691
  const numberFlag = Flags.custom({ async parse(input, _context, options) {
2937
3692
  const trimmed = input.trim();
@@ -3025,10 +3780,10 @@ function extractBodyFields(schema) {
3025
3780
  * the natural reading is "use the flags above; --raw-body for
3026
3781
  * the leftovers below."
3027
3782
  */
3028
- function renderRequestSchemaSummary(schema) {
3783
+ function renderRequestSchemaSummary(schema, setByFlags = []) {
3029
3784
  const fields = extractBodyFields(schema);
3030
3785
  if (fields.length === 0) return null;
3031
- const complex = fields.filter((f) => f.kind === "complex");
3786
+ const complex = fields.filter((f) => f.kind === "complex" && !setByFlags.includes(f.name));
3032
3787
  if (complex.length === 0) return null;
3033
3788
  const nameWidth = Math.min(24, Math.max(...complex.map((f) => f.name.length)));
3034
3789
  const descMax = 78;
@@ -3316,9 +4071,17 @@ function buildFlags(operation) {
3316
4071
  flags.json = Flags.boolean({ description: "Print exactly one JSON document on stdout, on success and on failure, and nothing on stderr. Stdout keeps its usual shape: the data payload, or with --envelope the full response envelope (data plus meta, including meta.cursor, and empty-result hints in summary). The next-cursor line is not printed; use --envelope to page. A failure prints the error with exit_code. Without --json, the cursor and notices go to stderr." });
3317
4072
  flags.envelope = Flags.boolean({ description: "Print the full response envelope, including pagination metadata such as meta.cursor. Defaults to printing only the data payload for backward compatibility." });
3318
4073
  }
4074
+ const positional = positionalPathParameter(operation);
4075
+ const pageMaximum = pagedLimitMaximum(operation);
3319
4076
  for (const parameter of [...operation.pathParams, ...operation.queryParams]) {
3320
4077
  const override = OPERATION_PATH_FLAGS[operation.sdkName]?.[parameter.name];
3321
- flags[parameterFlagName(operation.sdkName, parameter.name)] = flagForParameter(parameter, override ?? pagingFlagOverride(operation, parameter));
4078
+ flags[parameterFlagName(operation.sdkName, parameter.name)] = flagForParameter(parameter === positional ? {
4079
+ ...parameter,
4080
+ required: false
4081
+ } : pageMaximum !== void 0 && parameter.name === "limit" ? {
4082
+ ...parameter,
4083
+ maximum: PAGED_LIMIT_MAX
4084
+ } : parameter, override ?? pagingFlagOverride(operation, parameter));
3322
4085
  }
3323
4086
  const bodyFieldFlagToProperty = /* @__PURE__ */ new Map();
3324
4087
  if (operation.hasJsonBody) {
@@ -3343,7 +4106,21 @@ function buildFlags(operation) {
3343
4106
  }
3344
4107
  }
3345
4108
  const headerFlagToName = /* @__PURE__ */ new Map();
4109
+ if (takesStepUp(operation)) {
4110
+ for (const name of ["step-up-token", "browser"]) if (flags[name] !== void 0) throw new Error(`${operation.sdkName}: --${name} is both a parameter and the approval flag`);
4111
+ flags["step-up-token"] = Flags.string({
4112
+ description: STEP_UP_TOKEN_FLAG_DESCRIPTION,
4113
+ aliases: [headerFlagName(STEP_UP_HEADER)]
4114
+ });
4115
+ flags.browser = Flags.boolean({
4116
+ allowNo: true,
4117
+ default: true,
4118
+ description: BROWSER_FLAG_DESCRIPTION
4119
+ });
4120
+ headerFlagToName.set("step-up-token", STEP_UP_HEADER);
4121
+ }
3346
4122
  for (const parameter of operation.headerParams) {
4123
+ if (parameter.name === "Primitive-Step-Up") continue;
3347
4124
  const name = headerFlagName(parameter.name);
3348
4125
  if (RESERVED_FLAG_NAMES.has(name)) continue;
3349
4126
  if (flags[name] !== void 0) continue;
@@ -3360,6 +4137,11 @@ function buildFlags(operation) {
3360
4137
  default: true,
3361
4138
  description: ATTACHMENT_WAIT_FLAG_DESCRIPTION
3362
4139
  });
4140
+ const extension = OPERATION_EXTENSIONS[operation.sdkName];
4141
+ for (const [name, flag] of Object.entries(extension?.flags ?? {})) {
4142
+ if (flags[name] !== void 0) throw new Error(`${operation.sdkName}: --${name} is both a generated flag and a CLI extension flag`);
4143
+ flags[name] = flag;
4144
+ }
3363
4145
  return {
3364
4146
  flags,
3365
4147
  bodyFieldFlagToProperty,
@@ -3444,6 +4226,7 @@ const OPERATION_HINTS = {
3444
4226
  deleteMemory: "Tip: prefer `primitive memories delete <key> [--function <function-id>]` for the common delete path.",
3445
4227
  searchMemories: "Tip: prefer `primitive memories search [prefix] [--metadata-only] [--function <function-id>]` for the common search path.",
3446
4228
  startAgentSignup: "Tip: pass --terms-accepted. Capture the signup_token from the response and feed it to `primitive agent verify-agent-signup --signup-token <token> --verification-code <6-digit-code>` (the verify flag accepts --code as an alias). The high-level `primitive signup <email>` command walks an interactive user through both steps with friendlier prompts.",
4229
+ testEmailRule: "Tip: --expect-match and --expect-no-match (or --expect-file) set expect, the emails the rule must match and must not. When no expect is sent, by these flags or in the body, a saved rule's saved expectations are judged instead. The exit code follows whichever expectations the answer judged: 1 when one failed; 1 when one is undecided and testing again will not decide it (its email cannot be found or read, or cannot be classified); 3 when the only undecided ones can be decided later (not classified yet, or a question unanswered), so test again later. Exit 1 also means the request failed. With --json, error.code tells these apart: expectations_failed, expectations_unresolvable, expectations_undecided (exit 3), or the API's own code. Without --json a readable report follows the JSON on stderr: warnings, how each condition held, the expectations, and the values each email that did not match was compared on.",
3447
4230
  verifyAgentSignup: "Tip: pass --verification-code <code> (or --code; both work). The response carries OAuth tokens but not your assigned inbox domain; run `primitive domains list` (or `primitive whoami`) after success to see the managed *.primitive.email address that routes to this account."
3448
4231
  };
3449
4232
  const OPERATION_FLAG_ALIASES = { verifyAgentSignup: { verification_code: ["code"] } };
@@ -3451,9 +4234,11 @@ const OPERATION_EXTRA_BODY_FLAGS = { addDomain: { "mail-relay": {
3451
4234
  property: "mail_relay",
3452
4235
  description: "Keep your existing mailbox provider: the domain's MX points at a Primitive mail relay, which forwards mail to your mailboxes and stores a copy in Primitive. Requires mail relay to be enabled for your account. Google Workspace only for now."
3453
4236
  } } };
4237
+ const OPERATION_EXTENSIONS = { ...EMAIL_RULE_OPERATION_EXTENSIONS };
3454
4238
  const MAIL_RELAY_NOT_ENABLED_MESSAGE = "Mail relay is not enabled for this account, so the domain was not added. Retry without --mail-relay to point the domain's MX at Primitive directly, or ask Primitive to enable mail relay for your account.";
3455
4239
  const MAIL_RELAY_UNAVAILABLE_MESSAGE = "No mail relay is available on this API server right now, so the domain was not added. Retry later, or retry without --mail-relay to point the domain's MX at Primitive directly.";
3456
4240
  const OPERATION_ERROR_HINTS = {
4241
+ updateEmailRule: (code, _body, payload, path) => code === "email_rule_conflict" ? versionMovedHint(payload, path?.id) : void 0,
3457
4242
  verifyDomain: (code, _body, payload) => code === "dns_verification_failed" ? domainVerificationFailedHint(payload) : void 0,
3458
4243
  addDomain: (code, body) => {
3459
4244
  if (code === "mail_relay_unavailable") return MAIL_RELAY_UNAVAILABLE_MESSAGE;
@@ -3474,20 +4259,35 @@ const OPERATION_SUCCESS_HOOKS = { verifyAgentSignup: ({ envelope, configDir, api
3474
4259
  function createOperationCommand(operation) {
3475
4260
  const { flags, bodyFieldFlagToProperty, headerFlagToName } = buildFlags(operation);
3476
4261
  const baseDescription = operation.description !== null && operation.description !== void 0 ? canonicalizeCliReferences(operation.description) : `${operation.method} ${operation.path}`;
3477
- const schemaSummary = operation.hasJsonBody ? renderRequestSchemaSummary(operation.requestSchema) : null;
4262
+ const schemaSummary = operation.hasJsonBody ? renderRequestSchemaSummary(operation.requestSchema, OPERATION_EXTENSIONS[operation.sdkName]?.bodyProperties) : null;
3478
4263
  const hint = OPERATION_HINTS[operation.sdkName];
3479
4264
  const descriptionWithSchema = schemaSummary ? `${baseDescription}\n\n${schemaSummary}` : baseDescription;
3480
4265
  const fullDescription = hint ? `${descriptionWithSchema}\n\n${hint}` : descriptionWithSchema;
3481
4266
  const flagNames = Object.keys(flags);
4267
+ const extension = OPERATION_EXTENSIONS[operation.sdkName];
4268
+ const positional = positionalPathParameter(operation);
4269
+ const positionalFlag = positional ? parameterFlagName(operation.sdkName, positional.name) : void 0;
3482
4270
  class OperationCommand extends Command {
3483
4271
  static description = fullDescription;
3484
4272
  static flags = flags;
4273
+ static args = positional && positionalFlag ? { [positionalFlag]: Args.string({
4274
+ description: `${sentence(OPERATION_PATH_FLAGS[operation.sdkName]?.[positional.name]?.description ?? positional.description ?? positional.name)} The same as --${positionalFlag}; give one of the two.`,
4275
+ required: false
4276
+ }) } : {};
3485
4277
  static summary = operation.summary ?? `${operation.method} ${operation.path}`;
3486
4278
  async run() {
3487
- const { flags } = await this.parse(OperationCommand).catch((error) => {
4279
+ const { args, flags } = await this.parse(OperationCommand).catch((error) => {
3488
4280
  throw withFlagSuggestion(error, flagNames);
3489
4281
  });
3490
4282
  const parsedFlags = flags;
4283
+ if (positionalFlag) {
4284
+ const fromArgument = args[positionalFlag];
4285
+ const fromFlag = parsedFlags[positionalFlag];
4286
+ if (fromArgument !== void 0 && fromFlag !== void 0 && String(fromArgument) !== String(fromFlag)) throw new Errors.CLIError(`Give the ${positionalFlag} once: as the first argument or with --${positionalFlag}, not both with different values.`, { exit: 2 });
4287
+ const value = fromFlag ?? fromArgument;
4288
+ if (value === void 0 || value === "") throw new Errors.CLIError(`Missing the ${positionalFlag}: pass it as the first argument or with --${positionalFlag}.`, { exit: 2 });
4289
+ parsedFlags[positionalFlag] = value;
4290
+ }
3491
4291
  const headers = collectHeaderFlags(parsedFlags, headerFlagToName);
3492
4292
  if ([
3493
4293
  "startAgentSignup",
@@ -3504,6 +4304,11 @@ function createOperationCommand(operation) {
3504
4304
  if (operation.hasJsonBody) {
3505
4305
  const explicit = readJsonBody(parsedFlags);
3506
4306
  const overrides = collectBodyFieldFlags(parsedFlags, bodyFieldFlagToProperty);
4307
+ const extensionFlagFor = /* @__PURE__ */ new Map();
4308
+ for (const field of extension?.bodyFields?.(parsedFlags) ?? []) {
4309
+ overrides[field.property] = field.value;
4310
+ extensionFlagFor.set(field.property, field.flag);
4311
+ }
3507
4312
  if (Object.keys(overrides).length > 0) {
3508
4313
  if (explicit === void 0) body = overrides;
3509
4314
  else if (explicit !== null && typeof explicit === "object" && !Array.isArray(explicit)) body = {
@@ -3512,7 +4317,7 @@ function createOperationCommand(operation) {
3512
4317
  };
3513
4318
  else {
3514
4319
  const explicitKind = explicit === null ? "null" : Array.isArray(explicit) ? "array" : typeof explicit;
3515
- const overrideFlags = Object.keys(overrides).map((p) => `--${flagName(p)}`).join(", ");
4320
+ const overrideFlags = Object.keys(overrides).map((p) => extensionFlagFor.get(p) ?? `--${flagName(p)}`).join(", ");
3516
4321
  throw new Errors.CLIError(`--raw-body must be a JSON object when also passing per-field flags (got ${explicitKind}); supplied per-field flags: ${overrideFlags}. Either drop --raw-body and rely on the per-field flags, or move every field into the JSON --raw-body and drop the flags.`);
3517
4322
  }
3518
4323
  } else body = explicit;
@@ -3533,16 +4338,84 @@ function createOperationCommand(operation) {
3533
4338
  const replyStateSurface = replyStateSurfaceForOperation(operation.sdkName, query);
3534
4339
  const automatedSurface = automatedSurfaceForOperation(operation.sdkName, query);
3535
4340
  const fromVerified = fromVerifiedExpectation(operation.sdkName, query);
3536
- const callOperation = () => operationFn({
3537
- body,
3538
- client: apiClient.client,
3539
- ...Object.keys(headers).length > 0 ? { headers } : {},
3540
- parseAs: operation.binaryResponse ? "blob" : "auto",
3541
- path: collectValues(operation.pathParams, parsedFlags, operation.sdkName),
3542
- query,
3543
- responseStyle: "fields"
3544
- });
3545
- const result = ATTACHMENT_WAIT_OPERATIONS.has(operation.sdkName) ? await withAttachmentWait(callOperation, {
4341
+ const callOperation = (stepUpToken, pageQuery = query) => {
4342
+ const sent = stepUpToken ? {
4343
+ ...headers,
4344
+ [STEP_UP_HEADER]: stepUpToken
4345
+ } : headers;
4346
+ return operationFn({
4347
+ body,
4348
+ client: apiClient.client,
4349
+ ...Object.keys(sent).length > 0 ? { headers: sent } : {},
4350
+ parseAs: operation.binaryResponse ? "blob" : "auto",
4351
+ path: collectValues(operation.pathParams, parsedFlags, operation.sdkName),
4352
+ query: pageQuery,
4353
+ responseStyle: "fields"
4354
+ });
4355
+ };
4356
+ let stepUpUrl;
4357
+ const callWithApproval = async () => {
4358
+ const approved = await withApproval(async (token) => {
4359
+ const attempt = await callOperation(token);
4360
+ return {
4361
+ ok: !attempt.error,
4362
+ payload: attempt.error ? extractErrorPayload(attempt.error) : void 0,
4363
+ attempt
4364
+ };
4365
+ }, terminalApprovalIo({
4366
+ stepUpToken: typeof parsedFlags["step-up-token"] === "string" ? parsedFlags["step-up-token"] : void 0,
4367
+ openBrowser: parsedFlags.browser !== false,
4368
+ json: parsedFlags.json === true
4369
+ }));
4370
+ stepUpUrl = approved.stepUpUrl;
4371
+ return approved.attempt;
4372
+ };
4373
+ const pageMaximum = pagedLimitMaximum(operation);
4374
+ const wanted = typeof query.limit === "number" ? query.limit : 0;
4375
+ const paged = pageMaximum !== void 0 && wanted > pageMaximum;
4376
+ if (paged) {
4377
+ const single = typeof query.wait === "number" && query.wait > 0 ? "--wait" : query.since !== void 0 ? "--since" : void 0;
4378
+ if (single) throw new Errors.CLIError(`${single} takes a --limit of at most ${pageMaximum}, since it reads one page; a larger --limit is fetched in several requests.`, { exit: 2 });
4379
+ }
4380
+ let pagingNote;
4381
+ const callPaged = async () => {
4382
+ const max = pageMaximum ?? wanted;
4383
+ const rows = [];
4384
+ let sentCursor = query.cursor;
4385
+ let last = await callOperation(void 0, {
4386
+ ...query,
4387
+ limit: Math.min(max, wanted)
4388
+ });
4389
+ const budget = Math.ceil(wanted / max) + 20;
4390
+ for (let requests = 1;; requests += 1) {
4391
+ if (last.error) return last;
4392
+ const envelope = last.data;
4393
+ const page = Array.isArray(envelope?.data) ? envelope.data : [];
4394
+ rows.push(...page);
4395
+ const cursor = envelope?.meta?.cursor ?? void 0;
4396
+ const exhausted = Boolean(cursor) && cursor !== sentCursor && rows.length < wanted && requests >= budget;
4397
+ if (exhausted) pagingNote = `Stopped after ${requests} requests with ${rows.length} of ${wanted}; more may remain. Continue with --cursor ${cursor}.`;
4398
+ if (!cursor || cursor === sentCursor || rows.length >= wanted || exhausted) return {
4399
+ ...last,
4400
+ data: {
4401
+ ...envelope ?? {},
4402
+ data: rows,
4403
+ meta: {
4404
+ ...envelope?.meta ?? {},
4405
+ limit: wanted,
4406
+ cursor: cursor ?? null
4407
+ }
4408
+ }
4409
+ };
4410
+ sentCursor = cursor;
4411
+ last = await callOperation(void 0, {
4412
+ ...query,
4413
+ cursor,
4414
+ limit: Math.min(max, wanted - rows.length)
4415
+ });
4416
+ }
4417
+ };
4418
+ const result = paged ? await callPaged() : takesStepUp(operation) ? await callWithApproval() : ATTACHMENT_WAIT_OPERATIONS.has(operation.sdkName) ? await withAttachmentWait(callOperation, {
3546
4419
  wait: parsedFlags.wait !== false,
3547
4420
  errorCode: (error) => extractErrorCode(extractErrorPayload(error)),
3548
4421
  onFirstWait: () => {
@@ -3552,8 +4425,9 @@ function createOperationCommand(operation) {
3552
4425
  if (result.error) {
3553
4426
  const errorPayload = extractErrorPayload(result.error);
3554
4427
  writeErrorWithHints(errorPayload, result.response);
3555
- const operationErrorHint = OPERATION_ERROR_HINTS[operation.sdkName]?.(extractErrorCode(errorPayload), body, errorPayload);
4428
+ const operationErrorHint = OPERATION_ERROR_HINTS[operation.sdkName]?.(extractErrorCode(errorPayload), body, errorPayload, collectValues(operation.pathParams, parsedFlags, operation.sdkName));
3556
4429
  if (operationErrorHint) process.stderr.write(`${operationErrorHint}\n`);
4430
+ if (stepUpUrl) process.stderr.write(approveThenRerunHint(stepUpUrl));
3557
4431
  if (replyStateSurface && isAwaitingRejectedError(errorPayload)) process.stderr.write(`${awaitingRejectedError(replyStateSurface).message}\n`);
3558
4432
  if (automatedSurface && isAutomatedRejectedError(errorPayload)) process.stderr.write(`${automatedRejectedError(automatedSurface).message}\n`);
3559
4433
  surfaceUnauthorizedHint({
@@ -3625,6 +4499,7 @@ function createOperationCommand(operation) {
3625
4499
  const withEnvelope = parsedFlags.envelope === true;
3626
4500
  const cursor = envelope?.meta?.cursor;
3627
4501
  if (cursor && !jsonOutput) process.stderr.write(`next cursor: ${cursor}\n`);
4502
+ if (pagingNote && !jsonOutput) process.stderr.write(`${pagingNote}\n`);
3628
4503
  const summary = [];
3629
4504
  if (Array.isArray(envelope?.data) && envelope.data.length === 0) {
3630
4505
  const hint = EMPTY_RESULT_HINTS[operation.sdkName];
@@ -3655,7 +4530,40 @@ function createOperationCommand(operation) {
3655
4530
  const detail = hookError instanceof Error ? hookError.message : String(hookError);
3656
4531
  process.stderr.write(`Warning: ${operation.sdkName} succeeded but its post-success hook threw (${detail}). The response below is still valid; act on it manually.\n`);
3657
4532
  }
3658
- this.log(JSON.stringify(jsonOutput && withEnvelope ? operationJsonDocument(envelope, summary) : operationOutputPayload(envelope, withEnvelope), null, 2));
4533
+ let report;
4534
+ let reportError;
4535
+ try {
4536
+ report = extension?.report?.(envelope?.data, {
4537
+ body,
4538
+ path: collectValues(operation.pathParams, parsedFlags, operation.sdkName)
4539
+ });
4540
+ } catch (error) {
4541
+ reportError = error instanceof Error ? error.message : String(error);
4542
+ let fallback;
4543
+ try {
4544
+ fallback = extension?.fallbackExitCode?.(envelope?.data);
4545
+ } catch {
4546
+ fallback = 1;
4547
+ }
4548
+ if (fallback) report = {
4549
+ lines: [],
4550
+ exitCode: fallback,
4551
+ error: {
4552
+ code: "expectations_not_passed",
4553
+ message: "The answer's expectations did not all pass; read expectations in the answer."
4554
+ }
4555
+ };
4556
+ }
4557
+ let printed = jsonOutput && withEnvelope ? operationJsonDocument(envelope, summary) : operationOutputPayload(envelope, withEnvelope);
4558
+ if (jsonOutput && report?.exitCode && report.error && printed !== null && typeof printed === "object" && !Array.isArray(printed)) printed = {
4559
+ ...printed,
4560
+ error: report.error
4561
+ };
4562
+ this.log(JSON.stringify(printed, null, 2));
4563
+ if (reportError !== void 0) process.stderr.write(`Warning: ${operation.sdkName} succeeded but its report could not be built (${reportError}). The JSON above is the full answer; read it rather than the exit code.\n`);
4564
+ if (report && !jsonOutput) for (const line of report.lines) process.stderr.write(`${line}\n`);
4565
+ if (reportError !== void 0 && report?.exitCode && !jsonOutput) process.stderr.write(`The answer's expectations did not all pass, so the command exits ${report.exitCode}.\n`);
4566
+ if (report?.exitCode) process.exitCode = report.exitCode;
3659
4567
  });
3660
4568
  }
3661
4569
  }
@@ -3672,4 +4580,4 @@ const EMPTY_RESULT_HINTS = {
3672
4580
  function canonicalizeCliReferences(description) {
3673
4581
  return description.replaceAll("`primitive emails:latest`", "`primitive emails latest`").replaceAll("`primitive describe emails:get-email | jq '.responseSchema.properties'`", "`primitive describe emails:get | jq '.responseSchema.properties'`");
3674
4582
  }
3675
- export { deriveSendIdempotencyKey as $, wakeRelationship as A, ATTACHMENT_WAIT_MAX_MS as At, readEmailInteraction as B, CliOrgRefusedError as Bt, readThreadContext as C, currentMailSessionKey as Ct, skillFirstLine as D, formatAlreadySentNotice as Dt, serverRelationship as E, runtimeSessionVariableList as Et, readPendingMail as F, createCliApiClient as Ft, MAX_SEND_WAIT_TIMEOUT_MS as G, replyInteractionWarning as H, fetchCliOrgs as Ht, recordPendingMail as I, refreshStoredCliCredentials as It, assertValidIdempotencyKey as J, SEND_OUTCOME_HELP as K, removePendingMail as L, refuseOrgSelectionForProfile as Lt, clearConsumedPendingMail as M, ConnectedAgentKeyRequiredError as Mt, clearReadPendingMail as N, SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE as Nt, wakeReadCommand as O, withFlagSuggestion as Ot, pendingMailPath as P, createAuthenticatedCliApiClient as Pt, classifySendError as Q, interactionHumanLine as R, resolveCliApiRequestConfig as Rt, latestOwnOutbound as S, loadSkillLine as St, serverMuted as T, detectRuntimeSession as Tt, wakeInteractionSentence as U, revokeUserLogin as Ut, replyExpectation as V, credentialsFromUserLogin as Vt, IDEMPOTENCY_KEY_FLAG_DESCRIPTION as W, switchCurrentOrg as Wt, buildThrownSendFailureEnvelope as X, buildFollowUpCommand as Y, checkPriorReplies as Z, explainPendingMailNotFound as _, cliInvocation as _t, createOperationCommand as a, formatSendFailureSummary as at, displayAddress as b, generatedAgentName as bt, formatErrorPayload as c, reportSendCommandResult as ct, isGrantRefusal as d, sendWaitClampNotice as dt, excludeSignalReplies as et, parameterFlagName as f, sentHistoryWindowStart as ft, writeErrorWithHints as g, DEFAULT_ENROLL_NAME as gt, surfaceUnauthorizedHint as h, thrownErrorExitCode as ht, TIME_FLAG_DESCRIPTION as i, formatPriorRepliesWarning as it, claimPendingMail as j, withAttachmentWait as jt, wakeRecipientField as k, ATTACHMENT_WAIT_FLAG_DESCRIPTION as kt, hasControlCharacter as l, sendIdentityFields as lt, runWithTiming as m, successfulSendOutcome as mt, API_ERROR_CODES as n, formatDeletedEarlierSendNotice as nt, extractErrorCode as o, formatSendRecordFailureSummary as ot, readTextFileFlag as p, serializeErrorPayload as pt, apiResultHttpStatus as q, OPERATION_PATH_FLAGS as r, formatPriorRepliesCheckSkipped as rt, extractErrorPayload as s, priorRepliesThatWentOut as st, API_BASE_URL_FLAG_DESCRIPTION as t, followUpCommandPrefix as tt, headerFlagName as u, sendOutcomeExitCode as ut, bareAddress$1 as v, connectNextSteps as vt, sentInThread as w, detectAgentRuntime as wt, formatMailWakeLine as x, identitySuggestions as xt, describeWake as y, defaultConnectionName as yt, interactionNextActions as z, selectedOrgFromEnv as zt };
4583
+ export { replyExpectation as $, refuseOrgSelectionForProfile as $t, bareAddress$1 as A, connectNextSteps as At, wakeReadCommand as B, withFlagSuggestion as Bt, terminalApprovalIo as C, sendWaitClampNotice as Ct, listenForCopyHotkey as D, thrownErrorExitCode as Dt, formatBrowserLoginInstructions as E, successfulSendOutcome as Et, readThreadContext as F, currentMailSessionKey as Ft, clearReadPendingMail as G, ATTACHMENT_WAIT_FLAG_DESCRIPTION as Gt, wakeRelationship as H, classificationOf as Ht, sentInThread as I, detectAgentRuntime as It, recordPendingMail as J, ConnectedAgentKeyRequiredError as Jt, pendingMailPath as K, ATTACHMENT_WAIT_MAX_MS as Kt, serverMuted as L, detectRuntimeSession as Lt, displayAddress as M, generatedAgentName as Mt, formatMailWakeLine as N, identitySuggestions as Nt, openBrowser as O, DEFAULT_ENROLL_NAME as Ot, latestOwnOutbound as P, loadSkillLine as Pt, readEmailInteraction as Q, refreshStoredCliCredentials as Qt, serverRelationship as R, runtimeSessionVariableList as Rt, promptLine as S, sendOutcomeExitCode as St, canOfferCopyHotkey as T, serializeErrorPayload as Tt, claimPendingMail as U, isEmailId as Ut, wakeRecipientField as V, classificationFactLines as Vt, clearConsumedPendingMail as W, noFactsMessage as Wt, interactionHumanLine as X, createAuthenticatedCliApiClient as Xt, removePendingMail as Y, SAVED_CLI_OAUTH_SESSION_EXPIRED_MESSAGE as Yt, interactionNextActions as Z, createCliApiClient as Zt, writeErrorWithHints as _, formatSendFailureSummary as _t, createOperationCommand as a, revokeUserLogin as an, apiResultHttpStatus as at, STEP_UP_TOKEN_FLAG_DESCRIPTION as b, reportSendCommandResult as bt, formatErrorPayload as c, buildThrownSendFailureEnvelope as ct, isGrantRefusal as d, deriveSendIdempotencyKey as dt, resolveCliApiRequestConfig as en, replyInteractionWarning as et, parameterFlagName as f, excludeSignalReplies as ft, takesStepUp as g, formatPriorRepliesWarning as gt, surfaceUnauthorizedHint as h, formatPriorRepliesCheckSkipped as ht, TIME_FLAG_DESCRIPTION as i, fetchCliOrgs as in, SEND_OUTCOME_HELP as it, describeWake as j, defaultConnectionName as jt, explainPendingMailNotFound as k, cliInvocation as kt, hasControlCharacter as l, checkPriorReplies as lt, runWithTiming as m, formatDeletedEarlierSendNotice as mt, API_ERROR_CODES as n, CliOrgRefusedError as nn, IDEMPOTENCY_KEY_FLAG_DESCRIPTION as nt, extractErrorCode as o, switchCurrentOrg as on, assertValidIdempotencyKey as ot, readTextFileFlag as p, followUpCommandPrefix as pt, readPendingMail as q, withAttachmentWait as qt, OPERATION_PATH_FLAGS as r, credentialsFromUserLogin as rn, MAX_SEND_WAIT_TIMEOUT_MS as rt, extractErrorPayload as s, buildFollowUpCommand as st, API_BASE_URL_FLAG_DESCRIPTION as t, selectedOrgFromEnv as tn, wakeInteractionSentence as tt, headerFlagName as u, classifySendError as ut, BROWSER_FLAG_DESCRIPTION as v, formatSendRecordFailureSummary as vt, withApproval as w, sentHistoryWindowStart as wt, approveThenRerunHint as x, sendIdentityFields as xt, STEP_UP_HEADER as y, priorRepliesThatWentOut as yt, skillFirstLine as z, formatAlreadySentNotice as zt };