@constructive-io/graphql-codegen 4.7.3 → 4.8.0

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/core/generate.js CHANGED
@@ -66,6 +66,18 @@ const target_docs_generator_1 = require("./codegen/target-docs-generator");
66
66
  const introspect_1 = require("./introspect");
67
67
  const output_1 = require("./output");
68
68
  const pipeline_1 = require("./pipeline");
69
+ const workspace_1 = require("./workspace");
70
+ function resolveSkillsOutputDir(config, outputRoot) {
71
+ const workspaceRoot = (0, workspace_1.findWorkspaceRoot)(node_path_1.default.resolve(outputRoot)) ??
72
+ (0, workspace_1.findWorkspaceRoot)(process.cwd()) ??
73
+ process.cwd();
74
+ if (config.skillsPath) {
75
+ return node_path_1.default.isAbsolute(config.skillsPath)
76
+ ? config.skillsPath
77
+ : node_path_1.default.resolve(workspaceRoot, config.skillsPath);
78
+ }
79
+ return node_path_1.default.resolve(workspaceRoot, 'skills');
80
+ }
69
81
  async function generate(options = {}, internalOptions) {
70
82
  // Apply defaults to get resolved config
71
83
  const config = (0, config_1.getConfigOptions)(options);
@@ -261,6 +273,8 @@ async function generate(options = {}, internalOptions) {
261
273
  ...(customOperations.mutations ?? []),
262
274
  ];
263
275
  const allMcpTools = [];
276
+ const targetName = internalOptions?.targetName ?? 'default';
277
+ const skillsToWrite = [];
264
278
  if (runOrm) {
265
279
  if (docsConfig.readme) {
266
280
  const readme = (0, docs_generator_2.generateOrmReadme)(tables, allCustomOps);
@@ -274,8 +288,8 @@ async function generate(options = {}, internalOptions) {
274
288
  allMcpTools.push(...(0, docs_generator_2.getOrmMcpTools)(tables, allCustomOps));
275
289
  }
276
290
  if (docsConfig.skills) {
277
- for (const skill of (0, docs_generator_2.generateOrmSkills)(tables, allCustomOps)) {
278
- filesToWrite.push({ path: node_path_1.default.posix.join('orm', skill.fileName), content: skill.content });
291
+ for (const skill of (0, docs_generator_2.generateOrmSkills)(tables, allCustomOps, targetName)) {
292
+ skillsToWrite.push({ path: skill.fileName, content: skill.content });
279
293
  }
280
294
  }
281
295
  }
@@ -292,8 +306,8 @@ async function generate(options = {}, internalOptions) {
292
306
  allMcpTools.push(...(0, hooks_docs_generator_1.getHooksMcpTools)(tables, allCustomOps));
293
307
  }
294
308
  if (docsConfig.skills) {
295
- for (const skill of (0, hooks_docs_generator_1.generateHooksSkills)(tables, allCustomOps)) {
296
- filesToWrite.push({ path: node_path_1.default.posix.join('hooks', skill.fileName), content: skill.content });
309
+ for (const skill of (0, hooks_docs_generator_1.generateHooksSkills)(tables, allCustomOps, targetName)) {
310
+ skillsToWrite.push({ path: skill.fileName, content: skill.content });
297
311
  }
298
312
  }
299
313
  }
@@ -313,8 +327,8 @@ async function generate(options = {}, internalOptions) {
313
327
  allMcpTools.push(...(0, docs_generator_1.getCliMcpTools)(tables, allCustomOps, toolName));
314
328
  }
315
329
  if (docsConfig.skills) {
316
- for (const skill of (0, docs_generator_1.generateSkills)(tables, allCustomOps, toolName)) {
317
- filesToWrite.push({ path: node_path_1.default.posix.join('cli', skill.fileName), content: skill.content });
330
+ for (const skill of (0, docs_generator_1.generateSkills)(tables, allCustomOps, toolName, targetName)) {
331
+ skillsToWrite.push({ path: skill.fileName, content: skill.content });
318
332
  }
319
333
  }
320
334
  }
@@ -352,6 +366,21 @@ async function generate(options = {}, internalOptions) {
352
366
  };
353
367
  }
354
368
  allFilesWritten.push(...(writeResult.filesWritten ?? []));
369
+ if (skillsToWrite.length > 0) {
370
+ const skillsOutputDir = resolveSkillsOutputDir(config, outputRoot);
371
+ const skillsWriteResult = await (0, output_1.writeGeneratedFiles)(skillsToWrite, skillsOutputDir, [], {
372
+ pruneStaleFiles: false,
373
+ });
374
+ if (!skillsWriteResult.success) {
375
+ return {
376
+ success: false,
377
+ message: `Failed to write generated skill files: ${skillsWriteResult.errors?.join(', ')}`,
378
+ output: skillsOutputDir,
379
+ errors: skillsWriteResult.errors,
380
+ };
381
+ }
382
+ allFilesWritten.push(...(skillsWriteResult.filesWritten ?? []));
383
+ }
355
384
  }
356
385
  const generators = [
357
386
  runReactQuery && 'React Query',
@@ -530,7 +559,7 @@ async function generateMulti(options) {
530
559
  dryRun,
531
560
  schemaOnly,
532
561
  schemaOnlyFilename: schemaOnly ? `${name}.graphql` : undefined,
533
- }, useUnifiedCli ? { skipCli: true } : undefined);
562
+ }, useUnifiedCli ? { skipCli: true, targetName: name } : { targetName: name });
534
563
  results.push({ name, result });
