@haystackeditor/cli 0.10.1 → 0.10.3
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/dist/commands/config.d.ts +1 -5
- package/dist/commands/config.js +2 -56
- package/dist/commands/init.js +0 -30
- package/dist/commands/skills.js +3 -8
- package/dist/commands/status.js +0 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -58
- package/dist/types.d.ts +4 -4
- package/dist/types.js +4 -4
- package/dist/utils/secrets.js +1 -2
- package/package.json +1 -1
- package/dist/assets/skills/prepare-haystack.md +0 -323
- package/dist/assets/skills/secrets.md +0 -164
- package/dist/assets/skills/setup-external-sandbox.md +0 -243
- package/dist/assets/skills/setup-haystack.md +0 -639
- package/dist/assets/templates/CLAUDE.md.snippet +0 -42
- package/dist/assets/templates/haystack.yml +0 -193
- package/dist/commands/secrets.d.ts +0 -33
- package/dist/commands/secrets.js +0 -216
- package/dist/tools/fixtures.d.ts +0 -38
- package/dist/tools/fixtures.js +0 -199
- package/dist/tools/setup.d.ts +0 -43
- package/dist/tools/setup.js +0 -597
- package/dist/utils/skill.d.ts +0 -10
- package/dist/utils/skill.js +0 -1671
package/dist/tools/setup.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { HaystackConfig } from '../types.js';
|
|
2
|
-
import { ProjectDetection, DetectedService } from './detect.js';
|
|
3
|
-
/**
|
|
4
|
-
* Interactive setup flow for .haystack.json
|
|
5
|
-
*
|
|
6
|
-
* The MCP tool will return questions one at a time.
|
|
7
|
-
* The agent asks the user, collects answers, and calls back
|
|
8
|
-
* until the config is complete.
|
|
9
|
-
*/
|
|
10
|
-
export interface SetupQuestion {
|
|
11
|
-
id: string;
|
|
12
|
-
question: string;
|
|
13
|
-
type: 'text' | 'select' | 'multiselect' | 'confirm';
|
|
14
|
-
options?: string[];
|
|
15
|
-
default?: string;
|
|
16
|
-
hint?: string;
|
|
17
|
-
}
|
|
18
|
-
export interface SetupState {
|
|
19
|
-
step: 'project_name' | 'is_monorepo' | 'services_list' | 'service_details' | 'dev_command' | 'dev_port' | 'dev_ready_pattern' | 'verification_commands' | 'auth_needs_signin' | 'auth_strategy' | 'auth_env' | 'auth_token_type' | 'auth_token_name' | 'auth_token_secret' | 'db_needs_database' | 'db_strategy' | 'db_env' | 'db_seed_command' | 'complete';
|
|
20
|
-
answers: Record<string, unknown>;
|
|
21
|
-
config: Partial<HaystackConfig>;
|
|
22
|
-
/** For monorepo setup: which service we're configuring */
|
|
23
|
-
currentServiceIndex?: number;
|
|
24
|
-
/** For monorepo setup: list of services to configure */
|
|
25
|
-
serviceNames?: string[];
|
|
26
|
-
/** Auto-detected project info */
|
|
27
|
-
detection?: ProjectDetection;
|
|
28
|
-
/** Auto-detected services (for smart defaults) */
|
|
29
|
-
detectedServices?: DetectedService[];
|
|
30
|
-
}
|
|
31
|
-
export declare function createInitialState(rootDir?: string): SetupState;
|
|
32
|
-
/**
|
|
33
|
-
* Get a default value based on auto-detection.
|
|
34
|
-
*/
|
|
35
|
-
export declare function getDetectedDefault(state: SetupState, questionId: string): string | undefined;
|
|
36
|
-
export declare function getNextQuestion(state: SetupState): SetupQuestion | null;
|
|
37
|
-
export declare function advanceStep(state: SetupState): SetupState;
|
|
38
|
-
/**
|
|
39
|
-
* Build the final .haystack.json config from collected answers.
|
|
40
|
-
* This generates the schema that sandbox.py expects.
|
|
41
|
-
*/
|
|
42
|
-
export declare function buildConfig(state: SetupState): HaystackConfig;
|
|
43
|
-
export declare function configToJson(config: HaystackConfig): string;
|
package/dist/tools/setup.js
DELETED
|
@@ -1,597 +0,0 @@
|
|
|
1
|
-
import { detectProject } from './detect.js';
|
|
2
|
-
const QUESTIONS = {
|
|
3
|
-
project_name: {
|
|
4
|
-
id: 'project_name',
|
|
5
|
-
question: "What's the name of your project?",
|
|
6
|
-
type: 'text',
|
|
7
|
-
hint: 'This is just for display purposes in Haystack',
|
|
8
|
-
},
|
|
9
|
-
is_monorepo: {
|
|
10
|
-
id: 'is_monorepo',
|
|
11
|
-
question: 'Is this a monorepo with multiple services?',
|
|
12
|
-
type: 'select',
|
|
13
|
-
options: [
|
|
14
|
-
'no - Single app/service (frontend only)',
|
|
15
|
-
'yes - Multiple services (frontend + backend workers, APIs, etc.)',
|
|
16
|
-
],
|
|
17
|
-
hint: 'Monorepos can have different dev servers for each service',
|
|
18
|
-
},
|
|
19
|
-
services_list: {
|
|
20
|
-
id: 'services_list',
|
|
21
|
-
question: 'What services does your monorepo have? (comma-separated)',
|
|
22
|
-
type: 'text',
|
|
23
|
-
default: 'frontend, api',
|
|
24
|
-
hint: 'e.g., frontend, api-worker, auth-worker',
|
|
25
|
-
},
|
|
26
|
-
service_root: {
|
|
27
|
-
id: 'service_root',
|
|
28
|
-
question: 'What directory is this service in?',
|
|
29
|
-
type: 'text',
|
|
30
|
-
default: './',
|
|
31
|
-
hint: 'Relative to repo root, e.g., ./, infra/api-worker',
|
|
32
|
-
},
|
|
33
|
-
service_command: {
|
|
34
|
-
id: 'service_command',
|
|
35
|
-
question: 'What command starts this service?',
|
|
36
|
-
type: 'text',
|
|
37
|
-
default: 'pnpm dev',
|
|
38
|
-
hint: 'e.g., pnpm dev, pnpm wrangler dev --local',
|
|
39
|
-
},
|
|
40
|
-
service_port: {
|
|
41
|
-
id: 'service_port',
|
|
42
|
-
question: 'What port does this service run on?',
|
|
43
|
-
type: 'text',
|
|
44
|
-
hint: 'Leave blank for batch jobs that exit after running',
|
|
45
|
-
},
|
|
46
|
-
service_type: {
|
|
47
|
-
id: 'service_type',
|
|
48
|
-
question: 'What type of service is this?',
|
|
49
|
-
type: 'select',
|
|
50
|
-
options: [
|
|
51
|
-
'server - Long-running server (frontend, API, worker)',
|
|
52
|
-
'batch - Runs once and exits (analysis, build, migration)',
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
service_depends_on: {
|
|
56
|
-
id: 'service_depends_on',
|
|
57
|
-
question: 'What other services does this depend on? (comma-separated, leave blank if none)',
|
|
58
|
-
type: 'text',
|
|
59
|
-
hint: 'e.g., api-worker, auth-worker. These will start before this service.',
|
|
60
|
-
},
|
|
61
|
-
dev_command: {
|
|
62
|
-
id: 'dev_command',
|
|
63
|
-
question: 'What command starts your dev server?',
|
|
64
|
-
type: 'text',
|
|
65
|
-
default: 'pnpm dev',
|
|
66
|
-
hint: 'e.g., npm run dev, pnpm dev, yarn start',
|
|
67
|
-
},
|
|
68
|
-
dev_port: {
|
|
69
|
-
id: 'dev_port',
|
|
70
|
-
question: 'What port does your dev server run on?',
|
|
71
|
-
type: 'text',
|
|
72
|
-
default: '3000',
|
|
73
|
-
},
|
|
74
|
-
dev_ready_pattern: {
|
|
75
|
-
id: 'dev_ready_pattern',
|
|
76
|
-
question: 'What text appears in the terminal when the server is ready?',
|
|
77
|
-
type: 'text',
|
|
78
|
-
default: 'Local:',
|
|
79
|
-
hint: 'e.g., "Local:", "ready in", "listening on"',
|
|
80
|
-
},
|
|
81
|
-
verification_commands: {
|
|
82
|
-
id: 'verification_commands',
|
|
83
|
-
question: 'What commands should run to verify changes? (comma-separated)',
|
|
84
|
-
type: 'text',
|
|
85
|
-
default: 'pnpm build, pnpm lint, pnpm tsc --noEmit',
|
|
86
|
-
hint: 'These run during sandbox creation. Results are cached.',
|
|
87
|
-
},
|
|
88
|
-
auth_needs_signin: {
|
|
89
|
-
id: 'auth_needs_signin',
|
|
90
|
-
question: 'Does your app need signed-in user flows for verification?',
|
|
91
|
-
type: 'select',
|
|
92
|
-
options: [
|
|
93
|
-
'no - App works without auth or has an auth bypass env var',
|
|
94
|
-
'yes - Some flows need authenticated state to test properly',
|
|
95
|
-
],
|
|
96
|
-
hint: 'e.g., dashboards, user profiles, or admin features',
|
|
97
|
-
},
|
|
98
|
-
auth_strategy: {
|
|
99
|
-
id: 'auth_strategy',
|
|
100
|
-
question: 'How would you like to handle authentication in sandboxes?',
|
|
101
|
-
type: 'select',
|
|
102
|
-
options: [
|
|
103
|
-
'bypass - Use an env var to skip auth (recommended if your app supports it)',
|
|
104
|
-
'fixture - Mock auth endpoints with fixture data',
|
|
105
|
-
'token - Inject a pre-generated session token (cookie or localStorage)',
|
|
106
|
-
'test_account - Run full login flow with test credentials',
|
|
107
|
-
],
|
|
108
|
-
hint: 'Bypass is simplest. Token injection works for most apps.',
|
|
109
|
-
},
|
|
110
|
-
auth_env: {
|
|
111
|
-
id: 'auth_env',
|
|
112
|
-
question: 'What env var bypasses auth?',
|
|
113
|
-
type: 'text',
|
|
114
|
-
default: 'SKIP_AUTH=true',
|
|
115
|
-
hint: 'Format: VAR_NAME=value (e.g., VITE_SKIP_AUTH=true, NEXT_PUBLIC_SKIP_AUTH=true)',
|
|
116
|
-
},
|
|
117
|
-
auth_token_type: {
|
|
118
|
-
id: 'auth_token_type',
|
|
119
|
-
question: 'Where should the auth token be stored?',
|
|
120
|
-
type: 'select',
|
|
121
|
-
options: [
|
|
122
|
-
'cookie - Session cookie (most common)',
|
|
123
|
-
'localStorage - Browser localStorage',
|
|
124
|
-
'sessionStorage - Browser sessionStorage',
|
|
125
|
-
],
|
|
126
|
-
},
|
|
127
|
-
auth_token_name: {
|
|
128
|
-
id: 'auth_token_name',
|
|
129
|
-
question: "What's the name of the auth token/cookie?",
|
|
130
|
-
type: 'text',
|
|
131
|
-
default: 'session',
|
|
132
|
-
hint: 'e.g., session, auth_token, access_token, Authorization',
|
|
133
|
-
},
|
|
134
|
-
auth_token_secret: {
|
|
135
|
-
id: 'auth_token_secret',
|
|
136
|
-
question: "What secret name should store the token value?",
|
|
137
|
-
type: 'text',
|
|
138
|
-
default: 'AUTH_TOKEN',
|
|
139
|
-
hint: "You'll set the actual value with: haystack secrets set AUTH_TOKEN <your-token>",
|
|
140
|
-
},
|
|
141
|
-
db_needs_database: {
|
|
142
|
-
id: 'db_needs_database',
|
|
143
|
-
question: 'Does your app need a database for verification?',
|
|
144
|
-
type: 'select',
|
|
145
|
-
options: [
|
|
146
|
-
'no - API responses will be mocked with fixtures (recommended)',
|
|
147
|
-
'yes - Need real database queries to test properly',
|
|
148
|
-
],
|
|
149
|
-
hint: 'Fixtures are simpler. Only use a database if you need write operations or complex queries.',
|
|
150
|
-
},
|
|
151
|
-
db_strategy: {
|
|
152
|
-
id: 'db_strategy',
|
|
153
|
-
question: 'How should the database be configured in sandboxes?',
|
|
154
|
-
type: 'select',
|
|
155
|
-
options: [
|
|
156
|
-
'memory - In-memory database (SQLite :memory:)',
|
|
157
|
-
'seed - File-based database with seed data',
|
|
158
|
-
'docker - Spin up database containers (Postgres, MySQL, Redis)',
|
|
159
|
-
],
|
|
160
|
-
hint: 'Memory is simplest. Use Docker for production-like testing.',
|
|
161
|
-
},
|
|
162
|
-
db_env: {
|
|
163
|
-
id: 'db_env',
|
|
164
|
-
question: 'What environment variable sets the database connection?',
|
|
165
|
-
type: 'text',
|
|
166
|
-
default: 'DATABASE_URL=sqlite::memory:',
|
|
167
|
-
hint: 'Format: VAR=value. For SQLite: DATABASE_URL=sqlite::memory: or file:./test.db',
|
|
168
|
-
},
|
|
169
|
-
db_seed_command: {
|
|
170
|
-
id: 'db_seed_command',
|
|
171
|
-
question: 'What command seeds the database? (leave blank if none)',
|
|
172
|
-
type: 'text',
|
|
173
|
-
default: '',
|
|
174
|
-
hint: 'e.g., pnpm db:seed, pnpm prisma db seed, pnpm knex seed:run',
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
export function createInitialState(rootDir = process.cwd()) {
|
|
178
|
-
const detection = detectProject(rootDir);
|
|
179
|
-
return {
|
|
180
|
-
step: 'project_name',
|
|
181
|
-
answers: {},
|
|
182
|
-
config: {},
|
|
183
|
-
detection,
|
|
184
|
-
detectedServices: detection.services,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Get a default value based on auto-detection.
|
|
189
|
-
*/
|
|
190
|
-
export function getDetectedDefault(state, questionId) {
|
|
191
|
-
const detection = state.detection;
|
|
192
|
-
if (!detection)
|
|
193
|
-
return undefined;
|
|
194
|
-
switch (questionId) {
|
|
195
|
-
case 'project_name':
|
|
196
|
-
return detection.suggestions.projectName;
|
|
197
|
-
case 'is_monorepo':
|
|
198
|
-
return detection.isMonorepo
|
|
199
|
-
? 'yes - Multiple services (frontend + backend workers, APIs, etc.)'
|
|
200
|
-
: 'no - Single app/service (frontend only)';
|
|
201
|
-
case 'dev_command':
|
|
202
|
-
return detection.suggestions.devCommand;
|
|
203
|
-
case 'dev_port':
|
|
204
|
-
return String(detection.suggestions.port);
|
|
205
|
-
case 'dev_ready_pattern':
|
|
206
|
-
return detection.suggestions.readyPattern;
|
|
207
|
-
case 'auth_env':
|
|
208
|
-
return detection.suggestions.authBypassEnv || '';
|
|
209
|
-
case 'services_list':
|
|
210
|
-
// Use detected services if available
|
|
211
|
-
if (detection.services.length > 0) {
|
|
212
|
-
return detection.services.map((s) => s.name).join(', ');
|
|
213
|
-
}
|
|
214
|
-
return undefined;
|
|
215
|
-
default:
|
|
216
|
-
// Handle service-specific questions
|
|
217
|
-
if (questionId.startsWith('service_')) {
|
|
218
|
-
const parts = questionId.split('_');
|
|
219
|
-
const serviceName = parts.slice(1, -1).join('_');
|
|
220
|
-
const field = parts[parts.length - 1];
|
|
221
|
-
const detectedService = state.detectedServices?.find((s) => s.name === serviceName);
|
|
222
|
-
if (detectedService) {
|
|
223
|
-
switch (field) {
|
|
224
|
-
case 'root':
|
|
225
|
-
return detectedService.root;
|
|
226
|
-
case 'command':
|
|
227
|
-
return detectedService.framework?.devCommand || 'pnpm dev';
|
|
228
|
-
case 'port':
|
|
229
|
-
return detectedService.framework?.port?.toString();
|
|
230
|
-
case 'type':
|
|
231
|
-
return detectedService.type === 'batch'
|
|
232
|
-
? 'batch - Runs once and exits (analysis, build, migration)'
|
|
233
|
-
: 'server - Long-running server (frontend, API, worker)';
|
|
234
|
-
case 'depends_on':
|
|
235
|
-
return detectedService.depends_on?.join(', ') || '';
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return undefined;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
export function getNextQuestion(state) {
|
|
243
|
-
const applyDefault = (question) => {
|
|
244
|
-
const detectedDefault = getDetectedDefault(state, question.id);
|
|
245
|
-
if (detectedDefault) {
|
|
246
|
-
return { ...question, default: detectedDefault };
|
|
247
|
-
}
|
|
248
|
-
return question;
|
|
249
|
-
};
|
|
250
|
-
switch (state.step) {
|
|
251
|
-
case 'project_name':
|
|
252
|
-
return applyDefault(QUESTIONS.project_name);
|
|
253
|
-
case 'is_monorepo':
|
|
254
|
-
return applyDefault(QUESTIONS.is_monorepo);
|
|
255
|
-
case 'services_list':
|
|
256
|
-
return applyDefault(QUESTIONS.services_list);
|
|
257
|
-
case 'service_details': {
|
|
258
|
-
const serviceNames = state.serviceNames || [];
|
|
259
|
-
const idx = state.currentServiceIndex || 0;
|
|
260
|
-
const serviceName = serviceNames[idx];
|
|
261
|
-
if (!serviceName)
|
|
262
|
-
return null;
|
|
263
|
-
const serviceKey = `service_${serviceName}`;
|
|
264
|
-
if (!state.answers[`${serviceKey}_root`]) {
|
|
265
|
-
const question = {
|
|
266
|
-
...QUESTIONS.service_root,
|
|
267
|
-
id: `${serviceKey}_root`,
|
|
268
|
-
question: `[${serviceName}] ${QUESTIONS.service_root.question}`,
|
|
269
|
-
};
|
|
270
|
-
return applyDefault(question);
|
|
271
|
-
}
|
|
272
|
-
if (!state.answers[`${serviceKey}_command`]) {
|
|
273
|
-
const question = {
|
|
274
|
-
...QUESTIONS.service_command,
|
|
275
|
-
id: `${serviceKey}_command`,
|
|
276
|
-
question: `[${serviceName}] ${QUESTIONS.service_command.question}`,
|
|
277
|
-
};
|
|
278
|
-
return applyDefault(question);
|
|
279
|
-
}
|
|
280
|
-
if (!state.answers[`${serviceKey}_type`]) {
|
|
281
|
-
const question = {
|
|
282
|
-
...QUESTIONS.service_type,
|
|
283
|
-
id: `${serviceKey}_type`,
|
|
284
|
-
question: `[${serviceName}] ${QUESTIONS.service_type.question}`,
|
|
285
|
-
};
|
|
286
|
-
return applyDefault(question);
|
|
287
|
-
}
|
|
288
|
-
const serviceType = state.answers[`${serviceKey}_type`];
|
|
289
|
-
if (serviceType?.startsWith('server') && !state.answers[`${serviceKey}_port`]) {
|
|
290
|
-
const question = {
|
|
291
|
-
...QUESTIONS.service_port,
|
|
292
|
-
id: `${serviceKey}_port`,
|
|
293
|
-
question: `[${serviceName}] ${QUESTIONS.service_port.question}`,
|
|
294
|
-
};
|
|
295
|
-
return applyDefault(question);
|
|
296
|
-
}
|
|
297
|
-
// Ask about dependencies (only for frontend/main services that might proxy to others)
|
|
298
|
-
if (state.answers[`${serviceKey}_depends_on`] === undefined) {
|
|
299
|
-
const question = {
|
|
300
|
-
...QUESTIONS.service_depends_on,
|
|
301
|
-
id: `${serviceKey}_depends_on`,
|
|
302
|
-
question: `[${serviceName}] ${QUESTIONS.service_depends_on.question}`,
|
|
303
|
-
};
|
|
304
|
-
return applyDefault(question);
|
|
305
|
-
}
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
case 'dev_command':
|
|
309
|
-
return applyDefault(QUESTIONS.dev_command);
|
|
310
|
-
case 'dev_port':
|
|
311
|
-
return applyDefault(QUESTIONS.dev_port);
|
|
312
|
-
case 'dev_ready_pattern':
|
|
313
|
-
return applyDefault(QUESTIONS.dev_ready_pattern);
|
|
314
|
-
case 'verification_commands':
|
|
315
|
-
return applyDefault(QUESTIONS.verification_commands);
|
|
316
|
-
case 'auth_needs_signin':
|
|
317
|
-
return applyDefault(QUESTIONS.auth_needs_signin);
|
|
318
|
-
case 'auth_strategy':
|
|
319
|
-
return applyDefault(QUESTIONS.auth_strategy);
|
|
320
|
-
case 'auth_env':
|
|
321
|
-
return applyDefault(QUESTIONS.auth_env);
|
|
322
|
-
case 'auth_token_type':
|
|
323
|
-
return applyDefault(QUESTIONS.auth_token_type);
|
|
324
|
-
case 'auth_token_name':
|
|
325
|
-
return applyDefault(QUESTIONS.auth_token_name);
|
|
326
|
-
case 'auth_token_secret':
|
|
327
|
-
return applyDefault(QUESTIONS.auth_token_secret);
|
|
328
|
-
case 'db_needs_database':
|
|
329
|
-
return applyDefault(QUESTIONS.db_needs_database);
|
|
330
|
-
case 'db_strategy':
|
|
331
|
-
return applyDefault(QUESTIONS.db_strategy);
|
|
332
|
-
case 'db_env':
|
|
333
|
-
return applyDefault(QUESTIONS.db_env);
|
|
334
|
-
case 'db_seed_command':
|
|
335
|
-
return applyDefault(QUESTIONS.db_seed_command);
|
|
336
|
-
case 'complete':
|
|
337
|
-
return null;
|
|
338
|
-
default:
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
export function advanceStep(state) {
|
|
343
|
-
if (state.step === 'project_name') {
|
|
344
|
-
return { ...state, step: 'is_monorepo' };
|
|
345
|
-
}
|
|
346
|
-
if (state.step === 'is_monorepo') {
|
|
347
|
-
const isMonorepo = state.answers.is_monorepo?.startsWith('yes');
|
|
348
|
-
if (isMonorepo) {
|
|
349
|
-
return { ...state, step: 'services_list' };
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
return { ...state, step: 'dev_command' };
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
if (state.step === 'services_list') {
|
|
356
|
-
const servicesStr = state.answers.services_list || 'frontend';
|
|
357
|
-
const serviceNames = servicesStr
|
|
358
|
-
.split(',')
|
|
359
|
-
.map((s) => s.trim())
|
|
360
|
-
.filter(Boolean);
|
|
361
|
-
return {
|
|
362
|
-
...state,
|
|
363
|
-
step: 'service_details',
|
|
364
|
-
serviceNames,
|
|
365
|
-
currentServiceIndex: 0,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
if (state.step === 'service_details') {
|
|
369
|
-
const serviceNames = state.serviceNames || [];
|
|
370
|
-
const currentIdx = state.currentServiceIndex || 0;
|
|
371
|
-
const currentService = serviceNames[currentIdx];
|
|
372
|
-
if (currentService) {
|
|
373
|
-
const serviceKey = `service_${currentService}`;
|
|
374
|
-
const hasRoot = state.answers[`${serviceKey}_root`];
|
|
375
|
-
const hasCommand = state.answers[`${serviceKey}_command`];
|
|
376
|
-
const hasType = state.answers[`${serviceKey}_type`];
|
|
377
|
-
const isServer = state.answers[`${serviceKey}_type`]?.startsWith('server');
|
|
378
|
-
const hasPort = state.answers[`${serviceKey}_port`];
|
|
379
|
-
const hasDependsOn = state.answers[`${serviceKey}_depends_on`] !== undefined;
|
|
380
|
-
if (!hasRoot || !hasCommand || !hasType || (isServer && !hasPort) || !hasDependsOn) {
|
|
381
|
-
return state;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (currentIdx < serviceNames.length - 1) {
|
|
385
|
-
return {
|
|
386
|
-
...state,
|
|
387
|
-
currentServiceIndex: currentIdx + 1,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
return { ...state, step: 'verification_commands' };
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
// Simple transitions
|
|
395
|
-
const simpleTransitions = {
|
|
396
|
-
dev_command: 'dev_port',
|
|
397
|
-
dev_port: 'dev_ready_pattern',
|
|
398
|
-
dev_ready_pattern: 'verification_commands',
|
|
399
|
-
verification_commands: 'auth_needs_signin',
|
|
400
|
-
};
|
|
401
|
-
if (state.step in simpleTransitions) {
|
|
402
|
-
return { ...state, step: simpleTransitions[state.step] };
|
|
403
|
-
}
|
|
404
|
-
// Auth flow branching
|
|
405
|
-
if (state.step === 'auth_needs_signin') {
|
|
406
|
-
const needsSignin = state.answers.auth_needs_signin?.startsWith('yes');
|
|
407
|
-
if (needsSignin) {
|
|
408
|
-
return { ...state, step: 'auth_strategy' };
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
// Still ask about env var bypass in case they have one
|
|
412
|
-
return { ...state, step: 'auth_env' };
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (state.step === 'auth_strategy') {
|
|
416
|
-
const strategy = state.answers.auth_strategy?.split(' - ')[0];
|
|
417
|
-
if (strategy === 'bypass' || strategy === 'fixture') {
|
|
418
|
-
return { ...state, step: 'auth_env' };
|
|
419
|
-
}
|
|
420
|
-
else if (strategy === 'token') {
|
|
421
|
-
return { ...state, step: 'auth_token_type' };
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
// test_account - for now, complete (can expand later)
|
|
425
|
-
return { ...state, step: 'complete' };
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (state.step === 'auth_env') {
|
|
429
|
-
return { ...state, step: 'db_needs_database' };
|
|
430
|
-
}
|
|
431
|
-
if (state.step === 'auth_token_type') {
|
|
432
|
-
return { ...state, step: 'auth_token_name' };
|
|
433
|
-
}
|
|
434
|
-
if (state.step === 'auth_token_name') {
|
|
435
|
-
return { ...state, step: 'auth_token_secret' };
|
|
436
|
-
}
|
|
437
|
-
if (state.step === 'auth_token_secret') {
|
|
438
|
-
return { ...state, step: 'db_needs_database' };
|
|
439
|
-
}
|
|
440
|
-
// Database flow branching
|
|
441
|
-
if (state.step === 'db_needs_database') {
|
|
442
|
-
const needsDb = state.answers.db_needs_database?.startsWith('yes');
|
|
443
|
-
if (needsDb) {
|
|
444
|
-
return { ...state, step: 'db_strategy' };
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
return { ...state, step: 'complete' };
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (state.step === 'db_strategy') {
|
|
451
|
-
return { ...state, step: 'db_env' };
|
|
452
|
-
}
|
|
453
|
-
if (state.step === 'db_env') {
|
|
454
|
-
return { ...state, step: 'db_seed_command' };
|
|
455
|
-
}
|
|
456
|
-
if (state.step === 'db_seed_command') {
|
|
457
|
-
return { ...state, step: 'complete' };
|
|
458
|
-
}
|
|
459
|
-
return { ...state, step: 'complete' };
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Build the final .haystack.json config from collected answers.
|
|
463
|
-
* This generates the schema that sandbox.py expects.
|
|
464
|
-
*/
|
|
465
|
-
export function buildConfig(state) {
|
|
466
|
-
const answers = state.answers;
|
|
467
|
-
const isMonorepo = answers.is_monorepo?.startsWith('yes');
|
|
468
|
-
// Parse verification commands
|
|
469
|
-
const cmdString = answers.verification_commands || 'pnpm build, pnpm lint';
|
|
470
|
-
const commands = cmdString
|
|
471
|
-
.split(',')
|
|
472
|
-
.map((c) => c.trim())
|
|
473
|
-
.filter(Boolean)
|
|
474
|
-
.map((cmd) => {
|
|
475
|
-
// Generate a name from the command
|
|
476
|
-
const name = cmd
|
|
477
|
-
.replace(/^(npm run|pnpm|yarn|bun)\s+/, '')
|
|
478
|
-
.split(' ')[0]
|
|
479
|
-
.replace(/^--/, '');
|
|
480
|
-
return { name, run: cmd };
|
|
481
|
-
});
|
|
482
|
-
// Parse auth env var
|
|
483
|
-
const authEnv = answers.auth_env?.trim();
|
|
484
|
-
const devEnv = {};
|
|
485
|
-
if (authEnv && authEnv.includes('=')) {
|
|
486
|
-
const [key, ...valueParts] = authEnv.split('=');
|
|
487
|
-
devEnv[key] = valueParts.join('=');
|
|
488
|
-
}
|
|
489
|
-
let authConfig;
|
|
490
|
-
const needsSignin = answers.auth_needs_signin?.startsWith('yes');
|
|
491
|
-
const authStrategy = answers.auth_strategy?.split(' - ')[0] || 'bypass';
|
|
492
|
-
if (needsSignin || (authEnv && authEnv.includes('='))) {
|
|
493
|
-
authConfig = {
|
|
494
|
-
strategy: needsSignin ? authStrategy : 'bypass',
|
|
495
|
-
};
|
|
496
|
-
// Add env for bypass/fixture strategies
|
|
497
|
-
if (Object.keys(devEnv).length > 0) {
|
|
498
|
-
authConfig.env = devEnv;
|
|
499
|
-
}
|
|
500
|
-
// Add token config for token strategy
|
|
501
|
-
if (authStrategy === 'token') {
|
|
502
|
-
const tokenType = answers.auth_token_type?.split(' - ')[0] || 'cookie';
|
|
503
|
-
const tokenName = answers.auth_token_name || 'session';
|
|
504
|
-
const tokenSecret = answers.auth_token_secret || 'AUTH_TOKEN';
|
|
505
|
-
authConfig.token = {
|
|
506
|
-
type: tokenType,
|
|
507
|
-
name: tokenName,
|
|
508
|
-
secret: `$${tokenSecret}`, // Prefix with $ for secret reference
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
let databaseConfig;
|
|
513
|
-
const needsDb = answers.db_needs_database?.startsWith('yes');
|
|
514
|
-
if (needsDb) {
|
|
515
|
-
const dbStrategy = answers.db_strategy?.split(' - ')[0] || 'memory';
|
|
516
|
-
const dbEnvStr = answers.db_env?.trim();
|
|
517
|
-
const dbSeedCommand = answers.db_seed_command?.trim();
|
|
518
|
-
databaseConfig = {
|
|
519
|
-
strategy: dbStrategy,
|
|
520
|
-
};
|
|
521
|
-
// Parse database env var
|
|
522
|
-
if (dbEnvStr && dbEnvStr.includes('=')) {
|
|
523
|
-
const [key, ...valueParts] = dbEnvStr.split('=');
|
|
524
|
-
databaseConfig.env = { [key]: valueParts.join('=') };
|
|
525
|
-
}
|
|
526
|
-
// Add seed command if provided
|
|
527
|
-
if (dbSeedCommand) {
|
|
528
|
-
databaseConfig.seed_command = dbSeedCommand;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
// Build services config for monorepo mode
|
|
532
|
-
if (isMonorepo && state.serviceNames) {
|
|
533
|
-
const services = {};
|
|
534
|
-
for (const serviceName of state.serviceNames) {
|
|
535
|
-
const serviceKey = `service_${serviceName}`;
|
|
536
|
-
const root = answers[`${serviceKey}_root`];
|
|
537
|
-
const command = answers[`${serviceKey}_command`];
|
|
538
|
-
const serviceType = answers[`${serviceKey}_type`]?.split(' - ')[0];
|
|
539
|
-
const port = answers[`${serviceKey}_port`];
|
|
540
|
-
const dependsOnStr = answers[`${serviceKey}_depends_on`];
|
|
541
|
-
const dependsOn = dependsOnStr
|
|
542
|
-
? dependsOnStr
|
|
543
|
-
.split(',')
|
|
544
|
-
.map((s) => s.trim())
|
|
545
|
-
.filter(Boolean)
|
|
546
|
-
: undefined;
|
|
547
|
-
services[serviceName] = {
|
|
548
|
-
root: root === './' ? undefined : root,
|
|
549
|
-
command: command || 'pnpm dev',
|
|
550
|
-
type: serviceType === 'batch' ? 'batch' : undefined,
|
|
551
|
-
port: port ? parseInt(port, 10) : undefined,
|
|
552
|
-
ready_pattern: serviceType === 'server' ? 'ready|started|listening|Local:' : undefined,
|
|
553
|
-
env: Object.keys(devEnv).length > 0 ? devEnv : undefined,
|
|
554
|
-
depends_on: dependsOn?.length ? dependsOn : undefined,
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
const config = {
|
|
558
|
-
version: '1',
|
|
559
|
-
name: answers.project_name || 'my-app',
|
|
560
|
-
services,
|
|
561
|
-
verification: {
|
|
562
|
-
commands: commands.length > 0 ? commands : undefined,
|
|
563
|
-
},
|
|
564
|
-
};
|
|
565
|
-
if (authConfig) {
|
|
566
|
-
config.auth = authConfig;
|
|
567
|
-
}
|
|
568
|
-
if (databaseConfig) {
|
|
569
|
-
config.database = databaseConfig;
|
|
570
|
-
}
|
|
571
|
-
return config;
|
|
572
|
-
}
|
|
573
|
-
// Simple mode (single dev server)
|
|
574
|
-
const config = {
|
|
575
|
-
version: '1',
|
|
576
|
-
name: answers.project_name || 'my-app',
|
|
577
|
-
dev_server: {
|
|
578
|
-
command: answers.dev_command || 'pnpm dev',
|
|
579
|
-
port: parseInt(answers.dev_port || '3000', 10),
|
|
580
|
-
ready_pattern: answers.dev_ready_pattern || 'Local:',
|
|
581
|
-
env: Object.keys(devEnv).length > 0 ? devEnv : undefined,
|
|
582
|
-
},
|
|
583
|
-
verification: {
|
|
584
|
-
commands: commands.length > 0 ? commands : undefined,
|
|
585
|
-
},
|
|
586
|
-
};
|
|
587
|
-
if (authConfig) {
|
|
588
|
-
config.auth = authConfig;
|
|
589
|
-
}
|
|
590
|
-
if (databaseConfig) {
|
|
591
|
-
config.database = databaseConfig;
|
|
592
|
-
}
|
|
593
|
-
return config;
|
|
594
|
-
}
|
|
595
|
-
export function configToJson(config) {
|
|
596
|
-
return JSON.stringify(config, null, 2);
|
|
597
|
-
}
|
package/dist/utils/skill.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Create the .agents/skills/haystack.md file for agent discovery
|
|
3
|
-
* and .claude/commands/haystack.md for Claude Code slash command
|
|
4
|
-
*/
|
|
5
|
-
export declare function createSkillFile(): Promise<string>;
|
|
6
|
-
/**
|
|
7
|
-
* Create the .claude/commands/ files for Claude Code slash commands
|
|
8
|
-
* Users can invoke with /setup-haystack, /prepare-haystack, or /setup-haystack-secrets
|
|
9
|
-
*/
|
|
10
|
-
export declare function createClaudeCommand(): Promise<string>;
|