@gxp-dev/tools 2.0.85 → 2.0.87
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/bin/lib/commands/init.js +10 -0
- package/mcp/lib/config-tools.js +1 -1
- package/mcp/lib/server.js +6 -2
- package/package.json +1 -1
- package/runtime/dev-tools/StoreInspector.vue +26 -0
- package/runtime/stores/gxpPortalConfigStore.js +61 -3
- package/template/.claude/agents/gxp-developer.md +24 -0
- package/template/AGENTS.md +26 -0
- package/template/CLAUDE.md +25 -0
- package/template/GEMINI.md +14 -0
package/bin/lib/commands/init.js
CHANGED
|
@@ -77,6 +77,11 @@ function copyTemplateFiles(projectPath, paths, overwrite = false) {
|
|
|
77
77
|
dest: "src/DemoPage.vue",
|
|
78
78
|
desc: "DemoPage.vue (Example component)",
|
|
79
79
|
},
|
|
80
|
+
{
|
|
81
|
+
src: "src/DemoExperience.vue",
|
|
82
|
+
dest: "src/DemoExperience.vue",
|
|
83
|
+
desc: "DemoExperience.vue (Example experience flow)",
|
|
84
|
+
},
|
|
80
85
|
]
|
|
81
86
|
|
|
82
87
|
// Copy template files
|
|
@@ -168,6 +173,11 @@ function copyBundleFiles(projectPath, paths, overwrite = false) {
|
|
|
168
173
|
dest: "GEMINI.md",
|
|
169
174
|
desc: "GEMINI.md (Gemini Code Assist instructions)",
|
|
170
175
|
},
|
|
176
|
+
{
|
|
177
|
+
src: "CLAUDE.md",
|
|
178
|
+
dest: "CLAUDE.md",
|
|
179
|
+
desc: "CLAUDE.md (Claude Code project instructions)",
|
|
180
|
+
},
|
|
171
181
|
{
|
|
172
182
|
src: ".claude/agents/gxp-developer.md",
|
|
173
183
|
dest: ".claude/agents/gxp-developer.md",
|
package/mcp/lib/config-tools.js
CHANGED
|
@@ -285,7 +285,7 @@ const CONFIG_TOOLS = [
|
|
|
285
285
|
{
|
|
286
286
|
name: "config_extract_strings",
|
|
287
287
|
description:
|
|
288
|
-
"Scan a plugin's src/ directory for GxP datastore usage and directives (gxp-string, gxp-src, store.getString/getSetting/getAsset/getState calls) and return the extracted keys. Optionally merge them into app-manifest.json (linter-guarded — invalid writes are refused unless force=true).",
|
|
288
|
+
"Scan a plugin's src/ directory for GxP datastore usage and directives (gxp-string, gxp-src, store.getString/getSetting/getAsset/getState calls) and return the extracted keys. Optionally merge them into app-manifest.json (linter-guarded — invalid writes are refused unless force=true). Note: store.getUser/getUserName/getUserEmail/isAuthenticated and store.user are also valid accessors but read the platform-injected logged-in user (null when logged out) and have no manifest entry.",
|
|
289
289
|
inputSchema: {
|
|
290
290
|
type: "object",
|
|
291
291
|
properties: {
|
package/mcp/lib/server.js
CHANGED
|
@@ -46,7 +46,8 @@ const SERVER_INFO = {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const SERVER_DESCRIPTION =
|
|
49
|
-
"GxP toolkit MCP server: API specs, data models, config/manifest editing, documentation search, and plugin test helpers for AI coding assistants. UIKit component/story tools are served by the uikit's own Storybook MCP (gxdev storybook)
|
|
49
|
+
"GxP toolkit MCP server: API specs, data models, config/manifest editing, documentation search, and plugin test helpers for AI coding assistants. UIKit component/story tools are served by the uikit's own Storybook MCP (gxdev storybook).\n\n" +
|
|
50
|
+
"IMPORTANT context for plugin authors: GxP is a multi-tenant platform. The platform admin UI (not the plugin) owns all configuration of forms, quizzes, surveys, quiz builders, leaderboards, settings, strings, assets, and project metadata. Plugins do NOT define these — they consume them. At runtime the platform injects manifest data (settings, strings, assets, dependencies, permissions) and the logged-in user into the GxP store, and exposes platform-managed resources via the REST API documented in the OpenAPI spec. Plugins should access forms/quizzes/surveys and their admin-built questions exclusively through `store.callApi(operationId, identifier, data)` — for example `forms.show`, `forms.fields.index`, `forms.responses.store`, `quiz.state`, `quiz.questions`, `quiz.answer`, `quiz.leaderboard`, `survey.metrics`. Use the API spec tools (`search_api_endpoints`, `api_list_tags`, `get_endpoint_details`, `describe_data_models`) to discover the exact operationIds, parameters, and response schemas. The logged-in user is read via `store.user` / `store.getUser()` / `store.getUserName()` / `store.getUserEmail()` (null when logged out)."
|
|
50
51
|
|
|
51
52
|
/* -------------------- API spec search helpers (in-file) ------------------- */
|
|
52
53
|
|
|
@@ -348,7 +349,10 @@ async function startServer() {
|
|
|
348
349
|
|
|
349
350
|
const server = new Server(
|
|
350
351
|
{ name: SERVER_INFO.name, version: SERVER_INFO.version },
|
|
351
|
-
{
|
|
352
|
+
{
|
|
353
|
+
capabilities: { tools: {} },
|
|
354
|
+
instructions: SERVER_DESCRIPTION,
|
|
355
|
+
},
|
|
352
356
|
)
|
|
353
357
|
|
|
354
358
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
package/package.json
CHANGED
|
@@ -212,6 +212,31 @@
|
|
|
212
212
|
</div>
|
|
213
213
|
</div>
|
|
214
214
|
|
|
215
|
+
<div class="inspector-section">
|
|
216
|
+
<h3 class="section-title" @click="toggleSection('user')">
|
|
217
|
+
<span class="toggle-icon">{{ expandedSections.user ? "▼" : "▶" }}</span>
|
|
218
|
+
Logged-in User
|
|
219
|
+
<span class="item-count">{{ store.user ? "auth" : "null" }}</span>
|
|
220
|
+
</h3>
|
|
221
|
+
<div v-if="expandedSections.user" class="section-content">
|
|
222
|
+
<div v-if="!store.user" class="empty-state">
|
|
223
|
+
No user logged in (store.user === null)
|
|
224
|
+
</div>
|
|
225
|
+
<div v-else class="property-list">
|
|
226
|
+
<div
|
|
227
|
+
v-for="(value, key) in store.user"
|
|
228
|
+
:key="key"
|
|
229
|
+
class="property-item"
|
|
230
|
+
>
|
|
231
|
+
<span class="property-key">{{ key }}</span>
|
|
232
|
+
<span class="property-value" :class="getValueType(value)">
|
|
233
|
+
{{ formatValue(value) }}
|
|
234
|
+
</span>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
215
240
|
<div class="inspector-actions">
|
|
216
241
|
<button
|
|
217
242
|
class="action-btn"
|
|
@@ -251,6 +276,7 @@ const expandedSections = reactive({
|
|
|
251
276
|
assetList: false,
|
|
252
277
|
triggerState: false,
|
|
253
278
|
dependencyList: false,
|
|
279
|
+
user: false,
|
|
254
280
|
})
|
|
255
281
|
|
|
256
282
|
const editingKey = ref(null)
|
|
@@ -94,6 +94,19 @@ function getApiConfig() {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// Dev-only fallback user. In production the platform injects the real
|
|
98
|
+
// authenticated user (or null) — this dummy only ships when running under
|
|
99
|
+
// the Vite dev server so plugins can develop against the happy-path shape.
|
|
100
|
+
const DEV_DUMMY_USER = {
|
|
101
|
+
id: "dev-user-001",
|
|
102
|
+
first_name: "Jane",
|
|
103
|
+
last_name: "Developer",
|
|
104
|
+
name: "Jane Developer",
|
|
105
|
+
email: "jane.developer@example.com",
|
|
106
|
+
avatar: null,
|
|
107
|
+
roles: ["attendee"],
|
|
108
|
+
}
|
|
109
|
+
|
|
97
110
|
// Default values used when app-manifest.json doesn't exist or is missing keys
|
|
98
111
|
const defaultData = {
|
|
99
112
|
pluginVars: {
|
|
@@ -108,6 +121,7 @@ const defaultData = {
|
|
|
108
121
|
triggerState: {},
|
|
109
122
|
auth: null,
|
|
110
123
|
userSession: null,
|
|
124
|
+
user: import.meta.env.DEV ? { ...DEV_DUMMY_USER } : null,
|
|
111
125
|
pluginData: {},
|
|
112
126
|
portalAssets: {},
|
|
113
127
|
portal: null,
|
|
@@ -126,6 +140,7 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
|
|
|
126
140
|
// User session data (injected by platform in production)
|
|
127
141
|
const auth = ref(defaultData.auth)
|
|
128
142
|
const userSession = ref(defaultData.userSession)
|
|
143
|
+
const user = ref(defaultData.user ? { ...defaultData.user } : null)
|
|
129
144
|
const pluginData = ref({ ...defaultData.pluginData })
|
|
130
145
|
const portalAssets = ref({ ...defaultData.portalAssets })
|
|
131
146
|
const portal = ref(defaultData.portal)
|
|
@@ -599,6 +614,46 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
|
|
|
599
614
|
return permissionFlags.value.includes(flag)
|
|
600
615
|
}
|
|
601
616
|
|
|
617
|
+
/**
|
|
618
|
+
* Return the logged-in user object, or null when no user is authenticated.
|
|
619
|
+
*
|
|
620
|
+
* In production the platform injects the real user. In dev (Vite dev
|
|
621
|
+
* server) a dummy user is provided so plugins can develop against the
|
|
622
|
+
* happy path without a backend — clear it from the Dev Tools store
|
|
623
|
+
* inspector to simulate the logged-out state.
|
|
624
|
+
*/
|
|
625
|
+
function getUser() {
|
|
626
|
+
return user.value ?? null
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function isAuthenticated() {
|
|
630
|
+
return user.value !== null && user.value !== undefined
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Convenience helper — returns the user's display name (or `name`,
|
|
635
|
+
* or `first_name + last_name` if `name` is absent). Returns `fallback`
|
|
636
|
+
* when no user is logged in.
|
|
637
|
+
*/
|
|
638
|
+
function getUserName(fallback = null) {
|
|
639
|
+
const u = user.value
|
|
640
|
+
if (!u) {
|
|
641
|
+
return fallback
|
|
642
|
+
}
|
|
643
|
+
if (u.name) {
|
|
644
|
+
return u.name
|
|
645
|
+
}
|
|
646
|
+
const parts = [u.first_name, u.last_name].filter(Boolean)
|
|
647
|
+
return parts.length > 0 ? parts.join(" ") : fallback
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Returns the user's email, or `fallback` when no user is logged in.
|
|
652
|
+
*/
|
|
653
|
+
function getUserEmail(fallback = null) {
|
|
654
|
+
return user.value?.email ?? fallback
|
|
655
|
+
}
|
|
656
|
+
|
|
602
657
|
// Convenience method to add dev assets with proper URL
|
|
603
658
|
function addDevAsset(key, filename) {
|
|
604
659
|
const appPort =
|
|
@@ -640,9 +695,7 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
|
|
|
640
695
|
const callback = arg3
|
|
641
696
|
const primary = socketConnections.primary
|
|
642
697
|
if (!primary) {
|
|
643
|
-
console.warn(
|
|
644
|
-
"[GxP Store] listen(): primary socket not initialized",
|
|
645
|
-
)
|
|
698
|
+
console.warn("[GxP Store] listen(): primary socket not initialized")
|
|
646
699
|
return () => {}
|
|
647
700
|
}
|
|
648
701
|
if (
|
|
@@ -753,6 +806,7 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
|
|
|
753
806
|
permissionFlags,
|
|
754
807
|
auth,
|
|
755
808
|
userSession,
|
|
809
|
+
user,
|
|
756
810
|
pluginData,
|
|
757
811
|
portalAssets,
|
|
758
812
|
portal,
|
|
@@ -775,6 +829,10 @@ export const useGxpStore = defineStore("gxp-portal-app", () => {
|
|
|
775
829
|
getSetting,
|
|
776
830
|
getAsset,
|
|
777
831
|
getState,
|
|
832
|
+
getUser,
|
|
833
|
+
getUserName,
|
|
834
|
+
getUserEmail,
|
|
835
|
+
isAuthenticated,
|
|
778
836
|
hasPermission,
|
|
779
837
|
findDependency,
|
|
780
838
|
|
|
@@ -9,6 +9,19 @@ model: sonnet
|
|
|
9
9
|
|
|
10
10
|
You are an expert GxP plugin developer. You help build Vue 3 components for the GxP kiosk platform. You have access to the `gxp-api` MCP server — use it; do not guess at API shapes.
|
|
11
11
|
|
|
12
|
+
## Platform vs. plugin — read first
|
|
13
|
+
|
|
14
|
+
The **GxP platform itself** (not the plugin) manages all admin configuration. Plugins consume what the platform provides:
|
|
15
|
+
|
|
16
|
+
- **Forms, quizzes, surveys, and the quiz builder** are admin-built in the platform UI. The plugin reads admin-built form fields, quiz questions, scoring rules, leaderboards, and response data through `store.callApi` — never define them locally. Key operation families (discover the full set with `api_list_operation_ids` / `search_api_endpoints`):
|
|
17
|
+
- **Forms** — `forms.index`, `forms.show`, `forms.fields.index`, `forms.fields.show`, `forms.responses.index/store/show/update/destroy`, `my-responses.index/show`.
|
|
18
|
+
- **Quizzes** — `quiz.state`, `quiz.start`, `quiz.restart`, `quiz.questions`, `quiz.answer`, `quiz.answer.status`, `quiz.complete`, `quiz.timeout`, `quiz.leaderboard`.
|
|
19
|
+
- **Surveys** — `survey.metrics`, `survey.metrics.question`, `survey.metrics.questions`, `survey.metrics.timeseries`, `survey.live-results`.
|
|
20
|
+
- **Project settings, assets, strings, dependencies, permissions, and the logged-in user** are injected into the GxP store at boot. The plugin reads them via `store.getSetting/getString/getAsset/getState/hasPermission/getUser`.
|
|
21
|
+
- The plugin's `app-manifest.json` + `configuration.json` describe **only what the admin needs to configure for this specific plugin instance** — text, images, colors, which form/quiz the plugin should bind to (via `asyncSelect` against the platform list endpoints). Anything that already exists in the platform belongs upstream; don't recreate it.
|
|
22
|
+
|
|
23
|
+
If you're unsure whether the platform already provides something, search before building.
|
|
24
|
+
|
|
12
25
|
## Workflow — Follow This Every Time
|
|
13
26
|
|
|
14
27
|
Every plugin feature goes through seven phases. Do not skip phases. Do not implement before the plan is confirmed.
|
|
@@ -189,8 +202,19 @@ store.getSetting("primary_color", "#FFD600")
|
|
|
189
202
|
store.getAsset("hero_image", "/fallback.jpg")
|
|
190
203
|
store.getState("current_step", 0)
|
|
191
204
|
store.hasPermission("admin")
|
|
205
|
+
|
|
206
|
+
// Logged-in user (returns null when no user is authenticated)
|
|
207
|
+
store.getUser() // Full user object or null
|
|
208
|
+
store.getUserName("Guest") // Display name with fallback
|
|
209
|
+
store.getUserEmail() // Email or null
|
|
210
|
+
store.isAuthenticated() // boolean
|
|
192
211
|
```
|
|
193
212
|
|
|
213
|
+
`store.user` is **null when no user is logged in** — always guard. The
|
|
214
|
+
shape is `{ id, first_name, last_name, name, email, avatar, roles[] }`.
|
|
215
|
+
The platform injects this in production; `gxdev dev` ships a dummy
|
|
216
|
+
authenticated user so happy-path UI renders without a backend.
|
|
217
|
+
|
|
194
218
|
## API Calls — `store.callApi(operationId, identifier, data)`
|
|
195
219
|
|
|
196
220
|
**Every call to the GxP platform goes through `store.callApi`.** It is the primary, permission-aware API method. The low-level verb methods (`apiGet`/`apiPost`/...) still exist as escape hatches but they bypass the permission model — prefer `callApi` for all real work. Never use axios or fetch directly.
|
package/template/AGENTS.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
This is a GxP plugin project for the GxP kiosk platform. Follow these guidelines when working with this codebase.
|
|
4
4
|
|
|
5
|
+
## Platform vs. plugin — who owns what
|
|
6
|
+
|
|
7
|
+
**The GxP platform itself owns all admin configuration.** Plugins are consumers, not configurators:
|
|
8
|
+
|
|
9
|
+
- **Forms, quizzes, and surveys (including the quiz builder)** are built by admins in the platform UI. The plugin never defines fields, questions, scoring rules, leaderboards, or response schemas. At runtime the plugin reads the admin-built form/quiz/survey — and its questions — through `store.callApi`. Relevant operations: `forms.show`, `forms.fields.index`, `forms.responses.store/show/update`, `quiz.state`, `quiz.start`, `quiz.questions`, `quiz.answer`, `quiz.complete`, `quiz.leaderboard`, `survey.metrics`, `survey.live-results`. Discover the full set with `api_list_operation_ids` filtered by the `forms`/`quiz`/`survey` tags.
|
|
10
|
+
- **Project settings, assets, strings, dependencies, permissions, and the logged-in user** are injected into the GxP store at boot — plugins read them, they don't create them.
|
|
11
|
+
- The plugin's own `app-manifest.json` + `configuration.json` describe **what the admin must configure for THIS plugin instance** (text, images, colors, which form/quiz to bind to, etc.). Everything else lives upstream.
|
|
12
|
+
|
|
13
|
+
When in doubt, search for the operation via `search_api_endpoints` / `api_list_tags` before inventing local state.
|
|
14
|
+
|
|
5
15
|
## Development Workflow
|
|
6
16
|
|
|
7
17
|
Every task starts with understanding and ends with a validated, linted build. Do not skip steps.
|
|
@@ -222,6 +232,22 @@ store.updateAsset("key", "url")
|
|
|
222
232
|
store.updateState("key", "value")
|
|
223
233
|
```
|
|
224
234
|
|
|
235
|
+
### Logged-in user
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
const user = store.getUser() // Full user object, or `null` if logged out
|
|
239
|
+
store.getUserName("Guest") // Display name with fallback
|
|
240
|
+
store.getUserEmail() // Email or null
|
|
241
|
+
store.isAuthenticated() // boolean
|
|
242
|
+
// Or read the ref directly: store.user
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`store.user` is **`null` when no user is authenticated** — always guard
|
|
246
|
+
before dereferencing. Shape: `{ id, first_name, last_name, name, email,
|
|
247
|
+
avatar, roles[] }`. In `gxdev dev` a dummy authenticated user is injected
|
|
248
|
+
so the happy path renders without a backend; in production the platform
|
|
249
|
+
injects the real user.
|
|
250
|
+
|
|
225
251
|
## Real-Time Events
|
|
226
252
|
|
|
227
253
|
Every plugin has two streams of real-time events, both surfaced through the store:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# GxP Plugin — Claude Code Instructions
|
|
2
|
+
|
|
3
|
+
This is a GxP plugin project for the GxP kiosk platform. Use the shared
|
|
4
|
+
project guidelines below for everything you do here.
|
|
5
|
+
|
|
6
|
+
## Project guidelines (shared with Codex/Cursor)
|
|
7
|
+
|
|
8
|
+
@AGENTS.md
|
|
9
|
+
|
|
10
|
+
## Claude Code specifics
|
|
11
|
+
|
|
12
|
+
- **Subagent:** `.claude/agents/gxp-developer.md` — auto-invoked for GxP
|
|
13
|
+
plugin tasks. Use it for any non-trivial Vue/store/`callApi` work.
|
|
14
|
+
- **MCP servers:** wired in `.mcp.json` at the project root.
|
|
15
|
+
- `gxp-api` (via `mcp-serve` on PATH) — API specs, data models,
|
|
16
|
+
config/manifest editing, docs search, test helpers.
|
|
17
|
+
- `gxp-uikit-storybook` — UIKit component/story tools (only available
|
|
18
|
+
when `gxdev storybook` is running, served at
|
|
19
|
+
`http://localhost:6006/mcp`).
|
|
20
|
+
- **Settings:** `.claude/settings.json` — pre-allows the `gxp-api` MCP
|
|
21
|
+
tools so you don't get prompted on every call.
|
|
22
|
+
|
|
23
|
+
If the `api_*` / `config_*` / `docs_*` MCP tools aren't available, run
|
|
24
|
+
`claude mcp add gxp-api mcp-serve` and restart the session before
|
|
25
|
+
proceeding. Do not invent endpoints — discover them through the MCP.
|
package/template/GEMINI.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This is a GxP plugin project for the GxP kiosk platform built with Vue 3.
|
|
4
4
|
|
|
5
|
+
## Platform vs. plugin
|
|
6
|
+
|
|
7
|
+
The GxP platform owns admin configuration. Plugins consume it, they don't define it.
|
|
8
|
+
|
|
9
|
+
- **Forms, quizzes, surveys (including the quiz builder) are built in the platform admin UI.** The plugin reads admin-built fields/questions/scoring/leaderboards at runtime via `store.callApi` — operations like `forms.show`, `forms.fields.index`, `forms.responses.store`, `quiz.state`, `quiz.questions`, `quiz.answer`, `quiz.leaderboard`, `survey.metrics`. Use `api_list_operation_ids` / `search_api_endpoints` to discover the full set.
|
|
10
|
+
- **Project settings, assets, strings, dependencies, permissions, and the logged-in user** are injected into the GxP store at boot.
|
|
11
|
+
- The plugin's `app-manifest.json` + `configuration.json` describe only what the admin needs to configure for this plugin instance (text, images, colors, which form/quiz to bind to). Don't reinvent platform features locally.
|
|
12
|
+
|
|
5
13
|
## Development Workflow
|
|
6
14
|
|
|
7
15
|
Work through these steps in order. Do not skip.
|
|
@@ -157,6 +165,12 @@ store.updateString("key", "value")
|
|
|
157
165
|
store.updateSetting("key", "value")
|
|
158
166
|
store.updateAsset("key", "url")
|
|
159
167
|
store.updateState("key", "value")
|
|
168
|
+
|
|
169
|
+
// Logged-in user — returns `null` when no user is authenticated
|
|
170
|
+
store.getUser() // Full user object or null
|
|
171
|
+
store.getUserName("Guest") // Display name with fallback
|
|
172
|
+
store.getUserEmail() // Email or null
|
|
173
|
+
store.isAuthenticated() // boolean
|
|
160
174
|
```
|
|
161
175
|
|
|
162
176
|
## Real-Time Events
|