@dropthis/cli 0.4.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/dist/cli.cjs CHANGED
@@ -29,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
30
  mod
31
31
  ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
33
 
33
34
  // src/inline-auth.ts
34
35
  var inline_auth_exports = {};
@@ -150,42 +151,12 @@ var init_inline_auth = __esm({
150
151
  }
151
152
  });
152
153
 
153
- // src/program.ts
154
- var import_commander = require("commander");
155
- var import_picocolors4 = __toESM(require("picocolors"), 1);
156
-
157
- // src/auth.ts
158
- async function resolveCredential(input) {
159
- if (input.apiKey) return { apiKey: input.apiKey, source: "flag" };
160
- if (input.env.DROPTHIS_API_KEY) {
161
- return { apiKey: input.env.DROPTHIS_API_KEY, source: "env" };
162
- }
163
- const stored = await input.store.read();
164
- if (!stored) return null;
165
- return {
166
- apiKey: stored.apiKey,
167
- source: "storage",
168
- ...stored.keyId ? { keyId: stored.keyId } : {},
169
- ...stored.keyLast4 ? { keyLast4: stored.keyLast4 } : {},
170
- ...stored.accountId ? { accountId: stored.accountId } : {},
171
- ...stored.email ? { email: stored.email } : {}
172
- };
173
- }
174
- async function requireCredential(input) {
175
- const credential = await resolveCredential(input);
176
- if (!credential) {
177
- throw Object.assign(new Error("No API key found."), {
178
- code: "auth_error"
179
- });
180
- }
181
- return credential;
182
- }
183
- function maskKey(apiKey) {
184
- return apiKey.length <= 8 ? "sk_..." : `${apiKey.slice(0, 3)}...${apiKey.slice(-4)}`;
185
- }
186
-
187
- // src/fmt.ts
188
- var import_picocolors2 = __toESM(require("picocolors"), 1);
154
+ // src/cli.ts
155
+ var cli_exports = {};
156
+ __export(cli_exports, {
157
+ writeUsageErrorEnvelope: () => writeUsageErrorEnvelope
158
+ });
159
+ module.exports = __toCommonJS(cli_exports);
189
160
 
190
161
  // src/output.ts
