driggsby 0.3.1 → 0.3.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/README.md CHANGED
@@ -86,7 +86,9 @@ Every deploy after that uploads only the files that changed. Deploy with
86
86
  `--preview` to upload a version without
87
87
  changing what visitors see, `npx driggsby@latest versions` to list every
88
88
  kept version, and `npx driggsby@latest rollback` to switch which one is
89
- live — switching re-uploads nothing. `npx driggsby@latest query <tool>`
89
+ live — switching re-uploads nothing. `npx driggsby@latest delete` removes
90
+ a deployed app forever, after asking you to type its address name back
91
+ (`--yes` for scripts). `npx driggsby@latest query <tool>`
90
92
  runs one read-only data tool and prints its result as JSON, the same
91
93
  shape a `driggsby.watch` callback receives, so you can see the data
92
94
  before writing any render code.
@@ -0,0 +1,38 @@
1
+ // Argv parsing for driggsby delete: one optional positional (the app's
2
+ // address name), --yes for scripts and agents, help, and the shared
3
+ // usage-error shape — usage errors exit 2, echoed argv is sanitized by the
4
+ // shared builders.
5
+ import { helpCommand, unexpectedArgument, unexpectedFlagValue, } from "./args-shared.js";
6
+ import { DELETE_HELP } from "./help.js";
7
+ const DELETE_USAGE = "Usage: npx driggsby@latest delete [APP-ADDRESS-NAME] [--yes]";
8
+ export function parseDelete(argv) {
9
+ let slug = null;
10
+ let yes = false;
11
+ let optionsEnded = false;
12
+ for (const token of argv) {
13
+ if (!optionsEnded && token === "--") {
14
+ optionsEnded = true;
15
+ continue;
16
+ }
17
+ if (!optionsEnded && (token === "-h" || token === "--help")) {
18
+ return helpCommand(DELETE_HELP);
19
+ }
20
+ if (!optionsEnded && token === "--yes") {
21
+ yes = true;
22
+ continue;
23
+ }
24
+ if (!optionsEnded && token.startsWith("--yes=")) {
25
+ throw unexpectedFlagValue("--yes", token.slice("--yes=".length), DELETE_USAGE);
26
+ }
27
+ // Any other flag-looking token is a mistake, not a name; after "--"
28
+ // everything is positional and the server refuses unknown names.
29
+ if (!optionsEnded && token.startsWith("-") && token !== "-") {
30
+ throw unexpectedArgument(token, DELETE_USAGE);
31
+ }
32
+ if (slug !== null) {
33
+ throw unexpectedArgument(token, DELETE_USAGE);
34
+ }
35
+ slug = token;
36
+ }
37
+ return { kind: "delete", slug, yes };
38
+ }
package/dist/args.js CHANGED
@@ -7,6 +7,7 @@
7
7
  // clap's byte-level quirks.
8
8
  import { parseDeploy, parseRollback, parseVersions } from "./args-deploy.js";
9
9
  import { parseDev } from "./args-dev.js";
10
+ import { parseDelete } from "./args-delete.js";
10
11
  import { parseInit } from "./args-init.js";
11
12
  import { parseMcpSetup } from "./args-mcp-setup.js";
12
13
  import { parseQuery } from "./args-query.js";
