@cxtms/cx-schema 1.7.12 → 1.7.14

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.
@@ -12,6 +12,10 @@ Shared domain reference for CargoXplorer entities. Used by `cx-workflow` and `cx
12
12
 
13
13
  For CLI authentication, PAT tokens, org management, and publishing: see [ref-cli-auth.md](ref-cli-auth.md)
14
14
 
15
+ ## GraphQL Querying & Audit History
16
+
17
+ For running GraphQL queries via CLI, filter syntax (Lucene), pagination, audit change history, and field discovery: see [ref-graphql-query.md](ref-graphql-query.md)
18
+
15
19
  ## Feature File Layout
16
20
 
17
21
  All modules and workflows are organized under feature directories:
@@ -103,14 +103,11 @@ npx cxtms app install --branch develop
103
103
  # Install but skip modules that have unpublished local changes
104
104
  npx cxtms app install --skip-changed
105
105
 
106
- # Publish server changes to git (creates a PR)
107
- npx cxtms app publish
108
-
109
- # Publish with a custom commit message
110
- npx cxtms app publish --message "Add new shipping module"
106
+ # Publish server changes to git (creates a PR) — message is required
107
+ npx cxtms app publish -m "Add new shipping module"
111
108
 
112
109
  # Force publish all modules and workflows (not just changed ones)
113
- npx cxtms app publish --force
110
+ npx cxtms app publish -m "Full republish" --force
114
111
 
115
112
  # List installed app manifests on the server
116
113
  npx cxtms app list
@@ -118,6 +115,6 @@ npx cxtms app list
118
115
 
119
116
  **`app install`** reads `repository` and `branch` from `app.yaml`, downloads the repo on the server side, and installs/updates all modules and workflows. Use `--force` to reinstall even if the version hasn't changed. Use `--skip-changed` to preserve modules with unpublished changes.
120
117
 
121
- **`app publish`** takes the current server state and publishes it to git by creating a PR. The server increments the version, creates a publish branch, commits all module/workflow YAML files, and opens a pull request to the target branch.
118
+ **`app publish`** takes the current server state and publishes it to git by creating a PR. Requires a `-m` message describing the changes (like a git commit message). The server increments the version, creates a publish branch, commits all module/workflow YAML files, and opens a pull request to the target branch.
122
119
 
123
120
  **`app list`** shows all installed app manifests with their version, status flags (disabled, unpublished changes, update available), and repository info.
@@ -0,0 +1,320 @@
1
+ # GraphQL Query Reference
2
+
3
+ ## CLI Query Command
4
+
5
+ ```bash
6
+ npx cxtms query '<graphql-query>' # Inline query
7
+ npx cxtms query my-query.graphql # From file
8
+ npx cxtms query '<query>' --vars '{"key":"v"}' # With variables
9
+ ```
10
+
11
+ Requires active login (`npx cxtms login`). Uses the active organization.
12
+
13
+ ## Query Arguments
14
+
15
+ All root entity queries follow this pattern:
16
+
17
+ ```graphql
18
+ orders(
19
+ organizationId: Int! # Required — active org ID
20
+ filter: String # Lucene query syntax (see below)
21
+ search: String # Full-text search across key fields
22
+ orderBy: String # Comma-separated sort fields
23
+ take: Int # Page size (alias: limit)
24
+ skip: Int # Offset (alias: offset)
25
+ )
26
+ ```
27
+
28
+ Entity-specific extras:
29
+ - `orders` — `includeDraft: Boolean` (default false)
30
+
31
+ Available root queries: `orders`, `contacts`, `commodities`, `accountingTransactions`, `jobs`, and others.
32
+
33
+ ## Filter Syntax (Lucene Query)
34
+
35
+ Filters use **Lucene Query syntax** (not OData, not NCalc).
36
+
37
+ ### Basic field matching
38
+ ```
39
+ filter: "orderId:196596"
40
+ filter: "orderType:ParcelShipment"
41
+ filter: "status:Active"
42
+ ```
43
+
44
+ ### Wildcards
45
+ ```
46
+ filter: "name:*pending*" # Contains
47
+ filter: "code:ABC*" # Starts with
48
+ filter: "status:*Active" # Ends with
49
+ ```
50
+
51
+ ### Logical operators
52
+ ```
53
+ filter: "countryCode:US AND name:Test"
54
+ filter: "status:Active OR status:Pending"
55
+ filter: "NOT customValues.po:123"
56
+ filter: "orderType:ParcelShipment AND (status:Active OR status:Pending)"
57
+ ```
58
+
59
+ ### Nested paths (dot notation)
60
+ ```
61
+ filter: "orderEntities.entityType:Consignee"
62
+ filter: "orderEntities.contactId:7128"
63
+ filter: "jobs.orders.orderNumber:16*"
64
+ ```
65
+
66
+ ### Range queries
67
+ ```
68
+ filter: "lastModified:[\"2026-01-01\" TO \"2026-03-15\"]"
69
+ filter: "lastModified:[\"2026-01-01\" TO *]" # >= date
70
+ filter: "amount:[100 TO 500]"
71
+ ```
72
+
73
+ ### NULL checks
74
+ ```
75
+ filter: "customValues.po:NULL"
76
+ ```
77
+
78
+ ### CustomValues (JSONB fields)
79
+ ```
80
+ filter: "customValues.fieldName:value"
81
+ filter: "customValues.fieldName:\"exact match\""
82
+ filter: "customValues.fieldName:NULL"
83
+ ```
84
+
85
+ ### Filtered collections (bracket notation)
86
+ ```
87
+ filter: "children[category:CatA].name:test"
88
+ ```
89
+
90
+ ## OrderBy Syntax
91
+
92
+ ```
93
+ orderBy: "orderNumber" # Ascending
94
+ orderBy: "-orderNumber" # Descending
95
+ orderBy: "-created,orderNumber" # Multi-field
96
+ orderBy: "customValues.fieldName" # Custom field sort
97
+ orderBy: "orderNumber~ToInt32" # Type conversion during sort
98
+ ```
99
+
100
+ ## Pagination
101
+
102
+ ```graphql
103
+ {
104
+ orders(organizationId: 1, take: 50, skip: 0) {
105
+ items { orderId orderNumber }
106
+ totalCount
107
+ pageInfo { hasNextPage hasPreviousPage }
108
+ }
109
+ }
110
+ ```
111
+
112
+ ## Audit Change History
113
+
114
+ ### Entity-level: `changeHistory` computed field
115
+
116
+ Available on: **Order**, **Contact**, **Commodity**, **AccountingTransaction**
117
+
118
+ ```graphql
119
+ {
120
+ orders(organizationId: 1, filter: "orderId:196596", take: 1) {
121
+ items {
122
+ orderId
123
+ changeHistory(maxResults: 20) {
124
+ entityName
125
+ hasMoreRecords
126
+ continuationToken
127
+ changes {
128
+ state # "Added" | "Modified" | "Deleted"
129
+ userId
130
+ timestamp
131
+ user {
132
+ fullName
133
+ email
134
+ }
135
+ changedFields {
136
+ fieldName
137
+ originalValue
138
+ currentValue
139
+ fieldType
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ ```
147
+
148
+ Arguments: `startDate: DateTime`, `endDate: DateTime`, `maxResults: Int` (default 10)
149
+
150
+ ### Root-level: `auditChanges` query
151
+
152
+ Query audit changes directly without fetching the entity first:
153
+
154
+ ```graphql
155
+ {
156
+ auditChanges(
157
+ organizationId: 1
158
+ filter: "entityName:Order AND primaryKey:196596"
159
+ take: 20
160
+ ) {
161
+ items {
162
+ state
163
+ userId
164
+ timestamp
165
+ user { fullName email }
166
+ changedFields {
167
+ fieldName
168
+ originalValue
169
+ currentValue
170
+ fieldType
171
+ }
172
+ }
173
+ }
174
+ }
175
+ ```
176
+
177
+ Arguments: `organizationId: Int!`, `filter: String`, `search: String`, `take: Int`, `skip: Int`, `orderBy: String`
178
+
179
+ Filter fields: `entityName`, `primaryKey`, `userId`, `state`
180
+
181
+ ### Root-level: `auditChangeSummaries` query
182
+
183
+ High-level summary of changes (grouped by change event):
184
+
185
+ ```graphql
186
+ {
187
+ auditChangeSummaries(
188
+ organizationId: 1
189
+ startDate: "2026-03-01T00:00:00Z"
190
+ endDate: "2026-03-15T23:59:59Z"
191
+ maxResults: 50
192
+ ) {
193
+ items {
194
+ changeId
195
+ userId
196
+ timestamp
197
+ changePaths
198
+ organizationId
199
+ }
200
+ continuationToken
201
+ hasMoreRecords
202
+ }
203
+ }
204
+ ```
205
+
206
+ ### Root-level: `auditChange` — single change detail
207
+
208
+ ```graphql
209
+ {
210
+ auditChange(filePath: "<s3-file-path>") {
211
+ entityName
212
+ state
213
+ primaryKey
214
+ userId
215
+ timestamp
216
+ originalValues
217
+ currentValues
218
+ fields {
219
+ fieldName
220
+ originalValue
221
+ currentValue
222
+ fieldType
223
+ }
224
+ }
225
+ }
226
+ ```
227
+
228
+ ## GraphQL Type Reference
229
+
230
+ ### EntityAuditHistoryLightResult
231
+ | Field | Type |
232
+ |-------|------|
233
+ | `entityName` | `String` |
234
+ | `primaryKey` | `String` |
235
+ | `organizationId` | `Int` |
236
+ | `changes` | `[AuditChangeEntry!]!` |
237
+ | `continuationToken` | `String` |
238
+ | `hasMoreRecords` | `Boolean!` |
239
+
240
+ ### AuditChangeEntry
241
+ | Field | Type | Notes |
242
+ |-------|------|-------|
243
+ | `state` | `String` | "Added", "Modified", "Deleted" |
244
+ | `userId` | `String` | |
245
+ | `timestamp` | `DateTime!` | |
246
+ | `user` | `AuditUser` | Lazy-loaded resolver |
247
+ | `changedFields` | `[AuditChangeField!]!` | Lazy-loaded resolver |
248
+
249
+ ### AuditChangeField
250
+ | Field | Type |
251
+ |-------|------|
252
+ | `fieldName` | `String!` |
253
+ | `originalValue` | `Any` |
254
+ | `currentValue` | `Any` |
255
+ | `fieldType` | `String!` |
256
+
257
+ ### AuditUser
258
+ | Field | Type |
259
+ |-------|------|
260
+ | `id` | `String` |
261
+ | `firstName` | `String` |
262
+ | `lastName` | `String` |
263
+ | `fullName` | `String` |
264
+ | `userName` | `String` |
265
+ | `email` | `String` |
266
+
267
+ ## Discovering Fields
268
+
269
+ **Always discover field names before building a query.** Do not guess field names — use the tools below.
270
+
271
+ ### CLI: `cxtms gql` — schema exploration commands
272
+
273
+ ```bash
274
+ # List all query root fields (what you can query)
275
+ npx cxtms gql queries
276
+ npx cxtms gql queries --filter order
277
+
278
+ # List all mutation root fields
279
+ npx cxtms gql mutations
280
+ npx cxtms gql mutations --filter order
281
+
282
+ # List all types (filter by name)
283
+ npx cxtms gql types --filter audit
284
+ npx cxtms gql types --filter order
285
+
286
+ # Inspect a specific type — shows fields, arguments, input fields, enum values
287
+ npx cxtms gql type OrderGqlDto
288
+ npx cxtms gql type AuditChangeEntry
289
+ npx cxtms gql type EntityAuditHistoryLightResult
290
+ npx cxtms gql type CreateOrderInput
291
+ ```
292
+
293
+ ### TMS MCP: `graphql_schema` / `graphql_type` — equivalent MCP tools
294
+
295
+ When TMS MCP is available, the same discovery is available via MCP tools:
296
+
297
+ ```
298
+ graphql_schema(section: "queries", filter: "order")
299
+ graphql_schema(section: "types", filter: "audit")
300
+ graphql_type(typeName: "OrderGqlDto")
301
+ graphql_execute(query: "{ orders(organizationId: 1, take: 1) { items { orderId } } }")
302
+ ```
303
+
304
+ ### Workflow: discover → query
305
+
306
+ 1. **Find the type** — `cxtms gql types --filter order` to find type names
307
+ 2. **Inspect the type** — `cxtms gql type OrderGqlDto` to see all fields and their types
308
+ 3. **Check query args** — `cxtms gql queries --filter order` to see arguments
309
+ 4. **Build and run** — `cxtms query '<graphql-query>'`
310
+
311
+ ### Fallback: error-driven discovery
312
+
313
+ GraphQL error messages reveal valid field/type names:
314
+ - "The field `foo` does not exist on the type `BarType`" — tells you the type name
315
+ - "The argument `bar` does not exist" — tells you valid argument patterns
316
+
317
+ ### Other sources
318
+
319
+ - `npx cxtms schema <name>` — shows workflow/module JSON schema fields (not GraphQL)
320
+ - Entity reference files (`ref-entity-*.md`) — document common fields, computed properties, and enums
@@ -416,21 +416,18 @@ Deploy, undeploy, and publish commands are listed in the CLI section at the top
416
416
  Use `app publish` to push modified modules and workflows from the CX server to a GitHub repository. This creates a branch and pull request — it does NOT push directly to the target branch.
417
417
 
418
418
  ```bash
419
- # Publish all unpublished changes to GitHub (creates a PR)
420
- npx cxtms app publish
419
+ # Publish all unpublished changes to GitHub (creates a PR) — message is required
420
+ npx cxtms app publish -m "Add warehouse locations module"
421
421
 
422
422
  # Publish specific modules and/or workflows by YAML file
423
- npx cxtms app publish modules/my-module.yaml
424
- npx cxtms app publish modules/a.yaml workflows/b.yaml
425
-
426
- # Publish with a custom commit message
427
- npx cxtms app publish --message "Add warehouse locations module"
423
+ npx cxtms app publish -m "Fix country module" modules/my-module.yaml
424
+ npx cxtms app publish -m "Update billing" modules/a.yaml workflows/b.yaml
428
425
 
429
426
  # Force publish all modules and workflows (not just unpublished ones)
430
- npx cxtms app publish --force
427
+ npx cxtms app publish -m "Full republish" --force
431
428
 
432
429
  # Publish with explicit org
433
- npx cxtms app publish --org 42
430
+ npx cxtms app publish -m "Add warehouse locations module" --org 42
434
431
  ```
435
432
 
436
433
  **What `app publish` does:**
@@ -327,21 +327,18 @@ Deploy, undeploy, and publish commands are listed in the CLI section at the top
327
327
  Use `app publish` to push modified workflows and modules from the CX server to a GitHub repository. This creates a branch and pull request — it does NOT push directly to the target branch.
328
328
 
329
329
  ```bash
330
- # Publish all unpublished changes to GitHub (creates a PR)
331
- npx cxtms app publish
330
+ # Publish all unpublished changes to GitHub (creates a PR) — message is required
331
+ npx cxtms app publish -m "Add order notification workflow"
332
332
 
333
333
  # Publish specific workflows and/or modules by YAML file
334
- npx cxtms app publish workflows/my-workflow.yaml
335
- npx cxtms app publish workflows/a.yaml modules/b.yaml
336
-
337
- # Publish with a custom commit message
338
- npx cxtms app publish --message "Add order notification workflow"
334
+ npx cxtms app publish -m "Fix tracking workflow" workflows/my-workflow.yaml
335
+ npx cxtms app publish -m "Update shipping" workflows/a.yaml modules/b.yaml
339
336
 
340
337
  # Force publish all workflows and modules (not just unpublished ones)
341
- npx cxtms app publish --force
338
+ npx cxtms app publish -m "Full republish" --force
342
339
 
343
340
  # Publish with explicit org
344
- npx cxtms app publish --org 42
341
+ npx cxtms app publish -m "Add order notification workflow" --org 42
345
342
  ```
346
343
 
347
344
  **What `app publish` does:**
package/dist/cli.js CHANGED
@@ -119,6 +119,7 @@ ${chalk_1.default.bold.yellow('COMMANDS:')}
119
119
  ${chalk_1.default.green('publish')} Publish all modules and workflows to a CX server
120
120
  ${chalk_1.default.green('app')} Manage app manifests (install/upgrade from git, publish to git, list)
121
121
  ${chalk_1.default.green('query')} Run a GraphQL query against the CX server
122
+ ${chalk_1.default.green('gql')} Explore GraphQL schema (types, queries, mutations)
122
123
  ${chalk_1.default.green('schema')} Show JSON schema for a component or task
123
124
  ${chalk_1.default.green('example')} Show example YAML for a component or task
124
125
  ${chalk_1.default.green('list')} List available schemas (modules, workflows, tasks)
@@ -148,7 +149,7 @@ ${chalk_1.default.bold.yellow('OPTIONS:')}
148
149
  ${chalk_1.default.green('--output <file>')} Save workflow log to file (or -o)
149
150
  ${chalk_1.default.green('--console')} Print workflow log to stdout
150
151
  ${chalk_1.default.green('--json')} Download JSON log instead of text
151
- ${chalk_1.default.green('-m, --message <msg>')} Commit message for app publish
152
+ ${chalk_1.default.green('-m, --message <msg>')} Commit message for app publish (required)
152
153
  ${chalk_1.default.green('-b, --branch <branch>')} Branch override for app install/publish
153
154
  ${chalk_1.default.green('--force')} Force install (even if same version) or publish all
154
155
  ${chalk_1.default.green('--skip-changed')} Skip modules with unpublished changes during install
@@ -310,18 +311,15 @@ ${chalk_1.default.bold.yellow('APP COMMANDS:')}
310
311
  ${chalk_1.default.cyan(`${PROGRAM_NAME} app upgrade`)}
311
312
  ${chalk_1.default.cyan(`${PROGRAM_NAME} app upgrade --force`)}
312
313
 
313
- ${chalk_1.default.gray('# Publish server changes to git (creates a PR)')}
314
- ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish`)}
315
-
316
- ${chalk_1.default.gray('# Publish with a custom commit message')}
317
- ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish --message "Add new shipping module"`)}
314
+ ${chalk_1.default.gray('# Publish server changes to git (creates a PR) — message is required')}
315
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish -m "Add new shipping module"`)}
318
316
 
319
317
  ${chalk_1.default.gray('# Publish specific workflows and/or modules by YAML file')}
320
- ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish workflows/my-workflow.yaml`)}
321
- ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish workflows/a.yaml modules/b.yaml`)}
318
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish -m "Fix order workflow" workflows/my-workflow.yaml`)}
319
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish -m "Update billing" workflows/a.yaml modules/b.yaml`)}
322
320
 
