cheatchat 1.1.0 → 1.2.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/package.json +2 -1
- package/src/commands/ai.js +79 -0
- package/src/commands/help.js +13 -0
- package/src/commands/solve.js +87 -0
- package/src/index.js +16 -0
- package/src/services/api.js +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cheatchat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"bin": {
|
|
6
6
|
"chat": "./bin/chat.js"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"cli-table3": "^0.6.5",
|
|
28
28
|
"commander": "^15.0.0",
|
|
29
29
|
"dotenv": "^17.4.2",
|
|
30
|
+
"fs-extra": "^11.3.6",
|
|
30
31
|
"inquirer": "^14.0.2",
|
|
31
32
|
"ora": "^9.4.1",
|
|
32
33
|
"ws": "^8.21.0"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
import { aiChat } from "../services/api.js";
|
|
3
|
+
import { showBanner } from "../ui/banner.js";
|
|
4
|
+
import { createSpinner } from "../ui/spinner.js";
|
|
5
|
+
import {
|
|
6
|
+
printBlank,
|
|
7
|
+
printSystem,
|
|
8
|
+
printError,
|
|
9
|
+
} from "../ui/printer.js";
|
|
10
|
+
import { brand, accent, muted, whiteBold, success as successColor } from "../ui/color.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Interactive AI chat mode.
|
|
14
|
+
* Completely separate from the main chat application.
|
|
15
|
+
*/
|
|
16
|
+
export async function aiCommand() {
|
|
17
|
+
showBanner();
|
|
18
|
+
printBlank();
|
|
19
|
+
console.log(` ${whiteBold("🤖 AI Chat Mode")}`);
|
|
20
|
+
console.log(` ${muted("Ask anything. Type")} ${brand("exit")} ${muted("to quit.")}`);
|
|
21
|
+
printBlank();
|
|
22
|
+
|
|
23
|
+
const rl = readline.createInterface({
|
|
24
|
+
input: process.stdin,
|
|
25
|
+
output: process.stdout,
|
|
26
|
+
prompt: ` ${accent("You")} ${brand("›")} `,
|
|
27
|
+
terminal: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
rl.prompt();
|
|
31
|
+
|
|
32
|
+
rl.on("line", async (input) => {
|
|
33
|
+
const line = input.trim();
|
|
34
|
+
|
|
35
|
+
if (!line) {
|
|
36
|
+
rl.prompt();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (line.toLowerCase() === "exit") {
|
|
41
|
+
printBlank();
|
|
42
|
+
printSystem("Exiting AI chat. Goodbye! 👋");
|
|
43
|
+
printBlank();
|
|
44
|
+
rl.close();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const spinner = createSpinner("Thinking...");
|
|
50
|
+
spinner.start();
|
|
51
|
+
|
|
52
|
+
const result = await aiChat(line);
|
|
53
|
+
|
|
54
|
+
if (result.reply) {
|
|
55
|
+
spinner.stop();
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(` ${successColor("AI")} ${brand("›")} ${result.reply}`);
|
|
58
|
+
console.log();
|
|
59
|
+
} else {
|
|
60
|
+
spinner.fail("AI error");
|
|
61
|
+
printError(result.error || "Failed to get a response");
|
|
62
|
+
printBlank();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
rl.prompt();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
rl.on("close", () => {
|
|
69
|
+
process.exit(0);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
process.on("SIGINT", () => {
|
|
73
|
+
printBlank();
|
|
74
|
+
printSystem("Exiting AI chat. Goodbye! 👋");
|
|
75
|
+
printBlank();
|
|
76
|
+
rl.close();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
79
|
+
}
|
package/src/commands/help.js
CHANGED
|
@@ -38,5 +38,18 @@ export function helpCommand() {
|
|
|
38
38
|
console.log(` ${accent(cmd.padEnd(20))} ${muted(desc)}`);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
printBlank();
|
|
42
|
+
console.log(` ${whiteBold("AI Tools:")}`);
|
|
43
|
+
printBlank();
|
|
44
|
+
|
|
45
|
+
const aiCommands = [
|
|
46
|
+
["chat ai", "Interactive AI chat"],
|
|
47
|
+
["chat solve <file>", "Solve/improve a file with AI"],
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const [cmd, desc] of aiCommands) {
|
|
51
|
+
console.log(` ${brand(cmd.padEnd(20))} ${muted(desc)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
printBlank();
|
|
42
55
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { aiFile } from "../services/api.js";
|
|
4
|
+
import { showBanner } from "../ui/banner.js";
|
|
5
|
+
import { createSpinner } from "../ui/spinner.js";
|
|
6
|
+
import {
|
|
7
|
+
printBlank,
|
|
8
|
+
printSuccess,
|
|
9
|
+
printError,
|
|
10
|
+
} from "../ui/printer.js";
|
|
11
|
+
import { muted } from "../ui/color.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Strip markdown code fences from AI response (client-side safety net).
|
|
15
|
+
*/
|
|
16
|
+
function stripCodeFences(text) {
|
|
17
|
+
const trimmed = text.trim();
|
|
18
|
+
const fenceRegex = /^```[\w]*\n?([\s\S]*?)\n?```$/;
|
|
19
|
+
const match = trimmed.match(fenceRegex);
|
|
20
|
+
if (match) {
|
|
21
|
+
return match[1];
|
|
22
|
+
}
|
|
23
|
+
return trimmed;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Solve/improve a file using AI.
|
|
28
|
+
* Reads the file, sends it to POST /ai/file, overwrites with the result.
|
|
29
|
+
*/
|
|
30
|
+
export async function solveCommand(filePath) {
|
|
31
|
+
showBanner();
|
|
32
|
+
printBlank();
|
|
33
|
+
|
|
34
|
+
// Resolve the file path relative to cwd
|
|
35
|
+
const resolvedPath = path.resolve(process.cwd(), filePath);
|
|
36
|
+
const filename = path.basename(resolvedPath);
|
|
37
|
+
|
|
38
|
+
// Check if file exists
|
|
39
|
+
if (!(await fs.pathExists(resolvedPath))) {
|
|
40
|
+
printError(`File not found: ${resolvedPath}`);
|
|
41
|
+
printBlank();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check it's a file, not a directory
|
|
46
|
+
const stat = await fs.stat(resolvedPath);
|
|
47
|
+
if (!stat.isFile()) {
|
|
48
|
+
printError(`Not a file: ${resolvedPath}`);
|
|
49
|
+
printBlank();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Read file content
|
|
54
|
+
const content = await fs.readFile(resolvedPath, "utf-8");
|
|
55
|
+
|
|
56
|
+
if (content.trim().length === 0) {
|
|
57
|
+
printError("File is empty. Nothing to solve.");
|
|
58
|
+
printBlank();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(` ${muted("File:")} ${filename}`);
|
|
63
|
+
console.log(` ${muted("Size:")} ${content.length} characters`);
|
|
64
|
+
printBlank();
|
|
65
|
+
|
|
66
|
+
const spinner = createSpinner("🤖 Solving...");
|
|
67
|
+
spinner.start();
|
|
68
|
+
|
|
69
|
+
const result = await aiFile(filename, content);
|
|
70
|
+
|
|
71
|
+
if (result.content) {
|
|
72
|
+
// Strip code fences if present (client-side safety)
|
|
73
|
+
const cleanContent = stripCodeFences(result.content);
|
|
74
|
+
|
|
75
|
+
// Overwrite the original file
|
|
76
|
+
await fs.writeFile(resolvedPath, cleanContent, "utf-8");
|
|
77
|
+
|
|
78
|
+
spinner.succeed("🤖 Solving complete!");
|
|
79
|
+
printBlank();
|
|
80
|
+
printSuccess(`File updated: ${resolvedPath}`);
|
|
81
|
+
} else {
|
|
82
|
+
spinner.fail("Solving failed");
|
|
83
|
+
printError(result.error || "Failed to process the file");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
printBlank();
|
|
87
|
+
}
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { loginCommand } from "./commands/login.js";
|
|
|
5
5
|
import { logoutCommand } from "./commands/logout.js";
|
|
6
6
|
import { helpCommand } from "./commands/help.js";
|
|
7
7
|
import { connectCommand } from "./commands/connect.js";
|
|
8
|
+
import { aiCommand } from "./commands/ai.js";
|
|
9
|
+
import { solveCommand } from "./commands/solve.js";
|
|
8
10
|
import { whoamiFlow } from "./auth/whoami.js";
|
|
9
11
|
import { APP_NAME, APP_VERSION, APP_DESCRIPTION } from "./utils/constants.js";
|
|
10
12
|
|
|
@@ -67,6 +69,20 @@ program
|
|
|
67
69
|
helpCommand();
|
|
68
70
|
});
|
|
69
71
|
|
|
72
|
+
program
|
|
73
|
+
.command("ai")
|
|
74
|
+
.description("Enter interactive AI chat mode")
|
|
75
|
+
.action(async () => {
|
|
76
|
+
await aiCommand();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
program
|
|
80
|
+
.command("solve <file>")
|
|
81
|
+
.description("Solve/improve a source file using AI")
|
|
82
|
+
.action(async (file) => {
|
|
83
|
+
await solveCommand(file);
|
|
84
|
+
});
|
|
85
|
+
|
|
70
86
|
// ─── Default (no command) ────────────────────────────────────────────────────
|
|
71
87
|
|
|
72
88
|
if (process.argv.length <= 2) {
|
package/src/services/api.js
CHANGED
|
@@ -53,6 +53,34 @@ export async function getMe(token) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// ─── AI Endpoints (Public — No Auth Required) ───────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Send a prompt to the AI chat endpoint.
|
|
60
|
+
* Returns { reply } or { error }
|
|
61
|
+
*/
|
|
62
|
+
export async function aiChat(prompt) {
|
|
63
|
+
try {
|
|
64
|
+
const { data } = await api.post("/ai/chat", { prompt }, { timeout: 60000 });
|
|
65
|
+
return data;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return extractError(err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Send a file to the AI file solver endpoint.
|
|
73
|
+
* Returns { content } or { error }
|
|
74
|
+
*/
|
|
75
|
+
export async function aiFile(filename, content) {
|
|
76
|
+
try {
|
|
77
|
+
const { data } = await api.post("/ai/file", { filename, content }, { timeout: 60000 });
|
|
78
|
+
return data;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return extractError(err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
56
84
|
// ─── Error Extraction ────────────────────────────────────────────────────────
|
|
57
85
|
|
|
58
86
|
function extractError(err) {
|