@constructive-io/graphql-codegen 4.14.3 → 4.15.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/codegen/cli/docs-generator.d.ts +1 -1
- package/core/codegen/cli/docs-generator.js +66 -471
- package/core/codegen/docs-utils.d.ts +36 -1
- package/core/codegen/docs-utils.js +148 -2
- package/core/codegen/hooks-docs-generator.js +23 -122
- package/core/codegen/orm/docs-generator.js +29 -85
- package/esm/core/codegen/cli/docs-generator.d.ts +1 -1
- package/esm/core/codegen/cli/docs-generator.js +67 -472
- package/esm/core/codegen/docs-utils.d.ts +36 -1
- package/esm/core/codegen/docs-utils.js +145 -3
- package/esm/core/codegen/hooks-docs-generator.js +24 -123
- package/esm/core/codegen/orm/docs-generator.js +31 -87
- package/package.json +17 -17
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toKebabCase } from 'komoji';
|
|
2
|
-
import { flattenArgs, flattenedArgsToFlags, cleanTypeName, getEditableFields, getReadmeHeader, getReadmeFooter, gqlTypeToJsonSchemaType, buildSkillFile, buildSkillReference, } from '../docs-utils';
|
|
2
|
+
import { flattenArgs, flattenedArgsToFlags, cleanTypeName, getEditableFields, categorizeSpecialFields, buildSpecialFieldsMarkdown, getReadmeHeader, getReadmeFooter, gqlTypeToJsonSchemaType, buildSkillFile, buildSkillReference, } from '../docs-utils';
|
|
3
3
|
import { getScalarFields, getTableNames, getPrimaryKeyInfo, } from '../utils';
|
|
4
4
|
import { getFieldsWithDefaults } from './table-command-generator';
|
|
5
5
|
export { resolveDocsConfig } from '../docs-utils';
|
|
@@ -83,7 +83,7 @@ export function generateReadme(tables, customOperations, toolName, registry) {
|
|
|
83
83
|
const kebab = toKebabCase(singularName);
|
|
84
84
|
const pk = getPrimaryKeyInfo(table)[0];
|
|
85
85
|
const scalarFields = getScalarFields(table);
|
|
86
|
-
const editableFields = getEditableFields(table);
|
|
86
|
+
const editableFields = getEditableFields(table, registry);
|
|
87
87
|
lines.push(`### \`${kebab}\``);
|
|
88
88
|
lines.push('');
|
|
89
89
|
lines.push(`CRUD operations for ${table.name} records.`);
|
|
@@ -116,6 +116,8 @@ export function generateReadme(tables, customOperations, toolName, registry) {
|
|
|
116
116
|
if (requiredCreate.length === 0 && optionalCreate.length === 0) {
|
|
117
117
|
lines.push(`**Create fields:** ${editableFields.map((f) => `\`${f.name}\``).join(', ')}`);
|
|
118
118
|
}
|
|
119
|
+
const specialGroups = categorizeSpecialFields(table, registry);
|
|
120
|
+
lines.push(...buildSpecialFieldsMarkdown(specialGroups));
|
|
119
121
|
lines.push('');
|
|
120
122
|
}
|
|
121
123
|
}
|
|
@@ -169,228 +171,42 @@ export function generateReadme(tables, customOperations, toolName, registry) {
|
|
|
169
171
|
content: lines.join('\n'),
|
|
170
172
|
};
|
|
171
173
|
}
|
|
172
|
-
export function generateAgentsDocs(tables, customOperations, toolName,
|
|
174
|
+
export function generateAgentsDocs(tables, customOperations, toolName, _registry) {
|
|
173
175
|
const lines = [];
|
|
174
|
-
|
|
176
|
+
const tableCount = tables.length;
|
|
177
|
+
const customOpCount = customOperations.length;
|
|
178
|
+
lines.push(`# ${toolName} CLI`);
|
|
175
179
|
lines.push('');
|
|
176
180
|
lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
|
|
177
|
-
lines.push('> This document is structured for LLM/agent consumption.');
|
|
178
181
|
lines.push('');
|
|
179
|
-
lines.push('##
|
|
182
|
+
lines.push('## Stack');
|
|
180
183
|
lines.push('');
|
|
181
|
-
lines.push(
|
|
182
|
-
lines.push('
|
|
183
|
-
lines.push(
|
|
184
|
+
lines.push(`- Generated CLI for a GraphQL API (TypeScript)`);
|
|
185
|
+
lines.push(`- ${tableCount} table${tableCount !== 1 ? 's' : ''}${customOpCount > 0 ? `, ${customOpCount} custom operation${customOpCount !== 1 ? 's' : ''}` : ''}`);
|
|
186
|
+
lines.push(`- Config stored at \`~/.${toolName}/config/\` via appstash`);
|
|
184
187
|
lines.push('');
|
|
185
|
-
lines.push('##
|
|
186
|
-
lines.push('');
|
|
187
|
-
lines.push('Before running any data commands, you must:');
|
|
188
|
-
lines.push('');
|
|
189
|
-
lines.push(`1. Create a context: \`${toolName} context create <name> --endpoint <url>\``);
|
|
190
|
-
lines.push(`2. Activate it: \`${toolName} context use <name>\``);
|
|
191
|
-
lines.push(`3. Authenticate: \`${toolName} auth set-token <token>\``);
|
|
192
|
-
lines.push('');
|
|
193
|
-
lines.push('## TOOLS');
|
|
194
|
-
lines.push('');
|
|
195
|
-
lines.push('### TOOL: context');
|
|
196
|
-
lines.push('');
|
|
197
|
-
lines.push('Manage named API endpoint contexts (like kubectl contexts).');
|
|
198
|
-
lines.push('');
|
|
199
|
-
lines.push('```');
|
|
200
|
-
lines.push('SUBCOMMANDS:');
|
|
201
|
-
lines.push(` ${toolName} context create <name> --endpoint <url> Create a new context`);
|
|
202
|
-
lines.push(` ${toolName} context list List all contexts`);
|
|
203
|
-
lines.push(` ${toolName} context use <name> Set active context`);
|
|
204
|
-
lines.push(` ${toolName} context current Show active context`);
|
|
205
|
-
lines.push(` ${toolName} context delete <name> Delete a context`);
|
|
206
|
-
lines.push('');
|
|
207
|
-
lines.push('INPUT:');
|
|
208
|
-
lines.push(' name: string (required) - Context identifier');
|
|
209
|
-
lines.push(' endpoint: string (required for create) - GraphQL endpoint URL');
|
|
210
|
-
lines.push('');
|
|
211
|
-
lines.push('OUTPUT: JSON');
|
|
212
|
-
lines.push(' create: { name, endpoint }');
|
|
213
|
-
lines.push(' list: [{ name, endpoint, isCurrent, hasCredentials }]');
|
|
214
|
-
lines.push(' use: { name, endpoint }');
|
|
215
|
-
lines.push(' current: { name, endpoint }');
|
|
216
|
-
lines.push(' delete: { deleted: name }');
|
|
217
|
-
lines.push('```');
|
|
218
|
-
lines.push('');
|
|
219
|
-
lines.push('### TOOL: auth');
|
|
220
|
-
lines.push('');
|
|
221
|
-
lines.push('Manage authentication tokens per context.');
|
|
222
|
-
lines.push('');
|
|
223
|
-
lines.push('```');
|
|
224
|
-
lines.push('SUBCOMMANDS:');
|
|
225
|
-
lines.push(` ${toolName} auth set-token <token> Store bearer token for current context`);
|
|
226
|
-
lines.push(` ${toolName} auth status Show auth status for all contexts`);
|
|
227
|
-
lines.push(` ${toolName} auth logout Remove credentials for current context`);
|
|
228
|
-
lines.push('');
|
|
229
|
-
lines.push('INPUT:');
|
|
230
|
-
lines.push(' token: string (required for set-token) - Bearer token value');
|
|
231
|
-
lines.push('');
|
|
232
|
-
lines.push('OUTPUT: JSON');
|
|
233
|
-
lines.push(' set-token: { context, status: "authenticated" }');
|
|
234
|
-
lines.push(' status: [{ context, authenticated: boolean }]');
|
|
235
|
-
lines.push(' logout: { context, status: "logged out" }');
|
|
236
|
-
lines.push('```');
|
|
237
|
-
lines.push('');
|
|
238
|
-
lines.push('### TOOL: config');
|
|
239
|
-
lines.push('');
|
|
240
|
-
lines.push('Manage per-context key-value configuration variables.');
|
|
241
|
-
lines.push('');
|
|
242
|
-
lines.push('```');
|
|
243
|
-
lines.push('SUBCOMMANDS:');
|
|
244
|
-
lines.push(` ${toolName} config get <key> Get a config value`);
|
|
245
|
-
lines.push(` ${toolName} config set <key> <value> Set a config value`);
|
|
246
|
-
lines.push(` ${toolName} config list List all config values`);
|
|
247
|
-
lines.push(` ${toolName} config delete <key> Delete a config value`);
|
|
248
|
-
lines.push('');
|
|
249
|
-
lines.push('INPUT:');
|
|
250
|
-
lines.push(' key: string (required for get/set/delete) - Variable name');
|
|
251
|
-
lines.push(' value: string (required for set) - Variable value');
|
|
252
|
-
lines.push('');
|
|
253
|
-
lines.push('OUTPUT: JSON');
|
|
254
|
-
lines.push(' get: { key, value }');
|
|
255
|
-
lines.push(' set: { key, value }');
|
|
256
|
-
lines.push(' list: { vars: { key: value, ... } }');
|
|
257
|
-
lines.push(' delete: { deleted: key }');
|
|
258
|
-
lines.push('```');
|
|
259
|
-
lines.push('');
|
|
260
|
-
for (const table of tables) {
|
|
261
|
-
const { singularName } = getTableNames(table);
|
|
262
|
-
const kebab = toKebabCase(singularName);
|
|
263
|
-
const pk = getPrimaryKeyInfo(table)[0];
|
|
264
|
-
const scalarFields = getScalarFields(table);
|
|
265
|
-
const editableFields = getEditableFields(table);
|
|
266
|
-
const defaultFields = getFieldsWithDefaults(table, registry);
|
|
267
|
-
const requiredCreateFields = editableFields.filter((f) => !defaultFields.has(f.name));
|
|
268
|
-
const optionalCreateFields = editableFields.filter((f) => defaultFields.has(f.name));
|
|
269
|
-
const createFlags = [
|
|
270
|
-
...requiredCreateFields.map((f) => `--${f.name} <value>`),
|
|
271
|
-
...optionalCreateFields.map((f) => `[--${f.name} <value>]`),
|
|
272
|
-
].join(' ');
|
|
273
|
-
lines.push(`### TOOL: ${kebab}`);
|
|
274
|
-
lines.push('');
|
|
275
|
-
lines.push(`CRUD operations for ${table.name} records.`);
|
|
276
|
-
lines.push('');
|
|
277
|
-
lines.push('```');
|
|
278
|
-
lines.push('SUBCOMMANDS:');
|
|
279
|
-
lines.push(` ${toolName} ${kebab} list List all records`);
|
|
280
|
-
lines.push(` ${toolName} ${kebab} get --${pk.name} <value> Get one record`);
|
|
281
|
-
lines.push(` ${toolName} ${kebab} create ${createFlags}`);
|
|
282
|
-
lines.push(` ${toolName} ${kebab} update --${pk.name} <value> ${editableFields.map((f) => `[--${f.name} <value>]`).join(' ')}`);
|
|
283
|
-
lines.push(` ${toolName} ${kebab} delete --${pk.name} <value> Delete one record`);
|
|
284
|
-
lines.push('');
|
|
285
|
-
lines.push('INPUT FIELDS:');
|
|
286
|
-
for (const f of scalarFields) {
|
|
287
|
-
const isPk = f.name === pk.name;
|
|
288
|
-
lines.push(` ${f.name}: ${cleanTypeName(f.type.gqlType)}${isPk ? ' (primary key)' : ''}`);
|
|
289
|
-
}
|
|
290
|
-
lines.push('');
|
|
291
|
-
lines.push('EDITABLE FIELDS (for create/update):');
|
|
292
|
-
for (const f of editableFields) {
|
|
293
|
-
const optLabel = defaultFields.has(f.name) ? ' (optional, has backend default)' : '';
|
|
294
|
-
lines.push(` ${f.name}: ${cleanTypeName(f.type.gqlType)}${optLabel}`);
|
|
295
|
-
}
|
|
296
|
-
lines.push('');
|
|
297
|
-
lines.push('OUTPUT: JSON');
|
|
298
|
-
lines.push(` list: [{ ${scalarFields.map((f) => f.name).join(', ')} }]`);
|
|
299
|
-
lines.push(` get: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
300
|
-
lines.push(` create: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
301
|
-
lines.push(` update: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
302
|
-
lines.push(` delete: { ${pk.name} }`);
|
|
303
|
-
lines.push('```');
|
|
304
|
-
lines.push('');
|
|
305
|
-
}
|
|
306
|
-
for (const op of customOperations) {
|
|
307
|
-
const kebab = toKebabCase(op.name);
|
|
308
|
-
const flat = flattenArgs(op.args, registry);
|
|
309
|
-
lines.push(`### TOOL: ${kebab}`);
|
|
310
|
-
lines.push('');
|
|
311
|
-
lines.push(op.description || op.name);
|
|
312
|
-
lines.push('');
|
|
313
|
-
lines.push('```');
|
|
314
|
-
lines.push(`TYPE: ${op.kind}`);
|
|
315
|
-
if (flat.length > 0) {
|
|
316
|
-
const flags = flattenedArgsToFlags(flat);
|
|
317
|
-
lines.push(`USAGE: ${toolName} ${kebab} ${flags}`);
|
|
318
|
-
lines.push('');
|
|
319
|
-
lines.push('INPUT:');
|
|
320
|
-
for (const a of flat) {
|
|
321
|
-
const reqLabel = a.required ? ' (required)' : '';
|
|
322
|
-
lines.push(` ${a.flag}: ${a.type}${reqLabel}`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
lines.push(`USAGE: ${toolName} ${kebab}`);
|
|
327
|
-
lines.push('');
|
|
328
|
-
lines.push('INPUT: none');
|
|
329
|
-
}
|
|
330
|
-
lines.push('');
|
|
331
|
-
lines.push('OUTPUT: JSON');
|
|
332
|
-
lines.push('```');
|
|
333
|
-
lines.push('');
|
|
334
|
-
}
|
|
335
|
-
lines.push('## WORKFLOWS');
|
|
336
|
-
lines.push('');
|
|
337
|
-
lines.push('### Initial setup');
|
|
188
|
+
lines.push('## Quick Start');
|
|
338
189
|
lines.push('');
|
|
339
190
|
lines.push('```bash');
|
|
340
|
-
lines.push(`${toolName} context create dev --endpoint
|
|
191
|
+
lines.push(`${toolName} context create dev --endpoint <url>`);
|
|
341
192
|
lines.push(`${toolName} context use dev`);
|
|
342
|
-
lines.push(`${toolName} auth set-token
|
|
193
|
+
lines.push(`${toolName} auth set-token <token>`);
|
|
343
194
|
lines.push('```');
|
|
344
195
|
lines.push('');
|
|
345
|
-
|
|
346
|
-
const firstTable = tables[0];
|
|
347
|
-
const { singularName } = getTableNames(firstTable);
|
|
348
|
-
const kebab = toKebabCase(singularName);
|
|
349
|
-
const editableFields = getEditableFields(firstTable);
|
|
350
|
-
const pk = getPrimaryKeyInfo(firstTable)[0];
|
|
351
|
-
lines.push(`### CRUD workflow (${kebab})`);
|
|
352
|
-
lines.push('');
|
|
353
|
-
lines.push('```bash');
|
|
354
|
-
lines.push(`# List all`);
|
|
355
|
-
lines.push(`${toolName} ${kebab} list`);
|
|
356
|
-
lines.push('');
|
|
357
|
-
lines.push(`# Create`);
|
|
358
|
-
lines.push(`${toolName} ${kebab} create ${editableFields.map((f) => `--${f.name} "value"`).join(' ')}`);
|
|
359
|
-
lines.push('');
|
|
360
|
-
lines.push(`# Get by ${pk.name}`);
|
|
361
|
-
lines.push(`${toolName} ${kebab} get --${pk.name} <value>`);
|
|
362
|
-
lines.push('');
|
|
363
|
-
lines.push(`# Update`);
|
|
364
|
-
lines.push(`${toolName} ${kebab} update --${pk.name} <value> --${editableFields[0]?.name || 'field'} "new-value"`);
|
|
365
|
-
lines.push('');
|
|
366
|
-
lines.push(`# Delete`);
|
|
367
|
-
lines.push(`${toolName} ${kebab} delete --${pk.name} <value>`);
|
|
368
|
-
lines.push('```');
|
|
369
|
-
lines.push('');
|
|
370
|
-
}
|
|
371
|
-
lines.push('### Piping output');
|
|
196
|
+
lines.push('## Resources');
|
|
372
197
|
lines.push('');
|
|
373
|
-
lines.push(
|
|
374
|
-
lines.push(
|
|
375
|
-
lines.push(`${toolName} car list | jq '.'`);
|
|
376
|
-
lines.push('');
|
|
377
|
-
lines.push(`# Extract field`);
|
|
378
|
-
lines.push(`${toolName} car list | jq '.[].id'`);
|
|
198
|
+
lines.push(`- **Full API reference:** [README.md](./README.md) — CRUD docs for all ${tableCount} tables`);
|
|
199
|
+
lines.push('- **Schema types:** [types.ts](./types.ts)');
|
|
379
200
|
lines.push('');
|
|
380
|
-
lines.push(
|
|
381
|
-
lines.push(`${toolName} car list | jq 'length'`);
|
|
382
|
-
lines.push('```');
|
|
201
|
+
lines.push('## Conventions');
|
|
383
202
|
lines.push('');
|
|
384
|
-
lines.push('
|
|
203
|
+
lines.push('- All commands output JSON to stdout');
|
|
204
|
+
lines.push('- Use `--help` on any command for usage');
|
|
205
|
+
lines.push('- Exit 0 = success, 1 = error');
|
|
385
206
|
lines.push('');
|
|
386
|
-
lines.push('
|
|
387
|
-
lines.push('- `0`: Success');
|
|
388
|
-
lines.push('- `1`: Error (auth failure, not found, validation error, network error)');
|
|
207
|
+
lines.push('## Boundaries');
|
|
389
208
|
lines.push('');
|
|
390
|
-
lines.push('
|
|
391
|
-
lines.push('- "No active context": Run `context use <name>` first');
|
|
392
|
-
lines.push('- "Not authenticated": Run `auth set-token <token>` first');
|
|
393
|
-
lines.push('- "Record not found": The requested ID does not exist');
|
|
209
|
+
lines.push('All files in this directory are generated. Do not edit manually.');
|
|
394
210
|
lines.push('');
|
|
395
211
|
return {
|
|
396
212
|
fileName: 'AGENTS.md',
|
|
@@ -508,7 +324,7 @@ export function getCliMcpTools(tables, customOperations, toolName, registry) {
|
|
|
508
324
|
const kebab = toKebabCase(singularName);
|
|
509
325
|
const pk = getPrimaryKeyInfo(table)[0];
|
|
510
326
|
const scalarFields = getScalarFields(table);
|
|
511
|
-
const editableFields = getEditableFields(table);
|
|
327
|
+
const editableFields = getEditableFields(table, registry);
|
|
512
328
|
const defaultFields = getFieldsWithDefaults(table, registry);
|
|
513
329
|
const requiredCreateFieldNames = editableFields
|
|
514
330
|
.filter((f) => !defaultFields.has(f.name))
|
|
@@ -713,18 +529,23 @@ export function generateSkills(tables, customOperations, toolName, targetName, r
|
|
|
713
529
|
const { singularName } = getTableNames(table);
|
|
714
530
|
const kebab = toKebabCase(singularName);
|
|
715
531
|
const pk = getPrimaryKeyInfo(table)[0];
|
|
716
|
-
const editableFields = getEditableFields(table);
|
|
532
|
+
const editableFields = getEditableFields(table, registry);
|
|
717
533
|
const defaultFields = getFieldsWithDefaults(table, registry);
|
|
718
534
|
const createFlags = [
|
|
719
535
|
...editableFields.filter((f) => !defaultFields.has(f.name)).map((f) => `--${f.name} <value>`),
|
|
720
536
|
...editableFields.filter((f) => defaultFields.has(f.name)).map((f) => `[--${f.name} <value>]`),
|
|
721
537
|
].join(' ');
|
|
722
538
|
referenceNames.push(kebab);
|
|
539
|
+
const skillSpecialGroups = categorizeSpecialFields(table, registry);
|
|
540
|
+
const skillSpecialDesc = skillSpecialGroups.length > 0
|
|
541
|
+
? `CRUD operations for ${table.name} records via ${toolName} CLI\n\n` +
|
|
542
|
+
skillSpecialGroups.map((g) => `**${g.label}:** ${g.fields.map((f) => `\`${f.name}\``).join(', ')}\n${g.description}`).join('\n\n')
|
|
543
|
+
: `CRUD operations for ${table.name} records via ${toolName} CLI`;
|
|
723
544
|
files.push({
|
|
724
545
|
fileName: `${skillName}/references/${kebab}.md`,
|
|
725
546
|
content: buildSkillReference({
|
|
726
547
|
title: singularName,
|
|
727
|
-
description:
|
|
548
|
+
description: skillSpecialDesc,
|
|
728
549
|
usage: [
|
|
729
550
|
`${toolName} ${kebab} list`,
|
|
730
551
|
`${toolName} ${kebab} get --${pk.name} <value>`,
|
|
@@ -982,7 +803,7 @@ export function generateMultiTargetReadme(input) {
|
|
|
982
803
|
const kebab = toKebabCase(singularName);
|
|
983
804
|
const pk = getPrimaryKeyInfo(table)[0];
|
|
984
805
|
const scalarFields = getScalarFields(table);
|
|
985
|
-
const editableFields = getEditableFields(table);
|
|
806
|
+
const editableFields = getEditableFields(table, registry);
|
|
986
807
|
const defaultFields = getFieldsWithDefaults(table, registry);
|
|
987
808
|
lines.push(`### \`${tgt.name}:${kebab}\``);
|
|
988
809
|
lines.push('');
|
|
@@ -1015,6 +836,8 @@ export function generateMultiTargetReadme(input) {
|
|
|
1015
836
|
if (requiredCreate.length === 0 && optionalCreate.length === 0) {
|
|
1016
837
|
lines.push(`**Create fields:** ${editableFields.map((f) => `\`${f.name}\``).join(', ')}`);
|
|
1017
838
|
}
|
|
839
|
+
const mtSpecialGroups = categorizeSpecialFields(table, registry);
|
|
840
|
+
lines.push(...buildSpecialFieldsMarkdown(mtSpecialGroups));
|
|
1018
841
|
lines.push('');
|
|
1019
842
|
}
|
|
1020
843
|
for (const op of tgt.customOperations) {
|
|
@@ -1076,283 +899,50 @@ export function generateMultiTargetReadme(input) {
|
|
|
1076
899
|
};
|
|
1077
900
|
}
|
|
1078
901
|
export function generateMultiTargetAgentsDocs(input) {
|
|
1079
|
-
const { toolName, builtinNames, targets
|
|
902
|
+
const { toolName, builtinNames, targets } = input;
|
|
1080
903
|
const lines = [];
|
|
1081
|
-
|
|
904
|
+
const totalTables = targets.reduce((sum, t) => sum + t.tables.length, 0);
|
|
905
|
+
const totalCustomOps = targets.reduce((sum, t) => sum + t.customOperations.length, 0);
|
|
906
|
+
lines.push(`# ${toolName} CLI`);
|
|
1082
907
|
lines.push('');
|
|
1083
908
|
lines.push('<!-- @constructive-io/graphql-codegen - DO NOT EDIT -->');
|
|
1084
|
-
lines.push('> This document is structured for LLM/agent consumption.');
|
|
1085
909
|
lines.push('');
|
|
1086
|
-
lines.push('##
|
|
910
|
+
lines.push('## Stack');
|
|
1087
911
|
lines.push('');
|
|
1088
|
-
lines.push(
|
|
1089
|
-
lines.push(
|
|
1090
|
-
lines.push(
|
|
912
|
+
lines.push(`- Unified multi-target CLI for GraphQL APIs (TypeScript)`);
|
|
913
|
+
lines.push(`- ${targets.length} target${targets.length !== 1 ? 's' : ''}: ${targets.map((t) => t.name).join(', ')}`);
|
|
914
|
+
lines.push(`- ${totalTables} table${totalTables !== 1 ? 's' : ''}${totalCustomOps > 0 ? `, ${totalCustomOps} custom operation${totalCustomOps !== 1 ? 's' : ''}` : ''}`);
|
|
915
|
+
lines.push(`- Config stored at \`~/.${toolName}/config/\` via appstash`);
|
|
1091
916
|
lines.push('');
|
|
1092
|
-
lines.push('
|
|
1093
|
-
for (const tgt of targets) {
|
|
1094
|
-
lines.push(` ${tgt.name}: ${tgt.endpoint}`);
|
|
1095
|
-
}
|
|
1096
|
-
lines.push('');
|
|
1097
|
-
lines.push('COMMAND FORMAT:');
|
|
1098
|
-
lines.push(` ${toolName} <target>:<command> <subcommand> [flags] Target-specific commands`);
|
|
1099
|
-
lines.push(` ${toolName} ${builtinNames.context} <subcommand> [flags] Context management`);
|
|
1100
|
-
lines.push(` ${toolName} ${builtinNames.auth} <subcommand> [flags] Authentication`);
|
|
1101
|
-
lines.push(` ${toolName} ${builtinNames.config} <subcommand> [flags] Config key-value store`);
|
|
1102
|
-
lines.push('');
|
|
1103
|
-
lines.push('## PREREQUISITES');
|
|
1104
|
-
lines.push('');
|
|
1105
|
-
lines.push('Before running any data commands, you must:');
|
|
1106
|
-
lines.push('');
|
|
1107
|
-
lines.push(`1. Create a context: \`${toolName} ${builtinNames.context} create <name>\``);
|
|
1108
|
-
lines.push(` (prompts for per-target endpoints, defaults baked from config)`);
|
|
1109
|
-
lines.push(`2. Activate it: \`${toolName} ${builtinNames.context} use <name>\``);
|
|
1110
|
-
lines.push(`3. Authenticate: \`${toolName} ${builtinNames.auth} set-token <token>\``);
|
|
1111
|
-
lines.push('');
|
|
1112
|
-
lines.push('For local development, create a context accepting all defaults:');
|
|
917
|
+
lines.push('## Quick Start');
|
|
1113
918
|
lines.push('');
|
|
1114
919
|
lines.push('```bash');
|
|
1115
|
-
lines.push(`${toolName} ${builtinNames.context} create
|
|
1116
|
-
lines.push(`${toolName} ${builtinNames.context} use
|
|
920
|
+
lines.push(`${toolName} ${builtinNames.context} create dev`);
|
|
921
|
+
lines.push(`${toolName} ${builtinNames.context} use dev`);
|
|
1117
922
|
lines.push(`${toolName} ${builtinNames.auth} set-token <token>`);
|
|
1118
923
|
lines.push('```');
|
|
1119
924
|
lines.push('');
|
|
1120
|
-
lines.push('##
|
|
1121
|
-
lines.push('');
|
|
1122
|
-
lines.push(`### TOOL: ${builtinNames.context}`);
|
|
1123
|
-
lines.push('');
|
|
1124
|
-
lines.push('Manage named API endpoint contexts. Each context stores per-target endpoint overrides.');
|
|
1125
|
-
lines.push('');
|
|
1126
|
-
lines.push('```');
|
|
1127
|
-
lines.push('SUBCOMMANDS:');
|
|
1128
|
-
lines.push(` ${toolName} ${builtinNames.context} create <name> Create a new context`);
|
|
1129
|
-
lines.push(` ${toolName} ${builtinNames.context} list List all contexts`);
|
|
1130
|
-
lines.push(` ${toolName} ${builtinNames.context} use <name> Set active context`);
|
|
1131
|
-
lines.push(` ${toolName} ${builtinNames.context} current Show active context`);
|
|
1132
|
-
lines.push(` ${toolName} ${builtinNames.context} delete <name> Delete a context`);
|
|
1133
|
-
lines.push('');
|
|
1134
|
-
lines.push('CREATE OPTIONS:');
|
|
1135
|
-
for (const tgt of targets) {
|
|
1136
|
-
lines.push(` --${tgt.name}-endpoint: string (default: ${tgt.endpoint})`);
|
|
1137
|
-
}
|
|
1138
|
-
lines.push('');
|
|
1139
|
-
lines.push('OUTPUT: JSON');
|
|
1140
|
-
lines.push(' create: { name, endpoint, targets }');
|
|
1141
|
-
lines.push(' list: [{ name, endpoint, isCurrent, hasCredentials }]');
|
|
1142
|
-
lines.push(' use: { name, endpoint }');
|
|
1143
|
-
lines.push(' current: { name, endpoint }');
|
|
1144
|
-
lines.push(' delete: { deleted: name }');
|
|
1145
|
-
lines.push('```');
|
|
1146
|
-
lines.push('');
|
|
1147
|
-
lines.push(`### TOOL: ${builtinNames.auth}`);
|
|
1148
|
-
lines.push('');
|
|
1149
|
-
lines.push('Manage authentication tokens per context. One shared token across all targets.');
|
|
1150
|
-
lines.push('');
|
|
1151
|
-
lines.push('```');
|
|
1152
|
-
lines.push('SUBCOMMANDS:');
|
|
1153
|
-
lines.push(` ${toolName} ${builtinNames.auth} set-token <token> Store bearer token for current context`);
|
|
1154
|
-
lines.push(` ${toolName} ${builtinNames.auth} status Show auth status for all contexts`);
|
|
1155
|
-
lines.push(` ${toolName} ${builtinNames.auth} logout Remove credentials for current context`);
|
|
1156
|
-
lines.push('');
|
|
1157
|
-
lines.push('INPUT:');
|
|
1158
|
-
lines.push(' token: string (required for set-token) - Bearer token value');
|
|
1159
|
-
lines.push('');
|
|
1160
|
-
lines.push('OUTPUT: JSON');
|
|
1161
|
-
lines.push(' set-token: { context, status: "authenticated" }');
|
|
1162
|
-
lines.push(' status: [{ context, authenticated: boolean }]');
|
|
1163
|
-
lines.push(' logout: { context, status: "logged out" }');
|
|
1164
|
-
lines.push('```');
|
|
1165
|
-
lines.push('');
|
|
1166
|
-
lines.push(`### TOOL: ${builtinNames.config}`);
|
|
1167
|
-
lines.push('');
|
|
1168
|
-
lines.push('Manage per-context key-value configuration variables.');
|
|
1169
|
-
lines.push('');
|
|
1170
|
-
lines.push('```');
|
|
1171
|
-
lines.push('SUBCOMMANDS:');
|
|
1172
|
-
lines.push(` ${toolName} ${builtinNames.config} get <key> Get a config value`);
|
|
1173
|
-
lines.push(` ${toolName} ${builtinNames.config} set <key> <value> Set a config value`);
|
|
1174
|
-
lines.push(` ${toolName} ${builtinNames.config} list List all config values`);
|
|
1175
|
-
lines.push(` ${toolName} ${builtinNames.config} delete <key> Delete a config value`);
|
|
1176
|
-
lines.push('');
|
|
1177
|
-
lines.push('INPUT:');
|
|
1178
|
-
lines.push(' key: string (required for get/set/delete) - Variable name');
|
|
1179
|
-
lines.push(' value: string (required for set) - Variable value');
|
|
1180
|
-
lines.push('');
|
|
1181
|
-
lines.push('OUTPUT: JSON');
|
|
1182
|
-
lines.push(' get: { key, value }');
|
|
1183
|
-
lines.push(' set: { key, value }');
|
|
1184
|
-
lines.push(' list: { vars: { key: value, ... } }');
|
|
1185
|
-
lines.push(' delete: { deleted: key }');
|
|
1186
|
-
lines.push('```');
|
|
1187
|
-
lines.push('');
|
|
1188
|
-
lines.push('### TOOL: helpers (SDK)');
|
|
1189
|
-
lines.push('');
|
|
1190
|
-
lines.push('Typed client factories for use in scripts and services (generated helpers.ts).');
|
|
1191
|
-
lines.push('Resolves credentials via: appstash store -> env vars -> throw.');
|
|
925
|
+
lines.push('## Command Format');
|
|
1192
926
|
lines.push('');
|
|
1193
927
|
lines.push('```');
|
|
1194
|
-
lines.push(
|
|
1195
|
-
for (const tgt of targets) {
|
|
1196
|
-
const pascalName = tgt.name.charAt(0).toUpperCase() + tgt.name.slice(1);
|
|
1197
|
-
lines.push(` create${pascalName}Client(contextName?) Create a configured ${tgt.name} ORM client`);
|
|
1198
|
-
}
|
|
1199
|
-
lines.push('');
|
|
1200
|
-
lines.push('USAGE:');
|
|
1201
|
-
lines.push(` import { create${targets[0] ? targets[0].name.charAt(0).toUpperCase() + targets[0].name.slice(1) : 'Target'}Client } from './helpers';`);
|
|
1202
|
-
lines.push(` const client = create${targets[0] ? targets[0].name.charAt(0).toUpperCase() + targets[0].name.slice(1) : 'Target'}Client();`);
|
|
1203
|
-
lines.push('');
|
|
1204
|
-
lines.push('CREDENTIAL RESOLUTION:');
|
|
1205
|
-
lines.push(` 1. appstash store (~/.${toolName}/config/)`);
|
|
1206
|
-
const envPrefix = toolName.toUpperCase().replace(/-/g, '_');
|
|
1207
|
-
lines.push(` 2. env vars (${envPrefix}_TOKEN, ${envPrefix}_<TARGET>_ENDPOINT)`);
|
|
1208
|
-
lines.push(' 3. throws with actionable error message');
|
|
1209
|
-
lines.push('```');
|
|
1210
|
-
lines.push('');
|
|
1211
|
-
for (const tgt of targets) {
|
|
1212
|
-
for (const table of tgt.tables) {
|
|
1213
|
-
const { singularName } = getTableNames(table);
|
|
1214
|
-
const kebab = toKebabCase(singularName);
|
|
1215
|
-
const pk = getPrimaryKeyInfo(table)[0];
|
|
1216
|
-
const scalarFields = getScalarFields(table);
|
|
1217
|
-
const editableFields = getEditableFields(table);
|
|
1218
|
-
const defaultFields = getFieldsWithDefaults(table, registry);
|
|
1219
|
-
const requiredCreateFields = editableFields.filter((f) => !defaultFields.has(f.name));
|
|
1220
|
-
const optionalCreateFields = editableFields.filter((f) => defaultFields.has(f.name));
|
|
1221
|
-
const createFlags = [
|
|
1222
|
-
...requiredCreateFields.map((f) => `--${f.name} <value>`),
|
|
1223
|
-
...optionalCreateFields.map((f) => `[--${f.name} <value>]`),
|
|
1224
|
-
].join(' ');
|
|
1225
|
-
lines.push(`### TOOL: ${tgt.name}:${kebab}`);
|
|
1226
|
-
lines.push('');
|
|
1227
|
-
lines.push(`CRUD operations for ${table.name} records (${tgt.name} target).`);
|
|
1228
|
-
lines.push('');
|
|
1229
|
-
lines.push('```');
|
|
1230
|
-
lines.push('SUBCOMMANDS:');
|
|
1231
|
-
lines.push(` ${toolName} ${tgt.name}:${kebab} list List all records`);
|
|
1232
|
-
lines.push(` ${toolName} ${tgt.name}:${kebab} get --${pk.name} <value> Get one record`);
|
|
1233
|
-
lines.push(` ${toolName} ${tgt.name}:${kebab} create ${createFlags}`);
|
|
1234
|
-
lines.push(` ${toolName} ${tgt.name}:${kebab} update --${pk.name} <value> ${editableFields.map((f) => `[--${f.name} <value>]`).join(' ')}`);
|
|
1235
|
-
lines.push(` ${toolName} ${tgt.name}:${kebab} delete --${pk.name} <value> Delete one record`);
|
|
1236
|
-
lines.push('');
|
|
1237
|
-
lines.push('INPUT FIELDS:');
|
|
1238
|
-
for (const f of scalarFields) {
|
|
1239
|
-
const isPk = f.name === pk.name;
|
|
1240
|
-
lines.push(` ${f.name}: ${cleanTypeName(f.type.gqlType)}${isPk ? ' (primary key)' : ''}`);
|
|
1241
|
-
}
|
|
1242
|
-
lines.push('');
|
|
1243
|
-
lines.push('EDITABLE FIELDS (for create/update):');
|
|
1244
|
-
for (const f of editableFields) {
|
|
1245
|
-
const optLabel = defaultFields.has(f.name) ? ' (optional, has backend default)' : '';
|
|
1246
|
-
lines.push(` ${f.name}: ${cleanTypeName(f.type.gqlType)}${optLabel}`);
|
|
1247
|
-
}
|
|
1248
|
-
lines.push('');
|
|
1249
|
-
lines.push('OUTPUT: JSON');
|
|
1250
|
-
lines.push(` list: [{ ${scalarFields.map((f) => f.name).join(', ')} }]`);
|
|
1251
|
-
lines.push(` get: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
1252
|
-
lines.push(` create: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
1253
|
-
lines.push(` update: { ${scalarFields.map((f) => f.name).join(', ')} }`);
|
|
1254
|
-
lines.push(` delete: { ${pk.name} }`);
|
|
1255
|
-
lines.push('```');
|
|
1256
|
-
lines.push('');
|
|
1257
|
-
}
|
|
1258
|
-
for (const op of tgt.customOperations) {
|
|
1259
|
-
const kebab = toKebabCase(op.name);
|
|
1260
|
-
const flat = flattenArgs(op.args, registry);
|
|
1261
|
-
lines.push(`### TOOL: ${tgt.name}:${kebab}`);
|
|
1262
|
-
lines.push('');
|
|
1263
|
-
lines.push(op.description || op.name);
|
|
1264
|
-
lines.push('');
|
|
1265
|
-
lines.push('```');
|
|
1266
|
-
lines.push(`TYPE: ${op.kind}`);
|
|
1267
|
-
if (flat.length > 0) {
|
|
1268
|
-
const flags = flattenedArgsToFlags(flat);
|
|
1269
|
-
lines.push(`USAGE: ${toolName} ${tgt.name}:${kebab} ${flags}`);
|
|
1270
|
-
lines.push('');
|
|
1271
|
-
lines.push('INPUT:');
|
|
1272
|
-
for (const a of flat) {
|
|
1273
|
-
const reqLabel = a.required ? ' (required)' : '';
|
|
1274
|
-
lines.push(` ${a.flag}: ${a.type}${reqLabel}`);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
else {
|
|
1278
|
-
lines.push(`USAGE: ${toolName} ${tgt.name}:${kebab}`);
|
|
1279
|
-
lines.push('');
|
|
1280
|
-
lines.push('INPUT: none');
|
|
1281
|
-
}
|
|
1282
|
-
if (tgt.isAuthTarget && op.kind === 'mutation') {
|
|
1283
|
-
lines.push('');
|
|
1284
|
-
lines.push('FLAGS:');
|
|
1285
|
-
lines.push(' --save-token: boolean - Auto-save returned token to credentials');
|
|
1286
|
-
}
|
|
1287
|
-
lines.push('');
|
|
1288
|
-
lines.push('OUTPUT: JSON');
|
|
1289
|
-
lines.push('```');
|
|
1290
|
-
lines.push('');
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
lines.push('## WORKFLOWS');
|
|
1294
|
-
lines.push('');
|
|
1295
|
-
lines.push('### Initial setup');
|
|
1296
|
-
lines.push('');
|
|
1297
|
-
lines.push('```bash');
|
|
1298
|
-
lines.push(`${toolName} ${builtinNames.context} create dev`);
|
|
1299
|
-
lines.push(`${toolName} ${builtinNames.context} use dev`);
|
|
1300
|
-
lines.push(`${toolName} ${builtinNames.auth} set-token eyJhbGciOiJIUzI1NiIs...`);
|
|
928
|
+
lines.push(`${toolName} <target>:<command> <subcommand> [flags]`);
|
|
1301
929
|
lines.push('```');
|
|
1302
930
|
lines.push('');
|
|
1303
|
-
lines.push('
|
|
931
|
+
lines.push('## Resources');
|
|
1304
932
|
lines.push('');
|
|
1305
|
-
lines.push(
|
|
1306
|
-
lines.push(
|
|
1307
|
-
|
|
1308
|
-
const tgt = targets[i];
|
|
1309
|
-
const continuation = i < targets.length - 1 ? ' \\' : '';
|
|
1310
|
-
lines.push(` --${tgt.name}-endpoint https://${tgt.name}.prod.example.com/graphql${continuation}`);
|
|
1311
|
-
}
|
|
1312
|
-
lines.push(`${toolName} ${builtinNames.context} use production`);
|
|
1313
|
-
lines.push('```');
|
|
1314
|
-
lines.push('');
|
|
1315
|
-
if (targets.length > 0 && targets[0].tables.length > 0) {
|
|
1316
|
-
const tgt = targets[0];
|
|
1317
|
-
const table = tgt.tables[0];
|
|
1318
|
-
const { singularName } = getTableNames(table);
|
|
1319
|
-
const kebab = toKebabCase(singularName);
|
|
1320
|
-
const editableFields = getEditableFields(table);
|
|
1321
|
-
const pk = getPrimaryKeyInfo(table)[0];
|
|
1322
|
-
lines.push(`### CRUD workflow (${tgt.name}:${kebab})`);
|
|
1323
|
-
lines.push('');
|
|
1324
|
-
lines.push('```bash');
|
|
1325
|
-
lines.push(`${toolName} ${tgt.name}:${kebab} list`);
|
|
1326
|
-
lines.push(`${toolName} ${tgt.name}:${kebab} create ${editableFields.map((f) => `--${f.name} "value"`).join(' ')}`);
|
|
1327
|
-
lines.push(`${toolName} ${tgt.name}:${kebab} get --${pk.name} <value>`);
|
|
1328
|
-
lines.push(`${toolName} ${tgt.name}:${kebab} update --${pk.name} <value> --${editableFields[0]?.name || 'field'} "new-value"`);
|
|
1329
|
-
lines.push(`${toolName} ${tgt.name}:${kebab} delete --${pk.name} <value>`);
|
|
1330
|
-
lines.push('```');
|
|
1331
|
-
lines.push('');
|
|
1332
|
-
}
|
|
1333
|
-
lines.push('### Piping output');
|
|
933
|
+
lines.push(`- **Full API reference:** [README.md](./README.md) — CRUD docs for all ${totalTables} tables across ${targets.length} targets`);
|
|
934
|
+
lines.push('- **Schema types:** [types.ts](./types.ts)');
|
|
935
|
+
lines.push('- **SDK helpers:** [helpers.ts](./helpers.ts) — typed client factories');
|
|
1334
936
|
lines.push('');
|
|
1335
|
-
lines.push('
|
|
1336
|
-
if (targets.length > 0 && targets[0].tables.length > 0) {
|
|
1337
|
-
const tgt = targets[0];
|
|
1338
|
-
const kebab = toKebabCase(getTableNames(tgt.tables[0]).singularName);
|
|
1339
|
-
lines.push(`${toolName} ${tgt.name}:${kebab} list | jq '.'`);
|
|
1340
|
-
lines.push(`${toolName} ${tgt.name}:${kebab} list | jq '.[].id'`);
|
|
1341
|
-
lines.push(`${toolName} ${tgt.name}:${kebab} list | jq 'length'`);
|
|
1342
|
-
}
|
|
1343
|
-
lines.push('```');
|
|
937
|
+
lines.push('## Conventions');
|
|
1344
938
|
lines.push('');
|
|
1345
|
-
lines.push('
|
|
939
|
+
lines.push('- All commands output JSON to stdout');
|
|
940
|
+
lines.push('- Use `--help` on any command for usage');
|
|
941
|
+
lines.push('- Exit 0 = success, 1 = error');
|
|
1346
942
|
lines.push('');
|
|
1347
|
-
lines.push('
|
|
1348
|
-
lines.push('- `0`: Success');
|
|
1349
|
-
lines.push('- `1`: Error (auth failure, not found, validation error, network error)');
|
|
943
|
+
lines.push('## Boundaries');
|
|
1350
944
|
lines.push('');
|
|
1351
|
-
lines.push('
|
|
1352
|
-
lines.push(`- "No active context": Run \`${builtinNames.context} use <name>\` first`);
|
|
1353
|
-
lines.push(`- "Not authenticated": Run \`${builtinNames.auth} set-token <token>\` first`);
|
|
1354
|
-
lines.push('- "Unknown target": The target name is not recognized');
|
|
1355
|
-
lines.push('- "Record not found": The requested ID does not exist');
|
|
945
|
+
lines.push('All files in this directory are generated. Do not edit manually.');
|
|
1356
946
|
lines.push('');
|
|
1357
947
|
return {
|
|
1358
948
|
fileName: 'AGENTS.md',
|
|
@@ -1478,7 +1068,7 @@ export function getMultiTargetCliMcpTools(input) {
|
|
|
1478
1068
|
const kebab = toKebabCase(singularName);
|
|
1479
1069
|
const pk = getPrimaryKeyInfo(table)[0];
|
|
1480
1070
|
const scalarFields = getScalarFields(table);
|
|
1481
|
-
const editableFields = getEditableFields(table);
|
|
1071
|
+
const editableFields = getEditableFields(table, registry);
|
|
1482
1072
|
const defaultFields = getFieldsWithDefaults(table, registry);
|
|
1483
1073
|
const requiredCreateFieldNames = editableFields
|
|
1484
1074
|
.filter((f) => !defaultFields.has(f.name))
|
|
@@ -1740,7 +1330,7 @@ export function generateMultiTargetSkills(input) {
|
|
|
1740
1330
|
const { singularName } = getTableNames(table);
|
|
1741
1331
|
const kebab = toKebabCase(singularName);
|
|
1742
1332
|
const pk = getPrimaryKeyInfo(table)[0];
|
|
1743
|
-
const editableFields = getEditableFields(table);
|
|
1333
|
+
const editableFields = getEditableFields(table, registry);
|
|
1744
1334
|
const defaultFields = getFieldsWithDefaults(table, registry);
|
|
1745
1335
|
const createFlags = [
|
|
1746
1336
|
...editableFields.filter((f) => !defaultFields.has(f.name)).map((f) => `--${f.name} <value>`),
|
|
@@ -1748,11 +1338,16 @@ export function generateMultiTargetSkills(input) {
|
|
|
1748
1338
|
].join(' ');
|
|
1749
1339
|
const cmd = `${tgt.name}:${kebab}`;
|
|
1750
1340
|
tgtReferenceNames.push(kebab);
|
|
1341
|
+
const mtSkillSpecialGroups = categorizeSpecialFields(table, registry);
|
|
1342
|
+
const mtSkillSpecialDesc = mtSkillSpecialGroups.length > 0
|
|
1343
|
+
? `CRUD operations for ${table.name} records via ${toolName} CLI (${tgt.name} target)\n\n` +
|
|
1344
|
+
mtSkillSpecialGroups.map((g) => `**${g.label}:** ${g.fields.map((f) => `\`${f.name}\``).join(', ')}\n${g.description}`).join('\n\n')
|
|
1345
|
+
: `CRUD operations for ${table.name} records via ${toolName} CLI (${tgt.name} target)`;
|
|
1751
1346
|
files.push({
|
|
1752
1347
|
fileName: `${tgtSkillName}/references/${kebab}.md`,
|
|
1753
1348
|
content: buildSkillReference({
|
|
1754
1349
|
title: singularName,
|
|
1755
|
-
description:
|
|
1350
|
+
description: mtSkillSpecialDesc,
|
|
1756
1351
|
usage: [
|
|
1757
1352
|
`${toolName} ${cmd} list`,
|
|
1758
1353
|
`${toolName} ${cmd} get --${pk.name} <value>`,
|