Set up
Skills are installed by placing their folder where your agent loads skills.
The download comes from github.com/seclai/seclai-cli itself, not from our server, so what you get is exactly what its author published.
Install instructions come from the README
README
synced Oct 26, 2026Seclai CLI
Full-featured command-line interface for the Seclai platform. Manage agents, knowledge bases, sources, memory banks, evaluations, solutions, governance, and more — all from the terminal.
All commands return JSON to stdout, so you can pipe into jq or other tools.
Install
npm i -g @seclai/cli
Or run directly via npx (no install needed):
npx @seclai/cli agents list
Setup Skills and MCP
If you are using an AI coding agent like Claude Code, you can also install the skill individually with:
seclai skills install
This installs skills into all detected coding editors by default. Use --tool <tool> to scope it to one editor.
To install the Seclai MCP server into your editors (Cursor, Claude Code, VS Code, etc.):
seclai mcp configure --key "$SECLAI_API_KEY"
Or directly via npx:
npx skills add seclai/seclai-cli --full-depth --global --all
npx add-mcp https://api.seclai.com/mcp --header "X-API-Key: $SECLAI_API_KEY" --name Seclai
Documentation
Command reference (latest): https://seclai.github.io/seclai-cli/latest/
Authentication
The CLI supports two authentication methods:
API Key
Set the SECLAI_API_KEY environment variable, or pass --api-key per-command:
export SECLAI_API_KEY="sk-..."
# or inline
seclai --api-key "$SECLAI_API_KEY" agents list
SSO (OAuth2 Bearer Token)
SSO works out of the box with built-in production defaults — no configuration needed:
# Authenticate via browser (Authorization Code + PKCE)
seclai auth login
# Check authentication status
seclai auth status
# Refresh tokens manually
seclai auth refresh
# Log out (clears cached tokens)
seclai auth logout
For custom SSO settings (e.g. staging environment), use seclai configure sso
or set environment variables:
| Variable | Description | Default |
|---|---|---|
SECLAI_SSO_DOMAIN | Cognito domain | auth.seclai.com |
SECLAI_SSO_CLIENT_ID | Cognito app client ID | 4bgf8v9qmc5puivbaqon9n5lmr |
SECLAI_SSO_REGION | AWS region | us-west-2 |
Set profiles up interactively, and list the ones you have:
# Prompts for domain, client ID, region, and account ID
seclai configure sso [--profile-name <name>]
# Show every configured profile
seclai configure list
Use a named profile with --profile:
seclai --profile staging agents list
Tokens are cached in ~/.seclai/sso/cache/ and auto-refreshed when expired.
Environment Variables
| Variable | Description |
|---|---|
SECLAI_API_KEY | Default API key (alternative to --api-key) |
SECLAI_API_URL | Override API base URL (default: https://api.seclai.com) |
SECLAI_PROFILE | Default SSO profile name (default: default) |
SECLAI_CONFIG_DIR | Config directory path (default: ~/.seclai) |
SECLAI_API_VERSION | Dated API version (alternative to --api-version) |
SECLAI_SSO_DOMAIN | Override SSO domain (default: auth.seclai.com) |
SECLAI_SSO_CLIENT_ID | Override SSO client ID (default: 4bgf8v9qmc5puivbaqon9n5lmr) |
SECLAI_SSO_REGION | Override SSO region (default: us-west-2) |
Global Options
| Flag | Description |
|---|---|
--api-key <key> | Seclai API key |
--profile <name> | SSO profile name |
--account-id <id> | Account ID (X-Account-Id header) |
--config-dir <path> | Config directory path |
--api-version <date> | Opt into dated API changes released on or before this YYYY-MM-DD |
--allow-unknown-api-version | Send an --api-version this CLI was not built against |
--compact | Output compact (single-line) JSON |
-V, --version | Print version |
API Versions
The API is versioned by date. Responses can change shape between versions — a
bare array becoming a {data, pagination} envelope, for instance — so the CLI
sends no version header by default. Upgrading the CLI on its own never
changes what a command prints.
# See which version a request resolves to
seclai api-version get
# Opt one invocation into the changes released up to a date
seclai --api-version 2026-07-27 alerts list
# Or for every client on the account, not just this CLI
seclai api-version set 2026-07-27
seclai api-version clear # revert to the default
An --api-version this CLI was not built against is rejected, because a newer
version can reshape a response the CLI would then misread. Pass
--allow-unknown-api-version to send it anyway. api-version set is held to
the same standard — it rejects a version this release was not built against,
with the same override — because the pin applies to every client on the account
and nothing re-checks it afterwards.
Each version includes the changes of the ones before it. seclai api-version --help
prints the same list.
| Version | What it changes |
|---|---|
2026-07-01 | The baseline, applied when no version is sent and the account is not pinned |
2026-07-27 | List responses move to {data, pagination}, and a query parameter the endpoint does not declare is rejected with a 422 |
2026-08-03 | memory create and memory update reject a non-zero max_age_days, which reads as null; an omitted retention_days on create resolves per bank type |
2026-08-21 | sources create rejects an embedding dimension its embedder does not support — models embedders reports the supported ones |
2026-09-28 | Agent-definition writes such as agents def update use the current file-list grammar for a step's attachments |
2026-09-30 | A run's and a step's output, and a step's input, are the text rather than a JSON manifest. Files are in attachments on every version |
2026-10-03 | A new LLM step written without attachments takes its parent's files |
2026-10-05 | An item held for review by a governance policy reports content_status completed instead of failed; read held_for_review to find holds |
2026-10-13 | sources create and sources update reject a media_types value other than images or video, or any value on a source that is not a website or RSS feed, with a 400 where it was dropped |
2026-10-16 | An upload of any type is ready at once, and its extracted text is no longer written into a run's input |
2026-10-18 | A source's document_images defaults to true on a text-only embedder |
2026-10-19 | sources create rejects polling_max_items on any source but an RSS feed with a 400 where it was stored |
2026-10-21 | An evaluation result whose evaluator returned no verdict (status error) is left out of the failed counts, rates and average scores, and evals test-draft returns its score and passed as null instead of 0.5 and false |
2026-09-30 changes what agents run and agents runs get print: a script
that parses .output as JSON to find a run's files should read .attachments
instead, which is present whichever version is in effect.
--api-key, --profile, --account-id and --config-dir reject an empty
value. A shell expanding an unset variable passes "", which the SDK discards,
so --api-key "$KEY" with KEY unset would fall back to SECLAI_API_KEY or a
cached SSO session and run as a different identity — and --account-id "" would
act on the default org rather than the one you named. Guard the flag instead of
the value:
seclai ${KEY:+--api-key "$KEY"} agents list
An empty --api-version is still accepted, with a warning: it costs only the
version header. A future release will reject that too.
Commands
Agents
seclai agents list [--page N] [--limit N]
seclai agents create --json '{"name":"My Agent"}'
seclai agents get <agentId>
seclai agents update <agentId> --json '{"name":"Renamed"}'
seclai agents delete <agentId>
# Pause an agent across every trigger path, then resume it
seclai agents disable <agentId>
seclai agents enable <agentId>
# Which live agents call this one via a call_agent step?
seclai agents callers <agentId>
Triggers
# Alias, sender allowlist, and inbound-handling flags for an EMAIL_RECEIVED trigger
seclai agents triggers email-config <agentId> <triggerId> --json '{"alias":"support"}'
# Named input attachments a dynamic_input or email_received trigger accepts.
# Steps reference them as {{agent.attachments.<name>}}; min defaults to 0.
seclai agents triggers accepted-attachments <agentId> <triggerId> \
--json '{"accepted_attachments":[{"name":"invoice","content_types":["application/pdf"],"min":1,"max":1}]}'
Running Agents
Four modes: basic, streaming (SSE wait), events (NDJSON), and polling.
# Basic run (returns final result)
seclai agents run <agentId> --json '{"input":"Hello"}'
seclai agents run <agentId> --json-file ./run.json
cat run.json | seclai agents run <agentId> --json-file -
# SSE streaming (waits for done event or timeout)
seclai agents run <agentId> --json '{"input":"Hi"}' --stream --timeout-ms 60000
# NDJSON event stream (outputs every SSE event as a JSON line)
seclai agents run <agentId> --json '{"input":"Hi"}' --events
seclai agents run <agentId> --json '{"input":"Hi"}' --events --event-filter status
seclai agents run <agentId> --json '{"input":"Hi"}' --events --output data
seclai agents run <agentId> --json '{"input":"Hi","include_reasoning":true}' --events # adds stream_reasoning events
# Polling (submit then poll until complete)
seclai agents run <agentId> --json '{"input":"Hi"}' --poll --poll-interval-ms 2000
Agent Runs
seclai agents runs list <agentId> [--page N] [--limit N]
seclai agents runs get <runId> [--include-step-outputs]
seclai agents runs cancel <runId>
seclai agents runs delete <runId> # deprecated alias for `runs cancel`
seclai agents runs search [--page N] [--limit N] [--json '...']
seclai agents runs eval-results <agentId> <runId> [--page N] [--limit N] [--paged]
# Download a file attachment emitted by a run step. attachmentId is the
# URL-safe-base64 storage_key from run output manifests / webhooks.
seclai agents runs download-attachment <runId> <attachmentId> [--download-name <name>] [--output <path>]
Agent Definition
seclai agents def get <agentId>
seclai agents def update <agentId> --json '{"steps":[...]}'
Agent Export / Import
seclai agents export <agentId>
seclai agents export <agentId> --no-download
# Validate an agent_definition payload without creating an agent.
# Reports counts and any unresolved_refs you'll need to map with entity_remap
# when calling `agents create` or `agents update`.
# The body shape is `{ "agent_definition": <export payload> }`.
seclai agents preview-import --json-file ./preview-body.json
seclai agents export <agentId> \
| jq '{agent_definition: .}' \
| seclai agents preview-import --json-file -
Agent Input Upload
# Discover which files (if any) the agent expects before staging uploads.
seclai agents attachment-references <agentId>
seclai agents upload-input <agentId> --file ./data.csv [--file-name data.csv] [--mime-type text/csv]
seclai agents input-status <agentId> <uploadId>
Agent AI Assistant
seclai agents ai gen-steps <agentId> --user-input "Build a QA chatbot"
seclai agents ai step-config <agentId> --user-input "Configure the search step"
# --step-type is required: the API rejects the request without it.
seclai agents ai history <agentId> --step-type llm [--step-id <id>] [--limit N] [--offset N]
seclai agents ai mark <agentId> <conversationId> --json '{"accepted":true}'
Sources
seclai sources list [--page N] [--limit N] [--sort <field>] [--order asc|desc]
seclai sources create --json '{"name":"Docs","type":"manual"}'
seclai sources get <sourceId>
seclai sources update <sourceId> --json '{"name":"Renamed"}'
seclai sources delete <sourceId>
source is an alias for sources (e.g. seclai source list).
Upload
seclai sources upload <sourceId> --file ./doc.pdf [--title "Doc"] [--mime-type application/pdf] [--metadata '{}']
seclai sources upload-text <sourceId> --json '{"title":"Note","text":"Hello world"}'
Content Status
Whether what you uploaded has finished indexing. Both uploads return a
content_version_id; pass it back to poll.
seclai sources contents list <sourceId> [--page N] [--limit N] [--sort created_at|title|status] [--order asc|desc]
seclai sources contents list <sourceId> --status failed
seclai sources contents list <sourceId> --content-version-id <id> --content-version-id <id>
seclai sources contents status <sourceId> <contentVersionId>
list prints {data, pagination} on every API version. Repeat
--content-version-id to poll a batch in one request, and keep a request to
about 100 ids: they travel in the URL, and one over 8,192 bytes is rejected with
a 414. An empty id is refused rather than dropped, so an unset shell variable
cannot widen the listing.
Email sources
# Mail an email source refused: sender and reason, never subject or body
seclai sources email-rejections <sourceId> [--page N] [--limit N]
To accept a refused sender, add its address or domain to the source's
email_allowed_senders with sources update — which only helps when the
rejection says authenticated is true.
Exports
seclai sources exports list <sourceId> [--page N] [--limit N]
seclai sources exports create <sourceId>
seclai sources exports get <sourceId> <exportId>
seclai sources exports cancel <sourceId> <exportId>
seclai sources exports delete <sourceId> <exportId>
seclai sources exports download <sourceId> <exportId>
seclai sources exports estimate <sourceId>
Embedding Migration
seclai sources migration get <sourceId>
seclai sources migration start <sourceId>
seclai sources migration cancel <sourceId>
Contents
seclai contents get <contentVersionId> [--start N] [--end N]
seclai contents delete <contentVersionId>
seclai contents embeddings <contentVersionId> [--page N] [--limit N]
Attachments
Files attached to a content item — email attachments, RSS enclosures from
feed_attachments, web page media, the images inside a document — each with
whether it is indexed and, when indexing failed, why.
seclai contents attachments list <contentVersionId> [--page N] [--limit N]
seclai contents attachments get <contentVersionId> <attachmentId> [--start N] [--end N]
seclai contents attachments embeddings <contentVersionId> <attachmentId> [--page N] [--limit N]
seclai contents attachments download <contentVersionId> <attachmentId> [--output <path>]
download answers 403 for a file withheld by a governance policy or still
being indexed; get reports file_withheld first.
Upload / Replace
seclai contents upload <contentVersionId> --file ./updated.pdf [--metadata '{}']
seclai contents replace <contentVersionId> --file ./updated.pdf # alias
seclai contents replace-text <contentVersionId> --json '{"title":"Note","text":"Updated content"}'
Knowledge Bases
seclai kb list [--page N] [--limit N] [--sort <field>] [--order asc|desc]
seclai kb create --json '{"name":"Support KB"}'
seclai kb get <kbId>
seclai kb update <kbId> --json '{"name":"Renamed"}'
seclai kb delete <kbId>
Memory Banks
seclai memory list [--page N] [--limit N]
seclai memory create --json '{"name":"Chat Memory","type":"conversation"}'
seclai memory get <memoryBankId>
seclai memory update <memoryBankId> --json '{"name":"Renamed"}'
seclai memory delete <memoryBankId>
seclai memory stats <memoryBankId>
seclai memory agents <memoryBankId>
seclai memory compact <memoryBankId>
seclai memory delete-source <memoryBankId>
seclai memory templates
seclai memory test-compaction <memoryBankId> [--json '...']
seclai memory test-compaction-standalone [--json '...']
Memory AI Assistant
seclai memory ai generate --user-input "Configure compaction"
seclai memory ai last
seclai memory ai accept <conversationId> --json '{"accepted":true}'
Evaluations
Criteria
seclai evals criteria list <agentId> [--page N] [--limit N] [--paged]
seclai evals criteria create <agentId> --json '{"name":"Quality"}'
seclai evals criteria get <criteriaId>
seclai evals criteria update <criteriaId> --json '{"name":"Renamed"}'
seclai evals criteria delete <criteriaId>
seclai evals criteria summary <criteriaId>
--paged wraps the results in {"data": [...]} instead of returning a bare
array, so .data is a stable path to read whatever --api-version is in
effect. The same flag is on alerts configs list, models alerts list and
agents runs eval-results, whose top-level keys (configs, alerts) also
become data from 2026-07-27. Nothing is invented: the pagination block appears only once the API
sends one, from --api-version 2026-07-27. Migrate scripts to .data first,
then opt in to get .pagination.
Results
seclai evals results list <criteriaId> [--page N] [--limit N]
seclai evals results create <criteriaId> --json '{"run_id":"...","score":0.9}'
Agent-level
seclai evals compatible-runs <criteriaId> [--page N] [--limit N]
seclai evals test-draft <agentId> --json '{"criteria":{...}}'
seclai evals agent-results <agentId> [--page N] [--limit N]
seclai evals agent-runs <agentId> [--page N] [--limit N]
seclai evals non-manual-summary <agentId>
Solutions
seclai solutions list [--page N] [--limit N]
seclai solutions create --json '{"name":"My Solution"}'
seclai solutions get <solutionId>
seclai solutions update <solutionId> --json '{"name":"Renamed"}'
seclai solutions delete <solutionId>
Link / Unlink Resources
seclai solutions link <solutionId> --agents '["id1","id2"]' --kb '["id3"]' --sources '["id4"]'
seclai solutions unlink <solutionId> --agents '["id1"]'
Conversations
seclai solutions convos list <solutionId>
seclai solutions convos add <solutionId> --json '{"user_input":"Add a source"}'
seclai solutions convos mark <solutionId> <conversationId> --json '{"accepted":true}'
Solution AI Assistant
seclai solutions ai generate <solutionId> --user-input "Add a FAQ source"
seclai solutions ai kb <solutionId> --user-input "Create a knowledge base"
seclai solutions ai source <solutionId> --user-input "Create a file source"
seclai solutions ai accept <solutionId> <conversationId>
seclai solutions ai decline <solutionId> <conversationId>
Governance
seclai governance ai generate --user-input "Create a content safety policy"
seclai governance ai list
seclai governance ai accept <conversationId>
seclai governance ai decline <conversationId>
Alerts
seclai alerts list [--page N] [--limit N] [--status open]
seclai alerts get <alertId>
seclai alerts status <alertId> --json '{"status":"resolved"}'
seclai alerts comment <alertId> --json '{"comment":"Investigating"}'
seclai alerts subscribe <alertId>
seclai alerts unsubscribe <alertId>
There is no --severity: GET /alerts declares no such filter, so it never
filtered — it returned unfiltered rows that looked filtered. Filter client-side:
seclai alerts list | jq '[.data[] | select(.severity == "high")]'.
Alert Configurations
seclai alerts configs list [--page N] [--limit N]
seclai alerts configs create --json '{"name":"Critical Alerts","type":"email"}'
seclai alerts configs get <configId>
seclai alerts configs update <configId> --json '{"name":"Renamed"}'
seclai alerts configs delete <configId>
Organization Alert Preferences
seclai alerts prefs list
seclai alerts prefs update <organizationId> <alertType> --json '{"enabled":true}'
Models
seclai models list [--provider <name>] [--supports-tool-use] [--supports-thinking]
seclai models list --supports-input-media image --supports-output-media video
seclai models get <modelId>
# Each media-generation modality and tier, with its model and cost
seclai models tiers
# Embedding models a source can index with, and rerankers a knowledge base can use
seclai models embedders [--supports-input-media text|image|video|audio] [--paged]
seclai models rerankers [--paged]
embedders and rerankers print the list under models, with the defaults and
pricing beside it, on every API version. --paged prints it under data
instead, plus the pagination block once the API sends one.
Model Alerts
seclai models alerts list [--page N] [--limit N]
seclai models alerts mark-read <alertId>
seclai models alerts mark-all-read
seclai models alerts unread-count
Recommendations
seclai models recommendations <modelId>
Model Playground Experiments
seclai models experiments list [--days N] [--start-date <date>] [--end-date <date>] [--limit N] [--offset N]
seclai models experiments create --json '{"model_ids":["gpt-4o"],"prompt":"Compare responses"}'
seclai models experiments get <experimentId>
seclai models experiments cancel <experimentId>
seclai models experiments delete <experimentId> # soft-delete, preserves audit history
Search
seclai search --query "deployment guide" [--limit N] [--entity-type agent|source|kb]
# Search the Seclai documentation
seclai docs search --query "memory banks" [--mode keyword|semantic] [--limit N]
Account
# The authenticated user's account ID and organization memberships
seclai me
Agent email: the domains agents send from, the inbound blocklist, inbound health, and recipient opt-outs.
Domains
seclai email domains list
seclai email domains add --kind custom --value mail.example.com [--delegated]
seclai email domains verify <domainId> # check DNS now
seclai email domains set-primary <domainId>
seclai email domains test-email <domainId> # send a test to the account owner
seclai email domains dmarc <domainId> [--days N] [--top-sources N]
seclai email domains remove <domainId>
seclai email domains use-shared # revert to agent.seclai.com
add returns the DNS records to publish. Use --delegated when the domain's
DNS is delegated to Seclai, so those records are published for you.
Blocked Senders
seclai email blocked list [--limit N] [--offset N]
seclai email blocked add --sender-email spam@example.com [--note "phishing"]
seclai email blocked add --sender-email example.com --match-type domain
seclai email blocked remove <blockedId>
seclai email blocked auto-block-mode disabled|input|input_and_output
Inbound Health
seclai email inbound status # quota, pause state, queued runs
seclai email inbound rejections [--agent-id <id>] [--limit N]
seclai email inbound cancel-queued # fail all over-quota parked runs
seclai email inbound resume # lift the account-wide pause
Opt-outs
seclai email optouts list [--agent-id <id>] [--limit N] [--offset N]
seclai email optouts remove <optoutId>
Cloud Drives
The cloud-drive connections that file triggers, drive steps and cloud_drive
sources read from. Connecting a drive happens in the app; these commands inspect
and maintain the connections that exist.
seclai cloud-drives providers # providers that can be connected
seclai cloud-drives list
seclai cloud-drives get <connectionId>
seclai cloud-drives update <connectionId> [--name <name>] [--folder-path <path>]
seclai cloud-drives update <connectionId> --whole-drive
seclai cloud-drives agents <connectionId> # agents using the connection
seclai cloud-drives rejections <connectionId> [--limit N] # skipped files, newest first
seclai cloud-drives disconnect <connectionId> # revoke tokens, keep the connection
seclai cloud-drives delete <connectionId>
The listings print a plain array on every API version.
update changes only what you pass. Changing the folder resets the sync cursor,
so files already in the new folder do not fire triggers. An empty
--folder-path is refused, because the API reads it as the whole drive — say
--whole-drive when that is what you mean.
delete is refused with a 409 while an agent trigger or a content source still
depends on the connection. agents does not list content sources, so an empty
result does not mean the delete will go through.
rejections is where to look when an agent did not run for a file: a skipped
file fires no trigger. --limit takes 1 to 200 and defaults to 50.
AI Assistant
Top-level AI assistant for multi-domain operations.
seclai ai feedback --json '{"conversation_id":"...","feedback":"helpful"}'
seclai ai kb --user-input "Create a support knowledge base"
seclai ai source --user-input "Create a documentation source"
seclai ai solution --user-input "Build a customer support solution"
seclai ai memory --user-input "Create a conversation memory bank"
seclai ai memory-history
seclai ai accept <conversationId> [--json '...']
seclai ai decline <conversationId>
seclai ai memory-accept <conversationId> [--json '...']
Skills
Install Seclai skill files for AI coding tools (Copilot, Claude Code, Cursor, Windsurf, Codex, Kiro, Cline, Roo Code, Gemini, Antigravity).
# Auto-detect tools from workspace directory structure
seclai skills install
# Target a specific tool
seclai skills install --tool copilot
seclai skills install --tool claude
seclai skills install --tool cursor
seclai skills install --tool kiro
seclai skills install --tool cline
# Install for all supported tools
seclai skills install --tool all
# Specify a custom directory
seclai skills install --tool copilot --dir /path/to/project
# Via npx (no install required)
npx @seclai/cli skills install
Skills follow the Agent Skills specification. Each tool gets a seclai-cli/ directory containing a SKILL.md with YAML frontmatter and a references/ subdirectory for progressive disclosure:
| Tool | Directory |
|---|---|
| Copilot | .github/copilot/seclai-cli/ |
| Claude Code | .claude/skills/seclai-cli/ |
| Cursor | .cursor/skills/seclai-cli/ |
| Windsurf | .windsurf/skills/seclai-cli/ |
| Codex | .codex/skills/seclai-cli/ |
| Kiro | .kiro/steering/seclai-cli/ |
| Cline | .clinerules/seclai-cli/ |
| Roo Code | .roo/rules/seclai-cli/ |
| Gemini | .gemini/seclai-cli/ |
| Antigravity | .antigravity/seclai-cli/ |
You can also install skills using the skills CLI:
npx skills add seclai/seclai-cli
MCP Server
Configure the Seclai MCP server for AI coding tools:
# Auto-detect tools and write MCP config
seclai mcp configure --key YOUR_API_KEY
# Target a specific tool
seclai mcp configure --key YOUR_API_KEY --target claude-code
seclai mcp configure --key YOUR_API_KEY --target cursor
seclai mcp configure --key YOUR_API_KEY --target claude-desktop
seclai mcp configure --key YOUR_API_KEY --target windsurf
# Configure all known targets
seclai mcp configure --key YOUR_API_KEY --target all
# Show the MCP config snippet (for manual setup)
seclai mcp show
seclai mcp show --key YOUR_API_KEY
| Target | Config File | Scope |
|---|---|---|
| claude-code | .mcp.json | Project |
| cursor | .cursor/mcp.json | Project |
| claude-desktop | ~/Library/Application Support/Claude/claude_desktop_config.json | Global |
| windsurf | ~/.codeium/windsurf/mcp_config.json | Global |
The command merges into existing config files — it won't overwrite other MCP servers.
Shell Completion
Generate shell completion scripts for tab-completion of commands:
# Bash — add to ~/.bashrc
eval "$(seclai completion bash)"
# Zsh — add to ~/.zshrc
eval "$(seclai completion zsh)"
# Fish — save to completions directory
seclai completion fish > ~/.config/fish/completions/seclai.fish
Development
Install dependencies
npm install
Type checking
npm run typecheck
Build
npm run build
Run locally
npm run dev -- --help
Test
npm test
Test global install locally
npm run build
npm link
seclai --help