@dcoder-x/vite 0.1.5 → 0.1.6

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 (2) hide show
  1. package/README.md +348 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # @dcoder-x/vite
2
+
3
+ Clippy build plugin for **Vite** (React, SvelteKit, Remix, and any Vite-based project). At compile time the plugin:
4
+
5
+ 1. Injects stable `data-clippy-id` attributes into every interactive HTML element in your compiled output — never modifying your source files.
6
+ 2. Analyzes your component tree to extract state variables, event handlers, and conditional render relationships.
7
+ 3. Discovers all routes, navigation links, and user-flow paths.
8
+ 4. Emits two JSON artifacts — `clippy-policy.json` and `clippy-selectors.json` — that the Clippy runtime uses to execute user prompts locally without LLM calls.
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @dcoder-x/vite
16
+ # or
17
+ pnpm add @dcoder-x/vite
18
+ # or
19
+ yarn add @dcoder-x/vite
20
+ ```
21
+
22
+ `@dcoder-x/plugin-shared` is a transitive dependency and is installed automatically. You do not need to install it separately.
23
+
24
+ **Peer dependencies** (already present in any Vite project):
25
+
26
+ ```bash
27
+ npm install --save-dev vite @babel/parser @babel/traverse
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Quick start
33
+
34
+ Add `clippyVitePlugin` to your Vite plugin array:
35
+
36
+ ```ts
37
+ // vite.config.ts
38
+ import { defineConfig } from 'vite'
39
+ import react from '@vitejs/plugin-react'
40
+ import { clippyVitePlugin } from '@dcoder-x/vite'
41
+
42
+ export default defineConfig({
43
+ plugins: [
44
+ react(),
45
+ clippyVitePlugin({
46
+ apiKey: process.env.CLIPPY_API_KEY!,
47
+ projectId: process.env.CLIPPY_PROJECT_ID!,
48
+ }),
49
+ ],
50
+ })
51
+ ```
52
+
53
+ The plugin runs automatically during `vite build` — no additional scripts or CI steps required. It is a no-op during `vite dev` (apply mode is `'build'` only).
54
+
55
+ ---
56
+
57
+ ## Configuration
58
+
59
+ ```ts
60
+ interface ClippyPluginOptions {
61
+ /** Your Clippy API key. Get this from the Clippy developer dashboard. */
62
+ apiKey: string
63
+
64
+ /** Your Clippy project ID. Found in the project settings page. */
65
+ projectId: string
66
+
67
+ /**
68
+ * Skip uploading artifacts to the Clippy backend.
69
+ * Useful when you want to inspect the output before uploading.
70
+ * Artifacts are still written if localOutputDir is set.
71
+ * Default: false
72
+ */
73
+ skipUpload?: boolean
74
+
75
+ /**
76
+ * Only run the plugin during production builds (NODE_ENV === 'production').
77
+ * Default: false
78
+ */
79
+ productionOnly?: boolean
80
+
81
+ /**
82
+ * Write the generated JSON artifacts to a local directory in addition to
83
+ * (or instead of) uploading them.
84
+ *
85
+ * Two files are written:
86
+ * <localOutputDir>/clippy-policy.json
87
+ * <localOutputDir>/clippy-selectors.json
88
+ *
89
+ * Example: '.clippy-output'
90
+ */
91
+ localOutputDir?: string
92
+
93
+ /**
94
+ * How to upload artifacts to the Clippy backend.
95
+ *
96
+ * 'split' — upload clippy-policy.json and clippy-selectors.json as separate
97
+ * requests. Recommended for large projects.
98
+ * 'single' — bundle both artifacts into one gzipped payload.
99
+ *
100
+ * Default: 'split'
101
+ */
102
+ artifactUploadMode?: 'single' | 'split'
103
+
104
+ /**
105
+ * Provide a custom upload function. When set, the built-in HTTP uploader
106
+ * is bypassed entirely. Use this to integrate with your own artifact
107
+ * storage, a proxy endpoint, or a CI/CD pipeline step.
108
+ *
109
+ * Example:
110
+ *
111
+ * uploadAdapter: {
112
+ * uploadArtifacts: async (artifacts) => {
113
+ * await myApi.uploadPolicy(artifacts.policy)
114
+ * await myApi.uploadSelectors(artifacts.selectorManifest)
115
+ * return { skipped: false, policyUploaded: true, selectorsUploaded: true, mode: 'split' }
116
+ * }
117
+ * }
118
+ */
119
+ uploadAdapter?: {
120
+ uploadArtifacts?: (artifacts: PolicyArtifacts) => Promise<UploadResult>
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Recommended environment variable setup
126
+
127
+ ```bash
128
+ # .env (never commit API keys — use .env.local or CI secrets)
129
+ CLIPPY_API_KEY=pk_live_xxxxxxxxxxxx
130
+ CLIPPY_PROJECT_ID=proj_xxxxxxxxxxxx
131
+ ```
132
+
133
+ In Vite, environment variables must be prefixed with `VITE_` to be exposed to the browser. Clippy's API key and project ID are **only used at build time** inside the plugin, so they do not need the `VITE_` prefix and should not be exposed to the browser.
134
+
135
+ ```ts
136
+ // Access in vite.config.ts via process.env (not import.meta.env)
137
+ clippyVitePlugin({
138
+ apiKey: process.env.CLIPPY_API_KEY!,
139
+ projectId: process.env.CLIPPY_PROJECT_ID!,
140
+ })
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Output files
146
+
147
+ The plugin produces two files at the end of each build. Both share the same `buildId` so the Clippy runtime can link them to the active build.
148
+
149
+ ### `clippy-selectors.json`
150
+
151
+ A flat, deduplicated manifest of every interactive element across all routes. Used for LLM context injection and selector health monitoring.
152
+
153
+ ```jsonc
154
+ {
155
+ "version": "1.0.0",
156
+ "buildId": "abc123",
157
+ "generatedAt": "2026-06-02T10:00:00.000Z",
158
+ "selectors": [
159
+ {
160
+ // Stable ID injected as data-clippy-id in the compiled DOM.
161
+ // Format: ComponentName[-LabelText]-tag-lineNumber
162
+ "id": "SettingsPage-SaveChanges-button-205",
163
+
164
+ // CSS selector resolving this element in the live DOM.
165
+ "selector": "[data-clippy-id='SettingsPage-SaveChanges-button-205']",
166
+
167
+ "component": "SettingsPage",
168
+ "tag": "button",
169
+
170
+ // Human-readable label from aria-label, visible text, or placeholder.
171
+ // null when no static text is available.
172
+ "label": "Save Changes",
173
+
174
+ // All routes where this element is rendered.
175
+ // Shared components list multiple routes here rather than repeating.
176
+ "routes": ["/dashboard/settings"]
177
+ }
178
+ ]
179
+ }
180
+ ```
181
+
182
+ ### `clippy-policy.json`
183
+
184
+ The full knowledge document used by the Policy Executor for Tier 1 (no-LLM) flow execution and by the backend for LLM context enrichment.
185
+
186
+ ```jsonc
187
+ {
188
+ "version": "1.0.0",
189
+ "buildId": "abc123",
190
+ "generatedAt": "2026-06-02T10:00:00.000Z",
191
+ "bundler": "vite",
192
+
193
+ // All discovered routes with relative file paths.
194
+ "routes": [
195
+ {
196
+ "path": "/dashboard/settings",
197
+ "filePath": "src/pages/settings.tsx",
198
+ "isDynamic": false,
199
+ "params": [],
200
+ "layout": null,
201
+ "routerType": "react-router",
202
+ "semantic": "settings dashboard"
203
+ }
204
+ ],
205
+
206
+ // All interactive elements with selector candidates.
207
+ "selectors": [ /* ... */ ],
208
+
209
+ // Components with meaningful state or interaction data.
210
+ // Purely presentational components (Button, Card, etc.) are excluded.
211
+ "components": [
212
+ {
213
+ "name": "SettingsPage",
214
+ "filePath": "src/pages/settings.tsx",
215
+ "route": "/dashboard/settings",
216
+ "stateVariables": [
217
+ { "name": "isDirty", "setter": "setIsDirty" }
218
+ ],
219
+ "interactions": [
220
+ {
221
+ "trigger": { "event": "onClick", "element": "button", "setsState": "isDirty" },
222
+ "effect": {
223
+ "type": "asyncEffect",
224
+ "waitStrategy": "domSettle",
225
+ "settleMs": 300
226
+ }
227
+ }
228
+ ]
229
+ }
230
+ ],
231
+
232
+ // Multi-step navigation flows inferred from Link/anchor elements.
233
+ "flows": [
234
+ {
235
+ "flowId": "flow_1",
236
+ "page": "/auth/login",
237
+ "intentPatterns": ["log in", "sign in", "login"],
238
+ "steps": [
239
+ { "step": 1, "action": "navigate", "target": "[data-clippy-id='LoginPage-form-115']" },
240
+ { "step": 2, "action": "transition", "target": "[data-clippy-id='Dashboard-a-42']" }
241
+ ]
242
+ }
243
+ ]
244
+ }
245
+ ```
246
+
247
+ ---
248
+
249
+ ## How stable selectors work
250
+
251
+ The plugin transforms JSX at compile time — **not** your source files — to add two attributes to every native HTML element (`button`, `input`, `a`, `form`, `select`, `textarea`, `label`):
252
+
253
+ ```html
254
+ <!-- Your source (never modified): -->
255
+ <button>Save Changes</button>
256
+
257
+ <!-- Compiled output (injected by the plugin): -->
258
+ <button
259
+ data-clippy-id="SettingsPage-SaveChanges-button-205"
260
+ data-clippy-component="SettingsPage"
261
+ >
262
+ Save Changes
263
+ </button>
264
+ ```
265
+
266
+ The `data-clippy-id` format is `ComponentName[-LabelText]-tag-lineNumber`:
267
+
268
+ - **ComponentName** — the enclosing React component, or a route-derived name for page-level components.
269
+ - **LabelText** — the element's visible text or `aria-label`, sanitized to TitleCase, max two words. Omitted when no static text is available.
270
+ - **tag** — the lowercase HTML tag.
271
+ - **lineNumber** — the element's position in the source file, acting as a stable tiebreaker when multiple elements share the same name.
272
+
273
+ IDs are stable across builds as long as the component name and the element's file position don't change. CSS class hashes change with every Vite build; `data-clippy-id` does not.
274
+
275
+ React component calls (`<Button />`, `<Modal />`) are never touched — only native HTML tags receive injected attributes.
276
+
277
+ ---
278
+
279
+ ## Router support
280
+
281
+ | Router | Detection | Notes |
282
+ |---|---|---|
283
+ | React Router | AST — `createBrowserRouter`, `<Route path>` | Detected in `src/` |
284
+ | TanStack Router | Filesystem — `src/routes/**/*.tsx` | Dot-separated filenames |
285
+ | Pages Router (Next.js via Vite) | Filesystem — `pages/**/*.tsx` | |
286
+ | App Router (Next.js via Vite) | Filesystem — `app/**/page.tsx` | Prefer `@dcoder-x/next` for Next.js |
287
+
288
+ ---
289
+
290
+ ## Using local output for inspection
291
+
292
+ ```ts
293
+ clippyVitePlugin({
294
+ apiKey: process.env.CLIPPY_API_KEY!,
295
+ projectId: process.env.CLIPPY_PROJECT_ID!,
296
+ localOutputDir: '.clippy-output',
297
+ skipUpload: true,
298
+ })
299
+ ```
300
+
301
+ Add `.clippy-output` to `.gitignore`.
302
+
303
+ ---
304
+
305
+ ## CI/CD
306
+
307
+ The plugin runs at the end of `vite build` and uploads automatically. To control the upload yourself:
308
+
309
+ ```ts
310
+ clippyVitePlugin({
311
+ apiKey: process.env.CLIPPY_API_KEY!,
312
+ projectId: process.env.CLIPPY_PROJECT_ID!,
313
+ localOutputDir: 'dist/clippy',
314
+ skipUpload: true,
315
+ uploadAdapter: {
316
+ uploadArtifacts: async (artifacts) => {
317
+ // your upload logic
318
+ },
319
+ },
320
+ })
321
+ ```
322
+
323
+ ---
324
+
325
+ ## Troubleshooting
326
+
327
+ **`data-clippy-id` is not in the DOM**
328
+
329
+ Confirm you are inspecting the production build output (`vite build`), not the dev server. The plugin only runs during the build phase.
330
+
331
+ **Selectors show `button:contains(...)` instead of `data-clippy-id`**
332
+
333
+ The `text:contains()` fallback appears when the injected ID can't be matched back to a discovered element. Adding `aria-label` to the element resolves this permanently.
334
+
335
+ **No flows are generated**
336
+
337
+ Flows are inferred from `<a href>` and `<Link to/href>` elements pointing to known routes. If your navigation uses programmatic `router.push()` without visible links, flows will not be automatically detected. Consider adding visible navigation links or using the Clippy dashboard to record flows explicitly.
338
+
339
+ **Upload is failing**
340
+
341
+ Verify `CLIPPY_API_KEY` and `CLIPPY_PROJECT_ID` are set in your build environment (not in `VITE_`-prefixed variables, which are client-only). Set `localOutputDir` to confirm artifacts are generating correctly before troubleshooting the upload.
342
+
343
+ ---
344
+
345
+ ## Related packages
346
+
347
+ - [`@dcoder-x/next`](https://www.npmjs.com/package/@dcoder-x/next) — same plugin for Next.js projects
348
+ - [`@dcoder-x/plugin-shared`](https://www.npmjs.com/package/@dcoder-x/plugin-shared) — internal shared extractors (not needed directly)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcoder-x/vite",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -18,7 +18,7 @@
18
18
  }
19
19
  },
20
20
  "dependencies": {
21
- "@dcoder-x/plugin-shared": "^0.1.6"
21
+ "@dcoder-x/plugin-shared": "^0.1.7"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "vite": ">=4.0.0",