driggsby 0.2.1 → 0.2.3
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/dist/deploy/deploy-command.js +1 -0
- package/dist/dev/dev-command.js +1 -0
- package/dist/dev/dev-servers.js +1 -1
- package/dist/dev/host-page.js +69 -5
- package/dist/init/templates.js +160 -20
- package/package.json +3 -3
|
@@ -36,6 +36,7 @@ export async function runDeploy(options, environment = defaultCredentialEnvironm
|
|
|
36
36
|
const startedAt = io.now();
|
|
37
37
|
const { outcome, createdApp } = await deployProjectFiles({ baseUrl: session.baseUrl, token: session.token }, projectDirectory, config.slug, collected, {
|
|
38
38
|
live: !options.preview,
|
|
39
|
+
...(config.background !== null ? { background: config.background } : {}),
|
|
39
40
|
// The assigned slug is server text, so it prints quoted and
|
|
40
41
|
// length-bounded like every other string this CLI did not author.
|
|
41
42
|
// A validated slug tops out at 63 characters, so the line never
|
package/dist/dev/dev-command.js
CHANGED
|
@@ -52,6 +52,7 @@ export async function runDev(options = {}, environment = defaultCredentialEnviro
|
|
|
52
52
|
try {
|
|
53
53
|
servers = await startDevServers({
|
|
54
54
|
slug: config.slug,
|
|
55
|
+
background: config.background,
|
|
55
56
|
serveDirectory: config.serveDirectory,
|
|
56
57
|
sdkBundle,
|
|
57
58
|
runToolCall: (tool, argumentsObject) => broker.runToolCall(tool, argumentsObject),
|
package/dist/dev/dev-servers.js
CHANGED
|
@@ -117,7 +117,7 @@ async function handleHostRequest(options, hostPort, appOrigin, sseClients, reque
|
|
|
117
117
|
}
|
|
118
118
|
const path = requestPath(request);
|
|
119
119
|
if (request.method === "GET" && path === "/") {
|
|
120
|
-
sendHostPage(response, hostPageHtml(options.slug, appOrigin), appOrigin);
|
|
120
|
+
sendHostPage(response, hostPageHtml(options.slug, appOrigin, options.background), appOrigin);
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
123
|
if (request.method === "GET" && path === "/host.js") {
|
package/dist/dev/host-page.js
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
// The page's script is served as its own file so the page can carry a
|
|
9
9
|
// Content-Security-Policy with no inline scripts.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
11
|
+
// All interpolations below are HTML-safe by construction, not by escaping:
|
|
12
12
|
// `slug` has passed readProjectConfig's strict slug rules (lowercase
|
|
13
|
-
// alphanumerics and dashes only) before runDev ever calls this,
|
|
13
|
+
// alphanumerics and dashes only) before runDev ever calls this,
|
|
14
14
|
// `appOrigin` is built by startDevServers from a literal plus a bound port
|
|
15
|
-
// number
|
|
16
|
-
|
|
15
|
+
// number, and `background` has passed readProjectConfig's strict hex-color
|
|
16
|
+
// rule (or is null). Anything looser than those sources must not be
|
|
17
|
+
// passed here.
|
|
18
|
+
export function hostPageHtml(slug, appOrigin, background) {
|
|
17
19
|
return `<!doctype html>
|
|
18
20
|
<html lang="en" data-app-origin="${appOrigin}">
|
|
19
21
|
<head>
|
|
@@ -45,6 +47,9 @@ export function hostPageHtml(slug, appOrigin) {
|
|
|
45
47
|
flex: 1;
|
|
46
48
|
width: 100%;
|
|
47
49
|
border: none;
|
|
50
|
+
/* The app's declared background from driggsby.json, painted while
|
|
51
|
+
the app loads — the same surface the production host shows. */
|
|
52
|
+
background: ${background ?? "#ffffff"};
|
|
48
53
|
}
|
|
49
54
|
</style>
|
|
50
55
|
</head>
|
|
@@ -66,6 +71,47 @@ export const HOST_PAGE_JS = [
|
|
|
66
71
|
"const APP_ORIGIN = document.documentElement.dataset.appOrigin;",
|
|
67
72
|
'const frame = document.getElementById("app");',
|
|
68
73
|
"",
|
|
74
|
+
"// The app's in-page location (a URL fragment), mirrored into this",
|
|
75
|
+
"// page's own URL and handed back in the hello — the same route sync",
|
|
76
|
+
"// the production host does, same strict shape on both sides.",
|
|
77
|
+
"const APP_ROUTE_PATTERN = /^#[A-Za-z0-9\\/\\-._]{1,256}$/;",
|
|
78
|
+
"",
|
|
79
|
+
"function sanitizeAppRoute(value) {",
|
|
80
|
+
' return typeof value === "string" && APP_ROUTE_PATTERN.test(value) ? value : "";',
|
|
81
|
+
"}",
|
|
82
|
+
"",
|
|
83
|
+
"let appRoute = sanitizeAppRoute(window.location.hash);",
|
|
84
|
+
"",
|
|
85
|
+
"// URL writes are bounded (leading + one trailing per window) because",
|
|
86
|
+
"// the frame controls their rate, and each write is guarded: Safari",
|
|
87
|
+
"// hard-errors on rapid replaceState bursts.",
|
|
88
|
+
"const ROUTE_WRITE_WINDOW_MS = 1000;",
|
|
89
|
+
"let routeWriteTimer = null;",
|
|
90
|
+
"",
|
|
91
|
+
"function writeRouteToUrl() {",
|
|
92
|
+
" if (window.location.hash === appRoute) {",
|
|
93
|
+
" return;",
|
|
94
|
+
" }",
|
|
95
|
+
' const base = window.location.href.split("#")[0];',
|
|
96
|
+
" try {",
|
|
97
|
+
' history.replaceState(history.state, "", base + appRoute);',
|
|
98
|
+
" } catch (error) {",
|
|
99
|
+
" // A refused write costs only URL freshness; the next one retries.",
|
|
100
|
+
" }",
|
|
101
|
+
"}",
|
|
102
|
+
"",
|
|
103
|
+
"function applyRoute(hash) {",
|
|
104
|
+
" appRoute = hash;",
|
|
105
|
+
" if (routeWriteTimer !== null) {",
|
|
106
|
+
" return;",
|
|
107
|
+
" }",
|
|
108
|
+
" writeRouteToUrl();",
|
|
109
|
+
" routeWriteTimer = setTimeout(() => {",
|
|
110
|
+
" routeWriteTimer = null;",
|
|
111
|
+
" writeRouteToUrl();",
|
|
112
|
+
" }, ROUTE_WRITE_WINDOW_MS);",
|
|
113
|
+
"}",
|
|
114
|
+
"",
|
|
69
115
|
"function postToApp(message) {",
|
|
70
116
|
" if (frame.contentWindow) {",
|
|
71
117
|
" frame.contentWindow.postMessage(message, APP_ORIGIN);",
|
|
@@ -114,7 +160,25 @@ export const HOST_PAGE_JS = [
|
|
|
114
160
|
" }",
|
|
115
161
|
" const data = event.data;",
|
|
116
162
|
' if (data.type === "ready") {',
|
|
117
|
-
|
|
163
|
+
" const hello = { protocol: PROTOCOL, type: \"hello\" };",
|
|
164
|
+
' if (appRoute !== "") {',
|
|
165
|
+
" hello.route = appRoute;",
|
|
166
|
+
" }",
|
|
167
|
+
" postToApp(hello);",
|
|
168
|
+
" return;",
|
|
169
|
+
" }",
|
|
170
|
+
' if (data.type === "route") {',
|
|
171
|
+
' // A bare "#" is the explicit cleared sentinel: the app is back at',
|
|
172
|
+
" // its no-hash location, so the page URL drops its fragment.",
|
|
173
|
+
' if (data.hash === "#") {',
|
|
174
|
+
' applyRoute("");',
|
|
175
|
+
" return;",
|
|
176
|
+
" }",
|
|
177
|
+
" const hash = sanitizeAppRoute(data.hash);",
|
|
178
|
+
' if (hash === "") {',
|
|
179
|
+
" return;",
|
|
180
|
+
" }",
|
|
181
|
+
" applyRoute(hash);",
|
|
118
182
|
" return;",
|
|
119
183
|
" }",
|
|
120
184
|
' if (data.type !== "call") {',
|
package/dist/init/templates.js
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
// (`driggsby dev` locally, or the Driggsby console after a deploy). The
|
|
5
5
|
// sample values below are invented for the template and match the real
|
|
6
6
|
// tool result shapes only in structure, so the scaffold's render functions
|
|
7
|
-
// work unchanged when live data replaces them.
|
|
7
|
+
// work unchanged when live data replaces them. The HTML ships a skeleton
|
|
8
|
+
// of the final layout so the first paint — in every context, before any
|
|
9
|
+
// script runs — is muted bars that the first render replaces, fading in
|
|
10
|
+
// at the exact same size so nothing shifts.
|
|
8
11
|
export function indexHtml(slug) {
|
|
9
12
|
return `<!doctype html>
|
|
10
13
|
<html lang="en">
|
|
@@ -21,12 +24,56 @@ export function indexHtml(slug) {
|
|
|
21
24
|
<p class="sample-note" id="sample-note" hidden>Sample data — your real
|
|
22
25
|
accounts appear when this app runs inside Driggsby.</p>
|
|
23
26
|
</header>
|
|
27
|
+
<!-- The bars below are skeleton placeholders. app.js replaces them
|
|
28
|
+
with data on its first render; edit the render functions there,
|
|
29
|
+
and keep the four stat bars in step with what renderOverview
|
|
30
|
+
draws. (The account rows are just a plausible list length —
|
|
31
|
+
the real count comes from the data.) -->
|
|
24
32
|
<section aria-label="Overview">
|
|
25
|
-
<div class="stat-grid" id="overview"
|
|
33
|
+
<div class="stat-grid" id="overview" aria-busy="true">
|
|
34
|
+
<div class="stat">
|
|
35
|
+
<div class="skeleton skeleton-label"></div>
|
|
36
|
+
<div class="skeleton skeleton-value"></div>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="stat">
|
|
39
|
+
<div class="skeleton skeleton-label"></div>
|
|
40
|
+
<div class="skeleton skeleton-value"></div>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="stat">
|
|
43
|
+
<div class="skeleton skeleton-label"></div>
|
|
44
|
+
<div class="skeleton skeleton-value"></div>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="stat">
|
|
47
|
+
<div class="skeleton skeleton-label"></div>
|
|
48
|
+
<div class="skeleton skeleton-value"></div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
26
51
|
</section>
|
|
27
52
|
<section aria-label="Accounts">
|
|
28
53
|
<h2>Accounts</h2>
|
|
29
|
-
<div id="accounts"
|
|
54
|
+
<div id="accounts" aria-busy="true">
|
|
55
|
+
<div class="account-row">
|
|
56
|
+
<div class="account-names">
|
|
57
|
+
<div class="skeleton skeleton-name"></div>
|
|
58
|
+
<div class="skeleton skeleton-institution"></div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="skeleton skeleton-balance"></div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="account-row">
|
|
63
|
+
<div class="account-names">
|
|
64
|
+
<div class="skeleton skeleton-name"></div>
|
|
65
|
+
<div class="skeleton skeleton-institution"></div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="skeleton skeleton-balance"></div>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="account-row">
|
|
70
|
+
<div class="account-names">
|
|
71
|
+
<div class="skeleton skeleton-name"></div>
|
|
72
|
+
<div class="skeleton skeleton-institution"></div>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="skeleton skeleton-balance"></div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
30
77
|
</section>
|
|
31
78
|
</main>
|
|
32
79
|
<script type="module" src="/-/driggsby-sdk.js"></script>
|
|
@@ -44,6 +91,27 @@ export const APP_JS = `// Your app's code. Edit anything — driggsby dev reload
|
|
|
44
91
|
// Each watch is a live subscription: the callback runs with a fresh result
|
|
45
92
|
// whenever your Driggsby data changes. The sample data below matches the
|
|
46
93
|
// real result shapes, so the render functions work unchanged either way.
|
|
94
|
+
//
|
|
95
|
+
// These are the tools an app can watch — read-only, nothing else responds:
|
|
96
|
+
//
|
|
97
|
+
// get_overview money rollups + every linked account
|
|
98
|
+
// get_history balances, holdings, or debts over time
|
|
99
|
+
// (history_type is required: "account_balances",
|
|
100
|
+
// "investment_holdings", or "liabilities")
|
|
101
|
+
// list_accounts current balances per linked account
|
|
102
|
+
// list_assets_and_liabilities the full balance sheet, incl. manual items
|
|
103
|
+
// list_findings issues Driggsby has flagged
|
|
104
|
+
// list_investment_holdings current investment positions
|
|
105
|
+
// list_outstanding_debts linked debts with balances and rates
|
|
106
|
+
// list_recurring_transactions subscriptions, bills, and paychecks
|
|
107
|
+
// query_cash_sql SQL over cash transactions ({ sql: "..." })
|
|
108
|
+
// query_investment_sql SQL over investment activity ({ sql: "..." })
|
|
109
|
+
// search_cash_transactions bank and card transaction search
|
|
110
|
+
// search_investment_activity trades, dividends, and transfers
|
|
111
|
+
//
|
|
112
|
+
// Params mirror Driggsby's public MCP tools of the same names, with one
|
|
113
|
+
// simplification: those tools require a "reason" string, but a watch can
|
|
114
|
+
// leave it out — Driggsby fills one in naming the dashboard.
|
|
47
115
|
|
|
48
116
|
// ---------------------------------------------------------------------------
|
|
49
117
|
// Sample data. Every name and number here is invented. It paints only
|
|
@@ -104,6 +172,19 @@ function element(tag, className, text) {
|
|
|
104
172
|
return node;
|
|
105
173
|
}
|
|
106
174
|
|
|
175
|
+
// The page ships with a skeleton of this exact layout in its HTML — muted
|
|
176
|
+
// bars where the numbers will be — so the very first paint is already the
|
|
177
|
+
// final shape. The first render into a container replaces its skeleton and
|
|
178
|
+
// fades in, so data arriving reads as the page settling, never as content
|
|
179
|
+
// popping. classList.add is idempotent and re-adding a class never restarts
|
|
180
|
+
// its animation, so later live updates repaint in place without re-fading.
|
|
181
|
+
// aria-busy ships in the HTML so assistive tech hears "loading" until the
|
|
182
|
+
// first real content lands.
|
|
183
|
+
function revealOnce(container) {
|
|
184
|
+
container.classList.add("fade-in");
|
|
185
|
+
container.removeAttribute("aria-busy");
|
|
186
|
+
}
|
|
187
|
+
|
|
107
188
|
function renderOverview(result) {
|
|
108
189
|
const rollups = (result && result.summary_rollups) || {};
|
|
109
190
|
const container = document.getElementById("overview");
|
|
@@ -122,6 +203,7 @@ function renderOverview(result) {
|
|
|
122
203
|
);
|
|
123
204
|
container.append(stat);
|
|
124
205
|
}
|
|
206
|
+
revealOnce(container);
|
|
125
207
|
}
|
|
126
208
|
|
|
127
209
|
function renderAccounts(result) {
|
|
@@ -143,32 +225,32 @@ function renderAccounts(result) {
|
|
|
143
225
|
row.append(names, element("div", "account-balance", formatMoney(account.current_balance)));
|
|
144
226
|
container.append(row);
|
|
145
227
|
}
|
|
228
|
+
revealOnce(container);
|
|
146
229
|
}
|
|
147
230
|
|
|
148
231
|
// ---------------------------------------------------------------------------
|
|
149
232
|
// Live data. window.driggsby exists when the Driggsby SDK loaded — inside
|
|
150
233
|
// Driggsby, or under driggsby dev. Whether anything embeds this page is
|
|
151
234
|
// knowable synchronously: opened directly in a tab, no host will ever
|
|
152
|
-
// answer, so the sample data
|
|
153
|
-
// is revealed (it ships hidden in the HTML, so an embedded
|
|
154
|
-
// shows it, not even for the moment before this script runs).
|
|
155
|
-
// sample numbers never paint at all —
|
|
156
|
-
//
|
|
235
|
+
// answer, so the sample data replaces the skeleton right away and the note
|
|
236
|
+
// explaining it is revealed (it ships hidden in the HTML, so an embedded
|
|
237
|
+
// page never shows it, not even for the moment before this script runs).
|
|
238
|
+
// Embedded, sample numbers never paint at all — the shipped skeleton holds
|
|
239
|
+
// the layout until the first real results replace it and fade in.
|
|
157
240
|
// ---------------------------------------------------------------------------
|
|
158
241
|
|
|
159
242
|
const standalone = window.parent === window;
|
|
160
243
|
const note = document.getElementById("sample-note");
|
|
161
244
|
|
|
162
245
|
if (standalone) {
|
|
246
|
+
// The note and the sample values paint together (this whole branch is
|
|
247
|
+
// one synchronous task). The reveal is kept first so an edit that ever
|
|
248
|
+
// makes rendering async can't paint values before their label.
|
|
249
|
+
if (note) note.hidden = false;
|
|
163
250
|
renderOverview(SAMPLE_OVERVIEW);
|
|
164
251
|
renderAccounts(SAMPLE_ACCOUNTS);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (note) note.remove();
|
|
168
|
-
const accounts = document.getElementById("accounts");
|
|
169
|
-
if (accounts) {
|
|
170
|
-
accounts.replaceChildren(element("div", "loading-note", "Loading your accounts…"));
|
|
171
|
-
}
|
|
252
|
+
} else if (note) {
|
|
253
|
+
note.remove();
|
|
172
254
|
}
|
|
173
255
|
|
|
174
256
|
if (window.driggsby) {
|
|
@@ -235,10 +317,64 @@ h2 {
|
|
|
235
317
|
display: none;
|
|
236
318
|
}
|
|
237
319
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
320
|
+
/* The skeleton ships in the HTML so the first paint is the final layout as
|
|
321
|
+
muted bars; the first render replaces it and fades in (see app.js). The
|
|
322
|
+
bar margins make each skeleton block exactly the height of the text it
|
|
323
|
+
stands in for, so nothing shifts when data lands. */
|
|
324
|
+
.skeleton {
|
|
325
|
+
border-radius: 4px;
|
|
326
|
+
background: var(--hairline);
|
|
327
|
+
animation: skeleton-pulse 1.6s ease-in-out infinite;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.skeleton-label {
|
|
331
|
+
width: 64px;
|
|
332
|
+
height: 12px;
|
|
333
|
+
margin: 3.75px 0 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.skeleton-value {
|
|
337
|
+
width: 96px;
|
|
338
|
+
height: 20px;
|
|
339
|
+
margin: 8.75px 0 5px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.skeleton-name {
|
|
343
|
+
width: 150px;
|
|
344
|
+
height: 14px;
|
|
345
|
+
margin: 4px 0 8px;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.skeleton-institution {
|
|
349
|
+
width: 110px;
|
|
350
|
+
height: 12px;
|
|
351
|
+
margin: 4px 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.skeleton-balance {
|
|
355
|
+
width: 72px;
|
|
356
|
+
height: 14px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@keyframes skeleton-pulse {
|
|
360
|
+
0%, 100% { opacity: 1; }
|
|
361
|
+
50% { opacity: 0.55; }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.fade-in {
|
|
365
|
+
animation: fade-in 200ms ease-out;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@keyframes fade-in {
|
|
369
|
+
from { opacity: 0; }
|
|
370
|
+
to { opacity: 1; }
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@media (prefers-reduced-motion: reduce) {
|
|
374
|
+
.skeleton,
|
|
375
|
+
.fade-in {
|
|
376
|
+
animation: none;
|
|
377
|
+
}
|
|
242
378
|
}
|
|
243
379
|
|
|
244
380
|
.stat-grid {
|
|
@@ -281,6 +417,10 @@ h2 {
|
|
|
281
417
|
font-variant-numeric: tabular-nums;
|
|
282
418
|
}
|
|
283
419
|
`;
|
|
420
|
+
// "background" is the template's own page background (styles.css
|
|
421
|
+
// --background): Driggsby paints it while the app loads, so the loading
|
|
422
|
+
// surface matches the app from the first frame. An agent that re-themes
|
|
423
|
+
// the app should keep this equal to the page's real background color.
|
|
284
424
|
export function driggsbyJsonText(slug) {
|
|
285
|
-
return `{\n "slug": ${JSON.stringify(slug)},\n "serve": "."\n}\n`;
|
|
425
|
+
return `{\n "slug": ${JSON.stringify(slug)},\n "serve": ".",\n "background": "#ffffff"\n}\n`;
|
|
286
426
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driggsby",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Set up and build on Driggsby, the MCP server for personal finance.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">=18"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@driggsby/deploy": "0.2.
|
|
32
|
-
"@driggsby/sdk": "0.2.
|
|
31
|
+
"@driggsby/deploy": "0.2.3",
|
|
32
|
+
"@driggsby/sdk": "0.2.3"
|
|
33
33
|
}
|
|
34
34
|
}
|