@genui-a3/create 0.1.7 → 0.1.9
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/index.js +268 -80
- package/package.json +3 -1
- package/template/.cursor/rules/example-app.mdc +9 -0
- package/template/.cursorrules +112 -0
- package/template/CLAUDE.md +112 -0
- package/template/README.md +4 -0
- package/template/app/agents/age.ts +1 -0
- package/template/app/agents/greeting.ts +1 -0
- package/template/app/lib/providers/anthropic.ts +1 -1
- package/template/docs/CUSTOM_LOGGING.md +36 -0
- package/template/docs/CUSTOM_PROVIDERS.md +642 -0
- package/template/docs/LOGGING.md +104 -0
- package/template/docs/PROVIDERS.md +217 -0
- package/template/docs/QUICK-START-EXAMPLES.md +197 -0
- package/template/docs/RESILIENCE.md +226 -0
- package/template/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -130,6 +130,135 @@ function extractRegionFromIni(content, sectionName) {
|
|
|
130
130
|
return void 0;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// src/utils/validation.ts
|
|
134
|
+
function normalizeKey(key) {
|
|
135
|
+
return key.replace(/\s/g, "");
|
|
136
|
+
}
|
|
137
|
+
function buildAuthErrorMessage(status, detail, context) {
|
|
138
|
+
let message = `Invalid ${context.name} (${status}). ${detail}`;
|
|
139
|
+
if (status === 401) {
|
|
140
|
+
message += "\n\nMake sure:\n \u2022 Your credentials are active and not revoked\n \u2022 Your account has appropriate permissions\n \u2022 The credentials haven't exceeded rate limits";
|
|
141
|
+
if (context.helpUrl) {
|
|
142
|
+
message += `
|
|
143
|
+
|
|
144
|
+
Get help at: ${context.helpUrl}`;
|
|
145
|
+
}
|
|
146
|
+
} else if (status === 429) {
|
|
147
|
+
message = `${context.name} rate limited. Please try again in a moment.`;
|
|
148
|
+
}
|
|
149
|
+
return message;
|
|
150
|
+
}
|
|
151
|
+
async function validateOpenAIKey(apiKey) {
|
|
152
|
+
if (!apiKey.startsWith("sk-proj-")) {
|
|
153
|
+
return {
|
|
154
|
+
valid: false,
|
|
155
|
+
message: "Invalid format: OpenAI API key must start with sk-proj-\n\nGet a new key at: https://platform.openai.com/account/api-keys"
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const response = await fetch("https://api.openai.com/v1/models", {
|
|
160
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
161
|
+
signal: AbortSignal.timeout(1e4)
|
|
162
|
+
});
|
|
163
|
+
if (response.ok) {
|
|
164
|
+
return { valid: true, message: "OpenAI API key is valid." };
|
|
165
|
+
}
|
|
166
|
+
const body = await response.json().catch(() => null);
|
|
167
|
+
const detail = body?.error?.message ?? `HTTP ${response.status}`;
|
|
168
|
+
const message = buildAuthErrorMessage(response.status, detail, {
|
|
169
|
+
name: "OpenAI API key",
|
|
170
|
+
helpUrl: "https://platform.openai.com/account/api-keys"
|
|
171
|
+
});
|
|
172
|
+
return { valid: false, message };
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
175
|
+
return { valid: false, message: `Network error: ${msg}` };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function validateAnthropicKey(apiKey) {
|
|
179
|
+
if (!apiKey.startsWith("sk-ant-")) {
|
|
180
|
+
return {
|
|
181
|
+
valid: false,
|
|
182
|
+
message: "Invalid format: Anthropic API key must start with sk-ant-\n\nGet a new key at: https://console.anthropic.com/settings/keys"
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch("https://api.anthropic.com/v1/models", {
|
|
187
|
+
headers: {
|
|
188
|
+
"x-api-key": apiKey,
|
|
189
|
+
"anthropic-version": "2023-06-01"
|
|
190
|
+
},
|
|
191
|
+
signal: AbortSignal.timeout(1e4)
|
|
192
|
+
});
|
|
193
|
+
if (response.ok) {
|
|
194
|
+
return { valid: true, message: "Anthropic API key is valid." };
|
|
195
|
+
}
|
|
196
|
+
const body = await response.json().catch(() => null);
|
|
197
|
+
const detail = body?.error?.message ?? `HTTP ${response.status}`;
|
|
198
|
+
const message = buildAuthErrorMessage(response.status, detail, {
|
|
199
|
+
name: "Anthropic API key",
|
|
200
|
+
helpUrl: "https://console.anthropic.com/settings/keys"
|
|
201
|
+
});
|
|
202
|
+
return { valid: false, message };
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
205
|
+
return { valid: false, message: `Network error: ${msg}` };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function validateAwsCredentials(credentials) {
|
|
209
|
+
if (credentials.mode === "keys") {
|
|
210
|
+
if (!credentials.accessKeyId.match(/^AKIA[0-9A-Z]{16}$/)) {
|
|
211
|
+
return {
|
|
212
|
+
valid: false,
|
|
213
|
+
message: "Invalid format: AWS Access Key ID must start with AKIA and be 20 characters total\n\nManage keys at: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (credentials.secretAccessKey.length < 40) {
|
|
217
|
+
return {
|
|
218
|
+
valid: false,
|
|
219
|
+
message: "Invalid format: AWS Secret Access Key appears too short (should be ~40 characters)\n\nManage keys at: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const { BedrockClient, ListFoundationModelsCommand } = await import("@aws-sdk/client-bedrock");
|
|
225
|
+
let clientConfig;
|
|
226
|
+
if (credentials.mode === "profile") {
|
|
227
|
+
const { fromIni } = await import("@aws-sdk/credential-provider-ini");
|
|
228
|
+
clientConfig = {
|
|
229
|
+
region: credentials.region,
|
|
230
|
+
credentials: fromIni({ profile: credentials.profile })
|
|
231
|
+
};
|
|
232
|
+
} else {
|
|
233
|
+
clientConfig = {
|
|
234
|
+
region: credentials.region,
|
|
235
|
+
credentials: {
|
|
236
|
+
accessKeyId: credentials.accessKeyId,
|
|
237
|
+
secretAccessKey: credentials.secretAccessKey
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const client = new BedrockClient(clientConfig);
|
|
242
|
+
await client.send(new ListFoundationModelsCommand({ byOutputModality: "TEXT" }));
|
|
243
|
+
return { valid: true, message: "AWS credentials verified with Bedrock access." };
|
|
244
|
+
} catch (error) {
|
|
245
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
246
|
+
let message = `AWS validation failed: ${msg}`;
|
|
247
|
+
if (msg.includes("Could not resolve credentials")) {
|
|
248
|
+
const profile = credentials.mode === "profile" ? credentials.profile : "unknown";
|
|
249
|
+
message = `Could not find credentials for profile "${profile}" in ~/.aws/credentials
|
|
250
|
+
|
|
251
|
+
Set up a profile with: aws configure
|
|
252
|
+
Guide: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html`;
|
|
253
|
+
} else if (msg.includes("InvalidSignatureException") || msg.includes("InvalidSignature")) {
|
|
254
|
+
message = "Invalid AWS credentials (signature mismatch)\n\nMake sure:\n \u2022 Your Access Key ID and Secret Access Key are correct\n \u2022 They haven't been revoked or rotated\n\nManage credentials at: https://console.aws.amazon.com/iam/home#/security_credentials";
|
|
255
|
+
} else if (msg.includes("UnauthorizedOperation") || msg.includes("NotAuthorizedForSourceException")) {
|
|
256
|
+
message = "AWS credentials don't have permission to access Bedrock\n\nMake sure:\n \u2022 Your IAM user/role has Bedrock access\n \u2022 Your region has Bedrock enabled\n\nLearn more: https://docs.aws.amazon.com/bedrock/latest/userguide/";
|
|
257
|
+
}
|
|
258
|
+
return { valid: false, message };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
133
262
|
// src/utils/prompts.ts
|
|
134
263
|
function handleCancel(value) {
|
|
135
264
|
if (p.isCancel(value)) {
|
|
@@ -146,7 +275,7 @@ async function promptProjectName() {
|
|
|
146
275
|
defaultValue: "my-a3-quickstart"
|
|
147
276
|
});
|
|
148
277
|
handleCancel(value);
|
|
149
|
-
projectName = value;
|
|
278
|
+
projectName = value.trim();
|
|
150
279
|
}
|
|
151
280
|
return projectName;
|
|
152
281
|
}
|
|
@@ -159,7 +288,7 @@ async function promptAccessKeys(config) {
|
|
|
159
288
|
}
|
|
160
289
|
});
|
|
161
290
|
handleCancel(awsAccessKeyId);
|
|
162
|
-
config.awsAccessKeyId = awsAccessKeyId;
|
|
291
|
+
config.awsAccessKeyId = normalizeKey(awsAccessKeyId);
|
|
163
292
|
const awsSecretAccessKey = await p.password({
|
|
164
293
|
message: "AWS_SECRET_ACCESS_KEY:",
|
|
165
294
|
validate(input) {
|
|
@@ -167,31 +296,106 @@ async function promptAccessKeys(config) {
|
|
|
167
296
|
}
|
|
168
297
|
});
|
|
169
298
|
handleCancel(awsSecretAccessKey);
|
|
170
|
-
config.awsSecretAccessKey = awsSecretAccessKey;
|
|
299
|
+
config.awsSecretAccessKey = normalizeKey(awsSecretAccessKey);
|
|
171
300
|
}
|
|
172
301
|
async function promptOpenAIConfig(config) {
|
|
173
302
|
p.log.step(PROVIDER_META.openai.label);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
303
|
+
let validated = false;
|
|
304
|
+
while (!validated) {
|
|
305
|
+
const openaiApiKey = await p.password({
|
|
306
|
+
message: "OpenAI API key:",
|
|
307
|
+
validate(input) {
|
|
308
|
+
if (!input) return "API key is required (starts with sk-...). Get one at https://platform.openai.com/api-keys";
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
handleCancel(openaiApiKey);
|
|
312
|
+
config.openaiApiKey = normalizeKey(openaiApiKey);
|
|
313
|
+
const spin = p.spinner();
|
|
314
|
+
spin.start("Validating OpenAI credentials...");
|
|
315
|
+
const result = await validateOpenAIKey(config.openaiApiKey);
|
|
316
|
+
spin.stop(result.valid ? "OpenAI credentials verified" : "OpenAI validation failed");
|
|
317
|
+
if (result.valid) {
|
|
318
|
+
validated = true;
|
|
319
|
+
} else {
|
|
320
|
+
p.log.warn(result.message);
|
|
321
|
+
p.log.info("Please try again.");
|
|
179
322
|
}
|
|
180
|
-
}
|
|
181
|
-
handleCancel(openaiApiKey);
|
|
182
|
-
config.openaiApiKey = openaiApiKey;
|
|
323
|
+
}
|
|
183
324
|
}
|
|
184
325
|
async function promptAnthropicConfig(config) {
|
|
185
326
|
p.log.step(PROVIDER_META.anthropic.label);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
327
|
+
let validated = false;
|
|
328
|
+
while (!validated) {
|
|
329
|
+
const anthropicApiKey = await p.password({
|
|
330
|
+
message: "Anthropic API key:",
|
|
331
|
+
validate(input) {
|
|
332
|
+
if (!input) return "API key is required (starts with sk-ant-...). Get one at https://console.anthropic.com/settings/keys";
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
handleCancel(anthropicApiKey);
|
|
336
|
+
config.anthropicApiKey = normalizeKey(anthropicApiKey);
|
|
337
|
+
const spin = p.spinner();
|
|
338
|
+
spin.start("Validating Anthropic credentials...");
|
|
339
|
+
const result = await validateAnthropicKey(config.anthropicApiKey);
|
|
340
|
+
spin.stop(result.valid ? "Anthropic credentials verified" : "Anthropic validation failed");
|
|
341
|
+
if (result.valid) {
|
|
342
|
+
validated = true;
|
|
343
|
+
} else {
|
|
344
|
+
p.log.warn(result.message);
|
|
345
|
+
p.log.info("Please try again.");
|
|
191
346
|
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async function promptProfileSelection(profiles) {
|
|
350
|
+
const MANUAL_ENTRY = "__manual__";
|
|
351
|
+
const awsProfile = await p.select({
|
|
352
|
+
message: "AWS profile",
|
|
353
|
+
options: [
|
|
354
|
+
...profiles.map((prof) => ({ label: prof, value: prof })),
|
|
355
|
+
{ label: "Enter manually", value: MANUAL_ENTRY }
|
|
356
|
+
]
|
|
192
357
|
});
|
|
193
|
-
handleCancel(
|
|
194
|
-
|
|
358
|
+
handleCancel(awsProfile);
|
|
359
|
+
if (awsProfile === MANUAL_ENTRY) {
|
|
360
|
+
const manualProfile = await p.text({
|
|
361
|
+
message: "AWS profile name:",
|
|
362
|
+
placeholder: "default",
|
|
363
|
+
defaultValue: "default",
|
|
364
|
+
validate(input) {
|
|
365
|
+
if (!input) return "Profile name is required.";
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
handleCancel(manualProfile);
|
|
369
|
+
return manualProfile.trim();
|
|
370
|
+
}
|
|
371
|
+
return awsProfile;
|
|
372
|
+
}
|
|
373
|
+
async function promptDetectProfile(config) {
|
|
374
|
+
const profiles = detectAwsProfiles();
|
|
375
|
+
if (profiles.length > 0) {
|
|
376
|
+
return promptProfileSelection(profiles);
|
|
377
|
+
}
|
|
378
|
+
while (true) {
|
|
379
|
+
p.log.warn("No AWS profiles found in ~/.aws/credentials");
|
|
380
|
+
p.log.info("Set up a profile with: aws configure\n Guide: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html");
|
|
381
|
+
const action = await p.select({
|
|
382
|
+
message: "How would you like to proceed?",
|
|
383
|
+
options: [
|
|
384
|
+
{ label: "Try again", value: "retry", hint: "Re-check ~/.aws/credentials" },
|
|
385
|
+
{ label: "Use access keys instead", value: "keys", hint: "Provide key ID + secret directly" }
|
|
386
|
+
]
|
|
387
|
+
});
|
|
388
|
+
handleCancel(action);
|
|
389
|
+
if (action === "keys") {
|
|
390
|
+
config.bedrockAuthMode = "keys";
|
|
391
|
+
await promptAccessKeys(config);
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
const retryProfiles = detectAwsProfiles();
|
|
395
|
+
if (retryProfiles.length > 0) {
|
|
396
|
+
return promptProfileSelection(retryProfiles);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
195
399
|
}
|
|
196
400
|
async function promptBedrockConfig(config) {
|
|
197
401
|
p.log.step(PROVIDER_META.bedrock.label);
|
|
@@ -205,74 +409,58 @@ async function promptBedrockConfig(config) {
|
|
|
205
409
|
handleCancel(authMode);
|
|
206
410
|
config.bedrockAuthMode = authMode;
|
|
207
411
|
if (authMode === "profile") {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
handleCancel(awsProfile);
|
|
220
|
-
if (awsProfile === MANUAL_ENTRY) {
|
|
221
|
-
const manualProfile = await p.text({
|
|
222
|
-
message: "AWS profile name:",
|
|
223
|
-
placeholder: "default",
|
|
224
|
-
defaultValue: "default",
|
|
225
|
-
validate(input) {
|
|
226
|
-
if (!input) return "Profile name is required.";
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
handleCancel(manualProfile);
|
|
230
|
-
selectedProfile = manualProfile;
|
|
412
|
+
const profile = await promptDetectProfile(config);
|
|
413
|
+
if (profile) config.awsProfile = profile;
|
|
414
|
+
} else {
|
|
415
|
+
await promptAccessKeys(config);
|
|
416
|
+
}
|
|
417
|
+
let validated = false;
|
|
418
|
+
let firstAttempt = true;
|
|
419
|
+
while (!validated) {
|
|
420
|
+
if (!firstAttempt) {
|
|
421
|
+
if (config.bedrockAuthMode === "keys") {
|
|
422
|
+
await promptAccessKeys(config);
|
|
231
423
|
} else {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
options: [
|
|
239
|
-
{ label: "Enter a profile name", value: "manual", hint: "Configure the profile later with: aws configure --profile <name>" },
|
|
240
|
-
{ label: "Use access keys instead", value: "keys", hint: "Provide key ID + secret directly" }
|
|
241
|
-
]
|
|
242
|
-
});
|
|
243
|
-
handleCancel(noProfileAction);
|
|
244
|
-
if (noProfileAction === "manual") {
|
|
245
|
-
const awsProfile = await p.text({
|
|
246
|
-
message: "AWS profile name:",
|
|
247
|
-
placeholder: "default",
|
|
248
|
-
defaultValue: "default",
|
|
249
|
-
validate(input) {
|
|
250
|
-
if (!input) return "Profile name is required.";
|
|
251
|
-
}
|
|
424
|
+
const retryAction = await p.select({
|
|
425
|
+
message: "How would you like to retry?",
|
|
426
|
+
options: [
|
|
427
|
+
{ label: "Try a different profile", value: "profile" },
|
|
428
|
+
{ label: "Switch to access keys", value: "keys" }
|
|
429
|
+
]
|
|
252
430
|
});
|
|
253
|
-
handleCancel(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
431
|
+
handleCancel(retryAction);
|
|
432
|
+
if (retryAction === "keys") {
|
|
433
|
+
config.bedrockAuthMode = "keys";
|
|
434
|
+
await promptAccessKeys(config);
|
|
435
|
+
} else {
|
|
436
|
+
const profile = await promptDetectProfile(config);
|
|
437
|
+
if (profile) config.awsProfile = profile;
|
|
438
|
+
}
|
|
258
439
|
}
|
|
259
440
|
}
|
|
260
|
-
|
|
261
|
-
|
|
441
|
+
firstAttempt = false;
|
|
442
|
+
const currentDetectedRegion = config.bedrockAuthMode === "profile" ? detectAwsProfileRegion(config.awsProfile) : void 0;
|
|
443
|
+
const awsRegion = await p.text({
|
|
444
|
+
message: "AWS region:",
|
|
445
|
+
...currentDetectedRegion ? { initialValue: currentDetectedRegion } : { placeholder: "us-east-1" },
|
|
446
|
+
validate(input) {
|
|
447
|
+
if (!input) return "AWS region is required.";
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
handleCancel(awsRegion);
|
|
451
|
+
config.awsRegion = awsRegion.trim();
|
|
452
|
+
const spin = p.spinner();
|
|
453
|
+
spin.start("Validating AWS Bedrock credentials...");
|
|
454
|
+
const credentialInput = config.bedrockAuthMode === "profile" ? { mode: "profile", profile: config.awsProfile, region: config.awsRegion } : { mode: "keys", accessKeyId: config.awsAccessKeyId, secretAccessKey: config.awsSecretAccessKey, region: config.awsRegion };
|
|
455
|
+
const result = await validateAwsCredentials(credentialInput);
|
|
456
|
+
spin.stop(result.valid ? "AWS Bedrock credentials verified" : "AWS Bedrock validation failed");
|
|
457
|
+
if (result.valid) {
|
|
458
|
+
validated = true;
|
|
459
|
+
} else {
|
|
460
|
+
p.log.warn(result.message);
|
|
461
|
+
p.log.info("Re-entering credentials...");
|
|
262
462
|
}
|
|
263
|
-
} else {
|
|
264
|
-
await promptAccessKeys(config);
|
|
265
463
|
}
|
|
266
|
-
const detectedRegion = config.bedrockAuthMode === "profile" ? detectAwsProfileRegion(config.awsProfile) : void 0;
|
|
267
|
-
const awsRegion = await p.text({
|
|
268
|
-
message: "AWS region:",
|
|
269
|
-
...detectedRegion ? { initialValue: detectedRegion } : { placeholder: "us-east-1" },
|
|
270
|
-
validate(input) {
|
|
271
|
-
if (!input) return "AWS region is required.";
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
handleCancel(awsRegion);
|
|
275
|
-
config.awsRegion = awsRegion;
|
|
276
464
|
}
|
|
277
465
|
async function promptPrimaryProvider(providers, config) {
|
|
278
466
|
const primaryProvider = await p.select({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genui-a3/create",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "CLI scaffolding tool for A3 agentic apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"genui",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@aws-sdk/client-bedrock": "^3.750.0",
|
|
37
|
+
"@aws-sdk/credential-provider-ini": "^3.750.0",
|
|
36
38
|
"@clack/prompts": "1.1.0",
|
|
37
39
|
"chalk": "5.4.1",
|
|
38
40
|
"fs-extra": "11.3.0"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Example app rules (pointer to single source)
|
|
3
|
+
globs: "**"
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Example App
|
|
8
|
+
|
|
9
|
+
When working in this directory, follow the rules in **CLAUDE.md** in this directory (same content as `.cursorrules` via symlink). Do not duplicate rule content here; CLAUDE.md is the single source of truth for Claude Code and Cursor.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Example App
|
|
2
|
+
|
|
3
|
+
Next.js example for @genui-a3/core. AI rules for this app (Claude Code and Cursor). Single source of truth; Cursor uses via symlink `.cursorrules` → this file.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- Next.js 16.1.6 (App Router), React 19, TypeScript 5.9+
|
|
8
|
+
- Testing infrastructure planned (not yet configured)
|
|
9
|
+
- MUI 7 (@mui/material) + styled-components for styling (`app/theme.ts`, `app/ThemeProvider.tsx`)
|
|
10
|
+
|
|
11
|
+
## Dependencies
|
|
12
|
+
|
|
13
|
+
- Pin all npm package versions (no `^` or `~`).
|
|
14
|
+
- Use exact versions in package.json so installs are reproducible.
|
|
15
|
+
|
|
16
|
+
## React / TypeScript
|
|
17
|
+
|
|
18
|
+
- Named functions over arrow function assignments
|
|
19
|
+
- Proper types and interfaces; follow React hooks rules and lifecycle
|
|
20
|
+
- Error boundaries and handling where appropriate
|
|
21
|
+
- Path aliases (from `tsconfig.json`):
|
|
22
|
+
- `@atoms` → `./app/components/atoms`
|
|
23
|
+
- `@molecules` → `./app/components/molecules`
|
|
24
|
+
- `@organisms` → `./app/components/organisms`
|
|
25
|
+
- `@components` → `./app/components`
|
|
26
|
+
- `@constants` → `./app/constants`
|
|
27
|
+
- `types` → `./app/types` (bare module, no @ prefix)
|
|
28
|
+
|
|
29
|
+
## Dev Commands
|
|
30
|
+
|
|
31
|
+
- `npm run dev` — Start Next.js dev server
|
|
32
|
+
- `npm run build` — Build for production
|
|
33
|
+
- `npm run start` — Start production server
|
|
34
|
+
|
|
35
|
+
## Next.js
|
|
36
|
+
|
|
37
|
+
- Use App Router, Server Components, Client Components, Server Actions, middleware as appropriate
|
|
38
|
+
|
|
39
|
+
## Code Organization
|
|
40
|
+
|
|
41
|
+
- Single-responsibility components; separation of concerns
|
|
42
|
+
- Constants for config and string literals; clean folder structure; DRY
|
|
43
|
+
- Use Atomic Design for component hierarchy (see below)
|
|
44
|
+
- `app/api/` — API routes (agui, chat, stream endpoints)
|
|
45
|
+
- `app/(pages)/` — Route groups (agui, chat, stream pages)
|
|
46
|
+
- `app/agents/` — Agent implementations (e.g. age.ts, greeting.ts)
|
|
47
|
+
- `app/lib/providers/` — Provider factory functions (Anthropic, Bedrock, OpenAI)
|
|
48
|
+
|
|
49
|
+
## Component hierarchy (Atomic Design)
|
|
50
|
+
|
|
51
|
+
- Place UI components under `app/components/` in Atomic Design layers:
|
|
52
|
+
1. **atoms/** — Single-purpose primitives (e.g. MessageBubble, buttons, inputs).
|
|
53
|
+
2. **molecules/** — Compositions of atoms (e.g. ChatMessage, ChatInput).
|
|
54
|
+
3. **organisms/** — Compositions of molecules and layout (e.g. Chat, ChatMessageList).
|
|
55
|
+
- Pages import from organisms (or molecules when no organism exists).
|
|
56
|
+
- Each layer may only use components from the same or lower layers (atoms use MUI/styled only; molecules use atoms; organisms use molecules/atoms).
|
|
57
|
+
- Export public components via `index.ts` per folder.
|
|
58
|
+
|
|
59
|
+
## State & Data
|
|
60
|
+
|
|
61
|
+
- Appropriate React hooks; clear data flow
|
|
62
|
+
- Handle loading, error, and success states; predictable updates
|
|
63
|
+
|
|
64
|
+
## Testing
|
|
65
|
+
|
|
66
|
+
Testing infrastructure is not yet configured for the example app.
|
|
67
|
+
When added, tests should be comprehensive, runnable in isolation, and cover error cases.
|
|
68
|
+
|
|
69
|
+
## Naming Conventions
|
|
70
|
+
|
|
71
|
+
- camelCase for variables, functions, and file names
|
|
72
|
+
- PascalCase for React components, interfaces, and type aliases
|
|
73
|
+
- SCREAMING_SNAKE_CASE for module-level constants
|
|
74
|
+
|
|
75
|
+
## API & UX
|
|
76
|
+
|
|
77
|
+
- Proper error handling and loading states; correct response types; separate API logic
|
|
78
|
+
- Loading states, clear error messages, responsive design, accessibility
|
|
79
|
+
|
|
80
|
+
## Security
|
|
81
|
+
|
|
82
|
+
- Sensitive data handled appropriately (HIPAA-aware)
|
|
83
|
+
- Auth/authz where needed; no keys or tokens in code
|
|
84
|
+
|
|
85
|
+
## Docs
|
|
86
|
+
|
|
87
|
+
- Markdown per markdownlint (new line per sentence, "1." for lists; config inherited from root `.markdownlint.json`)
|
|
88
|
+
|
|
89
|
+
## Workflow
|
|
90
|
+
|
|
91
|
+
1. Understand requirements (business, technical, non-functional)
|
|
92
|
+
2. Plan implementation
|
|
93
|
+
3. Write tests, then code that passes them
|
|
94
|
+
4. Document decisions; review for best practices
|
|
95
|
+
|
|
96
|
+
Use latest React and TypeScript syntax. Code should be clean, readable, maintainable, efficient, and DRY.
|
|
97
|
+
|
|
98
|
+
## A3 Framework Documentation
|
|
99
|
+
|
|
100
|
+
The `docs/` directory contains essential A3 framework documentation.
|
|
101
|
+
**You MUST read the relevant files below** before implementing or modifying any feature that touches A3 agents, providers, sessions, resilience, or logging.
|
|
102
|
+
|
|
103
|
+
| File | Topic |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `docs/QUICK-START-EXAMPLES.md` | Agent definitions, registration, multi-agent flows, ChatSession usage |
|
|
106
|
+
| `docs/PROVIDERS.md` | Provider setup (Bedrock, OpenAI, Anthropic), config options, model fallback, per-agent overrides |
|
|
107
|
+
| `docs/RESILIENCE.md` | Retry, backoff, timeout config, error classification, resilience error handling |
|
|
108
|
+
| `docs/LOGGING.md` | Internal logging architecture, `log` singleton, log levels |
|
|
109
|
+
| `docs/CUSTOM_LOGGING.md` | Supplying a custom logger via `configureLogger()` |
|
|
110
|
+
|
|
111
|
+
When in doubt about A3 API usage, patterns, or configuration — **read the relevant doc file first**.
|
|
112
|
+
Any new `.md` files added to `docs/` are part of this documentation set and should be consulted as needed.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Example App
|
|
2
|
+
|
|
3
|
+
Next.js example for @genui-a3/core. AI rules for this app (Claude Code and Cursor). Single source of truth; Cursor uses via symlink `.cursorrules` → this file.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- Next.js 16.1.6 (App Router), React 19, TypeScript 5.9+
|
|
8
|
+
- Testing infrastructure planned (not yet configured)
|
|
9
|
+
- MUI 7 (@mui/material) + styled-components for styling (`app/theme.ts`, `app/ThemeProvider.tsx`)
|
|
10
|
+
|
|
11
|
+
## Dependencies
|
|
12
|
+
|
|
13
|
+
- Pin all npm package versions (no `^` or `~`).
|
|
14
|
+
- Use exact versions in package.json so installs are reproducible.
|
|
15
|
+
|
|
16
|
+
## React / TypeScript
|
|
17
|
+
|
|
18
|
+
- Named functions over arrow function assignments
|
|
19
|
+
- Proper types and interfaces; follow React hooks rules and lifecycle
|
|
20
|
+
- Error boundaries and handling where appropriate
|
|
21
|
+
- Path aliases (from `tsconfig.json`):
|
|
22
|
+
- `@atoms` → `./app/components/atoms`
|
|
23
|
+
- `@molecules` → `./app/components/molecules`
|
|
24
|
+
- `@organisms` → `./app/components/organisms`
|
|
25
|
+
- `@components` → `./app/components`
|
|
26
|
+
- `@constants` → `./app/constants`
|
|
27
|
+
- `types` → `./app/types` (bare module, no @ prefix)
|
|
28
|
+
|
|
29
|
+
## Dev Commands
|
|
30
|
+
|
|
31
|
+
- `npm run dev` — Start Next.js dev server
|
|
32
|
+
- `npm run build` — Build for production
|
|
33
|
+
- `npm run start` — Start production server
|
|
34
|
+
|
|
35
|
+
## Next.js
|
|
36
|
+
|
|
37
|
+
- Use App Router, Server Components, Client Components, Server Actions, middleware as appropriate
|
|
38
|
+
|
|
39
|
+
## Code Organization
|
|
40
|
+
|
|
41
|
+
- Single-responsibility components; separation of concerns
|
|
42
|
+
- Constants for config and string literals; clean folder structure; DRY
|
|
43
|
+
- Use Atomic Design for component hierarchy (see below)
|
|
44
|
+
- `app/api/` — API routes (agui, chat, stream endpoints)
|
|
45
|
+
- `app/(pages)/` — Route groups (agui, chat, stream pages)
|
|
46
|
+
- `app/agents/` — Agent implementations (e.g. age.ts, greeting.ts)
|
|
47
|
+
- `app/lib/providers/` — Provider factory functions (Anthropic, Bedrock, OpenAI)
|
|
48
|
+
|
|
49
|
+
## Component hierarchy (Atomic Design)
|
|
50
|
+
|
|
51
|
+
- Place UI components under `app/components/` in Atomic Design layers:
|
|
52
|
+
1. **atoms/** — Single-purpose primitives (e.g. MessageBubble, buttons, inputs).
|
|
53
|
+
2. **molecules/** — Compositions of atoms (e.g. ChatMessage, ChatInput).
|
|
54
|
+
3. **organisms/** — Compositions of molecules and layout (e.g. Chat, ChatMessageList).
|
|
55
|
+
- Pages import from organisms (or molecules when no organism exists).
|
|
56
|
+
- Each layer may only use components from the same or lower layers (atoms use MUI/styled only; molecules use atoms; organisms use molecules/atoms).
|
|
57
|
+
- Export public components via `index.ts` per folder.
|
|
58
|
+
|
|
59
|
+
## State & Data
|
|
60
|
+
|
|
61
|
+
- Appropriate React hooks; clear data flow
|
|
62
|
+
- Handle loading, error, and success states; predictable updates
|
|
63
|
+
|
|
64
|
+
## Testing
|
|
65
|
+
|
|
66
|
+
Testing infrastructure is not yet configured for the example app.
|
|
67
|
+
When added, tests should be comprehensive, runnable in isolation, and cover error cases.
|
|
68
|
+
|
|
69
|
+
## Naming Conventions
|
|
70
|
+
|
|
71
|
+
- camelCase for variables, functions, and file names
|
|
72
|
+
- PascalCase for React components, interfaces, and type aliases
|
|
73
|
+
- SCREAMING_SNAKE_CASE for module-level constants
|
|
74
|
+
|
|
75
|
+
## API & UX
|
|
76
|
+
|
|
77
|
+
- Proper error handling and loading states; correct response types; separate API logic
|
|
78
|
+
- Loading states, clear error messages, responsive design, accessibility
|
|
79
|
+
|
|
80
|
+
## Security
|
|
81
|
+
|
|
82
|
+
- Sensitive data handled appropriately (HIPAA-aware)
|
|
83
|
+
- Auth/authz where needed; no keys or tokens in code
|
|
84
|
+
|
|
85
|
+
## Docs
|
|
86
|
+
|
|
87
|
+
- Markdown per markdownlint (new line per sentence, "1." for lists; config inherited from root `.markdownlint.json`)
|
|
88
|
+
|
|
89
|
+
## Workflow
|
|
90
|
+
|
|
91
|
+
1. Understand requirements (business, technical, non-functional)
|
|
92
|
+
2. Plan implementation
|
|
93
|
+
3. Write tests, then code that passes them
|
|
94
|
+
4. Document decisions; review for best practices
|
|
95
|
+
|
|
96
|
+
Use latest React and TypeScript syntax. Code should be clean, readable, maintainable, efficient, and DRY.
|
|
97
|
+
|
|
98
|
+
## A3 Framework Documentation
|
|
99
|
+
|
|
100
|
+
The `docs/` directory contains essential A3 framework documentation.
|
|
101
|
+
**You MUST read the relevant files below** before implementing or modifying any feature that touches A3 agents, providers, sessions, resilience, or logging.
|
|
102
|
+
|
|
103
|
+
| File | Topic |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `docs/QUICK-START-EXAMPLES.md` | Agent definitions, registration, multi-agent flows, ChatSession usage |
|
|
106
|
+
| `docs/PROVIDERS.md` | Provider setup (Bedrock, OpenAI, Anthropic), config options, model fallback, per-agent overrides |
|
|
107
|
+
| `docs/RESILIENCE.md` | Retry, backoff, timeout config, error classification, resilience error handling |
|
|
108
|
+
| `docs/LOGGING.md` | Internal logging architecture, `log` singleton, log levels |
|
|
109
|
+
| `docs/CUSTOM_LOGGING.md` | Supplying a custom logger via `configureLogger()` |
|
|
110
|
+
|
|
111
|
+
When in doubt about A3 API usage, patterns, or configuration — **read the relevant doc file first**.
|
|
112
|
+
Any new `.md` files added to `docs/` are part of this documentation set and should be consulted as needed.
|
package/template/README.md
CHANGED
|
@@ -11,6 +11,7 @@ const agePayload = z.object({
|
|
|
11
11
|
|
|
12
12
|
export const ageAgent: Agent<State> = {
|
|
13
13
|
id: 'age',
|
|
14
|
+
description: "Gets the user's age.",
|
|
14
15
|
prompt: `
|
|
15
16
|
You are a friendly agent. Your goal is to learn the user's age.
|
|
16
17
|
If you don't know their age yet, ask for it politely.
|
|
@@ -19,6 +19,7 @@ const greetingPayload = z.object({
|
|
|
19
19
|
|
|
20
20
|
export const greetingAgent: Agent<State> = {
|
|
21
21
|
id: 'greeting',
|
|
22
|
+
description: 'Greets the user and learns their name.',
|
|
22
23
|
prompt: `
|
|
23
24
|
You are a friendly greeting agent. Your goal is to greet the user and learn their name.
|
|
24
25
|
You also handle name changes — if the user wants to update their name, collect the new one.
|