JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 59
  • Score
    100M100P100Q81864F
  • License MIT

Self-hosted AI Coding Assistant CLI

Package Exports

  • @semalt-ai/code
  • @semalt-ai/code/internals
  • @semalt-ai/code/package.json

Readme

Semalt.AI Code CLI

@semalt-ai/code is a self-hosted AI coding assistant for the terminal.

It provides an interactive chat interface, one-shot code generation, AI-assisted file editing, shell command execution, and an agent loop that can read files, write files, and run commands after user approval.

Features

  • Interactive terminal chat mode
  • OpenAI-compatible API integration
  • Streaming responses with terminal-friendly formatting
  • Optional reasoning stream display when the backend provides it
  • User-approved shell, file read, and file write actions
  • File and directory context loading
  • One-shot code task mode
  • AI-assisted file editing mode
  • Model discovery from the configured API

Requirements

  • Node.js >=18 (Node 16 is end-of-life; node --test is unreliable on it)
  • An OpenAI-compatible API endpoint

The default configuration expects a local API server at http://127.0.0.1:8800.

The CLI accepts api_base in either of these forms:

  • http://127.0.0.1:8800
  • http://127.0.0.1:8800/v1

Both formats are normalized automatically.

Installation

Install the package globally so the semalt-code command is available system-wide.

npm install -g @semalt-ai/code

After global installation, run:

semalt-code

Initial Setup

Create the CLI config:

semalt-code init --api-base http://127.0.0.1:8800 --api-key any --dashboard-url https://cli.semalt.ai --default-model default

This writes configuration to:

~/.semalt-ai/config.json

Example config:

{
  "api_base": "http://127.0.0.1:8800",
  "api_key": "any",
  "dashboard_url": "https://cli.semalt.ai",
  "auth_token": "",
  "default_model": "default",
  "temperature": 0.7,
  "request_timeout_ms": 900000,
  "stream": true
}

You can also set "api_base" to a URL that already ends with /v1.

Usage

semalt-code [command] [options]

Commands

  • semalt-code

  • semalt-code chat Starts interactive chat mode.

  • semalt-code code <prompt> Runs a one-shot coding task.

  • semalt-code edit <file> <instruction> Sends a file to the model and overwrites it with the returned result.

  • semalt-code shell <command> Runs a shell command with approval prompts.

  • semalt-code login Starts browser-based login and stores the confirmed CLI token in ~/.semalt-ai/config.json.

  • semalt-code whoami Shows the current user associated with the saved CLI auth token.

  • semalt-code logout Logs out the current CLI user and clears the saved local auth token.

  • semalt-code models Lists your models from the dashboard and lets you choose the current one for the CLI.

  • semalt-code models add Opens an interactive flow to add an API base URL, API key, and model ID as a reusable model profile.

  • semalt-code init Creates or updates the local config file.

  • semalt-code mcp <list|status|add|remove|auth> Manage MCP servers. add registers a server (stdio command/args or remote --url), status connects and reports each server's tools, auth runs the OAuth flow for a remote server (tokens are stored in the OS keychain). Discovered tools register as mcp__<server>__<tool> and run through the same approval-gated agent loop as built-ins — MCP results are treated as untrusted external content, and MCP tools require approval by default (opt in per server with allow/allowAll). In chat, /mcp shows the same status.

Options

  • -m, --model <name> Override the model name.

  • -v, --version Print the current CLI version.

  • -f, --file <path> Load one or more files or directories into the prompt context for code.

  • -a, --analyze After shell, ask the model to summarize the command output.

  • --dry-run For edit, show the generated result without saving it.

  • --api-base <url> Set the API base URL during init.

  • --api-key <key> Set the API key during init.

  • --default-model <name> Set the default model during init.

Interactive Chat Mode

Running semalt-code without arguments starts the terminal chat UI.

