@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.
@@ -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
- const { memories } = await apiClient.getMemory();
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
- const { entry, count } = await apiClient.addMemory(content, options.category);
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 apiClient.clearMemory();
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
- const { memories } = await apiClient.getMemory();
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
  }
@@ -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
- const me = await apiClient.getMe();
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
- spinner.stop();
27
- log.keyValue("User", chalk.yellow("Not logged in"));
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
- const { workspace: ws } = await apiClient.createWorkspace(repo);
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 apiClient.deleteWorkspace(id);
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
- const { workspaces } = await apiClient.listWorkspaces();
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
- const { workspace: ws } = await apiClient.getWorkspaceStatus(id);
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
  }
@@ -83,4 +83,9 @@ export const apiClient = {
83
83
  async clearMemory() {
84
84
  return request("/api/memory", { method: "DELETE" });
85
85
  },
86
+
87
+ // Usage
88
+ async getUsage() {
89
+ return request("/api/usage");
90
+ },
86
91
  };
@@ -74,15 +74,5 @@ function getDefaultConfig() {
74
74
  theme: "dark",
75
75
  fontSize: 14,
76
76
  },
77
- ai: {
78
- provider: "anthropic",
79
- model: "",
80
- keys: {
81
- anthropic: "",
82
- openai: "",
83
- deepseek: "",
84
- gemini: "",
85
- },
86
- },
87
77
  };
88
78
  }
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
  };