@inizioevoke/veeva-astroclm-core 1.0.2 → 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/dist/lib/index.d.ts +0 -4
- package/dist/lib/index.js +1 -4
- package/dist/lib/server.d.ts +5 -0
- package/dist/lib/server.js +5 -0
- package/dist/tasks/create-csv.d.ts +8 -4
- package/dist/tasks/create-csv.js +46 -12
- package/dist/types/veeva.d.ts +1 -0
- package/package.json +11 -3
- package/postinstall.mjs +17 -0
- package/src/lib/index.ts +1 -4
- package/src/lib/server.ts +5 -0
- package/src/tasks/create-csv.ts +55 -15
- package/src/types/veeva.d.ts +1 -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/dist/lib/index.d.ts
CHANGED
package/dist/lib/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { IVeevaConfig } from '../types';
|
|
2
|
+
export interface AdditionalField {
|
|
3
|
+
header: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
property?: string;
|
|
6
|
+
}
|
|
2
7
|
interface Params {
|
|
3
8
|
config: IVeevaConfig;
|
|
4
9
|
deployDir: string;
|
|
5
10
|
buildName: string;
|
|
6
11
|
fieldsOnly?: boolean;
|
|
7
12
|
additional?: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
shared: string[];
|
|
13
|
+
presentation: AdditionalField[];
|
|
14
|
+
slides: AdditionalField[];
|
|
15
|
+
shared: AdditionalField[];
|
|
12
16
|
};
|
|
13
17
|
}
|
|
14
18
|
export default function ({ config, deployDir, buildName, fieldsOnly, additional }: Params): Promise<void>;
|
package/dist/tasks/create-csv.js
CHANGED
|
@@ -4,7 +4,30 @@ import logger from '../lib/logger.js';
|
|
|
4
4
|
import { pathToSlideZip } from '../lib/utils.js';
|
|
5
5
|
export default async function ({ config, deployDir, buildName, fieldsOnly = false, additional }) {
|
|
6
6
|
logger.info('Creating multichannel loader CSV');
|
|
7
|
-
const headers =
|
|
7
|
+
const headers = fieldsOnly ? CSV_HEADERS_FIELDS_ONLY : CSV_HEADERS;
|
|
8
|
+
additional?.presentation?.forEach((a) => {
|
|
9
|
+
if (a.value || (a.property && config.presentation[a.property])) {
|
|
10
|
+
headers.push(a.header);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
additional?.slides?.forEach((a) => {
|
|
14
|
+
if (a.value) {
|
|
15
|
+
headers.push(a.header);
|
|
16
|
+
}
|
|
17
|
+
else if (a.property && config.presentation.slides) {
|
|
18
|
+
const slide = config.presentation.slides.find((s) => {
|
|
19
|
+
return s[a.property];
|
|
20
|
+
});
|
|
21
|
+
if (slide) {
|
|
22
|
+
headers.push(a.header);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
additional?.shared?.forEach((a) => {
|
|
27
|
+
if (a.value || (a.property && config.presentation.shared && config.presentation.shared[a.property])) {
|
|
28
|
+
headers.push(a.header);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
8
31
|
const presentation = config.presentation;
|
|
9
32
|
for (const [key, header] of Object.entries(BINDER_PRES_HEADERS)) {
|
|
10
33
|
if (presentation[key]) {
|
|
@@ -17,12 +40,12 @@ export default async function ({ config, deployDir, buildName, fieldsOnly = fals
|
|
|
17
40
|
}
|
|
18
41
|
}
|
|
19
42
|
const rows = [headers];
|
|
20
|
-
rows.push(createPresentationRow(headers, config
|
|
43
|
+
rows.push(createPresentationRow(headers, config, additional?.presentation));
|
|
21
44
|
for (let i = 0; i < config.presentation.slides.length; i++) {
|
|
22
|
-
rows.push(createSlideRow(headers, config, config.presentation.slides[i]
|
|
45
|
+
rows.push(createSlideRow(headers, config, config.presentation.slides[i], additional?.slides));
|
|
23
46
|
}
|
|
24
|
-
const shared = createSharedRow(headers, config);
|
|
25
|
-
shared && rows.push(shared
|
|
47
|
+
const shared = createSharedRow(headers, config, additional?.shared);
|
|
48
|
+
shared && rows.push(shared);
|
|
26
49
|
await writeFile(join(deployDir, `_${config.presentation.presentationId}_${buildName}.csv`), rows.map((r) => { return r.join(','); }).join('\n'), { encoding: 'utf8' });
|
|
27
50
|
}
|
|
28
51
|
const VEEVA_YES = 'YES', VEEVA_NO = 'NO', VEEVA_TYPE_PRESENTATION = 'Presentation', VEEVA_TYPE_SLIDE = 'Slide', VEEVA_TYPE_SHARED = 'Shared', VEEVA_LIFECYCLE_BINDER = 'Binder Lifecycle', VEEVA_LIFECYCLE_CRM_CONTENT = 'CRM Content Lifecycle', VEEVA_IOS_RESOLUTION_DEFAULT = 'Default For Device', VEEVA_MEDIATYPE_HTML = 'HTML',
|
|
@@ -30,10 +53,10 @@ const VEEVA_YES = 'YES', VEEVA_NO = 'NO', VEEVA_TYPE_PRESENTATION = 'Presentatio
|
|
|
30
53
|
HEADER_FIELDS_ONLY = 'Fields Only', HEADER_EXTERNAL_ID = 'external_id__v', HEADER_NAME = 'name__v', HEADER_TYPE = 'Type', HEADER_LIFECYCLE = 'lifecycle__v', HEADER_PRES_ID = 'pres.crm_presentation_id__v', HEADER_PRES_TITLE = 'pres.title__v', HEADER_PRES_PRODUCT = 'pres.product__v.name__v', HEADER_PRES_COUNTRY = 'pres.country__v.name__v', HEADER_PRES_LANGUAGE = 'pres.language__v', HEADER_PRES_CLM_CONTENT = 'pres.clm_content__v', HEADER_PRES_CLM_HIDDEN = 'pres.crm_hidden__v', HEADER_PRES_TRAINING = 'pres.crm_training__v', HEADER_PRES_CRM_TARGET = 'pres.crm_target_platform__v', HEADER_PRES_CRM_ORG = 'pres.crm_org__v.crm_org_id__v', HEADER_PRES_CRM_PRODUCT = 'pres.crm_product__v.id', // Get the Veeva ID from the URL
|
|
31
54
|
HEADER_PRES_CRM_CONTENT_GROUP = 'pres.crm_content_group__v.id', // Get the Veeva ID from the URL
|
|
32
55
|
HEADER_PRES_CRM_DETAIL_GROUP = 'pres.crm_detail_group__v.id', // Get the Veeva ID from the URL
|
|
33
|
-
HEADER_PRES_DETAIL_GROUP = 'pres.detail_group__v', HEADER_PRESENTATION = 'Presentation Link', HEADER_SLIDE_TITLE = 'slide.title__v', HEADER_SLIDE_PRODUCT = 'slide.product__v.name__v', HEADER_SLIDE_COUNTRY = 'slide.country__v.name__v', HEADER_SLIDE_LANGUAGE = 'slide.language__v', HEADER_SLIDE_MEDIA_TYPE = 'slide.crm_media_type__v', HEADER_SLIDE_IOS_RESOLUTION = 'slide.ios_resolution__v', HEADER_SLIDE_RELATED_SHARED = 'slide.related_shared_resource__v', HEADER_SLIDE_CLM_CONTENT = 'slide.clm_content__v', HEADER_SLIDE_FILE_NAME = 'slide.filename', HEADER_SLIDE_REACTIONS = 'slide.crm_custom_reaction__v', HEADER_SLIDE_DISABLE_ACTIONS = 'slide.crm_disable_actions__v', HEADER_SLIDE_CRM_TARGET = 'slide.crm_target_platform__v', HEADER_SLIDE_CRM_ORG = 'slide.crm_org__v.crm_org_id__v', HEADER_SLIDE_CRM_PRODUCT = 'slide.crm_product__v.id', // Get the Veeva ID from the URL
|
|
56
|
+
HEADER_PRES_DETAIL_GROUP = 'pres.detail_group__v', HEADER_PRES_EXP_DATE = 'pres.expiration_date__v', HEADER_PRESENTATION = 'Presentation Link', HEADER_SLIDE_TITLE = 'slide.title__v', HEADER_SLIDE_PRODUCT = 'slide.product__v.name__v', HEADER_SLIDE_COUNTRY = 'slide.country__v.name__v', HEADER_SLIDE_LANGUAGE = 'slide.language__v', HEADER_SLIDE_MEDIA_TYPE = 'slide.crm_media_type__v', HEADER_SLIDE_IOS_RESOLUTION = 'slide.ios_resolution__v', HEADER_SLIDE_RELATED_SHARED = 'slide.related_shared_resource__v', HEADER_SLIDE_CLM_CONTENT = 'slide.clm_content__v', HEADER_SLIDE_FILE_NAME = 'slide.filename', HEADER_SLIDE_REACTIONS = 'slide.crm_custom_reaction__v', HEADER_SLIDE_DISABLE_ACTIONS = 'slide.crm_disable_actions__v', HEADER_SLIDE_CRM_TARGET = 'slide.crm_target_platform__v', HEADER_SLIDE_CRM_ORG = 'slide.crm_org__v.crm_org_id__v', HEADER_SLIDE_CRM_PRODUCT = 'slide.crm_product__v.id', // Get the Veeva ID from the URL
|
|
34
57
|
HEADER_SLIDE_CRM_CONTENT_GROUP = 'slide.crm_content_group__v.id', // Get the Veeva ID from the URL
|
|
35
58
|
HEADER_SLIDE_CRM_DETAIL_GROUP = 'slide.crm_detail_group__v.id', // Get the Veeva ID from the URL
|
|
36
|
-
HEADER_SLIDE_CRM_SHARED = 'slide.crm_shared_resource__v', HEADER_SLIDE_DETAIL_GROUP = 'slide.detail_group__v';
|
|
59
|
+
HEADER_SLIDE_CRM_SHARED = 'slide.crm_shared_resource__v', HEADER_SLIDE_DETAIL_GROUP = 'slide.detail_group__v', HEADER_SLIDE_EXP_DATE = 'slide.expiration_date__v';
|
|
37
60
|
const CSV_HEADERS = [
|
|
38
61
|
HEADER_EXTERNAL_ID,
|
|
39
62
|
HEADER_TYPE,
|
|
@@ -71,7 +94,8 @@ const BINDER_PRES_HEADERS = {
|
|
|
71
94
|
crmProduct: HEADER_PRES_CRM_PRODUCT,
|
|
72
95
|
crmContentGroup: HEADER_PRES_CRM_CONTENT_GROUP,
|
|
73
96
|
crmDetailGroup: HEADER_PRES_CRM_DETAIL_GROUP,
|
|
74
|
-
detailGroup: HEADER_PRES_DETAIL_GROUP
|
|
97
|
+
detailGroup: HEADER_PRES_DETAIL_GROUP,
|
|
98
|
+
expirationDate: HEADER_PRES_EXP_DATE
|
|
75
99
|
};
|
|
76
100
|
const BINDER_SLIDE_HEADERS = {
|
|
77
101
|
language: HEADER_SLIDE_LANGUAGE,
|
|
@@ -80,9 +104,10 @@ const BINDER_SLIDE_HEADERS = {
|
|
|
80
104
|
crmProduct: HEADER_SLIDE_CRM_PRODUCT,
|
|
81
105
|
crmContentGroup: HEADER_SLIDE_CRM_CONTENT_GROUP,
|
|
82
106
|
crmDetailGroup: HEADER_SLIDE_CRM_DETAIL_GROUP,
|
|
83
|
-
detailGroup: HEADER_SLIDE_DETAIL_GROUP
|
|
107
|
+
detailGroup: HEADER_SLIDE_DETAIL_GROUP,
|
|
108
|
+
expirationDate: HEADER_SLIDE_EXP_DATE
|
|
84
109
|
};
|
|
85
|
-
function createPresentationRow(headers, config) {
|
|
110
|
+
function createPresentationRow(headers, config, additional) {
|
|
86
111
|
const row = new Array(headers.length).fill('');
|
|
87
112
|
const presentation = config.presentation;
|
|
88
113
|
addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
|
|
@@ -101,9 +126,12 @@ function createPresentationRow(headers, config) {
|
|
|
101
126
|
for (const [key, header] of Object.entries(BINDER_PRES_HEADERS)) {
|
|
102
127
|
addColumn(headers, row, header, presentation[key]);
|
|
103
128
|
}
|
|
129
|
+
additional?.forEach((a) => {
|
|
130
|
+
addColumn(headers, row, a.header, a.property ? presentation[a.property] : a.value);
|
|
131
|
+
});
|
|
104
132
|
return row;
|
|
105
133
|
}
|
|
106
|
-
function createSlideRow(headers, config, slide) {
|
|
134
|
+
function createSlideRow(headers, config, slide, additional) {
|
|
107
135
|
const row = new Array(headers.length).fill('');
|
|
108
136
|
addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
|
|
109
137
|
addColumn(headers, row, HEADER_EXTERNAL_ID, slide.externalId);
|
|
@@ -133,9 +161,12 @@ function createSlideRow(headers, config, slide) {
|
|
|
133
161
|
for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
|
|
134
162
|
addColumn(headers, row, header, slide[key] ?? config.presentation.slideSettings?.[key] ?? config.slideSettings?.[key]);
|
|
135
163
|
}
|
|
164
|
+
additional?.forEach((a) => {
|
|
165
|
+
addColumn(headers, row, a.header, a.property ? slide[a.property] : a.value);
|
|
166
|
+
});
|
|
136
167
|
return row;
|
|
137
168
|
}
|
|
138
|
-
function createSharedRow(headers, config) {
|
|
169
|
+
function createSharedRow(headers, config, additional) {
|
|
139
170
|
const shared = config.presentation.shared;
|
|
140
171
|
if (!shared) {
|
|
141
172
|
return null;
|
|
@@ -157,6 +188,9 @@ function createSharedRow(headers, config) {
|
|
|
157
188
|
for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
|
|
158
189
|
addColumn(headers, row, header, shared[key] ?? config.presentation.slideSettings?.[key] ?? config.slideSettings?.[key]);
|
|
159
190
|
}
|
|
191
|
+
additional?.forEach((a) => {
|
|
192
|
+
addColumn(headers, row, a.header, a.property ? shared[a.property] : a.value);
|
|
193
|
+
});
|
|
160
194
|
return row;
|
|
161
195
|
}
|
|
162
196
|
function addColumn(headers, row, header, value) {
|
package/dist/types/veeva.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,19 +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
|
+
"./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
|
+
},
|
|
17
25
|
"./tasks": "./dist/tasks/index.js",
|
|
18
26
|
"./types": "./dist/types/index.d.ts"
|
|
19
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}`);
|
package/src/lib/index.ts
CHANGED
package/src/tasks/create-csv.ts
CHANGED
|
@@ -4,21 +4,48 @@ import type { IVeevaConfig, IVeevaConfigSlide, Actions, IVeevaClmBinderFields }
|
|
|
4
4
|
import logger from '../lib/logger.js';
|
|
5
5
|
import { pathToSlideZip } from '../lib/utils.js';
|
|
6
6
|
|
|
7
|
+
export interface AdditionalField {
|
|
8
|
+
header: string;
|
|
9
|
+
value?: string;
|
|
10
|
+
property?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
interface Params {
|
|
8
14
|
config: IVeevaConfig
|
|
9
15
|
deployDir: string;
|
|
10
16
|
buildName: string;
|
|
11
17
|
fieldsOnly?: boolean;
|
|
12
18
|
additional?: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
shared: string[];
|
|
19
|
+
presentation: AdditionalField[];
|
|
20
|
+
slides: AdditionalField[];
|
|
21
|
+
shared: AdditionalField[];
|
|
17
22
|
}
|
|
18
23
|
}
|
|
19
24
|
export default async function({ config, deployDir, buildName, fieldsOnly = false, additional }: Params) {
|
|
20
25
|
logger.info('Creating multichannel loader CSV');
|
|
21
|
-
const headers: string[] =
|
|
26
|
+
const headers: string[] = fieldsOnly ? CSV_HEADERS_FIELDS_ONLY : CSV_HEADERS;
|
|
27
|
+
additional?.presentation?.forEach((a) => {
|
|
28
|
+
if (a.value || (a.property && config.presentation[a.property as keyof typeof config.presentation])) {
|
|
29
|
+
headers.push(a.header);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
additional?.slides?.forEach((a) => {
|
|
33
|
+
if (a.value) {
|
|
34
|
+
headers.push(a.header);
|
|
35
|
+
} else if (a.property && config.presentation.slides) {
|
|
36
|
+
const slide = config.presentation.slides.find((s) => {
|
|
37
|
+
return s[a.property as keyof typeof s]
|
|
38
|
+
});
|
|
39
|
+
if (slide) {
|
|
40
|
+
headers.push(a.header);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
additional?.shared?.forEach((a) => {
|
|
45
|
+
if (a.value || (a.property && config.presentation.shared && config.presentation.shared[a.property as keyof typeof config.presentation.shared])) {
|
|
46
|
+
headers.push(a.header);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
22
49
|
const presentation = config.presentation;
|
|
23
50
|
|
|
24
51
|
for (const [key, header] of Object.entries(BINDER_PRES_HEADERS)) {
|
|
@@ -33,12 +60,12 @@ export default async function({ config, deployDir, buildName, fieldsOnly = false
|
|
|
33
60
|
}
|
|
34
61
|
|
|
35
62
|
const rows: string[][] = [headers];
|
|
36
|
-
rows.push(createPresentationRow(headers, config
|
|
63
|
+
rows.push(createPresentationRow(headers, config, additional?.presentation));
|
|
37
64
|
for (let i=0; i<config.presentation.slides.length; i++) {
|
|
38
|
-
rows.push(createSlideRow(headers, config, config.presentation.slides[i]
|
|
65
|
+
rows.push(createSlideRow(headers, config, config.presentation.slides[i], additional?.slides));
|
|
39
66
|
}
|
|
40
|
-
const shared = createSharedRow(headers, config);
|
|
41
|
-
shared && rows.push(shared
|
|
67
|
+
const shared = createSharedRow(headers, config, additional?.shared);
|
|
68
|
+
shared && rows.push(shared);
|
|
42
69
|
|
|
43
70
|
await writeFile(join(deployDir, `_${config.presentation.presentationId}_${buildName}.csv`), rows.map((r)=>{return r.join(',');}).join('\n'), { encoding: 'utf8' });
|
|
44
71
|
}
|
|
@@ -76,6 +103,7 @@ const
|
|
|
76
103
|
HEADER_PRES_CRM_CONTENT_GROUP = 'pres.crm_content_group__v.id', // Get the Veeva ID from the URL
|
|
77
104
|
HEADER_PRES_CRM_DETAIL_GROUP = 'pres.crm_detail_group__v.id', // Get the Veeva ID from the URL
|
|
78
105
|
HEADER_PRES_DETAIL_GROUP = 'pres.detail_group__v',
|
|
106
|
+
HEADER_PRES_EXP_DATE = 'pres.expiration_date__v',
|
|
79
107
|
|
|
80
108
|
HEADER_PRESENTATION = 'Presentation Link',
|
|
81
109
|
HEADER_SLIDE_TITLE = 'slide.title__v',
|
|
@@ -95,7 +123,8 @@ const
|
|
|
95
123
|
HEADER_SLIDE_CRM_CONTENT_GROUP = 'slide.crm_content_group__v.id', // Get the Veeva ID from the URL
|
|
96
124
|
HEADER_SLIDE_CRM_DETAIL_GROUP = 'slide.crm_detail_group__v.id', // Get the Veeva ID from the URL
|
|
97
125
|
HEADER_SLIDE_CRM_SHARED = 'slide.crm_shared_resource__v',
|
|
98
|
-
HEADER_SLIDE_DETAIL_GROUP = 'slide.detail_group__v'
|
|
126
|
+
HEADER_SLIDE_DETAIL_GROUP = 'slide.detail_group__v',
|
|
127
|
+
HEADER_SLIDE_EXP_DATE = 'slide.expiration_date__v';
|
|
99
128
|
|
|
100
129
|
const CSV_HEADERS = [
|
|
101
130
|
HEADER_EXTERNAL_ID,
|
|
@@ -136,7 +165,8 @@ const BINDER_PRES_HEADERS: Record<keyof IVeevaClmBinderFields, string> = {
|
|
|
136
165
|
crmProduct: HEADER_PRES_CRM_PRODUCT,
|
|
137
166
|
crmContentGroup: HEADER_PRES_CRM_CONTENT_GROUP,
|
|
138
167
|
crmDetailGroup: HEADER_PRES_CRM_DETAIL_GROUP,
|
|
139
|
-
detailGroup: HEADER_PRES_DETAIL_GROUP
|
|
168
|
+
detailGroup: HEADER_PRES_DETAIL_GROUP,
|
|
169
|
+
expirationDate: HEADER_PRES_EXP_DATE
|
|
140
170
|
};
|
|
141
171
|
const BINDER_SLIDE_HEADERS: Record<keyof IVeevaClmBinderFields, string> = {
|
|
142
172
|
language: HEADER_SLIDE_LANGUAGE,
|
|
@@ -145,11 +175,12 @@ const BINDER_SLIDE_HEADERS: Record<keyof IVeevaClmBinderFields, string> = {
|
|
|
145
175
|
crmProduct: HEADER_SLIDE_CRM_PRODUCT,
|
|
146
176
|
crmContentGroup: HEADER_SLIDE_CRM_CONTENT_GROUP,
|
|
147
177
|
crmDetailGroup: HEADER_SLIDE_CRM_DETAIL_GROUP,
|
|
148
|
-
detailGroup: HEADER_SLIDE_DETAIL_GROUP
|
|
178
|
+
detailGroup: HEADER_SLIDE_DETAIL_GROUP,
|
|
179
|
+
expirationDate: HEADER_SLIDE_EXP_DATE
|
|
149
180
|
};
|
|
150
181
|
|
|
151
182
|
|
|
152
|
-
function createPresentationRow(headers: string[], config: IVeevaConfig): string[] {
|
|
183
|
+
function createPresentationRow(headers: string[], config: IVeevaConfig, additional?: AdditionalField[]): string[] {
|
|
153
184
|
const row: string[] = new Array(headers.length).fill('');
|
|
154
185
|
const presentation = config.presentation;
|
|
155
186
|
addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
|
|
@@ -168,10 +199,13 @@ function createPresentationRow(headers: string[], config: IVeevaConfig): string[
|
|
|
168
199
|
for (const [key, header] of Object.entries(BINDER_PRES_HEADERS)) {
|
|
169
200
|
addColumn(headers, row, header, presentation[key as keyof IVeevaClmBinderFields]);
|
|
170
201
|
}
|
|
202
|
+
additional?.forEach((a) => {
|
|
203
|
+
addColumn(headers, row, a.header, a.property ? presentation[a.property as keyof typeof presentation] : a.value)
|
|
204
|
+
});
|
|
171
205
|
return row;
|
|
172
206
|
}
|
|
173
207
|
|
|
174
|
-
function createSlideRow(headers: string[], config: IVeevaConfig, slide: IVeevaConfigSlide): string[] {
|
|
208
|
+
function createSlideRow(headers: string[], config: IVeevaConfig, slide: IVeevaConfigSlide, additional?: AdditionalField[]): string[] {
|
|
175
209
|
const row: string[] = new Array(headers.length).fill('');
|
|
176
210
|
addColumn(headers, row, HEADER_FIELDS_ONLY, VEEVA_YES);
|
|
177
211
|
addColumn(headers, row, HEADER_EXTERNAL_ID, slide.externalId);
|
|
@@ -206,10 +240,13 @@ function createSlideRow(headers: string[], config: IVeevaConfig, slide: IVeevaCo
|
|
|
206
240
|
for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
|
|
207
241
|
addColumn(headers, row, header, slide[key as keyof IVeevaClmBinderFields] ?? config.presentation.slideSettings?.[key as keyof IVeevaClmBinderFields] ?? config.slideSettings?.[key as keyof IVeevaClmBinderFields]);
|
|
208
242
|
}
|
|
243
|
+
additional?.forEach((a) => {
|
|
244
|
+
addColumn(headers, row, a.header, a.property ? slide[a.property as keyof typeof slide] : a.value)
|
|
245
|
+
});
|
|
209
246
|
return row;
|
|
210
247
|
}
|
|
211
248
|
|
|
212
|
-
function createSharedRow(headers: string[], config: IVeevaConfig): string[] | null {
|
|
249
|
+
function createSharedRow(headers: string[], config: IVeevaConfig, additional?: AdditionalField[]): string[] | null {
|
|
213
250
|
const shared = config.presentation.shared;
|
|
214
251
|
if (!shared) {
|
|
215
252
|
return null;
|
|
@@ -231,6 +268,9 @@ function createSharedRow(headers: string[], config: IVeevaConfig): string[] | nu
|
|
|
231
268
|
for (const [key, header] of Object.entries(BINDER_SLIDE_HEADERS)) {
|
|
232
269
|
addColumn(headers, row, header, shared[key as keyof IVeevaClmBinderFields] ?? config.presentation.slideSettings?.[key as keyof IVeevaClmBinderFields] ?? config.slideSettings?.[key as keyof IVeevaClmBinderFields]);
|
|
233
270
|
}
|
|
271
|
+
additional?.forEach((a) => {
|
|
272
|
+
addColumn(headers, row, a.header, a.property ? shared[a.property as keyof typeof shared] : a.value)
|
|
273
|
+
});
|
|
234
274
|
return row;
|
|
235
275
|
}
|
|
236
276
|
|