@constructive-io/graphql-codegen 4.7.3 → 4.8.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 (37) hide show
  1. package/core/codegen/cli/command-map-generator.js +36 -11
  2. package/core/codegen/cli/custom-command-generator.js +59 -13
  3. package/core/codegen/cli/docs-generator.d.ts +1 -1
  4. package/core/codegen/cli/docs-generator.js +137 -54
  5. package/core/codegen/cli/executor-generator.js +20 -6
  6. package/core/codegen/cli/infra-generator.js +17 -14
  7. package/core/codegen/cli/table-command-generator.js +209 -53
  8. package/core/codegen/docs-utils.d.ts +12 -1
  9. package/core/codegen/docs-utils.js +49 -1
  10. package/core/codegen/hooks-docs-generator.d.ts +1 -1
  11. package/core/codegen/hooks-docs-generator.js +47 -7
  12. package/core/codegen/orm/docs-generator.d.ts +1 -1
  13. package/core/codegen/orm/docs-generator.js +57 -20
  14. package/core/generate.d.ts +5 -0
  15. package/core/generate.js +48 -12
  16. package/core/workspace.d.ts +13 -0
  17. package/core/workspace.js +92 -0
  18. package/esm/core/codegen/cli/command-map-generator.js +36 -11
  19. package/esm/core/codegen/cli/custom-command-generator.js +60 -14
  20. package/esm/core/codegen/cli/docs-generator.d.ts +1 -1
  21. package/esm/core/codegen/cli/docs-generator.js +138 -55
  22. package/esm/core/codegen/cli/executor-generator.js +20 -6
  23. package/esm/core/codegen/cli/infra-generator.js +17 -14
  24. package/esm/core/codegen/cli/table-command-generator.js +210 -54
  25. package/esm/core/codegen/docs-utils.d.ts +12 -1
  26. package/esm/core/codegen/docs-utils.js +48 -1
  27. package/esm/core/codegen/hooks-docs-generator.d.ts +1 -1
  28. package/esm/core/codegen/hooks-docs-generator.js +48 -8
  29. package/esm/core/codegen/orm/docs-generator.d.ts +1 -1
  30. package/esm/core/codegen/orm/docs-generator.js +58 -21
  31. package/esm/core/generate.d.ts +5 -0
  32. package/esm/core/generate.js +48 -12
  33. package/esm/core/workspace.d.ts +13 -0
  34. package/esm/core/workspace.js +89 -0
  35. package/esm/types/config.d.ts +12 -4
  36. package/package.json +22 -22
  37. package/types/config.d.ts +12 -4
@@ -1,7 +1,7 @@
1
1
  import * as t from '@babel/types';
2
2
  import { toKebabCase } from 'komoji';
3
3
  import { generateCode } from '../babel-ast';
4
- import { getGeneratedFileHeader } from '../utils';
4
+ import { getGeneratedFileHeader, ucFirst } from '../utils';
5
5
  import { buildQuestionsArray } from './arg-mapper';
6
6
  function createImportDeclaration(moduleSpecifier, namedImports, typeOnly = false) {
7
7
  const specifiers = namedImports.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
@@ -65,22 +65,33 @@ function buildDefaultSelectString(returnType, isMutation) {
65
65
  }
66
66
  return '';
67
67
  }
68
- function buildOrmCustomCall(opKind, opName, argsExpr, selectExpr, hasArgs = true) {
68
+ function buildOrmCustomCall(opKind, opName, argsExpr, selectExpr, hasArgs = true, selectTypeName) {
69
69
  const callArgs = [];
70
+ // Helper: wrap { select } and cast to `{ select: XxxSelect }` via `unknown`.
71
+ // The ORM method's second parameter is `{ select: S } & StrictSelect<S, XxxSelect>`.
72
+ // We import the concrete Select type (e.g. CheckPasswordPayloadSelect) and cast
73
+ // `{ select: selectFields } as unknown as { select: XxxSelect }` so TS infers
74
+ // `S = XxxSelect` and StrictSelect is satisfied.
75
+ const castSelectWrapper = (sel) => {
76
+ const selectObj = t.objectExpression([
77
+ t.objectProperty(t.identifier('select'), sel),
78
+ ]);
79
+ if (!selectTypeName)
80
+ return selectObj;
81
+ return t.tsAsExpression(t.tsAsExpression(selectObj, t.tsUnknownKeyword()), t.tsTypeLiteral([
82
+ t.tsPropertySignature(t.identifier('select'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(selectTypeName)))),
83
+ ]));
84
+ };
70
85
  if (hasArgs) {
71
- // Operation has arguments: pass args as first param, select as second
86
+ // Operation has arguments: pass args as first param, select as second.
72
87
  callArgs.push(argsExpr);
73
88
  if (selectExpr) {
74
- callArgs.push(t.objectExpression([
75
- t.objectProperty(t.identifier('select'), selectExpr),
76
- ]));
89
+ callArgs.push(castSelectWrapper(selectExpr));
77
90
  }
78
91
  }
79
92
  else if (selectExpr) {
80
- // No arguments: pass { select } as the only param (ORM signature)
81
- callArgs.push(t.objectExpression([
82
- t.objectProperty(t.identifier('select'), selectExpr),
83
- ]));
93
+ // No arguments: pass { select } as the only param (ORM signature).
94
+ callArgs.push(castSelectWrapper(selectExpr));
84
95
  }
85
96
  return t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.memberExpression(t.identifier('client'), t.identifier(opKind)), t.identifier(opName)), callArgs), t.identifier('execute')), []);
