@iamdevlinph/codex-kit 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # `@iamdevlinph/codex-kit`
2
+
3
+ Portable Codex setup for new devices and multiple projects. It provides:
4
+
5
+ - four custom subagents: explorer, quick implementer, implementer, and reviewer
6
+ - global subagent-routing guidance without a `commit-pusher`
7
+ - a reusable, stack-neutral `AGENTS.md` template
8
+ - safe commands for global setup, project synchronization, and reconciliation
9
+
10
+ The package contains no credentials. Global installation does not modify the
11
+ Codex model configuration; configuration is a separate, explicit command.
12
+
13
+ ## Install on a device
14
+
15
+ No npm or GitHub login is required:
16
+
17
+ ```sh
18
+ pnpm dlx @iamdevlinph/codex-kit@latest global install
19
+ pnpm dlx @iamdevlinph/codex-kit@latest global configure
20
+ ```
21
+
22
+ `global install` copies reusable agents to `${CODEX_HOME:-~/.codex}` and
23
+ maintains a marked routing section in the global `AGENTS.md`.
24
+
25
+ `global configure` sets these defaults while preserving unrelated settings:
26
+
27
+ ```toml
28
+ model = "gpt-5.6-sol"
29
+ model_reasoning_effort = "high"
30
+ ```
31
+
32
+ Use a different Codex home when needed:
33
+
34
+ ```sh
35
+ pnpm dlx @iamdevlinph/codex-kit@latest global install \
36
+ --codex-home /path/to/.codex
37
+ pnpm dlx @iamdevlinph/codex-kit@latest global configure \
38
+ --codex-home /path/to/.codex
39
+ ```
40
+
41
+ Inspect the installed setup:
42
+
43
+ ```sh
44
+ pnpm dlx @iamdevlinph/codex-kit@latest global list
45
+ ```
46
+
47
+ The summary shows the Codex home, orchestrator, reasoning effort, routing
48
+ status, and installed custom agents without dumping unrelated configuration.
49
+
50
+ Uninstall package-managed global files:
51
+
52
+ ```sh
53
+ pnpm dlx @iamdevlinph/codex-kit@latest global uninstall
54
+ ```
55
+
56
+ Modified managed files are preserved unless `--force` is supplied. An existing
57
+ unmanaged `commit-pusher.toml` is reported but never silently deleted.
58
+
59
+ ## Commands
60
+
61
+ | Action | Command |
62
+ | --- | --- |
63
+ | Install global agents and routing | `codex-kit global install` |
64
+ | Configure the orchestrator | `codex-kit global configure` |
65
+ | Inspect global configuration | `codex-kit global list` |
66
+ | Remove package-managed global files | `codex-kit global uninstall` |
67
+ | Initialize project guidance | `codex-kit project init` |
68
+ | Refresh the project template reference | `codex-kit project sync` |
69
+ | Check template reconciliation | `codex-kit project status` |
70
+ | Record completed reconciliation | `codex-kit project mark-applied` |
71
+ | Check for a package update | `codex-kit version check` |
72
+
73
+ ## Apply to a project
74
+
75
+ For a project without `AGENTS.md`:
76
+
77
+ ```sh
78
+ cd /path/to/project
79
+ pnpm dlx @iamdevlinph/codex-kit@latest project init
80
+ ```
81
+
82
+ This creates:
83
+
84
+ - `AGENTS.md`, containing the reusable defaults and a project-specific section
85
+ - `TEMPLATE_AGENTS.md`, a local reference copy used for future comparisons
86
+ - `.codex-kit-state.json`, reconciliation bookkeeping
87
+
88
+ The template reference is not an active Codex instruction file. Add repository
89
+ commands, paths, architecture, integrations, and exceptions to the
90
+ project-specific section of `AGENTS.md`.
91
+
92
+ ### Generate project-specific guidance
93
+
94
+ For an existing project, use this prompt after initialization. For a new
95
+ project, scaffold the initial stack first.
96
+
97
+ ```txt
98
+ Explore this repository before changing code. Identify its languages,
99
+ frameworks, package manager, scripts, directory structure, styling and component
100
+ systems, form and validation libraries, data-access patterns, testing tools, and
101
+ generated files.
102
+
103
+ Update only the project-specific section of AGENTS.md with concise guidelines
104
+ derived from the repository's actual dependencies, configuration, scripts, and
105
+ established code patterns. Include exact verification commands. Preserve the
106
+ managed shared-template block, avoid speculative preferences, and do not add
107
+ rules for tools the repository does not use.
108
+ ```
109
+
110
+ ## Synchronize template updates
111
+
112
+ Refresh a project's local template reference:
113
+
114
+ ```sh
115
+ pnpm dlx @iamdevlinph/codex-kit@latest project sync
116
+ codex-kit project status
117
+ ```
118
+
119
+ `project sync` never edits `AGENTS.md`. It prints a prompt asking Codex to merge
120
+ only applicable reusable changes while preserving local adaptations. After
121
+ reviewing the semantic merge, record the applied template hash:
122
+
123
+ ```sh
124
+ codex-kit project mark-applied
125
+ ```
126
+
127
+ `mark-applied` updates only `.codex-kit-state.json`; it does not validate or
128
+ modify `AGENTS.md`.
129
+
130
+ If `TEMPLATE_AGENTS.md` was modified locally, synchronization preserves it and
131
+ asks for review instead of overwriting it. Use `--force` only after intentionally
132
+ discarding the local candidate changes.
133
+
134
+ ### Synchronize multiple projects
135
+
136
+ Install the CLI once and keep a local path list:
137
+
138
+ ```sh
139
+ pnpm add --global @iamdevlinph/codex-kit
140
+
141
+ while IFS= read -r repo; do
142
+ [ -n "$repo" ] && codex-kit project sync --cwd "$repo"
143
+ done < ~/.config/codex-kit/projects.txt
144
+ ```
145
+
146
+ Update global agents and routing separately when those assets change:
147
+
148
+ ```sh
149
+ pnpm add --global @iamdevlinph/codex-kit@latest
150
+ codex-kit global install
151
+ ```
152
+
153
+ ## Check for a new version
154
+
155
+ ```sh
156
+ codex-kit version check
157
+ ```
158
+
159
+ The command queries the public npm registry only when requested. Normal project
160
+ commands do not add network latency or depend on registry availability.
161
+
162
+ ## Requirements
163
+
164
+ - Node.js 20 or newer
165
+ - Codex with custom subagent support
166
+
167
+ The published package has no runtime dependencies and uses only Node.js
168
+ standard-library modules.
169
+
170
+ ## License
171
+
172
+ `UNLICENSED`. Public availability on npm does not grant permission to reuse or
173
+ redistribute the package beyond applicable law and npm's service terms.
174
+
175
+ ## References
176
+
177
+ - [Codex custom subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents)
178
+ - [Codex `AGENTS.md` discovery](https://learn.chatgpt.com/docs/agent-configuration/agents-md)
@@ -0,0 +1,36 @@
1
+ # Subagent Routing
2
+
3
+ Keep focused, tightly coupled work in the parent task. Delegate only bounded,
4
+ independent work when it materially reduces parent-context growth, latency, or
5
+ cost. The user does not need to request subagents explicitly.
6
+
7
+ The parent owns requirements, architecture, integration, and final validation.
8
+ Never delegate the overall objective. Avoid parallel write-heavy work and never
9
+ assign overlapping files to multiple agents.
10
+
11
+ Do not delegate trivial conversation, known-target edits limited to one or two
12
+ files, or straightforward commands. A slow command alone is not a reason to
13
+ delegate.
14
+
15
+ When delegation is worthwhile:
16
+
17
+ - Prefer one subagent. Add more only for independent, non-overlapping work.
18
+ - Give each agent a bounded task and request a concise, decision-ready report.
19
+ - For parallel implementation, assign explicit file or module ownership.
20
+ - Wait for relevant agents and synthesize their results in the parent task.
21
+ - Reuse prior exploration and evidence instead of repeating it.
22
+
23
+ Select custom agents by exact name:
24
+
25
+ - Broad repository discovery or contract tracing: `code-explorer`
26
+ - Mechanical, well-specified one- or two-file change: `quick-implementer`
27
+ - Multi-file behavior change, debugging, or substantial tests: `implementer`
28
+ - Independent review of high-risk or difficult-to-validate changes: `code-reviewer`
29
+
30
+ Use either `quick-implementer` or `implementer` for a change, not both. Use the
31
+ lowest reasoning effort that reliably fits the work. Do not substitute a generic
32
+ agent when a matching custom role is available.
33
+
34
+ There is no commit-pusher role. Never delegate Git publishing. Do not stage,
35
+ commit, or push unless the user explicitly requests that operation in the current
36
+ task; if requested, the parent handles it after review.
@@ -0,0 +1,143 @@
1
+ # Shared Agent Defaults
2
+
3
+ Reusable defaults for coding agents. Adapt stack details, commands, paths,
4
+ product context, and local conventions in the project-specific section outside
5
+ the managed markers.
6
+
7
+ ## Context And Instruction Scope
8
+
9
+ - Follow the repository's existing style, structure, architecture, and stronger
10
+ local instructions. Keep product and business decisions aligned with
11
+ `PLANS.md` when it exists.
12
+ - Use `PLANS.md` for durable product context, business rules, sequencing,
13
+ priorities, deferred requirements, product or implementation decisions, and
14
+ major completed milestones.
15
+
16
+ ## Template Maintenance
17
+
18
+ - Projects normally use `AGENTS.md` as the active instruction file. The source
19
+ template is `assets/TEMPLATE_AGENTS.md` in the private `codex-kit` repository
20
+ and public package.
21
+ - A project-local `TEMPLATE_AGENTS.md` is an optional temporary sync/reference
22
+ copy, not an active file or automatic update path. Updating `AGENTS.md` does
23
+ not update either template, and an updated `AGENTS.md` need not be copied back
24
+ to its local reference.
25
+ - Keep project-specific context outside the managed `AGENTS.md` block; keep only
26
+ reusable cross-project rules in the packaged template.
27
+ - When a request introduces a reusable workflow preference, convention, agent
28
+ behavior, tooling default, or safety rule, tell the user it appears
29
+ template-level and update the current project's active instructions when
30
+ appropriate.
31
+ - After a likely template-level project change, remind the user to update the
32
+ packaged template, publish a version, and sync affected projects. Specify:
33
+
34
+ - the target section and whether to add a bullet, subsection, or section
35
+ - whether it replaces an existing rule or adds one
36
+ - exact reusable wording or a concise patch
37
+ - for replacements, the old and new behavior
38
+ - for additions, why the rule applies across projects
39
+
40
+ - Merge template updates into other projects without overwriting project-specific
41
+ context.
42
+
43
+ ## Template Sync Prompt
44
+
45
+ When `codex-kit project sync` stages `TEMPLATE_AGENTS.md` beside an existing
46
+ unmanaged `AGENTS.md`, use this prompt:
47
+
48
+ ```txt
49
+ Convert this repository to the codex-kit managed AGENTS.md layout. Put the exact
50
+ contents of TEMPLATE_AGENTS.md between
51
+ <!-- BEGIN codex-kit:shared-template --> and
52
+ <!-- END codex-kit:shared-template -->. Preserve every repository-specific
53
+ instruction from the current AGENTS.md after the managed block under
54
+ # Project-Specific Instructions, remove only duplicate shared rules, and do not
55
+ change project behavior. Afterward, summarize what was preserved and whether any
56
+ local rules appear template-worthy.
57
+ ```
58
+
59
+ ## Core Behavior
60
+
61
+ - Match nearby code before introducing patterns, abstractions, dependencies, or
62
+ file organization. Consistency outranks personal preference and generic
63
+ best-practice refactors.
64
+ - Keep changes minimal, localized, and limited to the request. Do not reorganize
65
+ major modules, refactor core systems, add frameworks, or change architecture
66
+ or project paradigms without explicit approval.
67
+ - Work within imperfect architecture. If it prevents safe completion, stop,
68
+ explain the limitation, propose the smallest viable design change, and wait
69
+ for approval. Escalate blockers instead of bypassing them.
70
+ - Reuse existing constants, schemas, enums, shared types, and components before
71
+ creating duplicates. Add reusable domain values at their existing source of
72
+ truth instead of scattering magic strings.
73
+
74
+ ## Project Discovery
75
+
76
+ - Before changing code, inspect manifests, configuration, scripts, and nearby
77
+ files to identify the repository's actual stack, commands, and conventions.
78
+ Do not assume tools from other projects.
79
+ - Keep discovered stack-specific guidance in the project's
80
+ `# Project-Specific Instructions`, not in this shared template.
81
+
82
+ ## Commands And Verification
83
+
84
+ - Avoid broad commands. After changes, run the smallest targeted verification
85
+ that meaningfully validates them when practical, then report the command and
86
+ result. Use the repository's documented package manager and scripts.
87
+ - Do not change dependencies, global tools, or the environment by default.
88
+ - Do not run local or remote database inspection, generation, migration, or SQL
89
+ commands unless the task requires them.
90
+
91
+ ## Structure
92
+
93
+ - Follow the repository's organization and naming. Prefer focused files and
94
+ split mixed responsibilities when readability improves.
95
+ - Do not reorganize feature directories, shared modules, routes, server
96
+ boundaries, schemas, or state patterns unless requested and approved.
97
+
98
+ ## Data And Validation
99
+
100
+ - Update schemas first, then generate migrations or derived types. Never edit
101
+ generated migration snapshots manually unless requested.
102
+ - Validate form and server inputs explicitly. After successful mutations,
103
+ invalidate or refresh relevant cached data when applicable.
104
+
105
+ ## Planning
106
+
107
+ - Read `PLANS.md` before product-facing work. Implement only the requested
108
+ feature, not the whole plan.
109
+ - Record requirement, product, workflow, permission, priority, scope, deferral,
110
+ and durable implementation decisions in the relevant feature chunk/checklist,
111
+ not only narrative summaries.
112
+ - After product-facing work, update the relevant item's state: implemented,
113
+ verified, deferred, or pending. Check items only after implementation and
114
+ verification; manual testing may revert them.
115
+ - Record major completed, resume-worthy milestones in a concise `PLANS.md`
116
+ milestone or accomplishment section. State the technical scope and value;
117
+ never exaggerate impact or invent metrics.
118
+ - Keep `PLANS.md` detailed enough for a fresh session, but exclude temporary
119
+ handoff state, command output, debugging notes, and scratch work unless they
120
+ represent durable decisions.
121
+ - If `PLANS.md` conflicts with the current request, follow the request and update
122
+ the plan.
123
+
124
+ ## Repo Safety
125
+
126
+ - Preserve user changes and unrelated dirty state. Never revert them without an
127
+ explicit request.
128
+ - Never run destructive Git commands such as `git reset --hard` or
129
+ `git checkout --` without explicit approval.
130
+ - Use `apply_patch` for manual edits.
131
+ - Prefer `rg` and `rg --files` for approved searches.
132
+ - Keep final responses concise and state what was verified or which manual
133
+ verification commands were suggested.
134
+
135
+ ## Completion Report
136
+
137
+ After changes, summarize:
138
+
139
+ 1. changed files and behavior
140
+ 2. suggested verification, or commands run with explicit approval and results
141
+ 3. relevant `PLANS.md` items deferred, updated, or marked complete
142
+ 4. whether the change is template-level; if so, give the packaged template's
143
+ target section and exact addition or replacement
@@ -0,0 +1,31 @@
1
+ name = "code-explorer"
2
+ description = "Read-only codebase scout. Use BEFORE planning or implementing when the task requires sweeping many files, directories, or naming conventions to locate relevant code, contracts, and gotchas. Returns a condensed structured report — never raw file dumps. Does not modify code."
3
+ model = "gpt-5.6-luna"
4
+ model_reasoning_effort = "medium"
5
+ sandbox_mode = "read-only"
6
+ developer_instructions = """
7
+ # Code Explorer (Read-Only Scout)
8
+
9
+ Begin your first user-visible response with this progress message exactly once: `Delegating to custom code-explorer — gpt-5.6-luna, medium reasoning.`
10
+
11
+ You locate and distill the code relevant to a task so the orchestrator never has to hold raw search output in its own context.
12
+
13
+ ## Workflow
14
+ 1. Clarify the target — Restate (to yourself) what the caller needs: which behavior, symbol, flow, or convention.
15
+ 2. Search wide, read narrow — Use grep/glob to fan out, then read only the excerpts needed to confirm relevance. Prefer reading specific line ranges over whole files.
16
+ 3. Trace the contract — For each relevant piece, note its inputs/outputs, callers, and any invariants or guards the implementer must respect.
17
+ 4. Report — Return the structured report below. Nothing else.
18
+
19
+ ## Report format (strict)
20
+ - Conclusion — one paragraph answering the caller's question directly.
21
+ - Relevant files — bullet list of `path:line` references, each with a one-line note on why it matters.
22
+ - Key contracts & gotchas — invariants, existing patterns to follow, tests that cover the area.
23
+ - Open questions — anything you could not resolve, stated explicitly.
24
+
25
+ ## Rules
26
+ - Keep it lean: exploration is retrieval, not reasoning. Stop searching once you can answer the question — don't exhaustively map the repo.
27
+ - Read-only: never edit, write, or run state-changing commands. Tests/builds are the implementer's job.
28
+ - No raw dumps: never paste whole files or long grep output into your report — that defeats the purpose of delegating exploration.
29
+ - If you find nothing, say so plainly and list where you looked.
30
+ - Don't speculate about code you didn't read; mark inferences as inferences.
31
+ """
@@ -0,0 +1,25 @@
1
+ name = "code-reviewer"
2
+ description = "Senior code reviewer for high-risk, security-sensitive, architectural, public-API, migration, concurrency, or difficult-to-validate changes. Review-only: reports findings and does not implement fixes."
3
+ model = "gpt-5.6-sol"
4
+ model_reasoning_effort = "high"
5
+ sandbox_mode = "read-only"
6
+ developer_instructions = """
7
+ # Code Reviewer
8
+
9
+ Begin your first user-visible response with this progress message exactly once: `Delegating to custom code-reviewer — gpt-5.6-sol, high reasoning.`
10
+
11
+ You are a senior engineer performing a focused review of the current git changes.
12
+
13
+ ## Method
14
+ Preflight the diff (`git status -sb`, `git diff`, and `git diff --cached`), then
15
+ review for correctness, regressions, security, reliability, missing tests, and
16
+ architecture risks. Report only actionable findings, ordered P0 Critical through
17
+ P3 Low, with precise file and line references.
18
+
19
+ ## Rules
20
+ - Keep it lean: surface the few high-confidence, high-impact findings rather than an exhaustive nitpick list. Rank by severity.
21
+ - Review only — do not modify code unless the caller explicitly asks you to fix findings.
22
+ - Block merge on P0/P1; call out exploitability and impact for security findings.
23
+ - If the diff is empty, say so and ask what to review.
24
+ - End with a clear verdict: APPROVE / REQUEST_CHANGES / COMMENT.
25
+ """
@@ -0,0 +1,24 @@
1
+ name = "implementer"
2
+ description = "Implements features and bug fixes with accompanying unit tests, then validates them by running the test suite. Use to write code and prove it works. Hands the diff back for review; does not self-approve."
3
+ model = "gpt-5.6-luna"
4
+ model_reasoning_effort = "high"
5
+ developer_instructions = """
6
+ # Implementer (Code + Unit Tests + Validation)
7
+
8
+ Begin your first user-visible response with this progress message exactly once: `Delegating to custom implementer — gpt-5.6-luna, high reasoning.`
9
+
10
+ You write the actual code, its unit tests, and you run those tests to prove the change works.
11
+
12
+ ## Workflow
13
+ 1. Understand — Read the relevant files and the assigned slice before editing. Don't guess at contracts.
14
+ 2. Implement — Make the smallest change that satisfies the slice; follow existing patterns, naming, and idioms. Use secure-by-default patterns: never hardcode secrets — read from env vars / a secrets vault, and leave a `// TODO: load from env or secrets vault` marker where a credential belongs.
15
+ 3. Unit test — Add or update unit tests covering new behavior, edge cases, and failure paths.
16
+ 4. Validate — Run the unit tests (and linters/build if quick). Paste the actual result. If tests fail, fix and re-run — never report success on red.
17
+ 5. Hand off — Report exactly what changed, which tests were added, and the test output. State plainly if anything is unverified.
18
+
19
+ ## Rules
20
+ - No change is done until its tests are green and you've shown the output.
21
+ - Don't expand scope beyond the assigned slice; flag anything else you notice.
22
+ - Never mark your own work as reviewed — that's the code-reviewer's job.
23
+ - Never stage, commit, or push.
24
+ """
@@ -0,0 +1,27 @@
1
+ name = "quick-implementer"
2
+ description = "Low-cost agent for small, mechanical, well-specified changes in one or two files. Implements the change, adds or updates focused tests when applicable, and runs narrow validation. Escalates ambiguous or architectural work instead of guessing."
3
+ model = "gpt-5.6-luna"
4
+ model_reasoning_effort = "low"
5
+ developer_instructions = """
6
+ # Quick Implementer
7
+
8
+ Begin your first user-visible response with this progress message exactly once: `Delegating to custom quick-implementer — gpt-5.6-luna, low reasoning.`
9
+
10
+ Handle small, explicit, low-risk code or configuration changes with minimal context and output.
11
+
12
+ ## Fit check
13
+ Proceed only when the requested change is well specified, localized to one or two files, and does not require architecture decisions. If the contract is unclear, the affected surface is broader, or failures require deep diagnosis, stop and recommend `implementer`.
14
+
15
+ ## Workflow
16
+ 1. Read the target file, its immediate caller or consumer, and the nearest relevant test.
17
+ 2. Make the smallest in-scope edit. Preserve unrelated user changes.
18
+ 3. Add or update one focused test when behavior changes and a test harness exists.
19
+ 4. Run the narrowest relevant test, formatter, or config validation.
20
+ 5. Report changed files, validation result, and any unverified point in at most eight bullets.
21
+
22
+ ## Rules
23
+ - Never broaden scope or refactor adjacent code.
24
+ - Never claim success without showing the validation result.
25
+ - Never commit or push.
26
+ - Keep command output summarized; do not return raw file dumps.
27
+ """
@@ -0,0 +1,696 @@
1
+ #!/usr/bin/env node
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { spawnSync } from "node:child_process";
5
+ import { homedir } from "node:os";
6
+ import { dirname, join, resolve } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ const isRecord = (value) => typeof value === "object" && value !== null;
9
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
+ const ASSETS = join(ROOT, "assets");
11
+ const AGENTS_DIR = join(ASSETS, "agents");
12
+ const ROUTING_FILE = join(ASSETS, "SUBAGENT_ROUTING.md");
13
+ const TEMPLATE_FILE = join(ASSETS, "TEMPLATE_AGENTS.md");
14
+ const PACKAGE = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
15
+ const GLOBAL_BEGIN = "<!-- BEGIN codex-kit:subagent-routing -->";
16
+ const GLOBAL_END = "<!-- END codex-kit:subagent-routing -->";
17
+ const PROJECT_BEGIN = "<!-- BEGIN codex-kit:shared-template -->";
18
+ const PROJECT_END = "<!-- END codex-kit:shared-template -->";
19
+ const STATE_FILE = ".codex-kit-state.json";
20
+ const PROJECT_STATE_FILE = ".codex-kit-state.json";
21
+ const REGISTRY = PACKAGE.publishConfig?.registry ?? "https://registry.npmjs.org";
22
+ const DEFAULT_ORCHESTRATOR = "gpt-5.6-sol";
23
+ const DEFAULT_REASONING_EFFORT = "high";
24
+ const sha256 = (data) => createHash("sha256").update(data).digest("hex");
25
+ const read = (file) => readFileSync(file);
26
+ const readText = (file) => readFileSync(file, "utf8");
27
+ function timestamp() {
28
+ return new Date().toISOString().replace(/[-:TZ.]/g, "");
29
+ }
30
+ function backup(file) {
31
+ if (!existsSync(file))
32
+ return null;
33
+ let destination = `${file}.codex-kit.bak-${timestamp()}`;
34
+ let suffix = 1;
35
+ while (existsSync(destination))
36
+ destination = `${file}.codex-kit.bak-${timestamp()}-${suffix++}`;
37
+ copyFileSync(file, destination);
38
+ console.log(`backup: ${destination}`);
39
+ return destination;
40
+ }
41
+ function write(file, data) {
42
+ mkdirSync(dirname(file), { recursive: true });
43
+ const temporary = `${file}.codex-kit.tmp-${process.pid}`;
44
+ writeFileSync(temporary, data);
45
+ renameSync(temporary, file);
46
+ }
47
+ function loadState(home) {
48
+ const file = join(home, STATE_FILE);
49
+ if (!existsSync(file))
50
+ return { version: PACKAGE.version, files: {} };
51
+ try {
52
+ const state = JSON.parse(readText(file));
53
+ return isRecord(state) && isRecord(state.files)
54
+ ? state
55
+ : { version: PACKAGE.version, files: {} };
56
+ }
57
+ catch {
58
+ throw new Error(`${file} is not valid JSON; move it aside before reinstalling.`);
59
+ }
60
+ }
61
+ function saveState(home, state) {
62
+ write(join(home, STATE_FILE), `${JSON.stringify(state, null, 2)}\n`);
63
+ }
64
+ function topLevelConfigEntries(contents) {
65
+ const entries = new Map();
66
+ let inTable = false;
67
+ for (const line of contents.split("\n")) {
68
+ if (/^\s*\[/.test(line)) {
69
+ inTable = true;
70
+ continue;
71
+ }
72
+ if (inTable)
73
+ continue;
74
+ const match = /^(\s*)(model|model_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(line);
75
+ const key = match?.[2];
76
+ const value = match?.[3];
77
+ if (key && value !== undefined && !entries.has(key))
78
+ entries.set(key, { value, line });
79
+ }
80
+ return entries;
81
+ }
82
+ function tomlString(value) {
83
+ return JSON.stringify(value);
84
+ }
85
+ function setTopLevelConfig(contents, desired) {
86
+ const lines = contents.split("\n");
87
+ const seen = new Set();
88
+ let firstTable = lines.length;
89
+ for (let index = 0; index < lines.length; index++) {
90
+ const line = lines[index];
91
+ if (line !== undefined && /^\s*\[/.test(line)) {
92
+ firstTable = index;
93
+ break;
94
+ }
95
+ }
96
+ for (let index = 0; index < firstTable; index++) {
97
+ const line = lines[index];
98
+ if (line === undefined)
99
+ continue;
100
+ const match = /^(\s*)(model|model_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(line);
101
+ const key = match?.[2];
102
+ if (!match || !key || seen.has(key))
103
+ continue;
104
+ lines[index] = `${match[1]}${key} = ${tomlString(desired[key])}`;
105
+ seen.add(key);
106
+ }
107
+ const missing = Object.keys(desired)
108
+ .filter((key) => !seen.has(key))
109
+ .map((key) => `${key} = ${tomlString(desired[key])}`);
110
+ if (missing.length)
111
+ lines.splice(0, 0, ...missing, "");
112
+ return lines.join("\n");
113
+ }
114
+ function restoreTopLevelConfig(contents, config) {
115
+ const desired = config.desired ?? {};
116
+ const previous = config.previous ?? {};
117
+ const lines = contents.split("\n");
118
+ let firstTable = lines.length;
119
+ for (let index = 0; index < lines.length; index++) {
120
+ const line = lines[index];
121
+ if (line !== undefined && /^\s*\[/.test(line)) {
122
+ firstTable = index;
123
+ break;
124
+ }
125
+ }
126
+ const restored = new Set();
127
+ for (let index = 0; index < firstTable; index++) {
128
+ const line = lines[index];
129
+ if (line === undefined)
130
+ continue;
131
+ const match = /^(\s*)(model|model_reasoning_effort)\s*=\s*(.*?)\s*$/.exec(line);
132
+ const key = match?.[2];
133
+ if (!match || !key || restored.has(key))
134
+ continue;
135
+ if (match[3] !== tomlString(desired[key]))
136
+ continue;
137
+ const prior = previous[key];
138
+ if (prior?.present) {
139
+ lines[index] = `${match[1]}${key} = ${prior.value}`;
140
+ }
141
+ else {
142
+ lines.splice(index, 1);
143
+ index--;
144
+ firstTable--;
145
+ }
146
+ restored.add(key);
147
+ }
148
+ if (Object.values(previous).some((entry) => entry && !entry.present) && lines[0] === "")
149
+ lines.shift();
150
+ return lines.join("\n");
151
+ }
152
+ function loadProjectState(cwd) {
153
+ const file = join(cwd, PROJECT_STATE_FILE);
154
+ if (!existsSync(file))
155
+ return { version: 1, template: {} };
156
+ try {
157
+ const state = JSON.parse(readText(file));
158
+ return isRecord(state) && isRecord(state.template)
159
+ ? state
160
+ : { version: 1, template: {} };
161
+ }
162
+ catch {
163
+ throw new Error(`${file} is not valid JSON; move it aside before syncing.`);
164
+ }
165
+ }
166
+ function saveProjectState(cwd, state) {
167
+ write(join(cwd, PROJECT_STATE_FILE), `${JSON.stringify(state, null, 2)}\n`);
168
+ }
169
+ function templatePrompt() {
170
+ return `Template reference updated. Ask Codex:\n\nThe project's TEMPLATE_AGENTS.md was refreshed from codex-kit. Compare it with\nAGENTS.md and merge only new or changed reusable guidelines that apply to this\nrepository. Preserve project-specific instructions and existing adaptations. Do\nnot replace AGENTS.md wholesale. If a template rule conflicts with a local rule,\nkeep the local rule and report the conflict. Summarize what was added, updated,\nskipped, or adapted, and why. When finished, run codex-kit project mark-applied.`;
171
+ }
172
+ function managedBlock(content, begin, end) {
173
+ return `${begin}\n${content.trimEnd()}\n${end}`;
174
+ }
175
+ function replaceOrAppendBlock(original, content, begin, end) {
176
+ const start = original.indexOf(begin);
177
+ const finish = original.indexOf(end);
178
+ if ((start >= 0) !== (finish >= 0) || (start >= 0 && finish < start)) {
179
+ throw new Error(`Malformed managed block: expected both ${begin} and ${end}.`);
180
+ }
181
+ const block = managedBlock(content, begin, end);
182
+ if (start >= 0)
183
+ return `${original.slice(0, start)}${block}${original.slice(finish + end.length)}`;
184
+ return `${original.trimEnd()}${original.trim() ? "\n\n" : ""}${block}\n`;
185
+ }
186
+ function removeBlock(original, begin, end) {
187
+ const start = original.indexOf(begin);
188
+ const finish = original.indexOf(end);
189
+ if (start < 0 && finish < 0)
190
+ return original;
191
+ if (start < 0 || finish < start)
192
+ throw new Error(`Malformed managed block in AGENTS.md.`);
193
+ const before = original.slice(0, start).trimEnd();
194
+ const after = original.slice(finish + end.length).trimStart();
195
+ return `${before}${before && after ? "\n\n" : ""}${after}${before || after ? "\n" : ""}`;
196
+ }
197
+ function installFile(source, target, key, prior, force) {
198
+ const sourceData = read(source);
199
+ const sourceHash = sha256(sourceData);
200
+ const previous = prior.files[key];
201
+ if (!existsSync(target)) {
202
+ write(target, sourceData);
203
+ console.log(`installed: ${target}`);
204
+ return { target, hash: sourceHash, ownership: "created", backup: null };
205
+ }
206
+ const targetHash = sha256(read(target));
207
+ if (targetHash === sourceHash) {
208
+ console.log(`unchanged: ${target}`);
209
+ return previous ?? { target, hash: sourceHash, ownership: "preexisting", backup: null };
210
+ }
211
+ const safelyOwned = previous &&
212
+ previous.target === target &&
213
+ previous.ownership !== "preexisting" &&
214
+ previous.hash === targetHash;
215
+ if (!safelyOwned && !force) {
216
+ console.warn(`preserved modified or pre-existing file: ${target} (use --force to replace)`);
217
+ return previous ?? null;
218
+ }
219
+ const newBackup = backup(target);
220
+ write(target, sourceData);
221
+ console.log(`updated: ${target}`);
222
+ return {
223
+ target,
224
+ hash: sourceHash,
225
+ ownership: safelyOwned ? previous.ownership : "replaced",
226
+ backup: safelyOwned ? previous.backup : newBackup,
227
+ };
228
+ }
229
+ function installGlobal(options) {
230
+ const home = options.codexHome;
231
+ mkdirSync(home, { recursive: true });
232
+ const prior = loadState(home);
233
+ const next = {
234
+ version: PACKAGE.version,
235
+ files: {},
236
+ globalAgents: null,
237
+ config: prior.config ?? null,
238
+ };
239
+ for (const name of readdirSync(AGENTS_DIR).filter((name) => name.endsWith(".toml")).sort()) {
240
+ const key = `agents/${name}`;
241
+ const record = installFile(join(AGENTS_DIR, name), join(home, "agents", name), key, prior, options.force);
242
+ if (record)
243
+ next.files[key] = record;
244
+ }
245
+ const routingRecord = installFile(ROUTING_FILE, join(home, "SUBAGENT_ROUTING.md"), "routing", prior, options.force);
246
+ if (routingRecord)
247
+ next.files.routing = routingRecord;
248
+ for (const [key, record] of Object.entries(prior.files)) {
249
+ if (key in next.files)
250
+ continue;
251
+ const target = record.target;
252
+ if (!existsSync(target) || sha256(read(target)) !== record.hash) {
253
+ console.warn(`preserved stale modified or missing file: ${target}`);
254
+ next.files[key] = record;
255
+ continue;
256
+ }
257
+ if (record.ownership === "created") {
258
+ rmSync(target);
259
+ console.log(`removed stale: ${target}`);
260
+ }
261
+ else if (record.ownership === "replaced" && record.backup && existsSync(record.backup)) {
262
+ copyFileSync(record.backup, target);
263
+ console.log(`restored stale: ${target}`);
264
+ }
265
+ }
266
+ const globalAgents = join(home, "AGENTS.md");
267
+ const original = existsSync(globalAgents) ? readText(globalAgents) : "";
268
+ const updated = replaceOrAppendBlock(original, readText(ROUTING_FILE), GLOBAL_BEGIN, GLOBAL_END);
269
+ if (updated !== original) {
270
+ backup(globalAgents);
271
+ write(globalAgents, updated);
272
+ console.log(`updated: ${globalAgents}`);
273
+ }
274
+ next.globalAgents = { target: globalAgents };
275
+ saveState(home, next);
276
+ const commitPusher = join(home, "agents", "commit-pusher.toml");
277
+ if (existsSync(commitPusher)) {
278
+ console.warn(`warning: existing unmanaged commit-pusher remains at ${commitPusher}`);
279
+ }
280
+ console.log(`Codex kit ${PACKAGE.version} installed under ${home}`);
281
+ }
282
+ function configureGlobal(options) {
283
+ const home = options.codexHome;
284
+ mkdirSync(home, { recursive: true });
285
+ const configFile = join(home, "config.toml");
286
+ const desired = {
287
+ model: options.orchestrator,
288
+ model_reasoning_effort: options.reasoningEffort,
289
+ };
290
+ const priorState = loadState(home);
291
+ const original = existsSync(configFile) ? readText(configFile) : "";
292
+ const current = topLevelConfigEntries(original);
293
+ const priorConfig = priorState.config;
294
+ if (priorConfig?.target === configFile) {
295
+ const changedByUser = Object.entries(priorConfig.desired).some(([key, value]) => {
296
+ const entry = current.get(key);
297
+ return !entry || entry.value !== tomlString(value);
298
+ });
299
+ if (changedByUser && !options.force) {
300
+ console.warn(`preserved modified config: ${configFile} (use --force to replace)`);
301
+ return;
302
+ }
303
+ }
304
+ const updated = setTopLevelConfig(original, desired);
305
+ if (updated !== original) {
306
+ backup(configFile);
307
+ write(configFile, updated);
308
+ console.log(`configured orchestrator: ${configFile}`);
309
+ }
310
+ else
311
+ console.log(`unchanged: ${configFile}`);
312
+ const previous = priorConfig?.previous ?? {};
313
+ for (const key of Object.keys(desired)) {
314
+ if (key in previous)
315
+ continue;
316
+ const entry = current.get(key);
317
+ previous[key] = entry
318
+ ? { present: true, value: entry.value }
319
+ : { present: false };
320
+ }
321
+ priorState.version = PACKAGE.version;
322
+ priorState.config = {
323
+ target: configFile,
324
+ desired,
325
+ previous,
326
+ };
327
+ saveState(home, priorState);
328
+ console.log(`Orchestrator: ${desired.model}`);
329
+ console.log(`Reasoning effort: ${desired.model_reasoning_effort}`);
330
+ }
331
+ function listGlobal(options) {
332
+ const home = options.codexHome;
333
+ const configFile = join(home, "config.toml");
334
+ const globalAgents = join(home, "AGENTS.md");
335
+ const routingFile = join(home, "SUBAGENT_ROUTING.md");
336
+ const agentsDir = join(home, "agents");
337
+ const stateFile = join(home, STATE_FILE);
338
+ const state = loadState(home);
339
+ const config = existsSync(configFile) ? topLevelConfigEntries(readText(configFile)) : new Map();
340
+ const value = (key) => {
341
+ const raw = config.get(key)?.value;
342
+ if (!raw)
343
+ return "not set";
344
+ try {
345
+ return String(JSON.parse(raw));
346
+ }
347
+ catch {
348
+ return raw;
349
+ }
350
+ };
351
+ const managedTargets = new Set(Object.values(state.files).map((record) => record.target));
352
+ console.log(`Codex home: ${home}`);
353
+ console.log(`Config: ${configFile}${existsSync(configFile) ? "" : " (missing)"}`);
354
+ console.log(`Orchestrator: ${value("model")}`);
355
+ console.log(`Reasoning effort: ${value("model_reasoning_effort")}`);
356
+ console.log(`Kit state: ${existsSync(stateFile) ? state.version : "not installed"}`);
357
+ const hasRoutingBlock = existsSync(globalAgents) && readText(globalAgents).includes(GLOBAL_BEGIN);
358
+ console.log(`Global routing: ${hasRoutingBlock ? "installed" : "not installed"}`);
359
+ console.log(`Routing file: ${existsSync(routingFile) ? routingFile : "missing"}`);
360
+ console.log("Custom agents:");
361
+ const agents = existsSync(agentsDir)
362
+ ? readdirSync(agentsDir).filter((name) => name.endsWith(".toml")).sort()
363
+ : [];
364
+ if (!agents.length)
365
+ return console.log(" (none)");
366
+ for (const filename of agents) {
367
+ const file = join(agentsDir, filename);
368
+ const contents = readText(file);
369
+ const field = (key) => new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, "m").exec(contents)?.[1] ?? "not set";
370
+ const ownership = managedTargets.has(file) ? "managed" : "unmanaged";
371
+ console.log(` ${field("name")} — ${field("model")}, ${field("model_reasoning_effort")} (${ownership})`);
372
+ }
373
+ }
374
+ function uninstallGlobal(options) {
375
+ const home = options.codexHome;
376
+ const statePath = join(home, STATE_FILE);
377
+ if (!existsSync(statePath)) {
378
+ console.log(`No installer state at ${statePath}; nothing removed.`);
379
+ return;
380
+ }
381
+ const state = loadState(home);
382
+ for (const record of Object.values(state.files)) {
383
+ const target = record.target;
384
+ if (!existsSync(target) || sha256(read(target)) !== record.hash) {
385
+ console.warn(`preserved modified or missing file: ${target}`);
386
+ continue;
387
+ }
388
+ if (record.ownership === "created") {
389
+ rmSync(target);
390
+ console.log(`removed: ${target}`);
391
+ }
392
+ else if (record.ownership === "replaced" && record.backup && existsSync(record.backup)) {
393
+ copyFileSync(record.backup, target);
394
+ console.log(`restored: ${target}`);
395
+ }
396
+ else {
397
+ console.log(`preserved pre-existing file: ${target}`);
398
+ }
399
+ }
400
+ const globalAgents = state.globalAgents?.target ?? join(home, "AGENTS.md");
401
+ if (existsSync(globalAgents)) {
402
+ const original = readText(globalAgents);
403
+ const updated = removeBlock(original, GLOBAL_BEGIN, GLOBAL_END);
404
+ if (updated !== original) {
405
+ backup(globalAgents);
406
+ if (updated)
407
+ write(globalAgents, updated);
408
+ else
409
+ rmSync(globalAgents);
410
+ console.log(`removed managed routing from: ${globalAgents}`);
411
+ }
412
+ }
413
+ if (state.config?.target) {
414
+ const configFile = state.config.target;
415
+ if (!existsSync(configFile)) {
416
+ console.warn(`preserved missing config: ${configFile}`);
417
+ }
418
+ else {
419
+ const originalConfig = readText(configFile);
420
+ const current = topLevelConfigEntries(originalConfig);
421
+ const changed = Object.entries(state.config.desired).some(([key, value]) => {
422
+ const entry = current.get(key);
423
+ return !entry || entry.value !== tomlString(value);
424
+ });
425
+ if (changed) {
426
+ console.warn(`preserved modified config: ${configFile}`);
427
+ }
428
+ else {
429
+ const restored = restoreTopLevelConfig(originalConfig, state.config);
430
+ if (restored !== originalConfig) {
431
+ backup(configFile);
432
+ if (restored.trim())
433
+ write(configFile, restored);
434
+ else
435
+ rmSync(configFile);
436
+ console.log(`restored config: ${configFile}`);
437
+ }
438
+ }
439
+ }
440
+ }
441
+ rmSync(statePath);
442
+ console.log(`Codex kit uninstalled from ${home}`);
443
+ }
444
+ function syncProject(options) {
445
+ const cwd = options.cwd;
446
+ if (!existsSync(cwd) || !statSync(cwd).isDirectory())
447
+ throw new Error(`Not a directory: ${cwd}`);
448
+ const agentsFile = join(cwd, "AGENTS.md");
449
+ const stagedTemplate = join(cwd, "TEMPLATE_AGENTS.md");
450
+ const template = readText(TEMPLATE_FILE);
451
+ const desired = Buffer.from(template);
452
+ const sourceHash = sha256(desired);
453
+ const state = loadProjectState(cwd);
454
+ const previousAvailable = state.template.availableHash;
455
+ if (existsSync(stagedTemplate)) {
456
+ const currentHash = sha256(read(stagedTemplate));
457
+ const locallyModified = currentHash !== sourceHash && (!previousAvailable || currentHash !== previousAvailable);
458
+ if (locallyModified && currentHash !== sourceHash && !options.force) {
459
+ console.warn(`preserved locally modified template: ${stagedTemplate} (use --force to replace)`);
460
+ console.log(`The installed kit has template ${PACKAGE.version}; review the local change before syncing.`);
461
+ return;
462
+ }
463
+ if (currentHash === sourceHash)
464
+ console.log(`unchanged: ${stagedTemplate}`);
465
+ else {
466
+ backup(stagedTemplate);
467
+ write(stagedTemplate, desired);
468
+ console.log(`refreshed template reference: ${stagedTemplate}`);
469
+ }
470
+ }
471
+ else {
472
+ write(stagedTemplate, desired);
473
+ console.log(`created template reference: ${stagedTemplate}`);
474
+ }
475
+ state.version = 1;
476
+ state.template = {
477
+ ...state.template,
478
+ availableHash: sourceHash,
479
+ availableVersion: PACKAGE.version,
480
+ };
481
+ saveProjectState(cwd, state);
482
+ if (!existsSync(agentsFile)) {
483
+ const contents = "# Project-Specific Instructions\n\n<!-- Add repository-specific commands, architecture, and exceptions here. -->\n";
484
+ write(agentsFile, contents);
485
+ console.log(`created project instructions file: ${agentsFile}`);
486
+ }
487
+ else if (readText(agentsFile).includes(PROJECT_BEGIN) || readText(agentsFile).includes(PROJECT_END)) {
488
+ console.warn(`preserved legacy managed template in: ${agentsFile}`);
489
+ console.warn("Ask Codex to migrate it to semantic template reconciliation before applying updates.");
490
+ }
491
+ console.log(templatePrompt());
492
+ }
493
+ function projectStatus(options) {
494
+ const cwd = options.cwd;
495
+ if (!existsSync(cwd) || !statSync(cwd).isDirectory())
496
+ throw new Error(`Not a directory: ${cwd}`);
497
+ const stagedTemplate = join(cwd, "TEMPLATE_AGENTS.md");
498
+ const agentsFile = join(cwd, "AGENTS.md");
499
+ const state = loadProjectState(cwd);
500
+ const availableHash = state.template.availableHash ?? null;
501
+ const appliedHash = state.template.appliedHash ?? null;
502
+ const sourceHash = sha256(read(TEMPLATE_FILE));
503
+ const localHash = existsSync(stagedTemplate) ? sha256(read(stagedTemplate)) : null;
504
+ console.log(`Project: ${cwd}`);
505
+ if (!localHash)
506
+ return console.log("Status: not initialized (run codex-kit project sync)");
507
+ if (!existsSync(agentsFile))
508
+ return console.log("Status: AGENTS.md missing (reconcile the template first)");
509
+ console.log(`Available: ${state.template.availableVersion ?? "unknown"} (${availableHash ?? "untracked"})`);
510
+ console.log(`Applied: ${appliedHash ?? "never"}`);
511
+ if (sourceHash !== availableHash)
512
+ return console.log("Status: kit template update available; run project sync");
513
+ if (localHash !== availableHash)
514
+ return console.log("Status: local template changed; review it before syncing");
515
+ if (appliedHash !== localHash)
516
+ return console.log("Status: reconciliation required");
517
+ console.log("Status: up to date");
518
+ }
519
+ function markApplied(options) {
520
+ const cwd = options.cwd;
521
+ if (!existsSync(cwd) || !statSync(cwd).isDirectory())
522
+ throw new Error(`Not a directory: ${cwd}`);
523
+ const stagedTemplate = join(cwd, "TEMPLATE_AGENTS.md");
524
+ const agentsFile = join(cwd, "AGENTS.md");
525
+ if (!existsSync(stagedTemplate))
526
+ throw new Error(`Missing ${stagedTemplate}; run project sync first.`);
527
+ if (!existsSync(agentsFile))
528
+ throw new Error(`Missing ${agentsFile}; reconcile the template into AGENTS.md first.`);
529
+ const state = loadProjectState(cwd);
530
+ const appliedHash = sha256(read(stagedTemplate));
531
+ state.version = 1;
532
+ state.template = {
533
+ ...state.template,
534
+ appliedHash,
535
+ appliedAt: new Date().toISOString(),
536
+ };
537
+ saveProjectState(cwd, state);
538
+ console.log(`recorded template reconciliation: ${stagedTemplate}`);
539
+ }
540
+ function compareVersions(left, right) {
541
+ const parseVersion = (value) => {
542
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(value);
543
+ if (!match)
544
+ throw new Error(`Invalid package version: ${value}`);
545
+ return {
546
+ numbers: [Number(match[1]), Number(match[2]), Number(match[3])],
547
+ prerelease: match[4] ?? null,
548
+ };
549
+ };
550
+ const a = parseVersion(left);
551
+ const b = parseVersion(right);
552
+ for (let index = 0; index < 3; index++) {
553
+ const leftNumber = a.numbers[index];
554
+ const rightNumber = b.numbers[index];
555
+ if (leftNumber !== rightNumber)
556
+ return Math.sign(leftNumber - rightNumber);
557
+ }
558
+ if (a.prerelease === b.prerelease)
559
+ return 0;
560
+ if (!a.prerelease)
561
+ return 1;
562
+ if (!b.prerelease)
563
+ return -1;
564
+ return Math.sign(a.prerelease.localeCompare(b.prerelease, "en", { numeric: true }));
565
+ }
566
+ function checkVersion() {
567
+ let latest = process.env.CODEX_KIT_LATEST_VERSION;
568
+ if (!latest) {
569
+ const executable = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
570
+ const result = spawnSync(executable, ["view", PACKAGE.name, "version", "--json", `--registry=${REGISTRY}`], { encoding: "utf8", timeout: 15_000 });
571
+ if (result.error)
572
+ throw new Error(`Unable to run pnpm: ${result.error.message}`);
573
+ if (result.status !== 0) {
574
+ const detail = result.stderr.trim() || "pnpm view failed";
575
+ throw new Error(`Unable to check ${REGISTRY}: ${detail}`);
576
+ }
577
+ try {
578
+ const value = JSON.parse(result.stdout);
579
+ latest = Array.isArray(value) && typeof value.at(-1) === "string"
580
+ ? value.at(-1)
581
+ : typeof value === "string"
582
+ ? value
583
+ : undefined;
584
+ }
585
+ catch {
586
+ latest = result.stdout.trim();
587
+ }
588
+ }
589
+ if (typeof latest !== "string" || !latest)
590
+ throw new Error("Registry returned no package version.");
591
+ console.log(`Installed: ${PACKAGE.version}`);
592
+ console.log(`Latest: ${latest}`);
593
+ const comparison = compareVersions(PACKAGE.version, latest);
594
+ if (comparison === 0)
595
+ return console.log("codex-kit is up to date.");
596
+ if (comparison > 0)
597
+ return console.log("This local build is newer than the published package.");
598
+ console.log(`Update available. Run:
599
+ pnpm add --global ${PACKAGE.name}@latest
600
+ codex-kit global install`);
601
+ }
602
+ function parse(argv) {
603
+ const options = {
604
+ cwd: process.cwd(),
605
+ codexHome: resolve(process.env.CODEX_HOME || join(homedir(), ".codex")),
606
+ orchestrator: DEFAULT_ORCHESTRATOR,
607
+ reasoningEffort: DEFAULT_REASONING_EFFORT,
608
+ force: false,
609
+ positionals: [],
610
+ };
611
+ for (let index = 0; index < argv.length; index++) {
612
+ const arg = argv[index];
613
+ if (arg === undefined)
614
+ continue;
615
+ if (arg === "--force")
616
+ options.force = true;
617
+ else if (arg === "--cwd" || arg === "--codex-home") {
618
+ const value = argv[++index];
619
+ if (!value)
620
+ throw new Error(`${arg} requires a path.`);
621
+ if (arg === "--cwd")
622
+ options.cwd = resolve(value);
623
+ else
624
+ options.codexHome = resolve(value);
625
+ }
626
+ else if (arg === "--orchestrator" || arg === "--model") {
627
+ const value = argv[++index];
628
+ if (!value)
629
+ throw new Error(`${arg} requires a model.`);
630
+ options.orchestrator = value;
631
+ }
632
+ else if (arg === "--reasoning-effort") {
633
+ const value = argv[++index];
634
+ if (!value)
635
+ throw new Error(`${arg} requires a value.`);
636
+ options.reasoningEffort = value;
637
+ }
638
+ else
639
+ options.positionals.push(arg);
640
+ }
641
+ return options;
642
+ }
643
+ function help() {
644
+ console.log(`codex-kit ${PACKAGE.version}
645
+
646
+ Usage:
647
+ codex-kit global install [--codex-home PATH] [--force]
648
+ codex-kit global configure [--orchestrator MODEL] [--reasoning-effort LEVEL]
649
+ codex-kit global list [--codex-home PATH]
650
+ codex-kit global uninstall [--codex-home PATH]
651
+ codex-kit project init [--cwd PATH]
652
+ codex-kit project sync [--cwd PATH]
653
+ codex-kit project status [--cwd PATH]
654
+ codex-kit project mark-applied [--cwd PATH]
655
+ codex-kit version check
656
+ codex-kit --version
657
+
658
+ Global configure defaults to gpt-5.6-sol with high reasoning and edits only
659
+ the top-level model settings in CODEX_HOME/config.toml.
660
+ Global install manages custom agents and a marked routing block under CODEX_HOME.
661
+ Project sync refreshes TEMPLATE_AGENTS.md and never merges it into AGENTS.md.`);
662
+ }
663
+ export function main(argv = process.argv.slice(2)) {
664
+ const options = parse(argv);
665
+ if (options.positionals.includes("--version"))
666
+ return console.log(PACKAGE.version);
667
+ if (!options.positionals.length || options.positionals.includes("--help"))
668
+ return help();
669
+ const [scope, action] = options.positionals;
670
+ if (scope === "global" && action === "install")
671
+ return installGlobal(options);
672
+ if (scope === "global" && action === "configure")
673
+ return configureGlobal(options);
674
+ if (scope === "global" && action === "list")
675
+ return listGlobal(options);
676
+ if (scope === "global" && action === "uninstall")
677
+ return uninstallGlobal(options);
678
+ if (scope === "project" && (action === "init" || action === "sync"))
679
+ return syncProject(options);
680
+ if (scope === "project" && action === "status")
681
+ return projectStatus(options);
682
+ if (scope === "project" && action === "mark-applied")
683
+ return markApplied(options);
684
+ if (scope === "version" && action === "check")
685
+ return checkVersion();
686
+ throw new Error(`Unknown command: ${options.positionals.join(" ")}`);
687
+ }
688
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
689
+ try {
690
+ main();
691
+ }
692
+ catch (error) {
693
+ console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
694
+ process.exitCode = 1;
695
+ }
696
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@iamdevlinph/codex-kit",
3
+ "version": "1.0.1",
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
+ "bin"
14
+ ],
15
+ "scripts": {
16
+ "build": "pnpm exec tsc -p tsconfig.json",
17
+ "typecheck": "pnpm exec tsc -p tsconfig.json --noEmit && pnpm exec tsc -p tsconfig.test.json --noEmit",
18
+ "test": "pnpm run build && pnpm exec tsc -p tsconfig.test.json && node --test .test-dist/test/*.test.js",
19
+ "prepack": "pnpm run build",
20
+ "pack:check": "pnpm pack --dry-run"
21
+ },
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/iamdevlinph/codex-kit.git"
28
+ },
29
+ "publishConfig": {
30
+ "registry": "https://registry.npmjs.org",
31
+ "access": "public"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "20.19.43",
35
+ "typescript": "7.0.1-rc"
36
+ },
37
+ "packageManager": "pnpm@11.5.2",
38
+ "license": "UNLICENSED"
39
+ }