@seclai/cli 1.4.0 → 1.6.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 Command4 } 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";
@@ -74,10 +75,27 @@ function getCliVersion() {
74
75
  }
75
76
  function createClient(opts) {
76
77
  const seclaiOpts = {};
78
+ const identityFlags = [
79
+ ["apiKey", "--api-key", "Pass a key, or omit the flag to use SECLAI_API_KEY or SSO."],
80
+ ["profile", "--profile", "Pass a profile name, or omit the flag to use the default profile."],
81
+ ["accountId", "--account-id", "Pass an account ID, or omit the flag to use the default org."],
82
+ ["configDir", "--config-dir", "Pass a directory, or omit the flag to use ~/.seclai."]
83
+ ];
84
+ for (const [key, flag, hint] of identityFlags) {
85
+ const value = opts[key];
86
+ if (typeof value === "string" && value.trim().length === 0) {
87
+ throw new Error(`${flag} was given an empty value. ${hint}`);
88
+ }
89
+ }
77
90
  if (opts.apiKey !== void 0) seclaiOpts.apiKey = opts.apiKey;
78
91
  if (opts.profile !== void 0) seclaiOpts.profile = opts.profile;
79
92
  if (opts.configDir !== void 0) seclaiOpts.configDir = opts.configDir;
80
93
  if (opts.accountId !== void 0) seclaiOpts.accountId = opts.accountId;
94
+ const envVersion = process2.env.SECLAI_API_VERSION;
95
+ const blank = (v) => v === void 0 || v.trim().length === 0;
96
+ const version = blank(opts.apiVersion) ? opts.apiVersion !== void 0 ? void 0 : blank(envVersion) ? void 0 : envVersion : opts.apiVersion;
97
+ if (version !== void 0) seclaiOpts.apiVersion = version;
98
+ if (opts.allowUnknownApiVersion) seclaiOpts.allowUnknownApiVersion = true;
81
99
  const envUrl = process2.env.SECLAI_API_URL;
82
100
  seclaiOpts.baseUrl = envUrl && envUrl.length > 0 ? envUrl : "https://api.seclai.com";
83
101
  return new Seclai(seclaiOpts);
@@ -87,6 +105,11 @@ function printJson(rt, value) {
87
105
  rt.writeOut(`${JSON.stringify(value, null, indent)}
88
106
  `);
89
107
  }
108
+ function warnDeprecated(rt, message, fate = "rejected") {
109
+ const suffix = fate === "kept" ? "" : fate === "removed" ? " This will be removed in a future release." : " This will be rejected in a future release.";
110
+ rt.writeErr(`warning: ${message}${suffix}
111
+ `);
112
+ }
90
113
  function printError(rt, err) {
91
114
  if (err instanceof SeclaiAPIValidationError) {
92
115
  rt.writeErr(`${err.name}: ${err.message}
@@ -114,8 +137,13 @@ function printError(rt, err) {
114
137
  if (err instanceof SeclaiConfigurationError) {
115
138
  rt.writeErr(`${err.name}: ${err.message}
116
139
  `);
117
- rt.writeErr(`hint: Set the SECLAI_API_KEY environment variable or pass --api-key.
140
+ if (/api version/i.test(err.message)) {
141
+ rt.writeErr(`hint: Pass --allow-unknown-api-version to send it anyway.
142
+ `);
143
+ } else {
144
+ rt.writeErr(`hint: Set the SECLAI_API_KEY environment variable or pass --api-key.
118
145
  `);
146
+ }
119
147
  return;
120
148
  }
121
149
  if (err instanceof Error) {
@@ -134,6 +162,37 @@ async function run(rt, main) {
134
162
  rt.setExitCode(1);
135
163
  }
136
164
  }
165
+ function parseNumber(value) {
166
+ if (!/^\d+$/.test(value.trim())) {
167
+ throw new InvalidArgumentError("Expected a non-negative whole number.");
168
+ }
169
+ return Number(value.trim());
170
+ }
171
+ function withLimitOption(cmd, description = "Page size.") {
172
+ return cmd.option("--limit <n>", description, parseNumber);
173
+ }
174
+ function withOffsetListOptions(cmd) {
175
+ return withLimitOption(cmd).option("--offset <n>", "Number of items to skip.", parseNumber);
176
+ }
177
+ function toPagedEnvelope(res, ...legacyKeys) {
178
+ if (Array.isArray(res)) return { data: res };
179
+ if (res === null || typeof res !== "object") return res;
180
+ const obj = res;
181
+ if (Array.isArray(obj.data)) return obj;
182
+ for (const key of legacyKeys) {
183
+ if (Array.isArray(obj[key])) {
184
+ const { [key]: rows, ...rest } = obj;
185
+ return { data: rows, ...rest };
186
+ }
187
+ }
188
+ return res;
189
+ }
190
+ function offsetListOpts(opts) {
191
+ const o = {};
192
+ if (opts.limit !== void 0) o.limit = opts.limit;
193
+ if (opts.offset !== void 0) o.offset = opts.offset;
194
+ return o;
195
+ }
137
196
  function withJsonInputOptions(cmd) {
138
197
  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
198
  }
@@ -176,7 +235,7 @@ async function readAiInput(rt, opts) {
176
235
  // src/commands/agents.ts
177
236
  function register(program, rt) {
178
237
  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) => {
238
+ agents.command("list").description("List agents.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (opts) => {
180
239
  await run(rt, async () => {
181
240
  const client = createClient(program.opts());
182
241
  printJson(rt, await client.listAgents(listOpts(opts)));
@@ -209,7 +268,40 @@ function register(program, rt) {
209
268
  printJson(rt, { ok: true });
210
269
  });
211
270
  });
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) => {
271
+ agents.command("disable").description("Pause an agent across every trigger path (API, schedule, email, sub-agent calls).").argument("<agentId>", "Agent ID.").action(async (agentId) => {
272
+ await run(rt, async () => {
273
+ const client = createClient(program.opts());
274
+ printJson(rt, await client.disableAgent(agentId));
275
+ });
276
+ });
277
+ agents.command("enable").description("Resume a paused agent.").argument("<agentId>", "Agent ID.").action(async (agentId) => {
278
+ await run(rt, async () => {
279
+ const client = createClient(program.opts());
280
+ printJson(rt, await client.enableAgent(agentId));
281
+ });
282
+ });
283
+ agents.command("callers").description("List the live agents that call this agent via a call_agent step.").argument("<agentId>", "Agent ID.").action(async (agentId) => {
284
+ await run(rt, async () => {
285
+ const client = createClient(program.opts());
286
+ printJson(rt, await client.getAgentCallers(agentId));
287
+ });
288
+ });
289
+ const triggers = agents.command("triggers").description("Agent trigger configuration.");
290
+ 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) => {
291
+ await run(rt, async () => {
292
+ const client = createClient(program.opts());
293
+ const body = await readJsonInput(rt, { json: opts.json, jsonFile: opts.jsonFile });
294
+ printJson(
295
+ rt,
296
+ await client.setEmailTriggerConfig(
297
+ agentId,
298
+ triggerId,
299
+ body
300
+ )
301
+ );
302
+ });
303
+ });
304
+ 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
305
  await run(rt, async () => {
214
306
  const client = createClient(program.opts());
215
307
  const body = await readJsonInput(rt, { json: opts.json, jsonFile: opts.jsonFile });
@@ -257,7 +349,7 @@ function register(program, rt) {
257
349
  });
258
350
  });
259
351
  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) => {
352
+ 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
353
  await run(rt, async () => {
262
354
  const client = createClient(program.opts());
263
355
  const o = listOpts(opts);
@@ -274,10 +366,17 @@ function register(program, rt) {
274
366
  );
275
367
  });
276
368
  });
277
- runs.command("delete").description("Delete a run.").argument("<runId>", "Run ID.").action(async (runId) => {
369
+ 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
370
  await run(rt, async () => {
371
+ warnDeprecated(
372
+ rt,
373
+ `'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}.`,
374
+ // Kept, not scheduled for removal — the whole point of the alias is
375
+ // that existing scripts keep working.
376
+ "kept"
377
+ );
279
378
  const client = createClient(program.opts());
280
- await client.deleteAgentRun(runId);
379
+ await client.cancelAgentRun(runId);
281
380
  printJson(rt, { ok: true });
282
381
  });
283
382
  });
@@ -398,10 +497,20 @@ function register(program, rt) {
398
497
  printJson(rt, await client.generateStepConfig(agentId, body));
399
498
  });
400
499
  });
401
- ai.command("history").description("Get agent AI conversation history.").argument("<agentId>", "Agent ID.").action(async (agentId) => {
500
+ withOffsetListOptions(
501
+ ai.command("history").description("Get agent AI conversation history for one step type.").argument("<agentId>", "Agent ID.").requiredOption(
502
+ "--step-type <type>",
503
+ "Step type to read history for (e.g. llm). Required by the API."
504
+ ).option("--step-id <id>", "Restrict to a single step.")
505
+ ).action(async (agentId, opts) => {
402
506
  await run(rt, async () => {
403
507
  const client = createClient(program.opts());
404
- printJson(rt, await client.getAgentAiConversationHistory(agentId));
508
+ const o = {
509
+ ...offsetListOpts(opts),
510
+ stepType: opts.stepType
511
+ };
512
+ if (opts.stepId !== void 0) o.stepId = opts.stepId;
513
+ printJson(rt, await client.getAgentAiConversationHistory(agentId, o));
405
514
  });
406
515
  });
407
516
  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,10 +521,14 @@ function register(program, rt) {
412
521
  printJson(rt, { ok: true });
413
522
  });
414
523
  });
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) => {
524
+ 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).option(
525
+ "--paged",
526
+ "Wrap the results in {data: [...]}, the shape this endpoint moves to from --api-version 2026-07-27."
527
+ ).action(async (agentId, runId, opts) => {
416
528
  await run(rt, async () => {
417
529
  const client = createClient(program.opts());
418
- printJson(rt, await client.listRunEvaluationResults(agentId, runId, listOpts(opts)));
530
+ const res = await client.listRunEvaluationResults(agentId, runId, listOpts(opts));
531
+ printJson(rt, opts.paged ? toPagedEnvelope(res) : res);
419
532
  });
420
533
  });
421
534
  }
