@axinom/mosaic-agent-skills 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +121 -0
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +58 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/logger.d.ts +6 -0
  7. package/dist/logger.d.ts.map +1 -0
  8. package/dist/logger.js +15 -0
  9. package/dist/logger.js.map +1 -0
  10. package/dist/tools/graphql/index.d.ts +3 -0
  11. package/dist/tools/graphql/index.d.ts.map +1 -0
  12. package/dist/tools/graphql/index.js +84 -0
  13. package/dist/tools/graphql/index.js.map +1 -0
  14. package/dist/tools/graphql/tools.d.ts +71 -0
  15. package/dist/tools/graphql/tools.d.ts.map +1 -0
  16. package/dist/tools/graphql/tools.js +187 -0
  17. package/dist/tools/graphql/tools.js.map +1 -0
  18. package/dist/tools/graphql/utils.d.ts +20 -0
  19. package/dist/tools/graphql/utils.d.ts.map +1 -0
  20. package/dist/tools/graphql/utils.js +140 -0
  21. package/dist/tools/graphql/utils.js.map +1 -0
  22. package/dist/tools/skills/index.d.ts +3 -0
  23. package/dist/tools/skills/index.d.ts.map +1 -0
  24. package/dist/tools/skills/index.js +62 -0
  25. package/dist/tools/skills/index.js.map +1 -0
  26. package/dist/tools/skills/tools.d.ts +5 -0
  27. package/dist/tools/skills/tools.d.ts.map +1 -0
  28. package/dist/tools/skills/tools.js +67 -0
  29. package/dist/tools/skills/tools.js.map +1 -0
  30. package/dist/tools/skills/types.d.ts +12 -0
  31. package/dist/tools/skills/types.d.ts.map +1 -0
  32. package/dist/tools/skills/types.js +3 -0
  33. package/dist/tools/skills/types.js.map +1 -0
  34. package/dist/tools/skills/utils.d.ts +11 -0
  35. package/dist/tools/skills/utils.d.ts.map +1 -0
  36. package/dist/tools/skills/utils.js +127 -0
  37. package/dist/tools/skills/utils.js.map +1 -0
  38. package/package.json +40 -0
  39. package/skills/_shared/actions/execution-summary.md +53 -0
  40. package/skills/_shared/actions/typescript-codegen.md +32 -0
  41. package/skills/_shared/actions/typescript-validation.md +32 -0
  42. package/skills/_shared/conventions/field-component-mapping.md +155 -0
  43. package/skills/_shared/conventions/field-validation-schema.md +77 -0
  44. package/skills/_shared/discovery/discover-paths.md +52 -0
  45. package/skills/_shared/discovery/extract-entity-features.md +565 -0
  46. package/skills/generate-create-station/SKILL.md +93 -0
  47. package/skills/generate-create-station/refs/finalization/station-registration.md +163 -0
  48. package/skills/generate-create-station/refs/templates/create-form-station.md +206 -0
  49. package/skills/generate-create-station/refs/templates/graphql-operations.md +56 -0
  50. package/skills/generate-details-station/SKILL.md +125 -0
  51. package/skills/generate-details-station/refs/finalization/explorer-integration.md +242 -0
  52. package/skills/generate-details-station/refs/finalization/station-registration.md +139 -0
  53. package/skills/generate-details-station/refs/templates/actions.md +127 -0
  54. package/skills/generate-details-station/refs/templates/breadcrumb.md +67 -0
  55. package/skills/generate-details-station/refs/templates/details-form-station.md +589 -0
  56. package/skills/generate-details-station/refs/templates/details-wrapper.md +54 -0
  57. package/skills/generate-details-station/refs/templates/form-data-types.md +62 -0
  58. package/skills/generate-details-station/refs/templates/graphql-operations.md +256 -0
  59. package/skills/generate-details-station/refs/templates/managed-integrations/image-management-station.md +322 -0
  60. package/skills/generate-details-station/refs/templates/managed-integrations/video-management-station.md +283 -0
  61. package/skills/generate-explorer-station/SKILL.md +121 -0
  62. package/skills/generate-explorer-station/refs/finalization/station-registration.md +145 -0
  63. package/skills/generate-explorer-station/refs/select-columns-filters.md +63 -0
  64. package/skills/generate-explorer-station/refs/templates/bulk-edit.md +369 -0
  65. package/skills/generate-explorer-station/refs/templates/common/bulk-actions.md +64 -0
  66. package/skills/generate-explorer-station/refs/templates/common/columns.md +131 -0
  67. package/skills/generate-explorer-station/refs/templates/common/data-provider.md +83 -0
  68. package/skills/generate-explorer-station/refs/templates/common/filters.md +220 -0
  69. package/skills/generate-explorer-station/refs/templates/common/inline-actions.md +45 -0
  70. package/skills/generate-explorer-station/refs/templates/common/types.md +28 -0
  71. package/skills/generate-explorer-station/refs/templates/graphql-operations.md +235 -0
  72. package/skills/generate-explorer-station/refs/templates/navigation-explorer-station.md +144 -0
