@heysalad/cheri-cli 1.2.1 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,82 @@
2
2
 
3
3
  All notable changes to Cheri CLI will be documented in this file.
4
4
 
5
+ ## [1.3.1] - 2026-02-17
6
+
7
+ ### 🐛 Bug Fixes
8
+
9
+ #### Permission System
10
+ - **Fixed spinner blocking approval prompts**: Approval prompts now appear BEFORE spinners start, preventing terminal blocking
11
+ - **Added upfront permission request**: Tasks with write operations now ask for permissions at the start
12
+ - **Better user experience**: Users can approve entire session with [a]lways option
13
+
14
+ #### How It Works
15
+ 1. Agent analyzes task for write operations (build, create, modify, etc.)
16
+ 2. Shows upfront permission request: `[Y]es, [n]o, [a]lways`
17
+ 3. If approved, proceeds with task
18
+ 4. Individual risky operations still prompt for confirmation (unless 'always' was chosen)
19
+
20
+ #### Changes
21
+ - Moved approval logic before `ToolPanel` creation to avoid spinner blocking input
22
+ - Added `requestUpfrontPermissions()` function to analyze and request batch permissions
23
+ - Added `skipApproval` parameter to `executeTool()` to avoid double prompting
24
+ - Improved permission flow for better UX
25
+
26
+ ---
27
+
28
+ ## [1.3.0] - 2026-02-17
29
+
30
+ ### 🎯 Major Feature - Token-Tracked Sessions with Predictable Costs
31
+
32
+ #### Durable Objects Backend
33
+ - **Automatic Workspace Creation**: Workspaces are automatically initialized on login
34
+ - **Session Management**: Each chat is tracked as a separate session with token limits
35
+ - **Token Tracking**: Real-time tracking of input and output tokens
36
+ - **Cost Calculation**: Automatic cost estimation based on AWS Bedrock pricing
37
+ - Claude Sonnet 4: $3/M input, $15/M output
38
+ - Max ~$9 per 1M token session
39
+ - **Auto-Compaction**: Sessions automatically compact at 90% of 1M token limit
40
+ - **Force Compaction**: Sessions force compact at 100% to maintain limits
41
+
42
+ #### New Cloud Tools
43
+ - `get_workspace_stats` - View monthly token usage, limits, and costs
44
+ - `list_sessions` - List all chat sessions with token tracking
45
+ - `get_session_cost` - Get detailed cost breakdown for a specific session
46
+
47
+ #### Backend Architecture
48
+ - **WorkspaceDO**: One Durable Object per user managing all sessions
49
+ - **SessionDO**: One Durable Object per chat tracking messages and tokens
50
+ - **Persistent State**: Automatic state management with Cloudflare Durable Objects
51
+ - **Scalable**: Handles unlimited users with per-user isolation
52
+
53
+ #### API Enhancements
54
+ - `POST /api/workspace/init` - Initialize user workspace
55
+ - `POST /api/workspace/session/create` - Create new tracked session
56
+ - `GET /api/workspace/sessions` - List recent sessions
57
+ - `GET /api/workspace/session/:id` - Get specific session details
58
+ - `GET /api/workspace/stats` - Get workspace statistics
59
+ - `POST /api/session/:id/message` - Add message with token tracking
60
+ - `GET /api/session/:id/cost` - Get session cost estimate
61
+ - `POST /api/session/:id/compact` - Manual session compaction
62
+
63
+ ### ✨ Improved
64
+ - **Login Flow**: Now automatically initializes workspace with token tracking
65
+ - **Cost Visibility**: Users can see token usage and costs at any time
66
+ - **Predictable Billing**: 1M token limit per chat ensures known maximum costs
67
+
68
+ ### 📚 Documentation
69
+ - Added `ARCHITECTURE_PLAN.md` with complete system design
70
+ - Added `DURABLE_OBJECTS_STARTER.ts` with implementation reference
71
+ - Added `wrangler.toml` for Cloudflare Workers configuration
72
+
73
+ ### 🔧 Technical Details
74
+ - Integrated Durable Objects into existing Cloudflare Workers backend
75
+ - Added TypeScript interfaces for Workspace and Session types
76
+ - Implemented token estimation based on character count (~4 chars/token)
77
+ - Added pricing constants for multiple AI models (Claude, Nova)
78
+
79
+ ---
80
+
5
81
  ## [1.1.0] - 2026-02-17