@@ -21,7 +22,7 @@ const MCP_USAGE = "Usage: npx driggsby@latest mcp <COMMAND>";
21
22
  const ROOT_LEVEL = {
22
23
  helpText: ROOT_HELP,
23
24
  usage: ROOT_USAGE,
24
- subcommands: ["mcp", "login", "logout", "init", "dev", "deploy", "rollback", "versions", "query"],
25
+ subcommands: ["mcp", "login", "logout", "init", "dev", "deploy", "rollback", "versions", "delete", "query"],
25
26
  longFlags: ["help", "version"],
26
27
  subcommandFlags: [
27
28
  { name: "mcp", longFlags: ["help"] },
@@ -32,6 +33,7 @@ const ROOT_LEVEL = {
32
33
  { name: "deploy", longFlags: ["preview", "help"] },
33
34
  { name: "rollback", longFlags: ["to", "help"] },
34
35
  { name: "versions", longFlags: ["help"] },
36
+ { name: "delete", longFlags: ["yes", "help"] },
35
37
  { name: "query", longFlags: ["sql", "params", "help"] },
36
38
  ],
37
39
  usagePrefix: "npx driggsby@latest",
@@ -96,6 +98,8 @@ function dispatchSubcommand(name, rest) {
96
98
  return parseDeploy(rest);
97
99
  case "rollback":
98
100
  return parseRollback(rest);
101
+ case "delete":
102
+ return parseDelete(rest);
99
103
  case "versions":
100
104
  return parseVersions(rest);
101
105
  case "query":
@@ -0,0 +1,125 @@
1
+ // driggsby delete: permanently remove a deployed app — the app itself and
2
+ // every version Driggsby kept for it. There is no undo, so this command
3
+ // owns the "are you sure": interactively the person types the app's
4
+ // address name back; in a script or agent run, --yes is required.
5
+ import { access } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import { deleteApp, readProjectConfig, slugProblem } from "@driggsby/deploy";
8
+ import { apiBaseUrl } from "../api/base-url.js";
9
+ import { CliError } from "../cli-error.js";
10
+ import { defaultCredentialEnvironment, } from "../credentials/store.js";
11
+ import { capForTerminal, quotedForTerminal, wrapProse } from "../terminal-text.js";
12
+ import { MAX_SERVER_TEXT_CHARS, MAX_SERVER_URL_CHARS, deployFailure, requireDeploySession, } from "./api-session.js";
13
+ import { fetchVersionList } from "./versions-command.js";
14
+ const DELETE_RETRY_COMMAND = "npx driggsby@latest delete";
15
+ // The success line is `✓ Deleted ` (12 columns) plus the quoted name;
16
+ // 12 + 2 quotes + 64 = 78 keeps it inside 80 whatever the server sends.
17
+ const DELETED_LINE_NAME_CHARS = 64;
18
+ // Once the name is known (and validated), the retry names it too, so a
19
+ // retry from the wrong folder can't quietly target a different app.
20
+ function retryCommandFor(slug) {
21
+ return slug === null ? DELETE_RETRY_COMMAND : `${DELETE_RETRY_COMMAND} ${slug}`;
22
+ }
23
+ function defaultDeleteIo() {
24
+ return {
25
+ out: (text) => {
26
+ process.stdout.write(text);
27
+ },
28
+ ask: async (question) => {
29
+ const { createInterface } = await import("node:readline/promises");
30
+ const readline = createInterface({ input: process.stdin, output: process.stdout });
31
+ try {
32
+ return await readline.question(question);
33
+ }
34
+ finally {
35
+ readline.close();
36
+ }
37
+ },
38
+ interactive: process.stdin.isTTY && process.stdout.isTTY,
39
+ };
40
+ }
41
+ export async function runDelete(options, environment = defaultCredentialEnvironment(), io = defaultDeleteIo()) {
42
+ const projectDirectory = options.projectDirectory ?? process.cwd();
43
+ const baseUrl = apiBaseUrl(environment.env);
44
+ let resolvedSlug = null;
45
+ try {
46
+ const slug = options.slug === null
47
+ ? await slugFromProjectFolder(projectDirectory)
48
+ : validatedArgvSlug(options.slug);
49
+ resolvedSlug = slug;
50
+ const session = await requireDeploySession(environment);
51
+ const api = { baseUrl: session.baseUrl, token: session.token };
52
+ // The look-before-you-leap read: it shows exactly what is about to
53
+ // go, and an unknown name stops here — with delete's own not-found,
54
+ // never the version list's "deploy it first" suggestion — before the
55
+ // DELETE is ever sent.
56
+ const list = await fetchVersionList(api, slug, appAlreadyGone);
57
+ const versionCount = list.versions.length;
58
+ const versionsLine = versionCount === 1 ? "1 deployed version" : `${versionCount} deployed versions`;
59
+ // The slug passed the strict pattern above (argv) or came from the
60
+ // validated project config, so it prints bare here.
61
+ io.out(`${wrapProse(`Deleting ${slug} removes it forever: ${versionsLine}, its page in Driggsby, and its address:`)}\n` +
62
+ ` ${capForTerminal(list.url, MAX_SERVER_URL_CHARS)}\n\n`);
63
+ if (!options.yes) {
64
+ await confirmOrStop(slug, io);
65
+ }
66
+ const deleted = await deleteApp(api, slug);
67
+ // The name is server text: quoted so it reads as a name, never as the
68
+ // CLI's own sentence, and capped tighter than usual (64) so the line
69
+ // stays inside 80 columns whatever the server sends. The preflight
70
+ // above already named the address, so the slug isn't repeated here.
71
+ io.out(`✓ Deleted ${quotedForTerminal(deleted.appName, DELETED_LINE_NAME_CHARS)}\n\n`);
72
+ io.out(`${wrapProse("The app and its versions are gone, and its address now shows nothing. Deploying the app's folder again would create a new app at a new address.")}\n` +
73
+ "\nNext:\n" +
74
+ " Start a new app with npx driggsby@latest init\n");
75
+ return 0;
76
+ }
77
+ catch (error) {
78
+ throw deployFailure(error, baseUrl, retryCommandFor(resolvedSlug));
79
+ }
80
+ }
81
+ // A name typed on the command line is shape-gated before it goes anywhere:
82
+ // a typo gets the naming rules instead of a network round trip, nothing
83
+ // unvalidated is ever echoed, and the empty string can't slip through to
84
+ // match an empty confirmation answer.
85
+ function validatedArgvSlug(slug) {
86
+ const problem = slugProblem(slug);
87
+ if (problem === null)
88
+ return slug;
89
+ const flatProblem = problem.replaceAll("\n", " ");
90
+ throw new CliError(wrapProse(`${quotedForTerminal(slug, MAX_SERVER_TEXT_CHARS)} doesn't look like an app's address name: ${flatProblem}. Copy the name from the app's driggsby.json ("slug"), or from npx driggsby@latest versions run in its folder.`), 2);
91
+ }
92
+ // The app already isn't there — for a delete that is closer to done than
93
+ // to broken, and the one wrong suggestion would be "deploy it first".
94
+ function appAlreadyGone(slug) {
95
+ return new CliError(wrapProse(`There's no app named ${quotedForTerminal(slug, MAX_SERVER_TEXT_CHARS)} on your Driggsby account — maybe it was already deleted. Nothing was changed.`), 1);
96
+ }
97
+ // With no name given, the app is the one this folder deploys. Only a
98
+ // genuinely missing driggsby.json gets the "name it directly" guidance;
99
+ // a present-but-broken config surfaces its own precise error, the same
100
+ // one deploy and versions would show.
101
+ async function slugFromProjectFolder(projectDirectory) {
102
+ try {
103
+ await access(join(projectDirectory, "driggsby.json"));
104
+ }
105
+ catch {
106
+ throw new CliError(`${wrapProse("This folder doesn't have a driggsby.json, so there's no app here to delete. Name the app directly — the \"slug\" in its driggsby.json:")}\n` +
107
+ " npx driggsby@latest delete <APP-ADDRESS-NAME>", 1);
108
+ }
109
+ const config = await readProjectConfig(projectDirectory);
110
+ return config.slug;
111
+ }
112
+ // The person types the address name back. Anything else — including an
113
+ // empty answer — deletes nothing and says so. Scripts and agents have no
114
+ // one to ask, so they must say --yes explicitly.
115
+ async function confirmOrStop(slug, io) {
116
+ if (!io.interactive) {
117
+ // Already shape-validated, so echoing it into a command line is safe.
118
+ throw new CliError(`${wrapProse("Deleting can't be undone, and this terminal can't ask you to confirm. Say it explicitly:")}\n` +
119
+ ` npx driggsby@latest delete ${slug} --yes`, 2);
120
+ }
121
+ const answer = (await io.ask("Type the app's address name to confirm: ")).trim();
122
+ if (answer !== slug) {
123
+ throw new CliError("That didn't match, so nothing was deleted.", 1);
124
+ }
125
+ }
@@ -42,20 +42,25 @@ export async function runVersions(options, environment = defaultCredentialEnviro
42
42
  throw deployFailure(error, baseUrl, "npx driggsby@latest versions");
43
43
  }
44
44
  }
45
- // A 404 here means the app has never been deployed from this account, and
46
- // the fix is a deploy — not a retry.
47
- export async function fetchVersionList(api, slug) {
45
+ // A 404 here means the app isn't on this account. For versions and
46
+ // rollback the fix is a deploy; a caller whose right next step differs
47
+ // (delete) passes its own notFound shape. The slug is quoted and capped
48
+ // either way — delete hands this an argv string, not only config values.
49
+ export async function fetchVersionList(api, slug, notFound = firstDeployCreatesIt) {
48
50
  try {
49
51
  return await listVersions(api, slug);
50
52
  }
51
53
  catch (error) {
52
54
  if (error instanceof DeployApiError && error.status === 404) {
53
- throw new CliError(`${wrapProse(`There's no app named ${slug} on your Driggsby account yet. Its first deploy creates it:`)}\n` +
54
- " npx driggsby@latest deploy", 1);
55
+ throw notFound(slug);
55
56
  }
56
57
  throw error;
57
58
  }
58
59
  }
60
+ function firstDeployCreatesIt(slug) {
61
+ return new CliError(`${wrapProse(`There's no app named ${quotedForTerminal(slug, MAX_SERVER_TEXT_CHARS)} on your Driggsby account yet. Its first deploy creates it:`)}\n` +
62
+ " npx driggsby@latest deploy", 1);
63
+ }
59
64
  export function renderVersionRows(list) {
60
65
  const rows = list.versions.map((version) => ({
61
66
  label: `v${version.number}`,
package/dist/help.js CHANGED
@@ -19,6 +19,7 @@ Commands:
19
19
  deploy Deploy the app in this directory to Driggsby.
20
20
  rollback Switch which deployed version of your app is live.
21
21
  versions List your deployed app's versions.
22
+ delete Permanently remove a deployed app.
22
23
  query Run one Driggsby data tool and print its result as JSON.
23
24
 
24
25
  Options:
@@ -128,6 +129,19 @@ Options:
128
129
  npx driggsby@latest versions.
129
130
  -h, --help Print help
130
131
  `;
132
+ export const DELETE_HELP = `Permanently remove a deployed app.
133
+
134
+ This deletes the app and every version Driggsby kept for it. There is no
135
+ undo: the app's address shows nothing from that moment on. With no name,
136
+ the app in this folder's driggsby.json is the one deleted. You'll be asked
137
+ to type the name back to confirm; scripts and agents pass --yes instead.
138
+
139
+ Usage: npx driggsby@latest delete [APP-ADDRESS-NAME] [--yes]
140
+
141
+ Options:
142
+ --yes Skip the confirmation question. For scripts and agents.
143
+ -h, --help Print help
144
+ `;
131
145
  export const VERSIONS_HELP = `List your deployed app's versions.
132
146
 
133
147
  Shows every version Driggsby has kept for the app in this directory, which
@@ -176,12 +176,13 @@ h2 {
176
176
  font-variant-numeric: tabular-nums;
177
177
  }
178
178
 
179
- /* The skeleton ships in the HTML so the first paint is the final layout as
180
- muted bars; the first render replaces it and fades in (see app.js). Each
181
- bar's margins pad it to exactly the height of the text it stands in for,
182
- and the columns holding stacked bars are flex so those margins never
183
- collapse — the skeleton page and the data page measure identical, and
184
- nothing shifts when data lands. */
179
+ /* The skeleton ships in the HTML so the first paint is the final layout
180
+ as muted bars; the first data render replaces it in a dissolve (see the
181
+ "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. */
185
186
  .skeleton {
186
187
  border-radius: var(--radius-bar);
187
188
  background: var(--bg-mute);
@@ -230,18 +231,10 @@ h2 {
230
231
  50% { opacity: 0.55; }
231
232
  }
232
233
 
233
- .fade-in {
234
- animation: fade-in 200ms ease-out;
235
- }
236
-
237
- @keyframes fade-in {
238
- from { opacity: 0; }
239
- to { opacity: 1; }
240
- }
241
-
234
+ /* The skeleton-to-data dissolve itself is app.js's "Settling in" View
235
+ Transition; it needs no CSS here and already sits out reduced motion. */
242
236
  @media (prefers-reduced-motion: reduce) {
243
- .skeleton,
244
- .fade-in {
237
+ .skeleton {
245
238
  animation: none;
246
239
  }
247
240
  }
@@ -6,8 +6,8 @@
6
6
  // tool result shapes only in structure, so the scaffold's render functions
7
7
  // work unchanged when live data replaces them. The HTML ships a skeleton
8
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.
9
+ // script runs — is muted bars that the first render replaces at the exact
10
+ // same size, dissolving one into the other, so nothing shifts or pops.
11
11
  export function indexHtml(slug) {
12
12
  return `<!doctype html>
13
13
  <html lang="en">
@@ -190,19 +190,6 @@ function element(tag, className, text) {
190
190
  return node;
191
191
  }
192
192
 
193
- // The page ships with a skeleton of this exact layout in its HTML — muted
194
- // bars where the numbers will be — so the very first paint is already the
195
- // final shape. The first render into a container replaces its skeleton and
196
- // fades in, so data arriving reads as the page settling, never as content
197
- // popping. classList.add is idempotent and re-adding a class never restarts
198
- // its animation, so later live updates repaint in place without re-fading.
199
- // aria-busy ships in the HTML so assistive tech hears "loading" until the
200
- // first real content lands.
201
- function revealOnce(container) {
202
- container.classList.add("fade-in");
203
- container.removeAttribute("aria-busy");
204
- }
205
-
206
193
  function renderOverview(result) {
207
194
  const rollups = (result && result.summary_rollups) || {};
208
195
  const container = document.getElementById("overview");
@@ -221,7 +208,9 @@ function renderOverview(result) {
221
208
  );
222
209
  container.append(stat);
223
210
  }
224
- revealOnce(container);
211
+ // aria-busy ships in the HTML so assistive tech hears "loading" until
212
+ // the first content lands; every render clears it (harmless once gone).
213
+ container.removeAttribute("aria-busy");
225
214
  }
226
215
 
227
216
  function renderAccounts(result) {
@@ -231,7 +220,7 @@ function renderAccounts(result) {
231
220
  container.replaceChildren();
232
221
  if (accounts.length === 0) {
233
222
  container.append(element("div", "empty-row", "No linked accounts yet."));
234
- revealOnce(container);
223
+ container.removeAttribute("aria-busy");
235
224
  return;
236
225
  }
237
226
  for (const account of accounts) {
@@ -262,7 +251,99 @@ function renderAccounts(result) {
262
251
  );
263
252
  container.append(row);
264
253
  }
265
- revealOnce(container);
254
+ container.removeAttribute("aria-busy");
255
+ }
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // Settling in. The first data a section shows replaces its skeleton in a
259
+ // dissolve: the browser snapshots the page, the paint lands, and old
260
+ // crossfades into new (a View Transition — pixels that did not change
261
+ // don't visibly change, so what reads as animating is the bars becoming
262
+ // numbers). The skeleton and the data measure identical (see styles.css),
263
+ // so the dissolve reads as the page coming into focus — never a blink, a
264
+ // shift, or a pop. Where the browser has no View Transitions, or the
265
+ // person prefers reduced motion, the swap is simply instant.
266
+ //
267
+ // The beat below covers sections whose data lands together (the common
268
+ // case). A section whose first data trails in later gets its own beat and
269
+ // its own dissolve; starting it skips whatever remained of the first one's
270
+ // animation — the paints always land, only the crossfade is cut short.
271
+ // ---------------------------------------------------------------------------
272
+
273
+ // How long a section's first paint may wait so sections whose data lands
274
+ // in the same beat dissolve in together instead of one by one.
275
+ const SETTLE_TOGETHER_MS = 50;
276
+
277
+ let pendingFirstPaints = [];
278
+ let settleFlushScheduled = false;
279
+
280
+ function settle(paint) {
281
+ const reducedMotion =
282
+ typeof window.matchMedia === "function" &&
283
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
284
+ if (typeof document.startViewTransition !== "function" || reducedMotion) {
285
+ paint();
286
+ return;
287
+ }
288
+ pendingFirstPaints.push(paint);
289
+ if (settleFlushScheduled) return; // this paint rides the beat already in flight
290
+ settleFlushScheduled = true;
291
+ setTimeout(() => {
292
+ // Collected inside the flush, which runs inside the transition
293
+ // callback, so a first result that lands between the beat ending and
294
+ // the snapshot being taken joins this dissolve instead of cutting it
295
+ // short with a second one.
296
+ const flush = () => {
297
+ const paints = pendingFirstPaints;
298
+ pendingFirstPaints = [];
299
+ settleFlushScheduled = false;
300
+ for (const apply of paints) {
301
+ // One section's render must never stop another's: the scaffold
302
+ // is meant to be edited, and a throw mid-edit stays that
303
+ // section's problem — it heals on its own next result.
304
+ try {
305
+ apply();
306
+ } catch (error) {
307
+ console.error(error);
308
+ }
309
+ }
310
+ };
311
+ try {
312
+ const transition = document.startViewTransition(flush);
313
+ // A skipped transition (a hidden tab, a second dissolve starting)
314
+ // still runs flush; only the animation is dropped. Without this,
315
+ // the skip surfaces as an uncaught rejection in the console.
316
+ transition.ready.catch(() => {});
317
+ } catch (error) {
318
+ // If starting the transition itself threw, nothing will call
319
+ // flush; run it plain so the paints land and the beat resets.
320
+ console.error(error);
321
+ flush();
322
+ }
323
+ }, SETTLE_TOGETHER_MS);
324
+ }
325
+
326
+ // Wraps a render function for its watch: the first result goes through
327
+ // settle() above, and only the latest result paints if more arrive while
328
+ // that dissolve is still pending. Every later result repaints in place
329
+ // with no animation at all.
330
+ function settledRenderer(render) {
331
+ let hasSettled = false;
332
+ let firstPaintQueued = false;
333
+ let latestResult;
334
+ return (result) => {
335
+ if (hasSettled) {
336
+ render(result);
337
+ return;
338
+ }
339
+ latestResult = result;
340
+ if (firstPaintQueued) return;
341
+ firstPaintQueued = true;
342
+ settle(() => {
343
+ hasSettled = true;
344
+ render(latestResult);
345
+ });
346
+ };
266
347
  }
267
348
 
268
349
  // ---------------------------------------------------------------------------
@@ -273,7 +354,7 @@ function renderAccounts(result) {
273
354
  // shows its own notice explaining that the page runs with real data only
274
355
  // inside Driggsby. Embedded, sample numbers never paint at all — the
275
356
  // shipped skeleton holds the layout until the first real results replace
276
- // it and fade in.
357
+ // it in a dissolve (see "Settling in" above).
277
358
  // ---------------------------------------------------------------------------
278
359
 
279
360
  const standalone = window.parent === window;
@@ -284,12 +365,8 @@ if (standalone) {
284
365
  }
285
366
 
286
367
  if (window.driggsby) {
287
- driggsby.watch("get_overview", {}, (result) => {
288
- renderOverview(result);
289
- });
290
- driggsby.watch("list_accounts", {}, (result) => {
291
- renderAccounts(result);
292
- });
368
+ driggsby.watch("get_overview", {}, settledRenderer(renderOverview));
369
+ driggsby.watch("list_accounts", {}, settledRenderer(renderAccounts));
293
370
  }
294
371
  `;
295
372
  // "background" is the template's own page background (styles.css
package/dist/main.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { homedir } from "node:os";
4
4
  import { parseArgv } from "./args.js";
5
5
  import { CliError } from "./cli-error.js";
6
+ import { runDelete } from "./deploy/delete-command.js";
6
7
  import { runDeploy } from "./deploy/deploy-command.js";
7
8
  import { runDev } from "./dev/dev-command.js";
8
9
  import { runDevStop } from "./dev/dev-stop.js";
@@ -39,6 +40,8 @@ async function run(argv) {
39
40
  return command.stop ? await runDevStop(homedir()) : await runDev();
40
41
  case "deploy":
41
42
  return await runDeploy({ preview: command.preview });
43
+ case "delete":
44
+ return await runDelete({ slug: command.slug, yes: command.yes });
42
45
  case "rollback":
43
46
  return await runRollback({ toVersion: command.toVersion });
44
47
  case "versions":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "driggsby",
3
- "version": "0.3.1",
3
+ "version": "0.3.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.3.1",
32
- "@driggsby/sdk": "0.3.1"
31
+ "@driggsby/deploy": "0.3.3",
32
+ "@driggsby/sdk": "0.3.3"
33
33
  }
34
34
  }