86
97
  }
@@ -116,6 +127,18 @@ export function generateCustomCommand(op, options) {
116
127
  if (utilsImports.length > 0) {
117
128
  statements.push(createImportDeclaration(utilsPath, utilsImports));
118
129
  }
130
+ // Import the Variables type for this operation from the ORM query/mutation module.
131
+ // Custom operations define their own Variables types (e.g. CheckPasswordVariables)
132
+ // in the ORM layer. We import and cast CLI answers to this type for proper typing.
133
+ if (op.args.length > 0) {
134
+ const variablesTypeName = `${ucFirst(op.name)}Variables`;
135
+ // Commands are at cli/commands/xxx.ts (no target) or cli/commands/{target}/xxx.ts (with target).
136
+ // ORM query/mutation is at orm/{opKind}/ — two or three levels up from commands.
137
+ const ormOpPath = options?.targetName
138
+ ? `../../../orm/${opKind}`
139
+ : `../../orm/${opKind}`;
140
+ statements.push(createImportDeclaration(ormOpPath, [variablesTypeName], true));
141
+ }
119
142
  const questionsArray = op.args.length > 0
120
143
  ? buildQuestionsArray(op.args)
121
144
  : t.arrayExpression([]);
@@ -153,10 +176,16 @@ export function generateCustomCommand(op, options) {
153
176
  ])),
154
177
  ]));
155
178
  }
179
+ // Cast args to the specific Variables type for this operation.
180
+ // The ORM expects typed variables (e.g. CheckPasswordVariables), and CLI
181
+ // prompt answers are Record<string, unknown>. We cast through `unknown`
182
+ // first because Record<string, unknown> doesn't directly overlap with
183
+ // Variables types that have specific property types (like `input: SomeInput`).
184
+ const variablesTypeName = `${ucFirst(op.name)}Variables`;
156
185
  const argsExpr = op.args.length > 0
157
- ? (hasInputObjectArg
186
+ ? t.tsAsExpression(t.tsAsExpression(hasInputObjectArg
158
187
  ? t.identifier('parsedAnswers')
159
- : t.identifier('answers'))
188
+ : t.identifier('answers'), t.tsUnknownKeyword()), t.tsTypeReference(t.identifier(variablesTypeName)))
160
189
  : t.objectExpression([]);
161
190
  // For OBJECT return types, generate runtime select from --select flag
162
191
  // For scalar return types, no select is needed
@@ -166,14 +195,31 @@ export function generateCustomCommand(op, options) {
166
195
  // Generate: const selectFields = buildSelectFromPaths(argv.select ?? 'defaultFields')
167
196
  bodyStatements.push(t.variableDeclaration('const', [
168
197
  t.variableDeclarator(t.identifier('selectFields'), t.callExpression(t.identifier('buildSelectFromPaths'), [
169
- t.logicalExpression('??', t.memberExpression(t.identifier('argv'), t.identifier('select')), t.stringLiteral(defaultSelect)),
198
+ t.logicalExpression('??', t.tsAsExpression(t.memberExpression(t.identifier('argv'), t.identifier('select')), t.tsStringKeyword()), t.stringLiteral(defaultSelect)),
170
199
  ])),
171
200
  ]));
172
201
  selectExpr = t.identifier('selectFields');
173
202
  }
203
+ // Derive the Select type name from the operation's return type.
204
+ // e.g. CheckPasswordPayload → CheckPasswordPayloadSelect
205
+ // This is used to cast { select } to the proper type for StrictSelect.
206
+ let selectTypeName;
207
+ if (isObjectReturn) {
208
+ const baseReturnType = unwrapType(op.returnType);
209
+ if (baseReturnType.name) {
210
+ selectTypeName = `${baseReturnType.name}Select`;
211
+ }
212
+ }
213
+ // Import the Select type from orm/input-types if we have one
214
+ if (selectTypeName) {
215
+ const inputTypesPath = options?.targetName
216
+ ? `../../../orm/input-types`
217
+ : `../../orm/input-types`;
218
+ statements.push(createImportDeclaration(inputTypesPath, [selectTypeName], true));
219
+ }
174
220
  const hasArgs = op.args.length > 0;
