@letterstory/cli 0.177.37 → 0.177.39

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
@@ -88,7 +88,7 @@ import {
88
88
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
89
89
 
90
90
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
91
- export const VERSION = "0.177.37";
91
+ export const VERSION = "0.177.39";
92
92
 
93
93
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
94
94
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -97,6 +97,8 @@ export const EXIT_PAYMENT_REQUIRED = 3;
97
97
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
98
98
  // `negate` spelling) in a command's ARGS map must be listed; boolean-flags.test.ts enforces it.
99
99
  export const BOOLEAN_FLAGS = new Set([
100
+ // Models material: retain a request for attachments before preparing it.
101
+ "stage-only",
100
102
  // visits export: only the people or companies who fit the ICP, or only those who don't.
101
103
  "icp",
102
104
  "not-icp",
@@ -2093,7 +2095,13 @@ Models (your own writing models, each trained on writing you give it so it write
2093
2095
  Budget: the most one training run may cost (default $100)
2094
2096
  models show <id> Its links, its training and what it cost
2095
2097
  models upload <id> <file-or-folder>… Import files, folders or ZIPs without training
2096
- models material <id> "<material or request>" Paste material or find original sources
2098
+ models material <id> "<material or request>" [--stage-only] [--request <id>] [--from-request <id>]
2099
+ Prepare material; --stage-only creates a request for uploads
2100
+ models material-show <id> <request-id> [--selection <id>] [--offset <n>]
2101
+ Read progress and selected original text
2102
+ models material-review <id> <request-id> [<selection-id>…] [--cancel]
2103
+ Accept this request, or cancel it; never trains
2104
+ models material-history <id> [--offset <n>] Earlier material requests
2097
2105
  models add <id> <url>… [--train] Links to pieces of your writing; --train starts training
2098
2106
  models find <id> "<the writing to find>" [--max <n>] Letter finds the links and proposes them
2099
2107
  models confirm <id> [<link-id>…] [--keep-rest] [--no-train]
@@ -6355,7 +6355,9 @@ const FREE_USAGE = `Usage: admin billing free exposure [--json] wh
6355
6355
  admin billing free grant <org-id|name> <tokens> --why "…" [--key k] give an org free tokens (a Lago org: a one-off wallet); the same key is one grant
6356
6356
  admin billing free grants [--org <org-id>] [--limit n] staff grants, newest first, live and revoked
6357
6357
  admin billing free revoke <org-id> <ref> --why "…" take a staff grant back (ref from the list: grant:<id> or wallet:staff_…)
6358
- admin billing free preflight [--json] who the paywall stops the moment it is enforced, and whether it is ready`;
6358
+ admin billing free preflight [--json] who the paywall stops the moment it is enforced, and whether it is ready
6359
+ admin billing free pool <org-id|name>... --why "…" link orgs on free tokens so they spend from one balance
6360
+ admin billing free unpool <org-id|name> --why "…" take an org out of its shared balance`;
6359
6361
 
6360
6362
  const EXPOSURE_WORDS = {
6361
6363
  needs_legacy: "pay today: need a legacy mark (admin billing legacy mark --all)",
@@ -6457,6 +6459,28 @@ async function cmdAdminBillingFree(ctx) {
6457
6459
  });
6458
6460
  return 0;
6459
6461
  }
6462
+ if (positionals[1] === "pool" || positionals[1] === "unpool") {
6463
+ const pool = positionals[1] === "pool";
6464
+ const args = positionals.slice(2);
6465
+ if (pool ? args.length < 2 : args.length !== 1) throw new CliError(FREE_USAGE);
6466
+ const why = flagStr(flags.why);
6467
+ if (!why) throw new CliError(`Say why (--why "…"): it is kept with the change.\n${FREE_USAGE}`);
6468
+ const orgIds = [];
6469
+ for (const a of args) orgIds.push((await resolveBillingOrg(ctx, a)).orgId);
6470
+ const r = pool
6471
+ ? await staffPost(ctx, "/api/superadmin/billing/token-pools", { orgIds, why })
6472
+ : await staffRequest(ctx, "DELETE", "/api/superadmin/billing/token-pools", { orgId: orgIds[0], why });
6473
+ printResult(io, flags, r, (x) =>
6474
+ pool
6475
+ ? `Sharing one token balance (${x.poolId}):\n${x.orgs.map((o) => ` ${o.name} ${o.orgId}`).join("\n")}`
6476
+ : !x.changed
6477
+ ? `${orgIds[0]} is in no pool; nothing changed.`
6478
+ : x.left.length
6479
+ ? `Taken out. Still sharing: ${x.left.map((o) => o.name).join(", ")}.`
6480
+ : "Taken out. The pool had one org left, so it ended."
6481
+ );
6482
+ return 0;
6483
+ }
6460
6484
  if (positionals[1] === "grant") {
6461
6485
  const [orgArg, tokensArg] = [positionals[2], positionals[3]];
6462
6486
  const tokens = Number(String(tokensArg ?? "").replace(/[,_]/g, ""));
@@ -52,13 +52,31 @@ export const ARGS = {
52
52
  },
53
53
  import_writing_model_files: {
54
54
  model_id: modelId,
55
+ request_id: "request",
55
56
  files: { internal: "models upload <id> <file-or-folder>…; 3 MB batches" },
56
57
  },
57
58
  ingest_writing_model_material: {
58
59
  model_id: modelId,
60
+ request_id: "request",
61
+ from_request_id: "from-request",
62
+ stage: { flag: "stage-only", type: "bool" },
59
63
  text: { value: (flags, positionals) => positionals.slice(1).join(" ") },
60
64
  documents: { internal: "extract_document then generic mcp call" },
61
65
  },
66
+ list_writing_model_material: { model_id: modelId, offset: { flag: "offset", type: "num" } },
67
+ get_writing_model_material: {
68
+ model_id: modelId,
69
+ request_id: { positional: 1, required: true, label: "request-id" },
70
+ offset: { flag: "offset", type: "num" },
71
+ selection_id: "selection",
72
+ },
73
+ review_writing_model_material: {
74
+ model_id: modelId,
75
+ request_id: { positional: 1, required: true, label: "request-id" },
76
+ action: { value: (flags) => (flags.cancel ? "cancel" : "accept") },
77
+ accept: { value: (flags, p) => rest(2)(flags, p) ?? "all" },
78
+ exclude: { internal: "generic mcp call" },
79
+ },
62
80
  add_writing_model_texts: {
63
81
  model_id: modelId,
64
82
  documents: {
@@ -166,7 +184,11 @@ export async function cmdModels(ctx) {
166
184
  bytes += file.size;
167
185
  batch.push({ filename: file.filename, content_base64: readFileSync(file.path).toString("base64") });
168
186
  }
169
- result = await next.client.callTool("import_writing_model_files", { model_id: id, files: batch });
187
+ result = await next.client.callTool("import_writing_model_files", {
188
+ model_id: id,
189
+ files: batch,
190
+ ...(flagStr(next.flags.request) ? { request_id: flagStr(next.flags.request) } : {}),
191
+ });
170
192
  added += result.added;
171
193
  issues.push(...result.issues);
172
194
  uploads.splice(0, batch.length);
@@ -176,6 +198,17 @@ export async function cmdModels(ctx) {
176
198
  );
177
199
  return issues.length ? 1 : 0;
178
200
  }
201
+ case "material-review":
202
+ printResult(next.io, next.flags, await call(next, "review_writing_model_material"), (r) =>
203
+ JSON.stringify(r)
204
+ );
205
+ return 0;
206
+ case "material-history":
207
+ printResult(next.io, next.flags, await call(next, "list_writing_model_material"), (r) => JSON.stringify(r));
208
+ return 0;
209
+ case "material-show":
210
+ printResult(next.io, next.flags, await call(next, "get_writing_model_material"), (r) => JSON.stringify(r));
211
+ return 0;
179
212
  case "material": {
180
213
  refuseUnknownFlags(next, ARGS.ingest_writing_model_material, []);
181
214
  return modelShow(next, await call(next, "ingest_writing_model_material"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.177.37",
3
+ "version": "0.177.39",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",