From ctxl
Use the local Contextual CLI from shell-capable agents to inspect tenants, analyze flows, and apply safe edits. Use only in runtimes with local shell access.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ctxl:solai-cli [task][task]The summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill has two layers:
This skill has two layers:
ctxl commands against a live tenant. Requires local shell access (Claude Code, Claude Cowork, OpenCode, Codex, or similar).If shell access is unavailable, make clear that commands cannot be executed — but continue to use this skill's reference material to answer questions, explain concepts, or help the user plan what they would run.
On first invocation each session, check for plugin updates. Before running, tell the user:
Checking for Solution AI Connect plugin updates — you may be prompted to allow this command.
Then run:
claude plugin update ctxl@contextual-io
The raw command output uses ctxl as the marketplace plugin identifier, but ctxl is also the name of the separately-versioned Contextual CLI. To avoid confusion, always restate the result in "Solution AI Connect plugin" terms rather than letting the raw output stand:
Only run this check once per session.
The Contextual CLI (ctxl) must be installed globally before using this skill. Requires Node.js 18.0.0 or later.
Install via npm (or your preferred Node package manager — pnpm, yarn, bun, etc.):
npm install -g @contextual-io/cli
If the user has a preferred package manager or install method, defer to their choice. Visit npm/@contextual-io/cli for the latest version and release notes.
command -v ctxl && ctxl --version.ctxl is missing, tell the user it must already be installed locally before this skill can run.ctxl commands from memory.Two helper scripts are bundled with this skill. When invoking them, tell the user what is happening before running so the path doesn't appear alarming:
contextual_login.py — orchestrates browser-based login for a config. When invoking, tell the user: "Starting browser login for <config-id> — a verification code will appear shortly for you to confirm in your browser."
${CLAUDE_SKILL_DIR}/scripts/contextual_login.py --state-dir "${CLAUDE_PLUGIN_DATA:-${CLAUDE_SKILL_DIR}/.local}/login-jobs"
json_diff.py — previews the difference between the current and proposed version of a flow or record before any write is made. When invoking, tell the user: "Generating a diff so you can review what will change before anything is written."
${CLAUDE_SKILL_DIR}/scripts/json_diff.py <current-file> <proposed-file>
ctxl config or credential files directly — always use ctxl CLI commands to interact with configs and credentials.curl, fetch, custom headers, or handwritten HTTP requests.api-configuration) carry credential values (tokens, passwords, connection strings, API keys), and Agent records carry environment-variable values — treat all of these as secrets. Do not infer how or where a credential is stored from a record's shape or metadata — an empty _metaData.secrets array does not mean a credential is stored inline or is less protected; all credential fields are platform-managed secrets. Never raise a "secret-hygiene" concern or a storage judgement from record metadata. When you need information about one, retrieve only the non-secret fields you need with a projected list query (ctxl records list --type <type> --fields <…>) and reason about it by those fields (a Connection's id / name / provider / endpoint; an Agent's env-var labels) — so secret values never enter the session, where they're hard to scrub from a transcript. Never read a secret back to "verify" a Connection or AI Route — confirm it works by exercising the behavior, not by inspecting the value. And never route a secret through the chat: don't ask the user to paste or type a key, token, password, connection string, or env-var value into the conversation, and if they start to, stop them and decline — secrets belong only in the platform's credential / env-var fields, entered directly in the dashboard UI.solai-flow-editor/node-reference.md → "AI model selection (AI Routes)".ctxl config delete, ctxl records delete/remove/rm, or ctxl types delete/remove/rm.replace or patch operation — records replace, records patch, types replace, services patch — show a diff or the exact planned patch flags and ask for explicit confirmation. For services patch specifically, project the dependency-list change against the current services get output before asking. For pre-publish Build-composition advice on a service you own, use the solai-release-advisor skill.ctxl logs backlog flush is irreversible and tenant-wide. It DELETEs the tenant's log backlog, dropping the unconsumed lag — it is not per-user. Before invoking, show the user the current lag (ctxl logs backlog) and require explicit confirmation. Do not flush as part of a broader sequence — it must be its own confirmed step.ctxl logs is a passthrough for content tenant flows emitted. The message field is serialized by the emitting node (log-tap and similar) without platform-side redaction — so HTTP-handling flows that log request/response objects naturally surface Authorization headers, bodies, and cookies. The default consumption pattern in this skill is envelope-only projection before any logs output reaches the agent's context. ctxl logs (including --follow and --tail) emits line-formatted text, not JSON — project with sed -E 's/ (debug|info|warn|error|fatal): .*/ \1/' to keep the envelope and drop message; do not pipe ctxl logs into jq (it yields nothing and reads as a false "no logs"). (ctxl logs backlog is not a payload surface — it returns a tenant lag summary, counts only, no message content, so nothing to redact there.) Expand message only when the user explicitly asks for payload content, and pair the expansion with JWT redaction. See cli-reference.md → Safe consumption patterns. Applies on tenants the user owns.ctxl logs retrieves persistent Tenant Logs (running/deployed agents; CLQL-queryable). The Flow Editor's debug drawer is a separate, ephemeral surface, read via the flow-editor MCP logger_messages tool. If the user is working in the Flow Editor and says "check the logs/logger," they most likely mean the drawer — not ctxl logs. Default to the active context; CLQL is a Tenant-Logs tell; when context and cues conflict, ask. See cli-reference.md → Logs.--config-id <config-id> on tenant commands even if you already ran ctxl config use.cli-reference.md, node-reference.md, and this SKILL.md are kept current with empirically-verified build-time reality — canonical for CLI shapes, JSONL formats, flow record structure, node-level behavior, and sequencing rules. Use solai-knowledge for platform/runtime behavior not covered there. For genuinely cross-cutting queries (spanning both build-time mechanics and broader platform context), run both in parallel — the answers are complementary, not duplicative.grep, find, or otherwise enumerate the plugin install directory (~/.claude/plugins/...) to locate plugin reference content directly. That path is implementation detail and bypasses skill-level guidance (sequencing rules, validation patterns, hex-id pre-generation, etc.). To access plugin-side reference, invoke the relevant skill — it loads the reference content properly into context.ctxl mcp serve, verify the config is logged in. The server rejects expired or missing tokens at startup.Start with:
ctxl config list --json
ctxl config current --json
Config commands (config list, config current, config get) support --json for structured JSON output instead of the default table format. Records and types commands already output JSON by default.
If a config is missing and the user supplied a tenant identifier:
ctxl config add <config-id> --tenant-id <tenant-id>
Select the config before tenant work:
ctxl config use <config-id>
If a tenant command reports that the config is not logged in, unauthorized, expired, or otherwise needs auth:
Tell the user: "Your session isn't logged in or has expired — starting browser login for <config-id> now."
python3 "${CLAUDE_SKILL_DIR}/scripts/contextual_login.py" --state-dir "${CLAUDE_PLUGIN_DATA:-${CLAUDE_SKILL_DIR}/.local}/login-jobs" start <config-id>
python3 "${CLAUDE_SKILL_DIR}/scripts/contextual_login.py" --state-dir "${CLAUDE_PLUGIN_DATA:-${CLAUDE_SKILL_DIR}/.local}/login-jobs" await <job-id> --timeout-seconds 90
python3 "${CLAUDE_SKILL_DIR}/scripts/contextual_login.py" --state-dir "${CLAUDE_PLUGIN_DATA:-${CLAUDE_SKILL_DIR}/.local}/login-jobs" status <job-id>
Make sure the retried command includes --config-id <config-id>.
Do not ask the user to manually invoke another skill during normal CLI work.
Use cli-reference.md for exact command forms.
Type discovery follows two tracks:
ctxl types list to discover these. For normal callers, type listing is effectively limited to tenant-defined custom object types. Treat this reserved set as known IDs:
agentflowapi-configuration (known to users as "Connections")ai-routejwks-configurationauthorization-code-appctxl types list to discover these, then ctxl types get to inspect the chosen type.When inspecting a tenant, choose the track explicitly:
ctxl types list.ctxl types get native-object:<type-id> --config-id <config-id> to retrieve the full JSON schema — enums, patterns, constraints, defaults, and relations. This is the authoritative source for field shapes before any create or replace operation. Then use ctxl records ... --type <type-id> --config-id <config-id>.Common reads:
ctxl config list --json, ctxl config current --json, ctxl config get <config-id> --jsonctxl types list --config-id <config-id>, ctxl types get --type <type-id> --config-id <config-id>ctxl records list --type <type-id> --config-id <config-id>, ctxl records get --type <type-id> --id <id> --config-id <config-id>, ctxl records query native-object:<type-id> --query-file <file> --config-id <config-id>, ctxl records stats --type <type-id> --id <id> --config-id <config-id>Reserved admin component examples:
ctxl records list --type flow --config-id <config-id>
ctxl records list --type agent --config-id <config-id>
ctxl records list --type ai-route --config-id <config-id>
ctxl records list --type api-configuration --config-id <config-id>
ctxl records list --type jwks-configuration --config-id <config-id>
ctxl records list --type authorization-code-app --config-id <config-id>
When answering general questions about a tenant ("what's in this tenant?", "what does this tenant do?", "what solutions are built here?"), fetch all six reserved component types in parallel alongside ctxl types list for custom object types. Empty results are fine — they complete the picture. Do not skip any component type because it seems unlikely to have content.
Tenant-defined data object example:
ctxl types list --config-id <config-id>
ctxl types get --type <custom-type-id> --config-id <config-id>
ctxl records list --type <custom-type-id> --config-id <config-id>
For writes:
ctxl types add or ctxl records addctxl records patchctxl types replace or ctxl records replacectxl records query accepts a JSON file containing MongoDB-style query predicates. Fields are prefixed with $. and operators use the $ prefix.
Important: records query requires the native-object:<type-id> URI positional argument, not --type.
Pass a query inline via stdin (the default --query-file is -):
echo '{"$.status": "active"}' | ctxl records query native-object:<type-id> --config-id <config-id>
Or write the query to a file:
ctxl records query native-object:<type-id> --query-file query.json --config-id <config-id>
The query format follows MongoDB query predicates. Common operators:
{"$.field": "value"}$in: {"$.field": {"$in": ["val1", "val2"]}}$gt, $gte, $lt, $lte: {"$.balance": {"$gt": 100}}$and: {"$and": [{"$.field1": "a"}, {"$.field2": {"$lt": 0}}]}$or: {"$or": [{"$.status": "active"}, {"$.status": "pending"}]}$exists: {"$.field": {"$exists": true}}Query commands support the same pagination flags as records list (--page-size, --page-token, --include-total, --export, --progress).
For large result sets, use --page-size and --page-token to paginate:
ctxl records list --type <type-id> --page-size 50 --config-id <config-id>
To stream all records as JSONL, use --export (optionally with --progress for status output):
ctxl records list --type <type-id> --export --progress --config-id <config-id>
Maximum page size is 250. The CLI handles page-token chaining automatically during export.
The CLI has no built-in retry or rate-limit handling. If the platform returns a 429 or 5xx error, the command fails immediately. When running multiple commands in sequence, pace requests and avoid tight loops. If a command fails with a transient error, wait a few seconds before retrying manually.
Important write gotchas:
ctxl records add expects JSONL (one JSON object per line), not pretty-printed JSON.primaryKey on an Object Type is immutable once deployed — get it right before the first ctxl types add._metaData envelope from the platform automatically. Never include createdAt, updatedAt, hash, version, or secrets in a schema.Flows are records under --type flow.
Useful commands:
ctxl records list --type flow --config-id <config-id>
ctxl records get --type flow --id <flow-id> --config-id <config-id>
When summarizing a flow, inspect node_red_data.flows and report:
For creating new flows:
Always use the CLI-stub + flow editor handoff pattern. Never try to write complex node content (HTML, JavaScript, multi-line logic) through the CLI — shell escaping across Python/JSON/JS layers is error-prone and unreliable.
Step 1 — Clarify flow type before generating anything: Ask the user: "Is this an HTTP flow (serves requests), an event flow (triggered by object-type events or agents), or a scheduled flow (cron)?" The skeleton structure differs by type.
Step 2 — Create a minimal but complete skeleton via CLI:
Generate hex IDs for all nodes (see cli-reference.md). Build the skeleton with:
| Flow type | Skeleton |
|---|---|
| HTTP | http-in → function (stub) → http-response 200 + catch (uncaught) → log-tap (error/full) → http-response 500 |
| Event | contextual-start → function (stub) → contextual-end + catch (uncaught) → log-tap (error/full) → contextual-end |
| Scheduled | inject (cron) → function (stub) → contextual-end + catch (uncaught) → log-tap (error/full) → contextual-end |
Stub function node content: // TODO: implement\nreturn msg;
Always write the flow JSON to a file using a Python heredoc (<< 'PYEOF') — never inline complex content in a shell command. See cli-reference.md for the correct file-based approach.
Step 3 — Hand off to the flow editor:
After creating the flow, resolve the tenant ID first by running ctxl config current --json, then tell the user with the fully-resolved URL: "The flow skeleton is created with error handling in place — open it in your browser at https://<flow-id>.flow.<resolved-tenant-id>.my.contextual.io/.editor and I can build out the logic interactively through the Flow Editor, which is much cleaner for complex node content." Never hand the user a URL with <tenant-id> as a literal placeholder — fill it in.
For editing existing flows:
Preferred path: Flow Editor Session via MCP tunnel. Before editing a flow via CLI:
Check whether mcp__ctxl-flow-editor__* tools appear in the deferred tool list — if they do, the MCP server is running (Ctxl Tool or manual ctxl mcp serve).
If the tools are present, call list_sessions to check for active browser sessions. Tool availability alone does not mean a session exists — a session only exists when the user has the flow open in their browser.
Only if list_sessions returns one or more sessions, recommend the live editor path and offer to switch to solai-flow-editor. Reasons to prefer it:
Tell the user: "I can see a Flow Editor session is available — I can make these changes live in the editor so you can review them before saving. Would you like to do that, or continue via CLI?"
If no sessions are returned, proceed with the CLI path below. Optionally note that opening the flow in a browser would enable the live editing experience.
If the tools are not available at all, proceed directly with the CLI path and optionally note that opening the Ctxl Tool would enable live editing.
CLI path (when MCP tunnel is unavailable or user prefers it):
python3 "${CLAUDE_SKILL_DIR}/scripts/json_diff.py" <current-file> <proposed-file>
ctxl records replace --type flow --id <flow-id> --input-file <proposed-file> --config-id <config-id>
For record patches, show the exact ctxl records patch ... flags before confirmation.
flow-http, HTTP In paths are root-relative to the flow subdomain. Use /list, not /<flow-id>/list.msg.payload unless docs clearly say otherwise.log-tap must have outputs: 1 and a valid level.log-tap inline in the chain, not as a dead-end fork.flows_cred: {} must be inside node_red_data, not at the top level of the flow record.env: [] — preserve it when editing; omitting it may cause issues in the editor.ctxl mcp serve starts a local MCP HTTP server that bridges to live SolutionAI browser sessions. It binds to http://localhost:5051/ by default and exposes SolutionAI tools as standard MCP tools that AI agents can call.
For real-time flow interaction via AI agents, the MCP server is the preferred path over manual records get / records replace round-trips on flow records.
Use the MCP server when the user wants an AI agent (Claude, Cursor, etc.) to interact with SolutionAI flows in real time — editing nodes, reading flow state, or calling flow-scoped tools through the MCP protocol.
The active config must be logged in before starting. If auth is needed, follow the Automatic Auth Recovery flow first.
ctxl mcp serve --config-id <config-id>
With a specific flow pre-selected:
ctxl mcp serve --flow <flow-id> --config-id <config-id>
With tool name prefixing (adds ctxl_ prefix to all tool names):
ctxl mcp serve --tool-prefix --config-id <config-id>
With verbose diagnostics:
ctxl mcp serve -V --config-id <config-id>
-V is verbosity level 1 (runtime diagnostics); --trace raises it to level 2 (socket-level trace). Use --trace only when diagnosing a connection problem, not for normal operation.
Custom port:
ctxl mcp serve --port 8080 --config-id <config-id>
The server always exposes two meta-tools:
list_sessions — lists flows with available browser sessions. A browser must be open on the flow editor for a flow to appear. Returns flow IDs and names. Accepts an optional flowId filter.info — returns runtime state: tenant, interface type, connected flows, and any recent errors.All other tools are dynamically loaded from SolutionAI's tool manifest for the flow-editor interface (the default).
list_sessions is scoped to the current user and current tenant (from the active config). Other users' browser sessions never appear, even on a shared tenant.flowId. If --flow was passed at startup, that flow is used globally. Otherwise the agent must pass flowId with each call, or call list_sessions first to discover available flows.ctxl mcp serve yourself. The server must be started by the user in their own persistent terminal — any process the agent starts via shell is ephemeral and dies immediately. It cannot serve MCP tools.mcp__ctxl-flow-editor__* tools appear in the deferred tool list, the server is already running. Do not start another one. Load the tool schemas and call info/list_sessions to verify the connection. If the tools are not in the deferred list, tell the user to run ctxl mcp serve --config-id <config-id> in their own terminal.ctxl config use while the server is running has no effect. If the user needs to target a different tenant, the server must be stopped and restarted with the new config.ctxl mcp debug is a last-resort connectivity diagnostic — not the response to a missed dialog. When a tool call doesn't land, the overwhelmingly common cause is that the user simply didn't notice the "MCP requesting access" dialog in the Flow Editor's right sidebar. The fix is to re-trigger the call and ask them to watch for and accept it — that recovers the session without any diagnostic. Do not reach for ctxl mcp debug just because a dialog wasn't seen; that sends the user chasing a command when a second glance at the sidebar would have fixed it. Escalate to ctxl mcp debug [interface] --flow <flow-id> --trace (run by the user in their own terminal, like serve) only when the connection itself is suspect: no dialog appears across repeated attempts, calls keep failing after the user confirms they accepted, or the server reports socket errors. It prints a connect → ping → manifest → bind JSON report that isolates where the tunnel breaks.--pretty when running a command whose output the user is meant to read inline; omit --pretty when piping into jq, python3 -c, or programmatic consumers. See cli-reference.md → Output Formatting.Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Applies a firm's KYC/AML rules grid to parsed onboarding records: assigns risk rating, checks required documents, outputs rule outcomes with citations, and routes for escalation.
Generates daily or weekly digests of activity from connected sources (chat, email, docs, tasks, CRM), highlighting action items, decisions, mentions, and project updates.
npx claudepluginhub contextualio/solution-ai-connect --plugin ctxl