@axiomatic-labs/claudeflow 2.49.7 → 2.49.9

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/bin/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Claudeflow CLI entry point.
4
4
  //
5
5
  // Dual role:
6
- // 1. Package manager: `claudeflow install` / `version` (npx-friendly).
6
+ // 1. Package manager: `claudeflow install` / `version` (npx-friendly; installs Claude Code + Codex layers).
7
7
  // 2. Daily driver wrapper: `claudeflow [claude-args...]` spawns
8
8
  // `claude` with `--append-system-prompt-file` auto-injected when
9
9
  // an `append-system-prompt.md` is present in the project tree.
@@ -92,7 +92,7 @@ async function runSubcommand(command, args = []) {
92
92
  console.log("");
93
93
  console.log(" Claudeflow commands:");
94
94
  console.log(
95
- ` ${ui.CYAN}install${ui.RESET} Install or update Claudeflow in the current project`,
95
+ ` ${ui.CYAN}install${ui.RESET} Install or update Claudeflow for Claude Code and Codex`,
96
96
  );
97
97
  console.log(` ${ui.CYAN}version${ui.RESET} Show version info`);
98
98
  console.log(
package/lib/install.js CHANGED
@@ -8,15 +8,16 @@ const { writeLocalVersion, readLocalVersion } = require('./version.js');
8
8
  const ui = require('./ui.js');
9
9
 
10
10
  // claudeflow installs a small, namespaced footprint. Most of it is self-contained files that are simply
11
- // PLACED (playbooks, scripts, skills) — those can't conflict with the user's project. The things that need a
12
- // MERGE (never a clobber) are: .claude/settings.json (register our hooks + env alongside theirs), CLAUDE.md
13
- // (upsert our guidance block between markers user content outside stays untouched), and the root .mcp.json
14
- // (seed the base MCP servers add-if-absent never clobber a server the user configured). Those merges live
15
- // here, in Node, so install works on every platform with no shell dependency. The GitHub auth gate
16
- // (auth.js/download.js) is untouched.
11
+ // PLACED (playbooks, scripts, skills, agents) — those can't conflict with the user's project. The things that
12
+ // need a MERGE (never a clobber) are: .claude/settings.json, .codex/hooks.json, .codex/config.toml, CLAUDE.md,
13
+ // AGENTS.md, the root .mcp.json, and the local Codex marketplace entry. Those merges live here, in Node, so
14
+ // install works on every platform with no shell dependency. The GitHub auth gate (auth.js/download.js) is
15
+ // untouched.
17
16
 
18
17
  const CLAUDE_MARK = '<!-- claudeflow:begin -->';
19
18
  const CLAUDE_END = '<!-- claudeflow:end -->';
19
+ const CODEX_MARK = '<!-- codexflow:begin -->';
20
+ const CODEX_END = '<!-- codexflow:end -->';
20
21
  const HOOK_TAG = 'scripts/claudeflow/hook_'; // identifies OUR hook entries in settings.json
21
22
  // create-if-absent, never clobber. `test-context.json` ships NO default (it's filled at runtime by the
22
23
  // orchestrator with the project's TEST auth) — listing it here just means an update never clobbers it.
@@ -25,8 +26,9 @@ const PER_PROJECT_CONFIGS = ['brand.json', 'risk-signals.json', 'test-context.js
25
26
  // Paths appended to the project's .gitignore (add-if-absent). These hold TEST credentials / Playwright
26
27
  // storage state — they must NEVER be committed. test-context.json is per-project runtime state; .auth/
27
28
  // holds storageState files the orchestrator seeds for auth-gated functional verification.
28
- const GITIGNORE_LINES = ['.claudeflow/test-context.json', '.claudeflow/.auth/'];
29
+ const GITIGNORE_LINES = ['.claudeflow/test-context.json', '.claudeflow/.auth/', '.claudeflow/telemetry/', '.claudeflow/certification/'];
29
30
  const SHARED_CONFIGS = ['playbook-map.json', 'agent-map.json']; // always refreshed (shared system)
31
+ const CODEX_DOCS = ['claudeflow-codex-migration.md', 'codexflow-codex-native-workflow.md', 'codexflow-local-actions.md'];
30
32
 
31
33
  function rmrf(p) { fs.rmSync(p, { recursive: true, force: true }); }
32
34
 
@@ -78,6 +80,142 @@ function mergeSettings(cwd, payloadSettings) {
78
80
  fs.writeFileSync(sp, JSON.stringify(data, null, 2) + '\n');
79
81
  }
80
82
 
83
+ function mergeCodexHooks(cwd, payloadHooksFile) {
84
+ const hp = path.join(cwd, '.codex', 'hooks.json');
85
+ const payloadHooks = (payloadHooksFile && payloadHooksFile.hooks) || {};
86
+ if (!Object.keys(payloadHooks).length) return false;
87
+
88
+ let data = {};
89
+ if (fs.existsSync(hp)) {
90
+ try { data = JSON.parse(fs.readFileSync(hp, 'utf8')); } catch { return false; }
91
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) return false;
92
+ }
93
+
94
+ const hooks = data.hooks || (data.hooks = {});
95
+ for (const evt of Object.keys(hooks)) {
96
+ hooks[evt] = (hooks[evt] || []).filter(e => !(e.hooks || []).some(h => (h.command || '').includes(HOOK_TAG)));
97
+ if (!hooks[evt].length) delete hooks[evt];
98
+ }
99
+ for (const evt of Object.keys(payloadHooks)) {
100
+ const arr = hooks[evt] || (hooks[evt] = []);
101
+ for (const entry of payloadHooks[evt]) arr.push(entry);
102
+ }
103
+
104
+ fs.mkdirSync(path.dirname(hp), { recursive: true });
105
+ fs.writeFileSync(hp, JSON.stringify(data, null, 2) + '\n');
106
+ return true;
107
+ }
108
+
109
+ function tomlKey(line) {
110
+ const m = line.match(/^\s*([A-Za-z0-9_.-]+)\s*=/);
111
+ return m ? m[1] : null;
112
+ }
113
+
114
+ function escapeRe(s) {
115
+ return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
116
+ }
117
+
118
+ function hasTomlKey(text, key) {
119
+ return new RegExp(`^\\s*${escapeRe(key)}\\s*=`, 'm').test(text);
120
+ }
121
+
122
+ function splitTomlSections(text) {
123
+ const top = [];
124
+ const sections = [];
125
+ let current = top;
126
+ for (const line of String(text).split('\n')) {
127
+ const m = line.match(/^\s*\[([^\]]+)\]\s*$/);
128
+ if (m) {
129
+ const section = { name: m[1], lines: [line] };
130
+ sections.push(section);
131
+ current = section.lines;
132
+ } else {
133
+ current.push(line);
134
+ }
135
+ }
136
+ return { top, sections };
137
+ }
138
+
139
+ function insertBeforeFirstTomlSection(text, lines) {
140
+ if (!lines.length) return text;
141
+ const block = lines.join('\n') + '\n';
142
+ const firstSection = text.search(/^\s*\[[^\]]+\]\s*$/m);
143
+ if (firstSection === -1) return text.replace(/\s*$/, '\n') + block;
144
+ const prefix = text.slice(0, firstSection).replace(/\s*$/, '\n');
145
+ return prefix + block + '\n' + text.slice(firstSection);
146
+ }
147
+
148
+ function tomlSectionRange(text, name) {
149
+ const headerRe = new RegExp(`^\\s*\\[${escapeRe(name)}\\]\\s*$`, 'm');
150
+ const match = headerRe.exec(text);
151
+ if (!match) return null;
152
+ const nextRe = /^\s*\[[^\]]+\]\s*$/gm;
153
+ nextRe.lastIndex = match.index + match[0].length;
154
+ const next = nextRe.exec(text);
155
+ return { start: match.index, end: next ? next.index : text.length };
156
+ }
157
+
158
+ function mergeTomlSection(text, section) {
159
+ const range = tomlSectionRange(text, section.name);
160
+ if (!range) {
161
+ return text.replace(/\s*$/, '\n') + '\n' + section.lines.join('\n').replace(/\s*$/, '') + '\n';
162
+ }
163
+
164
+ const current = text.slice(range.start, range.end);
165
+ const missing = section.lines.slice(1).filter(line => {
166
+ const key = tomlKey(line);
167
+ return key && !hasTomlKey(current, key);
168
+ });
169
+ if (!missing.length) return text;
170
+ const insert = (text[range.end - 1] === '\n' ? '' : '\n') + missing.join('\n') + '\n';
171
+ return text.slice(0, range.end) + insert + text.slice(range.end);
172
+ }
173
+
174
+ function mergeCodexConfig(cwd, payloadConfigText) {
175
+ if (!payloadConfigText || !String(payloadConfigText).trim()) return false;
176
+ const cp = path.join(cwd, '.codex', 'config.toml');
177
+ fs.mkdirSync(path.dirname(cp), { recursive: true });
178
+ if (!fs.existsSync(cp)) {
179
+ fs.writeFileSync(cp, String(payloadConfigText).replace(/\s*$/, '\n'));
180
+ return true;
181
+ }
182
+
183
+ const before = fs.readFileSync(cp, 'utf8');
184
+ const payload = splitTomlSections(payloadConfigText);
185
+ const topAssignments = payload.top.filter(line => {
186
+ const key = tomlKey(line);
187
+ return key && !hasTomlKey(before, key);
188
+ });
189
+
190
+ let updated = insertBeforeFirstTomlSection(before, topAssignments);
191
+ for (const section of payload.sections) updated = mergeTomlSection(updated, section);
192
+ if (updated === before) return false;
193
+ fs.writeFileSync(cp, updated.replace(/\s*$/, '\n'));
194
+ return true;
195
+ }
196
+
197
+ function mergeCodexMarketplace(cwd, payloadMarketplace) {
198
+ const payloadPlugins = (payloadMarketplace && payloadMarketplace.plugins) || [];
199
+ if (!Array.isArray(payloadPlugins) || !payloadPlugins.length) return false;
200
+
201
+ const mp = path.join(cwd, '.agents', 'plugins', 'marketplace.json');
202
+ let data = {};
203
+ if (fs.existsSync(mp)) {
204
+ try { data = JSON.parse(fs.readFileSync(mp, 'utf8')); } catch { return false; }
205
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) return false;
206
+ }
207
+
208
+ if (!data.name && payloadMarketplace.name) data.name = payloadMarketplace.name;
209
+ if (!data.interface && payloadMarketplace.interface) data.interface = payloadMarketplace.interface;
210
+ const managedNames = new Set(payloadPlugins.map(p => p && p.name).filter(Boolean));
211
+ const existing = Array.isArray(data.plugins) ? data.plugins : [];
212
+ data.plugins = existing.filter(p => !managedNames.has(p && p.name)).concat(payloadPlugins);
213
+
214
+ fs.mkdirSync(path.dirname(mp), { recursive: true });
215
+ fs.writeFileSync(mp, JSON.stringify(data, null, 2) + '\n');
216
+ return true;
217
+ }
218
+
81
219
  // Seed the BASE MCP server set (playwright for visual QA / browser_verification, context7 for live library