535
564
  if (!result.success) {
536
565
  hasError = true;
@@ -613,17 +642,24 @@ async function generateMulti(options) {
613
642
  if (docsConfig.mcp) {
614
643
  allMcpTools.push(...(0, docs_generator_1.getMultiTargetCliMcpTools)(docsInput));
615
644
  }
616
- if (docsConfig.skills) {
617
- for (const skill of (0, docs_generator_1.generateMultiTargetSkills)(docsInput)) {
618
- cliFilesToWrite.push({ path: node_path_1.default.posix.join('cli', skill.fileName), content: skill.content });
619
- }
620
- }
621
645
  if (docsConfig.mcp && allMcpTools.length > 0) {
622
646
  const mcpFile = (0, target_docs_generator_1.generateCombinedMcpConfig)(allMcpTools, toolName);
623
647
  cliFilesToWrite.push({ path: node_path_1.default.posix.join('cli', mcpFile.fileName), content: mcpFile.content });
624
648
  }
625
649
  const { writeGeneratedFiles: writeFiles } = await Promise.resolve().then(() => __importStar(require('./output')));
626
650
  await writeFiles(cliFilesToWrite, '.', [], { pruneStaleFiles: false });
651
+ if (docsConfig.skills) {
652
+ const cliSkillsToWrite = (0, docs_generator_1.generateMultiTargetSkills)(docsInput).map((skill) => ({
653
+ path: skill.fileName,
654
+ content: skill.content,
655
+ }));
656
+ const firstTargetResolved = (0, config_1.getConfigOptions)({
657
+ ...(firstTargetConfig ?? {}),
658
+ ...(cliOverrides ?? {}),
659
+ });
660
+ const skillsOutputDir = resolveSkillsOutputDir(firstTargetResolved, firstTargetResolved.output);
661
+ await writeFiles(cliSkillsToWrite, skillsOutputDir, [], { pruneStaleFiles: false });
662
+ }
627
663
  }
628
664
  // Generate root-root README if multi-target
629
665
  if (names.length > 1 && targetInfos.length > 0 && !dryRun) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Find the workspace root directory.
3
+ *
4
+ * Search order:
5
+ * 1. pnpm-workspace.yaml (pnpm workspaces)
6
+ * 2. lerna.json (lerna workspaces)
7
+ * 3. package.json with "workspaces" field (npm/yarn workspaces)
8
+ * 4. Nearest package.json (fallback for non-workspace projects)
9
+ *
10
+ * @param cwd - Starting directory to search from (defaults to process.cwd())
11
+ * @returns The workspace root path, or null if nothing found
12
+ */
13
+ export declare function findWorkspaceRoot(cwd?: string): string | null;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findWorkspaceRoot = findWorkspaceRoot;
4
+ /**
5
+ * Workspace detection utilities
6
+ *
7
+ * Finds the root of a workspace by walking up directories looking for
8
+ * workspace markers (pnpm-workspace.yaml, lerna.json, package.json with workspaces).
9
+ * Falls back to the nearest package.json directory.
10
+ *
11
+ * Inspired by @pgpmjs/env walkUp / resolvePnpmWorkspace patterns.
12
+ */
13
+ const node_fs_1 = require("node:fs");
14
+ const node_path_1 = require("node:path");
15
+ /**
16
+ * Walk up directories from startDir looking for a file.
17
+ * Returns the directory containing the file, or null if not found.
18
+ */
19
+ function walkUp(startDir, filename) {
20
+ let currentDir = (0, node_path_1.resolve)(startDir);
21
+ while (currentDir) {
22
+ const targetPath = (0, node_path_1.resolve)(currentDir, filename);
23
+ if ((0, node_fs_1.existsSync)(targetPath)) {
24
+ return currentDir;
25
+ }
26
+ const parentDir = (0, node_path_1.dirname)(currentDir);
27
+ if (parentDir === currentDir) {
28
+ break;
29
+ }
30
+ currentDir = parentDir;
31
+ }
32
+ return null;
33
+ }
34
+ /**
35
+ * Find the first pnpm workspace root by looking for pnpm-workspace.yaml
36
+ */
37
+ function findPnpmWorkspace(cwd) {
38
+ return walkUp(cwd, 'pnpm-workspace.yaml');
39
+ }
40
+ /**
41
+ * Find the first lerna workspace root by looking for lerna.json
42
+ */
43
+ function findLernaWorkspace(cwd) {
44
+ return walkUp(cwd, 'lerna.json');
45
+ }
46
+ /**
47
+ * Find the first npm/yarn workspace root by looking for package.json with workspaces field
48
+ */
49
+ function findNpmWorkspace(cwd) {
50
+ let currentDir = (0, node_path_1.resolve)(cwd);
51
+ const root = (0, node_path_1.parse)(currentDir).root;
52
+ while (currentDir !== root) {
53
+ const packageJsonPath = (0, node_path_1.resolve)(currentDir, 'package.json');
54
+ if ((0, node_fs_1.existsSync)(packageJsonPath)) {
55
+ try {
56
+ const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, 'utf8'));
57
+ if (packageJson.workspaces) {
58
+ return currentDir;
59
+ }
60
+ }
61
+ catch {
62
+ // Ignore JSON parse errors
63
+ }
64
+ }
65
+ currentDir = (0, node_path_1.dirname)(currentDir);
66
+ }
67
+ return null;
68
+ }
69
+ /**
70
+ * Find the nearest package.json directory (fallback)
71
+ */
72
+ function findPackageRoot(cwd) {
73
+ return walkUp(cwd, 'package.json');
74
+ }
75
+ /**
76
+ * Find the workspace root directory.
77
+ *
78
+ * Search order:
79
+ * 1. pnpm-workspace.yaml (pnpm workspaces)
80
+ * 2. lerna.json (lerna workspaces)
81
+ * 3. package.json with "workspaces" field (npm/yarn workspaces)
82
+ * 4. Nearest package.json (fallback for non-workspace projects)
83
+ *
84
+ * @param cwd - Starting directory to search from (defaults to process.cwd())
85
+ * @returns The workspace root path, or null if nothing found
86
+ */
87
+ function findWorkspaceRoot(cwd = process.cwd()) {
88
+ return (findPnpmWorkspace(cwd) ??
89
+ findLernaWorkspace(cwd) ??
90
+ findNpmWorkspace(cwd) ??
91
+ findPackageRoot(cwd));
92
+ }
@@ -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
  }
