@iamdevlinph/codex-kit 1.0.12 → 1.0.14
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/LICENSE +18 -0
- package/README.md +28 -26
- package/assets/TEMPLATE_AGENTS.md +2 -0
- package/bin/codex-kit.js +51 -839
- package/bin/routing-hook.js +5 -25
- package/package.json +56 -38
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
This license applies only to files included in the published
|
|
2
|
+
@iamdevlinph/codex-kit npm package. It does not apply to repository-only files.
|
|
3
|
+
|
|
4
|
+
ISC License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2026 Devlin Pajaron
|
|
7
|
+
|
|
8
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
9
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
10
|
+
copyright notice and this permission notice appear in all copies.
|
|
11
|
+
|
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
13
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
14
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
15
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
16
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
17
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
18
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -143,47 +143,41 @@ hooks. Modified managed files are preserved unless `--force` is supplied.
|
|
|
143
143
|
|
|
144
144
|
## Apply to a project
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
### First-time setup
|
|
147
|
+
|
|
148
|
+
For a new blank project, scaffold its initial stack first. Then initialize
|
|
149
|
+
codex-kit once from the project root:
|
|
147
150
|
|
|
148
151
|
```sh
|
|
149
152
|
cd /path/to/project
|
|
150
153
|
pnpm dlx @iamdevlinph/codex-kit@latest project init
|
|
151
154
|
```
|
|
152
155
|
|
|
153
|
-
|
|
156
|
+
`project init` performs the initial template sync, so do not run `project sync`
|
|
157
|
+
immediately afterward. It creates or updates:
|
|
154
158
|
|
|
155
|
-
- `AGENTS.md`,
|
|
159
|
+
- `AGENTS.md`, only when missing; an existing file is preserved
|
|
156
160
|
- `TEMPLATE_AGENTS.md`, a local reference copy used for future comparisons
|
|
157
161
|
- `.codex-kit-state.json`, reconciliation bookkeeping
|
|
158
162
|
|
|
159
|
-
|
|
160
|
-
the
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
If `AGENTS.md` is missing or still contains only the untouched codex-kit
|
|
164
|
+
scaffold, the CLI prints a clearly marked initialization prompt. Copy everything
|
|
165
|
+
between `BEGIN CODEX INITIALIZATION PROMPT` and
|
|
166
|
+
`END CODEX INITIALIZATION PROMPT` into a Codex task opened at the project root.
|
|
167
|
+
The prompt asks Codex to verify that the project has enough substantive code,
|
|
168
|
+
dependencies, configuration, and scripts to derive reliable guidance. If not,
|
|
169
|
+
Codex stops without inventing rules or marking the template applied. Finish
|
|
170
|
+
scaffolding the project, rerun `project init`, and send the new CLI prompt.
|
|
163
171
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
```txt
|
|
168
|
-
Explore this repository before changing code. Identify its languages,
|
|
169
|
-
frameworks, package manager, scripts, directory structure, styling and component
|
|
170
|
-
systems, form and validation libraries, data-access patterns, testing tools, and
|
|
171
|
-
generated files.
|
|
172
|
-
|
|
173
|
-
Update only the project-specific section of AGENTS.md with concise guidelines
|
|
174
|
-
derived from the repository's actual dependencies, configuration, scripts, and
|
|
175
|
-
established code patterns. Include exact verification commands. Preserve the
|
|
176
|
-
managed shared-template block, avoid speculative preferences, and do not add
|
|
177
|
-
rules for tools the repository does not use.
|
|
178
|
-
```
|
|
172
|
+
When `AGENTS.md` already contains guidance, `project init` preserves it and
|
|
173
|
+
prints the reconciliation prompt described below instead.
|
|
179
174
|
|
|
180
175
|
## Synchronize template updates
|
|
181
176
|
|
|
182
|
-
|
|
177
|
+
After a newer codex-kit template is released, refresh an initialized project:
|
|
183
178
|
|
|
184
179
|
```sh
|
|
185
180
|
pnpm dlx @iamdevlinph/codex-kit@latest project sync
|
|
186
|
-
codex-kit project status
|
|
187
181
|
```
|
|
188
182
|
|
|
189
183
|
`project sync` never edits `AGENTS.md` or project skills. It routes Codex to the
|
|
@@ -195,10 +189,17 @@ database, deployment, and destructive-operation rules remain in `AGENTS.md`;
|
|
|
195
189
|
do not copy the complete template or introduce managed markers. After semantic
|
|
196
190
|
reconciliation and validation, record the applied template hash:
|
|
197
191
|
|
|
192
|
+
The CLI prints a clearly marked reconciliation prompt. Copy everything between
|
|
193
|
+
`BEGIN CODEX RECONCILIATION PROMPT` and `END CODEX RECONCILIATION PROMPT` into a
|
|
194
|
+
Codex task opened at the project root. That prompt tells Codex to validate and
|
|
195
|
+
then run:
|
|
196
|
+
|
|
198
197
|
```sh
|
|
199
198
|
codex-kit project mark-applied
|
|
200
199
|
```
|
|
201
200
|
|
|
201
|
+
You normally do not run that command manually.
|
|
202
|
+
|
|
202
203
|
`mark-applied` updates only `.codex-kit-state.json`; it does not validate or
|
|
203
204
|
modify `AGENTS.md`.
|
|
204
205
|
|
|
@@ -244,8 +245,9 @@ standard-library modules.
|
|
|
244
245
|
|
|
245
246
|
## License
|
|
246
247
|
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
Files included in the published `@iamdevlinph/codex-kit` npm package are
|
|
249
|
+
licensed under the [ISC License](LICENSE). Repository-only files remain
|
|
250
|
+
proprietary and are not covered by that license.
|
|
249
251
|
|
|
250
252
|
## References
|
|
251
253
|
|
|
@@ -113,6 +113,8 @@ conditional procedures into validated project skills.
|
|
|
113
113
|
this rule. If automated coverage is impractical, explain why and perform the
|
|
114
114
|
strongest targeted verification available.
|
|
115
115
|
- Run the relevant focused tests after changing tested behavior.
|
|
116
|
+
- When adding or updating dependencies, pin exact versions rather than ranges.
|
|
117
|
+
With pnpm, use `pnpm add -E` (`--save-exact`).
|
|
116
118
|
- Do not change dependencies, global tools, or the environment by default.
|
|
117
119
|
- Do not run local or remote database inspection, generation, migration, or SQL
|
|
118
120
|
commands unless the task requires them.
|
package/bin/codex-kit.js
CHANGED
|
@@ -1,813 +1,61 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { copyFileSync,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return destination;
|
|
46
|
-
}
|
|
47
|
-
function write(file, data) {
|
|
48
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
49
|
-
const temporary = `${file}.codex-kit.tmp-${process.pid}`;
|
|
50
|
-
writeFileSync(temporary, data);
|
|
51
|
-
renameSync(temporary, file);
|
|
52
|
-
}
|
|
53
|
-
function readJsonObject(file) {
|
|
54
|
-
if (!existsSync(file))
|
|
55
|
-
return {};
|
|
56
|
-
try {
|
|
57
|
-
const value = JSON.parse(readText(file));
|
|
58
|
-
if (isRecord(value))
|
|
59
|
-
return value;
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
// Use the actionable error below for invalid JSON and non-object roots.
|
|
63
|
-
}
|
|
64
|
-
throw new Error(`${file} must contain a JSON object; fix or move it before installing.`);
|
|
65
|
-
}
|
|
66
|
-
function shellQuote(value) {
|
|
67
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
68
|
-
}
|
|
69
|
-
function hookCommands(file) {
|
|
70
|
-
return {
|
|
71
|
-
command: `/usr/bin/env node ${shellQuote(file)}`,
|
|
72
|
-
commandWindows: `node ${JSON.stringify(file)}`,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function removeHookHandlers(root, state) {
|
|
76
|
-
const hooks = root.hooks;
|
|
77
|
-
if (!isRecord(hooks))
|
|
78
|
-
return;
|
|
79
|
-
for (const [event, groupsValue] of Object.entries(hooks)) {
|
|
80
|
-
if (!Array.isArray(groupsValue))
|
|
81
|
-
continue;
|
|
82
|
-
const groups = groupsValue.flatMap((groupValue) => {
|
|
83
|
-
if (!isRecord(groupValue) || !Array.isArray(groupValue.hooks))
|
|
84
|
-
return [groupValue];
|
|
85
|
-
const handlers = groupValue.hooks.filter((handler) => {
|
|
86
|
-
if (!isRecord(handler))
|
|
87
|
-
return true;
|
|
88
|
-
return handler.command !== state.command && handler.commandWindows !== state.commandWindows;
|
|
89
|
-
});
|
|
90
|
-
return handlers.length ? [{ ...groupValue, hooks: handlers }] : [];
|
|
91
|
-
});
|
|
92
|
-
if (groups.length)
|
|
93
|
-
hooks[event] = groups;
|
|
94
|
-
else
|
|
95
|
-
delete hooks[event];
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function installRoutingHooks(home, previous) {
|
|
99
|
-
const target = join(home, "hooks.json");
|
|
100
|
-
const hookFile = join(home, "codex-kit", "routing-hook.js");
|
|
101
|
-
const commands = hookCommands(hookFile);
|
|
102
|
-
const created = previous?.created ?? !existsSync(target);
|
|
103
|
-
const root = readJsonObject(target);
|
|
104
|
-
if (previous)
|
|
105
|
-
removeHookHandlers(root, previous);
|
|
106
|
-
const hooks = isRecord(root.hooks) ? root.hooks : {};
|
|
107
|
-
root.hooks = hooks;
|
|
108
|
-
const handler = {
|
|
109
|
-
type: "command",
|
|
110
|
-
command: commands.command,
|
|
111
|
-
commandWindows: commands.commandWindows,
|
|
112
|
-
timeout: 5,
|
|
113
|
-
};
|
|
114
|
-
const promptGroups = Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : [];
|
|
115
|
-
hooks.UserPromptSubmit = [
|
|
116
|
-
...promptGroups,
|
|
117
|
-
{ hooks: [{ ...handler, statusMessage: "Loading subagent routing" }] },
|
|
118
|
-
];
|
|
119
|
-
const startGroups = Array.isArray(hooks.SubagentStart) ? hooks.SubagentStart : [];
|
|
120
|
-
hooks.SubagentStart = [
|
|
121
|
-
...startGroups,
|
|
122
|
-
{ hooks: [{ ...handler, statusMessage: "Briefing delegated worker" }] },
|
|
123
|
-
];
|
|
124
|
-
const updated = `${JSON.stringify(root, null, 2)}\n`;
|
|
125
|
-
const original = existsSync(target) ? readText(target) : "";
|
|
126
|
-
if (updated !== original) {
|
|
127
|
-
backup(target);
|
|
128
|
-
write(target, updated);
|
|
129
|
-
console.log(`updated: ${target}`);
|
|
130
|
-
}
|
|
131
|
-
return { target, ...commands, created };
|
|
132
|
-
}
|
|
133
|
-
function uninstallRoutingHooks(state) {
|
|
134
|
-
if (!existsSync(state.target)) {
|
|
135
|
-
console.warn(`preserved missing hooks file: ${state.target}`);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const root = readJsonObject(state.target);
|
|
139
|
-
removeHookHandlers(root, state);
|
|
140
|
-
const hooks = root.hooks;
|
|
141
|
-
if (isRecord(hooks) && !Object.keys(hooks).length)
|
|
142
|
-
delete root.hooks;
|
|
143
|
-
backup(state.target);
|
|
144
|
-
if (state.created && !Object.keys(root).length)
|
|
145
|
-
rmSync(state.target);
|
|
146
|
-
else
|
|
147
|
-
write(state.target, `${JSON.stringify(root, null, 2)}\n`);
|
|
148
|
-
console.log(`removed managed routing hooks from: ${state.target}`);
|
|
149
|
-
}
|
|
150
|
-
function loadState(home) {
|
|
151
|
-
const file = join(home, STATE_FILE);
|
|
152
|
-
if (!existsSync(file))
|
|
153
|
-
return { version: PACKAGE.version, files: {} };
|
|
154
|
-
try {
|
|
155
|
-
const state = JSON.parse(readText(file));
|
|
156
|
-
return isRecord(state) && isRecord(state.files)
|
|
157
|
-
? state
|
|
158
|
-
: { version: PACKAGE.version, files: {} };
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
throw new Error(`${file} is not valid JSON; move it aside before reinstalling.`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function saveState(home, state) {
|
|
165
|
-
write(join(home, STATE_FILE), `${JSON.stringify(state, null, 2)}\n`);
|
|
166
|
-
}
|
|
167
|
-
function topLevelConfigEntries(contents) {
|
|
168
|
-
const entries = new Map();
|
|
169
|
-
let inTable = false;
|
|
170
|
-
for (const line of contents.split("\n")) {
|
|
171
|
-
if (/^\s*\[/.test(line)) {
|
|
172
|
-
inTable = true;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (inTable)
|
|
176
|
-
continue;
|
|
177
|
-
const match = /^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(line);
|
|
178
|
-
const key = match?.[2];
|
|
179
|
-
const value = match?.[3];
|
|
180
|
-
if (key && value !== undefined && !entries.has(key))
|
|
181
|
-
entries.set(key, { value, line });
|
|
182
|
-
}
|
|
183
|
-
return entries;
|
|
184
|
-
}
|
|
185
|
-
function tomlString(value) {
|
|
186
|
-
return JSON.stringify(value);
|
|
187
|
-
}
|
|
188
|
-
function setTopLevelConfig(contents, desired) {
|
|
189
|
-
const lines = contents.split("\n");
|
|
190
|
-
const seen = new Set();
|
|
191
|
-
let firstTable = lines.length;
|
|
192
|
-
for (let index = 0; index < lines.length; index++) {
|
|
193
|
-
const line = lines[index];
|
|
194
|
-
if (line !== undefined && /^\s*\[/.test(line)) {
|
|
195
|
-
firstTable = index;
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
for (let index = 0; index < firstTable; index++) {
|
|
200
|
-
const line = lines[index];
|
|
201
|
-
if (line === undefined)
|
|
202
|
-
continue;
|
|
203
|
-
const match = /^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(line);
|
|
204
|
-
const key = match?.[2];
|
|
205
|
-
if (!match || !key || seen.has(key))
|
|
206
|
-
continue;
|
|
207
|
-
lines[index] = `${match[1]}${key} = ${tomlString(desired[key])}`;
|
|
208
|
-
seen.add(key);
|
|
209
|
-
}
|
|
210
|
-
const missing = Object.keys(desired)
|
|
211
|
-
.filter((key) => !seen.has(key))
|
|
212
|
-
.map((key) => `${key} = ${tomlString(desired[key])}`);
|
|
213
|
-
if (missing.length)
|
|
214
|
-
lines.splice(0, 0, ...missing, "");
|
|
215
|
-
return lines.join("\n");
|
|
216
|
-
}
|
|
217
|
-
function restoreTopLevelConfig(contents, config) {
|
|
218
|
-
const desired = config.desired ?? {};
|
|
219
|
-
const previous = config.previous ?? {};
|
|
220
|
-
const lines = contents.split("\n");
|
|
221
|
-
let firstTable = lines.length;
|
|
222
|
-
for (let index = 0; index < lines.length; index++) {
|
|
223
|
-
const line = lines[index];
|
|
224
|
-
if (line !== undefined && /^\s*\[/.test(line)) {
|
|
225
|
-
firstTable = index;
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
const restored = new Set();
|
|
230
|
-
for (let index = 0; index < firstTable; index++) {
|
|
231
|
-
const line = lines[index];
|
|
232
|
-
if (line === undefined)
|
|
233
|
-
continue;
|
|
234
|
-
const match = /^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(line);
|
|
235
|
-
const key = match?.[2];
|
|
236
|
-
if (!match || !key || restored.has(key))
|
|
237
|
-
continue;
|
|
238
|
-
if (match[3] !== tomlString(desired[key]))
|
|
239
|
-
continue;
|
|
240
|
-
const prior = previous[key];
|
|
241
|
-
if (prior?.present) {
|
|
242
|
-
lines[index] = `${match[1]}${key} = ${prior.value}`;
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
lines.splice(index, 1);
|
|
246
|
-
index--;
|
|
247
|
-
firstTable--;
|
|
248
|
-
}
|
|
249
|
-
restored.add(key);
|
|
250
|
-
}
|
|
251
|
-
if (Object.values(previous).some((entry) => entry && !entry.present) && lines[0] === "")
|
|
252
|
-
lines.shift();
|
|
253
|
-
return lines.join("\n");
|
|
254
|
-
}
|
|
255
|
-
function loadProjectState(cwd) {
|
|
256
|
-
const file = join(cwd, PROJECT_STATE_FILE);
|
|
257
|
-
if (!existsSync(file))
|
|
258
|
-
return { version: 1, template: {} };
|
|
259
|
-
try {
|
|
260
|
-
const state = JSON.parse(readText(file));
|
|
261
|
-
return isRecord(state) && isRecord(state.template)
|
|
262
|
-
? state
|
|
263
|
-
: { version: 1, template: {} };
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
throw new Error(`${file} is not valid JSON; move it aside before syncing.`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
function saveProjectState(cwd, state) {
|
|
270
|
-
write(join(cwd, PROJECT_STATE_FILE), `${JSON.stringify(state, null, 2)}\n`);
|
|
271
|
-
}
|
|
272
|
-
function templatePrompt() {
|
|
273
|
-
return `Template reference updated. Use the global $${RECONCILE_SKILL} skill to reconcile it semantically.
|
|
2
|
+
import{realpathSync as $e}from"node:fs";import{resolve as lo}from"node:path";import{fileURLToPath as co}from"node:url";import{copyFileSync as Ve,existsSync as m,mkdirSync as Xe,readdirSync as N,rmSync as A,statSync as ge}from"node:fs";import{join as f}from"node:path";import{createHash as Oe}from"node:crypto";import{copyFileSync as Ce,existsSync as H,mkdirSync as _e,readFileSync as Q,renameSync as Ne,writeFileSync as Ie}from"node:fs";import{dirname as Le}from"node:path";var k=o=>typeof o=="object"&&o!==null,E=o=>Oe("sha256").update(o).digest("hex"),y=o=>Q(o),g=o=>Q(o,"utf8");function S(o){if(!H(o))return null;let e=()=>new Date().toISOString().replace(/[-:TZ.]/g,""),t=`${o}.codex-kit.bak-${e()}`,n=1;for(;H(t);)t=`${o}.codex-kit.bak-${e()}-${n++}`;return Ce(o,t),console.log(`backup: ${t}`),t}function u(o,e){_e(Le(o),{recursive:!0});let t=`${o}.codex-kit.tmp-${process.pid}`;Ie(t,e),Ne(t,o)}function K(o){if(!H(o))return{};try{let e=JSON.parse(g(o));if(k(e))return e}catch{}throw new Error(`${o} must contain a JSON object; fix or move it before installing.`)}import{readFileSync as je}from"node:fs";import{dirname as Ge,join as w,resolve as Re}from"node:path";import{fileURLToPath as Pe}from"node:url";var M=Re(Ge(Pe(import.meta.url)),".."),L=w(M,"assets"),D=w(L,"agents"),ee=w(L,"skills"),h="codex-kit-reconcile-agents",oe=w(ee,h,"SKILL.md"),te=w(ee,h,"agents","openai.yaml"),F=w(L,"SUBAGENT_ROUTING.md"),ne=w(M,"bin","routing-hook.js"),U=w(L,"TEMPLATE_AGENTS.md"),p=JSON.parse(je(w(M,"package.json"),"utf8")),J=p.publishConfig?.registry??"https://registry.npmjs.org";import{existsSync as ie,rmSync as Me}from"node:fs";import{join as De}from"node:path";import{copyFileSync as He,existsSync as j,rmSync as Ke}from"node:fs";import{join as re}from"node:path";var C=".codex-kit-state.json";function O(o){let e=re(o,C);if(!j(e))return{version:p.version,files:{}};try{let t=JSON.parse(g(e));return k(t)&&k(t.files)?t:{version:p.version,files:{}}}catch{throw new Error(`${e} is not valid JSON; move it aside before reinstalling.`)}}function G(o,e){u(re(o,C),`${JSON.stringify(e,null,2)}
|
|
3
|
+
`)}function B(o,e,t,n,r){let i=y(o),s=E(i),a=n.files[t];if(!j(e))return u(e,i),console.log(`installed: ${e}`),{target:e,hash:s,ownership:"created",backup:null};let c=E(y(e));if(c===s)return console.log(`unchanged: ${e}`),a??{target:e,hash:s,ownership:"preexisting",backup:null};let d=a&&a.target===e&&a.ownership!=="preexisting"&&a.hash===c;if(!d&&!r)return console.warn(`preserved modified or pre-existing file: ${e} (use --force to replace)`),a??null;let l=S(e);return u(e,i),console.log(`updated: ${e}`),{target:e,hash:s,ownership:d?a.ownership:"replaced",backup:d?a.backup:l}}function se(o){let{target:e}=o;!j(e)||E(y(e))!==o.hash?console.warn(`preserved modified or missing file: ${e}`):o.ownership==="created"?(Ke(e),console.log(`removed: ${e}`)):o.ownership==="replaced"&&o.backup&&j(o.backup)?(He(o.backup,e),console.log(`restored: ${e}`)):console.log(`preserved pre-existing file: ${e}`)}function R(o){let e=new Map,t=!1;for(let n of o.split(`
|
|
4
|
+
`)){if(/^\s*\[/.test(n)){t=!0;continue}if(t)continue;let r=/^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(n),i=r?.[2],s=r?.[3];i&&s!==void 0&&!e.has(i)&&e.set(i,{value:s,line:n})}return e}var _=o=>JSON.stringify(o);function Fe(o,e){let t=o.split(`
|
|
5
|
+
`),n=new Set,r=t.findIndex(s=>/^\s*\[/.test(s));r<0&&(r=t.length);for(let s=0;s<r;s++){let a=/^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(t[s]??""),c=a?.[2];!a||!c||n.has(c)||(t[s]=`${a[1]}${c} = ${_(e[c])}`,n.add(c))}let i=Object.keys(e).filter(s=>!n.has(s)).map(s=>`${s} = ${_(e[s])}`);return i.length&&t.splice(0,0,...i,""),t.join(`
|
|
6
|
+
`)}function Ue(o,e){let t=o.split(`
|
|
7
|
+
`),n=t.findIndex(i=>/^\s*\[/.test(i));n<0&&(n=t.length);let r=new Set;for(let i=0;i<n;i++){let s=/^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(t[i]??""),a=s?.[2];if(!s||!a||r.has(a)||s[3]!==_(e.desired[a]))continue;let c=e.previous[a];c?.present?t[i]=`${s[1]}${a} = ${c.value}`:(t.splice(i,1),i--,n--),r.add(a)}return Object.values(e.previous).some(i=>i&&!i.present)&&t[0]===""&&t.shift(),t.join(`
|
|
8
|
+
`)}function z(o){let e=o.codexHome,t=De(e,"config.toml"),n={model:o.orchestrator,model_reasoning_effort:o.reasoningEffort,plan_mode_reasoning_effort:o.planReasoningEffort},r=O(e),i=ie(t)?g(t):"",s=R(i);if(r.config?.target===t&&Object.entries(r.config.desired).some(([l,v])=>s.get(l)?.value!==_(v))&&!o.force){console.warn(`preserved modified config: ${t} (use --force to replace)`);return}let a=Fe(i,n);a!==i?(S(t),u(t,a),console.log(`configured orchestrator: ${t}`)):console.log(`unchanged: ${t}`);let c=r.config?.previous??{};for(let d of Object.keys(n)){if(d in c)continue;let l=s.get(d);c[d]=l?{present:!0,value:l.value}:{present:!1}}r.version=p.version,r.config={target:t,desired:n,previous:c},G(e,r),console.log(`Orchestrator: ${n.model}`),console.log(`Reasoning effort: ${n.model_reasoning_effort}`),console.log(`Plan mode reasoning effort: ${n.plan_mode_reasoning_effort}`)}function ae(o){if(!ie(o.target)){console.warn(`preserved missing config: ${o.target}`);return}let e=g(o.target),t=R(e);if(Object.entries(o.desired).some(([i,s])=>t.get(i)?.value!==_(s))){console.warn(`preserved modified config: ${o.target}`);return}let r=Ue(e,o);r!==e&&(S(o.target),r.trim()?u(o.target,r):Me(o.target),console.log(`restored config: ${o.target}`))}import{existsSync as V,rmSync as Je}from"node:fs";import{join as le}from"node:path";var Be=o=>`'${o.replaceAll("'",`'"'"'`)}'`,ze=o=>({command:`/usr/bin/env node ${Be(o)}`,commandWindows:`node ${JSON.stringify(o)}`});function ce(o,e){let t=o.hooks;if(k(t))for(let[n,r]of Object.entries(t)){if(!Array.isArray(r))continue;let i=r.flatMap(s=>{if(!k(s)||!Array.isArray(s.hooks))return[s];let a=s.hooks.filter(c=>!k(c)||c.command!==e.command&&c.commandWindows!==e.commandWindows);return a.length?[{...s,hooks:a}]:[]});i.length?t[n]=i:delete t[n]}}function de(o,e){let t=le(o,"hooks.json"),n=ze(le(o,"codex-kit","routing-hook.js")),r=e?.created??!V(t),i=K(t);e&&ce(i,e);let s=k(i.hooks)?i.hooks:{};i.hooks=s;let a={type:"command",...n,timeout:5};s.UserPromptSubmit=[...Array.isArray(s.UserPromptSubmit)?s.UserPromptSubmit:[],{hooks:[{...a,statusMessage:"Loading subagent routing"}]}],s.SubagentStart=[...Array.isArray(s.SubagentStart)?s.SubagentStart:[],{hooks:[{...a,statusMessage:"Briefing delegated worker"}]}];let c=`${JSON.stringify(i,null,2)}
|
|
9
|
+
`,d=V(t)?g(t):"";return c!==d&&(S(t),u(t,c),console.log(`updated: ${t}`)),{target:t,...n,created:r}}function fe(o){if(!V(o.target)){console.warn(`preserved missing hooks file: ${o.target}`);return}let e=K(o.target);ce(e,o),k(e.hooks)&&!Object.keys(e.hooks).length&&delete e.hooks,S(o.target),o.created&&!Object.keys(e).length?Je(o.target):u(o.target,`${JSON.stringify(e,null,2)}
|
|
10
|
+
`),console.log(`removed managed routing hooks from: ${o.target}`)}var X="<!-- BEGIN codex-kit:subagent-routing -->",pe="<!-- END codex-kit:subagent-routing -->",We=(o,e,t)=>`${e}
|
|
11
|
+
${o.trimEnd()}
|
|
12
|
+
${t}`;function qe(o,e,t,n){let r=o.indexOf(t),i=o.indexOf(n);if(r>=0!=i>=0||r>=0&&i<r)throw new Error(`Malformed managed block: expected both ${t} and ${n}.`);let s=We(e,t,n);return r>=0?`${o.slice(0,r)}${s}${o.slice(i+n.length)}`:`${o.trimEnd()}${o.trim()?`
|
|
13
|
+
|
|
14
|
+
`:""}${s}
|
|
15
|
+
`}function Ze(o,e,t){let n=o.indexOf(e),r=o.indexOf(t);if(n<0&&r<0)return o;if(n<0||r<n)throw new Error("Malformed managed block in AGENTS.md.");let i=o.slice(0,n).trimEnd(),s=o.slice(r+t.length).trimStart();return`${i}${i&&s?`
|
|
16
|
+
|
|
17
|
+
`:""}${s}${i||s?`
|
|
18
|
+
`:""}`}function me(o){let e=o.codexHome;Xe(e,{recursive:!0});let t=O(e),n={version:p.version,files:{},globalAgents:null,hooks:null,config:t.config??null};for(let d of N(D).filter(l=>l.endsWith(".toml")).sort()){let l=`agents/${d}`,v=B(f(D,d),f(e,"agents",d),l,t,o.force);v&&(n.files[l]=v)}let r=[[F,f(e,"SUBAGENT_ROUTING.md"),"routing"],[oe,f(e,"skills",h,"SKILL.md"),`skills/${h}/SKILL.md`],[te,f(e,"skills",h,"agents","openai.yaml"),`skills/${h}/agents/openai.yaml`],[ne,f(e,"codex-kit","routing-hook.js"),"routing-hook"]];for(let[d,l,v]of r){let I=B(d,l,v,t,o.force);I&&(n.files[v]=I)}for(let[d,l]of Object.entries(t.files))d in n.files||(!m(l.target)||E(y(l.target))!==l.hash?(console.warn(`preserved stale modified or missing file: ${l.target}`),n.files[d]=l):l.ownership==="created"?(A(l.target),console.log(`removed stale: ${l.target}`)):l.ownership==="replaced"&&l.backup&&m(l.backup)&&(Ve(l.backup,l.target),console.log(`restored stale: ${l.target}`)));let i=f(e,"AGENTS.md"),s=m(i)?g(i):"",a=qe(s,g(F),X,pe);a!==s&&(S(i),u(i,a),console.log(`updated: ${i}`)),n.globalAgents={target:i},n.hooks=de(e,t.hooks),G(e,n);let c=f(e,"agents","commit-pusher.toml");m(c)&&console.warn(`warning: existing unmanaged commit-pusher remains at ${c}`),console.log(`Codex kit ${p.version} installed under ${e}`)}function ue(o){let e=o.codexHome,t=f(e,"config.toml"),n=f(e,"AGENTS.md"),r=O(e),i=m(t)?R(g(t)):new Map,s=b=>{let x=i.get(b)?.value;if(!x)return"not set";try{return String(JSON.parse(x))}catch{return x}};console.log(`Codex home: ${e}`),console.log(`Config: ${t}${m(t)?"":" (missing)"}`),console.log(`Orchestrator: ${s("model")}`),console.log(`Reasoning effort: ${s("model_reasoning_effort")}`),console.log(`Plan mode reasoning effort: ${s("plan_mode_reasoning_effort")}`),console.log(`Kit state: ${m(f(e,C))?r.version:"not installed"}`),console.log(`Global routing: ${m(n)&&g(n).includes(X)?"installed":"not installed"}`),console.log(`Routing file: ${m(f(e,"SUBAGENT_ROUTING.md"))?f(e,"SUBAGENT_ROUTING.md"):"missing"}`);let a=r.hooks;console.log(`Routing hook: ${a&&m(a.target)&&g(a.target).includes(a.command)?"installed":"not installed"}`);let c=[[f(e,"skills",h,"SKILL.md"),r.files[`skills/${h}/SKILL.md`]],[f(e,"skills",h,"agents","openai.yaml"),r.files[`skills/${h}/agents/openai.yaml`]]],d=c.every(([b,x])=>m(b)&&x&&E(y(b))===x.hash)?"installed":c.some(([b])=>m(b))?"modified or incomplete":"missing";console.log(`Reconciliation skill: ${d}`),console.log("Custom agents:");let l=f(e,"agents"),v=m(l)?N(l).filter(b=>b.endsWith(".toml")).sort():[];if(!v.length){console.log(" (none)");return}let I=new Set(Object.values(r.files).map(b=>b.target));for(let b of v){let x=f(l,b),Te=g(x),P=Ae=>new RegExp(`^${Ae}\\s*=\\s*"([^"]*)"`,"m").exec(Te)?.[1]??"not set";console.log(` ${P("name")} \u2014 ${P("model")}, ${P("model_reasoning_effort")} (${I.has(x)?"managed":"unmanaged"})`)}}function he(o){let e=o.codexHome,t=f(e,C);if(!m(t)){console.log(`No installer state at ${t}; nothing removed.`);return}let n=O(e);for(let d of Object.values(n.files))se(d);let r=n.globalAgents?.target??f(e,"AGENTS.md");if(m(r)){let d=g(r),l=Ze(d,X,pe);l!==d&&(S(r),l?u(r,l):A(r),console.log(`removed managed routing from: ${r}`))}n.config&&ae(n.config),n.hooks&&fe(n.hooks);let i=f(e,"skills",h),s=f(i,"agents");m(s)&&ge(s).isDirectory()&&!N(s).length&&A(s,{recursive:!0}),m(i)&&ge(i).isDirectory()&&!N(i).length&&A(i,{recursive:!0});let a=f(e,"codex-kit","allowances");m(a)&&A(a,{recursive:!0,force:!0});let c=f(e,"codex-kit");m(c)&&!N(c).length&&A(c,{recursive:!0}),A(t),console.log(`Codex kit uninstalled from ${e}`)}import{existsSync as $,statSync as Ye}from"node:fs";import{join as T}from"node:path";var Ee=".codex-kit-state.json",Qe="<!-- BEGIN codex-kit:shared-template -->",eo="<!-- END codex-kit:shared-template -->",ke=`# Project-Specific Instructions
|
|
19
|
+
|
|
20
|
+
<!-- Add repository-specific commands, architecture, and exceptions here. -->
|
|
21
|
+
`;function W(o){let e=T(o,Ee);if(!$(e))return{version:1,template:{}};try{let t=JSON.parse(g(e));return k(t)&&k(t.template)?t:{version:1,template:{}}}catch{throw new Error(`${e} is not valid JSON; move it aside before syncing.`)}}var Se=(o,e)=>u(T(o,Ee),`${JSON.stringify(e,null,2)}
|
|
22
|
+
`),q=o=>{if(!$(o)||!Ye(o).isDirectory())throw new Error(`Not a directory: ${o}`)};function oo(){return`Project guidance needs initialization. Copy everything between the markers into Codex.
|
|
23
|
+
|
|
24
|
+
===== BEGIN CODEX INITIALIZATION PROMPT =====
|
|
25
|
+
Explore this repository before changing code. First determine whether it has a
|
|
26
|
+
substantially scaffolded implementation with enough dependency, configuration,
|
|
27
|
+
script, and source evidence to derive reliable project guidance.
|
|
28
|
+
|
|
29
|
+
If evidence is insufficient, do not add speculative rules or mark the template
|
|
30
|
+
applied. Report what still needs to be scaffolded, then stop.
|
|
31
|
+
|
|
32
|
+
If evidence is sufficient, identify the stack, package manager, scripts,
|
|
33
|
+
structure, established patterns, testing tools, and generated files. Add concise
|
|
34
|
+
project-specific guidance to AGENTS.md based only on repository evidence,
|
|
35
|
+
including exact verification commands. Then use the global
|
|
36
|
+
$${h} skill to merge applicable reusable guidance from
|
|
37
|
+
TEMPLATE_AGENTS.md while preserving AGENTS.md organization and local rules.
|
|
38
|
+
Validate the final instruction changes, mark the template applied only after
|
|
39
|
+
validation succeeds, and confirm codex-kit project status is up to date.
|
|
40
|
+
===== END CODEX INITIALIZATION PROMPT =====`}function to(){return`Template reference updated. Copy everything between the markers into Codex.
|
|
41
|
+
|
|
42
|
+
===== BEGIN CODEX RECONCILIATION PROMPT =====
|
|
43
|
+
Use the global $${h} skill to reconcile the existing AGENTS.md
|
|
44
|
+
with the refreshed TEMPLATE_AGENTS.md.
|
|
274
45
|
|
|
275
46
|
Inspect TEMPLATE_AGENTS.md, AGENTS.md, .codex-kit-state.json, existing
|
|
276
47
|
.agents/skills, and codex-kit project status. Preserve local adaptations and
|
|
277
48
|
AGENTS.md organization; merge only applicable reusable guidance. Keep critical
|
|
278
49
|
always-on safety and authorization rules in AGENTS.md, extract only concrete
|
|
279
50
|
conditional procedures into validated skills, and do not copy the complete
|
|
280
|
-
template or introduce managed markers.
|
|
281
|
-
and validation, then report any template-worthy generalized promotion.`;
|
|
282
|
-
}
|
|
283
|
-
function managedBlock(content, begin, end) {
|
|
284
|
-
return `${begin}\n${content.trimEnd()}\n${end}`;
|
|
285
|
-
}
|
|
286
|
-
function replaceOrAppendBlock(original, content, begin, end) {
|
|
287
|
-
const start = original.indexOf(begin);
|
|
288
|
-
const finish = original.indexOf(end);
|
|
289
|
-
if ((start >= 0) !== (finish >= 0) || (start >= 0 && finish < start)) {
|
|
290
|
-
throw new Error(`Malformed managed block: expected both ${begin} and ${end}.`);
|
|
291
|
-
}
|
|
292
|
-
const block = managedBlock(content, begin, end);
|
|
293
|
-
if (start >= 0)
|
|
294
|
-
return `${original.slice(0, start)}${block}${original.slice(finish + end.length)}`;
|
|
295
|
-
return `${original.trimEnd()}${original.trim() ? "\n\n" : ""}${block}\n`;
|
|
296
|
-
}
|
|
297
|
-
function removeBlock(original, begin, end) {
|
|
298
|
-
const start = original.indexOf(begin);
|
|
299
|
-
const finish = original.indexOf(end);
|
|
300
|
-
if (start < 0 && finish < 0)
|
|
301
|
-
return original;
|
|
302
|
-
if (start < 0 || finish < start)
|
|
303
|
-
throw new Error(`Malformed managed block in AGENTS.md.`);
|
|
304
|
-
const before = original.slice(0, start).trimEnd();
|
|
305
|
-
const after = original.slice(finish + end.length).trimStart();
|
|
306
|
-
return `${before}${before && after ? "\n\n" : ""}${after}${before || after ? "\n" : ""}`;
|
|
307
|
-
}
|
|
308
|
-
function installFile(source, target, key, prior, force) {
|
|
309
|
-
const sourceData = read(source);
|
|
310
|
-
const sourceHash = sha256(sourceData);
|
|
311
|
-
const previous = prior.files[key];
|
|
312
|
-
if (!existsSync(target)) {
|
|
313
|
-
write(target, sourceData);
|
|
314
|
-
console.log(`installed: ${target}`);
|
|
315
|
-
return { target, hash: sourceHash, ownership: "created", backup: null };
|
|
316
|
-
}
|
|
317
|
-
const targetHash = sha256(read(target));
|
|
318
|
-
if (targetHash === sourceHash) {
|
|
319
|
-
console.log(`unchanged: ${target}`);
|
|
320
|
-
return previous ?? { target, hash: sourceHash, ownership: "preexisting", backup: null };
|
|
321
|
-
}
|
|
322
|
-
const safelyOwned = previous &&
|
|
323
|
-
previous.target === target &&
|
|
324
|
-
previous.ownership !== "preexisting" &&
|
|
325
|
-
previous.hash === targetHash;
|
|
326
|
-
if (!safelyOwned && !force) {
|
|
327
|
-
console.warn(`preserved modified or pre-existing file: ${target} (use --force to replace)`);
|
|
328
|
-
return previous ?? null;
|
|
329
|
-
}
|
|
330
|
-
const newBackup = backup(target);
|
|
331
|
-
write(target, sourceData);
|
|
332
|
-
console.log(`updated: ${target}`);
|
|
333
|
-
return {
|
|
334
|
-
target,
|
|
335
|
-
hash: sourceHash,
|
|
336
|
-
ownership: safelyOwned ? previous.ownership : "replaced",
|
|
337
|
-
backup: safelyOwned ? previous.backup : newBackup,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
function installGlobal(options) {
|
|
341
|
-
const home = options.codexHome;
|
|
342
|
-
mkdirSync(home, { recursive: true });
|
|
343
|
-
const prior = loadState(home);
|
|
344
|
-
const next = {
|
|
345
|
-
version: PACKAGE.version,
|
|
346
|
-
files: {},
|
|
347
|
-
globalAgents: null,
|
|
348
|
-
hooks: null,
|
|
349
|
-
config: prior.config ?? null,
|
|
350
|
-
};
|
|
351
|
-
for (const name of readdirSync(AGENTS_DIR).filter((name) => name.endsWith(".toml")).sort()) {
|
|
352
|
-
const key = `agents/${name}`;
|
|
353
|
-
const record = installFile(join(AGENTS_DIR, name), join(home, "agents", name), key, prior, options.force);
|
|
354
|
-
if (record)
|
|
355
|
-
next.files[key] = record;
|
|
356
|
-
}
|
|
357
|
-
const routingRecord = installFile(ROUTING_FILE, join(home, "SUBAGENT_ROUTING.md"), "routing", prior, options.force);
|
|
358
|
-
if (routingRecord)
|
|
359
|
-
next.files.routing = routingRecord;
|
|
360
|
-
const reconciliationSkill = installFile(RECONCILE_SKILL_FILE, join(home, "skills", RECONCILE_SKILL, "SKILL.md"), `skills/${RECONCILE_SKILL}/SKILL.md`, prior, options.force);
|
|
361
|
-
if (reconciliationSkill)
|
|
362
|
-
next.files[`skills/${RECONCILE_SKILL}/SKILL.md`] = reconciliationSkill;
|
|
363
|
-
const reconciliationSkillMetadata = installFile(RECONCILE_SKILL_METADATA_FILE, join(home, "skills", RECONCILE_SKILL, "agents", "openai.yaml"), `skills/${RECONCILE_SKILL}/agents/openai.yaml`, prior, options.force);
|
|
364
|
-
if (reconciliationSkillMetadata)
|
|
365
|
-
next.files[`skills/${RECONCILE_SKILL}/agents/openai.yaml`] = reconciliationSkillMetadata;
|
|
366
|
-
const hookRecord = installFile(ROUTING_HOOK_FILE, join(home, "codex-kit", "routing-hook.js"), "routing-hook", prior, options.force);
|
|
367
|
-
if (hookRecord)
|
|
368
|
-
next.files["routing-hook"] = hookRecord;
|
|
369
|
-
for (const [key, record] of Object.entries(prior.files)) {
|
|
370
|
-
if (key in next.files)
|
|
371
|
-
continue;
|
|
372
|
-
const target = record.target;
|
|
373
|
-
if (!existsSync(target) || sha256(read(target)) !== record.hash) {
|
|
374
|
-
console.warn(`preserved stale modified or missing file: ${target}`);
|
|
375
|
-
next.files[key] = record;
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
if (record.ownership === "created") {
|
|
379
|
-
rmSync(target);
|
|
380
|
-
console.log(`removed stale: ${target}`);
|
|
381
|
-
}
|
|
382
|
-
else if (record.ownership === "replaced" && record.backup && existsSync(record.backup)) {
|
|
383
|
-
copyFileSync(record.backup, target);
|
|
384
|
-
console.log(`restored stale: ${target}`);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
const globalAgents = join(home, "AGENTS.md");
|
|
388
|
-
const original = existsSync(globalAgents) ? readText(globalAgents) : "";
|
|
389
|
-
const updated = replaceOrAppendBlock(original, readText(ROUTING_FILE), GLOBAL_BEGIN, GLOBAL_END);
|
|
390
|
-
if (updated !== original) {
|
|
391
|
-
backup(globalAgents);
|
|
392
|
-
write(globalAgents, updated);
|
|
393
|
-
console.log(`updated: ${globalAgents}`);
|
|
394
|
-
}
|
|
395
|
-
next.globalAgents = { target: globalAgents };
|
|
396
|
-
next.hooks = installRoutingHooks(home, prior.hooks);
|
|
397
|
-
saveState(home, next);
|
|
398
|
-
const commitPusher = join(home, "agents", "commit-pusher.toml");
|
|
399
|
-
if (existsSync(commitPusher)) {
|
|
400
|
-
console.warn(`warning: existing unmanaged commit-pusher remains at ${commitPusher}`);
|
|
401
|
-
}
|
|
402
|
-
console.log(`Codex kit ${PACKAGE.version} installed under ${home}`);
|
|
403
|
-
}
|
|
404
|
-
function configureGlobal(options) {
|
|
405
|
-
const home = options.codexHome;
|
|
406
|
-
mkdirSync(home, { recursive: true });
|
|
407
|
-
const configFile = join(home, "config.toml");
|
|
408
|
-
const desired = {
|
|
409
|
-
model: options.orchestrator,
|
|
410
|
-
model_reasoning_effort: options.reasoningEffort,
|
|
411
|
-
plan_mode_reasoning_effort: options.planReasoningEffort,
|
|
412
|
-
};
|
|
413
|
-
const priorState = loadState(home);
|
|
414
|
-
const original = existsSync(configFile) ? readText(configFile) : "";
|
|
415
|
-
const current = topLevelConfigEntries(original);
|
|
416
|
-
const priorConfig = priorState.config;
|
|
417
|
-
if (priorConfig?.target === configFile) {
|
|
418
|
-
const changedByUser = Object.entries(priorConfig.desired).some(([key, value]) => {
|
|
419
|
-
const entry = current.get(key);
|
|
420
|
-
return !entry || entry.value !== tomlString(value);
|
|
421
|
-
});
|
|
422
|
-
if (changedByUser && !options.force) {
|
|
423
|
-
console.warn(`preserved modified config: ${configFile} (use --force to replace)`);
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const updated = setTopLevelConfig(original, desired);
|
|
428
|
-
if (updated !== original) {
|
|
429
|
-
backup(configFile);
|
|
430
|
-
write(configFile, updated);
|
|
431
|
-
console.log(`configured orchestrator: ${configFile}`);
|
|
432
|
-
}
|
|
433
|
-
else
|
|
434
|
-
console.log(`unchanged: ${configFile}`);
|
|
435
|
-
const previous = priorConfig?.previous ?? {};
|
|
436
|
-
for (const key of Object.keys(desired)) {
|
|
437
|
-
if (key in previous)
|
|
438
|
-
continue;
|
|
439
|
-
const entry = current.get(key);
|
|
440
|
-
previous[key] = entry
|
|
441
|
-
? { present: true, value: entry.value }
|
|
442
|
-
: { present: false };
|
|
443
|
-
}
|
|
444
|
-
priorState.version = PACKAGE.version;
|
|
445
|
-
priorState.config = {
|
|
446
|
-
target: configFile,
|
|
447
|
-
desired,
|
|
448
|
-
previous,
|
|
449
|
-
};
|
|
450
|
-
saveState(home, priorState);
|
|
451
|
-
console.log(`Orchestrator: ${desired.model}`);
|
|
452
|
-
console.log(`Reasoning effort: ${desired.model_reasoning_effort}`);
|
|
453
|
-
console.log(`Plan mode reasoning effort: ${desired.plan_mode_reasoning_effort}`);
|
|
454
|
-
}
|
|
455
|
-
function listGlobal(options) {
|
|
456
|
-
const home = options.codexHome;
|
|
457
|
-
const configFile = join(home, "config.toml");
|
|
458
|
-
const globalAgents = join(home, "AGENTS.md");
|
|
459
|
-
const routingFile = join(home, "SUBAGENT_ROUTING.md");
|
|
460
|
-
const agentsDir = join(home, "agents");
|
|
461
|
-
const stateFile = join(home, STATE_FILE);
|
|
462
|
-
const state = loadState(home);
|
|
463
|
-
const config = existsSync(configFile) ? topLevelConfigEntries(readText(configFile)) : new Map();
|
|
464
|
-
const value = (key) => {
|
|
465
|
-
const raw = config.get(key)?.value;
|
|
466
|
-
if (!raw)
|
|
467
|
-
return "not set";
|
|
468
|
-
try {
|
|
469
|
-
return String(JSON.parse(raw));
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
return raw;
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
const managedTargets = new Set(Object.values(state.files).map((record) => record.target));
|
|
476
|
-
console.log(`Codex home: ${home}`);
|
|
477
|
-
console.log(`Config: ${configFile}${existsSync(configFile) ? "" : " (missing)"}`);
|
|
478
|
-
console.log(`Orchestrator: ${value("model")}`);
|
|
479
|
-
console.log(`Reasoning effort: ${value("model_reasoning_effort")}`);
|
|
480
|
-
console.log(`Plan mode reasoning effort: ${value("plan_mode_reasoning_effort")}`);
|
|
481
|
-
console.log(`Kit state: ${existsSync(stateFile) ? state.version : "not installed"}`);
|
|
482
|
-
const hasRoutingBlock = existsSync(globalAgents) && readText(globalAgents).includes(GLOBAL_BEGIN);
|
|
483
|
-
console.log(`Global routing: ${hasRoutingBlock ? "installed" : "not installed"}`);
|
|
484
|
-
console.log(`Routing file: ${existsSync(routingFile) ? routingFile : "missing"}`);
|
|
485
|
-
const routingHook = state.hooks;
|
|
486
|
-
const hooksInstalled = Boolean(routingHook &&
|
|
487
|
-
existsSync(routingHook.target) &&
|
|
488
|
-
readText(routingHook.target).includes(routingHook.command));
|
|
489
|
-
console.log(`Routing hook: ${hooksInstalled ? "installed" : "not installed"}`);
|
|
490
|
-
const skillTargets = [
|
|
491
|
-
[join(home, "skills", RECONCILE_SKILL, "SKILL.md"), state.files[`skills/${RECONCILE_SKILL}/SKILL.md`]],
|
|
492
|
-
[join(home, "skills", RECONCILE_SKILL, "agents", "openai.yaml"), state.files[`skills/${RECONCILE_SKILL}/agents/openai.yaml`]],
|
|
493
|
-
];
|
|
494
|
-
const skillStatus = skillTargets.every(([target, record]) => existsSync(target) && record && sha256(read(target)) === record.hash)
|
|
495
|
-
? "installed"
|
|
496
|
-
: skillTargets.some(([target]) => existsSync(target))
|
|
497
|
-
? "modified or incomplete"
|
|
498
|
-
: "missing";
|
|
499
|
-
console.log(`Reconciliation skill: ${skillStatus}`);
|
|
500
|
-
console.log("Custom agents:");
|
|
501
|
-
const agents = existsSync(agentsDir)
|
|
502
|
-
? readdirSync(agentsDir).filter((name) => name.endsWith(".toml")).sort()
|
|
503
|
-
: [];
|
|
504
|
-
if (!agents.length)
|
|
505
|
-
return console.log(" (none)");
|
|
506
|
-
for (const filename of agents) {
|
|
507
|
-
const file = join(agentsDir, filename);
|
|
508
|
-
const contents = readText(file);
|
|
509
|
-
const field = (key) => new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m").exec(contents)?.[1] ?? "not set";
|
|
510
|
-
const ownership = managedTargets.has(file) ? "managed" : "unmanaged";
|
|
511
|
-
console.log(` ${field("name")} — ${field("model")}, ${field("model_reasoning_effort")} (${ownership})`);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
function uninstallGlobal(options) {
|
|
515
|
-
const home = options.codexHome;
|
|
516
|
-
const statePath = join(home, STATE_FILE);
|
|
517
|
-
if (!existsSync(statePath)) {
|
|
518
|
-
console.log(`No installer state at ${statePath}; nothing removed.`);
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
const state = loadState(home);
|
|
522
|
-
for (const record of Object.values(state.files)) {
|
|
523
|
-
const target = record.target;
|
|
524
|
-
if (!existsSync(target) || sha256(read(target)) !== record.hash) {
|
|
525
|
-
console.warn(`preserved modified or missing file: ${target}`);
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
if (record.ownership === "created") {
|
|
529
|
-
rmSync(target);
|
|
530
|
-
console.log(`removed: ${target}`);
|
|
531
|
-
}
|
|
532
|
-
else if (record.ownership === "replaced" && record.backup && existsSync(record.backup)) {
|
|
533
|
-
copyFileSync(record.backup, target);
|
|
534
|
-
console.log(`restored: ${target}`);
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
console.log(`preserved pre-existing file: ${target}`);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
const globalAgents = state.globalAgents?.target ?? join(home, "AGENTS.md");
|
|
541
|
-
if (existsSync(globalAgents)) {
|
|
542
|
-
const original = readText(globalAgents);
|
|
543
|
-
const updated = removeBlock(original, GLOBAL_BEGIN, GLOBAL_END);
|
|
544
|
-
if (updated !== original) {
|
|
545
|
-
backup(globalAgents);
|
|
546
|
-
if (updated)
|
|
547
|
-
write(globalAgents, updated);
|
|
548
|
-
else
|
|
549
|
-
rmSync(globalAgents);
|
|
550
|
-
console.log(`removed managed routing from: ${globalAgents}`);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
if (state.config?.target) {
|
|
554
|
-
const configFile = state.config.target;
|
|
555
|
-
if (!existsSync(configFile)) {
|
|
556
|
-
console.warn(`preserved missing config: ${configFile}`);
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
const originalConfig = readText(configFile);
|
|
560
|
-
const current = topLevelConfigEntries(originalConfig);
|
|
561
|
-
const changed = Object.entries(state.config.desired).some(([key, value]) => {
|
|
562
|
-
const entry = current.get(key);
|
|
563
|
-
return !entry || entry.value !== tomlString(value);
|
|
564
|
-
});
|
|
565
|
-
if (changed) {
|
|
566
|
-
console.warn(`preserved modified config: ${configFile}`);
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
const restored = restoreTopLevelConfig(originalConfig, state.config);
|
|
570
|
-
if (restored !== originalConfig) {
|
|
571
|
-
backup(configFile);
|
|
572
|
-
if (restored.trim())
|
|
573
|
-
write(configFile, restored);
|
|
574
|
-
else
|
|
575
|
-
rmSync(configFile);
|
|
576
|
-
console.log(`restored config: ${configFile}`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
if (state.hooks)
|
|
582
|
-
uninstallRoutingHooks(state.hooks);
|
|
583
|
-
const skillDir = join(home, "skills", RECONCILE_SKILL);
|
|
584
|
-
const skillMetadataDir = join(skillDir, "agents");
|
|
585
|
-
if (existsSync(skillMetadataDir) && statSync(skillMetadataDir).isDirectory() && !readdirSync(skillMetadataDir).length) {
|
|
586
|
-
rmSync(skillMetadataDir, { recursive: true });
|
|
587
|
-
}
|
|
588
|
-
if (existsSync(skillDir) && statSync(skillDir).isDirectory() && !readdirSync(skillDir).length) {
|
|
589
|
-
rmSync(skillDir, { recursive: true });
|
|
590
|
-
}
|
|
591
|
-
const allowancesDir = join(home, "codex-kit", "allowances");
|
|
592
|
-
if (existsSync(allowancesDir))
|
|
593
|
-
rmSync(allowancesDir, { recursive: true, force: true });
|
|
594
|
-
const kitDir = join(home, "codex-kit");
|
|
595
|
-
if (existsSync(kitDir) && !readdirSync(kitDir).length)
|
|
596
|
-
rmSync(kitDir, { recursive: true });
|
|
597
|
-
rmSync(statePath);
|
|
598
|
-
console.log(`Codex kit uninstalled from ${home}`);
|
|
599
|
-
}
|
|
600
|
-
function syncProject(options) {
|
|
601
|
-
const cwd = options.cwd;
|
|
602
|
-
if (!existsSync(cwd) || !statSync(cwd).isDirectory())
|
|
603
|
-
throw new Error(`Not a directory: ${cwd}`);
|
|
604
|
-
const agentsFile = join(cwd, "AGENTS.md");
|
|
605
|
-
const stagedTemplate = join(cwd, "TEMPLATE_AGENTS.md");
|
|
606
|
-
const template = readText(TEMPLATE_FILE);
|
|
607
|
-
const desired = Buffer.from(template);
|
|
608
|
-
const sourceHash = sha256(desired);
|
|
609
|
-
const state = loadProjectState(cwd);
|
|
610
|
-
const previousAvailable = state.template.availableHash;
|
|
611
|
-
if (existsSync(stagedTemplate)) {
|
|
612
|
-
const currentHash = sha256(read(stagedTemplate));
|
|
613
|
-
const locallyModified = currentHash !== sourceHash && (!previousAvailable || currentHash !== previousAvailable);
|
|
614
|
-
if (locallyModified && currentHash !== sourceHash && !options.force) {
|
|
615
|
-
console.warn(`preserved locally modified template: ${stagedTemplate} (use --force to replace)`);
|
|
616
|
-
console.log(`The installed kit has template ${PACKAGE.version}; review the local change before syncing.`);
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
if (currentHash === sourceHash)
|
|
620
|
-
console.log(`unchanged: ${stagedTemplate}`);
|
|
621
|
-
else {
|
|
622
|
-
backup(stagedTemplate);
|
|
623
|
-
write(stagedTemplate, desired);
|
|
624
|
-
console.log(`refreshed template reference: ${stagedTemplate}`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
write(stagedTemplate, desired);
|
|
629
|
-
console.log(`created template reference: ${stagedTemplate}`);
|
|
630
|
-
}
|
|
631
|
-
state.version = 1;
|
|
632
|
-
state.template = {
|
|
633
|
-
...state.template,
|
|
634
|
-
availableHash: sourceHash,
|
|
635
|
-
availableVersion: PACKAGE.version,
|
|
636
|
-
};
|
|
637
|
-
saveProjectState(cwd, state);
|
|
638
|
-
if (!existsSync(agentsFile)) {
|
|
639
|
-
const contents = `# Project-Specific Instructions
|
|
51
|
+
template or introduce managed markers.
|
|
640
52
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
console.warn(`preserved legacy managed template in: ${agentsFile}`);
|
|
648
|
-
console.warn("Ask Codex to migrate it to semantic template reconciliation before applying updates.");
|
|
649
|
-
}
|
|
650
|
-
console.log(templatePrompt());
|
|
651
|
-
}
|
|
652
|
-
function projectStatus(options) {
|
|
653
|
-
const cwd = options.cwd;
|
|
654
|
-
if (!existsSync(cwd) || !statSync(cwd).isDirectory())
|
|
655
|
-
throw new Error(`Not a directory: ${cwd}`);
|
|
656
|
-
const stagedTemplate = join(cwd, "TEMPLATE_AGENTS.md");
|
|
657
|
-
const agentsFile = join(cwd, "AGENTS.md");
|
|
658
|
-
const state = loadProjectState(cwd);
|
|
659
|
-
const availableHash = state.template.availableHash ?? null;
|
|
660
|
-
const appliedHash = state.template.appliedHash ?? null;
|
|
661
|
-
const sourceHash = sha256(read(TEMPLATE_FILE));
|
|
662
|
-
const localHash = existsSync(stagedTemplate) ? sha256(read(stagedTemplate)) : null;
|
|
663
|
-
console.log(`Project: ${cwd}`);
|
|
664
|
-
if (!localHash)
|
|
665
|
-
return console.log("Status: not initialized (run codex-kit project sync)");
|
|
666
|
-
if (!existsSync(agentsFile))
|
|
667
|
-
return console.log("Status: AGENTS.md missing (reconcile the template first)");
|
|
668
|
-
console.log(`Available: ${state.template.availableVersion ?? "unknown"} (${availableHash ?? "untracked"})`);
|
|
669
|
-
console.log(`Applied: ${appliedHash ?? "never"}`);
|
|
670
|
-
if (sourceHash !== availableHash)
|
|
671
|
-
return console.log("Status: kit template update available; run project sync");
|
|
672
|
-
if (localHash !== availableHash)
|
|
673
|
-
return console.log("Status: local template changed; review it before syncing");
|
|
674
|
-
if (appliedHash !== localHash)
|
|
675
|
-
return console.log("Status: reconciliation required");
|
|
676
|
-
console.log("Status: up to date");
|
|
677
|
-
}
|
|
678
|
-
function markApplied(options) {
|
|
679
|
-
const cwd = options.cwd;
|
|
680
|
-
if (!existsSync(cwd) || !statSync(cwd).isDirectory())
|
|
681
|
-
throw new Error(`Not a directory: ${cwd}`);
|
|
682
|
-
const stagedTemplate = join(cwd, "TEMPLATE_AGENTS.md");
|
|
683
|
-
const agentsFile = join(cwd, "AGENTS.md");
|
|
684
|
-
if (!existsSync(stagedTemplate))
|
|
685
|
-
throw new Error(`Missing ${stagedTemplate}; run project sync first.`);
|
|
686
|
-
if (!existsSync(agentsFile))
|
|
687
|
-
throw new Error(`Missing ${agentsFile}; reconcile the template into AGENTS.md first.`);
|
|
688
|
-
const state = loadProjectState(cwd);
|
|
689
|
-
const appliedHash = sha256(read(stagedTemplate));
|
|
690
|
-
state.version = 1;
|
|
691
|
-
state.template = {
|
|
692
|
-
...state.template,
|
|
693
|
-
appliedHash,
|
|
694
|
-
appliedAt: new Date().toISOString(),
|
|
695
|
-
};
|
|
696
|
-
saveProjectState(cwd, state);
|
|
697
|
-
console.log(`recorded template reconciliation: ${stagedTemplate}`);
|
|
698
|
-
}
|
|
699
|
-
function compareVersions(left, right) {
|
|
700
|
-
const parseVersion = (value) => {
|
|
701
|
-
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(value);
|
|
702
|
-
if (!match)
|
|
703
|
-
throw new Error(`Invalid package version: ${value}`);
|
|
704
|
-
return {
|
|
705
|
-
numbers: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
706
|
-
prerelease: match[4] ?? null,
|
|
707
|
-
};
|
|
708
|
-
};
|
|
709
|
-
const a = parseVersion(left);
|
|
710
|
-
const b = parseVersion(right);
|
|
711
|
-
for (let index = 0; index < 3; index++) {
|
|
712
|
-
const leftNumber = a.numbers[index];
|
|
713
|
-
const rightNumber = b.numbers[index];
|
|
714
|
-
if (leftNumber !== rightNumber)
|
|
715
|
-
return Math.sign(leftNumber - rightNumber);
|
|
716
|
-
}
|
|
717
|
-
if (a.prerelease === b.prerelease)
|
|
718
|
-
return 0;
|
|
719
|
-
if (!a.prerelease)
|
|
720
|
-
return 1;
|
|
721
|
-
if (!b.prerelease)
|
|
722
|
-
return -1;
|
|
723
|
-
return Math.sign(a.prerelease.localeCompare(b.prerelease, "en", { numeric: true }));
|
|
724
|
-
}
|
|
725
|
-
function checkVersion() {
|
|
726
|
-
let latest = process.env.CODEX_KIT_LATEST_VERSION;
|
|
727
|
-
if (!latest) {
|
|
728
|
-
const executable = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
729
|
-
const result = spawnSync(executable, ["view", PACKAGE.name, "version", "--json", `--registry=${REGISTRY}`], { encoding: "utf8", timeout: 15_000 });
|
|
730
|
-
if (result.error)
|
|
731
|
-
throw new Error(`Unable to run pnpm: ${result.error.message}`);
|
|
732
|
-
if (result.status !== 0) {
|
|
733
|
-
const detail = result.stderr.trim() || "pnpm view failed";
|
|
734
|
-
throw new Error(`Unable to check ${REGISTRY}: ${detail}`);
|
|
735
|
-
}
|
|
736
|
-
try {
|
|
737
|
-
const value = JSON.parse(result.stdout);
|
|
738
|
-
latest = Array.isArray(value) && typeof value.at(-1) === "string"
|
|
739
|
-
? value.at(-1)
|
|
740
|
-
: typeof value === "string"
|
|
741
|
-
? value
|
|
742
|
-
: undefined;
|
|
743
|
-
}
|
|
744
|
-
catch {
|
|
745
|
-
latest = result.stdout.trim();
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
if (typeof latest !== "string" || !latest)
|
|
749
|
-
throw new Error("Registry returned no package version.");
|
|
750
|
-
console.log(`Installed: ${PACKAGE.version}`);
|
|
751
|
-
console.log(`Latest: ${latest}`);
|
|
752
|
-
const comparison = compareVersions(PACKAGE.version, latest);
|
|
753
|
-
if (comparison === 0)
|
|
754
|
-
return console.log("codex-kit is up to date.");
|
|
755
|
-
if (comparison > 0)
|
|
756
|
-
return console.log("This local build is newer than the published package.");
|
|
757
|
-
console.log(`Update available. Run:
|
|
758
|
-
pnpm add --global ${PACKAGE.name}@latest
|
|
759
|
-
codex-kit global install`);
|
|
760
|
-
}
|
|
761
|
-
function parse(argv) {
|
|
762
|
-
const options = {
|
|
763
|
-
cwd: process.cwd(),
|
|
764
|
-
codexHome: resolve(process.env.CODEX_HOME || join(homedir(), ".codex")),
|
|
765
|
-
orchestrator: DEFAULT_ORCHESTRATOR,
|
|
766
|
-
reasoningEffort: DEFAULT_REASONING_EFFORT,
|
|
767
|
-
planReasoningEffort: DEFAULT_PLAN_REASONING_EFFORT,
|
|
768
|
-
force: false,
|
|
769
|
-
positionals: [],
|
|
770
|
-
};
|
|
771
|
-
for (let index = 0; index < argv.length; index++) {
|
|
772
|
-
const arg = argv[index];
|
|
773
|
-
if (arg === undefined)
|
|
774
|
-
continue;
|
|
775
|
-
if (arg === "--force")
|
|
776
|
-
options.force = true;
|
|
777
|
-
else if (arg === "--cwd" || arg === "--codex-home") {
|
|
778
|
-
const value = argv[++index];
|
|
779
|
-
if (!value)
|
|
780
|
-
throw new Error(`${arg} requires a path.`);
|
|
781
|
-
if (arg === "--cwd")
|
|
782
|
-
options.cwd = resolve(value);
|
|
783
|
-
else
|
|
784
|
-
options.codexHome = resolve(value);
|
|
785
|
-
}
|
|
786
|
-
else if (arg === "--orchestrator" || arg === "--model") {
|
|
787
|
-
const value = argv[++index];
|
|
788
|
-
if (!value)
|
|
789
|
-
throw new Error(`${arg} requires a model.`);
|
|
790
|
-
options.orchestrator = value;
|
|
791
|
-
}
|
|
792
|
-
else if (arg === "--reasoning-effort") {
|
|
793
|
-
const value = argv[++index];
|
|
794
|
-
if (!value)
|
|
795
|
-
throw new Error(`${arg} requires a value.`);
|
|
796
|
-
options.reasoningEffort = value;
|
|
797
|
-
}
|
|
798
|
-
else if (arg === "--plan-reasoning-effort") {
|
|
799
|
-
const value = argv[++index];
|
|
800
|
-
if (!value)
|
|
801
|
-
throw new Error(`${arg} requires a value.`);
|
|
802
|
-
options.planReasoningEffort = value;
|
|
803
|
-
}
|
|
804
|
-
else
|
|
805
|
-
options.positionals.push(arg);
|
|
806
|
-
}
|
|
807
|
-
return options;
|
|
808
|
-
}
|
|
809
|
-
function help() {
|
|
810
|
-
console.log(`codex-kit ${PACKAGE.version}
|
|
53
|
+
Validate the final instruction changes. Mark applied only after reconciliation
|
|
54
|
+
and validation succeed, confirm codex-kit project status is up to date, then
|
|
55
|
+
report any template-worthy generalized promotion.
|
|
56
|
+
===== END CODEX RECONCILIATION PROMPT =====`}function be(o){let{cwd:e}=o;q(e);let t=T(e,"AGENTS.md"),n=T(e,"TEMPLATE_AGENTS.md"),r=Buffer.from(g(U)),i=E(r),s=W(e);if($(n)){let d=E(y(n));if(d!==i&&(!s.template.availableHash||d!==s.template.availableHash)&&!o.force){console.warn(`preserved locally modified template: ${n} (use --force to replace)`),console.log(`The installed kit has template ${p.version}; review the local change before syncing.`);return}d===i?console.log(`unchanged: ${n}`):(S(n),u(n,r),console.log(`refreshed template reference: ${n}`))}else u(n,r),console.log(`created template reference: ${n}`);if(s.version=1,s.template={...s.template,availableHash:i,availableVersion:p.version},Se(e,s),!$(t))u(t,ke),console.log(`created project instructions file: ${t}`);else{let d=g(t);(d.includes(Qe)||d.includes(eo))&&(console.warn(`preserved legacy managed template in: ${t}`),console.warn("Ask Codex to migrate it to semantic template reconciliation before applying updates."))}let c=g(t).trim()===ke.trim();console.log(c?oo():to())}function ye(o){let{cwd:e}=o;q(e);let t=T(e,"TEMPLATE_AGENTS.md"),n=W(e),r=n.template.availableHash??null,i=$(t)?E(y(t)):null;if(console.log(`Project: ${e}`),!i){console.log("Status: not initialized (run codex-kit project sync)");return}if(!$(T(e,"AGENTS.md"))){console.log("Status: AGENTS.md missing (reconcile the template first)");return}if(console.log(`Available: ${n.template.availableVersion??"unknown"} (${r??"untracked"})`),console.log(`Applied: ${n.template.appliedHash??"never"}`),E(y(U))!==r){console.log("Status: kit template update available; run project sync");return}if(i!==r){console.log("Status: local template changed; review it before syncing");return}if(n.template.appliedHash!==i){console.log("Status: reconciliation required");return}console.log("Status: up to date")}function ve(o){let{cwd:e}=o;q(e);let t=T(e,"TEMPLATE_AGENTS.md"),n=T(e,"AGENTS.md");if(!$(t))throw new Error(`Missing ${t}; run project sync first.`);if(!$(n))throw new Error(`Missing ${n}; reconcile the template into AGENTS.md first.`);let r=W(e);r.version=1,r.template={...r.template,appliedHash:E(y(t)),appliedAt:new Date().toISOString()},Se(e,r),console.log(`recorded template reconciliation: ${t}`)}import{spawnSync as no}from"node:child_process";function ro(o,e){let t=i=>{let s=/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(i);if(!s)throw new Error(`Invalid package version: ${i}`);return{numbers:[Number(s[1]),Number(s[2]),Number(s[3])],prerelease:s[4]??null}},n=t(o),r=t(e);for(let[i,s]of[[n.numbers[0],r.numbers[0]],[n.numbers[1],r.numbers[1]],[n.numbers[2],r.numbers[2]]])if(i!==s)return Math.sign(i-s);return n.prerelease===r.prerelease?0:n.prerelease?r.prerelease?Math.sign(n.prerelease.localeCompare(r.prerelease,"en",{numeric:!0})):-1:1}function xe(){let o=process.env.CODEX_KIT_LATEST_VERSION;if(!o){let t=no(process.platform==="win32"?"pnpm.cmd":"pnpm",["view",p.name,"version","--json",`--registry=${J}`],{encoding:"utf8",timeout:15e3});if(t.error)throw new Error(`Unable to run pnpm: ${t.error.message}`);if(t.status!==0)throw new Error(`Unable to check ${J}: ${t.stderr.trim()||"pnpm view failed"}`);try{let n=JSON.parse(t.stdout);o=Array.isArray(n)&&typeof n.at(-1)=="string"?n.at(-1):typeof n=="string"?n:void 0}catch{o=t.stdout.trim()}}if(!o)throw new Error("Registry returned no package version.");console.log(`Installed: ${p.version}`),console.log(`Latest: ${o}`);let e=ro(p.version,o);if(e===0){console.log("codex-kit is up to date.");return}if(e>0){console.log("This local build is newer than the published package.");return}console.log(`Update available. Run:
|
|
57
|
+
pnpm add --global ${p.name}@latest
|
|
58
|
+
codex-kit global install`)}import{homedir as so}from"node:os";import{join as io,resolve as Z}from"node:path";function we(o){let e={cwd:process.cwd(),codexHome:Z(process.env.CODEX_HOME||io(so(),".codex")),orchestrator:"gpt-5.6-sol",reasoningEffort:"low",planReasoningEffort:"high",force:!1,positionals:[]};for(let t=0;t<o.length;t++){let n=o[t];if(n!==void 0)if(n==="--force")e.force=!0;else if(n==="--cwd"||n==="--codex-home"){let r=o[++t];if(!r)throw new Error(`${n} requires a path.`);n==="--cwd"?e.cwd=Z(r):e.codexHome=Z(r)}else if(n==="--orchestrator"||n==="--model"){let r=o[++t];if(!r)throw new Error(`${n} requires a model.`);e.orchestrator=r}else if(n==="--reasoning-effort"||n==="--plan-reasoning-effort"){let r=o[++t];if(!r)throw new Error(`${n} requires a value.`);n==="--reasoning-effort"?e.reasoningEffort=r:e.planReasoningEffort=r}else e.positionals.push(n)}return e}function ao(){console.log(`codex-kit ${p.version}
|
|
811
59
|
|
|
812
60
|
Usage:
|
|
813
61
|
codex-kit <command> [options]
|
|
@@ -852,40 +100,4 @@ Examples:
|
|
|
852
100
|
codex-kit global install --force
|
|
853
101
|
codex-kit global configure --reasoning-effort low --plan-reasoning-effort high
|
|
854
102
|
codex-kit project sync --cwd /path/to/project --force
|
|
855
|
-
codex-kit project status --cwd /path/to/project`);
|
|
856
|
-
}
|
|
857
|
-
export function main(argv = process.argv.slice(2)) {
|
|
858
|
-
const options = parse(argv);
|
|
859
|
-
if (options.positionals.includes("--version"))
|
|
860
|
-
return console.log(PACKAGE.version);
|
|
861
|
-
if (!options.positionals.length || options.positionals.includes("--help"))
|
|
862
|
-
return help();
|
|
863
|
-
const [scope, action] = options.positionals;
|
|
864
|
-
if (scope === "global" && action === "install")
|
|
865
|
-
return installGlobal(options);
|
|
866
|
-
if (scope === "global" && action === "configure")
|
|
867
|
-
return configureGlobal(options);
|
|
868
|
-
if (scope === "global" && action === "list")
|
|
869
|
-
return listGlobal(options);
|
|
870
|
-
if (scope === "global" && action === "uninstall")
|
|
871
|
-
return uninstallGlobal(options);
|
|
872
|
-
if (scope === "project" && (action === "init" || action === "sync"))
|
|
873
|
-
return syncProject(options);
|
|
874
|
-
if (scope === "project" && action === "status")
|
|
875
|
-
return projectStatus(options);
|
|
876
|
-
if (scope === "project" && action === "mark-applied")
|
|
877
|
-
return markApplied(options);
|
|
878
|
-
if (scope === "version" && action === "check")
|
|
879
|
-
return checkVersion();
|
|
880
|
-
throw new Error(`Unknown command: ${options.positionals.join(" ")}`);
|
|
881
|
-
}
|
|
882
|
-
if (process.argv[1] &&
|
|
883
|
-
realpathSync(resolve(process.argv[1])) === realpathSync(fileURLToPath(import.meta.url))) {
|
|
884
|
-
try {
|
|
885
|
-
main();
|
|
886
|
-
}
|
|
887
|
-
catch (error) {
|
|
888
|
-
console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
889
|
-
process.exitCode = 1;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
103
|
+
codex-kit project status --cwd /path/to/project`)}function Y(o=process.argv.slice(2)){let e=we(o);if(e.positionals.includes("--version")){console.log(p.version);return}if(!e.positionals.length||e.positionals.includes("--help")){ao();return}let[t,n]=e.positionals;if(t==="global"&&n==="install")me(e);else if(t==="global"&&n==="configure")z(e);else if(t==="global"&&n==="list")ue(e);else if(t==="global"&&n==="uninstall")he(e);else if(t==="project"&&(n==="init"||n==="sync"))be(e);else if(t==="project"&&n==="status")ye(e);else if(t==="project"&&n==="mark-applied")ve(e);else if(t==="version"&&n==="check")xe();else throw new Error(`Unknown command: ${e.positionals.join(" ")}`)}if(process.argv[1]&&$e(lo(process.argv[1]))===$e(co(import.meta.url)))try{Y()}catch(o){console.error(`error: ${o instanceof Error?o.message:String(o)}`),process.exitCode=1}export{Y as main};
|
package/bin/routing-hook.js
CHANGED
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const input = JSON.parse(readFileSync(0, "utf8"));
|
|
8
|
-
function hookContext(event, context) {
|
|
9
|
-
return JSON.stringify({
|
|
10
|
-
hookSpecificOutput: {
|
|
11
|
-
hookEventName: event,
|
|
12
|
-
additionalContext: context,
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
if (input.hook_event_name === "UserPromptSubmit" && existsSync(routingFile)) {
|
|
17
|
-
const routing = readFileSync(routingFile, "utf8").trim();
|
|
18
|
-
process.stdout.write(`Codex-kit routing policy for this turn:\n\n${routing}\n\n` +
|
|
19
|
-
"Classify the task using this policy before acting. When it requires delegation, " +
|
|
20
|
-
"spawn the exact named role before doing that role's work. Agent definitions, not " +
|
|
21
|
-
"this policy, determine each role's model and reasoning effort.");
|
|
22
|
-
}
|
|
23
|
-
if (input.hook_event_name === "SubagentStart") {
|
|
24
|
-
process.stdout.write(hookContext("SubagentStart", `You are the delegated ${input.agent_type ?? "worker"}. ` +
|
|
25
|
-
"Follow the assigned scope, perform the role's work directly without further delegation, validate it, and return concise evidence."));
|
|
26
|
-
}
|
|
2
|
+
import{existsSync as r,readFileSync as n}from"node:fs";import{dirname as s,join as a,resolve as u}from"node:path";import{fileURLToPath as d}from"node:url";var c=u(s(d(import.meta.url)),".."),o=a(c,"SUBAGENT_ROUTING.md"),e=JSON.parse(n(0,"utf8"));function g(t,i){return JSON.stringify({hookSpecificOutput:{hookEventName:t,additionalContext:i}})}if(e.hook_event_name==="UserPromptSubmit"&&r(o)){let t=n(o,"utf8").trim();process.stdout.write(`Codex-kit routing policy for this turn:
|
|
3
|
+
|
|
4
|
+
${t}
|
|
5
|
+
|
|
6
|
+
Classify the task using this policy before acting. When it requires delegation, spawn the exact named role before doing that role's work. Agent definitions, not this policy, determine each role's model and reasoning effort.`)}e.hook_event_name==="SubagentStart"&&process.stdout.write(g("SubagentStart",`You are the delegated ${e.agent_type??"worker"}. Follow the assigned scope, perform the role's work directly without further delegation, validate it, and return concise evidence.`));
|
package/package.json
CHANGED
|
@@ -1,40 +1,58 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
2
|
+
"name": "@iamdevlinph/codex-kit",
|
|
3
|
+
"version": "1.0.14",
|
|
4
|
+
"description": "Portable Codex subagents and project AGENTS.md defaults.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codex-kit": "bin/codex-kit.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"assets/agents",
|
|
11
|
+
"assets/SUBAGENT_ROUTING.md",
|
|
12
|
+
"assets/TEMPLATE_AGENTS.md",
|
|
13
|
+
"assets/skills",
|
|
14
|
+
"bin"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "pnpm exec tsc -p tsconfig.json --noEmit && node scripts/build.mjs",
|
|
18
|
+
"typecheck": "pnpm exec tsc -p tsconfig.json --noEmit && pnpm exec tsc -p tsconfig.test.json --noEmit",
|
|
19
|
+
"test": "pnpm run build && pnpm exec vitest run",
|
|
20
|
+
"prepack": "pnpm run build",
|
|
21
|
+
"pack:check": "pnpm pack --dry-run",
|
|
22
|
+
"format": "biome check --write .",
|
|
23
|
+
"format:check": "biome check .",
|
|
24
|
+
"lint": "biome lint .",
|
|
25
|
+
"lint:fix": "biome lint --write .",
|
|
26
|
+
"check": "biome check",
|
|
27
|
+
"fix": "biome check --fix",
|
|
28
|
+
"prepare": "husky",
|
|
29
|
+
"precommit": "lint-staged"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/iamdevlinph/codex-kit.git"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"registry": "https://registry.npmjs.org",
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@biomejs/biome": "2.5.3",
|
|
44
|
+
"@types/node": "20.19.43",
|
|
45
|
+
"esbuild": "0.28.1",
|
|
46
|
+
"husky": "9.1.7",
|
|
47
|
+
"lint-staged": "17.0.8",
|
|
48
|
+
"typescript": "7.0.1-rc",
|
|
49
|
+
"vitest": "4.1.10"
|
|
50
|
+
},
|
|
51
|
+
"packageManager": "pnpm@11.5.2",
|
|
52
|
+
"license": "ISC",
|
|
53
|
+
"lint-staged": {
|
|
54
|
+
"*": [
|
|
55
|
+
"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
40
58
|
}
|