@krmxd/onegpt 0.0.0-beta → 0.0.2-beta
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 +6 -0
- package/bin/ogpt.js +68 -2
- package/package.json +1 -4
- package/src/cli.js +51 -5
- package/src/tools.js +50 -2
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ ogpt
|
|
|
24
24
|
git commit) - everything else just works
|
|
25
25
|
- Token dashboard with live tok/s charts at `http://127.0.0.1:8756` (`/dash`)
|
|
26
26
|
- Session history shared with the Python OGPT build (`/save`, `/load`, `/sessions`)
|
|
27
|
+
- resume where you left off: `ogpt --continue` (or `--session <id>`)
|
|
27
28
|
- Anti-sloth guardrails: tiny local models are kept on-task until every
|
|
28
29
|
requested file actually exists
|
|
29
30
|
|
|
@@ -37,5 +38,10 @@ ogpt
|
|
|
37
38
|
| `/auto` | toggle auto-approve |
|
|
38
39
|
| `/dash` | token dashboard |
|
|
39
40
|
| `/new` | fresh chat |
|
|
41
|
+
| `/export [md|json] [file]` | save the chat as Markdown or JSON |
|
|
42
|
+
| `/todos` | show the current todo list |
|
|
43
|
+
|
|
44
|
+
CLI flags: `ogpt -m <model>` · `-c/--continue` · `--session <id>` ·
|
|
45
|
+
`--auto` · `--no-stream`.
|
|
40
46
|
|
|
41
47
|
Config lives at `~/.config/ogpt/config.json`.
|
package/bin/ogpt.js
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
4
|
+
const [maj] = process.versions.node.split(".").map(Number);
|
|
5
|
+
if (maj < 18) {
|
|
6
|
+
console.error(`OGPT needs Node.js 18+ - you are running ${process.versions.node}.`);
|
|
7
|
+
console.error("Update Node, then try again: https://nodejs.org");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let CLI, getConfig;
|
|
12
|
+
try {
|
|
13
|
+
({ CLI } = require("../src/cli"));
|
|
14
|
+
({ getConfig } = require("../src/config"));
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error(`OGPT failed to start: ${e.message}`);
|
|
17
|
+
if (e.code === "MODULE_NOT_FOUND") {
|
|
18
|
+
console.error(`
|
|
19
|
+
The installation looks broken or out of date. Fix it with:
|
|
20
|
+
|
|
21
|
+
npm cache clean --force
|
|
22
|
+
npm i -g @krmxd/onegpt@latest --force
|
|
23
|
+
|
|
24
|
+
If you installed with 'npm link' and later moved or renamed the
|
|
25
|
+
source folder, re-link it:
|
|
26
|
+
|
|
27
|
+
cd <your-ogpt-repo>/nodejs && npm link
|
|
28
|
+
|
|
29
|
+
Or run straight from the repo without installing:
|
|
30
|
+
|
|
31
|
+
cd <your-ogpt-repo>/nodejs && node bin/ogpt.js`);
|
|
32
|
+
}
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
6
35
|
|
|
7
36
|
async function main() {
|
|
8
37
|
const args = process.argv.slice(2);
|
|
@@ -12,12 +41,18 @@ async function main() {
|
|
|
12
41
|
let prompt = [];
|
|
13
42
|
let noStream = false;
|
|
14
43
|
let autoApprove = false;
|
|
44
|
+
let continueLast = false;
|
|
45
|
+
let sessionId = "";
|
|
15
46
|
|
|
16
47
|
for (let i = 0; i < args.length; i++) {
|
|
17
48
|
if (args[i] === "--no-stream" || args[i] === "-s") {
|
|
18
49
|
noStream = true;
|
|
19
50
|
} else if (args[i] === "--auto") {
|
|
20
51
|
autoApprove = true;
|
|
52
|
+
} else if (args[i] === "--continue" || args[i] === "-c") {
|
|
53
|
+
continueLast = true;
|
|
54
|
+
} else if (args[i] === "--session") {
|
|
55
|
+
sessionId = args[++i] || "";
|
|
21
56
|
} else if (args[i] === "--model" || args[i] === "-m") {
|
|
22
57
|
const name = args[++i];
|
|
23
58
|
cfg.set("active_model", cfg.resolveModel(name));
|
|
@@ -30,6 +65,8 @@ async function main() {
|
|
|
30
65
|
Options:
|
|
31
66
|
-m, --model <oGPT-name|id> Override model (e.g. -m oGPT-2a)
|
|
32
67
|
-s, --no-stream Disable streaming
|
|
68
|
+
-c, --continue Resume your most recent chat
|
|
69
|
+
--session <id> Resume a specific session by ID
|
|
33
70
|
--auto Auto-approve all tools
|
|
34
71
|
-v, --version Show version
|
|
35
72
|
-h, --help Show help`);
|
|
@@ -46,6 +83,35 @@ Options:
|
|
|
46
83
|
if (autoApprove) {
|
|
47
84
|
cli.tools._autoApprove = true;
|
|
48
85
|
}
|
|
86
|
+
if (sessionId || continueLast) {
|
|
87
|
+
const fsMod = require("fs"), osMod = require("os"), pathMod = require("path");
|
|
88
|
+
const dir = pathMod.join(osMod.homedir(), ".config", "ogpt", "sessions");
|
|
89
|
+
let sid = sessionId;
|
|
90
|
+
if (!sid) {
|
|
91
|
+
try {
|
|
92
|
+
sid = fsMod.readdirSync(dir).filter((f) => f.endsWith(".meta.json"))
|
|
93
|
+
.map((f) => { try { return JSON.parse(fsMod.readFileSync(pathMod.join(dir, f), "utf-8")); } catch { return null; } })
|
|
94
|
+
.filter((m) => m && (m.msgs || 0) > 0)
|
|
95
|
+
.sort((a, b) => (b.updated || 0) - (a.updated || 0))[0]?.id || "";
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
99
|
+
let data = null;
|
|
100
|
+
if (sid) {
|
|
101
|
+
try { data = JSON.parse(fsMod.readFileSync(pathMod.join(dir, `${sid}.json`), "utf-8")); } catch {}
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(data) && data.length) {
|
|
104
|
+
cli.agent.history = data.map((d) => ({
|
|
105
|
+
role: d.role || "user", content: d.content || "", ts: Date.now() / 1000 }));
|
|
106
|
+
cli._sessionId = sid;
|
|
107
|
+
console.log(dim(continueLast && !sessionId
|
|
108
|
+
? `Continued last chat: ${sid}` : `Resumed session: ${sid}`));
|
|
109
|
+
} else if (sessionId) {
|
|
110
|
+
console.error(`Session not found: ${sessionId}`);
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
49
115
|
|
|
50
116
|
if (prompt.length) {
|
|
51
117
|
await cli.runSingle(prompt.join(" "));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krmxd/onegpt",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-beta",
|
|
4
4
|
"description": "OGPT - AI coding assistant for the terminal with a built-in local engine",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,9 +21,6 @@
|
|
|
21
21
|
],
|
|
22
22
|
"author": "OGPT Team",
|
|
23
23
|
"license": "MIT",
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"ollama": "^0.5.0"
|
|
26
|
-
},
|
|
27
24
|
"engines": {
|
|
28
25
|
"node": ">=18.0.0"
|
|
29
26
|
},
|
package/src/cli.js
CHANGED
|
@@ -284,6 +284,10 @@ class CLI {
|
|
|
284
284
|
"List plugins", "/plugins", "system", true));
|
|
285
285
|
reg(new Command("/mcp", () => this.info("No MCP servers configured."),
|
|
286
286
|
"List MCP servers", "/mcp", "system", true));
|
|
287
|
+
reg(new Command("/export", (a) => this.cmdExport(a),
|
|
288
|
+
"Export the chat as Markdown or JSON", "/export [md|json] [file]", "general"));
|
|
289
|
+
reg(new Command("/todos", () => this.cmdTodos(),
|
|
290
|
+
"Show the current todo list", "/todos", "tools", true));
|
|
287
291
|
|
|
288
292
|
reg(new Command("/tools", () => this.cmdTools(), "List available tools", "/tools", "tools", true));
|
|
289
293
|
reg(new Command("/approve", (a) => { this.print(this.tools.toggleApprove(a[0] || "")); }, "Auto-approve a specific tool", "/approve <tool>", "tools"));
|
|
@@ -683,8 +687,46 @@ class CLI {
|
|
|
683
687
|
this.print(`## Project Structure\n\`\`\`\n${lines.join("\n")}\n\`\`\``);
|
|
684
688
|
}
|
|
685
689
|
|
|
686
|
-
|
|
687
|
-
const
|
|
690
|
+
cmdExport(args) {
|
|
691
|
+
const fmt = args && args[0] === "json" ? "json" : "md";
|
|
692
|
+
const file = (args && args[1]) ||
|
|
693
|
+
`ogpt-chat-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-")}.${fmt}`;
|
|
694
|
+
try {
|
|
695
|
+
const msgs = this.agent.history.filter((m) => m.role !== "tool" &&
|
|
696
|
+
!String(m.content || "").startsWith("[Conversation summary]"));
|
|
697
|
+
if (!msgs.length) { this.info("Nothing to export - no conversation yet."); return; }
|
|
698
|
+
let body;
|
|
699
|
+
if (fmt === "json") {
|
|
700
|
+
body = JSON.stringify({ exported: new Date().toISOString(),
|
|
701
|
+
model: this.cfg.displayName(), messages: msgs }, null, 2);
|
|
702
|
+
} else {
|
|
703
|
+
body = `# OGPT chat export\n\n_${new Date().toLocaleString()} · ${this.cfg.displayName()} · ${msgs.length} messages_\n\n`
|
|
704
|
+
+ msgs.map((m) => `## ${m.role === "user" ? "You" : this.cfg.displayName()}\n\n${m.content}\n`).join("\n");
|
|
705
|
+
}
|
|
706
|
+
fs.writeFileSync(file, body);
|
|
707
|
+
this.success(`Exported ${msgs.length} messages to ${file}`);
|
|
708
|
+
} catch (e) {
|
|
709
|
+
this.error(`Export failed: ${e.message}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
_todosFile() {
|
|
714
|
+
return path.join(os.homedir(), ".config", "ogpt", "todos.json");
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
cmdTodos() {
|
|
718
|
+
let todos = [];
|
|
719
|
+
try {
|
|
720
|
+
const data = JSON.parse(fs.readFileSync(this._todosFile(), "utf-8"));
|
|
721
|
+
todos = Array.isArray(data) ? data : data.todos || [];
|
|
722
|
+
} catch {}
|
|
723
|
+
if (!todos.length) { this.info("No todos."); return; }
|
|
724
|
+
const rows = todos.map((t, i) =>
|
|
725
|
+
[String(i + 1), t.done ? "[x]" : "[ ]", String(t.text || "")]);
|
|
726
|
+
this._table("Todos", ["#", "Done", "Task"], rows);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
cmdSession(args) { const sub = (args && args[0]) || "list";
|
|
688
730
|
if (sub === "list") return this.cmdSessions();
|
|
689
731
|
if (sub === "save") return this.cmdSave();
|
|
690
732
|
if (sub === "new" || sub === "fresh") return this.cmdNew(args.slice(1));
|
|
@@ -716,12 +758,16 @@ class CLI {
|
|
|
716
758
|
const metaPath = path.join(dir, `${this._sessionId}.meta.json`);
|
|
717
759
|
try { meta = JSON.parse(fs.readFileSync(metaPath, "utf-8")); } catch {}
|
|
718
760
|
if (!meta.id) {
|
|
719
|
-
meta = { id: this._sessionId,
|
|
720
|
-
title: `Session ${new Date().toISOString().slice(0, 16).replace("T", " ")}`,
|
|
721
|
-
created: Date.now() / 1000, ...meta };
|
|
761
|
+
meta = { id: this._sessionId, title: "", created: Date.now() / 1000, ...meta };
|
|
722
762
|
}
|
|
723
763
|
meta.updated = Date.now() / 1000;
|
|
724
764
|
meta.msgs = data.length;
|
|
765
|
+
// Auto-title from the first real user message (same rule as Python).
|
|
766
|
+
const existing = (meta.title || "").trim();
|
|
767
|
+
if (!existing || existing.startsWith("Session ")) {
|
|
768
|
+
const first = data.find((m) => m.role === "user" && String(m.content || "").trim());
|
|
769
|
+
if (first) meta.title = String(first.content).trim().slice(0, 50);
|
|
770
|
+
}
|
|
725
771
|
fs.writeFileSync(metaPath, JSON.stringify(meta));
|
|
726
772
|
return this._sessionId;
|
|
727
773
|
} catch {
|
package/src/tools.js
CHANGED
|
@@ -397,8 +397,35 @@ async function runShell(command, cwd = ".", timeout = 30000) {
|
|
|
397
397
|
|
|
398
398
|
// Run code with an interpreter directly (argv, never a shell) so user code
|
|
399
399
|
// can't be mangled by quoting and packages install safely.
|
|
400
|
+
const _BIN_FALLBACKS = {
|
|
401
|
+
node: ["/usr/bin/node", "/usr/local/bin/node",
|
|
402
|
+
"/data/data/com.termux/files/usr/bin/node"],
|
|
403
|
+
python3: ["/usr/bin/python3", "/usr/local/bin/python3",
|
|
404
|
+
"/data/data/com.termux/files/usr/bin/python3"],
|
|
405
|
+
python: ["/usr/bin/python", "/data/data/com.termux/files/usr/bin/python"],
|
|
406
|
+
pip: ["/usr/bin/pip", "/usr/local/bin/pip",
|
|
407
|
+
"/data/data/com.termux/files/usr/bin/pip"],
|
|
408
|
+
pip3: ["/usr/bin/pip3", "/data/data/com.termux/files/usr/bin/pip3"],
|
|
409
|
+
npm: ["/usr/bin/npm", "/usr/local/bin/npm",
|
|
410
|
+
"/data/data/com.termux/files/usr/bin/npm"],
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
function resolveBin(bin) {
|
|
414
|
+
// spawnSync needs an absolute path when PATH is stripped in some
|
|
415
|
+
// sandboxes/Termux sessions - probe fallbacks before giving up.
|
|
416
|
+
try {
|
|
417
|
+
const found = require("child_process").execSync(
|
|
418
|
+
`command -v ${bin} 2>/dev/null`, { encoding: "utf-8", timeout: 3000 }).trim();
|
|
419
|
+
if (found) return found;
|
|
420
|
+
} catch {}
|
|
421
|
+
for (const p of _BIN_FALLBACKS[bin] || []) {
|
|
422
|
+
try { if (fs.existsSync(p)) return p; } catch {}
|
|
423
|
+
}
|
|
424
|
+
return bin; // let spawnSync produce its own ENOENT
|
|
425
|
+
}
|
|
426
|
+
|
|
400
427
|
function runInterpreter(bin, argv, cwd, timeout) {
|
|
401
|
-
const r = spawnSync(bin, argv, {
|
|
428
|
+
const r = spawnSync(resolveBin(bin), argv, {
|
|
402
429
|
cwd: path.resolve(cwd || "."), timeout: timeout || 30000,
|
|
403
430
|
encoding: "utf-8", maxBuffer: 16 * 1024 * 1024,
|
|
404
431
|
env: { ...process.env, PYTHONDONTWRITEBYTECODE: "1" },
|
|
@@ -786,6 +813,9 @@ const IMPLEMENTATIONS = {
|
|
|
786
813
|
if (!code.trim() && !file) {
|
|
787
814
|
return new ToolResult("", "Provide 'code' (Python source to run) or 'path' (a .py file)", false);
|
|
788
815
|
}
|
|
816
|
+
if (file && !fs.existsSync(path.resolve(args.cwd || ".", file))) {
|
|
817
|
+
return new ToolResult("", `File not found: ${file} (use a path relative to your workspace)`, false);
|
|
818
|
+
}
|
|
789
819
|
const argv = file ? [file] : ["-c", code];
|
|
790
820
|
return runInterpreter("python3", argv, args.cwd || ".", parseInt(args.timeout || 60, 10) * 1000);
|
|
791
821
|
},
|
|
@@ -796,7 +826,25 @@ const IMPLEMENTATIONS = {
|
|
|
796
826
|
if (!code.trim() && !file) {
|
|
797
827
|
return new ToolResult("", "Provide 'code' (JavaScript source to run) or 'path' (a .js file)", false);
|
|
798
828
|
}
|
|
799
|
-
|
|
829
|
+
let argv;
|
|
830
|
+
if (file) {
|
|
831
|
+
if (!fs.existsSync(path.resolve(args.cwd || ".", file))) {
|
|
832
|
+
return new ToolResult("", `File not found: ${file} (use a path relative to your workspace)`, false);
|
|
833
|
+
}
|
|
834
|
+
argv = [file];
|
|
835
|
+
} else if (/^\s*import\s|^\s*export\s|await\s+import\(/m.test(code)) {
|
|
836
|
+
// ESM code can't run through -e; stage it as a temp .mjs module.
|
|
837
|
+
const tmp = path.join(os.tmpdir(), `ogpt-run-${Date.now()}.mjs`);
|
|
838
|
+
fs.writeFileSync(tmp, code);
|
|
839
|
+
try {
|
|
840
|
+
return runInterpreter("node", [tmp], args.cwd || ".",
|
|
841
|
+
parseInt(args.timeout || 60, 10) * 1000);
|
|
842
|
+
} finally {
|
|
843
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
844
|
+
}
|
|
845
|
+
} else {
|
|
846
|
+
argv = ["-e", code];
|
|
847
|
+
}
|
|
800
848
|
return runInterpreter("node", argv, args.cwd || ".", parseInt(args.timeout || 60, 10) * 1000);
|
|
801
849
|
},
|
|
802
850
|
|