175
221
  bodyStatements.push(t.variableDeclaration('const', [
176
- t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCustomCall(opKind, op.name, argsExpr, selectExpr, hasArgs))),
222
+ t.variableDeclarator(t.identifier('result'), t.awaitExpression(buildOrmCustomCall(opKind, op.name, argsExpr, selectExpr, hasArgs, selectTypeName))),
177
223
  ]));
178
224
  if (options?.saveToken) {
179
225
  bodyStatements.push(t.ifStatement(t.logicalExpression('&&', t.memberExpression(t.identifier('argv'), t.identifier('saveToken')), t.identifier('result')), t.blockStatement([
@@ -5,7 +5,7 @@ export type { GeneratedDocFile, McpTool } from '../docs-utils';
5
5
  export declare function generateReadme(tables: CleanTable[], customOperations: CleanOperation[], toolName: string): GeneratedDocFile;
6
6
  export declare function generateAgentsDocs(tables: CleanTable[], customOperations: CleanOperation[], toolName: string): GeneratedDocFile;
7
7
  export declare function getCliMcpTools(tables: CleanTable[], customOperations: CleanOperation[], toolName: string): McpTool[];
8
- export declare function generateSkills(tables: CleanTable[], customOperations: CleanOperation[], toolName: string): GeneratedDocFile[];
8
+ export declare function generateSkills(tables: CleanTable[], customOperations: CleanOperation[], toolName: string, targetName: string): GeneratedDocFile[];
9
9
  export interface MultiTargetDocsInput {
10
10
  toolName: string;
11
11
  builtinNames: {
@@ -1,5 +1,5 @@
1
1
  import { toKebabCase } from 'komoji';
2
- import { formatArgType, getEditableFields, getReadmeHeader, getReadmeFooter, gqlTypeToJsonSchemaType, buildSkillFile, } from '../docs-utils';
2
+ import { formatArgType, getEditableFields, getReadmeHeader, getReadmeFooter, gqlTypeToJsonSchemaType, buildSkillFile, buildSkillReference, } from '../docs-utils';
3
3
  import { getScalarFields, getTableNames, getPrimaryKeyInfo, } from '../utils';
4
4
  export { resolveDocsConfig } from '../docs-utils';
5
5
  export function generateReadme(tables, customOperations, toolName) {
@@ -513,12 +513,16 @@ export function getCliMcpTools(tables, customOperations, toolName) {
513
513
  }
514
514
  return tools;
515
515
  }
516
- export function generateSkills(tables, customOperations, toolName) {
516
+ export function generateSkills(tables, customOperations, toolName, targetName) {
517
517
  const files = [];
518
+ const skillName = `cli-${targetName}`;
519
+ const referenceNames = [];
520
+ // Context reference
521
+ referenceNames.push('context');
518
522
  files.push({
519
- fileName: 'skills/context.md',
520
- content: buildSkillFile({
521
- name: `${toolName}-context`,
523
+ fileName: `${skillName}/references/context.md`,
524
+ content: buildSkillReference({
525
+ title: 'Context Management',
522
526
  description: `Manage API endpoint contexts for ${toolName}`,
523
527
  usage: [
524
528
  `${toolName} context create <name> --endpoint <url>`,
@@ -542,10 +546,12 @@ export function generateSkills(tables, customOperations, toolName) {
542
546
  ],
543
547
  }),
544
548
  });
549
+ // Auth reference
550
+ referenceNames.push('auth');
545
551
  files.push({
546
- fileName: 'skills/auth.md',
547
- content: buildSkillFile({
548
- name: `${toolName}-auth`,
552
+ fileName: `${skillName}/references/auth.md`,
553
+ content: buildSkillReference({
554
+ title: 'Authentication',
549
555
  description: `Manage authentication tokens for ${toolName}`,
550
556
  usage: [
551
557
  `${toolName} auth set-token <token>`,
@@ -564,15 +570,17 @@ export function generateSkills(tables, customOperations, toolName) {
564
570
  ],
565
571
  }),
566
572
  });
573
+ // Table references
567
574
  for (const table of tables) {
568
575
  const { singularName } = getTableNames(table);
569
576
  const kebab = toKebabCase(singularName);
570
577
  const pk = getPrimaryKeyInfo(table)[0];
571
578
  const editableFields = getEditableFields(table);
579
+ referenceNames.push(kebab);
572
580
  files.push({
573
- fileName: `skills/${kebab}.md`,
574
- content: buildSkillFile({
575
- name: `${toolName}-${kebab}`,
581
+ fileName: `${skillName}/references/${kebab}.md`,
582
+ content: buildSkillReference({
583
+ title: singularName,
576
584
  description: `CRUD operations for ${table.name} records via ${toolName} CLI`,
577
585
  usage: [
578
586
  `${toolName} ${kebab} list`,
@@ -596,29 +604,21 @@ export function generateSkills(tables, customOperations, toolName) {
596
604
  description: `Get a ${singularName} by ${pk.name}`,
597
605
  code: [`${toolName} ${kebab} get --${pk.name} <value>`],
598
606
  },
599
- {
600
- description: `Update a ${singularName}`,
601
- code: [
602
- `${toolName} ${kebab} update --${pk.name} <value> --${editableFields[0]?.name || 'field'} "new-value"`,
603
- ],
604
- },
605
- {
606
- description: `Delete a ${singularName}`,
607
- code: [`${toolName} ${kebab} delete --${pk.name} <value>`],
608
- },
609
607
  ],
610
608
  }),
611
609
  });
612
610
  }
611
+ // Custom operation references
613
612
  for (const op of customOperations) {
614
613
  const kebab = toKebabCase(op.name);
615
614
  const usage = op.args.length > 0
616
615
  ? `${toolName} ${kebab} ${op.args.map((a) => `--${a.name} <value>`).join(' ')}`
617
616
  : `${toolName} ${kebab}`;
617
+ referenceNames.push(kebab);
618
618
  files.push({
619
- fileName: `skills/${kebab}.md`,
620
- content: buildSkillFile({
621
- name: `${toolName}-${kebab}`,
619
+ fileName: `${skillName}/references/${kebab}.md`,
620
+ content: buildSkillReference({
621
+ title: op.name,
622
622
  description: op.description || `Execute the ${op.name} ${op.kind}`,
623
623
  usage: [usage],
624
624
  examples: [
@@ -630,6 +630,39 @@ export function generateSkills(tables, customOperations, toolName) {
630
630
  }),
631
631
  });
632
632
  }
633
+ // Overview SKILL.md
634
+ const tableKebabs = tables.slice(0, 5).map((t) => toKebabCase(getTableNames(t).singularName));
635
+ files.push({
636
+ fileName: `${skillName}/SKILL.md`,
637
+ content: buildSkillFile({
638
+ name: skillName,
639
+ description: `CLI tool (${toolName}) for the ${targetName} API — provides CRUD commands for ${tables.length} tables and ${customOperations.length} custom operations`,
640
+ usage: [
641
+ `# Context management`,
642
+ `${toolName} context create <name> --endpoint <url>`,
643
+ `${toolName} context use <name>`,
644
+ '',
645
+ `# Authentication`,
646
+ `${toolName} auth set-token <token>`,
647
+ '',
648
+ `# CRUD for any table (e.g. ${tableKebabs[0] || 'model'})`,
649
+ `${toolName} ${tableKebabs[0] || 'model'} list`,
650
+ `${toolName} ${tableKebabs[0] || 'model'} get --id <value>`,
651
+ `${toolName} ${tableKebabs[0] || 'model'} create --<field> <value>`,
652
+ ],
653
+ examples: [
654
+ {
655
+ description: 'Set up and query',
656
+ code: [
657
+ `${toolName} context create local --endpoint http://localhost:5000/graphql`,
658
+ `${toolName} context use local`,
659
+ `${toolName} auth set-token <token>`,
660
+ `${toolName} ${tableKebabs[0] || 'model'} list`,
661
+ ],
662
+ },
663
+ ],
664
+ }, referenceNames),
665
+ });
633
666
  return files;
634
667
  }
635
668
  export function generateMultiTargetReadme(input) {
@@ -1256,22 +1289,26 @@ export function getMultiTargetCliMcpTools(input) {
1256
1289
  export function generateMultiTargetSkills(input) {
1257
1290
  const { toolName, builtinNames, targets } = input;
1258
1291
  const files = [];
1259
- const contextUsage = [
1260
- `${toolName} ${builtinNames.context} create <name>`,
1261
- `${toolName} ${builtinNames.context} list`,
1262
- `${toolName} ${builtinNames.context} use <name>`,
1263
- `${toolName} ${builtinNames.context} current`,
1264
- `${toolName} ${builtinNames.context} delete <name>`,
1265
- ];
1292
+ // Generate one skill per target, plus a shared cli-common skill for context/auth
1293
+ const commonSkillName = 'cli-common';
1294
+ const commonReferenceNames = [];
1266
1295
  const contextCreateFlags = targets
1267
1296
  .map((t) => `--${t.name}-endpoint <url>`)
1268
1297
  .join(' ');
1298
+ // Context reference
1299
+ commonReferenceNames.push('context');
1269
1300
  files.push({
1270
- fileName: `skills/${builtinNames.context}.md`,
1271
- content: buildSkillFile({
1272
- name: `${toolName}-${builtinNames.context}`,
1301
+ fileName: `${commonSkillName}/references/context.md`,
1302
+ content: buildSkillReference({
1303
+ title: 'Context Management',
1273
1304
  description: `Manage API endpoint contexts for ${toolName} (multi-target: ${targets.map((t) => t.name).join(', ')})`,
1274
- usage: contextUsage,
1305
+ usage: [
1306
+ `${toolName} ${builtinNames.context} create <name>`,
1307
+ `${toolName} ${builtinNames.context} list`,
1308
+ `${toolName} ${builtinNames.context} use <name>`,
1309
+ `${toolName} ${builtinNames.context} current`,
1310
+ `${toolName} ${builtinNames.context} delete <name>`,
1311
+ ],
1275
1312
  examples: [
1276
1313
  {
1277
1314
  description: 'Create a context for local development (accept all defaults)',
@@ -1287,20 +1324,15 @@ export function generateMultiTargetSkills(input) {
1287
1324
  `${toolName} ${builtinNames.context} use production`,
1288
1325
  ],
1289
1326
  },
1290
- {
1291
- description: 'List and switch contexts',
1292
- code: [
1293
- `${toolName} ${builtinNames.context} list`,
1294
- `${toolName} ${builtinNames.context} use staging`,
1295
- ],
1296
- },
1297
1327
  ],
1298
1328
  }),
1299
1329
  });
1330
+ // Auth reference
1331
+ commonReferenceNames.push('auth');
1300
1332
  files.push({
1301
- fileName: `skills/${builtinNames.auth}.md`,
1302
- content: buildSkillFile({
1303
- name: `${toolName}-${builtinNames.auth}`,
1333
+ fileName: `${commonSkillName}/references/auth.md`,
1334
+ content: buildSkillReference({
1335
+ title: 'Authentication',
1304
1336
  description: `Manage authentication tokens for ${toolName} (shared across all targets)`,
1305
1337
  usage: [
1306
1338
  `${toolName} ${builtinNames.auth} set-token <token>`,
@@ -1319,17 +1351,48 @@ export function generateMultiTargetSkills(input) {
1319
1351
  ],
1320
1352
  }),
1321
1353
  });
1354
+ // Common SKILL.md
1355
+ files.push({
1356
+ fileName: `${commonSkillName}/SKILL.md`,
1357
+ content: buildSkillFile({
1358
+ name: commonSkillName,
1359
+ description: `Shared CLI utilities for ${toolName} — context management and authentication across targets: ${targets.map((t) => t.name).join(', ')}`,
1360
+ usage: [
1361
+ `# Context management`,
1362
+ `${toolName} ${builtinNames.context} create <name>`,
1363
+ `${toolName} ${builtinNames.context} use <name>`,
1364
+ '',
1365
+ `# Authentication`,
1366
+ `${toolName} ${builtinNames.auth} set-token <token>`,
1367
+ `${toolName} ${builtinNames.auth} status`,
1368
+ ],
1369
+ examples: [
1370
+ {
1371
+ description: 'Set up and authenticate',
1372
+ code: [
1373
+ `${toolName} ${builtinNames.context} create local`,
1374
+ `${toolName} ${builtinNames.context} use local`,
1375
+ `${toolName} ${builtinNames.auth} set-token <token>`,
1376
+ ],
1377
+ },
1378
+ ],
1379
+ }, commonReferenceNames),
1380
+ });
1381
+ // Generate one skill per target with table/op references
1322
1382
  for (const tgt of targets) {
1383
+ const tgtSkillName = `cli-${tgt.name}`;
1384
+ const tgtReferenceNames = [];
1323
1385
  for (const table of tgt.tables) {
1324
1386
  const { singularName } = getTableNames(table);
1325
1387
  const kebab = toKebabCase(singularName);
1326
1388
  const pk = getPrimaryKeyInfo(table)[0];
1327
1389
  const editableFields = getEditableFields(table);
1328
1390
  const cmd = `${tgt.name}:${kebab}`;
1391
+ tgtReferenceNames.push(kebab);
1329
1392
  files.push({
1330
- fileName: `skills/${tgt.name}-${kebab}.md`,
1331
- content: buildSkillFile({
1332
- name: `${toolName}-${cmd}`,
1393
+ fileName: `${tgtSkillName}/references/${kebab}.md`,
1394
+ content: buildSkillReference({
1395
+ title: singularName,
1333
1396
  description: `CRUD operations for ${table.name} records via ${toolName} CLI (${tgt.name} target)`,
1334
1397
  usage: [
1335
1398
  `${toolName} ${cmd} list`,
@@ -1349,10 +1412,6 @@ export function generateMultiTargetSkills(input) {
1349
1412
  `${toolName} ${cmd} create ${editableFields.map((f) => `--${f.name} "value"`).join(' ')}`,
1350
1413
  ],
1351
1414
  },
1352
- {
1353
- description: `Get a ${singularName} by ${pk.name}`,
1354
- code: [`${toolName} ${cmd} get --${pk.name} <value>`],
1355
- },
1356
1415
  ],
1357
1416
  }),
1358
1417
  });
@@ -1367,10 +1426,11 @@ export function generateMultiTargetSkills(input) {
1367
1426
  if (tgt.isAuthTarget && op.kind === 'mutation') {
1368
1427
  usageLines.push(`${baseUsage} --save-token`);
1369
1428
  }
1429
+ tgtReferenceNames.push(kebab);
1370
1430
  files.push({
1371
- fileName: `skills/${tgt.name}-${kebab}.md`,
1372
- content: buildSkillFile({
1373
- name: `${toolName}-${cmd}`,
1431
+ fileName: `${tgtSkillName}/references/${kebab}.md`,
1432
+ content: buildSkillReference({
1433
+ title: op.name,
1374
1434
  description: `${op.description || `Execute the ${op.name} ${op.kind}`} (${tgt.name} target)`,
1375
1435
  usage: usageLines,
1376
1436
  examples: [
@@ -1382,6 +1442,29 @@ export function generateMultiTargetSkills(input) {
1382
1442
  }),
1383
1443
  });
1384
1444
  }
1445
+ // Target SKILL.md
1446
+ const firstKebab = tgt.tables.length > 0
1447
+ ? toKebabCase(getTableNames(tgt.tables[0]).singularName)
1448
+ : 'model';
1449
+ files.push({
1450
+ fileName: `${tgtSkillName}/SKILL.md`,
1451
+ content: buildSkillFile({
1452
+ name: tgtSkillName,
1453
+ description: `CLI commands for the ${tgt.name} API target — ${tgt.tables.length} tables and ${tgt.customOperations.length} custom operations via ${toolName}`,
1454
+ usage: [
1455
+ `# CRUD for ${tgt.name} tables (e.g. ${firstKebab})`,
1456
+ `${toolName} ${tgt.name}:${firstKebab} list`,
1457
+ `${toolName} ${tgt.name}:${firstKebab} get --id <value>`,
1458
+ `${toolName} ${tgt.name}:${firstKebab} create --<field> <value>`,
1459
+ ],
1460
+ examples: [
1461
+ {
1462
+ description: `Query ${tgt.name} records`,
1463
+ code: [`${toolName} ${tgt.name}:${firstKebab} list`],
1464
+ },
1465
+ ],
1466
+ }, tgtReferenceNames),
1467
+ });
1385
1468
  }
1386
1469
  return files;
1387
1470
  }
@@ -49,9 +49,16 @@ export function generateExecutorFile(toolName, options) {
49
49
  ])),
50
50
  ])),
51
51
  ])),
52
- t.variableDeclaration('const', [
53
- t.variableDeclarator(t.identifier('headers'), t.objectExpression([])),
54
- ]),
52
+ (() => {
53
+ const headersId = t.identifier('headers');
54
+ headersId.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
55
+ t.tsStringKeyword(),
56
+ t.tsStringKeyword(),
57
+ ])));
58
+ return t.variableDeclaration('const', [
59
+ t.variableDeclarator(headersId, t.objectExpression([])),
60
+ ]);
61
+ })(),
55
62
  t.ifStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('hasValidCredentials')), [t.memberExpression(t.identifier('ctx'), t.identifier('name'))]), t.blockStatement([
56
63
  t.variableDeclaration('const', [
57
64
  t.variableDeclarator(t.identifier('creds'), t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('getCredentials')), [t.memberExpression(t.identifier('ctx'), t.identifier('name'))])),
@@ -156,9 +163,16 @@ export function generateMultiTargetExecutorFile(toolName, targets, options) {
156
163
  ], [t.identifier('targetName')]),
157
164
  ])),
158
165
  ])),
159
- t.variableDeclaration('const', [
160
- t.variableDeclarator(t.identifier('headers'), t.objectExpression([])),
161
- ]),
166
+ (() => {
167
+ const headersId = t.identifier('headers');
168
+ headersId.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
169
+ t.tsStringKeyword(),
170
+ t.tsStringKeyword(),
171
+ ])));
172
+ return t.variableDeclaration('const', [
173
+ t.variableDeclarator(headersId, t.objectExpression([])),
174
+ ]);
175
+ })(),
162
176
  t.variableDeclaration('let', [
163
177
  t.variableDeclarator(t.identifier('endpoint'), t.stringLiteral('')),
164
178
  ]),
@@ -96,7 +96,7 @@ Create Options:
96
96
  ]))),
97
97
  ]),
98
98
  t.returnStatement(t.callExpression(t.identifier('handleSubcommand'), [
99
- t.memberExpression(t.identifier('answer'), t.identifier('subcommand')),
99
+ t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('subcommand')), t.tsStringKeyword()),
100
100
  t.identifier('newArgv'),
101
101
  t.identifier('prompter'),
102
102
  t.identifier('store'),
@@ -185,7 +185,7 @@ function buildCreateHandler() {
185
185
  ])),
186
186
  ]),
187
187
  t.variableDeclaration('const', [
188
- t.variableDeclarator(t.identifier('answers'), t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('prompter'), t.identifier('prompt')), [
188
+ t.variableDeclarator(t.identifier('answers'), t.tsAsExpression(t.tsAsExpression(t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('prompter'), t.identifier('prompt')), [
189
189
  t.objectExpression([
190
190
  t.objectProperty(t.identifier('name'), t.identifier('name'), false, true),
191
191
  t.spreadElement(t.identifier('restArgv')),
@@ -204,7 +204,10 @@ function buildCreateHandler() {
204
204
  t.objectProperty(t.identifier('required'), t.booleanLiteral(true)),
205
205
  ]),
206
206
  ]),
207
- ]))),
207
+ ])), t.tsUnknownKeyword()), t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
208
+ t.tsStringKeyword(),
209
+ t.tsStringKeyword(),
210
+ ])))),
208
211
  ]),
