multi-agents-cli 1.0.68 → 1.0.69
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/init.js +236 -925
- package/lib/detect.js +132 -0
- package/lib/questions.js +266 -0
- package/lib/steps.js +89 -0
- package/lib/summary.js +140 -0
- package/lib/ui.js +119 -0
- package/lib/writers.js +150 -0
- package/package.json +2 -1
package/lib/ui.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ── Dependencies ──────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
|
|
7
|
+
// ── Colors ────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const c = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
bold: '\x1b[1m',
|
|
12
|
+
dim: '\x1b[2m',
|
|
13
|
+
green: '\x1b[32m',
|
|
14
|
+
blue: '\x1b[34m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
cyan: '\x1b[36m',
|
|
17
|
+
red: '\x1b[31m',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const bold = (s) => `${c.bold}${s}${c.reset}`;
|
|
21
|
+
const green = (s) => `${c.green}${s}${c.reset}`;
|
|
22
|
+
const yellow = (s) => `${c.yellow}${s}${c.reset}`;
|
|
23
|
+
const dim = (s) => `${c.dim}${s}${c.reset}`;
|
|
24
|
+
const cyan = (s) => `${c.cyan}${s}${c.reset}`;
|
|
25
|
+
const blue = (s) => `${c.blue}${s}${c.reset}`;
|
|
26
|
+
const red = (s) => `${c.red}${s}${c.reset}`;
|
|
27
|
+
|
|
28
|
+
// ── Prompts (arrow-key navigation) ───────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
let prompts;
|
|
31
|
+
try { prompts = require('prompts'); } catch { prompts = null; }
|
|
32
|
+
|
|
33
|
+
const rl = readline.createInterface({
|
|
34
|
+
input: process.stdin,
|
|
35
|
+
output: process.stdout,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const ask = (question) =>
|
|
39
|
+
new Promise((resolve) => rl.question(question, (a) => resolve(a.trim())));
|
|
40
|
+
|
|
41
|
+
const arrowSelect = async (message, choices, showBack = false, backLabel = '← Restart configuration') => {
|
|
42
|
+
const allChoices = showBack
|
|
43
|
+
? [...choices, { label: dim(backLabel) }]
|
|
44
|
+
: choices;
|
|
45
|
+
|
|
46
|
+
if (prompts && process.stdin.isTTY) {
|
|
47
|
+
const res = await prompts({
|
|
48
|
+
type: 'select',
|
|
49
|
+
name: 'value',
|
|
50
|
+
message,
|
|
51
|
+
choices: allChoices.map((c, i) => ({ title: typeof c === 'string' ? c : c.label, value: i })),
|
|
52
|
+
}, { onCancel: () => process.exit(0) });
|
|
53
|
+
return res.value ?? 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
allChoices.forEach((c, i) => console.log(` ${dim(`${i + 1}.`)} ${typeof c === 'string' ? c : c.label}`));
|
|
57
|
+
return new Promise(resolve => {
|
|
58
|
+
rl.question(`\n Select (1-${allChoices.length}): `, ans => {
|
|
59
|
+
const n = parseInt(ans) - 1;
|
|
60
|
+
resolve(!isNaN(n) && n >= 0 && n < allChoices.length ? n : 0);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const arrowConfirm = async (message) => {
|
|
66
|
+
if (prompts && process.stdin.isTTY) {
|
|
67
|
+
const res = await prompts({
|
|
68
|
+
type: 'confirm',
|
|
69
|
+
name: 'value',
|
|
70
|
+
message,
|
|
71
|
+
initial: true,
|
|
72
|
+
}, { onCancel: () => process.exit(0) });
|
|
73
|
+
return res.value ?? true;
|
|
74
|
+
}
|
|
75
|
+
return new Promise(resolve => {
|
|
76
|
+
rl.question(`${message} (y/n): `, ans => resolve(ans.toLowerCase() !== 'n'));
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// ── Layout helpers ────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
const separator = () => console.log(`\n${dim('─'.repeat(60))}`);
|
|
83
|
+
|
|
84
|
+
const showList = (items, showSkip = false) => {
|
|
85
|
+
items.forEach((item, i) => {
|
|
86
|
+
const label = typeof item === 'string' ? item : item.label;
|
|
87
|
+
console.log(` ${dim(`${i + 1}.`)} ${label}`);
|
|
88
|
+
});
|
|
89
|
+
if (showSkip) console.log(` ${dim('0.')} Skip ${dim('(agent will propose when needed)')}`);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const summaryLine = (label, value) => {
|
|
93
|
+
const padded = label.padEnd(20);
|
|
94
|
+
if (!value) {
|
|
95
|
+
console.log(` ${dim(padded)}: ${yellow('(skipped - agent will propose when needed)')}`);
|
|
96
|
+
} else {
|
|
97
|
+
console.log(` ${dim(padded)}: ${green(value)}`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const renderTrajectoryLines = (lines) => {
|
|
102
|
+
const HEADERS = ['Benefits', 'Best for', 'Use agents for', 'Handle manually'];
|
|
103
|
+
lines.forEach(l => {
|
|
104
|
+
if (!l) { console.log(''); return; }
|
|
105
|
+
if (l.startsWith('⚠')) console.log(` ${yellow(l)}`);
|
|
106
|
+
else if (HEADERS.includes(l)) console.log(`\n ${bold(l)}`);
|
|
107
|
+
else if (l.startsWith('·')) console.log(` ${l}`);
|
|
108
|
+
else console.log(` ${dim(l)}`);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
c, bold, green, yellow, dim, cyan, blue, red,
|
|
116
|
+
rl, ask,
|
|
117
|
+
arrowSelect, arrowConfirm,
|
|
118
|
+
separator, showList, summaryLine, renderTrajectoryLines,
|
|
119
|
+
};
|
package/lib/writers.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
// ── Colors (inline — avoid circular dep) ─────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
10
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
11
|
+
|
|
12
|
+
// ── Config writer ─────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
const writeConfig = (filePath, configs) => {
|
|
15
|
+
if (!fs.existsSync(filePath)) return;
|
|
16
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
17
|
+
|
|
18
|
+
for (const [key, value] of Object.entries(configs)) {
|
|
19
|
+
if (!value) continue;
|
|
20
|
+
const regex = new RegExp(`(# @config ${key}\\s*:)([^\\n]*)`, 'g');
|
|
21
|
+
content = content.replace(regex, `$1 ${value}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const [key, value] of Object.entries(configs)) {
|
|
25
|
+
if (!value) continue;
|
|
26
|
+
const token = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
27
|
+
content = content.replace(token, value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ── Gitignore helper ──────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const ensureGitignore = (ROOT, entry) => {
|
|
36
|
+
const p = path.join(ROOT, '.gitignore');
|
|
37
|
+
const content = fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '';
|
|
38
|
+
if (!content.includes(entry)) fs.appendFileSync(p, `\n${entry}\n`);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ── Directory copy ────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const copyDir = (src, dest) => {
|
|
44
|
+
if (!fs.existsSync(src)) return;
|
|
45
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
46
|
+
fs.readdirSync(src).forEach(file => {
|
|
47
|
+
const srcFile = path.join(src, file);
|
|
48
|
+
const destFile = path.join(dest, file);
|
|
49
|
+
if (fs.statSync(srcFile).isDirectory()) {
|
|
50
|
+
copyDir(srcFile, destFile);
|
|
51
|
+
} else {
|
|
52
|
+
fs.copyFileSync(srcFile, destFile);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ── Tracking structure ────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
const emptySlot = () => ({
|
|
60
|
+
branch: null,
|
|
61
|
+
timestamp: null,
|
|
62
|
+
launchedAt: null,
|
|
63
|
+
status: null,
|
|
64
|
+
missingCount: 0,
|
|
65
|
+
worktreePath: null,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const generateTrackingStructure = (config) => {
|
|
69
|
+
const bt = config.backend?.type;
|
|
70
|
+
|
|
71
|
+
const structure = {
|
|
72
|
+
client: {
|
|
73
|
+
UI: emptySlot(),
|
|
74
|
+
LOGIC: emptySlot(),
|
|
75
|
+
FORMS: emptySlot(),
|
|
76
|
+
ROUTING: emptySlot(),
|
|
77
|
+
TESTING: emptySlot(),
|
|
78
|
+
ACCESSIBILITY: emptySlot(),
|
|
79
|
+
},
|
|
80
|
+
shared: {
|
|
81
|
+
SECURITY: emptySlot(),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (bt === 'separate') {
|
|
86
|
+
structure.backend = {
|
|
87
|
+
INIT: emptySlot(),
|
|
88
|
+
API: emptySlot(),
|
|
89
|
+
LOGIC: emptySlot(),
|
|
90
|
+
AUTH: emptySlot(),
|
|
91
|
+
DB: emptySlot(),
|
|
92
|
+
EVENTS: emptySlot(),
|
|
93
|
+
JOBS: emptySlot(),
|
|
94
|
+
TESTING: emptySlot(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return structure;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// ── GitHub remote setup ───────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
const detectGitHubUser = () => {
|
|
104
|
+
try {
|
|
105
|
+
return execSync('gh api user --jq .login', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
106
|
+
} catch {}
|
|
107
|
+
try {
|
|
108
|
+
return execSync('git config user.name', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
109
|
+
} catch {}
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const setupUserRemote = (ROOT, projectName) => {
|
|
114
|
+
let currentOrigin = null;
|
|
115
|
+
try {
|
|
116
|
+
currentOrigin = execSync('git remote get-url origin',
|
|
117
|
+
{ cwd: ROOT, encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
118
|
+
} catch {}
|
|
119
|
+
|
|
120
|
+
if (currentOrigin && !currentOrigin.includes('multi-agents-template')) return;
|
|
121
|
+
|
|
122
|
+
if (currentOrigin?.includes('multi-agents-template')) {
|
|
123
|
+
try {
|
|
124
|
+
execSync('git remote remove origin', { cwd: ROOT, stdio: 'pipe' });
|
|
125
|
+
execSync(`git remote add upstream ${currentOrigin}`, { cwd: ROOT, stdio: 'pipe' });
|
|
126
|
+
console.log(dim(' ℹ Template remote moved to upstream'));
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const flagPath = path.join(ROOT, '.scaffold', '.remote-setup-needed');
|
|
131
|
+
fs.writeFileSync(flagPath, JSON.stringify({
|
|
132
|
+
projectName,
|
|
133
|
+
createdAt: new Date().toISOString(),
|
|
134
|
+
}), 'utf8');
|
|
135
|
+
|
|
136
|
+
console.log(`\n ${yellow('ℹ No remote configured.')} Your first agent session will set this up.`);
|
|
137
|
+
console.log(dim(' All work stays local until then.\n'));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
writeConfig,
|
|
144
|
+
ensureGitignore,
|
|
145
|
+
copyDir,
|
|
146
|
+
emptySlot,
|
|
147
|
+
generateTrackingStructure,
|
|
148
|
+
detectGitHubUser,
|
|
149
|
+
setupUserRemote,
|
|
150
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multi-agents-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.69",
|
|
4
4
|
"description": "Multi-agent workflow orchestration for Claude Code — isolated git worktrees, structured state tracking, autonomous task chaining",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"init.js",
|
|
24
24
|
"core/",
|
|
25
|
+
"lib/",
|
|
25
26
|
"README.md",
|
|
26
27
|
"LICENSE"
|
|
27
28
|
],
|