@iamdevlinph/codex-kit 1.0.17 → 1.0.19

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
@@ -8,8 +8,9 @@ Portable Codex setup for new devices and multiple projects. It provides:
8
8
  - a package-owned `codex-kit-reconcile-agents` skill for semantic reconciliation
9
9
  - safe commands for global setup, project synchronization, and reconciliation
10
10
 
11
- The package contains no credentials. Global installation does not modify the
12
- Codex model configuration; configuration is a separate, explicit command.
11
+ The package contains no credentials. Global installation configures the Codex
12
+ root orchestrator to `gpt-5.6-sol` with low normal reasoning and high Plan-mode
13
+ reasoning. Use the separate configuration command to override these defaults.
13
14
 
14
15
  ## Install on a device
15
16
 
@@ -18,7 +19,6 @@ No npm or GitHub login is required. For regular use, install the CLI globally:
18
19
  ```sh
19
20
  pnpm add --global @iamdevlinph/codex-kit@latest
20
21
  codex-kit global install
21
- codex-kit global configure
22
22
  ```
23
23
 
24
24
  Start a new Codex task or restart the client after installation. Codex may ask
@@ -69,11 +69,12 @@ do not justify duplicate agents.
69
69
  selected by the default automatic route. The root reuses delegated test evidence
70
70
  and normally performs only lightweight integration checks.
71
71
 
72
- `global configure` sets these defaults while preserving unrelated settings:
72
+ `global install` sets these defaults while preserving unrelated settings;
73
+ `global configure` can override them:
73
74
 
74
75
  ```toml
75
76
  model = "gpt-5.6-sol"
76
- model_reasoning_effort = "medium"
77
+ model_reasoning_effort = "low"
77
78
  plan_mode_reasoning_effort = "high"
78
79
  ```
79
80
 
@@ -123,6 +123,10 @@ conditional procedures into validated project skills.
123
123
 
124
124
  - Follow the repository's organization and naming. Prefer focused files and
125
125
  split mixed responsibilities when readability improves.
126
+ - Keep route and page files focused on page-level composition, data loading, and
127
+ orchestration. Extract substantial self-contained UI sections and complex
128
+ page-specific logic into colocated feature components or modules. Keep small
129
+ one-use markup inline; do not create components solely to reduce line count.
126
130
  - Use intent-revealing domain names. A reader should understand what a variable
127
131
  contains or what a helper guarantees at the call site without opening its
128
132
  implementation. Avoid vague transformation names such as `normalized`,
