@seclai/cli 1.1.0 → 1.2.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
@@ -72,6 +72,9 @@ function getCliVersion() {
72
72
  function createClient(opts) {
73
73
  const seclaiOpts = {};
74
74
  if (opts.apiKey !== void 0) seclaiOpts.apiKey = opts.apiKey;
75
+ if (opts.profile !== void 0) seclaiOpts.profile = opts.profile;
76
+ if (opts.configDir !== void 0) seclaiOpts.configDir = opts.configDir;
77
+ if (opts.accountId !== void 0) seclaiOpts.accountId = opts.accountId;
75
78
  const envUrl = process2.env.SECLAI_API_URL;
76
79
  seclaiOpts.baseUrl = envUrl && envUrl.length > 0 ? envUrl : "https://api.seclai.com";
77
80
  return new Seclai(seclaiOpts);
@@ -302,8 +305,8 @@ function register(program, rt) {
302
305
  agents.command("upload-input").description("Upload a file as agent input.").argument("<agentId>", "Agent ID.").requiredOption("--file <path>", "File to upload.").option("--file-name <name>", "Override filename.").option("--mime-type <type>", "MIME type.").action(async (agentId, opts) => {
303
306
  await run(rt, async () => {
304
307
  const client = createClient(program.opts());
305
- const { readFile: readFile2 } = await import("fs/promises");
306
- const bytes = new Uint8Array(await readFile2(opts.file));
308
+ const { readFile: readFile5 } = await import("fs/promises");
309
+ const bytes = new Uint8Array(await readFile5(opts.file));
307
310
  const o = { file: bytes };
308
311
  if (opts.fileName) o.fileName = opts.fileName;
309
312
  if (opts.mimeType) o.mimeType = opts.mimeType;
@@ -362,9 +365,11 @@ function register2(program, rt) {
362
365
  const sources = program.command("sources").alias("source").description("Manage content sources.");
363
366
  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) => {
364
367
  await run(rt, async () => {
365
- const client = createClient(program.opts());
368
+ const globalOpts = program.opts();
369
+ const client = createClient(globalOpts);
366
370
  const o = listOpts(opts);
367
- if (opts.accountId) o.accountId = opts.accountId;
371
+ const acctId = opts.accountId || globalOpts.accountId;
372
+ if (acctId) o.accountId = acctId;
368
373
  printJson(rt, await client.listSources(o));
369
374
  });
370
375
  });
@@ -1172,35 +1177,42 @@ The Seclai CLI (\`seclai\` / \`npx @seclai/cli\`) manages agents, knowledge base
1172
1177
 
1173
1178
  All commands output JSON to stdout. Pipe into \`jq\` for filtering.
1174
1179
 
1180
+ ## Quick start
1181
+
1182
+ \`\`\`bash
1183
+ # authenticate
1184
+ export SECLAI_API_KEY="sk-..."
1185
+
1186
+ # create an agent
1187
+ seclai agents create --json '{"name":"My Agent","description":"QA chatbot"}'
1188
+
1189
+ # configure steps via AI assistant
1190
+ seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot that uses a knowledge base"
1191
+
1192
+ # accept the generated plan
1193
+ seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1194
+
1195
+ # run the agent
1196
+ seclai agents run <agentId> --json '{"input":"How do I reset my password?"}' --stream
1197
+
1198
+ # list runs
1199
+ seclai agents runs list <agentId>
1200
+ \`\`\`
1201
+
1175
1202
  ## Authentication
1176
1203
 
1177
1204
  Set \`SECLAI_API_KEY\` env var or pass \`--api-key <key>\`.
1178
1205
  Override the API URL with \`SECLAI_API_URL\` (default: https://api.seclai.com).
1179
1206
 
1180
- ## Quick Reference
1181
-
1182
- | Domain | Command | Description |
1183
- |--------|---------|-------------|
1184
- | Agents | \`seclai agents list/create/get/update/delete\` | Manage agents |
1185
- | Agent Runs | \`seclai agents run <id> --json '...'\` | Run an agent |
1186
- | Agent Runs | \`seclai agents runs list/get/delete/cancel/search\` | Manage runs |
1187
- | Agent Def | \`seclai agents def get/update\` | Agent step definitions |
1188
- | Sources | \`seclai sources list/create/get/update/delete/upload/upload-text\` | Content sources |
1189
- | Source Exports | \`seclai sources exports list/create/get/cancel/delete/download/estimate\` | Export management |
1190
- | Contents | \`seclai contents get/delete/upload/replace-text/embeddings\` | Indexed content |
1191
- | Knowledge Bases | \`seclai kb list/create/get/update/delete\` | Knowledge bases |
1192
- | Memory Banks | \`seclai memory list/create/get/update/delete/stats/agents/compact/templates\` | Memory banks |
1193
- | Evaluations | \`seclai evals criteria list/create/get/update/delete/summary\` | Eval criteria |
1194
- | Evaluations | \`seclai evals results list/create\` | Eval results |
1195
- | Solutions | \`seclai solutions list/create/get/update/delete/link/unlink\` | Solutions |
1196
- | Governance | \`seclai governance ai generate/list/accept/decline\` | Governance AI |
1197
- | Alerts | \`seclai alerts list/get/status/comment/subscribe/unsubscribe\` | Alerts |
1198
- | Alert Config | \`seclai alerts configs list/create/get/update/delete\` | Alert configs |
1199
- | Models | \`seclai models alerts list/mark-read/mark-all-read/unread-count\` | Model alerts |
1200
- | Search | \`seclai search --query "text"\` | Global search |
1201
- | AI Assistant | \`seclai ai feedback/kb/source/solution/memory/accept/decline/memory-accept\` | AI assistant |
1202
-
1203
- ## Common Patterns
1207
+ ## Global options
1208
+
1209
+ \`\`\`bash
1210
+ --api-key <key> # Seclai API key (or set SECLAI_API_KEY)
1211
+ --compact # Output compact single-line JSON
1212
+ -V, --version # Print version
1213
+ \`\`\`
1214
+
1215
+ ## Common patterns
1204
1216
 
1205
1217
  ### JSON input
1206
1218
  Most create/update commands accept \`--json '{"key":"value"}'\` or \`--json-file path.json\`.
@@ -1208,76 +1220,45 @@ Use \`--json -\` or \`--json-file -\` to read from stdin.
1208
1220
 
1209
1221
  ### AI assistant shorthand
1210
1222
  AI generation commands accept \`--user-input <text>\` as shorthand for \`--json '{"user_input":"<text>"}'\`.
1211
- \`\`\`bash
1212
- seclai agents ai gen-steps <id> --user-input "Build a QA chatbot"
1213
- seclai ai kb --user-input "Create a support knowledge base"
1214
- \`\`\`
1215
-
1216
- ### Compact output
1217
- Use \`--compact\` for single-line JSON output (useful for scripting):
1218
- \`\`\`bash
1219
- seclai agents list --compact | jq -c '.[]'
1220
- \`\`\`
1221
1223
 
1222
1224
  ### Pagination
1223
1225
  List commands support \`--page <n>\` and \`--limit <n>\`. Some also support \`--sort <field>\` and \`--order asc|desc\`.
1224
1226
 
1225
- ### Streaming agent runs
1226
- \`\`\`bash
1227
- # Wait for completion via SSE, print final result
1228
- seclai agents run <id> --json '{"input":"Hello"}' --stream
1229
-
1230
- # Stream individual SSE events as NDJSON
1231
- seclai agents run <id> --json '{"input":"Hello"}' --events
1232
-
1233
- # Filter event types
1234
- seclai agents run <id> --json '{"input":"Hello"}' --events --event-filter "status,data"
1235
-
1236
- # Poll-based waiting
1237
- seclai agents run <id> --json '{"input":"Hello"}' --poll --poll-interval-ms 2000
1238
- \`\`\`
1239
-
1240
1227
  ### File uploads
1241
- \`\`\`bash
1242
- seclai sources upload <sourceId> --file ./doc.pdf --title "My Doc" --metadata '{"category":"docs"}'
1243
- seclai contents upload <contentVersionId> --file ./updated.pdf
1244
- \`\`\`
1228
+ Upload commands accept \`--file <path>\` (required), plus optional \`--title\`, \`--metadata '{"k":"v"}'\`, \`--metadata-file path.json\`, \`--file-name\`, \`--mime-type\`.
1245
1229
 
1246
- ## Detailed References
1230
+ ## Commands
1247
1231
 
1248
- - [Agents](references/agents.md) \u2014 CRUD, runs, definitions, AI assistant
1249
- - [Sources](references/sources.md) \u2014 content sources, uploads, exports, migrations
1250
- - [Knowledge Bases & Memory Banks](references/kb-memory.md) \u2014 KB CRUD, memory banks, compaction
1251
- - [Evaluations & Solutions](references/evals-solutions.md) \u2014 criteria, results, solutions, links
1252
- - [Alerts, Governance & More](references/alerts-governance.md) \u2014 alerts, governance, models, search, AI
1253
- `;
1254
- var AGENTS_SKILL = `# Seclai CLI \u2014 Agents
1232
+ ### Agents
1255
1233
 
1256
- ## CRUD
1257
1234
  \`\`\`bash
1258
1235
  seclai agents list [--page N] [--limit N]
1259
- seclai agents create --json '{"name":"My Agent",...}'
1236
+ seclai agents create --json '{"name":"My Agent","description":"..."}'
1260
1237
  seclai agents get <agentId>
1261
- seclai agents update <agentId> --json '{"name":"Updated"}'
1238
+ seclai agents update <agentId> --json '{"name":"Renamed"}'
1262
1239
  seclai agents delete <agentId>
1263
1240
  \`\`\`
1264
1241
 
1265
- ## Running Agents
1242
+ ### Running agents
1243
+
1266
1244
  \`\`\`bash
1267
- # Simple run
1245
+ # simple run \u2014 returns the final result
1268
1246
  seclai agents run <agentId> --json '{"input":"Hello"}'
1269
1247
 
1270
- # Stream (wait for final result via SSE)
1248
+ # stream \u2014 wait for completion via SSE, print final result
1271
1249
  seclai agents run <agentId> --json '{"input":"Hello"}' --stream [--timeout-ms 60000]
1272
1250
 
1273
- # Stream individual events as NDJSON
1274
- seclai agents run <agentId> --json '{"input":"Hello"}' --events [--event-filter "status,data"] [--output full|data|status]
1251
+ # events \u2014 stream individual SSE events as NDJSON lines
1252
+ # --output: full (entire event), data (event data only), status (status events only)
1253
+ # --event-filter: comma-separated event types to include, e.g. "status,data"
1254
+ seclai agents run <agentId> --json '{"input":"Hello"}' --events [--output full|data|status] [--event-filter "status,data"]
1275
1255
 
1276
- # Poll-based
1256
+ # poll \u2014 poll for completion
1277
1257
  seclai agents run <agentId> --json '{"input":"Hello"}' --poll [--poll-interval-ms 2000] [--include-step-outputs]
1278
1258
  \`\`\`
1279
1259
 
1280
- ## Runs Management
1260
+ ### Agent runs
1261
+
1281
1262
  \`\`\`bash
1282
1263
  seclai agents runs list <agentId> [--page N] [--limit N] [--status <status>]
1283
1264
  seclai agents runs get <runId> [--include-step-outputs]
@@ -1287,185 +1268,202 @@ seclai agents runs search --json '{"query":"..."}'
1287
1268
  seclai agents runs eval-results <agentId> <runId> [--page N] [--limit N]
1288
1269
  \`\`\`
1289
1270
 
1290
- ## Agent Definition
1271
+ ### Agent definitions
1272
+
1291
1273
  \`\`\`bash
1292
1274
  seclai agents def get <agentId>
1293
- seclai agents def update <agentId> --json '{"steps":[...]}'
1275
+ seclai agents def update <agentId> --json '{"steps":[{"step_type":"llm","config":{...}}]}'
1294
1276
  \`\`\`
1295
1277
 
1296
- ## Input Uploads
1278
+ ### Agent input uploads
1279
+
1297
1280
  \`\`\`bash
1298
1281
  seclai agents upload-input <agentId> --file ./input.pdf [--file-name name] [--mime-type type]
1299
1282
  seclai agents input-status <agentId> <uploadId>
1300
1283
  \`\`\`
1301
1284
 
1302
- ## AI Assistant
1285
+ ### Agent AI assistant
1286
+
1303
1287
  \`\`\`bash
1304
- seclai agents ai gen-steps <agentId> --user-input "Build a chat agent"
1305
- seclai agents ai step-config <agentId> --json '{"step_type":"...",}'
1288
+ seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot"
1289
+ seclai agents ai step-config <agentId> --json '{"step_type":"llm","user_input":"Configure the LLM step"}'
1306
1290
  seclai agents ai history <agentId>
1307
1291
  seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1308
1292
  \`\`\`
1309
- `;
1310
- var SOURCES_SKILL = `# Seclai CLI \u2014 Sources
1311
1293
 
1312
- ## CRUD
1294
+ ### Sources
1295
+
1313
1296
  \`\`\`bash
1314
1297
  seclai sources list [--page N] [--limit N] [--sort field] [--order asc|desc] [--account-id id]
1315
- seclai sources create --json '{"name":"My Source",...}'
1298
+ seclai sources create --json '{"name":"Docs","description":"Product documentation"}'
1316
1299
  seclai sources get <sourceId>
1317
- seclai sources update <sourceId> --json '{"name":"Updated"}'
1300
+ seclai sources update <sourceId> --json '{"name":"Updated Docs"}'
1318
1301
  seclai sources delete <sourceId>
1319
1302
  \`\`\`
1320
1303
 
1321
- ## File Upload
1304
+ ### Source uploads
1305
+
1322
1306
  \`\`\`bash
1323
- seclai sources upload <sourceId> --file ./doc.pdf [--title "Title"] [--metadata '{"k":"v"}'] [--file-name name] [--mime-type type]
1324
- seclai sources upload-text <sourceId> --json '{"text":"...","title":"..."}'
1307
+ seclai sources upload <sourceId> --file ./doc.pdf [--title "My Doc"] [--metadata '{"category":"docs"}'] [--file-name name] [--mime-type type]
1308
+ seclai sources upload-text <sourceId> --json '{"text":"Article content here...","title":"My Article"}'
1325
1309
  \`\`\`
1326
1310
 
1327
- ## Exports
1311
+ ### Source exports
1312
+
1328
1313
  \`\`\`bash
1329
1314
  seclai sources exports list <sourceId> [--page N] [--limit N]
1330
- seclai sources exports create <sourceId> --json '{"format":"..."}'
1315
+ seclai sources exports create <sourceId> --json '{"format":"jsonl"}'
1331
1316
  seclai sources exports get <sourceId> <exportId>
1332
1317
  seclai sources exports cancel <sourceId> <exportId>
1333
1318
  seclai sources exports delete <sourceId> <exportId>
1334
1319
  seclai sources exports download <sourceId> <exportId>
1335
- seclai sources exports estimate <sourceId> --json '{"format":"..."}'
1320
+ seclai sources exports estimate <sourceId> --json '{"format":"jsonl"}'
1336
1321
  \`\`\`
1337
1322
 
1338
- ## Embedding Migration
1323
+ ### Embedding migration
1324
+
1339
1325
  \`\`\`bash
1340
1326
  seclai sources migration get <sourceId>
1341
- seclai sources migration start <sourceId> --json '{"target_model":"..."}'
1327
+ seclai sources migration start <sourceId> --json '{"target_model":"text-embedding-3-large"}'
1342
1328
  seclai sources migration cancel <sourceId>
1343
1329
  \`\`\`
1344
- `;
1345
- var KB_MEMORY_SKILL = `# Seclai CLI \u2014 Knowledge Bases & Memory Banks
1346
1330
 
1347
- ## Knowledge Bases
1331
+ ### Contents (indexed content)
1332
+
1333
+ \`\`\`bash
1334
+ seclai contents get <contentVersionId> [--start N] [--end N]
1335
+ seclai contents delete <contentVersionId>
1336
+ seclai contents upload <contentVersionId> --file ./updated.pdf [--title "Title"] [--file-name name] [--mime-type type]
1337
+ seclai contents replace-text <contentVersionId> --json '{"text":"Replacement text","title":"Updated"}'
1338
+ seclai contents embeddings <contentVersionId> [--page N] [--limit N]
1339
+ \`\`\`
1340
+
1341
+ ### Knowledge bases
1342
+
1348
1343
  \`\`\`bash
1349
1344
  seclai kb list [--page N] [--limit N] [--sort field] [--order asc|desc]
1350
- seclai kb create --json '{"name":"My KB",...}'
1345
+ seclai kb create --json '{"name":"Support KB","description":"Customer support articles"}'
1351
1346
  seclai kb get <kbId>
1352
- seclai kb update <kbId> --json '{"name":"Updated"}'
1347
+ seclai kb update <kbId> --json '{"name":"Updated KB"}'
1353
1348
  seclai kb delete <kbId>
1354
1349
  \`\`\`
1355
1350
 
1356
- ## Memory Banks
1351
+ ### Memory banks
1352
+
1357
1353
  \`\`\`bash
1358
1354
  seclai memory list [--page N] [--limit N] [--sort field] [--order asc|desc]
1359
- seclai memory create --json '{"name":"My Bank","type":"conversation"}'
1355
+ # type: "conversation" (chat history) or "general" (structured facts)
1356
+ seclai memory create --json '{"name":"Chat Memory","type":"conversation"}'
1360
1357
  seclai memory get <memoryBankId>
1361
- seclai memory update <memoryBankId> --json '{"name":"Updated"}'
1358
+ seclai memory update <memoryBankId> --json '{"name":"Renamed"}'
1362
1359
  seclai memory delete <memoryBankId>
1363
1360
  \`\`\`
1364
1361
 
1365
- ## Memory Bank Utilities
1362
+ ### Memory bank utilities
1363
+
1366
1364
  \`\`\`bash
1367
1365
  seclai memory stats <memoryBankId>
1368
1366
  seclai memory agents <memoryBankId>
1369
1367
  seclai memory compact <memoryBankId>
1370
1368
  seclai memory delete-source <memoryBankId>
1371
1369
  seclai memory templates
1372
- seclai memory test-compaction <memoryBankId> --json '{"prompt":"..."}'
1373
- seclai memory test-compaction-standalone --json '{"prompt":"..."}'
1370
+ seclai memory test-compaction <memoryBankId> --json '{"prompt":"Summarize the conversation"}'
1371
+ seclai memory test-compaction-standalone --json '{"prompt":"Summarize the conversation"}'
1374
1372
  \`\`\`
1375
1373
 
1376
- ## Memory Bank AI
1374
+ ### Memory bank AI
1375
+
1377
1376
  \`\`\`bash
1378
1377
  seclai memory ai generate --user-input "Configure compaction for chat memory"
1379
1378
  seclai memory ai last
1380
1379
  seclai memory ai accept <conversationId> --json '{"accepted":true}'
1381
1380
  \`\`\`
1382
- `;
1383
- var EVALS_SOLUTIONS_SKILL = `# Seclai CLI \u2014 Evaluations & Solutions
1384
1381
 
1385
- ## Evaluation Criteria
1382
+ ### Evaluations \u2014 criteria
1383
+
1386
1384
  \`\`\`bash
1387
1385
  seclai evals criteria list <agentId> [--page N] [--limit N]
1388
- seclai evals criteria create <agentId> --json '{"name":"Quality",...}'
1386
+ seclai evals criteria create <agentId> --json '{"name":"Response Quality","description":"...","eval_type":"llm_judge"}'
1389
1387
  seclai evals criteria get <criteriaId>
1390
- seclai evals criteria update <criteriaId> --json '{"name":"Updated"}'
1388
+ seclai evals criteria update <criteriaId> --json '{"name":"Updated Criteria"}'
1391
1389
  seclai evals criteria delete <criteriaId>
1392
1390
  seclai evals criteria summary <criteriaId>
1393
1391
  \`\`\`
1394
1392
 
1395
- ## Evaluation Results
1393
+ ### Evaluations \u2014 results & runs
1394
+
1396
1395
  \`\`\`bash
1397
1396
  seclai evals results list <criteriaId> [--page N] [--limit N]
1398
1397
  seclai evals results create <criteriaId> --json '{"run_id":"...","score":0.9}'
1399
- \`\`\`
1400
-
1401
- ## Other Evaluation Commands
1402
- \`\`\`bash
1403
1398
  seclai evals compatible-runs <criteriaId> [--page N] [--limit N]
1404
- seclai evals test-draft <agentId> --json '{"criteria":{...},"run_id":"..."}'
1399
+ seclai evals test-draft <agentId> --json '{"criteria":{"name":"Test","eval_type":"llm_judge"},"run_id":"..."}'
1405
1400
  seclai evals agent-results <agentId> [--page N] [--limit N]
1406
1401
  seclai evals agent-runs <agentId> [--page N] [--limit N]
1407
1402
  seclai evals non-manual-summary <agentId>
1408
1403
  \`\`\`
1409
1404
 
1410
- ## Solutions
1405
+ ### Solutions
1406
+
1411
1407
  \`\`\`bash
1412
1408
  seclai solutions list [--page N] [--limit N] [--sort field] [--order asc|desc]
1413
- seclai solutions create --json '{"name":"My Solution"}'
1409
+ seclai solutions create --json '{"name":"Customer Support Solution"}'
1414
1410
  seclai solutions get <solutionId>
1415
1411
  seclai solutions update <solutionId> --json '{"name":"Updated"}'
1416
1412
  seclai solutions delete <solutionId>
1417
1413
  \`\`\`
1418
1414
 
1419
- ## Solution Links
1415
+ ### Solution links
1416
+
1420
1417
  \`\`\`bash
1421
- seclai solutions link <solutionId> --agents '["id1","id2"]' --kb '["id3"]' --sources '["id4"]'
1422
- seclai solutions unlink <solutionId> --agents '["id1"]'
1418
+ # link resources \u2014 each flag takes a JSON array of IDs
1419
+ seclai solutions link <solutionId> --agents '["agentId1"]' --kb '["kbId1"]' --sources '["sourceId1"]'
1420
+ seclai solutions unlink <solutionId> --agents '["agentId1"]'
1423
1421
  \`\`\`
1424
1422
 
1425
- ## Solution Conversations
1423
+ ### Solution conversations & AI
1424
+
1426
1425
  \`\`\`bash
1427
1426
  seclai solutions convos list <solutionId>
1428
- seclai solutions convos add <solutionId> --json '{"message":"..."}'
1427
+ seclai solutions convos add <solutionId> --json '{"message":"How should I structure this?"}'
1429
1428
  seclai solutions convos mark <solutionId> <conversationId> --json '{"accepted":true}'
1430
- \`\`\`
1431
1429
 
1432
- ## Solution AI
1433
- \`\`\`bash
1434
1430
  seclai solutions ai generate <solutionId> --user-input "Add an FAQ source"
1435
- seclai solutions ai kb <solutionId> --user-input "Create a knowledge base"
1436
- seclai solutions ai source <solutionId> --user-input "Create a file source"
1431
+ seclai solutions ai kb <solutionId> --user-input "Create a knowledge base for docs"
1432
+ seclai solutions ai source <solutionId> --user-input "Create a file source for PDFs"
1437
1433
  seclai solutions ai accept <solutionId> <conversationId> --json '{"accepted":true}'
1438
1434
  seclai solutions ai decline <solutionId> <conversationId>
1439
1435
  \`\`\`
1440
- `;
1441
- var ALERTS_GOVERNANCE_SKILL = `# Seclai CLI \u2014 Alerts, Governance, Models & Search
1442
1436
 
1443
- ## Alerts
1437
+ ### Alerts
1438
+
1444
1439
  \`\`\`bash
1445
- seclai alerts list [--page N] [--limit N] [--status <s>] [--severity <s>]
1440
+ seclai alerts list [--page N] [--limit N] [--status <status>] [--severity <severity>]
1446
1441
  seclai alerts get <alertId>
1447
1442
  seclai alerts status <alertId> --json '{"status":"resolved"}'
1448
- seclai alerts comment <alertId> --json '{"comment":"Fixed"}'
1443
+ seclai alerts comment <alertId> --json '{"comment":"Fixed the issue"}'
1449
1444
  seclai alerts subscribe <alertId>
1450
1445
  seclai alerts unsubscribe <alertId>
1451
1446
  \`\`\`
1452
1447
 
1453
- ## Alert Configurations
1448
+ ### Alert configurations
1449
+
1454
1450
  \`\`\`bash
1455
1451
  seclai alerts configs list [--page N] [--limit N]
1456
- seclai alerts configs create --json '{"name":"My Config",...}'
1452
+ seclai alerts configs create --json '{"name":"Latency Alert","description":"...","threshold":5000}'
1457
1453
  seclai alerts configs get <configId>
1458
- seclai alerts configs update <configId> --json '{"name":"Updated"}'
1454
+ seclai alerts configs update <configId> --json '{"threshold":3000}'
1459
1455
  seclai alerts configs delete <configId>
1460
1456
  \`\`\`
1461
1457
 
1462
- ## Alert Preferences
1458
+ ### Alert preferences
1459
+
1463
1460
  \`\`\`bash
1464
1461
  seclai alerts prefs list
1465
- seclai alerts prefs update <orgId> <alertType> --json '{"enabled":true}'
1462
+ seclai alerts prefs update <organizationId> <alertType> --json '{"enabled":true}'
1466
1463
  \`\`\`
1467
1464
 
1468
- ## Governance AI
1465
+ ### Governance AI
1466
+
1469
1467
  \`\`\`bash
1470
1468
  seclai governance ai generate --user-input "Create a content safety policy"
1471
1469
  seclai governance ai list
@@ -1473,7 +1471,8 @@ seclai governance ai accept <conversationId>
1473
1471
  seclai governance ai decline <conversationId>
1474
1472
  \`\`\`
1475
1473
 
1476
- ## Model Alerts
1474
+ ### Model alerts
1475
+
1477
1476
  \`\`\`bash
1478
1477
  seclai models alerts list [--page N] [--limit N]
1479
1478
  seclai models alerts mark-read <alertId>
@@ -1482,14 +1481,16 @@ seclai models alerts unread-count
1482
1481
  seclai models recommendations <modelId>
1483
1482
  \`\`\`
1484
1483
 
1485
- ## Search
1484
+ ### Search
1485
+
1486
1486
  \`\`\`bash
1487
1487
  seclai search --query "deployment guide" [--limit N] [--entity-type <type>]
1488
1488
  \`\`\`
1489
1489
 
1490
- ## AI Assistant
1490
+ ### AI assistant (global)
1491
+
1491
1492
  \`\`\`bash
1492
- seclai ai feedback --json '{"feedback":"..."}'
1493
+ seclai ai feedback --json '{"feedback":"The response was helpful"}'
1493
1494
  seclai ai kb --user-input "Create a support knowledge base"
1494
1495
  seclai ai source --user-input "Create a documentation source"
1495
1496
  seclai ai solution --user-input "Build a customer support solution"
@@ -1499,15 +1500,237 @@ seclai ai accept <conversationId> --json '{"accepted":true}'
1499
1500
  seclai ai decline <conversationId>
1500
1501
  seclai ai memory-accept <conversationId> --json '{"accepted":true}'
1501
1502
  \`\`\`
1503
+
1504
+ ### Skills
1505
+
1506
+ \`\`\`bash
1507
+ # install skill files into AI coding tool directories (auto-detects or specify)
1508
+ seclai skills install [--tool copilot|claude|cursor|windsurf|codex|kiro|cline|roo|gemini|antigravity|all] [--dir .]
1509
+ \`\`\`
1510
+
1511
+ ### MCP server
1512
+
1513
+ \`\`\`bash
1514
+ # configure MCP server access in AI coding tool config files
1515
+ seclai mcp configure --key <apiKey> [--target claude-code|cursor|claude-desktop|windsurf|all] [--dir .]
1516
+
1517
+ # show the MCP config JSON snippet
1518
+ seclai mcp show [--key <apiKey>]
1519
+ \`\`\`
1520
+
1521
+ ## Example: Create a source and upload content
1522
+
1523
+ \`\`\`bash
1524
+ seclai sources create --json '{"name":"Product Docs","description":"Product documentation source"}'
1525
+ # note the id from the output
1526
+ seclai sources upload <sourceId> --file ./docs.pdf --title "Product Manual" --metadata '{"version":"2.0"}'
1527
+ seclai sources get <sourceId>
1528
+ \`\`\`
1529
+
1530
+ ## Example: Set up a knowledge base with an agent
1531
+
1532
+ \`\`\`bash
1533
+ seclai kb create --json '{"name":"Support KB","description":"Customer support articles"}'
1534
+ seclai agents create --json '{"name":"Support Bot","description":"Answers customer questions"}'
1535
+ seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot that searches the Support KB"
1536
+ seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1537
+ seclai agents run <agentId> --json '{"input":"How do I reset my password?"}' --stream
1538
+ \`\`\`
1539
+
1540
+ ## Example: Evaluate agent quality
1541
+
1542
+ \`\`\`bash
1543
+ # create eval criteria
1544
+ seclai evals criteria create <agentId> --json '{"name":"Answer Accuracy","eval_type":"llm_judge","description":"Does the answer correctly address the question?"}'
1545
+ # find compatible runs
1546
+ seclai evals compatible-runs <criteriaId> --limit 5
1547
+ # test the criteria against a run without persisting
1548
+ seclai evals test-draft <agentId> --json '{"criteria":{"name":"Answer Accuracy","eval_type":"llm_judge"},"run_id":"<runId>"}'
1549
+ # create a persisted result
1550
+ seclai evals results create <criteriaId> --json '{"run_id":"<runId>","score":0.95}'
1551
+ # view summary
1552
+ seclai evals criteria summary <criteriaId>
1553
+ \`\`\`
1554
+
1555
+ ## Example: Solution with linked resources
1556
+
1557
+ \`\`\`bash
1558
+ seclai solutions create --json '{"name":"Customer Support"}'
1559
+ seclai solutions link <solutionId> --agents '["<agentId>"]' --kb '["<kbId>"]' --sources '["<sourceId>"]'
1560
+ seclai solutions get <solutionId>
1561
+ \`\`\`
1562
+
1563
+ ## Example: Memory-powered agent
1564
+
1565
+ \`\`\`bash
1566
+ seclai memory create --json '{"name":"User Preferences","type":"general"}'
1567
+ seclai agents create --json '{"name":"Personal Assistant","description":"Remembers user preferences"}'
1568
+ seclai agents ai gen-steps <agentId> --user-input "Build a chat agent that remembers user preferences. Use general memory bank <memoryBankId>"
1569
+ seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
1570
+ \`\`\`
1571
+
1572
+ ## Example: Governance policy setup
1573
+
1574
+ \`\`\`bash
1575
+ seclai governance ai generate --user-input "Create a content safety policy that blocks harmful outputs"
1576
+ seclai governance ai list
1577
+ seclai governance ai accept <conversationId>
1578
+ \`\`\`
1579
+
1580
+ ## Specific topics
1581
+
1582
+ * **Streaming & event modes** [references/streaming.md](references/streaming.md)
1583
+ * **File uploads & content management** [references/uploads.md](references/uploads.md)
1584
+ * **Evaluations workflow** [references/evaluations.md](references/evaluations.md)
1585
+ `;
1586
+ var STREAMING_REF = `# Streaming Agent Runs
1587
+
1588
+ ## Modes
1589
+
1590
+ ### --stream
1591
+ Wait for the agent run to complete via SSE. Prints the final result as a single JSON object.
1592
+ Useful when you want to block until done.
1593
+
1594
+ \`\`\`bash
1595
+ seclai agents run <agentId> --json '{"input":"Hello"}' --stream
1596
+ seclai agents run <agentId> --json '{"input":"Hello"}' --stream --timeout-ms 120000
1597
+ \`\`\`
1598
+
1599
+ ### --events
1600
+ Stream individual SSE events as NDJSON (one JSON object per line). Use for real-time processing.
1601
+
1602
+ \`\`\`bash
1603
+ # all events, full event objects
1604
+ seclai agents run <agentId> --json '{"input":"Hello"}' --events
1605
+
1606
+ # only data payloads (no event metadata)
1607
+ seclai agents run <agentId> --json '{"input":"Hello"}' --events --output data
1608
+
1609
+ # only status events
1610
+ seclai agents run <agentId> --json '{"input":"Hello"}' --events --output status
1611
+
1612
+ # filter specific event types
1613
+ seclai agents run <agentId> --json '{"input":"Hello"}' --events --event-filter "status,data"
1614
+ \`\`\`
1615
+
1616
+ Output modes for --events:
1617
+ - \`full\`: entire SSE event object (default)
1618
+ - \`data\`: only the data payload of each event
1619
+ - \`status\`: only events with status information
1620
+
1621
+ ### --poll
1622
+ Poll the API at intervals for run completion. Does not use SSE.
1623
+
1624
+ \`\`\`bash
1625
+ seclai agents run <agentId> --json '{"input":"Hello"}' --poll
1626
+ seclai agents run <agentId> --json '{"input":"Hello"}' --poll --poll-interval-ms 5000 --include-step-outputs
1627
+ \`\`\`
1628
+
1629
+ ### No flag
1630
+ Fire-and-forget: starts the run and immediately returns the run ID.
1631
+
1632
+ \`\`\`bash
1633
+ seclai agents run <agentId> --json '{"input":"Hello"}'
1634
+ # returns: {"id":"run_...","status":"queued",...}
1635
+ # check later:
1636
+ seclai agents runs get <runId>
1637
+ \`\`\`
1638
+ `;
1639
+ var UPLOADS_REF = `# File Uploads & Content Management
1640
+
1641
+ ## Upload to a source
1642
+ \`\`\`bash
1643
+ seclai sources upload <sourceId> --file ./doc.pdf
1644
+ seclai sources upload <sourceId> --file ./doc.pdf --title "My Doc" --metadata '{"category":"docs"}' --file-name "custom-name.pdf" --mime-type "application/pdf"
1645
+ seclai sources upload <sourceId> --file ./doc.pdf --metadata-file ./meta.json
1646
+ \`\`\`
1647
+
1648
+ ## Upload text directly
1649
+ \`\`\`bash
1650
+ seclai sources upload-text <sourceId> --json '{"text":"Article content here...","title":"My Article"}'
1651
+ \`\`\`
1652
+
1653
+ ## Upload input for agent runs
1654
+ \`\`\`bash
1655
+ seclai agents upload-input <agentId> --file ./input.pdf
1656
+ seclai agents upload-input <agentId> --file ./data.csv --file-name "report.csv" --mime-type "text/csv"
1657
+ seclai agents input-status <agentId> <uploadId>
1658
+ \`\`\`
1659
+
1660
+ ## Replace content
1661
+ \`\`\`bash
1662
+ # replace with file
1663
+ seclai contents upload <contentVersionId> --file ./updated.pdf
1664
+
1665
+ # replace with text
1666
+ seclai contents replace-text <contentVersionId> --json '{"text":"Updated content","title":"Revised Article"}'
1667
+ \`\`\`
1668
+
1669
+ ## Read content
1670
+ \`\`\`bash
1671
+ # full content
1672
+ seclai contents get <contentVersionId>
1673
+
1674
+ # text slice (0-based offsets)
1675
+ seclai contents get <contentVersionId> --start 0 --end 1000
1676
+
1677
+ # view embeddings
1678
+ seclai contents embeddings <contentVersionId> [--page N] [--limit N]
1679
+ \`\`\`
1680
+ `;
1681
+ var EVALUATIONS_REF = `# Evaluations Workflow
1682
+
1683
+ ## Step 1: Create evaluation criteria for an agent
1684
+ \`\`\`bash
1685
+ seclai evals criteria create <agentId> --json '{"name":"Answer Accuracy","description":"Does the answer correctly address the question?","eval_type":"llm_judge"}'
1686
+ \`\`\`
1687
+
1688
+ ## Step 2: Find runs to evaluate
1689
+ \`\`\`bash
1690
+ # list all runs for an agent
1691
+ seclai agents runs list <agentId> --limit 10
1692
+
1693
+ # or find runs compatible with specific criteria
1694
+ seclai evals compatible-runs <criteriaId> --limit 10
1695
+ \`\`\`
1696
+
1697
+ ## Step 3: Test criteria before committing
1698
+ \`\`\`bash
1699
+ seclai evals test-draft <agentId> --json '{"criteria":{"name":"Answer Accuracy","eval_type":"llm_judge","description":"..."},"run_id":"<runId>"}'
1700
+ \`\`\`
1701
+
1702
+ ## Step 4: Create evaluation results
1703
+ \`\`\`bash
1704
+ seclai evals results create <criteriaId> --json '{"run_id":"<runId>","score":0.95}'
1705
+ \`\`\`
1706
+
1707
+ ## Step 5: Review summaries
1708
+ \`\`\`bash
1709
+ seclai evals criteria summary <criteriaId>
1710
+ seclai evals agent-results <agentId>
1711
+ seclai evals agent-runs <agentId> --limit 20
1712
+ seclai evals non-manual-summary <agentId>
1713
+ \`\`\`
1714
+
1715
+ ## Managing criteria
1716
+ \`\`\`bash
1717
+ seclai evals criteria list <agentId>
1718
+ seclai evals criteria get <criteriaId>
1719
+ seclai evals criteria update <criteriaId> --json '{"name":"Updated Name"}'
1720
+ seclai evals criteria delete <criteriaId>
1721
+ \`\`\`
1722
+
1723
+ ## Viewing results
1724
+ \`\`\`bash
1725
+ seclai evals results list <criteriaId> [--page N] [--limit N]
1726
+ \`\`\`
1502
1727
  `;
1503
1728
  function getToolConfig(tool, destDir) {
1504
1729
  const skillFiles = [
1505
1730
  { name: "SKILL.md", content: SKILL_MD },
1506
- { name: "references/agents.md", content: AGENTS_SKILL },
1507
- { name: "references/sources.md", content: SOURCES_SKILL },
1508
- { name: "references/kb-memory.md", content: KB_MEMORY_SKILL },
1509
- { name: "references/evals-solutions.md", content: EVALS_SOLUTIONS_SKILL },
1510
- { name: "references/alerts-governance.md", content: ALERTS_GOVERNANCE_SKILL }
1731
+ { name: "references/streaming.md", content: STREAMING_REF },
1732
+ { name: "references/uploads.md", content: UPLOADS_REF },
1733
+ { name: "references/evaluations.md", content: EVALUATIONS_REF }
1511
1734
  ];
1512
1735
  switch (tool) {
1513
1736
  case "copilot":
@@ -1586,6 +1809,126 @@ function register13(program, rt) {
1586
1809
  });
1587
1810
  }
1588
1811
 
1812
+ // src/commands/mcp.ts
1813
+ import { existsSync as existsSync2 } from "fs";
1814
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
1815
+ import { dirname as dirname2, join as join2 } from "path";
1816
+ import { homedir, platform } from "os";
1817
+ var MCP_URL = "https://api.seclai.com/mcp";
1818
+ function buildMcpEntry(apiKey) {
1819
+ return {
1820
+ type: "streamable-http",
1821
+ url: MCP_URL,
1822
+ headers: { "X-API-Key": apiKey }
1823
+ };
1824
+ }
1825
+ function getTargets(destDir) {
1826
+ const home = homedir();
1827
+ const os = platform();
1828
+ const targets = [
1829
+ // Project-scoped configs
1830
+ { name: "claude-code", path: join2(destDir, ".mcp.json"), scope: "project" },
1831
+ { name: "cursor", path: join2(destDir, ".cursor", "mcp.json"), scope: "project" }
1832
+ ];
1833
+ if (os === "win32") {
1834
+ targets.push({
1835
+ name: "claude-desktop",
1836
+ path: join2(process.env["APPDATA"] ?? join2(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json"),
1837
+ scope: "global"
1838
+ });
1839
+ } else if (os === "darwin") {
1840
+ targets.push({
1841
+ name: "claude-desktop",
1842
+ path: join2(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
1843
+ scope: "global"
1844
+ });
1845
+ }
1846
+ targets.push({ name: "windsurf", path: join2(home, ".codeium", "windsurf", "mcp_config.json"), scope: "global" });
1847
+ return targets;
1848
+ }
1849
+ async function mergeConfig(filePath, apiKey) {
1850
+ let existing = {};
1851
+ if (existsSync2(filePath)) {
1852
+ try {
1853
+ const parsed = JSON.parse(await readFile2(filePath, "utf8"));
1854
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return false;
1855
+ existing = parsed;
1856
+ } catch {
1857
+ return false;
1858
+ }
1859
+ }
1860
+ const raw = existing["mcpServers"];
1861
+ const servers = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
1862
+ servers["seclai"] = buildMcpEntry(apiKey);
1863
+ existing["mcpServers"] = servers;
1864
+ await mkdir2(dirname2(filePath), { recursive: true });
1865
+ await writeFile2(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
1866
+ return true;
1867
+ }
1868
+ function detectTargets(destDir) {
1869
+ const all = getTargets(destDir);
1870
+ return all.filter((t) => {
1871
+ if (t.scope === "global") return existsSync2(dirname2(t.path));
1872
+ if (t.name === "claude-code") return existsSync2(join2(destDir, ".claude")) || existsSync2(join2(destDir, "CLAUDE.md"));
1873
+ if (t.name === "cursor") return existsSync2(join2(destDir, ".cursor"));
1874
+ return false;
1875
+ });
1876
+ }
1877
+ function register14(program, rt) {
1878
+ const mcp = program.command("mcp").description("Configure the Seclai MCP server for AI coding tools.");
1879
+ mcp.command("configure").description(
1880
+ "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."
1881
+ ).requiredOption("--key <key>", "Seclai API key to embed in the config.").option("--target <name>", "Target tool (claude-code|cursor|claude-desktop|windsurf|all). Auto-detects if omitted.").option("--dir <path>", "Project directory for project-scoped configs (default: current directory).", ".").action(async (opts) => {
1882
+ await run(rt, async () => {
1883
+ const destDir = opts.dir;
1884
+ const apiKey = opts.key;
1885
+ const allTargets = getTargets(destDir);
1886
+ let targets;
1887
+ if (opts.target === "all") {
1888
+ targets = allTargets;
1889
+ } else if (opts.target) {
1890
+ const found = allTargets.find((t) => t.name === opts.target);
1891
+ if (!found) {
1892
+ rt.writeErr(`Unknown target "${opts.target}". Use: claude-code, cursor, claude-desktop, windsurf, or all.
1893
+ `);
1894
+ rt.setExitCode(1);
1895
+ return;
1896
+ }
1897
+ targets = [found];
1898
+ } else {
1899
+ targets = detectTargets(destDir);
1900
+ if (targets.length === 0) {
1901
+ targets = [allTargets[0]];
1902
+ rt.writeErr("No MCP-compatible tool detected, defaulting to claude-code (.mcp.json).\n");
1903
+ }
1904
+ }
1905
+ let configured = 0;
1906
+ const failures = [];
1907
+ for (const target of targets) {
1908
+ const ok = await mergeConfig(target.path, apiKey);
1909
+ if (ok) {
1910
+ configured++;
1911
+ rt.writeErr(`Configured seclai MCP for ${target.name} \u2192 ${target.path}
1912
+ `);
1913
+ } else {
1914
+ failures.push(target.name);
1915
+ rt.writeErr(`Failed to parse existing config at ${target.path}, skipping.
1916
+ `);
1917
+ }
1918
+ }
1919
+ const allOk = failures.length === 0;
1920
+ printJson(rt, { ok: allOk, targets: targets.map((t) => t.name), filesWritten: configured, ...failures.length > 0 ? { failures } : {} });
1921
+ if (!allOk) rt.setExitCode(1);
1922
+ });
1923
+ });
1924
+ mcp.command("show").description("Show the Seclai MCP server JSON configuration snippet.").option("--key <key>", "API key to include (default: placeholder).").action(async (opts) => {
1925
+ await run(rt, async () => {
1926
+ const entry = buildMcpEntry(opts.key ?? "YOUR_API_KEY");
1927
+ printJson(rt, { mcpServers: { seclai: entry } });
1928
+ });
1929
+ });
1930
+ }
1931
+
1589
1932
  // src/commands/completion.ts
1590
1933
  var BASH = `#!/usr/bin/env bash
1591
1934
  # seclai bash completion \u2014 add to ~/.bashrc:
@@ -1597,7 +1940,7 @@ _seclai_completions() {
1597
1940
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
1598
1941
 
1599
1942
  # Top-level commands
1600
- commands="agents sources contents kb memory evals solutions governance alerts models search ai skills completion help"
1943
+ commands="agents sources contents kb memory evals solutions governance alerts models search ai skills mcp completion help"
1601
1944
 
1602
1945
  case "\${COMP_WORDS[1]}" in
1603
1946
  agents)
@@ -1650,6 +1993,7 @@ _seclai_completions() {
1650
1993
  esac ;;
1651
1994
  ai) COMPREPLY=( $(compgen -W "feedback kb source solution memory memory-history accept decline memory-accept" -- "$cur") ); return ;;
1652
1995
  skills) COMPREPLY=( $(compgen -W "install" -- "$cur") ); return ;;
1996
+ mcp) COMPREPLY=( $(compgen -W "configure show" -- "$cur") ); return ;;
1653
1997
  completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ); return ;;
1654
1998
  esac
1655
1999
 
@@ -1678,6 +2022,7 @@ _seclai() {
1678
2022
  'search:Search across Seclai resources'
1679
2023
  'ai:Top-level AI assistant'
1680
2024
  'skills:Install skill files for AI coding tools'
2025
+ 'mcp:Configure the Seclai MCP server'
1681
2026
  'completion:Generate shell completion scripts'
1682
2027
  'help:Display help for command'
1683
2028
  )
@@ -1730,6 +2075,9 @@ _seclai() {
1730
2075
  skills)
1731
2076
  local -a sub=(install)
1732
2077
  _describe 'subcommand' sub ;;
2078
+ mcp)
2079
+ local -a sub=(configure show)
2080
+ _describe 'subcommand' sub ;;
1733
2081
  completion)
1734
2082
  local -a sub=(bash zsh fish)
1735
2083
  _describe 'shell' sub ;;
@@ -1742,7 +2090,7 @@ _seclai "$@"
1742
2090
  var FISH = `# seclai fish completion \u2014 save to ~/.config/fish/completions/seclai.fish
1743
2091
  # seclai completion fish > ~/.config/fish/completions/seclai.fish
1744
2092
 
1745
- set -l top agents sources contents kb memory evals solutions governance alerts models search ai skills completion help
2093
+ set -l top agents sources contents kb memory evals solutions governance alerts models search ai skills mcp completion help
1746
2094
 
1747
2095
  # Top-level
1748
2096
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "agents" -d "Manage agents"
@@ -1758,6 +2106,7 @@ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "models" -d "
1758
2106
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "search" -d "Search resources"
1759
2107
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "ai" -d "AI assistant"
1760
2108
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "skills" -d "Skill files"
2109
+ complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "mcp" -d "MCP server config"
1761
2110
  complete -c seclai -n "not __fish_seen_subcommand_from $top" -f -a "completion" -d "Shell completions"
1762
2111
 
1763
2112
  # agents
@@ -1796,6 +2145,9 @@ complete -c seclai -n "__fish_seen_subcommand_from ai; and not __fish_seen_subco
1796
2145
  # skills
1797
2146
  complete -c seclai -n "__fish_seen_subcommand_from skills; and not __fish_seen_subcommand_from install" -f -a "install"
1798
2147
 
2148
+ # mcp
2149
+ complete -c seclai -n "__fish_seen_subcommand_from mcp; and not __fish_seen_subcommand_from configure show" -f -a "configure show"
2150
+
1799
2151
  # completion
1800
2152
  complete -c seclai -n "__fish_seen_subcommand_from completion; and not __fish_seen_subcommand_from bash zsh fish" -f -a "bash zsh fish"
1801
2153
 
@@ -1805,7 +2157,7 @@ complete -c seclai -l compact -d "Output compact JSON"
1805
2157
  complete -c seclai -s V -l version -d "Output version"
1806
2158
  `;
1807
2159
  var SCRIPTS = { bash: BASH, zsh: ZSH, fish: FISH };
1808
- function register14(program, rt) {
2160
+ function register15(program, rt) {
1809
2161
  const completion = program.command("completion").description("Generate shell completion scripts.").argument("<shell>", "Shell type: bash, zsh, or fish.").action(async (shell) => {
1810
2162
  const script = SCRIPTS[shell];
1811
2163
  if (!script) {
@@ -1818,6 +2170,453 @@ function register14(program, rt) {
1818
2170
  });
1819
2171
  }
1820
2172
 
2173
+ // src/commands/auth.ts
2174
+ import { randomBytes, createHash } from "crypto";
2175
+ import { createServer } from "http";
2176
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
2177
+ import { join as join3 } from "path";
2178
+ import { URL as URL2, URLSearchParams } from "url";
2179
+ import process3 from "process";
2180
+ import {
2181
+ loadSsoProfile,
2182
+ readSsoCache,
2183
+ writeSsoCache,
2184
+ deleteSsoCache,
2185
+ isTokenValid
2186
+ } from "@seclai/sdk";
2187
+ function generateCodeVerifier() {
2188
+ return randomBytes(32).toString("base64url");
2189
+ }
2190
+ function computeCodeChallenge(verifier) {
2191
+ return createHash("sha256").update(verifier).digest("base64url");
2192
+ }
2193
+ function waitForAuthCode(port, state) {
2194
+ return new Promise((resolve, reject) => {
2195
+ const server = createServer((req, res) => {
2196
+ const url = new URL2(req.url ?? "/", `http://localhost:${port}`);
2197
+ if (url.pathname !== "/callback") {
2198
+ res.writeHead(404);
2199
+ res.end();
2200
+ return;
2201
+ }
2202
+ const code = url.searchParams.get("code");
2203
+ const returnedState = url.searchParams.get("state");
2204
+ const error = url.searchParams.get("error");
2205
+ if (error) {
2206
+ res.writeHead(200, { "Content-Type": "text/html" });
2207
+ res.end("<html><body><h2>Authentication failed</h2><p>You can close this tab.</p></body></html>");
2208
+ reject(new Error(`OAuth error: ${error}`));
2209
+ server.close();
2210
+ return;
2211
+ }
2212
+ if (!code || returnedState !== state) {
2213
+ res.writeHead(400, { "Content-Type": "text/html" });
2214
+ res.end("<html><body><h2>Invalid callback</h2></body></html>");
2215
+ reject(new Error("Invalid callback: missing code or state mismatch"));
2216
+ server.close();
2217
+ return;
2218
+ }
2219
+ res.writeHead(200, { "Content-Type": "text/html" });
2220
+ res.end("<html><body><h2>Authenticated successfully!</h2><p>You can close this tab.</p></body></html>");
2221
+ resolve({
2222
+ code,
2223
+ cleanup: () => server.close()
2224
+ });
2225
+ });
2226
+ server.listen(port, "127.0.0.1");
2227
+ server.on("error", reject);
2228
+ });
2229
+ }
2230
+ async function exchangeCodeForTokens(profile, code, codeVerifier, redirectUri) {
2231
+ const tokenUrl = `https://${profile.ssoDomain}/oauth2/token`;
2232
+ const body = new URLSearchParams({
2233
+ grant_type: "authorization_code",
2234
+ client_id: profile.ssoClientId,
2235
+ code,
2236
+ redirect_uri: redirectUri,
2237
+ code_verifier: codeVerifier
2238
+ });
2239
+ const resp = await fetch(tokenUrl, {
2240
+ method: "POST",
2241
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2242
+ body: body.toString()
2243
+ });
2244
+ if (!resp.ok) {
2245
+ const text = await resp.text();
2246
+ throw new Error(`Token exchange failed (HTTP ${resp.status}): ${text}`);
2247
+ }
2248
+ const data = await resp.json();
2249
+ const expiresAt = new Date(Date.now() + data.expires_in * 1e3).toISOString();
2250
+ const entry = {
2251
+ accessToken: data.access_token,
2252
+ expiresAt,
2253
+ clientId: profile.ssoClientId,
2254
+ region: profile.ssoRegion,
2255
+ cognitoDomain: profile.ssoDomain
2256
+ };
2257
+ if (data.refresh_token) entry.refreshToken = data.refresh_token;
2258
+ if (data.id_token) entry.idToken = data.id_token;
2259
+ return entry;
2260
+ }
2261
+ var DEFAULT_CALLBACK_PORT = 9876;
2262
+ function resolveBaseUrl() {
2263
+ const envUrl = process3.env.SECLAI_API_URL;
2264
+ return envUrl && envUrl.length > 0 ? envUrl : "https://api.seclai.com";
2265
+ }
2266
+ async function fetchAccountId(accessToken) {
2267
+ const baseUrl = resolveBaseUrl();
2268
+ const resp = await fetch(`${baseUrl}/me`, {
2269
+ headers: { authorization: `Bearer ${accessToken}` }
2270
+ });
2271
+ if (!resp.ok) {
2272
+ const text = await resp.text();
2273
+ throw new Error(`Failed to resolve account ID from /me (HTTP ${resp.status}): ${text}`);
2274
+ }
2275
+ const data = await resp.json();
2276
+ return data.account_id;
2277
+ }
2278
+ async function updateConfigKey(configDir, profileName, key, value) {
2279
+ const configPath = join3(configDir, "config");
2280
+ let content = "";
2281
+ try {
2282
+ content = await readFile3(configPath, "utf-8");
2283
+ } catch {
2284
+ }
2285
+ const sectionHeader = profileName === "default" ? "[default]" : `[profile ${profileName}]`;
2286
+ const sectionIdx = content.indexOf(sectionHeader);
2287
+ if (sectionIdx === -1) {
2288
+ if (content.length > 0 && !content.endsWith("\n")) {
2289
+ content += "\n";
2290
+ }
2291
+ content += `
2292
+ ${sectionHeader}
2293
+ ${key} = ${value}
2294
+ `;
2295
+ } else {
2296
+ const afterHeader = sectionIdx + sectionHeader.length;
2297
+ const nextSectionMatch = content.slice(afterHeader).match(/\n\[/);
2298
+ const sectionEnd = nextSectionMatch ? afterHeader + nextSectionMatch.index : content.length;
2299
+ const sectionBody = content.slice(afterHeader, sectionEnd);
2300
+ const keyRegex = new RegExp(`^${key}\\s*=.*$`, "m");
2301
+ let newSectionBody;
2302
+ if (keyRegex.test(sectionBody)) {
2303
+ newSectionBody = sectionBody.replace(keyRegex, `${key} = ${value}`);
2304
+ } else {
2305
+ const trimmed = sectionBody.trimEnd();
2306
+ newSectionBody = `${trimmed}
2307
+ ${key} = ${value}
2308
+ `;
2309
+ }
2310
+ content = content.slice(0, afterHeader) + newSectionBody + content.slice(sectionEnd);
2311
+ }
2312
+ const { mkdir: mkdir4 } = await import("fs/promises");
2313
+ await mkdir4(configDir, { recursive: true });
2314
+ await writeFile3(configPath, content, { mode: 384 });
2315
+ }
2316
+ function resolveProfile(opts) {
2317
+ const profileName = opts.profile || process3.env.SECLAI_PROFILE || "default";
2318
+ let configDir = opts.configDir || process3.env.SECLAI_CONFIG_DIR;
2319
+ if (!configDir) {
2320
+ const home = process3.env.HOME ?? process3.env.USERPROFILE;
2321
+ if (home && home.trim() !== "") {
2322
+ configDir = join3(home, ".seclai");
2323
+ } else {
2324
+ configDir = join3(process3.cwd(), ".seclai");
2325
+ }
2326
+ }
2327
+ return { profileName, configDir };
2328
+ }
2329
+ async function loadProfile(rt, opts) {
2330
+ const { profileName, configDir } = resolveProfile(opts);
2331
+ const profile = await loadSsoProfile(configDir, profileName);
2332
+ return { profile, profileName, configDir };
2333
+ }
2334
+ function register16(program, rt) {
2335
+ const group = program.command("auth").description("SSO authentication (login/logout/status/refresh).");
2336
+ 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) => {
2337
+ await run(rt, async () => {
2338
+ const globalOpts = program.opts();
2339
+ const { profile, profileName, configDir } = await loadProfile(rt, globalOpts);
2340
+ const port = parseInt(opts.port ?? String(DEFAULT_CALLBACK_PORT), 10);
2341
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
2342
+ throw new Error(`Invalid port: ${opts.port}. Must be an integer between 1 and 65535.`);
2343
+ }
2344
+ const redirectUri = `http://localhost:${port}/callback`;
2345
+ const codeVerifier = generateCodeVerifier();
2346
+ const codeChallenge = computeCodeChallenge(codeVerifier);
2347
+ const state = randomBytes(16).toString("hex");
2348
+ const authUrl = new URL2(`https://${profile.ssoDomain}/oauth2/authorize`);
2349
+ authUrl.searchParams.set("response_type", "code");
2350
+ authUrl.searchParams.set("client_id", profile.ssoClientId);
2351
+ authUrl.searchParams.set("redirect_uri", redirectUri);
2352
+ authUrl.searchParams.set("scope", "openid profile email");
2353
+ authUrl.searchParams.set("state", state);
2354
+ authUrl.searchParams.set("code_challenge", codeChallenge);
2355
+ authUrl.searchParams.set("code_challenge_method", "S256");
2356
+ const authUrlStr = authUrl.toString();
2357
+ const codePromise = waitForAuthCode(port, state);
2358
+ if (opts.browser !== false) {
2359
+ const { spawn } = await import("child_process");
2360
+ const openCmd = process3.platform === "darwin" ? { cmd: "open", args: [authUrlStr] } : process3.platform === "win32" ? { cmd: "cmd", args: ["/c", "start", "", authUrlStr] } : { cmd: "xdg-open", args: [authUrlStr] };
2361
+ spawn(openCmd.cmd, openCmd.args, { stdio: "ignore", detached: true }).unref();
2362
+ rt.writeErr(`Opening browser for authentication...
2363
+ `);
2364
+ } else {
2365
+ rt.writeErr(`Open this URL in your browser:
2366
+
2367
+ ${authUrlStr}
2368
+
2369
+ `);
2370
+ }
2371
+ rt.writeErr("Waiting for authentication callback...\n");
2372
+ const { code, cleanup } = await codePromise;
2373
+ let tokens;
2374
+ try {
2375
+ rt.writeErr("Exchanging code for tokens...\n");
2376
+ tokens = await exchangeCodeForTokens(profile, code, codeVerifier, redirectUri);
2377
+ await writeSsoCache(configDir, profile, tokens);
2378
+ } finally {
2379
+ cleanup();
2380
+ }
2381
+ let accountId = profile.ssoAccountId;
2382
+ try {
2383
+ rt.writeErr("Resolving account ID...\n");
2384
+ accountId = await fetchAccountId(tokens.accessToken);
2385
+ await updateConfigKey(configDir, profileName, "sso_account_id", accountId);
2386
+ } catch {
2387
+ rt.writeErr("Warning: Could not resolve account ID from /me. You can set it manually with `seclai configure sso`.\n");
2388
+ }
2389
+ rt.writeErr("Successfully authenticated!\n");
2390
+ const loginResult = {
2391
+ status: "authenticated",
2392
+ profile: profileName,
2393
+ expiresAt: tokens.expiresAt
2394
+ };
2395
+ if (accountId) loginResult.accountId = accountId;
2396
+ printJson(rt, loginResult);
2397
+ });
2398
+ });
2399
+ group.command("logout").description("Remove cached SSO tokens for the current profile.").action(async () => {
2400
+ await run(rt, async () => {
2401
+ const globalOpts = program.opts();
2402
+ const { profile, profileName, configDir } = await loadProfile(rt, globalOpts);
2403
+ await deleteSsoCache(configDir, profile);
2404
+ rt.writeErr("Logged out successfully.\n");
2405
+ printJson(rt, { status: "logged_out", profile: profileName });
2406
+ });
2407
+ });
2408
+ group.command("status").description("Show current authentication status for the active profile.").action(async () => {
2409
+ await run(rt, async () => {
2410
+ const globalOpts = program.opts();
2411
+ const { profile: profileLoaded, profileName, configDir } = await loadProfile(rt, globalOpts);
2412
+ const cached = await readSsoCache(configDir, profileLoaded);
2413
+ if (!cached) {
2414
+ const notAuthResult = {
2415
+ profile: profileName,
2416
+ status: "not_authenticated"
2417
+ };
2418
+ if (profileLoaded.ssoAccountId) notAuthResult.accountId = profileLoaded.ssoAccountId;
2419
+ printJson(rt, notAuthResult);
2420
+ return;
2421
+ }
2422
+ const valid = isTokenValid(cached);
2423
+ const statusResult = {
2424
+ profile: profileName,
2425
+ status: valid ? "authenticated" : "expired",
2426
+ expiresAt: cached.expiresAt,
2427
+ hasRefreshToken: Boolean(cached.refreshToken)
2428
+ };
2429
+ if (profileLoaded.ssoAccountId) statusResult.accountId = profileLoaded.ssoAccountId;
2430
+ printJson(rt, statusResult);
2431
+ });
2432
+ });
2433
+ group.command("refresh").description("Manually refresh the SSO token for the current profile.").action(async () => {
2434
+ await run(rt, async () => {
2435
+ const globalOpts = program.opts();
2436
+ const { profile, profileName, configDir } = await loadProfile(rt, globalOpts);
2437
+ const cached = await readSsoCache(configDir, profile);
2438
+ if (!cached?.refreshToken) {
2439
+ throw new Error("No cached token with refresh token. Run `seclai auth login` first.");
2440
+ }
2441
+ const tokenUrl = `https://${profile.ssoDomain}/oauth2/token`;
2442
+ const body = new URLSearchParams({
2443
+ grant_type: "refresh_token",
2444
+ client_id: profile.ssoClientId,
2445
+ refresh_token: cached.refreshToken
2446
+ });
2447
+ const resp = await fetch(tokenUrl, {
2448
+ method: "POST",
2449
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2450
+ body: body.toString()
2451
+ });
2452
+ if (!resp.ok) {
2453
+ const text = await resp.text();
2454
+ throw new Error(`Token refresh failed (HTTP ${resp.status}): ${text}`);
2455
+ }
2456
+ const data = await resp.json();
2457
+ const refreshed = {
2458
+ accessToken: data.access_token,
2459
+ refreshToken: data.refresh_token ?? cached.refreshToken,
2460
+ expiresAt: new Date(Date.now() + data.expires_in * 1e3).toISOString(),
2461
+ clientId: profile.ssoClientId,
2462
+ region: profile.ssoRegion,
2463
+ cognitoDomain: profile.ssoDomain
2464
+ };
2465
+ if (data.id_token) refreshed.idToken = data.id_token;
2466
+ await writeSsoCache(configDir, profile, refreshed);
2467
+ rt.writeErr("Token refreshed successfully.\n");
2468
+ printJson(rt, {
2469
+ status: "refreshed",
2470
+ profile: profileName,
2471
+ expiresAt: refreshed.expiresAt
2472
+ });
2473
+ });
2474
+ });
2475
+ }
2476
+
2477
+ // src/commands/configure.ts
2478
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2479
+ import { join as join4 } from "path";
2480
+ import process4 from "process";
2481
+ import { createInterface } from "readline";
2482
+ import { DEFAULT_SSO_DOMAIN, DEFAULT_SSO_CLIENT_ID, DEFAULT_SSO_REGION } from "@seclai/sdk";
2483
+ function prompt(rt, question, defaultValue) {
2484
+ return new Promise((resolve) => {
2485
+ const rl = createInterface({
2486
+ input: rt.stdin,
2487
+ output: { write: (s) => {
2488
+ rt.writeErr(s);
2489
+ return true;
2490
+ } },
2491
+ terminal: false
2492
+ });
2493
+ const display = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
2494
+ rt.writeErr(display);
2495
+ rl.once("line", (answer) => {
2496
+ rl.close();
2497
+ resolve(answer.trim() || defaultValue || "");
2498
+ });
2499
+ });
2500
+ }
2501
+ function resolveConfigDir(opts) {
2502
+ if (opts.configDir) return opts.configDir;
2503
+ const env = process4.env.SECLAI_CONFIG_DIR;
2504
+ if (env) return env;
2505
+ const home = process4.env.HOME ?? process4.env.USERPROFILE ?? "";
2506
+ return join4(home, ".seclai");
2507
+ }
2508
+ function register17(program, rt) {
2509
+ const group = program.command("configure").description("Configure CLI profiles and settings.");
2510
+ 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) => {
2511
+ await run(rt, async () => {
2512
+ const globalOpts = program.opts();
2513
+ const profileName = opts.profileName || globalOpts.profile || "default";
2514
+ const configDir = resolveConfigDir(globalOpts);
2515
+ const configPath = join4(configDir, "config");
2516
+ rt.writeErr(`
2517
+ Configuring SSO profile "${profileName}".
2518
+ `);
2519
+ rt.writeErr(`Defaults: domain=${DEFAULT_SSO_DOMAIN}, region=${DEFAULT_SSO_REGION}
2520
+ `);
2521
+ rt.writeErr(`Press Enter to accept defaults.
2522
+
2523
+ `);
2524
+ const domain = await prompt(rt, "SSO domain", DEFAULT_SSO_DOMAIN);
2525
+ const clientId = await prompt(rt, "SSO client ID", DEFAULT_SSO_CLIENT_ID);
2526
+ const region = await prompt(rt, "SSO region", DEFAULT_SSO_REGION);
2527
+ const accountId = await prompt(rt, "Account ID (optional, resolved after login)");
2528
+ const isDefault = domain === DEFAULT_SSO_DOMAIN && clientId === DEFAULT_SSO_CLIENT_ID && region === DEFAULT_SSO_REGION && !accountId;
2529
+ if (isDefault && profileName === "default") {
2530
+ rt.writeErr(`
2531
+ Using built-in defaults \u2014 no config file needed.
2532
+ `);
2533
+ rt.writeErr(`Run \`seclai auth login\` to authenticate.
2534
+ `);
2535
+ printJson(rt, {
2536
+ profile: profileName,
2537
+ sso_domain: domain,
2538
+ sso_client_id: clientId,
2539
+ sso_region: region,
2540
+ note: "using built-in defaults"
2541
+ });
2542
+ return;
2543
+ }
2544
+ let content = "";
2545
+ try {
2546
+ content = await readFile4(configPath, "utf-8");
2547
+ } catch {
2548
+ }
2549
+ const sectionHeader = profileName === "default" ? "[default]" : `[profile ${profileName}]`;
2550
+ const lines = [];
2551
+ if (domain !== DEFAULT_SSO_DOMAIN) lines.push(`sso_domain = ${domain}`);
2552
+ if (clientId !== DEFAULT_SSO_CLIENT_ID) lines.push(`sso_client_id = ${clientId}`);
2553
+ if (region !== DEFAULT_SSO_REGION) lines.push(`sso_region = ${region}`);
2554
+ if (accountId) lines.push(`sso_account_id = ${accountId}`);
2555
+ const sectionBody = lines.join("\n");
2556
+ const sectionRegex = profileName === "default" ? /^\[default\][^\[]*(?=\[|$(?![\s\S]))/m : new RegExp(`^\\[profile ${escapeRegExp(profileName)}\\][^\\[]*(?=\\[|$(?![\\s\\S]))`, "m");
2557
+ if (sectionRegex.test(content)) {
2558
+ content = content.replace(sectionRegex, `${sectionHeader}
2559
+ ${sectionBody}
2560
+ `);
2561
+ } else {
2562
+ if (content.length > 0 && !content.endsWith("\n")) {
2563
+ content += "\n";
2564
+ }
2565
+ content += `
2566
+ ${sectionHeader}
2567
+ ${sectionBody}
2568
+ `;
2569
+ }
2570
+ await mkdir3(configDir, { recursive: true });
2571
+ await writeFile4(configPath, content, { mode: 384 });
2572
+ rt.writeErr(`
2573
+ Profile "${profileName}" saved to ${configPath}
2574
+ `);
2575
+ rt.writeErr(`Run \`seclai auth login --profile ${profileName}\` to authenticate.
2576
+ `);
2577
+ const result = {
2578
+ profile: profileName,
2579
+ configFile: configPath,
2580
+ sso_domain: domain,
2581
+ sso_client_id: clientId,
2582
+ sso_region: region
2583
+ };
2584
+ if (accountId) result.sso_account_id = accountId;
2585
+ printJson(rt, result);
2586
+ });
2587
+ });
2588
+ group.command("list").description("List all configured profiles.").action(async () => {
2589
+ await run(rt, async () => {
2590
+ const globalOpts = program.opts();
2591
+ const configDir = resolveConfigDir(globalOpts);
2592
+ const configPath = join4(configDir, "config");
2593
+ let content;
2594
+ try {
2595
+ content = await readFile4(configPath, "utf-8");
2596
+ } catch {
2597
+ printJson(rt, { profiles: [], configFile: configPath });
2598
+ return;
2599
+ }
2600
+ const profiles = [];
2601
+ for (const line of content.split("\n")) {
2602
+ const trimmed = line.trim();
2603
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
2604
+ const raw = trimmed.slice(1, -1).trim();
2605
+ if (raw.startsWith("profile ")) {
2606
+ profiles.push(raw.slice("profile ".length).trim());
2607
+ } else {
2608
+ profiles.push(raw);
2609
+ }
2610
+ }
2611
+ }
2612
+ printJson(rt, { profiles, configFile: configPath });
2613
+ });
2614
+ });
2615
+ }
2616
+ function escapeRegExp(s) {
2617
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2618
+ }
2619
+
1821
2620
  // src/cli.ts
1822
2621
  function createProgram(rt = defaultRuntime()) {
1823
2622
  const program = new Command();
@@ -1831,6 +2630,15 @@ All commands return JSON to stdout, making it easy to pipe into jq or other tool
1831
2630
  ).version(cliVersion, "-V, --version", "output the version").option(
1832
2631
  "--api-key <key>",
1833
2632
  "Seclai API key (defaults to SECLAI_API_KEY)."
2633
+ ).option(
2634
+ "--profile <name>",
2635
+ "SSO profile name (defaults to SECLAI_PROFILE, then 'default')."
2636
+ ).option(
2637
+ "--account-id <id>",
2638
+ "Account ID for multi-org targeting (X-Account-Id header)."
2639
+ ).option(
2640
+ "--config-dir <path>",
2641
+ "Config directory (defaults to SECLAI_CONFIG_DIR, then ~/.seclai)."
1834
2642
  ).option(
1835
2643
  "--compact",
1836
2644
  "Output compact JSON (no indentation)."
@@ -1839,16 +2647,19 @@ All commands return JSON to stdout, making it easy to pipe into jq or other tool
1839
2647
  "after",
1840
2648
  `
1841
2649
  Environment:
1842
- SECLAI_API_KEY Default API key (alternative to --api-key)
1843
- SECLAI_API_URL Override API base URL (default: https://api.seclai.com)
2650
+ SECLAI_API_KEY Default API key (alternative to --api-key)
2651
+ SECLAI_API_URL Override API base URL (default: https://api.seclai.com)
2652
+ SECLAI_PROFILE Default SSO profile (alternative to --profile)
2653
+ SECLAI_CONFIG_DIR Config directory (alternative to --config-dir)
1844
2654
 
1845
2655
  Examples:
1846
2656
  seclai agents list
1847
2657
  seclai agents run <agentId> --json '{"input":"Hello"}'
1848
2658
  seclai agents run <agentId> --json '{"input":"Hi"}' --events
1849
- seclai sources list
1850
- seclai kb list
1851
- seclai search --query "deployment guide"
2659
+ seclai configure sso
2660
+ seclai auth login
2661
+ seclai auth status
2662
+ seclai sources list --profile dev
1852
2663
  npx @seclai/cli agents list
1853
2664
  `
1854
2665
  );
@@ -1875,6 +2686,9 @@ Examples:
1875
2686
  register12(program, rt);
1876
2687
  register13(program, rt);
1877
2688
  register14(program, rt);
2689
+ register15(program, rt);
2690
+ register16(program, rt);
2691
+ register17(program, rt);
1878
2692
  return program;
1879
2693
  }
1880
2694
  async function runCli(argv, rt = defaultRuntime()) {