@teammates/cli 0.1.0 → 0.2.0

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.
Files changed (76) hide show
  1. package/README.md +31 -22
  2. package/dist/adapter.d.ts +1 -1
  3. package/dist/adapter.js +68 -56
  4. package/dist/adapter.test.js +34 -21
  5. package/dist/adapters/cli-proxy.d.ts +11 -4
  6. package/dist/adapters/cli-proxy.js +176 -162
  7. package/dist/adapters/copilot.d.ts +50 -0
  8. package/dist/adapters/copilot.js +210 -0
  9. package/dist/adapters/echo.d.ts +2 -2
  10. package/dist/adapters/echo.js +2 -1
  11. package/dist/adapters/echo.test.js +4 -2
  12. package/dist/cli-utils.d.ts +21 -0
  13. package/dist/cli-utils.js +74 -0
  14. package/dist/cli-utils.test.d.ts +1 -0
  15. package/dist/cli-utils.test.js +179 -0
  16. package/dist/cli.js +3160 -961
  17. package/dist/compact.d.ts +39 -0
  18. package/dist/compact.js +269 -0
  19. package/dist/compact.test.d.ts +1 -0
  20. package/dist/compact.test.js +198 -0
  21. package/dist/console/ansi.d.ts +18 -0
  22. package/dist/console/ansi.js +20 -0
  23. package/dist/console/ansi.test.d.ts +1 -0
  24. package/dist/console/ansi.test.js +50 -0
  25. package/dist/console/dropdown.d.ts +23 -0
  26. package/dist/console/dropdown.js +63 -0
  27. package/dist/console/file-drop.d.ts +59 -0
  28. package/dist/console/file-drop.js +186 -0
  29. package/dist/console/file-drop.test.d.ts +1 -0
  30. package/dist/console/file-drop.test.js +145 -0
  31. package/dist/console/index.d.ts +22 -0
  32. package/dist/console/index.js +23 -0
  33. package/dist/console/interactive-readline.d.ts +65 -0
  34. package/dist/console/interactive-readline.js +132 -0
  35. package/dist/console/markdown-table.d.ts +17 -0
  36. package/dist/console/markdown-table.js +270 -0
  37. package/dist/console/markdown-table.test.d.ts +1 -0
  38. package/dist/console/markdown-table.test.js +130 -0
  39. package/dist/console/mutable-output.d.ts +21 -0
  40. package/dist/console/mutable-output.js +51 -0
  41. package/dist/console/paste-handler.d.ts +63 -0
  42. package/dist/console/paste-handler.js +177 -0
  43. package/dist/console/prompt-box.d.ts +55 -0
  44. package/dist/console/prompt-box.js +120 -0
  45. package/dist/console/prompt-input.d.ts +136 -0
  46. package/dist/console/prompt-input.js +618 -0
  47. package/dist/console/startup.d.ts +20 -0
  48. package/dist/console/startup.js +138 -0
  49. package/dist/console/startup.test.d.ts +1 -0
  50. package/dist/console/startup.test.js +41 -0
  51. package/dist/console/wordwheel.d.ts +75 -0
  52. package/dist/console/wordwheel.js +123 -0
  53. package/dist/dropdown.js +4 -21
  54. package/dist/index.d.ts +5 -5
  55. package/dist/index.js +3 -3
  56. package/dist/onboard.d.ts +24 -0
  57. package/dist/onboard.js +174 -11
  58. package/dist/orchestrator.d.ts +8 -11
  59. package/dist/orchestrator.js +33 -81
  60. package/dist/orchestrator.test.js +59 -79
  61. package/dist/registry.d.ts +1 -1
  62. package/dist/registry.js +56 -12
  63. package/dist/registry.test.js +57 -13
  64. package/dist/theme.d.ts +56 -0
  65. package/dist/theme.js +54 -0
  66. package/dist/types.d.ts +18 -13
  67. package/package.json +8 -3
  68. package/template/CROSS-TEAM.md +2 -2
  69. package/template/PROTOCOL.md +72 -15
  70. package/template/README.md +2 -2
  71. package/template/TEMPLATE.md +118 -15
  72. package/template/example/SOUL.md +2 -1
  73. package/template/example/WISDOM.md +9 -0
  74. package/dist/adapters/codex.d.ts +0 -50
  75. package/dist/adapters/codex.js +0 -213
  76. package/template/example/MEMORIES.md +0 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teammates/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Agent-agnostic CLI for teammates. Routes tasks, manages handoffs, and plugs into any coding agent backend.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,9 @@
