@dropthis/cli 0.35.0 → 0.36.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
@@ -154,6 +154,8 @@ var init_inline_auth = __esm({
154
154
  // src/cli.ts
155
155
  var cli_exports = {};
156
156
  __export(cli_exports, {
157
+ wantsInsecureStore: () => wantsInsecureStore,
158
+ writeCredentialStorageError: () => writeCredentialStorageError,
157
159
  writeUsageErrorEnvelope: () => writeUsageErrorEnvelope
158
160
  });
159
161
  module.exports = __toCommonJS(cli_exports);
@@ -178,8 +180,11 @@ var UPLOAD_NEXT_ACTIONS = {
178
180
  too_many_active_uploads: "Cancel unused uploads or wait for expired uploads to be cleaned.",
179
181
  upload_not_complete: "Complete the upload session before publishing."
180
182
  };
183
+ function isCiEnv(env) {
184
+ return Boolean(env.CI);
185
+ }
181
186
  function createOutput(input) {
182
- const json = input.json === true || input.quiet === true || input.stdoutIsTTY === false || input.env.CI === "true";
187
+ const json = input.json === true || input.quiet === true || input.stdoutIsTTY === false || isCiEnv(input.env);
183
188
  return { mode: json ? "json" : "human", quiet: input.quiet === true };
184
189
  }
185
190
  function errorEnvelope(code, message, nextAction) {
@@ -255,7 +260,10 @@ function cliNextAction(error2) {
255
260
  return `This account belongs to more than one workspace \u2014 pick one. Switch it for every later command with: dropthis workspace use ${example} \u2014 or target just this one with: --workspace ${example}.${list}`;
256
261
  }
257
262
  if (error2.code === "network_error") {
258
- return "Could not reach the dropthis API \u2014 check your network or DROPTHIS_API_URL.";
263
+ return "Could not reach the dropthis API \u2014 check your network or DROPTHIS_API_URL. Run `dropthis doctor --online` to diagnose.";
264
+ }
265
+ if (error2.code === "insufficient_scope") {
266
+ return "This key lacks the required capability scope. Mint a team-scoped key: dropthis login request --email <email>, then dropthis login verify --email <email> --otp <code> --scope team.";
259
267
  }
260
268
  if (error2.code === "workspace_pinned") {
261
269
  return "Service keys are pinned to one workspace. To switch workspaces, use a delegated login (run: dropthis login) instead of a service key.";
@@ -285,10 +293,10 @@ function cliNextAction(error2) {
285
293
  return "Check the drop id or slug and retry; list your drops with dropthis list.";
286
294
  }
287
295
  if (error2.code === "invalid_api_key") {
288
- return "API key is invalid or revoked. Run dropthis login to re-authenticate, or set a valid DROPTHIS_API_KEY.";
296
+ return "API key is invalid or revoked. Run dropthis login to re-authenticate, or set a valid DROPTHIS_API_KEY. Run `dropthis doctor --online` to diagnose.";
289
297
  }
290
298
  if (error2.statusCode === 401 || error2.code === "missing_api_key") {
291
- return "Authenticate with dropthis login or set DROPTHIS_API_KEY.";
299
+ return "Authenticate with dropthis login or set DROPTHIS_API_KEY. Run `dropthis doctor --online` to diagnose.";
292
300
  }
293
301
  if (error2.statusCode === 413 || error2.code === "quota_exceeded") {
294
302
  return "Reduce the upload size or upgrade the account limit.";
@@ -299,6 +307,7 @@ function exitCodeFor(code) {
299
307
  if (code === "usage_error") return 2;
300
308
  if (code === "invalid_usage") return 2;
301
309
  if (code === "auth_error") return 3;
310
+ if (code === "credential_storage_unavailable") return 3;
302
311
  if (code === "local_input_error") return 4;
303
312
  if (code === "network_error") return 5;
304
313
  if (code === "verify_pending") return 6;
@@ -351,6 +360,9 @@ function maskKey(apiKey) {
351
360
  return apiKey.length <= 8 ? "sk_..." : `${apiKey.slice(0, 3)}...${apiKey.slice(-4)}`;
352
361
  }
353
362
 
363
+ // src/commands/account.ts
364
+ var prompts = __toESM(require("@clack/prompts"), 1);
365
+
354
366
  // src/fmt.ts
355
367
  var import_picocolors2 = __toESM(require("picocolors"), 1);
356
368
 
@@ -404,8 +416,8 @@ function writeResult(deps, link, verb, data) {
404
416
  `
405
417
  );
406
418
  } else {
407
- const text2 = typeof w.detail === "string" ? w.detail : typeof w.message === "string" ? w.message : typeof w.code === "string" ? w.code : void 0;
408
- if (text2) deps.stderr(`${hint(text2)}
419
+ const text3 = typeof w.detail === "string" ? w.detail : typeof w.message === "string" ? w.message : typeof w.code === "string" ? w.code : void 0;
420
+ if (text3) deps.stderr(`${hint(text3)}
409
421
  `);
410
422
  }
411
423
  }
@@ -498,6 +510,10 @@ function writeApiError(deps, details) {
498
510
  `
499
511
  );
500
512
  }
513
+ if (details.upgradeUrl) {
514
+ deps.stderr(`${hint(`Upgrade: ${details.upgradeUrl}`)}
515
+ `);
516
+ }
501
517
  }
502
518
  const nextAction = nextActionForApiError(details, "human");
503
519
  if (nextAction) deps.stderr(`${hint(nextAction)}
@@ -622,15 +638,16 @@ async function runAccountUpdate(input, deps) {
622
638
  return { exitCode: 0 };
623
639
  }
624
640
  async function runAccountDelete(input, deps) {
625
- if (!input.yes && input.interactive === false) {
641
+ if (!input.yes && input.interactive !== true) {
626
642
  return writeError(
627
643
  deps,
628
644
  "invalid_usage",
629
645
  "Pass --yes to delete the account in non-interactive mode."
630
646
  );
631
647
  }
648
+ let credential;
632
649
  try {
633
- await requireCredential({
650
+ credential = await requireCredential({
634
651
  ...deps.apiKey ? { apiKey: deps.apiKey } : {},
635
652
  env: deps.env,
636
653
  store: deps.store
@@ -638,6 +655,26 @@ async function runAccountDelete(input, deps) {
638
655
  } catch {
639
656
  return writeAuthError(deps);
640
657
  }
658
+ if (!input.yes) {
659
+ if (!credential.email) {
660
+ return writeError(
661
+ deps,
662
+ "invalid_usage",
663
+ "No stored account email to confirm against.",
664
+ "Re-run with --yes to confirm: dropthis account delete --yes"
665
+ );
666
+ }
667
+ const typed = await prompts.text({
668
+ message: `This permanently deletes your account and all its drops. Type ${credential.email} to confirm:`
669
+ });
670
+ if (prompts.isCancel(typed) || typed !== credential.email) {
671
+ deps.stderr(
672
+ `${error("Account deletion cancelled \u2014 confirmation did not match.")}
673
+ `
674
+ );
675
+ return { exitCode: 1 };
676
+ }
677
+ }
641
678
  const result = await deps.client.account.delete();
642
679
  if (result.error) return writeApiError(deps, result.error);
643
680
  writeHumanOrJson(deps, success("Account deleted"), {
@@ -648,7 +685,7 @@ async function runAccountDelete(input, deps) {
648
685
  }
649
686
 
650
687
  // src/commands/api-keys.ts
651
- var prompts = __toESM(require("@clack/prompts"), 1);
688
+ var prompts2 = __toESM(require("@clack/prompts"), 1);
652
689
 
653
690
  // src/types.ts
654
691
  function extractPagination(data) {
@@ -774,10 +811,10 @@ async function runApiKeysDelete(keyId, input, deps) {
774
811
  );
775
812
  }
776
813
  if (!input.yes && input.interactive !== false) {
777
- const confirmed = await prompts.confirm({
814
+ const confirmed = await prompts2.confirm({
778
815
  message: `Delete API key ${keyId}?`
779
816
  });
780
- if (prompts.isCancel(confirmed) || !confirmed) {
817
+ if (prompts2.isCancel(confirmed) || !confirmed) {
781
818
  return { exitCode: 0 };
782
819
  }
783
820
  }
@@ -1136,7 +1173,8 @@ function spreadData(data) {
1136
1173
  }
1137
1174
 
1138
1175
  // src/version.ts
1139
- var CLI_VERSION = true ? "0.35.0" : "0.0.0-dev";
1176
+ var CLI_VERSION = true ? "0.36.0" : "0.0.0-dev";
1177
+ var SDK_VERSION = true ? "0.32.0" : "0.0.0-dev";
1140
1178
 
1141
1179
  // src/commands/doctor.ts
1142
1180
  async function onlineChecks(deps, hasCredential) {
@@ -1213,39 +1251,35 @@ async function runDoctor(input, deps) {
1213
1251
  });
1214
1252
  const authSource = credential?.source ?? "missing";
1215
1253
  const storageBackend = stored?.storage ?? "none";
1254
+ const scopes = credential?.scopes ?? [];
1216
1255
  const pairs = [
1217
1256
  ["Version", CLI_VERSION],
1257
+ ["SDK", SDK_VERSION],
1218
1258
  ["Auth", authSource],
1219
1259
  ["Storage", storageBackend]
1220
1260
  ];
1261
+ if (scopes.length > 0) pairs.push(["Scopes", scopes.join(", ")]);
1262
+ const baseEnvelope = {
1263
+ version: CLI_VERSION,
1264
+ sdk_version: SDK_VERSION,
1265
+ auth: { source: authSource },
1266
+ storage: { backend: storageBackend },
1267
+ ...scopes.length > 0 ? { scopes } : {}
1268
+ };
1221
1269
  if (!input.online) {
1222
- writeKv(deps, pairs, {
1223
- ok: true,
1224
- version: CLI_VERSION,
1225
- auth: { source: authSource },
1226
- storage: { backend: storageBackend }
1227
- });
1270
+ writeKv(deps, pairs, { ok: true, ...baseEnvelope });
1228
1271
  return { exitCode: 0 };
1229
1272
  }
1230
- const { checks, exitCode } = await onlineChecks(
1231
- deps,
1232
- credential !== void 0
1233
- );
1273
+ const { checks, exitCode } = await onlineChecks(deps, credential !== null);
1234
1274
  for (const c of checks) {
1235
1275
  pairs.push([c.name, `${c.status} \u2014 ${c.detail}`]);
1236
1276
  }
1237
- writeKv(deps, pairs, {
1238
- ok: exitCode === 0,
1239
- version: CLI_VERSION,
1240
- auth: { source: authSource },
1241
- storage: { backend: storageBackend },
1242
- checks
1243
- });
1277
+ writeKv(deps, pairs, { ok: exitCode === 0, ...baseEnvelope, checks });
1244
1278
  return { exitCode };
1245
1279
  }
1246
1280
 
1247
1281
  // src/commands/domains.ts
1248
- var prompts2 = __toESM(require("@clack/prompts"), 1);
1282
+ var prompts3 = __toESM(require("@clack/prompts"), 1);
1249
1283
  var import_picocolors3 = __toESM(require("picocolors"), 1);
1250
1284
  function formatDnsTable(records) {
1251
1285
  if (records.length === 0) return "";
@@ -1544,7 +1578,7 @@ async function runDomainsVerify(idOrHostname, input, deps, sleep = defaultSleep)
1544
1578
  if (deps.outputMode === "json" && domain.dns.length > 0) {
1545
1579
  const elapsed = Math.round(elapsedMs / 1e3);
1546
1580
  deps.stderr(
1547
- `${JSON.stringify({ ok: false, pending: true, status: domain.status, elapsed })}
1581
+ `${JSON.stringify({ ok: true, pending: true, status: domain.status, elapsed })}
1548
1582
  `
1549
1583
  );
1550
1584
  }
@@ -1596,10 +1630,10 @@ async function runDomainsRemove(idOrHostname, input, deps) {
1596
1630
  );
1597
1631
  }
1598
1632
  if (!input.yes && input.interactive !== false) {
1599
- const confirmed = await prompts2.confirm({
1633
+ const confirmed = await prompts3.confirm({
1600
1634
  message: `Remove ${idOrHostname}? This removes the domain and unmounts its drops.`
1601
1635
  });
1602
- if (prompts2.isCancel(confirmed) || !confirmed) {
1636
+ if (prompts3.isCancel(confirmed) || !confirmed) {
1603
1637
  return { exitCode: 0 };
1604
1638
  }
1605
1639
  }
@@ -1635,7 +1669,7 @@ async function runDomainsRemove(idOrHostname, input, deps) {
1635
1669
  }
1636
1670
 
1637
1671
  // src/commands/drops.ts
1638
- var prompts3 = __toESM(require("@clack/prompts"), 1);
1672
+ var prompts4 = __toESM(require("@clack/prompts"), 1);
1639
1673
  var import_picocolors4 = __toESM(require("picocolors"), 1);
1640
1674
  async function resolveDropTarget(target, deps) {
1641
1675
  if (target.startsWith("drop_")) return { ok: true, dropId: target };
@@ -1805,10 +1839,10 @@ async function runDropsDelete(target, input, deps) {
1805
1839
  writeResolvedLine(deps, resolved);
1806
1840
  const dropId = resolved.dropId;
1807
1841
  if (!input.yes && input.interactive !== false) {
1808
- const confirmed = await prompts3.confirm({
1842
+ const confirmed = await prompts4.confirm({
1809
1843
  message: `Delete ${dropId}${dropId === target ? "" : ` (${target})`}?`
1810
1844
  });
1811
- if (prompts3.isCancel(confirmed) || !confirmed) {
1845
+ if (prompts4.isCancel(confirmed) || !confirmed) {
1812
1846
  return { exitCode: 0 };
1813
1847
  }
1814
1848
  }
@@ -1898,31 +1932,31 @@ async function runInvitationsAcceptById(input, deps) {
1898
1932
  }
1899
1933
 
1900
1934
  // src/commands/login.ts
1901
- var prompts4 = __toESM(require("@clack/prompts"), 1);
1935
+ var prompts5 = __toESM(require("@clack/prompts"), 1);
1902
1936
  async function runLoginInteractive(deps) {
1903
- prompts4.intro("dropthis login");
1904
- const email = await prompts4.text({
1937
+ prompts5.intro("dropthis login");
1938
+ const email = await prompts5.text({
1905
1939
  message: "Email address",
1906
1940
  validate: (v) => v?.includes("@") ? void 0 : "Enter a valid email"
1907
1941
  });
1908
- if (prompts4.isCancel(email)) {
1909
- prompts4.cancel("Login cancelled.");
1942
+ if (prompts5.isCancel(email)) {
1943
+ prompts5.cancel("Login cancelled.");
1910
1944
  return { exitCode: 0 };
1911
1945
  }
1912
1946
  const requestResult = await deps.client.auth.requestEmailOtp({ email });
1913
1947
  if (requestResult.error) {
1914
- prompts4.cancel(requestResult.error.message);
1948
+ prompts5.cancel(requestResult.error.message);
1915
1949
  return { exitCode: exitCodeFor("api_error") };
1916
1950
  }
1917
1951
  const maxAttempts = 3;
1918
1952
  let session;
1919
1953
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1920
- const otp = await prompts4.text({
1954
+ const otp = await prompts5.text({
1921
1955
  message: attempt === 1 ? "Paste the code from your email" : "Try again \u2014 paste the code from your email",
1922
1956
  validate: (v) => v && v.length >= 4 && v.length <= 8 ? void 0 : "Code must be 4\u20138 characters."
1923
1957
  });
1924
- if (prompts4.isCancel(otp)) {
1925
- prompts4.cancel("Login cancelled.");
1958
+ if (prompts5.isCancel(otp)) {
1959
+ prompts5.cancel("Login cancelled.");
1926
1960
  return { exitCode: 0 };
1927
1961
  }
1928
1962
  const result = await deps.client.auth.verifyEmailOtp({
@@ -1936,21 +1970,21 @@ async function runLoginInteractive(deps) {
1936
1970
  if (result.error.code === "otp_expired" && attempt < maxAttempts) {
1937
1971
  const resend = await deps.client.auth.requestEmailOtp({ email });
1938
1972
  if (resend.error) {
1939
- prompts4.cancel(resend.error.message);
1973
+ prompts5.cancel(resend.error.message);
1940
1974
  return { exitCode: exitCodeFor("api_error") };
1941
1975
  }
1942
- prompts4.log.info("That code expired \u2014 we sent a new one.");
1976
+ prompts5.log.info("That code expired \u2014 we sent a new one.");
1943
1977
  continue;
1944
1978
  }
1945
1979
  if (attempt < maxAttempts) {
1946
- prompts4.log.warning(result.error.message);
1980
+ prompts5.log.warning(result.error.message);
1947
1981
  } else {
1948
- prompts4.cancel(result.error.message);
1982
+ prompts5.cancel(result.error.message);
1949
1983
  return { exitCode: exitCodeFor("api_error") };
1950
1984
  }
1951
1985
  }
1952
1986
  if (!session) {
1953
- prompts4.cancel("Login failed.");
1987
+ prompts5.cancel("Login failed.");
1954
1988
  return { exitCode: exitCodeFor("api_error") };
1955
1989
  }
1956
1990
  const authedClient = deps.createClient(session.data.token);
@@ -1959,7 +1993,7 @@ async function runLoginInteractive(deps) {
1959
1993
  type: "delegated"
1960
1994
  });
1961
1995
  if (apiKey.error) {
1962
- prompts4.cancel(apiKey.error.message);
1996
+ prompts5.cancel(apiKey.error.message);
1963
1997
  return { exitCode: exitCodeFor("api_error") };
1964
1998
  }
1965
1999
  await deps.store.save({
@@ -1971,7 +2005,7 @@ async function runLoginInteractive(deps) {
1971
2005
  ...apiKey.data.scopes ? { scopes: apiKey.data.scopes } : {},
1972
2006
  storage: "secure"
1973
2007
  });
1974
- prompts4.outro("Logged in successfully.");
2008
+ prompts5.outro("Logged in successfully.");
1975
2009
  return { exitCode: 0 };
1976
2010
  }
1977
2011
  async function runLoginRequest(input, deps) {
@@ -2025,7 +2059,10 @@ async function runLoginVerify(input, deps) {
2025
2059
  account_id: apiKey.data.accountId ?? session.data.accountId,
2026
2060
  key_id: apiKey.data.id,
2027
2061
  key_last4: apiKey.data.keyLast4,
2028
- is_new_account: apiKey.data.isNewAccount ?? session.data.isNewAccount
2062
+ is_new_account: apiKey.data.isNewAccount ?? session.data.isNewAccount,
2063
+ // Echo the resolved capability scopes the server granted, so a scoped mint
2064
+ // visibly advertises what the new key can do (ADR 0068).
2065
+ ...apiKey.data.scopes?.length ? { scopes: apiKey.data.scopes } : {}
2029
2066
  });
2030
2067
  return { exitCode: 0 };
2031
2068
  }
@@ -2047,7 +2084,7 @@ async function runLogout(input, deps) {
2047
2084
  }
2048
2085
 
2049
2086
  // src/commands/members.ts
2050
- var prompts5 = __toESM(require("@clack/prompts"), 1);
2087
+ var prompts6 = __toESM(require("@clack/prompts"), 1);
2051
2088
  async function runMembersList(workspaceId, _input, deps) {
2052
2089
  try {
2053
2090
  await requireCredential({
@@ -2129,10 +2166,10 @@ async function runMembersRemove(workspaceId, accountId, input, deps) {
2129
2166
  );
2130
2167
  }
2131
2168
  if (!input.yes && input.interactive !== false) {
2132
- const confirmed = await prompts5.confirm({
2169
+ const confirmed = await prompts6.confirm({
2133
2170
  message: `Remove ${accountId} from workspace ${workspaceId}?`
2134
2171
  });
2135
- if (prompts5.isCancel(confirmed) || !confirmed) {
2172
+ if (prompts6.isCancel(confirmed) || !confirmed) {
2136
2173
  return { exitCode: 0 };
2137
2174
  }
2138
2175
  }
@@ -2742,7 +2779,7 @@ async function handleDryRun(dropId, input, raw, deps) {
2742
2779
  }
2743
2780
 
2744
2781
  // src/commands/update-settings.ts
2745
- var prompts6 = __toESM(require("@clack/prompts"), 1);
2782
+ var prompts7 = __toESM(require("@clack/prompts"), 1);
2746
2783
  var NO_SETTINGS_MESSAGE = "Nothing to update. Provide at least one settings option.";
2747
2784
  var NO_SETTINGS_NEXT_ACTION = "Run dropthis update-settings --help, or retry with one of: --title, --visibility, --password, --no-password, --noindex, --index, --expires-at, --no-expires, --domain, --shared, --slug, --metadata, or --metadata-file. To replace the content at the URL instead, use dropthis update-content <dropId> ./dist.";
2748
2785
  async function runUpdateSettings(target, raw, deps) {
@@ -2774,7 +2811,7 @@ async function runUpdateSettings(target, raw, deps) {
2774
2811
  if (shouldPrompt(deps)) {
2775
2812
  const prompted = await promptForSettings(
2776
2813
  dropId,
2777
- deps.prompts ?? prompts6
2814
+ deps.prompts ?? prompts7
2778
2815
  );
2779
2816
  if (!prompted) return { exitCode: 0 };
2780
2817
  parsed = { ...parsed, ...prompted };
@@ -3097,12 +3134,7 @@ async function runWhoami(_input, deps) {
3097
3134
  }
3098
3135
  const result = await deps.client.account.get();
3099
3136
  if (result.error) {
3100
- writeHumanOrJson(
3101
- deps,
3102
- `Not authenticated: ${result.error.message}. Run dropthis login.`,
3103
- { ok: true, authenticated: false, reason: result.error.message }
3104
- );
3105
- return { exitCode: 0 };
3137
+ return writeApiError(deps, result.error);
3106
3138
  }
3107
3139
  const account = result.data ?? {};
3108
3140
  const masked = maskKey(credential.apiKey);
@@ -3132,7 +3164,7 @@ async function runWhoami(_input, deps) {
3132
3164
  }
3133
3165
 
3134
3166
  // src/commands/workspace.ts
3135
- var prompts7 = __toESM(require("@clack/prompts"), 1);
3167
+ var prompts8 = __toESM(require("@clack/prompts"), 1);
3136
3168
  async function runWorkspaceList(_input, deps) {
3137
3169
  try {
3138
3170
  await requireCredential({
@@ -3258,10 +3290,10 @@ async function runWorkspaceDelete(workspaceId, input, deps) {
3258
3290
  );
3259
3291
  }
3260
3292
  if (!input.yes && input.interactive !== false) {
3261
- const confirmed = await prompts7.confirm({
3293
+ const confirmed = await prompts8.confirm({
3262
3294
  message: `Delete workspace ${workspaceId}?`
3263
3295
  });
3264
- if (prompts7.isCancel(confirmed) || !confirmed) {
3296
+ if (prompts8.isCancel(confirmed) || !confirmed) {
3265
3297
  return { exitCode: 0 };
3266
3298
  }
3267
3299
  }
@@ -3315,6 +3347,28 @@ var import_promises4 = require("fs/promises");
3315
3347
  var import_node_os = require("os");
3316
3348
  var import_node_path2 = require("path");
3317
3349
  var import_keyring = require("@napi-rs/keyring");
3350
+ var CredentialStorageError = class extends Error {
3351
+ code = "credential_storage_unavailable";
3352
+ constructor(message, options) {
3353
+ super(message, options);
3354
+ this.name = "CredentialStorageError";
3355
+ }
3356
+ };
3357
+ function isCredentialStorageError(error2) {
3358
+ return error2 instanceof CredentialStorageError || typeof error2 === "object" && error2 !== null && error2.code === "credential_storage_unavailable";
3359
+ }
3360
+ async function guardStorage(action, fn) {
3361
+ try {
3362
+ return await fn();
3363
+ } catch (error2) {
3364
+ if (error2 instanceof CredentialStorageError) throw error2;
3365
+ const detail = error2 instanceof Error ? error2.message : String(error2);
3366
+ throw new CredentialStorageError(
3367
+ `Could not ${action} stored credentials: ${detail}`,
3368
+ { cause: error2 }
3369
+ );
3370
+ }
3371
+ }
3318
3372
  var MemoryCredentialStore = class {
3319
3373
  credential = null;
3320
3374
  async read() {
@@ -3333,19 +3387,24 @@ var InsecureFileCredentialStore = class {
3333
3387
  this.filePath = credentialPath(options.configDir);
3334
3388
  }
3335
3389
  async read() {
3336
- const value = await readJsonFile(this.filePath);
3337
- if (!value) return null;
3338
- if (typeof value.apiKey !== "string" || !value.apiKey) return null;
3339
- return { ...value, storage: "insecure" };
3390
+ return guardStorage("read", async () => {
3391
+ const value = await readJsonFile(this.filePath);
3392
+ if (!value) return null;
3393
+ if (typeof value.apiKey !== "string" || !value.apiKey) return null;
3394
+ return { ...value, storage: "insecure" };
3395
+ });
3340
3396
  }
3341
3397
  async save(credential) {
3342
- await writeCredentialFile(this.filePath, {
3343
- ...credential,
3344
- storage: "insecure"
3345
- });
3398
+ await guardStorage(
3399
+ "save",
3400
+ () => writeCredentialFile(this.filePath, {
3401
+ ...credential,
3402
+ storage: "insecure"
3403
+ })
3404
+ );
3346
3405
  }
3347
3406
  async clear() {
3348
- await (0, import_promises4.rm)(this.filePath, { force: true });
3407
+ await guardStorage("clear", () => (0, import_promises4.rm)(this.filePath, { force: true }));
3349
3408
  }
3350
3409
  };
3351
3410
  var KeyringCredentialStore = class {
@@ -3356,23 +3415,29 @@ var KeyringCredentialStore = class {
3356
3415
  this.keyring = options.keyring ?? defaultKeyringAdapter();
3357
3416
  }
3358
3417
  async read() {
3359
- const metadata = await readJsonFile(this.filePath);
3360
- if (!metadata) return null;
3361
- const apiKey = await this.keyring.getPassword();
3362
- if (typeof apiKey !== "string" || !apiKey) return null;
3363
- return { ...metadata, apiKey, storage: "secure" };
3418
+ return guardStorage("read", async () => {
3419
+ const metadata = await readJsonFile(this.filePath);
3420
+ if (!metadata) return null;
3421
+ const apiKey = await this.keyring.getPassword();
3422
+ if (typeof apiKey !== "string" || !apiKey) return null;
3423
+ return { ...metadata, apiKey, storage: "secure" };
3424
+ });
3364
3425
  }
3365
3426
  async save(credential) {
3366
- await this.keyring.setPassword(credential.apiKey);
3367
- const { apiKey: _apiKey, ...metadata } = credential;
3368
- await writeCredentialFile(this.filePath, {
3369
- ...metadata,
3370
- storage: "secure"
3427
+ await guardStorage("save", async () => {
3428
+ await this.keyring.setPassword(credential.apiKey);
3429
+ const { apiKey: _apiKey, ...metadata } = credential;
3430
+ await writeCredentialFile(this.filePath, {
3431
+ ...metadata,
3432
+ storage: "secure"
3433
+ });
3371
3434
  });
3372
3435
  }
3373
3436
  async clear() {
3374
- await this.keyring.deletePassword();
3375
- await (0, import_promises4.rm)(this.filePath, { force: true });
3437
+ await guardStorage("clear", async () => {
3438
+ await this.keyring.deletePassword();
3439
+ await (0, import_promises4.rm)(this.filePath, { force: true });
3440
+ });
3376
3441
  }
3377
3442
  };
3378
3443
  function createCredentialStore(options = {}) {
@@ -3472,7 +3537,9 @@ function buildProgram(options = {}) {
3472
3537
  const writeErr = options.stderr ?? ((value) => process.stderr.write(value));
3473
3538
  const jsonModeForOutput = () => {
3474
3539
  const argv = program.rawArgs ?? process.argv;
3475
- 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;
3540
+ return argv.includes("--json") || argv.includes("--quiet") || argv.includes("-q") || isCiEnv(
3541
+ options.env ?? process.env
3542
+ ) || options.stdoutIsTTY === false || options.stdoutIsTTY === void 0 && process.stdout.isTTY !== true;
3476
3543
  };
3477
3544
  program.exitOverride();
3478
3545
  program.showSuggestionAfterError();
@@ -3511,6 +3578,10 @@ function buildProgram(options = {}) {
3511
3578
  program.option("--json", "Force JSON output");
3512
3579
  program.option("-q, --quiet", "Suppress status output and imply JSON");
3513
3580
  program.option("--no-interactive", "Disable interactive prompts");
3581
+ program.option(
3582
+ "--insecure-store",
3583
+ "Store credentials in a plaintext file instead of the OS keyring (for headless/CI boxes without a keyring; also DROPTHIS_INSECURE_STORE=1)"
3584
+ );
3514
3585
  program.addHelpText(
3515
3586
  "after",
3516
3587
  [
@@ -3580,6 +3651,16 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3580
3651
  ).action(async (inputs, commandOptions) => {
3581
3652
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
3582
3653
  if (inputs.length === 0 && stdinIsTTY && !commandOptions.manifest) {
3654
+ const writer = outputWriter(program, options, commandOptions);
3655
+ if (writer.outputMode !== "human") {
3656
+ process.exitCode = writeError(
3657
+ writer,
3658
+ "invalid_usage",
3659
+ "No input to publish.",
3660
+ "Pass a file, folder, URL, or piped stdin \u2014 e.g. dropthis publish ./dist."
3661
+ ).exitCode;
3662
+ return;
3663
+ }
3583
3664
  program.outputHelp();
3584
3665
  return;
3585
3666
  }
@@ -3624,12 +3705,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3624
3705
  return;
3625
3706
  }
3626
3707
  if (deps.outputMode === "human" && deps.interactive) {
3627
- const prompts8 = await import("@clack/prompts");
3628
- const confirmed = await prompts8.confirm({
3708
+ const prompts9 = await import("@clack/prompts");
3709
+ const confirmed = await prompts9.confirm({
3629
3710
  message: `Publish '${publishInput}' as inline text?`,
3630
3711
  initialValue: false
3631
3712
  });
3632
- if (prompts8.isCancel(confirmed) || !confirmed) {
3713
+ if (prompts9.isCancel(confirmed) || !confirmed) {
3633
3714
  return;
3634
3715
  }
3635
3716
  }
@@ -3759,7 +3840,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3759
3840
  }
3760
3841
  const result = await runUpdateSettings(dropId, dropOpts, {
3761
3842
  ...deps,
3762
- interactive: isInteractive(stdinIsTTY, deps.env, global)
3843
+ interactive: isInteractive(
3844
+ stdinIsTTY,
3845
+ deps.env,
3846
+ global,
3847
+ deps.outputMode
3848
+ )
3763
3849
  });
3764
3850
  process.exitCode = result.exitCode;
3765
3851
  }
@@ -3830,7 +3916,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3830
3916
  dropId,
3831
3917
  {
3832
3918
  ...commandOptions,
3833
- interactive: isInteractive(stdinIsTTY, deps.env, global)
3919
+ interactive: isInteractive(
3920
+ stdinIsTTY,
3921
+ deps.env,
3922
+ global,
3923
+ deps.outputMode
3924
+ )
3834
3925
  },
3835
3926
  deps
3836
3927
  );
@@ -3950,7 +4041,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
3950
4041
  hostname,
3951
4042
  {
3952
4043
  ...commandOptions,
3953
- interactive: isInteractive(stdinIsTTY, deps.env, global)
4044
+ interactive: isInteractive(
4045
+ stdinIsTTY,
4046
+ deps.env,
4047
+ global,
4048
+ deps.outputMode
4049
+ )
3954
4050
  },
3955
4051
  deps
3956
4052
  );
@@ -4001,7 +4097,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
4001
4097
  keyId,
4002
4098
  {
4003
4099
  ...commandOptions,
4004
- interactive: isInteractive(stdinIsTTY, deps.env, global)
4100
+ interactive: isInteractive(
4101
+ stdinIsTTY,
4102
+ deps.env,
4103
+ global,
4104
+ deps.outputMode
4105
+ )
4005
4106
  },
4006
4107
  deps
4007
4108
  );
@@ -4061,7 +4162,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
4061
4162
  workspaceId,
4062
4163
  {
4063
4164
  ...commandOptions,
4064
- interactive: isInteractive(stdinIsTTY, deps.env, global)
4165
+ interactive: isInteractive(
4166
+ stdinIsTTY,
4167
+ deps.env,
4168
+ global,
4169
+ deps.outputMode
4170
+ )
4065
4171
  },
4066
4172
  deps
4067
4173
  );
@@ -4123,7 +4229,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
4123
4229
  accountId,
4124
4230
  {
4125
4231
  ...commandOptions,
4126
- interactive: isInteractive(stdinIsTTY, deps.env, global)
4232
+ interactive: isInteractive(
4233
+ stdinIsTTY,
4234
+ deps.env,
4235
+ global,
4236
+ deps.outputMode
4237
+ )
4127
4238
  },
4128
4239
  deps
4129
4240
  );
@@ -4158,7 +4269,7 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
4158
4269
  const deps = await commandDeps(program, options, store, commandOptions);
4159
4270
  const global = globalOptions(program, commandOptions);
4160
4271
  const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY ?? false;
4161
- if (!isInteractive(stdinIsTTY, deps.env, global)) {
4272
+ if (!isInteractive(stdinIsTTY, deps.env, global, deps.outputMode)) {
4162
4273
  process.exitCode = writeError(
4163
4274
  deps,
4164
4275
  "invalid_usage",
@@ -4252,7 +4363,12 @@ ${import_picocolors6.default.bold("Canonical vs raw URLs:")}`,
4252
4363
  const result = await runAccountDelete(
4253
4364
  {
4254
4365
  ...commandOptions,
4255
- interactive: isInteractive(stdinIsTTY, deps.env, global)
4366
+ interactive: isInteractive(
4367
+ stdinIsTTY,
4368
+ deps.env,
4369
+ global,
4370
+ deps.outputMode
4371
+ )
4256
4372
  },
4257
4373
  deps
4258
4374
  );
@@ -4349,7 +4465,12 @@ async function buildPublishDeps(program, options, store, commandOptions, stdinIs
4349
4465
  env: context.env,
4350
4466
  store
4351
4467
  });
4352
- const interactive = isInteractive(stdinIsTTY, context.env, global);
4468
+ const interactive = isInteractive(
4469
+ stdinIsTTY,
4470
+ context.env,
4471
+ global,
4472
+ context.output.mode
4473
+ );
4353
4474
  const inlineAuth = async () => {
4354
4475
  const { runInlineAuth: runInlineAuth2 } = await Promise.resolve().then(() => (init_inline_auth(), inline_auth_exports));
4355
4476
  return runInlineAuth2({
@@ -4374,8 +4495,9 @@ async function buildPublishDeps(program, options, store, commandOptions, stdinIs
4374
4495
  }
4375
4496
  };
4376
4497
  }
4377
- function isInteractive(stdinIsTTY, env, global) {
4378
- return stdinIsTTY && !env.CI && !env.DROPTHIS_NON_INTERACTIVE && global.interactive !== false;
4498
+ function isInteractive(stdinIsTTY, env, global, outputMode) {
4499
+ if (outputMode !== "human") return false;
4500
+ return stdinIsTTY && !isCiEnv(env) && !env.DROPTHIS_NON_INTERACTIVE && global.interactive !== false;
4379
4501
  }
4380
4502
  function globalOptions(program, commandOptions) {
4381
4503
  const opts = program.opts();
@@ -4548,11 +4670,29 @@ function writeUsageErrorEnvelope(input) {
4548
4670
  }
4549
4671
  return 2;
4550
4672
  }
4673
+ function writeCredentialStorageError(input) {
4674
+ const nextAction = "Set DROPTHIS_API_KEY, or re-run with --insecure-store (or DROPTHIS_INSECURE_STORE=1) to use a plaintext credential file instead of the OS keyring.";
4675
+ if (input.json) {
4676
+ input.stderr(
4677
+ `${JSON.stringify(errorEnvelope("credential_storage_unavailable", input.message, nextAction))}
4678
+ `
4679
+ );
4680
+ } else {
4681
+ input.stderr(`${error(input.message)}
4682
+ `);
4683
+ input.stderr(`${hint(nextAction)}
4684
+ `);
4685
+ }
4686
+ return exitCodeFor("credential_storage_unavailable");
4687
+ }
4688
+ function wantsInsecureStore(argv, env) {
4689
+ return argv.includes("--insecure-store") || Boolean(env.DROPTHIS_INSECURE_STORE);
4690
+ }
4551
4691
  function isCommanderError(err) {
4552
4692
  return typeof err === "object" && err !== null && typeof err.code === "string" && err.code.startsWith("commander.");
4553
4693
  }
4554
4694
  function wantsJson(argv) {
4555
- return argv.includes("--json") || argv.includes("--quiet") || argv.includes("-q") || process.env.CI === "true" || process.stdout.isTTY !== true;
4695
+ return argv.includes("--json") || argv.includes("--quiet") || argv.includes("-q") || isCiEnv(process.env) || process.stdout.isTTY !== true;
4556
4696
  }
4557
4697
  var updateCache = readCache(updateCachePath());
4558
4698
  var showNotice = shouldShowNotice({
@@ -4572,11 +4712,26 @@ ${hint(notice)}
4572
4712
  `);
4573
4713
  }
4574
4714
  }
4575
- buildProgram({ store: createCredentialStore() }).parseAsync(process.argv).finally(() => {
4715
+ buildProgram({
4716
+ store: createCredentialStore({
4717
+ insecure: wantsInsecureStore(
4718
+ process.argv.slice(2),
4719
+ process.env
4720
+ )
4721
+ })
4722
+ }).parseAsync(process.argv).finally(() => {
4576
4723
  if (showNotice && shouldRefresh(updateCache, /* @__PURE__ */ new Date())) {
4577
4724
  scheduleBackgroundRefresh(updateCachePath());
4578
4725
  }
4579
4726
  }).catch((error2) => {
4727
+ if (isCredentialStorageError(error2)) {
4728
+ process.exitCode = writeCredentialStorageError({
4729
+ message: error2.message,
4730
+ json: wantsJson(process.argv.slice(2)),
4731
+ stderr: (value) => process.stderr.write(value)
4732
+ });
4733
+ return;
4734
+ }
4580
4735
  if (isCommanderError(error2)) {
4581
4736
  if (error2.code === "commander.help" || error2.code === "commander.helpDisplayed" || error2.code === "commander.version") {
4582
4737
  process.exitCode = 0;
@@ -4594,6 +4749,8 @@ buildProgram({ store: createCredentialStore() }).parseAsync(process.argv).finall
4594
4749
  });
4595
4750
  // Annotate the CommonJS export names for ESM import in node:
4596
4751
  0 && (module.exports = {
4752
+ wantsInsecureStore,
4753
+ writeCredentialStorageError,
4597
4754
  writeUsageErrorEnvelope
4598
4755
  });
4599
4756
  //# sourceMappingURL=cli.cjs.map