@letterstory/cli 0.146.1 → 0.148.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/lib/cli.mjs +5 -3
- package/lib/commands/admin.mjs +19 -1
- package/lib/commands/org.mjs +46 -1
- package/package.json +1 -1
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.
|
|
68
|
+
export const VERSION = "0.148.0";
|
|
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
|
|
@@ -945,7 +945,7 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
945
945
|
| reconcile [--org <org-id>] | forward
|
|
946
946
|
| payments [--org <org-id>] [--open] | payments retry <lago-invoice-id> | payments run [--dry]
|
|
947
947
|
| signups [--status open|card_saved|provisioned|paid|abandoned]
|
|
948
|
-
| cap <org-id> <dollars|off> [--why "…"]
|
|
948
|
+
| cap <org-id> <dollars|off> [--why "…"] | cancel <org-id> --why "…" --yes
|
|
949
949
|
Usage billing on Lago: the plan catalog, an org's
|
|
950
950
|
plan and gifted ratio, Lago's tokens against ours,
|
|
951
951
|
forwarding usage now, failed payments and the
|
|
@@ -1103,10 +1103,12 @@ Seers (event-driven signals -> drafts):
|
|
|
1103
1103
|
org export --split <dir> …as one file per record (for keeping config in git)
|
|
1104
1104
|
org join <dir> [--file <path>] Rebuild the bundle from a --split directory
|
|
1105
1105
|
org integrations Every integration and whether it's connected
|
|
1106
|
-
org billing [cap <dollars|off> [--why "…"] | request [--note "…"]]
|
|
1106
|
+
org billing [cap <dollars|off> [--why "…"] | request [--note "…"] | cancel [--why "…"] --yes]
|
|
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)
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -5213,7 +5213,8 @@ const LAGO_USAGE = `Usage: admin billing lago <command>
|
|
|
5213
5213
|
payments retry <lago-invoice-id> charge one failed invoice again now
|
|
5214
5214
|
payments run [--dry] one pass of the failed-payment loop now (--dry: report only)
|
|
5215
5215
|
signups [--status open|card_saved|provisioned|paid|abandoned] new paying orgs on their way onto Lago
|
|
5216
|
-
cap <org-id> <dollars|off> [--why "…"] set an account's monthly spend cap on overage (off: no cap)
|
|
5216
|
+
cap <org-id> <dollars|off> [--why "…"] set an account's monthly spend cap on overage (off: no cap)
|
|
5217
|
+
cancel <org-id> --why "…" --yes end an account's Lago plan now and refund its first invoice`;
|
|
5217
5218
|
|
|
5218
5219
|
/** admin billing lago … — staff operations on Lago (docs/billing/lago-integration.md). */
|
|
5219
5220
|
async function cmdAdminBilling(ctx) {
|
|
@@ -5336,6 +5337,23 @@ async function cmdAdminBilling(ctx) {
|
|
|
5336
5337
|
);
|
|
5337
5338
|
return 0;
|
|
5338
5339
|
}
|
|
5340
|
+
case "cancel": {
|
|
5341
|
+
const orgId = positionals[2];
|
|
5342
|
+
if (!orgId || !flagStr(flags.why)) throw new CliError(LAGO_USAGE);
|
|
5343
|
+
if (!flagBool(flags.yes))
|
|
5344
|
+
throw new CliError(
|
|
5345
|
+
"This ends the plan now and refunds its first invoice. Run again with --yes to do it."
|
|
5346
|
+
);
|
|
5347
|
+
const result = await staffRequest(ctx, "POST", `${LAGO}/cancel`, { orgId, reason: flagStr(flags.why) });
|
|
5348
|
+
printResult(
|
|
5349
|
+
io,
|
|
5350
|
+
flags,
|
|
5351
|
+
result,
|
|
5352
|
+
(r) =>
|
|
5353
|
+
`Cancelled ${orgId}: first invoice ${r.firstInvoice}${r.refundedCents ? `, $${(r.refundedCents / 100).toFixed(2)} refunded` : ""}.`
|
|
5354
|
+
);
|
|
5355
|
+
return 0;
|
|
5356
|
+
}
|
|
5339
5357
|
case "cap": {
|
|
5340
5358
|
const orgId = positionals[2];
|
|
5341
5359
|
const value = positionals[3];
|
package/lib/commands/org.mjs
CHANGED
|
@@ -28,11 +28,13 @@ 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" },
|
|
34
35
|
},
|
|
35
36
|
request_billing_tokens: { note: { flag: "note" } },
|
|
37
|
+
cancel_billing_plan: { reason: { flag: "why" } },
|
|
36
38
|
list_integrations: {},
|
|
37
39
|
get_linear_connection: {},
|
|
38
40
|
list_linear_targets: { team_id: { flag: "team" } },
|
|
@@ -110,7 +112,7 @@ const usd = (n) => `$${Number(n).toFixed(2)}`;
|
|
|
110
112
|
* `org billing`: the org's plan on Lago, this period against its allowance and spend cap, whether
|
|
111
113
|
* it is past due, and its failed payments (get_billing_payments). `org billing cap <dollars|off>`
|
|
112
114
|
* sets the cap (set_billing_spend_cap, admins); `org billing request` asks the admins for more
|
|
113
|
-
* (request_billing_tokens).
|
|
115
|
+
* (request_billing_tokens); `org billing usage` is this period by product and collection (get_billing).
|
|
114
116
|
*/
|
|
115
117
|
async function orgBilling(ctx) {
|
|
116
118
|
const { client, flags, io, positionals } = ctx;
|
|
@@ -125,6 +127,20 @@ async function orgBilling(ctx) {
|
|
|
125
127
|
);
|
|
126
128
|
return 0;
|
|
127
129
|
}
|
|
130
|
+
if (positionals[0] === "cancel") {
|
|
131
|
+
refuseUnknownFlags(ctx, ARGS.cancel_billing_plan, ["yes"]);
|
|
132
|
+
if (!flagBool(flags.yes))
|
|
133
|
+
throw new CliError("This ends the plan now and refunds its first invoice. Run again with --yes to do it.");
|
|
134
|
+
const result = await client.callTool("cancel_billing_plan", argsFromFlags(ARGS.cancel_billing_plan, flags, []));
|
|
135
|
+
printResult(io, flags, result, (r) =>
|
|
136
|
+
r.firstInvoice === "refunded"
|
|
137
|
+
? `Plan cancelled; $${(r.refundedCents / 100).toFixed(2)} refunded to the card.`
|
|
138
|
+
: r.firstInvoice === "voided"
|
|
139
|
+
? "Plan cancelled; its unpaid first invoice is voided."
|
|
140
|
+
: "Plan cancelled."
|
|
141
|
+
);
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
128
144
|
if (positionals[0] === "request") {
|
|
129
145
|
refuseUnknownFlags(ctx, ARGS.request_billing_tokens, []);
|
|
130
146
|
const result = await client.callTool(
|
|
@@ -134,6 +150,12 @@ async function orgBilling(ctx) {
|
|
|
134
150
|
printResult(io, flags, result, (r) => `Asked ${r.emailedTo} admin${r.emailedTo === 1 ? "" : "s"} for more.`);
|
|
135
151
|
return 0;
|
|
136
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
|
+
}
|
|
137
159
|
refuseUnknownFlags(ctx, {}, []);
|
|
138
160
|
const result = await client.callTool("get_billing_payments", {});
|
|
139
161
|
printResult(io, flags, result, (r) =>
|
|
@@ -165,6 +187,29 @@ async function orgBilling(ctx) {
|
|
|
165
187
|
return 0;
|
|
166
188
|
}
|
|
167
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
|
+
|
|
168
213
|
/** `org fields [section]`: what each setting in the bundle means (describe_org_config_fields). */
|
|
169
214
|
async function orgFields(ctx) {
|
|
170
215
|
const { client, flags, io, positionals } = ctx;
|