create-battle-plan 1.0.0 → 1.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/bin/cli.js +259 -81
- package/package.json +1 -1
- package/template/gitignore +4 -0
package/bin/cli.js
CHANGED
|
@@ -3,31 +3,16 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
|
+
const os = require('os');
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
input: process.stdin,
|
|
9
|
-
output: process.stdout,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
function ask(question) {
|
|
13
|
-
return new Promise((resolve) => {
|
|
14
|
-
rl.question(question, (answer) => resolve(answer.trim()));
|
|
15
|
-
});
|
|
16
|
-
}
|
|
8
|
+
// ── Helpers ──────────────────────────────────────────────
|
|
17
9
|
|
|
18
10
|
function slugify(text) {
|
|
19
|
-
return text
|
|
20
|
-
.toLowerCase()
|
|
21
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
22
|
-
.replace(/^-|-$/g, '');
|
|
11
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
23
12
|
}
|
|
24
13
|
|
|
25
14
|
function metricKey(text) {
|
|
26
|
-
return text
|
|
27
|
-
.trim()
|
|
28
|
-
.toLowerCase()
|
|
29
|
-
.replace(/[^a-z0-9]+/g, '_')
|
|
30
|
-
.replace(/^_|_$/g, '');
|
|
15
|
+
return text.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
|
|
31
16
|
}
|
|
32
17
|
|
|
33
18
|
function capitalize(text) {
|
|
@@ -47,13 +32,210 @@ function copyDir(src, dest) {
|
|
|
47
32
|
}
|
|
48
33
|
}
|
|
49
34
|
|
|
35
|
+
function shortPath(p) {
|
|
36
|
+
const home = os.homedir();
|
|
37
|
+
if (p === home) return '~';
|
|
38
|
+
if (p.startsWith(home + '/')) return '~/' + p.slice(home.length + 1);
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Colors ───────────────────────────────────────────────
|
|
43
|
+
|
|
50
44
|
const BOLD = '\x1b[1m';
|
|
51
45
|
const DIM = '\x1b[2m';
|
|
52
46
|
const GREEN = '\x1b[32m';
|
|
53
47
|
const CYAN = '\x1b[36m';
|
|
54
48
|
const YELLOW = '\x1b[33m';
|
|
55
49
|
const WHITE = '\x1b[37m';
|
|
50
|
+
const INVERSE = '\x1b[7m';
|
|
56
51
|
const RESET = '\x1b[0m';
|
|
52
|
+
const CLEAR_LINE = '\x1b[2K';
|
|
53
|
+
const HIDE_CURSOR = '\x1b[?25l';
|
|
54
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
55
|
+
|
|
56
|
+
// ── Simple question (readline) ───────────────────────────
|
|
57
|
+
|
|
58
|
+
let rl;
|
|
59
|
+
|
|
60
|
+
function initReadline() {
|
|
61
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function closeReadline() {
|
|
65
|
+
if (rl) { rl.close(); rl = null; }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function ask(question) {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Interactive folder picker (raw mode) ─────────────────
|
|
75
|
+
|
|
76
|
+
function getDirs(dir) {
|
|
77
|
+
try {
|
|
78
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
79
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith('.'))
|
|
80
|
+
.map((e) => e.name)
|
|
81
|
+
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
|
|
82
|
+
} catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function pickFolder(projectSlug) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
// Pause readline so we can use raw mode
|
|
90
|
+
closeReadline();
|
|
91
|
+
|
|
92
|
+
let cwd = process.cwd();
|
|
93
|
+
let selected = 0;
|
|
94
|
+
let mode = 'browse'; // 'browse' or 'input'
|
|
95
|
+
let inputBuffer = '';
|
|
96
|
+
|
|
97
|
+
function getOptions() {
|
|
98
|
+
const dirs = getDirs(cwd);
|
|
99
|
+
const options = [];
|
|
100
|
+
options.push({ label: `${GREEN}+ Create new folder here${RESET}`, action: 'create' });
|
|
101
|
+
options.push({ label: `${CYAN}» Install here as ${BOLD}${projectSlug}/${RESET}`, action: 'here' });
|
|
102
|
+
if (path.dirname(cwd) !== cwd) {
|
|
103
|
+
options.push({ label: `${DIM}../${RESET} ${DIM}(up)${RESET}`, action: 'up' });
|
|
104
|
+
}
|
|
105
|
+
for (const d of dirs) {
|
|
106
|
+
options.push({ label: ` ${d}/`, action: 'enter', dir: d });
|
|
107
|
+
}
|
|
108
|
+
return options;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function render() {
|
|
112
|
+
const options = getOptions();
|
|
113
|
+
const display = shortPath(cwd);
|
|
114
|
+
|
|
115
|
+
// Move cursor up to clear previous render
|
|
116
|
+
let output = '';
|
|
117
|
+
|
|
118
|
+
if (mode === 'input') {
|
|
119
|
+
output += `${CLEAR_LINE}\r${DIM}[6/6]${RESET} ${BOLD}Folder name:${RESET} ${inputBuffer}\x1b[K`;
|
|
120
|
+
process.stdout.write(output);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
output += `\x1b[H\x1b[2J`; // clear screen
|
|
125
|
+
output += `\n`;
|
|
126
|
+
output += `${DIM}[6/6]${RESET} ${BOLD}Where do you want to install it?${RESET}\n`;
|
|
127
|
+
output += `${DIM} ${display}${RESET}\n`;
|
|
128
|
+
output += `\n`;
|
|
129
|
+
output += `${DIM} ↑↓ navigate · enter select · q cancel${RESET}\n`;
|
|
130
|
+
output += `\n`;
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < options.length; i++) {
|
|
133
|
+
if (i === selected) {
|
|
134
|
+
output += ` ${INVERSE} › ${options[i].label} ${RESET}\n`;
|
|
135
|
+
} else {
|
|
136
|
+
output += ` ${options[i].label}\n`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
process.stdout.write(output);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function handleBrowseKey(key) {
|
|
144
|
+
const options = getOptions();
|
|
145
|
+
|
|
146
|
+
if (key === '\x1b[A' || key === 'k') {
|
|
147
|
+
// Up
|
|
148
|
+
selected = Math.max(0, selected - 1);
|
|
149
|
+
render();
|
|
150
|
+
} else if (key === '\x1b[B' || key === 'j') {
|
|
151
|
+
// Down
|
|
152
|
+
selected = Math.min(options.length - 1, selected + 1);
|
|
153
|
+
render();
|
|
154
|
+
} else if (key === '\r' || key === '\n') {
|
|
155
|
+
// Enter
|
|
156
|
+
const opt = options[selected];
|
|
157
|
+
if (opt.action === 'create') {
|
|
158
|
+
mode = 'input';
|
|
159
|
+
inputBuffer = projectSlug;
|
|
160
|
+
process.stdout.write(`\x1b[H\x1b[2J`);
|
|
161
|
+
process.stdout.write(`\n`);
|
|
162
|
+
process.stdout.write(`${DIM}[6/6]${RESET} ${BOLD}Folder name:${RESET} ${inputBuffer}`);
|
|
163
|
+
} else if (opt.action === 'here') {
|
|
164
|
+
finish(path.join(cwd, projectSlug));
|
|
165
|
+
} else if (opt.action === 'up') {
|
|
166
|
+
cwd = path.dirname(cwd);
|
|
167
|
+
selected = 0;
|
|
168
|
+
render();
|
|
169
|
+
} else if (opt.action === 'enter') {
|
|
170
|
+
cwd = path.join(cwd, opt.dir);
|
|
171
|
+
selected = 0;
|
|
172
|
+
render();
|
|
173
|
+
}
|
|
174
|
+
} else if (key === 'q' || key === '\x03') {
|
|
175
|
+
// q or ctrl-c
|
|
176
|
+
cleanup();
|
|
177
|
+
process.stdout.write(SHOW_CURSOR);
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function handleInputKey(key) {
|
|
183
|
+
if (key === '\r' || key === '\n') {
|
|
184
|
+
// Confirm
|
|
185
|
+
if (inputBuffer.length > 0) {
|
|
186
|
+
finish(path.join(cwd, inputBuffer));
|
|
187
|
+
}
|
|
188
|
+
} else if (key === '\x7f' || key === '\b') {
|
|
189
|
+
// Backspace
|
|
190
|
+
inputBuffer = inputBuffer.slice(0, -1);
|
|
191
|
+
process.stdout.write(`\r${CLEAR_LINE}`);
|
|
192
|
+
process.stdout.write(`${DIM}[6/6]${RESET} ${BOLD}Folder name:${RESET} ${inputBuffer}`);
|
|
193
|
+
} else if (key === '\x1b' || key === '\x03') {
|
|
194
|
+
// Escape or ctrl-c → back to browse
|
|
195
|
+
mode = 'browse';
|
|
196
|
+
render();
|
|
197
|
+
} else if (key.length === 1 && key.charCodeAt(0) >= 32) {
|
|
198
|
+
inputBuffer += key;
|
|
199
|
+
process.stdout.write(key);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cleanup() {
|
|
204
|
+
process.stdin.setRawMode(false);
|
|
205
|
+
process.stdin.removeListener('data', onData);
|
|
206
|
+
process.stdout.write(SHOW_CURSOR);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function finish(dir) {
|
|
210
|
+
cleanup();
|
|
211
|
+
process.stdout.write(`\x1b[H\x1b[2J`);
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(`${DIM}[6/6]${RESET} ${BOLD}Location:${RESET} ${shortPath(dir)}`);
|
|
214
|
+
console.log('');
|
|
215
|
+
resolve(dir);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function onData(data) {
|
|
219
|
+
const key = data.toString();
|
|
220
|
+
|
|
221
|
+
// Handle multi-byte escape sequences
|
|
222
|
+
if (mode === 'browse') {
|
|
223
|
+
handleBrowseKey(key);
|
|
224
|
+
} else {
|
|
225
|
+
handleInputKey(key);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
process.stdout.write(HIDE_CURSOR);
|
|
230
|
+
process.stdin.setRawMode(true);
|
|
231
|
+
process.stdin.resume();
|
|
232
|
+
process.stdin.on('data', onData);
|
|
233
|
+
|
|
234
|
+
render();
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Banner & Diagram ─────────────────────────────────────
|
|
57
239
|
|
|
58
240
|
function banner() {
|
|
59
241
|
console.log('');
|
|
@@ -72,10 +254,9 @@ function banner() {
|
|
|
72
254
|
console.log('');
|
|
73
255
|
}
|
|
74
256
|
|
|
75
|
-
function cascadeDiagram(domains
|
|
257
|
+
function cascadeDiagram(domains) {
|
|
76
258
|
const domainStr = domains.slice(0, 3).join(' ');
|
|
77
259
|
const dots = domains.length > 3 ? ' ...' : '';
|
|
78
|
-
const metricStr = metrics.slice(0, 3).map((m) => metricKey(m)).join(', ');
|
|
79
260
|
|
|
80
261
|
console.log(`${DIM} Your cascade:${RESET}`);
|
|
81
262
|
console.log('');
|
|
@@ -91,15 +272,34 @@ function cascadeDiagram(domains, metrics) {
|
|
|
91
272
|
console.log('');
|
|
92
273
|
}
|
|
93
274
|
|
|
275
|
+
// ── Domain suggestions ───────────────────────────────────
|
|
276
|
+
|
|
277
|
+
function suggestDomains(desc) {
|
|
278
|
+
const d = desc.toLowerCase();
|
|
279
|
+
const s = [];
|
|
280
|
+
if (/market|customer|user|audience|segment|icp/.test(d)) s.push('market');
|
|
281
|
+
if (/valid|test|hypothes|experiment|interview/.test(d)) s.push('validation');
|
|
282
|
+
if (/strat|position|compete|pricing|business/.test(d)) s.push('strategy');
|
|
283
|
+
if (/research|learn|study|paper|domain/.test(d)) s.push('research');
|
|
284
|
+
if (/content|write|blog|newsletter|social/.test(d)) s.push('content');
|
|
285
|
+
if (/logist|ops|supply|shipping|fulfil/.test(d)) s.push('logistics');
|
|
286
|
+
if (/product|feature|build|ship|release/.test(d)) s.push('product');
|
|
287
|
+
if (/sales|outreach|pipeline|deal|close/.test(d)) s.push('sales');
|
|
288
|
+
if (/fund|invest|pitch|raise|capital/.test(d)) s.push('fundraising');
|
|
289
|
+
if (s.length === 0) s.push('market', 'validation', 'strategy', 'research');
|
|
290
|
+
return s.join(', ');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Main ─────────────────────────────────────────────────
|
|
294
|
+
|
|
94
295
|
async function main() {
|
|
95
296
|
banner();
|
|
96
297
|
|
|
298
|
+
initReadline();
|
|
299
|
+
|
|
97
300
|
// Question 1: Project name
|
|
98
301
|
const projectName = await ask(`${DIM}[1/6]${RESET} ${BOLD}What's your project in one sentence?${RESET}\n> `);
|
|
99
|
-
if (!projectName) {
|
|
100
|
-
console.log('Project name is required.');
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
302
|
+
if (!projectName) { console.log('Project name is required.'); process.exit(1); }
|
|
103
303
|
console.log('');
|
|
104
304
|
|
|
105
305
|
// Question 2: Time horizon
|
|
@@ -112,22 +312,16 @@ async function main() {
|
|
|
112
312
|
const metricsRaw = await ask(
|
|
113
313
|
`${DIM}[3/6]${RESET} ${BOLD}What are the 3-5 key metrics you want to track?${RESET} ${DIM}(comma-separated, e.g., "outreach sent, calls booked, LOIs signed")${RESET}\n> `
|
|
114
314
|
);
|
|
115
|
-
if (!metricsRaw) {
|
|
116
|
-
console.log('At least one metric is required.');
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
315
|
+
if (!metricsRaw) { console.log('At least one metric is required.'); process.exit(1); }
|
|
119
316
|
const metrics = metricsRaw.split(',').map((m) => m.trim()).filter(Boolean);
|
|
120
317
|
console.log('');
|
|
121
318
|
|
|
122
319
|
// Question 4: Domains
|
|
123
|
-
const
|
|
320
|
+
const suggested = suggestDomains(projectName);
|
|
124
321
|
const domainsRaw = await ask(
|
|
125
|
-
`${DIM}[4/6]${RESET} ${BOLD}What domains does your work cover?${RESET} ${DIM}(comma-separated)\nSuggested based on your project: ${
|
|
322
|
+
`${DIM}[4/6]${RESET} ${BOLD}What domains does your work cover?${RESET} ${DIM}(comma-separated)\nSuggested based on your project: ${suggested}${RESET}\n> `
|
|
126
323
|
);
|
|
127
|
-
if (!domainsRaw) {
|
|
128
|
-
console.log('At least one domain is required.');
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
324
|
+
if (!domainsRaw) { console.log('At least one domain is required.'); process.exit(1); }
|
|
131
325
|
const domains = domainsRaw.split(',').map((d) => d.trim().toLowerCase()).filter(Boolean);
|
|
132
326
|
console.log('');
|
|
133
327
|
|
|
@@ -143,30 +337,37 @@ async function main() {
|
|
|
143
337
|
: [];
|
|
144
338
|
console.log('');
|
|
145
339
|
|
|
146
|
-
// Question 6:
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
340
|
+
// Question 6: Interactive folder picker
|
|
341
|
+
const projectSlug = slugify(projectName) || 'my-battle-plan';
|
|
342
|
+
const targetDir = await pickFolder(projectSlug);
|
|
343
|
+
|
|
344
|
+
// Re-init readline for any future questions
|
|
345
|
+
initReadline();
|
|
346
|
+
closeReadline();
|
|
153
347
|
|
|
154
|
-
|
|
348
|
+
// ── Scaffold ─────────────────────────────────────────
|
|
155
349
|
|
|
156
|
-
// --- Scaffold ---
|
|
157
350
|
console.log(`${DIM} ─────────────────────────────${RESET}`);
|
|
158
351
|
console.log('');
|
|
159
352
|
console.log(`${CYAN} Scaffolding...${RESET}`);
|
|
160
353
|
console.log('');
|
|
161
354
|
|
|
162
355
|
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
163
|
-
console.log(`${YELLOW}Warning: ${targetDir} already exists and is not empty.${RESET}`);
|
|
356
|
+
console.log(`${YELLOW} Warning: ${shortPath(targetDir)} already exists and is not empty.${RESET}`);
|
|
164
357
|
process.exit(1);
|
|
165
358
|
}
|
|
166
359
|
|
|
167
360
|
// Copy template
|
|
168
361
|
const templateDir = path.join(__dirname, '..', 'template');
|
|
169
362
|
copyDir(templateDir, targetDir);
|
|
363
|
+
|
|
364
|
+
// npm strips .gitignore from packages, so we ship it as 'gitignore' and rename
|
|
365
|
+
const gitignoreSrc = path.join(targetDir, 'gitignore');
|
|
366
|
+
const gitignoreDest = path.join(targetDir, '.gitignore');
|
|
367
|
+
if (fs.existsSync(gitignoreSrc) && !fs.existsSync(gitignoreDest)) {
|
|
368
|
+
fs.renameSync(gitignoreSrc, gitignoreDest);
|
|
369
|
+
}
|
|
370
|
+
|
|
170
371
|
console.log(`${DIM} + CLAUDE.md (system prompt)${RESET}`);
|
|
171
372
|
console.log(`${DIM} + tools/ (verification scripts)${RESET}`);
|
|
172
373
|
console.log(`${DIM} + .claude/commands/ (slash commands)${RESET}`);
|
|
@@ -338,16 +539,8 @@ ${peopleSections}
|
|
|
338
539
|
fs.writeFileSync(
|
|
339
540
|
path.join(targetDir, '.battle-plan-onboarding.json'),
|
|
340
541
|
JSON.stringify(
|
|
341
|
-
{
|
|
342
|
-
|
|
343
|
-
horizon,
|
|
344
|
-
metrics,
|
|
345
|
-
domains,
|
|
346
|
-
people,
|
|
347
|
-
installed_at: today,
|
|
348
|
-
},
|
|
349
|
-
null,
|
|
350
|
-
2
|
|
542
|
+
{ project_name: projectName, horizon, metrics, domains, people, installed_at: today },
|
|
543
|
+
null, 2
|
|
351
544
|
) + '\n'
|
|
352
545
|
);
|
|
353
546
|
|
|
@@ -365,21 +558,26 @@ ${peopleSections}
|
|
|
365
558
|
execSync('git commit -m "Initial battle plan scaffold"', {
|
|
366
559
|
cwd: targetDir,
|
|
367
560
|
stdio: 'pipe',
|
|
368
|
-
env: {
|
|
561
|
+
env: {
|
|
562
|
+
...process.env,
|
|
563
|
+
GIT_AUTHOR_NAME: 'Battle Plan', GIT_AUTHOR_EMAIL: 'noreply@battleplan.dev',
|
|
564
|
+
GIT_COMMITTER_NAME: 'Battle Plan', GIT_COMMITTER_EMAIL: 'noreply@battleplan.dev',
|
|
565
|
+
},
|
|
369
566
|
});
|
|
370
567
|
console.log(`${DIM} + git repo initialized${RESET}`);
|
|
371
568
|
} catch {
|
|
372
569
|
// git not available or failed — not critical
|
|
373
570
|
}
|
|
374
571
|
|
|
375
|
-
//
|
|
572
|
+
// ── Done ───────────────────────────────────────────
|
|
573
|
+
|
|
376
574
|
console.log('');
|
|
377
575
|
console.log(`${DIM} ─────────────────────────────${RESET}`);
|
|
378
576
|
console.log('');
|
|
379
577
|
console.log(`${GREEN}${BOLD} Ready.${RESET}`);
|
|
380
578
|
console.log('');
|
|
381
579
|
console.log(`${BOLD} Project:${RESET} ${projectName}`);
|
|
382
|
-
console.log(`${BOLD} Location:${RESET} ${targetDir}`);
|
|
580
|
+
console.log(`${BOLD} Location:${RESET} ${shortPath(targetDir)}`);
|
|
383
581
|
console.log(`${BOLD} Horizon:${RESET} ${horizon || 'not set'}`);
|
|
384
582
|
console.log(`${BOLD} Metrics:${RESET} ${metrics.join(', ')}`);
|
|
385
583
|
console.log(`${BOLD} Domains:${RESET} ${domains.join(', ')}`);
|
|
@@ -388,13 +586,13 @@ ${peopleSections}
|
|
|
388
586
|
}
|
|
389
587
|
console.log('');
|
|
390
588
|
|
|
391
|
-
cascadeDiagram(domains
|
|
589
|
+
cascadeDiagram(domains);
|
|
392
590
|
|
|
393
591
|
console.log(`${DIM} ─────────────────────────────${RESET}`);
|
|
394
592
|
console.log('');
|
|
395
593
|
console.log(`${CYAN}${BOLD} Next steps:${RESET}`);
|
|
396
594
|
console.log('');
|
|
397
|
-
console.log(` ${BOLD}cd ${path.relative(process.cwd(), targetDir)}${RESET}`);
|
|
595
|
+
console.log(` ${BOLD}cd ${path.relative(process.cwd(), targetDir) || '.'}${RESET}`);
|
|
398
596
|
console.log(` ${BOLD}claude${RESET}`);
|
|
399
597
|
console.log('');
|
|
400
598
|
console.log(` Then type ${GREEN}${BOLD}/good-morning${RESET} to start`);
|
|
@@ -407,28 +605,8 @@ ${peopleSections}
|
|
|
407
605
|
console.log('');
|
|
408
606
|
}
|
|
409
607
|
|
|
410
|
-
function suggestDomains(projectDescription) {
|
|
411
|
-
const desc = projectDescription.toLowerCase();
|
|
412
|
-
const suggestions = [];
|
|
413
|
-
|
|
414
|
-
if (/market|customer|user|audience|segment|icp/.test(desc)) suggestions.push('market');
|
|
415
|
-
if (/valid|test|hypothes|experiment|interview/.test(desc)) suggestions.push('validation');
|
|
416
|
-
if (/strat|position|compete|pricing|business/.test(desc)) suggestions.push('strategy');
|
|
417
|
-
if (/research|learn|study|paper|domain/.test(desc)) suggestions.push('research');
|
|
418
|
-
if (/content|write|blog|newsletter|social/.test(desc)) suggestions.push('content');
|
|
419
|
-
if (/logist|ops|supply|shipping|fulfil/.test(desc)) suggestions.push('logistics');
|
|
420
|
-
if (/product|feature|build|ship|release/.test(desc)) suggestions.push('product');
|
|
421
|
-
if (/sales|outreach|pipeline|deal|close/.test(desc)) suggestions.push('sales');
|
|
422
|
-
if (/fund|invest|pitch|raise|capital/.test(desc)) suggestions.push('fundraising');
|
|
423
|
-
|
|
424
|
-
if (suggestions.length === 0) {
|
|
425
|
-
suggestions.push('market', 'validation', 'strategy', 'research');
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return suggestions.join(', ');
|
|
429
|
-
}
|
|
430
|
-
|
|
431
608
|
main().catch((err) => {
|
|
609
|
+
process.stdout.write(SHOW_CURSOR);
|
|
432
610
|
console.error(err);
|
|
433
611
|
process.exit(1);
|
|
434
612
|
});
|
package/package.json
CHANGED