@@ -0,0 +1,256 @@
1
+ ---
2
+ name: graphql-operations
3
+ input:
4
+ - EntityFeatures
5
+ output:
6
+ - path: '{workflowsPath}/src/Stations/{EntityPlural}/{Entity}Details/{Entity}Details.graphql'
7
+ description:
8
+ 'GraphQL query, mutations, and search queries for details station'
9
+ ---
10
+
11
+ # Generate GraphQL File
12
+
13
+ Single file with entity query, update/delete mutations, search queries for
14
+ autocomplete.
15
+
16
+ ## Template
17
+
18
+ ```graphql
19
+ # Main query - fetch complete entity data
20
+ query {features.name}($id: {features.idType}!) {
21
+ {features.camelCase}(id: $id) {
22
+ # ID field
23
+ id
24
+
25
+ # Scalar fields from features.fields.scalars[]
26
+ # For each scalar in features.fields.scalars:
27
+ {scalar.name}
28
+
29
+ # TagLike associations from features.fields.associations.tagLike[]
30
+ # For each tagLike in features.fields.associations.tagLike:
31
+ {tagLike.connectionField} {
32
+ nodes {
33
+ {tagLike.displayField}
34
+ }
35
+ }
36
+
37
+ # ManyToMany associations from features.fields.associations.manyToMany[]
38
+ # For each manyToMany in features.fields.associations.manyToMany:
39
+ {manyToMany.connectionField} {
40
+ nodes {
41
+ {manyToMany.relatedField} {
42
+ {manyToMany.displayField}
43
+ }
44
+ }
45
+ }
46
+
47
+ # If features.capabilities.images exists:
48
+ {features.capabilities.images.connectionField} {
49
+ nodes {
50
+ imageType
51
+ imageId
52
+ }
53
+ }
54
+
55
+ # If features.capabilities.videos.mainVideoField exists:
56
+ {features.capabilities.videos.mainVideoField}
57
+
58
+ # If features.capabilities.videos.trailersConnection exists:
59
+ {features.capabilities.videos.trailersConnection} {
60
+ totalCount
61
+ }
62
+
63
+ # Audit fields
64
+ createdDate
65
+ createdUser
66
+ updatedDate
67
+ updatedUser
68
+ }
69
+
70
+ # If manyToMany associations exist, fetch global lookup data:
71
+ # For each manyToMany in features.fields.associations.manyToMany:
72
+ {manyToMany.relatedQueryField} {
73
+ nodes {
74
+ {manyToMany.displayField}
75
+ id
76
+ }
77
+ }
78
+ }
79
+
80
+ # Update mutation
81
+ mutation Update{features.name}($input: Update{features.name}Input!) {
82
+ update{features.name}(input: $input) {
83
+ clientMutationId
84
+ {features.camelCase} {
85
+ id
86
+ {features.titleField}
87
+ }
88
+ }
89
+ }
90
+
91
+ # Delete mutation (if features.mutations.delete === true)
92
+ mutation Delete{features.name}($input: Delete{features.name}Input!) {
93
+ delete{features.name}(input: $input) {
94
+ clientMutationId
95
+ }
96
+ }
97
+
98
+ # Breadcrumb query - minimal fields for breadcrumb display
99
+ query {features.name}Title($id: {features.idType}!) {
100
+ {features.camelCase}(id: $id) {
101
+ id
102
+ {features.titleField}
103
+ }
104
+ }
105
+
106
+ # Search queries for autocomplete (TagLike associations only)
107
+ # For each tagLike in features.fields.associations.tagLike:
108
+ # Use pattern: get{PascalConnectionField}Values helper query
109
+ query Search{features.name}{tagLike.label}($searchKey: String!, $limit: Int!) {
110
+ get{PascalCase(tagLike.connectionField)}Values(
111
+ filter: { startsWithInsensitive: $searchKey }
112
+ first: $limit
113
+ ) {
114
+ nodes
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Field Selection Rules
120
+
121
+ Include in main query:
122
+
123
+ - All `features.fields.scalars[]` fields (user-editable scalars)
124
+ - All `features.fields.associations.tagLike[]` connections
125
+ - All `features.fields.associations.manyToMany[]` connections
126
+ - Image/video capability fields if present
127
+ - Audit fields (`createdDate`, `createdUser`, `updatedDate`, `updatedUser`)
128
+ - Always: `id`, `titleField`, `nameField` (unique identifier fields)
129
+
130
+ **Note:** `features.fields.scalars` already excludes mutation-controlled fields
131
+ (marked with `scalar.isMutationControlled === true` during extraction)
132
+
133
+ ## Example
134
+
135
+ ```graphql
136
+ query Movie($id: Int!) {
137
+ movie(id: $id) {
138
+ id
139
+ title
140
+ originalTitle
141
+ synopsis
142
+ description
143
+ externalId
144
+ released
145
+ studio
146
+
147
+ moviesTags {
148
+ nodes {
149
+ name
150
+ }
151
+ }
152
+
153
+ moviesMovieGenres {
154
+ nodes {
155
+ movieGenres {
156
+ title
157
+ }
158
+ }
159
+ }
160
+
161
+ moviesCasts {
162
+ nodes {
163
+ name
164
+ }
165
+ }
166
+
167
+ moviesProductionCountries {
168
+ nodes {
169
+ name
170
+ }
171
+ }
172
+
173
+ moviesImages {
174
+ nodes {
175
+ imageType
176
+ imageId
177
+ }
178
+ }
179
+
180
+ mainVideoId
181
+
182
+ moviesTrailers {
183
+ totalCount
184
+ }
185
+
186
+ createdDate
187
+ createdUser
188
+ updatedDate
189
+ updatedUser
190
+ }
191
+
192
+ movieGenres {
193
+ nodes {
194
+ title
195
+ id
196
+ }
197
+ }
198
+ }
199
+
200
+ mutation UpdateMovie($input: UpdateMovieInput!) {
201
+ updateMovie(input: $input) {
202
+ clientMutationId
203
+ movie {
204
+ id
205
+ title
206
+ }
207
+ }
208
+ }
209
+
210
+ mutation DeleteMovie($input: DeleteMovieInput!) {
211
+ deleteMovie(input: $input) {
212
+ clientMutationId
213
+ }
214
+ }
215
+
216
+ query MovieTitle($id: Int!) {
217
+ movie(id: $id) {
218
+ id
219
+ title
220
+ }
221
+ }
222
+
223
+ query SearchMovieTags($searchKey: String!, $limit: Int!) {
224
+ getMoviesTagsValues(
225
+ filter: { startsWithInsensitive: $searchKey }
226
+ first: $limit
227
+ ) {
228
+ nodes
229
+ }
230
+ }
231
+
232
+ query SearchMovieCasts($searchKey: String!, $limit: Int!) {
233
+ getMoviesCastsValues(
234
+ filter: { startsWithInsensitive: $searchKey }
235
+ first: $limit
236
+ ) {
237
+ nodes
238
+ }
239
+ }
240
+
241
+ query SearchMovieProductionCountries($searchKey: String!, $limit: Int!) {
242
+ getMoviesProductionCountriesValues(
243
+ filter: { startsWithInsensitive: $searchKey }
244
+ first: $limit
245
+ ) {
246
+ nodes
247
+ }
248
+ }
249
+ ```
250
+
251
+ ## Notes
252
+
253
+ - VS Code may briefly show "Syntax Error: Unexpected <EOF>" when creating the
254
+ GraphQL file. This is a false positive caused by a race condition between file
255
+ creation and the `Apollo` extension's validation. It auto-resolves within
256
+ seconds and is safe to ignore and proceed.
@@ -0,0 +1,322 @@
1
+ ---
2
+ name: image-management-station
3
+ input:
4
+ - EntityFeatures (with capabilities.images)
5
+ - DiscoveredPaths
6
+ output:
7
+ - path: '{workflowsPath}/src/Stations/{EntityPlural}/{Entity}ImageManagement/'
8
+ description: 'Complete image management station (4 files)'
9
+ ---
10
+
11
+ # Generate Image Management Station
12
+
13
+ Creates station for managing entity images (COVER, TEASER, etc.).
14
+
15
+ `Conditional` - only generate if `EntityFeatures.capabilities.images` exists.
16
+
17
+ ## Prerequisites
18
+
19
+ **Verify capability exists:**
20
+
21
+ ```typescript
22
+ if (!EntityFeatures.capabilities.images) {
23
+ skip this step
24
+ }
25
+ ```
26
+
27
+ ## Files to Generate
28
+
29
+ 1. `{Entity}ImageManagement.tsx` - Router wrapper
30
+ 2. `{Entity}ImageManagementForm.tsx` - Form component
31
+ 3. `{Entity}ImageManagement.graphql` - Query
32
+ 4. `{Entity}ImageManagementQuickEdit.tsx` - Quick edit wrapper
33
+
34
+ ## File 1: Router Wrapper
35
+
36
+ **Path:** `{Entity}ImageManagement/{Entity}ImageManagement.tsx`
37
+
38
+ ```typescript
39
+ import React from 'react';
40
+ import { useParams } from 'react-router-dom';
41
+ import { {Entity}ImageManagementForm } from './{Entity}ImageManagementForm';
42
+
43
+ interface UrlParams {
44
+ {entityCamel}Id: string;
45
+ }
46
+
47
+ export const {Entity}ImageManagement: React.FC = () => {
48
+ const { {entityCamel}Id } = useParams<UrlParams>();
49
+
50
+ return <{Entity}ImageManagementForm {entityCamel}Id={Number({entityCamel}Id)} />;
51
+ };
52
+ ```
53
+
54
+ ## File 2: GraphQL Query
55
+
56
+ **Path:** `{Entity}ImageManagement/{Entity}ImageManagement.graphql`
57
+
58
+ ```graphql
59
+ query {Entity}Images($id: {features.idType}!) {
60
+ {features.camelCase}(id: $id) {
61
+ {features.capabilities.images.connectionField} {
62
+ nodes {
63
+ imageId
64
+ imageType
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## File 3: Form Component
72
+
73
+ **Path:** `{Entity}ImageManagement/{Entity}ImageManagementForm.tsx`
74
+
75
+ ```typescript
76
+ import {
77
+ createUpdateGQLFragmentGenerator,
78
+ Details,
79
+ DetailsProps,
80
+ } from '@axinom/mosaic-ui';
81
+ import { Field } from 'formik';
82
+ import gql from 'graphql-tag';
83
+ import React, { useCallback, useContext, useEffect, useState } from 'react';
84
+ import { client } from '../../../apolloClient';
85
+ import { ExtensionsContext } from '{discoveredPaths.extensionsContextPath}';
86
+ import {
87
+ {features.capabilities.images.imageTypeEnum},
88
+ {features.capabilities.images.nodeType},
89
+ Mutation,
90
+ MutationCreate{features.capabilities.images.nodeType}Args,
91
+ MutationDelete{features.capabilities.images.nodeType}By{Entity}IdAndImageTypeArgs,
92
+ MutationUpdate{features.capabilities.images.nodeType}By{Entity}IdAndImageTypeArgs,
93
+ use{Entity}ImagesQuery,
94
+ } from '../../../generated/graphql';
95
+
96
+ interface {Entity}ImageManagementFormProps {
97
+ {entityCamel}Id: number;
98
+ }
99
+
100
+ type ImageNodes = Pick<{features.capabilities.images.nodeType}, 'imageId' | 'imageType'> & {
101
+ __typename: '{features.capabilities.images.nodeType}';
102
+ };
103
+
104
+ type FormData = Record<{features.capabilities.images.imageTypeEnum}, string[]>;
105
+
106
+ const Form: React.FC<{ imageSelectField: unknown }> = ({
107
+ imageSelectField,
108
+ }) => {
109
+ return (
110
+ <>
111
+ {Object.keys({features.capabilities.images.imageTypeEnum}).map((type) => {
112
+ const field = {features.capabilities.images.imageTypeEnum}[type];
113
+ return (
114
+ <Field
115
+ key={field}
116
+ name={field}
117
+ label={type}
118
+ as={imageSelectField}
119
+ maxItems={1}
120
+ title="Select Image"
121
+ imageScope="{features.capabilities.images.scope}"
122
+ />
123
+ );
124
+ })}
125
+ </>
126
+ );
127
+ };
128
+
129
+ export const {Entity}ImageManagementForm: React.FC<
130
+ {Entity}ImageManagementFormProps
131
+ > = ({ {entityCamel}Id }) => {
132
+ const { loading, data, error } = use{Entity}ImagesQuery({
133
+ client,
134
+ variables: { id: {entityCamel}Id },
135
+ fetchPolicy: 'network-only',
136
+ });
137
+
138
+ const { initialImages } = useImageTypes(
139
+ data?.{entityCamel}?.{features.capabilities.images.connectionField}.nodes as ImageNodes[],
140
+ );
141
+
142
+ const onSubmit = useCallback(
143
+ async (
144
+ formData: FormData,
145
+ initialData: DetailsProps<FormData>['initialData'],
146
+ ): Promise<void> => {
147
+ const generateUpdateGQLFragment =
148
+ createUpdateGQLFragmentGenerator<Mutation>();
149
+
150
+ const mutations: string[] = [];
151
+
152
+ const generateCreateMutation = (
153
+ imageId: string,
154
+ imageType: {features.capabilities.images.imageTypeEnum},
155
+ ): string =>
156
+ generateUpdateGQLFragment<MutationCreate{features.capabilities.images.nodeType}Args>(
157
+ 'create{features.capabilities.images.nodeType}',
158
+ {
159
+ input: {
160
+ {camelNodeType}: {
161
+ {entityCamel}Id,
162
+ imageId,
163
+ imageType: { type: 'enum', value: imageType },
164
+ },
165
+ },
166
+ },
167
+ );
168
+
169
+ const generateDeleteMutation = (imageType: {features.capabilities.images.imageTypeEnum}): string =>
170
+ generateUpdateGQLFragment<MutationDelete{features.capabilities.images.nodeType}By{Entity}IdAndImageTypeArgs>(
171
+ 'delete{features.capabilities.images.nodeType}By{Entity}IdAndImageType',
172
+ {
173
+ input: { {entityCamel}Id, imageType: { type: 'enum', value: imageType } },
174
+ },
175
+ );
176
+
177
+ const generateUpdateMutation = (
178
+ imageId: string,
179
+ imageType: {features.capabilities.images.imageTypeEnum},
180
+ ): string =>
181
+ generateUpdateGQLFragment<MutationUpdate{features.capabilities.images.nodeType}By{Entity}IdAndImageTypeArgs>(
182
+ 'update{features.capabilities.images.nodeType}By{Entity}IdAndImageType',
183
+ {
184
+ input: {
185
+ patch: { imageId },
186
+ {entityCamel}Id,
187
+ imageType: { type: 'enum', value: imageType },
188
+ },
189
+ },
190
+ );
191
+
192
+ Object.entries(formData ?? {}).forEach(([imageType, imageId], idx) => {
193
+ const [imgId] = imageId;
194
+ const [initialValue] = initialData?.data?.[imageType];
195
+ const [currentValue] = formData[imageType];
196
+
197
+ switch (true) {
198
+ case initialValue === undefined && currentValue !== undefined:
199
+ mutations.push(
200
+ `assign${idx}:${generateCreateMutation(
201
+ imgId,
202
+ imageType as {features.capabilities.images.imageTypeEnum},
203
+ )}`,
204
+ );
205
+ break;
206
+ case initialValue !== undefined && currentValue === undefined:
207
+ mutations.push(
208
+ `assign${idx}:${generateDeleteMutation(
209
+ imageType as {features.capabilities.images.imageTypeEnum},
210
+ )}`,
211
+ );
212
+ break;
213
+ case initialValue !== currentValue:
214
+ mutations.push(
215
+ `assign${idx}:${generateUpdateMutation(
216
+ currentValue,
217
+ imageType as {features.capabilities.images.imageTypeEnum},
218
+ )}`,
219
+ );
220
+ break;
221
+ default:
222
+ break;
223
+ }
224
+ });
225
+
226
+ const GqlDoc = gql`mutation ImageAssignments {
227
+ ${mutations}
228
+ }`;
229
+
230
+ await client.mutate({ mutation: GqlDoc });
231
+ },
232
+ [{entityCamel}Id],
233
+ );
234
+
235
+ const { ImageSelectField } = useContext(ExtensionsContext);
236
+
237
+ return (
238
+ <Details<FormData>
239
+ defaultTitle="Image Management"
240
+ initialData={{
241
+ data: initialImages ?? {},
242
+ loading,
243
+ entityNotFound: data?.{entityCamel} === null,
244
+ error: error?.message,
245
+ }}
246
+ saveData={onSubmit}
247
+ >
248
+ <Form imageSelectField={ImageSelectField} />
249
+ </Details>
250
+ );
251
+ };
252
+
253
+ /**
254
+ * Creates the initial image type values
255
+ * @param nodes - data nodes
256
+ */
257
+ const useImageTypes = (
258
+ nodes: ImageNodes[] = [],
259
+ ): {
260
+ readonly initialImages: FormData;
261
+ } => {
262
+ const [initialImages, setInitialImages] = useState<FormData>(getImageTypes());
263
+
264
+ // Set all currently assigned images on the server
265
+ useEffect(() => {
266
+ if (nodes.length > 0) {
267
+ let temp = {} as FormData;
268
+
269
+ for (const { imageType, imageId } of nodes) {
270
+ temp = { ...temp, [imageType]: [imageId] };
271
+ }
272
+
273
+ setInitialImages((prevState) => {
274
+ return { ...prevState, ...temp };
275
+ });
276
+ }
277
+ }, [nodes]);
278
+
279
+ return { initialImages } as const;
280
+ };
281
+
282
+ /**
283
+ * Returns an image type with empty array value using the `{ImageTypeEnum}` enum
284
+ */
285
+ const getImageTypes = (): FormData => {
286
+ let types = {} as FormData;
287
+
288
+ Object.keys({features.capabilities.images.imageTypeEnum}).map((type) => {
289
+ const field = {features.capabilities.images.imageTypeEnum}[type];
290
+ types = { ...types, [field]: [] };
291
+ });
292
+
293
+ return types;
294
+ };
295
+ ```
296
+
297
+ ## File 4: Quick Edit Wrapper
298
+
299
+ **Path:** `{Entity}ImageManagement/{Entity}ImageManagementQuickEdit.tsx`
300
+
301
+ ```typescript
302
+ import { QuickEditContext, QuickEditContextType } from '@axinom/mosaic-ui';
303
+ import React, { useContext } from 'react';
304
+ import { {Entity}Data } from '../{EntityPlural}Explorer/common/{EntityPlural}.types';
305
+ import { {Entity}ImageManagementForm } from './{Entity}ImageManagementForm';
306
+
307
+ export const {Entity}ImageManagementQuickEdit: React.FC = () => {
308
+ const { selectedItem } =
309
+ useContext<QuickEditContextType<{Entity}Data>>(QuickEditContext);
310
+
311
+ return <{Entity}ImageManagementForm {entityCamel}Id={selectedItem.id} />;
312
+ };
313
+ ```
314
+
315
+ ## Notes
316
+
317
+ - Image scope uses `features.capabilities.images.scope` (e.g., `"movie"`,
318
+ `"episode"`)
319
+ - Form creates one field per image type from `imageTypeValues` enum
320
+ - Uses `ImageSelectField` from `ExtensionsContext` (media management
321
+ integration)
322
+ - Generates create/update/delete mutations dynamically based on form changes