@stackwright-pro/otters 0.3.0-alpha.0 → 1.0.0-alpha.2
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/package.json +9 -3
- package/scripts/install-agents.js +7 -9
- package/src/question-adapter.ts +296 -0
- package/src/stackwright-pro-api-otter.json +69 -2
- package/src/stackwright-pro-auth-otter.json +74 -1
- package/src/stackwright-pro-dashboard-otter.json +323 -188
- package/src/stackwright-pro-data-otter.json +89 -298
- package/src/stackwright-pro-foreman-otter.json +454 -424
- package/src/stackwright-pro-page-otter.json +3 -1
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackwright-pro/otters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "Stackwright Pro Otter Raft - AI agents for enterprise features (CAC auth, API dashboards, government use cases)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/Per-Aspera-LLC/stackwright-pro"
|
|
9
9
|
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"vitest": "^1.4.0",
|
|
12
|
+
"zod": "^3.22.4"
|
|
13
|
+
},
|
|
10
14
|
"exports": {
|
|
11
15
|
"./src": "./src",
|
|
12
16
|
"./pro-foreman": "./src/stackwright-pro-foreman-otter.json"
|
|
@@ -16,10 +20,12 @@
|
|
|
16
20
|
"src"
|
|
17
21
|
],
|
|
18
22
|
"peerDependencies": {
|
|
19
|
-
"@stackwright-pro/mcp": "
|
|
23
|
+
"@stackwright-pro/mcp": "0.2.0-alpha.0"
|
|
20
24
|
},
|
|
21
25
|
"scripts": {
|
|
22
26
|
"postinstall": "node scripts/install-agents.js",
|
|
23
|
-
"test": "
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"test:coverage": "vitest run --coverage"
|
|
24
30
|
}
|
|
25
31
|
}
|
|
@@ -12,12 +12,10 @@ const AGENTS_DIR = path.join(os.homedir(), '.code_puppy', 'agents');
|
|
|
12
12
|
|
|
13
13
|
// Resolve package root relative to this script's location
|
|
14
14
|
const scriptDir = __dirname;
|
|
15
|
-
const packageRoot =
|
|
16
|
-
? path.dirname(scriptDir) // Running from within the package
|
|
17
|
-
: path.join(scriptDir, 'packages', 'otters'); // Running from monorepo root
|
|
15
|
+
const packageRoot = path.resolve(scriptDir, '..');
|
|
18
16
|
|
|
19
17
|
// All Pro otters are in the src/ directory
|
|
20
|
-
const
|
|
18
|
+
const srcDir = path.join(packageRoot, 'src');
|
|
21
19
|
|
|
22
20
|
async function installAgents() {
|
|
23
21
|
try {
|
|
@@ -25,23 +23,23 @@ async function installAgents() {
|
|
|
25
23
|
await fs.promises.mkdir(AGENTS_DIR, { recursive: true });
|
|
26
24
|
|
|
27
25
|
// Copy all JSON files from src/ to ~/.code_puppy/agents/
|
|
28
|
-
const files = await fs.promises.readdir(
|
|
26
|
+
const files = await fs.promises.readdir(srcDir);
|
|
29
27
|
|
|
30
28
|
let installedCount = 0;
|
|
31
29
|
for (const file of files) {
|
|
32
30
|
if (file.endsWith('-otter.json')) {
|
|
33
|
-
const srcPath = path.join(
|
|
31
|
+
const srcPath = path.join(srcDir, file);
|
|
34
32
|
const destPath = path.join(AGENTS_DIR, file);
|
|
35
33
|
|
|
36
34
|
await fs.promises.copyFile(srcPath, destPath);
|
|
37
|
-
console.log(`✅ Installed
|
|
35
|
+
console.log(`✅ Installed: ${file}`);
|
|
38
36
|
installedCount++;
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
if (installedCount > 0) {
|
|
41
|
+
console.log(`Installing otters from: ${srcDir}`);
|
|
43
42
|
console.log(`\n🦦🦦 Pro otters installed to ${AGENTS_DIR}`);
|
|
44
|
-
console.log(' Run "code-puppy -i -a stackwright-pro-foreman-otter" to start!');
|
|
45
43
|
} else {
|
|
46
44
|
console.log('⚠️ No Pro otter files found to install');
|
|
47
45
|
}
|
|
@@ -51,4 +49,4 @@ async function installAgents() {
|
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
installAgents();
|
|
52
|
+
installAgents();
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Question Adapter - Converts between Question Manifest format and ask_user_question format
|
|
3
|
+
*
|
|
4
|
+
* The ask_user_question MCP tool requires:
|
|
5
|
+
* - question: string (full text)
|
|
6
|
+
* - header: string (max 12 chars, short label)
|
|
7
|
+
* - multi_select: boolean
|
|
8
|
+
* - options: {label, description}[] (REQUIRED, min 2)
|
|
9
|
+
*
|
|
10
|
+
* Our Question Manifest format has:
|
|
11
|
+
* - id: string (e.g., "api-1")
|
|
12
|
+
* - question: string
|
|
13
|
+
* - type: 'text' | 'select' | 'multi-select' | 'confirm'
|
|
14
|
+
* - options?: {label, value}[] (optional for text/confirm)
|
|
15
|
+
* - required?: boolean
|
|
16
|
+
* - default?: string | boolean | string[]
|
|
17
|
+
* - dependsOn?: {questionId, value}
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface QuestionManifestQuestion {
|
|
21
|
+
id: string;
|
|
22
|
+
question: string;
|
|
23
|
+
type: 'text' | 'select' | 'multi-select' | 'confirm';
|
|
24
|
+
required?: boolean;
|
|
25
|
+
options?: { label: string; value: string }[];
|
|
26
|
+
default?: string | boolean | string[];
|
|
27
|
+
help?: string;
|
|
28
|
+
dependsOn?: {
|
|
29
|
+
questionId: string;
|
|
30
|
+
value: string | string[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AskUserQuestionOption {
|
|
35
|
+
label: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AskUserQuestion {
|
|
40
|
+
question: string;
|
|
41
|
+
header: string;
|
|
42
|
+
multi_select: boolean;
|
|
43
|
+
options: AskUserQuestionOption[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Truncate string to max length, adding ellipsis if needed
|
|
48
|
+
*/
|
|
49
|
+
function truncate(str: string, maxLength: number): string {
|
|
50
|
+
if (str.length <= maxLength) return str;
|
|
51
|
+
return str.substring(0, maxLength - 1) + '…';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate a valid header from a question ID
|
|
56
|
+
* Headers must be max 12 chars, alphanumeric with hyphens
|
|
57
|
+
*/
|
|
58
|
+
function generateHeader(id: string): string {
|
|
59
|
+
// Remove numbers and prefix, keep meaningful part
|
|
60
|
+
// e.g., "api-1" -> "API-1", "auth-3" -> "AUTH-3"
|
|
61
|
+
const parts = id.split('-');
|
|
62
|
+
if (parts.length >= 2) {
|
|
63
|
+
const prefix = parts[0].toUpperCase().substring(0, 4);
|
|
64
|
+
const num = parts[1];
|
|
65
|
+
return truncate(`${prefix}-${num}`, 12);
|
|
66
|
+
}
|
|
67
|
+
return truncate(
|
|
68
|
+
id
|
|
69
|
+
.toUpperCase()
|
|
70
|
+
.replace(/[^A-Z0-9]/g, '')
|
|
71
|
+
.substring(0, 12),
|
|
72
|
+
12
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generate default options for question types that don't have options
|
|
78
|
+
*/
|
|
79
|
+
function generateDefaultOptions(type: string): AskUserQuestionOption[] {
|
|
80
|
+
switch (type) {
|
|
81
|
+
case 'confirm':
|
|
82
|
+
return [
|
|
83
|
+
{ label: 'Yes', description: 'Enable or confirm this option' },
|
|
84
|
+
{ label: 'No', description: 'Disable or decline this option' },
|
|
85
|
+
];
|
|
86
|
+
case 'text':
|
|
87
|
+
return [
|
|
88
|
+
{ label: 'Specify', description: 'I will provide a specific value' },
|
|
89
|
+
{ label: 'Skip', description: 'Use default or skip this question' },
|
|
90
|
+
];
|
|
91
|
+
default:
|
|
92
|
+
return [
|
|
93
|
+
{ label: 'Option 1', description: 'First option' },
|
|
94
|
+
{ label: 'Option 2', description: 'Second option' },
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Convert a single QuestionManifest question to ask_user_question format
|
|
101
|
+
*/
|
|
102
|
+
export function adaptQuestion(q: QuestionManifestQuestion): AskUserQuestion {
|
|
103
|
+
// Generate header from ID
|
|
104
|
+
const header = generateHeader(q.id);
|
|
105
|
+
|
|
106
|
+
// Determine multi_select
|
|
107
|
+
const multiSelect = q.type === 'multi-select';
|
|
108
|
+
|
|
109
|
+
// Handle options - use provided or generate defaults
|
|
110
|
+
let options: AskUserQuestionOption[];
|
|
111
|
+
|
|
112
|
+
if (q.options && q.options.length >= 2) {
|
|
113
|
+
// Use provided options, adapt format
|
|
114
|
+
options = q.options.map((opt) => ({
|
|
115
|
+
label: truncate(opt.label, 50),
|
|
116
|
+
description: opt.value !== opt.label ? opt.value : undefined,
|
|
117
|
+
}));
|
|
118
|
+
} else if (q.options && q.options.length === 1) {
|
|
119
|
+
// Single option - add a default
|
|
120
|
+
options = [
|
|
121
|
+
...q.options.map((opt) => ({ label: truncate(opt.label, 50), description: opt.value })),
|
|
122
|
+
{ label: 'Other', description: 'Specify a different value' },
|
|
123
|
+
];
|
|
124
|
+
} else {
|
|
125
|
+
// No options - generate defaults based on type
|
|
126
|
+
options = generateDefaultOptions(q.type);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Ensure minimum 2 options (MCP tool requirement)
|
|
130
|
+
if (options.length < 2) {
|
|
131
|
+
options.push({ label: 'Other', description: 'Alternative option' });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Limit to 6 options (MCP tool max)
|
|
135
|
+
options = options.slice(0, 6);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
question: q.question + (q.help ? `\n\n${q.help}` : ''),
|
|
139
|
+
header,
|
|
140
|
+
multi_select: multiSelect,
|
|
141
|
+
options,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Adapt multiple questions, filtering by dependsOn
|
|
147
|
+
*
|
|
148
|
+
* @param questions - All questions from manifest
|
|
149
|
+
* @param answers - Previously answered questions (for filtering conditionals)
|
|
150
|
+
* @returns Questions adapted for ask_user_question, with conditionals resolved
|
|
151
|
+
*/
|
|
152
|
+
export function adaptQuestions(
|
|
153
|
+
questions: QuestionManifestQuestion[],
|
|
154
|
+
answers: Record<string, string | string[] | boolean> = {}
|
|
155
|
+
): AskUserQuestion[] {
|
|
156
|
+
const adapted: AskUserQuestion[] = [];
|
|
157
|
+
|
|
158
|
+
for (const q of questions) {
|
|
159
|
+
// Check dependsOn condition
|
|
160
|
+
if (q.dependsOn) {
|
|
161
|
+
const dependsAnswer = answers[q.dependsOn.questionId];
|
|
162
|
+
if (dependsAnswer === undefined) {
|
|
163
|
+
// Parent question not answered yet - skip this conditional question
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check if the answer matches the condition
|
|
168
|
+
const expectedValues = Array.isArray(q.dependsOn.value)
|
|
169
|
+
? q.dependsOn.value
|
|
170
|
+
: [q.dependsOn.value];
|
|
171
|
+
|
|
172
|
+
const answerValue = Array.isArray(dependsAnswer) ? dependsAnswer[0] : dependsAnswer;
|
|
173
|
+
|
|
174
|
+
if (!expectedValues.includes(answerValue as string)) {
|
|
175
|
+
// Condition not met - skip this question
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
adapted.push(adaptQuestion(q));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return adapted;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Parse JSON response from LLM (handles various formats)
|
|
188
|
+
*/
|
|
189
|
+
export function parseLLMQuestionsResponse(text: string): QuestionManifestQuestion[] {
|
|
190
|
+
// Try to extract JSON from the response
|
|
191
|
+
let jsonStr = text;
|
|
192
|
+
|
|
193
|
+
// Remove markdown code blocks
|
|
194
|
+
jsonStr = jsonStr.replace(/```(?:json|javascript)?\s*/gi, '');
|
|
195
|
+
jsonStr = jsonStr.replace(/```\s*$/gm, '');
|
|
196
|
+
|
|
197
|
+
// Find JSON object or array
|
|
198
|
+
const firstBrace = jsonStr.indexOf('{');
|
|
199
|
+
const firstBracket = jsonStr.indexOf('[');
|
|
200
|
+
|
|
201
|
+
let start = -1;
|
|
202
|
+
if (firstBrace !== -1 && firstBracket !== -1) {
|
|
203
|
+
start = Math.min(firstBrace, firstBracket);
|
|
204
|
+
} else if (firstBrace !== -1) {
|
|
205
|
+
start = firstBrace;
|
|
206
|
+
} else if (firstBracket !== -1) {
|
|
207
|
+
start = firstBracket;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (start === -1) {
|
|
211
|
+
throw new Error('No JSON found in response');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
jsonStr = jsonStr.substring(start);
|
|
215
|
+
|
|
216
|
+
// Handle trailing text after JSON
|
|
217
|
+
const lastBrace = jsonStr.lastIndexOf('}');
|
|
218
|
+
const lastBracket = jsonStr.lastIndexOf(']');
|
|
219
|
+
const end = Math.max(lastBrace, lastBracket);
|
|
220
|
+
|
|
221
|
+
if (end === -1) {
|
|
222
|
+
throw new Error('Invalid JSON structure');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
jsonStr = jsonStr.substring(0, end + 1);
|
|
226
|
+
|
|
227
|
+
// Fix common issues
|
|
228
|
+
jsonStr = jsonStr.replace(/,(\s*[}\]])/g, '$1'); // Remove trailing commas
|
|
229
|
+
jsonStr = jsonStr.replace(/'/g, '"'); // Single quotes to double
|
|
230
|
+
|
|
231
|
+
const parsed = JSON.parse(jsonStr);
|
|
232
|
+
|
|
233
|
+
// Handle various JSON structures
|
|
234
|
+
let questions: unknown[];
|
|
235
|
+
|
|
236
|
+
if (Array.isArray(parsed)) {
|
|
237
|
+
questions = parsed;
|
|
238
|
+
} else if (parsed.questions && Array.isArray(parsed.questions)) {
|
|
239
|
+
questions = parsed.questions;
|
|
240
|
+
} else if (parsed.data && Array.isArray((parsed.data as Record<string, unknown>).questions)) {
|
|
241
|
+
questions = (parsed.data as Record<string, unknown>).questions as unknown[];
|
|
242
|
+
} else {
|
|
243
|
+
throw new Error('No questions array found in response');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return questions as QuestionManifestQuestion[];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Convert ask_user_question answers back to manifest format
|
|
251
|
+
*/
|
|
252
|
+
export function answersToManifestFormat(
|
|
253
|
+
answers: { question_header: string; selected_options: string[]; other_text?: string | null }[],
|
|
254
|
+
questions: QuestionManifestQuestion[]
|
|
255
|
+
): Record<string, string | string[] | boolean> {
|
|
256
|
+
const result: Record<string, string | string[] | boolean> = {};
|
|
257
|
+
|
|
258
|
+
for (const answer of answers) {
|
|
259
|
+
// Find matching question by header
|
|
260
|
+
const headerLower = answer.question_header.toLowerCase();
|
|
261
|
+
const question = questions.find((q) => {
|
|
262
|
+
const qHeader = generateHeader(q.id).toLowerCase();
|
|
263
|
+
return qHeader === headerLower || q.id.toLowerCase().includes(headerLower);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (!question) {
|
|
267
|
+
// Try to match by header prefix
|
|
268
|
+
const matched = questions.find((q) => {
|
|
269
|
+
const qHeader = generateHeader(q.id).toLowerCase();
|
|
270
|
+
return qHeader.startsWith(headerLower.split('-')[0]);
|
|
271
|
+
});
|
|
272
|
+
if (matched) {
|
|
273
|
+
result[matched.id] = answer.selected_options[0] || '';
|
|
274
|
+
}
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Handle multi-select vs single select
|
|
279
|
+
if (question.type === 'multi-select' || answer.selected_options.length > 1) {
|
|
280
|
+
result[question.id] = answer.selected_options;
|
|
281
|
+
} else if (question.type === 'confirm') {
|
|
282
|
+
result[question.id] = answer.selected_options[0] === 'Yes';
|
|
283
|
+
} else {
|
|
284
|
+
// For text or single select, use first option or other_text
|
|
285
|
+
if (answer.other_text) {
|
|
286
|
+
result[question.id] = answer.other_text;
|
|
287
|
+
} else if (answer.selected_options.length > 0) {
|
|
288
|
+
// Map label back to value if possible
|
|
289
|
+
const option = question.options?.find((o) => o.label === answer.selected_options[0]);
|
|
290
|
+
result[question.id] = option?.value ?? answer.selected_options[0];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
"name": "stackwright-pro-api-otter",
|
|
4
4
|
"display_name": "Stackwright Pro API Otter 🦦",
|
|
5
5
|
"description": "Analyzes API specs and extracts entity definitions",
|
|
6
|
-
"tools": [
|
|
6
|
+
"tools": [
|
|
7
|
+
"agent_run_shell_command",
|
|
8
|
+
"list_files",
|
|
9
|
+
"cp_list_files",
|
|
10
|
+
"cp_read_file",
|
|
11
|
+
"invoke_agent"
|
|
12
|
+
],
|
|
7
13
|
"user_prompt": "Hey! 🦦 I'm the API Otter. Give me an OpenAPI spec path and I'll extract what entities and endpoints it exposes.",
|
|
8
14
|
"system_prompt": [
|
|
9
15
|
"## YOUR JOB",
|
|
@@ -27,6 +33,67 @@
|
|
|
27
33
|
"- Base URL: {url}",
|
|
28
34
|
"",
|
|
29
35
|
"## PASS TO DATA OTTER",
|
|
30
|
-
"After analysis, invoke pro-data-otter to wire collections."
|
|
36
|
+
"After analysis, invoke pro-data-otter to wire collections.",
|
|
37
|
+
"",
|
|
38
|
+
"---",
|
|
39
|
+
"",
|
|
40
|
+
"## QUESTION_COLLECTION_MODE",
|
|
41
|
+
"",
|
|
42
|
+
"When invoked with QUESTION_COLLECTION_MODE=true, return questions for the user INSTEAD of doing work.",
|
|
43
|
+
"",
|
|
44
|
+
"If the prompt contains \"QUESTION_COLLECTION_MODE=true\", respond ONLY with this JSON (no other text):",
|
|
45
|
+
"",
|
|
46
|
+
"**IMPORTANT**: Your response MUST include a `requiredPackages` field alongside the `questions` array. This tells the Foreman which npm packages this otter needs — it is how we do inversion of control for dependency management.",
|
|
47
|
+
"",
|
|
48
|
+
"{",
|
|
49
|
+
" \"questions\": [",
|
|
50
|
+
" {",
|
|
51
|
+
" \"id\": \"api-1\",",
|
|
52
|
+
" \"question\": \"Do you have an existing OpenAPI spec?\",",
|
|
53
|
+
" \"type\": \"confirm\",",
|
|
54
|
+
" \"required\": true,",
|
|
55
|
+
" \"default\": \"no\"",
|
|
56
|
+
" },",
|
|
57
|
+
" {",
|
|
58
|
+
" \"id\": \"api-2\",",
|
|
59
|
+
" \"question\": \"What is the URL or path to your OpenAPI spec?\",",
|
|
60
|
+
" \"type\": \"text\",",
|
|
61
|
+
" \"required\": true,",
|
|
62
|
+
" \"dependsOn\": { \"questionId\": \"api-1\", \"value\": \"yes\" }",
|
|
63
|
+
" },",
|
|
64
|
+
" {",
|
|
65
|
+
" \"id\": \"api-3\",",
|
|
66
|
+
" \"question\": \"What is your API authentication method?\",",
|
|
67
|
+
" \"type\": \"select\",",
|
|
68
|
+
" \"options\": [",
|
|
69
|
+
" { \"label\": \"No auth\", \"value\": \"none\" },",
|
|
70
|
+
" { \"label\": \"API Key\", \"value\": \"api-key\" },",
|
|
71
|
+
" { \"label\": \"OAuth2\", \"value\": \"oauth2\" },",
|
|
72
|
+
" { \"label\": \"OIDC/SAML\", \"value\": \"oidc\" },",
|
|
73
|
+
" { \"label\": \"CAC/PIV (DoD)\", \"value\": \"cac\" }",
|
|
74
|
+
" ],",
|
|
75
|
+
" \"required\": true",
|
|
76
|
+
" },",
|
|
77
|
+
" {",
|
|
78
|
+
" \"id\": \"api-4\",",
|
|
79
|
+
" \"question\": \"Which entities from the spec do you need?\",",
|
|
80
|
+
" \"type\": \"multi-select\",",
|
|
81
|
+
" \"options\": [",
|
|
82
|
+
" { \"label\": \"I will tell you after you parse it\", \"value\": \"discover\" }",
|
|
83
|
+
" ],",
|
|
84
|
+
" \"required\": false,",
|
|
85
|
+
" \"help\": \"You can select specific entities or let me discover them after parsing the spec.\"",
|
|
86
|
+
" }",
|
|
87
|
+
" ],",
|
|
88
|
+
" \"requiredPackages\": {",
|
|
89
|
+
" \"dependencies\": {",
|
|
90
|
+
" \"@stackwright-pro/openapi\": \"latest\",",
|
|
91
|
+
" \"zod\": \"^3.23.0\"",
|
|
92
|
+
" },",
|
|
93
|
+
" \"devPackages\": {",
|
|
94
|
+
" \"@stoplight/prism-cli\": \"^5.14.2\"",
|
|
95
|
+
" }",
|
|
96
|
+
" }",
|
|
97
|
+
"}"
|
|
31
98
|
]
|
|
32
99
|
}
|
|
@@ -725,6 +725,79 @@
|
|
|
725
725
|
"",
|
|
726
726
|
"---",
|
|
727
727
|
"",
|
|
728
|
-
"Ready to wire up some authentication? 🦦🔐"
|
|
728
|
+
"Ready to wire up some authentication? 🦦🔐",
|
|
729
|
+
"",
|
|
730
|
+
"---",
|
|
731
|
+
"",
|
|
732
|
+
"## QUESTION_COLLECTION_MODE",
|
|
733
|
+
"",
|
|
734
|
+
"When invoked with QUESTION_COLLECTION_MODE=true, return questions for the user INSTEAD of doing work.",
|
|
735
|
+
"",
|
|
736
|
+
"If the prompt contains \"QUESTION_COLLECTION_MODE=true\", respond ONLY with this JSON (no other text):",
|
|
737
|
+
"",
|
|
738
|
+
"**IMPORTANT**: Your response MUST include a `requiredPackages` field alongside the `questions` array. This tells the Foreman which npm packages this otter needs — this is the IoC pattern for dependency declaration.",
|
|
739
|
+
"",
|
|
740
|
+
"{",
|
|
741
|
+
" \"questions\": [",
|
|
742
|
+
" {",
|
|
743
|
+
" \"id\": \"auth-1\",",
|
|
744
|
+
" \"question\": \"Who needs to access this application?\",",
|
|
745
|
+
" \"type\": \"select\",",
|
|
746
|
+
" \"options\": [",
|
|
747
|
+
" { \"label\": \"Everyone (public access)\", \"value\": \"public\" },",
|
|
748
|
+
" { \"label\": \"Logged-in users only\", \"value\": \"authenticated\" },",
|
|
749
|
+
" { \"label\": \"Government/military (CAC/PIV cards)\", \"value\": \"cac\" },",
|
|
750
|
+
" { \"label\": \"Enterprise users (SSO)\", \"value\": \"oidc\" }",
|
|
751
|
+
" ],",
|
|
752
|
+
" \"required\": true",
|
|
753
|
+
" },",
|
|
754
|
+
" {",
|
|
755
|
+
" \"id\": \"auth-2\",",
|
|
756
|
+
" \"question\": \"What authentication provider do you use?\",",
|
|
757
|
+
" \"type\": \"select\",",
|
|
758
|
+
" \"options\": [",
|
|
759
|
+
" { \"label\": \"Email/Password\", \"value\": \"email\" },",
|
|
760
|
+
" { \"label\": \"Azure AD / Okta / Ping / Cognito\", \"value\": \"enterprise\" },",
|
|
761
|
+
" { \"label\": \"DoD CAC/PIV (PKI)\", \"value\": \"cac\" }",
|
|
762
|
+
" ],",
|
|
763
|
+
" \"required\": false,",
|
|
764
|
+
" \"dependsOn\": { \"questionId\": \"auth-1\", \"value\": [\"authenticated\", \"oidc\", \"cac\"] }",
|
|
765
|
+
" },",
|
|
766
|
+
" {",
|
|
767
|
+
" \"id\": \"auth-3\",",
|
|
768
|
+
" \"question\": \"Do you need role-based access control (RBAC)?\",",
|
|
769
|
+
" \"type\": \"confirm\",",
|
|
770
|
+
" \"required\": true,",
|
|
771
|
+
" \"default\": \"yes\"",
|
|
772
|
+
" },",
|
|
773
|
+
" {",
|
|
774
|
+
" \"id\": \"auth-4\",",
|
|
775
|
+
" \"question\": \"Which roles do you need?\",",
|
|
776
|
+
" \"type\": \"multi-select\",",
|
|
777
|
+
" \"options\": [",
|
|
778
|
+
" { \"label\": \"Admin (full access)\", \"value\": \"ADMIN\" },",
|
|
779
|
+
" { \"label\": \"Analyst (read + export)\", \"value\": \"ANALYST\" },",
|
|
780
|
+
" { \"label\": \"User (basic access)\", \"value\": \"USER\" }",
|
|
781
|
+
" ],",
|
|
782
|
+
" \"required\": false,",
|
|
783
|
+
" \"dependsOn\": { \"questionId\": \"auth-3\", \"value\": \"yes\" }",
|
|
784
|
+
" },",
|
|
785
|
+
" {",
|
|
786
|
+
" \"id\": \"auth-5\",",
|
|
787
|
+
" \"question\": \"Do you need audit logging for compliance?\",",
|
|
788
|
+
" \"type\": \"confirm\",",
|
|
789
|
+
" \"required\": true,",
|
|
790
|
+
" \"default\": \"yes\"",
|
|
791
|
+
" }",
|
|
792
|
+
" ],",
|
|
793
|
+
" \"requiredPackages\": {",
|
|
794
|
+
" \"dependencies\": {",
|
|
795
|
+
" \"@stackwright-pro/auth\": \"latest\",",
|
|
796
|
+
" \"@stackwright-pro/auth-nextjs\": \"latest\"",
|
|
797
|
+
" },",
|
|
798
|
+
" \"devPackages\": {",
|
|
799
|
+
" }",
|
|
800
|
+
" }",
|
|
801
|
+
" }"
|
|
729
802
|
]
|
|
730
803
|
}
|