6
82
 
7
83
  ### ✨ Added - Beautiful UI & Animations
package/README.md CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  CLI for [Cheri](https://cheri.heysalad.app) — the AI-powered cloud IDE that never forgets.
4
4
 
5
- Manage workspaces, track API usage, and access your AI memory from the terminal.
5
+ **Features:**
6
+ - 🤖 AI-powered coding agent with tool execution
7
+ - 📊 Token-tracked sessions with predictable costs
8
+ - ☁️ Cloud workspace management
9
+ - 💾 Persistent memory system
10
+ - 🔧 Automatic workspace initialization
6
11
 
7
12
  ## Install
8
13
 
@@ -15,14 +20,14 @@ Requires Node.js 18+.
15
20
  ## Quick Start
16
21
 
17
22
  ```bash
18
- # Authenticate with your Cheri account
23
+ # Authenticate with your Cheri account (auto-initializes workspace)
19
24
  cheri login
20
25
 
21
- # Launch a cloud workspace
22
- cheri workspace launch owner/my-repo
26
+ # Start coding with AI agent
27
+ cheri agent "create a todo list app"
23
28
 
24
- # Check account status
25
- cheri status
29
+ # Check token usage and costs
30
+ cheri stats
26
31
 
27
32
  # View API usage and rate limits
28
33
  cheri usage
@@ -30,19 +35,40 @@ cheri usage
30
35
 
31
36
  ## Commands
32
37
 
38
+ ### AI Agent
33
39
  | Command | Description |
34
40
  |---|---|
35
- | `cheri login` | Authenticate with GitHub |
41
+ | `cheri agent <task>` | Start AI coding agent for a task |
42
+ | `cheri agent -i` | Interactive agent mode |
43
+ | `cheri agent -r <session-id>` | Resume a previous session |
44
+
45
+ ### Account & Authentication
46
+ | Command | Description |
47
+ |---|---|
48
+ | `cheri login` | Authenticate with GitHub (auto-initializes workspace) |
36
49
  | `cheri status` | Show account and workspace status |
37
50
  | `cheri usage` | Show API usage and rate limit status |
51
+ | `cheri stats` | Show token usage statistics and costs |
52
+
53
+ ### Workspaces
54
+ | Command | Description |
55
+ |---|---|
38
56
  | `cheri workspace launch <repo>` | Launch a new cloud workspace |
39
57
  | `cheri workspace list` | List all workspaces |
40
58
  | `cheri workspace stop <id>` | Stop a running workspace |
41
59
  | `cheri workspace status <id>` | Get workspace status |
60
+
61
+ ### Memory
62
+ | Command | Description |
63
+ |---|---|
42
64
  | `cheri memory show` | Show current memory entries |
43
65
  | `cheri memory add <text>` | Add a memory entry |
44
66
  | `cheri memory clear` | Clear all memory |
45
67
  | `cheri memory export` | Export memory to JSON |
68
+
69
+ ### Configuration
70
+ | Command | Description |
71
+ |---|---|
46
72
  | `cheri config list` | Show all configuration |
47
73
  | `cheri config get <key>` | Get a config value |
48
74
  | `cheri config set <key> <value>` | Set a config value |
@@ -60,6 +86,26 @@ $ cheri
60
86
  🍒 cheri > exit
61
87
  ```
62
88
 
89
+ ## Token Limits & Costs
90
+
91
+ ### Per Session (Chat)
92
+ - **Max Tokens**: 1,000,000 tokens per chat
93
+ - **Auto-Compact**: Triggers at 900K tokens (90%)
94
+ - **Force Compact**: Triggers at 1M tokens (100%)
95
+
96
+ ### Monthly Limits
97
+ | Plan | Sessions | Tokens/Month | Max Cost/Month |
98
+ |---|---|---|---|
99
+ | Free | 10 | 10M tokens | ~$90 |
100
+ | Pro | 100 | 100M tokens | ~$900 |
101
+
102
+ ### Pricing (AWS Bedrock Claude Sonnet 4)
103
+ - **Input**: $3.00 per 1M tokens
104
+ - **Output**: $15.00 per 1M tokens
105
+ - **Max per chat**: ~$9.00 (at 1M tokens, 50/50 split)
106
+
107
+ Use `cheri stats` to check your current token usage and costs.
108
+
63
109
  ## Rate Limits
64
110
 
65
111
  | Plan | Limit |
@@ -77,12 +123,6 @@ Config is stored in `~/.cheri/`. Set the API URL if self-hosting:
77
123
  cheri config set apiUrl https://your-instance.example.com
78
124
  ```
79
125
 
80
- ## Links
81
-
82
- - [Cheri Cloud IDE](https://cheri.heysalad.app)
83
- - [Dashboard](https://cheri.heysalad.app/dashboard)
84
- - [GitHub](https://github.com/chilu18/cloud-ide)
85
-
86
126
  ## 🔗 Links
87
127
 
88
128
  - **Homepage**: [cheri.heysalad.app](https://cheri.heysalad.app)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@heysalad/cheri-cli",
3
- "version": "1.2.1",
4
- "description": "Cheri CLI - AI-powered cloud IDE by HeySalad®. Like Claude Code, but for cloud workspaces. Now with beautiful animations and enhanced UI!",
3
+ "version": "1.3.1",
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": {
7
7
  "cheri": "./bin/cheri.js"
@@ -50,6 +50,9 @@ const CLOUD_TOOLS = [
50
50
  { name: "get_usage", description: "Get the user's API usage and rate limit statistics", parameters: { type: "object", properties: {}, required: [] } },
51
51
  { name: "get_config", description: "Get a configuration value by key (dot notation supported)", parameters: { type: "object", properties: { key: { type: "string", description: "Config key, e.g. 'ai.provider'" } }, required: ["key"] } },
52
52
  { name: "set_config", description: "Set a configuration value", parameters: { type: "object", properties: { key: { type: "string", description: "Config key" }, value: { type: "string", description: "Value to set" } }, required: ["key", "value"] } },
53
+ { name: "get_workspace_stats", description: "Get token usage statistics for your workspace (monthly usage, limits, costs)", parameters: { type: "object", properties: {}, required: [] } },
54
+ { name: "list_sessions", description: "List recent chat sessions with token tracking", parameters: { type: "object", properties: {}, required: [] } },
55
+ { name: "get_session_cost", description: "Get detailed token usage and cost estimate for a specific session", parameters: { type: "object", properties: { sessionId: { type: "string", description: "Session ID to query" } }, required: ["sessionId"] } },
53
56
  ];
54
57
 
55
58
  // ── Agent meta-tools ────────────────────────────────────────────────────────
@@ -99,6 +102,9 @@ async function executeCloudTool(name, args) {
99
102
  setConfigValue(args.key, args.value);
100
103
  return { key: args.key, value: args.value, status: "updated" };
101
104
  }
105
+ case "get_workspace_stats": return await apiClient.getWorkspaceStats();
106
+ case "list_sessions": return await apiClient.listSessions();
107
+ case "get_session_cost": return await apiClient.getSessionCost(args.sessionId);
102
108
  default: return { error: `Unknown cloud tool: ${name}` };
103
109
  }
104
110
  } catch (err) {
@@ -175,7 +181,7 @@ async function executeLocalToolEnhanced(name, args) {
175
181
  // ── Main tool execution with approval + hooks + permissions ───────────────────
176
182
  let sessionAutoApprove = false;
177
183
 
178
- async function executeTool(name, args, orchestrator, allTools, parseSSE, mcpManager) {
184
+ async function executeTool(name, args, orchestrator, allTools, parseSSE, mcpManager, skipApproval = false) {
179
185
  // Permission rules check
180
186
  const permission = checkPermission(name, args);
181
187
  if (permission === "deny") return { error: `Tool ${name} is denied by permission rules` };
@@ -206,7 +212,9 @@ async function executeTool(name, args, orchestrator, allTools, parseSSE, mcpMana
206
212
  }
207
213
 
208
214
  // Local tools — enhanced approval system
209
- 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) {
210
218
  const decision = shouldApprove(name, args);
211
219
  if (decision === "deny") return { error: "Denied by approval policy" };
212
220
  if (decision === "ask" || decision === "suggest") {
@@ -280,6 +288,51 @@ async function* parseSSEStream(response) {
280
288
  }
281
289
  }
282
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
+
283
336
  // ── Active session state ──────────────────────────────────────────────────────
284
337
  let currentSession = null;
285
338
 
@@ -291,6 +344,13 @@ export async function runAgent(userRequest, options = {}) {
291
344
  const memoryContext = getMemoryContext();
292
345
  const skillContext = getSkillContext(plugins.skills, userRequest);
293
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
+
294
354
  // Create provider (defaults to Cheri cloud which uses AWS Bedrock)
295
355
  const providerName = getConfigValue("agent.provider") || getConfigValue("ai.provider") || "cheri";
296
356
  let provider;
@@ -561,7 +621,26 @@ export async function runAgent(userRequest, options = {}) {
561
621
  const isMcp = mcpManager.isMcpTool(tc.name);
562
622
  const isLocal = !CLOUD_TOOL_NAMES.has(tc.name) && !AGENT_TOOL_NAMES.has(tc.name) && !isMcp;
563
623
 
564
- // 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)
565
644
  const panel = new ToolPanel(tc.name, input);
566
645
 
567
646
  // Command safety label for run_command
@@ -571,7 +650,8 @@ export async function runAgent(userRequest, options = {}) {
571
650
  panel.update(`running ${safetyLabel}`);
572
651
  }
573
652
 
574
- 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);
575
655
 
576
656
  // Determine result message based on tool type
577
657
  let resultMsg = '';
@@ -63,6 +63,16 @@ export async function loginFlow() {
63
63
  log.blank();
64
64
  log.success(`Logged in as ${chalk.cyan(me.ghLogin || me.userId)}`);
65
65
  log.keyValue("Plan", me.plan === "pro" ? chalk.green("Pro") : "Free");
66
+
67
+ // Initialize workspace for token tracking
68
+ try {
69
+ await apiClient.initWorkspace();
70
+ log.dim("✓ Workspace initialized with token tracking");
71
+ } catch (workspaceErr) {
72
+ // Workspace init is non-critical, just log it
73
+ log.dim(`⚠ Workspace init skipped: ${workspaceErr.message}`);
74
+ }
75
+
66
76
  log.blank();
67
77
  log.tip("Using Cheri cloud service (AWS Bedrock). Run 'cheri agent' to start coding!");
68
78
  } catch (err) {
@@ -128,4 +128,42 @@ export const apiClient = {
128
128
  async getModels() {
129
129
  return request("/api/chat/models");
130
130
  },
131
+
132
+ // Token-Tracked Workspaces & Sessions
133
+ async initWorkspace() {
134
+ return request("/api/workspace/init", { method: "POST" });
135
+ },
136
+
137
+ async createSession() {
138
+ return request("/api/workspace/session/create", { method: "POST" });
139
+ },
140
+
141
+ async listSessions() {
142
+ return request("/api/workspace/sessions");
143
+ },
144
+
145
+ async getSession(sessionId) {
146
+ return request(`/api/workspace/session/${encodeURIComponent(sessionId)}`);
147
+ },
148
+
149
+ async getWorkspaceStats() {
150
+ return request("/api/workspace/stats");
151
+ },
152
+
153
+ async addSessionMessage(sessionId, role, content, tokens = null) {
154
+ return request(`/api/session/${encodeURIComponent(sessionId)}/message`, {
155
+ method: "POST",
156
+ body: JSON.stringify({ role, content, tokens }),
157
+ });
158
+ },
159
+
160
+ async getSessionCost(sessionId) {
161
+ return request(`/api/session/${encodeURIComponent(sessionId)}/cost`);
162
+ },
163
+
164
+ async compactSession(sessionId) {
165
+ return request(`/api/session/${encodeURIComponent(sessionId)}/compact`, {
166
+ method: "POST",
167
+ });
168
+ },
131
169
  };