package/bin/codex-kit.js CHANGED
@@ -1,25 +1,602 @@
1
1
  #!/usr/bin/env node
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.
2
+
3
+ // src/codex-kit.ts
4
+ import { realpathSync } from "node:fs";
5
+ import { resolve as resolve3 } from "node:path";
6
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
7
+
8
+ // src/global/commands.ts
9
+ import {
10
+ copyFileSync as copyFileSync3,
11
+ existsSync as existsSync5,
12
+ mkdirSync as mkdirSync2,
13
+ readdirSync,
14
+ rmSync as rmSync4,
15
+ statSync
16
+ } from "node:fs";
17
+ import { join as join5 } from "node:path";
18
+
19
+ // src/files.ts
20
+ import { createHash } from "node:crypto";
21
+ import {
22
+ copyFileSync,
23
+ existsSync,
24
+ mkdirSync,
25
+ readFileSync,
26
+ renameSync,
27
+ writeFileSync
28
+ } from "node:fs";
29
+ import { dirname } from "node:path";
30
+ var isRecord = (value) => typeof value === "object" && value !== null;
31
+ var sha256 = (data) => createHash("sha256").update(data).digest("hex");
32
+ var read = (file) => readFileSync(file);
33
+ var readText = (file) => readFileSync(file, "utf8");
34
+ function backup(file) {
35
+ if (!existsSync(file)) return null;
36
+ const stamp = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:TZ.]/g, "");
37
+ let destination = `${file}.codex-kit.bak-${stamp()}`;
38
+ let suffix = 1;
39
+ while (existsSync(destination))
40
+ destination = `${file}.codex-kit.bak-${stamp()}-${suffix++}`;
41
+ copyFileSync(file, destination);
42
+ console.log(`backup: ${destination}`);
43
+ return destination;
44
+ }
45
+ function write(file, data) {
46
+ mkdirSync(dirname(file), { recursive: true });
47
+ const temporary = `${file}.codex-kit.tmp-${process.pid}`;
48
+ writeFileSync(temporary, data);
49
+ renameSync(temporary, file);
50
+ }
51
+ function readJsonObject(file) {
52
+ if (!existsSync(file)) return {};
53
+ try {
54
+ const value = JSON.parse(readText(file));
55
+ if (isRecord(value)) return value;
56
+ } catch {
57
+ }
58
+ throw new Error(
59
+ `${file} must contain a JSON object; fix or move it before installing.`
60
+ );
61
+ }
62
+
63
+ // src/package.ts
64
+ import { readFileSync as readFileSync2 } from "node:fs";
65
+ import { dirname as dirname2, join, resolve } from "node:path";
66
+ import { fileURLToPath } from "node:url";
67
+ var ROOT = resolve(dirname2(fileURLToPath(import.meta.url)), "..");
68
+ var ASSETS = join(ROOT, "assets");
69
+ var AGENTS_DIR = join(ASSETS, "agents");
70
+ var SKILLS_DIR = join(ASSETS, "skills");
71
+ var RECONCILE_SKILL = "codex-kit-reconcile-agents";
72
+ var RECONCILE_SKILL_FILE = join(
73
+ SKILLS_DIR,
74
+ RECONCILE_SKILL,
75
+ "SKILL.md"
76
+ );
77
+ var RECONCILE_SKILL_METADATA_FILE = join(
78
+ SKILLS_DIR,
79
+ RECONCILE_SKILL,
80
+ "agents",
81
+ "openai.yaml"
82
+ );
83
+ var ROUTING_FILE = join(ASSETS, "SUBAGENT_ROUTING.md");
84
+ var ROUTING_HOOK_FILE = join(ROOT, "bin", "routing-hook.js");
85
+ var TEMPLATE_FILE = join(ASSETS, "TEMPLATE_AGENTS.md");
86
+ var PACKAGE = JSON.parse(
87
+ readFileSync2(join(ROOT, "package.json"), "utf8")
88
+ );
89
+ var REGISTRY = PACKAGE.publishConfig?.registry ?? "https://registry.npmjs.org";
90
+
91
+ // src/global/config.ts
92
+ import { existsSync as existsSync3, rmSync as rmSync2 } from "node:fs";
93
+ import { join as join3 } from "node:path";
94
+
95
+ // src/global/state.ts
96
+ import { copyFileSync as copyFileSync2, existsSync as existsSync2, rmSync } from "node:fs";
97
+ import { join as join2 } from "node:path";
98
+ var STATE_FILE = ".codex-kit-state.json";
99
+ function loadState(home) {
100
+ const file = join2(home, STATE_FILE);
101
+ if (!existsSync2(file)) return { version: PACKAGE.version, files: {} };
102
+ try {
103
+ const state = JSON.parse(readText(file));
104
+ return isRecord(state) && isRecord(state.files) ? state : { version: PACKAGE.version, files: {} };
105
+ } catch {
106
+ throw new Error(
107
+ `${file} is not valid JSON; move it aside before reinstalling.`
108
+ );
109
+ }
110
+ }
111
+ function saveState(home, state) {
112
+ write(join2(home, STATE_FILE), `${JSON.stringify(state, null, 2)}
113
+ `);
114
+ }
115
+ function installFile(source, target, key, prior, force) {
116
+ const sourceData = read(source);
117
+ const sourceHash = sha256(sourceData);
118
+ const previous = prior.files[key];
119
+ if (!existsSync2(target)) {
120
+ write(target, sourceData);
121
+ console.log(`installed: ${target}`);
122
+ return { target, hash: sourceHash, ownership: "created", backup: null };
123
+ }
124
+ const targetHash = sha256(read(target));
125
+ if (targetHash === sourceHash) {
126
+ console.log(`unchanged: ${target}`);
127
+ return previous ?? {
128
+ target,
129
+ hash: sourceHash,
130
+ ownership: "preexisting",
131
+ backup: null
132
+ };
133
+ }
134
+ const safelyOwned = previous && previous.target === target && previous.ownership !== "preexisting" && previous.hash === targetHash;
135
+ if (!safelyOwned && !force) {
136
+ console.warn(
137
+ `preserved modified or pre-existing file: ${target} (use --force to replace)`
138
+ );
139
+ return previous ?? null;
140
+ }
141
+ const newBackup = backup(target);
142
+ write(target, sourceData);
143
+ console.log(`updated: ${target}`);
144
+ return {
145
+ target,
146
+ hash: sourceHash,
147
+ ownership: safelyOwned ? previous.ownership : "replaced",
148
+ backup: safelyOwned ? previous.backup : newBackup
149
+ };
150
+ }
151
+ function restoreFile(record) {
152
+ const { target } = record;
153
+ if (!existsSync2(target) || sha256(read(target)) !== record.hash) {
154
+ console.warn(`preserved modified or missing file: ${target}`);
155
+ } else if (record.ownership === "created") {
156
+ rmSync(target);
157
+ console.log(`removed: ${target}`);
158
+ } else if (record.ownership === "replaced" && record.backup && existsSync2(record.backup)) {
159
+ copyFileSync2(record.backup, target);
160
+ console.log(`restored: ${target}`);
161
+ } else console.log(`preserved pre-existing file: ${target}`);
162
+ }
163
+
164
+ // src/global/config.ts
165
+ function topLevelConfigEntries(contents) {
166
+ const entries = /* @__PURE__ */ new Map();
167
+ let inTable = false;
168
+ for (const line of contents.split("\n")) {
169
+ if (/^\s*\[/.test(line)) {
170
+ inTable = true;
171
+ continue;
172
+ }
173
+ if (inTable) continue;
174
+ const match = /^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(
175
+ line
176
+ );
177
+ const key = match?.[2];
178
+ const value = match?.[3];
179
+ if (key && value !== void 0 && !entries.has(key))
180
+ entries.set(key, { value, line });
181
+ }
182
+ return entries;
183
+ }
184
+ var tomlString = (value) => JSON.stringify(value);
185
+ function setTopLevelConfig(contents, desired) {
186
+ const lines = contents.split("\n");
187
+ const seen = /* @__PURE__ */ new Set();
188
+ let firstTable = lines.findIndex((line) => /^\s*\[/.test(line));
189
+ if (firstTable < 0) firstTable = lines.length;
190
+ for (let index = 0; index < firstTable; index++) {
191
+ const match = /^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(
192
+ lines[index] ?? ""
193
+ );
194
+ const key = match?.[2];
195
+ if (!match || !key || seen.has(key)) continue;
196
+ lines[index] = `${match[1]}${key} = ${tomlString(desired[key])}`;
197
+ seen.add(key);
198
+ }
199
+ const missing = Object.keys(desired).filter((key) => !seen.has(key)).map((key) => `${key} = ${tomlString(desired[key])}`);
200
+ if (missing.length) lines.splice(0, 0, ...missing, "");
201
+ return lines.join("\n");
202
+ }
203
+ function restoreTopLevelConfig(contents, config) {
204
+ const lines = contents.split("\n");
205
+ let firstTable = lines.findIndex((line) => /^\s*\[/.test(line));
206
+ if (firstTable < 0) firstTable = lines.length;
207
+ const restored = /* @__PURE__ */ new Set();
208
+ for (let index = 0; index < firstTable; index++) {
209
+ const match = /^(\s*)(model|model_reasoning_effort|plan_mode_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(
210
+ lines[index] ?? ""
211
+ );
212
+ const key = match?.[2];
213
+ if (!match || !key || restored.has(key) || match[3] !== tomlString(config.desired[key]))
214
+ continue;
215
+ const prior = config.previous[key];
216
+ if (prior?.present) lines[index] = `${match[1]}${key} = ${prior.value}`;
217
+ else {
218
+ lines.splice(index, 1);
219
+ index--;
220
+ firstTable--;
221
+ }
222
+ restored.add(key);
223
+ }
224
+ if (Object.values(config.previous).some((entry) => entry && !entry.present) && lines[0] === "")
225
+ lines.shift();
226
+ return lines.join("\n");
227
+ }
228
+ function configureGlobal(options) {
229
+ const home = options.codexHome;
230
+ const configFile = join3(home, "config.toml");
231
+ const desired = {
232
+ model: options.orchestrator,
233
+ model_reasoning_effort: options.reasoningEffort,
234
+ plan_mode_reasoning_effort: options.planReasoningEffort
235
+ };
236
+ const state = loadState(home);
237
+ const original = existsSync3(configFile) ? readText(configFile) : "";
238
+ const current = topLevelConfigEntries(original);
239
+ if (state.config?.target === configFile) {
240
+ const changed = Object.entries(state.config.desired).some(([key, value]) => current.get(key)?.value !== tomlString(value));
241
+ if (changed && !options.force) {
242
+ console.warn(
243
+ `preserved modified config: ${configFile} (use --force to replace)`
244
+ );
245
+ return;
246
+ }
247
+ }
248
+ const updated = setTopLevelConfig(original, desired);
249
+ if (updated !== original) {
250
+ backup(configFile);
251
+ write(configFile, updated);
252
+ console.log(`configured orchestrator: ${configFile}`);
253
+ } else console.log(`unchanged: ${configFile}`);
254
+ const previous = state.config?.previous ?? {};
255
+ for (const key of Object.keys(desired)) {
256
+ if (key in previous) continue;
257
+ const entry = current.get(key);
258
+ previous[key] = entry ? { present: true, value: entry.value } : { present: false };
259
+ }
260
+ state.version = PACKAGE.version;
261
+ state.config = { target: configFile, desired, previous };
262
+ saveState(home, state);
263
+ console.log(`Orchestrator: ${desired.model}`);
264
+ console.log(`Reasoning effort: ${desired.model_reasoning_effort}`);
265
+ console.log(
266
+ `Plan mode reasoning effort: ${desired.plan_mode_reasoning_effort}`
267
+ );
268
+ }
269
+ function restoreConfig(config) {
270
+ if (!existsSync3(config.target)) {
271
+ console.warn(`preserved missing config: ${config.target}`);
272
+ return;
273
+ }
274
+ const original = readText(config.target);
275
+ const current = topLevelConfigEntries(original);
276
+ const changed = Object.entries(config.desired).some(([key, value]) => current.get(key)?.value !== tomlString(value));
277
+ if (changed) {
278
+ console.warn(`preserved modified config: ${config.target}`);
279
+ return;
280
+ }
281
+ const restored = restoreTopLevelConfig(original, config);
282
+ if (restored === original) return;
283
+ backup(config.target);
284
+ if (restored.trim()) write(config.target, restored);
285
+ else rmSync2(config.target);
286
+ console.log(`restored config: ${config.target}`);
287
+ }
288
+
289
+ // src/global/hooks.ts
290
+ import { existsSync as existsSync4, rmSync as rmSync3 } from "node:fs";
291
+ import { join as join4 } from "node:path";
292
+ var shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
293
+ var hookCommands = (file) => ({
294
+ command: `/usr/bin/env node ${shellQuote(file)}`,
295
+ commandWindows: `node ${JSON.stringify(file)}`
296
+ });
297
+ function removeHookHandlers(root, state) {
298
+ const hooks = root.hooks;
299
+ if (!isRecord(hooks)) return;
300
+ for (const [event, groupsValue] of Object.entries(hooks)) {
301
+ if (!Array.isArray(groupsValue)) continue;
302
+ const groups = groupsValue.flatMap((groupValue) => {
303
+ if (!isRecord(groupValue) || !Array.isArray(groupValue.hooks))
304
+ return [groupValue];
305
+ const handlers = groupValue.hooks.filter(
306
+ (handler) => !isRecord(handler) || handler.command !== state.command && handler.commandWindows !== state.commandWindows
307
+ );
308
+ return handlers.length ? [{ ...groupValue, hooks: handlers }] : [];
309
+ });
310
+ if (groups.length) hooks[event] = groups;
311
+ else delete hooks[event];
312
+ }
313
+ }
314
+ function installRoutingHooks(home, previous) {
315
+ const target = join4(home, "hooks.json");
316
+ const commands = hookCommands(join4(home, "codex-kit", "routing-hook.js"));
317
+ const created = previous?.created ?? !existsSync4(target);
318
+ const root = readJsonObject(target);
319
+ if (previous) removeHookHandlers(root, previous);
320
+ const hooks = isRecord(root.hooks) ? root.hooks : {};
321
+ root.hooks = hooks;
322
+ const handler = { type: "command", ...commands, timeout: 5 };
323
+ hooks.UserPromptSubmit = [
324
+ ...Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : [],
325
+ { hooks: [{ ...handler, statusMessage: "Loading subagent routing" }] }
326
+ ];
327
+ hooks.SubagentStart = [
328
+ ...Array.isArray(hooks.SubagentStart) ? hooks.SubagentStart : [],
329
+ { hooks: [{ ...handler, statusMessage: "Briefing delegated worker" }] }
330
+ ];
331
+ const updated = `${JSON.stringify(root, null, 2)}
332
+ `;
333
+ const original = existsSync4(target) ? readText(target) : "";
334
+ if (updated !== original) {
335
+ backup(target);
336
+ write(target, updated);
337
+ console.log(`updated: ${target}`);
338
+ }
339
+ return { target, ...commands, created };
340
+ }
341
+ function uninstallRoutingHooks(state) {
342
+ if (!existsSync4(state.target)) {
343
+ console.warn(`preserved missing hooks file: ${state.target}`);
344
+ return;
345
+ }
346
+ const root = readJsonObject(state.target);
347
+ removeHookHandlers(root, state);
348
+ if (isRecord(root.hooks) && !Object.keys(root.hooks).length)
349
+ delete root.hooks;
350
+ backup(state.target);
351
+ if (state.created && !Object.keys(root).length) rmSync3(state.target);
352
+ else write(state.target, `${JSON.stringify(root, null, 2)}
353
+ `);
354
+ console.log(`removed managed routing hooks from: ${state.target}`);
355
+ }
356
+
357
+ // src/global/commands.ts
358
+ var GLOBAL_BEGIN = "<!-- BEGIN codex-kit:subagent-routing -->";
359
+ var GLOBAL_END = "<!-- END codex-kit:subagent-routing -->";
360
+ var managedBlock = (content, begin, end) => `${begin}
361
+ ${content.trimEnd()}
362
+ ${end}`;
363
+ function replaceOrAppendBlock(original, content, begin, end) {
364
+ const start = original.indexOf(begin);
365
+ const finish = original.indexOf(end);
366
+ if (start >= 0 !== finish >= 0 || start >= 0 && finish < start)
367
+ throw new Error(
368
+ `Malformed managed block: expected both ${begin} and ${end}.`
369
+ );
370
+ const block = managedBlock(content, begin, end);
371
+ return start >= 0 ? `${original.slice(0, start)}${block}${original.slice(finish + end.length)}` : `${original.trimEnd()}${original.trim() ? "\n\n" : ""}${block}
372
+ `;
373
+ }
374
+ function removeBlock(original, begin, end) {
375
+ const start = original.indexOf(begin);
376
+ const finish = original.indexOf(end);
377
+ if (start < 0 && finish < 0) return original;
378
+ if (start < 0 || finish < start)
379
+ throw new Error("Malformed managed block in AGENTS.md.");
380
+ const before = original.slice(0, start).trimEnd();
381
+ const after = original.slice(finish + end.length).trimStart();
382
+ return `${before}${before && after ? "\n\n" : ""}${after}${before || after ? "\n" : ""}`;
383
+ }
384
+ function installGlobal(options) {
385
+ const home = options.codexHome;
386
+ mkdirSync2(home, { recursive: true });
387
+ const prior = loadState(home);
388
+ const next = {
389
+ version: PACKAGE.version,
390
+ files: {},
391
+ globalAgents: null,
392
+ hooks: null,
393
+ config: prior.config ?? null
394
+ };
395
+ for (const name of readdirSync(AGENTS_DIR).filter((name2) => name2.endsWith(".toml")).sort()) {
396
+ const key = `agents/${name}`;
397
+ const record = installFile(
398
+ join5(AGENTS_DIR, name),
399
+ join5(home, "agents", name),
400
+ key,
401
+ prior,
402
+ options.force
403
+ );
404
+ if (record) next.files[key] = record;
405
+ }
406
+ const sources = [
407
+ [ROUTING_FILE, join5(home, "SUBAGENT_ROUTING.md"), "routing"],
408
+ [
409
+ RECONCILE_SKILL_FILE,
410
+ join5(home, "skills", RECONCILE_SKILL, "SKILL.md"),
411
+ `skills/${RECONCILE_SKILL}/SKILL.md`
412
+ ],
413
+ [
414
+ RECONCILE_SKILL_METADATA_FILE,
415
+ join5(home, "skills", RECONCILE_SKILL, "agents", "openai.yaml"),
416
+ `skills/${RECONCILE_SKILL}/agents/openai.yaml`
417
+ ],
418
+ [
419
+ ROUTING_HOOK_FILE,
420
+ join5(home, "codex-kit", "routing-hook.js"),
421
+ "routing-hook"
422
+ ]
423
+ ];
424
+ for (const [source, target, key] of sources) {
425
+ const record = installFile(source, target, key, prior, options.force);
426
+ if (record) next.files[key] = record;
427
+ }
428
+ for (const [key, record] of Object.entries(prior.files)) {
429
+ if (key in next.files) continue;
430
+ if (!existsSync5(record.target) || sha256(read(record.target)) !== record.hash) {
431
+ console.warn(
432
+ `preserved stale modified or missing file: ${record.target}`
433
+ );
434
+ next.files[key] = record;
435
+ } else if (record.ownership === "created") {
436
+ rmSync4(record.target);
437
+ console.log(`removed stale: ${record.target}`);
438
+ } else if (record.ownership === "replaced" && record.backup && existsSync5(record.backup)) {
439
+ copyFileSync3(record.backup, record.target);
440
+ console.log(`restored stale: ${record.target}`);
441
+ }
442
+ }
443
+ const globalAgents = join5(home, "AGENTS.md");
444
+ const original = existsSync5(globalAgents) ? readText(globalAgents) : "";
445
+ const updated = replaceOrAppendBlock(
446
+ original,
447
+ readText(ROUTING_FILE),
448
+ GLOBAL_BEGIN,
449
+ GLOBAL_END
450
+ );
451
+ if (updated !== original) {
452
+ backup(globalAgents);
453
+ write(globalAgents, updated);
454
+ console.log(`updated: ${globalAgents}`);
455
+ }
456
+ next.globalAgents = { target: globalAgents };
457
+ next.hooks = installRoutingHooks(home, prior.hooks);
458
+ saveState(home, next);
459
+ configureGlobal(options);
460
+ const commitPusher = join5(home, "agents", "commit-pusher.toml");
461
+ if (existsSync5(commitPusher))
462
+ console.warn(
463
+ `warning: existing unmanaged commit-pusher remains at ${commitPusher}`
464
+ );
465
+ console.log(`Codex kit ${PACKAGE.version} installed under ${home}`);
466
+ }
467
+ function listGlobal(options) {
468
+ const home = options.codexHome;
469
+ const configFile = join5(home, "config.toml");
470
+ const globalAgents = join5(home, "AGENTS.md");
471
+ const state = loadState(home);
472
+ const config = existsSync5(configFile) ? topLevelConfigEntries(readText(configFile)) : /* @__PURE__ */ new Map();
473
+ const value = (key) => {
474
+ const raw = config.get(key)?.value;
475
+ if (!raw) return "not set";
476
+ try {
477
+ return String(JSON.parse(raw));
478
+ } catch {
479
+ return raw;
480
+ }
481
+ };
482
+ console.log(`Codex home: ${home}`);
483
+ console.log(
484
+ `Config: ${configFile}${existsSync5(configFile) ? "" : " (missing)"}`
485
+ );
486
+ console.log(`Orchestrator: ${value("model")}`);
487
+ console.log(`Reasoning effort: ${value("model_reasoning_effort")}`);
488
+ console.log(
489
+ `Plan mode reasoning effort: ${value("plan_mode_reasoning_effort")}`
490
+ );
491
+ console.log(
492
+ `Kit state: ${existsSync5(join5(home, STATE_FILE)) ? state.version : "not installed"}`
493
+ );
494
+ console.log(
495
+ `Global routing: ${existsSync5(globalAgents) && readText(globalAgents).includes(GLOBAL_BEGIN) ? "installed" : "not installed"}`
496
+ );
497
+ console.log(
498
+ `Routing file: ${existsSync5(join5(home, "SUBAGENT_ROUTING.md")) ? join5(home, "SUBAGENT_ROUTING.md") : "missing"}`
499
+ );
500
+ const routingHook = state.hooks;
501
+ console.log(
502
+ `Routing hook: ${routingHook && existsSync5(routingHook.target) && readText(routingHook.target).includes(routingHook.command) ? "installed" : "not installed"}`
503
+ );
504
+ const skillTargets = [
505
+ [
506
+ join5(home, "skills", RECONCILE_SKILL, "SKILL.md"),
507
+ state.files[`skills/${RECONCILE_SKILL}/SKILL.md`]
508
+ ],
509
+ [
510
+ join5(home, "skills", RECONCILE_SKILL, "agents", "openai.yaml"),
511
+ state.files[`skills/${RECONCILE_SKILL}/agents/openai.yaml`]
512
+ ]
513
+ ];
514
+ const skillStatus = skillTargets.every(
515
+ ([target, record]) => existsSync5(target) && record && sha256(read(target)) === record.hash
516
+ ) ? "installed" : skillTargets.some(([target]) => existsSync5(target)) ? "modified or incomplete" : "missing";
517
+ console.log(`Reconciliation skill: ${skillStatus}`);
518
+ console.log("Custom agents:");
519
+ const agentsDir = join5(home, "agents");
520
+ const agents = existsSync5(agentsDir) ? readdirSync(agentsDir).filter((name) => name.endsWith(".toml")).sort() : [];
521
+ if (!agents.length) {
522
+ console.log(" (none)");
523
+ return;
524
+ }
525
+ const managed = new Set(
526
+ Object.values(state.files).map((record) => record.target)
527
+ );
528
+ for (const filename of agents) {
529
+ const file = join5(agentsDir, filename);
530
+ const contents = readText(file);
531
+ const field = (key) => new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m").exec(contents)?.[1] ?? "not set";
532
+ console.log(
533
+ ` ${field("name")} \u2014 ${field("model")}, ${field("model_reasoning_effort")} (${managed.has(file) ? "managed" : "unmanaged"})`
534
+ );
535
+ }
536
+ }
537
+ function uninstallGlobal(options) {
538
+ const home = options.codexHome;
539
+ const statePath = join5(home, STATE_FILE);
540
+ if (!existsSync5(statePath)) {
541
+ console.log(`No installer state at ${statePath}; nothing removed.`);
542
+ return;
543
+ }
544
+ const state = loadState(home);
545
+ for (const record of Object.values(state.files)) restoreFile(record);
546
+ const globalAgents = state.globalAgents?.target ?? join5(home, "AGENTS.md");
547
+ if (existsSync5(globalAgents)) {
548
+ const original = readText(globalAgents);
549
+ const updated = removeBlock(original, GLOBAL_BEGIN, GLOBAL_END);
550
+ if (updated !== original) {
551
+ backup(globalAgents);
552
+ if (updated) write(globalAgents, updated);
553
+ else rmSync4(globalAgents);
554
+ console.log(`removed managed routing from: ${globalAgents}`);
555
+ }
556
+ }
557
+ if (state.config) restoreConfig(state.config);
558
+ if (state.hooks) uninstallRoutingHooks(state.hooks);
559
+ const skillDir = join5(home, "skills", RECONCILE_SKILL);
560
+ const metadataDir = join5(skillDir, "agents");
561
+ if (existsSync5(metadataDir) && statSync(metadataDir).isDirectory() && !readdirSync(metadataDir).length)
562
+ rmSync4(metadataDir, { recursive: true });
563
+ if (existsSync5(skillDir) && statSync(skillDir).isDirectory() && !readdirSync(skillDir).length)
564
+ rmSync4(skillDir, { recursive: true });
565
+ const allowancesDir = join5(home, "codex-kit", "allowances");
566
+ if (existsSync5(allowancesDir))
567
+ rmSync4(allowancesDir, { recursive: true, force: true });
568
+ const kitDir = join5(home, "codex-kit");
569
+ if (existsSync5(kitDir) && !readdirSync(kitDir).length)
570
+ rmSync4(kitDir, { recursive: true });
571
+ rmSync4(statePath);
572
+ console.log(`Codex kit uninstalled from ${home}`);
573
+ }
574
+
575
+ // src/project/commands.ts
576
+ import { existsSync as existsSync6, statSync as statSync2 } from "node:fs";
577
+ import { join as join6 } from "node:path";
578
+ var STATE_FILE2 = ".codex-kit-state.json";
579
+ var PROJECT_BEGIN = "<!-- BEGIN codex-kit:shared-template -->";
580
+ var PROJECT_END = "<!-- END codex-kit:shared-template -->";
581
+ var PROJECT_SCAFFOLD = "# Project-Specific Instructions\n\n<!-- Add repository-specific commands, architecture, and exceptions here. -->\n";
582
+ function loadProjectState(cwd) {
583
+ const file = join6(cwd, STATE_FILE2);
584
+ if (!existsSync6(file)) return { version: 1, template: {} };
585
+ try {
586
+ const state = JSON.parse(readText(file));
587
+ return isRecord(state) && isRecord(state.template) ? state : { version: 1, template: {} };
588
+ } catch {
589
+ throw new Error(`${file} is not valid JSON; move it aside before syncing.`);
590
+ }
591
+ }
592
+ var saveProjectState = (cwd, state) => write(join6(cwd, STATE_FILE2), `${JSON.stringify(state, null, 2)}
593
+ `);
594
+ var requireDirectory = (cwd) => {
595
+ if (!existsSync6(cwd) || !statSync2(cwd).isDirectory())
596
+ throw new Error(`Not a directory: ${cwd}`);
597
+ };
598
+ function initializationPrompt() {
599
+ return `Project guidance needs initialization. Copy everything between the markers into Codex.
23
600
 
24
601
  ===== BEGIN CODEX INITIALIZATION PROMPT =====
25
602
  Explore this repository before changing code. First determine whether it has a
@@ -33,14 +610,17 @@ If evidence is sufficient, identify the stack, package manager, scripts,
33
610
  structure, established patterns, testing tools, and generated files. Add concise
34
611
  project-specific guidance to AGENTS.md based only on repository evidence,
35
612
  including exact verification commands. Then use the global
36
- $${h} skill to merge applicable reusable guidance from
613
+ $${RECONCILE_SKILL} skill to merge applicable reusable guidance from
37
614
  TEMPLATE_AGENTS.md while preserving AGENTS.md organization and local rules.
38
615
  Validate the final instruction changes, mark the template applied only after
39
616
  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.
617
+ ===== END CODEX INITIALIZATION PROMPT =====`;
618
+ }
619
+ function reconciliationPrompt() {
620
+ return `Template reference updated. Copy everything between the markers into Codex.
41
621
 
42
622
  ===== BEGIN CODEX RECONCILIATION PROMPT =====
43
- Use the global $${h} skill to reconcile the existing AGENTS.md
623
+ Use the global $${RECONCILE_SKILL} skill to reconcile the existing AGENTS.md
44
624
  with the refreshed TEMPLATE_AGENTS.md.
45
625
 
46
626
  Inspect TEMPLATE_AGENTS.md, AGENTS.md, .codex-kit-state.json, existing
@@ -53,9 +633,224 @@ template or introduce managed markers.
53
633
  Validate the final instruction changes. Mark applied only after reconciliation
54
634
  and validation succeed, confirm codex-kit project status is up to date, then
55
635
  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:"medium",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}
636
+ ===== END CODEX RECONCILIATION PROMPT =====`;
637
+ }
638
+ function syncProject(options) {
639
+ const { cwd } = options;
640
+ requireDirectory(cwd);
641
+ const agentsFile = join6(cwd, "AGENTS.md");
642
+ const stagedTemplate = join6(cwd, "TEMPLATE_AGENTS.md");
643
+ const desired = Buffer.from(readText(TEMPLATE_FILE));
644
+ const sourceHash = sha256(desired);
645
+ const state = loadProjectState(cwd);
646
+ if (existsSync6(stagedTemplate)) {
647
+ const currentHash = sha256(read(stagedTemplate));
648
+ const locallyModified = currentHash !== sourceHash && (!state.template.availableHash || currentHash !== state.template.availableHash);
649
+ if (locallyModified && !options.force) {
650
+ console.warn(
651
+ `preserved locally modified template: ${stagedTemplate} (use --force to replace)`
652
+ );
653
+ console.log(
654
+ `The installed kit has template ${PACKAGE.version}; review the local change before syncing.`
655
+ );
656
+ return;
657
+ }
658
+ if (currentHash === sourceHash) console.log(`unchanged: ${stagedTemplate}`);
659
+ else {
660
+ backup(stagedTemplate);
661
+ write(stagedTemplate, desired);
662
+ console.log(`refreshed template reference: ${stagedTemplate}`);
663
+ }
664
+ } else {
665
+ write(stagedTemplate, desired);
666
+ console.log(`created template reference: ${stagedTemplate}`);
667
+ }
668
+ state.version = 1;
669
+ state.template = {
670
+ ...state.template,
671
+ availableHash: sourceHash,
672
+ availableVersion: PACKAGE.version
673
+ };
674
+ saveProjectState(cwd, state);
675
+ const createdAgents = !existsSync6(agentsFile);
676
+ if (createdAgents) {
677
+ write(agentsFile, PROJECT_SCAFFOLD);
678
+ console.log(`created project instructions file: ${agentsFile}`);
679
+ } else {
680
+ const agents = readText(agentsFile);
681
+ if (agents.includes(PROJECT_BEGIN) || agents.includes(PROJECT_END)) {
682
+ console.warn(`preserved legacy managed template in: ${agentsFile}`);
683
+ console.warn(
684
+ "Ask Codex to migrate it to semantic template reconciliation before applying updates."
685
+ );
686
+ }
687
+ }
688
+ const needsInitialization = readText(agentsFile).trim() === PROJECT_SCAFFOLD.trim();
689
+ console.log(
690
+ needsInitialization ? initializationPrompt() : reconciliationPrompt()
691
+ );
692
+ }
693
+ function projectStatus(options) {
694
+ const { cwd } = options;
695
+ requireDirectory(cwd);
696
+ const stagedTemplate = join6(cwd, "TEMPLATE_AGENTS.md");
697
+ const state = loadProjectState(cwd);
698
+ const availableHash = state.template.availableHash ?? null;
699
+ const localHash = existsSync6(stagedTemplate) ? sha256(read(stagedTemplate)) : null;
700
+ console.log(`Project: ${cwd}`);
701
+ if (!localHash) {
702
+ console.log("Status: not initialized (run codex-kit project sync)");
703
+ return;
704
+ }
705
+ if (!existsSync6(join6(cwd, "AGENTS.md"))) {
706
+ console.log("Status: AGENTS.md missing (reconcile the template first)");
707
+ return;
708
+ }
709
+ console.log(
710
+ `Available: ${state.template.availableVersion ?? "unknown"} (${availableHash ?? "untracked"})`
711
+ );
712
+ console.log(`Applied: ${state.template.appliedHash ?? "never"}`);
713
+ if (sha256(read(TEMPLATE_FILE)) !== availableHash) {
714
+ console.log("Status: kit template update available; run project sync");
715
+ return;
716
+ }
717
+ if (localHash !== availableHash) {
718
+ console.log("Status: local template changed; review it before syncing");
719
+ return;
720
+ }
721
+ if (state.template.appliedHash !== localHash) {
722
+ console.log("Status: reconciliation required");
723
+ return;
724
+ }
725
+ console.log("Status: up to date");
726
+ }
727
+ function markApplied(options) {
728
+ const { cwd } = options;
729
+ requireDirectory(cwd);
730
+ const stagedTemplate = join6(cwd, "TEMPLATE_AGENTS.md");
731
+ const agentsFile = join6(cwd, "AGENTS.md");
732
+ if (!existsSync6(stagedTemplate))
733
+ throw new Error(`Missing ${stagedTemplate}; run project sync first.`);
734
+ if (!existsSync6(agentsFile))
735
+ throw new Error(
736
+ `Missing ${agentsFile}; reconcile the template into AGENTS.md first.`
737
+ );
738
+ const state = loadProjectState(cwd);
739
+ state.version = 1;
740
+ state.template = {
741
+ ...state.template,
742
+ appliedHash: sha256(read(stagedTemplate)),
743
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString()
744
+ };
745
+ saveProjectState(cwd, state);
746
+ console.log(`recorded template reconciliation: ${stagedTemplate}`);
747
+ }
748
+
749
+ // src/version.ts
750
+ import { spawnSync } from "node:child_process";
751
+ function compareVersions(left, right) {
752
+ const parse2 = (value) => {
753
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(value);
754
+ if (!match) throw new Error(`Invalid package version: ${value}`);
755
+ return {
756
+ numbers: [Number(match[1]), Number(match[2]), Number(match[3])],
757
+ prerelease: match[4] ?? null
758
+ };
759
+ };
760
+ const a = parse2(left);
761
+ const b = parse2(right);
762
+ for (const [leftNumber, rightNumber] of [
763
+ [a.numbers[0], b.numbers[0]],
764
+ [a.numbers[1], b.numbers[1]],
765
+ [a.numbers[2], b.numbers[2]]
766
+ ])
767
+ if (leftNumber !== rightNumber) return Math.sign(leftNumber - rightNumber);
768
+ if (a.prerelease === b.prerelease) return 0;
769
+ if (!a.prerelease) return 1;
770
+ if (!b.prerelease) return -1;
771
+ return Math.sign(
772
+ a.prerelease.localeCompare(b.prerelease, "en", { numeric: true })
773
+ );
774
+ }
775
+ function checkVersion() {
776
+ let latest = process.env.CODEX_KIT_LATEST_VERSION;
777
+ if (!latest) {
778
+ const result = spawnSync(
779
+ process.platform === "win32" ? "pnpm.cmd" : "pnpm",
780
+ ["view", PACKAGE.name, "version", "--json", `--registry=${REGISTRY}`],
781
+ { encoding: "utf8", timeout: 15e3 }
782
+ );
783
+ if (result.error)
784
+ throw new Error(`Unable to run pnpm: ${result.error.message}`);
785
+ if (result.status !== 0)
786
+ throw new Error(
787
+ `Unable to check ${REGISTRY}: ${result.stderr.trim() || "pnpm view failed"}`
788
+ );
789
+ try {
790
+ const value = JSON.parse(result.stdout);
791
+ latest = Array.isArray(value) && typeof value.at(-1) === "string" ? value.at(-1) : typeof value === "string" ? value : void 0;
792
+ } catch {
793
+ latest = result.stdout.trim();
794
+ }
795
+ }
796
+ if (!latest) throw new Error("Registry returned no package version.");
797
+ console.log(`Installed: ${PACKAGE.version}`);
798
+ console.log(`Latest: ${latest}`);
799
+ const comparison = compareVersions(PACKAGE.version, latest);
800
+ if (comparison === 0) {
801
+ console.log("codex-kit is up to date.");
802
+ return;
803
+ }
804
+ if (comparison > 0) {
805
+ console.log("This local build is newer than the published package.");
806
+ return;
807
+ }
808
+ console.log(
809
+ `Update available. Run:
810
+ pnpm add --global ${PACKAGE.name}@latest
811
+ codex-kit global install`
812
+ );
813
+ }
814
+
815
+ // src/cli/options.ts
816
+ import { homedir } from "node:os";
817
+ import { join as join7, resolve as resolve2 } from "node:path";
818
+ function parse(argv) {
819
+ const options = {
820
+ cwd: process.cwd(),
821
+ codexHome: resolve2(process.env.CODEX_HOME || join7(homedir(), ".codex")),
822
+ orchestrator: "gpt-5.6-sol",
823
+ reasoningEffort: "low",
824
+ planReasoningEffort: "high",
825
+ force: false,
826
+ positionals: []
827
+ };
828
+ for (let index = 0; index < argv.length; index++) {
829
+ const arg = argv[index];
830
+ if (arg === void 0) continue;
831
+ if (arg === "--force") options.force = true;
832
+ else if (arg === "--cwd" || arg === "--codex-home") {
833
+ const value = argv[++index];
834
+ if (!value) throw new Error(`${arg} requires a path.`);
835
+ if (arg === "--cwd") options.cwd = resolve2(value);
836
+ else options.codexHome = resolve2(value);
837
+ } else if (arg === "--orchestrator" || arg === "--model") {
838
+ const value = argv[++index];
839
+ if (!value) throw new Error(`${arg} requires a model.`);
840
+ options.orchestrator = value;
841
+ } else if (arg === "--reasoning-effort" || arg === "--plan-reasoning-effort") {
842
+ const value = argv[++index];
843
+ if (!value) throw new Error(`${arg} requires a value.`);
844
+ if (arg === "--reasoning-effort") options.reasoningEffort = value;
845
+ else options.planReasoningEffort = value;
846
+ } else options.positionals.push(arg);
847
+ }
848
+ return options;
849
+ }
850
+
851
+ // src/cli/main.ts
852
+ function help() {
853
+ console.log(`codex-kit ${PACKAGE.version}
59
854
 
60
855
  Usage:
61
856
  codex-kit <command> [options]
@@ -83,7 +878,7 @@ Options by command:
83
878
  --force Replace modified config managed by codex-kit.
84
879
  --orchestrator MODEL Set the root/orchestrator model (default: gpt-5.6-sol).
85
880
  --model MODEL Alias for --orchestrator.
86
- --reasoning-effort LEVEL Set normal reasoning effort (default: medium).
881
+ --reasoning-effort LEVEL Set normal reasoning effort (default: low).
87
882
  --plan-reasoning-effort LEVEL Set Plan-mode reasoning effort (default: high).
88
883
 
89
884
  global list, global uninstall
@@ -98,6 +893,47 @@ Options by command:
98
893
 
99
894
  Examples:
100
895
  codex-kit global install --force
101
- codex-kit global configure --reasoning-effort medium --plan-reasoning-effort high
896
+ codex-kit global configure --reasoning-effort low --plan-reasoning-effort high
102
897
  codex-kit project sync --cwd /path/to/project --force
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")||e.positionals.includes("-v")){console.log(p.version);return}if(!e.positionals.length||e.positionals.includes("--help")||e.positionals.includes("-h")){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};
898
+ codex-kit project status --cwd /path/to/project`);
899
+ }
900
+ function main(argv = process.argv.slice(2)) {
901
+ const options = parse(argv);
902
+ if (options.positionals.includes("--version") || options.positionals.includes("-v")) {
903
+ console.log(PACKAGE.version);
904
+ return;
905
+ }
906
+ if (!options.positionals.length || options.positionals.includes("--help") || options.positionals.includes("-h")) {
907
+ help();
908
+ return;
909
+ }
910
+ const [scope, action] = options.positionals;
911
+ if (scope === "global" && action === "install") installGlobal(options);
912
+ else if (scope === "global" && action === "configure")
913
+ configureGlobal(options);
914
+ else if (scope === "global" && action === "list") listGlobal(options);
915
+ else if (scope === "global" && action === "uninstall")
916
+ uninstallGlobal(options);
917
+ else if (scope === "project" && (action === "init" || action === "sync"))
918
+ syncProject(options);
919
+ else if (scope === "project" && action === "status") projectStatus(options);
920
+ else if (scope === "project" && action === "mark-applied")
921
+ markApplied(options);
922
+ else if (scope === "version" && action === "check") checkVersion();
923
+ else throw new Error(`Unknown command: ${options.positionals.join(" ")}`);
924
+ }
925
+
926
+ // src/codex-kit.ts
927
+ if (process.argv[1] && realpathSync(resolve3(process.argv[1])) === realpathSync(fileURLToPath2(import.meta.url))) {
928
+ try {
929
+ main();
930
+ } catch (error) {
931
+ console.error(
932
+ `error: ${error instanceof Error ? error.message : String(error)}`
933
+ );
934
+ process.exitCode = 1;
935
+ }
936
+ }
937
+ export {
938
+ main
939
+ };
@@ -1,6 +1,35 @@
1
1
  #!/usr/bin/env node
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
2
 
4
- ${t}
3
+ // src/routing-hook.ts
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ var home = resolve(dirname(fileURLToPath(import.meta.url)), "..");
8
+ var routingFile = join(home, "SUBAGENT_ROUTING.md");
9
+ var input = JSON.parse(readFileSync(0, "utf8"));
10
+ function hookContext(event, context) {
11
+ return JSON.stringify({
12
+ hookSpecificOutput: {
13
+ hookEventName: event,
14
+ additionalContext: context
15
+ }
16
+ });
17
+ }
18
+ if (input.hook_event_name === "UserPromptSubmit" && existsSync(routingFile)) {
19
+ const routing = readFileSync(routingFile, "utf8").trim();
20
+ process.stdout.write(
21
+ `Codex-kit routing policy for this turn:
5
22
 
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.`));
23
+ ${routing}
24
+
25
+ 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.`
26
+ );
27
+ }
28
+ if (input.hook_event_name === "SubagentStart") {
29
+ process.stdout.write(
30
+ hookContext(
31
+ "SubagentStart",
32
+ `You are the delegated ${input.agent_type ?? "worker"}. Follow the assigned scope, perform the role's work directly without further delegation, validate it, and return concise evidence.`
33
+ )
34
+ );
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamdevlinph/codex-kit",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Portable Codex subagents and project AGENTS.md defaults.",
5
5
  "type": "module",
6
6
  "bin": {