@kbediako/codex-orchestrator 0.1.1 → 0.1.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/README.md CHANGED
@@ -80,6 +80,7 @@ Use `npx codex-orchestrator resume --run <run-id>` to continue interrupted runs;
80
80
  - `codex-orchestrator mcp serve [--repo <path>] [--dry-run] [-- <extra args>]`: launch the MCP stdio server (delegates to `codex mcp-server`; stdout guard keeps protocol-only output, logs to stderr).
81
81
  - `codex-orchestrator init codex [--cwd <path>] [--force]`: copy starter templates into a repo (no overwrite unless `--force`).
82
82
  - `codex-orchestrator doctor [--format json]`: check optional tooling dependencies and print install commands.
83
+ - `codex-orchestrator devtools setup [--yes]`: print DevTools MCP setup instructions (`--yes` applies `codex mcp add ...`).
83
84
  - `codex-orchestrator self-check --format json`: emit a safe JSON health payload for smoke tests.
84
85
  - `codex-orchestrator --version`: print the package version.
85
86
 
@@ -216,7 +217,7 @@ Optional prompt overrides:
216
217
 
217
218
  `--no-interactive` disables the HUD only; set `CODEX_NON_INTERACTIVE=1` when you need to suppress Codex prompts (e.g., shortcut runs or custom automation).
218
219
 
219
- Check readiness with `codex-orchestrator doctor --format json` (reports DevTools skill availability).
220
+ Check readiness with `codex-orchestrator doctor --format json` (reports DevTools skill + MCP config availability). Use `codex-orchestrator devtools setup` to print setup steps.
220
221
 
221
222
  ## Mirror Workflows
222
223
  - `npm run mirror:fetch -- --project <name> [--dry-run] [--force]`: reads `packages/<project>/mirror.config.json` (origin, routes, asset roots, rewrite/block/allow lists), caches downloads **per project** under `.runs/<task>/mirror/<project>/cache`, strips tracker patterns, rewrites externals to `/external/<host>/...`, localizes OG/twitter preview images, rewrites share links off tracker-heavy hosts, and stages into `.runs/<task>/mirror/<project>/<timestamp>/staging/public` before promoting to `packages/<project>/public`. Non-origin assets fall back to Web Archive when the primary host is down; promotion is skipped if errors are detected unless `--force` is set. Manifests live at `.runs/<task>/mirror/<project>/<timestamp>/manifest.json` (warns when `MCP_RUNNER_TASK_ID` is unset; honors `compliance/permit.json` when present).
@@ -9,6 +9,7 @@ import { evaluateInteractiveGate } from '../orchestrator/src/cli/utils/interacti
9
9
  import { buildSelfCheckResult } from '../orchestrator/src/cli/selfCheck.js';
10
10
  import { initCodexTemplates, formatInitSummary } from '../orchestrator/src/cli/init.js';
11
11
  import { runDoctor, formatDoctorSummary } from '../orchestrator/src/cli/doctor.js';
12
+ import { formatDevtoolsSetupSummary, runDevtoolsSetup } from '../orchestrator/src/cli/devtoolsSetup.js';
12
13
  import { loadPackageInfo } from '../orchestrator/src/cli/utils/packageInfo.js';
13
14
  import { serveMcp } from '../orchestrator/src/cli/mcp.js';
