@letterstory/cli 0.176.66 → 0.176.68

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/README.md CHANGED
@@ -123,6 +123,8 @@ letterstory phantom resume <phantom-id>
123
123
  letterstory phantom diagnostics <phantom-id> # why is my site empty?
124
124
  letterstory phantom balance # sites live / granted, and the cadence cap
125
125
  letterstory phantom spread-looks [--apply] # a different look per site; dry run unless --apply
126
+ letterstory phantom source <phantom-id> code # build the site from its own repo; each new post merges in as Markdown
127
+ letterstory phantom source <phantom-id> template # back to the shared template (the repo is archived)
126
128
  letterstory phantom delete <phantom-id> --yes
127
129
  ```
128
130
 
package/lib/cli.mjs CHANGED
@@ -85,7 +85,7 @@ import {
85
85
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
86
86
 
87
87
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
88
- export const VERSION = "0.176.66";
88
+ export const VERSION = "0.176.68";
89
89
 
90
90
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
91
91
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -568,6 +568,8 @@ Phantomstory (a collection Letter hosts as an independent publication):
568
568
  The site's file joined to its collection's
569
569
  phantom rebuild <phantom-id> · phantom diagnostics <phantom-id> · phantom balance
570
570
  phantom spread-looks [--apply] Give each live site a different look (dry run by default)
571
+ phantom source <phantom-id> code|template Build the site from its own repo (every post a Markdown
572
+ file, new posts merged in), or the shared template again
571
573
  phantom domain check <domain> · phantom domain buy <phantom-id> <domain> --yes
572
574
  phantom domain connect <phantom-id> <domain>
573
575
  phantom delete <phantom-id> --yes
@@ -2144,7 +2146,13 @@ export async function run(argv, io = defaultIo()) {
2144
2146
  }
2145
2147
 
2146
2148
  try {
2147
- const config = resolveConfig({ url: str(flags.url), key: str(flags.key) });
2149
+ // A subcommand that declares its own `--key`/`--url` (`automate send --key <event id> --url <page>`) keeps them;
2150
+ // they never become the API key or the API's URL (2026-11-08 E2E: a 401 and a "path not found").
2151
+ const owns = handler.ownFlags?.(positionals[1]) ?? [];
2152
+ const config = resolveConfig({
2153
+ url: owns.includes("url") ? undefined : str(flags.url),
2154
+ key: owns.includes("key") ? undefined : str(flags.key),
2155
+ });
2148
2156
  // A silent refresh mid-command should persist the new tokens so the next
2149
2157
  // invocation doesn't have to refresh again — but only when the config we
2150
2158
  // resolved actually came from a saved OAuth session (a --key/env override
@@ -31,7 +31,7 @@ const TEMPLATES = [
31
31
  "monthly_page_refresh",
32
32
  ];
33
33
  const UNITS = ["hours", "days", "weeks", "months"];
34
- const STATUSES = ["pending", "proposed", "acting", "acted", "skipped", "failed", "rejected"];
34
+ const STATUSES = ["pending", "proposed", "acting", "acted", "answered", "skipped", "failed", "rejected"];
35
35
 
36
36
  const triggerParams = {
37
37
  object: {
@@ -211,6 +211,9 @@ export async function cmdAutomate(ctx) {
211
211
  }
212
212
  }
213
213
 
214
+ /** The global flags a subcommand reads as its own arguments, so the CLI never takes them as its key or URL. */
215
+ cmdAutomate.ownFlags = (sub) => (sub === "send" ? ["key", "url"] : []);
216
+
214
217
  async function run(ctx, tool, said, extra = {}) {
215
218
  const { client, flags, io } = ctx;
216
219
  const result = await client.callTool(tool, { ...argsFromFlags(ARGS[tool], flags, ctx.positionals), ...extra });
@@ -116,6 +116,11 @@ export const ARGS = {
116
116
  phantom_id: { positional: 0, label: "phantom-id", required: true },
117
117
  paused: { value: (flags, positionals, extra) => extra.paused },
118
118
  },
119
+ // `phantom source <id> code|template` — build the site from its own repo, or the shared template again.
120
+ set_phantom_source: {
121
+ phantom_id: { positional: 0, label: "phantom-id", required: true },
122
+ source: { positional: 1, label: "code|template", required: true },
123
+ },
119
124
  get_phantom_diagnostics: { phantom_id: { positional: 0, label: "phantom-id", required: true } },
120
125
  get_phantom_balance: {},
121
126
  spread_phantom_looks: { apply: { flag: "apply", type: "bool" } },
@@ -162,6 +167,8 @@ export async function cmdPhantom(ctx) {
162
167
  case "pause":
163
168
  case "resume":
164
169
  return phantomPause(rest, sub === "pause");
170
+ case "source":
171
+ return call(rest, "set_phantom_source");
165
172
  case "diagnostics":
166
173
  case "diag":
167
174
  return call(rest, "get_phantom_diagnostics");
@@ -173,7 +180,7 @@ export async function cmdPhantom(ctx) {
173
180
  return phantomDomain(rest);
174
181
  default:
175
182
  throw new CliError(
176
- `Unknown phantom subcommand: ${sub ?? "(none)"}. Try: list, get, new, config, visitors, delete, rebuild, pause, resume, diagnostics, balance, spread-looks, domain`
183
+ `Unknown phantom subcommand: ${sub ?? "(none)"}. Try: list, get, new, config, visitors, delete, rebuild, pause, resume, source, diagnostics, balance, spread-looks, domain`
177
184
  );
178
185
  }
179
186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.66",
3
+ "version": "0.176.68",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",