@claudetools/tools 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -67,19 +67,75 @@ CLAUDETOOLS_API_URL=https://api.claudetools.dev
67
67
  - **Impact analysis** for changes
68
68
  - **Pattern detection** (security, performance)
69
69
 
70
+ ### CodeDNA (AI Code Generation)
71
+ - **95-99% token savings** by generating production code from compact Entity DSL specs
72
+ - **Production-ready APIs** with models, controllers, routes, validation, tests
73
+ - **Express.js generator** available (FastAPI, NestJS, React coming soon)
74
+ - **Template registry** hosted on ClaudeTools API with global CDN
75
+
76
+ ### 10/10 Prompt Engineering Framework
77
+ - **Production-proven architecture** from Claude 4 analysis (~60K char prompt)
78
+ - **4 complexity tiers** (Minimal/Standard/Professional/Enterprise) with progressive disclosure
79
+ - **7 semantic layers** with XML boundaries for machine-parseability
80
+ - **Token-optimized templates** (500t → 10000t based on complexity)
81
+ - **Compliance verification** with automated validation script
82
+
83
+ #### Quick Example
84
+
85
+ ```typescript
86
+ // Instead of AI writing 1000 lines of code (30,000 tokens)...
87
+ // Use a compact Entity DSL spec (150 tokens):
88
+
89
+ codedna_generate_api({
90
+ spec: "User(email:string:unique, password:string:hashed, age:integer:min(18))",
91
+ framework: "express",
92
+ options: {
93
+ auth: true,
94
+ validation: true,
95
+ tests: true,
96
+ database: "postgresql"
97
+ }
98
+ })
99
+
100
+ // Returns 6 complete files:
101
+ // - src/models/user.model.ts
102
+ // - src/controllers/user.controller.ts
103
+ // - src/routes/user.routes.ts
104
+ // - src/validators/user.validator.ts
105
+ // - src/middleware/auth.middleware.ts
106
+ // - tests/user.test.ts
107
+
108
+ // Token savings: 11,100 tokens (98.7%)
109
+ // Cost savings: $0.11 per generation
110
+ // Time savings: 2-3 minutes → 10 seconds
111
+ ```
112
+
113
+ #### Available Tools
114
+
115
+ - `codedna_generate_api(spec, framework, options)` - Generate complete REST API
116
+ - `codedna_validate_spec(spec)` - Validate Entity DSL syntax
117
+ - `codedna_list_generators()` - List available code generators
118
+
119
+ See [CODEDNA_README.md](./CODEDNA_README.md) for full documentation.
120
+
70
121
  ## CLI
71
122
 
72
123
  ```bash
73
- claudetools --setup # Interactive configuration
74
- claudetools --version # Show version
75
- claudetools --help # Show help
76
- claudetools # Start MCP server
124
+ claudetools --setup # Interactive configuration
125
+ claudetools --version # Show version
126
+ claudetools --help # Show help
127
+ claudetools # Start MCP server
128
+ npm run prompt:verify # Verify prompt compliance with 10/10 framework
77
129
  ```
78
130
 
79
131
  ## Documentation
80
132
 
