@maestrofrontier/frontier 1.4.0 → 1.4.2
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/.cursorrules +194 -0
- package/GEMINI.md +42 -0
- package/README.md +56 -299
- package/docs/codex.md +98 -0
- package/integrations/README.md +16 -9
- package/integrations/codex/skills/frontier/SKILL.md +91 -0
- package/integrations/codex/skills/settings/SKILL.md +46 -0
- package/integrations/codex/skills/terse/SKILL.md +49 -0
- package/integrations/codex/skills/update/SKILL.md +29 -0
- package/package.json +4 -1
- package/scripts/install.cjs +175 -60
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontier
|
|
3
|
+
description: Maestro Frontier local multi-CLI fusion engine — switch mode, or run a prompt through the panel
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Drive the **Maestro Frontier** engine — a zero-dependency local multi-CLI fusion
|
|
7
|
+
engine (a parallel panel of local CLIs → a judge model's analysis → a grounded
|
|
8
|
+
synthesis). It is the same engine the Claude Code plugin ships; here it runs
|
|
9
|
+
through the `maestro` CLI with `--scope codex`.
|
|
10
|
+
|
|
11
|
+
**This is a typing shortcut, not a prompt hook.** Codex has no automatic
|
|
12
|
+
prompt hook, so arming a mode does **not** auto-run the engine on later prompts —
|
|
13
|
+
it only persists the mode. To actually fuse a prompt, invoke `run` explicitly
|
|
14
|
+
(step 3).
|
|
15
|
+
|
|
16
|
+
Map the user's request to one engine CLI call and run it from the repo root.
|
|
17
|
+
Do not edit the engine's state file by hand.
|
|
18
|
+
|
|
19
|
+
## 1. Switch mode
|
|
20
|
+
|
|
21
|
+
Persists to `~/.config/maestro/frontier-state.codex.json`; default `off`.
|
|
22
|
+
`--scope codex` keeps Codex's armed mode independent from Claude Code, Cline,
|
|
23
|
+
Cursor, and Gemini on the same machine:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
maestro frontier mode off --scope codex
|
|
27
|
+
maestro frontier mode single --model <model> --scope codex
|
|
28
|
+
maestro frontier mode fusion --preset <preset> --scope codex
|
|
29
|
+
maestro frontier mode fusion --preset custom --models <a,b,c> --scope codex
|
|
30
|
+
maestro frontier mode fusion --preset <preset> --judge <model> --synth <model> --scope codex
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Models: `opus` (Claude Opus 4.8, needs `claude`), `gpt-5.5` (needs `codex`),
|
|
34
|
+
`gemini` (needs `gemini`). Presets: `opus-duo`, `opus-gpt`, `gpt-duo`,
|
|
35
|
+
`frontier-trio`, `custom`. Judge + synth default to Opus; `--judge`/`--synth`
|
|
36
|
+
override for any preset (e.g. `--judge opus --synth gpt-5.5`). `gpt-duo` runs
|
|
37
|
+
judge + synth on GPT-5.5 — a Codex-only fusion that needs no `claude`.
|
|
38
|
+
|
|
39
|
+
## 2. Show current mode/preset
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
maestro frontier status --scope codex
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 3. Run a prompt through the current mode
|
|
46
|
+
|
|
47
|
+
This is the action that actually fuses, since nothing auto-runs here. Set a
|
|
48
|
+
mode first (step 1), then:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
maestro frontier run "<prompt>" --scope codex
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `off`: prints a notice, spawns nothing.
|
|
55
|
+
- `single`: dispatches the one selected CLI, prints its answer.
|
|
56
|
+
- `fusion`: runs the panel in parallel → judge → synthesizer; prints the final
|
|
57
|
+
answer (a one-line run meta goes to stderr). Report stdout verbatim.
|
|
58
|
+
|
|
59
|
+
On error the engine prints `ERROR [<reason>]: <detail>` to stderr and exits
|
|
60
|
+
non-zero — relay the reason.
|
|
61
|
+
|
|
62
|
+
## Indicator instruction
|
|
63
|
+
|
|
64
|
+
<!-- INDICATOR INSTRUCTION — apply every turn while this skill is active -->
|
|
65
|
+
|
|
66
|
+
Before composing your first reply each turn, run:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
maestro frontier status --scope codex
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Parse the returned JSON `mode` field:
|
|
73
|
+
|
|
74
|
+
- If `mode` is not `off`, make the **first line** of your reply exactly:
|
|
75
|
+
`Maestro Frontier ON (<label>)`
|
|
76
|
+
where `<label>` is formed as follows:
|
|
77
|
+
- single mode → `single · <model>` (e.g. `single · opus`)
|
|
78
|
+
- fusion mode → `fusion · <preset>` (e.g. `fusion · frontier-trio`);
|
|
79
|
+
for a custom preset use `fusion · custom (<model1>, <model2>, ...)`
|
|
80
|
+
- If `mode` is `off`, output no indicator line.
|
|
81
|
+
|
|
82
|
+
<!-- END INDICATOR INSTRUCTION -->
|
|
83
|
+
|
|
84
|
+
## Notes
|
|
85
|
+
|
|
86
|
+
- Real `single`/`fusion` runs spawn local CLIs and cost tokens; use small prompts.
|
|
87
|
+
`off` is free.
|
|
88
|
+
- Each model's CLI must be on `PATH`, or point at a specific build with
|
|
89
|
+
`MAESTRO_CLAUDE_BIN` / `MAESTRO_CODEX_BIN` / `MAESTRO_GEMINI_BIN`.
|
|
90
|
+
- Requires `maestro` on `PATH` (installed during Maestro setup). If it is
|
|
91
|
+
missing, install Maestro first.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: settings
|
|
3
|
+
description: View and change Maestro toggles (terse, frontier, context-bar) via the settings CLI
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
View or change **Maestro settings** for this project. The settings CLI manages
|
|
7
|
+
the three primary toggles: `terse`, `frontier`, and `context-bar`.
|
|
8
|
+
|
|
9
|
+
When the user invokes this skill, run the settings CLI from the repo root.
|
|
10
|
+
Do not edit settings files by hand.
|
|
11
|
+
|
|
12
|
+
## Discover available commands
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
node settings/cli.cjs --help
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If `settings/cli.cjs` is not present, run `maestro --help` to locate the
|
|
19
|
+
correct entry point.
|
|
20
|
+
|
|
21
|
+
## Common operations
|
|
22
|
+
|
|
23
|
+
List current settings:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node settings/cli.cjs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Set a toggle:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
node settings/cli.cjs terse <off|lite|full|ultra>
|
|
33
|
+
node settings/cli.cjs frontier <off|single|fusion>
|
|
34
|
+
node settings/cli.cjs context-bar <on|off>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If a subcommand name or argument differs from the above, follow the usage
|
|
38
|
+
printed by `--help` — do not guess flags.
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
|
|
42
|
+
- Changes persist in Maestro's settings store and apply to subsequent agent
|
|
43
|
+
turns in this project.
|
|
44
|
+
- Requires `node` on `PATH` and Maestro installed in the project root. If
|
|
45
|
+
`settings/cli.cjs` is missing, re-run the installer:
|
|
46
|
+
`npx github:mbanderas/maestro install --target codex`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: terse
|
|
3
|
+
description: Toggle Maestro terse output level (lite, full, ultra, off) via the settings CLI
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Toggle the **Maestro terse** output level for this environment. Terse mode
|
|
7
|
+
condenses agent replies; levels range from `off` (default verbosity) through
|
|
8
|
+
`lite`, `full`, and `ultra` (most compressed).
|
|
9
|
+
|
|
10
|
+
When the user invokes this skill, run the settings CLI to read or change the
|
|
11
|
+
terse level. Do not edit settings files by hand.
|
|
12
|
+
|
|
13
|
+
## Check current terse level
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node settings/cli.cjs --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Consult the help output for the exact read subcommand, then run it. If
|
|
20
|
+
`settings/cli.cjs` is not present, run `maestro --help` to discover the
|
|
21
|
+
correct path.
|
|
22
|
+
|
|
23
|
+
## Set terse level
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
node settings/cli.cjs terse <level>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Valid levels: `off` | `lite` | `full` | `ultra`
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
node settings/cli.cjs terse off
|
|
35
|
+
node settings/cli.cjs terse lite
|
|
36
|
+
node settings/cli.cjs terse full
|
|
37
|
+
node settings/cli.cjs terse ultra
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If the CLI rejects an argument or the subcommand name differs, run
|
|
41
|
+
`node settings/cli.cjs --help` first and follow the printed usage.
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
45
|
+
- The change persists in Maestro's settings store; it applies to subsequent
|
|
46
|
+
agent turns in this project.
|
|
47
|
+
- Requires `node` on `PATH` and Maestro installed in the project root. If
|
|
48
|
+
`settings/cli.cjs` is missing, re-run the Maestro installer:
|
|
49
|
+
`npx github:mbanderas/maestro install --target codex`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: update
|
|
3
|
+
description: Update Maestro to the latest version by re-running the installer for Codex
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Update **Maestro** to the latest marketplace code. This re-runs the installer,
|
|
7
|
+
which pulls the current release and overwrites the local Maestro files in place.
|
|
8
|
+
|
|
9
|
+
When the user invokes this skill, run the installer from the repo root:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx github:mbanderas/maestro install --target codex
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The installer is idempotent — it is safe to re-run against an existing
|
|
16
|
+
installation. It will:
|
|
17
|
+
|
|
18
|
+
- Pull the latest Maestro source from the repository.
|
|
19
|
+
- Overwrite skills, hooks, and settings scaffolding with the new versions.
|
|
20
|
+
- Leave project-local configuration (state files, secrets) untouched.
|
|
21
|
+
|
|
22
|
+
## Notes
|
|
23
|
+
|
|
24
|
+
- Requires `node` and `npx` on `PATH`.
|
|
25
|
+
- Run from the project root so the installer targets the correct directory.
|
|
26
|
+
- After the installer completes, restart the Codex session (or reload the
|
|
27
|
+
project) so updated skills and hooks take effect.
|
|
28
|
+
- If `npx` is unavailable, clone `https://github.com/mbanderas/maestro`
|
|
29
|
+
manually and follow the repository's install instructions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maestrofrontier/frontier",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "Achieve Frontier AI performance in your CLI by fusing the model CLIs you already run. Maestro Frontier is an opt-in, zero-dependency local multi-CLI fusion engine for AI coding agents: fan a prompt across a panel of any 1 to 8 local model CLIs you pick, have a judge model and a synthesizer you choose read the answers into a structured analysis and write one grounded synthesis (default Opus 4.8, override either with --judge/--synth). On a 100-task benchmark every fusion panel outscored its individual member models. Three adapters ship today: Opus 4.8, GPT-5.5, Gemini 3.1 Pro, with Kimi, DeepSeek, GLM, and Qwen to follow. Off, single, and fusion modes switch via /maestro:frontier. Built on Maestro orchestration discipline: decision-gated routing, verified done-claims, surgical scope, and structural enforcement hooks.",
|
|
5
5
|
"keywords": ["multi-cli-fusion", "fusion-engine", "frontier", "multi-agent", "orchestration", "claude-code", "gemini", "codex", "agents", "hooks", "doctrine"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
"scripts/install.cjs",
|
|
28
28
|
"AGENTS.md",
|
|
29
29
|
"CLAUDE.md",
|
|
30
|
+
"GEMINI.md",
|
|
31
|
+
".cursorrules",
|
|
30
32
|
"docs/orchestration.md",
|
|
33
|
+
"docs/codex.md",
|
|
31
34
|
"commands/",
|
|
32
35
|
"integrations/",
|
|
33
36
|
"hooks/frontier-autorun.cjs",
|
package/scripts/install.cjs
CHANGED
|
@@ -49,6 +49,20 @@ const WRAPPER_MAP = {
|
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
// Codex skill templates installed alongside the deprecated codex wrapper.
|
|
53
|
+
// Codex loads skills from <project>/.agents/skills/<name>/SKILL.md (project)
|
|
54
|
+
// or ~/.agents/skills/<name>/SKILL.md (global). No-clobber, like wrappers.
|
|
55
|
+
const CODEX_SKILLS = ['frontier', 'terse', 'settings', 'update'];
|
|
56
|
+
|
|
57
|
+
// Runtime adapter per target. The adapter imports @AGENTS.md (Cursor has no
|
|
58
|
+
// imports, so .cursorrules embeds the kernel). codex/cline/windsurf read
|
|
59
|
+
// AGENTS.md directly and need no adapter.
|
|
60
|
+
const ADAPTER_MAP = {
|
|
61
|
+
claude: 'CLAUDE.md',
|
|
62
|
+
gemini: 'GEMINI.md',
|
|
63
|
+
cursor: '.cursorrules',
|
|
64
|
+
};
|
|
65
|
+
|
|
52
66
|
// Marker dirs used for auto-detection (scanned inside project root)
|
|
53
67
|
const AUTO_MARKERS = [
|
|
54
68
|
{ dir: '.cursor', target: 'cursor' },
|
|
@@ -177,48 +191,53 @@ function detectTarget(projectRoot) {
|
|
|
177
191
|
// ---- install actions ----
|
|
178
192
|
|
|
179
193
|
/**
|
|
180
|
-
*
|
|
181
|
-
* @param {string}
|
|
182
|
-
* @param {boolean} dryRun
|
|
194
|
+
* Read a file from the package root. Returns string, or null (logs) on error.
|
|
195
|
+
* @param {string} rel
|
|
183
196
|
* @param {(msg: string) => void} log
|
|
184
|
-
* @returns {
|
|
197
|
+
* @returns {string|null}
|
|
185
198
|
*/
|
|
186
|
-
function
|
|
187
|
-
const dest = path.join(projectRoot, 'AGENTS.md');
|
|
188
|
-
const srcPath = path.join(PKG_ROOT, 'AGENTS.md');
|
|
189
|
-
|
|
190
|
-
let srcContent;
|
|
199
|
+
function readPkgFile(rel, log) {
|
|
191
200
|
try {
|
|
192
|
-
|
|
201
|
+
return fs.readFileSync(path.join(PKG_ROOT, rel), 'utf8');
|
|
193
202
|
} catch (err) {
|
|
194
|
-
log(`ERROR: cannot read package
|
|
195
|
-
return
|
|
203
|
+
log(`ERROR: cannot read package ${rel}: ${err.message}`);
|
|
204
|
+
return null;
|
|
196
205
|
}
|
|
206
|
+
}
|
|
197
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Install a doctrine/adapter markdown file. Append-only, idempotent, never
|
|
210
|
+
* clobbers user content above the maestro block; refuses symlinks.
|
|
211
|
+
* @param {string} dest absolute destination path
|
|
212
|
+
* @param {string} srcContent content to install
|
|
213
|
+
* @param {string} label short name for logs (e.g. "AGENTS.md")
|
|
214
|
+
* @param {boolean} dryRun
|
|
215
|
+
* @param {(msg: string) => void} log
|
|
216
|
+
* @returns {boolean} true = success (or no-op), false = error
|
|
217
|
+
*/
|
|
218
|
+
function appendOnlyDoctrine(dest, srcContent, label, dryRun, log) {
|
|
198
219
|
const block = `\n${SENTINEL}\n${srcContent}\n${SENTINEL_END}\n`;
|
|
199
220
|
|
|
200
|
-
// Check if dest exists
|
|
201
221
|
let existsStat;
|
|
202
222
|
try { existsStat = fs.lstatSync(dest); } catch { existsStat = null; }
|
|
203
223
|
|
|
204
224
|
if (existsStat) {
|
|
205
225
|
if (existsStat.isSymbolicLink()) {
|
|
206
|
-
log(`ERROR:
|
|
226
|
+
log(`ERROR: ${label} is a symlink — refusing to write through it: ${dest}`);
|
|
207
227
|
return false;
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
let existing;
|
|
211
231
|
try { existing = fs.readFileSync(dest, 'utf8'); } catch (err) {
|
|
212
|
-
log(`ERROR: cannot read existing
|
|
232
|
+
log(`ERROR: cannot read existing ${label}: ${err.message}`);
|
|
213
233
|
return false;
|
|
214
234
|
}
|
|
215
235
|
|
|
216
236
|
if (existing.includes(SENTINEL)) {
|
|
217
|
-
log(`[doctrine]
|
|
237
|
+
log(`[doctrine] ${label} already contains sentinel — skipping`);
|
|
218
238
|
return true;
|
|
219
239
|
}
|
|
220
240
|
|
|
221
|
-
// Append block
|
|
222
241
|
if (dryRun) {
|
|
223
242
|
log(`[dry-run] would append maestro doctrine to existing ${dest}`);
|
|
224
243
|
return true;
|
|
@@ -226,14 +245,14 @@ function installDoctrine(projectRoot, dryRun, log) {
|
|
|
226
245
|
|
|
227
246
|
const res = safeWrite(dest, existing + block);
|
|
228
247
|
if (!res.ok) {
|
|
229
|
-
log(`ERROR: failed to append to
|
|
248
|
+
log(`ERROR: failed to append to ${label}: ${res.reason}`);
|
|
230
249
|
return false;
|
|
231
250
|
}
|
|
232
|
-
log(`[doctrine] appended maestro block to existing
|
|
251
|
+
log(`[doctrine] appended maestro block to existing ${label}`);
|
|
233
252
|
return true;
|
|
234
253
|
}
|
|
235
254
|
|
|
236
|
-
// Absent — write fresh
|
|
255
|
+
// Absent — write fresh, wrapped in the sentinel so re-runs detect it.
|
|
237
256
|
if (dryRun) {
|
|
238
257
|
log(`[dry-run] would create ${dest}`);
|
|
239
258
|
return true;
|
|
@@ -247,13 +266,43 @@ function installDoctrine(projectRoot, dryRun, log) {
|
|
|
247
266
|
const freshContent = SENTINEL + '\n' + srcContent + '\n' + SENTINEL_END + '\n';
|
|
248
267
|
const res = safeWrite(dest, freshContent);
|
|
249
268
|
if (!res.ok) {
|
|
250
|
-
log(`ERROR: failed to write
|
|
269
|
+
log(`ERROR: failed to write ${label}: ${res.reason}`);
|
|
251
270
|
return false;
|
|
252
271
|
}
|
|
253
|
-
log(`[doctrine] wrote
|
|
272
|
+
log(`[doctrine] wrote ${label}`);
|
|
254
273
|
return true;
|
|
255
274
|
}
|
|
256
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Install the portable doctrine core (AGENTS.md) into the project root.
|
|
278
|
+
* @param {string} projectRoot
|
|
279
|
+
* @param {boolean} dryRun
|
|
280
|
+
* @param {(msg: string) => void} log
|
|
281
|
+
* @returns {boolean}
|
|
282
|
+
*/
|
|
283
|
+
function installDoctrine(projectRoot, dryRun, log) {
|
|
284
|
+
const src = readPkgFile('AGENTS.md', log);
|
|
285
|
+
if (src === null) return false;
|
|
286
|
+
return appendOnlyDoctrine(path.join(projectRoot, 'AGENTS.md'), src, 'AGENTS.md', dryRun, log);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Install the runtime adapter for a target (CLAUDE.md / GEMINI.md /
|
|
291
|
+
* .cursorrules). codex/cline/windsurf read AGENTS.md directly -> no-op.
|
|
292
|
+
* @param {string} target
|
|
293
|
+
* @param {string} projectRoot
|
|
294
|
+
* @param {boolean} dryRun
|
|
295
|
+
* @param {(msg: string) => void} log
|
|
296
|
+
* @returns {boolean}
|
|
297
|
+
*/
|
|
298
|
+
function installAdapter(target, projectRoot, dryRun, log) {
|
|
299
|
+
const rel = ADAPTER_MAP[target];
|
|
300
|
+
if (!rel) return true; // no adapter for this target
|
|
301
|
+
const src = readPkgFile(rel, log);
|
|
302
|
+
if (src === null) return false;
|
|
303
|
+
return appendOnlyDoctrine(path.join(projectRoot, rel), src, rel, dryRun, log);
|
|
304
|
+
}
|
|
305
|
+
|
|
257
306
|
/**
|
|
258
307
|
* Recursively copy srcDir -> destDir, skipping *.test.cjs files.
|
|
259
308
|
* @param {string} srcDir
|
|
@@ -345,9 +394,81 @@ function installEngine(projectRoot, dryRun, log) {
|
|
|
345
394
|
}
|
|
346
395
|
}
|
|
347
396
|
|
|
397
|
+
// docs/orchestration.md — the on-demand S2-S6 multi-agent protocol the
|
|
398
|
+
// kernel references. Maestro-owned reference file; copy (refuse symlinks).
|
|
399
|
+
const srcDocs = path.join(PKG_ROOT, 'docs', 'orchestration.md');
|
|
400
|
+
const destDocs = path.join(projectRoot, 'docs', 'orchestration.md');
|
|
401
|
+
if (dryRun) {
|
|
402
|
+
log(`[dry-run] would write ${destDocs}`);
|
|
403
|
+
} else if (isSymlink(destDocs)) {
|
|
404
|
+
log(`ERROR: docs/orchestration.md is a symlink — refusing: ${destDocs}`);
|
|
405
|
+
ok = false;
|
|
406
|
+
} else {
|
|
407
|
+
try {
|
|
408
|
+
fs.mkdirSync(path.dirname(destDocs), { recursive: true });
|
|
409
|
+
fs.writeFileSync(destDocs, fs.readFileSync(srcDocs));
|
|
410
|
+
log(`[doctrine] copied ${destDocs}`);
|
|
411
|
+
} catch (err) {
|
|
412
|
+
log(`ERROR: failed to copy docs/orchestration.md: ${err.message}`);
|
|
413
|
+
ok = false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
348
417
|
return ok;
|
|
349
418
|
}
|
|
350
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Copy a single package template file to dest, no-clobber. Skips when dest
|
|
422
|
+
* already exists, refuses symlinks, honors dry-run. Reuses safeMkdirp +
|
|
423
|
+
* safeWrite. Shared by wrapper and Codex-skill installs.
|
|
424
|
+
* @param {string} src absolute source path (under PKG_ROOT)
|
|
425
|
+
* @param {string} dest absolute destination path
|
|
426
|
+
* @param {string} label short tag for logs (e.g. "wrapper", "codex-skill")
|
|
427
|
+
* @param {boolean} dryRun
|
|
428
|
+
* @param {(msg: string) => void} log
|
|
429
|
+
* @returns {boolean} true = success (wrote, skipped, or planned), false = error
|
|
430
|
+
*/
|
|
431
|
+
function installNoClobberFile(src, dest, label, dryRun, log) {
|
|
432
|
+
// Check if dest exists already (no-clobber)
|
|
433
|
+
let destStat;
|
|
434
|
+
try { destStat = fs.lstatSync(dest); } catch { destStat = null; }
|
|
435
|
+
|
|
436
|
+
if (destStat) {
|
|
437
|
+
if (destStat.isSymbolicLink()) {
|
|
438
|
+
log(`ERROR: ${label} dest is a symlink — refusing: ${dest}`);
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
log(`[${label}] skipped (exists, not clobbered): ${dest}`);
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let srcContent;
|
|
446
|
+
try {
|
|
447
|
+
srcContent = fs.readFileSync(src, 'utf8');
|
|
448
|
+
} catch (err) {
|
|
449
|
+
log(`ERROR: cannot read template ${src}: ${err.message}`);
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (dryRun) {
|
|
454
|
+
log(`[dry-run] would create ${dest}`);
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (!safeMkdirp(dest)) {
|
|
459
|
+
log(`ERROR: could not create parent dir for ${dest}`);
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const res = safeWrite(dest, srcContent);
|
|
464
|
+
if (!res.ok) {
|
|
465
|
+
log(`ERROR: failed to write ${label} ${dest}: ${res.reason}`);
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
log(`[${label}] wrote ${dest}`);
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
|
|
351
472
|
/**
|
|
352
473
|
* Install wrapper file (no-clobber).
|
|
353
474
|
* @param {string} target
|
|
@@ -385,44 +506,31 @@ function installWrapper(target, projectRoot, userGlobal, dryRun, log) {
|
|
|
385
506
|
dest = path.join(projectRoot, mapping.proj);
|
|
386
507
|
}
|
|
387
508
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
try { destStat = fs.lstatSync(dest); } catch { destStat = null; }
|
|
391
|
-
|
|
392
|
-
if (destStat) {
|
|
393
|
-
if (destStat.isSymbolicLink()) {
|
|
394
|
-
log(`ERROR: wrapper dest is a symlink — refusing: ${dest}`);
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
log(`[wrapper] skipped (exists, not clobbered): ${dest}`);
|
|
398
|
-
return true;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
let srcContent;
|
|
402
|
-
try {
|
|
403
|
-
srcContent = fs.readFileSync(src, 'utf8');
|
|
404
|
-
} catch (err) {
|
|
405
|
-
log(`ERROR: cannot read template ${src}: ${err.message}`);
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (dryRun) {
|
|
410
|
-
log(`[dry-run] would create ${dest}`);
|
|
411
|
-
return true;
|
|
412
|
-
}
|
|
509
|
+
return installNoClobberFile(src, dest, 'wrapper', dryRun, log);
|
|
510
|
+
}
|
|
413
511
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
512
|
+
/**
|
|
513
|
+
* Install the Codex skill templates (no-clobber) alongside the codex wrapper.
|
|
514
|
+
* Project mode -> <project>/.agents/skills/<name>/SKILL.md; --user/global mode
|
|
515
|
+
* -> ~/.agents/skills/<name>/SKILL.md (mirrors installWrapper's dest logic).
|
|
516
|
+
* @param {string} projectRoot
|
|
517
|
+
* @param {boolean} userGlobal
|
|
518
|
+
* @param {boolean} dryRun
|
|
519
|
+
* @param {(msg: string) => void} log
|
|
520
|
+
* @returns {boolean}
|
|
521
|
+
*/
|
|
522
|
+
function installCodexSkills(projectRoot, userGlobal, dryRun, log) {
|
|
523
|
+
const skillsRoot = userGlobal
|
|
524
|
+
? path.join(os.homedir(), '.agents', 'skills')
|
|
525
|
+
: path.join(projectRoot, '.agents', 'skills');
|
|
418
526
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
527
|
+
let ok = true;
|
|
528
|
+
for (const name of CODEX_SKILLS) {
|
|
529
|
+
const src = path.join(PKG_ROOT, 'integrations', 'codex', 'skills', name, 'SKILL.md');
|
|
530
|
+
const dest = path.join(skillsRoot, name, 'SKILL.md');
|
|
531
|
+
if (!installNoClobberFile(src, dest, 'codex-skill', dryRun, log)) ok = false;
|
|
423
532
|
}
|
|
424
|
-
|
|
425
|
-
return true;
|
|
533
|
+
return ok;
|
|
426
534
|
}
|
|
427
535
|
|
|
428
536
|
// ---- main entry ----
|
|
@@ -461,17 +569,24 @@ function run(argv) {
|
|
|
461
569
|
|
|
462
570
|
let anyError = false;
|
|
463
571
|
|
|
464
|
-
// 1. Doctrine
|
|
572
|
+
// 1. Doctrine — portable AGENTS.md kernel + this target's runtime adapter.
|
|
465
573
|
if (!installDoctrine(project, dryRun, log)) anyError = true;
|
|
574
|
+
if (!installAdapter(target, project, dryRun, log)) anyError = true;
|
|
466
575
|
|
|
467
|
-
// 2. Engine
|
|
576
|
+
// 2. Engine — frontier/ + bin/maestro.cjs + docs/orchestration.md.
|
|
468
577
|
if (!installEngine(project, dryRun, log)) anyError = true;
|
|
469
578
|
|
|
470
|
-
// 3. Wrapper (skip if no
|
|
579
|
+
// 3. Wrapper — this target's /frontier command (skip if no target detected).
|
|
471
580
|
if (target !== 'none') {
|
|
472
581
|
if (!installWrapper(target, project, userGlobal, dryRun, log)) anyError = true;
|
|
473
582
|
}
|
|
474
583
|
|
|
584
|
+
// 3b. Codex skills — the .agents/skills/<name>/SKILL.md set ships alongside
|
|
585
|
+
// the deprecated codex prompt wrapper.
|
|
586
|
+
if (target === 'codex') {
|
|
587
|
+
if (!installCodexSkills(project, userGlobal, dryRun, log)) anyError = true;
|
|
588
|
+
}
|
|
589
|
+
|
|
475
590
|
if (anyError) {
|
|
476
591
|
log('install completed with errors (see above)');
|
|
477
592
|
return 1;
|