driggsby 0.5.1 → 0.6.0

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/README.md CHANGED
@@ -22,6 +22,12 @@ npx driggsby@latest mcp setup other
22
22
  After setup, authenticate Driggsby through your client — run `/mcp` in
23
23
  Claude Code, or sign in through the browser window Codex opens.
24
24
 
25
+ So your Driggsby MCP page can tell your connections apart, `login` sends
26
+ Driggsby this computer's name and operating system version (for example
27
+ `mbp-studio` and `macOS 15.6`) with its sign-in, and
28
+ `mcp setup claude-code` saves them as headers on the Driggsby entry it
29
+ adds to Claude Code, which sends them with each request.
30
+
25
31
  For another MCP client, choose Other. Driggsby currently supports only
26
32
  OAuth-based remote MCP at:
27
33
 
@@ -23,9 +23,12 @@ export function hostPageHtml(slug, appOrigin, background) {
23
23
  <meta name="viewport" content="width=device-width, initial-scale=1">
24
24
  <title>${slug} — driggsby dev</title>
25
25
  <style>
26
- /* The strip above the frame is the console's title row, in its own
27
- measured values: 46px tall on the black ground, 14px ink, a 14%
28
- white hairline under it. */
26
+ /* The strip above the frame is the console's title row over a
27
+ dashboard, in its own measured values: 46px tall on the black
28
+ ground with no rule under it, the name centered in 14px ink, and
29
+ the side slot in the row's quiet 14px tertiary. Equal side tracks
30
+ keep the name centered; the slot ellipsizes only when the two
31
+ would otherwise meet. */
29
32
  * { box-sizing: border-box; }
30
33
  html { color-scheme: dark; }
31
34
  body {
@@ -37,26 +40,42 @@ export function hostPageHtml(slug, appOrigin, background) {
37
40
  color: #fafafa;
38
41
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
39
42
  font-size: 14px;
40
- line-height: 21px;
43
+ line-height: 1.5;
41
44
  -webkit-font-smoothing: antialiased;
42
45
  }
43
46
  header {
44
- display: flex;
47
+ display: grid;
48
+ grid-template-columns: minmax(0, 1fr) fit-content(60%) minmax(0, 1fr);
49
+ column-gap: 16px;
45
50
  align-items: center;
46
- justify-content: space-between;
47
51
  height: 46px;
48
- padding: 0 16px;
49
- border-bottom: 1px solid rgba(255, 255, 255, 0.14);
52
+ padding: 0 20px;
53
+ border-bottom: 1px solid transparent;
54
+ }
55
+ header .slug,
56
+ header .mode {
57
+ grid-row: 1;
58
+ min-width: 0;
59
+ overflow: hidden;
60
+ text-overflow: ellipsis;
61
+ white-space: nowrap;
62
+ }
63
+ header .slug { grid-column: 2; }
64
+ header .mode {
65
+ grid-column: 3;
66
+ justify-self: end;
67
+ max-width: 100%;
68
+ color: #a1a1a1;
50
69
  }
51
- header .slug { font-weight: 500; }
52
- header .mode { color: #a1a1a1; font-size: 13px; }
53
70
  iframe {
54
71
  flex: 1;
55
72
  width: 100%;
56
73
  border: none;
57
74
  /* The app's declared background from driggsby.json, painted while
58
- the app loads — the same surface the production host shows. */
59
- background: ${background ?? "#000000"};
75
+ the app loads — the same surface the production host shows. With
76
+ none declared, Driggsby's own: its card surface on the black
77
+ page. */
78
+ background: ${background ?? "#0a0a0a"};
60
79
  }
61
80
  </style>
62
81
  </head>
package/dist/device.js ADDED
@@ -0,0 +1,127 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { isIP } from "node:net";
3
+ import { hostname, platform, release, version } from "node:os";
4
+ // Load-bearing: these allowlists are what make the values safe as command
5
+ // arguments (spawn-plan.ts quotes them for cmd.exe) and as "Name: value"
6
+ // headers. They must never admit % ! ^ & | < > " \ : or a control
7
+ // character; device.test.ts checks each.
8
+ const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$/;
9
+ const SYSTEM_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 ._/-]{0,63}$/;
10
+ const MAC_VERSION_FILE = "/System/Library/CoreServices/SystemVersion.plist";
11
+ // os-release(5): /etc first, then the vendor copy.
12
+ const LINUX_RELEASE_FILES = ["/etc/os-release", "/usr/lib/os-release"];
13
+ // Both files are a few hundred bytes; anything larger isn't one.
14
+ const MAX_FILE_BYTES = 64 * 1024;
15
+ // A PRETTY_NAME is a short line; a longer one is not parsed at all.
16
+ const MAX_PRETTY_NAME_CHARS = 256;
17
+ // Hostname labels that spell out an address ("ip-172-31-22-33",
18
+ // "c-73-162-12-34", "192-168-1-20") or are only digits name no computer.
19
+ const ADDRESS_LABEL = /^((ip|c)-)?\d{1,3}(-\d{1,3}){3}$|^\d+$/i;
20
+ // Windows 11 reports itself as NT 10.0; its builds start at 22000.
21
+ const WINDOWS_11_FIRST_BUILD = 22000;
22
+ export function describeDevice(facts) {
23
+ try {
24
+ const known = facts ?? realOsFacts();
25
+ return { name: deviceName(known.hostname), system: deviceSystem(known) };
26
+ }
27
+ catch {
28
+ // os.hostname() and os.release() can throw in unusual runtimes.
29
+ return { name: null, system: null };
30
+ }
31
+ }
32
+ // The known values alone, as the sign-in request carries them; null when
33
+ // nothing is known.
34
+ export function knownDevice(device) {
35
+ const known = {
36
+ ...(device.name === null ? {} : { name: device.name }),
37
+ ...(device.system === null ? {} : { system: device.system }),
38
+ };
39
+ return Object.keys(known).length === 0 ? null : known;
40
+ }
41
+ // The Driggsby headers `mcp setup` gives Claude Code, one per known value.
42
+ export function deviceHeaders(device) {
43
+ const headers = [];
44
+ if (device.name !== null) {
45
+ headers.push(`X-Driggsby-Device-Name: ${device.name}`);
46
+ }
47
+ if (device.system !== null) {
48
+ headers.push(`X-Driggsby-Device-System: ${device.system}`);
49
+ }
50
+ return headers;
51
+ }
52
+ // "mbp-studio.local" and "devbox-02.corp.example.test" are mbp-studio and
53
+ // devbox-02: the first label is the name people give their computer. An
54
+ // address or localhost names no computer.
55
+ function deviceName(raw) {
56
+ const trimmed = raw.trim();
57
+ if (isIP(trimmed) !== 0) {
58
+ return null;
59
+ }
60
+ const first = trimmed.split(".")[0] ?? "";
61
+ if (first.toLowerCase() === "localhost" || ADDRESS_LABEL.test(first)) {
62
+ return null;
63
+ }
64
+ return NAME_PATTERN.test(first) ? first : null;
65
+ }
66
+ function deviceSystem(facts) {
67
+ switch (facts.platform) {
68
+ case "darwin":
69
+ return allowed(macVersion(facts)) ?? "macOS";
70
+ case "linux":
71
+ return allowed(linuxPrettyName(facts)) ?? "Linux";
72
+ case "win32":
73
+ return facts.version.includes("Server") ? "Windows Server" : windowsName(facts.release);
74
+ default:
75
+ return null;
76
+ }
77
+ }
78
+ function macVersion(facts) {
79
+ const plist = facts.readFile(MAC_VERSION_FILE);
80
+ const version = plist?.match(/<key>ProductVersion<\/key>\s*<string>([0-9.]+)<\/string>/)?.[1];
81
+ return version === undefined ? null : `macOS ${version}`;
82
+ }
83
+ // PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" is Debian GNU/Linux 12.
84
+ function linuxPrettyName(facts) {
85
+ for (const path of LINUX_RELEASE_FILES) {
86
+ const osRelease = facts.readFile(path);
87
+ if (osRelease === null) {
88
+ continue;
89
+ }
90
+ const line = osRelease.split(/\r?\n/).find((each) => each.startsWith("PRETTY_NAME="));
91
+ if (line === undefined || line.length > MAX_PRETTY_NAME_CHARS) {
92
+ return null;
93
+ }
94
+ const value = line.slice("PRETTY_NAME=".length).trim().replace(/^["']|["']$/g, "");
95
+ return value.replace(/\s*\([^)]*\)\s*$/, "").trim();
96
+ }
97
+ return null;
98
+ }
99
+ // Windows 10 and 11 both report NT 10.0; anything else is just Windows.
100
+ function windowsName(nodeRelease) {
101
+ const [major, , buildText] = nodeRelease.split(".");
102
+ const build = Number(buildText);
103
+ if (major !== "10" || !Number.isInteger(build)) {
104
+ return "Windows";
105
+ }
106
+ return build >= WINDOWS_11_FIRST_BUILD ? "Windows 11" : "Windows 10";
107
+ }
108
+ function allowed(value) {
109
+ return value !== null && SYSTEM_PATTERN.test(value) ? value : null;
110
+ }
111
+ function realOsFacts() {
112
+ return { hostname: hostname(), platform: platform(), release: release(), version: version(), readFile: readTextFile };
113
+ }
114
+ // A regular file of a sane size only: never a FIFO or device node that
115
+ // would block, never something huge.
116
+ function readTextFile(path) {
117
+ try {
118
+ const stat = statSync(path);
119
+ if (!stat.isFile() || stat.size > MAX_FILE_BYTES) {
120
+ return null;
121
+ }
122
+ return readFileSync(path, "utf8");
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
@@ -1,25 +1,35 @@
1
1
  // The scaffold's styles.css. A deployed app lives inside Driggsby's dark
2
2
  // chrome, so the page is dark by default and its values are Driggsby's
3
- // own: the console's ground, card rung, hairline, ink ramp, radii, and
4
- // type sizes, written out as plain colors because the app is its own
5
- // origin and cannot read the console's stylesheet. --bg-app must stay
6
- // equal to driggsby.json's "background" (init-command.test.ts pins the
7
- // two together): Driggsby paints that color while the app loads.
3
+ // own: the console's palette, type, insets, and page column, measured off
4
+ // the console itself. The app is its own origin and cannot read the
5
+ // console's stylesheet, so each gray is written as the console paints it
6
+ // on its black page (the console's cards and fills are translucent over
7
+ // that ground) and each hue verbatim. The :root block is the same one
8
+ // Driggsby's own example dashboard carries. --bg-app must stay equal to
9
+ // driggsby.json's "background" (init-command.test.ts pins the two
10
+ // together): Driggsby paints that color while the app loads.
8
11
  export const STYLES_CSS = `/* Driggsby's console palette, so this page reads as part of the app it
9
12
  opens inside. Change these together if you re-theme; keep --bg-app equal
10
- to the "background" in driggsby.json. */
13
+ to the "background" in driggsby.json. The status tones are the
14
+ console's own, for gains, losses, warnings, and a live dot. */
11
15
  :root {
12
16
  color-scheme: dark;
13
17
  --bg-app: #000000;
14
18
  --bg-card: #0a0a0a;
15
19
  --bg-mute: #262626;
16
20
  --hairline: rgba(255, 255, 255, 0.14);
21
+ --hairline-strong: rgba(255, 255, 255, 0.22);
17
22
  --text-primary: #fafafa;
18
23
  --text-secondary: #c4c4c4;
19
24
  --text-tertiary: #a1a1a1;
25
+ --status-success: oklch(0.72 0.14 160);
26
+ --text-success: oklch(0.82 0.11 160);
27
+ --text-warning: oklch(0.85 0.11 85);
28
+ --text-error: oklch(0.8 0.13 25);
20
29
  --radius-card: 8px;
21
30
  --radius-bar: 4px;
22
31
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
32
+ --ease: cubic-bezier(0.2, 0, 0, 1);
23
33
  }
24
34
 
25
35
  * {
@@ -32,21 +42,25 @@ body {
32
42
  color: var(--text-primary);
33
43
  font-family: var(--font-sans);
34
44
  font-size: 14px;
35
- line-height: 21px;
45
+ line-height: 1.5;
36
46
  -webkit-font-smoothing: antialiased;
37
47
  }
38
48
 
49
+ /* The console's page column. */
39
50
  main {
40
- max-width: 800px;
51
+ max-width: 75rem;
41
52
  margin: 0 auto;
42
- padding: 24px 24px 64px;
53
+ padding: 24px;
54
+ }
55
+
56
+ main > header {
57
+ margin-bottom: 20px;
43
58
  }
44
59
 
45
60
  h1 {
46
61
  margin: 0;
47
62
  font-size: 24px;
48
63
  font-weight: 600;
49
- line-height: 36px;
50
64
  letter-spacing: -0.02em;
51
65
  }
52
66
 
@@ -54,24 +68,19 @@ h2 {
54
68
  margin: 24px 0 12px;
55
69
  font-size: 14px;
56
70
  font-weight: 500;
57
- line-height: 21px;
58
71
  letter-spacing: -0.02em;
59
72
  }
60
73
 
61
- /* One card, four cells; the 1px gap over the card's own hairline color
62
- draws the divider between cells however the grid wraps. */
74
+ /* The console's stat tiles: a card per figure, 16px apart, the row
75
+ capped at the console's 920px. */
63
76
  .stat-grid {
64
77
  display: grid;
65
78
  grid-template-columns: repeat(4, minmax(0, 1fr));
66
- gap: 1px;
67
- margin-top: 24px;
68
- border: 1px solid var(--hairline);
69
- border-radius: var(--radius-card);
70
- background: var(--hairline);
71
- overflow: hidden;
79
+ gap: 16px;
80
+ max-width: 920px;
72
81
  }
73
82
 
74
- @media (max-width: 640px) {
83
+ @media (max-width: 900px) {
75
84
  .stat-grid {
76
85
  grid-template-columns: repeat(2, minmax(0, 1fr));
77
86
  }
@@ -86,22 +95,22 @@ h2 {
86
95
  .stat {
87
96
  display: flex;
88
97
  flex-direction: column;
89
- padding: 16px;
98
+ padding: 14px 16px;
99
+ border: 1px solid var(--hairline);
100
+ border-radius: var(--radius-card);
90
101
  background: var(--bg-card);
91
102
  }
92
103
 
93
104
  .stat-label {
94
105
  font-size: 13px;
95
- line-height: 20px;
96
106
  color: var(--text-tertiary);
97
107
  }
98
108
 
99
109
  .stat-value {
100
- margin-top: 4px;
110
+ margin-top: 2px;
101
111
  font-size: 20px;
102
112
  font-weight: 600;
103
- line-height: 28px;
104
- letter-spacing: -0.02em;
113
+ letter-spacing: -0.01em;
105
114
  font-variant-numeric: tabular-nums;
106
115
  /* A currency string has no natural break, and a clipped balance is a
107
116
  wrong number. If a value ever outgrows its cell, it wraps. */
@@ -165,6 +174,7 @@ h2 {
165
174
  /* Holds its line even when empty, so every row measures the same as
166
175
  the skeleton row it replaces. */
167
176
  min-height: 21px;
177
+ margin-top: 1px;
168
178
  color: var(--text-secondary);
169
179
  overflow: hidden;
170
180
  text-overflow: ellipsis;
@@ -179,10 +189,10 @@ h2 {
179
189
  /* The skeleton ships in the HTML so the first paint is the final layout
180
190
  as muted bars; the first data render replaces it in a dissolve (see the
181
191
  "Settling in" section of app.js). Each bar's margins pad it to exactly
182
- the height of the text it stands in for, and the columns holding
183
- stacked bars are flex so those margins never collapse — the skeleton
184
- page and the data page measure identical, and nothing shifts when data
185
- lands. */
192
+ the height of the text it stands in for (the line, plus any margin the
193
+ text carries), and the columns holding stacked bars are flex so those
194
+ margins never collapse — the skeleton page and the data page measure
195
+ identical, and nothing shifts when data lands. */
186
196
  .skeleton {
187
197
  border-radius: var(--radius-bar);
188
198
  background: var(--bg-mute);
@@ -192,13 +202,13 @@ h2 {
192
202
  .skeleton-label {
193
203
  width: 64px;
194
204
  height: 12px;
195
- margin: 4px 0;
205
+ margin: 3.75px 0;
196
206
  }
197
207
 
198
208
  .skeleton-value {
199
209
  width: 96px;
200
210
  height: 20px;
201
- margin: 8px 0 4px;
211
+ margin: 7px 0 5px;
202
212
  }
203
213
 
204
214
  .skeleton-glyph {
@@ -217,7 +227,7 @@ h2 {
217
227
  .skeleton-institution {
218
228
  width: 110px;
219
229
  height: 12px;
220
- margin: 4.5px 0;
230
+ margin: 5.5px 0 4.5px;
221
231
  }
222
232
 
223
233
  .skeleton-balance {
@@ -4,6 +4,7 @@
4
4
  // token is returned by exactly one poll; callers must never poll again after
5
5
  // an approval.
6
6
  import { CliError } from "../cli-error.js";
7
+ import { knownDevice } from "../device.js";
7
8
  import { sanitizeForTerminal } from "../terminal-text.js";
8
9
  // The CLI's own sign-in: read, deploy, and the transaction-rule tools.
9
10
  const CLAIM_SCOPE = "driggsby.cli";
@@ -23,11 +24,13 @@ export const SIGN_IN_START_FAILURE = "We weren't able to start a Driggsby sign-i
23
24
  // Poll failures happen after the user may already have approved, so "start a
24
25
  // sign-in" would be the wrong stage to describe.
25
26
  const SIGN_IN_FINISH_FAILURE = "We weren't able to finish your Driggsby sign-in just now. Please try again\nin a minute.";
26
- export async function createClaimRequest(baseUrl) {
27
+ // device: this computer's name and system (device.ts), so the sign-in shows
28
+ // up on Driggsby's MCP page as the computer it is; only known values go.
29
+ export async function createClaimRequest(baseUrl, device = { name: null, system: null }) {
27
30
  const response = await fetch(`${baseUrl}/app-tokens/claim-requests`, {
28
31
  method: "POST",
29
32
  headers: { "Content-Type": "application/json" },
30
- body: JSON.stringify({ app_name: CLAIM_APP_NAME, scope: CLAIM_SCOPE }),
33
+ body: JSON.stringify({ app_name: CLAIM_APP_NAME, scope: CLAIM_SCOPE, ...deviceField(device) }),
31
34
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
32
35
  // Never follow a redirect: a 307/308 would replay this claim flow —
33
36
  // and, on the poll below, the poll secret — to whatever origin a
@@ -141,6 +144,11 @@ function stringField(record, field) {
141
144
  const value = record?.[field];
142
145
  return typeof value === "string" && value !== "" ? value : null;
143
146
  }
147
+ // The claim body's device field: only the values this computer knows.
148
+ function deviceField(device) {
149
+ const known = knownDevice(device);
150
+ return known === null ? {} : { device: known };
151
+ }
144
152
  // The server's user-facing error text, stripped of terminal control bytes
145
153
  // like every other string this CLI did not author, and length-bounded.
146
154
  function errorDescription(body) {
@@ -2,9 +2,10 @@
2
2
  // page, poll until they approve, and store the token. The token is shown to
3
3
  // no one — it goes straight from the poll response into the credential
4
4
  // store.
5
- import { apiBaseUrl, apiHost } from "../api/base-url.js";
5
+ import { apiBaseUrl, apiHost, baseUrlMayReceiveSavedSignIn } from "../api/base-url.js";
6
6
  import { blockedNetworkError } from "../api/network-error.js";
7
7
  import { CliError } from "../cli-error.js";
8
+ import { describeDevice } from "../device.js";
8
9
  import { defaultCredentialEnvironment, describeStorageLocation, environmentToken, saveToken, } from "../credentials/store.js";
9
10
  import { sanitizeForTerminal, wrapProse } from "../terminal-text.js";
10
11
  import { createClaimRequest, pollClaimRequest, SIGN_IN_START_FAILURE, } from "./claim-client.js";
@@ -69,7 +70,10 @@ async function storeApprovedToken(appToken, environment, warn) {
69
70
  }
70
71
  async function createClaim(baseUrl) {
71
72
  try {
72
- return await createClaimRequest(baseUrl);
73
+ // This computer's name goes only to Driggsby (or a local run), never
74
+ // to another host a base-URL override names.
75
+ const device = baseUrlMayReceiveSavedSignIn(baseUrl) ? describeDevice() : { name: null, system: null };
76
+ return await createClaimRequest(baseUrl, device);
73
77
  }
74
78
  catch (error) {
75
79
  const blocked = blockedNetworkError(error, {
@@ -1,6 +1,11 @@
1
1
  import {} from "./known-client.js";
2
2
  export const DRIGGSBY_MCP_URL = "https://app.driggsby.com/mcp";
3
- export function buildInstallerCommand(client, scope) {
3
+ // headers: this computer's name and system for Driggsby's MCP page
4
+ // (device.ts builds them from allowlisted values only). Claude Code takes
5
+ // them after the URL (--header is variadic, so before the name it would
6
+ // swallow it); Codex has no header option, so it never gets any. The
7
+ // commands printed for a person to run carry none.
8
+ export function buildInstallerCommand(client, scope, headers = []) {
4
9
  switch (client) {
5
10
  case "claude-code":
6
11
  return {
@@ -14,6 +19,7 @@ export function buildInstallerCommand(client, scope) {
14
19
  scope ?? "user",
15
20
  "driggsby",
16
21
  DRIGGSBY_MCP_URL,
22
+ ...headers.flatMap((header) => ["--header", header]),
17
23
  ],
18
24
  };
19
25
  case "codex":
@@ -1,5 +1,6 @@
1
1
  import { createInterface } from "node:readline/promises";
2
2
  import { CliError } from "../cli-error.js";
3
+ import { describeDevice, deviceHeaders } from "../device.js";
3
4
  import { trimLikeRust } from "../terminal-text.js";
4
5
  import { runClientCommand } from "./client-command.js";
5
6
  import { classifyExistingMcpConfig } from "./classify.js";
@@ -24,7 +25,11 @@ export async function runMcpSetup(options) {
24
25
  return;
25
26
  }
26
27
  write(`Adding Driggsby to ${displayName(client)} MCP config...\n`);
27
- const result = await runClientCommand(installer, streamConfigOutput(client));
28
+ // The command it runs also names this computer (Claude Code only); the
29
+ // commands it prints for a person stay the plain, portable ones.
30
+ const headers = installClient === "claude-code" ? deviceHeaders(describeDevice()) : [];
31
+ const runInstaller = buildInstallerCommand(installClient, options.scope, headers);
32
+ const result = await runClientCommand(runInstaller, streamConfigOutput(client));
28
33
  switch (result.kind) {
29
34
  case "output":
30
35
  if (result.output.succeeded) {
@@ -5,8 +5,9 @@
5
5
  // PATH + PATHEXT (never the current directory, which Windows shells would
6
6
  // search first) and run shims through an explicit, absolute cmd.exe
7
7
  // invocation with our own quoting. Real commands only ever carry fixed
8
- // constant arguments (see commands.ts); nothing user-controlled may ever
9
- // flow into a spawn plan.
8
+ // constant arguments (see commands.ts) or this computer's name and system,
9
+ // allowlisted by device.ts to characters cmd.exe treats as plain; nothing
10
+ // user-controlled may ever flow into a spawn plan.
10
11
  import { existsSync } from "node:fs";
11
12
  import { isAbsolute, join } from "node:path";
12
13
  import {} from "./commands.js";
@@ -80,8 +81,9 @@ export function resolveWindowsProgram(program, lookup = defaultLookup()) {
80
81
  // (the cmd convention) and trailing backslashes doubled (so a path ending
81
82
  // in \ cannot escape the closing quote for the child's own parser). The
82
83
  // safe set covers every constant argument the CLI actually runs, including
83
- // the MCP URL; only fixed constants and PATH-resolved program paths may
84
- // reach this function.
84
+ // the MCP URL; only fixed constants, PATH-resolved program paths, and the
85
+ // device values device.ts allowlists (no % ! ^ & | < > " \ or control
86
+ // characters) may reach this function.
85
87
  export function quoteForCmd(value) {
86
88
  if (/^[A-Za-z0-9_\-.:\\/=]+$/.test(value)) {
87
89
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "driggsby",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
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.5.1",
32
- "@driggsby/sdk": "0.5.1"
31
+ "@driggsby/deploy": "0.6.0",
32
+ "@driggsby/sdk": "0.6.0"
33
33
  }
34
34
  }