@heysalad/cheri-cli 0.2.0 → 0.3.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 +59 -31
- package/bin/cheri.js +6 -6
- package/package.json +7 -13
- package/src/commands/config.js +42 -26
- package/src/commands/init.js +82 -74
- package/src/commands/login.js +47 -40
- package/src/commands/memory.js +84 -60
- package/src/commands/status.js +50 -40
- package/src/commands/usage.js +64 -0
- package/src/commands/workspace.js +103 -69
- package/src/lib/api-client.js +5 -0
- package/src/lib/config-store.js +0 -10
- package/src/lib/logger.js +10 -0
- package/src/repl.js +217 -0
- package/src/commands/chat.js +0 -15
- package/src/lib/branding.js +0 -36
- 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/src/commands/memory.js
CHANGED
|
@@ -4,6 +4,86 @@ import { writeFileSync } from "fs";
|
|
|
4
4
|
import { apiClient } from "../lib/api-client.js";
|
|
5
5
|
import { log } from "../lib/logger.js";
|
|
6
6
|
|
|
7
|
+
export async function showMemory(options = {}) {
|
|
8
|
+
const spinner = ora("Fetching memories...").start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const { memories } = await apiClient.getMemory();
|
|
12
|
+
spinner.stop();
|
|
13
|
+
|
|
14
|
+
log.blank();
|
|
15
|
+
log.brand("AI Memory");
|
|
16
|
+
log.blank();
|
|
17
|
+
log.keyValue("Entries", `${memories.length}`);
|
|
18
|
+
|
|
19
|
+
if (memories.length === 0) {
|
|
20
|
+
log.blank();
|
|
21
|
+
log.dim(" No memories yet. Use 'memory add <content>' to store context.");
|
|
22
|
+
log.blank();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const limit = parseInt(options.limit || "10");
|
|
27
|
+
const recent = memories.slice(-limit);
|
|
28
|
+
|
|
29
|
+
log.keyValue(
|
|
30
|
+
"Last updated",
|
|
31
|
+
memories.length > 0
|
|
32
|
+
? timeSince(new Date(memories[memories.length - 1].timestamp))
|
|
33
|
+
: "never"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
log.blank();
|
|
37
|
+
log.dim(" Recent memories:");
|
|
38
|
+
|
|
39
|
+
recent.forEach((entry) => {
|
|
40
|
+
const category = chalk.dim(`(${entry.category})`);
|
|
41
|
+
const content =
|
|
42
|
+
entry.content.length > 60
|
|
43
|
+
? entry.content.slice(0, 60) + "..."
|
|
44
|
+
: entry.content;
|
|
45
|
+
console.log(` ${chalk.dim("-")} "${content}" ${category}`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
log.blank();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
spinner.fail("Failed to fetch memories");
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function addMemory(content, category = "general") {
|
|
56
|
+
const { entry, count } = await apiClient.addMemory(content, category);
|
|
57
|
+
log.success(`Memory saved (${count} total). Category: ${chalk.cyan(entry.category)}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function clearMemory() {
|
|
61
|
+
await apiClient.clearMemory();
|
|
62
|
+
log.success("All memories cleared.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function exportMemory(options = {}) {
|
|
66
|
+
const spinner = ora("Exporting memories...").start();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const { memories } = await apiClient.getMemory();
|
|
70
|
+
|
|
71
|
+
const exportData = {
|
|
72
|
+
version: "0.1.0",
|
|
73
|
+
exportedAt: new Date().toISOString(),
|
|
74
|
+
entryCount: memories.length,
|
|
75
|
+
entries: memories,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const outputFile = options.output || `cheri-memory-export.json`;
|
|
79
|
+
writeFileSync(outputFile, JSON.stringify(exportData, null, 2));
|
|
80
|
+
spinner.succeed(`Exported ${memories.length} entries to ${chalk.cyan(outputFile)}`);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
spinner.fail("Failed to export memories");
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
7
87
|
export function registerMemoryCommand(program) {
|
|
8
88
|
const memory = program
|
|
9
89
|
.command("memory")
|
|
@@ -14,49 +94,9 @@ export function registerMemoryCommand(program) {
|
|
|
14
94
|
.description("Show current memory entries")
|
|
15
95
|
.option("-l, --limit <n>", "Number of entries to show", "10")
|
|
16
96
|
.action(async (options) => {
|
|
17
|
-
const spinner = ora("Fetching memories...").start();
|
|
18
|
-
|
|
19
97
|
try {
|
|
20
|
-
|
|
21
|
-
spinner.stop();
|
|
22
|
-
|
|
23
|
-
log.blank();
|
|
24
|
-
log.brand("AI Memory");
|
|
25
|
-
log.blank();
|
|
26
|
-
log.keyValue("Entries", `${memories.length}`);
|
|
27
|
-
|
|
28
|
-
if (memories.length === 0) {
|
|
29
|
-
log.blank();
|
|
30
|
-
log.dim(" No memories yet. Use 'cheri memory add' to store context.");
|
|
31
|
-
log.blank();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const limit = parseInt(options.limit);
|
|
36
|
-
const recent = memories.slice(-limit);
|
|
37
|
-
|
|
38
|
-
log.keyValue(
|
|
39
|
-
"Last updated",
|
|
40
|
-
memories.length > 0
|
|
41
|
-
? timeSince(new Date(memories[memories.length - 1].timestamp))
|
|
42
|
-
: "never"
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
log.blank();
|
|
46
|
-
log.dim(" Recent memories:");
|
|
47
|
-
|
|
48
|
-
recent.forEach((entry) => {
|
|
49
|
-
const category = chalk.dim(`(${entry.category})`);
|
|
50
|
-
const content =
|
|
51
|
-
entry.content.length > 60
|
|
52
|
-
? entry.content.slice(0, 60) + "..."
|
|
53
|
-
: entry.content;
|
|
54
|
-
console.log(` ${chalk.dim("-")} "${content}" ${category}`);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
log.blank();
|
|
98
|
+
await showMemory(options);
|
|
58
99
|
} catch (err) {
|
|
59
|
-
spinner.fail("Failed to fetch memories");
|
|
60
100
|
log.error(err.message);
|
|
61
101
|
process.exit(1);
|
|
62
102
|
}
|
|
@@ -69,8 +109,7 @@ export function registerMemoryCommand(program) {
|
|
|
69
109
|
.option("-c, --category <category>", "Category", "general")
|
|
70
110
|
.action(async (content, options) => {
|
|
71
111
|
try {
|
|
72
|
-
|
|
73
|
-
log.success(`Memory saved (${count} total). Category: ${chalk.cyan(entry.category)}`);
|
|
112
|
+
await addMemory(content, options.category);
|
|
74
113
|
} catch (err) {
|
|
75
114
|
log.error(err.message);
|
|
76
115
|
process.exit(1);
|
|
@@ -82,8 +121,7 @@ export function registerMemoryCommand(program) {
|
|
|
82
121
|
.description("Clear all memory")
|
|
83
122
|
.action(async () => {
|
|
84
123
|
try {
|
|
85
|
-
await
|
|
86
|
-
log.success("All memories cleared.");
|
|
124
|
+
await clearMemory();
|
|
87
125
|
} catch (err) {
|
|
88
126
|
log.error(err.message);
|
|
89
127
|
process.exit(1);
|
|
@@ -95,23 +133,9 @@ export function registerMemoryCommand(program) {
|
|
|
95
133
|
.description("Export memory to JSON file")
|
|
96
134
|
.option("-o, --output <file>", "Output file path")
|
|
97
135
|
.action(async (options) => {
|
|
98
|
-
const spinner = ora("Exporting memories...").start();
|
|
99
|
-
|
|
100
136
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const exportData = {
|
|
104
|
-
version: "0.1.0",
|
|
105
|
-
exportedAt: new Date().toISOString(),
|
|
106
|
-
entryCount: memories.length,
|
|
107
|
-
entries: memories,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const outputFile = options.output || `cheri-memory-export.json`;
|
|
111
|
-
writeFileSync(outputFile, JSON.stringify(exportData, null, 2));
|
|
112
|
-
spinner.succeed(`Exported ${memories.length} entries to ${chalk.cyan(outputFile)}`);
|
|
137
|
+
await exportMemory(options);
|
|
113
138
|
} catch (err) {
|
|
114
|
-
spinner.fail("Failed to export memories");
|
|
115
139
|
log.error(err.message);
|
|
116
140
|
process.exit(1);
|
|
117
141
|
}
|
package/src/commands/status.js
CHANGED
|
@@ -4,53 +4,63 @@ import { getConfigValue } from "../lib/config-store.js";
|
|
|
4
4
|
import { apiClient } from "../lib/api-client.js";
|
|
5
5
|
import { log } from "../lib/logger.js";
|
|
6
6
|
|
|
7
|
+
export async function showStatus() {
|
|
8
|
+
log.blank();
|
|
9
|
+
log.brand("Status");
|
|
10
|
+
|
|
11
|
+
// Account info
|
|
12
|
+
log.header("Account");
|
|
13
|
+
const spinner = ora("Fetching account info...").start();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const me = await apiClient.getMe();
|
|
17
|
+
spinner.stop();
|
|
18
|
+
|
|
19
|
+
log.keyValue("User", chalk.cyan(me.ghLogin || me.userId));
|
|
20
|
+
log.keyValue("Plan", me.plan === "pro" ? chalk.green("Pro") : "Free");
|
|
21
|
+
} catch (err) {
|
|
22
|
+
spinner.stop();
|
|
23
|
+
log.keyValue("User", chalk.yellow("Not logged in"));
|
|
24
|
+
log.dim(` Run ${chalk.cyan("cheri login")} to authenticate.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Workspaces
|
|
28
|
+
log.header("Workspaces");
|
|
29
|
+
try {
|
|
30
|
+
const { workspaces } = await apiClient.listWorkspaces();
|
|
31
|
+
if (workspaces.length === 0) {
|
|
32
|
+
log.keyValue("Count", "0");
|
|
33
|
+
log.dim(` Run ${chalk.cyan("cheri workspace launch owner/repo")} to create one.`);
|
|
34
|
+
} else {
|
|
35
|
+
log.keyValue("Count", `${workspaces.length}`);
|
|
36
|
+
workspaces.forEach((ws) => {
|
|
37
|
+
const statusIcon = ws.status === "running" ? chalk.green("●") : chalk.dim("○");
|
|
38
|
+
const statusColor = ws.status === "running" ? chalk.green : chalk.dim;
|
|
39
|
+
console.log(` ${statusIcon} ${ws.id} (${ws.repo || ws.id}) — ${statusColor(ws.status)}`);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
log.dim(" Could not fetch workspaces.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Config info
|
|
47
|
+
log.header("Configuration");
|
|
48
|
+
log.keyValue("API URL", getConfigValue("apiUrl") || "https://cheri.heysalad.app");
|
|
49
|
+
log.keyValue("Config dir", chalk.dim("~/.cheri/"));
|
|
50
|
+
|
|
51
|
+
log.blank();
|
|
52
|
+
}
|
|
53
|
+
|
|
7
54
|
export function registerStatusCommand(program) {
|
|
8
55
|
program
|
|
9
56
|
.command("status")
|
|
10
57
|
.description("Show account and workspace status")
|
|
11
58
|
.action(async () => {
|
|
12
|
-
log.blank();
|
|
13
|
-
log.brand("Status");
|
|
14
|
-
|
|
15
|
-
// Account info
|
|
16
|
-
log.header("Account");
|
|
17
|
-
const spinner = ora("Fetching account info...").start();
|
|
18
|
-
|
|
19
59
|
try {
|
|
20
|
-
|
|
21
|
-
spinner.stop();
|
|
22
|
-
|
|
23
|
-
log.keyValue("User", chalk.cyan(me.ghLogin || me.userId));
|
|
24
|
-
log.keyValue("Plan", me.plan === "pro" ? chalk.green("Pro") : "Free");
|
|
60
|
+
await showStatus();
|
|
25
61
|
} catch (err) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
log.dim(` Run ${chalk.cyan("cheri login")} to authenticate.`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Workspaces
|
|
32
|
-
log.header("Workspaces");
|
|
33
|
-
try {
|
|
34
|
-
const { workspaces } = await apiClient.listWorkspaces();
|
|
35
|
-
if (workspaces.length === 0) {
|
|
36
|
-
log.keyValue("Count", "0");
|
|
37
|
-
log.dim(` Run ${chalk.cyan("cheri workspace launch owner/repo")} to create one.`);
|
|
38
|
-
} else {
|
|
39
|
-
log.keyValue("Count", `${workspaces.length}`);
|
|
40
|
-
workspaces.forEach((ws) => {
|
|
41
|
-
const statusColor = ws.status === "running" ? chalk.green : chalk.dim;
|
|
42
|
-
console.log(` ${statusColor("●")} ${ws.id} (${ws.repo || ws.id}) — ${statusColor(ws.status)}`);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
} catch {
|
|
46
|
-
log.dim(" Could not fetch workspaces.");
|
|
62
|
+
log.error(err.message);
|
|
63
|
+
process.exit(1);
|
|
47
64
|
}
|
|
48
|
-
|
|
49
|
-
// Config info
|
|
50
|
-
log.header("Configuration");
|
|
51
|
-
log.keyValue("API URL", getConfigValue("apiUrl") || "https://cheri.heysalad.app");
|
|
52
|
-
log.keyValue("Config dir", chalk.dim("~/.cheri/"));
|
|
53
|
-
|
|
54
|
-
log.blank();
|
|
55
65
|
});
|
|
56
66
|
}
|
|
@@ -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
|
+
}
|
|
@@ -3,6 +3,105 @@ import ora from "ora";
|
|
|
3
3
|
import { apiClient } from "../lib/api-client.js";
|
|
4
4
|
import { log } from "../lib/logger.js";
|
|
5
5
|
|
|
6
|
+
export async function launchWorkspace(repo) {
|
|
7
|
+
log.blank();
|
|
8
|
+
log.brand("Launching workspace");
|
|
9
|
+
log.blank();
|
|
10
|
+
|
|
11
|
+
const spinner = ora("Creating workspace...").start();
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const { workspace: ws } = await apiClient.createWorkspace(repo);
|
|
15
|
+
spinner.succeed("Workspace is live!");
|
|
16
|
+
|
|
17
|
+
log.blank();
|
|
18
|
+
log.keyValue("ID", chalk.cyan(ws.id));
|
|
19
|
+
log.keyValue("Repo", chalk.cyan(ws.repo));
|
|
20
|
+
log.keyValue("Status", chalk.green(ws.status));
|
|
21
|
+
log.keyValue("URL", chalk.cyan.underline(ws.url));
|
|
22
|
+
log.blank();
|
|
23
|
+
log.dim("Open the URL above in your browser to start coding.");
|
|
24
|
+
log.blank();
|
|
25
|
+
} catch (err) {
|
|
26
|
+
spinner.fail("Failed to launch workspace");
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function stopWorkspace(id) {
|
|
32
|
+
const spinner = ora(`Stopping workspace: ${id}...`).start();
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await apiClient.deleteWorkspace(id);
|
|
36
|
+
spinner.succeed(`Workspace '${chalk.cyan(id)}' stopped.`);
|
|
37
|
+
log.dim(" Data preserved. Launch again to resume.");
|
|
38
|
+
} catch (err) {
|
|
39
|
+
spinner.fail("Failed to stop workspace");
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function listWorkspaces() {
|
|
45
|
+
const spinner = ora("Fetching workspaces...").start();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const { workspaces } = await apiClient.listWorkspaces();
|
|
49
|
+
spinner.stop();
|
|
50
|
+
|
|
51
|
+
log.blank();
|
|
52
|
+
log.brand("Workspaces");
|
|
53
|
+
log.blank();
|
|
54
|
+
|
|
55
|
+
if (workspaces.length === 0) {
|
|
56
|
+
log.dim(" No workspaces found. Run 'cheri workspace launch owner/repo' to create one.");
|
|
57
|
+
log.blank();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Table header
|
|
62
|
+
console.log(
|
|
63
|
+
` ${chalk.dim("ID".padEnd(24))} ${chalk.dim("REPO".padEnd(24))} ${chalk.dim("STATUS".padEnd(10))} ${chalk.dim("URL")}`
|
|
64
|
+
);
|
|
65
|
+
console.log(chalk.dim(" " + "─".repeat(90)));
|
|
66
|
+
|
|
67
|
+
workspaces.forEach((ws) => {
|
|
68
|
+
const statusIcon =
|
|
69
|
+
ws.status === "running" ? chalk.green("●") : chalk.dim("○");
|
|
70
|
+
const statusColor =
|
|
71
|
+
ws.status === "running" ? chalk.green : chalk.dim;
|
|
72
|
+
console.log(
|
|
73
|
+
` ${ws.id.padEnd(24)} ${(ws.repo || "").padEnd(24)} ${statusIcon} ${statusColor(ws.status.padEnd(9))} ${chalk.cyan(ws.url || "")}`
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
log.blank();
|
|
78
|
+
} catch (err) {
|
|
79
|
+
spinner.fail("Failed to fetch workspaces");
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function getWorkspaceStatus(id) {
|
|
85
|
+
const spinner = ora("Fetching workspace status...").start();
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const { workspace: ws } = await apiClient.getWorkspaceStatus(id);
|
|
89
|
+
spinner.stop();
|
|
90
|
+
|
|
91
|
+
log.blank();
|
|
92
|
+
log.brand(`Workspace: ${ws.id}`);
|
|
93
|
+
log.blank();
|
|
94
|
+
log.keyValue("Status", ws.status === "running" ? chalk.green("● " + ws.status) : chalk.dim("○ " + ws.status));
|
|
95
|
+
log.keyValue("Repo", chalk.cyan(ws.repo || ""));
|
|
96
|
+
log.keyValue("URL", chalk.cyan.underline(ws.url || ""));
|
|
97
|
+
log.keyValue("Created", new Date(ws.createdAt).toLocaleString());
|
|
98
|
+
log.blank();
|
|
99
|
+
} catch (err) {
|
|
100
|
+
spinner.fail("Failed to fetch workspace status");
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
6
105
|
export function registerWorkspaceCommand(program) {
|
|
7
106
|
const workspace = program
|
|
8
107
|
.command("workspace")
|
|
@@ -13,26 +112,9 @@ export function registerWorkspaceCommand(program) {
|
|
|
13
112
|
.description("Launch a new cloud workspace")
|
|
14
113
|
.argument("<repo>", "GitHub repository (owner/repo)")
|
|
15
114
|
.action(async (repo) => {
|
|
16
|
-
log.blank();
|
|
17
|
-
log.brand("Launching workspace");
|
|
18
|
-
log.blank();
|
|
19
|
-
|
|
20
|
-
const spinner = ora("Creating workspace...").start();
|
|
21
|
-
|
|
22
115
|
try {
|
|
23
|
-
|
|
24
|
-
spinner.succeed("Workspace is live!");
|
|
25
|
-
|
|
26
|
-
log.blank();
|
|
27
|
-
log.keyValue("ID", chalk.cyan(ws.id));
|
|
28
|
-
log.keyValue("Repo", chalk.cyan(ws.repo));
|
|
29
|
-
log.keyValue("Status", chalk.green(ws.status));
|
|
30
|
-
log.keyValue("URL", chalk.cyan.underline(ws.url));
|
|
31
|
-
log.blank();
|
|
32
|
-
log.dim("Open the URL above in your browser to start coding.");
|
|
33
|
-
log.blank();
|
|
116
|
+
await launchWorkspace(repo);
|
|
34
117
|
} catch (err) {
|
|
35
|
-
spinner.fail("Failed to launch workspace");
|
|
36
118
|
log.error(err.message);
|
|
37
119
|
process.exit(1);
|
|
38
120
|
}
|
|
@@ -43,14 +125,9 @@ export function registerWorkspaceCommand(program) {
|
|
|
43
125
|
.description("Stop a running workspace")
|
|
44
126
|
.argument("<id>", "Workspace ID")
|
|
45
127
|
.action(async (id) => {
|
|
46
|
-
const spinner = ora(`Stopping workspace: ${id}...`).start();
|
|
47
|
-
|
|
48
128
|
try {
|
|
49
|
-
await
|
|
50
|
-
spinner.succeed(`Workspace '${chalk.cyan(id)}' stopped.`);
|
|
51
|
-
log.dim(" Data preserved. Launch again to resume.");
|
|
129
|
+
await stopWorkspace(id);
|
|
52
130
|
} catch (err) {
|
|
53
|
-
spinner.fail("Failed to stop workspace");
|
|
54
131
|
log.error(err.message);
|
|
55
132
|
process.exit(1);
|
|
56
133
|
}
|
|
@@ -60,39 +137,9 @@ export function registerWorkspaceCommand(program) {
|
|
|
60
137
|
.command("list")
|
|
61
138
|
.description("List all workspaces")
|
|
62
139
|
.action(async () => {
|
|
63
|
-
const spinner = ora("Fetching workspaces...").start();
|
|
64
|
-
|
|
65
140
|
try {
|
|
66
|
-
|
|
67
|
-
spinner.stop();
|
|
68
|
-
|
|
69
|
-
log.blank();
|
|
70
|
-
log.brand("Workspaces");
|
|
71
|
-
log.blank();
|
|
72
|
-
|
|
73
|
-
if (workspaces.length === 0) {
|
|
74
|
-
log.dim(" No workspaces found. Run 'cheri workspace launch owner/repo' to create one.");
|
|
75
|
-
log.blank();
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Table header
|
|
80
|
-
console.log(
|
|
81
|
-
` ${chalk.dim("ID".padEnd(24))} ${chalk.dim("REPO".padEnd(24))} ${chalk.dim("STATUS".padEnd(10))} ${chalk.dim("URL")}`
|
|
82
|
-
);
|
|
83
|
-
console.log(chalk.dim(" " + "-".repeat(90)));
|
|
84
|
-
|
|
85
|
-
workspaces.forEach((ws) => {
|
|
86
|
-
const statusColor =
|
|
87
|
-
ws.status === "running" ? chalk.green : chalk.dim;
|
|
88
|
-
console.log(
|
|
89
|
-
` ${ws.id.padEnd(24)} ${(ws.repo || "").padEnd(24)} ${statusColor(ws.status.padEnd(10))} ${chalk.cyan(ws.url || "")}`
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
log.blank();
|
|
141
|
+
await listWorkspaces();
|
|
94
142
|
} catch (err) {
|
|
95
|
-
spinner.fail("Failed to fetch workspaces");
|
|
96
143
|
log.error(err.message);
|
|
97
144
|
process.exit(1);
|
|
98
145
|
}
|
|
@@ -103,22 +150,9 @@ export function registerWorkspaceCommand(program) {
|
|
|
103
150
|
.description("Get workspace status")
|
|
104
151
|
.argument("<id>", "Workspace ID")
|
|
105
152
|
.action(async (id) => {
|
|
106
|
-
const spinner = ora("Fetching workspace status...").start();
|
|
107
|
-
|
|
108
153
|
try {
|
|
109
|
-
|
|
110
|
-
spinner.stop();
|
|
111
|
-
|
|
112
|
-
log.blank();
|
|
113
|
-
log.brand(`Workspace: ${ws.id}`);
|
|
114
|
-
log.blank();
|
|
115
|
-
log.keyValue("Status", ws.status === "running" ? chalk.green(ws.status) : chalk.dim(ws.status));
|
|
116
|
-
log.keyValue("Repo", chalk.cyan(ws.repo || ""));
|
|
117
|
-
log.keyValue("URL", chalk.cyan.underline(ws.url || ""));
|
|
118
|
-
log.keyValue("Created", new Date(ws.createdAt).toLocaleString());
|
|
119
|
-
log.blank();
|
|
154
|
+
await getWorkspaceStatus(id);
|
|
120
155
|
} catch (err) {
|
|
121
|
-
spinner.fail("Failed to fetch workspace status");
|
|
122
156
|
log.error(err.message);
|
|
123
157
|
process.exit(1);
|
|
124
158
|
}
|
package/src/lib/api-client.js
CHANGED
package/src/lib/config-store.js
CHANGED
package/src/lib/logger.js
CHANGED
|
@@ -36,4 +36,14 @@ export const log = {
|
|
|
36
36
|
console.log(chalk.dim(prefix) + " " + item);
|
|
37
37
|
});
|
|
38
38
|
},
|
|
39
|
+
banner(version = "0.1.0") {
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(` ${chalk.red("🍒")} ${chalk.red.bold("Cheri")}`);
|
|
42
|
+
console.log(` ${chalk.dim("AI-powered cloud IDE by HeySalad")}`);
|
|
43
|
+
console.log(` ${chalk.dim("v" + version)}`);
|
|
44
|
+
console.log();
|
|
45
|
+
},
|
|
46
|
+
tip(msg) {
|
|
47
|
+
console.log(` ${chalk.blue("tip")} ${chalk.dim(msg)}`);
|
|
48
|
+
},
|
|
39
49
|
};
|