apple-llm 0.1.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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/chunk-FQTRQ3KP.js +1590 -0
- package/dist/cli.cjs +2027 -0
- package/dist/cli.js +456 -0
- package/dist/index.cjs +1667 -0
- package/dist/index.d.cts +690 -0
- package/dist/index.d.ts +690 -0
- package/dist/index.js +80 -0
- package/package.json +61 -0
- package/swift/helper.swift +900 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AppleLLM,
|
|
4
|
+
AppleLLMError,
|
|
5
|
+
captureScreenshot,
|
|
6
|
+
installCloudShortcut,
|
|
7
|
+
parseImageFlag,
|
|
8
|
+
probe
|
|
9
|
+
} from "./chunk-FQTRQ3KP.js";
|
|
10
|
+
|
|
11
|
+
// src/cli.ts
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
function parseArgs(argv) {
|
|
14
|
+
const flags = { positional: [], image: [], document: [], tool: [] };
|
|
15
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
16
|
+
const arg = argv[i];
|
|
17
|
+
const take = (flag) => {
|
|
18
|
+
const v = argv[++i];
|
|
19
|
+
if (v === void 0 || v.startsWith("--")) {
|
|
20
|
+
throw new Error(`${flag} needs a value.
|
|
21
|
+
|
|
22
|
+
${USAGE}`);
|
|
23
|
+
}
|
|
24
|
+
return v;
|
|
25
|
+
};
|
|
26
|
+
switch (arg) {
|
|
27
|
+
case "--tier":
|
|
28
|
+
flags.tier = take("--tier");
|
|
29
|
+
break;
|
|
30
|
+
case "--system":
|
|
31
|
+
flags.system = take("--system");
|
|
32
|
+
break;
|
|
33
|
+
case "--schema":
|
|
34
|
+
flags.schema = take("--schema");
|
|
35
|
+
break;
|
|
36
|
+
case "--temperature":
|
|
37
|
+
flags.temperature = take("--temperature");
|
|
38
|
+
break;
|
|
39
|
+
case "--max-tokens":
|
|
40
|
+
flags.maxTokens = take("--max-tokens");
|
|
41
|
+
break;
|
|
42
|
+
case "--web-search":
|
|
43
|
+
flags.webSearch = true;
|
|
44
|
+
break;
|
|
45
|
+
case "--force":
|
|
46
|
+
flags.force = true;
|
|
47
|
+
break;
|
|
48
|
+
case "--json":
|
|
49
|
+
flags.json = true;
|
|
50
|
+
break;
|
|
51
|
+
case "--image":
|
|
52
|
+
flags.image.push(take("--image"));
|
|
53
|
+
break;
|
|
54
|
+
case "--document":
|
|
55
|
+
flags.document.push(take("--document"));
|
|
56
|
+
break;
|
|
57
|
+
case "--tool":
|
|
58
|
+
flags.tool.push(take("--tool"));
|
|
59
|
+
break;
|
|
60
|
+
case "--session":
|
|
61
|
+
flags.session = take("--session");
|
|
62
|
+
break;
|
|
63
|
+
case "--stream":
|
|
64
|
+
flags.stream = true;
|
|
65
|
+
break;
|
|
66
|
+
case "--mode":
|
|
67
|
+
flags.mode = take("--mode");
|
|
68
|
+
break;
|
|
69
|
+
case "--use-case":
|
|
70
|
+
flags.useCase = take("--use-case");
|
|
71
|
+
break;
|
|
72
|
+
case "--guardrails":
|
|
73
|
+
flags.guardrails = take("--guardrails");
|
|
74
|
+
break;
|
|
75
|
+
case "--seed":
|
|
76
|
+
flags.seed = take("--seed");
|
|
77
|
+
break;
|
|
78
|
+
case "--greedy":
|
|
79
|
+
flags.greedy = true;
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
flags.positional.push(arg);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return flags;
|
|
86
|
+
}
|
|
87
|
+
async function readStdin() {
|
|
88
|
+
const chunks = [];
|
|
89
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
90
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
91
|
+
}
|
|
92
|
+
var KNOWN_TOOLS = /* @__PURE__ */ new Set(["ocr", "barcode", "spotlight"]);
|
|
93
|
+
function parseTools(values, usageError) {
|
|
94
|
+
if (values.length === 0) return void 0;
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const value of values) {
|
|
97
|
+
for (const part of value.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
98
|
+
if (!KNOWN_TOOLS.has(part)) {
|
|
99
|
+
throw new Error(`--tool must be ocr, barcode, or spotlight (got "${part}").`);
|
|
100
|
+
}
|
|
101
|
+
if (!out.includes(part)) out.push(part);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
var USAGE = `apple-llm \u2014 Apple's on-device and Private Cloud Compute models
|
|
107
|
+
|
|
108
|
+
apple-llm probe what this machine can do
|
|
109
|
+
apple-llm setup-cloud [--web-search] install the Shortcut the cloud tier needs
|
|
110
|
+
apple-llm run [options] <prompt|-> one completion ("-" reads stdin)
|
|
111
|
+
apple-llm count [options] <prompt|-> tokens this prompt costs, before sending
|
|
112
|
+
apple-llm ask-screen [options] <question|-> screenshot, then ask (device tier)
|
|
113
|
+
apple-llm history --session <id> show a conversation's mirrored turns
|
|
114
|
+
apple-llm reset [--session <id>] drop one conversation, or all
|
|
115
|
+
apple-llm rewrite|proofread|summarize|draft <text|-> Write with Siri presets
|
|
116
|
+
|
|
117
|
+
run options:
|
|
118
|
+
--tier device|cloud|auto default: auto
|
|
119
|
+
--system <text> system instructions
|
|
120
|
+
--schema <file.json> ask for JSON matching this JSON Schema
|
|
121
|
+
--temperature <n> default 0.4 (0 degenerates; see the README)
|
|
122
|
+
--max-tokens <n>
|
|
123
|
+
--image <path[::label]> attach an image, optionally labelled (repeatable; macOS 27+)
|
|
124
|
+
--document <path> inline a text document into the prompt (repeatable)
|
|
125
|
+
--tool ocr|barcode|spotlight built-in on-device tool (repeatable)
|
|
126
|
+
--session <id> continue a named conversation across calls
|
|
127
|
+
--stream print partials as they arrive (device text only)
|
|
128
|
+
--use-case contentTagging Apple's tagging-specialised model
|
|
129
|
+
--guardrails permissive relax guardrails for rewriting tasks
|
|
130
|
+
--seed <n> reproducible sampling (top-k, seeded)
|
|
131
|
+
--greedy greedy decoding (deterministic, but degenerates)
|
|
132
|
+
--web-search cloud tier only
|
|
133
|
+
|
|
134
|
+
ask-screen options: --mode interactive|window|fullscreen (default interactive),
|
|
135
|
+
plus --system, --session, --max-tokens.
|
|
136
|
+
`;
|
|
137
|
+
async function main() {
|
|
138
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
139
|
+
let flags;
|
|
140
|
+
try {
|
|
141
|
+
flags = parseArgs(rest);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
144
|
+
`);
|
|
145
|
+
return 2;
|
|
146
|
+
}
|
|
147
|
+
const usageError = (message) => {
|
|
148
|
+
process.stderr.write(`${message}
|
|
149
|
+
|
|
150
|
+
${USAGE}`);
|
|
151
|
+
return 2;
|
|
152
|
+
};
|
|
153
|
+
if (command === void 0 || command === "--help" || command === "-h" || command === "help") {
|
|
154
|
+
process.stdout.write(USAGE);
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
if (command === "probe") {
|
|
158
|
+
const result = await probe((p) => process.stderr.write(` ${p.status}
|
|
159
|
+
`));
|
|
160
|
+
if (flags.json === true) {
|
|
161
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
162
|
+
`);
|
|
163
|
+
return result.device.available || result.cloud.available ? 0 : 1;
|
|
164
|
+
}
|
|
165
|
+
const d = result.device;
|
|
166
|
+
process.stdout.write("on-device\n");
|
|
167
|
+
process.stdout.write(` available ${d.available}
|
|
168
|
+
`);
|
|
169
|
+
if (d.variant !== void 0) process.stdout.write(` variant ${d.variant}
|
|
170
|
+
`);
|
|
171
|
+
if (d.contextSize !== void 0) process.stdout.write(` context ${d.contextSize} tokens
|
|
172
|
+
`);
|
|
173
|
+
if (d.capabilities !== void 0) {
|
|
174
|
+
const on = Object.entries(d.capabilities).filter(([, v]) => v).map(([k]) => k);
|
|
175
|
+
process.stdout.write(` supports ${on.join(", ") || "none reported"}
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
178
|
+
if (d.useCases !== void 0) process.stdout.write(` use cases ${d.useCases.join(", ")}
|
|
179
|
+
`);
|
|
180
|
+
if (d.reason !== void 0) process.stdout.write(` reason ${d.reason}
|
|
181
|
+
`);
|
|
182
|
+
const c = result.cloud;
|
|
183
|
+
process.stdout.write("\ncloud (private cloud compute)\n");
|
|
184
|
+
process.stdout.write(` available ${c.available}
|
|
185
|
+
`);
|
|
186
|
+
process.stdout.write(` installed ${c.installed === true}
|
|
187
|
+
`);
|
|
188
|
+
if (c.contextSize !== void 0) process.stdout.write(` context ${c.contextSize} tokens
|
|
189
|
+
`);
|
|
190
|
+
if (c.quota !== void 0) {
|
|
191
|
+
const approaching = c.quota.approachingLimit === true ? " (approaching limit)" : "";
|
|
192
|
+
process.stdout.write(` quota ${c.quota.status}${approaching}
|
|
193
|
+
`);
|
|
194
|
+
if (c.quota.resetDate !== void 0) process.stdout.write(` resets ${c.quota.resetDate}
|
|
195
|
+
`);
|
|
196
|
+
}
|
|
197
|
+
if (c.reason !== void 0) process.stdout.write(` reason ${c.reason}
|
|
198
|
+
`);
|
|
199
|
+
return d.available || c.available ? 0 : 1;
|
|
200
|
+
}
|
|
201
|
+
if (command === "setup-cloud") {
|
|
202
|
+
await installCloudShortcut((p) => process.stderr.write(` ${p.status}
|
|
203
|
+
`), {
|
|
204
|
+
force: flags.force,
|
|
205
|
+
webSearch: flags.webSearch
|
|
206
|
+
});
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
if (command === "count") {
|
|
210
|
+
const arg = flags.positional[0];
|
|
211
|
+
if (arg === void 0) {
|
|
212
|
+
process.stderr.write('count needs a prompt, or "-" to read stdin.\n');
|
|
213
|
+
return 2;
|
|
214
|
+
}
|
|
215
|
+
const prompt = arg === "-" ? await readStdin() : arg;
|
|
216
|
+
const llm = new AppleLLM({ tier: "device" });
|
|
217
|
+
let tools;
|
|
218
|
+
try {
|
|
219
|
+
tools = parseTools(flags.tool, usageError);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return usageError(err instanceof Error ? err.message : String(err));
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const { tokens, contextSize } = await llm.countTokens(prompt, {
|
|
225
|
+
system: flags.system,
|
|
226
|
+
images: flags.image.length > 0 ? flags.image.map(parseImageFlag) : void 0,
|
|
227
|
+
tools
|
|
228
|
+
});
|
|
229
|
+
const pct = contextSize > 0 ? Math.round(tokens / contextSize * 100) : 0;
|
|
230
|
+
process.stdout.write(`${tokens} tokens of ${contextSize} (${pct}%)
|
|
231
|
+
`);
|
|
232
|
+
return tokens > contextSize ? 1 : 0;
|
|
233
|
+
} finally {
|
|
234
|
+
llm.close();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (command === "history") {
|
|
238
|
+
if (flags.session === void 0) return usageError("history needs --session <id>.");
|
|
239
|
+
const llm = new AppleLLM({ tier: "device" });
|
|
240
|
+
try {
|
|
241
|
+
const { instructions, history } = await llm.history(flags.session);
|
|
242
|
+
if (flags.json === true) {
|
|
243
|
+
process.stdout.write(`${JSON.stringify({ sessionId: flags.session, instructions, history }, null, 2)}
|
|
244
|
+
`);
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
for (const turn of history) {
|
|
248
|
+
process.stdout.write(`${turn.role === "user" ? "you" : "model"}: ${turn.content}
|
|
249
|
+
`);
|
|
250
|
+
}
|
|
251
|
+
return 0;
|
|
252
|
+
} finally {
|
|
253
|
+
llm.close();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (command === "reset") {
|
|
257
|
+
const llm = new AppleLLM({ tier: "device" });
|
|
258
|
+
try {
|
|
259
|
+
await llm.resetSession(flags.session);
|
|
260
|
+
process.stdout.write(flags.session ? `reset ${flags.session}
|
|
261
|
+
` : "reset all sessions\n");
|
|
262
|
+
return 0;
|
|
263
|
+
} finally {
|
|
264
|
+
llm.close();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (command === "rewrite" || command === "proofread" || command === "summarize" || command === "draft") {
|
|
268
|
+
const arg = flags.positional[0];
|
|
269
|
+
if (arg === void 0) {
|
|
270
|
+
process.stderr.write(`${command} needs text, or "-" to read stdin.
|
|
271
|
+
`);
|
|
272
|
+
return 2;
|
|
273
|
+
}
|
|
274
|
+
const text = arg === "-" ? await readStdin() : arg;
|
|
275
|
+
const llm = new AppleLLM({
|
|
276
|
+
tier: "device",
|
|
277
|
+
guardrails: "permissive",
|
|
278
|
+
onProgress: (p) => process.stderr.write(` ${p.status}
|
|
279
|
+
`)
|
|
280
|
+
});
|
|
281
|
+
try {
|
|
282
|
+
const out = command === "rewrite" ? await llm.rewrite(text, { system: flags.system }) : command === "proofread" ? await llm.proofread(text, { system: flags.system }) : command === "summarize" ? await llm.summarize(text, { system: flags.system }) : await llm.draft(text, { system: flags.system });
|
|
283
|
+
process.stdout.write(`${out}
|
|
284
|
+
`);
|
|
285
|
+
return 0;
|
|
286
|
+
} finally {
|
|
287
|
+
llm.close();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (command === "ask-screen") {
|
|
291
|
+
const arg = flags.positional[0];
|
|
292
|
+
if (arg === void 0) {
|
|
293
|
+
process.stderr.write('ask-screen needs a question, or "-" to read stdin.\n');
|
|
294
|
+
return 2;
|
|
295
|
+
}
|
|
296
|
+
if (flags.mode !== void 0 && !["interactive", "window", "fullscreen"].includes(flags.mode)) {
|
|
297
|
+
return usageError(`--mode must be interactive, window, or fullscreen (got "${flags.mode}").`);
|
|
298
|
+
}
|
|
299
|
+
const question = arg === "-" ? await readStdin() : arg;
|
|
300
|
+
const maxTokens = flags.maxTokens === void 0 ? void 0 : Number(flags.maxTokens);
|
|
301
|
+
if (maxTokens !== void 0 && Number.isNaN(maxTokens)) {
|
|
302
|
+
return usageError(`--max-tokens must be a number (got "${flags.maxTokens}").`);
|
|
303
|
+
}
|
|
304
|
+
process.stderr.write(" select a screen region (Esc cancels)\n");
|
|
305
|
+
const shot = await captureScreenshot(
|
|
306
|
+
flags.mode ?? "interactive"
|
|
307
|
+
);
|
|
308
|
+
const llm = new AppleLLM({
|
|
309
|
+
tier: "device",
|
|
310
|
+
maxTokens,
|
|
311
|
+
onProgress: (p) => process.stderr.write(` ${p.status}
|
|
312
|
+
`)
|
|
313
|
+
});
|
|
314
|
+
try {
|
|
315
|
+
const out = await llm.text(question, {
|
|
316
|
+
system: flags.system,
|
|
317
|
+
sessionId: flags.session,
|
|
318
|
+
images: [...flags.image.map(parseImageFlag), shot]
|
|
319
|
+
});
|
|
320
|
+
process.stdout.write(`${out}
|
|
321
|
+
`);
|
|
322
|
+
return 0;
|
|
323
|
+
} finally {
|
|
324
|
+
const { rm } = await import("fs/promises");
|
|
325
|
+
await rm(shot, { force: true }).catch(() => void 0);
|
|
326
|
+
llm.close();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (command === "run") {
|
|
330
|
+
const arg = flags.positional[0];
|
|
331
|
+
if (arg === void 0) {
|
|
332
|
+
process.stderr.write('run needs a prompt, or "-" to read stdin.\n');
|
|
333
|
+
return 2;
|
|
334
|
+
}
|
|
335
|
+
if (flags.tier !== void 0 && flags.tier !== "device" && flags.tier !== "cloud" && flags.tier !== "auto") {
|
|
336
|
+
return usageError(`--tier must be device, cloud, or auto (got "${flags.tier}").`);
|
|
337
|
+
}
|
|
338
|
+
const temperature = flags.temperature === void 0 ? void 0 : Number(flags.temperature);
|
|
339
|
+
if (temperature !== void 0 && Number.isNaN(temperature)) {
|
|
340
|
+
return usageError(`--temperature must be a number (got "${flags.temperature}").`);
|
|
341
|
+
}
|
|
342
|
+
const maxTokens = flags.maxTokens === void 0 ? void 0 : Number(flags.maxTokens);
|
|
343
|
+
if (maxTokens !== void 0 && Number.isNaN(maxTokens)) {
|
|
344
|
+
return usageError(`--max-tokens must be a number (got "${flags.maxTokens}").`);
|
|
345
|
+
}
|
|
346
|
+
const seed = flags.seed === void 0 ? void 0 : Number(flags.seed);
|
|
347
|
+
if (seed !== void 0 && Number.isNaN(seed)) {
|
|
348
|
+
return usageError(`--seed must be a number (got "${flags.seed}").`);
|
|
349
|
+
}
|
|
350
|
+
if (flags.useCase !== void 0 && flags.useCase !== "general" && flags.useCase !== "contentTagging") {
|
|
351
|
+
return usageError(`--use-case must be general or contentTagging (got "${flags.useCase}").`);
|
|
352
|
+
}
|
|
353
|
+
if (flags.guardrails !== void 0 && flags.guardrails !== "default" && flags.guardrails !== "permissive") {
|
|
354
|
+
return usageError(`--guardrails must be default or permissive (got "${flags.guardrails}").`);
|
|
355
|
+
}
|
|
356
|
+
const prompt = arg === "-" ? await readStdin() : arg;
|
|
357
|
+
const tier = flags.tier ?? "auto";
|
|
358
|
+
let tools;
|
|
359
|
+
try {
|
|
360
|
+
tools = parseTools(flags.tool, usageError);
|
|
361
|
+
} catch (err) {
|
|
362
|
+
return usageError(err instanceof Error ? err.message : String(err));
|
|
363
|
+
}
|
|
364
|
+
const sampling = flags.greedy === true ? { mode: "greedy" } : seed !== void 0 ? { mode: "topK", k: 50, seed } : void 0;
|
|
365
|
+
const llm = new AppleLLM({
|
|
366
|
+
tier,
|
|
367
|
+
temperature,
|
|
368
|
+
maxTokens,
|
|
369
|
+
useCase: flags.useCase,
|
|
370
|
+
guardrails: flags.guardrails,
|
|
371
|
+
sampling,
|
|
372
|
+
onProgress: (p) => process.stderr.write(` ${p.status}
|
|
373
|
+
`)
|
|
374
|
+
});
|
|
375
|
+
const images = flags.image.length > 0 ? flags.image.map(parseImageFlag) : void 0;
|
|
376
|
+
const documents = flags.document.length > 0 ? flags.document : void 0;
|
|
377
|
+
try {
|
|
378
|
+
if (flags.schema !== void 0) {
|
|
379
|
+
if (flags.stream === true) {
|
|
380
|
+
return usageError("--stream is text only; schemas need a complete response.");
|
|
381
|
+
}
|
|
382
|
+
let schemaText;
|
|
383
|
+
try {
|
|
384
|
+
schemaText = await readFile(flags.schema, "utf8");
|
|
385
|
+
} catch (err) {
|
|
386
|
+
throw new AppleLLMError(
|
|
387
|
+
`Could not read schema file "${flags.schema}": ${err instanceof Error ? err.message : String(err)}`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
let schema;
|
|
391
|
+
try {
|
|
392
|
+
schema = JSON.parse(schemaText);
|
|
393
|
+
} catch (err) {
|
|
394
|
+
throw new AppleLLMError(
|
|
395
|
+
`Schema file "${flags.schema}" is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const out = await llm.json(prompt, {
|
|
399
|
+
schema,
|
|
400
|
+
system: flags.system,
|
|
401
|
+
webSearch: flags.webSearch,
|
|
402
|
+
images,
|
|
403
|
+
documents,
|
|
404
|
+
sessionId: flags.session,
|
|
405
|
+
tools
|
|
406
|
+
});
|
|
407
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}
|
|
408
|
+
`);
|
|
409
|
+
} else if (flags.stream === true) {
|
|
410
|
+
if (tier === "cloud") {
|
|
411
|
+
return usageError("--stream needs the on-device tier.");
|
|
412
|
+
}
|
|
413
|
+
const out = await llm.stream(prompt, {
|
|
414
|
+
system: flags.system,
|
|
415
|
+
images,
|
|
416
|
+
documents,
|
|
417
|
+
sessionId: flags.session,
|
|
418
|
+
tools,
|
|
419
|
+
onDelta: (delta) => process.stdout.write(delta)
|
|
420
|
+
});
|
|
421
|
+
process.stdout.write("\n");
|
|
422
|
+
void out;
|
|
423
|
+
} else {
|
|
424
|
+
const out = await llm.text(prompt, {
|
|
425
|
+
system: flags.system,
|
|
426
|
+
webSearch: flags.webSearch,
|
|
427
|
+
images,
|
|
428
|
+
documents,
|
|
429
|
+
sessionId: flags.session,
|
|
430
|
+
tools
|
|
431
|
+
});
|
|
432
|
+
process.stdout.write(`${out}
|
|
433
|
+
`);
|
|
434
|
+
}
|
|
435
|
+
} finally {
|
|
436
|
+
llm.close();
|
|
437
|
+
}
|
|
438
|
+
return 0;
|
|
439
|
+
}
|
|
440
|
+
process.stderr.write(`Unknown command "${command}".
|
|
441
|
+
|
|
442
|
+
${USAGE}`);
|
|
443
|
+
return 2;
|
|
444
|
+
}
|
|
445
|
+
main().then((code) => {
|
|
446
|
+
process.exitCode = code;
|
|
447
|
+
}).catch((err) => {
|
|
448
|
+
if (err instanceof AppleLLMError) {
|
|
449
|
+
process.stderr.write(`${err.name}: ${err.message}
|
|
450
|
+
`);
|
|
451
|
+
} else {
|
|
452
|
+
process.stderr.write(`${err instanceof Error ? err.stack ?? err.message : String(err)}
|
|
453
|
+
`);
|
|
454
|
+
}
|
|
455
|
+
process.exitCode = 1;
|
|
456
|
+
});
|