cli-five 0.1.0
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/LICENSE +21 -0
- package/README.md +106 -0
- package/bin/cli-five.mjs +8 -0
- package/package.json +35 -0
- package/src/cli.mjs +65 -0
- package/src/commands/doctor.mjs +54 -0
- package/src/commands/init.mjs +87 -0
- package/src/commands/list-stacks.mjs +13 -0
- package/src/steps/confirm.mjs +38 -0
- package/src/steps/detect.mjs +64 -0
- package/src/steps/interview.mjs +98 -0
- package/src/steps/scaffold.mjs +139 -0
- package/src/steps/skills.mjs +162 -0
- package/src/util/fs.mjs +49 -0
- package/src/util/git.mjs +12 -0
- package/src/util/log.mjs +11 -0
- package/templates/.github/agents/coder.agent.md +59 -0
- package/templates/.github/agents/designer.agent.md +56 -0
- package/templates/.github/agents/orchestrator.agent.md +79 -0
- package/templates/.github/agents/planner.agent.md +69 -0
- package/templates/.github/agents/reviewer.agent.md +88 -0
- package/templates/.github/copilot-instructions.md.tmpl +28 -0
- package/templates/.github/instructions/README.md +14 -0
- package/templates/.github/skills/README.md +14 -0
- package/templates/AGENTS.md.tmpl +25 -0
- package/templates/PROJECT.md.tmpl +27 -0
- package/templates/STATE.md.tmpl +22 -0
- package/templates/agent-diary.md.tmpl +5 -0
- package/templates/decisions.md.tmpl +15 -0
- package/templates/histories/coder.md +5 -0
- package/templates/histories/designer.md +5 -0
- package/templates/histories/orchestrator.md +5 -0
- package/templates/histories/planner.md +5 -0
- package/templates/histories/reviewer.md +5 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 idusortus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# cli-five
|
|
2
|
+
|
|
3
|
+
> **Code Like I'm Five** — scaffold a 5-agent VS Code Copilot team into any repo.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx cli-five init
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
That's it. Run it in any folder. It interviews you, scaffolds `.github/agents/` + project memory files, searches skills.sh for relevant skills, and tells you what to do next.
|
|
10
|
+
|
|
11
|
+
## What you get
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
your-repo/
|
|
15
|
+
├── .github/
|
|
16
|
+
│ ├── agents/ # 5 agents: Orchestrator, Planner, Coder, Designer, Reviewer
|
|
17
|
+
│ ├── copilot-instructions.md # Persona + project mandates from interview
|
|
18
|
+
│ ├── instructions/ # Empty — populate via /agent-customization in chat
|
|
19
|
+
│ └── skills/ # Empty — populate via skills.sh
|
|
20
|
+
├── AGENTS.md # Tool-agnostic project context (agents.md standard)
|
|
21
|
+
├── PROJECT.md # Long-form vision (rarely changes)
|
|
22
|
+
├── STATE.md # Cross-session status (changes constantly)
|
|
23
|
+
├── decisions.md # Architectural decision log
|
|
24
|
+
├── agent-diary.md # Append-only session log
|
|
25
|
+
└── histories/ # Per-agent accumulated learnings
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Commands
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx cli-five init # interview + scaffold + skill discovery
|
|
32
|
+
npx cli-five doctor # validate an existing setup
|
|
33
|
+
npx cli-five list-stacks # show detectable tech stacks
|
|
34
|
+
npx cli-five help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Flags
|
|
38
|
+
|
|
39
|
+
| Flag | Effect |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `--yes`, `-y` | Accept interview defaults (overwrite gate still active) |
|
|
42
|
+
| `--force` | Overwrite without confirmation (use with `--yes`) |
|
|
43
|
+
| `--dry-run` | Print actions, write nothing |
|
|
44
|
+
| `--no-skills` | Skip skills.sh discovery step |
|
|
45
|
+
| `--cost-mode <m>` | Set cost mode: `premium`, `cheap`, or `mixed` |
|
|
46
|
+
| `--cwd <path>` | Run against a different directory |
|
|
47
|
+
|
|
48
|
+
## What `init` does
|
|
49
|
+
|
|
50
|
+
1. **Detect** — fingerprints stack (Node/TS, Python, .NET, Kotlin, Rust, Go, etc.). Brownfield-aware.
|
|
51
|
+
2. **git init** — if needed. Asks first.
|
|
52
|
+
3. **Overwrite gate** — double-confirms ("Proceed?" then "R U Sure?"). Only `--force --yes` bypasses.
|
|
53
|
+
4. **Interview** — name, one-liner, stack, frameworks, goals, constraints, cost mode, persona toggle.
|
|
54
|
+
5. **Scaffold** — writes 18 files. Substitutes answers into templates. Swaps `model:` per cost mode.
|
|
55
|
+
6. **Skill discovery** — queries skills.sh via `npx skills find <term>` per detected stack. Recommends known-good skills, offers multiselect install, and launches interactive browser.
|
|
56
|
+
7. **Next steps** — tells you to run `/agent-customization` for stack-specific instructions.
|
|
57
|
+
|
|
58
|
+
## Cost modes
|
|
59
|
+
|
|
60
|
+
| Agent | `premium` (1x–3x) | `cheap` (0x) | `mixed` |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| Orchestrator | Claude Sonnet 4.6 | GPT-4.1 | GPT-4.1 |
|
|
63
|
+
| Planner | Claude Opus 4.6 | GPT-4o | GPT-4o |
|
|
64
|
+
| Coder | GPT-5.3-Codex | GPT-4.1 | GPT-5.3-Codex |
|
|
65
|
+
| Designer | Claude Opus 4.6 | GPT-4o | GPT-4o |
|
|
66
|
+
| Reviewer | Claude Opus 4.6 | GPT-5 mini | Claude Opus 4.6 |
|
|
67
|
+
|
|
68
|
+
Override from CLI: `npx cli-five init --cost-mode cheap`
|
|
69
|
+
Change anytime by editing the `model:` line in `.github/agents/*.agent.md`.
|
|
70
|
+
|
|
71
|
+
## Required VS Code settings
|
|
72
|
+
|
|
73
|
+
```jsonc
|
|
74
|
+
{
|
|
75
|
+
"chat.subagents.allowInvocationsFromSubagents": true
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## What this is not
|
|
80
|
+
|
|
81
|
+
- **Not a runtime.** Once scaffolded, your repo doesn't depend on `cli-five`. You can uninstall the package and the agents still work.
|
|
82
|
+
- **Not a workflow engine.** No `/plan-phase`, no `/ship`, no 50 slash commands. Use `/agent-customization` in Copilot Chat to extend.
|
|
83
|
+
- **Not Ralph.** No issue-polling daemons, no autonomous loops, no crypto tokens.
|
|
84
|
+
|
|
85
|
+
## Why the name
|
|
86
|
+
|
|
87
|
+
ELI5 → CLI5. Code Like I'm Five. Five agents. Get it? Yeah, it's a stretch. But the npm name was available.
|
|
88
|
+
|
|
89
|
+
## Influences
|
|
90
|
+
|
|
91
|
+
- [bradygaster/squad](https://github.com/bradygaster/squad) — see [docs/squad-review.md](docs/squad-review.md)
|
|
92
|
+
- [burkeholland/ultralight](https://github.com/burkeholland/ultralight) — see [docs/ultralight-review.md](docs/ultralight-review.md)
|
|
93
|
+
- [gsd-build/get-shit-done](https://github.com/gsd-build/get-shit-done) — see [docs/gsd-review.md](docs/gsd-review.md)
|
|
94
|
+
|
|
95
|
+
## Local development
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git clone https://github.com/idusortus/squad-mine
|
|
99
|
+
cd squad-mine
|
|
100
|
+
npm install
|
|
101
|
+
node bin/cli-five.mjs init --cwd /tmp/test-target
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT.
|
package/bin/cli-five.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cli-five",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Code Like I'm Five — scaffold a 5-agent VS Code Copilot team into any repo.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cli-five": "bin/cli-five.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test tests/",
|
|
18
|
+
"smoke": "node bin/cli-five.mjs --help"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"copilot",
|
|
25
|
+
"vscode",
|
|
26
|
+
"agents",
|
|
27
|
+
"scaffold",
|
|
28
|
+
"ai"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"kleur": "^4.1.5",
|
|
33
|
+
"prompts": "^2.4.2"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import kleur from 'kleur';
|
|
2
|
+
import { init } from './commands/init.mjs';
|
|
3
|
+
import { doctor } from './commands/doctor.mjs';
|
|
4
|
+
import { listStacks } from './commands/list-stacks.mjs';
|
|
5
|
+
import { STACK_SIGNATURES } from './steps/detect.mjs';
|
|
6
|
+
|
|
7
|
+
const HELP = `${kleur.bold('cli-five')} ${kleur.gray('— Code Like I\'m Five')}
|
|
8
|
+
Scaffold a 5-agent Copilot team into any repo.
|
|
9
|
+
|
|
10
|
+
${kleur.bold('Usage')}
|
|
11
|
+
npx cli-five <command> [options]
|
|
12
|
+
|
|
13
|
+
${kleur.bold('Commands')}
|
|
14
|
+
init Interview + scaffold .github/* and project memory files
|
|
15
|
+
doctor Validate an existing cli-five setup
|
|
16
|
+
list-stacks Show detectable tech stacks
|
|
17
|
+
help Show this message
|
|
18
|
+
|
|
19
|
+
${kleur.bold('Flags')}
|
|
20
|
+
--yes, -y Accept defaults and skip confirmations (still gated on overwrite)
|
|
21
|
+
--force Overwrite without confirmation. Dangerous. Use with --yes.
|
|
22
|
+
--dry-run Print actions without writing files
|
|
23
|
+
--no-skills Skip the skills.sh discovery step
|
|
24
|
+
--cost-mode <m> Override cost mode (premium, cheap, mixed) — skips interview question
|
|
25
|
+
--cwd <path> Run against a directory other than the current one
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
export async function run(argv) {
|
|
29
|
+
const args = parse(argv);
|
|
30
|
+
const cmd = args._[0] || 'help';
|
|
31
|
+
|
|
32
|
+
switch (cmd) {
|
|
33
|
+
case 'init':
|
|
34
|
+
return init(args);
|
|
35
|
+
case 'doctor':
|
|
36
|
+
return doctor(args);
|
|
37
|
+
case 'list-stacks':
|
|
38
|
+
return listStacks();
|
|
39
|
+
case 'help':
|
|
40
|
+
case '--help':
|
|
41
|
+
case '-h':
|
|
42
|
+
process.stdout.write(HELP);
|
|
43
|
+
return;
|
|
44
|
+
default:
|
|
45
|
+
process.stderr.write(kleur.red(`Unknown command: ${cmd}\n\n`));
|
|
46
|
+
process.stdout.write(HELP);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parse(argv) {
|
|
52
|
+
const out = { _: [], yes: false, force: false, dryRun: false, skills: true, costMode: null, cwd: process.cwd() };
|
|
53
|
+
for (let i = 0; i < argv.length; i++) {
|
|
54
|
+
const a = argv[i];
|
|
55
|
+
if (a === '--yes' || a === '-y') out.yes = true;
|
|
56
|
+
else if (a === '--force') out.force = true;
|
|
57
|
+
else if (a === '--dry-run') out.dryRun = true;
|
|
58
|
+
else if (a === '--no-skills') out.skills = false;
|
|
59
|
+
else if (a === '--cost-mode') out.costMode = argv[++i];
|
|
60
|
+
else if (a === '--cwd') out.cwd = argv[++i];
|
|
61
|
+
else if (a === '--help' || a === '-h') out._.push('help');
|
|
62
|
+
else out._.push(a);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import kleur from 'kleur';
|
|
4
|
+
import { log } from '../util/log.mjs';
|
|
5
|
+
|
|
6
|
+
const REQUIRED = [
|
|
7
|
+
'.github/copilot-instructions.md',
|
|
8
|
+
'.github/agents/orchestrator.agent.md',
|
|
9
|
+
'.github/agents/planner.agent.md',
|
|
10
|
+
'.github/agents/coder.agent.md',
|
|
11
|
+
'.github/agents/designer.agent.md',
|
|
12
|
+
'.github/agents/reviewer.agent.md',
|
|
13
|
+
'PROJECT.md',
|
|
14
|
+
'STATE.md',
|
|
15
|
+
'decisions.md',
|
|
16
|
+
'agent-diary.md',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const OPTIONAL = [
|
|
20
|
+
'.github/instructions',
|
|
21
|
+
'.github/skills',
|
|
22
|
+
'AGENTS.md',
|
|
23
|
+
'histories/orchestrator.md',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export async function doctor(args) {
|
|
27
|
+
const cwd = args.cwd;
|
|
28
|
+
log.raw(kleur.bold().magenta('\ncli-five doctor') + kleur.gray(` ${cwd}`));
|
|
29
|
+
let fail = 0;
|
|
30
|
+
|
|
31
|
+
log.step('Required');
|
|
32
|
+
for (const p of REQUIRED) {
|
|
33
|
+
const ok = existsSync(join(cwd, p));
|
|
34
|
+
if (ok) log.ok(p);
|
|
35
|
+
else {
|
|
36
|
+
log.err(p);
|
|
37
|
+
fail++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
log.step('Optional');
|
|
42
|
+
for (const p of OPTIONAL) {
|
|
43
|
+
if (existsSync(join(cwd, p))) log.ok(p);
|
|
44
|
+
else log.warn(`missing (ok): ${p}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
log.raw('');
|
|
48
|
+
if (fail === 0) {
|
|
49
|
+
log.ok('All required files present.');
|
|
50
|
+
} else {
|
|
51
|
+
log.err(`${fail} required file(s) missing. Run \`npx cli-five init\`.`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import kleur from 'kleur';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { log } from '../util/log.mjs';
|
|
4
|
+
import { detect } from '../steps/detect.mjs';
|
|
5
|
+
import { confirmOverwriteIfNeeded } from '../steps/confirm.mjs';
|
|
6
|
+
import { interview } from '../steps/interview.mjs';
|
|
7
|
+
import { scaffold, summarize } from '../steps/scaffold.mjs';
|
|
8
|
+
import { skillDiscovery } from '../steps/skills.mjs';
|
|
9
|
+
import { isGitRepo, gitInit } from '../util/git.mjs';
|
|
10
|
+
|
|
11
|
+
export async function init(args) {
|
|
12
|
+
const cwd = args.cwd;
|
|
13
|
+
log.raw(kleur.bold().magenta('\ncli-five init') + kleur.gray(` ${cwd}`));
|
|
14
|
+
|
|
15
|
+
// 1. Detect
|
|
16
|
+
log.step('1/5 Detect workspace');
|
|
17
|
+
const detected = detect(cwd);
|
|
18
|
+
log.info(`Project: ${kleur.bold(detected.projectName)}`);
|
|
19
|
+
log.info(`Mode: ${detected.isBrownfield ? kleur.yellow('brownfield') : kleur.green('greenfield')}`);
|
|
20
|
+
if (detected.stacks.length) log.info(`Stack: ${detected.stacks.map((s) => s.label).join(', ')}`);
|
|
21
|
+
if (!detected.hasGit) log.warn('Not a git repository.');
|
|
22
|
+
|
|
23
|
+
// 2. git init if needed
|
|
24
|
+
if (!detected.hasGit) {
|
|
25
|
+
if (args.yes || (await ask('Run `git init`?', true))) {
|
|
26
|
+
gitInit(cwd);
|
|
27
|
+
log.ok('Initialised git repo.');
|
|
28
|
+
} else {
|
|
29
|
+
log.warn('Continuing without git. You will regret this.');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Overwrite gate
|
|
34
|
+
log.step('2/5 Confirm overwrites');
|
|
35
|
+
const ok = await confirmOverwriteIfNeeded(detected, args);
|
|
36
|
+
if (!ok) {
|
|
37
|
+
log.warn('Aborted. Nothing written.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!detected.hasAgents && !detected.hasCopilotInstructions) log.dim('No collisions.');
|
|
41
|
+
|
|
42
|
+
// 4. Interview
|
|
43
|
+
log.step('3/5 Interview');
|
|
44
|
+
const answers = await interview(detected, args);
|
|
45
|
+
|
|
46
|
+
// CLI --cost-mode override
|
|
47
|
+
if (args.costMode && ['premium', 'cheap', 'mixed'].includes(args.costMode)) {
|
|
48
|
+
answers.costMode = args.costMode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 5. Scaffold
|
|
52
|
+
log.step('4/5 Scaffold');
|
|
53
|
+
const written = scaffold({ cwd, answers, args });
|
|
54
|
+
if (args.dryRun) log.warn('--dry-run: no files written. Plan:');
|
|
55
|
+
log.raw(summarize(written, cwd));
|
|
56
|
+
if (!args.dryRun) log.ok(`Wrote ${written.length} files.`);
|
|
57
|
+
|
|
58
|
+
// 6. Skill discovery
|
|
59
|
+
log.step('5/5 Skill discovery');
|
|
60
|
+
await skillDiscovery({ cwd, answers, args });
|
|
61
|
+
|
|
62
|
+
// 7. Next steps
|
|
63
|
+
printNextSteps(answers);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function ask(message, initial = false) {
|
|
67
|
+
const { v } = await prompts({ type: 'confirm', name: 'v', message, initial });
|
|
68
|
+
return Boolean(v);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function printNextSteps(answers) {
|
|
72
|
+
log.raw('');
|
|
73
|
+
log.raw(kleur.bold().green('Done. Next steps:'));
|
|
74
|
+
log.raw('');
|
|
75
|
+
log.raw(` 1. Open this folder in VS Code Insiders.`);
|
|
76
|
+
log.raw(` 2. Enable Copilot subagents (settings.json):`);
|
|
77
|
+
log.raw(kleur.gray(` "chat.subagents.allowInvocationsFromSubagents": true`));
|
|
78
|
+
log.raw(` 3. Open Copilot Chat and verify the 5 agents appear (@ menu).`);
|
|
79
|
+
log.raw(` 4. Ask the Orchestrator to do something:`);
|
|
80
|
+
log.raw(kleur.gray(` @Orchestrator read PROJECT.md and propose Phase 1.`));
|
|
81
|
+
log.raw(` 5. Generate stack-specific instructions:`);
|
|
82
|
+
log.raw(kleur.gray(` Run /agent-customization in chat — it will read PROJECT.md`));
|
|
83
|
+
log.raw(kleur.gray(` and produce .github/instructions/*.instructions.md files.`));
|
|
84
|
+
log.raw('');
|
|
85
|
+
log.raw(kleur.dim('Edit cost mode anytime by changing `model:` in .github/agents/*.agent.md.'));
|
|
86
|
+
log.raw('');
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import kleur from 'kleur';
|
|
2
|
+
import { STACK_SIGNATURES } from '../steps/detect.mjs';
|
|
3
|
+
|
|
4
|
+
export function listStacks() {
|
|
5
|
+
process.stdout.write(kleur.bold('\nDetectable stacks\n\n'));
|
|
6
|
+
process.stdout.write(` ${kleur.gray('ID'.padEnd(18))} ${kleur.gray('Label'.padEnd(22))} ${kleur.gray('Detected by')}\n`);
|
|
7
|
+
process.stdout.write(` ${'─'.repeat(18)} ${'─'.repeat(22)} ${'─'.repeat(40)}\n`);
|
|
8
|
+
for (const s of STACK_SIGNATURES) {
|
|
9
|
+
const trigger = s.files ? s.files.join(', ') : `glob: ${s.glob}`;
|
|
10
|
+
process.stdout.write(` ${s.id.padEnd(18)} ${s.label.padEnd(22)} ${trigger}\n`);
|
|
11
|
+
}
|
|
12
|
+
process.stdout.write('\n');
|
|
13
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import { log } from '../util/log.mjs';
|
|
3
|
+
|
|
4
|
+
export async function confirmOverwriteIfNeeded(detected, args) {
|
|
5
|
+
const collisions = collideList(detected);
|
|
6
|
+
if (collisions.length === 0) return true;
|
|
7
|
+
|
|
8
|
+
log.warn('Existing cli-five artifacts detected:');
|
|
9
|
+
for (const c of collisions) log.dim(` - ${c}`);
|
|
10
|
+
|
|
11
|
+
if (args.force && args.yes) {
|
|
12
|
+
log.warn('--force --yes set. Overwriting without prompts. Hope you have git.');
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const first = await prompts({
|
|
17
|
+
type: 'confirm',
|
|
18
|
+
name: 'ok',
|
|
19
|
+
message: 'This will OVERWRITE the files above. Proceed?',
|
|
20
|
+
initial: false,
|
|
21
|
+
});
|
|
22
|
+
if (!first.ok) return false;
|
|
23
|
+
|
|
24
|
+
const second = await prompts({
|
|
25
|
+
type: 'confirm',
|
|
26
|
+
name: 'sure',
|
|
27
|
+
message: 'R U Sure? Last chance.',
|
|
28
|
+
initial: false,
|
|
29
|
+
});
|
|
30
|
+
return Boolean(second.sure);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collideList(detected) {
|
|
34
|
+
const out = [];
|
|
35
|
+
if (detected.hasAgents) out.push('.github/agents/');
|
|
36
|
+
if (detected.hasCopilotInstructions) out.push('.github/copilot-instructions.md');
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const STACK_SIGNATURES = [
|
|
5
|
+
{ id: 'node-ts', label: 'Node + TypeScript', files: ['tsconfig.json', 'package.json'] },
|
|
6
|
+
{ id: 'node', label: 'Node', files: ['package.json'] },
|
|
7
|
+
{ id: 'python', label: 'Python', files: ['pyproject.toml', 'requirements.txt', 'setup.py'] },
|
|
8
|
+
{ id: 'dotnet', label: '.NET', glob: /\.(csproj|fsproj|sln)$/i },
|
|
9
|
+
{ id: 'rust', label: 'Rust', files: ['Cargo.toml'] },
|
|
10
|
+
{ id: 'go', label: 'Go', files: ['go.mod'] },
|
|
11
|
+
{ id: 'kotlin-android', label: 'Kotlin / Android', files: ['build.gradle.kts', 'settings.gradle.kts'] },
|
|
12
|
+
{ id: 'gradle', label: 'Gradle (JVM)', files: ['build.gradle', 'settings.gradle'] },
|
|
13
|
+
{ id: 'maven', label: 'Maven (JVM)', files: ['pom.xml'] },
|
|
14
|
+
{ id: 'ruby', label: 'Ruby', files: ['Gemfile'] },
|
|
15
|
+
{ id: 'php', label: 'PHP', files: ['composer.json'] },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function detect(cwd) {
|
|
19
|
+
const entries = safeRead(cwd);
|
|
20
|
+
const names = new Set(entries);
|
|
21
|
+
const stacks = [];
|
|
22
|
+
|
|
23
|
+
for (const sig of STACK_SIGNATURES) {
|
|
24
|
+
if (sig.files && sig.files.some((f) => names.has(f))) stacks.push(sig);
|
|
25
|
+
else if (sig.glob && entries.some((n) => sig.glob.test(n))) stacks.push(sig);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const hasGit = names.has('.git');
|
|
29
|
+
const hasGithub = names.has('.github');
|
|
30
|
+
const hasAgents = existsSync(join(cwd, '.github', 'agents'));
|
|
31
|
+
const hasCopilotInstructions = existsSync(join(cwd, '.github', 'copilot-instructions.md'));
|
|
32
|
+
const projectName = guessName(cwd, names);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
cwd,
|
|
36
|
+
projectName,
|
|
37
|
+
hasGit,
|
|
38
|
+
hasGithub,
|
|
39
|
+
hasAgents,
|
|
40
|
+
hasCopilotInstructions,
|
|
41
|
+
isBrownfield: stacks.length > 0,
|
|
42
|
+
stacks,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function safeRead(cwd) {
|
|
47
|
+
try {
|
|
48
|
+
return readdirSync(cwd);
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function guessName(cwd, names) {
|
|
55
|
+
if (names.has('package.json')) {
|
|
56
|
+
try {
|
|
57
|
+
const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
58
|
+
if (pkg?.name) return pkg.name;
|
|
59
|
+
} catch {
|
|
60
|
+
/* ignore */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return basename(cwd);
|
|
64
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
|
|
3
|
+
const COST_MODES = [
|
|
4
|
+
{ title: 'Premium (default)', value: 'premium', description: 'Sonnet/Opus/Codex. Best quality, real cost.' },
|
|
5
|
+
{ title: 'Cheap (0x)', value: 'cheap', description: 'GPT-4.1 / GPT-4o / GPT-5 mini. Free tier-friendly.' },
|
|
6
|
+
{ title: 'Mixed', value: 'mixed', description: 'Premium Coder + Reviewer, cheap everything else.' },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export async function interview(detected, args) {
|
|
10
|
+
if (args.yes) return defaults(detected);
|
|
11
|
+
|
|
12
|
+
const onCancel = () => {
|
|
13
|
+
throw new Error('Interview cancelled. Nothing was written.');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const answers = await prompts(
|
|
17
|
+
[
|
|
18
|
+
{
|
|
19
|
+
type: 'text',
|
|
20
|
+
name: 'projectName',
|
|
21
|
+
message: 'Project name',
|
|
22
|
+
initial: detected.projectName,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: 'text',
|
|
26
|
+
name: 'oneLiner',
|
|
27
|
+
message: 'One-line description (becomes PROJECT.md vision)',
|
|
28
|
+
initial: '',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'list',
|
|
32
|
+
name: 'stack',
|
|
33
|
+
message: 'Tech stack (comma separated)',
|
|
34
|
+
initial: detected.stacks.map((s) => s.label).join(', '),
|
|
35
|
+
separator: ',',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'list',
|
|
39
|
+
name: 'frameworks',
|
|
40
|
+
message: 'Frameworks / key libraries (comma separated, optional)',
|
|
41
|
+
initial: '',
|
|
42
|
+
separator: ',',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'text',
|
|
46
|
+
name: 'goals',
|
|
47
|
+
message: 'Primary goal of this project (one sentence)',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
name: 'constraints',
|
|
52
|
+
message: 'Hard constraints (perf, deps, deploy, compliance — one sentence, optional)',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'select',
|
|
56
|
+
name: 'costMode',
|
|
57
|
+
message: 'Agent cost mode',
|
|
58
|
+
choices: COST_MODES,
|
|
59
|
+
initial: 0,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'confirm',
|
|
63
|
+
name: 'snark',
|
|
64
|
+
message: 'Include the snarky persona block in copilot-instructions.md?',
|
|
65
|
+
initial: true,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
{ onCancel },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return normalize({ ...defaults(detected), ...answers });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function defaults(detected) {
|
|
75
|
+
return {
|
|
76
|
+
projectName: detected.projectName,
|
|
77
|
+
oneLiner: '',
|
|
78
|
+
stack: detected.stacks.map((s) => s.label),
|
|
79
|
+
frameworks: [],
|
|
80
|
+
goals: '',
|
|
81
|
+
constraints: '',
|
|
82
|
+
costMode: 'premium',
|
|
83
|
+
snark: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalize(a) {
|
|
88
|
+
const trim = (s) => (typeof s === 'string' ? s.trim() : s);
|
|
89
|
+
return {
|
|
90
|
+
...a,
|
|
91
|
+
projectName: trim(a.projectName),
|
|
92
|
+
oneLiner: trim(a.oneLiner),
|
|
93
|
+
goals: trim(a.goals),
|
|
94
|
+
constraints: trim(a.constraints),
|
|
95
|
+
stack: (a.stack || []).map(trim).filter(Boolean),
|
|
96
|
+
frameworks: (a.frameworks || []).map(trim).filter(Boolean),
|
|
97
|
+
};
|
|
98
|
+
}
|