@@ -20,6 +20,16 @@ export interface SkillDefinition {
20
20
  }[];
21
21
  language?: string;
22
22
  }
23
+ export interface SkillReferenceDefinition {
24
+ title: string;
25
+ description: string;
26
+ usage: string[];
27
+ examples: {
28
+ description: string;
29
+ code: string[];
30
+ }[];
31
+ language?: string;
32
+ }
23
33
  export declare function getReadmeHeader(title: string): string[];
24
34
  export declare function getReadmeFooter(): string[];
25
35
  export declare function resolveDocsConfig(docs: DocsConfig | boolean | undefined): DocsConfig;
@@ -27,4 +37,5 @@ export declare function formatArgType(arg: CleanOperation['args'][number]): stri
27
37
  export declare function formatTypeRef(t: CleanOperation['args'][number]['type']): string;
28
38
  export declare function getEditableFields(table: CleanTable): CleanField[];
29
39
  export declare function gqlTypeToJsonSchemaType(gqlType: string): string;
30
- export declare function buildSkillFile(skill: SkillDefinition): string;
40
+ export declare function buildSkillFile(skill: SkillDefinition, referenceNames?: string[]): string;
41
+ export declare function buildSkillReference(ref: SkillReferenceDefinition): string;
@@ -79,9 +79,15 @@ export function gqlTypeToJsonSchemaType(gqlType) {
79
79
  return 'string';
80
80
  }