323
321
  ${chalk_1.default.gray('# Force publish all modules and workflows')}
324
- ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish --force`)}
322
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} app publish -m "Full republish" --force`)}
325
323
 
326
324
  ${chalk_1.default.gray('# List installed app manifests on the server')}
327
325
  ${chalk_1.default.cyan(`${PROGRAM_NAME} app list`)}
@@ -336,6 +334,20 @@ ${chalk_1.default.bold.yellow('QUERY COMMANDS:')}
336
334
  ${chalk_1.default.gray('# Pass variables as JSON')}
337
335
  ${chalk_1.default.cyan(`${PROGRAM_NAME} query my-query.graphql --vars '{"id": 42}'`)}
338
336
 
337
+ ${chalk_1.default.bold.yellow('GRAPHQL SCHEMA EXPLORATION:')}
338
+ ${chalk_1.default.gray('# List all queries, mutations, and types')}
339
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} gql queries`)}
340
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} gql mutations`)}
341
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} gql types`)}
342
+
343
+ ${chalk_1.default.gray('# Filter by name')}
344
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} gql types --filter audit`)}
345
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} gql queries --filter order`)}
346
+
347
+ ${chalk_1.default.gray('# Inspect a specific type')}
348
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} gql type OrderGqlDto`)}
349
+ ${chalk_1.default.cyan(`${PROGRAM_NAME} gql type AuditChangeEntry`)}
350
+
339
351
  ${chalk_1.default.bold.yellow('VALIDATION TYPES:')}