@@ -423,7 +536,7 @@ function register(program, rt) {
423
536
  // src/commands/sources.ts
424
537
  function register2(program, rt) {
425
538
  const sources = program.command("sources").alias("source").description("Manage content sources.");
426
- sources.command("list").description("List sources.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).option("--sort <field>", "Sort field.").option("--order <asc|desc>", "Sort direction.").option("--account-id <id>", "Filter by account ID.").action(async (opts) => {
539
+ sources.command("list").description("List sources.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option("--sort <field>", "Sort field.").option("--order <asc|desc>", "Sort direction.").option("--account-id <id>", "Filter by account ID.").action(async (opts) => {
427
540
  await run(rt, async () => {
428
541
  const globalOpts = program.opts();
429
542
  const client = createClient(globalOpts);
@@ -476,7 +589,7 @@ function register2(program, rt) {
476
589
  });
477
590
  });
478
591
  const exports_ = sources.command("exports").description("Manage source exports.");
479
- exports_.command("list").description("List exports for a source.").argument("<sourceId>", "Source ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (sourceId, opts) => {
592
+ exports_.command("list").description("List exports for a source.").argument("<sourceId>", "Source ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (sourceId, opts) => {
480
593
  await run(rt, async () => {
481
594
  const client = createClient(program.opts());
482
595
  printJson(rt, await client.listSourceExports(sourceId, listOpts(opts)));
@@ -547,7 +660,7 @@ function register2(program, rt) {
547
660
  // src/commands/contents.ts
548
661
  function register3(program, rt) {
549
662
  const contents = program.command("contents").description("Manage indexed content and embeddings.");
550
- contents.command("get").description("Get content version details.").argument("<contentVersionId>", "Content version ID.").option("--start <n>", "Text start offset (0-based).", (v) => Number(v)).option("--end <n>", "Text end offset (exclusive).", (v) => Number(v)).action(async (contentVersionId, opts) => {
663
+ contents.command("get").description("Get content version details.").argument("<contentVersionId>", "Content version ID.").option("--start <n>", "Text start offset (0-based).", parseNumber).option("--end <n>", "Text end offset (exclusive).", parseNumber).action(async (contentVersionId, opts) => {
551
664
  await run(rt, async () => {
552
665
  const client = createClient(program.opts());
553
666
  const o = {};
@@ -578,7 +691,7 @@ function register3(program, rt) {
578
691
  printJson(rt, await client.replaceContentWithInlineText(contentVersionId, body));
579
692
  });
580
693
  });
581
- contents.command("embeddings").description("List embeddings for a content version.").argument("<contentVersionId>", "Content version ID.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).action(async (contentVersionId, opts) => {
694
+ contents.command("embeddings").description("List embeddings for a content version.").argument("<contentVersionId>", "Content version ID.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).action(async (contentVersionId, opts) => {
582
695
  await run(rt, async () => {
583
696
  const client = createClient(program.opts());
584
697
  printJson(rt, await client.listContentEmbeddings(contentVersionId, listOpts(opts)));
@@ -589,7 +702,7 @@ function register3(program, rt) {
589
702
  // src/commands/kb.ts
590
703
  function register4(program, rt) {
591
704
  const kb = program.command("kb").description("Manage knowledge bases.");
592
- kb.command("list").description("List knowledge bases.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).option("--sort <field>", "Sort field.").option("--order <asc|desc>", "Sort direction.").action(async (opts) => {
705
+ kb.command("list").description("List knowledge bases.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option("--sort <field>", "Sort field.").option("--order <asc|desc>", "Sort direction.").action(async (opts) => {
593
706
  await run(rt, async () => {
594
707
  const client = createClient(program.opts());
595
708
  printJson(rt, await client.listKnowledgeBases(listOpts(opts)));
@@ -627,7 +740,7 @@ function register4(program, rt) {
627
740
  // src/commands/memory.ts
628
741
  function register5(program, rt) {
629
742
  const memory = program.command("memory").description("Manage memory banks.");
630
- memory.command("list").description("List memory banks.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).option("--sort <field>", "Sort field.").option("--order <asc|desc>", "Sort direction.").action(async (opts) => {
743
+ memory.command("list").description("List memory banks.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option("--sort <field>", "Sort field.").option("--order <asc|desc>", "Sort direction.").action(async (opts) => {
631
744
  await run(rt, async () => {
632
745
  const client = createClient(program.opts());
633
746
  printJson(rt, await client.listMemoryBanks(listOpts(opts)));
@@ -735,10 +848,16 @@ function register5(program, rt) {
735
848
  function register6(program, rt) {
736
849
  const evals = program.command("evals").description("Manage evaluations.");
737
850
  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) => {
851
+ 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(
852
+ "--paged",
853
+ "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."
854
+ ).action(async (agentId, opts) => {
739
855
  await run(rt, async () => {
740
856
  const client = createClient(program.opts());
741
- printJson(rt, await client.listEvaluationCriteria(agentId, listOpts(opts)));
857
+ printJson(
858
+ rt,
859
+ opts.paged ? await client.listEvaluationCriteriaPage(agentId, listOpts(opts)) : await client.listEvaluationCriteria(agentId, listOpts(opts))
860
+ );
742
861
  });
743
862
  });
744
863
  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 +894,7 @@ function register6(program, rt) {
775
894
  });
776
895
  });
777
896
  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) => {
897
+ 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
898
  await run(rt, async () => {
780
899
  const client = createClient(program.opts());
781
900
  printJson(rt, await client.listEvaluationResults(criteriaId, listOpts(opts)));
@@ -788,7 +907,7 @@ function register6(program, rt) {
788
907
  printJson(rt, await client.createEvaluationResult(criteriaId, body));
789
908
  });
790
909
  });
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) => {
910
+ 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
911
  await run(rt, async () => {
793
912
  const client = createClient(program.opts());
794
913
  printJson(rt, await client.listCompatibleRuns(criteriaId, listOpts(opts)));
@@ -801,13 +920,13 @@ function register6(program, rt) {
801
920
  printJson(rt, await client.testDraftEvaluation(agentId, body));
802
921
  });
803
922
  });
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) => {
923
+ 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
924
  await run(rt, async () => {
806
925
  const client = createClient(program.opts());
807
926
  printJson(rt, await client.listAgentEvaluationResults(agentId, listOpts(opts)));
808
927
  });
809
928
  });
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) => {
929
+ 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
930
  await run(rt, async () => {
812
931
  const client = createClient(program.opts());
813
932
  printJson(rt, await client.listEvaluationRuns(agentId, listOpts(opts)));
@@ -824,7 +943,7 @@ function register6(program, rt) {
824
943
  // src/commands/solutions.ts
825
944
  function register7(program, rt) {
826
945
  const solutions = program.command("solutions").description("Manage solutions.");
827
- solutions.command("list").description("List solutions.").option("--page <n>", "Page number.", (v) => Number(v)).option("--limit <n>", "Page size.", (v) => Number(v)).option("--sort <field>", "Sort field.").option("--order <asc|desc>", "Sort direction.").action(async (opts) => {
946
+ solutions.command("list").description("List solutions.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option("--sort <field>", "Sort field.").option("--order <asc|desc>", "Sort direction.").action(async (opts) => {
828
947
  await run(rt, async () => {
829
948
  const client = createClient(program.opts());
830
949
  printJson(rt, await client.listSolutions(listOpts(opts)));
@@ -1002,12 +1121,11 @@ function register8(program, rt) {
1002
1121
  // src/commands/alerts.ts
1003
1122
  function register9(program, rt) {
1004
1123
  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) => {
1124
+ alerts.command("list").description("List alerts.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option("--status <status>", "Filter by status.").action(async (opts) => {
1006
1125
  await run(rt, async () => {
1007
1126
  const client = createClient(program.opts());
1008
1127
  const o = listOpts(opts);
1009
1128
  if (opts.status) o.status = opts.status;
1010
- if (opts.severity) o.severity = opts.severity;
1011
1129
  printJson(rt, await client.listAlerts(o));
1012
1130
  });
1013
1131
  });
@@ -1044,10 +1162,14 @@ function register9(program, rt) {
1044
1162
  });
1045
1163
  });
1046
1164
  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) => {
1165
+ configs.command("list").description("List alert configurations.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option(
1166
+ "--paged",
1167
+ "Wrap the results in {data: [...]}, the shape this endpoint moves to from --api-version 2026-07-27."
1168
+ ).action(async (opts) => {
1048
1169
  await run(rt, async () => {
1049
1170
  const client = createClient(program.opts());
1050
- printJson(rt, await client.listAlertConfigs(listOpts(opts)));
1171
+ const res = await client.listAlertConfigs(listOpts(opts));
1172
+ printJson(rt, opts.paged ? toPagedEnvelope(res, "configs") : res);
1051
1173
  });
1052
1174
  });
1053
1175
  configs.command("create").description("Create an alert configuration.").option("--json <json>", "Config body JSON.").option("--json-file <path>", "Config body JSON file.").action(async (opts) => {
@@ -1093,19 +1215,220 @@ function register9(program, rt) {
1093
1215
  });
1094
1216
  }
1095
1217
 
1096
- // src/commands/models.ts
1218
+ // src/commands/email.ts
1219
+ import { Argument, Option } from "commander";
1097
1220
  function register10(program, rt) {
1221
+ const email = program.command("email").description("Agent email: sending domains, inbound blocklist, inbound health, and opt-outs.");
1222
+ const domains = email.command("domains").description("Agent-email sending domains.");
1223
+ domains.command("list").description("List the account's email domains and the plan limits for adding more.").action(async () => {
1224
+ await run(rt, async () => {
1225
+ const client = createClient(program.opts());
1226
+ printJson(rt, await client.listEmailDomains());
1227
+ });
1228
+ });
1229
+ domains.command("add").description("Add and provision a new agent-email domain. Returns the DNS records to publish.").addOption(
1230
+ new Option("--kind <kind>", "'vanity' (a subdomain of seclai.com) or 'custom' (your own domain).").choices(["vanity", "custom"]).makeOptionMandatory()
1231
+ ).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) => {
1232
+ await run(rt, async () => {
1233
+ const client = createClient(program.opts());
1234
+ const body = {
1235
+ kind: opts.kind,
1236
+ value: opts.value
1237
+ };
1238
+ if (opts.delegated) body.delegated = true;
1239
+ printJson(rt, await client.addEmailDomain(body));
1240
+ });
1241
+ });
1242
+ domains.command("remove").description("Remove a domain and tear down its sending identity and inbound routing.").argument("<domainId>", "Domain ID.").action(async (domainId) => {
1243
+ await run(rt, async () => {
1244
+ const client = createClient(program.opts());
1245
+ printJson(rt, await client.removeEmailDomain(domainId));
1246
+ });
1247
+ });
1248
+ domains.command("verify").description("Run a verification check on a domain immediately.").argument("<domainId>", "Domain ID.").action(async (domainId) => {
1249
+ await run(rt, async () => {
1250
+ const client = createClient(program.opts());
1251
+ printJson(rt, await client.verifyEmailDomain(domainId));
1252
+ });
1253
+ });
1254
+ domains.command("set-primary").description("Promote a verified domain to the account's primary sending domain.").argument("<domainId>", "Domain ID.").action(async (domainId) => {
1255
+ await run(rt, async () => {
1256
+ const client = createClient(program.opts());
1257
+ printJson(rt, await client.setPrimaryEmailDomain(domainId));
1258
+ });
1259
+ });
1260
+ domains.command("use-shared").description("Revert to the shared agent.seclai.com sending and inbound domain.").action(async () => {
1261
+ await run(rt, async () => {
1262
+ const client = createClient(program.opts());
1263
+ await client.useSharedEmailDomain();
1264
+ printJson(rt, { ok: true });
1265
+ });
1266
+ });
1267
+ domains.command("test-email").description("Send a test message from a verified domain to the account owner.").argument("<domainId>", "Domain ID.").action(async (domainId) => {
1268
+ await run(rt, async () => {
1269
+ const client = createClient(program.opts());
1270
+ printJson(rt, await client.sendEmailDomainTestEmail(domainId));
1271
+ });
1272
+ });
1273
+ 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) => {
1274
+ await run(rt, async () => {
1275
+ const client = createClient(program.opts());
1276
+ const o = {};
1277
+ if (opts.days !== void 0) o.days = opts.days;
1278
+ if (opts.topSources !== void 0) o.topSources = opts.topSources;
1279
+ printJson(rt, await client.getDmarcSummary(domainId, o));
1280
+ });
1281
+ });
1282
+ const blocked = email.command("blocked").description("Inbound email sender blocklist.");
1283
+ withOffsetListOptions(
1284
+ blocked.command("list").description("List blocked inbound senders (newest first) and the account's auto-block mode.")
1285
+ ).action(async (opts) => {
1286
+ await run(rt, async () => {
1287
+ const client = createClient(program.opts());
1288
+ printJson(rt, await client.listBlockedEmailSenders(offsetListOpts(opts)));
1289
+ });
1290
+ });
1291
+ 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'.").addOption(
1292
+ new Option("--match-type <type>", "'address' or 'domain'.").choices(["address", "domain"]).default("address")
1293
+ ).option("--note <text>", "Why the sender was blocked.").action(async (opts) => {
1294
+ await run(rt, async () => {
1295
+ const client = createClient(program.opts());
1296
+ const body = {
1297
+ sender_email: opts.senderEmail,
1298
+ match_type: opts.matchType
1299
+ };
1300
+ if (opts.note !== void 0) body.note = opts.note;
1301
+ printJson(rt, await client.blockEmailSender(body));
1302
+ });
1303
+ });
1304
+ blocked.command("remove").description("Unblock a sender.").argument("<blockedId>", "Blocklist entry ID.").action(async (blockedId) => {
1305
+ await run(rt, async () => {
1306
+ const client = createClient(program.opts());
1307
+ await client.unblockEmailSender(blockedId);
1308
+ printJson(rt, { ok: true });
1309
+ });
1310
+ });
1311
+ blocked.command("auto-block-mode").description("Set whether a governance BLOCK on an authenticated sender auto-adds them to the blocklist.").addArgument(
1312
+ // A closed set the API will only ever 422 on. Failing the parse names the
1313
+ // accepted values; the server names nothing.
1314
+ new Argument("<mode>", "How governance BLOCK verdicts feed the blocklist.").choices([
1315
+ "disabled",
1316
+ "input",
1317
+ "input_and_output"
1318
+ ])
1319
+ ).action(async (mode) => {
1320
+ await run(rt, async () => {
1321
+ const client = createClient(program.opts());
1322
+ printJson(rt, await client.setAutoBlockMode({ mode }));
1323
+ });
1324
+ });
1325
+ const inbound = email.command("inbound").description("Inbound email health and queue control.");
1326
+ inbound.command("status").description("Get inbound-email quota usage, pause state, and queued-run counts.").action(async () => {
1327
+ await run(rt, async () => {
1328
+ const client = createClient(program.opts());
1329
+ printJson(rt, await client.getInboundEmailStatus());
1330
+ });
1331
+ });
1332
+ 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) => {
1333
+ await run(rt, async () => {
1334
+ const client = createClient(program.opts());
1335
+ const o = {};
1336
+ if (opts.agentId !== void 0) o.agentId = opts.agentId;
1337
+ if (opts.limit !== void 0) o.limit = opts.limit;
1338
+ printJson(rt, await client.listInboundEmailRejections(o));
1339
+ });
1340
+ });
1341
+ inbound.command("cancel-queued").description("Fail all of the account's queued (over-quota) inbound-email runs at once.").action(async () => {
1342
+ await run(rt, async () => {
1343
+ const client = createClient(program.opts());
1344
+ printJson(rt, await client.cancelQueuedEmailRuns());
1345
+ });
1346
+ });
1347
+ inbound.command("resume").description("Manually lift the account-wide inbound-email pause.").action(async () => {
1348
+ await run(rt, async () => {
1349
+ const client = createClient(program.opts());
1350
+ printJson(rt, await client.resumeInboundEmail());
1351
+ });
1352
+ });
1353
+ const optouts = email.command("optouts").description("Recipients who opted out of agent email.");
1354
+ withOffsetListOptions(
1355
+ optouts.command("list").description("List agent-email opt-outs.").option("--agent-id <id>", "Restrict to one agent.")
1356
+ ).action(async (opts) => {
1357
+ await run(rt, async () => {
1358
+ const client = createClient(program.opts());
1359
+ const o = offsetListOpts(opts);
1360
+ if (opts.agentId !== void 0) o.agentId = opts.agentId;
1361
+ printJson(rt, await client.listAgentEmailOptOuts(o));
1362
+ });
1363
+ });
1364
+ optouts.command("remove").description("Remove an opt-out so the recipient can receive agent email again.").argument("<optoutId>", "Opt-out ID.").action(async (optoutId) => {
1365
+ await run(rt, async () => {
1366
+ const client = createClient(program.opts());
1367
+ await client.removeAgentEmailOptOut(optoutId);
1368
+ printJson(rt, { ok: true });
1369
+ });
1370
+ });
1371
+ }
1372
+
1373
+ // src/commands/account.ts
1374
+ import { SeclaiApiVersion } from "@seclai/sdk";
1375
+ function register11(program, rt) {
1376
+ program.command("me").description("Show the authenticated user's account ID and organization memberships.").action(async () => {
1377
+ await run(rt, async () => {
1378
+ const client = createClient(program.opts());
1379
+ printJson(rt, await client.getMe());
1380
+ });
1381
+ });
1382
+ const version = program.command("api-version").description("Read or pin the account's dated API version.");
1383
+ version.command("get").description(
1384
+ "Show the version a request resolves to. Reflects --api-version when passed, otherwise the account pin, otherwise the default."
1385
+ ).action(async () => {
1386
+ await run(rt, async () => {
1387
+ const client = createClient(program.opts());
1388
+ printJson(rt, await client.getApiVersion());
1389
+ });
1390
+ });
1391
+ 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) => {
1392
+ await run(rt, async () => {
1393
+ const known = Object.values(SeclaiApiVersion);
1394
+ if (!known.includes(date) && !program.opts().allowUnknownApiVersion) {
1395
+ throw new Error(
1396
+ `Unknown API version "${date}". This release knows ${[...new Set(known)].sort().join(", ")}. Pass --allow-unknown-api-version to pin it anyway.`
1397
+ );
1398
+ }
1399
+ const client = createClient(program.opts());
1400
+ printJson(rt, await client.updateApiVersion(date));
1401
+ });
1402
+ });
1403
+ version.command("clear").description("Remove the account's version pin, reverting to the default version.").action(async () => {
1404
+ await run(rt, async () => {
1405
+ const client = createClient(program.opts());
1406
+ printJson(rt, await client.updateApiVersion(null));
1407
+ });
1408
+ });
1409
+ }
1410
+
1411
+ // src/commands/models.ts
1412
+ function register12(program, rt) {
1098
1413
  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) => {
1414
+ 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
1415
  await run(rt, async () => {
1101
1416
  const client = createClient(program.opts());
1102
1417
  const o = {};
1103
1418
  if (opts.provider !== void 0) o.provider = opts.provider;
1104
1419
  if (opts.supportsToolUse !== void 0) o.supportsToolUse = opts.supportsToolUse;
1105
1420
  if (opts.supportsThinking !== void 0) o.supportsThinking = opts.supportsThinking;
1421
+ if (opts.supportsInputMedia !== void 0) o.supportsInputMedia = opts.supportsInputMedia;
1422
+ if (opts.supportsOutputMedia !== void 0) o.supportsOutputMedia = opts.supportsOutputMedia;
1106
1423
  printJson(rt, await client.listModels(o));
1107
1424
  });
1108
1425
  });
1426
+ models.command("tiers").description("Show each media-generation modality and tier with its model and cost.").action(async () => {
1427
+ await run(rt, async () => {
1428
+ const client = createClient(program.opts());
1429
+ printJson(rt, await client.getGenerationTiers());
1430
+ });
1431
+ });
1109
1432
  models.command("get").description("Get full details for a specific model.").argument("<modelId>", "Model ID.").action(async (modelId) => {
1110
1433
  await run(rt, async () => {
1111
1434
  const client = createClient(program.opts());
@@ -1113,10 +1436,14 @@ function register10(program, rt) {
1113
1436
  });
1114
1437
  });
1115
1438
  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) => {
1439
+ alerts.command("list").description("List model alerts.").option("--page <n>", "Page number.", parseNumber).option("--limit <n>", "Page size.", parseNumber).option(
1440
+ "--paged",
1441
+ "Wrap the results in {data: [...]}, the shape this endpoint moves to from --api-version 2026-07-27."
1442
+ ).action(async (opts) => {
1117
1443
  await run(rt, async () => {
1118
1444
  const client = createClient(program.opts());
1119
- printJson(rt, await client.listModelAlerts(listOpts(opts)));
1445
+ const res = await client.listModelAlerts(listOpts(opts));
1446
+ printJson(rt, opts.paged ? toPagedEnvelope(res, "alerts") : res);
1120
1447
  });
1121
1448
  });
1122
1449
  alerts.command("mark-read").description("Mark a model alert as read.").argument("<alertId>", "Alert ID.").action(async (alertId) => {
@@ -1146,15 +1473,15 @@ function register10(program, rt) {
1146
1473
  });
1147
1474
  });
1148
1475
  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) => {
1476
+ withOffsetListOptions(
1477
+ 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).")
1478
+ ).action(async (opts) => {
1150
1479
  await run(rt, async () => {
1151
1480
  const client = createClient(program.opts());
1152
- const o = {};
1481
+ const o = offsetListOpts(opts);
1153
1482
  if (opts.days !== void 0) o.days = opts.days;
1154
1483
  if (opts.startDate !== void 0) o.startDate = opts.startDate;
1155
1484
  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
1485
  printJson(rt, await client.listExperiments(o));
1159
1486
  });
1160
1487
  });
@@ -1187,20 +1514,31 @@ function register10(program, rt) {
1187
1514
  }
1188
1515
 
1189
1516
  // 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) => {
1517
+ import { Option as Option2 } from "commander";
1518
+ function register13(program, rt) {
1519
+ 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
1520
  await run(rt, async () => {
1193
1521
  const client = createClient(program.opts());
1194
1522
  const o = { query: opts.query };
1195
1523
  if (opts.limit !== void 0) o.limit = opts.limit;
1196
- if (opts.entityType) o.entityType = opts.entityType;
1524
+ if (opts.entityType !== void 0) o.entityType = opts.entityType;
1197
1525
  printJson(rt, await client.search(o));
1198
1526
  });
1199
1527
  });
1528
+ const docs = program.command("docs").description("Seclai documentation.");
1529
+ docs.command("search").description("Search the Seclai documentation.").requiredOption("--query <text>", "Search query text.").addOption(new Option2("--mode <mode>", "Search mode.").choices(["keyword", "semantic"])).option("--limit <n>", "Max results.", parseNumber).action(async (opts) => {
1530
+ await run(rt, async () => {
1531
+ const client = createClient(program.opts());
1532
+ const o = { query: opts.query };
1533
+ if (opts.mode !== void 0) o.mode = opts.mode;
1534
+ if (opts.limit !== void 0) o.limit = opts.limit;
1535
+ printJson(rt, await client.searchDocs(o));
1536
+ });
1537
+ });
1200
1538
  }
1201
1539
 
1202
1540
  // src/commands/ai.ts
1203
- function register12(program, rt) {
1541
+ function register14(program, rt) {
1204
1542
  const ai = program.command("ai").description("Top-level AI assistant.");
1205
1543
  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
1544
  await run(rt, async () => {
@@ -1278,73 +1616,138 @@ function register12(program, rt) {
1278
1616
  import { existsSync, statSync } from "fs";
1279
1617
  import { mkdir, writeFile } from "fs/promises";
1280
1618
  import { dirname, join } from "path";
1281
- var SKILL_MD = `---
1619
+ var SKILL_FILES = [
1620
+ { name: "SKILL.md", content: `---
1282
1621
  name: seclai-cli
1283
1622
  description: >-
1284
1623
  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.
1624
+ solutions, governance, alerts, agent email, and models via the CLI. Use when
1625
+ working with the Seclai platform or when the user mentions Seclai CLI commands.
1287
1626
  ---
1288
1627
 
1289
1628
  # Seclai CLI
1290
1629
 
1291
- The Seclai CLI (\`seclai\` / \`npx @seclai/cli\`) manages agents, knowledge bases, sources, memory banks, evaluations, solutions, governance, alerts, and more from the terminal.
1630
+ The Seclai CLI (\`seclai\` / \`npx @seclai/cli\`) manages agents, knowledge bases,
1631
+ sources, memory banks, evaluations, solutions, governance, alerts, agent email,
1632
+ and models from the terminal.
1292
1633
 
1293
- All commands output JSON to stdout. Pipe into \`jq\` for filtering.
1634
+ Every command writes JSON to stdout. Pipe into \`jq\` for filtering. Errors go to
1635
+ stderr and set a non-zero exit code, so \`set -e\` scripts fail as expected.
1636
+
1637
+ **Find the commands for a task in the map below, then read that reference file.**
1638
+ Only this page is loaded up front; the references are read on demand.
1294
1639
 
1295
1640
  ## Quick start
1296
1641
 
1297
1642
  \`\`\`bash
1298
- # authenticate
1299
1643
  export SECLAI_API_KEY="sk-..."
1300
1644
 
1301
- # create an agent
1302
1645
  seclai agents create --json '{"name":"My Agent","description":"QA chatbot"}'
1303
-
1304
- # configure steps via AI assistant
1305
1646
  seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot that uses a knowledge base"
1306
-
1307
- # accept the generated plan
1308
1647
  seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1309
-
1310
- # run the agent
1311
1648
  seclai agents run <agentId> --json '{"input":"How do I reset my password?"}' --stream
1312
-
1313
- # list runs
1314
1649
  seclai agents runs list <agentId>
1315
1650
  \`\`\`
1316
1651
 
1652
+ ## Command map
1653
+
1654
+ | Group | What it covers | Reference |
1655
+ | --- | --- | --- |
1656
+ | \`agents\` | Agents, runs, definitions, export/import, input uploads, triggers, agent AI | [references/agents.md](references/agents.md) |
1657
+ | \`sources\` \`contents\` \`kb\` \`memory\` | Sources and uploads, exports, embedding migration, indexed content, knowledge bases, memory banks | [references/knowledge.md](references/knowledge.md) |
1658
+ | \`evals\` | Evaluation criteria, results, runs, agent-level summaries | [references/evaluations.md](references/evaluations.md) |
1659
+ | \`solutions\` \`governance\` | Solutions, resource links, conversations, solution and governance AI | [references/solutions.md](references/solutions.md) |
1660
+ | \`alerts\` | Alerts, alert configurations, organization preferences | [references/alerts.md](references/alerts.md) |
1661
+ | \`email\` | Agent email: sending domains, inbound blocklist, inbound health, opt-outs | [references/email.md](references/email.md) |
1662
+ | \`models\` | Model catalog, generation tiers, model alerts, recommendations, playground experiments | [references/models.md](references/models.md) |
1663
+ | \`auth\` \`configure\` \`api-version\` \`mcp\` \`skills\` \`completion\` | Authentication, profiles, API version pinning, editor integration | [references/setup.md](references/setup.md) |
1664
+ | \`ai\` | Top-level AI assistant for knowledge bases, sources, solutions and memory | [references/ai-assistant.md](references/ai-assistant.md) |
1665
+ | \`search\` \`docs\` \`me\` | Search across resources, search the documentation, show the authenticated account | below, under [Search and account](#search-and-account) |
1666
+
1667
+ Cross-cutting topics: [streaming and event modes](references/streaming.md),
1668
+ [file uploads](references/uploads.md).
1669
+
1317
1670
  ## Authentication
1318
1671
 
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).
1672
+ Two modes:
1673
+
1674
+ 1. **API key** \u2014 set \`SECLAI_API_KEY\`, or pass \`--api-key <key>\`.
1675
+ 2. **SSO** \u2014 \`seclai auth login\` for browser-based OAuth2/PKCE. Tokens are cached
1676
+ locally and refreshed automatically.
1677
+
1678
+ Override the API host with \`SECLAI_API_URL\` (default \`https://api.seclai.com\`).
1321
1679
 
1322
1680
  ## Global options
1323
1681
 
1324
1682
  \`\`\`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
1683
+ --api-key <key> # or set SECLAI_API_KEY
1684
+ --profile <name> # SSO profile (or SECLAI_PROFILE, default 'default')
1685
+ --account-id <id> # multi-org targeting (X-Account-Id header)
1686
+ --config-dir <path> # or SECLAI_CONFIG_DIR, default ~/.seclai
1687
+ --api-version <date> # or SECLAI_API_VERSION; see below
1688
+ --allow-unknown-api-version # send a version this CLI was not built against
1689
+ --compact # single-line JSON
1690
+ -V, --version
1691
+ \`\`\`
1692
+
1693
+ ## API versions
1694
+
1695
+ The API is versioned by date, and a version can change a response's shape \u2014 a
1696
+ bare array becoming \`{data, pagination}\`, for instance. **The CLI sends no
1697
+ version header by default**, so upgrading it never changes what a command
1698
+ prints. Opt in per invocation, or pin the account:
1699
+
1700
+ \`\`\`bash
1701
+ seclai api-version get # what does a request resolve to?
1702
+ seclai --api-version 2026-07-27 alerts list # this invocation only
1703
+ seclai api-version set 2026-07-27 # every client on the account
1704
+ seclai api-version clear
1328
1705
  \`\`\`
1329
1706
 
1707
+ An \`--api-version\` this CLI was not built against is rejected, because a newer
1708
+ version can reshape a response the CLI would then misread. Pass
1709
+ \`--allow-unknown-api-version\` to send it anyway. \`api-version set\` takes a
1710
+ \`YYYY-MM-DD\` date and rejects anything else, because the pin applies to every
1711
+ client on the account.
1712
+
1713
+ \`--api-key\`, \`--profile\`, \`--account-id\` and \`--config-dir\` reject an empty
1714
+ value. A shell expanding an unset variable passes \`""\`, which the SDK's
1715
+ credential chain discards, so each would silently resolve elsewhere \u2014 a
1716
+ different identity, another account's cached tokens, or the default org. Guard
1717
+ the flag rather than the value: \`seclai \${KEY:+--api-key "$KEY"} agents list\`.
1718
+
1719
+ An empty \`--api-version\` is accepted with a warning, since it costs only the
1720
+ version header; a future release will reject it too.
1721
+
1330
1722
  ## Common patterns
1331
1723
 
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.
1724
+ **JSON input.** Most create/update commands take \`--json '{"key":"value"}'\` or
1725
+ \`--json-file path.json\`. Use \`-\` as the value to read from stdin.
1726
+
1727
+ **AI shorthand.** AI generation commands accept \`--user-input <text>\` in place of
1728
+ \`--json '{"user_input":"<text>"}'\`.
1729
+
1730
+ **Pagination.** List commands take \`--page <n>\` and \`--limit <n>\`; some add
1731
+ \`--sort <field>\` and \`--order asc|desc\`. A few endpoints paginate by offset
1732
+ instead and take \`--limit\` / \`--offset\`.
1335
1733
 
1336
- ### AI assistant shorthand
1337
- AI generation commands accept \`--user-input <text>\` as shorthand for \`--json '{"user_input":"<text>"}'\`.
1734
+ **Uploads.** Upload commands take \`--file <path>\`, plus optional \`--title\`,
1735
+ \`--metadata '{"k":"v"}'\`, \`--metadata-file\`, \`--file-name\` and \`--mime-type\`.
1338
1736
 
1339
- ### Pagination
1340
- List commands support \`--page <n>\` and \`--limit <n>\`. Some also support \`--sort <field>\` and \`--order asc|desc\`.
1737
+ ## Search and account
1341
1738
 
1342
- ### File uploads
1343
- Upload commands accept \`--file <path>\` (required), plus optional \`--title\`, \`--metadata '{"k":"v"}'\`, \`--metadata-file path.json\`, \`--file-name\`, \`--mime-type\`.
1739
+ \`\`\`bash
1740
+ seclai search --query "deployment guide" [--limit N] [--entity-type <type>]
1741
+ seclai docs search --query "memory banks" [--mode keyword|semantic] [--limit N]
1742
+ seclai me # account ID and organization memberships
1743
+ \`\`\`
1744
+ ` },
1745
+ { name: "references/agents.md", content: `# Agents
1344
1746
 
1345
- ## Commands
1747
+ Agents, their runs, definitions, export/import, input uploads, triggers, and the
1748
+ agent AI assistant.
1346
1749
 
1347
- ### Agents
1750
+ ## CRUD and lifecycle
1348
1751
 
1349
1752
  \`\`\`bash
1350
1753
  seclai agents list [--page N] [--limit N]
@@ -1352,100 +1755,382 @@ seclai agents create --json '{"name":"My Agent","description":"..."}'
1352
1755
  seclai agents get <agentId>
1353
1756
  seclai agents update <agentId> --json '{"name":"Renamed"}'
1354
1757
  seclai agents delete <agentId>
1758
+
1759
+ # pause across every trigger path (API, schedule, email, sub-agent calls)
1760
+ seclai agents disable <agentId>
1761
+ seclai agents enable <agentId>
1762
+
1763
+ # which live agents call this one via a call_agent step?
1764
+ seclai agents callers <agentId>
1765
+ \`\`\`
1766
+
1767
+ ## Triggers
1768
+
1769
+ \`\`\`bash
1770
+ # alias, sender allowlist and inbound-handling flags for an EMAIL_RECEIVED trigger
1771
+ seclai agents triggers email-config <agentId> <triggerId> --json '{"alias":"support"}'
1355
1772
  \`\`\`
1356
1773
 
1357
- ### Running agents
1774
+ ## Running agents
1775
+
1776
+ Four modes: basic, streaming, NDJSON events, and polling. See
1777
+ [streaming.md](streaming.md) for event shapes and filtering.
1358
1778
 
1359
1779
  \`\`\`bash
1360
1780
  # simple run \u2014 returns the final result
1361
1781
  seclai agents run <agentId> --json '{"input":"Hello"}'
1362
1782
 
1363
- # stream \u2014 wait for completion via SSE, print final result
1783
+ # stream \u2014 wait for completion via SSE, print the final result
1364
1784
  seclai agents run <agentId> --json '{"input":"Hello"}' --stream [--timeout-ms 60000]
1365
1785
 
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"
1786
+ # events \u2014 every SSE event as an NDJSON line
1787
+ # --output: full (entire event), data (event data only), status (one-line summary)
1788
+ # --event-filter: comma-separated event types, e.g. "status,data"
1369
1789
  seclai agents run <agentId> --json '{"input":"Hello"}' --events [--output full|data|status] [--event-filter "status,data"]
1370
1790
 
1371
- # poll \u2014 poll for completion
1791
+ # poll \u2014 submit, then poll until complete
1372
1792
  seclai agents run <agentId> --json '{"input":"Hello"}' --poll [--poll-interval-ms 2000] [--include-step-outputs]
1373
1793
  \`\`\`
1374
1794
 
1375
- ### Agent runs
1795
+ ## Runs
1376
1796
 
1377
1797
  \`\`\`bash
1378
1798
  seclai agents runs list <agentId> [--page N] [--limit N] [--status <status>]
1379
1799
  seclai agents runs get <runId> [--include-step-outputs]
1380
- seclai agents runs delete <runId>
1381
1800
  seclai agents runs cancel <runId>
1801
+ seclai agents runs delete <runId> # deprecated alias for \`runs cancel\`; the API has no delete-a-run operation
1382
1802
  seclai agents runs search --json '{"query":"..."}'
1383
1803
  seclai agents runs eval-results <agentId> <runId> [--page N] [--limit N]
1804
+
1805
+ # Download a file emitted by a run step. attachmentId is the URL-safe-base64
1806
+ # storage_key from run output manifests or webhooks.
1807
+ seclai agents runs download-attachment <runId> <attachmentId> [--download-name <name>] [--output <path>]
1384
1808
  \`\`\`
1385
1809
 
1386
- ### Agent definitions
1810
+ Without \`--output\`, raw bytes go to stdout \u2014 redirect to a file rather than
1811
+ letting them hit the terminal.
1812
+
1813
+ ## Definitions
1387
1814
 
1388
1815
  \`\`\`bash
1389
1816
  seclai agents def get <agentId>
1390
- seclai agents def update <agentId> --json '{"steps":[{"step_type":"llm","config":{...}}]}'
1817
+ seclai agents def update <agentId> --json '{"steps":[{"step_type":"llm","config":{}}]}'
1391
1818
  \`\`\`
1392
1819
 
1393
- ### Agent input uploads
1820
+ ## Export and import
1394
1821
 
1395
1822
  \`\`\`bash
1823
+ # portable JSON snapshot of an agent definition
1824
+ seclai agents export <agentId> [--no-download]
1825
+
1826
+ # Validate an agent_definition payload before importing \u2014 no writes.
1827
+ # Reports counts and any unresolved_refs (knowledge bases, memory banks, source
1828
+ # connections or sub-agents that do not exist in this account).
1829
+ seclai agents export <agentId> \\
1830
+ | jq '{agent_definition: .}' \\
1831
+ | seclai agents preview-import --json-file -
1832
+
1833
+ # Import via \`agents create\` (or \`agents update\`) with agent_definition set to
1834
+ # the export payload, and entity_remap mapping unresolved source UUIDs to target
1835
+ # UUIDs taken from preview-import's unresolved_refs[*].alternatives.
1836
+ seclai agents create --json '{"name":"Imported","trigger_type":"dynamic_input","agent_definition":{},"entity_remap":{}}'
1837
+ \`\`\`
1838
+
1839
+ ## Input uploads
1840
+
1841
+ \`\`\`bash
1842
+ # What files (if any) does this agent expect? requires_uploads reports whether it
1843
+ # accepts files; the agent block lists the names, indexes and patterns a run-time
1844
+ # batch must satisfy. Call this before staging uploads.
1845
+ seclai agents attachment-references <agentId>
1846
+
1396
1847
  seclai agents upload-input <agentId> --file ./input.pdf [--file-name name] [--mime-type type]
1397
1848
  seclai agents input-status <agentId> <uploadId>
1398
1849
  \`\`\`
1399
1850
 
1400
- ### Agent AI assistant
1851
+ ## Agent AI assistant
1401
1852
 
1402
1853
  \`\`\`bash
1403
1854
  seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot"
1404
1855
  seclai agents ai step-config <agentId> --json '{"step_type":"llm","user_input":"Configure the LLM step"}'
1405
- seclai agents ai history <agentId>
1856
+
1857
+ # --step-type is required; the API rejects the request without it
1858
+ seclai agents ai history <agentId> --step-type llm [--step-id <id>] [--limit N] [--offset N]
1859
+
1406
1860
  seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1407
1861
  \`\`\`
1408
1862
 
1409
- ### Sources
1863
+ ## Example: knowledge-base-backed agent
1410
1864
 
1411
1865
  \`\`\`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>
1866
+ seclai kb create --json '{"name":"Support KB","description":"Customer support articles"}'
1867
+ seclai agents create --json '{"name":"Support Bot","description":"Answers customer questions"}'
1868
+ seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot that searches the Support KB"
1869
+ seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1870
+ seclai agents run <agentId> --json '{"input":"How do I reset my password?"}' --stream
1417
1871
  \`\`\`
1418
1872
 
1419
- ### Source uploads
1873
+ ## Example: memory-powered agent
1420
1874
 
1421
1875
  \`\`\`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"}'
1876
+ seclai memory create --json '{"name":"User Preferences","type":"general"}'
1877
+ seclai agents create --json '{"name":"Personal Assistant","description":"Remembers user preferences"}'
1878
+ seclai agents ai gen-steps <agentId> --user-input "Build a chat agent that remembers user preferences. Use general memory bank <memoryBankId>"
1879
+ seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1424
1880
  \`\`\`
1881
+ ` },
1882
+ { name: "references/ai-assistant.md", content: `# Top-level AI assistant
1425
1883
 
1426
- ### Source exports
1884
+ \`seclai ai\` creates resources from a natural-language description, without
1885
+ starting from a solution or an agent. The domain-scoped assistants \u2014
1886
+ \`agents ai\`, \`memory ai\`, \`solutions ai\`, \`governance ai\` \u2014 live with their
1887
+ resources.
1427
1888
 
1428
1889
  \`\`\`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"}'
1890
+ seclai ai kb --user-input "Create a support knowledge base"
1891
+ seclai ai source --user-input "Create a documentation source"
1892
+ seclai ai solution --user-input "Build a customer support solution"
1893
+ seclai ai memory --user-input "Create a conversation memory bank"
1894
+
1895
+ seclai ai memory-history
1896
+ seclai ai accept <conversationId> --json '{"accepted":true}'
1897
+ seclai ai decline <conversationId>
1898
+ seclai ai memory-accept <conversationId> --json '{"accepted":true}'
1899
+
1900
+ seclai ai feedback --json '{"feedback":"The response was helpful"}'
1436
1901
  \`\`\`
1437
1902
 
1438
- ### Embedding migration
1903
+ ## The generate-then-accept cycle
1904
+
1905
+ Every assistant command returns a *proposal* with a conversation ID. Nothing is
1906
+ created until you accept it:
1439
1907
 
1440
1908
  \`\`\`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>
1909
+ seclai ai kb --user-input "Create a support knowledge base"
1910
+ # read the proposal, note the conversation id
1911
+ seclai ai accept <conversationId> --json '{"accepted":true}'
1444
1912
  \`\`\`
1445
1913
 
1446
- ### Contents (indexed content)
1914
+ Memory-bank proposals have their own accept command (\`ai memory-accept\`) and
1915
+ their own history (\`ai memory-history\`); everything else uses \`ai accept\` /
1916
+ \`ai decline\`.
1917
+ ` },
1918
+ { name: "references/alerts.md", content: `# Alerts
1447
1919
 
1448
- \`\`\`bash
1920
+ Account alerts, the configurations that raise them, and per-organization
1921
+ delivery preferences.
1922
+
1923
+ Model-catalog alerts are separate \u2014 see [models.md](models.md).
1924
+
1925
+ ## Alerts
1926
+
1927
+ \`\`\`bash
1928
+ seclai alerts list [--page N] [--limit N] [--status <status>]
1929
+ seclai alerts get <alertId>
1930
+ seclai alerts status <alertId> --json '{"status":"resolved"}'
1931
+ seclai alerts comment <alertId> --json '{"comment":"Fixed the issue"}'
1932
+ seclai alerts subscribe <alertId>
1933
+ seclai alerts unsubscribe <alertId>
1934
+ \`\`\`
1935
+
1936
+ \`GET /alerts\` declares no severity filter, so there is no \`--severity\` \u2014 it
1937
+ never filtered anything, and returned unfiltered rows that looked filtered.
1938
+ Filter client-side instead:
1939
+
1940
+ \`\`\`bash
1941
+ seclai alerts list | jq '[.data[] | select(.severity == "high")]'
1942
+ \`\`\`
1943
+
1944
+ ## Alert configurations
1945
+
1946
+ \`\`\`bash
1947
+ seclai alerts configs list [--page N] [--limit N]
1948
+ seclai alerts configs create --json '{"name":"Latency Alert","description":"...","threshold":5000}'
1949
+ seclai alerts configs get <configId>
1950
+ seclai alerts configs update <configId> --json '{"threshold":3000}'
1951
+ seclai alerts configs delete <configId>
1952
+ \`\`\`
1953
+
1954
+ ## Organization preferences
1955
+
1956
+ \`\`\`bash
1957
+ seclai alerts prefs list
1958
+ seclai alerts prefs update <organizationId> <alertType> --json '{"enabled":true}'
1959
+ \`\`\`
1960
+
1961
+ Preferences are per organization and per alert type, so \`update\` takes both.
1962
+ ` },
1963
+ { name: "references/email.md", content: `# Agent email
1964
+
1965
+ The domains agents send from, the inbound blocklist, inbound health, and
1966
+ recipient opt-outs.
1967
+
1968
+ Per-agent inbound configuration (alias, sender allowlist) lives on the trigger \u2014
1969
+ see \`agents triggers email-config\` in [agents.md](agents.md).
1970
+
1971
+ ## Sending domains
1972
+
1973
+ \`\`\`bash
1974
+ seclai email domains list
1975
+ seclai email domains add --kind custom --value mail.example.com [--delegated]
1976
+ seclai email domains verify <domainId> # run a DNS check now
1977
+ seclai email domains set-primary <domainId>
1978
+ seclai email domains test-email <domainId> # send a test to the account owner
1979
+ seclai email domains dmarc <domainId> [--days N] [--top-sources N]
1980
+ seclai email domains remove <domainId>
1981
+ seclai email domains use-shared # revert to agent.seclai.com
1982
+ \`\`\`
1983
+
1984
+ \`--kind\` is \`vanity\` (a subdomain of seclai.com) or \`custom\` (your own domain).
1985
+ \`add\` returns the DNS records to publish; pass \`--delegated\` when the domain's
1986
+ DNS is delegated to Seclai so those records are published for you. A domain must
1987
+ verify before \`set-primary\` will accept it.
1988
+
1989
+ ## Inbound sender blocklist
1990
+
1991
+ \`\`\`bash
1992
+ seclai email blocked list [--limit N] [--offset N]
1993
+ seclai email blocked add --sender-email spam@example.com [--note "phishing"]
1994
+ seclai email blocked add --sender-email example.com --match-type domain
1995
+ seclai email blocked remove <blockedId>
1996
+ seclai email blocked auto-block-mode disabled|input|input_and_output
1997
+ \`\`\`
1998
+
1999
+ \`--match-type\` is \`address\` (the default) or \`domain\`. \`auto-block-mode\` controls
2000
+ whether a governance BLOCK on an authenticated sender adds them to the blocklist
2001
+ automatically.
2002
+
2003
+ ## Inbound health
2004
+
2005
+ \`\`\`bash
2006
+ seclai email inbound status # quota usage, pause state, queued run counts
2007
+ seclai email inbound rejections [--agent-id <id>] [--limit N]
2008
+ seclai email inbound cancel-queued # fail every over-quota parked run at once
2009
+ seclai email inbound resume # lift the account-wide pause
2010
+ \`\`\`
2011
+
2012
+ When inbound email exceeds quota, runs park in a QUEUED state and the account
2013
+ pauses. \`status\` shows both; \`cancel-queued\` clears the backlog and \`resume\`
2014
+ lifts the pause. Check \`rejections\` first \u2014 it reports why messages were turned
2015
+ away, which is usually the more useful answer.
2016
+
2017
+ ## Recipient opt-outs
2018
+
2019
+ \`\`\`bash
2020
+ seclai email optouts list [--agent-id <id>] [--limit N] [--offset N]
2021
+ seclai email optouts remove <optoutId>
2022
+ \`\`\`
2023
+
2024
+ Removing an opt-out lets that recipient receive agent email again.
2025
+ ` },
2026
+ { name: "references/evaluations.md", content: `# Evaluations Workflow
2027
+
2028
+ ## Step 1: Create evaluation criteria for an agent
2029
+ \`\`\`bash
2030
+ seclai evals criteria create <agentId> --json '{"name":"Answer Accuracy","description":"Does the answer correctly address the question?","eval_type":"llm_judge"}'
2031
+ \`\`\`
2032
+
2033
+ ## Step 2: Find runs to evaluate
2034
+ \`\`\`bash
2035
+ # list all runs for an agent
2036
+ seclai agents runs list <agentId> --limit 10
2037
+
2038
+ # or find runs compatible with specific criteria
2039
+ seclai evals compatible-runs <criteriaId> --limit 10
2040
+ \`\`\`
2041
+
2042
+ ## Step 3: Test criteria before committing
2043
+ \`\`\`bash
2044
+ seclai evals test-draft <agentId> --json '{"criteria":{"name":"Answer Accuracy","eval_type":"llm_judge","description":"..."},"run_id":"<runId>"}'
2045
+ \`\`\`
2046
+
2047
+ ## Step 4: Create evaluation results
2048
+ \`\`\`bash
2049
+ seclai evals results create <criteriaId> --json '{"run_id":"<runId>","score":0.95}'
2050
+ \`\`\`
2051
+
2052
+ ## Step 5: Review summaries
2053
+ \`\`\`bash
2054
+ seclai evals criteria summary <criteriaId>
2055
+ seclai evals agent-results <agentId>
2056
+ seclai evals agent-runs <agentId> --limit 20
2057
+ seclai evals non-manual-summary <agentId>
2058
+ \`\`\`
2059
+
2060
+ ## Managing criteria
2061
+ \`\`\`bash
2062
+ seclai evals criteria list <agentId> [--page N] [--limit N] [--paged]
2063
+ seclai evals criteria get <criteriaId>
2064
+ seclai evals criteria update <criteriaId> --json '{"name":"Updated Name"}'
2065
+ seclai evals criteria delete <criteriaId>
2066
+ \`\`\`
2067
+
2068
+ \`--paged\` wraps the results in \`{"data": [...]}\` instead of returning a bare
2069
+ array, so \`.data\` is a stable path to read whatever \`--api-version\` is in effect.
2070
+ Nothing is invented: the \`pagination\` block appears only once the API sends one,
2071
+ from \`--api-version 2026-07-27\`. Move scripts to \`.data\` first, then opt in to
2072
+ get \`.pagination\`.
2073
+
2074
+ ## Viewing results
2075
+ \`\`\`bash
2076
+ seclai evals results list <criteriaId> [--page N] [--limit N]
2077
+ seclai evals compatible-runs <criteriaId> [--page N] [--limit N]
2078
+ seclai evals agent-results <agentId> [--page N] [--limit N]
2079
+ seclai evals agent-runs <agentId> [--page N] [--limit N]
2080
+ \`\`\`
2081
+ ` },
2082
+ { name: "references/knowledge.md", content: `# Sources, content, knowledge bases and memory banks
2083
+
2084
+ The ingestion side of Seclai: where documents come from, how they are indexed,
2085
+ and the stores agents read from.
2086
+
2087
+ For upload mechanics \u2014 MIME types, size limits, metadata \u2014 see
2088
+ [uploads.md](uploads.md).
2089
+
2090
+ ## Sources
2091
+
2092
+ \`\`\`bash
2093
+ seclai sources list [--page N] [--limit N] [--sort field] [--order asc|desc] [--account-id id]
2094
+ seclai sources create --json '{"name":"Docs","description":"Product documentation"}'
2095
+ seclai sources get <sourceId>
2096
+ seclai sources update <sourceId> --json '{"name":"Updated Docs"}'
2097
+ seclai sources delete <sourceId>
2098
+ \`\`\`
2099
+
2100
+ \`source\` is accepted as an alias for \`sources\`.
2101
+
2102
+ ## Source uploads
2103
+
2104
+ \`\`\`bash
2105
+ seclai sources upload <sourceId> --file ./doc.pdf [--title "My Doc"] [--metadata '{"category":"docs"}'] [--file-name name] [--mime-type type]
2106
+ seclai sources upload-text <sourceId> --json '{"text":"Article content here...","title":"My Article"}'
2107
+ \`\`\`
2108
+
2109
+ ## Source exports
2110
+
2111
+ \`\`\`bash
2112
+ seclai sources exports list <sourceId> [--page N] [--limit N]
2113
+ seclai sources exports create <sourceId> --json '{"format":"jsonl"}'
2114
+ seclai sources exports get <sourceId> <exportId>
2115
+ seclai sources exports cancel <sourceId> <exportId>
2116
+ seclai sources exports delete <sourceId> <exportId>
2117
+ seclai sources exports download <sourceId> <exportId>
2118
+ seclai sources exports estimate <sourceId> --json '{"format":"jsonl"}'
2119
+ \`\`\`
2120
+
2121
+ \`estimate\` reports the size and cost before you commit to \`create\`.
2122
+
2123
+ ## Embedding migration
2124
+
2125
+ \`\`\`bash
2126
+ seclai sources migration get <sourceId>
2127
+ seclai sources migration start <sourceId> --json '{"target_model":"text-embedding-3-large"}'
2128
+ seclai sources migration cancel <sourceId>
2129
+ \`\`\`
2130
+
2131
+ ## Contents (indexed content)
2132
+
2133
+ \`\`\`bash
1449
2134
  seclai contents get <contentVersionId> [--start N] [--end N]
1450
2135
  seclai contents delete <contentVersionId>
1451
2136
  seclai contents upload <contentVersionId> --file ./updated.pdf [--title "Title"] [--file-name name] [--mime-type type]
@@ -1453,7 +2138,10 @@ seclai contents replace-text <contentVersionId> --json '{"text":"Replacement tex
1453
2138
  seclai contents embeddings <contentVersionId> [--page N] [--limit N]
1454
2139
  \`\`\`
1455
2140
 
1456
- ### Knowledge bases
2141
+ \`--start\` / \`--end\` on \`contents get\` slice the returned text by character
2142
+ offset, which is how you inspect a long document without pulling all of it.
2143
+
2144
+ ## Knowledge bases
1457
2145
 
1458
2146
  \`\`\`bash
1459
2147
  seclai kb list [--page N] [--limit N] [--sort field] [--order asc|desc]
@@ -1463,7 +2151,7 @@ seclai kb update <kbId> --json '{"name":"Updated KB"}'
1463
2151
  seclai kb delete <kbId>
1464
2152
  \`\`\`
1465
2153
 
1466
- ### Memory banks
2154
+ ## Memory banks
1467
2155
 
1468
2156
  \`\`\`bash
1469
2157
  seclai memory list [--page N] [--limit N] [--sort field] [--order asc|desc]
@@ -1474,11 +2162,11 @@ seclai memory update <memoryBankId> --json '{"name":"Renamed"}'
1474
2162
  seclai memory delete <memoryBankId>
1475
2163
  \`\`\`
1476
2164
 
1477
- ### Memory bank utilities
2165
+ ### Utilities
1478
2166
 
1479
2167
  \`\`\`bash
1480
2168
  seclai memory stats <memoryBankId>
1481
- seclai memory agents <memoryBankId>
2169
+ seclai memory agents <memoryBankId> # agents using this bank
1482
2170
  seclai memory compact <memoryBankId>
1483
2171
  seclai memory delete-source <memoryBankId>
1484
2172
  seclai memory templates
@@ -1486,6 +2174,9 @@ seclai memory test-compaction <memoryBankId> --json '{"prompt":"Summarize the co
1486
2174
  seclai memory test-compaction-standalone --json '{"prompt":"Summarize the conversation"}'
1487
2175
  \`\`\`
1488
2176
 
2177
+ Both \`test-compaction\` commands are dry runs \u2014 they show what compaction would
2178
+ produce without writing to the bank.
2179
+
1489
2180
  ### Memory bank AI
1490
2181
 
1491
2182
  \`\`\`bash
@@ -1494,180 +2185,190 @@ seclai memory ai last
1494
2185
  seclai memory ai accept <conversationId> --json '{"accepted":true}'
1495
2186
  \`\`\`
1496
2187
 
1497
- ### Evaluations \u2014 criteria
2188
+ ## Example: create a source and upload content
1498
2189
 
1499
2190
  \`\`\`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"}'
1502
- seclai evals criteria get <criteriaId>
1503
- seclai evals criteria update <criteriaId> --json '{"name":"Updated Criteria"}'
1504
- seclai evals criteria delete <criteriaId>
1505
- seclai evals criteria summary <criteriaId>
2191
+ seclai sources create --json '{"name":"Product Docs","description":"Product documentation source"}'
2192
+ # note the id from the output
2193
+ seclai sources upload <sourceId> --file ./docs.pdf --title "Product Manual" --metadata '{"version":"2.0"}'
2194
+ seclai sources get <sourceId>
1506
2195
  \`\`\`
2196
+ ` },
2197
+ { name: "references/models.md", content: `# Models
1507
2198
 
1508
- ### Evaluations \u2014 results & runs
2199
+ The model catalog, media-generation tiers, model-catalog alerts, recommendations
2200
+ and the playground.
1509
2201
 
1510
- \`\`\`bash
1511
- seclai evals results list <criteriaId> [--page N] [--limit N]
1512
- seclai evals results create <criteriaId> --json '{"run_id":"...","score":0.9}'
1513
- 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
- seclai evals agent-results <agentId> [--page N] [--limit N]
1516
- seclai evals agent-runs <agentId> [--page N] [--limit N]
1517
- seclai evals non-manual-summary <agentId>
1518
- \`\`\`
1519
-
1520
- ### Solutions
2202
+ ## Catalog
1521
2203
 
1522
2204
  \`\`\`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>
2205
+ seclai models list [--provider <name>] [--supports-tool-use] [--supports-thinking]
2206
+ seclai models list [--supports-input-media <media>] [--supports-output-media <media>]
2207
+ seclai models get <modelId>
2208
+
2209
+ # each media-generation modality and tier, with its model and cost
2210
+ seclai models tiers
1528
2211
  \`\`\`
1529
2212
 
1530
- ### Solution links
2213
+ The capability flags compose, so \`--supports-tool-use --supports-thinking\`
2214
+ returns only models with both. \`--supports-input-media\` / \`--supports-output-media\`
2215
+ take a modality such as \`image\`, \`audio\` or \`video\`.
2216
+
2217
+ ## Model alerts
1531
2218
 
1532
2219
  \`\`\`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"]'
2220
+ seclai models alerts list [--page N] [--limit N]
2221
+ seclai models alerts mark-read <alertId>
2222
+ seclai models alerts mark-all-read
2223
+ seclai models alerts unread-count
1536
2224
  \`\`\`
1537
2225
 
1538
- ### Solution conversations & AI
2226
+ These are catalog alerts \u2014 deprecations, price changes, new models \u2014 not the
2227
+ account alerts in [alerts.md](alerts.md).
1539
2228
 
1540
- \`\`\`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}'
2229
+ ## Recommendations
1544
2230
 
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>
2231
+ \`\`\`bash
2232
+ seclai models recommendations <modelId>
1550
2233
  \`\`\`
1551
2234
 
1552
- ### Alerts
2235
+ Suggests replacements for a model, which is how you act on a deprecation alert.
2236
+
2237
+ ## Playground experiments
1553
2238
 
1554
2239
  \`\`\`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>
2240
+ seclai models experiments list [--days N] [--start-date <date>] [--end-date <date>] [--limit N] [--offset N]
2241
+ seclai models experiments create --json '{"model_ids":["gpt-4o"],"prompt":"Compare responses"}'
2242
+ seclai models experiments get <experimentId>
2243
+ seclai models experiments cancel <experimentId>
2244
+ seclai models experiments delete <experimentId> # soft-delete, preserves audit history
1561
2245
  \`\`\`
1562
2246
 
1563
- ### Alert configurations
2247
+ \`create\` takes several \`model_ids\` and runs the same prompt against each, which
2248
+ is the point \u2014 side-by-side comparison. \`cancel\` stops a running experiment;
2249
+ \`delete\` soft-deletes a finished one.
2250
+ ` },
2251
+ { name: "references/setup.md", content: `# Setup: authentication, profiles, API version, editor integration
2252
+
2253
+ ## SSO authentication
1564
2254
 
1565
2255
  \`\`\`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>
2256
+ seclai auth login [--port <port>] [--no-browser] # OAuth2 + PKCE in the browser
2257
+ seclai auth status # active profile's auth state
2258
+ seclai auth refresh # refresh the token manually
2259
+ seclai auth logout # clear cached tokens
1571
2260
  \`\`\`
1572
2261
 
1573
- ### Alert preferences
2262
+ Tokens are cached under the config directory and refreshed automatically, so
2263
+ \`auth refresh\` is only needed to force it. An API key in \`SECLAI_API_KEY\` takes a
2264
+ different path entirely and needs none of this.
2265
+
2266
+ ## Profiles
1574
2267
 
1575
2268
  \`\`\`bash
1576
- seclai alerts prefs list
1577
- seclai alerts prefs update <organizationId> <alertType> --json '{"enabled":true}'
2269
+ seclai configure sso [--profile-name <name>] # interactive: domain, client ID, region, account ID
2270
+ seclai configure list # every configured profile
1578
2271
  \`\`\`
1579
2272
 
1580
- ### Governance AI
2273
+ Profiles live in \`~/.seclai/config\` (override with \`--config-dir\` or
2274
+ \`SECLAI_CONFIG_DIR\`). Select one per invocation with \`--profile <name>\`, or set
2275
+ \`SECLAI_PROFILE\`.
2276
+
2277
+ ## API version
1581
2278
 
1582
2279
  \`\`\`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>
2280
+ seclai api-version get # what version does a request resolve to?
2281
+ seclai api-version set <date> # pin the account \u2014 affects every client
2282
+ seclai api-version clear # remove the pin
1587
2283
  \`\`\`
1588
2284
 
1589
- ### Model alerts
2285
+ \`set\` and \`clear\` change the account, not just this CLI. To affect only your own
2286
+ invocation, use the \`--api-version\` global option instead.
2287
+
2288
+ ## MCP server
1590
2289
 
1591
2290
  \`\`\`bash
1592
- seclai models alerts list [--page N] [--limit N]
1593
- seclai models alerts mark-read <alertId>
1594
- seclai models alerts mark-all-read
1595
- seclai models alerts unread-count
1596
- seclai models recommendations <modelId>
2291
+ # write Seclai MCP server config into AI coding tool config files
2292
+ seclai mcp configure --key <apiKey> [--target claude-code|cursor|claude-desktop|windsurf|all] [--dir .]
2293
+
2294
+ # print the config JSON for manual setup
2295
+ seclai mcp show [--key <apiKey>]
1597
2296
  \`\`\`
1598
2297
 
1599
- ### Search
2298
+ ## Skill files
1600
2299
 
1601
2300
  \`\`\`bash
1602
- seclai search --query "deployment guide" [--limit N] [--entity-type <type>]
2301
+ # install these skill files into AI coding tool directories
2302
+ seclai skills install [--tool copilot|claude|cursor|windsurf|codex|kiro|cline|roo|gemini|antigravity|all] [--dir .]
1603
2303
  \`\`\`
1604
2304
 
1605
- ### AI assistant (global)
2305
+ With no \`--tool\`, the target is detected from the directory structure.
2306
+
2307
+ ## Shell completion
1606
2308
 
1607
2309
  \`\`\`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}'
2310
+ seclai completion bash # eval "$(seclai completion bash)" in ~/.bashrc
2311
+ seclai completion zsh # eval "$(seclai completion zsh)" in ~/.zshrc
2312
+ seclai completion fish # seclai completion fish > ~/.config/fish/completions/seclai.fish
1617
2313
  \`\`\`
2314
+ ` },
2315
+ { name: "references/solutions.md", content: `# Solutions and governance
1618
2316
 
1619
- ### Skills
2317
+ Solutions group agents, knowledge bases and sources into one deliverable.
2318
+ Governance defines the policies applied to agent input and output.
2319
+
2320
+ ## Solutions
1620
2321
 
1621
2322
  \`\`\`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 .]
2323
+ seclai solutions list [--page N] [--limit N] [--sort field] [--order asc|desc]
2324
+ seclai solutions create --json '{"name":"Customer Support Solution"}'
2325
+ seclai solutions get <solutionId>
2326
+ seclai solutions update <solutionId> --json '{"name":"Updated"}'
2327
+ seclai solutions delete <solutionId>
1624
2328
  \`\`\`
1625
2329
 
1626
- ### MCP server
2330
+ ## Linking resources
1627
2331
 
1628
2332
  \`\`\`bash
1629
- # configure MCP server access in AI coding tool config files
1630
- seclai mcp configure --key <apiKey> [--target claude-code|cursor|claude-desktop|windsurf|all] [--dir .]
1631
-
1632
- # show the MCP config JSON snippet
1633
- seclai mcp show [--key <apiKey>]
2333
+ # each flag takes a JSON array of IDs
2334
+ seclai solutions link <solutionId> --agents '["agentId1"]' --kb '["kbId1"]' --sources '["sourceId1"]'
2335
+ seclai solutions unlink <solutionId> --agents '["agentId1"]'
1634
2336
  \`\`\`
1635
2337
 
1636
- ## Example: Create a source and upload content
2338
+ ## Conversations
1637
2339
 
1638
2340
  \`\`\`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>
2341
+ seclai solutions convos list <solutionId>
2342
+ seclai solutions convos add <solutionId> --json '{"message":"How should I structure this?"}'
2343
+ seclai solutions convos mark <solutionId> <conversationId> --json '{"accepted":true}'
1643
2344
  \`\`\`
1644
2345
 
1645
- ## Example: Set up a knowledge base with an agent
2346
+ ## Solution AI
1646
2347
 
1647
2348
  \`\`\`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
2349
+ seclai solutions ai generate <solutionId> --user-input "Add an FAQ source"
2350
+ seclai solutions ai kb <solutionId> --user-input "Create a knowledge base for docs"
2351
+ seclai solutions ai source <solutionId> --user-input "Create a file source for PDFs"
2352
+ seclai solutions ai accept <solutionId> <conversationId> --json '{"accepted":true}'
2353
+ seclai solutions ai decline <solutionId> <conversationId>
1653
2354
  \`\`\`
1654
2355
 
1655
- ## Example: Evaluate agent quality
2356
+ \`ai kb\` and \`ai source\` create the resource and link it to the solution in one
2357
+ step, which is why they live here rather than under \`kb\` or \`sources\`.
2358
+
2359
+ ## Governance AI
1656
2360
 
1657
2361
  \`\`\`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>
2362
+ seclai governance ai generate --user-input "Create a content safety policy"
2363
+ seclai governance ai list
2364
+ seclai governance ai accept <conversationId>
2365
+ seclai governance ai decline <conversationId>
1668
2366
  \`\`\`
1669
2367
 
1670
- ## Example: Solution with linked resources
2368
+ A generated policy is a proposal until accepted \u2014 \`generate\` alone changes
2369
+ nothing.
2370
+
2371
+ ## Example: solution with linked resources
1671
2372
 
1672
2373
  \`\`\`bash
1673
2374
  seclai solutions create --json '{"name":"Customer Support"}'
@@ -1675,30 +2376,15 @@ seclai solutions link <solutionId> --agents '["<agentId>"]' --kb '["<kbId>"]' --
1675
2376
  seclai solutions get <solutionId>
1676
2377
  \`\`\`
1677
2378
 
1678
- ## Example: Memory-powered agent
1679
-
1680
- \`\`\`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}'
1685
- \`\`\`
1686
-
1687
- ## Example: Governance policy setup
2379
+ ## Example: governance policy setup
1688
2380
 
1689
2381
  \`\`\`bash
1690
2382
  seclai governance ai generate --user-input "Create a content safety policy that blocks harmful outputs"
1691
2383
  seclai governance ai list
1692
2384
  seclai governance ai accept <conversationId>
1693
2385
  \`\`\`
1694
-
1695
- ## Specific topics
1696
-
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
2386
+ ` },
2387
+ { name: "references/streaming.md", content: `# Streaming Agent Runs
1702
2388
 
1703
2389
  ## Modes
1704
2390
 
@@ -1750,8 +2436,8 @@ seclai agents run <agentId> --json '{"input":"Hello"}'
1750
2436
  # check later:
1751
2437
  seclai agents runs get <runId>
1752
2438
  \`\`\`
1753
- `;
1754
- var UPLOADS_REF = `# File Uploads & Content Management
2439
+ ` },
2440
+ { name: "references/uploads.md", content: `# File Uploads & Content Management
1755
2441
 
1756
2442
  ## Upload to a source
1757
2443
  \`\`\`bash
@@ -1767,11 +2453,21 @@ seclai sources upload-text <sourceId> --json '{"text":"Article content here...",
1767
2453
 
1768
2454
  ## Upload input for agent runs
1769
2455
  \`\`\`bash
2456
+ # Check what files (if any) the agent expects before uploading. requires_uploads
2457
+ # reports whether the agent accepts files; the agent block lists the exact names /
2458
+ # indexes / patterns a run-time batch must satisfy.
2459
+ seclai agents attachment-references <agentId>
1770
2460
  seclai agents upload-input <agentId> --file ./input.pdf
1771
2461
  seclai agents upload-input <agentId> --file ./data.csv --file-name "report.csv" --mime-type "text/csv"
1772
2462
  seclai agents input-status <agentId> <uploadId>
1773
2463
  \`\`\`
1774
2464
 
2465
+ ## Download an attachment emitted by a run
2466
+ \`\`\`bash
2467
+ # attachmentId is the URL-safe-base64 storage_key from run output manifests / webhooks.
2468
+ seclai agents runs download-attachment <runId> <attachmentId> --output ./out.pdf
2469
+ \`\`\`
2470
+
1775
2471
  ## Replace content
1776
2472
  \`\`\`bash
1777
2473
  # replace with file
@@ -1792,61 +2488,10 @@ seclai contents get <contentVersionId> --start 0 --end 1000
1792
2488
  # view embeddings
1793
2489
  seclai contents embeddings <contentVersionId> [--page N] [--limit N]
1794
2490
  \`\`\`
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
- `;
2491
+ ` }
2492
+ ];
1843
2493
  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
- ];
2494
+ const skillFiles = SKILL_FILES;
1850
2495
  switch (tool) {
1851
2496
  case "copilot":
1852
2497
  return { dir: join(destDir, ".github", "copilot", "seclai-cli"), files: skillFiles };
@@ -1888,7 +2533,7 @@ function detectTools(destDir) {
1888
2533
  if (existsSync(join(destDir, ".antigravity"))) detected.push("antigravity");
1889
2534
  return detected;
1890
2535
  }
1891
- function register13(program, rt) {
2536
+ function register15(program, rt) {
1892
2537
  const skills = program.command("skills").description("Install Seclai CLI skill files for AI coding tools.");
1893
2538
  skills.command("install").description(
1894
2539
  "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 +2634,7 @@ function detectTargets(destDir) {
1989
2634
  return false;
1990
2635
  });
1991
2636
  }
1992
- function register14(program, rt) {
2637
+ function register16(program, rt) {
1993
2638
  const mcp = program.command("mcp").description("Configure the Seclai MCP server for AI coding tools.");
1994
2639
  mcp.command("configure").description(
1995
2640
  "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."
@@ -2045,237 +2690,156 @@ function register14(program, rt) {
2045
2690
  }
2046
2691
 
2047
2692
  // src/commands/completion.ts
2048
- var BASH = `#!/usr/bin/env bash
2693
+ function collectTree(program) {
2694
+ const tree = /* @__PURE__ */ new Map();
2695
+ const walk = (cmd, prefix) => {
2696
+ const children = [];
2697
+ for (const sub of cmd.commands) {
2698
+ const name = sub.name();
2699
+ if (name === "help") continue;
2700
+ children.push({ name, description: sub.description().replace(/\s+/g, " ").trim() });
2701
+ walk(sub, [...prefix, name]);
2702
+ }
2703
+ if (children.length > 0) tree.set(prefix.join(" "), children);
2704
+ };
2705
+ walk(program, []);
2706
+ return tree;
2707
+ }
2708
+ function globalOptions(program) {
2709
+ return program.options.filter((o) => o.long && o.long !== "--help").map((o) => ({
2710
+ flag: o.long,
2711
+ description: o.description.replace(/\s+/g, " ").trim()
2712
+ }));
2713
+ }
2714
+ var names = (children) => children.map((c) => c.name).join(" ");
2715
+ function renderBash(tree, options) {
2716
+ const cases = [...tree.entries()].map(([prefix, children]) => ` ${JSON.stringify(prefix)}) __seclai_reply "${names(children)}" ;;`).join("\n");
2717
+ return `#!/usr/bin/env bash
2049
2718
  # seclai bash completion \u2014 add to ~/.bashrc:
2050
2719
  # eval "$(seclai completion bash)"
2720
+ #
2721
+ # Generated from the command tree by \`seclai completion bash\`. Do not edit.
2722
+
2723
+ __seclai_reply() {
2724
+ COMPREPLY=( $(compgen -W "$1" -- "$__seclai_cur") )
2725
+ }
2051
2726
 
2052
2727
  _seclai_completions() {
2053
- local cur prev commands
2054
- cur="\${COMP_WORDS[COMP_CWORD]}"
2055
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
2056
-
2057
- # Top-level commands
2058
- commands="agents sources contents kb memory evals solutions governance alerts models search ai skills mcp completion help"
2059
-
2060
- case "\${COMP_WORDS[1]}" in
2061
- agents)
2062
- 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 ;;
2067
- esac ;;
2068
- sources|source)
2069
- case "\${COMP_WORDS[2]}" in
2070
- exports) COMPREPLY=( $(compgen -W "list create get cancel delete download estimate" -- "$cur") ); return ;;
2071
- migration) COMPREPLY=( $(compgen -W "get start cancel" -- "$cur") ); return ;;
2072
- *) COMPREPLY=( $(compgen -W "list create get update delete upload upload-text exports migration" -- "$cur") ); return ;;
2073
- esac ;;
2074
- contents) COMPREPLY=( $(compgen -W "get delete upload replace replace-text embeddings" -- "$cur") ); return ;;
2075
- kb) COMPREPLY=( $(compgen -W "list create get update delete" -- "$cur") ); return ;;
2076
- memory)
2077
- case "\${COMP_WORDS[2]}" in
2078
- ai) COMPREPLY=( $(compgen -W "generate last accept" -- "$cur") ); return ;;
2079
- *) COMPREPLY=( $(compgen -W "list create get update delete stats agents compact delete-source templates test-compaction test-compaction-standalone ai" -- "$cur") ); return ;;
2080
- esac ;;
2081
- evals)
2082
- case "\${COMP_WORDS[2]}" in
2083
- criteria) COMPREPLY=( $(compgen -W "list create get update delete summary" -- "$cur") ); return ;;
2084
- results) COMPREPLY=( $(compgen -W "list create" -- "$cur") ); return ;;
2085
- *) COMPREPLY=( $(compgen -W "criteria results compatible-runs test-draft agent-results agent-runs non-manual-summary" -- "$cur") ); return ;;
2086
- esac ;;
2087
- solutions)
2088
- case "\${COMP_WORDS[2]}" in
2089
- convos) COMPREPLY=( $(compgen -W "list add mark" -- "$cur") ); return ;;
2090
- ai) COMPREPLY=( $(compgen -W "generate kb source accept decline" -- "$cur") ); return ;;
2091
- *) COMPREPLY=( $(compgen -W "list create get update delete link unlink convos ai" -- "$cur") ); return ;;
2092
- esac ;;
2093
- governance)
2094
- case "\${COMP_WORDS[2]}" in
2095
- ai) COMPREPLY=( $(compgen -W "generate list accept decline" -- "$cur") ); return ;;
2096
- *) COMPREPLY=( $(compgen -W "ai" -- "$cur") ); return ;;
2097
- esac ;;
2098
- alerts)
2099
- case "\${COMP_WORDS[2]}" in
2100
- configs) COMPREPLY=( $(compgen -W "list create get update delete" -- "$cur") ); return ;;
2101
- prefs) COMPREPLY=( $(compgen -W "list update" -- "$cur") ); return ;;
2102
- *) COMPREPLY=( $(compgen -W "list get status comment subscribe unsubscribe configs prefs" -- "$cur") ); return ;;
2103
- esac ;;
2104
- models)
2105
- case "\${COMP_WORDS[2]}" in
2106
- alerts) COMPREPLY=( $(compgen -W "list mark-read mark-all-read unread-count" -- "$cur") ); return ;;
2107
- experiments) COMPREPLY=( $(compgen -W "list create get cancel delete" -- "$cur") ); return ;;
2108
- *) COMPREPLY=( $(compgen -W "alerts recommendations experiments" -- "$cur") ); return ;;
2109
- esac ;;
2110
- ai) COMPREPLY=( $(compgen -W "feedback kb source solution memory memory-history accept decline memory-accept" -- "$cur") ); return ;;
2111
- skills) COMPREPLY=( $(compgen -W "install" -- "$cur") ); return ;;
2112
- mcp) COMPREPLY=( $(compgen -W "configure show" -- "$cur") ); return ;;
2113
- completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ); return ;;
2728
+ local __seclai_cur prefix i
2729
+ __seclai_cur="\${COMP_WORDS[COMP_CWORD]}"
2730
+
2731
+ # Everything typed so far except the program name and the word being
2732
+ # completed, with flags dropped, joined by spaces.
2733
+ prefix=""
2734
+ for ((i = 1; i < COMP_CWORD; i++)); do
2735
+ case "\${COMP_WORDS[i]}" in
2736
+ -*) continue ;;
2737
+ esac
2738
+ if [ -z "$prefix" ]; then
2739
+ prefix="\${COMP_WORDS[i]}"
2740
+ else
2741
+ prefix="$prefix \${COMP_WORDS[i]}"
2742
+ fi
2743
+ done
2744
+
2745
+ case "$__seclai_cur" in
2746
+ -*) __seclai_reply "${options.map((o) => o.flag).join(" ")}"; return ;;
2114
2747
  esac
2115
2748
 
2116
- COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
2749
+ case "$prefix" in
2750
+ ${cases}
2751
+ *) COMPREPLY=() ;;
2752
+ esac
2117
2753
  }
2118
2754
 
2119
2755
  complete -F _seclai_completions seclai
2120
2756
  `;
2121
- var ZSH = `#compdef seclai
2757
+ }
2758
+ function renderZsh(tree, options) {
2759
+ const cases = [...tree.entries()].map(([prefix, children]) => {
2760
+ const described = children.map((c) => `'${c.name}:${c.description.replace(/['`$]/g, "")}'`).join(" ");
2761
+ return ` ${JSON.stringify(prefix)}) sub=(${described}) ;;`;
2762
+ }).join("\n");
2763
+ const opts = options.map((o) => `'${o.flag}[${o.description.replace(/['\]`$]/g, "")}]'`).join(" ");
2764
+ return `#compdef seclai
2122
2765
  # seclai zsh completion \u2014 add to ~/.zshrc:
2123
2766
  # eval "$(seclai completion zsh)"
2767
+ #
2768
+ # Generated from the command tree by \`seclai completion zsh\`. Do not edit.
2124
2769
 
2125
2770
  _seclai() {
2126
- local -a commands
2127
- commands=(
2128
- 'agents:Manage agents, runs, definitions, and AI assistance'
2129
- 'sources:Manage content sources'
2130
- 'contents:Manage indexed content and embeddings'
2131
- 'kb:Manage knowledge bases'
2132
- 'memory:Manage memory banks'
2133
- 'evals:Manage evaluations'
2134
- 'solutions:Manage solutions'
2135
- 'governance:Governance AI assistant'
2136
- 'alerts:Manage alerts and alert configurations'
2137
- 'models:Model alerts and recommendations'
2138
- 'search:Search across Seclai resources'
2139
- 'ai:Top-level AI assistant'
2140
- 'skills:Install skill files for AI coding tools'
2141
- 'mcp:Configure the Seclai MCP server'
2142
- 'completion:Generate shell completion scripts'
2143
- 'help:Display help for command'
2144
- )
2145
-
2146
- _arguments -C \\
2147
- '--api-key[Seclai API key]:key' \\
2148
- '--compact[Output compact JSON]' \\
2149
- '-V[Output version]' \\
2150
- '-h[Display help]' \\
2151
- '1:command:->cmd' \\
2152
- '*::arg:->args'
2153
-
2154
- case $state in
2155
- cmd) _describe 'command' commands ;;
2156
- args)
2157
- case \${words[1]} in
2158
- agents)
2159
- local -a sub=(list create get update delete run runs def export preview-import upload-input input-status attachment-references ai)
2160
- _describe 'subcommand' sub ;;
2161
- sources|source)
2162
- local -a sub=(list create get update delete upload upload-text exports migration)
2163
- _describe 'subcommand' sub ;;
2164
- contents)
2165
- local -a sub=(get delete upload replace replace-text embeddings)
2166
- _describe 'subcommand' sub ;;
2167
- kb)
2168
- local -a sub=(list create get update delete)
2169
- _describe 'subcommand' sub ;;
2170
- memory)
2171
- local -a sub=(list create get update delete stats agents compact delete-source templates test-compaction test-compaction-standalone ai)
2172
- _describe 'subcommand' sub ;;
2173
- evals)
2174
- local -a sub=(criteria results compatible-runs test-draft agent-results agent-runs non-manual-summary)
2175
- _describe 'subcommand' sub ;;
2176
- solutions)
2177
- local -a sub=(list create get update delete link unlink convos ai)
2178
- _describe 'subcommand' sub ;;
2179
- governance)
2180
- local -a sub=(ai)
2181
- _describe 'subcommand' sub ;;
2182
- alerts)
2183
- local -a sub=(list get status comment subscribe unsubscribe configs prefs)
2184
- _describe 'subcommand' sub ;;
2185
- models)
2186
- local -a sub=(alerts recommendations experiments)
2187
- _describe 'subcommand' sub ;;
2188
- ai)
2189
- local -a sub=(feedback kb source solution memory memory-history accept decline memory-accept)
2190
- _describe 'subcommand' sub ;;
2191
- skills)
2192
- local -a sub=(install)
2193
- _describe 'subcommand' sub ;;
2194
- mcp)
2195
- local -a sub=(configure show)
2196
- _describe 'subcommand' sub ;;
2197
- completion)
2198
- local -a sub=(bash zsh fish)
2199
- _describe 'shell' sub ;;
2200
- esac ;;
2771
+ local prefix cur
2772
+ local -a sub opts
2773
+ cur="\${words[CURRENT]}"
2774
+
2775
+ opts=(${opts})
2776
+ if [[ "$cur" == -* ]]; then
2777
+ _describe 'option' opts
2778
+ return
2779
+ fi
2780
+
2781
+ # Words typed so far, minus the program name, the word being completed, and
2782
+ # any flags.
2783
+ prefix="\${(j: :)\${(M)words[2,CURRENT-1]:#[^-]*}}"
2784
+
2785
+ case "$prefix" in
2786
+ ${cases}
2787
+ *) return ;;
2201
2788
  esac
2789
+
2790
+ _describe 'command' sub
2202
2791
  }
2203
2792
 
2204
- _seclai "$@"
2793
+ compdef _seclai seclai
2205
2794
  `;
2206
- var FISH = `# seclai fish completion \u2014 save to ~/.config/fish/completions/seclai.fish
2795
+ }
2796
+ function renderFish(tree, options) {
2797
+ const lines = [...tree.entries()].map(
2798
+ ([prefix, children]) => children.map(
2799
+ (c) => `complete -c seclai -f -n "__seclai_at '${prefix}'" -a "${c.name}" -d "${c.description.replace(/["$`]/g, "")}"`
2800
+ ).join("\n")
2801
+ ).join("\n");
2802
+ const opts = options.map(
2803
+ (o) => `complete -c seclai -l ${o.flag.replace(/^--/, "")} -d "${o.description.replace(/["$`]/g, "")}"`
2804
+ ).join("\n");
2805
+ return `# seclai fish completion \u2014 save to your completions directory:
2207
2806
  # seclai completion fish > ~/.config/fish/completions/seclai.fish
2208
-
2209
- set -l top agents sources contents kb memory evals solutions governance alerts models search ai skills mcp completion help
2210
-
2211
- # Top-level
2212
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "agents" -d "Manage agents"
2213
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "sources" -d "Manage sources"
2214
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "contents" -d "Manage content"
2215
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "kb" -d "Knowledge bases"
2216
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "memory" -d "Memory banks"
2217
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "evals" -d "Evaluations"
2218
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "solutions" -d "Solutions"
2219
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "governance" -d "Governance AI"
2220
- 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"
2222
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "search" -d "Search resources"
2223
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "ai" -d "AI assistant"
2224
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "skills" -d "Skill files"
2225
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "mcp" -d "MCP server config"
2226
- complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "completion" -d "Shell completions"
2227
-
2228
- # 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"
2230
-
2231
- # sources
2232
- 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"
2233
-
2234
- # contents
2235
- complete -c seclai -n "__fish_seen_subcommand_from contents; and not __fish_seen_subcommand_from get delete upload replace replace-text embeddings" -f -a "get delete upload replace replace-text embeddings"
2236
-
2237
- # kb
2238
- complete -c seclai -n "__fish_seen_subcommand_from kb; and not __fish_seen_subcommand_from list create get update delete" -f -a "list create get update delete"
2239
-
2240
- # memory
2241
- complete -c seclai -n "__fish_seen_subcommand_from memory; and not __fish_seen_subcommand_from list create get update delete stats agents compact delete-source templates test-compaction test-compaction-standalone ai" -f -a "list create get update delete stats agents compact delete-source templates test-compaction test-compaction-standalone ai"
2242
-
2243
- # evals
2244
- complete -c seclai -n "__fish_seen_subcommand_from evals; and not __fish_seen_subcommand_from criteria results compatible-runs test-draft agent-results agent-runs non-manual-summary" -f -a "criteria results compatible-runs test-draft agent-results agent-runs non-manual-summary"
2245
-
2246
- # solutions
2247
- complete -c seclai -n "__fish_seen_subcommand_from solutions; and not __fish_seen_subcommand_from list create get update delete link unlink convos ai" -f -a "list create get update delete link unlink convos ai"
2248
-
2249
- # governance
2250
- complete -c seclai -n "__fish_seen_subcommand_from governance; and not __fish_seen_subcommand_from ai" -f -a "ai"
2251
-
2252
- # alerts
2253
- 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
-
2255
- # 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"
2257
-
2258
- # ai
2259
- 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"
2260
-
2261
- # skills
2262
- complete -c seclai -n "__fish_seen_subcommand_from skills; and not __fish_seen_subcommand_from install" -f -a "install"
2263
-
2264
- # mcp
2265
- complete -c seclai -n "__fish_seen_subcommand_from mcp; and not __fish_seen_subcommand_from configure show" -f -a "configure show"
2266
-
2267
- # completion
2268
- complete -c seclai -n "__fish_seen_subcommand_from completion; and not __fish_seen_subcommand_from bash zsh fish" -f -a "bash zsh fish"
2807
+ #
2808
+ # Generated from the command tree by \`seclai completion fish\`. Do not edit.
2809
+
2810
+ function __seclai_prefix
2811
+ set -l toks (commandline -opc)
2812
+ set -e toks[1]
2813
+ set -l out
2814
+ for t in $toks
2815
+ if not string match -q -- '-*' $t
2816
+ set -a out $t
2817
+ end
2818
+ end
2819
+ string join ' ' $out
2820
+ end
2821
+
2822
+ function __seclai_at
2823
+ test (__seclai_prefix) = "$argv[1]"
2824
+ end
2825
+
2826
+ ${lines}
2269
2827
 
2270
2828
  # Global options
2271
- complete -c seclai -l api-key -d "Seclai API key"
2272
- complete -c seclai -l compact -d "Output compact JSON"
2273
- complete -c seclai -s V -l version -d "Output version"
2829
+ ${opts}
2274
2830
  `;
2275
- var SCRIPTS = { bash: BASH, zsh: ZSH, fish: FISH };
2276
- function register15(program, rt) {
2277
- const completion = program.command("completion").description("Generate shell completion scripts.").argument("<shell>", "Shell type: bash, zsh, or fish.").action(async (shell) => {
2278
- const script = SCRIPTS[shell];
2831
+ }
2832
+ function renderCompletion(shell, program) {
2833
+ const tree = collectTree(program);
2834
+ const options = globalOptions(program);
2835
+ if (shell === "bash") return renderBash(tree, options);
2836
+ if (shell === "zsh") return renderZsh(tree, options);
2837
+ if (shell === "fish") return renderFish(tree, options);
2838
+ return void 0;
2839
+ }
2840
+ function register17(program, rt) {
2841
+ program.command("completion").description("Generate shell completion scripts.").argument("<shell>", "Shell type: bash, zsh, or fish.").action((shell) => {
2842
+ const script = renderCompletion(shell, program);
2279
2843
  if (!script) {
2280
2844
  rt.writeErr(`Unknown shell "${shell}". Use: bash, zsh, or fish.
2281
2845
  `);
@@ -2447,7 +3011,7 @@ async function loadProfile(rt, opts) {
2447
3011
  const profile = await loadSsoProfile(configDir, profileName);
2448
3012
  return { profile, profileName, configDir };
2449
3013
  }
2450
- function register16(program, rt) {
3014
+ function register18(program, rt) {
2451
3015
  const group = program.command("auth").description("SSO authentication (login/logout/status/refresh).");
2452
3016
  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
3017
  await run(rt, async () => {
@@ -2621,7 +3185,7 @@ function resolveConfigDir(opts) {
2621
3185
  const home = process4.env.HOME ?? process4.env.USERPROFILE ?? "";
2622
3186
  return join4(home, ".seclai");
2623
3187
  }
2624
- function register17(program, rt) {
3188
+ function register19(program, rt) {
2625
3189
  const group = program.command("configure").description("Configure CLI profiles and settings.");
2626
3190
  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
3191
  await run(rt, async () => {
@@ -2735,7 +3299,7 @@ function escapeRegExp(s) {
2735
3299
 
2736
3300
  // src/cli.ts
2737
3301
  function createProgram(rt = defaultRuntime()) {
2738
- const program = new Command();
3302
+ const program = new Command4();
2739
3303
  const cliVersion = getCliVersion();
2740
3304
  program.name("seclai").description(
2741
3305
  `Seclai Command Line Interface (v${cliVersion})
@@ -2755,6 +3319,12 @@ All commands return JSON to stdout, making it easy to pipe into jq or other tool
2755
3319
  ).option(
2756
3320
  "--config-dir <path>",
2757
3321
  "Config directory (defaults to SECLAI_CONFIG_DIR, then ~/.seclai)."
3322
+ ).option(
3323
+ "--api-version <date>",
3324
+ "Opt into dated API changes released on or before this YYYY-MM-DD (defaults to SECLAI_API_VERSION; omitted means the account default)."
3325
+ ).option(
3326
+ "--allow-unknown-api-version",
3327
+ "Send an --api-version this CLI was not built against instead of rejecting it."
2758
3328
  ).option(
2759
3329
  "--compact",
2760
3330
  "Output compact JSON (no indentation)."
@@ -2767,6 +3337,7 @@ Environment:
2767
3337
  SECLAI_API_URL Override API base URL (default: https://api.seclai.com)
2768
3338
  SECLAI_PROFILE Default SSO profile (alternative to --profile)
2769
3339
  SECLAI_CONFIG_DIR Config directory (alternative to --config-dir)
3340
+ SECLAI_API_VERSION Dated API version (alternative to --api-version)
2770
3341
 
2771
3342
  Examples:
2772
3343
  seclai agents list
@@ -2786,6 +3357,13 @@ Examples:
2786
3357
  program.exitOverride();
2787
3358
  program.hook("preAction", (thisCommand) => {
2788
3359
  const globalOpts = thisCommand.opts();
3360
+ if (typeof globalOpts.apiVersion === "string" && globalOpts.apiVersion.trim().length === 0) {
3361
+ warnDeprecated(
3362
+ rt,
3363
+ "--api-version was given an empty value and is being ignored. Pass a YYYY-MM-DD date, or omit the flag to use the account default.",
3364
+ "rejected"
3365
+ );
3366
+ }
2789
3367
  rt.compact = Boolean(globalOpts.compact);
2790
3368
  });
2791
3369
  register(program, rt);
@@ -2805,6 +3383,8 @@ Examples:
2805
3383
  register15(program, rt);
2806
3384
  register16(program, rt);
2807
3385
  register17(program, rt);
3386
+ register18(program, rt);
3387
+ register19(program, rt);
2808
3388
  return program;
2809
3389
  }
2810
3390
  async function runCli(argv, rt = defaultRuntime()) {
@@ -2851,4 +3431,3 @@ export {
2851
3431
  createProgram,
2852
3432
  runCli
2853
3433
  };
2854
- //# sourceMappingURL=cli.js.map