driggsby 0.2.2 → 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.
@@ -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
@@ -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),
@@ -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") {
@@ -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
- // Both interpolations below are HTML-safe by construction, not by escaping:
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, and
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. Anything looser than those two sources must not be passed here.
16
- export function hostPageHtml(slug, appOrigin) {
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
- ' postToApp({ protocol: PROTOCOL, type: "hello" });',
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") {',
@@ -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.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.2",
32
- "@driggsby/sdk": "0.2.2"
31
+ "@driggsby/deploy": "0.2.3",
32
+ "@driggsby/sdk": "0.2.3"
33
33
  }
34
34
  }