@letterstory/cli 0.177.34 → 0.177.36

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.34";
91
+ export const VERSION = "0.177.36";
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;
@@ -2092,6 +2092,8 @@ Models (your own writing models, each trained on writing you give it so it write
2092
2092
  models create "<name>" [--budget <usd>] [--description <d>]
2093
2093
  Budget: the most one training run may cost (default $100)
2094
2094
  models show <id> Its links, its training and what it cost
2095
+ 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
2095
2097
  models add <id> <url>… [--train] Links to pieces of your writing; --train starts training
2096
2098
  models find <id> "<the writing to find>" [--max <n>] Letter finds the links and proposes them
2097
2099
  models confirm <id> [<link-id>…] [--keep-rest] [--no-train]
@@ -2100,7 +2102,7 @@ Models (your own writing models, each trained on writing you give it so it write
2100
2102
  models train <id> [--retry] Train the next version, or try a stopped one again
2101
2103
  models stop <id> Stop what is training; what it spent is charged
2102
2104
  models rewrite <id> "<source text>" Rewrite supplied prose in its trained voice
2103
- models compare <id> "<source text>" Compare the model with Claude and GPT using examples
2105
+ models compare <id> "<source text>" Compare your model, Astra and Claude + Skill
2104
2106
  models write <id> "<what to write>" [--words <n>] Legacy ask-based generation
2105
2107
  models update <id> [--name <n>] [--budget <usd>] [--description <d>]
2106
2108
  models archive <id> | models restore <id>
@@ -8,6 +8,7 @@
8
8
  // letterstory models create "<name>" [--budget <usd>] [--description <d>]
9
9
  // letterstory models show <id>
10
10
  // letterstory models add <id> <url>… [--train]
11
+ // letterstory models upload <id> <file-or-folder>…
11
12
  // letterstory models material <id> "<material or request>"
12
13
  // letterstory models find <id> "<the writing to find>" [--max <n>]
13
14
  // letterstory models confirm <id> [<link-id>…] [--keep-rest] [--no-train] no link ids: all of them
@@ -18,6 +19,8 @@
18
19
  // letterstory models update <id> [--name <n>] [--budget <usd>] [--description <d>]
19
20
  // letterstory models archive <id> | models restore <id>
20
21
 
22
+ import { readFileSync, readdirSync, statSync } from "node:fs";
23
+ import { join, basename } from "node:path";
21
24
  import { CliError } from "../client.mjs";
22
25
  import { argsFromFlags, flagStr, printResult, refuseUnknownFlags } from "./shared.mjs";
23
26
 
@@ -47,6 +50,10 @@ export const ARGS = {
47
50
  urls: { value: rest(1) },
48
51
  train: { flag: "train", type: "bool" },
49
52
  },
53
+ import_writing_model_files: {
54
+ model_id: modelId,
55
+ files: { internal: "models upload <id> <file-or-folder>…; 3 MB batches" },
56
+ },
50
57
  ingest_writing_model_material: {
51
58
  model_id: modelId,
52
59
  text: { value: (flags, positionals) => positionals.slice(1).join(" ") },
@@ -131,6 +138,44 @@ export async function cmdModels(ctx) {
131
138
  }
132
139
  case "show":
133
140
  return modelShow(next, await call(next, "get_writing_model"));
141
+ case "upload": {
142
+ refuseUnknownFlags(next, ARGS.import_writing_model_files, []);
143
+ const [id, ...paths] = next.positionals;
144
+ if (!id || !paths.length) throw new CliError("Give files: models upload <id> <file-or-folder>…");
145
+ const uploads = [];
146
+ function visit(path, name) {
147
+ const stat = statSync(path);
148
+ if (stat.isDirectory()) {
149
+ for (const entry of readdirSync(path, { withFileTypes: true })) {
150
+ if (entry.isSymbolicLink()) continue;
151
+ visit(join(path, entry.name), `${name}/${entry.name}`);
152
+ }
153
+ } else uploads.push({ path, filename: name, size: stat.size });
154
+ }
155
+ for (const path of paths) visit(path, basename(path));
156
+ let result;
157
+ const issues = [];
158
+ let added = 0;
159
+ while (uploads.length) {
160
+ const batch = [];
161
+ let bytes = 0;
162
+ for (const file of uploads) {
163
+ if (file.size > 3_000_000)
164
+ throw new CliError(`${file.filename}: compress as ZIP under 3 MB and retry`);
165
+ if (batch.length >= 50 || bytes + file.size > 3_000_000) break;
166
+ bytes += file.size;
167
+ batch.push({ filename: file.filename, content_base64: readFileSync(file.path).toString("base64") });
168
+ }
169
+ result = await next.client.callTool("import_writing_model_files", { model_id: id, files: batch });
170
+ added += result.added;
171
+ issues.push(...result.issues);
172
+ uploads.splice(0, batch.length);
173
+ }
174
+ printResult(next.io, next.flags, { ...result, added, issues }, (r) =>
175
+ [`${r.added} sources added`, ...r.issues.map((i) => `${i.filename}: ${i.message}`)].join("\n")
176
+ );
177
+ return issues.length ? 1 : 0;
178
+ }
134
179
  case "material": {
135
180
  refuseUnknownFlags(next, ARGS.ingest_writing_model_material, []);
136
181
  return modelShow(next, await call(next, "ingest_writing_model_material"));
@@ -201,8 +246,11 @@ export async function cmdModels(ctx) {
201
246
  printResult(next.io, next.flags, result, (r) =>
202
247
  sub === "rewrite"
203
248
  ? r.text
204
- : Object.entries(r.outputs)
205
- .map(([name, out]) => `${name}:\n${out.text ?? out.error}`)
249
+ : ["custom", "astra", "claude"]
250
+ .map(
251
+ (name) =>
252
+ `${name}:\n${r.outputs[name]?.text ?? r.outputs[name]?.error ?? "Unavailable"}`
253
+ )
206
254
  .join("\n\n")
207
255
  );
208
256
  return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.177.34",
3
+ "version": "0.177.36",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",