@axiomatic-labs/claudeflow 2.49.24 → 2.49.26
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/lib/install.js +147 -16
- package/package.json +1 -1
package/lib/install.js
CHANGED
|
@@ -27,7 +27,7 @@ const PER_PROJECT_CONFIGS = ['brand.json', 'risk-signals.json', 'test-context.js
|
|
|
27
27
|
// Paths appended to the project's .gitignore (add-if-absent). These hold TEST credentials / Playwright
|
|
28
28
|
// storage state — they must NEVER be committed. test-context.json is per-project runtime state; .auth/
|
|
29
29
|
// holds storageState files the orchestrator seeds for auth-gated functional verification.
|
|
30
|
-
const GITIGNORE_LINES = ['__pycache__/', '*.pyc', '.claudeflow/test-context.json', '.claudeflow/.auth/', '.claudeflow/telemetry/', '.claudeflow/certification/', '.claudeflow/tools/'];
|
|
30
|
+
const GITIGNORE_LINES = ['__pycache__/', '*.pyc', '.claudeflow/test-context.json', '.claudeflow/.auth/', '.claudeflow/telemetry/', '.claudeflow/certification/', '.claudeflow/tools/', '.claudeflow/backups/'];
|
|
31
31
|
const SHARED_CONFIGS = ['playbook-map.json', 'agent-map.json']; // always refreshed (shared system)
|
|
32
32
|
const CODEX_DOCS = ['claudeflow-codex-migration.md', 'codexflow-codex-native-workflow.md', 'codexflow-local-actions.md'];
|
|
33
33
|
|
|
@@ -272,6 +272,110 @@ function copyPath(src, dst) {
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
function safeRelative(base, target) {
|
|
276
|
+
const rel = path.relative(base, target);
|
|
277
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return path.basename(target);
|
|
278
|
+
return rel;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function uniqueBackupPath(backupRoot, rel) {
|
|
282
|
+
const parsed = path.parse(rel);
|
|
283
|
+
let candidate = path.join(backupRoot, rel);
|
|
284
|
+
let i = 1;
|
|
285
|
+
while (fs.existsSync(candidate)) {
|
|
286
|
+
candidate = path.join(backupRoot, parsed.dir, `${parsed.name}.${i}${parsed.ext}`);
|
|
287
|
+
i++;
|
|
288
|
+
}
|
|
289
|
+
return candidate;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function moveAsideManagedPath(target, backupRoot, relBase) {
|
|
293
|
+
const rel = safeRelative(relBase, target);
|
|
294
|
+
const backup = uniqueBackupPath(backupRoot, rel);
|
|
295
|
+
fs.mkdirSync(path.dirname(backup), { recursive: true });
|
|
296
|
+
fs.renameSync(target, backup);
|
|
297
|
+
return backup;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function makeWritableTree(target) {
|
|
301
|
+
if (!fs.existsSync(target)) return;
|
|
302
|
+
const st = fs.lstatSync(target);
|
|
303
|
+
if (st.isDirectory()) {
|
|
304
|
+
try { fs.chmodSync(target, 0o755); } catch {}
|
|
305
|
+
for (const name of fs.readdirSync(target)) makeWritableTree(path.join(target, name));
|
|
306
|
+
try { fs.chmodSync(target, 0o755); } catch {}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (st.isFile()) {
|
|
310
|
+
try { fs.chmodSync(target, 0o644); } catch {}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function replaceManagedPath(src, dst, backupRoot, relBase) {
|
|
315
|
+
let movedAside = null;
|
|
316
|
+
if (fs.existsSync(dst)) {
|
|
317
|
+
try {
|
|
318
|
+
rmrf(dst);
|
|
319
|
+
} catch {
|
|
320
|
+
// Recover user-owned managed paths that lost write bits. Root-owned paths still require
|
|
321
|
+
// a one-time chown; the caller reports that explicitly instead of hiding the failure.
|
|
322
|
+
try { makeWritableTree(dst); rmrf(dst); }
|
|
323
|
+
catch { movedAside = moveAsideManagedPath(dst, backupRoot, relBase || path.dirname(dst)); }
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
copyPath(src, dst);
|
|
327
|
+
return { movedAside };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function chownTree(target, uid, gid) {
|
|
331
|
+
if (!fs.existsSync(target)) return 0;
|
|
332
|
+
let count = 0;
|
|
333
|
+
const st = fs.lstatSync(target);
|
|
334
|
+
if (st.isDirectory() && !st.isSymbolicLink()) {
|
|
335
|
+
for (const name of fs.readdirSync(target)) count += chownTree(path.join(target, name), uid, gid);
|
|
336
|
+
}
|
|
337
|
+
try { fs.chownSync(target, uid, gid); count++; } catch {}
|
|
338
|
+
return count;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function addExisting(paths, p) {
|
|
342
|
+
if (fs.existsSync(p)) paths.push(p);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function addExistingChildren(paths, dir, predicate) {
|
|
346
|
+
if (!fs.existsSync(dir)) return;
|
|
347
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
348
|
+
if (predicate(e)) paths.push(path.join(dir, e.name));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function repairSudoOwnership(cwd, options = {}) {
|
|
353
|
+
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
354
|
+
if (!isRoot && options.forceSudoOwnershipRepair !== true) return 0;
|
|
355
|
+
const uid = parseInt(options.sudoUid || process.env.SUDO_UID || '', 10);
|
|
356
|
+
const gid = parseInt(options.sudoGid || process.env.SUDO_GID || '', 10);
|
|
357
|
+
if (!Number.isInteger(uid) || uid <= 0 || !Number.isInteger(gid) || gid < 0) return 0;
|
|
358
|
+
|
|
359
|
+
const paths = [];
|
|
360
|
+
addExisting(paths, path.join(cwd, '.claudeflow'));
|
|
361
|
+
addExisting(paths, path.join(cwd, 'scripts', 'claudeflow'));
|
|
362
|
+
addExisting(paths, path.join(cwd, '.claude', 'settings.json'));
|
|
363
|
+
addExistingChildren(paths, path.join(cwd, '.claude', 'skills'), e => e.isDirectory() && e.name.startsWith('claudeflow-'));
|
|
364
|
+
addExistingChildren(paths, path.join(cwd, '.claude', 'agents'), e => e.isFile() && e.name.startsWith('claudeflow-') && e.name.endsWith('.md'));
|
|
365
|
+
addExistingChildren(paths, path.join(cwd, '.agents', 'skills'), e => e.isDirectory() && e.name.startsWith('claudeflow-'));
|
|
366
|
+
addExisting(paths, path.join(cwd, '.agents', 'plugins', 'marketplace.json'));
|
|
367
|
+
addExisting(paths, path.join(cwd, '.codex', 'hooks.json'));
|
|
368
|
+
addExisting(paths, path.join(cwd, '.codex', 'config.toml'));
|
|
369
|
+
addExistingChildren(paths, path.join(cwd, '.codex', 'agents'), e => e.isFile() && e.name.startsWith('claudeflow-') && e.name.endsWith('.toml'));
|
|
370
|
+
addExisting(paths, path.join(cwd, 'plugins', 'codexflow'));
|
|
371
|
+
for (const doc of CODEX_DOCS) addExisting(paths, path.join(cwd, 'docs', doc));
|
|
372
|
+
for (const f of ['CLAUDE.md', 'AGENTS.md', '.mcp.json', '.gitignore']) addExisting(paths, path.join(cwd, f));
|
|
373
|
+
|
|
374
|
+
let count = 0;
|
|
375
|
+
for (const p of [...new Set(paths)]) count += chownTree(p, uid, gid);
|
|
376
|
+
return count;
|
|
377
|
+
}
|
|
378
|
+
|
|
275
379
|
function pathHash(p) {
|
|
276
380
|
if (!fs.existsSync(p)) return null;
|
|
277
381
|
const h = crypto.createHash('sha256');
|
|
@@ -306,8 +410,8 @@ function syncManagedGlobalDir(srcDir, dstDir, backupRoot, filter) {
|
|
|
306
410
|
copyPath(d, path.join(backupRoot, path.basename(dstDir), e.name));
|
|
307
411
|
out.backups++;
|
|
308
412
|
}
|
|
309
|
-
|
|
310
|
-
|
|
413
|
+
const r = replaceManagedPath(s, d, backupRoot, dstDir);
|
|
414
|
+
if (r.movedAside) out.backups++;
|
|
311
415
|
out.changed++;
|
|
312
416
|
} catch {
|
|
313
417
|
out.failed++;
|
|
@@ -606,6 +710,8 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
606
710
|
globalAgents: 0,
|
|
607
711
|
globalChanged: 0,
|
|
608
712
|
globalBackups: 0,
|
|
713
|
+
localBackups: 0,
|
|
714
|
+
ownershipRepaired: 0,
|
|
609
715
|
codexSkills: 0,
|
|
610
716
|
codexAgents: 0,
|
|
611
717
|
codexHooks: false,
|
|
@@ -627,15 +733,26 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
627
733
|
// two steps a user most needs (the env block wires agent-teams + the autocompact window, etc.). Before this,
|
|
628
734
|
// any throw in steps 1–4 silently left settings/CLAUDE.md unconfigured. Each step records its name on failure
|
|
629
735
|
// so `run()` can warn instead of reporting a clean success over a partial install.
|
|
630
|
-
const step = (name, fn) => { try { fn(); } catch { out.failed.push(name); } };
|
|
736
|
+
const step = (name, fn) => { try { fn(); } catch (err) { out.failed.push(`${name}${err && err.code ? ':' + err.code : ''}`); } };
|
|
737
|
+
const stamp = new Date().toISOString().replace(/\D/g, '').slice(0, 14) + '-' + process.pid;
|
|
738
|
+
const backupRoot = path.join(cwd, '.claudeflow', 'backups', `install-${stamp}`);
|
|
739
|
+
const replace = (s, d, relBase, label) => {
|
|
740
|
+
try {
|
|
741
|
+
const r = replaceManagedPath(s, d, backupRoot, relBase || cwd);
|
|
742
|
+
if (r.movedAside) out.localBackups++;
|
|
743
|
+
return true;
|
|
744
|
+
} catch (err) {
|
|
745
|
+
out.failed.push(`${label || path.relative(cwd, d)}${err && err.code ? ':' + err.code : ''}`);
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
};
|
|
631
749
|
|
|
632
750
|
// 1) Playbooks — always refreshed (shared system).
|
|
633
751
|
step('playbooks', () => {
|
|
634
752
|
const srcPlaybooks = path.join(src, '.claudeflow', 'playbooks');
|
|
635
753
|
if (fs.existsSync(srcPlaybooks)) {
|
|
636
754
|
const dst = path.join(cwd, '.claudeflow', 'playbooks');
|
|
637
|
-
|
|
638
|
-
out.playbooks = countFiles(dst);
|
|
755
|
+
if (replace(srcPlaybooks, dst, cwd, 'playbooks')) out.playbooks = countFiles(dst);
|
|
639
756
|
}
|
|
640
757
|
});
|
|
641
758
|
|
|
@@ -648,7 +765,7 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
648
765
|
}
|
|
649
766
|
for (const c of SHARED_CONFIGS) {
|
|
650
767
|
const s = path.join(src, '.claudeflow', c);
|
|
651
|
-
if (fs.existsSync(s))
|
|
768
|
+
if (fs.existsSync(s)) replace(s, path.join(cwd, '.claudeflow', c), cwd, `configs:${c}`);
|
|
652
769
|
}
|
|
653
770
|
});
|
|
654
771
|
|
|
@@ -671,7 +788,7 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
671
788
|
const dst = path.join(cwd, 'scripts', 'claudeflow');
|
|
672
789
|
fs.mkdirSync(dst, { recursive: true });
|
|
673
790
|
for (const e of fs.readdirSync(srcScripts)) {
|
|
674
|
-
|
|
791
|
+
if (replace(path.join(srcScripts, e), path.join(dst, e), cwd, `scripts:${e}`)) out.scripts++;
|
|
675
792
|
}
|
|
676
793
|
}
|
|
677
794
|
});
|
|
@@ -685,7 +802,7 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
685
802
|
for (const e of fs.readdirSync(srcSkills, { withFileTypes: true })) {
|
|
686
803
|
if (!e.isDirectory() || !e.name.startsWith('claudeflow-')) continue;
|
|
687
804
|
const d = path.join(dst, e.name);
|
|
688
|
-
|
|
805
|
+
if (replace(path.join(srcSkills, e.name), d, cwd, `skills:${e.name}`)) out.skills++;
|
|
689
806
|
}
|
|
690
807
|
}
|
|
691
808
|
});
|
|
@@ -698,7 +815,7 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
698
815
|
fs.mkdirSync(dst, { recursive: true });
|
|
699
816
|
for (const e of fs.readdirSync(srcAgents, { withFileTypes: true })) {
|
|
700
817
|
if (!e.isFile() || !e.name.startsWith('claudeflow-') || !e.name.endsWith('.md')) continue;
|
|
701
|
-
|
|
818
|
+
if (replace(path.join(srcAgents, e.name), path.join(dst, e.name), cwd, `agents:${e.name}`)) out.agents++;
|
|
702
819
|
}
|
|
703
820
|
}
|
|
704
821
|
});
|
|
@@ -725,7 +842,7 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
725
842
|
for (const e of fs.readdirSync(srcSkills, { withFileTypes: true })) {
|
|
726
843
|
if (!e.isDirectory() || !e.name.startsWith('claudeflow-')) continue;
|
|
727
844
|
const d = path.join(dst, e.name);
|
|
728
|
-
|
|
845
|
+
if (replace(path.join(srcSkills, e.name), d, cwd, `codex-skills:${e.name}`)) out.codexSkills++;
|
|
729
846
|
}
|
|
730
847
|
}
|
|
731
848
|
});
|
|
@@ -738,7 +855,7 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
738
855
|
fs.mkdirSync(dst, { recursive: true });
|
|
739
856
|
for (const e of fs.readdirSync(srcAgents, { withFileTypes: true })) {
|
|
740
857
|
if (!e.isFile() || !e.name.startsWith('claudeflow-') || !e.name.endsWith('.toml')) continue;
|
|
741
|
-
|
|
858
|
+
if (replace(path.join(srcAgents, e.name), path.join(dst, e.name), cwd, `codex-agents:${e.name}`)) out.codexAgents++;
|
|
742
859
|
}
|
|
743
860
|
}
|
|
744
861
|
});
|
|
@@ -803,8 +920,7 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
803
920
|
const srcPlugin = path.join(src, 'plugins', 'codexflow');
|
|
804
921
|
if (fs.existsSync(srcPlugin)) {
|
|
805
922
|
const dst = path.join(cwd, 'plugins', 'codexflow');
|
|
806
|
-
|
|
807
|
-
out.codexPlugin = countFiles(dst);
|
|
923
|
+
if (replace(srcPlugin, dst, cwd, 'codex-plugin')) out.codexPlugin = countFiles(dst);
|
|
808
924
|
}
|
|
809
925
|
});
|
|
810
926
|
|
|
@@ -817,7 +933,7 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
817
933
|
for (const doc of CODEX_DOCS) {
|
|
818
934
|
const s = path.join(srcDocs, doc);
|
|
819
935
|
if (!fs.existsSync(s)) continue;
|
|
820
|
-
|
|
936
|
+
if (replace(s, path.join(dstDocs, doc), cwd, `docs:${doc}`)) out.docs++;
|
|
821
937
|
}
|
|
822
938
|
}
|
|
823
939
|
});
|
|
@@ -825,6 +941,11 @@ function installFromPayload(src, cwd, options = {}) {
|
|
|
825
941
|
// 12) .gitignore — ensure runtime and credential paths are ignored (add-if-absent; never commit creds).
|
|
826
942
|
step('gitignore', () => { out.gitignore = mergeGitignore(cwd, GITIGNORE_LINES); });
|
|
827
943
|
|
|
944
|
+
// 13) If the user ran `sudo npx ... install`, avoid leaving the managed framework owned by root.
|
|
945
|
+
step('sudo-ownership', () => {
|
|
946
|
+
if (options.repairSudoOwnership === true) out.ownershipRepaired = repairSudoOwnership(cwd, options);
|
|
947
|
+
});
|
|
948
|
+
|
|
828
949
|
return out;
|
|
829
950
|
}
|
|
830
951
|
|
|
@@ -884,6 +1005,7 @@ async function run() {
|
|
|
884
1005
|
syncGlobalClaude: process.env.CLAUDEFLOW_NO_GLOBAL_SYNC !== '1',
|
|
885
1006
|
configureTools: true,
|
|
886
1007
|
configureSerena: true,
|
|
1008
|
+
repairSudoOwnership: true,
|
|
887
1009
|
});
|
|
888
1010
|
writeLocalVersion(`v${version}`);
|
|
889
1011
|
|
|
@@ -892,10 +1014,12 @@ async function run() {
|
|
|
892
1014
|
ui.success(`claudeflow v${version} installed`);
|
|
893
1015
|
ui.info(` ${summary.playbooks} playbooks · ${summary.scripts} scripts · ${summary.skills} skills`
|
|
894
1016
|
+ (summary.globalSkills || summary.globalAgents
|
|
895
|
-
|
|
1017
|
+
? ` · global Claude repaired (${summary.globalSkills} skills, ${summary.globalAgents} agents`
|
|
896
1018
|
+ (summary.globalBackups ? `, ${summary.globalBackups} backups` : '')
|
|
897
1019
|
+ ')'
|
|
898
1020
|
: '')
|
|
1021
|
+
+ (summary.localBackups ? ` · ${summary.localBackups} local stale path${summary.localBackups > 1 ? 's' : ''} moved to backup` : '')
|
|
1022
|
+
+ (summary.ownershipRepaired ? ` · ownership restored on ${summary.ownershipRepaired} path${summary.ownershipRepaired > 1 ? 's' : ''}` : '')
|
|
899
1023
|
+ (summary.codexSkills ? ` · ${summary.codexSkills} Codex skills` : '')
|
|
900
1024
|
+ (summary.codexAgents ? ` · ${summary.codexAgents} Codex agents` : '')
|
|
901
1025
|
+ (summary.codexHooks ? ' · Codex hooks merged' : '')
|
|
@@ -909,6 +1033,12 @@ async function run() {
|
|
|
909
1033
|
+ (summary.agentsMd ? ' · AGENTS.md guidance synced' : ''));
|
|
910
1034
|
if (summary.failed && summary.failed.length) {
|
|
911
1035
|
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).`);
|
|
1036
|
+
if (summary.failed.some(f => /:EACCES|:EPERM/.test(f))) {
|
|
1037
|
+
ui.warn(' Permission repair: one or more managed claudeflow paths are not writable by your user.');
|
|
1038
|
+
ui.warn(' From the project root, run once:');
|
|
1039
|
+
ui.warn(' sudo chown -R "$(whoami):$(id -gn)" .claudeflow scripts/claudeflow .claude/skills/claudeflow-* .claude/agents/claudeflow-* .agents/skills/claudeflow-* .codex/agents/claudeflow-* plugins/codexflow');
|
|
1040
|
+
ui.warn(' Then re-run: npx @axiomatic-labs/claudeflow@latest install');
|
|
1041
|
+
}
|
|
912
1042
|
}
|
|
913
1043
|
ui.done(`Describe a change and the ${ui.BOLD}claudeflow-implementer${ui.RESET} agent will build it.`);
|
|
914
1044
|
}
|
|
@@ -928,5 +1058,6 @@ module.exports.optionalSerenaCommand = optionalSerenaCommand;
|
|
|
928
1058
|
module.exports.serenaMcpServer = serenaMcpServer;
|
|
929
1059
|
module.exports.serenaCodexToml = serenaCodexToml;
|
|
930
1060
|
module.exports.ensureManagedTools = ensureManagedTools;
|
|
1061
|
+
module.exports.repairSudoOwnership = repairSudoOwnership;
|
|
931
1062
|
module.exports.ensureSerenaTool = ensureSerenaTool;
|
|
932
1063
|
module.exports.ensureAstGrepTool = ensureAstGrepTool;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.49.
|
|
3
|
+
"version": "2.49.26",
|
|
4
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"
|