@letterstory/cli 0.121.2 → 0.121.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cli.mjs CHANGED
@@ -60,7 +60,7 @@ import {
60
60
  } from "./commands.mjs";
61
61
 
62
62
  // Keep in sync with cli/package.json.
63
- export const VERSION = "0.121.2";
63
+ export const VERSION = "0.121.3";
64
64
 
65
65
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
66
66
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -1261,6 +1261,12 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
1261
1261
  repo set-site --repo <owner/name> --site <host> [--not] [--branch <name>|none]
1262
1262
  Say which site a repo builds (--not: correct a wrong label)
1263
1263
  repo reset-site --repo <owner/name> --site <host> Back to automatic detection
1264
+ repo secrets --repo <owner/name> Preview secrets: names and when set (never values)
1265
+ repo set-secret --repo <owner/name> --secret <KEY> --value <v> | --value-file <path|->
1266
+ Set an env var the preview site gets (use test credentials:
1267
+ the preview URL is public to anyone with the link)
1268
+ repo delete-secret --repo <owner/name> --secret <KEY>
1269
+ Delete one
1264
1270
  repo status <run-id> Phase, spend, PR link and Claude's summary
1265
1271
  repo activity <run-id> [--follow] What the sandbox is doing, line by line (--follow: until it stops)
1266
1272
  repo list [--status running|succeeded|no_changes|failed|cancelled] [--limit n] [--audit <id>]
@@ -7,6 +7,8 @@
7
7
  // repo repos → list_github_app_repos repo branches → list_repo_branches
8
8
  // repo prs → list_open_pull_requests (every open PR, whoever opened it)
9
9
  // repo install-letterprove → letterprove_open_install_pr
10
+ // repo secrets → list_repo_preview_secrets repo set-secret → set_repo_preview_secret
11
+ // repo delete-secret → delete_repo_preview_secret
10
12
  // repo suggestions → list_suggested_fixes
11
13
  // repo settings → get_repo_change_settings, or set_repo_change_settings with --action / --branch
12
14
  // repo slack-targets → list_slack_fix_targets repo slack-test → send_slack_test_message
@@ -25,6 +27,14 @@ import {
25
27
  refuseUnknownFlags,
26
28
  } from "./shared.mjs";
27
29
 
30
+ /** `--value <v>` or `--value-file <path|->`; a file's single trailing newline is dropped. */
31
+ function secretValue(flags) {
32
+ const inline = flagStr(flags.value);
33
+ const value = readTextInput(flags, "value", "value-file");
34
+ if (value === undefined) throw new CliError("Pass the value with --value <value> or --value-file <path|->.");
35
+ return inline === undefined ? value.replace(/\r?\n$/, "") : value;
36
+ }
37
+
28
38
  /** `--repo owner/name` → the two schema keys; --owner/--name also work. */
