Laki Agent Docs
Command reference
Every Laki Agent capability, grouped by family, with its interface and safety class.
The complete surface of Laki Agent, grouped by family. Safety classes: *read-only* and *free* run without approval; *gated* requires human approval; *auth* requires an approved Laki Bits session; *na* marks internal events.
Laki Bits Auth (app gate)
Loopback Firebase sign-in that gates the entire app. Every agent run and terminal open re-checks for an approved Laki Bits session as defense-in-depth; the stored token is re-verified against laki-api on each status check to catch expiry/revocation. Implemented as ipcMain.handle channels in ipc.js, exposed on window.laki.auth in preload.js.
| Command | Interface | Safety | What it does |
|---|---|---|---|
auth:status | ipc | read-only | Reports whether the user is signed in with an approved Laki Bits account. Re-verifies the stored token against laki-api on every call, so it catches expiry, revocation, or a not-yet-approved account and reports the reason. |
auth:login | ipc | auth | Starts the loopback Firebase sign-in flow (opens a browser). Enforces account approval and throws otherwise. On success, broadcasts auth:changed to the UI. |
auth:logout | ipc | auth | Clears the stored Laki Bits session and broadcasts the signed-out state to the UI. |
auth:changed | internal | event | Push event the main process sends to the renderer whenever sign-in state changes, so the UI can gate/ungate the app live. |
Embedded Browser Control
Drive-the-browser wrapper over the embedded Chromium WebContentsView tabs (browser.js + tabs.js), plus multi-tab lifecycle (new/close/activate/list), layout/region control, and a 'sign out of Salesforce' data wipe. Navigation, back/forward/reload/stop, and view geometry. All tabs share one persistent Salesforce partition (a new tab into a signed-in org is already logged in); exactly one tab is visible at a time. Distinct from the agent's read-only 'browser sight' tools (CDP, see agent tools). URL input is normalized (bare domains, full URLs, or Google search fallback).
| Command | Interface | Safety | What it does |
|---|---|---|---|
browser:navigate | ipc | free | Loads a URL in the embedded Chromium view. Accepts a bare domain, a full URL, or plain search text (which becomes a Google search). |
browser:back | ipc | free | Goes back one entry in the embedded browser's navigation history, if possible. |
browser:forward | ipc | free | Goes forward one entry in the embedded browser's navigation history, if possible. |
browser:reload | ipc | free | Reloads the current page in the embedded browser. |
browser:stop | ipc | free | Stops the in-flight page load in the embedded browser. |
browser:setRegion | ipc | free | Tells the main process the pixel rectangle where the native browser view should sit, so the surrounding chrome (address bar + sidebar) frames it correctly. |
browser:setHidden | ipc | free | Hides the native browser view while an HTML modal is open (else the modal renders behind it), and shows it again on close. |
browser:clearData | ipc | gated | Wipes the persistent 'salesforce' partition's cookies and storage, effectively signing the user out of all Salesforce sessions in the embedded browser. |
tabs:new | ipc | free | Creates a new embedded browser tab and makes it active. Optional url (defaults to the Salesforce login home). All tabs ride the shared 'persist:salesforce' partition, so a new tab into an already-signed-in org is already logged in. |
tabs:close | ipc | free | Closes a tab by id, detaching its CDP first and activating a neighbor if it was active. Refuses to close the last remaining tab. |
tabs:activate | ipc | free | Makes the given tab the active (visible) one, lazily loading its page on first activation-while-signed-in and syncing the address bar to it. |
tabs:list | ipc | read-only | Returns the current tab strip ({tabs:[{tabId,title,url,favicon,active,loading}], activeTabId}) for initial paint before the renderer subscribes to tabs:state. |
tabs:state | internal | event | Push event carrying the full tab list + activeTabId, sent on any create/close/activate/title/favicon/loading change so the strip stays in sync. |
browser:state | internal | event | Push event carrying the live {tabId,url,title,canGoBack,canGoForward,loading} so the address bar stays in sync with the embedded browser. Tagged with tabId — the renderer applies it only when it matches the active tab. |
browser:download | internal | event | Push event reporting a download's name, state (completed/cancelled/…), and path. |
Browser Sight (agent tools, CDP)
The agent's read-only 'line of sight' into the embedded page(s), delivered as agent-loop tool names (agent.js) backed by CDP instrumentation (cdp.js). These run FREELY with no approval, like the Salesforce read tools. CDP attaches LAZILY on first read (never during SSO/MFA login, to avoid tripping bot-detection). Multi-tab: reads target the agent's tab (the active tab by default; open_tab/switch_tab re-pin it); read_page/list_network accept an optional tabId. The reads are also exposed to the renderer as a manual debug panel via inspect:* channels.
| Command | Interface | Safety | What it does |
|---|---|---|---|
read_page | agent-tool | read-only | Returns a model-sized snapshot of the rendered DOM the user is looking at: url, title, visible body text (clipped), headings, links, and buttons. Reflects post-Lightning-hydration render, not raw HTML. |
list_network | agent-tool | read-only | Returns the recent network requests the page made (method, url, type, status, mimeType) from a rolling 250-entry per-tab buffer. Lets the agent reason about a SaaS app the way a developer with DevTools open would. |
get_url | agent-tool | read-only | Returns the current page URL of the agent's target tab. |
navigate | agent-tool | free | Loads a URL in the agent's target tab from within the agent loop. Runs freely (not a write proposal). |
list_tabs | agent-tool | read-only | Returns the open browser tabs [{tabId, title, url, active}] so the agent can see and target multiple orgs/areas at once. |
open_tab | agent-tool | free | Opens a NEW browser tab at a url and makes it the agent's target (and the visible tab), so the agent can work in a second org/area without disturbing the user's current tab. Runs freely (navigation, not a write). |
switch_tab | agent-tool | free | Makes an existing tab the agent's target (and the visible one) for subsequent reads. Runs freely. |
inspect:readDom | ipc | read-only | Manual, renderer-facing version of read_page for a debug/inspection panel: the same CDP DOM snapshot, invoked directly by the UI rather than the agent. |
inspect:listNetwork | ipc | read-only | Manual, renderer-facing version of list_network for a debug/inspection panel: the recent CDP network log, invoked directly by the UI. |
Model Providers & BYO Keys
Provider management for the agent's LLM: BYO Anthropic/OpenAI keys (Keychain-backed via secrets.js) plus the NEW keyless 'claude-cli' provider that piggybacks on the user's already-authenticated Claude Code CLI (subscription login, no API key stored). The provider config channels (settings:*) are exposed on window.laki.settings; the active provider drives agent.js's complete(). claude-cli scrubs billing/auth env vars and disables all built-in CLI tools so the child is a pure text turn.
| Command | Interface | Safety | What it does |
|---|---|---|---|
settings:getProviders | ipc | read-only | Returns the list of configured model providers and which one is active (never returns raw API keys, which live in the OS keychain). |
settings:saveProvider | ipc | auth | Saves or adds a model provider config (provider type, model, base URL, optional API key). API keys are stored in the OS keychain, not in plain config. |
settings:setActive | ipc | free | Marks one saved provider as the active model the agent will use for its next run. |
settings:deleteProvider | ipc | gated | Removes a saved provider config (and its stored key) from the app. |
settings:checkClaudeCli | ipc | read-only | Availability probe for the keyless 'Claude CLI' provider: resolves the claude binary through a login shell and validates --version. Reports whether Claude Code is installed and signed in, with its version or an error. No key required — it reuses the user's Claude Code subscription login. |
settings:checkConnections | ipc | read-only | The 3-way 'is everything actually connected?' check: is the active MODEL reachable (Claude CLI login or a BYO key), is the SALESFORCE CLI authenticated to any org, and does the org the browser is CURRENTLY showing match one of the sf-authenticated orgs. Uses the browser's real current URL, never a renderer-supplied value. |
claude-cli-complete | internal | event | The keyless provider's completion backend: shells out to `claude -p --output-format json` over stdin, honoring the user's Claude Code subscription. Scrubs ANTHROPIC_API_KEY/AUTH_TOKEN/BASE_URL/MODEL and all CLAUDE_CODE_* env vars to prevent billing/config diversion, runs with --tools "" (all built-in tools disabled) in an empty scratch dir, and checks both the exit code AND the JSON is_error flag. |
Salesforce READ Tools (agent, free)
Read-only Salesforce CLI wrappers (sfcli.js READ_TOOLS) the agent may call FREELY inside the ReAct loop — no approval. Each shells out to the user's own sf ... --json, inheriting the user's org auth via --target-org (tokens are never handled). The org alias is INJECTED by the loop from the model's args (alias/org/targetOrg), never trusted blindly. These are advertised to the model via readCatalog() in the system prompt.
| Command | Interface | Safety | What it does |
|---|---|---|---|
list_orgs | agent-tool | read-only | Lists the orgs the local sf CLI is authenticated to (alias, username, edition, isDevHub, instanceUrl), de-duped across sf's org buckets. Needs no org alias. |
org_info | agent-tool | read-only | Returns identity and instance info for the target org (username, id/orgId, instanceUrl, apiVersion, status, edition). Deliberately omits the access token. |
list_sobjects | agent-tool | read-only | Lists SObjects rolled up by namespace — managed_namespaces reveal installed managed packages (e.g. SBQQ = Salesforce CPQ), plus a total and a small name sample. The best way to check whether a package is installed. |
describe_sobject | agent-tool | read-only | Describes one SObject's fields (name, type, label, required, refersTo) and child relationships, trimmed to fit model context. Used to discover real API names before querying or proposing a write. |
soql_query | agent-tool | read-only | Runs a read-only SOQL query (the raw LakiQL capability) and returns totalSize plus cleaned records (Salesforce 'attributes' noise stripped, capped at 200 rows). |
Salesforce WRITE Tools (human-approved gate)
The write path (sfcli.js WRITE_TOOLS + agent.js propose flow). The model can NEVER write directly — it may only PROPOSE a write via the 'propose' JSON form. sfcli.planWrite() builds a WritePlan (summary, targetOrg, targetEdition, exact command, materialized files, warnings); the agent surfaces it via the approval:request round-trip; only after a human clicks approve does applyPlan() materialize files into a throwaway sfdx project and deploy. On rejection the model is told not to re-propose. targetEdition is read so a human can refuse a live/prod org.
| Command | Interface | Safety | What it does |
|---|---|---|---|
create_custom_field | write-tool | gated | Proposes creating ONE custom field on an object by deploying a CustomField. The plan shows the exact field (object.API name, type, label), the target org and edition, and warnings (e.g. new fields have no field-level security until granted via a permission set). Nothing runs until a human approves. |
approval:request | internal | event | Push event carrying the WritePlan (or gated-command plan) to the renderer so a human can review the exact change and approve or reject it. This is the human-in-the-loop gate. |
approval:respond | ipc | gated | The human's response to a pending write/command approval. Resolves the pending gate promise with true (approve) or false (reject); on false the corresponding write/command never runs. |
run_command (agent CLI, free vs gated)
A general CLI tool for the agent (sfcli.js run_command helpers + commands.js classifier). The model passes argv (preferred) or a command string; commands.toArgv() normalizes and rejects shell metacharacters. commands.classify() is the deny-by-default security boundary: only a positively allow-listed {bin, subcommand} with no denied flag runs 'free' (immediately); everything else is 'gated' and routed through the SAME human approval gate as writes. Uses spawn(shell:false) + argv array (no shell-metachar injection) + a per-bin denied-flag scan (argument-injection defense) + wall-clock timeout + per-stream output cap.
| Command | Interface | Safety | What it does |
|---|---|---|---|
run_command:free | agent-tool | free | Runs a single read-only CLI command immediately, with no approval. Only positively allow-listed programs+subcommands qualify: sf reads (org list/display, sobject list/describe, data query, apex log read, limits, deploy report), sfdx legacy reads, git reads (status/log/diff/show/branch/remote/rev-parse/describe/ls-files/shortlog/tag/config --get/--list), npm reads (ls/view/outdated/why/ping/config get), and pwd/ls/which/echo/whoami/date/node --version/python(3) --version. Top-level --version/--help are always free. |
run_command:gated | agent-tool | gated | Any command not on the read-only allowlist (deploys, DML, data import, git push/commit, npm install, file writes, unknown programs, a denied flag like git -c/--exec-path, or any chaining/redirect) is turned into a WritePlan and shown to a human for approval FIRST — the exact argv, working directory, target org, reason, and warnings are displayed. Nothing runs until approved; a rejected command must not be re-proposed. |
Terminal Tab (human's own shell)
A persistent user shell the HUMAN drives directly (terminal.js), streamed to the renderer via term:data. This is the human's channel (same trust as opening Terminal.app) — distinct from the model's gated run_command. Pure-Node child_process pipe (no PTY, no native dep), so full-screen curses apps won't render but sf/git/npm/line output work. open()/input() are gated behind an approved Laki Bits session (same check as agent:send). Runs the login shell with TERM=dumb, PAGER/GIT_PAGER=cat.
| Command | Interface | Safety | What it does |
|---|---|---|---|
term:open | ipc | auth | Opens (or reuses) a persistent login-shell session and starts streaming its merged stdout+stderr to the UI. Returns the working directory and shell. Requires an approved Laki Bits session. |
term:input | ipc | auth | Writes the user's keystrokes/command text to the shell's stdin. Requires an approved session. |
term:interrupt | ipc | auth | Sends SIGINT to the running shell child — best-effort Ctrl-C without a PTY (signals the child, not a full process group). |
term:close | ipc | auth | Kills the shell session (SIGKILL) and tears down its output stream. |
term:data | internal | event | Push event streaming merged stdout+stderr chunks from the shell to the UI as they arrive, plus exit/error markers. |
Agent Run Lifecycle
The top-level agent run channels (ipc.js) that drive the ReAct loop in agent.js. agent:send is the entry point (re-checks for an approved session as defense-in-depth, enforces a single concurrent run, and best-effort mirrors the run to laki-api for read-only web replay); agent:cancel aborts via AbortController; agent:event streams the live trace. The loop itself dispatches the browser-sight, Salesforce-read, run_command, and write-proposal tools cataloged in the other families.
| Command | Interface | Safety | What it does |
|---|---|---|---|
agent:send | ipc | auth | Runs the agent's ReAct loop on a natural-language message: model -> tool calls (browser sight, SF reads, run_command) -> write proposals gated by human approval -> a final markdown answer, streamed live. Refuses without an approved Laki Bits session and refuses if a run is already in progress. |
agent:cancel | ipc | free | Aborts the in-flight agent run via its AbortController. |
agent:event | internal | event | Push event streaming the live run trace to the UI: assistant thoughts, tool calls with observations, approval requests, the final answer, and errors. |