81
133
  - [GitHub](https://github.com/claudetools/memory)
82
134
  - [Configuration Guide](./CONFIG.md)
135
+ - [CodeDNA Guide](./CODEDNA_README.md) - AI code generation with 95-99% token savings
136
+ - [10/10 Prompt Framework](./docs/PROMPT_TIER_TEMPLATES.md) - Production-grade prompt engineering
137
+ - [Claude 4 Analysis](./docs/CLAUDE4_PROMPT_ANALYSIS.md) - Insights from ~60K char production prompt
138
+ - [Research Methodology](./docs/RESEARCH_METHODOLOGY_EXTRACTION.md) - Claude.ai Desktop research patterns
83
139
 
84
140
  ## License
85
141
 
package/dist/cli.js CHANGED
File without changes
@@ -1,6 +1,21 @@
1
1
  export interface EntitySpec {
2
2
  name: string;
3
3
  fields: Field[];
4
+ hooks?: LifecycleHooks;
5
+ permissions?: Permission[];
6
+ }
7
+ export interface LifecycleHooks {
8
+ beforeCreate?: string[];
9
+ afterCreate?: string[];
10
+ beforeUpdate?: string[];
11
+ afterUpdate?: string[];
12
+ beforeDelete?: string[];
13
+ afterDelete?: string[];
14
+ }
15
+ export interface Permission {
16
+ action: 'create' | 'read' | 'update' | 'delete';
17
+ roles?: string[];
18
+ condition?: string;
4
19
  }
5
20
  export interface Field {
6
21
  name: string;
@@ -9,13 +24,20 @@ export interface Field {
9
24
  }
10
25
  export type FieldType = {
11
26
  kind: 'primitive';
12
- value: 'string' | 'integer' | 'decimal' | 'boolean' | 'datetime';
27
+ value: 'string' | 'integer' | 'decimal' | 'boolean' | 'datetime' | 'email' | 'url' | 'json';
28
+ } | {
29
+ kind: 'array';
30
+ itemType: 'string' | 'integer' | 'decimal' | 'boolean';
13
31
  } | {
14
32
  kind: 'reference';
15
33
  entity: string;
34
+ relation?: 'oneToMany' | 'manyToMany';
16
35
  } | {
17
36
  kind: 'enum';
18
37
  values: string[];
38
+ } | {
39
+ kind: 'computed';
40
+ expression: string;
19
41
  };
20
42
  export type Constraint = {
21
43
  kind: 'unique';
@@ -34,6 +56,21 @@ export type Constraint = {
34
56
  } | {
35
57
  kind: 'default';
36
58
  value: string;
59
+ } | {
60
+ kind: 'length';
61
+ min?: number;
62
+ max?: number;
63
+ } | {
64
+ kind: 'pattern';
65
+ regex: string;
66
+ } | {
67
+ kind: 'email';
68
+ } | {
69
+ kind: 'url';
70
+ } | {
71
+ kind: 'nullable';
72
+ } | {
73
+ kind: 'immutable';
37
74
  };
38
75
  export declare class EntityParser {
39
76
  /**
@@ -54,11 +91,11 @@ export declare class EntityParser {
54
91
  */
55
92
  private parseField;
56
93
  /**
57
- * Parse field type (primitive, reference, enum)
94
+ * Parse field type (primitive, reference, enum, array, computed)
58
95
  */
59
96
  private parseType;
60
97
  /**
61
- * Parse field constraint (unique, required, hashed, min, max, default)
98
+ * Parse field constraint (unique, required, hashed, min, max, default, length, pattern, email, url, nullable, immutable)
62
99
  */
63
100
  private parseConstraint;
64
101
  }
@@ -22,12 +22,12 @@ export class EntityParser {
22
22
  }
23
23
  const name = nameMatch[1];
24
24
  // Extract fields section
25
- const fieldsMatch = spec.match(/\((.+)\)$/);
25
+ const fieldsMatch = spec.match(/\((.*)\)$/);
26
26
  if (!fieldsMatch) {
27
27
  throw new Error('Invalid entity spec: missing closing parenthesis');
28
28
  }
29
29
  const fieldsStr = fieldsMatch[1];
30
- const fields = this.parseFields(fieldsStr);
30
+ const fields = fieldsStr.trim() ? this.parseFields(fieldsStr) : [];
31
31
  return { name, fields };
32
32
  }
33
33
  /**
@@ -84,14 +84,36 @@ export class EntityParser {
84
84
  return { name, type, constraints };
85
85
  }
86
86
  /**
87
- * Parse field type (primitive, reference, enum)
87
+ * Parse field type (primitive, reference, enum, array, computed)
88
88
  */
89
89
  parseType(typeStr) {
90
- // Check for reference: ref(EntityName)
90
+ // Check for reference with relation: refMany(EntityName) or refOne(EntityName)
91
+ // oneToMany = entity has many of this type (e.g., User has many Posts)
92
+ // manyToMany = entities have many-to-many relationship
93
+ const refManyMatch = typeStr.match(/^refMany\(([A-Z][a-zA-Z0-9]*)\)$/);
94
+ if (refManyMatch) {
95
+ return { kind: 'reference', entity: refManyMatch[1], relation: 'manyToMany' };
96
+ }
97
+ const refOneMatch = typeStr.match(/^refOne\(([A-Z][a-zA-Z0-9]*)\)$/);
98
+ if (refOneMatch) {
99
+ return { kind: 'reference', entity: refOneMatch[1], relation: 'oneToMany' };
100
+ }
101
+ // Check for reference: ref(EntityName) - default simple reference
91
102
  const refMatch = typeStr.match(/^ref\(([A-Z][a-zA-Z0-9]*)\)$/);
92
103
  if (refMatch) {
93
104
  return { kind: 'reference', entity: refMatch[1] };
94
105
  }
106
+ // Check for array: array(string), array(integer), etc.
107
+ const arrayMatch = typeStr.match(/^array\((string|integer|decimal|boolean)\)$/);
108
+ if (arrayMatch) {
109
+ const itemType = arrayMatch[1];
110
+ return { kind: 'array', itemType };
111
+ }
112
+ // Check for computed: computed(expression)
113
+ const computedMatch = typeStr.match(/^computed\((.+)\)$/);
114
+ if (computedMatch) {
115
+ return { kind: 'computed', expression: computedMatch[1] };
116
+ }
95
117
  // Check for enum: enum(val1,val2,val3)
96
118
  const enumMatch = typeStr.match(/^enum\((.+)\)$/);
97
119
  if (enumMatch) {
@@ -101,15 +123,15 @@ export class EntityParser {
101
123
  }
102
124
  return { kind: 'enum', values };
103
125
  }
104
- // Primitive types
105
- const primitives = ['string', 'integer', 'decimal', 'boolean', 'datetime'];
126
+ // Primitive types (including new email, url, json)
127
+ const primitives = ['string', 'integer', 'decimal', 'boolean', 'datetime', 'email', 'url', 'json'];
106
128
  if (primitives.includes(typeStr)) {
107
129
  return { kind: 'primitive', value: typeStr };
108
130
  }
109
131
  throw new Error(`Unknown field type: ${typeStr}`);
110
132
  }
111
133
  /**
112
- * Parse field constraint (unique, required, hashed, min, max, default)
134
+ * Parse field constraint (unique, required, hashed, min, max, default, length, pattern, email, url, nullable, immutable)
113
135
  */
114
136
  parseConstraint(constraintStr) {
115
137
  // Simple constraints
@@ -121,7 +143,15 @@ export class EntityParser {
121
143
  return { kind: 'hashed' };
122
144
  if (constraintStr === 'index')
123
145
  return { kind: 'index' };
124
- // Parameterized constraints: min(18), max(100), default(true)
146
+ if (constraintStr === 'email')
147
+ return { kind: 'email' };
148
+ if (constraintStr === 'url')
149
+ return { kind: 'url' };
150
+ if (constraintStr === 'nullable')
151
+ return { kind: 'nullable' };
152
+ if (constraintStr === 'immutable')
153
+ return { kind: 'immutable' };
154
+ // Parameterized constraints: min(18), max(100), default(true), length(10,100), pattern(/regex/)
125
155
  const paramMatch = constraintStr.match(/^([a-z]+)\((.+)\)$/);
126
156
  if (paramMatch) {
127
157
  const [, kind, value] = paramMatch;
@@ -142,6 +172,33 @@ export class EntityParser {
142
172
  if (kind === 'default') {
143
173
  return { kind: 'default', value };
144
174
  }
175
+ if (kind === 'length') {
176
+ // Support both length(max) and length(min,max)
177
+ const parts = value.split(',').map(v => v.trim());
178
+ if (parts.length === 1) {
179
+ const maxValue = Number(parts[0]);
180
+ if (isNaN(maxValue)) {
181
+ throw new Error(`Invalid length constraint value: ${parts[0]}`);
182
+ }
183
+ return { kind: 'length', max: maxValue };
184
+ }
185
+ else if (parts.length === 2) {
186
+ const minValue = Number(parts[0]);
187
+ const maxValue = Number(parts[1]);
188
+ if (isNaN(minValue) || isNaN(maxValue)) {
189
+ throw new Error(`Invalid length constraint values: ${value}`);
190
+ }
191
+ return { kind: 'length', min: minValue, max: maxValue };
192
+ }
193
+ else {
194
+ throw new Error(`Invalid length constraint format: ${value}`);
195
+ }
196
+ }
197
+ if (kind === 'pattern') {
198
+ // Remove surrounding slashes if present
199
+ const regex = value.replace(/^\/(.+)\/$/, '$1');
200
+ return { kind: 'pattern', regex };
201
+ }
145
202
  }
146
203
  throw new Error(`Unknown constraint: ${constraintStr}`);
147
204
  }
@@ -8,12 +8,13 @@
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
10
  import { fileURLToPath } from 'url';
11
+ import { errorTracker } from '../helpers/error-tracking.js';
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
  export class TemplateRegistry {
13
14
  baseUrl;
14
15
  cacheDir;
15
16
  useCache;
16
- constructor(baseUrl = 'https://templates.claudetools.com', useCache = true) {
17
+ constructor(baseUrl = 'https://api.claudetools.dev/api/v1/codedna', useCache = true) {
17
18
  this.baseUrl = baseUrl;
18
19
  this.cacheDir = path.join(__dirname, '../../templates');
19
20
  this.useCache = useCache;
@@ -82,6 +83,8 @@ export class TemplateRegistry {
82
83
  }
83
84
  catch (error) {
84
85
  console.warn('Failed to fetch template from registry, using local fallback:', error);
86
+ // Track template fetch error
87
+ await errorTracker.trackTemplateFetchError(generatorId, templateFile, error instanceof Error ? error : new Error(String(error)));
85
88
  return this.getLocalTemplate(generatorId, templateFile);
86
89
  }
87
90
  }
@@ -30,6 +30,8 @@ export class TemplateEngine {
30
30
  addFilters() {
31
31
  // Pluralize: User → Users
32
32
  this.env.addFilter('plural', (str) => {
33
+ if (str === 'person')
34
+ return 'people';
33
35
  if (str.endsWith('y'))
34
36
  return str.slice(0, -1) + 'ies';
35
37
  if (str.endsWith('s'))
@@ -44,64 +46,101 @@ export class TemplateEngine {
44
46
  this.env.addFilter('upper', (str) => {
45
47
  return str.toUpperCase();
46
48
  });
47
- // camelCase: user_name → userName
49
+ // camelCase: user_name → userName, first-name → firstName, my value → myValue
48
50
  this.env.addFilter('camel', (str) => {
49
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
51
+ return str
52
+ .replace(/[-_\s]([a-zA-Z])/g, (_, letter) => letter.toUpperCase())
53
+ .replace(/^[A-Z]/, letter => letter.toLowerCase());
50
54
  });
51
- // PascalCase: user_name → UserName
55
+ // PascalCase: user_name → UserName, first-name → FirstName, my value → MyValue
52
56
  this.env.addFilter('pascal', (str) => {
53
- const camel = str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
57
+ const camel = str.replace(/[-_\s]([a-zA-Z])/g, (_, letter) => letter.toUpperCase());
54
58
  return camel.charAt(0).toUpperCase() + camel.slice(1);
55
59
  });
56
- // snake_case: userName → user_name
60
+ // snake_case: userName → user_name, FirstName → first_name
57
61
  this.env.addFilter('snake', (str) => {
58
- return str.replace(/[A-Z]/g, letter => '_' + letter.toLowerCase());
62
+ return str
63
+ .replace(/[A-Z]/g, letter => '_' + letter.toLowerCase())
64
+ .replace(/^_/, '');
59
65
  });
60
- // kebab-case: userName → user-name
66
+ // kebab-case: userName → user-name, FirstName → first-name
61
67
  this.env.addFilter('kebab', (str) => {
62
- return str.replace(/[A-Z]/g, letter => '-' + letter.toLowerCase());
68
+ return str
69
+ .replace(/[A-Z]/g, letter => '-' + letter.toLowerCase())
70
+ .replace(/^-/, '');
63
71
  });
64
72
  // Check if array includes value
65
73
  this.env.addFilter('includes', (arr, val) => {
66
74
  return Array.isArray(arr) && arr.includes(val);
67
75
  });
68
- // Get TypeScript type for field
69
- this.env.addFilter('tsType', (field) => {
70
- if (field.type.kind === 'primitive') {
76
+ // Get TypeScript type for field type
77
+ this.env.addFilter('tsType', (type) => {
78
+ if (type.kind === 'primitive') {
71
79
  const typeMap = {
72
80
  string: 'string',
73
81
  integer: 'number',
74
82
  decimal: 'number',
75
83
  boolean: 'boolean',
76
84
  datetime: 'Date',
85
+ email: 'string',
86
+ url: 'string',
87
+ json: 'any',
77
88
  };
78
- return typeMap[field.type.value];
89
+ return typeMap[type.value] || 'any';
79
90
  }
80
- if (field.type.kind === 'reference') {
81
- return field.type.entity;
91
+ if (type.kind === 'array') {
92
+ const itemTypeMap = {
93
+ string: 'string',
94
+ integer: 'number',
95
+ decimal: 'number',
96
+ boolean: 'boolean',
97
+ };
98
+ return `${itemTypeMap[type.itemType]}[]`;
99
+ }
100
+ if (type.kind === 'reference') {
101
+ return type.entity;
102
+ }
103
+ if (type.kind === 'enum') {
104
+ return type.values.map((v) => `'${v}'`).join(' | ');
82
105
  }
83
- if (field.type.kind === 'enum') {
84
- return field.type.values.map(v => `'${v}'`).join(' | ');
106
+ if (type.kind === 'computed') {
107
+ return 'any'; // Computed fields can be any type
85
108
  }
86
109
  return 'any';
87
110
  });
88
- // Get SQL type for field
89
- this.env.addFilter('sqlType', (field) => {
90
- if (field.type.kind === 'primitive') {
111
+ // Get SQL type for field type
112
+ this.env.addFilter('sqlType', (type) => {
113
+ if (type.kind === 'primitive') {
91
114
  const typeMap = {
92
115
  string: 'VARCHAR(255)',
93
116
  integer: 'INTEGER',
94
117
  decimal: 'DECIMAL(10,2)',
95
118
  boolean: 'BOOLEAN',
96
119
  datetime: 'TIMESTAMP',
120
+ email: 'VARCHAR(255)',
121
+ url: 'VARCHAR(512)',
122
+ json: 'JSONB',
97
123
  };
98
- return typeMap[field.type.value];
124
+ return typeMap[type.value] || 'TEXT';
99
125
  }
100
- if (field.type.kind === 'reference') {
126
+ if (type.kind === 'array') {
127
+ // PostgreSQL array syntax
128
+ const itemTypeMap = {
129
+ string: 'TEXT',
130
+ integer: 'INTEGER',
131
+ decimal: 'DECIMAL(10,2)',
132
+ boolean: 'BOOLEAN',
133
+ };
134
+ return `${itemTypeMap[type.itemType]}[]`;
135
+ }
136
+ if (type.kind === 'reference') {
101
137
  return 'INTEGER'; // Foreign key
102
138
  }
103
- if (field.type.kind === 'enum') {
104
- return `ENUM(${field.type.values.map(v => `'${v}'`).join(',')})`;
139
+ if (type.kind === 'enum') {
140
+ return `ENUM(${type.values.map((v) => `'${v}'`).join(',')})`;
141
+ }
142
+ if (type.kind === 'computed') {
143
+ return 'GENERATED'; // Virtual/computed column
105
144
  }
106
145
  return 'TEXT';
107
146
  });
@@ -109,22 +148,17 @@ export class TemplateEngine {
109
148
  this.env.addFilter('hasConstraint', (field, constraintKind) => {
110
149
  return field.constraints.some(c => c.kind === constraintKind);
111
150
  });
112
- // Get constraint value (for min, max, default)
151
+ // Get constraint object (for min, max, default)
113
152
  this.env.addFilter('getConstraint', (field, constraintKind) => {
114
- const constraint = field.constraints.find(c => c.kind === constraintKind);
115
- if (!constraint)
116
- return null;
117
- if ('value' in constraint)
118
- return constraint.value;
119
- return true;
153
+ return field.constraints.find(c => c.kind === constraintKind) || null;
120
154
  });
121
155
  // Join array with custom separator
122
156
  this.env.addFilter('joinWith', (arr, separator) => {
123
157
  return arr.join(separator);
124
158
  });
125
- // First letter uppercase
159
+ // First letter uppercase, rest lowercase
126
160
  this.env.addFilter('capitalize', (str) => {
127
- return str.charAt(0).toUpperCase() + str.slice(1);
161
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
128
162
  });
129
163
  // Indent lines
130
164
  this.env.addFilter('indent', (str, spaces = 2) => {
@@ -104,7 +104,7 @@ export declare function handleValidateSpec(args: any): Promise<{
104
104
  fields: {
105
105
  name: string;
106
106
  type: import("../codedna/parser.js").FieldType;
107
- constraints: ("default" | "min" | "max" | "required" | "unique" | "hashed" | "index")[];
107
+ constraints: ("default" | "length" | "email" | "min" | "max" | "url" | "required" | "pattern" | "nullable" | "unique" | "hashed" | "index" | "immutable")[];
108
108
  }[];
109
109
  };
110
110
  summary: {
@@ -8,6 +8,8 @@
8
8
  import { validateSpec } from '../codedna/parser.js';
9
9
  import { TemplateRegistry } from '../codedna/registry.js';
10
10
  import { ExpressApiGenerator } from '../codedna/generators/express-api.js';
11
+ import { errorTracker } from '../helpers/error-tracking.js';
12
+ import { analytics } from '../helpers/usage-analytics.js';
11
13
  // Singleton registry instance
12
14
  const registry = new TemplateRegistry();
13
15
  /**
@@ -18,6 +20,8 @@ export async function handleGenerateApi(args) {
18
20
  // Validate Entity DSL specification
19
21
  const validation = validateSpec(spec);
20
22
  if (!validation.valid) {
23
+ // Track validation error
24
+ await errorTracker.trackValidationError(spec, validation.errors || []);
21
25
  return {
22
26
  error: 'Invalid entity specification',
23
27
  details: validation.errors,
@@ -47,8 +51,24 @@ export async function handleGenerateApi(args) {
47
51
  };
48
52
  }
49
53
  try {
54
+ const startTime = Date.now();
50
55
  // Generate code
51
56
  const result = await generator.generate(validation.parsed, options);
57
+ const executionTime = Date.now() - startTime;
58
+ // Track successful generation
59
+ await analytics.trackGeneration({
60
+ operation: 'generate_api',
61
+ generator: `${framework}-api`,
62
+ framework,
63
+ entityName: validation.parsed.name,
64
+ fieldCount: validation.parsed.fields.length,
65
+ specLength: spec.length,
66
+ filesGenerated: result.metadata.filesGenerated,
67
+ linesOfCode: result.metadata.linesOfCode,
68
+ tokensSaved: result.metadata.estimatedTokensSaved,
69
+ options: options,
70
+ executionTimeMs: executionTime,
71
+ });
52
72
  return {
53
73
  success: true,
54
74
  files: result.files,
@@ -62,6 +82,8 @@ export async function handleGenerateApi(args) {
62
82
  };
63
83
  }
64
84
  catch (error) {
85
+ // Track generation error
86
+ await errorTracker.trackGenerationError(framework, spec, error instanceof Error ? error : new Error(String(error)));
65
87
  return {
66
88
  error: 'Code generation failed',
67
89
  message: error instanceof Error ? error.message : String(error),
@@ -129,6 +151,8 @@ export async function handleListGenerators() {
129
151
  };
130
152
  }
131
153
  catch (error) {
154
+ // Track network error for registry access
155
+ await errorTracker.trackNetworkError('list_generators', error instanceof Error ? error : new Error(String(error)));
132
156
  return {
133
157
  error: 'Failed to list generators',
134
158
  message: error instanceof Error ? error.message : String(error),
@@ -141,7 +165,9 @@ export async function handleListGenerators() {
141
165
  export async function handleValidateSpec(args) {
142
166
  const { spec } = args;
143
167
  const validation = validateSpec(spec);
168
+ // Track validation
144
169
  if (validation.valid && validation.parsed) {
170
+ await analytics.trackValidation(spec, true, validation.parsed.name, validation.parsed.fields.length);
145
171
  return {
146
172
  valid: true,
147
173
  entity: {
@@ -160,6 +186,7 @@ export async function handleValidateSpec(args) {
160
186
  },
161
187
  };
162
188
  }
189
+ await analytics.trackValidation(spec, false);
163
190
  return {
164
191
  valid: false,
165
192
  errors: validation.errors,
@@ -2,12 +2,19 @@
2
2
  // API Client and Memory Operations
3
3
  // =============================================================================
4
4
  import { API_BASE_URL, DEFAULT_USER_ID } from './config.js';
5
+ import { getConfig } from './config-manager.js';
5
6
  export async function apiRequest(endpoint, method = 'GET', body) {
7
+ const config = getConfig();
8
+ const apiKey = config.apiKey || process.env.CLAUDETOOLS_API_KEY || process.env.MEMORY_API_KEY;
9
+ if (!apiKey) {
10
+ throw new Error('No API key found. Set CLAUDETOOLS_API_KEY or MEMORY_API_KEY in environment or ~/.claudetools/config.json');
11
+ }
6
12
  const url = `${API_BASE_URL}${endpoint}`;
7
13
  const options = {
8
14
  method,
9
15
  headers: {
10
16
  'Content-Type': 'application/json',
17
+ 'Authorization': `Bearer ${apiKey}`,
11
18
  },
12
19
  };
13
20
  if (body) {
@@ -0,0 +1,34 @@
1
+ import { type ErrorRateResult } from './error-tracking.js';
2
+ export interface DashboardMetrics {
3
+ timeRange: string;
4
+ errorMetrics: ErrorRateResult;
5
+ alerts: Alert[];
6
+ recommendations: string[];
7
+ }
8
+ export interface Alert {
9
+ level: 'info' | 'warning' | 'critical';
10
+ message: string;
11
+ metric: string;
12
+ value: number;
13
+ threshold: number;
14
+ }
15
+ /**
16
+ * Get dashboard metrics for a time range
17
+ */
18
+ export declare function getDashboardMetrics(startTime: string, endTime: string): Promise<DashboardMetrics>;
19
+ /**
20
+ * Get error metrics for last 24 hours
21
+ */
22
+ export declare function getLast24Hours(): Promise<DashboardMetrics>;
23
+ /**
24
+ * Get error metrics for last 7 days
25
+ */
26
+ export declare function getLast7Days(): Promise<DashboardMetrics>;
27
+ /**
28
+ * Print dashboard to console
29
+ */
30
+ export declare function printDashboard(metrics: DashboardMetrics): void;
31
+ /**
32
+ * CLI command: npm run codedna:monitor
33
+ */
34
+ export declare function runMonitoring(): Promise<void>;