@heysalad/cheri-cli 1.3.0 โ†’ 1.3.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,55 @@
2
2
 
3
3
  All notable changes to Cheri CLI will be documented in this file.
4
4
 
5
+ ## [1.3.2] - 2026-02-17
6
+
7
+ ### ๐Ÿ”ง Improvements
8
+
9
+ #### CLI Core
10
+ - **Version from package.json**: CLI now reads version dynamically from `package.json` instead of hardcoding
11
+ - **Agent & usage commands registered**: `cheri agent` and `cheri usage` now properly registered in the main binary
12
+ - **Interactive REPL on no args**: Running `cheri` with no arguments now launches the interactive REPL instead of showing help
13
+
14
+ #### Configuration
15
+ - **Multi-layer config system**: Config now merges defaults โ†’ user config โ†’ project config โ†’ env vars (highest priority)
16
+ - **Deep merge**: Nested config objects merge correctly instead of overwriting
17
+ - **Environment variable support**: All settings overridable via `CHERI_*` env vars
18
+
19
+ #### Commands
20
+ - **workspace**: Extracted `launchWorkspace()`, `stopWorkspace()`, `listWorkspaces()` as importable functions
21
+ - **status**: Extracted `showStatus()` as importable function for use in REPL
22
+ - **memory, config, init**: Consistent refactor to exportable functions
23
+
24
+ #### Backend
25
+ - **Default model switched to GPT-OSS 120B**: Faster true streaming via Bedrock OpenAI-compatible endpoint
26
+ - **Durable Objects fixed**: WorkspaceDO and SessionDO now correctly bound in deployed worker
27
+ - **Claude Sonnet 4.5 added**: Available as selectable model (`us.anthropic.claude-sonnet-4-5-20250929-v1:0`)
28
+
29
+ ---
30
+
31
+ ## [1.3.1] - 2026-02-17
32
+
33
+ ### ๐Ÿ› Bug Fixes
34
+
35
+ #### Permission System
36
+ - **Fixed spinner blocking approval prompts**: Approval prompts now appear BEFORE spinners start, preventing terminal blocking
37
+ - **Added upfront permission request**: Tasks with write operations now ask for permissions at the start
38
+ - **Better user experience**: Users can approve entire session with [a]lways option
39
+
40
+ #### How It Works
41
+ 1. Agent analyzes task for write operations (build, create, modify, etc.)
42
+ 2. Shows upfront permission request: `[Y]es, [n]o, [a]lways`
43
+ 3. If approved, proceeds with task
44
+ 4. Individual risky operations still prompt for confirmation (unless 'always' was chosen)
45
+
46
+ #### Changes
47
+ - Moved approval logic before `ToolPanel` creation to avoid spinner blocking input
48
+ - Added `requestUpfrontPermissions()` function to analyze and request batch permissions
49
+ - Added `skipApproval` parameter to `executeTool()` to avoid double prompting
50
+ - Improved permission flow for better UX
51
+
52
+ ---
53
+
5
54
  ## [1.3.0] - 2026-02-17
6
55
 
7
56
  ### ๐ŸŽฏ Major Feature - Token-Tracked Sessions with Predictable Costs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heysalad/cheri-cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Cheri CLI - AI-powered cloud IDE by HeySaladยฎ. With token-tracked sessions, predictable AI costs, and automatic workspace management.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -181,7 +181,7 @@ async function executeLocalToolEnhanced(name, args) {
181
181
  // โ”€โ”€ Main tool execution with approval + hooks + permissions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
182
182
  let sessionAutoApprove = false;
183
183
 