191
162
  var UPLOAD_NEXT_ACTIONS = {
@@ -213,7 +184,7 @@ function apiErrorEnvelope(error2) {
213
184
  return {
214
185
  ok: false,
215
186
  error: {
216
- code: error2.code ?? "api_error",
187
+ code: normalizeApiErrorCode(error2),
217
188
  message: error2.message,
218
189
  ...error2.statusCode !== void 0 ? { status: error2.statusCode } : {},
219
190
  ...typeof error2.detail === "string" ? { detail: error2.detail } : {},
@@ -226,6 +197,11 @@ function apiErrorEnvelope(error2) {
226
197
  }
227
198
  };
228
199
  }
200
+ function normalizeApiErrorCode(error2) {
201
+ if (error2.code) return error2.code;
202
+ if (error2.statusCode === 404) return "not_found";
203
+ return "api_error";
204
+ }
229
205
  function nextActionForApiError(error2) {
230
206
  if (error2.suggestion) return error2.suggestion;
231
207
  const uploadNextAction = error2.code ? UPLOAD_NEXT_ACTIONS[error2.code] : void 0;
@@ -233,6 +209,9 @@ function nextActionForApiError(error2) {
233
209
  if (error2.code === "revision_conflict") {
234
210
  return "Fetch the drop, merge your changes, and retry with the current revision.";
235
211
  }
212
+ if (error2.statusCode === 404 || error2.code === "not_found") {
213
+ return "Check the drop id or slug and retry; list your drops with dropthis drops list.";
214
+ }
236
215
  if (error2.statusCode === 401 || error2.code === "missing_api_key") {
237
216
  return "Authenticate with dropthis login or set DROPTHIS_API_KEY.";
238
217
  }
@@ -248,6 +227,7 @@ function nextActionForApiError(error2) {
248
227
  return "Fix the request or retry after checking the drop state.";
249
228
  }
250
229
  function exitCodeFor(code) {
230
+ if (code === "usage_error") return 2;
251
231
  if (code === "invalid_usage") return 2;
252
232
  if (code === "auth_error") return 3;
253
233
  if (code === "local_input_error") return 4;
@@ -255,6 +235,43 @@ function exitCodeFor(code) {
255
235
  return 1;
256
236
  }
257
237
 
238
+ // src/program.ts
239
+ var import_commander = require("commander");
240
+ var import_picocolors4 = __toESM(require("picocolors"), 1);
241
+
242
+ // src/auth.ts
243
+ async function resolveCredential(input) {
244
+ if (input.apiKey) return { apiKey: input.apiKey, source: "flag" };
245
+ if (input.env.DROPTHIS_API_KEY) {
246
+ return { apiKey: input.env.DROPTHIS_API_KEY, source: "env" };
247
+ }
248
+ const stored = await input.store.read();
249
+ if (!stored) return null;
250
+ return {
251
+ apiKey: stored.apiKey,
252
+ source: "storage",
253
+ ...stored.keyId ? { keyId: stored.keyId } : {},
254
+ ...stored.keyLast4 ? { keyLast4: stored.keyLast4 } : {},
255
+ ...stored.accountId ? { accountId: stored.accountId } : {},
256
+ ...stored.email ? { email: stored.email } : {}
257
+ };
258
+ }
259
+ async function requireCredential(input) {
260
+ const credential = await resolveCredential(input);
261
+ if (!credential) {
262
+ throw Object.assign(new Error("No API key found."), {
263
+ code: "auth_error"
264
+ });
265
+ }
266
+ return credential;
267
+ }
268
+ function maskKey(apiKey) {
269
+ return apiKey.length <= 8 ? "sk_..." : `${apiKey.slice(0, 3)}...${apiKey.slice(-4)}`;
270
+ }
271
+
272
+ // src/fmt.ts
273
+ var import_picocolors2 = __toESM(require("picocolors"), 1);
274
+
258
275
  // src/ui.ts
259
276
  var import_picocolors = __toESM(require("picocolors"), 1);
260
277
  var symbols = {
@@ -295,13 +312,13 @@ function writeResult(deps, link, verb, data) {
295
312
  }
296
313
  function formatDropDetails(data) {
297
314
  const parts = [];
298
- const size = data.sizeBytes ?? data.size_bytes;
315
+ const size = data.sizeBytes;
299
316
  if (typeof size === "number") parts.push(formatBytes(size));
300
- const ct = data.contentType ?? data.content_type;
317
+ const ct = data.contentType;
301
318
  if (typeof ct === "string") parts.push(ct.split(";")[0]);
302
319
  const vis = data.visibility;
303
320
  if (vis === "unlisted") parts.push("unlisted");
304
- const exp = data.expiresAt ?? data.expires_at;
321
+ const exp = data.expiresAt;
305
322
  if (typeof exp === "string") parts.push(`expires ${exp.split("T")[0]}`);
306
323
  if (parts.length === 0) return "";
307
324
  return ` ${import_picocolors2.default.dim(parts.join(" \xB7 "))}`;
@@ -404,16 +421,66 @@ async function runAccountGet(_input, deps) {
404
421
  }
405
422
  const result = await deps.client.account.get();
406
423
  if (result.error) {
407
- return writeApiErrorSimple(deps, result.error.message);
424
+ return writeApiError(deps, result.error);
408
425
  }
409
426
  const data = result.data;
410
427
  const pairs = [];
411
428
  if (data?.id) pairs.push(["ID", String(data.id)]);
412
429
  if (data?.email) pairs.push(["Email", String(data.email)]);
413
430
  if (data?.plan) pairs.push(["Plan", String(data.plan)]);
431
+ if (data?.displayName) pairs.push(["Name", String(data.displayName)]);
432
+ if (data?.status) pairs.push(["Status", String(data.status)]);
433
+ if (data?.createdAt)
434
+ pairs.push(["Created", String(data.createdAt).split("T")[0]]);
414
435
  writeKv(deps, pairs, { ok: true, account: result.data });
415
436
  return { exitCode: 0 };
416
437
  }
438
+ async function runAccountUpdate(input, deps) {
439
+ try {
440
+ await requireCredential({
441
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
442
+ env: deps.env,
443
+ store: deps.store
444
+ });
445
+ } catch {
446
+ return writeAuthError(deps);
447
+ }
448
+ const result = await deps.client.account.update({
449
+ displayName: input.displayName
450
+ });
451
+ if (result.error) return writeApiError(deps, result.error);
452
+ const data = result.data;
453
+ const pairs = [];
454
+ if (data?.id) pairs.push(["ID", String(data.id)]);
455
+ if (data?.displayName) pairs.push(["Name", String(data.displayName)]);
456
+ writeKv(deps, pairs, { ok: true, account: result.data });
457
+ return { exitCode: 0 };
458
+ }
459
+ async function runAccountDelete(input, deps) {
460
+ if (!input.yes && input.interactive === false) {
461
+ return writeError(
462
+ deps,
463
+ "invalid_usage",
464
+ "Pass --yes to delete the account in non-interactive mode."
465
+ );
466
+ }
467
+ try {
468
+ await requireCredential({
469
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
470
+ env: deps.env,
471
+ store: deps.store
472
+ });
473
+ } catch {
474
+ return writeAuthError(deps);
475
+ }
476
+ const result = await deps.client.account.delete();
477
+ if (result.error) return writeApiError(deps, result.error);
478
+ writeHumanOrJson(deps, success("Account deleted"), {
479
+ ok: true,
480
+ deleted: true
481
+ });
482
+ return { exitCode: 0 };
483
+ }
417
484
 
418
485
  // src/commands/api-keys.ts
419
486
  var prompts = __toESM(require("@clack/prompts"), 1);
@@ -445,12 +512,12 @@ async function runApiKeysCreate(input, deps) {
445
512
  return writeAuthError(deps);
446
513
  }
447
514
  const result = await deps.client.apiKeys.create({ label: input.label });
448
- if (result.error) return writeApiErrorSimple(deps, result.error.message);
515
+ if (result.error) return writeApiError(deps, result.error);
449
516
  const data = result.data;
450
517
  const pairs = [];
451
518
  if (data.id) pairs.push(["ID", String(data.id)]);
452
519
  if (data.key) pairs.push(["Key", String(data.key)]);
453
- if (data.last4) pairs.push(["Last 4", String(data.last4)]);
520
+ if (data.keyLast4) pairs.push(["Last 4", String(data.keyLast4)]);
454
521
  writeKv(deps, pairs, { ok: true, api_key: result.data });
455
522
  return { exitCode: 0 };
456
523
  }
@@ -465,8 +532,8 @@ async function runApiKeysList(_input, deps) {
465
532
  return writeAuthError(deps);
466
533
  }
467
534
  const result = await deps.client.apiKeys.list();
468
- if (result.error) return writeApiErrorSimple(deps, result.error.message);
469
- const raw = unwrapListData(result.data, "apiKeys", "api_keys");
535
+ if (result.error) return writeApiError(deps, result.error);
536
+ const raw = unwrapListData(result.data);
470
537
  const items = Array.isArray(raw) ? raw : void 0;
471
538
  if (deps.outputMode === "human") {
472
539
  if (!items || items.length === 0) {
@@ -474,7 +541,7 @@ async function runApiKeysList(_input, deps) {
474
541
  } else {
475
542
  for (const item of items) {
476
543
  deps.stdout(
477
- `${item.id ?? ""} ${item.label ?? ""} ...${item.last4 ?? ""}
544
+ `${item.id ?? ""} ${item.label ?? ""} ...${item.keyLast4 ?? ""}
478
545
  `
479
546
  );
480
547
  }
@@ -510,7 +577,7 @@ async function runApiKeysDelete(keyId, input, deps) {
510
577
  return writeAuthError(deps);
511
578
  }
512
579
  const result = await deps.client.apiKeys.delete(keyId);
513
- if (result?.error) return writeApiErrorSimple(deps, result.error.message);
580
+ if (result?.error) return writeApiError(deps, result.error);
514
581
  writeHumanOrJson(deps, success(`Deleted ${keyId}`), {
515
582
  ok: true,
516
583
  deleted: true,
@@ -520,12 +587,9 @@ async function runApiKeysDelete(keyId, input, deps) {
520
587
  }
521
588
 
522
589
  // src/catalog.ts
523
- var COMMAND_CATALOG = [
524
- {
525
- name: "publish",
526
- description: "Publish an input and return a Dropthis URL.",
527
- arguments: ["input"],
528
- options: ["--json", "--url", "--dry-run", "--title", "--metadata"],
590
+ var COMMAND_ANNOTATIONS = {
591
+ publish: {
592
+ auth: "required",
529
593
  examples: [
530
594
  "dropthis ./file.html",
531
595
  "dropthis publish ./site --json",
@@ -533,69 +597,70 @@ var COMMAND_CATALOG = [
533
597
  "dropthis publish ./dist --dry-run"
534
598
  ]
535
599
  },
536
- {
537
- name: "deployments list <dropId>",
538
- description: "List deployments for a drop.",
539
- arguments: ["dropId"],
540
- options: ["--json", "--limit", "--cursor"],
600
+ drops: {
541
601
  auth: "required",
542
- examples: ["dropthis deployments list drop_abc --json"]
543
- },
544
- {
545
- name: "deployments get <dropId> <deploymentId>",
546
- description: "Get deployment details for a drop.",
547
- arguments: ["dropId", "deploymentId"],
548
- options: ["--json"],
549
- auth: "required",
550
- examples: ["dropthis deployments get drop_abc dep_abc --json"]
551
- },
552
- {
553
- name: "drops",
554
- description: "List, inspect, update, and delete drops.",
555
602
  examples: [
556
603
  "dropthis drops list --json",
557
604
  "dropthis drops update drop_abc ./site --json",
558
605
  "dropthis drops update drop_abc --title 'New Title' --dry-run"
559
606
  ]
560
607
  },
561
- {
562
- name: "api-keys",
563
- description: "Create, list, and delete API keys.",
608
+ deployments: { auth: "required" },
609
+ "deployments list": {
610
+ auth: "required",
611
+ examples: ["dropthis deployments list drop_abc --json"]
612
+ },
613
+ "deployments get": {
614
+ auth: "required",
615
+ examples: ["dropthis deployments get drop_abc dep_abc --json"]
616
+ },
617
+ "api-keys": {
618
+ auth: "required",
564
619
  examples: ["dropthis api-keys create --label CI --json"]
565
620
  },
566
- {
567
- name: "login",
568
- description: "Authenticate with email OTP and store an API key.",
621
+ login: {
569
622
  examples: [
570
623
  "dropthis login verify --email user@example.com --otp 123456 --json"
571
624
  ]
572
625
  },
573
- {
574
- name: "logout",
575
- description: "Remove stored local credentials.",
576
- examples: ["dropthis logout --json"]
577
- },
578
- {
579
- name: "whoami",
580
- description: "Show current auth source and credential metadata.",
581
- examples: ["dropthis whoami --json"]
582
- },
583
- {
584
- name: "account",
585
- description: "Get account details.",
586
- examples: ["dropthis account --json"]
626
+ logout: { examples: ["dropthis logout --json"] },
627
+ whoami: { auth: "required", examples: ["dropthis whoami --json"] },
628
+ account: { auth: "required", examples: ["dropthis account --json"] },
629
+ "account get": { auth: "required" },
630
+ "account update": {
631
+ auth: "required",
632
+ examples: ["dropthis account update --display-name 'Ada' --json"]
587
633
  },
588
- {
589
- name: "doctor",
590
- description: "Report local CLI diagnostics.",
591
- examples: ["dropthis doctor --json"]
634
+ "account delete": {
635
+ auth: "required",
636
+ examples: ["dropthis account delete --yes --json"]
592
637
  },
593
- {
594
- name: "commands",
595
- description: "Print this machine-readable command tree.",
596
- examples: ["dropthis commands --json"]
597
- }
598
- ];
638
+ doctor: { examples: ["dropthis doctor --json"] },
639
+ commands: { examples: ["dropthis commands --json"] }
640
+ };
641
+ function describeArgs(cmd) {
642
+ return cmd.registeredArguments.map((a) => a.name());
643
+ }
644
+ function describeOptions(cmd) {
645
+ return cmd.options.map((o) => o.long ?? o.short ?? o.flags);
646
+ }
647
+ function toEntry(cmd, path) {
648
+ const fullPath = [...path, cmd.name()];
649
+ const annotation = COMMAND_ANNOTATIONS[fullPath.join(" ")];
650
+ const subs = cmd.commands.map((sub) => toEntry(sub, fullPath));
651
+ return {
652
+ name: cmd.name(),
653
+ description: cmd.description(),
654
+ ...describeArgs(cmd).length ? { arguments: describeArgs(cmd) } : {},
655
+ ...describeOptions(cmd).length ? { options: describeOptions(cmd) } : {},
656
+ ...annotation?.auth ? { auth: annotation.auth } : {},
657
+ ...annotation?.examples ? { examples: annotation.examples } : {},
658
+ ...subs.length ? { subcommands: subs } : {}
659
+ };
660
+ }
661
+ function buildCatalog(program) {
662
+ return program.commands.map((cmd) => toEntry(cmd, []));
663
+ }
599
664
 
600
665
  // src/commands/commands.ts
601
666
  async function runCommands(_input, deps) {
@@ -603,7 +668,7 @@ async function runCommands(_input, deps) {
603
668
  `${JSON.stringify({
604
669
  ok: true,
605
670
  output: "JSON by default in CI, pipes, non-TTY, --json, or --quiet.",
606
- commands: COMMAND_CATALOG
671
+ commands: buildCatalog(deps.program)
607
672
  })}
608
673
  `
609
674
  );
@@ -640,7 +705,7 @@ async function runDeploymentsList(dropId, input, deps) {
640
705
  } else if (Array.isArray(items)) {
641
706
  for (const item of items) {
642
707
  deps.stdout(
643
- `${item.id ?? ""} rev ${item.revision ?? "?"} ${item.created_at ?? ""}
708
+ `${item.id ?? ""} rev ${item.revision ?? "?"} ${item.createdAt ?? ""}
644
709
  `
645
710
  );
646
711
  }
@@ -671,7 +736,7 @@ async function runDeploymentsGet(dropId, deploymentId, _input, deps) {
671
736
  if (data.id) pairs.push(["ID", String(data.id)]);
672
737
  if (data.revision !== void 0)
673
738
  pairs.push(["Revision", String(data.revision)]);
674
- if (data.created_at) pairs.push(["Created", String(data.created_at)]);
739
+ if (data.createdAt) pairs.push(["Created", String(data.createdAt)]);
675
740
  writeKv(deps, pairs, { ok: true, deployment: result.data });
676
741
  return { exitCode: 0 };
677
742
  }
@@ -689,13 +754,13 @@ async function runDoctor(_input, deps) {
689
754
  const authSource = credential?.source ?? "missing";
690
755
  const storageBackend = stored?.storage ?? "none";
691
756
  const pairs = [
692
- ["Version", "0.4.1"],
757
+ ["Version", "0.6.0"],
693
758
  ["Auth", authSource],
694
759
  ["Storage", storageBackend]
695
760
  ];
696
761
  writeKv(deps, pairs, {
697
762
  ok: true,
698
- version: "0.4.1",
763
+ version: "0.6.0",
699
764
  auth: { source: authSource },
700
765
  storage: { backend: storageBackend }
701
766
  });
@@ -719,7 +784,7 @@ async function runDropsList(input, deps) {
719
784
  ...input.cursor ? { cursor: input.cursor } : {}
720
785
  };
721
786
  const result = await deps.client.drops.list(params);
722
- if (result.error) return writeApiErrorSimple(deps, result.error.message);
787
+ if (result.error) return writeApiError(deps, result.error);
723
788
  const raw = unwrapListData(result.data, "drops");
724
789
  const items = Array.isArray(raw) ? raw : void 0;
725
790
  if (deps.outputMode === "human") {
@@ -749,7 +814,7 @@ async function runDropsGet(dropId, _input, deps) {
749
814
  return writeAuthError(deps);
750
815
  }
751
816
  const result = await deps.client.drops.get(dropId);
752
- if (result.error) return writeApiErrorSimple(deps, result.error.message);
817
+ if (result.error) return writeApiError(deps, result.error);
753
818
  const data = result.data;
754
819
  const pairs = [];
755
820
  if (data.id) pairs.push(["ID", String(data.id)]);
@@ -758,7 +823,7 @@ async function runDropsGet(dropId, _input, deps) {
758
823
  if (data.visibility) pairs.push(["Visibility", String(data.visibility)]);
759
824
  if (data.revision !== void 0)
760
825
  pairs.push(["Revision", String(data.revision)]);
761
- if (data.created_at) pairs.push(["Created", String(data.created_at)]);
826
+ if (data.createdAt) pairs.push(["Created", String(data.createdAt)]);
762
827
  if (data.accessible !== void 0)
763
828
  pairs.push(["Accessible", String(data.accessible)]);
764
829
  if (data.persistent !== void 0)
@@ -794,7 +859,7 @@ async function runDropsDelete(dropId, input, deps) {
794
859
  return writeAuthError(deps);
795
860
  }
796
861
  const result = await deps.client.drops.delete(dropId);
797
- if (result?.error) return writeApiErrorSimple(deps, result.error.message);
862
+ if (result?.error) return writeApiError(deps, result.error);
798
863
  writeHumanOrJson(deps, success(`Deleted ${dropId}`), {
799
864
  ok: true,
800
865
  deleted: true,
@@ -946,51 +1011,16 @@ async function runLogout(input, deps) {
946
1011
  return { exitCode: 0 };
947
1012
  }
948
1013
 
949
- // src/commands/publish.ts
950
- var import_promises3 = require("fs/promises");
951
- var import_node_path2 = require("path");
952
-
953
1014
  // src/drop-operations.ts
954
1015
  var import_node_crypto = require("crypto");
955
- var import_node_path = require("path");
956
1016
  var MAX_BUNDLE_FILE_COUNT = 200;
957
1017
  function isFileSystemError(error2) {
958
1018
  if (!(error2 instanceof Error)) return false;
959
1019
  const code = error2.code;
960
1020
  return code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "ELIMIT";
961
1021
  }
962
- function mimeForPath(filePath) {
963
- const ext = (0, import_node_path.extname)(filePath).toLowerCase();
964
- const types = {
965
- ".html": "text/html",
966
- ".htm": "text/html",
967
- ".css": "text/css",
968
- ".js": "text/javascript",
969
- ".mjs": "text/javascript",
970
- ".json": "application/json",
971
- ".xml": "application/xml",
972
- ".svg": "image/svg+xml",
973
- ".txt": "text/plain",
974
- ".md": "text/markdown",
975
- ".jpg": "image/jpeg",
976
- ".jpeg": "image/jpeg",
977
- ".png": "image/png",
978
- ".gif": "image/gif",
979
- ".webp": "image/webp",
980
- ".ico": "image/x-icon",
981
- ".avif": "image/avif",
982
- ".pdf": "application/pdf",
983
- ".zip": "application/zip",
984
- ".woff": "font/woff",
985
- ".woff2": "font/woff2",
986
- ".ttf": "font/ttf",
987
- ".mp4": "video/mp4",
988
- ".webm": "video/webm"
989
- };
990
- return types[ext] ?? "application/octet-stream";
991
- }
992
- function withIdempotencyKey(options, prefix) {
993
- return options.idempotencyKey ? options : { ...options, idempotencyKey: `cli_${prefix}_${(0, import_node_crypto.randomUUID)()}` };
1022
+ function withIdempotencyKey(options, _prefix) {
1023
+ return options.idempotencyKey ? options : { ...options, idempotencyKey: (0, import_node_crypto.randomUUID)() };
994
1024
  }
995
1025
  function validateManifestFileCount(count) {
996
1026
  if (count > MAX_BUNDLE_FILE_COUNT) {
@@ -1003,11 +1033,23 @@ function validateManifestFileCount(count) {
1003
1033
  }
1004
1034
  }
1005
1035
  function formatDryRunManifest(prepared, extra) {
1036
+ if (prepared.kind === "source") {
1037
+ return {
1038
+ ok: true,
1039
+ dryRun: true,
1040
+ kind: "source",
1041
+ ...extra,
1042
+ sourceUrl: prepared.sourceUrl,
1043
+ options: prepared.options,
1044
+ ...prepared.metadata ? { metadata: prepared.metadata } : {}
1045
+ };
1046
+ }
1006
1047
  const totalBytes = prepared.manifest.files.reduce(
1007
1048
  (sum, f) => sum + f.sizeBytes,
1008
1049
  0
1009
1050
  );
1010
1051
  return {
1052
+ ok: true,
1011
1053
  dryRun: true,
1012
1054
  kind: "staged",
1013
1055
  ...extra,
@@ -1020,15 +1062,6 @@ function formatDryRunManifest(prepared, extra) {
1020
1062
  totalBytes
1021
1063
  };
1022
1064
  }
1023
- function dryRunOptionsBody(options) {
1024
- const result = {};
1025
- if (options.title) result.title = options.title;
1026
- if (options.visibility) result.visibility = options.visibility;
1027
- if (options.password !== void 0) result.password = options.password;
1028
- if (options.noindex !== void 0) result.noindex = options.noindex;
1029
- if (options.expiresAt) result.expires_at = options.expiresAt;
1030
- return result;
1031
- }
1032
1065
 
1033
1066
  // src/options.ts
1034
1067
  var import_promises = require("fs/promises");
@@ -1156,16 +1189,16 @@ async function runPublish(input, raw, deps) {
1156
1189
  if (raw.dryRun) {
1157
1190
  return handlePublishDryRun(input, raw, deps, client);
1158
1191
  }
1159
- const singleInput = Array.isArray(input) ? input[0] : input;
1192
+ const hasInput = Array.isArray(input) ? input.length > 0 : input !== void 0;
1160
1193
  const spin = shouldSpin(deps.outputMode) ? createSpinner("Publishing\u2026", deps.stderr) : void 0;
1161
1194
  try {
1162
- if (raw.fromJson && singleInput) {
1195
+ if (raw.fromJson && hasInput) {
1163
1196
  throw new Error("Use either <input> or --from-json, not both.");
1164
1197
  }
1165
1198
  const options = withIdempotencyKey(await parseDropOptions(raw), "pub");
1166
1199
  const jsonBody = raw.fromJson ? await (deps.readJsonObject ?? readJsonObjectFile)(raw.fromJson) : void 0;
1167
1200
  const idempotencyOpt = options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {};
1168
- const doPublish = (c) => jsonBody ? c.drops.createRaw(jsonBody, idempotencyOpt) : singleInput ? c.publish(singleInput, options) : (() => {
1201
+ const doPublish = (c) => jsonBody ? c.drops.createRaw(jsonBody, idempotencyOpt) : hasInput ? c.publish(input, options) : (() => {
1169
1202
  throw new Error("Publish requires <input> or --from-json.");
1170
1203
  })();
1171
1204
  const result = await doPublish(client);
@@ -1233,7 +1266,7 @@ async function handlePublishDryRun(input, raw, deps, client) {
1233
1266
  raw.fromJson
1234
1267
  );
1235
1268
  deps.stdout(
1236
- `${JSON.stringify({ dryRun: true, kind: "raw_json", endpoint: "POST /drops", body }, null, 2)}
1269
+ `${JSON.stringify({ ok: true, dryRun: true, kind: "raw_json", endpoint: "POST /drops", body }, null, 2)}
1237
1270
  `
1238
1271
  );
1239
1272
  return { exitCode: 0 };
@@ -1242,17 +1275,14 @@ async function handlePublishDryRun(input, raw, deps, client) {
1242
1275
  throw new Error("Publish requires <input> or --from-json.");
1243
1276
  }
1244
1277
  const options = await parseDropOptions(raw);
1245
- if (Array.isArray(input)) {
1246
- return dryRunMultiFile(input, options, deps);
1247
- }
1248
1278
  if (!client.prepare) {
1249
1279
  throw new Error("Client does not support dry-run.");
1250
1280
  }
1251
1281
  const prepared = await client.prepare(input, options);
1252
1282
  const output = formatDryRunManifest(prepared);
1253
- validateManifestFileCount(
1254
- output.manifest.files.length
1255
- );
1283
+ if (prepared.kind === "staged") {
1284
+ validateManifestFileCount(prepared.manifest.files.length);
1285
+ }
1256
1286
  deps.stdout(`${JSON.stringify(output, null, 2)}
1257
1287
  `);
1258
1288
  return { exitCode: 0 };
@@ -1262,48 +1292,11 @@ async function handlePublishDryRun(input, raw, deps, client) {
1262
1292
  return writeError(deps, code, message);
1263
1293
  }
1264
1294
  }
1265
- async function dryRunMultiFile(inputs, options, deps) {
1266
- const files = [];
1267
- for (const filePath of inputs) {
1268
- let info;
1269
- try {
1270
- info = await (0, import_promises3.stat)(filePath);
1271
- } catch {
1272
- return writeError(
1273
- deps,
1274
- "local_input_error",
1275
- `File not found: ${filePath}`
1276
- );
1277
- }
1278
- if (info.isDirectory()) {
1279
- return writeError(
1280
- deps,
1281
- "local_input_error",
1282
- `Cannot mix directories with multiple file arguments. Use a single folder instead: dropthis publish ${filePath}`
1283
- );
1284
- }
1285
- files.push({
1286
- path: (0, import_node_path2.basename)(filePath),
1287
- contentType: mimeForPath(filePath),
1288
- sizeBytes: info.size
1289
- });
1290
- }
1291
- validateManifestFileCount(files.length);
1292
- const entry = options.entry ?? files.find((f) => f.path === "index.html")?.path ?? files[0]?.path ?? null;
1293
- const totalBytes = files.reduce((sum, f) => sum + f.sizeBytes, 0);
1294
- const output = {
1295
- dryRun: true,
1296
- kind: "staged",
1297
- manifest: { files, entry },
1298
- options: dryRunOptionsBody(options),
1299
- totalBytes
1300
- };
1301
- deps.stdout(`${JSON.stringify(output, null, 2)}
1302
- `);
1303
- return { exitCode: 0 };
1304
- }
1305
1295
 
1306
1296
  // src/commands/update.ts
1297
+ var prompts4 = __toESM(require("@clack/prompts"), 1);
1298
+ var NO_UPDATE_MESSAGE = "Nothing to update. Provide an input path or at least one update option.";
1299
+ var NO_UPDATE_NEXT_ACTION = "Run dropthis drops update --help, or retry with an input path (for example, dropthis drops update <dropId> ./dist) or one of: --title, --slug, --visibility, --password, --no-password, --noindex, --index, --expires-at, or --metadata.";
1307
1300
  async function runUpdate(target, input, raw, deps) {
1308
1301
  try {
1309
1302
  await requireCredential({
@@ -1317,14 +1310,31 @@ async function runUpdate(target, input, raw, deps) {
1317
1310
  if (raw.dryRun) {
1318
1311
  return handleUpdateDryRun(target, input, raw, deps);
1319
1312
  }
1320
- const spin = shouldSpin(deps.outputMode) ? createSpinner("Updating\u2026", deps.stderr) : void 0;
1313
+ let spin;
1321
1314
  try {
1322
1315
  assertDropId(target);
1323
1316
  if (raw.fromJson && input) {
1324
1317
  throw new Error("Use either [input] or --from-json, not both.");
1325
1318
  }
1326
- const parsed = await parseDropOptions(raw);
1319
+ let updateInput = input;
1320
+ let parsed = await parseDropOptions(raw);
1321
+ if (!updateInput && !raw.fromJson && !hasUpdateFields(parsed)) {
1322
+ if (shouldPromptForUpdate(deps)) {
1323
+ const prompted = await promptForUpdate(target, deps.prompts ?? prompts4);
1324
+ if (!prompted) return { exitCode: 0 };
1325
+ updateInput = prompted.input;
1326
+ parsed = { ...parsed, ...prompted.options };
1327
+ } else {
1328
+ return writeError(
1329
+ deps,
1330
+ "invalid_usage",
1331
+ NO_UPDATE_MESSAGE,
1332
+ NO_UPDATE_NEXT_ACTION
1333
+ );
1334
+ }
1335
+ }
1327
1336
  const revisionOptions = raw.ifRevision !== void 0 ? { ifRevision: Number(raw.ifRevision) } : {};
1337
+ spin = shouldSpin(deps.outputMode) ? createSpinner("Updating\u2026", deps.stderr) : void 0;
1328
1338
  const result = raw.fromJson ? await deps.client.deployments.createRaw(
1329
1339
  target,
1330
1340
  await (deps.readJsonObject ?? readJsonObjectFile)(raw.fromJson),
@@ -1335,17 +1345,14 @@ async function runUpdate(target, input, raw, deps) {
1335
1345
  },
1336
1346
  "upd"
1337
1347
  )
1338
- ) : input ? await deps.client.update(
1348
+ ) : updateInput ? await deps.client.deploy(
1339
1349
  target,
1340
- input,
1341
- withIdempotencyKey(
1342
- {
1343
- ...parsed,
1344
- ...revisionOptions
1345
- },
1346
- "upd"
1347
- )
1348
- ) : await deps.client.update(target, parsed, revisionOptions);
1350
+ updateInput,
1351
+ withIdempotencyKey({ ...parsed, ...revisionOptions }, "upd")
1352
+ ) : await deps.client.update(
1353
+ target,
1354
+ withIdempotencyKey({ ...parsed, ...revisionOptions }, "upd")
1355
+ );
1349
1356
  if (result.error) {
1350
1357
  spin?.fail();
1351
1358
  return writeApiError(deps, result.error);
@@ -1362,6 +1369,162 @@ async function runUpdate(target, input, raw, deps) {
1362
1369
  return writeError(deps, code, message);
1363
1370
  }
1364
1371
  }
1372
+ function hasUpdateFields(options) {
1373
+ const { idempotencyKey: _, ...fields } = options;
1374
+ return Object.keys(fields).length > 0;
1375
+ }
1376
+ function shouldPromptForUpdate(deps) {
1377
+ return deps.outputMode === "human" && deps.interactive === true;
1378
+ }
1379
+ async function promptForUpdate(target, prompt) {
1380
+ prompt.intro(`Update ${target}`);
1381
+ const fields = await prompt.multiselect({
1382
+ message: "What do you want to update?",
1383
+ required: true,
1384
+ options: [
1385
+ {
1386
+ value: "content",
1387
+ label: "Content",
1388
+ hint: "replace files, folder, URL, or text"
1389
+ },
1390
+ { value: "title", label: "Title", hint: "--title" },
1391
+ { value: "slug", label: "Slug", hint: "--slug" },
1392
+ { value: "visibility", label: "Visibility", hint: "--visibility" },
1393
+ {
1394
+ value: "password",
1395
+ label: "Password",
1396
+ hint: "--password or --no-password"
1397
+ },
1398
+ {
1399
+ value: "indexing",
1400
+ label: "Search indexing",
1401
+ hint: "--noindex or --index"
1402
+ },
1403
+ { value: "expiresAt", label: "Expiration", hint: "--expires-at" },
1404
+ { value: "metadata", label: "Metadata", hint: "--metadata" }
1405
+ ]
1406
+ });
1407
+ if (prompt.isCancel(fields)) {
1408
+ prompt.cancel("Update cancelled.");
1409
+ return void 0;
1410
+ }
1411
+ const selected = new Set(fields);
1412
+ const options = {};
1413
+ let input;
1414
+ if (selected.has("content")) {
1415
+ const value = await prompt.text({
1416
+ message: "Content path, URL, or text",
1417
+ validate: requireNonEmpty("Enter content or a path.")
1418
+ });
1419
+ if (prompt.isCancel(value)) return cancelUpdate(prompt);
1420
+ input = value.trim();
1421
+ }
1422
+ if (selected.has("title")) {
1423
+ const value = await prompt.text({
1424
+ message: "Title",
1425
+ validate: requireNonEmpty("Enter a title.")
1426
+ });
1427
+ if (prompt.isCancel(value)) return cancelUpdate(prompt);
1428
+ options.title = value.trim();
1429
+ }
1430
+ if (selected.has("slug")) {
1431
+ const value = await prompt.text({
1432
+ message: "Slug",
1433
+ validate: requireNonEmpty("Enter a slug.")
1434
+ });
1435
+ if (prompt.isCancel(value)) return cancelUpdate(prompt);
1436
+ options.slug = value.trim();
1437
+ }
1438
+ if (selected.has("visibility")) {
1439
+ const value = await prompt.select({
1440
+ message: "Visibility",
1441
+ options: [
1442
+ { value: "public", label: "Public" },
1443
+ { value: "unlisted", label: "Unlisted" }
1444
+ ]
1445
+ });
1446
+ if (prompt.isCancel(value)) return cancelUpdate(prompt);
1447
+ options.visibility = value;
1448
+ }
1449
+ if (selected.has("password")) {
1450
+ const action = await prompt.select({
1451
+ message: "Password",
1452
+ options: [
1453
+ { value: "set", label: "Set password" },
1454
+ { value: "remove", label: "Remove password" }
1455
+ ]
1456
+ });
1457
+ if (prompt.isCancel(action)) return cancelUpdate(prompt);
1458
+ if (action === "set") {
1459
+ const value = await prompt.password({
1460
+ message: "Password",
1461
+ validate: requireNonEmpty("Enter a password.")
1462
+ });
1463
+ if (prompt.isCancel(value)) return cancelUpdate(prompt);
1464
+ options.password = value;
1465
+ } else {
1466
+ options.password = null;
1467
+ }
1468
+ }
1469
+ if (selected.has("indexing")) {
1470
+ const value = await prompt.select({
1471
+ message: "Search indexing",
1472
+ options: [
1473
+ { value: "noindex", label: "Prevent indexing" },
1474
+ { value: "index", label: "Allow indexing" }
1475
+ ]
1476
+ });
1477
+ if (prompt.isCancel(value)) return cancelUpdate(prompt);
1478
+ options.noindex = value === "noindex";
1479
+ }
1480
+ if (selected.has("expiresAt")) {
1481
+ const value = await prompt.text({
1482
+ message: "Expiration date",
1483
+ placeholder: "2025-12-31T23:59:59Z",
1484
+ validate: validateIsoDate
1485
+ });
1486
+ if (prompt.isCancel(value)) return cancelUpdate(prompt);
1487
+ options.expiresAt = value.trim();
1488
+ }
1489
+ if (selected.has("metadata")) {
1490
+ const value = await prompt.text({
1491
+ message: "Metadata JSON object",
1492
+ placeholder: '{"source":"cli"}',
1493
+ validate: validateJsonObject
1494
+ });
1495
+ if (prompt.isCancel(value)) return cancelUpdate(prompt);
1496
+ options.metadata = parseJsonObject2(value);
1497
+ }
1498
+ return { ...input ? { input } : {}, options };
1499
+ }
1500
+ function cancelUpdate(prompt) {
1501
+ prompt.cancel("Update cancelled.");
1502
+ return void 0;
1503
+ }
1504
+ function requireNonEmpty(message) {
1505
+ return (value) => value?.trim() ? void 0 : message;
1506
+ }
1507
+ function validateIsoDate(value) {
1508
+ if (!value?.trim()) return "Enter an ISO 8601 date.";
1509
+ const d = new Date(value);
1510
+ return Number.isNaN(d.getTime()) ? "Enter a valid ISO 8601 date." : void 0;
1511
+ }
1512
+ function validateJsonObject(value) {
1513
+ if (!value?.trim()) return "Enter a JSON object.";
1514
+ try {
1515
+ parseJsonObject2(value);
1516
+ return void 0;
1517
+ } catch {
1518
+ return "Enter a valid JSON object.";
1519
+ }
1520
+ }
1521
+ function parseJsonObject2(value) {
1522
+ const parsed = JSON.parse(value);
1523
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1524
+ throw new Error("Metadata must be a JSON object.");
1525
+ }
1526
+ return parsed;
1527
+ }
1365
1528
  async function handleUpdateDryRun(target, input, raw, deps) {
1366
1529
  if (raw.url) {
1367
1530
  return writeError(
@@ -1380,7 +1543,7 @@ async function handleUpdateDryRun(target, input, raw, deps) {
1380
1543
  raw.fromJson
1381
1544
  );
1382
1545
  deps.stdout(
1383
- `${JSON.stringify({ dryRun: true, kind: "raw_json", endpoint: `POST /drops/${target}/deployments`, body }, null, 2)}
1546
+ `${JSON.stringify({ ok: true, dryRun: true, kind: "raw_json", endpoint: `POST /drops/${target}/deployments`, body }, null, 2)}
1384
1547
  `
1385
1548
  );
1386
1549
  return { exitCode: 0 };
@@ -1396,7 +1559,7 @@ async function handleUpdateDryRun(target, input, raw, deps) {
1396
1559
  if (options.expiresAt) fields.expires_at = options.expiresAt;
1397
1560
  if (options.metadata) fields.metadata = options.metadata;
1398
1561
  deps.stdout(
1399
- `${JSON.stringify({ dryRun: true, kind: "options_update", target, fields }, null, 2)}
1562
+ `${JSON.stringify({ ok: true, dryRun: true, kind: "options_update", target, fields }, null, 2)}
1400
1563
  `
1401
1564
  );
1402
1565
  return { exitCode: 0 };
@@ -1406,7 +1569,9 @@ async function handleUpdateDryRun(target, input, raw, deps) {
1406
1569
  }
1407
1570
  const prepared = await deps.client.prepare(input, options);
1408
1571
  const output = formatDryRunManifest(prepared, { target });
1409
- validateManifestFileCount(prepared.manifest.files.length);
1572
+ if (prepared.kind === "staged") {
1573
+ validateManifestFileCount(prepared.manifest.files.length);
1574
+ }
1410
1575
  deps.stdout(`${JSON.stringify(output, null, 2)}
1411
1576
  `);
1412
1577
  return { exitCode: 0 };
@@ -1427,6 +1592,7 @@ function assertDropId(target) {
1427
1592
  // src/commands/whoami.ts
1428
1593
  async function runWhoami(_input, deps) {
1429
1594
  const credential = await resolveCredential({
1595
+ ...deps.apiKey ? { apiKey: deps.apiKey } : {},
1430
1596
  env: deps.env,
1431
1597
  store: deps.store
1432
1598
  });
@@ -1437,21 +1603,34 @@ async function runWhoami(_input, deps) {
1437
1603
  });
1438
1604
  return { exitCode: 0 };
1439
1605
  }
1606
+ const result = await deps.client.account.get();
1607
+ if (result.error) {
1608
+ writeHumanOrJson(
1609
+ deps,
1610
+ `Not authenticated: ${result.error.message}. Run dropthis login.`,
1611
+ { ok: true, authenticated: false, reason: result.error.message }
1612
+ );
1613
+ return { exitCode: 0 };
1614
+ }
1615
+ const account = result.data ?? {};
1440
1616
  const masked = maskKey(credential.apiKey);
1617
+ const last4 = credential.keyLast4 ?? credential.apiKey.slice(-4);
1441
1618
  const pairs = [];
1442
- if (credential.email) pairs.push(["Email", credential.email]);
1619
+ if (credential.email ?? account.email)
1620
+ pairs.push(["Email", String(credential.email ?? account.email)]);
1443
1621
  pairs.push(["Key", masked]);
1444
1622
  pairs.push(["Source", credential.source]);
1445
- if (credential.accountId) pairs.push(["Account", credential.accountId]);
1623
+ if (credential.accountId ?? account.id)
1624
+ pairs.push(["Account", String(credential.accountId ?? account.id)]);
1446
1625
  const jsonEnvelope = {
1447
1626
  ok: true,
1448
1627
  authenticated: true,
1449
1628
  source: credential.source,
1450
1629
  masked_key: masked,
1451
1630
  ...credential.keyId ? { key_id: credential.keyId } : {},
1452
- ...credential.keyLast4 ? { key_last4: credential.keyLast4 } : {},
1453
- ...credential.accountId ? { account_id: credential.accountId } : {},
1454
- ...credential.email ? { email: credential.email } : {}
1631
+ ...last4 ? { key_last4: last4 } : {},
1632
+ ...credential.accountId ?? account.id ? { account_id: String(credential.accountId ?? account.id) } : {},
1633
+ ...credential.email ?? account.email ? { email: String(credential.email ?? account.email) } : {}
1455
1634
  };
1456
1635
  writeKv(deps, pairs, jsonEnvelope);
1457
1636
  return { exitCode: 0 };
@@ -1484,9 +1663,9 @@ function createContext(input) {
1484
1663
  }
1485
1664
 
1486
1665
  // src/storage.ts
1487
- var import_promises4 = require("fs/promises");
1666
+ var import_promises3 = require("fs/promises");
1488
1667
  var import_node_os = require("os");
1489
- var import_node_path3 = require("path");
1668
+ var import_node_path = require("path");
1490
1669
  var import_keyring = require("@napi-rs/keyring");
1491
1670
  var MemoryCredentialStore = class {
1492
1671
  credential = null;
@@ -1518,7 +1697,7 @@ var InsecureFileCredentialStore = class {
1518
1697
  });
1519
1698
  }
1520
1699
  async clear() {
1521
- await (0, import_promises4.rm)(this.filePath, { force: true });
1700
+ await (0, import_promises3.rm)(this.filePath, { force: true });
1522
1701
  }
1523
1702
  };
1524
1703
  var KeyringCredentialStore = class {
@@ -1545,7 +1724,7 @@ var KeyringCredentialStore = class {
1545
1724
  }
1546
1725
  async clear() {
1547
1726
  await this.keyring.deletePassword();
1548
- await (0, import_promises4.rm)(this.filePath, { force: true });
1727
+ await (0, import_promises3.rm)(this.filePath, { force: true });
1549
1728
  }
1550
1729
  };
1551
1730
  function createCredentialStore(options = {}) {
@@ -1563,14 +1742,14 @@ function defaultKeyringAdapter() {
1563
1742
  };
1564
1743
  }
1565
1744
  function credentialPath(configDir) {
1566
- return (0, import_node_path3.join)(
1567
- configDir ?? (0, import_node_path3.join)((0, import_node_os.homedir)(), ".config", "dropthis"),
1745
+ return (0, import_node_path.join)(
1746
+ configDir ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "dropthis"),
1568
1747
  "credentials.json"
1569
1748
  );
1570
1749
  }
1571
1750
  async function readJsonFile(path) {
1572
1751
  try {
1573
- return JSON.parse(await (0, import_promises4.readFile)(path, "utf8"));
1752
+ return JSON.parse(await (0, import_promises3.readFile)(path, "utf8"));
1574
1753
  } catch (error2) {
1575
1754
  if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
1576
1755
  return null;
@@ -1579,20 +1758,34 @@ async function readJsonFile(path) {
1579
1758
  }
1580
1759
  }
1581
1760
  async function writeCredentialFile(path, credential) {
1582
- await (0, import_promises4.mkdir)((0, import_node_path3.dirname)(path), { recursive: true });
1761
+ await (0, import_promises3.mkdir)((0, import_node_path.dirname)(path), { recursive: true });
1583
1762
  const tmpPath = `${path}.${process.pid}.tmp`;
1584
- await (0, import_promises4.writeFile)(tmpPath, `${JSON.stringify(credential, null, 2)}
1763
+ await (0, import_promises3.writeFile)(tmpPath, `${JSON.stringify(credential, null, 2)}
1585
1764
  `, {
1586
1765
  mode: 384
1587
1766
  });
1588
- await (0, import_promises4.chmod)(tmpPath, 384);
1589
- await (0, import_promises4.rename)(tmpPath, path);
1767
+ await (0, import_promises3.chmod)(tmpPath, 384);
1768
+ await (0, import_promises3.rename)(tmpPath, path);
1590
1769
  }
1591
1770
 
1592
1771
  // src/program.ts
1593
1772
  function buildProgram(options = {}) {
1594
1773
  const program = new import_commander.Command();
1595
1774
  const store = options.store ?? new MemoryCredentialStore();
1775
+ const writeErr = options.stderr ?? ((value) => process.stderr.write(value));
1776
+ const jsonModeForOutput = () => {
1777
+ const argv = program.rawArgs ?? process.argv;
1778
+ return argv.includes("--json") || argv.includes("--quiet") || argv.includes("-q") || options.env?.CI === "true" || options.env === void 0 && process.env.CI === "true" || options.stdoutIsTTY === false || options.stdoutIsTTY === void 0 && process.stdout.isTTY !== true;
1779
+ };
1780
+ program.exitOverride();
1781
+ program.configureOutput({
1782
+ writeErr,
1783
+ // In JSON mode, suppress commander's default plain-text error so the
1784
+ // single JSON envelope written by cli.ts's catch is the only output.
1785
+ outputError: (str, write) => {
1786
+ if (!jsonModeForOutput()) write(str);
1787
+ }
1788
+ });
1596
1789
  program.name("dropthis").description(
1597
1790
  [
1598
1791
  import_picocolors4.default.dim("Publish anything online and get a URL back."),
@@ -1601,7 +1794,7 @@ function buildProgram(options = {}) {
1601
1794
  ` ${import_picocolors4.default.cyan("dropthis login")}`,
1602
1795
  ` ${import_picocolors4.default.cyan("dropthis publish ./dist")}`
1603
1796
  ].join("\n")
1604
- ).version("0.4.1").configureHelp({
1797
+ ).version("0.6.0").configureHelp({
1605
1798
  subcommandTerm(cmd) {
1606
1799
  const args = cmd.registeredArguments.map((a) => {
1607
1800
  const name = a.name();
@@ -1631,7 +1824,10 @@ function buildProgram(options = {}) {
1631
1824
  ).option(
1632
1825
  "--metadata <json>",
1633
1826
  `Attach JSON key-value pairs, e.g. '{"source":"ci"}'`
1634
- ).option("--metadata-file <path>", "Read metadata JSON from a file").option("--path <name>", "Set filename when publishing from stdin").option("--url", "Print only the published URL (no JSON envelope)").option("--dry-run", "Show what would be published without publishing").option("--json", "Force JSON output").option("--from-json <path>", "Send raw API request body from a JSON file").option(
1827
+ ).option("--metadata-file <path>", "Read metadata JSON from a file").option("--path <name>", "Set filename when publishing from stdin").option("--url", "Print only the published URL (no JSON envelope)").option(
1828
+ "--dry-run",
1829
+ "Show what would be published without publishing (JSON; cannot combine with --url)"
1830
+ ).option("--json", "Force JSON output").option("--from-json <path>", "Send raw API request body from a JSON file").option(
1635
1831
  "--idempotency-key <key>",
1636
1832
  "Prevent duplicate publishes on retry (auto-generated)"
1637
1833
  ).action(async (inputs, commandOptions) => {
@@ -1649,15 +1845,11 @@ function buildProgram(options = {}) {
1649
1845
  );
1650
1846
  let publishInput;
1651
1847
  try {
1652
- if (commandOptions.dryRun && inputs.length > 1) {
1653
- publishInput = inputs;
1654
- } else {
1655
- publishInput = await resolvePublishInputs(
1656
- inputs,
1657
- options.stdin,
1658
- stdinIsTTY
1659
- );
1660
- }
1848
+ publishInput = await resolvePublishInputs(
1849
+ inputs,
1850
+ options.stdin,
1851
+ stdinIsTTY
1852
+ );
1661
1853
  } catch (err) {
1662
1854
  const msg = err instanceof Error ? err.message : "Invalid input.";
1663
1855
  process.exitCode = writeError(deps, "local_input_error", msg).exitCode;
@@ -1700,7 +1892,10 @@ function buildProgram(options = {}) {
1700
1892
  "--if-revision <n>",
1701
1893
  "Fail if current revision doesn't match (optimistic lock)",
1702
1894
  parseInteger
1703
- ).option("--url", "Print only the published URL (no JSON envelope)").option("--dry-run", "Show what would be updated without updating").option("--json", "Force JSON output").option(
1895
+ ).option("--url", "Print only the published URL (no JSON envelope)").option(
1896
+ "--dry-run",
1897
+ "Show what would be updated without updating (JSON; cannot combine with --url)"
1898
+ ).option("--json", "Force JSON output").option(
1704
1899
  "--from-json <path>",
1705
1900
  "Send raw API deployment body from a JSON file"
1706
1901
  ).option(
@@ -1708,6 +1903,8 @@ function buildProgram(options = {}) {
1708
1903
  "Prevent duplicate updates on retry (auto-generated)"
1709
1904
  ).action(
1710
1905
  async (dropId, input, commandOptions) => {
1906
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
1907
+ const global = globalOptions(program, commandOptions);
1711
1908
  const deps = await commandDeps(program, options, store, commandOptions);
1712
1909
  let updateInput;
1713
1910
  try {
@@ -1729,7 +1926,10 @@ function buildProgram(options = {}) {
1729
1926
  process.exitCode = writeError(deps, "invalid_usage", msg).exitCode;
1730
1927
  return;
1731
1928
  }
1732
- const result = await runUpdate(dropId, updateInput, dropOpts, deps);
1929
+ const result = await runUpdate(dropId, updateInput, dropOpts, {
1930
+ ...deps,
1931
+ interactive: isInteractive(stdinIsTTY, deps.env, global)
1932
+ });
1733
1933
  process.exitCode = result.exitCode;
1734
1934
  }
1735
1935
  );
@@ -1864,11 +2064,36 @@ function buildProgram(options = {}) {
1864
2064
  const result = await runWhoami(commandOptions, deps);
1865
2065
  process.exitCode = result.exitCode;
1866
2066
  });
1867
- program.command("account").description("Show account details").option("--json", "Force JSON output").action(async (commandOptions) => {
2067
+ const account = program.command("account").description("Show or manage your account");
2068
+ account.command("get", { isDefault: true }).description("Show account details").option("--json", "Force JSON output").action(async (commandOptions) => {
1868
2069
  const deps = await commandDeps(program, options, store, commandOptions);
1869
2070
  const result = await runAccountGet(commandOptions, deps);
1870
2071
  process.exitCode = result.exitCode;
1871
2072
  });
2073
+ account.command("update").description("Update account display name").requiredOption("--display-name <name>", "Set the display name").option("--json", "Force JSON output").action(async (commandOptions) => {
2074
+ const deps = await commandDeps(program, options, store, commandOptions);
2075
+ const result = await runAccountUpdate(
2076
+ {
2077
+ displayName: commandOptions.displayName,
2078
+ ...commandOptions.json !== void 0 ? { json: commandOptions.json } : {}
2079
+ },
2080
+ deps
2081
+ );
2082
+ process.exitCode = result.exitCode;
2083
+ });
2084
+ account.command("delete").description("Delete your account").option("--yes", "Confirm deletion").option("--json", "Force JSON output").action(async (commandOptions) => {
2085
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
2086
+ const global = globalOptions(program, commandOptions);
2087
+ const deps = await commandDeps(program, options, store, commandOptions);
2088
+ const result = await runAccountDelete(
2089
+ {
2090
+ ...commandOptions,
2091
+ interactive: isInteractive(stdinIsTTY, deps.env, global)
2092
+ },
2093
+ deps
2094
+ );
2095
+ process.exitCode = result.exitCode;
2096
+ });
1872
2097
  program.command("doctor").description("Report CLI diagnostics").option("--json", "Force JSON output").action(async (commandOptions) => {
1873
2098
  const deps = await commandDeps(
1874
2099
  program,
@@ -1886,7 +2111,7 @@ function buildProgram(options = {}) {
1886
2111
  store,
1887
2112
  commandOptions
1888
2113
  );
1889
- const result = await runCommands(commandOptions, deps);
2114
+ const result = await runCommands(commandOptions, { ...deps, program });
1890
2115
  process.exitCode = result.exitCode;
1891
2116
  });
1892
2117
  return program;
@@ -2015,44 +2240,12 @@ async function resolvePublishInputs(inputs, stdin, stdinIsTTY) {
2015
2240
  }
2016
2241
  return inputs[0];
2017
2242
  }
2018
- const {
2019
- mkdtemp,
2020
- copyFile,
2021
- writeFile: writeFile2,
2022
- stat: fsStat
2023
- } = await import("fs/promises");
2024
- const { join: join2, basename: pathBasename } = await import("path");
2025
- const { tmpdir } = await import("os");
2026
- const tmpDir = await mkdtemp(join2(tmpdir(), "dropthis-multi-"));
2027
- const resolved = [];
2028
- for (const input of inputs) {
2029
- if (input === "-") {
2030
- const buf = await readStdinBytes(stdin ?? process.stdin);
2031
- const tmpPath = join2(tmpDir, `stdin-${Date.now()}`);
2032
- await writeFile2(tmpPath, buf);
2033
- resolved.push(tmpPath);
2034
- } else {
2035
- resolved.push(input);
2036
- }
2037
- }
2038
- const names = resolved.map((f) => pathBasename(f));
2039
- const dupes = names.filter((n, i) => names.indexOf(n) !== i);
2040
- if (dupes.length > 0) {
2243
+ if (inputs.includes("-")) {
2041
2244
  throw new Error(
2042
- `Duplicate file names: ${[...new Set(dupes)].join(", ")}. Rename files to avoid collisions.`
2245
+ "Cannot read stdin (-) alongside multiple file arguments. Pass file paths only."
2043
2246
  );
2044
2247
  }
2045
- for (const filePath of resolved) {
2046
- const info = await fsStat(filePath);
2047
- if (info.isDirectory()) {
2048
- throw new Error(
2049
- `Cannot mix directories with multiple file arguments. Use a single folder instead: dropthis publish ${filePath}`
2050
- );
2051
- }
2052
- const destName = pathBasename(filePath);
2053
- await copyFile(filePath, join2(tmpDir, destName));
2054
- }
2055
- return tmpDir;
2248
+ return inputs;
2056
2249
  }
2057
2250
  async function readStdinBytes(stdin) {
2058
2251
  const chunks = [];
@@ -2065,8 +2258,42 @@ async function readStdinBytes(stdin) {
2065
2258
  }
2066
2259
 
2067
2260
  // src/cli.ts
2261
+ function writeUsageErrorEnvelope(input) {
2262
+ if (input.json) {
2263
+ input.stderr(
2264
+ `${JSON.stringify(errorEnvelope("usage_error", input.message))}
2265
+ `
2266
+ );
2267
+ } else {
2268
+ input.stderr(`${input.message}
2269
+ `);
2270
+ }
2271
+ return 2;
2272
+ }
2273
+ function isCommanderError(err) {
2274
+ return typeof err === "object" && err !== null && typeof err.code === "string" && err.code.startsWith("commander.");
2275
+ }
2276
+ function wantsJson(argv) {
2277
+ return argv.includes("--json") || argv.includes("--quiet") || argv.includes("-q") || process.env.CI === "true" || process.stdout.isTTY !== true;
2278
+ }
2068
2279
  buildProgram({ store: createCredentialStore() }).parseAsync(process.argv).catch((error2) => {
2280
+ if (isCommanderError(error2)) {
2281
+ if (error2.code === "commander.help" || error2.code === "commander.helpDisplayed" || error2.code === "commander.version") {
2282
+ process.exitCode = 0;
2283
+ return;
2284
+ }
2285
+ process.exitCode = writeUsageErrorEnvelope({
2286
+ message: error2.message,
2287
+ json: wantsJson(process.argv.slice(2)),
2288
+ stderr: (value) => process.stderr.write(value)
2289
+ });
2290
+ return;
2291
+ }
2069
2292
  console.error(error2);
2070
2293
  process.exitCode = 1;
2071
2294
  });
2295
+ // Annotate the CommonJS export names for ESM import in node:
2296
+ 0 && (module.exports = {
2297
+ writeUsageErrorEnvelope
2298
+ });
2072
2299
  //# sourceMappingURL=cli.cjs.map