@seclai/cli 1.4.0 → 1.5.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.
package/dist/cli.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { Command } from "commander";
4
+ import { Command as Command3 } from "commander";
5
5
  import { realpathSync } from "fs";
6
6
  import { fileURLToPath, pathToFileURL } from "url";
7
7
 
8
8
  // src/helpers.ts
9
+ import { InvalidArgumentError } from "commander";
9
10
  import { readFile } from "fs/promises";
10
11
  import { readFileSync } from "fs";
11
12
  import process2 from "process";
@@ -78,6 +79,10 @@ function createClient(opts) {
78
79
  if (opts.profile !== void 0) seclaiOpts.profile = opts.profile;
79
80
  if (opts.configDir !== void 0) seclaiOpts.configDir = opts.configDir;
80
81
  if (opts.accountId !== void 0) seclaiOpts.accountId = opts.accountId;
82
+ const envVersion = process2.env.SECLAI_API_VERSION;
83
+ const version = opts.apiVersion ?? (envVersion && envVersion.length > 0 ? envVersion : void 0);
84
+ if (version !== void 0) seclaiOpts.apiVersion = version;
85
+ if (opts.allowUnknownApiVersion) seclaiOpts.allowUnknownApiVersion = true;
81
86
  const envUrl = process2.env.SECLAI_API_URL;
82
87
  seclaiOpts.baseUrl = envUrl && envUrl.length > 0 ? envUrl : "https://api.seclai.com";
83
88
  return new Seclai(seclaiOpts);
@@ -87,6 +92,10 @@ function printJson(rt, value) {
87
92
  rt.writeOut(`${JSON.stringify(value, null, indent)}
88
93
  `);
89
94
  }
95
+ function warnDeprecated(rt, message) {
96
+ rt.writeErr(`warning: ${message} This will be rejected in a future release.
97
+ `);
98
+ }
90
99
  function printError(rt, err) {
91
100
  if (err instanceof SeclaiAPIValidationError) {
92
101
  rt.writeErr(`${err.name}: ${err.message}
@@ -134,6 +143,25 @@ async function run(rt, main) {
134
143
  rt.setExitCode(1);
135
144
  }
136
145
  }
146
+ function parseNumber(value) {
147
+ const parsed = value.trim() === "" ? Number.NaN : Number(value);
148
+ if (!Number.isFinite(parsed)) {
149
+ throw new InvalidArgumentError("Expected a number.");
150
+ }
151
+ return parsed;
152
+ }
153
+ function withLimitOption(cmd) {
154
+ return cmd.option("--limit <n>", "Page size.", parseNumber);
155
+ }
156
+ function withOffsetListOptions(cmd) {
157
+ return withLimitOption(cmd).option("--offset <n>", "Number of items to skip.", parseNumber);
158
+ }
159
+ function offsetListOpts(opts) {
160
+ const o = {};
161
+ if (opts.limit !== void 0) o.limit = opts.limit;
162
+ if (opts.offset !== void 0) o.offset = opts.offset;
163
+ return o;
164
+ }
137
165
  function withJsonInputOptions(cmd) {
138
166
  return cmd.option("--json <json>", "Inline JSON body. Use '-' to read from stdin.").option("--json-file <path>", "Path to JSON file. Use '-' to read from stdin.");
139
167
  }
@@ -176,7 +204,7 @@ async function readAiInput(rt, opts) {
176
204
  // src/commands/agents.ts
177
205
  function register(program, rt) {
178
206
  const agents = program.command("agents").description("Manage agents, runs, definitions, export, and AI assistance.");
179
- agents.command("list").description("List agents.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (opts) => {
207
+ agents.command("list").description("List agents.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (opts) => {
180
208
  await run(rt, async () => {
181
209
  const client = createClient(program.opts());
182
210
  printJson(rt, await client.listAgents(listOpts(opts)));
@@ -209,7 +237,40 @@ function register(program, rt) {
209
237
  printJson(rt, { ok: true });
210
238
  });
211
239
  });
212
- agents.command("run").description("Run an agent. Use --stream/--events/--poll for different modes.").argument("<agentId>", "Agent ID.").option("--json <json>", "Inline JSON body. Use '-' for stdin.").option("--json-file <path>", "JSON file path. Use '-' for stdin.").option("--stream", "Stream and print final result when done.").option("--events", "Stream SSE events as newline-delimited JSON.").option("--event-filter <types>", "Comma-separated event types to show (with --events).").option("--output <mode>", "Output mode: 'full' prints entire event, 'data' prints only the data field, 'status' prints a one-line summary.", "full").option("--poll", "Poll until completion instead of streaming.").option("--poll-interval-ms <n>", "Poll interval in ms (with --poll).", (v) => Number(v)).option("--timeout-ms <n>", "Client-side timeout in ms.", (v) => Number(v)).option("--include-step-outputs", "Include step outputs (with --poll).").action(async (agentId, opts) => {
240
+ agents.command("disable").description("Pause an agent across every trigger path (API, schedule, email, sub-agent calls).").argument("<agentId>", "Agent ID.").action(async (agentId) => {
241
+ await run(rt, async () => {
242
+ const client = createClient(program.opts());
243
+ printJson(rt, await client.disableAgent(agentId));
244
+ });
245
+ });
246
+ agents.command("enable").description("Resume a paused agent.").argument("<agentId>", "Agent ID.").action(async (agentId) => {
247
+ await run(rt, async () => {
248
+ const client = createClient(program.opts());
249
+ printJson(rt, await client.enableAgent(agentId));
250
+ });
251
+ });
252
+ agents.command("callers").description("List the live agents that call this agent via a call_agent step.").argument("<agentId>", "Agent ID.").action(async (agentId) => {
253
+ await run(rt, async () => {
254
+ const client = createClient(program.opts());
255
+ printJson(rt, await client.getAgentCallers(agentId));
256
+ });
257
+ });
258
+ const triggers = agents.command("triggers").description("Agent trigger configuration.");
259
+ triggers.command("email-config").description("Set the alias, sender allowlist, and inbound-handling flags on an EMAIL_RECEIVED trigger.").argument("<agentId>", "Agent ID.").argument("<triggerId>", "Trigger ID.").option("--json <json>", "Config body JSON. Use '-' for stdin.").option("--json-file <path>", "Config body JSON file. Use '-' for stdin.").action(async (agentId, triggerId, opts) => {
260
+ await run(rt, async () => {
261
+ const client = createClient(program.opts());
262
+ const body = await readJsonInput(rt, { json: opts.json, jsonFile: opts.jsonFile });
263
+ printJson(
264
+ rt,
265
+ await client.setEmailTriggerConfig(
266
+ agentId,
267
+ triggerId,
268
+ body
269
+ )
270
+ );
271
+ });
272
+ });
273
+ agents.command("run").description("Run an agent. Use --stream/--events/--poll for different modes.").argument("<agentId>", "Agent ID.").option("--json <json>", "Inline JSON body. Use '-' for stdin.").option("--json-file <path>", "JSON file path. Use '-' for stdin.").option("--stream", "Stream and print final result when done.").option("--events", "Stream SSE events as newline-delimited JSON.").option("--event-filter <types>", "Comma-separated event types to show (with --events).").option("--output <mode>", "Output mode: 'full' prints entire event, 'data' prints only the data field, 'status' prints a one-line summary.", "full").option("--poll", "Poll until completion instead of streaming.").option("--poll-interval-ms <n>", "Poll interval in ms (with --poll).", parseNumber).option("--timeout-ms <n>", "Client-side timeout in ms.", parseNumber).option("--include-step-outputs", "Include step outputs (with --poll).").action(async (agentId, opts) => {
213
274
  await run(rt, async () => {
214
275
  const client = createClient(program.opts());
215
276
  const body = await readJsonInput(rt, { json: opts.json, jsonFile: opts.jsonFile });
@@ -257,7 +318,7 @@ function register(program, rt) {
257
318
  });
258
319
  });
259
320
  const runs = agents.command("runs").description("Manage agent runs.");
260
- runs.command("list").description("List runs for an agent.").argument("<agentId>", "Agent ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).option("--status <status>", "Filter by run status (e.g. queued, running, completed, failed, cancelled).").action(async (agentId, opts) => {
321
+ runs.command("list").description("List runs for an agent.").argument("<agentId>", "Agent ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option("--status <status>", "Filter by run status (e.g. queued, running, completed, failed, cancelled).").action(async (agentId, opts) => {
261
322
  await run(rt, async () => {
262
323
  const client = createClient(program.opts());
263
324
  const o = listOpts(opts);
@@ -274,10 +335,14 @@ function register(program, rt) {
274
335
  );
275
336
  });
276
337
  });
277
- runs.command("delete").description("Delete a run.").argument("<runId>", "Run ID.").action(async (runId) => {
338
+ runs.command("delete").description("Deprecated alias for 'runs cancel'. The API has no delete-a-run operation.").argument("<runId>", "Run ID.").action(async (runId) => {
278
339
  await run(rt, async () => {
340
+ warnDeprecated(
341
+ rt,
342
+ `'agents runs delete' is deprecated and cancels the run rather than deleting it \u2014 the API has no delete-a-run operation. Use 'agents runs cancel', which also prints the cancelled run instead of {"ok": true}.`
343
+ );
279
344
  const client = createClient(program.opts());
280
- await client.deleteAgentRun(runId);
345
+ await client.cancelAgentRun(runId);
281
346
  printJson(rt, { ok: true });
282
347
  });
283
348
  });
@@ -398,10 +463,20 @@ function register(program, rt) {
398
463
  printJson(rt, await client.generateStepConfig(agentId, body));
399
464
  });
400
465
  });
401
- ai.command("history").description("Get agent AI conversation history.").argument("<agentId>", "Agent ID.").action(async (agentId) => {
466
+ withOffsetListOptions(
467
+ ai.command("history").description("Get agent AI conversation history for one step type.").argument("<agentId>", "Agent ID.").requiredOption(
468
+ "--step-type <type>",
469
+ "Step type to read history for (e.g. llm). Required by the API."
470
+ ).option("--step-id <id>", "Restrict to a single step.")
471
+ ).action(async (agentId, opts) => {
402
472
  await run(rt, async () => {
403
473
  const client = createClient(program.opts());
404
- printJson(rt, await client.getAgentAiConversationHistory(agentId));
474
+ const o = {
475
+ ...offsetListOpts(opts),
476
+ stepType: opts.stepType
477
+ };
478
+ if (opts.stepId !== void 0) o.stepId = opts.stepId;
479
+ printJson(rt, await client.getAgentAiConversationHistory(agentId, o));
405
480
  });
406
481
  });
407
482
  ai.command("mark").description("Mark an AI suggestion (accept/reject).").argument("<agentId>", "Agent ID.").argument("<conversationId>", "Conversation ID.").option("--json <json>", "Mark body JSON.").option("--json-file <path>", "Mark body JSON file.").action(async (agentId, conversationId, opts) => {
@@ -412,7 +487,7 @@ function register(program, rt) {
412
487
  printJson(rt, { ok: true });
413
488
  });
414
489
  });
415
- runs.command("eval-results").description("List evaluation results for a run.").argument("<agentId>", "Agent ID.").argument("<runId>", "Run ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (agentId, runId, opts) => {
490
+ runs.command("eval-results").description("List evaluation results for a run.").argument("<agentId>", "Agent ID.").argument("<runId>", "Run ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (agentId, runId, opts) => {
416
491
  await run(rt, async () => {
417
492
  const client = createClient(program.opts());
418
493
  printJson(rt, await client.listRunEvaluationResults(agentId, runId, listOpts(opts)));
@@ -735,10 +810,16 @@ function register5(program, rt) {
735
810
  function register6(program, rt) {
736
811
  const evals = program.command("evals").description("Manage evaluations.");
737
812
  const criteria = evals.command("criteria").description("Evaluation criteria.");
738
- criteria.command("list").description("List evaluation criteria for an agent.").argument("<agentId>", "Agent ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (agentId, opts) => {
813
+ criteria.command("list").description("List evaluation criteria for an agent.").argument("<agentId>", "Agent ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option(
814
+ "--paged",
815
+ "Wrap the results in {data: [...]} instead of returning a bare array. The pagination block is included once the API sends one, from --api-version 2026-07-27."
816
+ ).action(async (agentId, opts) => {
739
817
  await run(rt, async () => {
740
818
  const client = createClient(program.opts());
741
- printJson(rt, await client.listEvaluationCriteria(agentId, listOpts(opts)));
819
+ printJson(
820
+ rt,
821
+ opts.paged ? await client.listEvaluationCriteriaPage(agentId, listOpts(opts)) : await client.listEvaluationCriteria(agentId, listOpts(opts))
822
+ );
742
823
  });
743
824
  });
744
825
  criteria.command("create").description("Create evaluation criteria.").argument("<agentId>", "Agent ID.").option("--json <json>", "Criteria body JSON.").option("--json-file <path>", "Criteria body JSON file.").action(async (agentId, opts) => {
@@ -775,7 +856,7 @@ function register6(program, rt) {
775
856
  });
776
857
  });
777
858
  const results = evals.command("results").description("Evaluation results.");
778
- results.command("list").description("List results for criteria.").argument("<criteriaId>", "Criteria ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (criteriaId, opts) => {
859
+ results.command("list").description("List results for criteria.").argument("<criteriaId>", "Criteria ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (criteriaId, opts) => {
779
860
  await run(rt, async () => {
780
861
  const client = createClient(program.opts());
781
862
  printJson(rt, await client.listEvaluationResults(criteriaId, listOpts(opts)));
@@ -788,7 +869,7 @@ function register6(program, rt) {
788
869
  printJson(rt, await client.createEvaluationResult(criteriaId, body));
789
870
  });
790
871
  });
791
- evals.command("compatible-runs").description("List runs compatible with criteria.").argument("<criteriaId>", "Criteria ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (criteriaId, opts) => {
872
+ evals.command("compatible-runs").description("List runs compatible with criteria.").argument("<criteriaId>", "Criteria ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (criteriaId, opts) => {
792
873
  await run(rt, async () => {
793
874
  const client = createClient(program.opts());
794
875
  printJson(rt, await client.listCompatibleRuns(criteriaId, listOpts(opts)));
@@ -801,13 +882,13 @@ function register6(program, rt) {
801
882
  printJson(rt, await client.testDraftEvaluation(agentId, body));
802
883
  });
803
884
  });
804
- evals.command("agent-results").description("List all evaluation results for an agent.").argument("<agentId>", "Agent ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (agentId, opts) => {
885
+ evals.command("agent-results").description("List all evaluation results for an agent.").argument("<agentId>", "Agent ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (agentId, opts) => {
805
886
  await run(rt, async () => {
806
887
  const client = createClient(program.opts());
807
888
  printJson(rt, await client.listAgentEvaluationResults(agentId, listOpts(opts)));
808
889
  });
809
890
  });
810
- evals.command("agent-runs").description("List evaluation run summaries for an agent.").argument("<agentId>", "Agent ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (agentId, opts) => {
891
+ evals.command("agent-runs").description("List evaluation run summaries for an agent.").argument("<agentId>", "Agent ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (agentId, opts) => {
811
892
  await run(rt, async () => {
812
893
  const client = createClient(program.opts());
813
894
  printJson(rt, await client.listEvaluationRuns(agentId, listOpts(opts)));
@@ -1002,12 +1083,20 @@ function register8(program, rt) {
1002
1083
  // src/commands/alerts.ts
1003
1084
  function register9(program, rt) {
1004
1085
  const alerts = program.command("alerts").description("Manage alerts and alert configurations.");
1005
- alerts.command("list").description("List alerts.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).option("--status <status>", "Filter by status.").option("--severity <severity>", "Filter by severity.").action(async (opts) => {
1086
+ alerts.command("list").description("List alerts.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option("--status <status>", "Filter by status.").option(
1087
+ "--severity <severity>",
1088
+ "Deprecated and ignored \u2014 GET /alerts declares no severity filter. Filter with jq instead."
1089
+ ).action(async (opts) => {
1006
1090
  await run(rt, async () => {
1007
1091
  const client = createClient(program.opts());
1008
1092
  const o = listOpts(opts);
1009
1093
  if (opts.status) o.status = opts.status;
1010
- if (opts.severity) o.severity = opts.severity;
1094
+ if (opts.severity !== void 0) {
1095
+ warnDeprecated(
1096
+ rt,
1097
+ `'alerts list --severity' is ignored \u2014 the API has no severity filter, so it never filtered anything. Filter client-side, e.g. | jq '[.data[] | select(.severity == "high")]'.`
1098
+ );
1099
+ }
1011
1100
  printJson(rt, await client.listAlerts(o));
1012
1101
  });
1013
1102
  });
@@ -1044,7 +1133,7 @@ function register9(program, rt) {
1044
1133
  });
1045
1134
  });
1046
1135
  const configs = alerts.command("configs").description("Alert configurations.");
1047
- configs.command("list").description("List alert configurations.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (opts) => {
1136
+ configs.command("list").description("List alert configurations.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (opts) => {
1048
1137
  await run(rt, async () => {
1049
1138
  const client = createClient(program.opts());
1050
1139
  printJson(rt, await client.listAlertConfigs(listOpts(opts)));
@@ -1093,19 +1182,203 @@ function register9(program, rt) {
1093
1182
  });
1094
1183
  }
1095
1184
 
1096
- // src/commands/models.ts
1185
+ // src/commands/email.ts
1097
1186
  function register10(program, rt) {
1187
+ const email = program.command("email").description("Agent email: sending domains, inbound blocklist, inbound health, and opt-outs.");
1188
+ const domains = email.command("domains").description("Agent-email sending domains.");
1189
+ domains.command("list").description("List the account's email domains and the plan limits for adding more.").action(async () => {
1190
+ await run(rt, async () => {
1191
+ const client = createClient(program.opts());
1192
+ printJson(rt, await client.listEmailDomains());
1193
+ });
1194
+ });
1195
+ domains.command("add").description("Add and provision a new agent-email domain. Returns the DNS records to publish.").requiredOption("--kind <kind>", "'vanity' (a subdomain of seclai.com) or 'custom' (your own domain).").requiredOption("--value <domain>", "The domain to add.").option("--delegated", "The domain's DNS is delegated to Seclai, so records are published automatically.").action(async (opts) => {
1196
+ await run(rt, async () => {
1197
+ const client = createClient(program.opts());
1198
+ const body = {
1199
+ kind: opts.kind,
1200
+ value: opts.value
1201
+ };
1202
+ if (opts.delegated) body.delegated = true;
1203
+ printJson(rt, await client.addEmailDomain(body));
1204
+ });
1205
+ });
1206
+ domains.command("remove").description("Remove a domain and tear down its sending identity and inbound routing.").argument("<domainId>", "Domain ID.").action(async (domainId) => {
1207
+ await run(rt, async () => {
1208
+ const client = createClient(program.opts());
1209
+ printJson(rt, await client.removeEmailDomain(domainId));
1210
+ });
1211
+ });
1212
+ domains.command("verify").description("Run a verification check on a domain immediately.").argument("<domainId>", "Domain ID.").action(async (domainId) => {
1213
+ await run(rt, async () => {
1214
+ const client = createClient(program.opts());
1215
+ printJson(rt, await client.verifyEmailDomain(domainId));
1216
+ });
1217
+ });
1218
+ domains.command("set-primary").description("Promote a verified domain to the account's primary sending domain.").argument("<domainId>", "Domain ID.").action(async (domainId) => {
1219
+ await run(rt, async () => {
1220
+ const client = createClient(program.opts());
1221
+ printJson(rt, await client.setPrimaryEmailDomain(domainId));
1222
+ });
1223
+ });
1224
+ domains.command("use-shared").description("Revert to the shared agent.seclai.com sending and inbound domain.").action(async () => {
1225
+ await run(rt, async () => {
1226
+ const client = createClient(program.opts());
1227
+ await client.useSharedEmailDomain();
1228
+ printJson(rt, { ok: true });
1229
+ });
1230
+ });
1231
+ domains.command("test-email").description("Send a test message from a verified domain to the account owner.").argument("<domainId>", "Domain ID.").action(async (domainId) => {
1232
+ await run(rt, async () => {
1233
+ const client = createClient(program.opts());
1234
+ printJson(rt, await client.sendEmailDomainTestEmail(domainId));
1235
+ });
1236
+ });
1237
+ domains.command("dmarc").description("Get the DMARC aggregate-report summary for a domain.").argument("<domainId>", "Domain ID.").option("--days <n>", "Reporting window in days.", parseNumber).option("--top-sources <n>", "How many sending sources to include.", parseNumber).action(async (domainId, opts) => {
1238
+ await run(rt, async () => {
1239
+ const client = createClient(program.opts());
1240
+ const o = {};
1241
+ if (opts.days !== void 0) o.days = opts.days;
1242
+ if (opts.topSources !== void 0) o.topSources = opts.topSources;
1243
+ printJson(rt, await client.getDmarcSummary(domainId, o));
1244
+ });
1245
+ });
1246
+ const blocked = email.command("blocked").description("Inbound email sender blocklist.");
1247
+ withOffsetListOptions(
1248
+ blocked.command("list").description("List blocked inbound senders (newest first) and the account's auto-block mode.")
1249
+ ).action(async (opts) => {
1250
+ await run(rt, async () => {
1251
+ const client = createClient(program.opts());
1252
+ printJson(rt, await client.listBlockedEmailSenders(offsetListOpts(opts)));
1253
+ });
1254
+ });
1255
+ blocked.command("add").description("Block an inbound sender address or domain.").requiredOption("--sender-email <email>", "Sender address, or the domain when --match-type is 'domain'.").option("--match-type <type>", "'address' or 'domain'.", "address").option("--note <text>", "Why the sender was blocked.").action(async (opts) => {
1256
+ await run(rt, async () => {
1257
+ const client = createClient(program.opts());
1258
+ const body = {
1259
+ sender_email: opts.senderEmail,
1260
+ match_type: opts.matchType
1261
+ };
1262
+ if (opts.note !== void 0) body.note = opts.note;
1263
+ printJson(rt, await client.blockEmailSender(body));
1264
+ });
1265
+ });
1266
+ blocked.command("remove").description("Unblock a sender.").argument("<blockedId>", "Blocklist entry ID.").action(async (blockedId) => {
1267
+ await run(rt, async () => {
1268
+ const client = createClient(program.opts());
1269
+ await client.unblockEmailSender(blockedId);
1270
+ printJson(rt, { ok: true });
1271
+ });
1272
+ });
1273
+ blocked.command("auto-block-mode").description("Set whether a governance BLOCK on an authenticated sender auto-adds them to the blocklist.").argument("<mode>", "One of 'disabled', 'input', or 'input_and_output'.").action(async (mode) => {
1274
+ await run(rt, async () => {
1275
+ const client = createClient(program.opts());
1276
+ printJson(rt, await client.setAutoBlockMode({ mode }));
1277
+ });
1278
+ });
1279
+ const inbound = email.command("inbound").description("Inbound email health and queue control.");
1280
+ inbound.command("status").description("Get inbound-email quota usage, pause state, and queued-run counts.").action(async () => {
1281
+ await run(rt, async () => {
1282
+ const client = createClient(program.opts());
1283
+ printJson(rt, await client.getInboundEmailStatus());
1284
+ });
1285
+ });
1286
+ inbound.command("rejections").description("List recently rejected inbound emails and why they were rejected.").option("--agent-id <id>", "Restrict to one agent.").option("--limit <n>", "Maximum rejections to return.", parseNumber).action(async (opts) => {
1287
+ await run(rt, async () => {
1288
+ const client = createClient(program.opts());
1289
+ const o = {};
1290
+ if (opts.agentId !== void 0) o.agentId = opts.agentId;
1291
+ if (opts.limit !== void 0) o.limit = opts.limit;
1292
+ printJson(rt, await client.listInboundEmailRejections(o));
1293
+ });
1294
+ });
1295
+ inbound.command("cancel-queued").description("Fail all of the account's queued (over-quota) inbound-email runs at once.").action(async () => {
1296
+ await run(rt, async () => {
1297
+ const client = createClient(program.opts());
1298
+ printJson(rt, await client.cancelQueuedEmailRuns());
1299
+ });
1300
+ });
1301
+ inbound.command("resume").description("Manually lift the account-wide inbound-email pause.").action(async () => {
1302
+ await run(rt, async () => {
1303
+ const client = createClient(program.opts());
1304
+ printJson(rt, await client.resumeInboundEmail());
1305
+ });
1306
+ });
1307
+ const optouts = email.command("optouts").description("Recipients who opted out of agent email.");
1308
+ withOffsetListOptions(
1309
+ optouts.command("list").description("List agent-email opt-outs.").option("--agent-id <id>", "Restrict to one agent.")
1310
+ ).action(async (opts) => {
1311
+ await run(rt, async () => {
1312
+ const client = createClient(program.opts());
1313
+ const o = offsetListOpts(opts);
1314
+ if (opts.agentId !== void 0) o.agentId = opts.agentId;
1315
+ printJson(rt, await client.listAgentEmailOptOuts(o));
1316
+ });
1317
+ });
1318
+ optouts.command("remove").description("Remove an opt-out so the recipient can receive agent email again.").argument("<optoutId>", "Opt-out ID.").action(async (optoutId) => {
1319
+ await run(rt, async () => {
1320
+ const client = createClient(program.opts());
1321
+ await client.removeAgentEmailOptOut(optoutId);
1322
+ printJson(rt, { ok: true });
1323
+ });
1324
+ });
1325
+ }
1326
+
1327
+ // src/commands/account.ts
1328
+ function register11(program, rt) {
1329
+ program.command("me").description("Show the authenticated user's account ID and organization memberships.").action(async () => {
1330
+ await run(rt, async () => {
1331
+ const client = createClient(program.opts());
1332
+ printJson(rt, await client.getMe());
1333
+ });
1334
+ });
1335
+ const version = program.command("api-version").description("Read or pin the account's dated API version.");
1336
+ version.command("get").description(
1337
+ "Show the version a request resolves to. Reflects --api-version when passed, otherwise the account pin, otherwise the default."
1338
+ ).action(async () => {
1339
+ await run(rt, async () => {
1340
+ const client = createClient(program.opts());
1341
+ printJson(rt, await client.getApiVersion());
1342
+ });
1343
+ });
1344
+ version.command("set").description("Pin the account to a dated API version. Affects every client, not just this CLI.").argument("<date>", "API version as YYYY-MM-DD.").action(async (date) => {
1345
+ await run(rt, async () => {
1346
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
1347
+ throw new Error(`Expected an API version as YYYY-MM-DD, got "${date}".`);
1348
+ }
1349
+ const client = createClient(program.opts());
1350
+ printJson(rt, await client.updateApiVersion(date));
1351
+ });
1352
+ });
1353
+ version.command("clear").description("Remove the account's version pin, reverting to the default version.").action(async () => {
1354
+ await run(rt, async () => {
1355
+ const client = createClient(program.opts());
1356
+ printJson(rt, await client.updateApiVersion(null));
1357
+ });
1358
+ });
1359
+ }
1360
+
1361
+ // src/commands/models.ts
1362
+ function register12(program, rt) {
1098
1363
  const models = program.command("models").description("Models, model alerts, recommendations, and playground experiments.");
1099
- models.command("list").description("List models grouped by provider.").option("--provider <provider>", "Filter by provider name.").option("--supports-tool-use", "Only models that support tool use.").option("--supports-thinking", "Only models that support thinking.").action(async (opts) => {
1364
+ models.command("list").description("List models grouped by provider.").option("--provider <provider>", "Filter by provider name.").option("--supports-tool-use", "Only models that support tool use.").option("--supports-thinking", "Only models that support thinking.").option("--supports-input-media <media>", "Only models accepting this input modality (e.g. image, audio).").option("--supports-output-media <media>", "Only models producing this output modality (e.g. image, video).").action(async (opts) => {
1100
1365
  await run(rt, async () => {
1101
1366
  const client = createClient(program.opts());
1102
1367
  const o = {};
1103
1368
  if (opts.provider !== void 0) o.provider = opts.provider;
1104
1369
  if (opts.supportsToolUse !== void 0) o.supportsToolUse = opts.supportsToolUse;
1105
1370
  if (opts.supportsThinking !== void 0) o.supportsThinking = opts.supportsThinking;
1371
+ if (opts.supportsInputMedia !== void 0) o.supportsInputMedia = opts.supportsInputMedia;
1372
+ if (opts.supportsOutputMedia !== void 0) o.supportsOutputMedia = opts.supportsOutputMedia;
1106
1373
  printJson(rt, await client.listModels(o));
1107
1374
  });
1108
1375
  });
1376
+ models.command("tiers").description("Show each media-generation modality and tier with its model and cost.").action(async () => {
1377
+ await run(rt, async () => {
1378
+ const client = createClient(program.opts());
1379
+ printJson(rt, await client.getGenerationTiers());
1380
+ });
1381
+ });
1109
1382
  models.command("get").description("Get full details for a specific model.").argument("<modelId>", "Model ID.").action(async (modelId) => {
1110
1383
  await run(rt, async () => {
1111
1384
  const client = createClient(program.opts());
@@ -1113,7 +1386,7 @@ function register10(program, rt) {
1113
1386
  });
1114
1387
  });
1115
1388
  const alerts = models.command("alerts").description("Model alerts.");
1116
- alerts.command("list").description("List model alerts.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (opts) => {
1389
+ alerts.command("list").description("List model alerts.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (opts) => {
1117
1390
  await run(rt, async () => {
1118
1391
  const client = createClient(program.opts());
1119
1392
  printJson(rt, await client.listModelAlerts(listOpts(opts)));
@@ -1146,15 +1419,15 @@ function register10(program, rt) {
1146
1419
  });
1147
1420
  });
1148
1421
  const experiments = models.command("experiments").description("Model playground experiments.");
1149
- experiments.command("list").description("List model playground experiments.").option("--days <n>", "Filter to last N days.", (v) => Number(v)).option("--start-date <date>", "Start date (ISO 8601).").option("--end-date <date>", "End date (ISO 8601).").option("--limit <n>", "Page size.", (v) => Number(v)).option("--offset <n>", "Offset.", (v) => Number(v)).action(async (opts) => {
1422
+ withOffsetListOptions(
1423
+ experiments.command("list").description("List model playground experiments.").option("--days <n>", "Filter to last N days.", parseNumber).option("--start-date <date>", "Start date (ISO 8601).").option("--end-date <date>", "End date (ISO 8601).")
1424
+ ).action(async (opts) => {
1150
1425
  await run(rt, async () => {
1151
1426
  const client = createClient(program.opts());
1152
- const o = {};
1427
+ const o = offsetListOpts(opts);
1153
1428
  if (opts.days !== void 0) o.days = opts.days;
1154
1429
  if (opts.startDate !== void 0) o.startDate = opts.startDate;
1155
1430
  if (opts.endDate !== void 0) o.endDate = opts.endDate;
1156
- if (opts.limit !== void 0) o.limit = opts.limit;
1157
- if (opts.offset !== void 0) o.offset = opts.offset;
1158
1431
  printJson(rt, await client.listExperiments(o));
1159
1432
  });
1160
1433
  });
@@ -1187,8 +1460,9 @@ function register10(program, rt) {
1187
1460
  }
1188
1461
 
1189
1462
  // src/commands/search.ts
1190
- function register11(program, rt) {
1191
- program.command("search").description("Search across Seclai resources.").requiredOption("--query <text>", "Search query text.").option("--limit <n>", "Max results.", (v) => Number(v)).option("--entity-type <type>", "Filter by entity type (e.g. agent, source, knowledge_base, memory_bank).").action(async (opts) => {
1463
+ import { Option } from "commander";
1464
+ function register13(program, rt) {
1465
+ program.command("search").description("Search across Seclai resources.").requiredOption("--query <text>", "Search query text.").option("--limit <n>", "Max results.", parseNumber).option("--entity-type <type>", "Filter by entity type (e.g. agent, source, knowledge_base, memory_bank).").action(async (opts) => {
1192
1466
  await run(rt, async () => {
1193
1467
  const client = createClient(program.opts());
1194
1468
  const o = { query: opts.query };
@@ -1197,10 +1471,20 @@ function register11(program, rt) {
1197
1471
  printJson(rt, await client.search(o));
1198
1472
  });
1199
1473
  });
1474
+ const docs = program.command("docs").description("Seclai documentation.");
1475
+ docs.command("search").description("Search the Seclai documentation.").requiredOption("--query <text>", "Search query text.").addOption(new Option("--mode <mode>", "Search mode.").choices(["keyword", "semantic"])).option("--limit <n>", "Max results.", parseNumber).action(async (opts) => {
1476
+ await run(rt, async () => {
1477
+ const client = createClient(program.opts());
1478
+ const o = { query: opts.query };
1479
+ if (opts.mode !== void 0) o.mode = opts.mode;
1480
+ if (opts.limit !== void 0) o.limit = opts.limit;
1481
+ printJson(rt, await client.searchDocs(o));
1482
+ });
1483
+ });
1200
1484
  }
1201
1485
 
1202
1486
  // src/commands/ai.ts
1203
- function register12(program, rt) {
1487
+ function register14(program, rt) {
1204
1488
  const ai = program.command("ai").description("Top-level AI assistant.");
1205
1489
  ai.command("feedback").description("Submit AI feedback.").option("--json <json>", "Feedback body JSON.").option("--json-file <path>", "Feedback body JSON file.").action(async (opts) => {
1206
1490
  await run(rt, async () => {
@@ -1278,73 +1562,137 @@ function register12(program, rt) {
1278
1562
  import { existsSync, statSync } from "fs";
1279
1563
  import { mkdir, writeFile } from "fs/promises";
1280
1564
  import { dirname, join } from "path";
1281
- var SKILL_MD = `---
1565
+ var SKILL_FILES = [
1566
+ { name: "SKILL.md", content: `---
1282
1567
  name: seclai-cli
1283
1568
  description: >-
1284
1569
  Manage Seclai agents, knowledge bases, sources, memory banks, evaluations,
1285
- solutions, governance, alerts, and more via the CLI. Use when working with
1286
- the Seclai platform or when the user mentions Seclai CLI commands.
1570
+ solutions, governance, alerts, agent email, and models via the CLI. Use when
1571
+ working with the Seclai platform or when the user mentions Seclai CLI commands.
1287
1572
  ---
1288
1573
 
1289
1574
  # Seclai CLI
1290
1575
 
1291
- The Seclai CLI (\`seclai\` / \`npx @seclai/cli\`) manages agents, knowledge bases, sources, memory banks, evaluations, solutions, governance, alerts, and more from the terminal.
1576
+ The Seclai CLI (\`seclai\` / \`npx @seclai/cli\`) manages agents, knowledge bases,
1577
+ sources, memory banks, evaluations, solutions, governance, alerts, agent email,
1578
+ and models from the terminal.
1292
1579
 
1293
- All commands output JSON to stdout. Pipe into \`jq\` for filtering.
1580
+ Every command writes JSON to stdout. Pipe into \`jq\` for filtering. Errors go to
1581
+ stderr and set a non-zero exit code, so \`set -e\` scripts fail as expected.
1582
+
1583
+ **Find the commands for a task in the map below, then read that reference file.**
1584
+ Only this page is loaded up front; the references are read on demand.
1294
1585
 
1295
1586
  ## Quick start
1296
1587
 
1297
1588
  \`\`\`bash
1298
- # authenticate
1299
1589
  export SECLAI_API_KEY="sk-..."
1300
1590
 
1301
- # create an agent
1302
1591
  seclai agents create --json '{"name":"My Agent","description":"QA chatbot"}'
1303
-
1304
- # configure steps via AI assistant
1305
1592
  seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot that uses a knowledge base"
1306
-
1307
- # accept the generated plan
1308
1593
  seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1309
-
1310
- # run the agent
1311
1594
  seclai agents run <agentId> --json '{"input":"How do I reset my password?"}' --stream
1312
-
1313
- # list runs
1314
1595
  seclai agents runs list <agentId>
1315
1596
  \`\`\`
1316
1597
 
1598
+ ## Command map
1599
+
1600
+ | Group | What it covers | Reference |
1601
+ | --- | --- | --- |
1602
+ | \`agents\` | Agents, runs, definitions, export/import, input uploads, triggers, agent AI | [references/agents.md](references/agents.md) |
1603
+ | \`sources\` \`contents\` \`kb\` \`memory\` | Sources and uploads, exports, embedding migration, indexed content, knowledge bases, memory banks | [references/knowledge.md](references/knowledge.md) |
1604
+ | \`evals\` | Evaluation criteria, results, runs, agent-level summaries | [references/evaluations.md](references/evaluations.md) |
1605
+ | \`solutions\` \`governance\` | Solutions, resource links, conversations, solution and governance AI | [references/solutions.md](references/solutions.md) |
1606
+ | \`alerts\` | Alerts, alert configurations, organization preferences | [references/alerts.md](references/alerts.md) |
1607
+ | \`email\` | Agent email: sending domains, inbound blocklist, inbound health, opt-outs | [references/email.md](references/email.md) |
1608
+ | \`models\` | Model catalog, generation tiers, model alerts, recommendations, playground experiments | [references/models.md](references/models.md) |
1609
+ | \`auth\` \`configure\` \`api-version\` \`mcp\` \`skills\` \`completion\` | Authentication, profiles, API version pinning, editor integration | [references/setup.md](references/setup.md) |
1610
+ | \`ai\` | Top-level AI assistant for knowledge bases, sources, solutions and memory | [references/ai-assistant.md](references/ai-assistant.md) |
1611
+
1612
+ Cross-cutting topics: [streaming and event modes](references/streaming.md),
1613
+ [file uploads](references/uploads.md).
1614
+
1317
1615
  ## Authentication
1318
1616
 
1319
- Set \`SECLAI_API_KEY\` env var or pass \`--api-key <key>\`.
1320
- Override the API URL with \`SECLAI_API_URL\` (default: https://api.seclai.com).
1617
+ Two modes:
1618
+
1619
+ 1. **API key** \u2014 set \`SECLAI_API_KEY\`, or pass \`--api-key <key>\`.
1620
+ 2. **SSO** \u2014 \`seclai auth login\` for browser-based OAuth2/PKCE. Tokens are cached
1621
+ locally and refreshed automatically.
1622
+
1623
+ Override the API host with \`SECLAI_API_URL\` (default \`https://api.seclai.com\`).
1321
1624
 
1322
1625
  ## Global options
1323
1626
 
1324
1627
  \`\`\`bash
1325
- --api-key <key> # Seclai API key (or set SECLAI_API_KEY)
1326
- --compact # Output compact single-line JSON
1327
- -V, --version # Print version
1628
+ --api-key <key> # or set SECLAI_API_KEY
1629
+ --profile <name> # SSO profile (or SECLAI_PROFILE, default 'default')
1630
+ --account-id <id> # multi-org targeting (X-Account-Id header)
1631
+ --config-dir <path> # or SECLAI_CONFIG_DIR, default ~/.seclai
1632
+ --api-version <date> # or SECLAI_API_VERSION; see below
1633
+ --allow-unknown-api-version # send a version this CLI was not built against
1634
+ --compact # single-line JSON
1635
+ -V, --version
1636
+ \`\`\`
1637
+
1638
+ ## API versions
1639
+
1640
+ The API is versioned by date, and a version can change a response's shape \u2014 a
1641
+ bare array becoming \`{data, pagination}\`, for instance. **The CLI sends no
1642
+ version header by default**, so upgrading it never changes what a command
1643
+ prints. Opt in per invocation, or pin the account:
1644
+
1645
+ \`\`\`bash
1646
+ seclai api-version get # what does a request resolve to?
1647
+ seclai --api-version 2026-07-27 alerts list # this invocation only
1648
+ seclai api-version set 2026-07-27 # every client on the account
1649
+ seclai api-version clear
1328
1650
  \`\`\`
1329
1651
 
1652
+ An \`--api-version\` this CLI was not built against is rejected, because a newer
1653
+ version can reshape a response the CLI would then misread. Pass
1654
+ \`--allow-unknown-api-version\` to send it anyway. \`api-version set\` takes a
1655
+ \`YYYY-MM-DD\` date and rejects anything else, because the pin applies to every
1656
+ client on the account.
1657
+
1658
+ \`--api-key\`, \`--profile\`, \`--account-id\` and \`--config-dir\` reject an empty
1659
+ value. A shell expanding an unset variable passes \`""\`, which the SDK's
1660
+ credential chain discards, so each would silently resolve elsewhere \u2014 a
1661
+ different identity, another account's cached tokens, or the default org. Guard
1662
+ the flag rather than the value: \`seclai \${KEY:+--api-key "$KEY"} agents list\`.
1663
+
1664
+ An empty \`--api-version\` is accepted with a warning, since it costs only the
1665
+ version header; a future release will reject it too.
1666
+
1330
1667
  ## Common patterns
1331
1668
 
1332
- ### JSON input
1333
- Most create/update commands accept \`--json '{"key":"value"}'\` or \`--json-file path.json\`.
1334
- Use \`--json -\` or \`--json-file -\` to read from stdin.
1669
+ **JSON input.** Most create/update commands take \`--json '{"key":"value"}'\` or
1670
+ \`--json-file path.json\`. Use \`-\` as the value to read from stdin.
1671
+
1672
+ **AI shorthand.** AI generation commands accept \`--user-input <text>\` in place of
1673
+ \`--json '{"user_input":"<text>"}'\`.
1674
+
1675
+ **Pagination.** List commands take \`--page <n>\` and \`--limit <n>\`; some add
1676
+ \`--sort <field>\` and \`--order asc|desc\`. A few endpoints paginate by offset
1677
+ instead and take \`--limit\` / \`--offset\`.
1335
1678
 
1336
- ### AI assistant shorthand
1337
- AI generation commands accept \`--user-input <text>\` as shorthand for \`--json '{"user_input":"<text>"}'\`.
1679
+ **Uploads.** Upload commands take \`--file <path>\`, plus optional \`--title\`,
1680
+ \`--metadata '{"k":"v"}'\`, \`--metadata-file\`, \`--file-name\` and \`--mime-type\`.
1338
1681
 
1339
- ### Pagination
1340
- List commands support \`--page <n>\` and \`--limit <n>\`. Some also support \`--sort <field>\` and \`--order asc|desc\`.
1682
+ ## Search and account
1341
1683
 
1342
- ### File uploads
1343
- Upload commands accept \`--file <path>\` (required), plus optional \`--title\`, \`--metadata '{"k":"v"}'\`, \`--metadata-file path.json\`, \`--file-name\`, \`--mime-type\`.
1684
+ \`\`\`bash
1685
+ seclai search --query "deployment guide" [--limit N] [--entity-type <type>]
1686
+ seclai docs search --query "memory banks" [--mode keyword|semantic] [--limit N]
1687
+ seclai me # account ID and organization memberships
1688
+ \`\`\`
1689
+ ` },
1690
+ { name: "references/agents.md", content: `# Agents
1344
1691
 
1345
- ## Commands
1692
+ Agents, their runs, definitions, export/import, input uploads, triggers, and the
1693
+ agent AI assistant.
1346
1694
 
1347
- ### Agents
1695
+ ## CRUD and lifecycle
1348
1696
 
1349
1697
  \`\`\`bash
1350
1698
  seclai agents list [--page N] [--limit N]
@@ -1352,353 +1700,636 @@ seclai agents create --json '{"name":"My Agent","description":"..."}'
1352
1700
  seclai agents get <agentId>
1353
1701
  seclai agents update <agentId> --json '{"name":"Renamed"}'
1354
1702
  seclai agents delete <agentId>
1703
+
1704
+ # pause across every trigger path (API, schedule, email, sub-agent calls)
1705
+ seclai agents disable <agentId>
1706
+ seclai agents enable <agentId>
1707
+
1708
+ # which live agents call this one via a call_agent step?
1709
+ seclai agents callers <agentId>
1710
+ \`\`\`
1711
+
1712
+ ## Triggers
1713
+
1714
+ \`\`\`bash
1715
+ # alias, sender allowlist and inbound-handling flags for an EMAIL_RECEIVED trigger
1716
+ seclai agents triggers email-config <agentId> <triggerId> --json '{"alias":"support"}'
1355
1717
  \`\`\`
1356
1718
 
1357
- ### Running agents
1719
+ ## Running agents
1720
+
1721
+ Four modes: basic, streaming, NDJSON events, and polling. See
1722
+ [streaming.md](streaming.md) for event shapes and filtering.
1358
1723
 
1359
1724
  \`\`\`bash
1360
1725
  # simple run \u2014 returns the final result
1361
1726
  seclai agents run <agentId> --json '{"input":"Hello"}'
1362
1727
 
1363
- # stream \u2014 wait for completion via SSE, print final result
1728
+ # stream \u2014 wait for completion via SSE, print the final result
1364
1729
  seclai agents run <agentId> --json '{"input":"Hello"}' --stream [--timeout-ms 60000]
1365
1730
 
1366
- # events \u2014 stream individual SSE events as NDJSON lines
1367
- # --output: full (entire event), data (event data only), status (status events only)
1368
- # --event-filter: comma-separated event types to include, e.g. "status,data"
1731
+ # events \u2014 every SSE event as an NDJSON line
1732
+ # --output: full (entire event), data (event data only), status (one-line summary)
1733
+ # --event-filter: comma-separated event types, e.g. "status,data"
1369
1734
  seclai agents run <agentId> --json '{"input":"Hello"}' --events [--output full|data|status] [--event-filter "status,data"]
1370
1735
 
1371
- # poll \u2014 poll for completion
1736
+ # poll \u2014 submit, then poll until complete
1372
1737
  seclai agents run <agentId> --json '{"input":"Hello"}' --poll [--poll-interval-ms 2000] [--include-step-outputs]
1373
1738
  \`\`\`
1374
1739
 
1375
- ### Agent runs
1740
+ ## Runs
1376
1741
 
1377
1742
  \`\`\`bash
1378
1743
  seclai agents runs list <agentId> [--page N] [--limit N] [--status <status>]
1379
1744
  seclai agents runs get <runId> [--include-step-outputs]
1380
- seclai agents runs delete <runId>
1381
1745
  seclai agents runs cancel <runId>
1746
+ seclai agents runs delete <runId> # deprecated alias for \`runs cancel\`; the API has no delete-a-run operation
1382
1747
  seclai agents runs search --json '{"query":"..."}'
1383
1748
  seclai agents runs eval-results <agentId> <runId> [--page N] [--limit N]
1749
+
1750
+ # Download a file emitted by a run step. attachmentId is the URL-safe-base64
1751
+ # storage_key from run output manifests or webhooks.
1752
+ seclai agents runs download-attachment <runId> <attachmentId> [--download-name <name>] [--output <path>]
1384
1753
  \`\`\`
1385
1754
 
1386
- ### Agent definitions
1755
+ Without \`--output\`, raw bytes go to stdout \u2014 redirect to a file rather than
1756
+ letting them hit the terminal.
1757
+
1758
+ ## Definitions
1387
1759
 
1388
1760
  \`\`\`bash
1389
1761
  seclai agents def get <agentId>
1390
- seclai agents def update <agentId> --json '{"steps":[{"step_type":"llm","config":{...}}]}'
1762
+ seclai agents def update <agentId> --json '{"steps":[{"step_type":"llm","config":{}}]}'
1391
1763
  \`\`\`
1392
1764
 
1393
- ### Agent input uploads
1765
+ ## Export and import
1394
1766
 
1395
1767
  \`\`\`bash
1768
+ # portable JSON snapshot of an agent definition
1769
+ seclai agents export <agentId> [--no-download]
1770
+
1771
+ # Validate an agent_definition payload before importing \u2014 no writes.
1772
+ # Reports counts and any unresolved_refs (knowledge bases, memory banks, source
1773
+ # connections or sub-agents that do not exist in this account).
1774
+ seclai agents export <agentId> \\
1775
+ | jq '{agent_definition: .}' \\
1776
+ | seclai agents preview-import --json-file -
1777
+
1778
+ # Import via \`agents create\` (or \`agents update\`) with agent_definition set to
1779
+ # the export payload, and entity_remap mapping unresolved source UUIDs to target
1780
+ # UUIDs taken from preview-import's unresolved_refs[*].alternatives.
1781
+ seclai agents create --json '{"name":"Imported","trigger_type":"dynamic_input","agent_definition":{},"entity_remap":{}}'
1782
+ \`\`\`
1783
+
1784
+ ## Input uploads
1785
+
1786
+ \`\`\`bash
1787
+ # What files (if any) does this agent expect? requires_uploads reports whether it
1788
+ # accepts files; the agent block lists the names, indexes and patterns a run-time
1789
+ # batch must satisfy. Call this before staging uploads.
1790
+ seclai agents attachment-references <agentId>
1791
+
1396
1792
  seclai agents upload-input <agentId> --file ./input.pdf [--file-name name] [--mime-type type]
1397
1793
  seclai agents input-status <agentId> <uploadId>
1398
1794
  \`\`\`
1399
1795
 
1400
- ### Agent AI assistant
1796
+ ## Agent AI assistant
1401
1797
 
1402
1798
  \`\`\`bash
1403
1799
  seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot"
1404
1800
  seclai agents ai step-config <agentId> --json '{"step_type":"llm","user_input":"Configure the LLM step"}'
1405
- seclai agents ai history <agentId>
1801
+
1802
+ # --step-type is required; the API rejects the request without it
1803
+ seclai agents ai history <agentId> --step-type llm [--step-id <id>] [--limit N] [--offset N]
1804
+
1406
1805
  seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1407
1806
  \`\`\`
1408
1807
 
1409
- ### Sources
1808
+ ## Example: knowledge-base-backed agent
1410
1809
 
1411
1810
  \`\`\`bash
1412
- seclai sources list [--page N] [--limit N] [--sort field] [--order asc|desc] [--account-id id]
1413
- seclai sources create --json '{"name":"Docs","description":"Product documentation"}'
1414
- seclai sources get <sourceId>
1415
- seclai sources update <sourceId> --json '{"name":"Updated Docs"}'
1416
- seclai sources delete <sourceId>
1811
+ seclai kb create --json '{"name":"Support KB","description":"Customer support articles"}'
1812
+ seclai agents create --json '{"name":"Support Bot","description":"Answers customer questions"}'
1813
+ seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot that searches the Support KB"
1814
+ seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1815
+ seclai agents run <agentId> --json '{"input":"How do I reset my password?"}' --stream
1417
1816
  \`\`\`
1418
1817
 
1419
- ### Source uploads
1818
+ ## Example: memory-powered agent
1420
1819
 
1421
1820
  \`\`\`bash
1422
- seclai sources upload <sourceId> --file ./doc.pdf [--title "My Doc"] [--metadata '{"category":"docs"}'] [--file-name name] [--mime-type type]
1423
- seclai sources upload-text <sourceId> --json '{"text":"Article content here...","title":"My Article"}'
1821
+ seclai memory create --json '{"name":"User Preferences","type":"general"}'
1822
+ seclai agents create --json '{"name":"Personal Assistant","description":"Remembers user preferences"}'
1823
+ seclai agents ai gen-steps <agentId> --user-input "Build a chat agent that remembers user preferences. Use general memory bank <memoryBankId>"
1824
+ seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1424
1825
  \`\`\`
1826
+ ` },
1827
+ { name: "references/ai-assistant.md", content: `# Top-level AI assistant
1425
1828
 
1426
- ### Source exports
1829
+ \`seclai ai\` creates resources from a natural-language description, without
1830
+ starting from a solution or an agent. The domain-scoped assistants \u2014
1831
+ \`agents ai\`, \`memory ai\`, \`solutions ai\`, \`governance ai\` \u2014 live with their
1832
+ resources.
1427
1833
 
1428
1834
  \`\`\`bash
1429
- seclai sources exports list <sourceId> [--page N] [--limit N]
1430
- seclai sources exports create <sourceId> --json '{"format":"jsonl"}'
1431
- seclai sources exports get <sourceId> <exportId>
1432
- seclai sources exports cancel <sourceId> <exportId>
1433
- seclai sources exports delete <sourceId> <exportId>
1434
- seclai sources exports download <sourceId> <exportId>
1435
- seclai sources exports estimate <sourceId> --json '{"format":"jsonl"}'
1436
- \`\`\`
1835
+ seclai ai kb --user-input "Create a support knowledge base"
1836
+ seclai ai source --user-input "Create a documentation source"
1837
+ seclai ai solution --user-input "Build a customer support solution"
1838
+ seclai ai memory --user-input "Create a conversation memory bank"
1437
1839
 
1438
- ### Embedding migration
1840
+ seclai ai memory-history
1841
+ seclai ai accept <conversationId> --json '{"accepted":true}'
1842
+ seclai ai decline <conversationId>
1843
+ seclai ai memory-accept <conversationId> --json '{"accepted":true}'
1439
1844
 
1440
- \`\`\`bash
1441
- seclai sources migration get <sourceId>
1442
- seclai sources migration start <sourceId> --json '{"target_model":"text-embedding-3-large"}'
1443
- seclai sources migration cancel <sourceId>
1845
+ seclai ai feedback --json '{"feedback":"The response was helpful"}'
1444
1846
  \`\`\`
1445
1847
 
1446
- ### Contents (indexed content)
1848
+ ## The generate-then-accept cycle
1849
+
1850
+ Every assistant command returns a *proposal* with a conversation ID. Nothing is
1851
+ created until you accept it:
1447
1852
 
1448
1853
  \`\`\`bash
1449
- seclai contents get <contentVersionId> [--start N] [--end N]
1450
- seclai contents delete <contentVersionId>
1451
- seclai contents upload <contentVersionId> --file ./updated.pdf [--title "Title"] [--file-name name] [--mime-type type]
1452
- seclai contents replace-text <contentVersionId> --json '{"text":"Replacement text","title":"Updated"}'
1453
- seclai contents embeddings <contentVersionId> [--page N] [--limit N]
1854
+ seclai ai kb --user-input "Create a support knowledge base"
1855
+ # read the proposal, note the conversation id
1856
+ seclai ai accept <conversationId> --json '{"accepted":true}'
1454
1857
  \`\`\`
1455
1858
 
1456
- ### Knowledge bases
1859
+ Memory-bank proposals have their own accept command (\`ai memory-accept\`) and
1860
+ their own history (\`ai memory-history\`); everything else uses \`ai accept\` /
1861
+ \`ai decline\`.
1862
+ ` },
1863
+ { name: "references/alerts.md", content: `# Alerts
1864
+
1865
+ Account alerts, the configurations that raise them, and per-organization
1866
+ delivery preferences.
1867
+
1868
+ Model-catalog alerts are separate \u2014 see [models.md](models.md).
1869
+
1870
+ ## Alerts
1457
1871
 
1458
1872
  \`\`\`bash
1459
- seclai kb list [--page N] [--limit N] [--sort field] [--order asc|desc]
1460
- seclai kb create --json '{"name":"Support KB","description":"Customer support articles"}'
1461
- seclai kb get <kbId>
1462
- seclai kb update <kbId> --json '{"name":"Updated KB"}'
1463
- seclai kb delete <kbId>
1873
+ seclai alerts list [--page N] [--limit N] [--status <status>]
1874
+ seclai alerts get <alertId>
1875
+ seclai alerts status <alertId> --json '{"status":"resolved"}'
1876
+ seclai alerts comment <alertId> --json '{"comment":"Fixed the issue"}'
1877
+ seclai alerts subscribe <alertId>
1878
+ seclai alerts unsubscribe <alertId>
1464
1879
  \`\`\`
1465
1880
 
1466
- ### Memory banks
1881
+ \`GET /alerts\` declares no severity filter. \`--severity\` still parses, but it is
1882
+ ignored with a warning and will be removed \u2014 it never filtered anything. Filter
1883
+ client-side instead:
1467
1884
 
1468
1885
  \`\`\`bash
1469
- seclai memory list [--page N] [--limit N] [--sort field] [--order asc|desc]
1470
- # type: "conversation" (chat history) or "general" (structured facts)
1471
- seclai memory create --json '{"name":"Chat Memory","type":"conversation"}'
1472
- seclai memory get <memoryBankId>
1473
- seclai memory update <memoryBankId> --json '{"name":"Renamed"}'
1474
- seclai memory delete <memoryBankId>
1886
+ seclai alerts list | jq '[.data[] | select(.severity == "high")]'
1475
1887
  \`\`\`
1476
1888
 
1477
- ### Memory bank utilities
1889
+ ## Alert configurations
1478
1890
 
1479
1891
  \`\`\`bash
1480
- seclai memory stats <memoryBankId>
1481
- seclai memory agents <memoryBankId>
1482
- seclai memory compact <memoryBankId>
1483
- seclai memory delete-source <memoryBankId>
1484
- seclai memory templates
1485
- seclai memory test-compaction <memoryBankId> --json '{"prompt":"Summarize the conversation"}'
1486
- seclai memory test-compaction-standalone --json '{"prompt":"Summarize the conversation"}'
1892
+ seclai alerts configs list [--page N] [--limit N]
1893
+ seclai alerts configs create --json '{"name":"Latency Alert","description":"...","threshold":5000}'
1894
+ seclai alerts configs get <configId>
1895
+ seclai alerts configs update <configId> --json '{"threshold":3000}'
1896
+ seclai alerts configs delete <configId>
1487
1897
  \`\`\`
1488
1898
 
1489
- ### Memory bank AI
1899
+ ## Organization preferences
1490
1900
 
1491
1901
  \`\`\`bash
1492
- seclai memory ai generate --user-input "Configure compaction for chat memory"
1493
- seclai memory ai last
1494
- seclai memory ai accept <conversationId> --json '{"accepted":true}'
1902
+ seclai alerts prefs list
1903
+ seclai alerts prefs update <organizationId> <alertType> --json '{"enabled":true}'
1904
+ \`\`\`
1905
+
1906
+ Preferences are per organization and per alert type, so \`update\` takes both.
1907
+ ` },
1908
+ { name: "references/email.md", content: `# Agent email
1909
+
1910
+ The domains agents send from, the inbound blocklist, inbound health, and
1911
+ recipient opt-outs.
1912
+
1913
+ Per-agent inbound configuration (alias, sender allowlist) lives on the trigger \u2014
1914
+ see \`agents triggers email-config\` in [agents.md](agents.md).
1915
+
1916
+ ## Sending domains
1917
+
1918
+ \`\`\`bash
1919
+ seclai email domains list
1920
+ seclai email domains add --kind custom --value mail.example.com [--delegated]
1921
+ seclai email domains verify <domainId> # run a DNS check now
1922
+ seclai email domains set-primary <domainId>
1923
+ seclai email domains test-email <domainId> # send a test to the account owner
1924
+ seclai email domains dmarc <domainId> [--days N] [--top-sources N]
1925
+ seclai email domains remove <domainId>
1926
+ seclai email domains use-shared # revert to agent.seclai.com
1927
+ \`\`\`
1928
+
1929
+ \`--kind\` is \`vanity\` (a subdomain of seclai.com) or \`custom\` (your own domain).
1930
+ \`add\` returns the DNS records to publish; pass \`--delegated\` when the domain's
1931
+ DNS is delegated to Seclai so those records are published for you. A domain must
1932
+ verify before \`set-primary\` will accept it.
1933
+
1934
+ ## Inbound sender blocklist
1935
+
1936
+ \`\`\`bash
1937
+ seclai email blocked list [--limit N] [--offset N]
1938
+ seclai email blocked add --sender-email spam@example.com [--note "phishing"]
1939
+ seclai email blocked add --sender-email example.com --match-type domain
1940
+ seclai email blocked remove <blockedId>
1941
+ seclai email blocked auto-block-mode disabled|input|input_and_output
1495
1942
  \`\`\`
1496
1943
 
1497
- ### Evaluations \u2014 criteria
1944
+ \`--match-type\` is \`address\` (the default) or \`domain\`. \`auto-block-mode\` controls
1945
+ whether a governance BLOCK on an authenticated sender adds them to the blocklist
1946
+ automatically.
1498
1947
 
1948
+ ## Inbound health
1949
+
1950
+ \`\`\`bash
1951
+ seclai email inbound status # quota usage, pause state, queued run counts
1952
+ seclai email inbound rejections [--agent-id <id>] [--limit N]
1953
+ seclai email inbound cancel-queued # fail every over-quota parked run at once
1954
+ seclai email inbound resume # lift the account-wide pause
1955
+ \`\`\`
1956
+
1957
+ When inbound email exceeds quota, runs park in a QUEUED state and the account
1958
+ pauses. \`status\` shows both; \`cancel-queued\` clears the backlog and \`resume\`
1959
+ lifts the pause. Check \`rejections\` first \u2014 it reports why messages were turned
1960
+ away, which is usually the more useful answer.
1961
+
1962
+ ## Recipient opt-outs
1963
+
1964
+ \`\`\`bash
1965
+ seclai email optouts list [--agent-id <id>] [--limit N] [--offset N]
1966
+ seclai email optouts remove <optoutId>
1967
+ \`\`\`
1968
+
1969
+ Removing an opt-out lets that recipient receive agent email again.
1970
+ ` },
1971
+ { name: "references/evaluations.md", content: `# Evaluations Workflow
1972
+
1973
+ ## Step 1: Create evaluation criteria for an agent
1974
+ \`\`\`bash
1975
+ seclai evals criteria create <agentId> --json '{"name":"Answer Accuracy","description":"Does the answer correctly address the question?","eval_type":"llm_judge"}'
1976
+ \`\`\`
1977
+
1978
+ ## Step 2: Find runs to evaluate
1979
+ \`\`\`bash
1980
+ # list all runs for an agent
1981
+ seclai agents runs list <agentId> --limit 10
1982
+
1983
+ # or find runs compatible with specific criteria
1984
+ seclai evals compatible-runs <criteriaId> --limit 10
1985
+ \`\`\`
1986
+
1987
+ ## Step 3: Test criteria before committing
1988
+ \`\`\`bash
1989
+ seclai evals test-draft <agentId> --json '{"criteria":{"name":"Answer Accuracy","eval_type":"llm_judge","description":"..."},"run_id":"<runId>"}'
1990
+ \`\`\`
1991
+
1992
+ ## Step 4: Create evaluation results
1993
+ \`\`\`bash
1994
+ seclai evals results create <criteriaId> --json '{"run_id":"<runId>","score":0.95}'
1995
+ \`\`\`
1996
+
1997
+ ## Step 5: Review summaries
1499
1998
  \`\`\`bash
1500
- seclai evals criteria list <agentId> [--page N] [--limit N]
1501
- seclai evals criteria create <agentId> --json '{"name":"Response Quality","description":"...","eval_type":"llm_judge"}'
1999
+ seclai evals criteria summary <criteriaId>
2000
+ seclai evals agent-results <agentId>
2001
+ seclai evals agent-runs <agentId> --limit 20
2002
+ seclai evals non-manual-summary <agentId>
2003
+ \`\`\`
2004
+
2005
+ ## Managing criteria
2006
+ \`\`\`bash
2007
+ seclai evals criteria list <agentId> [--page N] [--limit N] [--paged]
1502
2008
  seclai evals criteria get <criteriaId>
1503
- seclai evals criteria update <criteriaId> --json '{"name":"Updated Criteria"}'
2009
+ seclai evals criteria update <criteriaId> --json '{"name":"Updated Name"}'
1504
2010
  seclai evals criteria delete <criteriaId>
1505
- seclai evals criteria summary <criteriaId>
1506
2011
  \`\`\`
1507
2012
 
1508
- ### Evaluations \u2014 results & runs
2013
+ \`--paged\` wraps the results in \`{"data": [...]}\` instead of returning a bare
2014
+ array, so \`.data\` is a stable path to read whatever \`--api-version\` is in effect.
2015
+ Nothing is invented: the \`pagination\` block appears only once the API sends one,
2016
+ from \`--api-version 2026-07-27\`. Move scripts to \`.data\` first, then opt in to
2017
+ get \`.pagination\`.
1509
2018
 
2019
+ ## Viewing results
1510
2020
  \`\`\`bash
1511
2021
  seclai evals results list <criteriaId> [--page N] [--limit N]
1512
- seclai evals results create <criteriaId> --json '{"run_id":"...","score":0.9}'
1513
2022
  seclai evals compatible-runs <criteriaId> [--page N] [--limit N]
1514
- seclai evals test-draft <agentId> --json '{"criteria":{"name":"Test","eval_type":"llm_judge"},"run_id":"..."}'
1515
2023
  seclai evals agent-results <agentId> [--page N] [--limit N]
1516
2024
  seclai evals agent-runs <agentId> [--page N] [--limit N]
1517
- seclai evals non-manual-summary <agentId>
1518
2025
  \`\`\`
2026
+ ` },
2027
+ { name: "references/knowledge.md", content: `# Sources, content, knowledge bases and memory banks
1519
2028
 
1520
- ### Solutions
2029
+ The ingestion side of Seclai: where documents come from, how they are indexed,
2030
+ and the stores agents read from.
2031
+
2032
+ For upload mechanics \u2014 MIME types, size limits, metadata \u2014 see
2033
+ [uploads.md](uploads.md).
2034
+
2035
+ ## Sources
1521
2036
 
1522
2037
  \`\`\`bash
1523
- seclai solutions list [--page N] [--limit N] [--sort field] [--order asc|desc]
1524
- seclai solutions create --json '{"name":"Customer Support Solution"}'
1525
- seclai solutions get <solutionId>
1526
- seclai solutions update <solutionId> --json '{"name":"Updated"}'
1527
- seclai solutions delete <solutionId>
2038
+ seclai sources list [--page N] [--limit N] [--sort field] [--order asc|desc] [--account-id id]
2039
+ seclai sources create --json '{"name":"Docs","description":"Product documentation"}'
2040
+ seclai sources get <sourceId>
2041
+ seclai sources update <sourceId> --json '{"name":"Updated Docs"}'
2042
+ seclai sources delete <sourceId>
1528
2043
  \`\`\`
1529
2044
 
1530
- ### Solution links
2045
+ \`source\` is accepted as an alias for \`sources\`.
2046
+
2047
+ ## Source uploads
1531
2048
 
1532
2049
  \`\`\`bash
1533
- # link resources \u2014 each flag takes a JSON array of IDs
1534
- seclai solutions link <solutionId> --agents '["agentId1"]' --kb '["kbId1"]' --sources '["sourceId1"]'
1535
- seclai solutions unlink <solutionId> --agents '["agentId1"]'
2050
+ seclai sources upload <sourceId> --file ./doc.pdf [--title "My Doc"] [--metadata '{"category":"docs"}'] [--file-name name] [--mime-type type]
2051
+ seclai sources upload-text <sourceId> --json '{"text":"Article content here...","title":"My Article"}'
1536
2052
  \`\`\`
1537
2053
 
1538
- ### Solution conversations & AI
2054
+ ## Source exports
1539
2055
 
1540
2056
  \`\`\`bash
1541
- seclai solutions convos list <solutionId>
1542
- seclai solutions convos add <solutionId> --json '{"message":"How should I structure this?"}'
1543
- seclai solutions convos mark <solutionId> <conversationId> --json '{"accepted":true}'
2057
+ seclai sources exports list <sourceId> [--page N] [--limit N]
2058
+ seclai sources exports create <sourceId> --json '{"format":"jsonl"}'
2059
+ seclai sources exports get <sourceId> <exportId>
2060
+ seclai sources exports cancel <sourceId> <exportId>
2061
+ seclai sources exports delete <sourceId> <exportId>
2062
+ seclai sources exports download <sourceId> <exportId>
2063
+ seclai sources exports estimate <sourceId> --json '{"format":"jsonl"}'
2064
+ \`\`\`
1544
2065
 
1545
- seclai solutions ai generate <solutionId> --user-input "Add an FAQ source"
1546
- seclai solutions ai kb <solutionId> --user-input "Create a knowledge base for docs"
1547
- seclai solutions ai source <solutionId> --user-input "Create a file source for PDFs"
1548
- seclai solutions ai accept <solutionId> <conversationId> --json '{"accepted":true}'
1549
- seclai solutions ai decline <solutionId> <conversationId>
2066
+ \`estimate\` reports the size and cost before you commit to \`create\`.
2067
+
2068
+ ## Embedding migration
2069
+
2070
+ \`\`\`bash
2071
+ seclai sources migration get <sourceId>
2072
+ seclai sources migration start <sourceId> --json '{"target_model":"text-embedding-3-large"}'
2073
+ seclai sources migration cancel <sourceId>
1550
2074
  \`\`\`
1551
2075
 
1552
- ### Alerts
2076
+ ## Contents (indexed content)
1553
2077
 
1554
2078
  \`\`\`bash
1555
- seclai alerts list [--page N] [--limit N] [--status <status>] [--severity <severity>]
1556
- seclai alerts get <alertId>
1557
- seclai alerts status <alertId> --json '{"status":"resolved"}'
1558
- seclai alerts comment <alertId> --json '{"comment":"Fixed the issue"}'
1559
- seclai alerts subscribe <alertId>
1560
- seclai alerts unsubscribe <alertId>
2079
+ seclai contents get <contentVersionId> [--start N] [--end N]
2080
+ seclai contents delete <contentVersionId>
2081
+ seclai contents upload <contentVersionId> --file ./updated.pdf [--title "Title"] [--file-name name] [--mime-type type]
2082
+ seclai contents replace-text <contentVersionId> --json '{"text":"Replacement text","title":"Updated"}'
2083
+ seclai contents embeddings <contentVersionId> [--page N] [--limit N]
1561
2084
  \`\`\`
1562
2085
 
1563
- ### Alert configurations
2086
+ \`--start\` / \`--end\` on \`contents get\` slice the returned text by character
2087
+ offset, which is how you inspect a long document without pulling all of it.
2088
+
2089
+ ## Knowledge bases
1564
2090
 
1565
2091
  \`\`\`bash
1566
- seclai alerts configs list [--page N] [--limit N]
1567
- seclai alerts configs create --json '{"name":"Latency Alert","description":"...","threshold":5000}'
1568
- seclai alerts configs get <configId>
1569
- seclai alerts configs update <configId> --json '{"threshold":3000}'
1570
- seclai alerts configs delete <configId>
2092
+ seclai kb list [--page N] [--limit N] [--sort field] [--order asc|desc]
2093
+ seclai kb create --json '{"name":"Support KB","description":"Customer support articles"}'
2094
+ seclai kb get <kbId>
2095
+ seclai kb update <kbId> --json '{"name":"Updated KB"}'
2096
+ seclai kb delete <kbId>
1571
2097
  \`\`\`
1572
2098
 
1573
- ### Alert preferences
2099
+ ## Memory banks
1574
2100
 
1575
2101
  \`\`\`bash
1576
- seclai alerts prefs list
1577
- seclai alerts prefs update <organizationId> <alertType> --json '{"enabled":true}'
2102
+ seclai memory list [--page N] [--limit N] [--sort field] [--order asc|desc]
2103
+ # type: "conversation" (chat history) or "general" (structured facts)
2104
+ seclai memory create --json '{"name":"Chat Memory","type":"conversation"}'
2105
+ seclai memory get <memoryBankId>
2106
+ seclai memory update <memoryBankId> --json '{"name":"Renamed"}'
2107
+ seclai memory delete <memoryBankId>
1578
2108
  \`\`\`
1579
2109
 
1580
- ### Governance AI
2110
+ ### Utilities
1581
2111
 
1582
2112
  \`\`\`bash
1583
- seclai governance ai generate --user-input "Create a content safety policy"
1584
- seclai governance ai list
1585
- seclai governance ai accept <conversationId>
1586
- seclai governance ai decline <conversationId>
2113
+ seclai memory stats <memoryBankId>
2114
+ seclai memory agents <memoryBankId> # agents using this bank
2115
+ seclai memory compact <memoryBankId>
2116
+ seclai memory delete-source <memoryBankId>
2117
+ seclai memory templates
2118
+ seclai memory test-compaction <memoryBankId> --json '{"prompt":"Summarize the conversation"}'
2119
+ seclai memory test-compaction-standalone --json '{"prompt":"Summarize the conversation"}'
1587
2120
  \`\`\`
1588
2121
 
1589
- ### Model alerts
2122
+ Both \`test-compaction\` commands are dry runs \u2014 they show what compaction would
2123
+ produce without writing to the bank.
2124
+
2125
+ ### Memory bank AI
2126
+
2127
+ \`\`\`bash
2128
+ seclai memory ai generate --user-input "Configure compaction for chat memory"
2129
+ seclai memory ai last
2130
+ seclai memory ai accept <conversationId> --json '{"accepted":true}'
2131
+ \`\`\`
2132
+
2133
+ ## Example: create a source and upload content
2134
+
2135
+ \`\`\`bash
2136
+ seclai sources create --json '{"name":"Product Docs","description":"Product documentation source"}'
2137
+ # note the id from the output
2138
+ seclai sources upload <sourceId> --file ./docs.pdf --title "Product Manual" --metadata '{"version":"2.0"}'
2139
+ seclai sources get <sourceId>
2140
+ \`\`\`
2141
+ ` },
2142
+ { name: "references/models.md", content: `# Models
2143
+
2144
+ The model catalog, media-generation tiers, model-catalog alerts, recommendations
2145
+ and the playground.
2146
+
2147
+ ## Catalog
2148
+
2149
+ \`\`\`bash
2150
+ seclai models list [--provider <name>] [--supports-tool-use] [--supports-thinking]
2151
+ seclai models list [--supports-input-media <media>] [--supports-output-media <media>]
2152
+ seclai models get <modelId>
2153
+
2154
+ # each media-generation modality and tier, with its model and cost
2155
+ seclai models tiers
2156
+ \`\`\`
2157
+
2158
+ The capability flags compose, so \`--supports-tool-use --supports-thinking\`
2159
+ returns only models with both. \`--supports-input-media\` / \`--supports-output-media\`
2160
+ take a modality such as \`image\`, \`audio\` or \`video\`.
2161
+
2162
+ ## Model alerts
1590
2163
 
1591
2164
  \`\`\`bash
1592
2165
  seclai models alerts list [--page N] [--limit N]
1593
2166
  seclai models alerts mark-read <alertId>
1594
2167
  seclai models alerts mark-all-read
1595
2168
  seclai models alerts unread-count
2169
+ \`\`\`
2170
+
2171
+ These are catalog alerts \u2014 deprecations, price changes, new models \u2014 not the
2172
+ account alerts in [alerts.md](alerts.md).
2173
+
2174
+ ## Recommendations
2175
+
2176
+ \`\`\`bash
1596
2177
  seclai models recommendations <modelId>
1597
2178
  \`\`\`
1598
2179
 
1599
- ### Search
2180
+ Suggests replacements for a model, which is how you act on a deprecation alert.
2181
+
2182
+ ## Playground experiments
1600
2183
 
1601
2184
  \`\`\`bash
1602
- seclai search --query "deployment guide" [--limit N] [--entity-type <type>]
2185
+ seclai models experiments list [--days N] [--start-date <date>] [--end-date <date>] [--limit N] [--offset N]
2186
+ seclai models experiments create --json '{"model_ids":["gpt-4o"],"prompt":"Compare responses"}'
2187
+ seclai models experiments get <experimentId>
2188
+ seclai models experiments cancel <experimentId>
2189
+ seclai models experiments delete <experimentId> # soft-delete, preserves audit history
1603
2190
  \`\`\`
1604
2191
 
1605
- ### AI assistant (global)
2192
+ \`create\` takes several \`model_ids\` and runs the same prompt against each, which
2193
+ is the point \u2014 side-by-side comparison. \`cancel\` stops a running experiment;
2194
+ \`delete\` soft-deletes a finished one.
2195
+ ` },
2196
+ { name: "references/setup.md", content: `# Setup: authentication, profiles, API version, editor integration
2197
+
2198
+ ## SSO authentication
1606
2199
 
1607
2200
  \`\`\`bash
1608
- seclai ai feedback --json '{"feedback":"The response was helpful"}'
1609
- seclai ai kb --user-input "Create a support knowledge base"
1610
- seclai ai source --user-input "Create a documentation source"
1611
- seclai ai solution --user-input "Build a customer support solution"
1612
- seclai ai memory --user-input "Create a conversation memory bank"
1613
- seclai ai memory-history
1614
- seclai ai accept <conversationId> --json '{"accepted":true}'
1615
- seclai ai decline <conversationId>
1616
- seclai ai memory-accept <conversationId> --json '{"accepted":true}'
2201
+ seclai auth login [--port <port>] [--no-browser] # OAuth2 + PKCE in the browser
2202
+ seclai auth status # active profile's auth state
2203
+ seclai auth refresh # refresh the token manually
2204
+ seclai auth logout # clear cached tokens
1617
2205
  \`\`\`
1618
2206
 
1619
- ### Skills
2207
+ Tokens are cached under the config directory and refreshed automatically, so
2208
+ \`auth refresh\` is only needed to force it. An API key in \`SECLAI_API_KEY\` takes a
2209
+ different path entirely and needs none of this.
2210
+
2211
+ ## Profiles
1620
2212
 
1621
2213
  \`\`\`bash
1622
- # install skill files into AI coding tool directories (auto-detects or specify)
1623
- seclai skills install [--tool copilot|claude|cursor|windsurf|codex|kiro|cline|roo|gemini|antigravity|all] [--dir .]
2214
+ seclai configure sso [--profile-name <name>] # interactive: domain, client ID, region, account ID
2215
+ seclai configure list # every configured profile
1624
2216
  \`\`\`
1625
2217
 
1626
- ### MCP server
2218
+ Profiles live in \`~/.seclai/config\` (override with \`--config-dir\` or
2219
+ \`SECLAI_CONFIG_DIR\`). Select one per invocation with \`--profile <name>\`, or set
2220
+ \`SECLAI_PROFILE\`.
2221
+
2222
+ ## API version
1627
2223
 
1628
2224
  \`\`\`bash
1629
- # configure MCP server access in AI coding tool config files
2225
+ seclai api-version get # what version does a request resolve to?
2226
+ seclai api-version set <date> # pin the account \u2014 affects every client
2227
+ seclai api-version clear # remove the pin
2228
+ \`\`\`
2229
+
2230
+ \`set\` and \`clear\` change the account, not just this CLI. To affect only your own
2231
+ invocation, use the \`--api-version\` global option instead.
2232
+
2233
+ ## MCP server
2234
+
2235
+ \`\`\`bash
2236
+ # write Seclai MCP server config into AI coding tool config files
1630
2237
  seclai mcp configure --key <apiKey> [--target claude-code|cursor|claude-desktop|windsurf|all] [--dir .]
1631
2238
 
1632
- # show the MCP config JSON snippet
2239
+ # print the config JSON for manual setup
1633
2240
  seclai mcp show [--key <apiKey>]
1634
2241
  \`\`\`
1635
2242
 
1636
- ## Example: Create a source and upload content
2243
+ ## Skill files
1637
2244
 
1638
2245
  \`\`\`bash
1639
- seclai sources create --json '{"name":"Product Docs","description":"Product documentation source"}'
1640
- # note the id from the output
1641
- seclai sources upload <sourceId> --file ./docs.pdf --title "Product Manual" --metadata '{"version":"2.0"}'
1642
- seclai sources get <sourceId>
2246
+ # install these skill files into AI coding tool directories
2247
+ seclai skills install [--tool copilot|claude|cursor|windsurf|codex|kiro|cline|roo|gemini|antigravity|all] [--dir .]
1643
2248
  \`\`\`
1644
2249
 
1645
- ## Example: Set up a knowledge base with an agent
2250
+ With no \`--tool\`, the target is detected from the directory structure.
2251
+
2252
+ ## Shell completion
1646
2253
 
1647
2254
  \`\`\`bash
1648
- seclai kb create --json '{"name":"Support KB","description":"Customer support articles"}'
1649
- seclai agents create --json '{"name":"Support Bot","description":"Answers customer questions"}'
1650
- seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot that searches the Support KB"
1651
- seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1652
- seclai agents run <agentId> --json '{"input":"How do I reset my password?"}' --stream
2255
+ seclai completion bash # eval "$(seclai completion bash)" in ~/.bashrc
2256
+ seclai completion zsh # eval "$(seclai completion zsh)" in ~/.zshrc
2257
+ seclai completion fish # seclai completion fish > ~/.config/fish/completions/seclai.fish
1653
2258
  \`\`\`
2259
+ ` },
2260
+ { name: "references/solutions.md", content: `# Solutions and governance
1654
2261
 
1655
- ## Example: Evaluate agent quality
2262
+ Solutions group agents, knowledge bases and sources into one deliverable.
2263
+ Governance defines the policies applied to agent input and output.
2264
+
2265
+ ## Solutions
1656
2266
 
1657
2267
  \`\`\`bash
1658
- # create eval criteria
1659
- seclai evals criteria create <agentId> --json '{"name":"Answer Accuracy","eval_type":"llm_judge","description":"Does the answer correctly address the question?"}'
1660
- # find compatible runs
1661
- seclai evals compatible-runs <criteriaId> --limit 5
1662
- # test the criteria against a run without persisting
1663
- seclai evals test-draft <agentId> --json '{"criteria":{"name":"Answer Accuracy","eval_type":"llm_judge"},"run_id":"<runId>"}'
1664
- # create a persisted result
1665
- seclai evals results create <criteriaId> --json '{"run_id":"<runId>","score":0.95}'
1666
- # view summary
1667
- seclai evals criteria summary <criteriaId>
2268
+ seclai solutions list [--page N] [--limit N] [--sort field] [--order asc|desc]
2269
+ seclai solutions create --json '{"name":"Customer Support Solution"}'
2270
+ seclai solutions get <solutionId>
2271
+ seclai solutions update <solutionId> --json '{"name":"Updated"}'
2272
+ seclai solutions delete <solutionId>
1668
2273
  \`\`\`
1669
2274
 
1670
- ## Example: Solution with linked resources
2275
+ ## Linking resources
1671
2276
 
1672
2277
  \`\`\`bash
1673
- seclai solutions create --json '{"name":"Customer Support"}'
1674
- seclai solutions link <solutionId> --agents '["<agentId>"]' --kb '["<kbId>"]' --sources '["<sourceId>"]'
1675
- seclai solutions get <solutionId>
2278
+ # each flag takes a JSON array of IDs
2279
+ seclai solutions link <solutionId> --agents '["agentId1"]' --kb '["kbId1"]' --sources '["sourceId1"]'
2280
+ seclai solutions unlink <solutionId> --agents '["agentId1"]'
1676
2281
  \`\`\`
1677
2282
 
1678
- ## Example: Memory-powered agent
2283
+ ## Conversations
1679
2284
 
1680
2285
  \`\`\`bash
1681
- seclai memory create --json '{"name":"User Preferences","type":"general"}'
1682
- seclai agents create --json '{"name":"Personal Assistant","description":"Remembers user preferences"}'
1683
- seclai agents ai gen-steps <agentId> --user-input "Build a chat agent that remembers user preferences. Use general memory bank <memoryBankId>"
1684
- seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
2286
+ seclai solutions convos list <solutionId>
2287
+ seclai solutions convos add <solutionId> --json '{"message":"How should I structure this?"}'
2288
+ seclai solutions convos mark <solutionId> <conversationId> --json '{"accepted":true}'
1685
2289
  \`\`\`
1686
2290
 
1687
- ## Example: Governance policy setup
2291
+ ## Solution AI
1688
2292
 
1689
2293
  \`\`\`bash
1690
- seclai governance ai generate --user-input "Create a content safety policy that blocks harmful outputs"
2294
+ seclai solutions ai generate <solutionId> --user-input "Add an FAQ source"
2295
+ seclai solutions ai kb <solutionId> --user-input "Create a knowledge base for docs"
2296
+ seclai solutions ai source <solutionId> --user-input "Create a file source for PDFs"
2297
+ seclai solutions ai accept <solutionId> <conversationId> --json '{"accepted":true}'
2298
+ seclai solutions ai decline <solutionId> <conversationId>
2299
+ \`\`\`
2300
+
2301
+ \`ai kb\` and \`ai source\` create the resource and link it to the solution in one
2302
+ step, which is why they live here rather than under \`kb\` or \`sources\`.
2303
+
2304
+ ## Governance AI
2305
+
2306
+ \`\`\`bash
2307
+ seclai governance ai generate --user-input "Create a content safety policy"
1691
2308
  seclai governance ai list
1692
2309
  seclai governance ai accept <conversationId>
2310
+ seclai governance ai decline <conversationId>
1693
2311
  \`\`\`
1694
2312
 
1695
- ## Specific topics
2313
+ A generated policy is a proposal until accepted \u2014 \`generate\` alone changes
2314
+ nothing.
1696
2315
 
1697
- * **Streaming & event modes** [references/streaming.md](references/streaming.md)
1698
- * **File uploads & content management** [references/uploads.md](references/uploads.md)
1699
- * **Evaluations workflow** [references/evaluations.md](references/evaluations.md)
1700
- `;
1701
- var STREAMING_REF = `# Streaming Agent Runs
2316
+ ## Example: solution with linked resources
2317
+
2318
+ \`\`\`bash
2319
+ seclai solutions create --json '{"name":"Customer Support"}'
2320
+ seclai solutions link <solutionId> --agents '["<agentId>"]' --kb '["<kbId>"]' --sources '["<sourceId>"]'
2321
+ seclai solutions get <solutionId>
2322
+ \`\`\`
2323
+
2324
+ ## Example: governance policy setup
2325
+
2326
+ \`\`\`bash
2327
+ seclai governance ai generate --user-input "Create a content safety policy that blocks harmful outputs"
2328
+ seclai governance ai list
2329
+ seclai governance ai accept <conversationId>
2330
+ \`\`\`
2331
+ ` },
2332
+ { name: "references/streaming.md", content: `# Streaming Agent Runs
1702
2333
 
1703
2334
  ## Modes
1704
2335
 
@@ -1750,8 +2381,8 @@ seclai agents run <agentId> --json '{"input":"Hello"}'
1750
2381
  # check later:
1751
2382
  seclai agents runs get <runId>
1752
2383
  \`\`\`
1753
- `;
1754
- var UPLOADS_REF = `# File Uploads & Content Management
2384
+ ` },
2385
+ { name: "references/uploads.md", content: `# File Uploads & Content Management
1755
2386
 
1756
2387
  ## Upload to a source
1757
2388
  \`\`\`bash
@@ -1767,11 +2398,21 @@ seclai sources upload-text <sourceId> --json '{"text":"Article content here...",
1767
2398
 
1768
2399
  ## Upload input for agent runs
1769
2400
  \`\`\`bash
2401
+ # Check what files (if any) the agent expects before uploading. requires_uploads
2402
+ # reports whether the agent accepts files; the agent block lists the exact names /
2403
+ # indexes / patterns a run-time batch must satisfy.
2404
+ seclai agents attachment-references <agentId>
1770
2405
  seclai agents upload-input <agentId> --file ./input.pdf
1771
2406
  seclai agents upload-input <agentId> --file ./data.csv --file-name "report.csv" --mime-type "text/csv"
1772
2407
  seclai agents input-status <agentId> <uploadId>
1773
2408
  \`\`\`
1774
2409
 
2410
+ ## Download an attachment emitted by a run
2411
+ \`\`\`bash
2412
+ # attachmentId is the URL-safe-base64 storage_key from run output manifests / webhooks.
2413
+ seclai agents runs download-attachment <runId> <attachmentId> --output ./out.pdf
2414
+ \`\`\`
2415
+
1775
2416
  ## Replace content
1776
2417
  \`\`\`bash
1777
2418
  # replace with file
@@ -1792,61 +2433,10 @@ seclai contents get <contentVersionId> --start 0 --end 1000
1792
2433
  # view embeddings
1793
2434
  seclai contents embeddings <contentVersionId> [--page N] [--limit N]
1794
2435
  \`\`\`
1795
- `;
1796
- var EVALUATIONS_REF = `# Evaluations Workflow
1797
-
1798
- ## Step 1: Create evaluation criteria for an agent
1799
- \`\`\`bash
1800
- seclai evals criteria create <agentId> --json '{"name":"Answer Accuracy","description":"Does the answer correctly address the question?","eval_type":"llm_judge"}'
1801
- \`\`\`
1802
-
1803
- ## Step 2: Find runs to evaluate
1804
- \`\`\`bash
1805
- # list all runs for an agent
1806
- seclai agents runs list <agentId> --limit 10
1807
-
1808
- # or find runs compatible with specific criteria
1809
- seclai evals compatible-runs <criteriaId> --limit 10
1810
- \`\`\`
1811
-
1812
- ## Step 3: Test criteria before committing
1813
- \`\`\`bash
1814
- seclai evals test-draft <agentId> --json '{"criteria":{"name":"Answer Accuracy","eval_type":"llm_judge","description":"..."},"run_id":"<runId>"}'
1815
- \`\`\`
1816
-
1817
- ## Step 4: Create evaluation results
1818
- \`\`\`bash
1819
- seclai evals results create <criteriaId> --json '{"run_id":"<runId>","score":0.95}'
1820
- \`\`\`
1821
-
1822
- ## Step 5: Review summaries
1823
- \`\`\`bash
1824
- seclai evals criteria summary <criteriaId>
1825
- seclai evals agent-results <agentId>
1826
- seclai evals agent-runs <agentId> --limit 20
1827
- seclai evals non-manual-summary <agentId>
1828
- \`\`\`
1829
-
1830
- ## Managing criteria
1831
- \`\`\`bash
1832
- seclai evals criteria list <agentId>
1833
- seclai evals criteria get <criteriaId>
1834
- seclai evals criteria update <criteriaId> --json '{"name":"Updated Name"}'
1835
- seclai evals criteria delete <criteriaId>
1836
- \`\`\`
1837
-
1838
- ## Viewing results
1839
- \`\`\`bash
1840
- seclai evals results list <criteriaId> [--page N] [--limit N]
1841
- \`\`\`
1842
- `;
2436
+ ` }
2437
+ ];
1843
2438
  function getToolConfig(tool, destDir) {
1844
- const skillFiles = [
1845
- { name: "SKILL.md", content: SKILL_MD },
1846
- { name: "references/streaming.md", content: STREAMING_REF },
1847
- { name: "references/uploads.md", content: UPLOADS_REF },
1848
- { name: "references/evaluations.md", content: EVALUATIONS_REF }
1849
- ];
2439
+ const skillFiles = SKILL_FILES;
1850
2440
  switch (tool) {
1851
2441
  case "copilot":
1852
2442
  return { dir: join(destDir, ".github", "copilot", "seclai-cli"), files: skillFiles };
@@ -1888,7 +2478,7 @@ function detectTools(destDir) {
1888
2478
  if (existsSync(join(destDir, ".antigravity"))) detected.push("antigravity");
1889
2479
  return detected;
1890
2480
  }
1891
- function register13(program, rt) {
2481
+ function register15(program, rt) {
1892
2482
  const skills = program.command("skills").description("Install Seclai CLI skill files for AI coding tools.");
1893
2483
  skills.command("install").description(
1894
2484
  "Write Seclai CLI skill/instruction files into the current workspace.\n\nDetected tools: copilot, claude, cursor, windsurf, codex, kiro, cline, roo, gemini, antigravity.\nUse --tool to target a specific tool, or 'all' for all supported tools."
@@ -1989,7 +2579,7 @@ function detectTargets(destDir) {
1989
2579
  return false;
1990
2580
  });
1991
2581
  }
1992
- function register14(program, rt) {
2582
+ function register16(program, rt) {
1993
2583
  const mcp = program.command("mcp").description("Configure the Seclai MCP server for AI coding tools.");
1994
2584
  mcp.command("configure").description(
1995
2585
  "Add the Seclai MCP server to AI coding tool config files.\n\nTargets: claude-code, cursor, claude-desktop, windsurf.\nUse --target to pick a specific tool, or 'all' for all known targets."
@@ -2055,15 +2645,16 @@ _seclai_completions() {
2055
2645
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
2056
2646
 
2057
2647
  # Top-level commands
2058
- commands="agents sources contents kb memory evals solutions governance alerts models search ai skills mcp completion help"
2648
+ commands="agents sources contents kb memory evals solutions governance alerts email models search docs me api-version ai skills mcp completion auth configure help"
2059
2649
 
2060
2650
  case "\${COMP_WORDS[1]}" in
2061
2651
  agents)
2062
2652
  case "\${COMP_WORDS[2]}" in
2063
- runs) COMPREPLY=( $(compgen -W "list get delete cancel search eval-results download-attachment" -- "$cur") ); return ;;
2064
- def) COMPREPLY=( $(compgen -W "get update" -- "$cur") ); return ;;
2065
- ai) COMPREPLY=( $(compgen -W "gen-steps step-config history mark" -- "$cur") ); return ;;
2066
- *) COMPREPLY=( $(compgen -W "list create get update delete run runs def export preview-import upload-input input-status attachment-references ai" -- "$cur") ); return ;;
2653
+ runs) COMPREPLY=( $(compgen -W "list get delete cancel search eval-results download-attachment" -- "$cur") ); return ;;
2654
+ def) COMPREPLY=( $(compgen -W "get update" -- "$cur") ); return ;;
2655
+ ai) COMPREPLY=( $(compgen -W "gen-steps step-config history mark" -- "$cur") ); return ;;
2656
+ triggers) COMPREPLY=( $(compgen -W "email-config" -- "$cur") ); return ;;
2657
+ *) COMPREPLY=( $(compgen -W "list create get update delete disable enable callers triggers run runs def export preview-import upload-input input-status attachment-references ai" -- "$cur") ); return ;;
2067
2658
  esac ;;
2068
2659
  sources|source)
2069
2660
  case "\${COMP_WORDS[2]}" in
@@ -2101,12 +2692,24 @@ _seclai_completions() {
2101
2692
  prefs) COMPREPLY=( $(compgen -W "list update" -- "$cur") ); return ;;
2102
2693
  *) COMPREPLY=( $(compgen -W "list get status comment subscribe unsubscribe configs prefs" -- "$cur") ); return ;;
2103
2694
  esac ;;
2695
+ email)
2696
+ case "\${COMP_WORDS[2]}" in
2697
+ domains) COMPREPLY=( $(compgen -W "list add remove verify set-primary use-shared test-email dmarc" -- "$cur") ); return ;;
2698
+ blocked) COMPREPLY=( $(compgen -W "list add remove auto-block-mode" -- "$cur") ); return ;;
2699
+ inbound) COMPREPLY=( $(compgen -W "status rejections cancel-queued resume" -- "$cur") ); return ;;
2700
+ optouts) COMPREPLY=( $(compgen -W "list remove" -- "$cur") ); return ;;
2701
+ *) COMPREPLY=( $(compgen -W "domains blocked inbound optouts" -- "$cur") ); return ;;
2702
+ esac ;;
2104
2703
  models)
2105
2704
  case "\${COMP_WORDS[2]}" in
2106
2705
  alerts) COMPREPLY=( $(compgen -W "list mark-read mark-all-read unread-count" -- "$cur") ); return ;;
2107
2706
  experiments) COMPREPLY=( $(compgen -W "list create get cancel delete" -- "$cur") ); return ;;
2108
- *) COMPREPLY=( $(compgen -W "alerts recommendations experiments" -- "$cur") ); return ;;
2707
+ *) COMPREPLY=( $(compgen -W "list get tiers alerts recommendations experiments" -- "$cur") ); return ;;
2109
2708
  esac ;;
2709
+ docs) COMPREPLY=( $(compgen -W "search" -- "$cur") ); return ;;
2710
+ api-version) COMPREPLY=( $(compgen -W "get set clear" -- "$cur") ); return ;;
2711
+ auth) COMPREPLY=( $(compgen -W "login logout status refresh" -- "$cur") ); return ;;
2712
+ configure) COMPREPLY=( $(compgen -W "sso list" -- "$cur") ); return ;;
2110
2713
  ai) COMPREPLY=( $(compgen -W "feedback kb source solution memory memory-history accept decline memory-accept" -- "$cur") ); return ;;
2111
2714
  skills) COMPREPLY=( $(compgen -W "install" -- "$cur") ); return ;;
2112
2715
  mcp) COMPREPLY=( $(compgen -W "configure show" -- "$cur") ); return ;;
@@ -2134,17 +2737,28 @@ _seclai() {
2134
2737
  'solutions:Manage solutions'
2135
2738
  'governance:Governance AI assistant'
2136
2739
  'alerts:Manage alerts and alert configurations'
2137
- 'models:Model alerts and recommendations'
2740
+ 'email:Agent email domains, blocklist, inbound health, opt-outs'
2741
+ 'models:Models, model alerts, recommendations, experiments'
2138
2742
  'search:Search across Seclai resources'
2743
+ 'docs:Search the Seclai documentation'
2744
+ 'me:Show the authenticated user and organizations'
2745
+ 'api-version:Read or pin the dated API version'
2139
2746
  'ai:Top-level AI assistant'
2140
2747
  'skills:Install skill files for AI coding tools'
2141
2748
  'mcp:Configure the Seclai MCP server'
2142
2749
  'completion:Generate shell completion scripts'
2750
+ 'auth:SSO authentication'
2751
+ 'configure:Manage SSO profiles'
2143
2752
  'help:Display help for command'
2144
2753
  )
2145
2754
 
2146
2755
  _arguments -C \\
2147
2756
  '--api-key[Seclai API key]:key' \\
2757
+ '--profile[SSO profile name]:name' \\
2758
+ '--account-id[Account ID (X-Account-Id header)]:id' \\
2759
+ '--config-dir[Config directory]:path' \\
2760
+ '--api-version[Dated API version (YYYY-MM-DD)]:date' \\
2761
+ '--allow-unknown-api-version[Permit an unrecognized --api-version]' \\
2148
2762
  '--compact[Output compact JSON]' \\
2149
2763
  '-V[Output version]' \\
2150
2764
  '-h[Display help]' \\
@@ -2156,7 +2770,7 @@ _seclai() {
2156
2770
  args)
2157
2771
  case \${words[1]} in
2158
2772
  agents)
2159
- local -a sub=(list create get update delete run runs def export preview-import upload-input input-status attachment-references ai)
2773
+ local -a sub=(list create get update delete disable enable callers triggers run runs def export preview-import upload-input input-status attachment-references ai)
2160
2774
  _describe 'subcommand' sub ;;
2161
2775
  sources|source)
2162
2776
  local -a sub=(list create get update delete upload upload-text exports migration)
@@ -2182,8 +2796,23 @@ _seclai() {
2182
2796
  alerts)
2183
2797
  local -a sub=(list get status comment subscribe unsubscribe configs prefs)
2184
2798
  _describe 'subcommand' sub ;;
2799
+ email)
2800
+ local -a sub=(domains blocked inbound optouts)
2801
+ _describe 'subcommand' sub ;;
2185
2802
  models)
2186
- local -a sub=(alerts recommendations experiments)
2803
+ local -a sub=(list get tiers alerts recommendations experiments)
2804
+ _describe 'subcommand' sub ;;
2805
+ docs)
2806
+ local -a sub=(search)
2807
+ _describe 'subcommand' sub ;;
2808
+ api-version)
2809
+ local -a sub=(get set clear)
2810
+ _describe 'subcommand' sub ;;
2811
+ auth)
2812
+ local -a sub=(login logout status refresh)
2813
+ _describe 'subcommand' sub ;;
2814
+ configure)
2815
+ local -a sub=(sso list)
2187
2816
  _describe 'subcommand' sub ;;
2188
2817
  ai)
2189
2818
  local -a sub=(feedback kb source solution memory memory-history accept decline memory-accept)
@@ -2206,7 +2835,7 @@ _seclai "$@"
2206
2835
  var FISH = `# seclai fish completion \u2014 save to ~/.config/fish/completions/seclai.fish
2207
2836
  # seclai completion fish > ~/.config/fish/completions/seclai.fish
2208
2837
 
2209
- set -l top agents sources contents kb memory evals solutions governance alerts models search ai skills mcp completion help
2838
+ set -l top agents sources contents kb memory evals solutions governance alerts email models search docs me api-version ai skills mcp completion auth configure help
2210
2839
 
2211
2840
  # Top-level
2212
2841
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "agents" -d "Manage agents"
@@ -2218,15 +2847,21 @@ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "evals" -d "E
2218
2847
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "solutions" -d "Solutions"
2219
2848
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "governance" -d "Governance AI"
2220
2849
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "alerts" -d "Alerts"
2221
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "models" -d "Model alerts"
2850
+ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "email" -d "Agent email"
2851
+ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "models" -d "Models and model alerts"
2222
2852
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "search" -d "Search resources"
2853
+ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "docs" -d "Search documentation"
2854
+ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "me" -d "Authenticated user"
2855
+ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "api-version" -d "Dated API version"
2223
2856
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "ai" -d "AI assistant"
2224
2857
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "skills" -d "Skill files"
2225
2858
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "mcp" -d "MCP server config"
2226
2859
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "completion" -d "Shell completions"
2860
+ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "auth" -d "SSO authentication"
2861
+ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "configure" -d "Manage SSO profiles"
2227
2862
 
2228
2863
  # agents
2229
- complete -c seclai -n "__fish_seen_subcommand_from agents; and not __fish_seen_subcommand_from list create get update delete run runs def export preview-import upload-input input-status attachment-references ai" -f -a "list create get update delete run runs def export preview-import upload-input input-status attachment-references ai"
2864
+ complete -c seclai -n "__fish_seen_subcommand_from agents; and not __fish_seen_subcommand_from list create get update delete disable enable callers triggers run runs def export preview-import upload-input input-status attachment-references ai" -f -a "list create get update delete disable enable callers triggers run runs def export preview-import upload-input input-status attachment-references ai"
2230
2865
 
2231
2866
  # sources
2232
2867
  complete -c seclai -n "__fish_seen_subcommand_from sources; and not __fish_seen_subcommand_from list create get update delete upload upload-text exports migration" -f -a "list create get update delete upload upload-text exports migration"
@@ -2252,8 +2887,23 @@ complete -c seclai -n "__fish_seen_subcommand_from governance; and not __fish_se
2252
2887
  # alerts
2253
2888
  complete -c seclai -n "__fish_seen_subcommand_from alerts; and not __fish_seen_subcommand_from list get status comment subscribe unsubscribe configs prefs" -f -a "list get status comment subscribe unsubscribe configs prefs"
2254
2889
 
2890
+ # email
2891
+ complete -c seclai -n "__fish_seen_subcommand_from email; and not __fish_seen_subcommand_from domains blocked inbound optouts" -f -a "domains blocked inbound optouts"
2892
+
2255
2893
  # models
2256
- complete -c seclai -n "__fish_seen_subcommand_from models; and not __fish_seen_subcommand_from alerts recommendations experiments" -f -a "alerts recommendations experiments"
2894
+ complete -c seclai -n "__fish_seen_subcommand_from models; and not __fish_seen_subcommand_from list get tiers alerts recommendations experiments" -f -a "list get tiers alerts recommendations experiments"
2895
+
2896
+ # docs
2897
+ complete -c seclai -n "__fish_seen_subcommand_from docs; and not __fish_seen_subcommand_from search" -f -a "search"
2898
+
2899
+ # api-version
2900
+ complete -c seclai -n "__fish_seen_subcommand_from api-version; and not __fish_seen_subcommand_from get set clear" -f -a "get set clear"
2901
+
2902
+ # auth
2903
+ complete -c seclai -n "__fish_seen_subcommand_from auth; and not __fish_seen_subcommand_from login logout status refresh" -f -a "login logout status refresh"
2904
+
2905
+ # configure
2906
+ complete -c seclai -n "__fish_seen_subcommand_from configure; and not __fish_seen_subcommand_from sso list" -f -a "sso list"
2257
2907
 
2258
2908
  # ai
2259
2909
  complete -c seclai -n "__fish_seen_subcommand_from ai; and not __fish_seen_subcommand_from feedback kb source solution memory memory-history accept decline memory-accept" -f -a "feedback kb source solution memory memory-history accept decline memory-accept"
@@ -2269,11 +2919,16 @@ complete -c seclai -n "__fish_seen_subcommand_from completion; and not __fish_se
2269
2919
 
2270
2920
  # Global options
2271
2921
  complete -c seclai -l api-key -d "Seclai API key"
2922
+ complete -c seclai -l profile -d "SSO profile name"
2923
+ complete -c seclai -l account-id -d "Account ID (X-Account-Id header)"
2924
+ complete -c seclai -l config-dir -d "Config directory"
2925
+ complete -c seclai -l api-version -d "Dated API version (YYYY-MM-DD)"
2926
+ complete -c seclai -l allow-unknown-api-version -d "Permit an unrecognized --api-version"
2272
2927
  complete -c seclai -l compact -d "Output compact JSON"
2273
2928
  complete -c seclai -s V -l version -d "Output version"
2274
2929
  `;
2275
2930
  var SCRIPTS = { bash: BASH, zsh: ZSH, fish: FISH };
2276
- function register15(program, rt) {
2931
+ function register17(program, rt) {
2277
2932
  const completion = program.command("completion").description("Generate shell completion scripts.").argument("<shell>", "Shell type: bash, zsh, or fish.").action(async (shell) => {
2278
2933
  const script = SCRIPTS[shell];
2279
2934
  if (!script) {
@@ -2447,7 +3102,7 @@ async function loadProfile(rt, opts) {
2447
3102
  const profile = await loadSsoProfile(configDir, profileName);
2448
3103
  return { profile, profileName, configDir };
2449
3104
  }
2450
- function register16(program, rt) {
3105
+ function register18(program, rt) {
2451
3106
  const group = program.command("auth").description("SSO authentication (login/logout/status/refresh).");
2452
3107
  group.command("login").description("Authenticate via SSO using Authorization Code + PKCE flow.").option("--port <port>", "Local callback port", String(DEFAULT_CALLBACK_PORT)).option("--no-browser", "Print the URL instead of opening a browser").action(async (opts) => {
2453
3108
  await run(rt, async () => {
@@ -2621,7 +3276,7 @@ function resolveConfigDir(opts) {
2621
3276
  const home = process4.env.HOME ?? process4.env.USERPROFILE ?? "";
2622
3277
  return join4(home, ".seclai");
2623
3278
  }
2624
- function register17(program, rt) {
3279
+ function register19(program, rt) {
2625
3280
  const group = program.command("configure").description("Configure CLI profiles and settings.");
2626
3281
  group.command("sso").description("Configure an SSO profile with optional overrides. Defaults to production Seclai SSO.").option("--profile-name <name>", "Profile name to configure (default: from --profile flag)").action(async (opts) => {
2627
3282
  await run(rt, async () => {
@@ -2734,8 +3389,15 @@ function escapeRegExp(s) {
2734
3389
  }
2735
3390
 
2736
3391
  // src/cli.ts
3392
+ var VALUED_GLOBAL_OPTIONS = [
3393
+ ["apiKey", "--api-key", "reject", "Pass a key, or omit the flag to use SECLAI_API_KEY or SSO."],
3394
+ ["profile", "--profile", "reject", "Pass a profile name, or omit the flag to use the default profile."],
3395
+ ["accountId", "--account-id", "reject", "Pass an account ID, or omit the flag to use the default org."],
3396
+ ["configDir", "--config-dir", "reject", "Pass a directory, or omit the flag to use ~/.seclai."],
3397
+ ["apiVersion", "--api-version", "warn", "Pass a YYYY-MM-DD date, or omit the flag to use the account default."]
3398
+ ];
2737
3399
  function createProgram(rt = defaultRuntime()) {
2738
- const program = new Command();
3400
+ const program = new Command3();
2739
3401
  const cliVersion = getCliVersion();
2740
3402
  program.name("seclai").description(
2741
3403
  `Seclai Command Line Interface (v${cliVersion})
@@ -2755,6 +3417,12 @@ All commands return JSON to stdout, making it easy to pipe into jq or other tool
2755
3417
  ).option(
2756
3418
  "--config-dir <path>",
2757
3419
  "Config directory (defaults to SECLAI_CONFIG_DIR, then ~/.seclai)."
3420
+ ).option(
3421
+ "--api-version <date>",
3422
+ "Opt into dated API changes released on or before this YYYY-MM-DD (defaults to SECLAI_API_VERSION; omitted means the account default)."
3423
+ ).option(
3424
+ "--allow-unknown-api-version",
3425
+ "Send an --api-version this CLI was not built against instead of rejecting it."
2758
3426
  ).option(
2759
3427
  "--compact",
2760
3428
  "Output compact JSON (no indentation)."
@@ -2767,6 +3435,7 @@ Environment:
2767
3435
  SECLAI_API_URL Override API base URL (default: https://api.seclai.com)
2768
3436
  SECLAI_PROFILE Default SSO profile (alternative to --profile)
2769
3437
  SECLAI_CONFIG_DIR Config directory (alternative to --config-dir)
3438
+ SECLAI_API_VERSION Dated API version (alternative to --api-version)
2770
3439
 
2771
3440
  Examples:
2772
3441
  seclai agents list
@@ -2786,6 +3455,15 @@ Examples:
2786
3455
  program.exitOverride();
2787
3456
  program.hook("preAction", (thisCommand) => {
2788
3457
  const globalOpts = thisCommand.opts();
3458
+ for (const [key, flag, policy, hint] of VALUED_GLOBAL_OPTIONS) {
3459
+ const value = globalOpts[key];
3460
+ if (typeof value !== "string" || value.length > 0) continue;
3461
+ if (policy === "reject") {
3462
+ throw new Error(`${flag} was given an empty value. ${hint}`);
3463
+ }
3464
+ delete globalOpts[key];
3465
+ warnDeprecated(rt, `${flag} was given an empty value and is being ignored. ${hint}`);
3466
+ }
2789
3467
  rt.compact = Boolean(globalOpts.compact);
2790
3468
  });
2791
3469
  register(program, rt);
@@ -2805,6 +3483,8 @@ Examples:
2805
3483
  register15(program, rt);
2806
3484
  register16(program, rt);
2807
3485
  register17(program, rt);
3486
+ register18(program, rt);
3487
+ register19(program, rt);
2808
3488
  return program;
2809
3489
  }
2810
3490
  async function runCli(argv, rt = defaultRuntime()) {