@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 +3 -1
- package/lib/commands/models.mjs +45 -0
- package/package.json +1 -1
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.
|
|
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]
|
package/lib/commands/models.mjs
CHANGED
|
@@ -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"));
|