driggsby 0.2.2 → 0.2.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.
|
@@ -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,108 @@ 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 because the frame controls their rate, but",
|
|
86
|
+
"// ordinary clicking should not feel the bound — a token bucket does",
|
|
87
|
+
"// both, matching the production host: a flurry of up to 15",
|
|
88
|
+
"// navigations writes instantly, sustained clicking faster than one",
|
|
89
|
+
"// per 600ms then paces at 600ms, and a frame spamming routes drains",
|
|
90
|
+
"// the bucket and degrades to paced trailing writes that still land",
|
|
91
|
+
"// the final state. Burst (15) plus refills (one per 600ms = 50 per",
|
|
92
|
+
"// 30s) tops out at 65 writes per 30s, leaving at least ~35 of",
|
|
93
|
+
"// Safari's ~100-per-30s per-document replaceState budget for the",
|
|
94
|
+
"// host page's own writes. Keep these two constants identical to the",
|
|
95
|
+
"// production host's; nothing pins them across the two codebases.",
|
|
96
|
+
"const ROUTE_WRITE_BUCKET_CAPACITY = 15;",
|
|
97
|
+
"const ROUTE_WRITE_REFILL_MS = 600;",
|
|
98
|
+
"let routeTokens = ROUTE_WRITE_BUCKET_CAPACITY;",
|
|
99
|
+
"let routeLastRefillMs = Date.now();",
|
|
100
|
+
"let routeWriteTimer = null;",
|
|
101
|
+
"",
|
|
102
|
+
"function refillRouteTokens() {",
|
|
103
|
+
" const now = Date.now();",
|
|
104
|
+
" // A backward clock step would otherwise strand the refill mark in",
|
|
105
|
+
" // the future and stall every write until the clock caught back up.",
|
|
106
|
+
" if (now < routeLastRefillMs) {",
|
|
107
|
+
" routeLastRefillMs = now;",
|
|
108
|
+
" }",
|
|
109
|
+
" const earned = Math.floor((now - routeLastRefillMs) / ROUTE_WRITE_REFILL_MS);",
|
|
110
|
+
" if (earned < 1) {",
|
|
111
|
+
" return;",
|
|
112
|
+
" }",
|
|
113
|
+
" routeTokens = Math.min(ROUTE_WRITE_BUCKET_CAPACITY, routeTokens + earned);",
|
|
114
|
+
" routeLastRefillMs += earned * ROUTE_WRITE_REFILL_MS;",
|
|
115
|
+
"}",
|
|
116
|
+
"",
|
|
117
|
+
"function takeRouteToken() {",
|
|
118
|
+
" refillRouteTokens();",
|
|
119
|
+
" if (routeTokens < 1) {",
|
|
120
|
+
" return false;",
|
|
121
|
+
" }",
|
|
122
|
+
" routeTokens -= 1;",
|
|
123
|
+
" return true;",
|
|
124
|
+
"}",
|
|
125
|
+
"",
|
|
126
|
+
"function routeRetryDelayMs() {",
|
|
127
|
+
" refillRouteTokens();",
|
|
128
|
+
" if (routeTokens >= 1) {",
|
|
129
|
+
" return 1;",
|
|
130
|
+
" }",
|
|
131
|
+
" const sinceRefill = Date.now() - routeLastRefillMs;",
|
|
132
|
+
" return Math.min(ROUTE_WRITE_REFILL_MS, Math.max(1, ROUTE_WRITE_REFILL_MS - sinceRefill));",
|
|
133
|
+
"}",
|
|
134
|
+
"",
|
|
135
|
+
"function writeRouteToUrl() {",
|
|
136
|
+
" if (window.location.hash === appRoute) {",
|
|
137
|
+
" return;",
|
|
138
|
+
" }",
|
|
139
|
+
' const base = window.location.href.split("#")[0];',
|
|
140
|
+
" try {",
|
|
141
|
+
' history.replaceState(history.state, "", base + appRoute);',
|
|
142
|
+
" } catch (error) {",
|
|
143
|
+
" // A refused write costs only URL freshness; the next one retries.",
|
|
144
|
+
" }",
|
|
145
|
+
"}",
|
|
146
|
+
"",
|
|
147
|
+
"function scheduleTrailingRouteWrite() {",
|
|
148
|
+
" routeWriteTimer = setTimeout(() => {",
|
|
149
|
+
" routeWriteTimer = null;",
|
|
150
|
+
" if (takeRouteToken()) {",
|
|
151
|
+
" writeRouteToUrl();",
|
|
152
|
+
" } else {",
|
|
153
|
+
" // The clock can land a hair before the token accrues; wait out",
|
|
154
|
+
" // the remainder rather than writing over budget.",
|
|
155
|
+
" scheduleTrailingRouteWrite();",
|
|
156
|
+
" }",
|
|
157
|
+
" }, routeRetryDelayMs());",
|
|
158
|
+
"}",
|
|
159
|
+
"",
|
|
160
|
+
"function applyRoute(hash) {",
|
|
161
|
+
" appRoute = hash;",
|
|
162
|
+
" if (routeWriteTimer !== null) {",
|
|
163
|
+
" return; // the trailing write reads appRoute",
|
|
164
|
+
" }",
|
|
165
|
+
" // Already mirrored: nothing to write, no token spent on nothing.",
|
|
166
|
+
" if (window.location.hash === hash) {",
|
|
167
|
+
" return;",
|
|
168
|
+
" }",
|
|
169
|
+
" if (takeRouteToken()) {",
|
|
170
|
+
" writeRouteToUrl();",
|
|
171
|
+
" return;",
|
|
172
|
+
" }",
|
|
173
|
+
" scheduleTrailingRouteWrite();",
|
|
174
|
+
"}",
|
|
175
|
+
"",
|
|
69
176
|
"function postToApp(message) {",
|
|
70
177
|
" if (frame.contentWindow) {",
|
|
71
178
|
" frame.contentWindow.postMessage(message, APP_ORIGIN);",
|
|
@@ -114,7 +221,25 @@ export const HOST_PAGE_JS = [
|
|
|
114
221
|
" }",
|
|
115
222
|
" const data = event.data;",
|
|
116
223
|
' if (data.type === "ready") {',
|
|
117
|
-
|
|
224
|
+
" const hello = { protocol: PROTOCOL, type: \"hello\" };",
|
|
225
|
+
' if (appRoute !== "") {',
|
|
226
|
+
" hello.route = appRoute;",
|
|
227
|
+
" }",
|
|
228
|
+
" postToApp(hello);",
|
|
229
|
+
" return;",
|
|
230
|
+
" }",
|
|
231
|
+
' if (data.type === "route") {',
|
|
232
|
+
' // A bare "#" is the explicit cleared sentinel: the app is back at',
|
|
233
|
+
" // its no-hash location, so the page URL drops its fragment.",
|
|
234
|
+
' if (data.hash === "#") {',
|
|
235
|
+
' applyRoute("");',
|
|
236
|
+
" return;",
|
|
237
|
+
" }",
|
|
238
|
+
" const hash = sanitizeAppRoute(data.hash);",
|
|
239
|
+
' if (hash === "") {',
|
|
240
|
+
" return;",
|
|
241
|
+
" }",
|
|
242
|
+
" applyRoute(hash);",
|
|
118
243
|
" return;",
|
|
119
244
|
" }",
|
|
120
245
|
' if (data.type !== "call") {',
|
package/dist/init/templates.js
CHANGED
|
@@ -91,6 +91,27 @@ export const APP_JS = `// Your app's code. Edit anything — driggsby dev reload
|
|
|
91
91
|
// Each watch is a live subscription: the callback runs with a fresh result
|
|
92
92
|
// whenever your Driggsby data changes. The sample data below matches the
|
|
93
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.
|
|
94
115
|
|
|
95
116
|
// ---------------------------------------------------------------------------
|
|
96
117
|
// Sample data. Every name and number here is invented. It paints only
|
|
@@ -396,6 +417,10 @@ h2 {
|
|
|
396
417
|
font-variant-numeric: tabular-nums;
|
|
397
418
|
}
|
|
398
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.
|
|
399
424
|
export function driggsbyJsonText(slug) {
|
|
400
|
-
return `{\n "slug": ${JSON.stringify(slug)},\n "serve": "."\n}\n`;
|
|
425
|
+
return `{\n "slug": ${JSON.stringify(slug)},\n "serve": ".",\n "background": "#ffffff"\n}\n`;
|
|
401
426
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driggsby",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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.4",
|
|
32
|
+
"@driggsby/sdk": "0.2.4"
|
|
33
33
|
}
|
|
34
34
|
}
|