@semalt-ai/code 1.8.5 → 1.20.0
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.
- package/.claude/settings.local.json +7 -1
- package/.github/workflows/ci.yml +69 -0
- package/ARCHITECTURE.md +6 -95
- package/CLAUDE.md +196 -316
- package/README.md +148 -4
- package/docs/ARCHITECTURE.md +1321 -0
- package/docs/CONFIG.md +340 -0
- package/docs/HISTORY.md +245 -0
- package/examples/embed.js +74 -0
- package/index.js +251 -10
- package/lib/agent.js +856 -120
- package/lib/api.js +239 -50
- package/lib/args.js +74 -2
- package/lib/audit.js +23 -1
- package/lib/background.js +584 -0
- package/lib/checkpoints.js +757 -0
- package/lib/commands/auth.js +94 -0
- package/lib/commands/chat-session.js +489 -0
- package/lib/commands/chat-slash.js +415 -0
- package/lib/commands/chat-turn.js +669 -0
- package/lib/commands/chat.js +407 -0
- package/lib/commands/custom.js +157 -0
- package/lib/commands/history-utils.js +66 -0
- package/lib/commands/index.js +268 -0
- package/lib/commands/mcp.js +113 -0
- package/lib/commands/oneshot.js +193 -0
- package/lib/commands/registry.js +269 -0
- package/lib/commands/tasks.js +89 -0
- package/lib/compact.js +87 -0
- package/lib/config.js +360 -11
- package/lib/constants.js +401 -3
- package/lib/deny.js +199 -0
- package/lib/doctor.js +160 -0
- package/lib/headless.js +202 -0
- package/lib/hooks.js +286 -0
- package/lib/images.js +270 -0
- package/lib/internals.js +49 -0
- package/lib/mcp/boundary.js +131 -0
- package/lib/mcp/client.js +270 -0
- package/lib/mcp/oauth.js +134 -0
- package/lib/memory.js +209 -0
- package/lib/metrics.js +37 -2
- package/lib/payload.js +54 -0
- package/lib/permission-rules.js +401 -0
- package/lib/permissions.js +123 -26
- package/lib/pricing.js +67 -0
- package/lib/proc.js +62 -0
- package/lib/prompts.js +99 -8
- package/lib/sandbox.js +568 -0
- package/lib/sdk.js +328 -0
- package/lib/secrets.js +211 -0
- package/lib/skills.js +223 -0
- package/lib/subagents.js +516 -0
- package/lib/tool_registry.js +2862 -0
- package/lib/tool_specs.js +263 -9
- package/lib/tools.js +352 -1039
- package/lib/ui/anim.js +86 -0
- package/lib/ui/ansi.js +17 -27
- package/lib/ui/chat-history.js +253 -71
- package/lib/ui/create-ui.js +67 -24
- package/lib/ui/diff.js +90 -25
- package/lib/ui/file-activity.js +236 -0
- package/lib/ui/format.js +195 -29
- package/lib/ui/input-field.js +21 -11
- package/lib/ui/md-stream.js +234 -0
- package/lib/ui/render-operation.js +113 -0
- package/lib/ui/select.js +1 -4
- package/lib/ui/status-bar.js +146 -36
- package/lib/ui/stream.js +20 -13
- package/lib/ui/theme.js +190 -44
- package/lib/ui/tool-operation.js +190 -0
- package/lib/ui/utils.js +9 -5
- package/lib/ui/web-activity.js +270 -0
- package/lib/ui/writer.js +159 -45
- package/lib/ui.js +1 -1
- package/lib/verify.js +229 -0
- package/lib/web-extract.js +213 -0
- package/lib/web-summarize.js +68 -0
- package/package.json +19 -4
- package/scripts/lint.js +57 -0
- package/test/agent-loop.test.js +389 -0
- package/test/anim-driver.test.js +153 -0
- package/test/ask-user-display.test.js +226 -0
- package/test/ask-user-gate.test.js +231 -0
- package/test/background.test.js +414 -0
- package/test/chat-history-nocolor.test.js +155 -0
- package/test/chat-relogin.test.js +207 -0
- package/test/chat.test.js +114 -0
- package/test/checkpoints-agent.test.js +181 -0
- package/test/checkpoints.test.js +650 -0
- package/test/command-registry.test.js +160 -0
- package/test/compact.test.js +116 -0
- package/test/completion-lazy.test.js +52 -0
- package/test/config-merge.test.js +324 -0
- package/test/config-quarantine.test.js +128 -0
- package/test/config-write-guard-allow-anywhere.test.js +56 -0
- package/test/config-write-guard-skip.test.js +46 -0
- package/test/config-write-guard.test.js +153 -0
- package/test/context-split.test.js +215 -0
- package/test/cost-doctor.test.js +142 -0
- package/test/custom-commands-chat.test.js +106 -0
- package/test/custom-commands.test.js +230 -0
- package/test/defer-detail-band.test.js +403 -0
- package/test/deny-windows.test.js +120 -0
- package/test/deny.test.js +83 -0
- package/test/detail-band-tab-flatten.test.js +242 -0
- package/test/download-allow-anywhere.test.js +66 -0
- package/test/download-confine.test.js +153 -0
- package/test/exec-diff.test.js +268 -0
- package/test/executors.test.js +599 -0
- package/test/extract-tool-calls.test.js +349 -0
- package/test/fetch-url-validation.test.js +219 -0
- package/test/file-activity.test.js +522 -0
- package/test/fixtures/tool-calls.js +57 -0
- package/test/fixtures/web-page.js +91 -0
- package/test/git-tools.test.js +384 -0
- package/test/grep-glob-serialize.test.js +242 -0
- package/test/grep-glob.test.js +268 -0
- package/test/grep-path-target.test.js +227 -0
- package/test/harness/README.md +57 -0
- package/test/harness/chat-harness.js +143 -0
- package/test/harness/memwarn-headless-child.js +65 -0
- package/test/harness/mock-llm.js +120 -0
- package/test/harness/mock-mcp-server.js +142 -0
- package/test/harness/sse-server.js +69 -0
- package/test/headless.test.js +348 -0
- package/test/history-utils.test.js +88 -0
- package/test/hooks-agent.test.js +238 -0
- package/test/hooks-verify-sandbox.test.js +232 -0
- package/test/hooks.test.js +216 -0
- package/test/http-get-user-agent.test.js +142 -0
- package/test/images-api.test.js +208 -0
- package/test/images.test.js +238 -0
- package/test/input-field-ctrl-o.test.js +37 -0
- package/test/live-height-physical.test.js +281 -0
- package/test/max-iterations.test.js +218 -0
- package/test/mcp-boundary.test.js +57 -0
- package/test/mcp-client.test.js +267 -0
- package/test/mcp-oauth.test.js +86 -0
- package/test/md-stream.test.js +183 -0
- package/test/memory-truncation-warning.test.js +222 -0
- package/test/memory.test.js +198 -0
- package/test/native-dispatch.test.js +409 -0
- package/test/native-live-narration.test.js +254 -0
- package/test/output-chokepoint.test.js +188 -0
- package/test/output-heredoc-leak.test.js +195 -0
- package/test/output-preview.test.js +245 -0
- package/test/path-guards.test.js +134 -0
- package/test/payload.test.js +99 -0
- package/test/permission-rules-agent.test.js +210 -0
- package/test/permission-rules.test.js +297 -0
- package/test/permissions.test.js +362 -0
- package/test/plan-mode.test.js +167 -0
- package/test/read-paginate.test.js +275 -0
- package/test/readonly-tools.test.js +177 -0
- package/test/render-operation.test.js +317 -0
- package/test/replay-descriptor-xml.test.js +216 -0
- package/test/replay-descriptor.test.js +189 -0
- package/test/replay-web-aggregate.test.js +291 -0
- package/test/replay-web-persist.test.js +241 -0
- package/test/result-cap.test.js +233 -0
- package/test/running-glyph-anim.test.js +111 -0
- package/test/sandbox-agent.test.js +147 -0
- package/test/sandbox-integration.test.js +216 -0
- package/test/sandbox.test.js +408 -0
- package/test/sdk.test.js +234 -0
- package/test/shell-output-cap.test.js +181 -0
- package/test/skills-chat.test.js +110 -0
- package/test/skills.test.js +295 -0
- package/test/smoke.test.js +68 -0
- package/test/status-bar-driver.test.js +93 -0
- package/test/status-bar-pause.test.js +164 -0
- package/test/status-bar-resync.test.js +188 -0
- package/test/stream-parser.test.js +171 -0
- package/test/subagents-agent.test.js +178 -0
- package/test/subagents.test.js +222 -0
- package/test/theme-palette.test.js +166 -0
- package/test/tool-registry.test.js +85 -0
- package/test/trim-budget.test.js +101 -0
- package/test/truncate-visible.test.js +78 -0
- package/test/verify-agent.test.js +317 -0
- package/test/verify.test.js +141 -0
- package/test/view-image.test.js +199 -0
- package/test/web-activity-ordering.test.js +203 -0
- package/test/web-activity.test.js +207 -0
- package/test/web-data-extraction-guidance.test.js +71 -0
- package/test/web-extract.test.js +185 -0
- package/test/web-fetch-agent.test.js +291 -0
- package/test/web-fetch-mode.test.js +193 -0
- package/test/web-search.test.js +380 -0
- package/lib/commands.js +0 -1438
- package/path +0 -1
package/index.js
CHANGED
|
@@ -6,7 +6,8 @@ const os = require('os');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
|
|
8
8
|
const { PACKAGE_JSON } = require('./lib/constants');
|
|
9
|
-
const { loadConfig, saveConfig, configSet, configShow } = require('./lib/config');
|
|
9
|
+
const { loadConfig, loadUserConfig, saveConfig, configSet, configShow, userLayerForPersist, readUserConfig, loadProjectConfig } = require('./lib/config');
|
|
10
|
+
const { loadRuleLayers } = require('./lib/permission-rules');
|
|
10
11
|
const ui = require('./lib/ui');
|
|
11
12
|
const { registerTerminalCleanup } = require('./lib/ui/terminal');
|
|
12
13
|
const { createPermissionManager } = require('./lib/permissions');
|
|
@@ -14,6 +15,7 @@ const { createToolExecutor, extractToolCalls } = require('./lib/tools');
|
|
|
14
15
|
const { readFileContext } = require('./lib/context');
|
|
15
16
|
const { createApiClient } = require('./lib/api');
|
|
16
17
|
const { createAgentRunner } = require('./lib/agent');
|
|
18
|
+
const { createCheckpointStore, latestSession } = require('./lib/checkpoints');
|
|
17
19
|
const { createCommands } = require('./lib/commands');
|
|
18
20
|
const { parseArgs } = require('./lib/args');
|
|
19
21
|
const { CONFIG_PATH } = require('./lib/constants');
|
|
@@ -31,9 +33,19 @@ function getConfig() {
|
|
|
31
33
|
return config;
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
// Persist a caller's config object to the USER file, then re-merge the layered
|
|
37
|
+
// view. Only the keys the caller actually changed (vs the current merged view)
|
|
38
|
+
// are layered onto config.json, so env/project/flag overrides the caller merely
|
|
39
|
+
// carried along are never baked in (Task 2.2). All getConfig()/setConfig() call
|
|
40
|
+
// sites are unchanged — they still see the merged view and pass full objects.
|
|
41
|
+
function persistConfig(nextConfig) {
|
|
42
|
+
const layer = userLayerForPersist(nextConfig, config, loadUserConfig());
|
|
43
|
+
saveConfig(layer);
|
|
44
|
+
config = loadConfig();
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
function setConfig(nextConfig) {
|
|
35
|
-
|
|
36
|
-
saveConfig(config);
|
|
48
|
+
persistConfig(nextConfig);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
// Pre-scan argv for permission tier flags before creating PermissionManager
|
|
@@ -47,17 +59,99 @@ if (_argv.includes('--allow-all')) {
|
|
|
47
59
|
if (_argv.includes('--allow-net')) _allowedTiers.push('net');
|
|
48
60
|
}
|
|
49
61
|
const _readonly = _argv.includes('--readonly');
|
|
62
|
+
// The single explicit opt-out of all safety. Pre-scanned here (like the tier
|
|
63
|
+
// flags) so the PermissionManager is constructed with the right mode before any
|
|
64
|
+
// command runs. tools.js reads the same flag from argv for the deny-list bypass.
|
|
65
|
+
const _skipPermissions = _argv.includes('--dangerously-skip-permissions');
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
// Per-pattern permission rules (Task 4.1). The user and project layers are read
|
|
68
|
+
// INDEPENDENTLY (not through the shallow-merged config) so the project layer
|
|
69
|
+
// stays separate and can be structurally prevented from widening the user
|
|
70
|
+
// posture. Malformed rules are dropped with a startup warning.
|
|
71
|
+
const _ruleLayers = loadRuleLayers(
|
|
72
|
+
readUserConfig(),
|
|
73
|
+
loadProjectConfig(process.cwd()),
|
|
74
|
+
// audit: allowed — pre-UI startup warning, fires before the TUI initialises.
|
|
75
|
+
(msg) => process.stderr.write(`⚠ ${msg}\n`),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const permissionManager = createPermissionManager(ui, {
|
|
79
|
+
allowedTiers: _allowedTiers,
|
|
80
|
+
readonly: _readonly,
|
|
81
|
+
skipPermissions: _skipPermissions,
|
|
82
|
+
rules: _ruleLayers,
|
|
83
|
+
cwd: process.cwd(),
|
|
84
|
+
});
|
|
85
|
+
// Checkpoints & rewind (Task 4.3). One store per process, shared by the executor
|
|
86
|
+
// (capture point) and the agent runner (per-turn linkage). It auto-generates a
|
|
87
|
+
// session id; cmdChat realigns it with the chat session.id before any turn.
|
|
88
|
+
//
|
|
89
|
+
// Restore-path re-validation (Task 4.3b, Part 1): a rewind that would write/delete
|
|
90
|
+
// a path is re-checked against the CURRENT guards — isPathSafe (CWD confinement /
|
|
91
|
+
// --allow-anywhere), the secret-file guard, the protected-config write guard, and
|
|
92
|
+
// any active `deny` permission rule — so a restore can never re-write a path the
|
|
93
|
+
// guards now forbid. A failing target is refused (skipped), never aborting the
|
|
94
|
+
// whole rewind; `force` does NOT bypass this (it overrides only the external-mod
|
|
95
|
+
// check). This guard lives in the executor owner, not anywhere the model reaches.
|
|
96
|
+
const { isPathSafe: _isPathSafe, isProtectedSecretPath: _isProtectedSecretPath, isProtectedConfigPath: _isProtectedConfigPath } = require('./lib/tools');
|
|
97
|
+
function restoreGuard(targetPath, { willDelete } = {}) {
|
|
98
|
+
if (!_isPathSafe(targetPath)) return { ok: false, reason: 'path is now outside the allowed area (isPathSafe / --allow-anywhere)' };
|
|
99
|
+
if (_isProtectedSecretPath(targetPath)) return { ok: false, reason: 'path is a protected secret file' };
|
|
100
|
+
if (_isProtectedConfigPath(targetPath)) return { ok: false, reason: 'path is a protected config path' };
|
|
101
|
+
const verdict = permissionManager.resolveRule([willDelete ? 'delete_file' : 'write_file', targetPath]);
|
|
102
|
+
if (verdict && verdict.decision === 'deny') {
|
|
103
|
+
return { ok: false, reason: `blocked by a deny permission rule${verdict.reason ? ` (${verdict.reason})` : ''}` };
|
|
104
|
+
}
|
|
105
|
+
return { ok: true };
|
|
106
|
+
}
|
|
107
|
+
const checkpointStore = createCheckpointStore({ getConfig, restoreGuard });
|
|
108
|
+
// OS sandbox fallback approver (Task 4.4). When the kernel sandbox is unavailable
|
|
109
|
+
// in `auto` mode, agentExecShell asks a HUMAN here before running a command
|
|
110
|
+
// unsandboxed. Non-TTY → refuse (no way to ask → never a silent unsandboxed run).
|
|
111
|
+
// This lives in the executor owner, NOT anywhere the model can reach, so the
|
|
112
|
+
// agent can never approve its own escape.
|
|
113
|
+
async function onUnsandboxed({ command, reason, installHint } = {}) {
|
|
114
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
115
|
+
// audit: allowed — interactive confirm outside the agent's stream.
|
|
116
|
+
process.stderr.write(`\n⚠ OS sandbox unavailable (${reason}). The command will run WITHOUT kernel-level confinement:\n ${command}\n`);
|
|
117
|
+
if (installHint) process.stderr.write(` ${installHint}\n`);
|
|
118
|
+
try {
|
|
119
|
+
const idx = await ui.interactiveSelect(
|
|
120
|
+
['No, do not run it', 'Yes, run it unsandboxed'],
|
|
121
|
+
(item, isSelected) => {
|
|
122
|
+
const cursor = isSelected ? `${ui.FG_YELLOW}❯${ui.RST}` : ' ';
|
|
123
|
+
const style = isSelected ? ui.FG_CYAN : ui.FG_GRAY;
|
|
124
|
+
return ` ${cursor} ${style}${item}${ui.RST}`;
|
|
125
|
+
},
|
|
126
|
+
{ initialIndex: 0 },
|
|
127
|
+
);
|
|
128
|
+
return idx === 1;
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
53
133
|
const apiClient = createApiClient({
|
|
54
134
|
getConfig,
|
|
135
|
+
// Route the api.js learned-context-length persistence through the same
|
|
136
|
+
// user-layer rebase so a learned value lands in config.json without baking
|
|
137
|
+
// in any active env/project/flag override (Task 2.2).
|
|
55
138
|
saveConfig: (nextConfig) => {
|
|
56
|
-
|
|
57
|
-
config = nextConfig;
|
|
139
|
+
persistConfig(nextConfig);
|
|
58
140
|
},
|
|
59
141
|
ui,
|
|
60
142
|
});
|
|
143
|
+
const { agentExecShell, agentExecFile, describePermission } = createToolExecutor(permissionManager, ui, getConfig, {
|
|
144
|
+
checkpointStore,
|
|
145
|
+
onUnsandboxed,
|
|
146
|
+
// Web-fetch secondary summarizer (Task W.1): http_get runs a separate cheap
|
|
147
|
+
// LLM call to summarize extracted page content; only the summary enters the
|
|
148
|
+
// main context.
|
|
149
|
+
webChat: (messages, opts) => apiClient.chatComplete(messages, opts),
|
|
150
|
+
// Web search (Task W.2b): the web_search tool calls the backend /api/search
|
|
151
|
+
// via dashboardSearch and returns compact snippets so the agent can pick
|
|
152
|
+
// targeted URLs to fetch with http_get instead of guessing.
|
|
153
|
+
webSearch: (query, opts) => apiClient.dashboardSearch(query, opts),
|
|
154
|
+
});
|
|
61
155
|
const { runAgentLoop } = createAgentRunner({
|
|
62
156
|
chatStream: apiClient.chatStream,
|
|
63
157
|
extractToolCalls: (reply, options = {}) => extractToolCalls(reply, {
|
|
@@ -70,7 +164,38 @@ const { runAgentLoop } = createAgentRunner({
|
|
|
70
164
|
permissionManager,
|
|
71
165
|
ui,
|
|
72
166
|
getConfig,
|
|
167
|
+
checkpoints: checkpointStore,
|
|
168
|
+
// Command hooks + self-verification run through the same OS sandbox as
|
|
169
|
+
// agentExecShell (Pre-Task 5.0a); share the human-approval fallback so an
|
|
170
|
+
// unavailable sandbox can be approved interactively (never a silent run).
|
|
171
|
+
onUnsandboxed,
|
|
73
172
|
});
|
|
173
|
+
// Subagents (Task 3.6). Register the `spawn_agent` tool once at startup so it is
|
|
174
|
+
// available in both interactive chat and headless one-shot runs. The manager
|
|
175
|
+
// builds CONSTRAINED child runners that share this process's permission manager
|
|
176
|
+
// (no privilege escalation) and reuse the same executors. Custom agent
|
|
177
|
+
// definitions are discovered from .semalt/agents (project) + ~/.semalt-ai/agents.
|
|
178
|
+
const { createSubagentManager, discoverAgentDefs, buildSpawnAgentEntry } = require('./lib/subagents');
|
|
179
|
+
const { registerDynamicTool } = require('./lib/tool_registry');
|
|
180
|
+
try {
|
|
181
|
+
const subagentManager = createSubagentManager({
|
|
182
|
+
chatStream: apiClient.chatStream,
|
|
183
|
+
extractToolCalls: (reply, options = {}) => extractToolCalls(reply, {
|
|
184
|
+
repairMalformedXml: !!getConfig().repair_malformed_tool_xml,
|
|
185
|
+
...options,
|
|
186
|
+
}),
|
|
187
|
+
agentExecShell,
|
|
188
|
+
agentExecFile,
|
|
189
|
+
describePermission,
|
|
190
|
+
permissionManager,
|
|
191
|
+
ui,
|
|
192
|
+
getConfig,
|
|
193
|
+
agentDefs: discoverAgentDefs({ cwd: process.cwd() }),
|
|
194
|
+
maxConcurrency: getConfig().subagents && getConfig().subagents.max_concurrency,
|
|
195
|
+
});
|
|
196
|
+
registerDynamicTool(buildSpawnAgentEntry(subagentManager));
|
|
197
|
+
} catch { /* subagents are best-effort; never block startup */ }
|
|
198
|
+
|
|
74
199
|
const commands = createCommands({
|
|
75
200
|
getConfig,
|
|
76
201
|
setConfig,
|
|
@@ -80,6 +205,7 @@ const commands = createCommands({
|
|
|
80
205
|
runAgentLoop,
|
|
81
206
|
readFileContext,
|
|
82
207
|
agentExecShell,
|
|
208
|
+
checkpointStore,
|
|
83
209
|
});
|
|
84
210
|
|
|
85
211
|
async function main() {
|
|
@@ -92,6 +218,24 @@ async function main() {
|
|
|
92
218
|
|
|
93
219
|
const command = rawArgs[0];
|
|
94
220
|
|
|
221
|
+
// Internal entry (Task 5.3): the detached child of a background task. Not a
|
|
222
|
+
// user-facing command — `semalt-code run --background` spawns it. It reads its
|
|
223
|
+
// spec from <taskDir> and runs the agent via the SDK facade with the
|
|
224
|
+
// launch-fixed policy, writing progress/result/status into the task dir. No
|
|
225
|
+
// terminal to reach after this point — pure execution.
|
|
226
|
+
if (command === '__bg-exec') {
|
|
227
|
+
const taskDir = rawArgs[1];
|
|
228
|
+
if (!taskDir) { process.exit(1); }
|
|
229
|
+
const { runBackgroundChild } = require('./lib/background');
|
|
230
|
+
try {
|
|
231
|
+
const r = await runBackgroundChild({ taskDir });
|
|
232
|
+
process.exit(r && r.status === 'completed' ? 0 : 1);
|
|
233
|
+
} catch {
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
95
239
|
if (command === '--help' || command === '-h') {
|
|
96
240
|
writer.scrollback(`
|
|
97
241
|
Semalt.AI — Self-hosted AI Coding Assistant
|
|
@@ -104,17 +248,25 @@ Commands:
|
|
|
104
248
|
code <prompt> Generate code from a prompt
|
|
105
249
|
edit <file> <instruction> Edit a file with AI
|
|
106
250
|
shell <command> Run and optionally analyze a shell command
|
|
251
|
+
run --background <prompt> Launch a detached background agent task
|
|
252
|
+
tasks <subcmd> Manage background tasks: list | status | result | kill | prune
|
|
107
253
|
login Authorize CLI via browser
|
|
108
254
|
whoami Show current authorized user
|
|
109
255
|
logout Clear current CLI login
|
|
256
|
+
auth set-key [k] Store the API key in the OS keychain (not plaintext config)
|
|
257
|
+
mcp <subcmd> Manage MCP servers: list | status | add | remove | auth
|
|
110
258
|
models Choose a model
|
|
111
259
|
init Initialize config
|
|
260
|
+
rewind [seq] [code|conversation|both] List checkpoints or restore files and/or conversation (default both)
|
|
261
|
+
sandbox Show OS sandbox status (mode, tool, availability, network)
|
|
112
262
|
|
|
113
263
|
Options:
|
|
114
264
|
-m, --model <name> Model name
|
|
115
265
|
-r, --resume <chat-id> Resume a saved chat (chat command)
|
|
116
266
|
-f, --file <path> Load file into context (code command)
|
|
267
|
+
--image <path> Attach an image (PNG/JPEG/WebP/GIF); repeatable
|
|
117
268
|
-a, --analyze Analyze output with AI (shell command)
|
|
269
|
+
-b, --background Launch as a detached background task (run command)
|
|
118
270
|
--dry-run Don't save changes (edit command)
|
|
119
271
|
--api-base <url> API base URL (init)
|
|
120
272
|
--api-key <key> API key (init)
|
|
@@ -132,8 +284,20 @@ Options:
|
|
|
132
284
|
--allow-net Auto-approve network operations
|
|
133
285
|
--allow-all Auto-approve everything (use carefully)
|
|
134
286
|
--allow-anywhere Allow writes outside the project CWD and in sensitive dirs
|
|
287
|
+
--no-network Kernel-level no-network for sandboxed shell commands
|
|
288
|
+
(bwrap --unshare-net / Seatbelt deny). Binary on/off —
|
|
289
|
+
no host proxy, no domain allowlist, no TLS interception.
|
|
290
|
+
Same effect as sandbox.network "off" in config.
|
|
135
291
|
--readonly Block all write operations
|
|
136
|
-
--
|
|
292
|
+
--max-iterations <n> Cap agent-loop iterations per turn (default 125);
|
|
293
|
+
0 or "unlimited" removes the cap (power-user choice)
|
|
294
|
+
--no-verify Skip self-verification (config.verify) for this run
|
|
295
|
+
--dangerously-skip-permissions
|
|
296
|
+
DANGER: fully auto-approve every tool call AND disable
|
|
297
|
+
the destructive-command deny-list and config-file read
|
|
298
|
+
guard. The only way to auto-approve in non-TTY mode;
|
|
299
|
+
without it, headless runs refuse calls that would need
|
|
300
|
+
interactive confirmation. Use only in trusted sandboxes.
|
|
137
301
|
-v, --version Show CLI version
|
|
138
302
|
|
|
139
303
|
Config: ${CONFIG_PATH}
|
|
@@ -166,11 +330,45 @@ Config: ${CONFIG_PATH}
|
|
|
166
330
|
await commands.cmdWhoAmI();
|
|
167
331
|
} else if (command === 'logout') {
|
|
168
332
|
await commands.cmdLogout();
|
|
333
|
+
} else if (command === 'auth') {
|
|
334
|
+
const sub = rawArgs[1];
|
|
335
|
+
if (sub === 'set-key') {
|
|
336
|
+
await commands.cmdAuthSetKey(rawArgs[2]);
|
|
337
|
+
} else {
|
|
338
|
+
process.stderr.write(`Usage: semalt-code auth set-key [key]\n`);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
} else if (command === 'run') {
|
|
342
|
+
const { opts, positional } = parseArgs(rawArgs.slice(1));
|
|
343
|
+
if (opts.background) {
|
|
344
|
+
await commands.cmdRun(opts, positional);
|
|
345
|
+
} else {
|
|
346
|
+
// `run` without --background is a foreground one-shot, like `code`.
|
|
347
|
+
await commands.cmdCode(opts, positional);
|
|
348
|
+
}
|
|
349
|
+
} else if (command === 'tasks') {
|
|
350
|
+
await commands.cmdTasks(rawArgs[1], rawArgs.slice(2));
|
|
351
|
+
} else if (command === 'mcp') {
|
|
352
|
+
await commands.cmdMcp(rawArgs[1], rawArgs.slice(2));
|
|
169
353
|
} else if (command === 'models') {
|
|
170
354
|
await commands.cmdModels();
|
|
171
355
|
} else if (command === 'init') {
|
|
172
356
|
const { opts } = parseArgs(rawArgs.slice(1));
|
|
173
357
|
commands.cmdInit(opts);
|
|
358
|
+
} else if (command === 'doctor') {
|
|
359
|
+
const { diagnose, formatDoctorReport } = require('./lib/doctor');
|
|
360
|
+
const ping = async () => {
|
|
361
|
+
const cfg = getConfig();
|
|
362
|
+
if (!cfg.auth_token) return null;
|
|
363
|
+
try { const r = await apiClient.dashboardWhoAmI(); return !!(r && r.user); } catch { return false; }
|
|
364
|
+
};
|
|
365
|
+
const result = await diagnose({ getConfig, pingDashboard: ping });
|
|
366
|
+
writer.scrollback(formatDoctorReport(result));
|
|
367
|
+
await writer.flush();
|
|
368
|
+
} else if (command === 'sandbox') {
|
|
369
|
+
const { sandboxStatusReport } = require('./lib/sandbox');
|
|
370
|
+
writer.scrollback(sandboxStatusReport({ getConfig }));
|
|
371
|
+
await writer.flush();
|
|
174
372
|
} else if (command === 'audit') {
|
|
175
373
|
try {
|
|
176
374
|
const content = fs.readFileSync(AUDIT_LOG, 'utf8');
|
|
@@ -189,6 +387,43 @@ Config: ${CONFIG_PATH}
|
|
|
189
387
|
writer.scrollback('No audit log found.');
|
|
190
388
|
}
|
|
191
389
|
await writer.flush();
|
|
390
|
+
} else if (command === 'rewind') {
|
|
391
|
+
// Standalone rewind: a fresh process with no in-memory session, so target
|
|
392
|
+
// the most-recently-active session's checkpoints. Conversation rewind here
|
|
393
|
+
// operates on the saved session file (SessionStorage) of the same id.
|
|
394
|
+
const { formatCheckpointList, formatRewindResult, normalizeRewindMode, REWIND_MODES } = require('./lib/checkpoints');
|
|
395
|
+
const { SessionStorage } = require('./lib/storage');
|
|
396
|
+
const force = rawArgs.includes('--force') || rawArgs.includes('force');
|
|
397
|
+
const tokens = rawArgs.slice(1).filter((a) => a !== '--force' && a !== 'force');
|
|
398
|
+
const modeToken = tokens.find((t) => REWIND_MODES.includes(String(t).toLowerCase()));
|
|
399
|
+
const mode = normalizeRewindMode(modeToken);
|
|
400
|
+
const target = tokens.find((t) => t !== modeToken);
|
|
401
|
+
const session = latestSession();
|
|
402
|
+
if (!session) {
|
|
403
|
+
writer.scrollback('No checkpoints found.');
|
|
404
|
+
await writer.flush();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const store = createCheckpointStore({ getConfig, sessionId: session, restoreGuard });
|
|
408
|
+
if (!target || target === 'list') {
|
|
409
|
+
writer.scrollback(formatCheckpointList(store.list(session), { session }));
|
|
410
|
+
} else if (mode === null) {
|
|
411
|
+
writer.scrollback(`Unknown rewind mode "${modeToken}". Use one of: ${REWIND_MODES.join(', ')}.`);
|
|
412
|
+
} else {
|
|
413
|
+
// Load the saved session's messages for a conversation/both rewind. The
|
|
414
|
+
// checkpoint session id matches the SessionStorage id (chat aligns them).
|
|
415
|
+
let saved = null;
|
|
416
|
+
const wantConversation = mode === 'conversation' || mode === 'both';
|
|
417
|
+
if (wantConversation) { try { saved = new SessionStorage().load(session); } catch { saved = null; } }
|
|
418
|
+
const messages = saved && Array.isArray(saved.messages) ? saved.messages : null;
|
|
419
|
+
const res = store.rewind(target === 'last' ? 'last' : target, { force, session, mode, messages });
|
|
420
|
+
if (res.conversation && res.conversation.ok && saved) {
|
|
421
|
+
saved.messages = res.conversation.messages;
|
|
422
|
+
try { new SessionStorage().save(saved); } catch { /* best effort */ }
|
|
423
|
+
}
|
|
424
|
+
writer.scrollback(formatRewindResult(res));
|
|
425
|
+
}
|
|
426
|
+
await writer.flush();
|
|
192
427
|
} else if (command === 'config') {
|
|
193
428
|
const sub = rawArgs[1];
|
|
194
429
|
if (sub === 'set') {
|
|
@@ -213,8 +448,14 @@ Config: ${CONFIG_PATH}
|
|
|
213
448
|
}
|
|
214
449
|
await writer.flush();
|
|
215
450
|
} else {
|
|
216
|
-
const { opts } = parseArgs(rawArgs);
|
|
217
|
-
|
|
451
|
+
const { opts, positional } = parseArgs(rawArgs);
|
|
452
|
+
// `-p/--print` (or any --output-format) turns a bare prompt into a headless
|
|
453
|
+
// one-shot run instead of opening interactive chat (Task 2.4).
|
|
454
|
+
if (opts.print) {
|
|
455
|
+
await commands.cmdCode(opts, positional);
|
|
456
|
+
} else {
|
|
457
|
+
await commands.cmdChat(opts);
|
|
458
|
+
}
|
|
218
459
|
}
|
|
219
460
|
}
|
|
220
461
|
|