82
220
  // docs during Discover) into the project's root `.mcp.json` — the set the old framework shipped, restored.
83
221
  // ADD-IF-ABSENT by server key: create the file if missing, else merge in only the keys the user doesn't
@@ -126,32 +264,57 @@ function mergeGitignore(cwd, lines) {
126
264
  // • CLAUDE.md WITH the block → REPLACE the marked region in place, so guidance updates PROPAGATE on `update`
127
265
  // (the old code appended-once and never refreshed → a stale project never got new disciplines).
128
266
  // • begin marker but no end (corrupted) → leave it untouched (don't risk eating user content)
129
- function syncClaudeMd(cwd, snippetPath) {
267
+ function syncMarkedMd(cwd, targetName, snippetPath, beginMark, endMark) {
130
268
  if (!fs.existsSync(snippetPath)) return false;
131
- const target = path.join(cwd, 'CLAUDE.md');
269
+ const target = path.join(cwd, targetName);
132
270
  const block = fs.readFileSync(snippetPath, 'utf8').trim();
133
271
  if (!fs.existsSync(target)) {
134
272
  fs.writeFileSync(target, block + '\n');
135
273
  return true;
136
274
  }
137
275
  const cur = fs.readFileSync(target, 'utf8');
138
- const b = cur.indexOf(CLAUDE_MARK);
276
+ const b = cur.indexOf(beginMark);
139
277
  if (b === -1) {
140
278
  fs.writeFileSync(target, cur.replace(/\s*$/, '') + '\n\n' + block + '\n');
141
279
  return true;
142
280
  }
143
- const e = cur.indexOf(CLAUDE_END, b);
281
+ const e = cur.indexOf(endMark, b);
144
282
  if (e === -1) return false; // begin without end → corrupt region; don't clobber
145
- const updated = cur.slice(0, b) + block + cur.slice(e + CLAUDE_END.length);
283
+ const updated = cur.slice(0, b) + block + cur.slice(e + endMark.length);
146
284
  if (updated === cur) return false; // already current
147
285
  fs.writeFileSync(target, updated);
148
286
  return true;
149
287
  }
150
288
 
289
+ function syncClaudeMd(cwd, snippetPath) {
290
+ return syncMarkedMd(cwd, 'CLAUDE.md', snippetPath, CLAUDE_MARK, CLAUDE_END);
291
+ }
292
+
293
+ function syncAgentsMd(cwd, snippetPath) {
294
+ return syncMarkedMd(cwd, 'AGENTS.md', snippetPath, CODEX_MARK, CODEX_END);
295
+ }
296
+
151
297
  // Place + merge the framework from an extracted payload dir into a project. Pure filesystem, no network —
152
298
  // so it is testable on a throwaway dir.
153
299
  function installFromPayload(src, cwd) {
154
- const out = { playbooks: 0, scripts: 0, skills: 0, agents: 0, mcp: 0, gitignore: 0, claudeMd: false, failed: [] };
300
+ const out = {
301
+ playbooks: 0,
302
+ scripts: 0,
303
+ skills: 0,
304
+ agents: 0,
305
+ codexSkills: 0,
306
+ codexAgents: 0,
307
+ codexHooks: false,
308
+ codexConfig: false,
309
+ codexMarketplace: false,
310
+ codexPlugin: 0,
311
+ docs: 0,
312
+ mcp: 0,
313
+ gitignore: 0,
314
+ claudeMd: false,
315
+ agentsMd: false,
316
+ failed: [],
317
+ };
155
318
  // Each placement step is ISOLATED: a failure in one (a copy error, a permission hiccup, a weird file in the
156
319
  // user's tree) must NOT abort the rest — above all it must not skip the settings/env + CLAUDE.md config, the
157
320
  // two steps a user most needs (the env block wires agent-teams + the autocompact window, etc.). Before this,
@@ -222,6 +385,33 @@ function installFromPayload(src, cwd) {
222
385
  }
223
386
  });
224
387
 
388
+ // 4c) Codex skills — replace shipped claudeflow-* skills in .agents; preserve user skills.
389
+ step('codex-skills', () => {
390
+ const srcSkills = path.join(src, '.agents', 'skills');
391
+ if (fs.existsSync(srcSkills)) {
392
+ const dst = path.join(cwd, '.agents', 'skills');
393
+ fs.mkdirSync(dst, { recursive: true });
394
+ for (const e of fs.readdirSync(srcSkills, { withFileTypes: true })) {
395
+ if (!e.isDirectory() || !e.name.startsWith('claudeflow-')) continue;
396
+ const d = path.join(dst, e.name);
397
+ try { rmrf(d); copyDir(path.join(srcSkills, e.name), d); out.codexSkills++; } catch {}
398
+ }
399
+ }
400
+ });
401
+
402
+ // 4d) Codex agents — replace shipped claudeflow-*.toml agents; preserve user agents.
403
+ step('codex-agents', () => {
404
+ const srcAgents = path.join(src, '.codex', 'agents');
405
+ if (fs.existsSync(srcAgents)) {
406
+ const dst = path.join(cwd, '.codex', 'agents');
407
+ fs.mkdirSync(dst, { recursive: true });
408
+ for (const e of fs.readdirSync(srcAgents, { withFileTypes: true })) {
409
+ if (!e.isFile() || !e.name.startsWith('claudeflow-') || !e.name.endsWith('.toml')) continue;
410
+ try { fs.copyFileSync(path.join(srcAgents, e.name), path.join(dst, e.name)); out.codexAgents++; } catch {}
411
+ }
412
+ }
413
+ });
414
+
225
415
  // 5) settings.json — MERGE the shipped hooks + env (add-if-absent) into the user's settings (never clobber).
226
416
  // ALWAYS runs (isolated above) — this is what wires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS et al.
227
417
  step('settings', () => {
@@ -246,7 +436,50 @@ function installFromPayload(src, cwd) {
246
436
  }
247
437
  });
248
438
 
249
- // 8) .gitignoreensure the TEST-credential paths are ignored (add-if-absent; never commit creds).
439
+ // 8) AGENTS.mdupsert the Codex guidance block between markers, preserving project guidance.
440
+ step('agents-md', () => {
441
+ out.agentsMd = syncAgentsMd(cwd, path.join(src, 'assets', 'AGENTS.snippet.md'));
442
+ });
443
+
444
+ // 9) Codex hooks/config — merge hooks, and add missing default config keys/sections only.
445
+ step('codex-hooks', () => {
446
+ const srcHooks = path.join(src, '.codex', 'hooks.json');
447
+ if (fs.existsSync(srcHooks)) out.codexHooks = mergeCodexHooks(cwd, JSON.parse(fs.readFileSync(srcHooks, 'utf8')));
448
+ });
449
+ step('codex-config', () => {
450
+ const srcConfig = path.join(src, '.codex', 'config.toml');
451
+ if (fs.existsSync(srcConfig)) out.codexConfig = mergeCodexConfig(cwd, fs.readFileSync(srcConfig, 'utf8'));
452
+ });
453
+
454
+ // 10) Codex marketplace + local plugin — managed codexflow entry only; user plugins are preserved.
455
+ step('codex-marketplace', () => {
456
+ const srcMarketplace = path.join(src, '.agents', 'plugins', 'marketplace.json');
457
+ if (fs.existsSync(srcMarketplace)) out.codexMarketplace = mergeCodexMarketplace(cwd, JSON.parse(fs.readFileSync(srcMarketplace, 'utf8')));
458
+ });
459
+ step('codex-plugin', () => {
460
+ const srcPlugin = path.join(src, 'plugins', 'codexflow');
461
+ if (fs.existsSync(srcPlugin)) {
462
+ const dst = path.join(cwd, 'plugins', 'codexflow');
463
+ rmrf(dst); copyDir(srcPlugin, dst);
464
+ out.codexPlugin = countFiles(dst);
465
+ }
466
+ });
467
+
468
+ // 11) Codex docs — managed filenames only.
469
+ step('codex-docs', () => {
470
+ const srcDocs = path.join(src, 'docs');
471
+ if (fs.existsSync(srcDocs)) {
472
+ const dstDocs = path.join(cwd, 'docs');
473
+ fs.mkdirSync(dstDocs, { recursive: true });
474
+ for (const doc of CODEX_DOCS) {
475
+ const s = path.join(srcDocs, doc);
476
+ if (!fs.existsSync(s)) continue;
477
+ try { fs.copyFileSync(s, path.join(dstDocs, doc)); out.docs++; } catch {}
478
+ }
479
+ }
480
+ });
481
+
482
+ // 12) .gitignore — ensure runtime and credential paths are ignored (add-if-absent; never commit creds).
250
483
  step('gitignore', () => { out.gitignore = mergeGitignore(cwd, GITIGNORE_LINES); });
251
484
 
252
485
  return out;
@@ -311,8 +544,14 @@ async function run() {
311
544
 
312
545
  ui.success(`claudeflow v${version} installed`);
313
546
  ui.info(` ${summary.playbooks} playbooks · ${summary.scripts} scripts · ${summary.skills} skills`
547
+ + (summary.codexSkills ? ` · ${summary.codexSkills} Codex skills` : '')
548
+ + (summary.codexAgents ? ` · ${summary.codexAgents} Codex agents` : '')
549
+ + (summary.codexHooks ? ' · Codex hooks merged' : '')
550
+ + (summary.codexConfig ? ' · Codex config synced' : '')
551
+ + (summary.codexMarketplace ? ' · Codex plugin registered' : '')
314
552
  + (summary.mcp ? ` · ${summary.mcp} MCP server${summary.mcp > 1 ? 's' : ''} added to .mcp.json` : '')
315
- + (summary.claudeMd ? ' · CLAUDE.md guidance synced' : ''));
553
+ + (summary.claudeMd ? ' · CLAUDE.md guidance synced' : '')
554
+ + (summary.agentsMd ? ' · AGENTS.md guidance synced' : ''));
316
555
  if (summary.failed && summary.failed.length) {
317
556
  ui.warn(` some steps did not complete: ${summary.failed.join(', ')}. Re-run \`npx @axiomatic-labs/claudeflow@latest install\` (the @latest avoids a stale npx cache).`);
318
557
  }
@@ -323,5 +562,9 @@ module.exports = run;
323
562
  module.exports.installFromPayload = installFromPayload;
324
563
  module.exports.mergeSettings = mergeSettings;
325
564
  module.exports.syncClaudeMd = syncClaudeMd;
565
+ module.exports.syncAgentsMd = syncAgentsMd;
326
566
  module.exports.mergeMcp = mergeMcp;
327
567
  module.exports.mergeGitignore = mergeGitignore;
568
+ module.exports.mergeCodexHooks = mergeCodexHooks;
569
+ module.exports.mergeCodexConfig = mergeCodexConfig;
570
+ module.exports.mergeCodexMarketplace = mergeCodexMarketplace;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@axiomatic-labs/claudeflow",
3
- "version": "2.49.7",
4
- "description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
3
+ "version": "2.49.9",
4
+ "description": "Claudeflow — AI-powered development toolkit for Claude Code and Codex. Skills, agents, hooks, and quality gates that ship production apps.",
5
5
  "bin": {
6
6
  "claudeflow": "./bin/cli.js"
7
7
  },
@@ -18,6 +18,7 @@
18
18
  "keywords": [
19
19
  "claude",
20
20
  "claude-code",
21
+ "codex",
21
22
  "ai",
22
23
  "ai-agents",
23
24
  "ai-coding",