@hustle-together/api-dev-tools 3.11.1 → 3.12.1

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.
Files changed (139) hide show
  1. package/.claude/agents/code-reviewer.md +170 -0
  2. package/.claude/agents/docs-generator.md +80 -0
  3. package/.claude/agents/implementation-reviewer.md +119 -0
  4. package/.claude/agents/parallel-researcher.md +52 -0
  5. package/.claude/agents/research-validator.md +116 -0
  6. package/.claude/agents/schema-generator.md +70 -0
  7. package/.claude/agents/test-writer.md +104 -0
  8. package/.claude/api-dev-state.json +228 -56
  9. package/.claude/commands/README.md +21 -10
  10. package/.claude/commands/add-command.md +8 -5
  11. package/.claude/commands/api-create.md +36 -25
  12. package/.claude/commands/api-env.md +1 -0
  13. package/.claude/commands/api-interview.md +32 -19
  14. package/.claude/commands/api-research.md +47 -21
  15. package/.claude/commands/api-status.md +21 -1
  16. package/.claude/commands/api-verify.md +14 -13
  17. package/.claude/commands/beepboop.md +4 -5
  18. package/.claude/commands/busycommit.md +2 -3
  19. package/.claude/commands/commit.md +2 -3
  20. package/.claude/commands/cycle.md +2 -7
  21. package/.claude/commands/gap.md +2 -3
  22. package/.claude/commands/green.md +2 -7
  23. package/.claude/commands/issue.md +3 -8
  24. package/.claude/commands/ntfy-setup.md +91 -0
  25. package/.claude/commands/ntfy-test.md +74 -0
  26. package/.claude/commands/plan.md +2 -3
  27. package/.claude/commands/pr.md +2 -3
  28. package/.claude/commands/publish.md +40 -0
  29. package/.claude/commands/red.md +2 -7
  30. package/.claude/commands/refactor.md +2 -7
  31. package/.claude/commands/spike.md +2 -7
  32. package/.claude/commands/summarize.md +2 -3
  33. package/.claude/commands/tdd.md +2 -7
  34. package/.claude/commands/worktree-add.md +208 -216
  35. package/.claude/commands/worktree-cleanup.md +172 -178
  36. package/.claude/settings.json +63 -12
  37. package/.claude/settings.local.json +2 -1
  38. package/.claude-plugin/marketplace.json +2 -11
  39. package/.skills/README.md +55 -53
  40. package/.skills/_shared/settings.json +1 -1
  41. package/.skills/add-command/SKILL.md +10 -5
  42. package/.skills/api-create/SKILL.md +146 -35
  43. package/.skills/api-env/SKILL.md +1 -0
  44. package/.skills/api-interview/SKILL.md +32 -19
  45. package/.skills/api-research/SKILL.md +47 -21
  46. package/.skills/api-status/SKILL.md +21 -1
  47. package/.skills/api-verify/SKILL.md +14 -13
  48. package/.skills/beepboop/SKILL.md +6 -5
  49. package/.skills/busycommit/SKILL.md +4 -3
  50. package/.skills/commit/SKILL.md +4 -3
  51. package/.skills/cycle/SKILL.md +4 -7
  52. package/.skills/gap/SKILL.md +4 -3
  53. package/.skills/green/SKILL.md +4 -7
  54. package/.skills/issue/SKILL.md +5 -8
  55. package/.skills/plan/SKILL.md +4 -3
  56. package/.skills/pr/SKILL.md +4 -3
  57. package/.skills/publish/SKILL.md +160 -0
  58. package/.skills/red/SKILL.md +4 -7
  59. package/.skills/refactor/SKILL.md +4 -7
  60. package/.skills/spike/SKILL.md +4 -7
  61. package/.skills/summarize/SKILL.md +4 -3
  62. package/.skills/tdd/SKILL.md +4 -7
  63. package/.skills/update-todos/SKILL.md +22 -0
  64. package/.skills/worktree-add/SKILL.md +210 -216
  65. package/.skills/worktree-cleanup/SKILL.md +183 -187
  66. package/CHANGELOG.md +97 -79
  67. package/README.md +161 -7142
  68. package/bin/cli.js +448 -805
  69. package/commands/README.md +66 -31
  70. package/commands/add-command.md +8 -5
  71. package/commands/beepboop.md +4 -5
  72. package/commands/busycommit.md +2 -3
  73. package/commands/commit.md +2 -3
  74. package/commands/cycle.md +2 -7
  75. package/commands/gap.md +2 -3
  76. package/commands/green.md +2 -7
  77. package/commands/hustle-api-continue.md +8 -5
  78. package/commands/hustle-api-create.md +70 -29
  79. package/commands/hustle-api-env.md +1 -0
  80. package/commands/hustle-api-interview.md +32 -19
  81. package/commands/hustle-api-research.md +47 -21
  82. package/commands/hustle-api-sessions.md +8 -7
  83. package/commands/hustle-api-status.md +21 -1
  84. package/commands/hustle-api-verify.md +14 -13
  85. package/commands/hustle-combine.md +488 -241
  86. package/commands/hustle-ui-create-page.md +113 -50
  87. package/commands/hustle-ui-create.md +179 -26
  88. package/commands/issue.md +3 -8
  89. package/commands/plan.md +2 -3
  90. package/commands/pr.md +2 -3
  91. package/commands/red.md +2 -7
  92. package/commands/refactor.md +2 -7
  93. package/commands/spike.md +2 -7
  94. package/commands/summarize.md +2 -3
  95. package/commands/tdd.md +2 -7
  96. package/commands/worktree-add.md +208 -216
  97. package/commands/worktree-cleanup.md +172 -178
  98. package/hooks/api-workflow-check.py +5 -3
  99. package/hooks/enforce-component-type-confirm.py +97 -0
  100. package/hooks/lib/__init__.py +1 -0
  101. package/hooks/lib/greptile.py +355 -0
  102. package/hooks/lib/ntfy.py +209 -0
  103. package/hooks/notify-input-needed.py +73 -0
  104. package/hooks/notify-phase-complete.py +90 -0
  105. package/hooks/run-code-review.py +246 -0
  106. package/hooks/track-token-usage.py +121 -0
  107. package/package.json +13 -3
  108. package/scripts/collect-test-results.ts +102 -77
  109. package/scripts/extract-parameters.ts +112 -70
  110. package/scripts/generate-test-manifest.ts +118 -77
  111. package/templates/.env.example +57 -0
  112. package/templates/BRAND_GUIDE.md +92 -52
  113. package/templates/CLAUDE-SECTION.md +40 -37
  114. package/templates/SPEC.json +186 -38
  115. package/templates/api-dev-state.json +33 -4
  116. package/templates/api-showcase/_components/APICard.tsx +22 -18
  117. package/templates/api-showcase/_components/APIModal.tsx +110 -64
  118. package/templates/api-showcase/_components/APIShowcase.tsx +53 -35
  119. package/templates/api-showcase/_components/APITester.tsx +128 -67
  120. package/templates/api-showcase/page.tsx +4 -4
  121. package/templates/api-test/page.tsx +51 -30
  122. package/templates/api-test/test-structure/route.ts +43 -34
  123. package/templates/component/Component.stories.tsx +41 -39
  124. package/templates/component/Component.test.tsx +96 -78
  125. package/templates/component/Component.tsx +63 -52
  126. package/templates/component/Component.types.ts +10 -6
  127. package/templates/component/Component.visual.spec.ts +170 -0
  128. package/templates/component/index.ts +2 -2
  129. package/templates/dev-tools/_components/DevToolsLanding.tsx +8 -8
  130. package/templates/dev-tools/page.tsx +4 -3
  131. package/templates/mcp-servers.json +30 -2
  132. package/templates/page/page.e2e.test.ts +56 -48
  133. package/templates/page/page.tsx +3 -3
  134. package/templates/shared/HeroHeader.tsx +16 -15
  135. package/templates/shared/index.ts +1 -1
  136. package/templates/ui-showcase/_components/PreviewCard.tsx +20 -20
  137. package/templates/ui-showcase/_components/PreviewModal.tsx +149 -108
  138. package/templates/ui-showcase/_components/UIShowcase.tsx +43 -35
  139. package/templates/ui-showcase/page.tsx +4 -4
