@clonst/clonst 1.0.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/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CodexProvider } from "./providers/codex.js";
5
+ import { formatReviewError, reviewInputShape, reviewOutputShape, runReview } from "./tools/review.js";
6
+ import { loadConfig } from "./utils/config.js";
7
+ import { logStderr } from "./utils/logger.js";
8
+ import { logsDir } from "./utils/paths.js";
9
+ import { spawnCLI } from "./utils/process.js";
10
+ export const SERVER_NAME = "clonst";
11
+ export const SERVER_VERSION = "1.0.0";
12
+ // IMPORTANT: with the stdio transport, stdout is reserved for the MCP protocol.
13
+ // All human-readable logging goes to stderr (logStderr), never console.log.
14
+ async function main() {
15
+ const server = new McpServer({
16
+ name: SERVER_NAME,
17
+ version: SERVER_VERSION,
18
+ });
19
+ server.registerTool("clonst_ping", {
20
+ description: "Full Clonst server diagnostic: health, codex CLI availability and version, " +
21
+ "login status, loaded config, logs directory. Consumes no LLM quota.",
22
+ inputSchema: {},
23
+ }, async () => {
24
+ const [versionResult, loginResult] = await Promise.all([
25
+ spawnCLI("codex", ["--version"], { timeoutMs: 15_000 }).catch(() => null),
26
+ spawnCLI("codex", ["login", "status"], { timeoutMs: 15_000 }).catch(() => null),
27
+ ]);
28
+ const codexAvailable = versionResult?.exitCode === 0;
29
+ const loggedIn = loginResult?.exitCode === 0;
30
+ return {
31
+ content: [
32
+ {
33
+ type: "text",
34
+ text: JSON.stringify({
35
+ status: "ok",
36
+ server: SERVER_NAME,
37
+ version: SERVER_VERSION,
38
+ node: process.version,
39
+ codex_available: codexAvailable,
40
+ codex_version: codexAvailable ? versionResult.stdout.trim() : null,
41
+ codex_logged_in: codexAvailable ? loggedIn : null,
42
+ config: loadConfig(),
43
+ logs_dir: logsDir(),
44
+ ...(codexAvailable
45
+ ? loggedIn
46
+ ? {}
47
+ : { hint: "Reconnect the CLI: codex login" }
48
+ : { hint: "Install the CLI: npm install -g @openai/codex, then codex login" }),
49
+ }),
50
+ },
51
+ ],
52
+ };
53
+ });
54
+ server.registerTool("clonst_review", {
55
+ description: "Have a plan, code or proposal critiqued by Codex (a second LLM, through its CLI and the user's " +
56
+ "ChatGPT subscription). One call = one structured critique (APPROVED/CHANGES_NEEDED verdict, " +
57
+ "required changes, suggestions, risks). The revision loop lives on your side: apply the critiques, " +
58
+ "then call this tool again with the revised content and the returned thread_id - the reviewer keeps " +
59
+ "its session memory across rounds. Loop until consensus=true, following the returned next_action " +
60
+ "field, or until the user decides to stop. " +
61
+ "WHEN TO USE IT: the criterion is LOGIC, not size. Call it by default, without being asked, for any " +
62
+ "development that touches the project's logic or behavior (business logic, computations, data flows, " +
63
+ "models, routes, APIs, state, error handling, concurrency, security, migrations), and for plans and " +
64
+ "architecture decisions before coding. Do NOT use it for pure presentation (static HTML/CSS, copy), " +
65
+ "documentation, renames without behavior change, or throwaway content the user will not run. Every " +
66
+ "call consumes the user's subscription quota; when the scope is unclear, ask the user.",
67
+ inputSchema: reviewInputShape,
68
+ outputSchema: reviewOutputShape,
69
+ }, async (input) => {
70
+ try {
71
+ const result = await runReview(input, new CodexProvider(), loadConfig());
72
+ return {
73
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
74
+ structuredContent: result,
75
+ };
76
+ }
77
+ catch (err) {
78
+ return {
79
+ content: [{ type: "text", text: JSON.stringify(formatReviewError(err), null, 2) }],
80
+ isError: true,
81
+ };
82
+ }
83
+ });
84
+ const transport = new StdioServerTransport();
85
+ await server.connect(transport);
86
+ logStderr(`v${SERVER_VERSION} started (stdio transport)`);
87
+ }
88
+ main().catch((err) => {
89
+ logStderr(`Fatal startup error: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`);
90
+ process.exit(1);
91
+ });
92
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Typed provider error. `hint` is a concrete action for the user
3
+ * (e.g. "codex login"), forwarded as is to the calling LLM which presents it.
4
+ * The complete raw outputs are already saved to disk by the time this error is
5
+ * thrown (rule: never lose a paid response).
6
+ */
7
+ export class ProviderError extends Error {
8
+ kind;
9
+ hint;
10
+ constructor(kind, message, hint) {
11
+ super(message);
12
+ this.kind = kind;
13
+ this.hint = hint;
14
+ this.name = "ProviderError";
15
+ }
16
+ }
17
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/providers/base.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAEpB;IAEA;IAHlB,YACkB,IAAuB,EACvC,OAAe,EACC,IAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAmB;QAEvB,SAAI,GAAJ,IAAI,CAAS;QAG7B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF"}
@@ -0,0 +1,300 @@
1
+ import { readFileSync, rmSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { assertSafeArg, spawnCLI } from "../utils/process.js";
5
+ import { logStderr } from "../utils/logger.js";
6
+ import { ProviderError, } from "./base.js";
7
+ /**
8
+ * Reads the codex CLI's effective defaults from its config (~/.codex/config.toml,
9
+ * or CODEX_HOME when set - the same variable the CLI itself uses).
10
+ * Best-effort, for DISPLAY only: just the ROOT keys count, reading stops at the
11
+ * first [section] table ([profiles.x] blocks carry their own model keys, which
12
+ * do not apply here). null when the file is absent, unreadable or the key is
13
+ * missing: codex's internal default cannot be known without spending quota, so
14
+ * we show "unknown" rather than an invention.
15
+ */
16
+ export function readCodexDefaults() {
17
+ const home = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
18
+ const result = {
19
+ model: null,
20
+ reasoningEffort: null,
21
+ };
22
+ // Same whitelist as the overrides: exotic TOML (escapes, spaces...) becomes
23
+ // null ("unknown") instead of an incorrectly displayed value.
24
+ const safe = (value) => (/^[A-Za-z0-9._-]+$/.test(value) ? value : null);
25
+ try {
26
+ const raw = readFileSync(path.join(home, "config.toml"), "utf-8");
27
+ for (const line of raw.split(/\r?\n/)) {
28
+ if (/^\s*\[/.test(line))
29
+ break; // end of the TOML root keys
30
+ const model = line.match(/^\s*model\s*=\s*"([^"]+)"/);
31
+ if (model) {
32
+ result.model = safe(model[1]);
33
+ continue;
34
+ }
35
+ const effort = line.match(/^\s*model_reasoning_effort\s*=\s*"([^"]+)"/);
36
+ if (effort)
37
+ result.reasoningEffort = safe(effort[1]);
38
+ }
39
+ }
40
+ catch {
41
+ // File absent or unreadable: defaults unknown, null assumed (display only)
42
+ }
43
+ return result;
44
+ }
45
+ export function parseCodexEvents(jsonl) {
46
+ const result = {
47
+ threadId: null,
48
+ agentMessages: [],
49
+ usage: null,
50
+ malformedLines: 0,
51
+ };
52
+ for (const line of jsonl.split("\n")) {
53
+ const trimmed = line.trim();
54
+ if (!trimmed)
55
+ continue;
56
+ let event;
57
+ try {
58
+ event = JSON.parse(trimmed);
59
+ }
60
+ catch {
61
+ result.malformedLines++;
62
+ continue;
63
+ }
64
+ if (typeof event !== "object" || event === null) {
65
+ result.malformedLines++;
66
+ continue;
67
+ }
68
+ const e = event;
69
+ if (e.type === "thread.started") {
70
+ if (typeof e.thread_id === "string" && e.thread_id.length > 0) {
71
+ result.threadId = e.thread_id;
72
+ }
73
+ else {
74
+ result.malformedLines++;
75
+ }
76
+ }
77
+ else if (e.type === "item.completed") {
78
+ const item = (e.item ?? null);
79
+ if (item && item.type === "agent_message") {
80
+ if (typeof item.text === "string") {
81
+ result.agentMessages.push(item.text);
82
+ }
83
+ else {
84
+ result.malformedLines++;
85
+ }
86
+ }
87
+ // Other item types (reasoning, tool calls...) are legitimate and ignored.
88
+ }
89
+ else if (e.type === "turn.completed") {
90
+ const usage = (e.usage ?? null);
91
+ if (usage && typeof usage === "object" && !Array.isArray(usage)) {
92
+ const numeric = {};
93
+ for (const [key, value] of Object.entries(usage)) {
94
+ if (typeof value === "number")
95
+ numeric[key] = value;
96
+ }
97
+ result.usage = numeric;
98
+ }
99
+ else {
100
+ result.malformedLines++;
101
+ }
102
+ }
103
+ // Other event types (turn.started, ...) are ignored.
104
+ }
105
+ return result;
106
+ }
107
+ const DEFAULT_RUNTIME = { command: "codex", prefixArgs: [] };
108
+ const INSTALL_HINT = "Install the CLI: npm install -g @openai/codex, then codex login";
109
+ const LOGIN_HINT = "Reconnect the CLI: codex login (auth is shared with the VS Code extension)";
110
+ const QUOTA_HINT_BASE = "Codex usage quota reached (ChatGPT subscription limit, rolling window). " +
111
+ "Continue the work WITHOUT review and note what will need reviewing; ";
112
+ const QUOTA_HINT_RESUME = QUOTA_HINT_BASE +
113
+ "the Codex session stays resumable later through the same thread_id, once the window resets.";
114
+ const QUOTA_HINT_NEW = QUOTA_HINT_BASE + "relaunch the review later (new session: no thread_id was created for this round).";
115
+ export class CodexProvider {
116
+ runtime;
117
+ name = "codex";
118
+ constructor(runtime = DEFAULT_RUNTIME) {
119
+ this.runtime = runtime;
120
+ }
121
+ async isAvailable() {
122
+ try {
123
+ const result = await spawnCLI(this.runtime.command, [...this.runtime.prefixArgs, "--version"], {
124
+ timeoutMs: 15_000,
125
+ });
126
+ return result.exitCode === 0;
127
+ }
128
+ catch {
129
+ return false;
130
+ }
131
+ }
132
+ /**
133
+ * Builds the invocation arguments. Exposed for tests.
134
+ * New session: `[-c ...] exec - --sandbox read-only ...`
135
+ * Resume: `[-c ...] exec resume <thread_id> - ...` (no --sandbox: the
136
+ * resume subcommand rejects it, the sandbox is inherited from the session).
137
+ * Model/effort overrides are ROOT-level codex `-c` flags (before the
138
+ * subcommand), accepted uniformly by exec and resume: without them the CLI
139
+ * uses ~/.codex/config.toml as before.
140
+ */
141
+ buildArgs(threadId, lastMessagePath, overrides) {
142
+ const args = [...this.runtime.prefixArgs];
143
+ if (overrides?.model !== undefined) {
144
+ args.push("-c", `model=${assertSafeArg(overrides.model, "codex_model")}`);
145
+ }
146
+ if (overrides?.reasoningEffort !== undefined) {
147
+ args.push("-c", `model_reasoning_effort=${assertSafeArg(overrides.reasoningEffort, "codex_reasoning_effort")}`);
148
+ }
149
+ if (threadId !== undefined) {
150
+ args.push("exec", "resume", assertSafeArg(threadId, "thread_id"), "-");
151
+ }
152
+ else {
153
+ args.push("exec", "-", "--sandbox", "read-only");
154
+ }
155
+ args.push("--json", "--skip-git-repo-check", "--output-last-message", lastMessagePath);
156
+ return args;
157
+ }
158
+ async invoke(invocation) {
159
+ const { logger, tag } = invocation;
160
+ const lastMessagePath = logger.rawPath(`${tag}_last-message.txt`);
161
+ // Deterministic path: a file left by a previous invocation would be re-read
162
+ // as if it came from this one (silent stale response). Removed BEFORE the spawn.
163
+ try {
164
+ rmSync(lastMessagePath, { force: true });
165
+ }
166
+ catch (err) {
167
+ // force:true covers ENOENT; this is a lock/permission issue. Continuing
168
+ // would risk re-reading a stale file: fail explicitly with context.
169
+ throw new Error(`Cannot remove the stale last-message file (session ${logger.sessionId}, tag ${tag}): ` +
170
+ `${err instanceof Error ? err.message : String(err)}`);
171
+ }
172
+ const args = this.buildArgs(invocation.threadId, lastMessagePath, {
173
+ model: invocation.model,
174
+ reasoningEffort: invocation.reasoningEffort,
175
+ });
176
+ let spawnResult;
177
+ try {
178
+ spawnResult = await spawnCLI(this.runtime.command, args, {
179
+ cwd: invocation.cwd,
180
+ timeoutMs: invocation.timeoutMs,
181
+ stdin: invocation.prompt,
182
+ });
183
+ }
184
+ catch (err) {
185
+ throw new ProviderError("cli_not_found", `Cannot launch the codex CLI: ${err instanceof Error ? err.message : String(err)}`, INSTALL_HINT);
186
+ }
187
+ // Raw save BEFORE any interpretation (rule: never lose a paid response)
188
+ logger.saveRaw(`${tag}_stdout.jsonl`, spawnResult.stdout);
189
+ if (spawnResult.stderr.trim()) {
190
+ logger.saveRaw(`${tag}_stderr.txt`, spawnResult.stderr);
191
+ }
192
+ if (spawnResult.timedOut) {
193
+ logger.log({ tag, actor: this.name, event: "timeout", duration_ms: spawnResult.durationMs });
194
+ throw new ProviderError("timeout", `codex did not respond within the allotted time (${spawnResult.durationMs} ms). ` +
195
+ `Partial outputs are saved in the session logs.`);
196
+ }
197
+ if (spawnResult.exitCode !== 0) {
198
+ const stderr = spawnResult.stderr.trim();
199
+ // Locale-independent detection: cmd.exe returns 9009 for an unknown
200
+ // command, sh returns 127. Text patterns (including localized Windows
201
+ // messages) are only a secondary net.
202
+ const notFound = spawnResult.exitCode === 9009 ||
203
+ spawnResult.exitCode === 127 ||
204
+ /not recognized|command not found|pas reconnu|introuvable/i.test(stderr);
205
+ const authIssue = /login|logged out|unauthorized|401/i.test(stderr);
206
+ const quotaIssue = /usage limit|rate limit|too many requests|quota|429/i.test(stderr);
207
+ logger.log({
208
+ tag,
209
+ actor: this.name,
210
+ event: notFound ? "cli_not_found" : "exec_failed",
211
+ exit_code: spawnResult.exitCode,
212
+ duration_ms: spawnResult.durationMs,
213
+ });
214
+ const hint = notFound
215
+ ? INSTALL_HINT
216
+ : quotaIssue
217
+ ? invocation.threadId !== undefined
218
+ ? QUOTA_HINT_RESUME
219
+ : QUOTA_HINT_NEW
220
+ : authIssue
221
+ ? LOGIN_HINT
222
+ : undefined;
223
+ throw new ProviderError(notFound ? "cli_not_found" : "exec_failed", `codex failed (exit ${spawnResult.exitCode}).\nFull stderr:\n${stderr || "(empty)"}`, hint);
224
+ }
225
+ const events = parseCodexEvents(spawnResult.stdout);
226
+ if (events.malformedLines > 0) {
227
+ logStderr(`codex: ${events.malformedLines} unparsable JSONL line(s) (raw saved: ${tag}_stdout.jsonl)`);
228
+ }
229
+ // Primary source: the --output-last-message file written by the CLI itself.
230
+ // Fallback: the last agent_message event of the JSONL stream.
231
+ let text = null;
232
+ try {
233
+ const fromFile = readFileSync(lastMessagePath, "utf-8");
234
+ if (fromFile.trim())
235
+ text = fromFile;
236
+ }
237
+ catch {
238
+ // file absent: fall back to the JSONL stream
239
+ }
240
+ if (text === null && events.agentMessages.length > 0) {
241
+ text = events.agentMessages[events.agentMessages.length - 1];
242
+ }
243
+ if (text === null) {
244
+ logger.log({ tag, actor: this.name, event: "empty_response", duration_ms: spawnResult.durationMs });
245
+ throw new ProviderError("empty_response", `codex finished (exit 0) without a usable agent message. ` +
246
+ `Raw stream saved: ${tag}_stdout.jsonl`);
247
+ }
248
+ if (events.threadId === null) {
249
+ // Session resumption is Clonst's central mechanism: a response without a
250
+ // thread_id makes the next round impossible, so this must be visible.
251
+ logStderr(`codex: usable response but NO thread_id emitted (${tag}): session resumption will be impossible for this round`);
252
+ logger.log({ tag, actor: this.name, event: "missing_thread_id" });
253
+ }
254
+ else if (invocation.threadId !== undefined && events.threadId !== invocation.threadId) {
255
+ // Session divergence: the CLI re-emitted a thread_id different from the
256
+ // resumed one. A serious anomaly (later rounds would target another
257
+ // session), but not fatal: the response is already paid for and the
258
+ // critique remains usable.
259
+ logStderr(`codex: re-emitted thread_id (${events.threadId}) DIFFERS from the resumed one (${invocation.threadId}) - possible session divergence (${tag})`);
260
+ logger.log({
261
+ tag,
262
+ actor: this.name,
263
+ event: "thread_id_mismatch",
264
+ requested: invocation.threadId,
265
+ received: events.threadId,
266
+ });
267
+ }
268
+ // Best-effort resolution of the model/effort actually used (for display in
269
+ // the final report): requested override, else the ~/.codex/config.toml
270
+ // default. Codex JSONL events do not expose the model (verified on 0.142.5).
271
+ const defaults = invocation.model === undefined || invocation.reasoningEffort === undefined
272
+ ? readCodexDefaults()
273
+ : { model: null, reasoningEffort: null };
274
+ const model = invocation.model ?? defaults.model;
275
+ const reasoningEffort = invocation.reasoningEffort ?? defaults.reasoningEffort;
276
+ logger.log({
277
+ tag,
278
+ actor: this.name,
279
+ event: "invoke_ok",
280
+ thread_id: events.threadId,
281
+ resumed: invocation.threadId !== undefined,
282
+ duration_ms: spawnResult.durationMs,
283
+ usage: events.usage,
284
+ model,
285
+ reasoning_effort: reasoningEffort,
286
+ // File name (not an absolute path): the session's raw directory may be
287
+ // renamed by a migration, the name stays valid by construction.
288
+ last_message_file: `${tag}_last-message.txt`,
289
+ });
290
+ return {
291
+ text,
292
+ threadId: events.threadId,
293
+ durationMs: spawnResult.durationMs,
294
+ usage: events.usage,
295
+ model,
296
+ reasoningEffort,
297
+ };
298
+ }
299
+ }
300
+ //# sourceMappingURL=codex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/providers/codex.ts"],"names":[],"mappings":"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"}