@wilm-ai/wilma-cli 1.5.2 → 1.6.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 +3 -0
- package/dist/index.js +266 -11
- package/package.json +10 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wilm.ai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -60,10 +60,13 @@ wilma grades list [--limit 20] [--student <id|name>] [--all-students] [--json]
|
|
|
60
60
|
```bash
|
|
61
61
|
wilma news list [--limit 20] [--student <id|name>] [--all-students] [--json]
|
|
62
62
|
wilma news read <id> [--student <id|name>] [--json]
|
|
63
|
+
wilma news resource download <news-id> <resource-id> [--student <id|name>] [--output <directory>] [--json]
|
|
63
64
|
wilma messages list [--folder inbox] [--limit 20] [--student <id|name>] [--all-students] [--json]
|
|
64
65
|
wilma messages read <id> [--student <id|name>] [--json]
|
|
65
66
|
```
|
|
66
67
|
|
|
68
|
+
`news read --json` includes a `resources` array with every link in the bulletin. Any resource can be passed to the download command (the resource id also accepts a bare number, e.g. `1` for `resource-1`). Wilma-hosted files download through the authenticated session; external URLs are fetched with an isolated, unauthenticated request — no Wilma credentials are ever sent to external hosts. The result `status` reports what actually happened: `downloaded` (use the returned `path`), or `not_a_file` when the URL answered with a web page instead of a file (for example a sharing link that requires signing in — open it in a browser instead). `--output` defaults to the current directory; existing files are never overwritten.
|
|
69
|
+
|
|
67
70
|
### Attendance / lesson notes
|
|
68
71
|
```bash
|
|
69
72
|
wilma attendance list [--date YYYY-MM-DD] [--student <id|name>] [--all-students] [--json]
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { emitKeypressEvents } from "node:readline";
|
|
3
3
|
import { select, input, password } from "@inquirer/prompts";
|
|
4
4
|
import { createHmac } from "node:crypto";
|
|
5
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
5
|
+
import { readFile, writeFile, mkdir, open, rm } from "node:fs/promises";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import { homedir } from "node:os";
|
|
7
8
|
import { dirname, resolve } from "node:path";
|
|
8
9
|
import { WilmaClient, MfaRequiredError, listTenants, } from "@wilm-ai/wilma-client";
|
|
9
10
|
import { clearConfig, getConfigPath, loadConfig, obfuscateSecret, revealSecret, saveConfig, } from "./config.js";
|
|
@@ -373,7 +374,7 @@ async function chooseStudentFromProfile(stored, baseProfile, onMfa) {
|
|
|
373
374
|
return students.find((s) => s.studentNumber === selected) ?? null;
|
|
374
375
|
}
|
|
375
376
|
async function handleCommand(args, config) {
|
|
376
|
-
const { command, subcommand, flags } = parseArgs(args);
|
|
377
|
+
const { command, subcommand, resourceAction, flags } = parseArgs(args);
|
|
377
378
|
if (command === "config" && subcommand === "clear") {
|
|
378
379
|
await clearConfig();
|
|
379
380
|
console.log(`Cleared config at ${getConfigPath()}`);
|
|
@@ -405,6 +406,38 @@ async function handleCommand(args, config) {
|
|
|
405
406
|
return;
|
|
406
407
|
}
|
|
407
408
|
if (command === "news") {
|
|
409
|
+
if (subcommand === "resource") {
|
|
410
|
+
if (resourceAction !== "download") {
|
|
411
|
+
throw new Error('Expected "wilma news resource download <news-id> <resource-id>"');
|
|
412
|
+
}
|
|
413
|
+
const newsId = parseReadId(flags.id, "news");
|
|
414
|
+
const resourceId = normalizeResourceId(flags.resourceId);
|
|
415
|
+
if (!resourceId) {
|
|
416
|
+
throw new Error("Missing news resource id (for example, resource-1 or just 1)");
|
|
417
|
+
}
|
|
418
|
+
if (!flags.student) {
|
|
419
|
+
const students = await getStudentsForCommand(profile, config);
|
|
420
|
+
if (students.length > 1) {
|
|
421
|
+
console.error("Multiple students found. Use --student <id> to specify which one:");
|
|
422
|
+
students.forEach((s) => console.error(` ${s.studentNumber} ${s.name}`));
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const studentInfo = await resolveStudentForFlags(profile, config, flags.student);
|
|
427
|
+
if (!studentInfo && !profile.studentNumber) {
|
|
428
|
+
await printStudentSelectionHelp(profile, config);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const perStudentClient = await WilmaClient.login({
|
|
432
|
+
...profile,
|
|
433
|
+
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
434
|
+
}, mfaCallback);
|
|
435
|
+
await outputNewsResourceDownload(perStudentClient, newsId, resourceId, {
|
|
436
|
+
output: flags.output,
|
|
437
|
+
json: flags.json,
|
|
438
|
+
});
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
408
441
|
if (subcommand === "read" && flags.id) {
|
|
409
442
|
const newsId = parseReadId(flags.id, "news");
|
|
410
443
|
if (!flags.student) {
|
|
@@ -424,7 +457,9 @@ async function handleCommand(args, config) {
|
|
|
424
457
|
...profile,
|
|
425
458
|
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
426
459
|
}, mfaCallback);
|
|
427
|
-
await outputNewsItem(perStudentClient, newsId, flags.json
|
|
460
|
+
await outputNewsItem(perStudentClient, newsId, flags.json, {
|
|
461
|
+
studentNumber: studentInfo?.studentNumber ?? profile.studentNumber,
|
|
462
|
+
});
|
|
428
463
|
return;
|
|
429
464
|
}
|
|
430
465
|
if (flags.allStudents) {
|
|
@@ -635,6 +670,7 @@ function printUsage() {
|
|
|
635
670
|
console.log(" wilma kids list [--json]");
|
|
636
671
|
console.log(" wilma news list [--limit 20] [--student <id|name>] [--all-students] [--json]");
|
|
637
672
|
console.log(" wilma news read <id> [--student <id|name>] [--json]");
|
|
673
|
+
console.log(" wilma news resource download <news-id> <resource-id> [--student <id|name>] [--output <directory>] [--json]");
|
|
638
674
|
console.log(" wilma messages list [--folder inbox] [--limit 20] [--student <id|name>] [--all-students] [--json]");
|
|
639
675
|
console.log(" wilma messages read <id> [--student <id|name>] [--json]");
|
|
640
676
|
console.log(" wilma attendance list [--date YYYY-MM-DD] [--student <id|name>] [--all-students] [--json]");
|
|
@@ -674,6 +710,7 @@ function parseArgs(args) {
|
|
|
674
710
|
const subcommand = rawSubcommand?.startsWith("--") ? undefined : rawSubcommand;
|
|
675
711
|
const rest = rawSubcommand?.startsWith("--") ? [rawSubcommand, ...rawRest] : rawRest;
|
|
676
712
|
const flags = {};
|
|
713
|
+
const positionals = [];
|
|
677
714
|
let i = 0;
|
|
678
715
|
while (i < rest.length) {
|
|
679
716
|
const arg = rest[i];
|
|
@@ -738,14 +775,27 @@ function parseArgs(args) {
|
|
|
738
775
|
i += 2;
|
|
739
776
|
continue;
|
|
740
777
|
}
|
|
741
|
-
if (
|
|
742
|
-
flags.
|
|
743
|
-
i +=
|
|
778
|
+
if (arg === "--output") {
|
|
779
|
+
flags.output = rest[i + 1];
|
|
780
|
+
i += 2;
|
|
744
781
|
continue;
|
|
745
782
|
}
|
|
783
|
+
if (!arg.startsWith("--")) {
|
|
784
|
+
positionals.push(arg);
|
|
785
|
+
}
|
|
746
786
|
i += 1;
|
|
747
787
|
}
|
|
748
|
-
|
|
788
|
+
const resourceAction = command === "news" && subcommand === "resource"
|
|
789
|
+
? positionals[0]
|
|
790
|
+
: undefined;
|
|
791
|
+
if (resourceAction) {
|
|
792
|
+
flags.id = positionals[1];
|
|
793
|
+
flags.resourceId = positionals[2];
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
flags.id = positionals[0];
|
|
797
|
+
}
|
|
798
|
+
return { command, subcommand, resourceAction, flags };
|
|
749
799
|
}
|
|
750
800
|
function parseReadId(raw, entity) {
|
|
751
801
|
if (!raw) {
|
|
@@ -952,11 +1002,11 @@ async function outputNews(client, opts) {
|
|
|
952
1002
|
console.log(`- ${prefix}${date} ${compactText(item.title)} (id:${item.wilmaId})`.trim());
|
|
953
1003
|
});
|
|
954
1004
|
}
|
|
955
|
-
async function outputNewsItem(client, id, json) {
|
|
1005
|
+
async function outputNewsItem(client, id, json, opts) {
|
|
956
1006
|
const item = await client.news.get(id);
|
|
957
1007
|
if (json) {
|
|
958
1008
|
console.log(JSON.stringify(item, null, 2));
|
|
959
|
-
return;
|
|
1009
|
+
return item;
|
|
960
1010
|
}
|
|
961
1011
|
console.log(`\n${item.title}`);
|
|
962
1012
|
if (item.subtitle)
|
|
@@ -965,6 +1015,172 @@ async function outputNewsItem(client, id, json) {
|
|
|
965
1015
|
console.log(item.published.toISOString());
|
|
966
1016
|
if (item.content)
|
|
967
1017
|
console.log(`\n${formatContent(item.content)}`);
|
|
1018
|
+
if (item.resources?.length) {
|
|
1019
|
+
console.log(`\nResources (${item.resources.length})`);
|
|
1020
|
+
for (const resource of item.resources) {
|
|
1021
|
+
const origin = resource.authContext === "wilma" ? "Wilma" : new URL(resource.url).host;
|
|
1022
|
+
console.log(`\n[${resource.id}] ${resource.label} (${origin})`);
|
|
1023
|
+
console.log(` ${resource.url}`);
|
|
1024
|
+
}
|
|
1025
|
+
if (!opts?.suppressDownloadHint) {
|
|
1026
|
+
const student = opts?.studentNumber ?? "<id|name>";
|
|
1027
|
+
console.log(`\nDownload any resource with:\n wilma news resource download ${item.wilmaId} <resource-id> --student ${student} --output <directory>`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return item;
|
|
1031
|
+
}
|
|
1032
|
+
const MAX_NEWS_RESOURCE_BYTES = 50 * 1024 * 1024;
|
|
1033
|
+
async function outputNewsResourceDownload(client, newsId, resourceId, opts) {
|
|
1034
|
+
const item = await client.news.get(newsId);
|
|
1035
|
+
const resource = item.resources?.find((candidate) => candidate.id === resourceId);
|
|
1036
|
+
if (!resource) {
|
|
1037
|
+
const known = (item.resources ?? []).map((r) => r.id).join(", ") || "none";
|
|
1038
|
+
throw new Error(`News resource "${resourceId}" not found in news item ${newsId} (available: ${known})`);
|
|
1039
|
+
}
|
|
1040
|
+
const fetched = await client.news.fetchResource(newsId, resourceId, { item });
|
|
1041
|
+
if (fetched.status === "not_a_file" || !fetched.response) {
|
|
1042
|
+
const result = {
|
|
1043
|
+
status: "not_a_file",
|
|
1044
|
+
newsId,
|
|
1045
|
+
resource,
|
|
1046
|
+
availableActions: ["open_in_browser"],
|
|
1047
|
+
message: "The link answered with a web page instead of a file. It may require signing in — open the URL in a browser instead.",
|
|
1048
|
+
};
|
|
1049
|
+
if (opts.json) {
|
|
1050
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
console.log(`\n${resource.label}`);
|
|
1054
|
+
console.log(result.message);
|
|
1055
|
+
console.log(resource.url);
|
|
1056
|
+
}
|
|
1057
|
+
return result;
|
|
1058
|
+
}
|
|
1059
|
+
const { response } = fetched;
|
|
1060
|
+
if (!response.ok) {
|
|
1061
|
+
await response.body?.cancel();
|
|
1062
|
+
throw new Error(`News resource download failed with HTTP ${response.status}`);
|
|
1063
|
+
}
|
|
1064
|
+
const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim() || null;
|
|
1065
|
+
const declaredLength = Number(response.headers.get("content-length") ?? "0");
|
|
1066
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_NEWS_RESOURCE_BYTES) {
|
|
1067
|
+
await response.body?.cancel();
|
|
1068
|
+
throw new Error("News resource exceeds the 50 MB download limit");
|
|
1069
|
+
}
|
|
1070
|
+
const outputDirectory = resolve(opts.output ?? process.cwd());
|
|
1071
|
+
await mkdir(outputDirectory, { recursive: true });
|
|
1072
|
+
const preferredName = fileNameFromResponse(resource, response.headers.get("content-disposition"), contentType);
|
|
1073
|
+
const { path, handle } = await createUniqueDownloadFile(outputDirectory, preferredName);
|
|
1074
|
+
let sizeBytes = 0;
|
|
1075
|
+
try {
|
|
1076
|
+
if (!response.body) {
|
|
1077
|
+
throw new Error("News resource response had no body");
|
|
1078
|
+
}
|
|
1079
|
+
const reader = response.body.getReader();
|
|
1080
|
+
while (true) {
|
|
1081
|
+
const { done, value } = await reader.read();
|
|
1082
|
+
if (done)
|
|
1083
|
+
break;
|
|
1084
|
+
sizeBytes += value.byteLength;
|
|
1085
|
+
if (sizeBytes > MAX_NEWS_RESOURCE_BYTES) {
|
|
1086
|
+
await reader.cancel();
|
|
1087
|
+
throw new Error("News resource exceeds the 50 MB download limit");
|
|
1088
|
+
}
|
|
1089
|
+
let offset = 0;
|
|
1090
|
+
while (offset < value.byteLength) {
|
|
1091
|
+
const { bytesWritten } = await handle.write(value, offset, value.byteLength - offset);
|
|
1092
|
+
if (bytesWritten === 0) {
|
|
1093
|
+
throw new Error("Could not write news resource to disk");
|
|
1094
|
+
}
|
|
1095
|
+
offset += bytesWritten;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
await handle.close();
|
|
1099
|
+
}
|
|
1100
|
+
catch (error) {
|
|
1101
|
+
await handle.close().catch(() => undefined);
|
|
1102
|
+
await rm(path, { force: true });
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
const result = {
|
|
1106
|
+
status: "downloaded",
|
|
1107
|
+
newsId,
|
|
1108
|
+
resourceId,
|
|
1109
|
+
path,
|
|
1110
|
+
contentType,
|
|
1111
|
+
sizeBytes,
|
|
1112
|
+
};
|
|
1113
|
+
if (opts.json) {
|
|
1114
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
console.log(`Downloaded ${resource.label} to ${path}`);
|
|
1118
|
+
}
|
|
1119
|
+
return result;
|
|
1120
|
+
}
|
|
1121
|
+
function normalizeResourceId(raw) {
|
|
1122
|
+
if (!raw)
|
|
1123
|
+
return undefined;
|
|
1124
|
+
return /^\d+$/.test(raw) ? `resource-${raw}` : raw;
|
|
1125
|
+
}
|
|
1126
|
+
function fileNameFromResponse(resource, contentDisposition, contentType) {
|
|
1127
|
+
const encoded = /filename\*=UTF-8''([^;]+)/i.exec(contentDisposition ?? "")?.[1];
|
|
1128
|
+
const quoted = /filename="([^"]+)"/i.exec(contentDisposition ?? "")?.[1];
|
|
1129
|
+
const plain = /filename=([^;]+)/i.exec(contentDisposition ?? "")?.[1]?.trim();
|
|
1130
|
+
let name = encoded ? decodeURIComponentSafely(encoded) : quoted ?? plain ?? resource.fileName ?? resource.label;
|
|
1131
|
+
name = sanitizeFileName(name);
|
|
1132
|
+
if (!/\.[A-Za-z0-9]{1,8}$/.test(name)) {
|
|
1133
|
+
const extension = extensionForContentType(contentType);
|
|
1134
|
+
if (extension)
|
|
1135
|
+
name += extension;
|
|
1136
|
+
}
|
|
1137
|
+
return name || "wilma-resource";
|
|
1138
|
+
}
|
|
1139
|
+
function sanitizeFileName(value) {
|
|
1140
|
+
return value
|
|
1141
|
+
.replace(/[\\/:*?"<>|\u0000-\u001f]/g, "_")
|
|
1142
|
+
.replace(/^\.+|\.+$/g, "")
|
|
1143
|
+
.trim()
|
|
1144
|
+
.slice(0, 180);
|
|
1145
|
+
}
|
|
1146
|
+
function extensionForContentType(contentType) {
|
|
1147
|
+
const extensions = {
|
|
1148
|
+
"application/pdf": ".pdf",
|
|
1149
|
+
"application/zip": ".zip",
|
|
1150
|
+
"image/jpeg": ".jpg",
|
|
1151
|
+
"image/png": ".png",
|
|
1152
|
+
"text/plain": ".txt",
|
|
1153
|
+
"text/csv": ".csv",
|
|
1154
|
+
};
|
|
1155
|
+
return contentType ? extensions[contentType] ?? "" : "";
|
|
1156
|
+
}
|
|
1157
|
+
function decodeURIComponentSafely(value) {
|
|
1158
|
+
try {
|
|
1159
|
+
return decodeURIComponent(value);
|
|
1160
|
+
}
|
|
1161
|
+
catch {
|
|
1162
|
+
return value;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
async function createUniqueDownloadFile(directory, preferredName) {
|
|
1166
|
+
const dot = preferredName.lastIndexOf(".");
|
|
1167
|
+
const hasExtension = dot > 0;
|
|
1168
|
+
const stem = hasExtension ? preferredName.slice(0, dot) : preferredName;
|
|
1169
|
+
const extension = hasExtension ? preferredName.slice(dot) : "";
|
|
1170
|
+
for (let index = 0; index < 1000; index += 1) {
|
|
1171
|
+
const candidate = index === 0 ? preferredName : `${stem}-${index}${extension}`;
|
|
1172
|
+
const path = resolve(directory, candidate);
|
|
1173
|
+
try {
|
|
1174
|
+
const handle = await open(path, "wx", 0o600);
|
|
1175
|
+
return { path, handle };
|
|
1176
|
+
}
|
|
1177
|
+
catch (error) {
|
|
1178
|
+
if (error.code !== "EEXIST") {
|
|
1179
|
+
throw error;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
throw new Error("Could not choose a unique output filename");
|
|
968
1184
|
}
|
|
969
1185
|
async function outputExams(client, opts) {
|
|
970
1186
|
const exams = await client.exams.list();
|
|
@@ -1298,7 +1514,40 @@ async function selectNewsToRead(client) {
|
|
|
1298
1514
|
});
|
|
1299
1515
|
if (!selected || selected === "back")
|
|
1300
1516
|
return;
|
|
1301
|
-
await outputNewsItem(client, Number(selected), false);
|
|
1517
|
+
const item = await outputNewsItem(client, Number(selected), false, { suppressDownloadHint: true });
|
|
1518
|
+
if (item?.resources?.length) {
|
|
1519
|
+
await offerResourceDownloads(client, item);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async function offerResourceDownloads(client, item) {
|
|
1523
|
+
while (true) {
|
|
1524
|
+
const resources = item.resources ?? [];
|
|
1525
|
+
const choices = [
|
|
1526
|
+
{ value: "back", name: "Continue" },
|
|
1527
|
+
...resources.map((resource) => ({
|
|
1528
|
+
value: resource.id,
|
|
1529
|
+
name: `Download "${compactText(resource.label)}"`,
|
|
1530
|
+
})),
|
|
1531
|
+
];
|
|
1532
|
+
const selected = await selectOrCancel({ message: "Resources", choices }, false);
|
|
1533
|
+
if (!selected || selected === "back")
|
|
1534
|
+
return;
|
|
1535
|
+
const outputDirectory = await inputOrCancel({
|
|
1536
|
+
message: "Save to directory",
|
|
1537
|
+
default: resolve(homedir(), "Downloads"),
|
|
1538
|
+
});
|
|
1539
|
+
if (outputDirectory === null)
|
|
1540
|
+
continue;
|
|
1541
|
+
try {
|
|
1542
|
+
await outputNewsResourceDownload(client, item.wilmaId, selected, {
|
|
1543
|
+
output: outputDirectory,
|
|
1544
|
+
json: false,
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
catch (error) {
|
|
1548
|
+
console.error("Download failed:", error instanceof Error ? error.message : error);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1302
1551
|
}
|
|
1303
1552
|
async function selectMessageToRead(client, folder) {
|
|
1304
1553
|
const messages = await client.messages.list(folder);
|
|
@@ -1758,6 +2007,12 @@ main().catch((err) => {
|
|
|
1758
2007
|
console.error("For interactive use, run 'wilma' without arguments.");
|
|
1759
2008
|
process.exit(1);
|
|
1760
2009
|
}
|
|
1761
|
-
|
|
2010
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2011
|
+
if (process.argv.includes("--json")) {
|
|
2012
|
+
console.log(JSON.stringify({ status: "error", message }, null, 2));
|
|
2013
|
+
}
|
|
2014
|
+
else {
|
|
2015
|
+
console.error("CLI error:", message);
|
|
2016
|
+
}
|
|
1762
2017
|
process.exit(1);
|
|
1763
2018
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wilm-ai/wilma-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,17 +8,9 @@
|
|
|
8
8
|
"wilmai": "dist/index.js",
|
|
9
9
|
"wilma": "dist/index.js"
|
|
10
10
|
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsc -p tsconfig.json",
|
|
13
|
-
"lint": "echo 'add lint'",
|
|
14
|
-
"test": "echo 'add tests'",
|
|
15
|
-
"start": "node dist/index.js",
|
|
16
|
-
"prepack": "pnpm --filter @wilm-ai/wilma-client build && pnpm --filter @wilm-ai/wilma-cli build",
|
|
17
|
-
"test:live": "tsc -p tsconfig.json && node test/live.spec.mjs"
|
|
18
|
-
},
|
|
19
11
|
"dependencies": {
|
|
20
12
|
"@inquirer/prompts": "^5.3.8",
|
|
21
|
-
"@wilm-ai/wilma-client": "
|
|
13
|
+
"@wilm-ai/wilma-client": "^1.5.0"
|
|
22
14
|
},
|
|
23
15
|
"devDependencies": {
|
|
24
16
|
"typescript": "^5.6.3"
|
|
@@ -29,5 +21,12 @@
|
|
|
29
21
|
],
|
|
30
22
|
"publishConfig": {
|
|
31
23
|
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json",
|
|
27
|
+
"lint": "echo 'add lint'",
|
|
28
|
+
"test": "pnpm --filter @wilm-ai/wilma-client build && pnpm run build && node test/news-resources.mjs",
|
|
29
|
+
"start": "node dist/index.js",
|
|
30
|
+
"test:live": "tsc -p tsconfig.json && node test/live.spec.mjs"
|
|
32
31
|
}
|
|
33
|
-
}
|
|
32
|
+
}
|