14
15
  async function main() {
@@ -52,6 +53,9 @@ async function main() {
52
53
  case 'doctor':
53
54
  await handleDoctor(args);
54
55
  break;
56
+ case 'devtools':
57
+ await handleDevtools(args);
58
+ break;
55
59
  case 'mcp':
56
60
  await handleMcp(args);
57
61
  break;
@@ -388,6 +392,30 @@ async function handleDoctor(rawArgs) {
388
392
  console.log(line);
389
393
  }
390
394
  }
395
+ async function handleDevtools(rawArgs) {
396
+ const { positionals, flags } = parseArgs(rawArgs);
397
+ const subcommand = positionals.shift();
398
+ if (!subcommand) {
399
+ throw new Error('devtools requires a subcommand (setup).');
400
+ }
401
+ if (subcommand !== 'setup') {
402
+ throw new Error(`Unknown devtools subcommand: ${subcommand}`);
403
+ }
404
+ const format = flags['format'] === 'json' ? 'json' : 'text';
405
+ const apply = Boolean(flags['yes']);
406
+ if (format === 'json' && apply) {
407
+ throw new Error('devtools setup does not support --format json with --yes.');
408
+ }
409
+ const result = await runDevtoolsSetup({ apply });
410
+ if (format === 'json') {
411
+ console.log(JSON.stringify(result, null, 2));
412
+ return;
413
+ }
414
+ const summary = formatDevtoolsSetupSummary(result);
415
+ for (const line of summary) {
416
+ console.log(line);
417
+ }
418
+ }
391
419
  async function handleMcp(rawArgs) {
392
420
  const { positionals, flags } = parseArgs(rawArgs);
393
421
  const subcommand = positionals.shift();
@@ -562,6 +590,9 @@ Commands:
562
590
  self-check [--format json]
563
591
  init codex [--cwd <path>] [--force]
564
592
  doctor [--format json]
593
+ devtools setup Print DevTools MCP setup instructions.
594
+ --yes Apply setup by running "codex mcp add ...".
595
+ --format json Emit machine-readable output (dry-run only).
565
596
  mcp serve [--repo <path>] [--dry-run] [-- <extra args>]
566
597
  version | --version
567
598
 
@@ -0,0 +1,66 @@
1
+ import { spawn } from 'node:child_process';
2
+ import process from 'node:process';
3
+ import { buildDevtoolsSetupPlan, resolveDevtoolsReadiness } from './utils/devtools.js';
4
+ export async function runDevtoolsSetup(options = {}) {
5
+ const env = options.env ?? process.env;
6
+ const plan = buildDevtoolsSetupPlan(env);
7
+ const readiness = resolveDevtoolsReadiness(env);
8
+ if (!options.apply) {
9
+ return { status: 'planned', plan, readiness };
10
+ }
11
+ if (readiness.config.status === 'ok') {
12
+ return {
13
+ status: 'skipped',
14
+ reason: 'DevTools MCP is already configured.',
15
+ plan,
16
+ readiness
17
+ };
18
+ }
19
+ if (readiness.config.status === 'invalid') {
20
+ throw new Error(`Cannot apply DevTools setup because config.toml is invalid: ${readiness.config.path}`);
21
+ }
22
+ await applyDevtoolsSetup(plan, env);
23
+ return { status: 'applied', plan, readiness };
24
+ }
25
+ export function formatDevtoolsSetupSummary(result) {
26
+ const lines = [];
27
+ lines.push(`DevTools setup: ${result.status}`);
28
+ if (result.reason) {
29
+ lines.push(`Note: ${result.reason}`);
30
+ }
31
+ lines.push(`- Codex home: ${result.plan.codexHome}`);
32
+ lines.push(`- Skill: ${result.readiness.skill.status} (${result.readiness.skill.path})`);
33
+ const configLabel = result.readiness.config.status === 'invalid'
34
+ ? `invalid (${result.readiness.config.path})`
35
+ : `${result.readiness.config.status} (${result.readiness.config.path})`;
36
+ lines.push(`- Config: ${configLabel}`);
37
+ if (result.readiness.config.detail) {
38
+ lines.push(` detail: ${result.readiness.config.detail}`);
39
+ }
40
+ if (result.readiness.config.error) {
41
+ lines.push(` error: ${result.readiness.config.error}`);
42
+ }
43
+ lines.push(`- Command: ${result.plan.commandLine}`);
44
+ lines.push('- Config snippet:');
45
+ for (const line of result.plan.configSnippet.split('\n')) {
46
+ lines.push(` ${line}`);
47
+ }
48
+ if (result.status === 'planned') {
49
+ lines.push('Run with --yes to apply this setup.');
50
+ }
51
+ return lines;
52
+ }
53
+ async function applyDevtoolsSetup(plan, env) {
54
+ await new Promise((resolve, reject) => {
55
+ const child = spawn(plan.command, plan.args, { stdio: 'inherit', env });
56
+ child.once('error', (error) => reject(error instanceof Error ? error : new Error(String(error))));
57
+ child.once('exit', (code) => {
58
+ if (code === 0) {
59
+ resolve();
60
+ }
61
+ else {
62
+ reject(new Error(`codex mcp add exited with code ${code ?? 'unknown'}`));
63
+ }
64
+ });
65
+ });
66
+ }
@@ -1,7 +1,5 @@
1
- import { existsSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
1
  import process from 'node:process';
2
+ import { buildDevtoolsSetupPlan, DEVTOOLS_SKILL_NAME, resolveDevtoolsReadiness } from './utils/devtools.js';
5
3
  import { resolveOptionalDependency } from './utils/optionalDeps.js';
6
4
  const OPTIONAL_DEPENDENCIES = [
7
5
  {
@@ -12,7 +10,6 @@ const OPTIONAL_DEPENDENCIES = [
12
10
  { name: 'pixelmatch', install: 'npm install --save-dev pixelmatch' },
13
11
  { name: 'cheerio', install: 'npm install --save-dev cheerio' }
14
12
  ];
15
- const DEVTOOLS_SKILL_NAME = 'chrome-devtools';
16
13
  export function runDoctor(cwd = process.cwd()) {
17
14
  const dependencies = OPTIONAL_DEPENDENCIES.map((entry) => {
18
15
  const resolved = resolveOptionalDependency(entry.name, cwd);
@@ -26,20 +23,34 @@ export function runDoctor(cwd = process.cwd()) {
26
23
  install: entry.install
27
24
  };
28
25
  });
29
- const codexHome = resolveCodexHome();
30
- const skillPath = join(codexHome, 'skills', DEVTOOLS_SKILL_NAME, 'SKILL.md');
31
- const skillInstalled = existsSync(skillPath);
26
+ const readiness = resolveDevtoolsReadiness();
27
+ const setupPlan = buildDevtoolsSetupPlan();
32
28
  const devtools = {
33
- status: skillInstalled ? 'ok' : 'missing',
29
+ status: readiness.status,
34
30
  skill: {
35
31
  name: DEVTOOLS_SKILL_NAME,
36
- status: skillInstalled ? 'ok' : 'missing',
37
- path: skillPath,
38
- install: skillInstalled
32
+ status: readiness.skill.status,
33
+ path: readiness.skill.path,
34
+ install: readiness.skill.status === 'ok'
39
35
  ? undefined
40
36
  : [
41
- `Copy the ${DEVTOOLS_SKILL_NAME} skill into ${join(codexHome, 'skills', DEVTOOLS_SKILL_NAME)}`,
42
- `Expected file: ${skillPath}`
37
+ `Copy the ${DEVTOOLS_SKILL_NAME} skill into ${setupPlan.codexHome}/skills/${DEVTOOLS_SKILL_NAME}`,
38
+ `Expected file: ${readiness.skill.path}`
39
+ ]
40
+ },
41
+ config: {
42
+ status: readiness.config.status,
43
+ path: readiness.config.path,
44
+ detail: readiness.config.detail,
45
+ error: readiness.config.error,
46
+ install: readiness.config.status === 'ok'
47
+ ? undefined
48
+ : [
49
+ 'Run: codex-orchestrator devtools setup',
50
+ `Run: ${setupPlan.commandLine}`,
51
+ `Config path: ${setupPlan.configPath}`,
52
+ 'Config snippet:',
53
+ ...setupPlan.configSnippet.split('\n')
43
54
  ]
44
55
  },
45
56
  enablement: [
@@ -48,9 +59,12 @@ export function runDoctor(cwd = process.cwd()) {
48
59
  ]
49
60
  };
50
61
  const missing = dependencies.filter((dep) => dep.status === 'missing').map((dep) => dep.name);
51
- if (!skillInstalled) {
62
+ if (readiness.skill.status === 'missing') {
52
63
  missing.push(DEVTOOLS_SKILL_NAME);
53
64
  }
65
+ if (readiness.config.status !== 'ok') {
66
+ missing.push(`${DEVTOOLS_SKILL_NAME}-config`);
67
+ }
54
68
  return {
55
69
  status: missing.length === 0 ? 'ok' : 'warning',
56
70
  missing,
@@ -83,15 +97,26 @@ export function formatDoctorSummary(result) {
83
97
  lines.push(` install: ${instruction}`);
84
98
  }
85
99
  }
100
+ if (result.devtools.config.status === 'ok') {
101
+ lines.push(` - config.toml: ok (${result.devtools.config.path})`);
102
+ }
103
+ else {
104
+ const label = result.devtools.config.status === 'invalid'
105
+ ? `invalid (${result.devtools.config.path})`
106
+ : `missing (${result.devtools.config.path})`;
107
+ lines.push(` - config.toml: ${label}`);
108
+ if (result.devtools.config.detail) {
109
+ lines.push(` detail: ${result.devtools.config.detail}`);
110
+ }
111
+ if (result.devtools.config.error) {
112
+ lines.push(` error: ${result.devtools.config.error}`);
113
+ }
114
+ for (const instruction of result.devtools.config.install ?? []) {
115
+ lines.push(` install: ${instruction}`);
116
+ }
117
+ }
86
118
  for (const line of result.devtools.enablement) {
87
119
  lines.push(` - ${line}`);
88
120
  }
89
121
  return lines;
90
122
  }
91
- function resolveCodexHome() {
92
- const override = process.env.CODEX_HOME?.trim();
93
- if (override) {
94
- return override;
95
- }
96
- return join(homedir(), '.codex');
97
- }
@@ -1,5 +1,29 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import process from 'node:process';
1
5
  import { EnvUtils } from '../../../../packages/shared/config/env.js';
6
+ export const DEVTOOLS_SKILL_NAME = 'chrome-devtools';
2
7
  export const DEVTOOLS_CONFIG_OVERRIDE = 'mcp_servers.chrome-devtools.enabled=true';
8
+ const DEVTOOLS_CONFIG_FILENAME = 'config.toml';
9
+ const DEVTOOLS_MCP_COMMAND = [
10
+ 'mcp',
11
+ 'add',
12
+ DEVTOOLS_SKILL_NAME,
13
+ '--',
14
+ 'npx',
15
+ '-y',
16
+ 'chrome-devtools-mcp@latest',
17
+ '--categoryEmulation',
18
+ '--categoryPerformance',
19
+ '--categoryNetwork'
20
+ ];
21
+ const DEVTOOLS_CONFIG_SNIPPET = [
22
+ '[mcp_servers.chrome-devtools]',
23
+ 'command = "npx"',
24
+ 'args = ["-y", "chrome-devtools-mcp@latest", "--categoryEmulation", "--categoryPerformance", "--categoryNetwork"]',
25
+ 'enabled = false'
26
+ ].join('\n');
3
27
  export function isDevtoolsEnabled(env = process.env) {
4
28
  const raw = env.CODEX_REVIEW_DEVTOOLS;
5
29
  if (!raw) {
@@ -7,12 +31,166 @@ export function isDevtoolsEnabled(env = process.env) {
7
31
  }
8
32
  return EnvUtils.isTrue(raw.trim().toLowerCase());
9
33
  }
34
+ export function resolveCodexHome(env = process.env) {
35
+ const override = env.CODEX_HOME?.trim();
36
+ if (override) {
37
+ return override;
38
+ }
39
+ return join(homedir(), '.codex');
40
+ }
41
+ export function resolveCodexConfigPath(env = process.env) {
42
+ return join(resolveCodexHome(env), DEVTOOLS_CONFIG_FILENAME);
43
+ }
44
+ export function resolveDevtoolsReadiness(env = process.env) {
45
+ const codexHome = resolveCodexHome(env);
46
+ const skillPath = join(codexHome, 'skills', DEVTOOLS_SKILL_NAME, 'SKILL.md');
47
+ const skillInstalled = existsSync(skillPath);
48
+ const config = inspectDevtoolsConfig(env);
49
+ const configReady = config.status === 'ok';
50
+ let status;
51
+ if (config.status === 'invalid') {
52
+ status = 'invalid-config';
53
+ }
54
+ else if (!skillInstalled && !configReady) {
55
+ status = 'missing-both';
56
+ }
57
+ else if (!skillInstalled) {
58
+ status = 'missing-skill';
59
+ }
60
+ else if (!configReady) {
61
+ status = 'missing-config';
62
+ }
63
+ else {
64
+ status = 'ok';
65
+ }
66
+ return {
67
+ status,
68
+ skill: {
69
+ status: skillInstalled ? 'ok' : 'missing',
70
+ path: skillPath
71
+ },
72
+ config
73
+ };
74
+ }
75
+ export function buildDevtoolsSetupPlan(env = process.env) {
76
+ const codexHome = resolveCodexHome(env);
77
+ const configPath = resolveCodexConfigPath(env);
78
+ const args = [...DEVTOOLS_MCP_COMMAND];
79
+ return {
80
+ codexHome,
81
+ configPath,
82
+ command: 'codex',
83
+ args,
84
+ commandLine: ['codex', ...args].join(' '),
85
+ configSnippet: DEVTOOLS_CONFIG_SNIPPET
86
+ };
87
+ }
10
88
  export function resolveCodexCommand(args, env = process.env) {
11
89
  if (!isDevtoolsEnabled(env)) {
12
90
  return { command: 'codex', args };
13
91
  }
92
+ const readiness = resolveDevtoolsReadiness(env);
93
+ if (readiness.status !== 'ok') {
94
+ throw new Error(formatDevtoolsPreflightError(readiness));
95
+ }
14
96
  return {
15
97
  command: 'codex',
16
98
  args: ['-c', DEVTOOLS_CONFIG_OVERRIDE, ...args]
17
99
  };
18
100
  }
101
+ export function formatDevtoolsPreflightError(readiness) {
102
+ const lines = ['DevTools MCP is not ready for this run.'];
103
+ lines.push(`- Skill: ${readiness.skill.status} (${readiness.skill.path})`);
104
+ const configStatus = readiness.config.status === 'invalid'
105
+ ? `invalid (${readiness.config.path})`
106
+ : `${readiness.config.status} (${readiness.config.path})`;
107
+ lines.push(`- Config: ${configStatus}`);
108
+ if (readiness.config.detail) {
109
+ lines.push(` detail: ${readiness.config.detail}`);
110
+ }
111
+ if (readiness.config.error) {
112
+ lines.push(` error: ${readiness.config.error}`);
113
+ }
114
+ lines.push('Run `codex-orchestrator doctor --format json` for details.');
115
+ lines.push('Run `codex-orchestrator devtools setup` to configure the MCP server.');
116
+ return lines.join('\n');
117
+ }
118
+ function inspectDevtoolsConfig(env = process.env) {
119
+ const configPath = resolveCodexConfigPath(env);
120
+ if (!existsSync(configPath)) {
121
+ return { status: 'missing', path: configPath, detail: 'config.toml not found' };
122
+ }
123
+ let raw;
124
+ try {
125
+ raw = readFileSync(configPath, 'utf8');
126
+ }
127
+ catch (error) {
128
+ return {
129
+ status: 'invalid',
130
+ path: configPath,
131
+ error: error instanceof Error ? error.message : String(error)
132
+ };
133
+ }
134
+ const hasEntry = hasDevtoolsConfigEntry(raw);
135
+ if (hasEntry) {
136
+ return { status: 'ok', path: configPath };
137
+ }
138
+ return {
139
+ status: 'missing',
140
+ path: configPath,
141
+ detail: 'chrome-devtools entry not found'
142
+ };
143
+ }
144
+ function hasDevtoolsConfigEntry(raw) {
145
+ const lines = raw.split('\n');
146
+ let currentTable = null;
147
+ for (const line of lines) {
148
+ const trimmed = stripTomlComment(line).trim();
149
+ if (!trimmed) {
150
+ continue;
151
+ }
152
+ const tableMatch = trimmed.match(/^\[(.+)\]$/);
153
+ if (tableMatch) {
154
+ currentTable = tableMatch[1]?.trim() ?? null;
155
+ if (currentTable === 'mcp_servers.chrome-devtools' ||
156
+ currentTable === 'mcp_servers."chrome-devtools"' ||
157
+ currentTable === "mcp_servers.'chrome-devtools'") {
158
+ return true;
159
+ }
160
+ continue;
161
+ }
162
+ if (trimmed.startsWith('mcp_servers.')) {
163
+ if (trimmed.startsWith('mcp_servers."chrome-devtools".')) {
164
+ return true;
165
+ }
166
+ if (trimmed.startsWith("mcp_servers.'chrome-devtools'.")) {
167
+ return true;
168
+ }
169
+ if (trimmed.startsWith('mcp_servers.chrome-devtools.')) {
170
+ return true;
171
+ }
172
+ if (trimmed.startsWith('mcp_servers."chrome-devtools"=')) {
173
+ return true;
174
+ }
175
+ if (trimmed.startsWith("mcp_servers.'chrome-devtools'=")) {
176
+ return true;
177
+ }
178
+ if (trimmed.startsWith('mcp_servers.chrome-devtools=')) {
179
+ return true;
180
+ }
181
+ }
182
+ if (currentTable === 'mcp_servers') {
183
+ if (/^"?chrome-devtools"?\s*=/.test(trimmed)) {
184
+ return true;
185
+ }
186
+ }
187
+ }
188
+ return false;
189
+ }
190
+ function stripTomlComment(line) {
191
+ const index = line.indexOf('#');
192
+ if (index === -1) {
193
+ return line;
194
+ }
195
+ return line.slice(0, index);
196
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbediako/codex-orchestrator",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -80,10 +80,18 @@
80
80
  "pngjs": "^7.0.0"
81
81
  },
82
82
  "peerDependenciesMeta": {
83
- "cheerio": { "optional": true },
84
- "pixelmatch": { "optional": true },
85
- "playwright": { "optional": true },
86
- "pngjs": { "optional": true }
83
+ "cheerio": {
84
+ "optional": true
85
+ },
86
+ "pixelmatch": {
87
+ "optional": true
88
+ },
89
+ "playwright": {
90
+ "optional": true
91
+ },
92
+ "pngjs": {
93
+ "optional": true
94
+ }
87
95
  },
88
96
  "overrides": {
89
97
  "esbuild": "^0.25.11"