@heysalad/cheri-cli 0.2.0 → 0.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/README.md +58 -30
- 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/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,12 +7,12 @@ 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
|
|
|
12
12
|
program
|
|
13
13
|
.name("cheri")
|
|
14
14
|
.description("Cheri CLI - AI-powered cloud IDE by HeySalad")
|
|
15
|
-
.version("0.
|
|
15
|
+
.version("0.1.0");
|
|
16
16
|
|
|
17
17
|
registerLoginCommand(program);
|
|
18
18
|
registerInitCommand(program);
|
|
@@ -20,12 +20,12 @@ registerStatusCommand(program);
|
|
|
20
20
|
registerMemoryCommand(program);
|
|
21
21
|
registerConfigCommand(program);
|
|
22
22
|
registerWorkspaceCommand(program);
|
|
23
|
-
|
|
23
|
+
registerUsageCommand(program);
|
|
24
24
|
|
|
25
|
-
// If no args, launch interactive REPL
|
|
25
|
+
// If no args, launch interactive command REPL
|
|
26
26
|
if (!process.argv.slice(2).length) {
|
|
27
|
-
const {
|
|
28
|
-
await
|
|
27
|
+
const { startCommandRepl } = await import("../src/repl.js");
|
|
28
|
+
await startCommandRepl();
|
|
29
29
|
} else {
|
|
30
30
|
program.parse(process.argv);
|
|
31
31
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heysalad/cheri-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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/config.js
CHANGED
|
@@ -2,6 +2,42 @@ import chalk from "chalk";
|
|
|
2
2
|
import { getConfig, getConfigValue, setConfigValue } from "../lib/config-store.js";
|
|
3
3
|
import { log } from "../lib/logger.js";
|
|
4
4
|
|
|
5
|
+
export function listConfig() {
|
|
6
|
+
const cfg = getConfig();
|
|
7
|
+
|
|
8
|
+
log.blank();
|
|
9
|
+
log.brand("Configuration");
|
|
10
|
+
log.blank();
|
|
11
|
+
|
|
12
|
+
printObject(cfg, "");
|
|
13
|
+
log.blank();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getConfigKey(key) {
|
|
17
|
+
const value = getConfigValue(key);
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
throw new Error(`Key '${key}' not found.`);
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === "object") {
|
|
22
|
+
console.log(JSON.stringify(value, null, 2));
|
|
23
|
+
} else {
|
|
24
|
+
console.log(value);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function setConfigKey(key, value) {
|
|
29
|
+
// Try to parse as JSON (for arrays, numbers, booleans)
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(value);
|
|
33
|
+
} catch {
|
|
34
|
+
parsed = value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setConfigValue(key, parsed);
|
|
38
|
+
log.success(`Set ${chalk.cyan(key)} = ${chalk.white(value)}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
5
41
|
export function registerConfigCommand(program) {
|
|
6
42
|
const config = program
|
|
7
43
|
.command("config")
|
|
@@ -11,46 +47,26 @@ export function registerConfigCommand(program) {
|
|
|
11
47
|
.command("list")
|
|
12
48
|
.description("Show all configuration values")
|
|
13
49
|
.action(() => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
log.blank();
|
|
17
|
-
log.brand("Configuration");
|
|
18
|
-
log.blank();
|
|
19
|
-
|
|
20
|
-
printObject(cfg, "");
|
|
21
|
-
log.blank();
|
|
50
|
+
listConfig();
|
|
22
51
|
});
|
|
23
52
|
|
|
24
53
|
config
|
|
25
54
|
.command("get <key>")
|
|
26
55
|
.description("Get a configuration value")
|
|
27
56
|
.action((key) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
57
|
+
try {
|
|
58
|
+
getConfigKey(key);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
log.error(err.message);
|
|
31
61
|
process.exit(1);
|
|
32
62
|
}
|
|
33
|
-
if (typeof value === "object") {
|
|
34
|
-
console.log(JSON.stringify(value, null, 2));
|
|
35
|
-
} else {
|
|
36
|
-
console.log(value);
|
|
37
|
-
}
|
|
38
63
|
});
|
|
39
64
|
|
|
40
65
|
config
|
|
41
66
|
.command("set <key> <value>")
|
|
42
67
|
.description("Set a configuration value")
|
|
43
68
|
.action((key, value) => {
|
|
44
|
-
|
|
45
|
-
let parsed;
|
|
46
|
-
try {
|
|
47
|
-
parsed = JSON.parse(value);
|
|
48
|
-
} catch {
|
|
49
|
-
parsed = value;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
setConfigValue(key, parsed);
|
|
53
|
-
log.success(`Set ${chalk.cyan(key)} = ${chalk.white(value)}`);
|
|
69
|
+
setConfigKey(key, value);
|
|
54
70
|
});
|
|
55
71
|
}
|
|
56
72
|
|
package/src/commands/init.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ora from "ora";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { existsSync, mkdirSync, writeFileSync
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { setConfigValue } from "../lib/config-store.js";
|
|
7
7
|
import { log } from "../lib/logger.js";
|
|
@@ -79,6 +79,86 @@ const TEMPLATES = {
|
|
|
79
79
|
`,
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
export async function initProject(options = {}) {
|
|
83
|
+
log.blank();
|
|
84
|
+
log.brand("Initializing project...");
|
|
85
|
+
log.blank();
|
|
86
|
+
|
|
87
|
+
const aiDir = join(process.cwd(), AI_DIR);
|
|
88
|
+
|
|
89
|
+
if (existsSync(aiDir) && !options.yes) {
|
|
90
|
+
const { overwrite } = await inquirer.prompt([
|
|
91
|
+
{
|
|
92
|
+
type: "confirm",
|
|
93
|
+
name: "overwrite",
|
|
94
|
+
message: "A .ai/ directory already exists. Add missing files?",
|
|
95
|
+
default: true,
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
if (!overwrite) {
|
|
99
|
+
log.info("Initialization cancelled.");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let projectName = options.name;
|
|
105
|
+
if (!projectName && !options.yes) {
|
|
106
|
+
const answers = await inquirer.prompt([
|
|
107
|
+
{
|
|
108
|
+
type: "input",
|
|
109
|
+
name: "name",
|
|
110
|
+
message: "Project name:",
|
|
111
|
+
default: process.cwd().split("/").pop(),
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
projectName = answers.name;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
projectName = projectName || process.cwd().split("/").pop();
|
|
118
|
+
|
|
119
|
+
const spinner = ora("Creating project constitution...").start();
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
if (!existsSync(aiDir)) {
|
|
123
|
+
mkdirSync(aiDir, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const created = [];
|
|
127
|
+
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
128
|
+
const filePath = join(aiDir, filename);
|
|
129
|
+
if (!existsSync(filePath)) {
|
|
130
|
+
writeFileSync(filePath, content);
|
|
131
|
+
created.push(filename);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
spinner.succeed("Project constitution created");
|
|
136
|
+
log.blank();
|
|
137
|
+
|
|
138
|
+
if (created.length > 0) {
|
|
139
|
+
created.forEach((file) => {
|
|
140
|
+
console.log(` ${chalk.green("+")} .ai/${file}`);
|
|
141
|
+
});
|
|
142
|
+
} else {
|
|
143
|
+
log.info("All constitution files already exist.");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Save project config
|
|
147
|
+
setConfigValue("project.name", projectName);
|
|
148
|
+
setConfigValue("project.initializedAt", new Date().toISOString());
|
|
149
|
+
|
|
150
|
+
log.blank();
|
|
151
|
+
log.success(`Project '${chalk.bold(projectName)}' initialized.`);
|
|
152
|
+
log.dim(
|
|
153
|
+
`Run ${chalk.cyan("cheri workspace launch owner/repo")} to start coding in the cloud.`
|
|
154
|
+
);
|
|
155
|
+
log.blank();
|
|
156
|
+
} catch (err) {
|
|
157
|
+
spinner.fail("Failed to initialize project");
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
82
162
|
export function registerInitCommand(program) {
|
|
83
163
|
program
|
|
84
164
|
.command("init")
|
|
@@ -86,81 +166,9 @@ export function registerInitCommand(program) {
|
|
|
86
166
|
.option("-n, --name <name>", "Project name")
|
|
87
167
|
.option("-y, --yes", "Skip prompts and use defaults")
|
|
88
168
|
.action(async (options) => {
|
|
89
|
-
log.blank();
|
|
90
|
-
log.brand("Initializing project...");
|
|
91
|
-
log.blank();
|
|
92
|
-
|
|
93
|
-
const aiDir = join(process.cwd(), AI_DIR);
|
|
94
|
-
|
|
95
|
-
if (existsSync(aiDir) && !options.yes) {
|
|
96
|
-
const { overwrite } = await inquirer.prompt([
|
|
97
|
-
{
|
|
98
|
-
type: "confirm",
|
|
99
|
-
name: "overwrite",
|
|
100
|
-
message: "A .ai/ directory already exists. Add missing files?",
|
|
101
|
-
default: true,
|
|
102
|
-
},
|
|
103
|
-
]);
|
|
104
|
-
if (!overwrite) {
|
|
105
|
-
log.info("Initialization cancelled.");
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
let projectName = options.name;
|
|
111
|
-
if (!projectName && !options.yes) {
|
|
112
|
-
const answers = await inquirer.prompt([
|
|
113
|
-
{
|
|
114
|
-
type: "input",
|
|
115
|
-
name: "name",
|
|
116
|
-
message: "Project name:",
|
|
117
|
-
default: process.cwd().split("/").pop(),
|
|
118
|
-
},
|
|
119
|
-
]);
|
|
120
|
-
projectName = answers.name;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
projectName = projectName || process.cwd().split("/").pop();
|
|
124
|
-
|
|
125
|
-
const spinner = ora("Creating project constitution...").start();
|
|
126
|
-
|
|
127
169
|
try {
|
|
128
|
-
|
|
129
|
-
mkdirSync(aiDir, { recursive: true });
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const created = [];
|
|
133
|
-
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
134
|
-
const filePath = join(aiDir, filename);
|
|
135
|
-
if (!existsSync(filePath)) {
|
|
136
|
-
writeFileSync(filePath, content);
|
|
137
|
-
created.push(filename);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
spinner.succeed("Project constitution created");
|
|
142
|
-
log.blank();
|
|
143
|
-
|
|
144
|
-
if (created.length > 0) {
|
|
145
|
-
created.forEach((file) => {
|
|
146
|
-
console.log(` ${chalk.green("+")} .ai/${file}`);
|
|
147
|
-
});
|
|
148
|
-
} else {
|
|
149
|
-
log.info("All constitution files already exist.");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Save project config
|
|
153
|
-
setConfigValue("project.name", projectName);
|
|
154
|
-
setConfigValue("project.initializedAt", new Date().toISOString());
|
|
155
|
-
|
|
156
|
-
log.blank();
|
|
157
|
-
log.success(`Project '${chalk.bold(projectName)}' initialized.`);
|
|
158
|
-
log.dim(
|
|
159
|
-
`Run ${chalk.cyan("cheri workspace launch owner/repo")} to start coding in the cloud.`
|
|
160
|
-
);
|
|
161
|
-
log.blank();
|
|
170
|
+
await initProject(options);
|
|
162
171
|
} catch (err) {
|
|
163
|
-
spinner.fail("Failed to initialize project");
|
|
164
172
|
log.error(err.message);
|
|
165
173
|
process.exit(1);
|
|
166
174
|
}
|
package/src/commands/login.js
CHANGED
|
@@ -4,53 +4,60 @@ import { setConfigValue, 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 loginFlow() {
|
|
8
|
+
const apiUrl = getConfigValue("apiUrl") || "https://cheri.heysalad.app";
|
|
9
|
+
|
|
10
|
+
log.blank();
|
|
11
|
+
log.brand("Login to Cheri");
|
|
12
|
+
log.blank();
|
|
13
|
+
|
|
14
|
+
log.info("Step 1: Open this URL in your browser to authenticate:");
|
|
15
|
+
log.blank();
|
|
16
|
+
console.log(` ${chalk.cyan.underline(`${apiUrl}/auth/github`)}`);
|
|
17
|
+
log.blank();
|
|
18
|
+
log.info("Step 2: After login, visit the token page:");
|
|
19
|
+
console.log(` ${chalk.cyan.underline(`${apiUrl}/auth/token?user=YOUR_USERNAME`)}`);
|
|
20
|
+
log.blank();
|
|
21
|
+
log.info("Step 3: Copy your API token and paste it below.");
|
|
22
|
+
log.blank();
|
|
23
|
+
|
|
24
|
+
const { token } = await inquirer.prompt([
|
|
25
|
+
{
|
|
26
|
+
type: "password",
|
|
27
|
+
name: "token",
|
|
28
|
+
message: "Paste your API token:",
|
|
29
|
+
mask: "*",
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
if (!token || !token.trim()) {
|
|
34
|
+
throw new Error("No token provided.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setConfigValue("token", token.trim());
|
|
38
|
+
|
|
39
|
+
// Verify token works
|
|
40
|
+
try {
|
|
41
|
+
const me = await apiClient.getMe();
|
|
42
|
+
log.blank();
|
|
43
|
+
log.success(`Logged in as ${chalk.cyan(me.ghLogin || me.userId)}`);
|
|
44
|
+
log.keyValue("Plan", me.plan === "pro" ? chalk.green("Pro") : "Free");
|
|
45
|
+
log.blank();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
setConfigValue("token", "");
|
|
48
|
+
throw new Error(`Token verification failed: ${err.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
7
52
|
export function registerLoginCommand(program) {
|
|
8
53
|
program
|
|
9
54
|
.command("login")
|
|
10
55
|
.description("Authenticate with Cheri cloud IDE")
|
|
11
56
|
.action(async () => {
|
|
12
|
-
const apiUrl = getConfigValue("apiUrl") || "https://cheri.heysalad.app";
|
|
13
|
-
|
|
14
|
-
log.blank();
|
|
15
|
-
log.brand("Login to Cheri");
|
|
16
|
-
log.blank();
|
|
17
|
-
|
|
18
|
-
log.info("Step 1: Open this URL in your browser to authenticate:");
|
|
19
|
-
log.blank();
|
|
20
|
-
console.log(` ${chalk.cyan.underline(`${apiUrl}/auth/github`)}`);
|
|
21
|
-
log.blank();
|
|
22
|
-
log.info("Step 2: After login, visit the token page:");
|
|
23
|
-
console.log(` ${chalk.cyan.underline(`${apiUrl}/auth/token?user=YOUR_USERNAME`)}`);
|
|
24
|
-
log.blank();
|
|
25
|
-
log.info("Step 3: Copy your API token and paste it below.");
|
|
26
|
-
log.blank();
|
|
27
|
-
|
|
28
|
-
const { token } = await inquirer.prompt([
|
|
29
|
-
{
|
|
30
|
-
type: "password",
|
|
31
|
-
name: "token",
|
|
32
|
-
message: "Paste your API token:",
|
|
33
|
-
mask: "*",
|
|
34
|
-
},
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
if (!token || !token.trim()) {
|
|
38
|
-
log.error("No token provided.");
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
setConfigValue("token", token.trim());
|
|
43
|
-
|
|
44
|
-
// Verify token works
|
|
45
57
|
try {
|
|
46
|
-
|
|
47
|
-
log.blank();
|
|
48
|
-
log.success(`Logged in as ${chalk.cyan(me.ghLogin || me.userId)}`);
|
|
49
|
-
log.keyValue("Plan", me.plan === "pro" ? chalk.green("Pro") : "Free");
|
|
50
|
-
log.blank();
|
|
58
|
+
await loginFlow();
|
|
51
59
|
} catch (err) {
|
|
52
|
-
log.error(
|
|
53
|
-
setConfigValue("token", "");
|
|
60
|
+
log.error(err.message);
|
|
54
61
|
process.exit(1);
|
|
55
62
|
}
|
|
56
63
|
});
|