codex-potter 0.1.31 → 0.2.0-next.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 CHANGED
@@ -1,24 +1,10 @@
1
1
  # CodexPotter
2
2
 
3
- Install:
3
+ Set up CodexPotter for use from Codex:
4
4
 
5
5
  ```sh
6
- npm install -g codex-potter
6
+ npx codex-potter@next setup
7
7
  ```
8
8
 
9
- Supported platforms:
10
-
11
- - macOS: Apple Silicon + Intel
12
- - Linux: x86_64 + aarch64
13
- - Windows: x86_64 + aarch64 (ARM64)
14
-
15
- Packaging note:
16
-
17
- - The `codex-potter` npm package ships a small cross-platform launcher.
18
- - The native binary payload is delivered via platform-specific optional dependencies.
19
- If you see an error like `Missing optional dependency codex-potter-<platform>`, reinstall
20
- CodexPotter so your package manager can fetch the correct platform package.
21
-
22
- ---
23
-
24
- See https://github.com/breezewish/CodexPotter for full usage.
9
+ The setup command configures the global gitignore, installs the CodexPotter
10
+ subagent profile, and installs the `$loop` skill.
@@ -1,233 +1,523 @@
1
1
  #!/usr/bin/env node
2
- // Unified entry point for CodexPotter.
3
2
 
