@heysalad/cheri-cli 0.4.0 → 0.6.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/README.md +58 -30
- package/bin/cheri.js +3 -3
- package/package.json +7 -13
- package/src/commands/agent.js +70 -50
- package/src/commands/memory.js +4 -12
- package/src/commands/usage.js +64 -0
- package/src/lib/api-client.js +40 -0
- package/src/lib/config-store.js +0 -10
- package/src/lib/logger.js +1 -1
- package/src/repl.js +18 -22
- package/src/commands/chat.js +0 -15
- package/src/lib/branding.js +0 -36
- package/src/lib/deepseek-client.js +0 -64
- package/src/lib/providers/anthropic.js +0 -66
- package/src/lib/providers/base.js +0 -34
- package/src/lib/providers/gemini.js +0 -89
- package/src/lib/providers/index.js +0 -47
- package/src/lib/providers/openai.js +0 -105
- package/src/lib/renderer.js +0 -44
- package/src/lib/repl.js +0 -225
- package/src/lib/tools/command-tools.js +0 -34
- package/src/lib/tools/file-tools.js +0 -73
- package/src/lib/tools/index.js +0 -32
- package/src/lib/tools/search-tools.js +0 -95
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cheri-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for [Cheri](https://cheri.heysalad.app) — the AI-powered cloud IDE that never forgets.
|
|
4
|
+
|
|
5
|
+
Manage workspaces, track API usage, and access your AI memory from the terminal.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -8,53 +10,79 @@ AI-powered cloud IDE by [HeySalad](https://heysalad.app). Like Claude Code, but
|
|
|
8
10
|
npm install -g @heysalad/cheri-cli
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Requires Node.js 18+.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
12
16
|
|
|
13
17
|
```bash
|
|
14
|
-
#
|
|
18
|
+
# Authenticate with your Cheri account
|
|
15
19
|
cheri login
|
|
16
20
|
|
|
21
|
+
# Launch a cloud workspace
|
|
22
|
+
cheri workspace launch owner/my-repo
|
|
23
|
+
|
|
17
24
|
# Check account status
|
|
18
25
|
cheri status
|
|
19
26
|
|
|
20
|
-
#
|
|
21
|
-
cheri
|
|
22
|
-
|
|
23
|
-
# List your workspaces
|
|
24
|
-
cheri workspace list
|
|
27
|
+
# View API usage and rate limits
|
|
28
|
+
cheri usage
|
|
29
|
+
```
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
| Command | Description |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `cheri login` | Authenticate with GitHub |
|
|
36
|
+
| `cheri status` | Show account and workspace status |
|
|
37
|
+
| `cheri usage` | Show API usage and rate limit status |
|
|
38
|
+
| `cheri workspace launch <repo>` | Launch a new cloud workspace |
|
|
39
|
+
| `cheri workspace list` | List all workspaces |
|
|
40
|
+
| `cheri workspace stop <id>` | Stop a running workspace |
|
|
41
|
+
| `cheri workspace status <id>` | Get workspace status |
|
|
42
|
+
| `cheri memory show` | Show current memory entries |
|
|
43
|
+
| `cheri memory add <text>` | Add a memory entry |
|
|
44
|
+
| `cheri memory clear` | Clear all memory |
|
|
45
|
+
| `cheri memory export` | Export memory to JSON |
|
|
46
|
+
| `cheri config list` | Show all configuration |
|
|
47
|
+
| `cheri config get <key>` | Get a config value |
|
|
48
|
+
| `cheri config set <key> <value>` | Set a config value |
|
|
49
|
+
| `cheri init` | Initialize a project |
|
|
50
|
+
|
|
51
|
+
## Interactive REPL
|
|
52
|
+
|
|
53
|
+
Run `cheri` with no arguments to enter the interactive REPL:
|
|
28
54
|
|
|
29
|
-
|
|
30
|
-
cheri
|
|
55
|
+
```
|
|
56
|
+
$ cheri
|
|
57
|
+
🍒 cheri > help
|
|
58
|
+
🍒 cheri > workspace list
|
|
59
|
+
🍒 cheri > usage
|
|
60
|
+
🍒 cheri > exit
|
|
61
|
+
```
|
|
31
62
|
|
|
32
|
-
|
|
33
|
-
cheri memory show
|
|
34
|
-
cheri memory add "Always use TypeScript strict mode"
|
|
35
|
-
cheri memory clear
|
|
63
|
+
## Rate Limits
|
|
36
64
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
65
|
+
| Plan | Limit |
|
|
66
|
+
|---|---|
|
|
67
|
+
| Free | 100 requests/hour |
|
|
68
|
+
| Pro | 1,000 requests/hour |
|
|
41
69
|
|
|
42
|
-
|
|
70
|
+
Use `cheri usage` to check your current rate limit status.
|
|
43
71
|
|
|
44
|
-
|
|
45
|
-
2. **`cheri workspace launch`** spins up a cloud workspace with code-server (VS Code in browser)
|
|
46
|
-
3. **`cheri memory`** stores persistent context that follows you across sessions
|
|
47
|
-
4. **`cheri init`** creates a local `.ai/` directory with project constitution files
|
|
72
|
+
## Configuration
|
|
48
73
|
|
|
49
|
-
|
|
74
|
+
Config is stored in `~/.cheri/`. Set the API URL if self-hosting:
|
|
50
75
|
|
|
51
|
-
|
|
76
|
+
```bash
|
|
77
|
+
cheri config set apiUrl https://your-instance.example.com
|
|
78
|
+
```
|
|
52
79
|
|
|
53
80
|
## Links
|
|
54
81
|
|
|
55
82
|
- [Cheri Cloud IDE](https://cheri.heysalad.app)
|
|
56
|
-
- [
|
|
83
|
+
- [Dashboard](https://cheri.heysalad.app/dashboard)
|
|
84
|
+
- [GitHub](https://github.com/chilu18/cloud-ide)
|
|
57
85
|
|
|
58
86
|
## License
|
|
59
87
|
|
|
60
|
-
MIT
|
|
88
|
+
MIT
|
package/bin/cheri.js
CHANGED
|
@@ -7,13 +7,13 @@ import { registerStatusCommand } from "../src/commands/status.js";
|
|
|
7
7
|
import { registerMemoryCommand } from "../src/commands/memory.js";
|
|
8
8
|
import { registerConfigCommand } from "../src/commands/config.js";
|
|
9
9
|
import { registerWorkspaceCommand } from "../src/commands/workspace.js";
|
|
10
|
-
import {
|
|
10
|
+
import { registerUsageCommand } from "../src/commands/usage.js";
|
|
11
11
|
import { registerAgentCommand } from "../src/commands/agent.js";
|
|
12
12
|
|
|
13
13
|
program
|
|
14
14
|
.name("cheri")
|
|
15
15
|
.description("Cheri CLI - AI-powered cloud IDE by HeySalad")
|
|
16
|
-
.version("0.
|
|
16
|
+
.version("0.1.0");
|
|
17
17
|
|
|
18
18
|
registerLoginCommand(program);
|
|
19
19
|
registerInitCommand(program);
|
|
@@ -21,7 +21,7 @@ registerStatusCommand(program);
|
|
|
21
21
|
registerMemoryCommand(program);
|
|
22
22
|
registerConfigCommand(program);
|
|
23
23
|
registerWorkspaceCommand(program);
|
|
24
|
-
|
|
24
|
+
registerUsageCommand(program);
|
|
25
25
|
registerAgentCommand(program);
|
|
26
26
|
|
|
27
27
|
// If no args, launch interactive command REPL
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heysalad/cheri-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Cheri CLI - AI-powered cloud IDE by HeySalad. Like Claude Code, but for cloud workspaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,15 +8,14 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
|
-
"src/"
|
|
12
|
-
"README.md"
|
|
11
|
+
"src/"
|
|
13
12
|
],
|
|
14
13
|
"scripts": {
|
|
15
14
|
"start": "node bin/cheri.js",
|
|
16
15
|
"dev": "node bin/cheri.js",
|
|
17
|
-
"release:patch": "npm version patch && npm publish
|
|
18
|
-
"release:minor": "npm version minor && npm publish
|
|
19
|
-
"release:major": "npm version major && npm publish
|
|
16
|
+
"release:patch": "npm version patch && npm publish && git push && git push --tags",
|
|
17
|
+
"release:minor": "npm version minor && npm publish && git push && git push --tags",
|
|
18
|
+
"release:major": "npm version major && npm publish && git push && git push --tags"
|
|
20
19
|
},
|
|
21
20
|
"keywords": [
|
|
22
21
|
"cloud-ide",
|
|
@@ -29,23 +28,18 @@
|
|
|
29
28
|
],
|
|
30
29
|
"repository": {
|
|
31
30
|
"type": "git",
|
|
32
|
-
"url": "https://github.com/
|
|
31
|
+
"url": "https://github.com/chilu18/cloud-ide.git",
|
|
32
|
+
"directory": "cli"
|
|
33
33
|
},
|
|
34
|
-
"homepage": "https://cheri.heysalad.app",
|
|
35
34
|
"author": "HeySalad",
|
|
36
35
|
"license": "MIT",
|
|
37
36
|
"engines": {
|
|
38
37
|
"node": ">=18"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
41
|
-
"@anthropic-ai/sdk": "^0.74.0",
|
|
42
|
-
"@google/generative-ai": "^0.24.1",
|
|
43
40
|
"chalk": "^5.3.0",
|
|
44
41
|
"commander": "^12.1.0",
|
|
45
42
|
"inquirer": "^9.2.23",
|
|
46
|
-
"marked": "^15.0.12",
|
|
47
|
-
"marked-terminal": "^7.3.0",
|
|
48
|
-
"openai": "^6.22.0",
|
|
49
43
|
"ora": "^8.0.1"
|
|
50
44
|
}
|
|
51
45
|
}
|
package/src/commands/agent.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { apiClient } from "../lib/api-client.js";
|
|
2
|
-
import { getConfigValue, setConfigValue } from "../lib/config-store.js";
|
|
3
|
-
import { streamChatCompletion } from "../lib/deepseek-client.js";
|
|
4
2
|
import { log } from "../lib/logger.js";
|
|
5
3
|
import chalk from "chalk";
|
|
6
4
|
|
|
@@ -30,9 +28,7 @@ const TOOLS = [
|
|
|
30
28
|
description: "Launch a new cloud workspace for a GitHub repository",
|
|
31
29
|
parameters: {
|
|
32
30
|
type: "object",
|
|
33
|
-
properties: {
|
|
34
|
-
repo: { type: "string", description: "GitHub repo in owner/name format" },
|
|
35
|
-
},
|
|
31
|
+
properties: { repo: { type: "string", description: "GitHub repo in owner/name format" } },
|
|
36
32
|
required: ["repo"],
|
|
37
33
|
},
|
|
38
34
|
},
|
|
@@ -44,9 +40,7 @@ const TOOLS = [
|
|
|
44
40
|
description: "Stop and delete a running workspace",
|
|
45
41
|
parameters: {
|
|
46
42
|
type: "object",
|
|
47
|
-
properties: {
|
|
48
|
-
id: { type: "string", description: "Workspace ID to stop" },
|
|
49
|
-
},
|
|
43
|
+
properties: { id: { type: "string", description: "Workspace ID to stop" } },
|
|
50
44
|
required: ["id"],
|
|
51
45
|
},
|
|
52
46
|
},
|
|
@@ -58,9 +52,7 @@ const TOOLS = [
|
|
|
58
52
|
description: "Get the status of a specific workspace",
|
|
59
53
|
parameters: {
|
|
60
54
|
type: "object",
|
|
61
|
-
properties: {
|
|
62
|
-
id: { type: "string", description: "Workspace ID" },
|
|
63
|
-
},
|
|
55
|
+
properties: { id: { type: "string", description: "Workspace ID" } },
|
|
64
56
|
required: ["id"],
|
|
65
57
|
},
|
|
66
58
|
},
|
|
@@ -111,9 +103,7 @@ const TOOLS = [
|
|
|
111
103
|
description: "Get a configuration value by key (dot notation supported)",
|
|
112
104
|
parameters: {
|
|
113
105
|
type: "object",
|
|
114
|
-
properties: {
|
|
115
|
-
key: { type: "string", description: "Config key, e.g. 'ai.provider'" },
|
|
116
|
-
},
|
|
106
|
+
properties: { key: { type: "string", description: "Config key, e.g. 'ai.provider'" } },
|
|
117
107
|
required: ["key"],
|
|
118
108
|
},
|
|
119
109
|
},
|
|
@@ -156,11 +146,15 @@ async function executeTool(name, args) {
|
|
|
156
146
|
return await apiClient.clearMemory();
|
|
157
147
|
case "get_usage":
|
|
158
148
|
return await apiClient.getUsage();
|
|
159
|
-
case "get_config":
|
|
149
|
+
case "get_config": {
|
|
150
|
+
const { getConfigValue } = await import("../lib/config-store.js");
|
|
160
151
|
return { key: args.key, value: getConfigValue(args.key) };
|
|
161
|
-
|
|
152
|
+
}
|
|
153
|
+
case "set_config": {
|
|
154
|
+
const { setConfigValue } = await import("../lib/config-store.js");
|
|
162
155
|
setConfigValue(args.key, args.value);
|
|
163
156
|
return { key: args.key, value: args.value, status: "updated" };
|
|
157
|
+
}
|
|
164
158
|
default:
|
|
165
159
|
return { error: `Unknown tool: ${name}` };
|
|
166
160
|
}
|
|
@@ -169,6 +163,36 @@ async function executeTool(name, args) {
|
|
|
169
163
|
}
|
|
170
164
|
}
|
|
171
165
|
|
|
166
|
+
// Parse SSE stream from the cloud proxy
|
|
167
|
+
async function* parseSSEStream(response) {
|
|
168
|
+
const reader = response.body.getReader();
|
|
169
|
+
const decoder = new TextDecoder();
|
|
170
|
+
let buffer = "";
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
while (true) {
|
|
174
|
+
const { done, value } = await reader.read();
|
|
175
|
+
if (done) break;
|
|
176
|
+
|
|
177
|
+
buffer += decoder.decode(value, { stream: true });
|
|
178
|
+
const lines = buffer.split("\n");
|
|
179
|
+
buffer = lines.pop() || "";
|
|
180
|
+
|
|
181
|
+
for (const line of lines) {
|
|
182
|
+
if (line.startsWith("data: ")) {
|
|
183
|
+
const data = line.slice(6).trim();
|
|
184
|
+
if (data === "[DONE]") return;
|
|
185
|
+
try {
|
|
186
|
+
yield JSON.parse(data);
|
|
187
|
+
} catch {}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} finally {
|
|
192
|
+
reader.releaseLock();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
172
196
|
export async function runAgent(userRequest) {
|
|
173
197
|
const messages = [
|
|
174
198
|
{ role: "system", content: SYSTEM_PROMPT },
|
|
@@ -178,77 +202,73 @@ export async function runAgent(userRequest) {
|
|
|
178
202
|
const MAX_ITERATIONS = 10;
|
|
179
203
|
|
|
180
204
|
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
181
|
-
|
|
205
|
+
const response = await apiClient.chatStream(messages, TOOLS);
|
|
206
|
+
|
|
207
|
+
let fullText = "";
|
|
182
208
|
const toolCalls = {};
|
|
183
209
|
|
|
184
|
-
for await (const chunk of
|
|
210
|
+
for await (const chunk of parseSSEStream(response)) {
|
|
185
211
|
const delta = chunk.choices?.[0]?.delta;
|
|
186
|
-
|
|
212
|
+
const finishReason = chunk.choices?.[0]?.finish_reason;
|
|
187
213
|
|
|
188
|
-
|
|
189
|
-
if (delta.content) {
|
|
214
|
+
if (delta?.content) {
|
|
190
215
|
process.stdout.write(delta.content);
|
|
191
|
-
|
|
216
|
+
fullText += delta.content;
|
|
192
217
|
}
|
|
193
218
|
|
|
194
|
-
|
|
195
|
-
if (delta.tool_calls) {
|
|
219
|
+
if (delta?.tool_calls) {
|
|
196
220
|
for (const tc of delta.tool_calls) {
|
|
197
221
|
const idx = tc.index;
|
|
198
222
|
if (!toolCalls[idx]) {
|
|
199
|
-
toolCalls[idx] = { id: tc.id || "",
|
|
223
|
+
toolCalls[idx] = { id: tc.id || "", name: "", arguments: "" };
|
|
200
224
|
}
|
|
201
225
|
if (tc.id) toolCalls[idx].id = tc.id;
|
|
202
|
-
if (tc.function?.name) toolCalls[idx].
|
|
203
|
-
if (tc.function?.arguments) toolCalls[idx].
|
|
226
|
+
if (tc.function?.name) toolCalls[idx].name = tc.function.name;
|
|
227
|
+
if (tc.function?.arguments) toolCalls[idx].arguments += tc.function.arguments;
|
|
204
228
|
}
|
|
205
229
|
}
|
|
230
|
+
|
|
231
|
+
if (finishReason) break;
|
|
206
232
|
}
|
|
207
233
|
|
|
208
234
|
const toolCallList = Object.values(toolCalls);
|
|
209
235
|
|
|
210
236
|
// No tool calls — final text response, done
|
|
211
237
|
if (toolCallList.length === 0) {
|
|
212
|
-
if (
|
|
238
|
+
if (fullText) process.stdout.write("\n");
|
|
213
239
|
return;
|
|
214
240
|
}
|
|
215
241
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
}
|
|
242
|
+
if (fullText) process.stdout.write("\n");
|
|
243
|
+
|
|
244
|
+
// Build assistant message with tool calls
|
|
245
|
+
const assistantMsg = { role: "assistant", content: fullText || null };
|
|
246
|
+
assistantMsg.tool_calls = toolCallList.map((tc) => ({
|
|
247
|
+
id: tc.id,
|
|
248
|
+
type: "function",
|
|
249
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
250
|
+
}));
|
|
225
251
|
messages.push(assistantMsg);
|
|
226
252
|
|
|
227
|
-
// Execute each tool
|
|
253
|
+
// Execute each tool and add results
|
|
228
254
|
for (const tc of toolCallList) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
args = JSON.parse(tc.function.arguments || "{}");
|
|
233
|
-
} catch {
|
|
234
|
-
args = {};
|
|
235
|
-
}
|
|
255
|
+
let input = {};
|
|
256
|
+
try { input = JSON.parse(tc.arguments); } catch {}
|
|
236
257
|
|
|
237
|
-
log.info(`Calling ${chalk.cyan(
|
|
258
|
+
log.info(`Calling ${chalk.cyan(tc.name)}${Object.keys(input).length ? chalk.dim(" " + JSON.stringify(input)) : ""}`);
|
|
238
259
|
|
|
239
|
-
const result = await executeTool(
|
|
240
|
-
const resultStr = JSON.stringify(result);
|
|
260
|
+
const result = await executeTool(tc.name, input);
|
|
241
261
|
|
|
242
262
|
if (result.error) {
|
|
243
263
|
log.error(result.error);
|
|
244
264
|
} else {
|
|
245
|
-
log.success(
|
|
265
|
+
log.success(tc.name);
|
|
246
266
|
}
|
|
247
267
|
|
|
248
268
|
messages.push({
|
|
249
269
|
role: "tool",
|
|
250
270
|
tool_call_id: tc.id,
|
|
251
|
-
content:
|
|
271
|
+
content: JSON.stringify(result),
|
|
252
272
|
});
|
|
253
273
|
}
|
|
254
274
|
}
|
package/src/commands/memory.js
CHANGED
|
@@ -53,21 +53,13 @@ export async function showMemory(options = {}) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export async function addMemory(content, category = "general") {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
log.success(`Memory saved (${count} total). Category: ${chalk.cyan(entry.category)}`);
|
|
59
|
-
} catch (err) {
|
|
60
|
-
throw err;
|
|
61
|
-
}
|
|
56
|
+
const { entry, count } = await apiClient.addMemory(content, category);
|
|
57
|
+
log.success(`Memory saved (${count} total). Category: ${chalk.cyan(entry.category)}`);
|
|
62
58
|
}
|
|
63
59
|
|
|
64
60
|
export async function clearMemory() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
log.success("All memories cleared.");
|
|
68
|
-
} catch (err) {
|
|
69
|
-
throw err;
|
|
70
|
-
}
|
|
61
|
+
await apiClient.clearMemory();
|
|
62
|
+
log.success("All memories cleared.");
|
|
71
63
|
}
|
|
72
64
|
|
|
73
65
|
export async function exportMemory(options = {}) {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { apiClient } from "../lib/api-client.js";
|
|
4
|
+
import { log } from "../lib/logger.js";
|
|
5
|
+
|
|
6
|
+
export async function showUsage() {
|
|
7
|
+
log.blank();
|
|
8
|
+
log.brand("Usage");
|
|
9
|
+
|
|
10
|
+
const spinner = ora("Fetching usage data...").start();
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const data = await apiClient.getUsage();
|
|
14
|
+
spinner.stop();
|
|
15
|
+
|
|
16
|
+
// Rate limit
|
|
17
|
+
log.header("Rate Limit");
|
|
18
|
+
log.keyValue("Plan", data.plan === "pro" ? chalk.green("Pro") : "Free");
|
|
19
|
+
log.keyValue("Limit", `${data.rateLimit.limit} requests/hour`);
|
|
20
|
+
const remaining = data.rateLimit.remaining;
|
|
21
|
+
const limit = data.rateLimit.limit;
|
|
22
|
+
const remainColor = remaining > limit * 0.5 ? chalk.green : remaining > limit * 0.1 ? chalk.yellow : chalk.red;
|
|
23
|
+
log.keyValue("Remaining", remainColor(`${remaining}`));
|
|
24
|
+
log.keyValue("Resets at", data.rateLimit.resetsAt);
|
|
25
|
+
|
|
26
|
+
// Today's usage
|
|
27
|
+
log.header("Today");
|
|
28
|
+
log.keyValue("Requests", `${data.usage.today.requests}`);
|
|
29
|
+
const endpoints = data.usage.today.endpoints || {};
|
|
30
|
+
if (Object.keys(endpoints).length > 0) {
|
|
31
|
+
for (const [ep, count] of Object.entries(endpoints)) {
|
|
32
|
+
console.log(` ${chalk.dim(ep)} ${chalk.cyan(count)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Summary
|
|
37
|
+
log.header("Summary");
|
|
38
|
+
log.keyValue("Last 7 days", `${data.usage.last7d.requests} requests`);
|
|
39
|
+
log.keyValue("Last 30 days", `${data.usage.last30d.requests} requests`);
|
|
40
|
+
log.keyValue("All time", `${data.summary.totalRequests} requests`);
|
|
41
|
+
if (data.summary.memberSince) {
|
|
42
|
+
log.keyValue("Member since", new Date(data.summary.memberSince).toLocaleDateString());
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
spinner.stop();
|
|
46
|
+
log.error(err.message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
log.blank();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function registerUsageCommand(program) {
|
|
53
|
+
program
|
|
54
|
+
.command("usage")
|
|
55
|
+
.description("Show API usage and rate limit status")
|
|
56
|
+
.action(async () => {
|
|
57
|
+
try {
|
|
58
|
+
await showUsage();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
log.error(err.message);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
package/src/lib/api-client.js
CHANGED
|
@@ -88,4 +88,44 @@ export const apiClient = {
|
|
|
88
88
|
async getUsage() {
|
|
89
89
|
return request("/api/usage");
|
|
90
90
|
},
|
|
91
|
+
|
|
92
|
+
// AI Chat (streaming)
|
|
93
|
+
async chatStream(messages, tools = [], options = {}) {
|
|
94
|
+
const baseUrl = getBaseUrl();
|
|
95
|
+
const token = getToken();
|
|
96
|
+
|
|
97
|
+
if (!token) {
|
|
98
|
+
throw new Error("Not logged in. Run 'cheri login' first.");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const body = {
|
|
102
|
+
messages,
|
|
103
|
+
stream: true,
|
|
104
|
+
};
|
|
105
|
+
if (tools.length > 0) body.tools = tools;
|
|
106
|
+
if (options.model) body.model = options.model;
|
|
107
|
+
|
|
108
|
+
const res = await fetch(`${baseUrl}/api/chat/completions`, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: {
|
|
111
|
+
Authorization: `Bearer ${token}`,
|
|
112
|
+
"Content-Type": "application/json",
|
|
113
|
+
},
|
|
114
|
+
body: JSON.stringify(body),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
const text = await res.text();
|
|
119
|
+
let msg;
|
|
120
|
+
try { msg = JSON.parse(text).error || text; } catch { msg = text; }
|
|
121
|
+
throw new Error(`AI error (${res.status}): ${msg}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return res;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// AI Models
|
|
128
|
+
async getModels() {
|
|
129
|
+
return request("/api/chat/models");
|
|
130
|
+
},
|
|
91
131
|
};
|
package/src/lib/config-store.js
CHANGED
package/src/lib/logger.js
CHANGED
|
@@ -36,7 +36,7 @@ export const log = {
|
|
|
36
36
|
console.log(chalk.dim(prefix) + " " + item);
|
|
37
37
|
});
|
|
38
38
|
},
|
|
39
|
-
banner(version = "0.
|
|
39
|
+
banner(version = "0.1.0") {
|
|
40
40
|
console.log();
|
|
41
41
|
console.log(` ${chalk.red("🍒")} ${chalk.red.bold("Cheri")}`);
|
|
42
42
|
console.log(` ${chalk.dim("AI-powered cloud IDE by HeySalad")}`);
|
package/src/repl.js
CHANGED
|
@@ -9,6 +9,7 @@ import { showStatus } from "./commands/status.js";
|
|
|
9
9
|
import { listConfig, getConfigKey, setConfigKey } from "./commands/config.js";
|
|
10
10
|
import { loginFlow } from "./commands/login.js";
|
|
11
11
|
import { initProject } from "./commands/init.js";
|
|
12
|
+
import { showUsage } from "./commands/usage.js";
|
|
12
13
|
import { runAgent } from "./commands/agent.js";
|
|
13
14
|
|
|
14
15
|
const COMMANDS = {
|
|
@@ -21,13 +22,13 @@ const COMMANDS = {
|
|
|
21
22
|
"memory clear": { args: "", desc: "Clear all memory" },
|
|
22
23
|
"memory export": { args: "", desc: "Export memory to JSON" },
|
|
23
24
|
"status": { args: "", desc: "Show account & workspace status" },
|
|
25
|
+
"usage": { args: "", desc: "Show API usage & rate limits" },
|
|
24
26
|
"config list": { args: "", desc: "Show all configuration" },
|
|
25
27
|
"config get": { args: "<key>", desc: "Get a config value" },
|
|
26
28
|
"config set": { args: "<k> <v>",desc: "Set a config value" },
|
|
27
29
|
"login": { args: "", desc: "Authenticate with Cheri" },
|
|
28
30
|
"init": { args: "", desc: "Initialize a project" },
|
|
29
|
-
"
|
|
30
|
-
"agent": { args: "<request>", desc: "AI agent - natural language command" },
|
|
31
|
+
"agent": { args: "<msg>", desc: "AI agent — natural language commands" },
|
|
31
32
|
"help": { args: "", desc: "Show this help" },
|
|
32
33
|
"clear": { args: "", desc: "Clear the terminal" },
|
|
33
34
|
"exit": { args: "", desc: "Exit Cheri" },
|
|
@@ -76,6 +77,10 @@ async function dispatch(input) {
|
|
|
76
77
|
await showStatus();
|
|
77
78
|
return;
|
|
78
79
|
|
|
80
|
+
case "usage":
|
|
81
|
+
await showUsage();
|
|
82
|
+
return;
|
|
83
|
+
|
|
79
84
|
case "login":
|
|
80
85
|
await loginFlow();
|
|
81
86
|
return;
|
|
@@ -84,25 +89,6 @@ async function dispatch(input) {
|
|
|
84
89
|
await initProject();
|
|
85
90
|
return;
|
|
86
91
|
|
|
87
|
-
case "chat": {
|
|
88
|
-
const { startRepl: startChatRepl } = await import("./lib/repl.js");
|
|
89
|
-
await startChatRepl({
|
|
90
|
-
provider: sub || undefined,
|
|
91
|
-
model: rest || undefined,
|
|
92
|
-
});
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
case "agent": {
|
|
97
|
-
const agentInput = parts.slice(1).join(" ");
|
|
98
|
-
if (!agentInput) {
|
|
99
|
-
log.error("Usage: agent <request>");
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
await runAgent(agentInput);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
92
|
case "workspace":
|
|
107
93
|
switch (sub) {
|
|
108
94
|
case "launch":
|
|
@@ -183,6 +169,16 @@ async function dispatch(input) {
|
|
|
183
169
|
return;
|
|
184
170
|
}
|
|
185
171
|
|
|
172
|
+
case "agent": {
|
|
173
|
+
const agentInput = parts.slice(1).join(" ");
|
|
174
|
+
if (!agentInput) {
|
|
175
|
+
log.error("Usage: agent <your request>");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
await runAgent(agentInput);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
186
182
|
default:
|
|
187
183
|
log.warn(`Unknown command: '${cmd}'. Type ${chalk.cyan("help")} for available commands.`);
|
|
188
184
|
}
|
|
@@ -225,7 +221,7 @@ export async function startCommandRepl() {
|
|
|
225
221
|
process.exit(0);
|
|
226
222
|
});
|
|
227
223
|
|
|
228
|
-
// Handle Ctrl+C gracefully
|
|
224
|
+
// Handle Ctrl+C gracefully
|
|
229
225
|
rl.on("SIGINT", () => {
|
|
230
226
|
console.log(chalk.dim("\nGoodbye! 🍒\n"));
|
|
231
227
|
process.exit(0);
|
package/src/commands/chat.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { startRepl } from "../lib/repl.js";
|
|
2
|
-
|
|
3
|
-
export function registerChatCommand(program) {
|
|
4
|
-
program
|
|
5
|
-
.command("chat")
|
|
6
|
-
.description("Start an interactive AI coding session")
|
|
7
|
-
.option("-p, --provider <provider>", "AI provider (anthropic, openai, deepseek, gemini)")
|
|
8
|
-
.option("-m, --model <model>", "Model to use (overrides provider default)")
|
|
9
|
-
.action(async (options) => {
|
|
10
|
-
await startRepl({
|
|
11
|
-
provider: options.provider,
|
|
12
|
-
model: options.model,
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
}
|