340
352
  ${chalk_1.default.bold('module')} - CargoXplorer UI module definitions (components, routes, entities)
341
353
  ${chalk_1.default.bold('workflow')} - CargoXplorer workflow definitions (activities, tasks, triggers)
@@ -3017,6 +3029,12 @@ async function runAppPublish(orgOverride, message, branch, force, targetFiles) {
3017
3029
  const domain = session.domain;
3018
3030
  const token = session.access_token;
3019
3031
  const orgId = await resolveOrgId(domain, token, orgOverride);
3032
+ if (!message) {
3033
+ console.error(chalk_1.default.red('Error: --message (-m) is required for app publish'));
3034
+ console.error(chalk_1.default.gray('Describe what changed, similar to a git commit message.'));
3035
+ console.error(chalk_1.default.gray(`Example: ${PROGRAM_NAME} app publish -m "Add new shipping module"`));
3036
+ process.exit(2);
3037
+ }
3020
3038
  const appYaml = readAppYaml();
3021
3039
  const appManifestId = appYaml.id;
3022
3040
  if (!appManifestId) {
@@ -3188,6 +3206,169 @@ async function runQuery(queryArg, variables) {
3188
3206
  console.log(JSON.stringify(data, null, 2));
3189
3207
  }
3190
3208
  // ============================================================================
3209
+ // GQL Schema Exploration Command
3210
+ // ============================================================================
3211
+ async function runGql(sub, filter) {
3212
+ if (!sub) {
3213
+ console.error(chalk_1.default.red('Error: subcommand required'));
3214
+ console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} gql <queries|mutations|types|type> [name] [--filter <text>]`));
3215
+ process.exit(2);
3216
+ }
3217
+ const session = resolveSession();
3218
+ if (sub === 'type') {
3219
+ if (!filter) {
3220
+ console.error(chalk_1.default.red('Error: type name required'));
3221
+ console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} gql type <TypeName>`));
3222
+ process.exit(2);
3223
+ }
3224
+ await runGqlType(session, filter);
3225
+ }
3226
+ else if (sub === 'queries') {
3227
+ await runGqlRootFields(session, 'queryType', filter);
3228
+ }
3229
+ else if (sub === 'mutations') {
3230
+ await runGqlRootFields(session, 'mutationType', filter);
3231
+ }
3232
+ else if (sub === 'types') {
3233
+ await runGqlTypes(session, filter);
3234
+ }
3235
+ else {
3236
+ console.error(chalk_1.default.red(`Unknown gql subcommand: ${sub}`));
3237
+ console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} gql <queries|mutations|types|type> [--filter <text>]`));
3238
+ process.exit(2);
3239
+ }
3240
+ }
3241
+ function formatGqlType(t) {
3242
+ if (!t)
3243
+ return 'unknown';
3244
+ if (t.kind === 'NON_NULL')
3245
+ return `${formatGqlType(t.ofType)}!`;
3246
+ if (t.kind === 'LIST')
3247
+ return `[${formatGqlType(t.ofType)}]`;
3248
+ return t.name || 'unknown';
3249
+ }
3250
+ async function runGqlType(session, typeName) {
3251
+ const query = `{
3252
+ __type(name: "${typeName}") {
3253
+ name kind description
3254
+ fields { name description type { name kind ofType { name kind ofType { name kind ofType { name kind } } } } args { name type { name kind ofType { name kind ofType { name kind } } } defaultValue } }
3255
+ inputFields { name type { name kind ofType { name kind ofType { name kind } } } defaultValue }
3256
+ enumValues { name description }
3257
+ }
3258
+ }`;
3259
+ const data = await graphqlRequest(session.domain, session.access_token, query, {});
3260
+ const type = data.__type;
3261
+ if (!type) {
3262
+ console.error(chalk_1.default.red(`Type "${typeName}" not found`));
3263
+ process.exit(1);
3264
+ }
3265
+ console.log(chalk_1.default.bold.cyan(`${type.name}`) + chalk_1.default.gray(` (${type.kind})`));
3266
+ if (type.description)
3267
+ console.log(chalk_1.default.gray(type.description));
3268
+ console.log('');
3269
+ if (type.fields && type.fields.length > 0) {
3270
+ console.log(chalk_1.default.bold.yellow('Fields:'));
3271
+ for (const f of type.fields) {
3272
+ const typeStr = formatGqlType(f.type);
3273
+ let line = ` ${chalk_1.default.green(f.name)}: ${chalk_1.default.cyan(typeStr)}`;
3274
+ if (f.args && f.args.length > 0) {
3275
+ const argsStr = f.args.map((a) => {
3276
+ const argType = formatGqlType(a.type);
3277
+ return a.defaultValue ? `${a.name}: ${argType} = ${a.defaultValue}` : `${a.name}: ${argType}`;
3278
+ }).join(', ');
3279
+ line += chalk_1.default.gray(` (${argsStr})`);
3280
+ }
3281
+ if (f.description)
3282
+ line += chalk_1.default.gray(` — ${f.description}`);
3283
+ console.log(line);
3284
+ }
3285
+ }
3286
+ if (type.inputFields && type.inputFields.length > 0) {
3287
+ console.log(chalk_1.default.bold.yellow('Input Fields:'));
3288
+ for (const f of type.inputFields) {
3289
+ const typeStr = formatGqlType(f.type);
3290
+ let line = ` ${chalk_1.default.green(f.name)}: ${chalk_1.default.cyan(typeStr)}`;
3291
+ if (f.defaultValue)
3292
+ line += chalk_1.default.gray(` = ${f.defaultValue}`);
3293
+ console.log(line);
3294
+ }
3295
+ }
3296
+ if (type.enumValues && type.enumValues.length > 0) {
3297
+ console.log(chalk_1.default.bold.yellow('Enum Values:'));
3298
+ for (const v of type.enumValues) {
3299
+ let line = ` ${chalk_1.default.green(v.name)}`;
3300
+ if (v.description)
3301
+ line += chalk_1.default.gray(` — ${v.description}`);
3302
+ console.log(line);
3303
+ }
3304
+ }
3305
+ }
3306
+ async function runGqlRootFields(session, rootType, filter) {
3307
+ const query = `{
3308
+ __schema {
3309
+ ${rootType} {
3310
+ fields { name description args { name type { name kind ofType { name kind ofType { name kind } } } defaultValue } type { name kind ofType { name kind ofType { name kind } } } }
3311
+ }
3312
+ }
3313
+ }`;
3314
+ const data = await graphqlRequest(session.domain, session.access_token, query, {});
3315
+ const fields = data.__schema?.[rootType]?.fields || [];
3316
+ const filtered = filter
3317
+ ? fields.filter((f) => f.name.toLowerCase().includes(filter.toLowerCase()))
3318
+ : fields;
3319
+ const label = rootType === 'queryType' ? 'Queries' : 'Mutations';
3320
+ console.log(chalk_1.default.bold.yellow(`${label}${filter ? ` (filter: "${filter}")` : ''}:`));
3321
+ console.log('');
3322
+ for (const f of filtered) {
3323
+ const returnType = formatGqlType(f.type);
3324
+ console.log(` ${chalk_1.default.green(f.name)}: ${chalk_1.default.cyan(returnType)}`);
3325
+ if (f.description)
3326
+ console.log(` ${chalk_1.default.gray(f.description)}`);
3327
+ if (f.args && f.args.length > 0) {
3328
+ for (const a of f.args) {
3329
+ const argType = formatGqlType(a.type);
3330
+ const def = a.defaultValue ? chalk_1.default.gray(` = ${a.defaultValue}`) : '';
3331
+ console.log(` ${chalk_1.default.white(a.name)}: ${chalk_1.default.cyan(argType)}${def}`);
3332
+ }
3333
+ }
3334
+ console.log('');
3335
+ }
3336
+ console.log(chalk_1.default.gray(`${filtered.length} ${label.toLowerCase()} found`));
3337
+ }
3338
+ async function runGqlTypes(session, filter) {
3339
+ const query = `{
3340
+ __schema {
3341
+ types { name kind description }
3342
+ }
3343
+ }`;
3344
+ const data = await graphqlRequest(session.domain, session.access_token, query, {});
3345
+ const types = (data.__schema?.types || [])
3346
+ .filter((t) => !t.name.startsWith('__'))
3347
+ .filter((t) => !filter || t.name.toLowerCase().includes(filter.toLowerCase()));
3348
+ const grouped = {};
3349
+ for (const t of types) {
3350
+ const kind = t.kind || 'OTHER';
3351
+ if (!grouped[kind])
3352
+ grouped[kind] = [];
3353
+ grouped[kind].push(t);
3354
+ }
3355
+ const kindOrder = ['OBJECT', 'INPUT_OBJECT', 'ENUM', 'INTERFACE', 'UNION', 'SCALAR'];
3356
+ for (const kind of kindOrder) {
3357
+ const items = grouped[kind];
3358
+ if (!items || items.length === 0)
3359
+ continue;
3360
+ console.log(chalk_1.default.bold.yellow(`${kind} (${items.length}):`));
3361
+ for (const t of items.sort((a, b) => a.name.localeCompare(b.name))) {
3362
+ let line = ` ${chalk_1.default.green(t.name)}`;
3363
+ if (t.description)
3364
+ line += chalk_1.default.gray(` — ${t.description}`);
3365
+ console.log(line);
3366
+ }
3367
+ console.log('');
3368
+ }
3369
+ console.log(chalk_1.default.gray(`${types.length} types found${filter ? ` matching "${filter}"` : ''}`));
3370
+ }
3371
+ // ============================================================================
3191
3372
  // Extract Command
3192
3373
  // ============================================================================
3193
3374
  function runExtract(sourceFile, componentName, targetFile, copy) {
@@ -3398,7 +3579,7 @@ function parseArgs(args) {
3398
3579
  reportFormat: 'json'
3399
3580
  };
3400
3581
  // Check for commands
3401
- const commands = ['validate', 'schema', 'example', 'list', 'help', 'version', 'report', 'init', 'create', 'extract', 'sync-schemas', 'install-skills', 'update', 'setup-claude', 'login', 'logout', 'pat', 'appmodule', 'orgs', 'workflow', 'publish', 'query', 'app'];
3582
+ const commands = ['validate', 'schema', 'example', 'list', 'help', 'version', 'report', 'init', 'create', 'extract', 'sync-schemas', 'install-skills', 'update', 'setup-claude', 'login', 'logout', 'pat', 'appmodule', 'orgs', 'workflow', 'publish', 'query', 'gql', 'app'];
3402
3583
  if (args.length > 0 && commands.includes(args[0])) {
3403
3584
  command = args[0];
3404
3585
  args = args.slice(1);
@@ -3509,6 +3690,9 @@ function parseArgs(args) {
3509
3690
  options.file = [];
3510
3691
  options.file.push(args[++i]);
3511
3692
  }
3693
+ else if (arg === '--filter') {
3694
+ options.filter = args[++i];
3695
+ }
3512
3696
  else if (arg === '--force') {
3513
3697
  options.force = true;
3514
3698
  }
@@ -4435,6 +4619,14 @@ async function main() {
4435
4619
  }
4436
4620
  process.exit(0);
4437
4621
  }
4622
+ // Handle gql command (no schemas needed)
4623
+ if (command === 'gql') {
4624
+ const sub = files[0];
4625
+ // For 'gql type <name>', the type name is in files[1] — use it as filter
4626
+ const filterArg = sub === 'type' ? (files[1] || options.filter) : options.filter;
4627
+ await runGql(sub, filterArg);
4628
+ process.exit(0);
4629
+ }
4438
4630
  // Handle query command (no schemas needed)
4439
4631
  if (command === 'query') {
4440
4632
  await runQuery(files[0], options.vars);