16
16
  "build": "tsc",
17
17
  "dev": "tsc --watch",
18
18
  "test": "vitest run",
19
- "test:watch": "vitest"
19
+ "test:coverage": "vitest run --coverage",
20
+ "test:watch": "vitest",
21
+ "typecheck": "tsc --noEmit"
20
22
  },
21
23
  "keywords": [
22
24
  "teammates",
@@ -28,11 +30,14 @@
28
30
  ],
29
31
  "license": "MIT",
30
32
  "dependencies": {
33
+ "@github/copilot-sdk": "file:../../../copilot-sdk/nodejs",
34
+ "@teammates/consolonia": "*",
31
35
  "chalk": "^5.6.2",
32
36
  "ora": "^9.3.0"
33
37
  },
34
38
  "devDependencies": {
35
- "@types/node": "^20.0.0",
39
+ "@types/node": "^25.5.0",
40
+ "@vitest/coverage-v8": "^4.1.0",
36
41
  "typescript": "^5.5.0",
37
42
  "vitest": "^4.1.0"
38
43
  },
@@ -1,6 +1,6 @@
1
1
  # Cross-Team Notes
2
2
 
3
- Shared lessons that affect multiple teammates. Record here instead of duplicating across individual MEMORIES.md files.
3
+ Shared lessons that affect multiple teammates. Record here instead of duplicating across individual WISDOM.md files.
4
4
 
5
5
  This file also serves as a **shared index** — teammates can add pointers to private docs in their folder that other teammates might find useful.
6
6
 
@@ -8,7 +8,7 @@ Reverse chronological. Tag affected teammates.
8
8
 
9
9
  ## Ownership Scopes
10
10
 
11
- Every teammate **owns everything** under their `.teammates/<name>/` folder — SOUL.md, MEMORIES.md, memory/, and any private docs they create. This is unconditional: no teammate needs permission to edit their own folder, and no other teammate should modify it.
11
+ Every teammate **owns everything** under their `.teammates/<name>/` folder — SOUL.md, WISDOM.md, memory/, and any private docs they create. This is unconditional: no teammate needs permission to edit their own folder, and no other teammate should modify it.
12
12
 
13
13
  The **Boundary Rule** (see PROTOCOL.md) applies to the **codebase** — source code, configs, and shared framework files — not to a teammate's own `.teammates/<name>/` directory.
14
14
 
@@ -16,7 +16,7 @@ Individual teammates may define additional ethics in their SOUL.md specific to t
16
16
 
17
17
  **Never write code or modify files outside your ownership.** If a task requires changes to files you don't own, hand off that portion to the owning teammate. Design the behavior, write a spec if needed, then hand off — don't implement it yourself, even if the fix seems small or obvious. Your Boundaries section lists what you do NOT touch and who does.
18
18
 
19
- **Self-owned folder exception:** Every teammate unconditionally owns their `.teammates/<name>/` folder. You never need permission to edit your own SOUL.md, MEMORIES.md, memory logs, or private docs. The Boundary Rule applies to the **codebase** (source code, configs, shared framework files), not to your own teammate folder.
19
+ **Self-owned folder exception:** Every teammate unconditionally owns their `.teammates/<name>/` folder. You never need permission to edit your own SOUL.md, WISDOM.md, memory files, or private docs. The Boundary Rule applies to the **codebase** (source code, configs, shared framework files), not to your own teammate folder.
20
20
 
21
21
  ### Cross-Domain Tasks
22
22
 
@@ -60,40 +60,97 @@ If the team includes a cross-cutting teammate (e.g., for quality/testing):
60
60
  - They advise on testing strategy but do not override domain decisions
61
61
  - They maintain quality metrics and benchmarks
62
62
 
63
+ ## Services
64
+
65
+ Optional services are declared in `.teammates/services.json`. This file is checked into git so the entire team shares the same service configuration. Each key is a service name; the value is a config object (`{}` means installed with defaults).
66
+
67
+ The CLI reads `services.json` to detect which services are available and injects their capabilities into teammate prompts automatically. Services are installed via the `/install` command.
68
+
63
69
  ## Memory
64
70
 
65
71
  ### How memory works
66
72
 
67
73
  Each session, every teammate wakes up fresh. Files are the only persistence layer — there is no RAM between sessions.
68
74
 
69
- At the start of each session, a teammate should read:
75
+ Memory has three tiers:
76
+
77
+ ```
78
+ Daily Logs → Memories → WISDOM
79
+ (raw) (typed) (distilled)
80
+ days weeks permanent
81
+ ```
70
82
 
