@letterstory/cli 0.121.1 → 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.1";
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,7 +1261,14 @@ 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
1271
+ repo activity <run-id> [--follow] What the sandbox is doing, line by line (--follow: until it stops)
1265
1272
  repo list [--status running|succeeded|no_changes|failed|cancelled] [--limit n] [--audit <id>]
1266
1273
  [--source site_audit_issue|site_quick_check|letterprove_install]
1267
1274
  repo publish <run-id> Make PR on a preview in review
@@ -1,11 +1,14 @@
1
1
  // `repo` — changes to the org's own repos, made by Claude Code in a sandbox and opened as a
2
2
  // pull request by the Letterstory GitHub App (src/lib/repo-changes). One subcommand per tool:
3
3
  // repo start → start_repo_change repo status → get_repo_change
4
+ // repo activity → get_repo_change_activity (what the sandbox is doing; --follow polls)
4
5
  // repo list → list_repo_changes repo cancel → cancel_repo_change
5
6
  // repo publish → publish_repo_change (Make PR on a preview)
6
7
  // repo repos → list_github_app_repos repo branches → list_repo_branches
7
8
  // repo prs → list_open_pull_requests (every open PR, whoever opened it)
8
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
9
12
  // repo suggestions → list_suggested_fixes
10
13
  // repo settings → get_repo_change_settings, or set_repo_change_settings with --action / --branch
11
14
  // repo slack-targets → list_slack_fix_targets repo slack-test → send_slack_test_message
@@ -24,6 +27,14 @@ import {
24
27
  refuseUnknownFlags,
25
28
  } from "./shared.mjs";
26
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
+
27
38
  /** `--repo owner/name` → the two schema keys; --owner/--name also work. */
28
39
  function splitRepo(flags) {
29
40
  const full = flagStr(flags.repo);
@@ -68,6 +79,21 @@ const ARGS = {
68
79
  repo_name: { value: (flags) => splitRepo(flags).name },
69
80
  site_host: { flag: "site", required: true },
70
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
+ },
71
97
  fix_audit_issue: {
72
98
  // --issue <type>, repeatable or comma-separated: several go into one PR (issue_types).
73
99
  issue_type: { value: (flags) => (issueList(flags).length === 1 ? issueList(flags)[0] : undefined) },
@@ -138,6 +164,7 @@ const ARGS = {
138
164
  autopilot_min_severity: { flag: "autopilot-severity", enum: ["critical", "warning"] },
139
165
  },
140
166
  get_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
167
+ get_repo_change_activity: { run_id: { positional: 0, required: true, label: "run-id" } },
141
168
  cancel_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
142
169
  publish_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
143
170
  list_repo_changes: {
@@ -184,6 +211,8 @@ export async function cmdRepo(ctx) {
184
211
  return repoStart(rest);
185
212
  case "status":
186
213
  return repoStatus(rest);
214
+ case "activity":
215
+ return repoActivity(rest);
187
216
  case "fix-audit":
188
217
  return repoFixAudit(rest);
189
218
  case "install-letterprove":
@@ -194,6 +223,12 @@ export async function cmdRepo(ctx) {
194
223
  return repoSetSite(rest);
195
224
  case "reset-site":
196
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);
197
232
  case "list":
198
233
  return repoList(rest);
199
234
  case "cancel":
@@ -216,7 +251,7 @@ export async function cmdRepo(ctx) {
216
251
  return repoSlackTest(rest);
217
252
  default:
218
253
  throw new CliError(
219
- `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`
220
255
  );
221
256
  }
222
257
  }
@@ -396,6 +431,45 @@ async function repoResetSite(ctx) {
396
431
  return 0;
397
432
  }
398
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
+
399
473
  async function repoStatus(ctx) {
400
474
  const { client, flags, io } = ctx;
401
475
  const result = await client.callTool(
@@ -407,6 +481,39 @@ async function repoStatus(ctx) {
407
481
  return 0;
408
482
  }
409
483
 
484
+ const ACTIVITY_MARK = { progress: "·", stage: "▸", say: "»", tool: "$", error: "!" };
485
+
486
+ /** `repo activity <run-id> [--follow]`: the build's feed; --follow prints new lines until it stops. */
487
+ async function repoActivity(ctx) {
488
+ const { client, flags, io } = ctx;
489
+ refuseUnknownFlags(ctx, {}, ["follow"]);
490
+ const args = argsFromFlags(ARGS.get_repo_change_activity, flags, ctx.positionals);
491
+ const line = (l) => `${l.at.slice(11, 19)} ${ACTIVITY_MARK[l.kind] ?? " "} ${l.text}`;
492
+ let { activity } = await client.callTool("get_repo_change_activity", args);
493
+ if (flags.json && !flags.follow) {
494
+ printResult(io, flags, activity);
495
+ return 0;
496
+ }
497
+ let seen = 0;
498
+ const print = () => {
499
+ for (const l of activity.lines.slice(seen)) io.log(flags.json ? JSON.stringify(l) : line(l));
500
+ seen = activity.lines.length;
501
+ };
502
+ print();
503
+ if (!flags.follow) return 0;
504
+ await followJob(
505
+ io,
506
+ async () => {
507
+ ({ activity } = await client.callTool("get_repo_change_activity", args));
508
+ print();
509
+ return { done: !activity.live, value: activity };
510
+ },
511
+ { intervalMs: 3000, ceilingMs: 60 * 60 * 1000 }
512
+ );
513
+ ok(ctx, `Run ${activity.runId} is ${activity.status} (${activity.phase}).`);
514
+ return 0;
515
+ }
516
+
410
517
  async function repoList(ctx) {
411
518
  const { client, flags, io } = ctx;
412
519
  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.1",
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",