@letterstory/cli 0.177.34 → 0.177.35

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.35";
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]
@@ -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"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.177.34",
3
+ "version": "0.177.35",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",