4
- import { spawn } from "node:child_process";
5
- import { existsSync } from "fs";
6
- import { createRequire } from "node:module";
7
- import path from "path";
8
- import { fileURLToPath } from "url";
3
+ import { spawn, spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import readline from "node:readline";
7
+ import { fileURLToPath } from "node:url";
9
8
 
10
- // __dirname equivalent in ESM
11
9
  const __filename = fileURLToPath(import.meta.url);
12
10
  const __dirname = path.dirname(__filename);
13
- const require = createRequire(import.meta.url);
14
-
15
- const PLATFORM_PACKAGE_BY_TARGET = {
16
- "x86_64-unknown-linux-musl": "codex-potter-linux-x64",
17
- "aarch64-unknown-linux-musl": "codex-potter-linux-arm64",
18
- "x86_64-apple-darwin": "codex-potter-darwin-x64",
19
- "aarch64-apple-darwin": "codex-potter-darwin-arm64",
20
- "x86_64-pc-windows-msvc": "codex-potter-win32-x64",
21
- "aarch64-pc-windows-msvc": "codex-potter-win32-arm64",
11
+
12
+ const CODEXPOTTER_GITIGNORE_ENTRY = "/.codexpotter";
13
+ const LOOP_SKILL_COMMAND = [
14
+ "skills",
15
+ "add",
16
+ "--yes",
17
+ "-g",
18
+ "https://github.com/breezewish/CodexPotter/tree/v2",
19
+ "-a",
20
+ "codex",
21
+ ];
22
+
23
+ const colorEnabled = shouldUseColor();
24
+ const colors = {
25
+ bold: "\x1b[1m",
26
+ cyan: "\x1b[36m",
27
+ dim: "\x1b[2m",
28
+ green: "\x1b[32m",
29
+ red: "\x1b[31m",
30
+ reset: "\x1b[0m",
31
+ yellow: "\x1b[33m",
22
32
  };
23
33
 
24
- const { platform, arch } = process;
25
-
26
- let targetTriple = null;
27
- switch (platform) {
28
- case "linux":
29
- case "android":
30
- switch (arch) {
31
- case "x64":
32
- targetTriple = "x86_64-unknown-linux-musl";
33
- break;
34
- case "arm64":
35
- targetTriple = "aarch64-unknown-linux-musl";
36
- break;
37
- default:
38
- break;
39
- }
40
- break;
41
- case "darwin":
42
- switch (arch) {
43
- case "x64":
44
- targetTriple = "x86_64-apple-darwin";
45
- break;
46
- case "arm64":
47
- targetTriple = "aarch64-apple-darwin";
48
- break;
49
- default:
50
- break;
34
+ async function main(args) {
35
+ const { command, yes, errors } = parseArgs(args);
36
+ if (errors.length > 0) {
37
+ printErrors(errors);
38
+ printAvailableCommands();
39
+ return 1;
40
+ }
41
+
42
+ if (command !== "setup") {
43
+ if (command) {
44
+ printErrors([`Unknown command: ${command}`]);
51
45
  }
52
- break;
53
- case "win32":
54
- switch (arch) {
55
- case "x64":
56
- targetTriple = "x86_64-pc-windows-msvc";
57
- break;
58
- case "arm64":
59
- targetTriple = "aarch64-pc-windows-msvc";
60
- break;
61
- default:
62
- break;
46
+ printAvailableCommands();
47
+ return command ? 1 : 0;
48
+ }
49
+
50
+ await runSetup({ yes });
51
+ return 0;
52
+ }
53
+
54
+ function parseArgs(args) {
55
+ const remaining = [];
56
+ const errors = [];
57
+ let yes = false;
58
+
59
+ for (const arg of args) {
60
+ if (arg === "--yes" || arg === "-y") {
61
+ yes = true;
62
+ } else if (arg === "--help" || arg === "-h") {
63
+ return { command: null, yes, errors };
64
+ } else if (arg.startsWith("-")) {
65
+ errors.push(`Unknown option: ${arg}`);
66
+ } else {
67
+ remaining.push(arg);
63
68
  }
64
- break;
65
- default:
66
- break;
69
+ }
70
+
71
+ if (remaining.length > 1) {
72
+ errors.push(`Unexpected argument: ${remaining[1]}`);
73
+ }
74
+
75
+ return { command: remaining[0] ?? null, yes, errors };
67
76
  }
68
77
 
69
- if (!targetTriple) {
70
- throw new Error(`Unsupported platform: ${platform} (${arch})`);
78
+ function printAvailableCommands() {
79
+ console.log(format("CodexPotter", "bold"));
80
+ console.log("");
81
+ console.log("Usage:");
82
+ console.log(" codex-potter setup [--yes]");
83
+ console.log("");
84
+ console.log("Available commands:");
85
+ console.log(" setup Configure CodexPotter for $loop.");
71
86
  }
72
87
 
73
- const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
74
- if (!platformPackage) {
75
- throw new Error(`Unsupported target triple: ${targetTriple}`);
88
+ function printErrors(errors) {
89
+ for (const error of errors) {
90
+ console.error(`${format("Error:", "red")} ${error}`);
91
+ }
76
92
  }
77
93
 
78
- const codexPotterBinaryName =
79
- process.platform === "win32" ? "codex-potter.exe" : "codex-potter";
80
- const localVendorRoot = path.join(__dirname, "..", "vendor");
81
- const localBinaryPath = path.join(
82
- localVendorRoot,
83
- targetTriple,
84
- "codex-potter",
85
- codexPotterBinaryName,
86
- );
87
- const packageManager = detectPackageManager();
94
+ async function runSetup({ yes }) {
95
+ const home = getHomeDir();
96
+ const resourcePath = path.join(
97
+ __dirname,
98
+ "..",
99
+ "resources",
100
+ "potter_worker.toml",
101
+ );
102
+ const profilePath = path.join(home, ".codex", "agents", "potter_worker.toml");
103
+ const profileContent = await fs.promises.readFile(resourcePath, "utf8");
88
104
 
89
- let vendorRoot;
90
- try {
91
- const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
92
- vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
93
- } catch {
94
- if (existsSync(localBinaryPath)) {
95
- vendorRoot = localVendorRoot;
105
+ const globalGitignore = resolveGlobalGitignore(home);
106
+ const gitignoreContent = await readTextIfExists(globalGitignore.path);
107
+ const gitignoreNeedsWrite = !gitignoreIgnoresCodexPotter(gitignoreContent);
108
+
109
+ const currentProfileContent = await readTextIfExists(profilePath);
110
+ const profileNeedsWrite = currentProfileContent !== profileContent;
111
+ const skillInstaller = resolveSkillInstaller();
112
+
113
+ printSetupPlan({
114
+ gitignoreNeedsWrite,
115
+ profileNeedsWrite,
116
+ profilePath,
117
+ skillInstaller,
118
+ });
119
+
120
+ if (!yes && !(await confirm())) {
121
+ console.log("Setup cancelled.");
122
+ return;
123
+ }
124
+
125
+ if (gitignoreNeedsWrite) {
126
+ const gitignoreUpdated = await ensureCodexPotterIgnored(globalGitignore.path);
127
+ if (gitignoreUpdated) {
128
+ console.log(
129
+ `${format("✓ Added", "green")} ${format(
130
+ CODEXPOTTER_GITIGNORE_ENTRY,
131
+ "dim",
132
+ )} to ${format(displayPath(globalGitignore.path), "dim")}`,
133
+ );
134
+ }
135
+ }
136
+
137
+ if (profileNeedsWrite) {
138
+ await fs.promises.mkdir(path.dirname(profilePath), { recursive: true });
139
+ await fs.promises.writeFile(profilePath, profileContent, "utf8");
140
+ console.log(
141
+ `${format("✓ Added", "green")} subagent profile ${format(
142
+ displayPath(profilePath),
143
+ "dim",
144
+ )}`,
145
+ );
146
+ }
147
+
148
+ await runLoopSkillInstaller(skillInstaller);
149
+
150
+ console.log("");
151
+ console.log(format("✨ CodexPotter setup complete!", "green"));
152
+ console.log(
153
+ `${format("Usage in Codex:", "bold")} ${format(
154
+ "$loop",
155
+ "cyan",
156
+ )} <your_instruction>`,
157
+ );
158
+ }
159
+
160
+ function printSetupPlan({
161
+ gitignoreNeedsWrite,
162
+ profileNeedsWrite,
163
+ profilePath,
164
+ skillInstaller,
165
+ }) {
166
+ console.log(format("CodexPotter setup", "bold"));
167
+ console.log("");
168
+ console.log(
169
+ `${statusLabel(gitignoreNeedsWrite)} Ignore ${format(
170
+ CODEXPOTTER_GITIGNORE_ENTRY,
171
+ "dim",
172
+ )} in global gitignore`,
173
+ );
174
+ console.log(
175
+ `${statusLabel(profileNeedsWrite)} Add subagent profile ${format(
176
+ displayPath(profilePath),
177
+ "dim",
178
+ )}`,
179
+ );
180
+ console.log(
181
+ `${statusLabel(true)} Install / update skill: ${format(
182
+ skillInstaller.displayCommand,
183
+ "cyan",
184
+ )}`,
185
+ );
186
+ console.log("");
187
+ }
188
+
189
+ function statusLabel(needsAction) {
190
+ return needsAction ? format("□ Todo:", "yellow") : format("✓ Skip:", "green");
191
+ }
192
+
193
+ async function confirm() {
194
+ if (process.stdin.isTTY && process.stdout.isTTY) {
195
+ return confirmInteractively();
96
196
  }
197
+
198
+ return confirmFromLineInput();
97
199
  }
98
200
 
99
- if (!vendorRoot) {
100
- throw missingOptionalDependencyError(platformPackage, packageManager);
201
+ async function confirmInteractively() {
202
+ return await new Promise((resolve) => {
203
+ let selected = true;
204
+ let settled = false;
205
+ const input = process.stdin;
206
+
207
+ const render = () => {
208
+ process.stdout.write(`\r\x1b[2K${confirmPrompt(selected)}`);
209
+ };
210
+
211
+ const settle = (value) => {
212
+ if (settled) {
213
+ return;
214
+ }
215
+ settled = true;
216
+ input.off("keypress", onKeypress);
217
+ input.off("close", onClose);
218
+ input.off("end", onClose);
219
+ if (input.isTTY) {
220
+ input.setRawMode(false);
221
+ }
222
+ process.stdout.write("\n");
223
+ resolve(value);
224
+ };
225
+
226
+ const onClose = () => {
227
+ settle(false);
228
+ };
229
+
230
+ const onKeypress = (text, key = {}) => {
231
+ if (key.ctrl && key.name === "c") {
232
+ settle(false);
233
+ } else if (key.name === "return") {
234
+ settle(selected);
235
+ } else if (key.name === "escape") {
236
+ settle(false);
237
+ } else if (key.name === "left" || key.name === "up") {
238
+ selected = true;
239
+ render();
240
+ } else if (key.name === "right" || key.name === "down") {
241
+ selected = false;
242
+ render();
243
+ } else if (key.name === "tab" || text === " ") {
244
+ selected = !selected;
245
+ render();
246
+ } else if (text && text.toLowerCase() === "y") {
247
+ settle(true);
248
+ } else if (text && text.toLowerCase() === "n") {
249
+ settle(false);
250
+ }
251
+ };
252
+
253
+ readline.emitKeypressEvents(input);
254
+ input.on("keypress", onKeypress);
255
+ input.on("close", onClose);
256
+ input.on("end", onClose);
257
+ input.setRawMode(true);
258
+ input.resume();
259
+ render();
260
+ });
101
261
  }
102
262
 
103
- const archRoot = path.join(vendorRoot, targetTriple);
104
- const binaryPath = path.join(archRoot, "codex-potter", codexPotterBinaryName);
263
+ async function confirmFromLineInput() {
264
+ const answer = await new Promise((resolve) => {
265
+ let settled = false;
266
+ const rl = readline.createInterface({
267
+ input: process.stdin,
268
+ output: process.stdout,
269
+ terminal: false,
270
+ });
105
271
 
106
- // Use an asynchronous spawn instead of spawnSync so that Node is able to
107
- // respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
108
- // executing. This allows us to forward those signals to the child process
109
- // and guarantees that when either the child terminates or the parent
110
- // receives a fatal signal, both processes exit in a predictable manner.
272
+ const settle = (value) => {
273
+ if (settled) {
274
+ return;
275
+ }
276
+ settled = true;
277
+ rl.close();
278
+ resolve(value);
279
+ };
111
280
 
112
- function getUpdatedPath(newDirs) {
113
- const pathSep = process.platform === "win32" ? ";" : ":";
114
- const existingPath = process.env.PATH || "";
115
- const updatedPath = [
116
- ...newDirs,
117
- ...existingPath.split(pathSep).filter(Boolean),
118
- ].join(pathSep);
119
- return updatedPath;
281
+ rl.on("close", () => {
282
+ settle("");
283
+ });
284
+ rl.question(confirmPrompt(true), settle);
285
+ });
286
+
287
+ return /^(y|yes)$/i.test(String(answer).trim());
120
288
  }
121
289
 
122
- /**
123
- * Use heuristics to detect the package manager that was used to install CodexPotter
124
- * in order to give the user a hint about how to update it.
125
- */
126
- function detectPackageManager() {
127
- const userAgent = process.env.npm_config_user_agent || "";
128
- if (/\bbun\//.test(userAgent)) {
129
- return "bun";
290
+ function confirmPrompt(yesSelected) {
291
+ const yes = yesSelected
292
+ ? `${format("●", "green")} Yes`
293
+ : format("○ Yes", "dim");
294
+ const no = yesSelected
295
+ ? format(" / No", "dim")
296
+ : ` ${format("/", "dim")} ${format("●", "green")} No`;
297
+ return `Continue? ${yes}${no} `;
298
+ }
299
+
300
+ function resolveGlobalGitignore(home) {
301
+ const configured = spawnSync(
302
+ "git",
303
+ ["config", "--global", "--path", "--get", "core.excludesfile"],
304
+ { encoding: "utf8" },
305
+ );
306
+
307
+ if (configured.status === 0) {
308
+ const configuredPath = configured.stdout.trim();
309
+ if (configuredPath) {
310
+ const resolvedPath = expandHome(configuredPath, home);
311
+ return {
312
+ path: resolvedPath,
313
+ };
314
+ }
130
315
  }
131
316
 
132
- const execPath = process.env.npm_execpath || "";
133
- if (execPath.includes("bun")) {
134
- return "bun";
317
+ const configHome = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
318
+ const resolvedPath = path.join(configHome, "git", "ignore");
319
+ return {
320
+ path: resolvedPath,
321
+ };
322
+ }
323
+
324
+ async function readTextIfExists(filePath) {
325
+ try {
326
+ return await fs.promises.readFile(filePath, "utf8");
327
+ } catch (error) {
328
+ if (error && error.code === "ENOENT") {
329
+ return "";
330
+ }
331
+ throw error;
135
332
  }
333
+ }
136
334
 
137
- if (
138
- __dirname.includes(".bun/install/global") ||
139
- __dirname.includes(".bun\\install\\global")
140
- ) {
141
- return "bun";
335
+ async function ensureCodexPotterIgnored(filePath) {
336
+ const currentContent = await readTextIfExists(filePath);
337
+ if (gitignoreIgnoresCodexPotter(currentContent)) {
338
+ return false;
142
339
  }
143
340
 
144
- return userAgent ? "npm" : null;
341
+ let updated = currentContent;
342
+ if (updated && !updated.endsWith("\n")) {
343
+ updated += "\n";
344
+ }
345
+ updated += `${CODEXPOTTER_GITIGNORE_ENTRY}\n`;
346
+ await writeTextAtomic(filePath, updated);
347
+ return true;
145
348
  }
146
349
 
147
- function reinstallCommand(packageManager) {
148
- return packageManager === "bun"
149
- ? "bun install -g codex-potter@latest"
150
- : "npm install -g codex-potter@latest";
350
+ async function writeTextAtomic(filePath, content) {
351
+ const dir = path.dirname(filePath);
352
+ await fs.promises.mkdir(dir, { recursive: true });
353
+ const tempPath = path.join(
354
+ dir,
355
+ `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`,
356
+ );
357
+
358
+ try {
359
+ await fs.promises.writeFile(tempPath, content, "utf8");
360
+ await fs.promises.rename(tempPath, filePath);
361
+ } catch (error) {
362
+ await fs.promises.rm(tempPath, { force: true }).catch(() => {});
363
+ throw error;
364
+ }
365
+ }
366
+
367
+ function gitignoreIgnoresCodexPotter(contents) {
368
+ if (!contents) {
369
+ return false;
370
+ }
371
+
372
+ return simpleGitignoreIgnoresCodexPotter(contents);
373
+ }
374
+
375
+ function simpleGitignoreIgnoresCodexPotter(contents) {
376
+ let ignored = false;
377
+
378
+ for (const rawLine of contents.split(/\r?\n/)) {
379
+ const parsed = parseGitignoreLine(rawLine);
380
+ if (!parsed) {
381
+ continue;
382
+ }
383
+
384
+ if (patternMatchesCodexPotter(parsed.pattern)) {
385
+ ignored = !parsed.negated;
386
+ }
387
+ }
388
+
389
+ return ignored;
390
+ }
391
+
392
+ function parseGitignoreLine(rawLine) {
393
+ const line = rawLine.trim();
394
+ if (!line || line.startsWith("#")) {
395
+ return null;
396
+ }
397
+
398
+ if (line.startsWith("!")) {
399
+ return { negated: true, pattern: line.slice(1) };
400
+ }
401
+
402
+ return { negated: false, pattern: line };
151
403
  }
152
404
 
153
- function missingOptionalDependencyError(platformPackage, packageManager) {
154
- return new Error(
155
- `Missing optional dependency ${platformPackage}. Reinstall CodexPotter: ${reinstallCommand(packageManager)}`,
405
+ function patternMatchesCodexPotter(pattern) {
406
+ const normalized = pattern.replace(/\\/g, "/").replace(/^\/+/, "");
407
+ const withoutTrailingSlash = normalized.replace(/\/+$/, "");
408
+
409
+ return (
410
+ withoutTrailingSlash === ".codexpotter" ||
411
+ withoutTrailingSlash === "**/.codexpotter" ||
412
+ normalized === ".codexpotter/**" ||
413
+ normalized === "**/.codexpotter/**"
156
414
  );
157
415
  }
158
416
 
159
- const additionalDirs = [];
160
- const pathDir = path.join(archRoot, "path");
161
- if (existsSync(pathDir)) {
162
- additionalDirs.push(pathDir);
163
- }
164
- const updatedPath = getUpdatedPath(additionalDirs);
165
-
166
- const env = { ...process.env, PATH: updatedPath };
167
- delete env.CODEX_POTTER_MANAGED_BY_NPM;
168
- delete env.CODEX_POTTER_MANAGED_BY_BUN;
169
- const packageManagerEnvVar =
170
- packageManager === "bun"
171
- ? "CODEX_POTTER_MANAGED_BY_BUN"
172
- : "CODEX_POTTER_MANAGED_BY_NPM";
173
- env[packageManagerEnvVar] = "1";
174
-
175
- const child = spawn(binaryPath, process.argv.slice(2), {
176
- stdio: "inherit",
177
- env,
178
- });
179
-
180
- child.on("error", (err) => {
181
- // Typically triggered when the binary is missing or not executable.
182
- // Re-throwing here will terminate the parent with a non-zero exit code
183
- // while still printing a helpful stack trace.
184
- // eslint-disable-next-line no-console
185
- console.error(err);
186
- process.exit(1);
187
- });
188
-
189
- // Forward common termination signals to the child so that it shuts down
190
- // gracefully. In the handler we temporarily disable the default behavior of
191
- // exiting immediately; once the child has been signaled we simply wait for
192
- // its exit event which will in turn terminate the parent (see below).
193
- const forwardSignal = (signal) => {
194
- if (child.killed) {
195
- return;
417
+ function resolveSkillInstaller() {
418
+ const runner = detectPackageRunner();
419
+ const args =
420
+ runner === "npx" ? ["--yes", ...LOOP_SKILL_COMMAND] : LOOP_SKILL_COMMAND;
421
+ return {
422
+ command: executableForRunner(runner),
423
+ args,
424
+ displayCommand: `${runner} ${args.join(" ")}`,
425
+ };
426
+ }
427
+
428
+ function detectPackageRunner() {
429
+ const userAgent = process.env.npm_config_user_agent || "";
430
+ const execPath = process.env.npm_execpath || "";
431
+ const execName = path.basename(execPath).toLowerCase();
432
+ if (/\bbun\//.test(userAgent) || execName.startsWith("bun")) {
433
+ return "bunx";
196
434
  }
197
- try {
198
- child.kill(signal);
199
- } catch {
200
- /* ignore */
435
+ return "npx";
436
+ }
437
+
438
+ function executableForRunner(runner) {
439
+ if (process.platform === "win32") {
440
+ return `${runner}.cmd`;
201
441
  }
202
- };
442
+ return runner;
443
+ }
203
444
 
204
- const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"];
205
- forwardedSignals.forEach((sig) => {
206
- process.on(sig, () => forwardSignal(sig));
207
- });
208
-
209
- // When the child exits, mirror its termination reason in the parent so that
210
- // shell scripts and other tooling observe the correct exit status.
211
- // Wrap the lifetime of the child process in a Promise so that we can await
212
- // its termination in a structured way. The Promise resolves with an object
213
- // describing how the child exited: either via exit code or due to a signal.
214
- const childResult = await new Promise((resolve) => {
215
- child.on("exit", (code, signal) => {
216
- if (signal) {
217
- resolve({ type: "signal", signal });
218
- } else {
219
- resolve({ type: "code", exitCode: code ?? 1 });
220
- }
445
+ async function runLoopSkillInstaller(skillInstaller) {
446
+ const child = spawn(skillInstaller.command, skillInstaller.args, {
447
+ stdio: "inherit",
221
448
  });
222
- });
223
449
 
224
- if (childResult.type === "signal") {
225
- forwardedSignals.forEach((sig) => {
226
- process.removeAllListeners(sig);
450
+ const result = await new Promise((resolve, reject) => {
451
+ child.on("error", reject);
452
+ child.on("exit", (code, signal) => {
453
+ resolve({ code, signal });
454
+ });
227
455
  });
228
- // Re-emit the same signal so that the parent terminates with the expected
229
- // semantics (this also sets the correct exit code of 128 + n).
230
- process.kill(process.pid, childResult.signal);
231
- } else {
232
- process.exit(childResult.exitCode);
456
+
457
+ if (result.signal) {
458
+ throw new Error(`Loop skill installer terminated by ${result.signal}.`);
459
+ }
460
+
461
+ if (result.code !== 0) {
462
+ throw new Error(`Loop skill installer failed with exit code ${result.code}.`);
463
+ }
464
+ }
465
+
466
+ function getHomeDir() {
467
+ const home = process.env.HOME || process.env.USERPROFILE;
468
+ if (!home) {
469
+ throw new Error("Cannot determine the home directory.");
470
+ }
471
+ return home;
472
+ }
473
+
474
+ function expandHome(value, home) {
475
+ if (value === "~") {
476
+ return home;
477
+ }
478
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
479
+ return path.join(home, value.slice(2));
480
+ }
481
+ return value;
482
+ }
483
+
484
+ function displayPath(filePath) {
485
+ const home = process.env.HOME || process.env.USERPROFILE;
486
+ if (!home) {
487
+ return filePath;
488
+ }
489
+
490
+ const relative = path.relative(home, filePath);
491
+ if (relative === "") {
492
+ return "~";
493
+ }
494
+ if (!relative.startsWith("..") && !path.isAbsolute(relative)) {
495
+ return path.join("~", relative);
496
+ }
497
+ return filePath;
498
+ }
499
+
500
+ function shouldUseColor() {
501
+ if (process.env.NO_COLOR) {
502
+ return false;
503
+ }
504
+ if (process.env.FORCE_COLOR && process.env.FORCE_COLOR !== "0") {
505
+ return true;
506
+ }
507
+ return Boolean(process.stdout.isTTY);
508
+ }
509
+
510
+ function format(text, color) {
511
+ if (!colorEnabled) {
512
+ return text;
513
+ }
514
+ return `${colors[color]}${text}${colors.reset}`;
515
+ }
516
+
517
+ try {
518
+ const exitCode = await main(process.argv.slice(2));
519
+ process.exit(exitCode);
520
+ } catch (error) {
521
+ console.error(`${format("Error:", "red")} ${error.message}`);
522
+ process.exit(1);
233
523
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-potter",
3
- "version": "0.1.31",
3
+ "version": "0.2.0-next.1",
4
4
  "license": "Apache-2.0",
5
5
  "bin": {
6
6
  "codex-potter": "bin/codex-potter.js"
@@ -10,20 +10,16 @@
10
10
  "node": ">=16"
11
11
  },
12
12
  "files": [
13
- "bin"
13
+ "bin",
14
+ "resources"
14
15
  ],
16
+ "scripts": {
17
+ "test": "node --test"
18
+ },
15
19
  "repository": {
16
20
  "type": "git",
17
21
  "url": "https://github.com/breezewish/CodexPotter",
18
22
  "directory": "npm"
19
23
  },
20
- "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc",
21
- "optionalDependencies": {
22
- "codex-potter-linux-x64": "npm:codex-potter@0.1.31-linux-x64",
23
- "codex-potter-linux-arm64": "npm:codex-potter@0.1.31-linux-arm64",
24
- "codex-potter-darwin-x64": "npm:codex-potter@0.1.31-darwin-x64",
25
- "codex-potter-darwin-arm64": "npm:codex-potter@0.1.31-darwin-arm64",
26
- "codex-potter-win32-x64": "npm:codex-potter@0.1.31-win32-x64",
27
- "codex-potter-win32-arm64": "npm:codex-potter@0.1.31-win32-arm64"
28
- }
24
+ "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc"
29
25
  }
@@ -0,0 +1,122 @@
1
+ name = "potter_worker"
2
+ description = "General purpose worker, only used in CodexPotter loop"
3
+ developer_instructions = """
4
+
5
+ <WORKFLOW_INSTRUCTIONS>
6
+
7
+ Run the workflow below to implement the overall goal recorded in the handoff file.
8
+ Keep handoff file updated until all listed tasks are complete or handoff file's `status == skip`.
9
+
10
+ - `.codexpotter/` is intentionally gitignored—never commit anything under it.
11
+ - Sections in handoff file: Overall Goal, In Progress, Todo, Done
12
+ - handoff file's status in front matter: initial / open / skip
13
+
14
+ # Phase: `status == initial`
15
+
16
+ 1. Resolve and fully understand user's request in `Overall Goal`.
17
+
18
+ 2. For user request that:
19
+ - requires broken down into smaller tasks:
20
+ set status to `open` and create smaller tasks in `Todo`.
21
+ - can be done / answered immediately:
22
+ do so and record in `Done`, set status to `skip`. No need to create other tasks.
23
+
24
+ # Phase: `status == open`
25
+
26
+ 1. Always continue tasks in `In Progress` first (if any).
27
+ - If none are in progress, pick from `Todo` (not necessarily first, choose wisely).
28
+ - You may start multiple related tasks, but don't start multiple large/complex ones at once.
29
+
30
+ 2. When start tasks, move them from `Todo` -> `In Progress` (keep text unchanged).
31
+
32
+ 3. When complete a task:
33
+
34
+ 3.1. Append an entry to `Done` including:
35
+ - what you completed (concise, derived from the original task, keep necessary details)
36
+ - key decisions + rationale
37
+ - files changed (if any)
38
+ - learnings for future iterations (optional)
39
+
40
+ Keep it concise (brevity > grammar).
41
+
42
+ 3.2. Remove task from `Todo`/`In Progress`.
43
+
44
+ 3.3. Create a git commit for your changes (if any) with a succinct message. No need to commit the handoff file.
45
+
46
+ 4. You may add/remove `Todo` tasks as needed.
47
+ - Break large tasks into small, concrete steps; adjust tasks as your understanding improves.
48
+
49
+ 5. If all tasks are complete, do strict review and try to enhance:
50
+
51
+ 5.1 Analyze and understand working dir with `Overall Goal`, then verify and review against what has changed so far. Utilize review skills if available.
52
+
53
+ handoff file's front matter recorded git commit before change; use it to learn diffs.
54
+
55
+ 5.2 Identify issues, missing parts, unaligned areas, or possible improvements, and add them to `Todo`.
56
+
57
+ IMPORTANT PRINCIPLE: `Done` TASKS COULD BE MISLEADING, always be critical and skeptical about `Done` tasks,
58
+ as they could be written by previous low-quality agents, claimed to be done but actually incomplete,
59
+ incorrect, not well-designed, not respecting project's standard, or not aligned with the overall goal at all.
60
+ You must find the good path to achieve the overall goal based on the first principle, without being biased by the existing "Done" tasks.
61
+
62
+ 5.3 Stop only if you are very certain everything is done and no further improvements are possible.
63
+
64
+ If the user request was fulfilled by replying directly without any artifact files or code changes, you can stop once all tasks are done — no further improvements are needed.
65
+
66
+ # Do Improvements
67
+
68
+ When all tasks are complete AND overall goal is to make changes, consider improvements of various kinds, for example but not limited to:
69
+
70
+ **Coding kind**:
71
+
72
+ - polish, simplify, quality, performance, edge cases, error handling, UX, docs, etc.
73
+
74
+ When polishing codes, follow the first principle, try to simplify the solution, instead of bloating the code with extra checks, fallbacks, or safety nets that may hide potential issues.
75
+ The goal of polishing is to find real missing pieces, make the code more elegant, simple and efficient, not to add more layers of complexity.
76
+
77
+ **Docs / research / reports kind:**
78
+
79
+ - correctness, completeness, readability, logical clarity, accuracy
80
+ - remove irrelevant and redundant content
81
+
82
+ # Requirements
83
+
84
+ - Don't ask the user questions. Decide and act autonomously.
85
+ - Keep working until all tasks in the handoff file are complete.
86
+ - Follow engineering rules in `AGENTS.md` (if present).
87
+ - NEVER mention this workflow / "developer instruction" or what workflow steps you have followed in your response. This workflow should be transparent to the user.
88
+ - You must NOT change handoff file status from `open` to `skip`.
89
+ - To avoid regression, read full handoff file.
90
+ - NEVER change any text in `Overall Goal`.
91
+
92
+ # Knowledge capture (`.codexpotter/kb/`)
93
+
94
+ - Before starting, read `.codexpotter/kb/README.md` (if present).
95
+ - After deep research/exploration of a module, write intermediate facts + code locations to `.codexpotter/kb/xxx.md` and update the README index.
96
+ - KB files may be stale; **code is the source of truth**—update KB promptly when conflicts are found.
97
+ - No need to commit KB files.
98
+
99
+ # When all tasks are done or the project is skipped
100
+
101
+ Mark handoff file's `finite_incantatem` to true ONLY IF you have not changed any file or code since you received this workflow instruction.
102
+
103
+ Updating handoff files or files under `.codexpotter/kb` doesn't matter, but any other file changes indicate you have done some work,
104
+ so `finite_incantatem` should be kept false.
105
+
106
+ # Review guidelines
107
+
108
+ When you are acting as a reviewer for the code change made so far, here are the general guidelines
109
+ for determining whether something is a bug and should be fixed:
110
+
111
+ - It meaningfully impacts the accuracy, performance, security, or maintainability of the code.
112
+ - The bug is discrete and actionable (i.e. not a general issue with the codebase or a combination of multiple issues).
113
+ - Fixing the bug does not demand a level of rigor that is not present in the rest of the codebase (e.g. one doesn't need very detailed comments and input validation in a repository of one-off scripts in personal projects)
114
+ - The bug was introduced by this project's change.
115
+ - The author of the original PR would likely fix the issue if they were made aware of it.
116
+ - The bug does not rely on unstated assumptions about the codebase or author's intent.
117
+ - It is not enough to speculate that a change may disrupt another part of the codebase, to be considered a bug, one must identify the other parts of the code that are provably affected.
118
+ - The bug is clearly not just an intentional change by the original author.
119
+
120
+ </WORKFLOW_INSTRUCTIONS>
121
+
122
+ """