29
39
  function splitRepo(flags) {
30
40
  const full = flagStr(flags.repo);
@@ -69,6 +79,21 @@ const ARGS = {
69
79
  repo_name: { value: (flags) => splitRepo(flags).name },
70
80
  site_host: { flag: "site", required: true },
71
81
  },
82
+ list_repo_preview_secrets: {
83
+ repo_owner: { value: (flags) => splitRepo(flags).owner },
84
+ repo_name: { value: (flags) => splitRepo(flags).name },
85
+ },
86
+ set_repo_preview_secret: {
87
+ repo_owner: { value: (flags) => splitRepo(flags).owner },
88
+ repo_name: { value: (flags) => splitRepo(flags).name },
89
+ key: { flag: "secret", required: true },
90
+ value: { value: (flags) => secretValue(flags) },
91
+ },
92
+ delete_repo_preview_secret: {
93
+ repo_owner: { value: (flags) => splitRepo(flags).owner },
94
+ repo_name: { value: (flags) => splitRepo(flags).name },
95
+ key: { flag: "secret", required: true },
96
+ },
72
97
  fix_audit_issue: {
73
98
  // --issue <type>, repeatable or comma-separated: several go into one PR (issue_types).
74
99
  issue_type: { value: (flags) => (issueList(flags).length === 1 ? issueList(flags)[0] : undefined) },
@@ -198,6 +223,12 @@ export async function cmdRepo(ctx) {
198
223
  return repoSetSite(rest);
199
224
  case "reset-site":
200
225
  return repoResetSite(rest);
226
+ case "secrets":
227
+ return repoSecrets(rest);
228
+ case "set-secret":
229
+ return repoSetSecret(rest);
230
+ case "delete-secret":
231
+ return repoDeleteSecret(rest);
201
232
  case "list":
202
233
  return repoList(rest);
203
234
  case "cancel":
@@ -220,7 +251,7 @@ export async function cmdRepo(ctx) {
220
251
  return repoSlackTest(rest);
221
252
  default:
222
253
  throw new CliError(
223
- `Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, sites, set-site, reset-site, status, list, publish, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
254
+ `Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, sites, set-site, reset-site, secrets, set-secret, delete-secret, status, list, publish, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
224
255
  );
225
256
  }
226
257
  }
@@ -400,6 +431,45 @@ async function repoResetSite(ctx) {
400
431
  return 0;
401
432
  }
402
433
 
434
+ /** `repo secrets --repo owner/name`: the repo's preview secrets, names and when each was set (never values). */
435
+ async function repoSecrets(ctx) {
436
+ const { client, flags, io } = ctx;
437
+ refuseUnknownFlags(ctx, {}, ["repo", "owner", "name"]);
438
+ const result = await client.callTool(
439
+ "list_repo_preview_secrets",
440
+ argsFromFlags(ARGS.list_repo_preview_secrets, flags)
441
+ );
442
+ printResult(io, flags, result, (r) =>
443
+ r.secrets.length
444
+ ? r.secrets.map((s) => `${s.key} (set ${s.updatedAt})`).join("\n")
445
+ : "No preview secrets for this repo."
446
+ );
447
+ return 0;
448
+ }
449
+
450
+ /** `repo set-secret --repo owner/name --secret KEY --value v | --value-file path|-`: set or overwrite one. */
451
+ async function repoSetSecret(ctx) {
452
+ const { client, flags, io } = ctx;
453
+ refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "secret", "value", "value-file"]);
454
+ const result = await client.callTool("set_repo_preview_secret", argsFromFlags(ARGS.set_repo_preview_secret, flags));
455
+ ok(ctx, result.message);
456
+ printResult(io, flags, result.secret);
457
+ return 0;
458
+ }
459
+
460
+ /** `repo delete-secret --repo owner/name --secret KEY`: delete one. */
461
+ async function repoDeleteSecret(ctx) {
462
+ const { client, flags, io } = ctx;
463
+ refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "secret"]);
464
+ const result = await client.callTool(
465
+ "delete_repo_preview_secret",
466
+ argsFromFlags(ARGS.delete_repo_preview_secret, flags)
467
+ );
468
+ ok(ctx, result.message);
469
+ printResult(io, flags, result);
470
+ return 0;
471
+ }
472
+
403
473
  async function repoStatus(ctx) {
404
474
  const { client, flags, io } = ctx;
405
475
  const result = await client.callTool(
package/lib/documents.mjs CHANGED
@@ -83,6 +83,8 @@ export const JSON_ARG_FLAG_PAIRS = [
83
83
  ["csv", "csv-file"],
84
84
  // A site proposal's tool input, parsed as JSON (site.mjs proposalInput).
85
85
  ["input", "input-file"],
86
+ // A preview secret's value, sent byte for byte (repo.mjs `repo set-secret`); never extracted.
87
+ ["value", "value-file"],
86
88
  // A Vercel API token read verbatim, so it stays out of shell history (outbound.mjs mail vercel-key set).
87
89
  ["token", "token-file"],
88
90
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.121.2",
3
+ "version": "0.121.3",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",