Available interactive commands:

  • /help
  • /file <path>
  • /whoami
  • /logout
  • /model
  • /login
  • /model <name>
  • /models
  • /clear
  • /compact
  • /cost
  • /shell <cmd>
  • !<cmd>
  • /approve
  • /config
  • exit

What /file does

  • If you pass a file, its full content is added to the conversation context.
  • If you pass a directory, the CLI recursively loads up to 50 non-hidden files.

Agent Behavior

The assistant is instructed to use special tool-like tags:

  • <exec>...</exec> for shell commands
  • <read_file>...</read_file> for file reads
  • <write_file path="...">...</write_file> for file writes

When the model emits these actions, the CLI:

  1. Detects them in the response
  2. Prompts the user for approval
  3. Executes the action
  4. Sends the result back to the model
  5. Continues for up to 125 iterations

This makes the tool behave like a lightweight terminal agent while keeping the user in control.

Examples

Start chat

semalt-code

Ask for a coding task with file context

semalt-code code -f package.json -f index.js "Explain this project and suggest improvements"

Edit a file with AI

semalt-code edit index.js "Refactor duplicated logic into helper functions"

Preview an edit without saving

semalt-code edit index.js "Add better error handling" --dry-run

Run and analyze a shell command

semalt-code shell -a "npm test"

List models

semalt-code models

Add a saved model profile

semalt-code models add

The CLI will ask for:

  • API Base URL
  • API Key
  • Model ID

Each saved profile is appended to the profile list.

Saved profiles can then be selected inside chat mode with /model or /models.

Show the current version

semalt-code --version

Embedding SDK

@semalt-ai/code can be embedded in another program as a library, not just run as a CLI. There are two tiers, physically separated by the package exports map:

Import Surface Stability
require('@semalt-ai/code') createAgent — the high-level facade Stable (semver)
require('@semalt-ai/code/internals') createAgentRunner, createApiClient, the registries, … Unstable — no guarantee, may change in any release

Both subpaths work for require and import (the package is CommonJS; ESM consumers get the named exports via interop).

The facade

const { createAgent } = require('@semalt-ai/code');

const agent = createAgent({
  apiBase: 'http://127.0.0.1:8800',
  apiKey:  process.env.SEMALT_API_KEY,
  model:   'my-model',
  // permission policy — see below
});

const res = await agent.run('Summarise README.md in three bullets');
// res = { result, toolCalls, usage, cost, stopReason, verifyStatus, messages }
console.log(res.result);

await agent.close(); // REQUIRED — releases MCP connections / processes

run(prompt, opts?) executes a prompt to completion and returns the same structured envelope headless mode produces, plus messages so you can continue the conversation (agent.run(next, { messages: res.messages })). Stream events with agent.on('token' | 'assistant' | 'tool' | 'tool-start' | 'error' | 'warning' | 'done', cb).

Permission policy — safe by default

There is no terminal in embedded use, so the facade takes a programmatic permission policy. With neither a policy provided, the default is to refuse every mutating / effectful tool (read-only tools still run) — it never auto-approves:

// 1) an async approver callback
createAgent({ /* … */, approve: async (call) => {
  // call = { actionType, description, tag, rule }
  return call.tag !== 'delete_file'; // your decision
}});

// 2) preset allow/deny/ask rules (the same engine as the CLI's per-pattern rules)
createAgent({ /* … */, rules: [
  { tool: 'write_file', path: 'src/**', action: 'allow' },
  { tool: 'shell',      pattern: 'git *', action: 'allow' },
  { tool: 'shell',      pattern: '/curl.*\\| *sh/', action: 'deny' },
]});

// 3) coarse tiers (like --allow-fs/exec/net) and read-only
createAgent({ /* … */, allow: ['fs', 'net'], readonly: true });

The OS sandbox and the destructive-command deny-list stay on regardless of there being no TTY. The sandbox is opt-out only via explicit config (sandbox: { mode: 'off' }); a host can permit running a command unsandboxed when the kernel primitive is missing by supplying onUnsandboxed. A deny rule and the deny-list are honored even under the deliberate dangerouslySkipPermissions: true gate opt-out.