209
212
  t.variableDeclaration('const', [
210
213
  t.variableDeclarator(t.identifier('contextName'), t.memberExpression(t.identifier('answers'), t.identifier('name'))),
@@ -341,7 +344,7 @@ function buildUseHandler() {
341
344
  ]),
342
345
  ]))),
343
346
  ]),
344
- t.expressionStatement(t.assignmentExpression('=', t.identifier('contextName'), t.memberExpression(t.identifier('answer'), t.identifier('name')))),
347
+ t.expressionStatement(t.assignmentExpression('=', t.identifier('contextName'), t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('name')), t.tsStringKeyword()))),
345
348
  ])),
346
349
  t.ifStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('setCurrentContext')), [t.identifier('contextName')]), t.blockStatement([
347
350
  t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('log')), [
@@ -457,7 +460,7 @@ function buildDeleteHandler() {
457
460
  ]),
458
461
  ]))),
459
462
  ]),
460
- t.expressionStatement(t.assignmentExpression('=', t.identifier('contextName'), t.memberExpression(t.identifier('answer'), t.identifier('name')))),
463
+ t.expressionStatement(t.assignmentExpression('=', t.identifier('contextName'), t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('name')), t.tsStringKeyword()))),
461
464
  ])),
462
465
  t.ifStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('deleteContext')), [t.identifier('contextName')]), t.blockStatement([
463
466
  t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('log')), [
@@ -549,7 +552,7 @@ Options:
549
552
  ]))),