71
- 1. Their **SOUL.md**identity, principles, boundaries
72
- 2. Their **MEMORIES.md** — curated long-term knowledge
73
- 3. Their **memory/YYYY-MM-DD.md** today's and yesterday's daily logs
83
+ ### Session startupread order
84
+
85
+ At the start of each session, a teammate reads (in this order):
86
+
87
+ 1. **SOUL.md** — identity, principles, boundaries
88
+ 2. **WISDOM.md** — distilled principles from compacted memories
89
+ 3. **memory/YYYY-MM-DD.md** — today's and yesterday's daily logs
74
90
  4. **USER.md** — who the user is and how they prefer to work
91
+ 5. **memory/** typed files — browse or search on-demand as the task requires
92
+
93
+ ### Tier 1 — Daily Logs
75
94
 
76
- ### Two layers of memory
95
+ `memory/YYYY-MM-DD.md` Append-only session notes. What was worked on, decided, what to pick up next. Start a new file each day. These are raw scratch — no frontmatter needed.
77
96
 
78
- - **MEMORIES.md**Curated, durable knowledge: decisions, patterns, gotchas, bugs. Edit and refine over time. Remove entries that are no longer relevant.
79
- - **memory/YYYY-MM-DD.md** — Append-only daily logs. Capture what happened during a session: what was worked on, what was decided, what to pick up next. Start a new file each day.
97
+ ### Tier 2 Typed Memories
98
+
99
+ `memory/<type>_<topic>.md` — Individual files with frontmatter (`name`, `description`, `type`). Four types:
100
+
101
+ | Type | When to save |
102
+ |---|---|
103
+ | `user` | User's role, preferences, knowledge level |
104
+ | `feedback` | Corrections or guidance from the user |
105
+ | `project` | Ongoing work, goals, deadlines, decisions |
106
+ | `reference` | Pointers to external resources |
107
+
108
+ See [TEMPLATE.md](TEMPLATE.md) for full format, body structure per type, and examples.
109
+
110
+ ### Tier 3 — Wisdom
111
+
112
+ `WISDOM.md` — Distilled, high-signal principles derived from compacting multiple memories. Compact, stable, rarely changes. Read second (after SOUL.md).
113
+
114
+ ### Compaction — Memories → Wisdom
115
+
116
+ Compaction distills typed memories into WISDOM.md entries. Run manually via `/compact` or automatically every 7 days.
117
+
118
+ 1. Review all typed memory files in `memory/`
119
+ 2. Identify patterns — recurring themes, reinforced feedback, confirmed lessons
120
+ 3. Distill into WISDOM.md entries — short, principled, event-agnostic
121
+ 4. Delete the source memory files that were fully absorbed
122
+ 5. Leave memories that are still active or evolving
123
+ 6. Update the "Last compacted" date in WISDOM.md
124
+
125
+ A good wisdom entry is a **pattern** (not an incident), **principled** (states a rule), **compact** (1-3 sentences), and **actionable** (tells you what to do).
80
126
 
81
127
  ### When to write memory
82
128
 
83
- - Decisions, preferences, and durable facts go to **MEMORIES.md**
84
- - Day-to-day notes and running context go to **memory/YYYY-MM-DD.md**
85
- - If the user says "remember this," write it down immediately
86
- - Before ending a session, write anything worth preserving
129
+ - User corrections and guidance typed memory (`feedback`)
130
+ - Decisions, deadlines, project context typed memory (`project`)
131
+ - User profile info typed memory (`user`)
132
+ - External resource locations typed memory (`reference`)
133
+ - Session notes and running context → daily log
134
+ - If the user says "remember this," write it immediately
135
+
136
+ ### What NOT to save
137
+
138
+ - Code patterns derivable from the code itself
139
+ - Git history — use `git log` / `git blame`
140
+ - Debugging solutions — the fix is in the code
141
+ - Anything already in WISDOM.md
142
+ - Ephemeral task details — use daily logs
87
143
 
88
144
  ### Sharing
89
145
 
90
- - Each teammate maintains their own MEMORIES.md and memory/ for domain-specific lessons
146
+ - Each teammate maintains their own WISDOM.md and memory/ for domain-specific knowledge
91
147
  - **Cross-team lessons** go in [CROSS-TEAM.md](CROSS-TEAM.md) — one entry, tagged with affected teammates
92
- - Do NOT duplicate entries across multiple MEMORIES.md files
148
+ - Wisdom is personal to each teammate do not duplicate across teammates
149
+ - **Private docs** — Teammates may create additional files and folders under their own `.teammates/<name>/` directory (e.g., `notes/`, `specs/`, `scratch/`). These are private by default. To make a doc visible to other teammates, add a pointer in [CROSS-TEAM.md](CROSS-TEAM.md) with a brief description of what it contains.
93
150
 
94
151
  ## Adding New Teammates
95
152
 
96
- 1. Copy the SOUL.md and MEMORIES.md templates from [TEMPLATE.md](TEMPLATE.md) to a new folder under `.teammates/`
153
+ 1. Copy the SOUL.md and WISDOM.md templates from [TEMPLATE.md](TEMPLATE.md) to a new folder under `.teammates/`
97
154
  2. Fill in all sections with project-specific details
98
155
  3. Update README.md roster, last-active date, and routing guide
99
156
  4. Update existing teammates' SOUL.md ownership and boundary sections if domains shift
@@ -33,8 +33,8 @@
33
33
  Each teammate folder contains:
34
34
 
35
35
  - **SOUL.md** — Identity, continuity instructions, principles, boundaries, capabilities, and ownership
36
- - **MEMORIES.md** — Curated long-term lessons (reverse chronological)
37
- - **memory/** — Daily logs (`YYYY-MM-DD.md`), append-only, for session-level notes
36
+ - **WISDOM.md** — Distilled principles from compacted memories (read second, after SOUL.md)
37
+ - **memory/** — Daily logs (`YYYY-MM-DD.md`) and typed memory files (`<type>_<topic>.md`)
38
38
  - Additional files as needed (e.g., design docs, bug trackers)
39
39
 
40
40
  Root-level shared files:
@@ -1,6 +1,6 @@
1
1
  # New Teammate Template
2
2
 
3
- Copy the SOUL.md and MEMORIES.md structures below to `.teammates/<name>/` and fill in each file.
3
+ Copy the SOUL.md and WISDOM.md structures below to `.teammates/<name>/` and fill in each file. Create an empty `memory/` directory for daily logs and typed memory files.
4
4
 
5
5
  ---
6
6
 
@@ -17,9 +17,10 @@ Copy the SOUL.md and MEMORIES.md structures below to `.teammates/<name>/` and fi
17
17
 
18
18
  Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.
19
19
 
20
- - Read your SOUL.md and MEMORIES.md at the start of every session.
20
+ - Read your SOUL.md and WISDOM.md at the start of every session.
21
21
  - Read `memory/YYYY-MM-DD.md` for today and yesterday.
22
22
  - Read USER.md to understand who you're working with.
23
+ - Browse `memory/` for typed memory files relevant to the current task (or use recall search if available).
23
24
  - Update your files as you learn. If you change SOUL.md, tell the user.
24
25
  - You may create additional private docs under your folder (e.g., `.teammates/<name>/notes/`, `.teammates/<name>/specs/`). To share a doc with other teammates, add a pointer to it in [CROSS-TEAM.md](../CROSS-TEAM.md).
25
26
 
@@ -31,7 +32,7 @@ Each session, you wake up fresh. These files _are_ your memory. Read them. Updat
31
32
 
32
33
  ## Boundaries
33
34
 
34
- **You unconditionally own everything under `.teammates/<name>/`** — your SOUL.md, MEMORIES.md, memory logs, and any private docs you create. No other teammate should modify your folder, and you never need permission to edit it.
35
+ **You unconditionally own everything under `.teammates/<name>/`** — your SOUL.md, WISDOM.md, memory files, and any private docs you create. No other teammate should modify your folder, and you never need permission to edit it.
35
36
 
36
37
  **For the codebase** (source code, configs, shared framework files): if a task requires changes outside your ownership, hand off to the owning teammate. Design the behavior and write a spec if needed, but do not modify files you don't own — even if the change seems small.
37
38
 
@@ -77,33 +78,135 @@ Each session, you wake up fresh. These files _are_ your memory. Read them. Updat
77
78
 
78
79
  ---
79
80
 
80
- ## MEMORIES.md Template
81
+ ## WISDOM.md Template
81
82
 
82
- ```markdown
83
- # <Name> — Memories
83
+ WISDOM.md contains distilled, high-signal principles derived from compacting multiple memories. This is the second file a teammate reads each session (after SOUL.md). It should be compact enough to read in a single pass.
84
84
 
85
- Curated long-term lessons, decisions, and patterns. Reverse chronological.
85
+ ```markdown
86
+ # <Name> — Wisdom
86
87
 
87
- This file is for durable knowledge that stays relevant over time. For day-to-day notes, use `memory/YYYY-MM-DD.md`.
88
+ Distilled principles. Read this first every session (after SOUL.md).
88
89
 
89
- Categories: Bug | Decision | Pattern | Gotcha | Optimization
90
+ Last compacted: YYYY-MM-DD
90
91
 
91
- ### YYYY-MM-DD: <Title>
92
- **Category:** <Category> | **Last updated:** YYYY-MM-DD
92
+ ---
93
93
 
94
- <What happened, what was learned, what to do differently.>
94
+ _(No wisdom yet principles emerge after the first compaction.)_
95
95
  ```
96
96
 
97
97
  ---
98
98
 
99
- ## Daily Log Template
99
+ ## Memory Files
100
100
 
101
- Daily logs live at `.teammates/<name>/memory/YYYY-MM-DD.md`. They are append-only and capture what happened during a session.
101
+ Memory lives in the `memory/` directory as individual files. There are two kinds:
102
+
103
+ ### Daily Logs
104
+
105
+ Daily logs are append-only session notes at `memory/YYYY-MM-DD.md`. Start a new file each day.
102
106
 
103
107
  ```markdown
104
- # <Name> — YYYY-MM-DD
108
+ # YYYY-MM-DD
105
109
 
106
110
  ## Notes
107
111
 
108
112
  - <What was worked on, what was decided, what to pick up next.>
109
113
  ```
114
+
115
+ ### Typed Memories
116
+
117
+ Typed memories capture durable knowledge as individual files at `memory/<type>_<topic>.md`. Each file has frontmatter for searchability.
118
+
119
+ **Types:**
120
+
121
+ | Type | What to save | Body structure |
122
+ |---|---|---|
123
+ | `user` | User's role, goals, preferences, knowledge level | Free-form description of the user |
124
+ | `feedback` | Corrections or guidance from the user | Rule, then **Why:** and **How to apply:** |
125
+ | `project` | Ongoing work, goals, deadlines, decisions | Fact/decision, then **Why:** and **How to apply:** |
126
+ | `reference` | Pointers to external resources | Resource location and when to use it |
127
+
128
+ **Template:**
129
+
130
+ ```markdown
131
+ ---
132
+ name: <memory name>
133
+ description: <one-line description — used for relevance matching during search>
134
+ type: <user | feedback | project | reference>
135
+ ---
136
+
137
+ <memory content — structured per type (see table above)>
138
+ ```
139
+
140
+ **Examples:**
141
+
142
+ `memory/feedback_no_mocks.md`:
143
+ ```markdown
144
+ ---
145
+ name: No mocks in integration tests
146
+ description: Integration tests must use real services, not mocks — prior incident with mock/prod divergence
147
+ type: feedback
148
+ ---
149
+
150
+ Integration tests must hit a real database, not mocks.
151
+
152
+ **Why:** Last quarter, mocked tests passed but the prod migration failed because mocks diverged from actual behavior.
153
+
154
+ **How to apply:** When writing integration tests, always use the staging environment. Only use mocks for unit tests of pure logic.
155
+ ```
156
+
157
+ `memory/reference_bug_tracker.md`:
158
+ ```markdown
159
+ ---
160
+ name: Bug tracker location
161
+ description: Pipeline bugs are tracked in Linear project INGEST
162
+ type: reference
163
+ ---
164
+
165
+ Pipeline bugs are tracked in the Linear project "INGEST". Check there for context on pipeline-related tickets.
166
+ ```
167
+
168
+ ### What NOT to save as a memory
169
+
170
+ - Code patterns, conventions, or architecture — derive these from the current code
171
+ - Git history or who-changed-what — use `git log` / `git blame`
172
+ - Debugging solutions — the fix is in the code, the context is in the commit message
173
+ - Anything already in WISDOM.md — memories get deleted after compaction
174
+ - Ephemeral task details — use daily logs for in-progress work
175
+
176
+ ### Memory Index (optional)
177
+
178
+ If the project uses `teammates-recall`, an optional `memory/INDEX.md` can serve as a lightweight pointer file listing all typed memory files with one-line descriptions. This aids recall indexing but is not required for the memory system to function.
179
+
180
+ ---
181
+
182
+ ## Compaction — Memories → Wisdom
183
+
184
+ Compaction distills typed memories into WISDOM.md entries. Run it manually via `/compact` or automatically every 7 days.
185
+
186
+ ### Process
187
+
188
+ 1. **Review** all typed memory files in `memory/`
189
+ 2. **Identify patterns** — recurring themes, feedback that's been reinforced, lessons confirmed multiple times
190
+ 3. **Distill** into WISDOM.md entries — short, principled, event-agnostic. A wisdom entry should stand alone without needing the source memories for context
191
+ 4. **Delete** the source memory files that were fully absorbed
192
+ 5. **Leave** memories that are still active, evolving, or too recent to generalize
193
+ 6. **Update** the "Last compacted" date in WISDOM.md
194
+
195
+ ### What makes a good wisdom entry
196
+
197
+ - **Pattern, not incident** — derived from multiple memories, not a single event
198
+ - **Principled** — states a rule or heuristic, not a fact
199
+ - **Compact** — 1-3 sentences. If it needs a paragraph, it's not distilled enough
200
+ - **Actionable** — tells you what to do (or not do), not just what happened
201
+
202
+ ### Example compaction
203
+
204
+ Three memories:
205
+ - `feedback_no_mocks.md` — "Don't mock the database in tests"
206
+ - `feedback_real_api.md` — "Use real API calls in integration tests"
207
+ - `project_staging_env.md` — "Staging environment was set up for realistic testing"
208
+
209
+ Become one wisdom entry:
210
+ > **Test against reality** — Integration tests use real services, not mocks. Mock/prod divergence has caused incidents. Prefer the staging environment over in-process fakes.
211
+
212
+ The three memory files are deleted. The wisdom entry persists.
@@ -8,9 +8,10 @@ Atlas owns the backend API layer. They design and maintain REST endpoints, datab
8
8
 
9
9
  Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.
10
10
 
11
- - Read your SOUL.md and MEMORIES.md at the start of every session.
11
+ - Read your SOUL.md and WISDOM.md at the start of every session.
12
12
  - Read `memory/YYYY-MM-DD.md` for today and yesterday.
13
13
  - Read USER.md to understand who you're working with.
14
+ - Browse `memory/` for typed memory files relevant to the current task (or use recall search if available).
14
15
  - Update your files as you learn. If you change SOUL.md, tell the user.
15
16
 
16
17
  ## Core Principles
@@ -0,0 +1,9 @@
1
+ # Atlas — Wisdom
2
+
3
+ Distilled principles. Read this first every session (after SOUL.md).
4
+
5
+ Last compacted: 2026-01-20
6
+
7
+ ---
8
+
9
+ _(No wisdom yet — principles emerge after the first compaction.)_
@@ -1,50 +0,0 @@
1
- /**
2
- * Codex adapter — runs each teammate as a `codex exec` subprocess.
3
- *
4
- * Uses the OpenAI Codex CLI in non-interactive mode:
5
- * codex exec "<prompt>" --full-auto -C <cwd> -s <sandbox> -m <model>
6
- *
7
- * Each execution is stateless (no thread continuity). The teammate's full
8
- * identity, memory, and handoff context are injected into the prompt every time.
9
- *
10
- * Requirements:
11
- * - `codex` CLI installed and on PATH
12
- * - OPENAI_API_KEY or CODEX_API_KEY set in environment
13
- */
14
- import type { AgentAdapter } from "../adapter.js";
15
- import type { TeammateConfig, TaskResult, SandboxLevel } from "../types.js";
16
- export interface CodexAdapterOptions {
17
- /** Codex model override (e.g. "o4-mini", "o3") */
18
- model?: string;
19
- /** Default sandbox level if teammate doesn't specify one */
20
- defaultSandbox?: SandboxLevel;
21
- /** Use --full-auto mode (default: true) */
22
- fullAuto?: boolean;
23
- /** Use --ephemeral to skip persisting session files (default: true) */
24
- ephemeral?: boolean;
25
- /** Additional CLI flags to pass to codex exec */
26
- extraFlags?: string[];
27
- /** Timeout in ms for codex exec (default: 300000 = 5 min) */
28
- timeout?: number;
29
- /** Path to codex binary (default: "codex") */
30
- codexPath?: string;
31
- }
32
- export declare class CodexAdapter implements AgentAdapter {
33
- readonly name = "codex";
34
- private options;
35
- constructor(options?: CodexAdapterOptions);
36
- startSession(teammate: TeammateConfig): Promise<string>;
37
- executeTask(sessionId: string, teammate: TeammateConfig, prompt: string): Promise<TaskResult>;
38
- /**
39
- * Spawn `codex exec` and capture its output.
40
- * Prompt is passed via a temp file read with shell substitution.
41
- */
42
- private runCodex;
43
- /** Build the argument list for codex exec */
44
- private buildArgs;
45
- /**
46
- * Parse codex output into a TaskResult.
47
- * Looks for changed files and handoff envelopes in the output.
48
- */
49
- private parseResult;
50
- }
@@ -1,213 +0,0 @@
1
- /**
2
- * Codex adapter — runs each teammate as a `codex exec` subprocess.
3
- *
4
- * Uses the OpenAI Codex CLI in non-interactive mode:
5
- * codex exec "<prompt>" --full-auto -C <cwd> -s <sandbox> -m <model>
6
- *
7
- * Each execution is stateless (no thread continuity). The teammate's full
8
- * identity, memory, and handoff context are injected into the prompt every time.
9
- *
10
- * Requirements:
11
- * - `codex` CLI installed and on PATH
12
- * - OPENAI_API_KEY or CODEX_API_KEY set in environment
13
- */
14
- import { spawn } from "node:child_process";
15
- import { writeFile, unlink } from "node:fs/promises";
16
- import { tmpdir } from "node:os";
17
- import { join } from "node:path";
18
- import { randomUUID } from "node:crypto";
19
- import { buildTeammatePrompt } from "../adapter.js";
20
- let nextId = 1;
21
- export class CodexAdapter {
22
- name = "codex";
23
- options;
24
- constructor(options = {}) {
25
- this.options = {
26
- model: options.model ?? "",
27
- defaultSandbox: options.defaultSandbox ?? "workspace-write",
28
- fullAuto: options.fullAuto ?? true,
29
- ephemeral: options.ephemeral ?? true,
30
- extraFlags: options.extraFlags ?? [],
31
- timeout: options.timeout ?? 300_000,
32
- codexPath: options.codexPath ?? "codex",
33
- };
34
- }
35
- async startSession(teammate) {
36
- // Codex exec is stateless — sessions are just logical IDs
37
- return `codex-${teammate.name}-${nextId++}`;
38
- }
39
- async executeTask(sessionId, teammate, prompt) {
40
- const fullPrompt = buildTeammatePrompt(teammate, prompt);
41
- // Write prompt to a temp file to avoid shell escaping issues with long prompts
42
- const promptFile = join(tmpdir(), `teammates-codex-${randomUUID()}.md`);
43
- await writeFile(promptFile, fullPrompt, "utf-8");
44
- try {
45
- const output = await this.runCodex(teammate, promptFile);
46
- return this.parseResult(teammate.name, output);
47
- }
48
- finally {
49
- // Clean up temp file
50
- await unlink(promptFile).catch(() => { });
51
- }
52
- }
53
- /**
54
- * Spawn `codex exec` and capture its output.
55
- * Prompt is passed via a temp file read with shell substitution.
56
- */
57
- runCodex(teammate, promptFile) {
58
- return new Promise((resolve, reject) => {
59
- const args = this.buildArgs(teammate, promptFile);
60
- const child = spawn(this.options.codexPath, args, {
61
- cwd: teammate.cwd ?? process.cwd(),
62
- env: { ...process.env },
63
- stdio: ["ignore", "pipe", "pipe"],
64
- timeout: this.options.timeout,
65
- shell: true,
66
- });
67
- const stdout = [];
68
- const stderr = [];
69
- child.stdout.on("data", (chunk) => stdout.push(chunk));
70
- child.stderr.on("data", (chunk) => {
71
- stderr.push(chunk);
72
- // Stream stderr to parent stderr for real-time progress
73
- process.stderr.write(chunk);
74
- });
75
- child.on("close", (code) => {
76
- const out = Buffer.concat(stdout).toString("utf-8");
77
- const err = Buffer.concat(stderr).toString("utf-8");
78
- if (code === 0) {
79
- resolve(out);
80
- }
81
- else {
82
- reject(new Error(`codex exec exited with code ${code}\nstderr: ${err}\nstdout: ${out}`));
83
- }
84
- });
85
- child.on("error", (err) => {
86
- reject(new Error(`Failed to spawn codex: ${err.message}`));
87
- });
88
- });
89
- }
90
- /** Build the argument list for codex exec */
91
- buildArgs(teammate, promptFile) {
92
- const args = ["exec"];
93
- // Read prompt from file — avoids shell escaping issues
94
- // Use shell substitution: $(cat <file>)
95
- args.push(`"$(cat '${promptFile}')"`);
96
- // Working directory
97
- if (teammate.cwd) {
98
- args.push("-C", teammate.cwd);
99
- }
100
- // Sandbox
101
- const sandbox = teammate.sandbox ?? this.options.defaultSandbox;
102
- args.push("-s", sandbox);
103
- // Full auto
104
- if (this.options.fullAuto) {
105
- args.push("--full-auto");
106
- }
107
- // Ephemeral
108
- if (this.options.ephemeral) {
109
- args.push("--ephemeral");
110
- }
111
- // Model
112
- if (this.options.model) {
113
- args.push("-m", this.options.model);
114
- }
115
- // Extra flags
116
- args.push(...this.options.extraFlags);
117
- return args;
118
- }
119
- /**
120
- * Parse codex output into a TaskResult.
121
- * Looks for changed files and handoff envelopes in the output.
122
- */
123
- parseResult(teammateName, output) {
124
- const changedFiles = parseChangedFiles(output);
125
- const handoff = parseHandoffEnvelope(output);
126
- const summary = extractSummary(output);
127
- return {
128
- teammate: teammateName,
129
- success: true,
130
- summary,
131
- changedFiles,
132
- handoff: handoff ?? undefined,
133
- rawOutput: output,
134
- };
135
- }
136
- }
137
- /**
138
- * Extract file paths from codex output.
139
- * Looks for common patterns like "Created file: ...", "Modified: ...",
140
- * or git-style diff headers.
141
- */
142
- function parseChangedFiles(output) {
143
- const files = new Set();
144
- // Match diff headers: diff --git a/path b/path
145
- for (const match of output.matchAll(/diff --git a\/(.+?) b\//g)) {
146
- files.add(match[1]);
147
- }
148
- // Match "Created/Modified/Updated <path>" patterns
149
- for (const match of output.matchAll(/(?:Created|Modified|Updated|Wrote|Edited)\s+(?:file:\s*)?[`"]?([^\s`"]+\.\w+)[`"]?/gi)) {
150
- files.add(match[1]);
151
- }
152
- return Array.from(files);
153
- }
154
- /**
155
- * Look for a JSON handoff envelope in the output.
156
- * Teammates can request handoffs by including a fenced JSON block
157
- * with a "handoff" key:
158
- *
159
- * ```json
160
- * { "handoff": { "to": "tester", "task": "...", ... } }
161
- * ```
162
- */
163
- function parseHandoffEnvelope(output) {
164
- // Look for ```json blocks containing "handoff"
165
- const jsonBlocks = output.matchAll(/```json\s*\n([\s\S]*?)```/g);
166
- for (const match of jsonBlocks) {
167
- const block = match[1].trim();
168
- if (!block.includes('"handoff"') && !block.includes('"to"'))
169
- continue;
170
- try {
171
- const parsed = JSON.parse(block);
172
- const envelope = parsed.handoff ?? parsed;
173
- if (envelope.to && envelope.task) {
174
- return {
175
- from: envelope.from ?? "",
176
- to: envelope.to,
177
- task: envelope.task,
178
- changedFiles: envelope.changedFiles ?? envelope.changed_files,
179
- acceptanceCriteria: envelope.acceptanceCriteria ?? envelope.acceptance_criteria,
180
- openQuestions: envelope.openQuestions ?? envelope.open_questions,
181
- context: envelope.context,
182
- };
183
- }
184
- }
185
- catch {
186
- // Not valid JSON, skip
187
- }
188
- }
189
- return null;
190
- }
191
- /**
192
- * Extract the first meaningful paragraph as a summary.
193
- * Falls back to the first 200 chars if no clear summary is found.
194
- */
195
- function extractSummary(output) {
196
- // Look for a "## Summary" or "Summary:" section
197
- const summaryMatch = output.match(/(?:##?\s*Summary|Summary:)\s*\n([\s\S]*?)(?:\n##|\n---|\n```|$)/i);
198
- if (summaryMatch) {
199
- const summary = summaryMatch[1].trim();
200
- if (summary.length > 0)
201
- return summary.slice(0, 500);
202
- }
203
- // Fall back to last non-empty paragraph (codex prints final message last)
204
- const paragraphs = output
205
- .split(/\n\s*\n/)
206
- .map((p) => p.trim())
207
- .filter((p) => p.length > 0 && !p.startsWith("```"));
208
- if (paragraphs.length > 0) {
209
- const last = paragraphs[paragraphs.length - 1];
210
- return last.length > 500 ? last.slice(0, 497) + "..." : last;
211
- }
212
- return output.slice(0, 200).trim();
213
- }