@@ -14,8 +14,8 @@
14
14
  * @generated by @hustle-together/api-dev-tools v3.0
15
15
  */
16
16
 
17
- import fs from 'fs';
18
- import path from 'path';
17
+ import fs from "fs";
18
+ import path from "path";
19
19
 
20
20
  // ============================================
21
21
  // Types
@@ -80,7 +80,11 @@ interface ManifestOutput {
80
80
  // File Discovery
81
81
  // ============================================
82
82
 
83
- function findFiles(baseDir: string, pattern: RegExp, exclude: string[] = ['node_modules', '.git', 'dist']): string[] {
83
+ function findFiles(
84
+ baseDir: string,
85
+ pattern: RegExp,
86
+ exclude: string[] = ["node_modules", ".git", "dist"],
87
+ ): string[] {
84
88
  const files: string[] = [];
85
89
 
86
90
  function walk(dir: string) {
@@ -109,9 +113,13 @@ function findFiles(baseDir: string, pattern: RegExp, exclude: string[] = ['node_
109
113
  // Test File Parser
110
114
  // ============================================
111
115
 
112
- function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: string; method?: string } {
113
- const content = fs.readFileSync(filePath, 'utf-8');
114
- const lines = content.split('\n');
116
+ function parseTestFile(filePath: string): {
117
+ groups: TestGroup[];
118
+ endpoint?: string;
119
+ method?: string;
120
+ } {
121
+ const content = fs.readFileSync(filePath, "utf-8");
122
+ const lines = content.split("\n");
115
123
 
116
124
  const rootGroups: TestGroup[] = [];
117
125
  const groupStack: TestGroup[] = [];
@@ -130,7 +138,9 @@ function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: stri
130
138
  }
131
139
 
132
140
  // Look for HTTP method
133
- const methodMatch = content.match(/(?:GET|POST|PUT|DELETE|PATCH)\s*(?:request|endpoint)?/i);
141
+ const methodMatch = content.match(
142
+ /(?:GET|POST|PUT|DELETE|PATCH)\s*(?:request|endpoint)?/i,
143
+ );
134
144
  if (methodMatch) {
135
145
  method = methodMatch[0].split(/\s/)[0].toUpperCase();
136
146
  }
@@ -145,7 +155,7 @@ function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: stri
145
155
  const group: TestGroup = {
146
156
  name: describeMatch[1],
147
157
  tests: [],
148
- groups: []
158
+ groups: [],
149
159
  };
150
160
 
151
161
  if (groupStack.length > 0) {
@@ -163,11 +173,11 @@ function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: stri
163
173
  if (testMatch && groupStack.length > 0) {
164
174
  const testCase: TestCase = {
165
175
  name: testMatch[1],
166
- line: lineNum
176
+ line: lineNum,
167
177
  };
168
178
 
169
179
  // Extract description from test name
170
- if (testCase.name.includes('should')) {
180
+ if (testCase.name.includes("should")) {
171
181
  testCase.description = testCase.name;
172
182
  }
173
183
 
@@ -196,7 +206,7 @@ function parseTestFile(filePath: string): { groups: TestGroup[]; endpoint?: stri
196
206
  // ============================================
197
207
 
198
208
  function parseZodSchema(filePath: string): ParameterInfo[] {
199
- const content = fs.readFileSync(filePath, 'utf-8');
209
+ const content = fs.readFileSync(filePath, "utf-8");
200
210
  const parameters: ParameterInfo[] = [];
201
211
 
202
212
  // Match z.object({ ... }) blocks
@@ -215,11 +225,15 @@ function parseZodSchema(filePath: string): ParameterInfo[] {
215
225
  const param: ParameterInfo = {
216
226
  name,
217
227
  type: mapZodType(type),
218
- required: !modifiers.includes('.optional()') && !modifiers.includes('.nullable()')
228
+ required:
229
+ !modifiers.includes(".optional()") &&
230
+ !modifiers.includes(".nullable()"),
219
231
  };
220
232
 
221
233
  // Extract description from .describe()
222
- const descMatch = modifiers.match(/\.describe\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/);
234
+ const descMatch = modifiers.match(
235
+ /\.describe\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/,
236
+ );
223
237
  if (descMatch) {
224
238
  param.description = descMatch[1];
225
239
  }
@@ -235,12 +249,12 @@ function parseZodSchema(filePath: string): ParameterInfo[] {
235
249
  }
236
250
 
237
251
  // Extract enum values for z.enum()
238
- if (type === 'enum' && typeArgs) {
252
+ if (type === "enum" && typeArgs) {
239
253
  const enumMatch = typeArgs.match(/\[([^\]]+)\]/);
240
254
  if (enumMatch) {
241
255
  param.enum = enumMatch[1]
242
- .split(',')
243
- .map(s => s.trim().replace(/['"`]/g, ''));
256
+ .split(",")
257
+ .map((s) => s.trim().replace(/['"`]/g, ""));
244
258
  }
245
259
  }
246
260
 
@@ -253,23 +267,23 @@ function parseZodSchema(filePath: string): ParameterInfo[] {
253
267
 
254
268
  function mapZodType(zodType: string): string {
255
269
  const typeMap: Record<string, string> = {
256
- 'string': 'string',
257
- 'number': 'number',
258
- 'boolean': 'boolean',
259
- 'array': 'array',
260
- 'object': 'object',
261
- 'enum': 'enum',
262
- 'literal': 'literal',
263
- 'union': 'union',
264
- 'date': 'date',
265
- 'any': 'any',
266
- 'unknown': 'unknown',
267
- 'null': 'null',
268
- 'undefined': 'undefined',
269
- 'void': 'void',
270
- 'never': 'never',
271
- 'bigint': 'bigint',
272
- 'symbol': 'symbol'
270
+ string: "string",
271
+ number: "number",
272
+ boolean: "boolean",
273
+ array: "array",
274
+ object: "object",
275
+ enum: "enum",
276
+ literal: "literal",
277
+ union: "union",
278
+ date: "date",
279
+ any: "any",
280
+ unknown: "unknown",
281
+ null: "null",
282
+ undefined: "undefined",
283
+ void: "void",
284
+ never: "never",
285
+ bigint: "bigint",
286
+ symbol: "symbol",
273
287
  };
274
288
  return typeMap[zodType] || zodType;
275
289
  }
@@ -278,14 +292,17 @@ function mapZodType(zodType: string): string {
278
292
  // Route File Parser
279
293
  // ============================================
280
294
 
281
- function parseRouteFile(filePath: string): { methods: string[]; description?: string } {
282
- const content = fs.readFileSync(filePath, 'utf-8');
295
+ function parseRouteFile(filePath: string): {
296
+ methods: string[];
297
+ description?: string;
298
+ } {
299
+ const content = fs.readFileSync(filePath, "utf-8");
283
300
  const methods: string[] = [];
284
301
 
285
302
  // Find exported HTTP method handlers
286
303
  const methodPatterns = [
287
304
  /export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/g,
288
- /export\s+const\s+(GET|POST|PUT|DELETE|PATCH)\s*=/g
305
+ /export\s+const\s+(GET|POST|PUT|DELETE|PATCH)\s*=/g,
289
306
  ];
290
307
 
291
308
  for (const pattern of methodPatterns) {
@@ -314,8 +331,10 @@ interface InterviewState {
314
331
  phase: number;
315
332
  }
316
333
 
317
- function readInterviewState(baseDir: string): Map<string, Record<string, string>> {
318
- const stateFile = path.join(baseDir, '.claude', 'api-dev-state.json');
334
+ function readInterviewState(
335
+ baseDir: string,
336
+ ): Map<string, Record<string, string>> {
337
+ const stateFile = path.join(baseDir, ".claude", "api-dev-state.json");
319
338
  const decisions = new Map<string, Record<string, string>>();
320
339
 
321
340
  if (!fs.existsSync(stateFile)) {
@@ -323,7 +342,7 @@ function readInterviewState(baseDir: string): Map<string, Record<string, string>
323
342
  }
324
343
 
325
344
  try {
326
- const content = fs.readFileSync(stateFile, 'utf-8');
345
+ const content = fs.readFileSync(stateFile, "utf-8");
327
346
  const state = JSON.parse(content);
328
347
 
329
348
  if (Array.isArray(state.endpoints)) {
@@ -345,7 +364,7 @@ function readInterviewState(baseDir: string): Map<string, Record<string, string>
345
364
  // ============================================
346
365
 
347
366
  function generateManifest(baseDir: string): ManifestOutput {
348
- console.log('🔍 Scanning for test files...');
367
+ console.log("🔍 Scanning for test files...");
349
368
 
350
369
  // Find all test files
351
370
  const testFiles = findFiles(baseDir, /\.(test|spec)\.(ts|tsx)$/);
@@ -393,9 +412,9 @@ function generateManifest(baseDir: string): ManifestOutput {
393
412
 
394
413
  // Determine category from path
395
414
  const pathParts = relativePath.split(path.sep);
396
- let category = 'General';
397
- if (pathParts.includes('api')) {
398
- const apiIndex = pathParts.indexOf('api');
415
+ let category = "General";
416
+ if (pathParts.includes("api")) {
417
+ const apiIndex = pathParts.indexOf("api");
399
418
  if (apiIndex + 1 < pathParts.length) {
400
419
  category = pathParts[apiIndex + 1];
401
420
  }
@@ -403,14 +422,20 @@ function generateManifest(baseDir: string): ManifestOutput {
403
422
  categories.add(category);
404
423
 
405
424
  // Find matching route file
406
- const routeDir = path.dirname(testFile).replace('__tests__', '').replace('.test', '');
425
+ const routeDir = path
426
+ .dirname(testFile)
427
+ .replace("__tests__", "")
428
+ .replace(".test", "");
407
429
  const possibleRoutes = [
408
- path.join(routeDir, 'route.ts'),
409
- path.join(routeDir, 'route.tsx'),
410
- testFile.replace(/\.(test|spec)\.(ts|tsx)$/, '.ts')
430
+ path.join(routeDir, "route.ts"),
431
+ path.join(routeDir, "route.tsx"),
432
+ testFile.replace(/\.(test|spec)\.(ts|tsx)$/, ".ts"),
411
433
  ];
412
434
 
413
- let routeInfo = { methods: [method || 'GET'], description: undefined as string | undefined };
435
+ let routeInfo = {
436
+ methods: [method || "GET"],
437
+ description: undefined as string | undefined,
438
+ };
414
439
  for (const routePath of possibleRoutes) {
415
440
  if (fs.existsSync(routePath)) {
416
441
  routeInfo = parseRouteFile(routePath);
@@ -419,9 +444,11 @@ function generateManifest(baseDir: string): ManifestOutput {
419
444
  }
420
445
 
421
446
  // Find matching schema file
422
- const schemaBaseName = path.basename(testFile).replace(/\.(test|spec)\.(ts|tsx)$/, '');
423
- const matchingSchemas = schemaFiles.filter(s =>
424
- s.includes(schemaBaseName) || s.includes(category.toLowerCase())
447
+ const schemaBaseName = path
448
+ .basename(testFile)
449
+ .replace(/\.(test|spec)\.(ts|tsx)$/, "");
450
+ const matchingSchemas = schemaFiles.filter(
451
+ (s) => s.includes(schemaBaseName) || s.includes(category.toLowerCase()),
425
452
  );
426
453
 
427
454
  let parameters: ParameterInfo[] = [];
@@ -434,49 +461,53 @@ function generateManifest(baseDir: string): ManifestOutput {
434
461
 
435
462
  // Generate endpoint ID
436
463
  const endpointId = endpoint
437
- .replace(/^\/api\//, '')
438
- .replace(/\//g, '-')
439
- .replace(/[^a-z0-9-]/gi, '');
464
+ .replace(/^\/api\//, "")
465
+ .replace(/\//g, "-")
466
+ .replace(/[^a-z0-9-]/gi, "");
440
467
 
441
468
  const manifest: EndpointManifest = {
442
469
  id: endpointId,
443
470
  name: groups[0]?.name || endpointId,
444
471
  endpoint,
445
- method: routeInfo.methods[0] || method || 'GET',
472
+ method: routeInfo.methods[0] || method || "GET",
446
473
  description: routeInfo.description || `API endpoint: ${endpoint}`,
447
474
  category,
448
475
  parameters: {
449
- query: parameters.filter(p => !p.name.startsWith('body')),
450
- body: parameters.filter(p => p.name.startsWith('body') || p.name === 'data'),
451
- headers: []
476
+ query: parameters.filter((p) => !p.name.startsWith("body")),
477
+ body: parameters.filter(
478
+ (p) => p.name.startsWith("body") || p.name === "data",
479
+ ),
480
+ headers: [],
452
481
  },
453
482
  responses: {
454
- success: { status: 200, description: 'Successful response' },
483
+ success: { status: 200, description: "Successful response" },
455
484
  error: [
456
- { status: 400, description: 'Bad request' },
457
- { status: 500, description: 'Internal server error' }
458
- ]
485
+ { status: 400, description: "Bad request" },
486
+ { status: 500, description: "Internal server error" },
487
+ ],
459
488
  },
460
489
  testFile: relativePath,
461
490
  testCount,
462
491
  testCases,
463
492
  interviewDecisions: decisions,
464
- generatedAt: new Date().toISOString()
493
+ generatedAt: new Date().toISOString(),
465
494
  };
466
495
 
467
496
  endpoints.push(manifest);
468
- console.log(` ✅ Generated manifest for ${endpoint} (${testCount} tests)`);
497
+ console.log(
498
+ ` ✅ Generated manifest for ${endpoint} (${testCount} tests)`,
499
+ );
469
500
  }
470
501
 
471
502
  return {
472
- version: '3.0.0',
503
+ version: "3.0.0",
473
504
  generatedAt: new Date().toISOString(),
474
505
  endpoints,
475
506
  summary: {
476
507
  totalEndpoints: endpoints.length,
477
508
  totalTests,
478
- categories: Array.from(categories)
479
- }
509
+ categories: Array.from(categories),
510
+ },
480
511
  };
481
512
  }
482
513
 
@@ -487,12 +518,18 @@ function generateManifest(baseDir: string): ManifestOutput {
487
518
  function main() {
488
519
  const args = process.argv.slice(2);
489
520
  const baseDir = args[0] || process.cwd();
490
- const outputPath = args[1] || path.join(baseDir, 'src', 'app', 'api-test', 'api-tests-manifest.json');
491
-
492
- console.log('═══════════════════════════════════════════════════════════════');
493
- console.log(' 📋 API Test Manifest Generator');
494
- console.log(' @hustle-together/api-dev-tools v3.0');
495
- console.log('═══════════════════════════════════════════════════════════════');
521
+ const outputPath =
522
+ args[1] ||
523
+ path.join(baseDir, "src", "app", "api-test", "api-tests-manifest.json");
524
+
525
+ console.log(
526
+ "═══════════════════════════════════════════════════════════════",
527
+ );
528
+ console.log(" 📋 API Test Manifest Generator");
529
+ console.log(" @hustle-together/api-dev-tools v3.0");
530
+ console.log(
531
+ "═══════════════════════════════════════════════════════════════",
532
+ );
496
533
  console.log(`\n📁 Base directory: ${baseDir}`);
497
534
  console.log(`📄 Output file: ${outputPath}\n`);
498
535
 
@@ -507,13 +544,17 @@ function main() {
507
544
  // Write manifest
508
545
  fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
509
546
 
510
- console.log('\n═══════════════════════════════════════════════════════════════');
511
- console.log(' ✅ Manifest generated successfully!');
512
- console.log('═══════════════════════════════════════════════════════════════');
547
+ console.log(
548
+ "\n═══════════════════════════════════════════════════════════════",
549
+ );
550
+ console.log(" ✅ Manifest generated successfully!");
551
+ console.log(
552
+ "═══════════════════════════════════════════════════════════════",
553
+ );
513
554
  console.log(`\n📊 Summary:`);
514
555
  console.log(` • Endpoints: ${manifest.summary.totalEndpoints}`);
515
556
  console.log(` • Tests: ${manifest.summary.totalTests}`);
516
- console.log(` • Categories: ${manifest.summary.categories.join(', ')}`);
557
+ console.log(` • Categories: ${manifest.summary.categories.join(", ")}`);
517
558
  console.log(`\n📄 Output: ${outputPath}\n`);
518
559
  }
519
560
 
@@ -0,0 +1,57 @@
1
+ # =============================================================================
2
+ # API Dev Tools - Environment Configuration
3
+ # =============================================================================
4
+ # Copy this file to .env in your project root and fill in your values.
5
+ # Never commit .env files with real credentials to git!
6
+ # =============================================================================
7
+
8
+ # -----------------------------------------------------------------------------
9
+ # NTFY Notifications (Optional)
10
+ # -----------------------------------------------------------------------------
11
+ # Get a free topic at https://ntfy.sh or self-host
12
+ NTFY_ENABLED=false
13
+ NTFY_SERVER=https://ntfy.sh
14
+ NTFY_TOPIC=your-unique-topic-name
15
+
16
+ # -----------------------------------------------------------------------------
17
+ # GitHub Integration
18
+ # -----------------------------------------------------------------------------
19
+ # Create at: https://github.com/settings/tokens
20
+ # Required scopes: repo, read:org
21
+ GITHUB_PERSONAL_ACCESS_TOKEN=
22
+
23
+ # -----------------------------------------------------------------------------
24
+ # Common API Keys (Add as needed for your integrations)
25
+ # -----------------------------------------------------------------------------
26
+ # These are examples - add keys for the APIs you integrate
27
+
28
+ # OpenAI
29
+ OPENAI_API_KEY=
30
+
31
+ # Anthropic
32
+ ANTHROPIC_API_KEY=
33
+
34
+ # Vercel AI SDK
35
+ # (Uses OPENAI_API_KEY or ANTHROPIC_API_KEY automatically)
36
+
37
+ # -----------------------------------------------------------------------------
38
+ # Development Settings
39
+ # -----------------------------------------------------------------------------
40
+ # Storybook URL for visual testing
41
+ STORYBOOK_URL=http://localhost:6006
42
+
43
+ # API base URL for testing
44
+ API_BASE_URL=http://localhost:3000
45
+
46
+ # -----------------------------------------------------------------------------
47
+ # Token Usage Tracking
48
+ # -----------------------------------------------------------------------------
49
+ # Track Claude Code token usage per session
50
+ TRACK_TOKEN_USAGE=true
51
+
52
+ # -----------------------------------------------------------------------------
53
+ # TypeDoc Documentation
54
+ # -----------------------------------------------------------------------------
55
+ # Auto-generate docs after Phase 12
56
+ TYPEDOC_AUTO_GENERATE=true
57
+ TYPEDOC_OUTPUT_DIR=docs/api