550
553
  ]),
551
554
  t.returnStatement(t.callExpression(t.identifier('handleAuthSubcommand'), [
552
- t.memberExpression(t.identifier('answer'), t.identifier('subcommand')),
555
+ t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('subcommand')), t.tsStringKeyword()),
553
556
  t.identifier('newArgv'),
554
557
  t.identifier('prompter'),
555
558
  t.identifier('store'),
@@ -651,7 +654,7 @@ function buildSetTokenHandler() {
651
654
  ]),
652
655
  ]))),
653
656
  ]),
654
- t.expressionStatement(t.assignmentExpression('=', t.identifier('tokenValue'), t.memberExpression(t.identifier('answer'), t.identifier('token')))),
657
+ t.expressionStatement(t.assignmentExpression('=', t.identifier('tokenValue'), t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('token')), t.tsStringKeyword()))),
655
658
  ])),
656
659
  t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('setCredentials')), [
657
660
  t.memberExpression(t.identifier('current'), t.identifier('name')),
@@ -770,7 +773,7 @@ function buildLogoutHandler() {
770
773
  ]),
771
774
  ]))),
772
775
  ]),
773
- t.ifStatement(t.unaryExpression('!', t.memberExpression(t.identifier('confirm'), t.identifier('confirm'))), t.blockStatement([t.returnStatement()])),
776
+ t.ifStatement(t.unaryExpression('!', t.tsAsExpression(t.memberExpression(t.identifier('confirm'), t.identifier('confirm')), t.tsBooleanKeyword())), t.blockStatement([t.returnStatement()])),
774
777
  t.ifStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('removeCredentials')), [
775
778
  t.memberExpression(t.identifier('current'), t.identifier('name')),
776
779
  ]), t.blockStatement([
@@ -870,7 +873,7 @@ ${targets.map((tgt) => ` --${tgt.name}-endpoint <url> ${tgt.name} endpoint (de
870
873
  ]))),
871
874
  ]),
872
875
  t.returnStatement(t.callExpression(t.identifier('handleSubcommand'), [
873
- t.memberExpression(t.identifier('answer'), t.identifier('subcommand')),
876
+ t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('subcommand')), t.tsStringKeyword()),
874
877
  t.identifier('newArgv'),
875
878
  t.identifier('prompter'),
876
879
  t.identifier('store'),
@@ -969,7 +972,7 @@ function buildMultiTargetCreateHandler(targets) {
969
972
  const targetsObjProps = targets.map((target) => {
970
973
  const fieldName = `${target.name}Endpoint`;
971
974
  return t.objectProperty(t.stringLiteral(target.name), t.objectExpression([
972
- t.objectProperty(t.identifier('endpoint'), t.memberExpression(t.identifier('answers'), t.identifier(fieldName))),
975
+ t.objectProperty(t.identifier('endpoint'), t.tsAsExpression(t.memberExpression(t.identifier('answers'), t.identifier(fieldName)), t.tsStringKeyword())),
973
976
  ]));
974
977
  });
975
978
  const body = [
@@ -991,7 +994,7 @@ function buildMultiTargetCreateHandler(targets) {
991
994
  ]))),
992
995
  ]),
993
996
  t.variableDeclaration('const', [
994
- t.variableDeclarator(t.identifier('contextName'), t.memberExpression(t.identifier('answers'), t.identifier('name'))),
997
+ t.variableDeclarator(t.identifier('contextName'), t.tsAsExpression(t.memberExpression(t.identifier('answers'), t.identifier('name')), t.tsStringKeyword())),
995
998
  ]),
996
999
  t.variableDeclaration('const', [
997
1000
  t.variableDeclarator(t.identifier('targets'), t.objectExpression(targetsObjProps)),
@@ -999,7 +1002,7 @@ function buildMultiTargetCreateHandler(targets) {
999
1002
  t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('store'), t.identifier('createContext')), [
1000
1003
  t.identifier('contextName'),
1001
1004
  t.objectExpression([
1002
- t.objectProperty(t.identifier('endpoint'), t.memberExpression(t.identifier('answers'), t.identifier(`${targets[0].name}Endpoint`))),
1005
+ t.objectProperty(t.identifier('endpoint'), t.tsAsExpression(t.memberExpression(t.identifier('answers'), t.identifier(`${targets[0].name}Endpoint`)), t.tsStringKeyword())),
1003
1006
  t.objectProperty(t.identifier('targets'), t.identifier('targets')),
1004
1007
  ]),
1005
1008
  ])),
@@ -1022,7 +1025,7 @@ function buildMultiTargetCreateHandler(targets) {
1022
1025
  t.templateLiteral([
1023
1026
  t.templateElement({ raw: ` ${target.name}: `, cooked: ` ${target.name}: ` }),
1024
1027
  t.templateElement({ raw: '', cooked: '' }, true),
1025
- ], [t.memberExpression(t.identifier('answers'), t.identifier(fieldName))]),
1028
+ ], [t.tsAsExpression(t.memberExpression(t.identifier('answers'), t.identifier(fieldName)), t.tsStringKeyword())]),
1026
1029
  ])));
1027
1030
  }
1028
1031
  const func = t.functionDeclaration(t.identifier('handleCreate'), [argvParam, prompterParam, storeParam], t.blockStatement(body), false, true);
@@ -1098,7 +1101,7 @@ Options:
1098
1101
  ]))),
1099
1102
  ]),
1100
1103
  t.returnStatement(t.callExpression(t.identifier('handleAuthSubcommand'), [
1101
- t.memberExpression(t.identifier('answer'), t.identifier('subcommand')),
1104
+ t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('subcommand')), t.tsStringKeyword()),
1102
1105
  t.identifier('newArgv'),
1103
1106
  t.identifier('prompter'),
1104
1107
  t.identifier('store'),