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.

CommandInterfaceSafetyWhat it does
auth:statusipcread-onlyReports 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:loginipcauthStarts the loopback Firebase sign-in flow (opens a browser). Enforces account approval and throws otherwise. On success, broadcasts auth:changed to the UI.
auth:logoutipcauthClears the stored Laki Bits session and broadcasts the signed-out state to the UI.
auth:changedinternaleventPush 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).

CommandInterfaceSafetyWhat it does
browser:navigateipcfreeLoads a URL in the embedded Chromium view. Accepts a bare domain, a full URL, or plain search text (which becomes a Google search).
browser:backipcfreeGoes back one entry in the embedded browser's navigation history, if possible.
browser:forwardipcfreeGoes forward one entry in the embedded browser's navigation history, if possible.
browser:reloadipcfreeReloads the current page in the embedded browser.
browser:stopipcfreeStops the in-flight page load in the embedded browser.
browser:setRegionipcfreeTells the main process the pixel rectangle where the native browser view should sit, so the surrounding chrome (address bar + sidebar) frames it correctly.
browser:setHiddenipcfreeHides the native browser view while an HTML modal is open (else the modal renders behind it), and shows it again on close.
browser:clearDataipcgatedWipes the persistent 'salesforce' partition's cookies and storage, effectively signing the user out of all Salesforce sessions in the embedded browser.
tabs:newipcfreeCreates 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:closeipcfreeCloses a tab by id, detaching its CDP first and activating a neighbor if it was active. Refuses to close the last remaining tab.
tabs:activateipcfreeMakes 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:listipcread-onlyReturns the current tab strip ({tabs:[{tabId,title,url,favicon,active,loading}], activeTabId}) for initial paint before the renderer subscribes to tabs:state.
tabs:stateinternaleventPush event carrying the full tab list + activeTabId, sent on any create/close/activate/title/favicon/loading change so the strip stays in sync.
browser:stateinternaleventPush 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:downloadinternaleventPush 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.

CommandInterfaceSafetyWhat it does
read_pageagent-toolread-onlyReturns 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_networkagent-toolread-onlyReturns 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_urlagent-toolread-onlyReturns the current page URL of the agent's target tab.
navigateagent-toolfreeLoads a URL in the agent's target tab from within the agent loop. Runs freely (not a write proposal).
list_tabsagent-toolread-onlyReturns the open browser tabs [{tabId, title, url, active}] so the agent can see and target multiple orgs/areas at once.
open_tabagent-toolfreeOpens 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_tabagent-toolfreeMakes an existing tab the agent's target (and the visible one) for subsequent reads. Runs freely.
inspect:readDomipcread-onlyManual, 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:listNetworkipcread-onlyManual, 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.

CommandInterfaceSafetyWhat it does
settings:getProvidersipcread-onlyReturns the list of configured model providers and which one is active (never returns raw API keys, which live in the OS keychain).
settings:saveProvideripcauthSaves 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:setActiveipcfreeMarks one saved provider as the active model the agent will use for its next run.
settings:deleteProvideripcgatedRemoves a saved provider config (and its stored key) from the app.
settings:checkClaudeCliipcread-onlyAvailability 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:checkConnectionsipcread-onlyThe 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-completeinternaleventThe 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.

CommandInterfaceSafetyWhat it does
list_orgsagent-toolread-onlyLists 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_infoagent-toolread-onlyReturns identity and instance info for the target org (username, id/orgId, instanceUrl, apiVersion, status, edition). Deliberately omits the access token.
list_sobjectsagent-toolread-onlyLists 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_sobjectagent-toolread-onlyDescribes 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_queryagent-toolread-onlyRuns 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.

CommandInterfaceSafetyWhat it does
create_custom_fieldwrite-toolgatedProposes 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:requestinternaleventPush 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:respondipcgatedThe 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.

CommandInterfaceSafetyWhat it does
run_command:freeagent-toolfreeRuns 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:gatedagent-toolgatedAny 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.

CommandInterfaceSafetyWhat it does
term:openipcauthOpens (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:inputipcauthWrites the user's keystrokes/command text to the shell's stdin. Requires an approved session.
term:interruptipcauthSends SIGINT to the running shell child — best-effort Ctrl-C without a PTY (signals the child, not a full process group).
term:closeipcauthKills the shell session (SIGKILL) and tears down its output stream.
term:datainternaleventPush 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. A run also emits two content-free product-usage events (agent_run_started {provider, has_active_org} and agent_run_completed {status, error_class, provider, duration_ms, tool_calls}) on a channel deliberately separate from the run-replay feed. duration_ms is quantized to a fixed ladder and tool_calls excludes workspace-document reads. They carry no prompt, no answer, no org identifier, no skill count, and no workspace-document count.

CommandInterfaceSafetyWhat it does
agent:sendipcauthRuns 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:cancelipcfreeAborts the in-flight agent run via its AbortController.
agent:eventinternaleventPush event streaming the live run trace to the UI: assistant thoughts, tool calls with observations, approval requests, the final answer, and errors.

Product usage events

A content-free product-analytics channel (main/usage.js), separate from the run-replay session feed. Main-process only: it sends a closed list of event names whose properties are enumerations, clamped integers, booleans and length-bounded version numbers — never prompts, answers, SOQL, org names, aliases, instance URLs, file paths, error text, or free text of any kind. It is not reachable from the agent's tool surface, it has no offline queue, and it stops entirely when you switch it off in Settings → Privacy.

CommandInterfaceSafetyWhat it does
usage:emitipcfreeThe single renderer-to-main channel for the one usage event the UI owns (layout_changed). Main re-validates the event name and every property against its own copy of the registry and stamps the whole envelope; the renderer cannot name any other event and cannot set the surface, app version, platform, timestamp, or event id.
usage:setEnabledipcfreePersists the per-user preference to userData/usagePrefs.json. When off, the emitter makes no network request at all — not even the feature probe. An unreadable preferences file is treated as OFF.
usage:getEnabledipcread-onlyReports whether product usage events are currently being sent, and whether the preferences file was readable.