@semalt-ai/code 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(grep -oP '.{0,120}chat:stash.{0,120}' /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js)",
5
+ "Bash(grep -oP '.{0,100}externalEditor.{0,100}' /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js)"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,99 @@
1
+ # semalt-code — Architecture Reference
2
+
3
+ ## lib/ File Responsibilities
4
+
5
+ | File | Responsibility |
6
+ |------|----------------|
7
+ | `lib/agent.js` | Agent loop: iterates up to 10 times, streams LLM response, extracts tool calls, dispatches execution, accumulates results back into the message history |
8
+ | `lib/api.js` | HTTP client for both OpenAI-compatible inference (`chatStream`, `chatSync`) and dashboard REST calls (auth, models, chat history); manually parses `text/event-stream` |
9
+ | `lib/args.js` | CLI argument parser; maps flags (`-m`, `-f`, `--dry-run`, …) to an `opts` object and collects positional args |
10
+ | `lib/commands.js` | All top-level command handlers: `cmdChat`, `cmdCode`, `cmdEdit`, `cmdShell`, `cmdLogin`, `cmdLogout`, `cmdWhoAmI`, `cmdModels`, `cmdInit` |
11
+ | `lib/config.js` | Read/write `~/.semalt-ai/config.json`; `normalizeConfig` merges defaults, migrates legacy keys, validates types |
12
+ | `lib/constants.js` | `DEFAULT_CONFIG`, `DEFAULT_API_TIMEOUT_MS`, `CONFIG_PATH`, `PACKAGE_JSON` |
13
+ | `lib/context.js` | Loads file or directory trees into a prompt context string; caps directory walks at 50 files, single files at 10 000 chars |
14
+ | `lib/permissions.js` | Per-session approval tracking; interactive yes/always/no prompt before each tool action; `toggleAll` for `/approve` |
15
+ | `lib/prompts.js` | Returns the system prompt string that instructs the LLM which XML tool tags to use and how |
16
+ | `lib/tools.js` | Implements `agentExecShell` and `agentExecFile` (16 file/env/network actions) plus `extractToolCalls` regex parser |
17
+ | `lib/ui.js` | All terminal rendering: ANSI constants, `StreamRenderer` (markdown + tool-call display + inline diff), `readInteractiveInput`, `interactiveSelect`, `printBanner`, `printStatusBar` |
18
+
19
+ ---
20
+
21
+ ## Agent Loop Flow
22
+
23
+ 1. `runAgentLoop(messages, model)` is called with the current message array (max 10 iterations).
24
+ 2. `chatStream(messages, { model })` sends the array to the LLM and streams tokens through `StreamRenderer` to the terminal.
25
+ 3. The full assistant text is appended to `messages` as `{ role: 'assistant', content }`.
26
+ 4. `extractToolCalls(reply)` scans the text for XML tool tags and fenced shell blocks; returns an ordered list of `[action, ...args]` tuples.
27
+ 5. If no tool calls are found, the loop ends.
28
+ 6. For each tool call, `permissionManager.askPermission(type, description)` prompts the user (yes / yes-always / no).
29
+ 7. Approved shell calls go to `agentExecShell`; all other calls go to `agentExecFile`.
30
+ 8. Results (or denial messages) are concatenated and pushed to `messages` as a `user` turn: `"Tool execution results:\n\n…\n\nContinue with the task."`.
31
+ 9. If any call was denied, a warning is printed and the loop continues with partial results.
32
+ 10. Go to step 2.
33
+
34
+ ---
35
+
36
+ ## Tool Tags
37
+
38
+ All tags are parsed by `extractToolCalls` in `lib/tools.js` and displayed by `StreamRenderer` in `lib/ui.js`.
39
+
40
+ | Tag | Syntax | Action dispatched |
41
+ |-----|--------|-------------------|
42
+ | `exec` | `<exec>command</exec>` | `shell` |
43
+ | `shell` | `<shell>command</shell>` | `shell` (alias) |
44
+ | `run_command` | `<run_command>command</run_command>` | `shell` (alias) |
45
+ | `run` | `<run>command</run>` | `shell` (alias) |
46
+ | Fenced shell block | ` ```shell\ncommand\n``` ` | `shell` (one call per non-comment line) |
47
+ | `read_file` (content) | `<read_file>/path</read_file>` | `read` |
48
+ | `read_file` (attribute) | `<read_file path="/path"/>` | `read` |
49
+ | `write_file` | `<write_file path="/path">content</write_file>` | `write` |
50
+ | `append_file` | `<append_file path="/path">content</append_file>` | `append` |
51
+ | `list_dir` | `<list_dir>/path</list_dir>` | `list_dir` |
52
+ | `search_files` | `<search_files>pattern</search_files>` | `search_files` |
53
+ | `delete_file` | `<delete_file>/path</delete_file>` | `delete_file` |
54
+ | `make_dir` | `<make_dir>/path</make_dir>` | `make_dir` |
55
+ | `remove_dir` | `<remove_dir>/path</remove_dir>` | `remove_dir` |
56
+ | `get_env` | `<get_env>VAR_NAME</get_env>` | `get_env` |
57
+ | `set_env` | `<set_env name="VAR" value="val"/>` | `set_env` |
58
+ | `move_file` | `<move_file src="/src" dst="/dst"/>` | `move_file` |
59
+ | `copy_file` | `<copy_file src="/src" dst="/dst"/>` | `copy_file` |
60
+ | `edit_file` | `<edit_file path="/path" line="N">new line content</edit_file>` | `edit_file` |
61
+ | `search_in_file` | `<search_in_file path="/path">regex</search_in_file>` | `search_in_file` |
62
+ | `replace_in_file` | `<replace_in_file path="/path" search="old" replace="new"></replace_in_file>` | `replace_in_file` |
63
+ | `download` | `<download>https://url</download>` | `download` |
64
+ | `upload` | `<upload path="/path">base64content</upload>` | `upload` |
65
+
66
+ ---
67
+
68
+ ## DEFAULT_CONFIG Keys
69
+
70
+ Defined in `lib/constants.js` and merged/validated by `lib/config.js`.
71
+
72
+ | Key | Default | Description |
73
+ |-----|---------|-------------|
74
+ | `api_base` | `"http://127.0.0.1:8800"` | OpenAI-compatible inference base URL (normalized to include `/v1`) |
75
+ | `api_key` | `"any"` | API key sent as `Authorization: Bearer` to the inference endpoint |
76
+ | `dashboard_url` | `"https://cli.semalt.ai"` | Base URL for the web dashboard (auth, models, chat history) |
77
+ | `auth_token` | `""` | Bearer token written by `semalt login`, cleared by `logout` |
78
+ | `default_model` | `"default"` | Model identifier sent in chat completions payloads |
79
+ | `dashboard_model_id` | `null` | Integer PK of the active model in `available_models`; required for chat history sync |
80
+ | `temperature` | `0.7` | Sampling temperature passed to the LLM |
81
+ | `request_timeout_ms` | `900000` | HTTP request timeout for inference calls (ms) |
82
+ | `stream` | `true` | Whether to use streaming (`text/event-stream`) for inference |
83
+ | `models` | `[]` | Local model profile overrides (each: `api_base`, `api_key`, `model`) |
84
+ | `theme` | `"dark"` | Terminal color theme |
85
+ | `max_file_size_kb` | `512` | Maximum file size the agent will read (KB) |
86
+ | `command_timeout_ms` | `30000` | Timeout for shell command execution (ms) |
87
+ | `max_output_lines` | `50` | Maximum lines of tool output shown before truncation |
88
+ | `show_token_count` | `true` | Display token usage in the status line after each turn |
89
+ | `show_cost` | `false` | Display estimated cost alongside token count |
90
+
91
+ ---
92
+
93
+ ## Planned Additions
94
+
95
+ | File | Intended Responsibility |
96
+ |------|------------------------|
97
+ | `lib/audit.js` | Persistent log of all tool calls executed per session (command, args, exit code, timestamp); used for replay and compliance review |
98
+ | `lib/storage.js` | Local SQLite or JSON-lines store for offline chat history, message deduplication, and cache of dashboard responses |
99
+ | `lib/metrics.js` | Collects per-session telemetry (token counts, latency, tool-call frequency) and exposes summary output for `/compact` and future analytics export |
package/CLAUDE.md ADDED
@@ -0,0 +1,349 @@
1
+ # semalt-code — CLI Agent
2
+
3
+ Node.js CLI tool that lets AI agents interact with code via an iterative tool-use loop. Zero external dependencies; uses only Node.js built-ins.
4
+
5
+ Published as `@semalt-ai/code`. Invokable as `semalt-code` or `semalt`.
6
+
7
+ ---
8
+
9
+ ## Directory Layout
10
+
11
+ ```
12
+ semalt-code/
13
+ ├── index.js # Entry point: arg parsing, module wiring, command dispatch
14
+ ├── lib/
15
+ │ ├── api.js # HTTP client for dashboard auth + OpenAI-compatible inference
16
+ │ ├── agent.js # Agent loop: stream → extract tools → execute → repeat
17
+ │ ├── commands.js # All CLI command handlers (chat, code, edit, shell, login, …)
18
+ │ ├── tools.js # File and shell operation implementations
19
+ │ ├── prompts.js # System prompt for the LLM (tells it to use exec/read/write tags)
20
+ │ ├── ui.js # Barrel: re-exports everything from lib/ui/
21
+ │ ├── ui/
22
+ │ │ ├── ansi.js # ANSI escape constants, THEME, color codes, SPINNER_DEFS
23
+ │ │ ├── utils.js # getCols, getRows, stripAnsi, hr, boxLine, insertCharAt, …
24
+ │ │ ├── diff.js # renderDiff (LCS diff), renderMarkdown, _mdInline
25
+ │ │ ├── stream.js # StreamRenderer — live token-by-token terminal output
26
+ │ │ ├── legacy.js # StatusBar (cmdCode/cmdEdit), interactiveSelect, SelectMenu
27
+ │ │ ├── layout.js # LayoutManager — terminal geometry, resize events
28
+ │ │ ├── chat-history.js# ChatHistory — bubble rendering, scroll, streaming slots
29
+ │ │ ├── status-bar.js # FullStatusBar — animated TUI status line
30
+ │ │ ├── input-field.js # InputField, parseKeySequence, SLASH_CMDS
31
+ │ │ └── create-ui.js # createUI factory + non-TTY no-op fallback
32
+ │ ├── context.js # Loads file/directory content into the prompt
33
+ │ ├── config.js # Read/write ~/.semalt-ai/config.json
34
+ │ ├── permissions.js # Per-session approval tracking for tool calls
35
+ │ ├── args.js # CLI argument parser
36
+ │ ├── constants.js # CONFIG_PATH, DEFAULT_CONFIG, DEFAULT_API_TIMEOUT_MS
37
+ │ ├── audit.js # Append-only audit log for all tool executions
38
+ │ ├── storage.js # Local session persistence and resume
39
+ │ └── metrics.js # Token counting, cost estimation, latency tracking
40
+ ├── package.json # name: @semalt-ai/code, version: 1.8.0, bin: semalt / semalt-code
41
+ └── README.md
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Tech Stack
47
+
48
+ | Component | Technology |
49
+ |-----------|-----------|
50
+ | Runtime | Node.js ≥ 16, CommonJS (`require`) |
51
+ | HTTP | Built-in `http`/`https` modules |
52
+ | Shell exec | `child_process.spawnSync` |
53
+ | File I/O | `fs` module |
54
+ | Terminal UI | Raw ANSI escape codes (no chalk, no readline wrappers) |
55
+ | Config | JSON at `~/.semalt-ai/config.json` |
56
+ | LLM protocol | OpenAI-compatible streaming (`text/event-stream`) |
57
+
58
+ ---
59
+
60
+ ## CLI Commands
61
+
62
+ ```
63
+ semalt-code # interactive chat (default)
64
+ semalt-code chat # interactive chat (explicit)
65
+ semalt-code code <prompt> # one-shot task with optional file context
66
+ semalt-code edit <file> <instruction> # targeted file edit
67
+ semalt-code shell <command> # run shell, optionally ask LLM to analyze output
68
+ semalt-code login # browser-based device auth against dashboard
69
+ semalt-code logout # clear stored auth_token
70
+ semalt-code whoami # show authenticated user
71
+ semalt-code models # interactive model selector (fetches from dashboard)
72
+ semalt-code init [options] # create/update ~/.semalt-ai/config.json
73
+ semalt-code audit # print last 50 audit log entries
74
+ semalt-code config [set <key> <val>] # show or update config keys
75
+ ```
76
+
77
+ ### Common Flags
78
+
79
+ ```
80
+ -m, --model <name> override model for this invocation
81
+ -r, --resume <chat-id> resume a dashboard chat by ID
82
+ -f, --file <path> load file or directory as context
83
+ -a, --analyze have LLM analyze shell output (used with `shell`)
84
+ --dry-run preview file edits without writing
85
+ --api-base <url> LLM API base URL (overrides config)
86
+ --api-key <key> API key (overrides config)
87
+ --dashboard-url <url> dashboard base URL (overrides config)
88
+ --default-model <name> set default model in config
89
+ --show-think display model reasoning (thinking) content
90
+ --debug print raw AI response (stderr) each iteration
91
+ --allow-fs auto-approve all filesystem operations
92
+ --allow-exec auto-approve shell command execution
93
+ --allow-net auto-approve network operations
94
+ --allow-all auto-approve everything (use carefully)
95
+ --readonly block all write operations
96
+ --new skip session resume prompt
97
+ -v, --version print version
98
+ -h, --help print help
99
+ ```
100
+
101
+ ### In-Chat Slash Commands
102
+
103
+ | Command | Effect |
104
+ |---------|--------|
105
+ | `/help` | List slash commands |
106
+ | `/file <path>` | Attach file or directory to context |
107
+ | `/history` | Browse and load a local saved session |
108
+ | `/chats` | Browse and resume a saved chat from the dashboard |
109
+ | `/new` | Start a fresh conversation (detach from current saved chat) |
110
+ | `/model [name]` | Show or switch model |
111
+ | `/models` | Interactive model picker from dashboard |
112
+ | `/shell <cmd>` or `!<cmd>` | Execute shell command |
113
+ | `/compact` | Show token usage estimate and session metrics |
114
+ | `/clear` | Reset conversation history |
115
+ | `/approve` | Toggle auto-approval of tool calls |
116
+ | `/config` | Print current config |
117
+ | `/login` | Start device auth flow |
118
+ | `/whoami` | Show current user |
119
+ | `/logout` | Clear auth token |
120
+ | `exit` / `quit` | Exit |
121
+
122
+ ---
123
+
124
+ ## Agent Loop (`lib/agent.js`)
125
+
126
+ Maximum 10 iterations per user turn.
127
+
128
+ ```
129
+ 1. Send messages[] to LLM via chatStream()
130
+ 2. Stream response tokens to terminal (StreamRenderer)
131
+ 3. After full response: extract tool-call tags from text
132
+ 4. If no tool tags → done
133
+ 5. For each tag: request user permission (once / always / no)
134
+ 6. Execute approved operations via ToolExecutor (wrapped in try/catch)
135
+ 7. Append tool results to messages[]
136
+ 8. Goto 1
137
+ ```
138
+
139
+ Each tool dispatch is wrapped in try/catch; errors print a warning and continue to the next tag rather than aborting the loop.
140
+
141
+ ### Tool Tags (parsed from LLM text)
142
+
143
+ ```xml
144
+ <exec>shell command here</exec>
145
+ <shell>shell command here</shell>
146
+ <read_file>/absolute/or/relative/path</read_file>
147
+ <read_file path="/path/to/file"/>
148
+ <write_file path="/path/to/file">file content here</write_file>
149
+ <create_file path="/path/to/file">file content here</create_file>
150
+ <append_file path="/path/to/file">content to append</append_file>
151
+ <list_dir>/path/to/dir</list_dir>
152
+ <search_files pattern="*.ts" dir="src"/>
153
+ <delete_file>/path/to/file</delete_file>
154
+ <make_dir>/path/to/dir</make_dir>
155
+ <remove_dir>/path/to/dir</remove_dir>
156
+ <get_env>ENV_VAR_NAME</get_env>
157
+ <set_env name="VAR" value="value"/>
158
+ <move_file src="/old/path" dst="/new/path"/>
159
+ <copy_file src="/src/path" dst="/dst/path"/>
160
+ <edit_file path="/file" line="42">replacement line content</edit_file>
161
+ <search_in_file path="/file">regex pattern</search_in_file>
162
+ <replace_in_file path="/file" search="old" replace="new"></replace_in_file>
163
+ <download>https://example.com/file.zip</download>
164
+ <upload path="/local/path">base64encodedcontent</upload>
165
+ <file_stat>/path/to/file</file_stat>
166
+ <http_get url="https://example.com/api"/>
167
+ <ask_user question="What is your preferred language?"/>
168
+ <store_memory key="project_lang">TypeScript</store_memory>
169
+ <recall_memory key="project_lang"/>
170
+ <list_memories/>
171
+ <system_info/>
172
+ ```
173
+
174
+ The system prompt (`lib/prompts.js`) instructs the LLM to use exactly these tags. Do not change tag names without updating both `prompts.js` and the parser in `agent.js`.
175
+
176
+ ---
177
+
178
+ ## Tool Operations (`lib/tools.js`)
179
+
180
+ All operations request permission before execution unless auto-approved.
181
+ Output truncated to `config.max_output_lines` (default 20) to avoid filling context.
182
+
183
+ | Action | Description |
184
+ |--------|-------------|
185
+ | `read` | Read file content |
186
+ | `write` | Write file (creates parent dirs) |
187
+ | `append` | Append to file |
188
+ | `list_dir` | List directory contents |
189
+ | `delete_file` | Delete file |
190
+ | `make_dir` | Create directory (recursive) |
191
+ | `remove_dir` | Remove directory (recursive) |
192
+ | `move_file` | Move/rename file |
193
+ | `copy_file` | Copy file |
194
+ | `search_files` | Find files matching glob pattern |
195
+ | `search_in_file` | Regex search within file |
196
+ | `replace_in_file` | Replace text in file (regex, optional flags) |
197
+ | `edit_file` | Replace a specific line number in a file |
198
+ | `get_env` / `set_env` | Read/write environment variables |
199
+ | `download` | HTTP GET → save to file |
200
+ | `upload` | Write base64-encoded content to file |
201
+ | `file_stat` | Stat a file (size, mtime, type, mode) |
202
+ | `http_get` | HTTP GET → return body (truncated to max_output_lines) |
203
+ | `ask_user` | Prompt user for input; auto-answers 'y' in non-TTY mode |
204
+ | `store_memory` | Persist a key/value pair to `~/.semalt-ai/memory.json` |
205
+ | `recall_memory` | Read a key from `~/.semalt-ai/memory.json` |
206
+ | `list_memories` | List all stored memory keys |
207
+ | `system_info` | Return platform, arch, hostname, memory, Node version, cwd |
208
+
209
+ ---
210
+
211
+ ## Audit Log (`lib/audit.js`)
212
+
213
+ Every tool execution is appended to `~/.semalt-ai/audit.log` as NDJSON:
214
+ ```json
215
+ {"ts":"2026-01-01T00:00:00.000Z","tag":"exec","input":"{\"command\":\"ls\"}","approved":true,"result":"ok"}
216
+ ```
217
+
218
+ View the last 50 entries with `semalt-code audit`.
219
+
220
+ ---
221
+
222
+ ## Session Storage (`lib/storage.js`)
223
+
224
+ Local chat sessions are saved to `~/.semalt-ai/sessions/` as JSON files named `<timestamp>-<id>.json`. The `chat` command offers to resume the most recent session (< 24 h old) on startup unless `--new` or `--resume` is passed. Use `/history` in-chat to browse and load any saved session.
225
+
226
+ ---
227
+
228
+ ## Metrics (`lib/metrics.js`)
229
+
230
+ `Metrics` is instantiated per `runAgentLoop` call and tracks per-turn token usage, latency, and total session duration. A summary box is printed on exit (SIGINT or natural quit) and after `cmdCode` runs. Use `/compact` in-chat to see the live summary.
231
+
232
+ ---
233
+
234
+ ## API Client (`lib/api.js`)
235
+
236
+ Handles two distinct concerns:
237
+
238
+ **Inference** (OpenAI-compatible):
239
+ - `chatStream(messages, model, opts)` → streams tokens, calls `onToken`, returns `{ content, usage }`
240
+ - URL: `config.api_base` normalized to include `/v1` if missing
241
+ - Supports `reasoning_content` field for extended-thinking models
242
+
243
+ **Dashboard** (cli.semalt.ai backend):
244
+ - `requestCliLogin()` → `POST /api/auth/cli/request`
245
+ - `getCliLoginStatus(id, token)` → `POST /api/auth/cli/status`
246
+ - `dashboardWhoAmI()` → `GET /api/auth/me`
247
+ - `dashboardLogout()` → `POST /api/auth/logout`
248
+ - `dashboardListModels()` → `GET /api/models`
249
+ - `dashboardGetModelForCli(id)` → `GET /api/models/{id}/cli`
250
+ - `dashboardCreateChat(title, modelDbId)` → `POST /api/chats`
251
+ - `dashboardListChats()` → `GET /api/chats`
252
+ - `dashboardGetChat(id)` → `GET /api/chats/{id}`
253
+ - `dashboardSaveMessages(chatId, messages)` → `POST /api/chats/{id}/messages/batch`
254
+
255
+ All dashboard calls send `Authorization: Bearer <auth_token>` from config.
256
+
257
+ ---
258
+
259
+ ## Config File (`~/.semalt-ai/config.json`)
260
+
261
+ Managed by `lib/config.js`. Normalized on every load. The config directory is created automatically if it does not exist.
262
+
263
+ ```json
264
+ {
265
+ "api_base": "http://127.0.0.1:8800",
266
+ "api_key": "any",
267
+ "dashboard_url": "https://cli.semalt.ai",
268
+ "auth_token": "",
269
+ "default_model": "default",
270
+ "dashboard_model_id": null,
271
+ "temperature": 0.7,
272
+ "request_timeout_ms": 900000,
273
+ "stream": true,
274
+ "theme": "dark",
275
+ "max_file_size_kb": 512,
276
+ "command_timeout_ms": 30000,
277
+ "max_output_lines": 50,
278
+ "show_token_count": true,
279
+ "show_cost": false,
280
+ "context_length": null,
281
+ "models": [
282
+ {
283
+ "name": "local-llama",
284
+ "api_base": "http://127.0.0.1:11434",
285
+ "api_key": "any",
286
+ "model": "llama3",
287
+ "context_length": 8192
288
+ }
289
+ ]
290
+ }
291
+ ```
292
+
293
+ - `api_base` is normalized to always include `/v1`.
294
+ - Legacy key `semalt_base_url` is migrated to `api_base` on load.
295
+ - `auth_token` is written by `semalt-code login` and cleared by `logout`.
296
+ - `dashboard_model_id` is the integer PK of the active model in `available_models`; written when a model is selected via `/models`. Required for chat history sync — if null, history sync is silently skipped.
297
+ - `max_file_size_kb` caps how large a file may be before read is refused (default 512 KB).
298
+ - `command_timeout_ms` caps shell command execution time (default 30 s).
299
+ - `max_output_lines` caps shell and HTTP response lines returned to the agent (default 50).
300
+ - `show_token_count` controls whether token count is shown in the status bar.
301
+ - `show_cost` reserved for future cost-display feature.
302
+ - `context_length` / `models[].context_length` — token limit used for context-usage bar and warnings.
303
+ - Local `models[]` entries override dashboard models when selected.
304
+
305
+ ---
306
+
307
+ ## Key Patterns & Invariants
308
+
309
+ - **No dependencies**: keep it that way. Any new feature must use Node.js built-ins only.
310
+ - **CommonJS**: all files use `require()`/`module.exports`. Do not use ES `import`/`export`.
311
+ - **Streaming**: `api.js` manually parses `text/event-stream`. The parser in `chatStream()` handles partial JSON lines — be careful editing it.
312
+ - **Permissions are per-session**: `PermissionManager` resets on each CLI invocation. Approvals never persist to disk. In non-TTY mode all tool calls are auto-approved with a warning.
313
+ - **Token counting is approximate**: `estimateTokens()` divides char count by 4. It is used only for the `/compact` display — do not rely on it for hard limits.
314
+ - **Tool output is truncated**: `tools.js` caps output at `max_output_lines` (default 50). Configurable via config.
315
+ - **Max 10 agent iterations**: hard-coded in `agent.js`. Prevents runaway loops.
316
+ - **Malformed tags are skipped**: each tool dispatch in the agent loop is wrapped in try/catch; errors emit a warning line and continue to the next tool call.
317
+
318
+ ---
319
+
320
+ ## Development & Publishing
321
+
322
+ ```bash
323
+ # Run locally
324
+ node index.js chat
325
+
326
+ # Symlink for global use during dev
327
+ npm link
328
+
329
+ # Publish to npm
330
+ npm publish --access public
331
+ ```
332
+
333
+ Version is in `package.json`. Bump it with every published change.
334
+
335
+ ---
336
+
337
+ ## Keeping This File Up-to-Date
338
+
339
+ Update this file when:
340
+ - A new CLI command or slash command is added (update the commands tables).
341
+ - A new tool action is added to `tools.js` (update the Tool Operations table).
342
+ - The agent loop behavior changes (max iterations, tag format, approval flow).
343
+ - A new `lib/` module is added.
344
+ - The config schema changes (new keys, renamed keys, migration logic).
345
+ - A new dashboard API call is added to `api.js`.
346
+ - The system prompt in `prompts.js` changes in a way that affects tool-tag syntax.
347
+ - The Node.js version requirement changes.
348
+
349
+ When renaming or removing a tool tag, update **both** `prompts.js` and `agent.js` atomically and note it here.
package/index.js CHANGED
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+
4
8
  const { PACKAGE_JSON } = require('./lib/constants');
5
- const { loadConfig, saveConfig } = require('./lib/config');
9
+ const { loadConfig, saveConfig, configSet, configShow } = require('./lib/config');
6
10
  const ui = require('./lib/ui');
7
11
  const { createPermissionManager } = require('./lib/permissions');
8
12
  const { createToolExecutor, extractToolCalls } = require('./lib/tools');
@@ -12,6 +16,7 @@ const { createAgentRunner } = require('./lib/agent');
12
16
  const { createCommands } = require('./lib/commands');
13
17
  const { parseArgs } = require('./lib/args');
14
18
  const { CONFIG_PATH } = require('./lib/constants');
19
+ const { AUDIT_LOG } = require('./lib/audit');
15
20
 
16
21
  let config = loadConfig();
17
22
 
@@ -24,8 +29,20 @@ function setConfig(nextConfig) {
24
29
  saveConfig(config);
25
30
  }
26
31
 
27
- const permissionManager = createPermissionManager(ui);
28
- const { agentExecShell, agentExecFile } = createToolExecutor(permissionManager, ui);
32
+ // Pre-scan argv for permission tier flags before creating PermissionManager
33
+ const _argv = process.argv.slice(2);
34
+ const _allowedTiers = [];
35
+ if (_argv.includes('--allow-all')) {
36
+ _allowedTiers.push('fs', 'exec', 'net', 'sys');
37
+ } else {
38
+ if (_argv.includes('--allow-fs')) _allowedTiers.push('fs');
39
+ if (_argv.includes('--allow-exec')) _allowedTiers.push('exec');
40
+ if (_argv.includes('--allow-net')) _allowedTiers.push('net');
41
+ }
42
+ const _readonly = _argv.includes('--readonly');
43
+
44
+ const permissionManager = createPermissionManager(ui, { allowedTiers: _allowedTiers, readonly: _readonly });
45
+ const { agentExecShell, agentExecFile } = createToolExecutor(permissionManager, ui, getConfig);
29
46
  const apiClient = createApiClient({
30
47
  getConfig,
31
48
  saveConfig: (nextConfig) => {
@@ -77,12 +94,12 @@ Commands:
77
94
  login Authorize CLI via browser
78
95
  whoami Show current authorized user
79
96
  logout Clear current CLI login
80
- models Choose one of your dashboard models
81
- models add Add a saved model profile
97
+ models Choose a model
82
98
  init Initialize config
83
99
 
84
100
  Options:
85
101
  -m, --model <name> Model name
102
+ -r, --resume <chat-id> Resume a saved chat (chat command)
86
103
  -f, --file <path> Load file into context (code command)
87
104
  -a, --analyze Analyze output with AI (shell command)
88
105
  --dry-run Don't save changes (edit command)
@@ -90,6 +107,14 @@ Options:
90
107
  --api-key <key> API key (init)
91
108
  --dashboard-url <url> Dashboard URL (init)
92
109
  --default-model <name> Default model (init)
110
+ --show-think Display model reasoning (thinking) content
111
+ --debug Print messages sent to agent + raw AI response (stderr) each iteration
112
+ --allow-fs Auto-approve all filesystem operations
113
+ --allow-exec Auto-approve shell command execution
114
+ --allow-net Auto-approve network operations
115
+ --allow-all Auto-approve everything (use carefully)
116
+ --readonly Block all write operations
117
+ --new Skip session resume prompt
93
118
  -v, --version Show CLI version
94
119
 
95
120
  Config: ${CONFIG_PATH}
@@ -121,11 +146,48 @@ Config: ${CONFIG_PATH}
121
146
  } else if (command === 'logout') {
122
147
  await commands.cmdLogout();
123
148
  } else if (command === 'models') {
124
- if (rawArgs[1] === 'add') await commands.cmdModelsAdd();
125
- else await commands.cmdModels();
149
+ await commands.cmdModels();
126
150
  } else if (command === 'init') {
127
151
  const { opts } = parseArgs(rawArgs.slice(1));
128
152
  commands.cmdInit(opts);
153
+ } else if (command === 'audit') {
154
+ try {
155
+ const content = fs.readFileSync(AUDIT_LOG, 'utf8');
156
+ const lines = content.trim().split('\n').filter((l) => l.trim());
157
+ const last50 = lines.slice(-50);
158
+ for (const line of last50) {
159
+ try {
160
+ const entry = JSON.parse(line);
161
+ const icon = entry.approved ? `${ui.FG_GREEN}✓${ui.RST}` : `${ui.FG_RED}✗${ui.RST}`;
162
+ console.log(`${icon} ${line}`);
163
+ } catch {
164
+ console.log(line);
165
+ }
166
+ }
167
+ } catch {
168
+ console.log('No audit log found.');
169
+ }
170
+ } else if (command === 'config') {
171
+ const sub = rawArgs[1];
172
+ if (sub === 'set') {
173
+ const key = rawArgs[2];
174
+ const value = rawArgs[3];
175
+ if (!key || value === undefined) {
176
+ process.stderr.write(`Usage: semalt-code config set <key> <value>\n`);
177
+ process.exit(1);
178
+ }
179
+ let parsed;
180
+ try {
181
+ parsed = JSON.parse(value);
182
+ } catch {
183
+ parsed = value;
184
+ }
185
+ configSet(key, parsed);
186
+ console.log(`Set ${key} = ${JSON.stringify(parsed)}`);
187
+ } else {
188
+ // default: "show" or bare "config"
189
+ console.log(configShow());
190
+ }
129
191
  } else {
130
192
  const { opts } = parseArgs(rawArgs);
131
193
  await commands.cmdChat(opts);