@letterstory/cli 0.147.0 → 0.148.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/lib/cli.mjs CHANGED
@@ -65,7 +65,7 @@ import {
65
65
 
66
66
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
67
67
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
68
- export const VERSION = "0.147.0";
68
+ export const VERSION = "0.148.1";
69
69
 
70
70
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
71
71
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -1107,6 +1107,8 @@ Seers (event-driven signals -> drafts):
1107
1107
  Usage billing: plan, this period against the
1108
1108
  allowance and spend cap, failed payments; set
1109
1109
  the cap (admins) or ask the admins for more
1110
+ org billing usage This period by product and collection: quantities,
1111
+ and tokens and dollars when billed by usage
1110
1112
  org linear [teams [--team <id|key>] | set --team <id|key> [--project <id>|none] | disconnect]
1111
1113
  Where Letter files Linear issues (connect in Settings)
1112
1114
  org diff <file|dir> What applying a bundle would change (reads only)
@@ -28,6 +28,7 @@ export const ARGS = {
28
28
  describe_org_config_fields: {},
29
29
  get_org_config_source: {},
30
30
  get_billing_payments: {},
31
+ get_billing: {},
31
32
  set_billing_spend_cap: {
32
33
  cap_usd: { value: (_flags, positionals) => capFrom(requirePositional(positionals, 0, "dollars|off")) },
33
34
  reason: { flag: "why" },
@@ -111,7 +112,7 @@ const usd = (n) => `$${Number(n).toFixed(2)}`;
111
112
  * `org billing`: the org's plan on Lago, this period against its allowance and spend cap, whether
112
113
  * it is past due, and its failed payments (get_billing_payments). `org billing cap <dollars|off>`
113
114
  * sets the cap (set_billing_spend_cap, admins); `org billing request` asks the admins for more
114
- * (request_billing_tokens).
115
+ * (request_billing_tokens); `org billing usage` is this period by product and collection (get_billing).
115
116
  */
116
117
  async function orgBilling(ctx) {
117
118
  const { client, flags, io, positionals } = ctx;
@@ -149,6 +150,12 @@ async function orgBilling(ctx) {
149
150
  printResult(io, flags, result, (r) => `Asked ${r.emailedTo} admin${r.emailedTo === 1 ? "" : "s"} for more.`);
150
151
  return 0;
151
152
  }
153
+ if (positionals[0] === "usage") {
154
+ refuseUnknownFlags(ctx, ARGS.get_billing, []);
155
+ const result = await client.callTool("get_billing", argsFromFlags(ARGS.get_billing, flags, []));
156
+ printResult(io, flags, result, billingUsageText);
157
+ return 0;
158
+ }
152
159
  refuseUnknownFlags(ctx, {}, []);
153
160
  const result = await client.callTool("get_billing_payments", {});
154
161
  printResult(io, flags, result, (r) =>
@@ -180,6 +187,29 @@ async function orgBilling(ctx) {
180
187
  return 0;
181
188
  }
182
189
 
190
+ const tokensOrQty = (p) => `${p.billableQuantity}${p.tokens === null ? "" : ` ${p.tokens} tokens`}`;
191
+
192
+ /** `org billing usage` as text: the period, the totals, then each product and each collection. */
193
+ function billingUsageText(r) {
194
+ const lines = [
195
+ `${r.plan ? `Plan: ${r.plan.name}${r.plan.ratio !== 1 ? ` (${r.plan.ratio}x)` : ""}. ` : ""}This period: ${r.period.from.slice(0, 10)} to ${r.period.to.slice(0, 10)}${r.period.basis === "ours" ? " (Lago unreadable: our own rating)" : ""}.`,
196
+ ];
197
+ if (r.tokens)
198
+ lines.push(
199
+ `Tokens: ${r.tokens.used} used of ${r.tokens.allowance} included${r.tokens.complimentary ? ` (+${r.tokens.complimentary} complimentary)` : ""}${r.tokens.arriving ? ` (${r.tokens.arriving} of them still being counted)` : ""}${r.tokens.running ? `, ${r.tokens.running} held by running work` : ""}.`
200
+ );
201
+ else lines.push("Not billed by usage: quantities only.");
202
+ if (r.usd) lines.push(`Plan fee ${usd(r.usd.planFee)}; overage so far ${usd(r.usd.overage)}.`);
203
+ if (r.byProduct.length === 0) return [...lines, "No usage this period."].join("\n");
204
+ lines.push("", "By product:", ...r.byProduct.map((p) => ` ${p.label} ${tokensOrQty(p)}`));
205
+ lines.push("", "By collection:");
206
+ for (const c of r.byCollection) {
207
+ lines.push(` ${c.name}${c.tokens === null ? "" : ` ${c.tokens} tokens`}`);
208
+ for (const p of c.products) lines.push(` ${p.label} ${tokensOrQty(p)}`);
209
+ }
210
+ return lines.join("\n");
211
+ }
212
+
183
213
  /** `org fields [section]`: what each setting in the bundle means (describe_org_config_fields). */
184
214
  async function orgFields(ctx) {
185
215
  const { client, flags, io, positionals } = ctx;
@@ -4,6 +4,7 @@
4
4
  // site tree <host> [path] → get_site_tree site page <host> <path> → get_site_page
5
5
  // site builds <host> → list_build_suggestions (--limit n)
6
6
  // site research-builds <host> → research_builds (Build's suggestion pass: propose, check, Jev, store)
7
+ // site research-fixes <host> → research_fixes (Fix's suggestion pass: exact edits on pages that exist)
7
8
  // site metrics <host> [path] → get_site_metrics (--window 7d|28d|90d, --page <path>…, --no-compare)
8
9
  // site observations <host> → list_site_observations site observe <host> → observe_site
9
10
  // site dismiss <host> <id> → dismiss_site_observation
@@ -87,6 +88,9 @@ const ARGS = {
87
88
  research_builds: {
88
89
  host: { positional: 0, required: true, label: "host" },
89
90
  },
91
+ research_fixes: {
92
+ host: { positional: 0, required: true, label: "host" },
93
+ },
90
94
  get_site_metrics: {
91
95
  host: { positional: 0, required: true, label: "host" },
92
96
  path: { positional: 1, label: "path" },
@@ -210,6 +214,8 @@ export async function cmdSite(ctx) {
210
214
  return siteTree(rest);
211
215
  case "research-builds":
212
216
  return siteResearchBuilds(rest);
217
+ case "research-fixes":
218
+ return siteResearchFixes(rest);
213
219
  case "builds":
214
220
  return siteBuilds(rest);
215
221
  case "metrics":
@@ -439,6 +445,23 @@ async function siteResearchBuilds(ctx) {
439
445
  return 0;
440
446
  }
441
447
 
448
+ async function siteResearchFixes(ctx) {
449
+ const { client, flags, io } = ctx;
450
+ refuseUnknownFlags(ctx, ARGS.research_fixes);
451
+ const result = await client.callTool("research_fixes", argsFromFlags(ARGS.research_fixes, flags, ctx.positionals));
452
+ printResult(io, flags, result, (r) => {
453
+ if (!r.ran) return `Not run (${r.reason ?? "fresh"}); the stored fixes are on the site's Fix list.`;
454
+ const lines = r.suggestions.map(
455
+ (s) => `KEPT ${s.page}: ${s.summary}\n now: ${s.current}\n after: ${s.change}\n ${s.id}`
456
+ );
457
+ for (const x of r.rejected) lines.push(`DROPPED ${x.page}: ${x.summary} (${x.reason})`);
458
+ if (r.pass?.resolved) lines.push(`Resolved ${r.pass.resolved} open fix(es) whose page changed.`);
459
+ if (!r.suggestions.length) lines.push("Nothing passed: zero is a valid result.");
460
+ return lines.join("\n");
461
+ });
462
+ return 0;
463
+ }
464
+
442
465
  async function siteMetrics(ctx) {
443
466
  const { client, flags, io } = ctx;
444
467
  refuseUnknownFlags(ctx, ARGS.get_site_metrics);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.147.0",
3
+ "version": "0.148.1",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",