@inizioevoke/veeva-astroclm-core 1.0.3 → 1.0.4

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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: inizioevoke-veeva-astroclm
3
+ description: >
4
+ Skills and tools for working in this Veeva CLM Astro project (inizioevoke-veeva-astroclm).
5
+ Use this skill for any project-specific tasks such as creating pages, managing content, or
6
+ running project utilities. Triggers on any request that involves scaffolding, generating,
7
+ or managing project files in this Veeva CLM project.
8
+ ---
9
+
10
+ # inizioevoke-veeva-astroclm Skills
11
+
12
+ ## Available skills
13
+
14
+ - **Page Manager** — Create a new page in the project → read `page-manager/SKILL.md`
15
+ - **Veeva Config Manager** — Manage Veeva CLM configuration → read `veeva-config-manager/SKILL.md`
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: inizioevoke-veeva-astroclm-pagemanager
3
+ description: >
4
+ Scaffolds a new page in this Veeva CLM Astro project. Use this skill whenever the user asks to
5
+ create a new page, add a page, scaffold a slide, generate a new route, or set up a new CLM slide —
6
+ even if they don't say "page manager" explicitly. If the user is working in this project and wants
7
+ a new page, this skill should always trigger.
8
+ ---
9
+
10
+ # Page Manager
11
+
12
+ ## Step 1 — Ask for the page path
13
+
14
+ Ask: "What should the page path be? (relative to `src/contents/`, e.g. `home` or `module-1/slide-2`)"
15
+
16
+ ## Step 2 — Ask for the layout
17
+
18
+ Read `src/layouts/` to discover available layouts (each subdirectory containing a `.astro` file is a layout). Present them as a numbered list, for example:
19
+
20
+ ```
21
+ Which layout would you like to use?
22
+ 1. GlobalLayout
23
+ 2. LeftNavLayout
24
+ 3. TopNavLayout
25
+ ```
26
+
27
+ The user can respond with just a number. Resolve it to the layout name (e.g. `TopNavLayout`) before proceeding.
28
+
29
+ ## Step 3 — Check for veeva-config.ts
30
+
31
+ Check if `veeva-config.ts` exists in the project root. If it does, ask the user: "Would you like to add a slide entry for this page in `veeva-config.ts`?"
32
+
33
+ ## Step 4 — Create files
34
+
35
+ Given the page path (e.g. `this/one`):
36
+ - `contentName` = last segment of the path (e.g. `one`)
37
+ - `pageName` = full path with `/` replaced by `-` (e.g. `this-one`)
38
+
39
+ Check that neither `src/pages/<pageName>.astro` nor `src/contents/<path>/` already exist — if either does, stop and tell the user.
40
+
41
+ **Write `src/pages/<pageName>.astro`:**
42
+ ```astro
43
+ ---
44
+ import Page from '@/contents/<path>/<contentName>.astro';
45
+ ---
46
+ <Page />
47
+ ```
48
+
49
+ **Create `src/contents/<path>/` and write `<contentName>.astro`:**
50
+ ```astro
51
+ ---
52
+ import <layoutName> from '@/layouts/<layoutName>/<layoutName>.astro';
53
+
54
+ import './<contentName>.scss';
55
+ ---
56
+ <<layoutName>>
57
+ <!-- Insert page content here -->
58
+
59
+ <script>
60
+ import './<contentName>';
61
+ </script>
62
+ </<layoutName>>
63
+ ```
64
+
65
+ **Write `src/contents/<path>/<contentName>.scss`:** empty file
66
+
67
+ **Write `src/contents/<path>/<contentName>.ts`:** empty file
68
+
69
+ ## Step 5 — Add slide to veeva-config.ts
70
+
71
+ If the user said yes in Step 3, add a new slide entry to `presentation.slides` following the pattern in `../veeva-config-manager/manage.md` (Step 3 — Resolve missing slides).
72
+
73
+ ## Step 6 — Report
74
+
75
+ Tell the user which files were created.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: inizioevoke-veeva-astroclm-veeva-config-manager
3
+ description: >
4
+ Manages Veeva CLM configuration for this project. Use this skill whenever the user asks to
5
+ create, generate, or set up a Veeva config, initialize a veeva-config.ts, scaffold a new
6
+ presentation, or run the veeva-config-manager. Also trigger when the user says things like
7
+ "set up the config", "create the config file", "initialize the presentation config",
8
+ "sync the config", "check the config", or "update the slides in the config".
9
+ ---
10
+
11
+ # Veeva Config Manager
12
+
13
+ ## Available skills
14
+
15
+ - **Create config** — Generate a `veeva-config.ts` file for the project → read `create.md`
16
+ - **Manage config** — Sync `veeva-config.ts` slides with `src/pages/` → read `manage.md`
@@ -0,0 +1,154 @@
1
+ # veeva-create-config
2
+
3
+ Interactively collect presentation metadata from the user and write a
4
+ `veeva-config.ts` file to the project root, replicating the flow in
5
+ `src/apps/veeva-config-manager/create.ts`.
6
+
7
+ ## What you need from the user
8
+
9
+ Gather **all** of the following before writing any files. Ask for them
10
+ conversationally, batching related questions into a single message to minimize
11
+ round-trips:
12
+
13
+ | Field | Options / constraints |
14
+ |---|---|
15
+ | **Product name** | Free text, required |
16
+ | **Presentation name** | Free text, required |
17
+ | **Presentation ID** | Free text; default = presentation name lowercased, non-alphanumeric → `_` |
18
+ | **IVA dimensions** | `1024 × 768`, `1366 × 1024` *(default)*, `1376 × 1032` |
19
+ | **iOS resolution** | `Default For Device` *(default)*, `Scale To Fit`, `Scale To 1024x768` |
20
+ | **Disabled actions** | Multi-select, none required: History Buttons, Navigation Bar, Pinch to Exit, Reactions, Rotation Lock, Swipe, Zoom |
21
+ | **CRM target** | `Salesforce` *(default)*, `Vault` |
22
+
23
+ ## Generating IDs
24
+
25
+ You will need two kinds of random IDs. Implement them inline with JavaScript/TypeScript logic or generate them yourself:
26
+
27
+ - **External ID prefix**: First 3 chars of `product` (uppercased) + `_` + an 8-char alphanumeric random string (uppercase A–Z, 0–9).
28
+ Example: product `Velorixin` → `VEL_3K9BW12X`
29
+ - **Per-slide ID**: An independent 8-char alphanumeric random string for each slide, unique within the set.
30
+
31
+ To generate an 8-char random-ish ID in a bash snippet you can run:
32
+ ```bash
33
+ node -e "const a='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';let r='';for(let i=0;i<8;i++)r+=a[Math.floor(Math.random()*36)];console.log(r)"
34
+ ```
35
+ Or generate them yourself — just make them look like `3K9BW12X` (8 uppercase alphanumeric chars).
36
+
37
+ ## Discovering slides
38
+
39
+ Read all `.astro` files in `src/pages/`:
40
+
41
+ ```bash
42
+ ls src/pages/*.astro 2>/dev/null
43
+ ```
44
+
45
+ For each file `some-page-name.astro`:
46
+ - `path` = filename without `.astro` extension (e.g. `some-page-name`)
47
+ - `title` = title-case the path by splitting on `-`, capitalising each part, joining with space (e.g. `Some Page Name`)
48
+ - `externalId` = `formatExtId('<SLIDE_ID>')` — keep as a function call literal in the output. Ensure that `<SLIDE_ID>` is unique
49
+
50
+ ## Output format
51
+
52
+ Write `veeva-config.ts` to the **project root** using exactly this template
53
+ (replace all `##...##` tokens with the collected/generated values):
54
+
55
+ ```ts
56
+ import type { IVeevaClmBinderFields, IVeevaConfig, VeevaEnv } from "@inizioevoke/veeva-astroclm-core/types";
57
+ import { formatExtId as __formatExtId } from '@inizioevoke/veeva-astroclm-core/lib';
58
+
59
+ interface IVeevaConfigArgs {
60
+ veevaEnv?: VeevaEnv;
61
+ isTraining?: boolean;
62
+ }
63
+ export default function ({ veevaEnv = 'client', isTraining = false }: IVeevaConfigArgs = {}): IVeevaConfig {
64
+ const isEvokeBuild = veevaEnv === 'evoke';
65
+ const EXT_ID = `##EXTERNAL_ID##${isTraining ? '_TRN' : ''}`;
66
+ const PRESENTATION_NAME = '##PRESENTATION_NAME##';
67
+
68
+ const formatExtId = (id: string) => {
69
+ return __formatExtId(EXT_ID, id);
70
+ };
71
+
72
+ const binderFields: IVeevaClmBinderFields = {
73
+ // language: isEvokeBuild ? undefined : 'English',
74
+ // crmOrg: isEvokeBuild ? undefined : { crmId: ['CRM_ORD_ID'] },
75
+ // crmProduct: isEvokeBuild ? undefined : { id: 'VEEVA_ID_FROM_URL' },
76
+ // crmDetailGroup: isEvokeBuild ? undefined : { id: 'VEEVA_ID_FROM_URL' },
77
+ // detailGroup: undefined
78
+ };
79
+
80
+ return {
81
+ dimensions: { width: ##DIMENSIONS_WIDTH##, height: ##DIMENSIONS_HEIGHT## },
82
+ thumbnails: {
83
+ default: {
84
+ queryStringParams: {
85
+ screenshots: true
86
+ }
87
+ }
88
+ },
89
+ presentation: {
90
+ externalId: EXT_ID,
91
+ presentationId: `##PRESENTATION_ID##${isTraining ? '_trn' : ''}`,
92
+ product: '##PRODUCT##',
93
+ name: `${PRESENTATION_NAME}${isTraining ? ' [Training]' : ''}`,
94
+ country: 'United States',
95
+ isTraining,
96
+ crmTarget: '##CRM_TARGET##',
97
+ ...binderFields,
98
+
99
+ shared: {
100
+ externalId: formatExtId('SHARED'),
101
+ ...binderFields
102
+ },
103
+
104
+ slideSettings: {
105
+ mediaType: 'HTML',
106
+ disableActions: ##DISABLE_ACTIONS##,
107
+ iosResolution: '##IOS_RESOLUTION##',
108
+ ...binderFields
109
+ },
110
+ slides: ##SLIDES##
111
+ }
112
+ };
113
+ }
114
+
115
+ ```
116
+
117
+ ### Token substitution reference
118
+
119
+ | Token | Value |
120
+ |---|---|
121
+ | `##EXTERNAL_ID##` | e.g. `OZE_3K9BW12X` |
122
+ | `##PRESENTATION_NAME##` | cleaned presentation name (trim, collapse double spaces) |
123
+ | `##PRESENTATION_ID##` | cleaned presentation ID |
124
+ | `##PRODUCT##` | cleaned product name |
125
+ | `##DIMENSIONS_WIDTH##` | numeric width, no quotes (e.g. `1366`) |
126
+ | `##DIMENSIONS_HEIGHT##` | numeric height, no quotes (e.g. `1024`) |
127
+ | `##IOS_RESOLUTION##` | string value as-is (e.g. `Default For Device`) |
128
+ | `##DISABLE_ACTIONS##` | TS array literal e.g. `['Swipe', 'Zoom']` or `[]` |
129
+ | `##CRM_TARGET##` | `Salesforce` or `Vault` |
130
+ | `##SLIDES##` | Array of slide objects (see below) |
131
+
132
+ ### Slides array format
133
+
134
+ ```ts
135
+ [{
136
+ path: 'some-page-name',
137
+ title: 'Some Page Name',
138
+ externalId: formatExtId('3K9BW12X')
139
+ }, {
140
+ path: 'another-page',
141
+ title: 'Another Page',
142
+ externalId: formatExtId('7XZ2M4QR')
143
+ }]
144
+ ```
145
+
146
+ ## "Clean input" rule
147
+
148
+ Before using any free-text value in the output: `.trim()` and replace runs of
149
+ two or more spaces with a single space. Do not otherwise transform user input.
150
+
151
+ ## After writing the file
152
+
153
+ Tell the user where the file was written (`veeva-config.ts` in the project
154
+ root) and list the slides that were detected.
@@ -0,0 +1,48 @@
1
+ # veeva-manage-config
2
+
3
+ Audit `veeva-config.ts` against `src/pages/` to keep slides in sync with the actual pages in the project.
4
+
5
+ ## Step 1 — Discover pages and slides
6
+
7
+ Run in parallel:
8
+
9
+ 1. List all `.astro` files in `src/pages/`:
10
+ ```bash
11
+ ls src/pages/*.astro 2>/dev/null
12
+ ```
13
+ Extract the `path` for each: filename without the `.astro` extension (e.g. `some-page-name`).
14
+
15
+ 2. Read `veeva-config.ts` from the project root and extract the `path` property from each entry in `presentation.slides`.
16
+
17
+ ## Step 2 — Compare
18
+
19
+ Identify:
20
+ - **Missing slides** — pages in `src/pages/` that have no matching entry in `slides`
21
+ - **Extraneous slides** — entries in `slides` whose `path` has no matching file in `src/pages/`
22
+
23
+ If everything is in sync, tell the user and stop.
24
+
25
+ ## Step 3 — Resolve missing slides
26
+
27
+ For each missing slide, ask the user: "No slide entry found for `<path>`. Add it to `veeva-config.ts`?"
28
+
29
+ If yes, add a new slide object to the `slides` array following the same pattern from `create.md`:
30
+ - `path` = the page filename without `.astro`
31
+ - `title` = title-case by splitting on `-`, capitalising each part, joining with a space
32
+ - `externalId` = `formatExtId('<SLIDE_ID>')` where `<SLIDE_ID>` is a new unique 8-char uppercase alphanumeric string, different from all existing slide IDs in the file
33
+
34
+ Generate the ID with:
35
+ ```bash
36
+ node -e "const a='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';let r='';for(let i=0;i<8;i++)r+=a[Math.floor(Math.random()*36)];console.log(r)"
37
+ ```
38
+
39
+ ## Step 4 — Resolve extraneous slides
40
+
41
+ For each extraneous slide, ask the user: "Slide entry `<path>` has no matching page. Remove it or comment it out?"
42
+
43
+ - **Remove** — delete the slide object from the array entirely
44
+ - **Comment out** — wrap the slide object in a block comment: `/* { path: '...', ... } */`
45
+
46
+ ## Step 5 — Write the file
47
+
48
+ If any changes were made, write the updated `veeva-config.ts` and tell the user what was added, removed, or commented out.
package/package.json CHANGED
@@ -1,20 +1,27 @@
1
1
  {
2
2
  "name": "@inizioevoke/veeva-astroclm-core",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "",
7
7
  "type": "module",
8
8
  "scripts": {
9
9
  "prepublishOnly": "npm run build",
10
- "build": "npx tsx build.ts --cmd pre && tsc && npx tsx build.ts --cmd post"
10
+ "build": "npx tsx build.ts --cmd pre && tsc && npx tsx build.ts --cmd post",
11
+ "postinstall": "node postinstall.mjs"
11
12
  },
12
13
  "exports": {
13
14
  "./apps": "./dist/apps/index.js",
14
15
  "./env": "./dist/env/index.js",
15
16
  "./integrations": "./dist/integrations/index.js",
16
- "./lib": "./dist/lib/index.js",
17
- "./lib/server": "./dist/lib/server.js",
17
+ "./lib": {
18
+ "types": "./dist/lib/index.d.ts",
19
+ "default": "./dist/lib/index.js"
20
+ },
21
+ "./lib/server": {
22
+ "types": "./dist/lib/server.d.ts",
23
+ "default": "./dist/lib/server.js"
24
+ },
18
25
  "./tasks": "./dist/tasks/index.js",
19
26
  "./types": "./dist/types/index.d.ts"
20
27
  },
@@ -0,0 +1,17 @@
1
+ import { copyFileSync, mkdirSync, rmSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+
7
+ // Resolve the consuming project's root (three levels up from node_modules/@inizioevoke/astro-core)
8
+ const projectRoot = join(__dirname, '..', '..', '..');
9
+ const destDir = join(projectRoot, '.claude', 'skills', 'inizioevoke-veeva-astroclm-core');
10
+ const src = join(__dirname, 'agents', 'claude', 'SKILL.md');
11
+ const dest = join(destDir, 'SKILL.md');
12
+
13
+ rmSync(destDir, { recursive: true, force: true });
14
+ mkdirSync(destDir, { recursive: true });
15
+
16
+ copyFileSync(src, dest);
17
+ console.log(`[@inizioevoke/veeva-astroclm-core] Copied skill to ${dest}`);