@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.
- package/agents/claude/SKILL.md +15 -0
- package/agents/claude/page-manager/SKILL.md +75 -0
- package/agents/claude/veeva-config-manager/SKILL.md +16 -0
- package/agents/claude/veeva-config-manager/create.md +154 -0
- package/agents/claude/veeva-config-manager/manage.md +48 -0
- package/package.json +11 -4
- package/postinstall.mjs +17 -0
|
@@ -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
|
+
"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":
|
|
17
|
-
|
|
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
|
},
|
package/postinstall.mjs
ADDED
|
@@ -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}`);
|