By default the SDK does not read your ~/.semalt-ai/config.json (a server wants isolation, not the operator's personal defaults) — pass loadUserConfig: true to layer it in. Pass arbitrary config under config: { … }.

Lifecycle & multi-instance

  • Always call await agent.close() when done. It disconnects MCP servers and frees handles. run() after close() throws.

  • Each createAgent instance keeps its own config — two instances never share config state.

  • Process-global state (documented limitation). A few things are process-wide, not per-instance, because they were built for the single-process CLI:

    • the dynamic tool registry (MCP + spawn_agent) is global — two instances with different MCP servers would see each other's tools;
    • file-path confinement (isPathSafe) and the deny-list/secret/config guards read process.cwd() and process.argv once at module load, so they're shared by all instances and the deny-list opt-out requires launching the host process with --dangerously-skip-permissions;
    • the stdout-chrome-suppression flag is process-wide.

    For most embeddings (one agent per process, or instances sharing a CWD and MCP set) none of this matters; run fully-isolated agents in separate processes if it does.

A runnable example lives in examples/embed.js.

How Responses Are Rendered

The CLI formats streamed output for terminal readability:

  • headings
  • bullet lists
  • numbered lists
  • fenced code blocks
  • diff-like output
  • inline code and file paths

If the backend returns reasoning_content, the CLI also shows a lightweight thinking section during streaming.

Dependency Policy

This project keeps its runtime dependency surface minimal, vetted, and pinned. It ran with zero runtime dependencies through its first phases; as of v1.9.0 it has a single one — the official Model Context Protocol SDK, @modelcontextprotocol/sdk — adopted to implement MCP against its reference (rather than hand-rolling the protocol).

The policy for any runtime dependency:

  • Minimal & justified — added only when a Node.js built-in genuinely cannot do the job, with a recorded rationale.
  • Pinned to an exact version — no ^/~ ranges in package.json. Upgrades are deliberate, reviewed commits.
  • Reviewed with the lockfilepackage-lock.json is committed; adding or bumping a dependency is a reviewed change.

Supply-chain checks. CI runs npm ci (lockfile integrity) and npm audit --omit=dev --audit-level=high (fails on HIGH/CRITICAL advisories in the runtime tree). The full audit-findings policy is documented in CLAUDE.md.

The SDK is ESM-only while this project is CommonJS, so it is loaded in exactly one place — lib/mcp/boundary.js, via dynamic import() — and the rest of the codebase stays CommonJS.

Notes and Limitations

  • It uses Node's built-in http and https modules for all networking; the only runtime dependency is the MCP SDK (see Dependency Policy above).
  • The edit command writes the model output directly back to the target file, so review prompts and backend behavior carefully.
  • Shell and file operations are approval-based, but they still execute on the local system after approval.

Not yet implemented

A few capabilities are intentionally absent today — documented here so you don't build on something that isn't wired up. See Deferred / Not Yet Implemented in CLAUDE.md for the full list and roadmap status.

  • MCP tools are interactive-chat only — they are not connected in the code/edit/shell one-shot commands or headless -p/--print mode.
  • No session auto-resume — there's no "resume your last session?" prompt at startup; use /history (local sessions) or --resume <chat-id> (dashboard chats).
  • Proxy env vars are not consumedHTTPS_PROXY/HTTP_PROXY are read into config but outbound HTTP does not yet route through a proxy agent (matters on corporate networks).
  • Planned for Phase 4+: per-pattern permissions, self-verification, checkpoints/rewind, and an OS sandbox.

Contributing

PRs must pass the CI pipeline (npm ci + npm audit + lint + tests on Linux/macOS/Windows, Node 18 & 20) before they can be merged. Run npm ci && npm run lint && npm test locally first. Any dependency change must follow the Dependency Policy above (exact pin, committed lockfile, justification).

License

MIT