184
- async function executeTool(name, args, orchestrator, allTools, parseSSE, mcpManager) {
184
+ async function executeTool(name, args, orchestrator, allTools, parseSSE, mcpManager, skipApproval = false) {
185
185
  // Permission rules check
186
186
  const permission = checkPermission(name, args);
187
187
  if (permission === "deny") return { error: `Tool ${name} is denied by permission rules` };
@@ -212,7 +212,9 @@ async function executeTool(name, args, orchestrator, allTools, parseSSE, mcpMana
212
212
  }
213
213
 
214
214
  // Local tools โ€” enhanced approval system
215
- if (!sessionAutoApprove) {
215
+ // Note: For tools executed via executeOne in agent loop, approval is handled
216
+ // BEFORE spinner creation to avoid blocking terminal. This is a fallback.
217
+ if (!sessionAutoApprove && !skipApproval) {
216
218
  const decision = shouldApprove(name, args);
217
219
  if (decision === "deny") return { error: "Denied by approval policy" };
218
220
  if (decision === "ask" || decision === "suggest") {
@@ -286,6 +288,51 @@ async function* parseSSEStream(response) {
286
288
  }
287
289
  }
288
290
 
291
+ // โ”€โ”€ Upfront permission request โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
292
+ async function requestUpfrontPermissions(userRequest) {
293
+ const approvalMode = getApprovalMode();
294
+
295
+ // Skip if in auto mode or already auto-approved this session
296
+ if (approvalMode === "auto" || sessionAutoApprove) {
297
+ return true;
298
+ }
299
+
300
+ // Check if request likely involves write operations
301
+ const writeKeywords = ['build', 'create', 'make', 'write', 'add', 'implement', 'generate', 'fix', 'update', 'modify', 'delete', 'install', 'setup'];
302
+ const hasWriteIntent = writeKeywords.some(kw => userRequest.toLowerCase().includes(kw));
303
+
304
+ if (!hasWriteIntent) {
305
+ return true; // No write operations expected, proceed
306
+ }
307
+
308
+ // Show upfront permission request
309
+ console.log("");
310
+ log.info("This task may require file modifications and command execution.");
311
+ console.log("");
312
+
313
+ return new Promise((resolve) => {
314
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
315
+ rl.question(
316
+ chalk.yellow(` Grant permissions for this session? ${chalk.dim("[Y]es, [n]o, [a]lways")} `),
317
+ (answer) => {
318
+ rl.close();
319
+ const a = answer.trim().toLowerCase();
320
+ if (a === "a" || a === "always") {
321
+ sessionAutoApprove = true;
322
+ log.success("Auto-approve enabled for this session");
323
+ resolve(true);
324
+ } else if (a === "n" || a === "no") {
325
+ log.warn("Permissions denied");
326
+ resolve(false);
327
+ } else {
328
+ log.success("Permissions granted - will ask before risky operations");
329
+ resolve(true);
330
+ }
331
+ }
332
+ );
333
+ });
334
+ }
335
+
289
336
  // โ”€โ”€ Active session state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
290
337
  let currentSession = null;
291
338
 
@@ -297,6 +344,13 @@ export async function runAgent(userRequest, options = {}) {
297
344
  const memoryContext = getMemoryContext();
298
345
  const skillContext = getSkillContext(plugins.skills, userRequest);
299
346
 
347
+ // Request upfront permissions if needed
348
+ const hasPermission = await requestUpfrontPermissions(userRequest);
349
+ if (!hasPermission) {
350
+ log.error("Task cancelled - permissions denied");
351
+ return;
352
+ }
353
+
300
354
  // Create provider (defaults to Cheri cloud which uses AWS Bedrock)
301
355
  const providerName = getConfigValue("agent.provider") || getConfigValue("ai.provider") || "cheri";
302
356
  let provider;
@@ -567,7 +621,26 @@ export async function runAgent(userRequest, options = {}) {
567
621
  const isMcp = mcpManager.isMcpTool(tc.name);
568
622
  const isLocal = !CLOUD_TOOL_NAMES.has(tc.name) && !AGENT_TOOL_NAMES.has(tc.name) && !isMcp;
569
623
 
570
- // Create animated panel for this tool execution
624
+ // Check if approval is needed BEFORE creating spinner
625
+ let needsApproval = false;
626
+ if (isLocal && !sessionAutoApprove) {
627
+ const decision = shouldApprove(tc.name, input);
628
+ if (decision === "ask" || decision === "suggest") {
629
+ needsApproval = true;
630
+
631
+ // Get approval WITHOUT spinner running
632
+ const approved = await promptApproval(tc.name, input, decision);
633
+ if (approved === "auto") {
634
+ sessionAutoApprove = true;
635
+ } else if (!approved) {
636
+ return { id: tc.id, result: { error: "User denied execution" } };
637
+ }
638
+ } else if (decision === "deny") {
639
+ return { id: tc.id, result: { error: "Denied by approval policy" } };
640
+ }
641
+ }
642
+
643
+ // Create animated panel for this tool execution (AFTER approval)
571
644
  const panel = new ToolPanel(tc.name, input);
572
645
 
573
646
  // Command safety label for run_command
@@ -577,7 +650,8 @@ export async function runAgent(userRequest, options = {}) {
577
650
  panel.update(`running ${safetyLabel}`);
578
651
  }
579
652
 
580
- const result = await executeTool(tc.name, input, orchestrator, ALL_TOOLS, parseSSEStream, mcpManager);
653
+ // Skip approval in executeTool since we already handled it above
654
+ const result = await executeTool(tc.name, input, orchestrator, ALL_TOOLS, parseSSEStream, mcpManager, needsApproval);
581
655
 
582
656
  // Determine result message based on tool type
583
657
  let resultMsg = '';