81
81
  }
82
- export function buildSkillFile(skill) {
82
+ export function buildSkillFile(skill, referenceNames) {
83
83
  const lang = skill.language ?? 'bash';
84
84
  const lines = [];
85
+ // YAML frontmatter (Agent Skills format)
86
+ lines.push('---');
87
+ lines.push(`name: ${skill.name}`);
88
+ lines.push(`description: ${skill.description}`);
89
+ lines.push('---');
90
+ lines.push('');
85
91
  lines.push(`# ${skill.name}`);
86
92
  lines.push('');
87
93
  lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
@@ -108,5 +114,46 @@ export function buildSkillFile(skill) {
108
114
  lines.push('```');
109
115
  lines.push('');
110
116
  }
117
+ if (referenceNames && referenceNames.length > 0) {
118
+ lines.push('## References');
119
+ lines.push('');
120
+ lines.push('See the `references/` directory for detailed per-entity API documentation:');
121
+ lines.push('');
122
+ for (const name of referenceNames) {
123
+ lines.push(`- [${name}](references/${name}.md)`);
124
+ }
125
+ lines.push('');
126
+ }
127
+ return lines.join('\n');
128
+ }
129
+ export function buildSkillReference(ref) {
130
+ const lang = ref.language ?? 'bash';
131
+ const lines = [];
132
+ lines.push(`# ${ref.title}`);
133
+ lines.push('');
134
+ lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
135
+ lines.push('');
136
+ lines.push(ref.description);
137
+ lines.push('');
138
+ lines.push('## Usage');
139
+ lines.push('');
140
+ lines.push(`\`\`\`${lang}`);
141
+ for (const u of ref.usage) {
142
+ lines.push(u);
143
+ }
144
+ lines.push('```');
145
+ lines.push('');
146
+ lines.push('## Examples');
147
+ lines.push('');
148
+ for (const ex of ref.examples) {
149
+ lines.push(`### ${ex.description}`);
150
+ lines.push('');
151
+ lines.push(`\`\`\`${lang}`);
152
+ for (const cmd of ex.code) {
153
+ lines.push(cmd);
154
+ }
155
+ lines.push('```');
156
+ lines.push('');
157
+ }
111
158
  return lines.join('\n');
112
159
  }
@@ -3,4 +3,4 @@ import type { GeneratedDocFile, McpTool } from './docs-utils';
3
3
  export declare function generateHooksReadme(tables: CleanTable[], customOperations: CleanOperation[]): GeneratedDocFile;
4
4
  export declare function generateHooksAgentsDocs(tables: CleanTable[], customOperations: CleanOperation[]): GeneratedDocFile;
5
5
  export declare function getHooksMcpTools(tables: CleanTable[], customOperations: CleanOperation[]): McpTool[];
6
- export declare function generateHooksSkills(tables: CleanTable[], customOperations: CleanOperation[]): GeneratedDocFile[];
6
+ export declare function generateHooksSkills(tables: CleanTable[], customOperations: CleanOperation[], targetName: string): GeneratedDocFile[];