codemax-cli 0.1.1 → 0.1.3
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 +14 -1
- package/bin/codemax.cjs +3 -2
- package/package.json +13 -4
- package/src/chat/app.ts +552 -40
- package/src/chat/logo.ts +37 -0
- package/src/chat/transcript.ts +22 -4
- package/src/cli.ts +46 -1
- package/src/config.ts +18 -0
- package/src/core/awake.ts +345 -0
- package/src/core/brief.ts +1 -1
- package/src/core/journal.ts +4 -2
- package/src/core/review.ts +67 -0
- package/src/core/update.ts +90 -0
- package/src/engines/index.ts +6 -6
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve, sep } from "node:path";
|
|
3
|
+
import { CODEMAX_HOME, ensureDir } from "./paths.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Update checks and self-update. At most every 12 hours (3 s timeout, never
|
|
7
|
+
* blocking startup) codemax asks for the newest version from the place its own
|
|
8
|
+
* update comes from: npm for npm installs, GitHub releases for the installer,
|
|
9
|
+
* source checkouts and standalone builds. So a release is only offered where
|
|
10
|
+
* `/update` can actually fetch it. `/update` / `codemax update` then updates
|
|
11
|
+
* the way codemax was installed.
|
|
12
|
+
*/
|
|
13
|
+
const REGISTRY = "https://registry.npmjs.org/codemax-cli/latest";
|
|
14
|
+
const RELEASES = "https://api.github.com/repos/SkidGod4444/codemax-cli/releases/latest";
|
|
15
|
+
const CHECK_EVERY_MS = 12 * 60 * 60 * 1000;
|
|
16
|
+
const CACHE = () => join(CODEMAX_HOME, "update-check.json");
|
|
17
|
+
|
|
18
|
+
export type InstallKind = "npm" | "script" | "source" | "binary";
|
|
19
|
+
|
|
20
|
+
/** How this codemax was installed, judged from where its code lives. */
|
|
21
|
+
export function installKind(root = resolve(import.meta.dir, "..", "..")): { kind: InstallKind; root: string } {
|
|
22
|
+
if (root.includes("$bunfs") || root.startsWith("/$bunfs") || root.startsWith("B:\\~BUN")) return { kind: "binary", root };
|
|
23
|
+
if (root.includes(`${sep}node_modules${sep}codemax-cli`) || root.includes("/node_modules/codemax-cli")) return { kind: "npm", root };
|
|
24
|
+
if (existsSync(join(root, ".git")) && /[\\/]\.codemax[\\/]cli$/.test(root)) return { kind: "script", root };
|
|
25
|
+
if (existsSync(join(root, ".git"))) return { kind: "source", root };
|
|
26
|
+
return { kind: "npm", root };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** a > b for x.y.z versions (pre-release suffixes ignored). */
|
|
30
|
+
export function isNewer(a: string, b: string): boolean {
|
|
31
|
+
const p = (v: string) => v.replace(/^v/, "").split("-")[0]!.split(".").map((n) => Number(n) || 0);
|
|
32
|
+
const [x, y] = [p(a), p(b)];
|
|
33
|
+
for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) > (y[i] ?? 0);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function latestVersion(source: "npm" | "github" = "npm", fetchImpl: typeof fetch = fetch, timeoutMs = 3000): Promise<string | null> {
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetchImpl(source === "npm" ? REGISTRY : RELEASES, { signal: AbortSignal.timeout(timeoutMs), headers: { accept: source === "npm" ? "application/json" : "application/vnd.github+json" } });
|
|
40
|
+
if (!res.ok) return null;
|
|
41
|
+
const body = (await res.json()) as { version?: unknown; tag_name?: unknown };
|
|
42
|
+
const v = String((source === "npm" ? body.version : body.tag_name) ?? "").replace(/^v/, "");
|
|
43
|
+
return /^\d+\.\d+\.\d+/.test(v) ? v : null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Where this install's updates come from. */
|
|
50
|
+
export function updateSource(kind: InstallKind = installKind().kind): "npm" | "github" {
|
|
51
|
+
return kind === "npm" ? "npm" : "github";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The latest version when it is newer than `current`; null otherwise. Cached for 12 hours. */
|
|
55
|
+
export async function checkForUpdate(current: string, o: { force?: boolean; fetchImpl?: typeof fetch; source?: "npm" | "github" } = {}): Promise<string | null> {
|
|
56
|
+
const source = o.source ?? updateSource();
|
|
57
|
+
let cached: { at: number; latest: string | null; source?: string } | null = null;
|
|
58
|
+
try {
|
|
59
|
+
cached = existsSync(CACHE()) ? JSON.parse(readFileSync(CACHE(), "utf8")) : null;
|
|
60
|
+
} catch {
|
|
61
|
+
cached = null;
|
|
62
|
+
}
|
|
63
|
+
let latest = cached?.latest ?? null;
|
|
64
|
+
if (o.force || !cached || cached.source !== source || Date.now() - cached.at > CHECK_EVERY_MS) {
|
|
65
|
+
latest = await latestVersion(source, o.fetchImpl);
|
|
66
|
+
try {
|
|
67
|
+
ensureDir(CODEMAX_HOME);
|
|
68
|
+
writeFileSync(CACHE(), JSON.stringify({ at: Date.now(), latest, source }) + "\n");
|
|
69
|
+
} catch {
|
|
70
|
+
/* read-only home: check again next time */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return latest && isNewer(latest, current) ? latest : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The command that updates this install, or null with an explanation when it cannot update itself. */
|
|
77
|
+
export function updatePlan(install = installKind(), platform = process.platform): { cmd: string[][]; env?: Record<string, string>; manual?: string; label: string } {
|
|
78
|
+
switch (install.kind) {
|
|
79
|
+
case "npm":
|
|
80
|
+
return { cmd: [["npm", "install", "-g", "codemax-cli@latest"]], label: "npm install -g codemax-cli@latest" };
|
|
81
|
+
case "script":
|
|
82
|
+
return platform === "win32"
|
|
83
|
+
? { cmd: [["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(install.root, "install.ps1")]], env: { CODEMAX_DIR: install.root }, label: "the codemax installer (newest release)" }
|
|
84
|
+
: { cmd: [["bash", join(install.root, "install.sh")]], env: { CODEMAX_DIR: install.root }, label: "the codemax installer (newest release)" };
|
|
85
|
+
case "source":
|
|
86
|
+
return { cmd: [["git", "-C", install.root, "pull", "--ff-only"], ["bun", "install", "--cwd", install.root]], label: `git pull in ${install.root}` };
|
|
87
|
+
case "binary":
|
|
88
|
+
return { cmd: [], manual: "This is a standalone build; download the new release from https://github.com/SkidGod4444/codemax-cli/releases or switch to `npm install -g codemax-cli`.", label: "manual download" };
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/engines/index.ts
CHANGED
|
@@ -35,7 +35,7 @@ function grokUsageTail(cwd: string, sessionId: string): { read(): UsageSample[]
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const CLAUDE_ADAPTER_PKG = "@
|
|
38
|
+
const CLAUDE_ADAPTER_PKG = "@agentclientprotocol/claude-agent-acp@0.93.0";
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Command for the Claude ACP adapter (it runs the user's own `claude`).
|
|
@@ -46,8 +46,8 @@ function claudeAdapterCmd(): string[] | null {
|
|
|
46
46
|
const runningOnBun = basename(process.execPath).toLowerCase().startsWith("bun");
|
|
47
47
|
if (process.platform !== "win32") {
|
|
48
48
|
const candidates = [
|
|
49
|
-
join(import.meta.dir, "..", "..", "node_modules", ".bin", "claude-
|
|
50
|
-
join(process.cwd(), "node_modules", ".bin", "claude-
|
|
49
|
+
join(import.meta.dir, "..", "..", "node_modules", ".bin", "claude-agent-acp"),
|
|
50
|
+
join(process.cwd(), "node_modules", ".bin", "claude-agent-acp"),
|
|
51
51
|
];
|
|
52
52
|
for (const c of candidates) if (existsSync(c)) return [c];
|
|
53
53
|
}
|
|
@@ -55,15 +55,15 @@ function claudeAdapterCmd(): string[] | null {
|
|
|
55
55
|
// do not (Windows) and when no `bun` is on PATH (installed through npm).
|
|
56
56
|
if (runningOnBun) {
|
|
57
57
|
try {
|
|
58
|
-
const pkgJson = Bun.resolveSync("@
|
|
58
|
+
const pkgJson = Bun.resolveSync("@agentclientprotocol/claude-agent-acp/package.json", import.meta.dir);
|
|
59
59
|
const bin = JSON.parse(readFileSync(pkgJson, "utf8")).bin;
|
|
60
|
-
const entry = typeof bin === "string" ? bin : bin?.["claude-
|
|
60
|
+
const entry = typeof bin === "string" ? bin : bin?.["claude-agent-acp"];
|
|
61
61
|
if (entry && existsSync(join(dirname(pkgJson), entry))) return [process.execPath, join(dirname(pkgJson), entry)];
|
|
62
62
|
} catch {
|
|
63
63
|
/* not installed next to codemax */
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
const onPath = Bun.which("claude-
|
|
66
|
+
const onPath = Bun.which("claude-agent-acp");
|
|
67
67
|
if (onPath) return [onPath];
|
|
68
68
|
const bun = Bun.which("bun") ?? (runningOnBun ? process.execPath : null);
|
|
69
69
|
const bunx = Bun.which("bunx") ? [Bun.which("bunx")!] : bun ? [bun, "x"] : null;
|