@gscdump/devframe 0.0.0-stage → 5.7.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Harlan Wilton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,105 @@
1
- # Temporary Holding Version
1
+ # @gscdump/devframe
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Search Console data for the page you are developing, in Vite DevTools, Nuxt DevTools, and any [devframe](https://devfra.me) hub.
4
+
5
+ The dock shows clicks, impressions, CTR, and average position for the page that is open in your app, with the change against the previous window, a daily chart, and the top queries. It follows client-side navigation. Type any path or URL to read another page.
6
+
7
+ The data comes from your gscdump [Hosted record](https://gscdump.com), so the devtool makes no calls to Google.
8
+
9
+ ```bash
10
+ npm install -D @gscdump/devframe
11
+ ```
12
+
13
+ ## Vite
14
+
15
+ Enable Vite DevTools and add the plugin:
16
+
17
+ ```ts
18
+ // vite.config.ts
19
+ import { gscdump } from '@gscdump/devframe/vite'
20
+ import { defineConfig } from 'vite'
21
+
22
+ export default defineConfig({
23
+ devtools: true,
24
+ plugins: [gscdump({ site: 'example.com' })],
25
+ })
26
+ ```
27
+
28
+ `@gscdump/devframe/vite` needs `@vitejs/devtools` in the project.
29
+
30
+ ## Nuxt
31
+
32
+ Nuxt DevTools 4 hosts Vite DevTools docks. Add the plugin to Nuxt's Vite config:
33
+
34
+ ```ts
35
+ // nuxt.config.ts
36
+ import { gscdump } from '@gscdump/devframe/vite'
37
+
38
+ export default defineNuxtConfig({
39
+ devtools: { enabled: true },
40
+ vite: {
41
+ plugins: [gscdump({ site: 'example.com' })],
42
+ },
43
+ })
44
+ ```
45
+
46
+ ## Any devframe hub
47
+
48
+ `createGscdumpDevframe()` returns a devframe definition for any hub or adapter:
49
+
50
+ ```ts
51
+ import { initHub } from '@devframes/hub/initiate'
52
+ import { createGscdumpDevframe } from '@gscdump/devframe'
53
+
54
+ const hub = initHub({
55
+ base: '/__devframes/',
56
+ devframes: [createGscdumpDevframe({ site: 'example.com' })],
57
+ })
58
+ ```
59
+
60
+ ## Credentials
61
+
62
+ The devtool reads with a Hosted credential. It looks in this order:
63
+
64
+ 1. The `apiKey` option.
65
+ 2. The `GSCDUMP_API_KEY` environment variable: a user API key from gscdump.com.
66
+ 3. The CLI's Hosted login. Run `gscdump auth login --mode hosted` once.
67
+
68
+ The credential stays in the dev server process. The panel never receives it.
69
+
70
+ ## Options
71
+
72
+ | Option | Default | Description |
73
+ | --- | --- | --- |
74
+ | `site` | The only Site | The Site to read: a Site ID, a Site URL, or its host. If the credential holds several Sites and `site` is not set, the panel asks you to pick one. |
75
+ | `apiKey` | See [Credentials](#credentials) | A gscdump user API key. |
76
+ | `apiRoot` | `GSCDUMP_API_ROOT`, then `https://gscdump.com/api` | The Hosted API root. |
77
+
78
+ ## Pages and windows
79
+
80
+ Search Console stores a Site's pages by path. The devtool reads the path of the page in view, with its query string, and ignores the dev server's origin. Search Console matches a path exactly, so `/blog/` and `/blog` are different pages.
81
+
82
+ The window ends three days before today in Pacific time, because Google has not finalized newer days. The gscdump.com dashboard uses the same end. If the Hosted record ends earlier, the window ends on its last day. The comparison window is the same number of days just before it.
83
+
84
+ ## Chrome extension (prototype)
85
+
86
+ The same panel also builds as a Chrome side panel. It shows the stats for the page in the current tab, on your live site. It is not on the Chrome Web Store yet.
87
+
88
+ ```bash
89
+ pnpm --filter @gscdump/devframe build:extension
90
+ ```
91
+
92
+ 1. Open `chrome://extensions` and turn on Developer mode.
93
+ 2. Select Load unpacked, then select `packages/devframe/dist-extension`.
94
+ 3. Sign in to gscdump.com in the same browser.
95
+ 4. Open a page on one of your Sites, then select the extension's toolbar button.
96
+
97
+ The extension stores no API key. It reads your Sites with your gscdump.com login, and the tab's host selects the Site. It asks for two permissions: `tabs`, to read the current tab's address, and access to gscdump.com.
98
+
99
+ ## Coding agents
100
+
101
+ The `get-page-stats` function is also an MCP tool. When the host serves MCP (Vite DevTools does), a coding agent can read a page's clicks, impressions, CTR, position, and top queries while it changes the page.
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,77 @@
1
+ import { cliHeadersOf } from "./credential.mjs";
2
+ import { hostOf } from "./source.mjs";
3
+ import { z } from "zod";
4
+ const accountSchema = z.object({ sites: z.array(z.object({
5
+ siteId: z.string(),
6
+ siteUrl: z.string(),
7
+ oldestDateSynced: z.string().nullable().optional(),
8
+ newestDateSynced: z.string().nullable().optional()
9
+ }).passthrough()) }).passthrough();
10
+ async function readAccount(credential, request) {
11
+ const response = await request(`${credential.apiRoot}/cli/me`, {
12
+ headers: cliHeadersOf(credential),
13
+ redirect: "error",
14
+ signal: AbortSignal.timeout(3e4)
15
+ }).catch((error) => error instanceof Error ? error : new Error(String(error)));
16
+ if (response instanceof Error) return {
17
+ _tag: "Unavailable",
18
+ message: `gscdump.com did not answer: ${response.message}`
19
+ };
20
+ if (response.status === 401 || response.status === 403) return { _tag: "Rejected" };
21
+ if (!response.ok) return {
22
+ _tag: "Unavailable",
23
+ message: `gscdump.com answered ${response.status}.`
24
+ };
25
+ const parsed = accountSchema.safeParse(await response.json().catch(() => null));
26
+ if (!parsed.success) return {
27
+ _tag: "Unavailable",
28
+ message: "gscdump.com returned account data this version cannot read."
29
+ };
30
+ return {
31
+ _tag: "Ok",
32
+ sites: parsed.data.sites.map((site) => ({
33
+ siteId: site.siteId,
34
+ siteUrl: site.siteUrl,
35
+ host: hostOf(site.siteUrl) ?? site.siteUrl,
36
+ oldestDate: site.oldestDateSynced ?? null,
37
+ newestDate: site.newestDateSynced ?? null
38
+ }))
39
+ };
40
+ }
41
+ function siteKey(value) {
42
+ const text = value.trim();
43
+ if (text.startsWith("sc-domain:")) return text.slice(10).toLowerCase().replace(/\.$/, "") || void 0;
44
+ const url = URL.parse(/^[a-z][a-z\d+.-]*:\/\//i.test(text) ? text : `https://${text}`);
45
+ if (!url || !url.hostname) return void 0;
46
+ return `${url.hostname.toLowerCase()}${url.pathname.replace(/\/+$/, "")}`;
47
+ }
48
+ function matchSite(sites, target) {
49
+ if (!target) return sites.length === 1 ? {
50
+ _tag: "Found",
51
+ site: sites[0]
52
+ } : {
53
+ _tag: "Missing",
54
+ target: null
55
+ };
56
+ const exact = sites.find((site) => site.siteId === target || site.siteUrl === target);
57
+ if (exact) return {
58
+ _tag: "Found",
59
+ site: exact
60
+ };
61
+ const key = siteKey(target);
62
+ const matches = key === void 0 ? [] : sites.filter((site) => siteKey(site.siteUrl) === key);
63
+ if (matches.length === 1) return {
64
+ _tag: "Found",
65
+ site: matches[0]
66
+ };
67
+ if (matches.length > 1) return {
68
+ _tag: "Ambiguous",
69
+ target,
70
+ matches
71
+ };
72
+ return {
73
+ _tag: "Missing",
74
+ target
75
+ };
76
+ }
77
+ export { matchSite, readAccount };
@@ -0,0 +1 @@
1
+ import{n as e,r as t,t as n}from"./index-DA7Lgn7_.js";var r=`devframe:client-id`,i;function a(e=globalThis.window){try{let n=e?.sessionStorage;if(n){let e=n.getItem(r);return e||(e=t(),n.setItem(r,e)),e}}catch{}return i??=t(),i}function o(t){t.client.register({name:`devframe:agent:invoke-client-tool`,type:`action`,jsonSerializable:!0,handler:async(e,t)=>{let r=n().find(t=>t.id===e);if(!r)throw Error(`[devframe/agent] browser tool "${e}" not found`);return await r.invoke(t)}});let r=!1,i=!1,o=0,s=()=>{r||i||(r=!0,queueMicrotask(async()=>{if(r=!1,i)return;let e=n().map(({invoke:e,...t})=>t);(e.length!==0||o!==0)&&(o=e.length,await t.callOptional(`devframe:agent:sync-client-tools`,a(),e).catch(()=>{}))}))},c=e(s),l=t.events.on(`connection:status`,e=>{e===`connected`&&s()});return s(),()=>{i=!0,c(),l()}}export{o as setupBrowserAgentRpcBridge};
@@ -0,0 +1 @@
1
+ .chart[data-v-83d970de]{width:100%;position:relative}svg[data-v-83d970de]{display:block;overflow:visible}.legend[data-v-83d970de]{flex-wrap:wrap;align-items:center;gap:4px 14px;margin-bottom:6px;font-size:12px;display:flex}.legend .metric[data-v-83d970de]{margin-left:auto}.item[data-v-83d970de],.row[data-v-83d970de]{align-items:center;gap:6px;display:inline-flex}.key[data-v-83d970de]{border-top:2px solid var(--series);width:14px;height:0;display:inline-block}.key.dashed[data-v-83d970de]{opacity:.55;border-top-style:dashed}.grid line[data-v-83d970de]{stroke:var(--grid);stroke-width:1px}.grid text[data-v-83d970de]{fill:var(--dimmed);font-size:11px}.line[data-v-83d970de]{fill:none;stroke:var(--series);stroke-width:2px;stroke-linejoin:round;stroke-linecap:round}.line.previous[data-v-83d970de]{stroke-dasharray:4 4;opacity:.55}.area[data-v-83d970de]{fill:var(--series);opacity:.1}.crosshair[data-v-83d970de]{stroke:var(--dimmed);stroke-width:1px}.marker[data-v-83d970de]{fill:var(--series);stroke:var(--surface);stroke-width:2px}.marker.previous[data-v-83d970de]{opacity:.55}.hit[data-v-83d970de]{fill:#0000;cursor:crosshair}.tooltip[data-v-83d970de]{border:1px solid var(--border);background:var(--surface);pointer-events:none;white-space:nowrap;border-radius:6px;gap:2px;padding:6px 8px;font-size:12px;display:grid;position:absolute;top:30px;transform:translate(10px);box-shadow:0 4px 12px #0000001f}.tooltip.flip[data-v-83d970de]{transform:translate(calc(-100% - 10px))}.tooltip strong[data-v-83d970de]{margin-left:8px}.frame[data-v-244bcef0]{container-type:inline-size}.short[data-v-244bcef0]{display:none}@container (width<=520px){.full[data-v-244bcef0]{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.short[data-v-244bcef0]{display:inline}th.query[data-v-244bcef0]{width:40%}}.queries[data-v-244bcef0]{border-collapse:collapse;table-layout:fixed;width:100%;font-size:13px}th[data-v-244bcef0],td[data-v-244bcef0]{border-bottom:1px solid var(--grid);text-align:right;white-space:nowrap;padding:6px 8px}thead th[data-v-244bcef0]{color:var(--muted);font-size:12px;font-weight:500}th.query[data-v-244bcef0]{text-align:left;text-overflow:ellipsis;width:46%;font-weight:400;overflow:hidden}thead th.query[data-v-244bcef0]{font-weight:500}tbody tr:last-child th[data-v-244bcef0],tbody tr:last-child td[data-v-244bcef0]{border-bottom:0}.tile[data-v-b318b2b5]{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);text-align:left;cursor:pointer;flex-direction:column;align-items:flex-start;gap:2px;min-width:0;padding:10px 12px;display:flex}.tile[data-v-b318b2b5]:hover{border-color:var(--dimmed)}.tile.selected[data-v-b318b2b5]{border-color:var(--series);box-shadow:inset 0 -2px 0 var(--series)}.label[data-v-b318b2b5]{color:var(--muted);align-items:center;gap:6px;font-size:12px;display:inline-flex}.key[data-v-b318b2b5]{background:var(--series);border-radius:1px;width:10px;height:2px}.value[data-v-b318b2b5]{font-size:22px;font-weight:600;line-height:1.2}.delta[data-v-b318b2b5]{color:var(--muted);font-size:12px}.delta.good[data-v-b318b2b5]{color:var(--good)}.delta.bad[data-v-b318b2b5]{color:var(--bad)}.panel[data-v-5b06b029]{flex-direction:column;gap:12px;max-width:1100px;margin:0 auto;padding:14px 16px 24px;display:flex}.header[data-v-5b06b029]{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px 16px;display:flex}.brand[data-v-5b06b029]{align-items:baseline;gap:8px;display:flex}.controls[data-v-5b06b029]{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.site[data-v-5b06b029]{color:var(--muted);font-size:13px}.page-bar[data-v-5b06b029]{gap:8px;display:flex}.page-field[data-v-5b06b029]{flex:1;min-width:0}.page-field input[data-v-5b06b029]{width:100%}.button[data-v-5b06b029]:disabled{opacity:.5;cursor:not-allowed}.notice[data-v-5b06b029]{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);margin:0;padding:12px 14px}.notice p[data-v-5b06b029]{margin:6px 0}.notice ul[data-v-5b06b029]{margin:6px 0 10px;padding-left:18px}.notice.error[data-v-5b06b029]{border-color:color-mix(in srgb, var(--bad) 40%, var(--border))}.actions[data-v-5b06b029]{gap:8px;display:flex}.site-list[data-v-5b06b029]{flex-wrap:wrap;gap:8px;margin-top:8px;display:flex}.link-button[data-v-5b06b029]{color:var(--link);cursor:pointer;background:0 0;border:0;padding:0;text-decoration:underline}.report[data-v-5b06b029]{flex-direction:column;gap:12px;transition:opacity .12s;display:flex}.report.loading[data-v-5b06b029]{opacity:.6}.tiles[data-v-5b06b029]{grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:8px;display:grid}.block[data-v-5b06b029]{padding:12px}.section-title[data-v-5b06b029]{margin:0 0 6px;font-size:13px;font-weight:600}.footer[data-v-5b06b029]{flex-wrap:wrap;justify-content:space-between;gap:8px;font-size:13px;display:flex}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--bg:#fbf8f3;--surface:#fff;--border:#e6dfd3;--grid:#ece6db;--text:#1b2130;--muted:#555b66;--dimmed:#7a808b;--primary:#c23e00;--link:#345fb2;--good:#1a7f37;--bad:#b42318;--metric-clicks:var(--primary);--metric-impressions:var(--link);--metric-ctr:#0f766e;--metric-position:#6b5a9e;--radius:8px;color:var(--text);background:var(--bg);font-family:Geist,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;font-size:14px;line-height:1.5}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--bg:#080b14;--surface:#131722;--border:#262c3a;--grid:#1d2230;--text:#e9e5e0;--muted:#a3a8b3;--dimmed:#7d8390;--primary:#f8894b;--link:#82b5ef;--good:#4ac26b;--bad:#f97066;--metric-ctr:#4fc1ae;--metric-position:#a993f0}}*{box-sizing:border-box}body{background:var(--bg);margin:0}button,input,select{font:inherit;color:inherit}a{color:var(--link)}.mono{font-family:Geist Mono,ui-monospace,SFMono-Regular,Menlo,monospace}.tabular{font-variant-numeric:tabular-nums}.muted{color:var(--muted)}.sr-only{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.control{border:1px solid var(--border);background:var(--surface);border-radius:6px;height:32px;padding:0 10px}.control:focus-visible,button:focus-visible,a:focus-visible{outline:2px solid var(--link);outline-offset:1px}.button{border:1px solid var(--border);background:var(--surface);cursor:pointer;white-space:nowrap;height:32px;color:var(--text);border-radius:6px;align-items:center;gap:6px;padding:0 12px;text-decoration:none;display:inline-flex}.button:hover{border-color:var(--dimmed)}.button[aria-pressed=true]{border-color:var(--primary);color:var(--primary)}.card{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}