@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 +178 -0
- package/assets/SUBAGENT_ROUTING.md +36 -0
- package/assets/TEMPLATE_AGENTS.md +143 -0
- package/assets/agents/code-explorer.toml +31 -0
- package/assets/agents/code-reviewer.toml +25 -0
- package/assets/agents/implementer.toml +24 -0
- package/assets/agents/quick-implementer.toml +27 -0
- package/bin/codex-kit.js +696 -0
- package/package.json +39 -0
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
|
+
"""
|
package/bin/codex-kit.js
ADDED
|
@@ -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
|
+
}
|