@dropthis/cli 0.8.0 → 0.9.1

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
@@ -210,7 +210,7 @@ function nextActionForApiError(error2) {
210
210
  return "Fetch the drop, merge your changes, and retry with the current revision.";
211
211
  }
212
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.";
213
+ return "Check the drop id or slug and retry; list your drops with dropthis list.";
214
214
  }
215
215
  if (error2.statusCode === 401 || error2.code === "missing_api_key") {
216
216
  return "Authenticate with dropthis login or set DROPTHIS_API_KEY.";
@@ -591,21 +591,36 @@ var COMMAND_ANNOTATIONS = {
591
591
  publish: {
592
592
  auth: "required",
593
593
  examples: [
594
- "dropthis ./file.html",
594
+ "dropthis ./report.html",
595
595
  "dropthis publish ./site --json",
596
596
  "dropthis publish ./site --url",
597
597
  "dropthis publish ./dist --dry-run"
598
598
  ]
599
599
  },
600
- drops: {
600
+ "update-content": {
601
601
  auth: "required",
602
- examples: [
603
- "dropthis drops list --json",
604
- "dropthis drops update drop_abc ./site --json",
605
- "dropthis drops update drop_abc --title 'New Title' --dry-run"
606
- ]
602
+ examples: ["dropthis update-content drop_abc ./site --json"]
603
+ },
604
+ "update-settings": {
605
+ auth: "required",
606
+ examples: ["dropthis update-settings drop_abc --title 'Launch' --json"]
607
+ },
608
+ get: {
609
+ auth: "required",
610
+ examples: ["dropthis get drop_abc --json"]
611
+ },
612
+ list: {
613
+ auth: "required",
614
+ examples: ["dropthis list --json"]
615
+ },
616
+ delete: {
617
+ auth: "required",
618
+ examples: ["dropthis delete drop_abc --yes --json"]
619
+ },
620
+ deployments: {
621
+ auth: "required",
622
+ examples: ["dropthis deployments drop_abc --json"]
607
623
  },
608
- deployments: { auth: "required" },
609
624
  "deployments list": {
610
625
  auth: "required",
611
626
  examples: ["dropthis deployments list drop_abc --json"]
@@ -754,13 +769,13 @@ async function runDoctor(_input, deps) {
754
769
  const authSource = credential?.source ?? "missing";
755
770
  const storageBackend = stored?.storage ?? "none";
756
771
  const pairs = [
757
- ["Version", "0.8.0"],
772
+ ["Version", "0.9.1"],
758
773
  ["Auth", authSource],
759
774
  ["Storage", storageBackend]
760
775
  ];
761
776
  writeKv(deps, pairs, {
762
777
  ok: true,
763
- version: "0.8.0",
778
+ version: "0.9.1",
764
779
  auth: { source: authSource },
765
780
  storage: { backend: storageBackend }
766
781
  });
@@ -1177,7 +1192,7 @@ async function runPublish(input, raw, deps) {
1177
1192
  const spin = shouldSpin(deps.outputMode) ? createSpinner("Publishing\u2026", deps.stderr) : void 0;
1178
1193
  try {
1179
1194
  const options = withIdempotencyKey(await parseDropOptions(raw), "pub");
1180
- const doPublish = (c) => hasInput ? c.publish(input, options) : (() => {
1195
+ const doPublish = (c) => hasInput ? c.drops.publish(input, options) : (() => {
1181
1196
  throw new Error("Publish requires <input>.");
1182
1197
  })();
1183
1198
  const result = await doPublish(client);
@@ -1260,32 +1275,17 @@ async function handlePublishDryRun(input, raw, deps, client) {
1260
1275
  }
1261
1276
  }
1262
1277
 
1263
- // src/commands/update.ts
1264
- var prompts4 = __toESM(require("@clack/prompts"), 1);
1265
- var NO_UPDATE_MESSAGE = "Nothing to update. Provide an input path or at least one update option.";
1266
- 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.";
1267
- var DEPLOY_REJECTS_SETTINGS = "A deployment changes content only \u2014 it can't change settings in the same call.";
1268
- var DEPLOY_SETTINGS_NEXT_ACTION = "Deploy the new content first (dropthis drops update <dropId> ./dist), then change settings separately (dropthis drops update <dropId> --title ... --visibility ...). Settings on an existing drop never require re-uploading content.";
1269
- var SETTINGS_KEYS = [
1270
- "slug",
1271
- "title",
1272
- "visibility",
1273
- "password",
1274
- "noindex",
1275
- "expiresAt",
1276
- "metadata"
1277
- ];
1278
- function hasSettingsFields(options) {
1279
- return SETTINGS_KEYS.some((key) => options[key] !== void 0);
1280
- }
1281
- function contentDeployOptions(options) {
1278
+ // src/commands/update-content.ts
1279
+ var NO_CONTENT_MESSAGE = "update-content requires content. Provide a file, folder, URL, or text to ship a new deployment.";
1280
+ var NO_CONTENT_NEXT_ACTION = "Pass an input, e.g. dropthis update-content <dropId> ./dist. To change title, visibility, password, slug, expiry, or metadata, use dropthis update-settings <dropId> instead.";
1281
+ function contentOptions(options) {
1282
1282
  const out = {};
1283
1283
  if (options.entry !== void 0) out.entry = options.entry;
1284
1284
  if (options.contentType !== void 0) out.contentType = options.contentType;
1285
1285
  if (options.path !== void 0) out.path = options.path;
1286
1286
  return out;
1287
1287
  }
1288
- async function runUpdate(target, input, raw, deps) {
1288
+ async function runUpdateContent(dropId, input, raw, deps) {
1289
1289
  try {
1290
1290
  await requireCredential({
1291
1291
  ...raw.apiKey ?? deps.apiKey ? { apiKey: raw.apiKey ?? deps.apiKey } : {},
@@ -1296,48 +1296,30 @@ async function runUpdate(target, input, raw, deps) {
1296
1296
  return writeAuthError(deps);
1297
1297
  }
1298
1298
  if (raw.dryRun) {
1299
- return handleUpdateDryRun(target, input, raw, deps);
1299
+ return handleDryRun(dropId, input, raw, deps);
1300
1300
  }
1301
1301
  let spin;
1302
1302
  try {
1303
- assertDropId(target);
1304
- let updateInput = input;
1305
- let parsed = await parseDropOptions(raw);
1306
- if (!updateInput && !hasUpdateFields(parsed)) {
1307
- if (shouldPromptForUpdate(deps)) {
1308
- const prompted = await promptForUpdate(target, deps.prompts ?? prompts4);
1309
- if (!prompted) return { exitCode: 0 };
1310
- updateInput = prompted.input;
1311
- parsed = { ...parsed, ...prompted.options };
1312
- } else {
1313
- return writeError(
1314
- deps,
1315
- "invalid_usage",
1316
- NO_UPDATE_MESSAGE,
1317
- NO_UPDATE_NEXT_ACTION
1318
- );
1319
- }
1320
- }
1321
- if (updateInput && hasSettingsFields(parsed)) {
1303
+ assertDropId(dropId);
1304
+ const hasInput = Array.isArray(input) ? input.length > 0 : input !== void 0;
1305
+ if (!hasInput) {
1322
1306
  return writeError(
1323
1307
  deps,
1324
1308
  "invalid_usage",
1325
- DEPLOY_REJECTS_SETTINGS,
1326
- DEPLOY_SETTINGS_NEXT_ACTION
1309
+ NO_CONTENT_MESSAGE,
1310
+ NO_CONTENT_NEXT_ACTION
1327
1311
  );
1328
1312
  }
1313
+ const parsed = await parseDropOptions(raw);
1329
1314
  const revisionOptions = raw.ifRevision !== void 0 ? { ifRevision: Number(raw.ifRevision) } : {};
1330
- spin = shouldSpin(deps.outputMode) ? createSpinner("Updating\u2026", deps.stderr) : void 0;
1331
- const result = updateInput ? await deps.client.deploy(
1332
- target,
1333
- updateInput,
1315
+ spin = shouldSpin(deps.outputMode) ? createSpinner("Updating content\u2026", deps.stderr) : void 0;
1316
+ const result = await deps.client.drops.updateContent(
1317
+ dropId,
1318
+ input,
1334
1319
  withIdempotencyKey(
1335
- { ...contentDeployOptions(parsed), ...revisionOptions },
1320
+ { ...contentOptions(parsed), ...revisionOptions },
1336
1321
  "upd"
1337
1322
  )
1338
- ) : await deps.client.update(
1339
- target,
1340
- withIdempotencyKey({ ...parsed, ...revisionOptions }, "upd")
1341
1323
  );
1342
1324
  if (result.error) {
1343
1325
  spin?.fail();
@@ -1355,24 +1337,130 @@ async function runUpdate(target, input, raw, deps) {
1355
1337
  return writeError(deps, code, message);
1356
1338
  }
1357
1339
  }
1358
- function hasUpdateFields(options) {
1359
- const { idempotencyKey: _, ...fields } = options;
1360
- return Object.keys(fields).length > 0;
1340
+ async function handleDryRun(dropId, input, raw, deps) {
1341
+ if (raw.url) {
1342
+ return writeError(
1343
+ deps,
1344
+ "invalid_usage",
1345
+ "--url and --dry-run cannot be combined.",
1346
+ "Remove --url; dry-run outputs JSON describing what would be published."
1347
+ );
1348
+ }
1349
+ try {
1350
+ assertDropId(dropId);
1351
+ const hasInput = Array.isArray(input) ? input.length > 0 : input !== void 0;
1352
+ if (!hasInput) {
1353
+ return writeError(
1354
+ deps,
1355
+ "invalid_usage",
1356
+ NO_CONTENT_MESSAGE,
1357
+ NO_CONTENT_NEXT_ACTION
1358
+ );
1359
+ }
1360
+ const options = await parseDropOptions(raw);
1361
+ if (!deps.client.prepare) {
1362
+ throw new Error("Client does not support dry-run.");
1363
+ }
1364
+ const prepared = await deps.client.prepare(input, options);
1365
+ const output = formatDryRunManifest(prepared, { target: dropId });
1366
+ if (prepared.kind === "staged") {
1367
+ validateManifestFileCount(prepared.manifest.files.length);
1368
+ }
1369
+ deps.stdout(`${JSON.stringify(output, null, 2)}
1370
+ `);
1371
+ return { exitCode: 0 };
1372
+ } catch (error2) {
1373
+ const code = isFileSystemError(error2) ? "local_input_error" : "invalid_usage";
1374
+ const message = error2 instanceof Error ? error2.message : "Dry-run failed.";
1375
+ return writeError(deps, code, message);
1376
+ }
1361
1377
  }
1362
- function shouldPromptForUpdate(deps) {
1378
+ function assertDropId(target) {
1379
+ if (!target.startsWith("drop_")) {
1380
+ throw new Error(
1381
+ "update-content needs the full drop_\u2026 id from the publish response, not the URL slug, for example drop_..."
1382
+ );
1383
+ }
1384
+ }
1385
+
1386
+ // src/commands/update-settings.ts
1387
+ var prompts4 = __toESM(require("@clack/prompts"), 1);
1388
+ var NO_SETTINGS_MESSAGE = "Nothing to update. Provide at least one settings option.";
1389
+ var NO_SETTINGS_NEXT_ACTION = "Run dropthis update-settings --help, or retry with one of: --title, --slug, --visibility, --password, --no-password, --noindex, --index, --expires-at, --metadata, or --metadata-file. To replace the content at the URL instead, use dropthis update-content <dropId> ./dist.";
1390
+ async function runUpdateSettings(dropId, raw, deps) {
1391
+ try {
1392
+ await requireCredential({
1393
+ ...raw.apiKey ?? deps.apiKey ? { apiKey: raw.apiKey ?? deps.apiKey } : {},
1394
+ env: deps.env,
1395
+ store: deps.store
1396
+ });
1397
+ } catch {
1398
+ return writeAuthError(deps);
1399
+ }
1400
+ if (raw.dryRun) {
1401
+ return handleDryRun2(dropId, raw, deps);
1402
+ }
1403
+ let spin;
1404
+ try {
1405
+ assertDropId2(dropId);
1406
+ let parsed = await parseDropOptions(raw);
1407
+ if (!hasSettingsFields(parsed)) {
1408
+ if (shouldPrompt(deps)) {
1409
+ const prompted = await promptForSettings(
1410
+ dropId,
1411
+ deps.prompts ?? prompts4
1412
+ );
1413
+ if (!prompted) return { exitCode: 0 };
1414
+ parsed = { ...parsed, ...prompted };
1415
+ } else {
1416
+ return writeError(
1417
+ deps,
1418
+ "invalid_usage",
1419
+ NO_SETTINGS_MESSAGE,
1420
+ NO_SETTINGS_NEXT_ACTION
1421
+ );
1422
+ }
1423
+ }
1424
+ const revisionOptions = raw.ifRevision !== void 0 ? { ifRevision: Number(raw.ifRevision) } : {};
1425
+ spin = shouldSpin(deps.outputMode) ? createSpinner("Updating settings\u2026", deps.stderr) : void 0;
1426
+ const result = await deps.client.drops.updateSettings(
1427
+ dropId,
1428
+ withIdempotencyKey({ ...parsed, ...revisionOptions }, "upd")
1429
+ );
1430
+ if (result.error) {
1431
+ spin?.fail();
1432
+ return writeApiError(deps, result.error);
1433
+ }
1434
+ spin?.stop();
1435
+ if (raw.url) deps.stdout(`${result.data.url}
1436
+ `);
1437
+ else writeResult(deps, result.data.url, "Updated", result.data);
1438
+ return { exitCode: 0 };
1439
+ } catch (error2) {
1440
+ const message = error2 instanceof Error ? error2.message : "Update failed.";
1441
+ spin?.fail();
1442
+ return writeError(deps, "invalid_usage", message);
1443
+ }
1444
+ }
1445
+ function hasSettingsFields(options) {
1446
+ const {
1447
+ idempotencyKey: _,
1448
+ entry: __,
1449
+ contentType: ___,
1450
+ path: ____,
1451
+ ...settings
1452
+ } = options;
1453
+ return Object.values(settings).some((value) => value !== void 0);
1454
+ }
1455
+ function shouldPrompt(deps) {
1363
1456
  return deps.outputMode === "human" && deps.interactive === true;
1364
1457
  }
1365
- async function promptForUpdate(target, prompt) {
1366
- prompt.intro(`Update ${target}`);
1458
+ async function promptForSettings(target, prompt) {
1459
+ prompt.intro(`Update settings for ${target}`);
1367
1460
  const fields = await prompt.multiselect({
1368
- message: "What do you want to update?",
1461
+ message: "What do you want to change?",
1369
1462
  required: true,
1370
1463
  options: [
1371
- {
1372
- value: "content",
1373
- label: "Content",
1374
- hint: "replace files, folder, URL, or text"
1375
- },
1376
1464
  { value: "title", label: "Title", hint: "--title" },
1377
1465
  { value: "slug", label: "Slug", hint: "--slug" },
1378
1466
  { value: "visibility", label: "Visibility", hint: "--visibility" },
@@ -1396,21 +1484,12 @@ async function promptForUpdate(target, prompt) {
1396
1484
  }
1397
1485
  const selected = new Set(fields);
1398
1486
  const options = {};
1399
- let input;
1400
- if (selected.has("content")) {
1401
- const value = await prompt.text({
1402
- message: "Content path, URL, or text",
1403
- validate: requireNonEmpty("Enter content or a path.")
1404
- });
1405
- if (prompt.isCancel(value)) return cancelUpdate(prompt);
1406
- input = value.trim();
1407
- }
1408
1487
  if (selected.has("title")) {
1409
1488
  const value = await prompt.text({
1410
1489
  message: "Title",
1411
1490
  validate: requireNonEmpty("Enter a title.")
1412
1491
  });
1413
- if (prompt.isCancel(value)) return cancelUpdate(prompt);
1492
+ if (prompt.isCancel(value)) return cancel2(prompt);
1414
1493
  options.title = value.trim();
1415
1494
  }
1416
1495
  if (selected.has("slug")) {
@@ -1418,7 +1497,7 @@ async function promptForUpdate(target, prompt) {
1418
1497
  message: "Slug",
1419
1498
  validate: requireNonEmpty("Enter a slug.")
1420
1499
  });
1421
- if (prompt.isCancel(value)) return cancelUpdate(prompt);
1500
+ if (prompt.isCancel(value)) return cancel2(prompt);
1422
1501
  options.slug = value.trim();
1423
1502
  }
1424
1503
  if (selected.has("visibility")) {
@@ -1429,7 +1508,7 @@ async function promptForUpdate(target, prompt) {
1429
1508
  { value: "unlisted", label: "Unlisted" }
1430
1509
  ]
1431
1510
  });
1432
- if (prompt.isCancel(value)) return cancelUpdate(prompt);
1511
+ if (prompt.isCancel(value)) return cancel2(prompt);
1433
1512
  options.visibility = value;
1434
1513
  }
1435
1514
  if (selected.has("password")) {
@@ -1440,13 +1519,13 @@ async function promptForUpdate(target, prompt) {
1440
1519
  { value: "remove", label: "Remove password" }
1441
1520
  ]
1442
1521
  });
1443
- if (prompt.isCancel(action)) return cancelUpdate(prompt);
1522
+ if (prompt.isCancel(action)) return cancel2(prompt);
1444
1523
  if (action === "set") {
1445
1524
  const value = await prompt.password({
1446
1525
  message: "Password",
1447
1526
  validate: requireNonEmpty("Enter a password.")
1448
1527
  });
1449
- if (prompt.isCancel(value)) return cancelUpdate(prompt);
1528
+ if (prompt.isCancel(value)) return cancel2(prompt);
1450
1529
  options.password = value;
1451
1530
  } else {
1452
1531
  options.password = null;
@@ -1460,7 +1539,7 @@ async function promptForUpdate(target, prompt) {
1460
1539
  { value: "index", label: "Allow indexing" }
1461
1540
  ]
1462
1541
  });
1463
- if (prompt.isCancel(value)) return cancelUpdate(prompt);
1542
+ if (prompt.isCancel(value)) return cancel2(prompt);
1464
1543
  options.noindex = value === "noindex";
1465
1544
  }
1466
1545
  if (selected.has("expiresAt")) {
@@ -1469,7 +1548,7 @@ async function promptForUpdate(target, prompt) {
1469
1548
  placeholder: "2025-12-31T23:59:59Z",
1470
1549
  validate: validateIsoDate
1471
1550
  });
1472
- if (prompt.isCancel(value)) return cancelUpdate(prompt);
1551
+ if (prompt.isCancel(value)) return cancel2(prompt);
1473
1552
  options.expiresAt = value.trim();
1474
1553
  }
1475
1554
  if (selected.has("metadata")) {
@@ -1478,12 +1557,12 @@ async function promptForUpdate(target, prompt) {
1478
1557
  placeholder: '{"source":"cli"}',
1479
1558
  validate: validateJsonObject
1480
1559
  });
1481
- if (prompt.isCancel(value)) return cancelUpdate(prompt);
1560
+ if (prompt.isCancel(value)) return cancel2(prompt);
1482
1561
  options.metadata = parseJsonObject2(value);
1483
1562
  }
1484
- return { ...input ? { input } : {}, options };
1563
+ return options;
1485
1564
  }
1486
- function cancelUpdate(prompt) {
1565
+ function cancel2(prompt) {
1487
1566
  prompt.cancel("Update cancelled.");
1488
1567
  return void 0;
1489
1568
  }
@@ -1511,54 +1590,40 @@ function parseJsonObject2(value) {
1511
1590
  }
1512
1591
  return parsed;
1513
1592
  }
1514
- async function handleUpdateDryRun(target, input, raw, deps) {
1593
+ async function handleDryRun2(dropId, raw, deps) {
1515
1594
  if (raw.url) {
1516
1595
  return writeError(
1517
1596
  deps,
1518
1597
  "invalid_usage",
1519
1598
  "--url and --dry-run cannot be combined.",
1520
- "Remove --url; dry-run outputs JSON describing what would be published."
1599
+ "Remove --url; dry-run outputs JSON describing what would change."
1521
1600
  );
1522
1601
  }
1523
1602
  try {
1524
- assertDropId(target);
1603
+ assertDropId2(dropId);
1525
1604
  const options = await parseDropOptions(raw);
1526
- if (!input) {
1527
- const fields = {};
1528
- if (options.slug) fields.slug = options.slug;
1529
- if (options.title) fields.title = options.title;
1530
- if (options.visibility) fields.visibility = options.visibility;
1531
- if (options.password !== void 0) fields.password = options.password;
1532
- if (options.noindex !== void 0) fields.noindex = options.noindex;
1533
- if (options.expiresAt) fields.expires_at = options.expiresAt;
1534
- if (options.metadata) fields.metadata = options.metadata;
1535
- deps.stdout(
1536
- `${JSON.stringify({ ok: true, dryRun: true, kind: "options_update", target, fields }, null, 2)}
1605
+ const fields = {};
1606
+ if (options.slug) fields.slug = options.slug;
1607
+ if (options.title) fields.title = options.title;
1608
+ if (options.visibility) fields.visibility = options.visibility;
1609
+ if (options.password !== void 0) fields.password = options.password;
1610
+ if (options.noindex !== void 0) fields.noindex = options.noindex;
1611
+ if (options.expiresAt) fields.expires_at = options.expiresAt;
1612
+ if (options.metadata) fields.metadata = options.metadata;
1613
+ deps.stdout(
1614
+ `${JSON.stringify({ ok: true, dryRun: true, kind: "settings_update", target: dropId, fields }, null, 2)}
1537
1615
  `
1538
- );
1539
- return { exitCode: 0 };
1540
- }
1541
- if (!deps.client.prepare) {
1542
- throw new Error("Client does not support dry-run.");
1543
- }
1544
- const prepared = await deps.client.prepare(input, options);
1545
- const output = formatDryRunManifest(prepared, { target });
1546
- if (prepared.kind === "staged") {
1547
- validateManifestFileCount(prepared.manifest.files.length);
1548
- }
1549
- deps.stdout(`${JSON.stringify(output, null, 2)}
1550
- `);
1616
+ );
1551
1617
  return { exitCode: 0 };
1552
1618
  } catch (error2) {
1553
- const code = isFileSystemError(error2) ? "local_input_error" : "invalid_usage";
1554
1619
  const message = error2 instanceof Error ? error2.message : "Dry-run failed.";
1555
- return writeError(deps, code, message);
1620
+ return writeError(deps, "invalid_usage", message);
1556
1621
  }
1557
1622
  }
1558
- function assertDropId(target) {
1623
+ function assertDropId2(target) {
1559
1624
  if (!target.startsWith("drop_")) {
1560
1625
  throw new Error(
1561
- "Update target must be a drop id returned by --json output, for example drop_..."
1626
+ "update-settings needs the full drop_\u2026 id from the publish response, not the URL slug, for example drop_..."
1562
1627
  );
1563
1628
  }
1564
1629
  }
@@ -1768,7 +1833,7 @@ function buildProgram(options = {}) {
1768
1833
  ` ${import_picocolors4.default.cyan("dropthis login")}`,
1769
1834
  ` ${import_picocolors4.default.cyan("dropthis publish ./dist")}`
1770
1835
  ].join("\n")
1771
- ).version("0.8.0").configureHelp({
1836
+ ).version("0.9.1").configureHelp({
1772
1837
  subcommandTerm(cmd) {
1773
1838
  const args = cmd.registeredArguments.map((a) => {
1774
1839
  const name = a.name();
@@ -1788,7 +1853,7 @@ function buildProgram(options = {}) {
1788
1853
  program.option("-q, --quiet", "Suppress status output and imply JSON");
1789
1854
  program.option("--no-interactive", "Disable interactive prompts");
1790
1855
  program.command("publish [input...]", { isDefault: true }).description(
1791
- "Publish files, folders, URLs, strings, or stdin.\nMultiple files are bundled into one drop.\nUse - to read stdin explicitly, or pipe without args."
1856
+ "Publish content to a permanent public URL (also: share, post, put online, make public, get a link).\nFiles, folders, URLs, strings, or stdin; multiple files bundle into one drop. Use - for stdin, or pipe without args.\nCreates a NEW drop each run \u2014 to change one you already published, use update-content (the files) or update-settings (title/visibility/password/slug/expiry/metadata) with its drop_\u2026 id, not publish again."
1792
1857
  ).option("--title <title>", "Drop title").option("--visibility <v>", "public or unlisted (default: public)").option("--password <password>", "Require password to view").option("--noindex", "Prevent search-engine indexing").option("--expires-at <datetime>", "Auto-delete after this ISO 8601 date").option(
1793
1858
  "--content-type <mime>",
1794
1859
  "Override MIME type (auto-detected from extension)"
@@ -1840,46 +1905,35 @@ function buildProgram(options = {}) {
1840
1905
  const result = await runPublish(publishInput, dropOpts, deps);
1841
1906
  process.exitCode = result.exitCode;
1842
1907
  });
1843
- const drops = program.command("drops").description("Manage published drops");
1844
- drops.command("list").description("List your drops").option("--limit <n>", "Page size", parseInteger).option("--cursor <cursor>", "Pagination cursor").option("--json", "Force JSON output").action(
1845
- async (commandOptions) => {
1846
- const deps = await commandDeps(program, options, store, commandOptions);
1847
- const result = await runDropsList(commandOptions, deps);
1848
- process.exitCode = result.exitCode;
1849
- }
1850
- );
1851
- drops.command("get <dropId>").description("Show drop details").option("--json", "Force JSON output").action(async (dropId, commandOptions) => {
1852
- const deps = await commandDeps(program, options, store, commandOptions);
1853
- const result = await runDropsGet(dropId, commandOptions, deps);
1854
- process.exitCode = result.exitCode;
1855
- });
1856
- drops.command("update <dropId> [input]").description("Update content or metadata").option("--slug <slug>", "Change the vanity slug").option("--title <title>", "Change title").option("--visibility <v>", "Set to public or unlisted").option("--password <password>", "Require password to view").option("--no-password", "Remove password protection").option("--noindex", "Prevent search-engine indexing").option("--index", "Allow search-engine indexing").option("--expires-at <datetime>", "Auto-delete after this ISO 8601 date").option(
1857
- "--content-type <mime>",
1858
- "Override MIME type (auto-detected from extension)"
1908
+ program.command("update-content <dropId> [input...]").description(
1909
+ "Replace a drop's content, same URL (pass the full drop_\u2026 id, not the slug).\nShips a new deployment. Settings stay unchanged \u2014 use update-settings for title, visibility, password, slug, expiry, or metadata."
1859
1910
  ).option(
1860
1911
  "--entry <path>",
1861
1912
  "Entry file for multi-file bundles (default: index.html)"
1862
1913
  ).option(
1863
- "--metadata <json>",
1864
- `Attach JSON key-value pairs, e.g. '{"source":"ci"}'`
1865
- ).option("--metadata-file <path>", "Read metadata JSON from a file").option("--path <name>", "Set filename for stdin content").option(
1914
+ "--content-type <mime>",
1915
+ "Override MIME type (auto-detected from extension)"
1916
+ ).option("--path <name>", "Set filename for stdin content").option(
1866
1917
  "--if-revision <n>",
1867
1918
  "Fail if current revision doesn't match (optimistic lock)",
1868
1919
  parseInteger
1869
1920
  ).option("--url", "Print only the published URL (no JSON envelope)").option(
1870
1921
  "--dry-run",
1871
- "Show what would be updated without updating (JSON; cannot combine with --url)"
1922
+ "Show what would be shipped without updating (JSON; cannot combine with --url)"
1872
1923
  ).option("--json", "Force JSON output").option(
1873
1924
  "--idempotency-key <key>",
1874
1925
  "Prevent duplicate updates on retry (auto-generated)"
1875
1926
  ).action(
1876
- async (dropId, input, commandOptions) => {
1877
- const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
1878
- const global = globalOptions(program, commandOptions);
1927
+ async (dropId, inputs, commandOptions) => {
1879
1928
  const deps = await commandDeps(program, options, store, commandOptions);
1929
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
1880
1930
  let updateInput;
1881
1931
  try {
1882
- updateInput = input ? await resolveCommandInput(input, options.stdin) : void 0;
1932
+ updateInput = await resolvePublishInputs(
1933
+ inputs,
1934
+ options.stdin,
1935
+ stdinIsTTY
1936
+ );
1883
1937
  } catch (err) {
1884
1938
  const msg = err instanceof Error ? err.message : "Invalid input.";
1885
1939
  process.exitCode = writeError(
@@ -1897,14 +1951,63 @@ function buildProgram(options = {}) {
1897
1951
  process.exitCode = writeError(deps, "invalid_usage", msg).exitCode;
1898
1952
  return;
1899
1953
  }
1900
- const result = await runUpdate(dropId, updateInput, dropOpts, {
1954
+ const result = await runUpdateContent(
1955
+ dropId,
1956
+ updateInput,
1957
+ dropOpts,
1958
+ deps
1959
+ );
1960
+ process.exitCode = result.exitCode;
1961
+ }
1962
+ );
1963
+ program.command("update-settings <dropId>").description(
1964
+ "Change a drop's title, visibility, password, vanity slug, expiry, or metadata (pass the full drop_\u2026 id, not the slug).\nContent stays unchanged \u2014 use update-content to replace the files at the URL."
1965
+ ).option("--slug <slug>", "Change the vanity slug").option("--title <title>", "Change title").option("--visibility <v>", "Set to public or unlisted").option("--password <password>", "Require password to view").option("--no-password", "Remove password protection").option("--noindex", "Prevent search-engine indexing").option("--index", "Allow search-engine indexing").option("--expires-at <datetime>", "Auto-delete after this ISO 8601 date").option(
1966
+ "--metadata <json>",
1967
+ `Attach JSON key-value pairs, e.g. '{"source":"ci"}'`
1968
+ ).option("--metadata-file <path>", "Read metadata JSON from a file").option(
1969
+ "--if-revision <n>",
1970
+ "Fail if current revision doesn't match (optimistic lock)",
1971
+ parseInteger
1972
+ ).option("--url", "Print only the published URL (no JSON envelope)").option(
1973
+ "--dry-run",
1974
+ "Show what would change without updating (JSON; cannot combine with --url)"
1975
+ ).option("--json", "Force JSON output").option(
1976
+ "--idempotency-key <key>",
1977
+ "Prevent duplicate updates on retry (auto-generated)"
1978
+ ).action(
1979
+ async (dropId, commandOptions) => {
1980
+ const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
1981
+ const global = globalOptions(program, commandOptions);
1982
+ const deps = await commandDeps(program, options, store, commandOptions);
1983
+ let dropOpts;
1984
+ try {
1985
+ dropOpts = toDropOptions(commandOptions);
1986
+ } catch (err) {
1987
+ const msg = err instanceof Error ? err.message : "Invalid option.";
1988
+ process.exitCode = writeError(deps, "invalid_usage", msg).exitCode;
1989
+ return;
1990
+ }
1991
+ const result = await runUpdateSettings(dropId, dropOpts, {
1901
1992
  ...deps,
1902
1993
  interactive: isInteractive(stdinIsTTY, deps.env, global)
1903
1994
  });
1904
1995
  process.exitCode = result.exitCode;
1905
1996
  }
1906
1997
  );
1907
- drops.command("delete <dropId>").description("Delete a drop").option("--yes", "Confirm deletion").option("--json", "Force JSON output").action(
1998
+ program.command("get <dropId>").description("Show drop details (pass the full drop_\u2026 id)").option("--json", "Force JSON output").action(async (dropId, commandOptions) => {
1999
+ const deps = await commandDeps(program, options, store, commandOptions);
2000
+ const result = await runDropsGet(dropId, commandOptions, deps);
2001
+ process.exitCode = result.exitCode;
2002
+ });
2003
+ program.command("list").description("List your drops").option("--limit <n>", "Page size", parseInteger).option("--cursor <cursor>", "Pagination cursor").option("--json", "Force JSON output").action(
2004
+ async (commandOptions) => {
2005
+ const deps = await commandDeps(program, options, store, commandOptions);
2006
+ const result = await runDropsList(commandOptions, deps);
2007
+ process.exitCode = result.exitCode;
2008
+ }
2009
+ );
2010
+ program.command("delete <dropId>").description("Delete a drop (pass the full drop_\u2026 id)").option("--yes", "Confirm deletion").option("--json", "Force JSON output").action(
1908
2011
  async (dropId, commandOptions) => {
1909
2012
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
1910
2013
  const global = globalOptions(program, commandOptions);
@@ -1920,6 +2023,26 @@ function buildProgram(options = {}) {
1920
2023
  process.exitCode = result.exitCode;
1921
2024
  }
1922
2025
  );
2026
+ const deployments = program.command("deployments").description("View deployment history");
2027
+ deployments.command("list <dropId>").description("List deployments for a drop").option("--limit <n>", "Page size", parseInteger).option("--cursor <cursor>", "Pagination cursor").option("--json", "Force JSON output").action(
2028
+ async (dropId, commandOptions) => {
2029
+ const deps = await commandDeps(program, options, store, commandOptions);
2030
+ const result = await runDeploymentsList(dropId, commandOptions, deps);
2031
+ process.exitCode = result.exitCode;
2032
+ }
2033
+ );
2034
+ deployments.command("get <dropId> <deploymentId>").description("Show deployment details").option("--json", "Force JSON output").action(
2035
+ async (dropId, deploymentId, commandOptions) => {
2036
+ const deps = await commandDeps(program, options, store, commandOptions);
2037
+ const result = await runDeploymentsGet(
2038
+ dropId,
2039
+ deploymentId,
2040
+ commandOptions,
2041
+ deps
2042
+ );
2043
+ process.exitCode = result.exitCode;
2044
+ }
2045
+ );
1923
2046
  const apiKeys = program.command("api-keys").description("Manage API keys");
1924
2047
  apiKeys.command("create").description("Create an API key").option("--label <label>", "API key label", "CLI").option("--json", "Force JSON output").action(async (commandOptions) => {
1925
2048
  const deps = await commandDeps(program, options, store, commandOptions);
@@ -1947,26 +2070,6 @@ function buildProgram(options = {}) {
1947
2070
  process.exitCode = result.exitCode;
1948
2071
  }
1949
2072
  );
1950
- const deployments = program.command("deployments").description("View deployment history");
1951
- deployments.command("list <dropId>").description("List deployments for a drop").option("--limit <n>", "Page size", parseInteger).option("--cursor <cursor>", "Pagination cursor").option("--json", "Force JSON output").action(
1952
- async (dropId, commandOptions) => {
1953
- const deps = await commandDeps(program, options, store, commandOptions);
1954
- const result = await runDeploymentsList(dropId, commandOptions, deps);
1955
- process.exitCode = result.exitCode;
1956
- }
1957
- );
1958
- deployments.command("get <dropId> <deploymentId>").description("Show deployment details").option("--json", "Force JSON output").action(
1959
- async (dropId, deploymentId, commandOptions) => {
1960
- const deps = await commandDeps(program, options, store, commandOptions);
1961
- const result = await runDeploymentsGet(
1962
- dropId,
1963
- deploymentId,
1964
- commandOptions,
1965
- deps
1966
- );
1967
- process.exitCode = result.exitCode;
1968
- }
1969
- );
1970
2073
  program.command("login").description("Authenticate with email OTP").option("--email <email>", "Email address").option("--otp <otp>", "One-time passcode").option("--json", "Force JSON output").action(
1971
2074
  async (commandOptions) => {
1972
2075
  if (!commandOptions.email || !commandOptions.otp) {
@@ -2191,11 +2294,6 @@ function parseInteger(value) {
2191
2294
  if (Number.isNaN(n)) throw new import_commander.InvalidArgumentError("Expected an integer.");
2192
2295
  return n;
2193
2296
  }
2194
- async function resolveCommandInput(input, stdin) {
2195
- if (input !== "-") return input;
2196
- const buf = await readStdinBytes(stdin ?? process.stdin);
2197
- return buf.toString("utf8");
2198
- }
2199
2297
  async function resolvePublishInputs(inputs, stdin, stdinIsTTY) {
2200
2298
  if (inputs.length === 0) {
2201
2299
  if (stdinIsTTY) return void 0;