@shulex/cxclawctl 0.1.8 → 0.1.10
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/README.md +7 -36
- package/capabilities.lock.json +3 -2
- package/dist/cli.js +205 -113
- package/dist/cli.js.map +1 -1
- package/package.json +3 -1
- package/runtime/auth-wait-state.d.mts +6 -0
- package/runtime/auth-wait-state.mjs +56 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { readFile as
|
|
4
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
5
5
|
|
|
6
6
|
// src/auth.ts
|
|
7
7
|
import open from "open";
|
|
@@ -12,7 +12,7 @@ var AUTH_SERVER_URL = "https://ai.solvea.cx/api_v2/ticket";
|
|
|
12
12
|
// package.json
|
|
13
13
|
var package_default = {
|
|
14
14
|
name: "@shulex/cxclawctl",
|
|
15
|
-
version: "0.1.
|
|
15
|
+
version: "0.1.10",
|
|
16
16
|
description: "Solvea CXClaw CLI and Codex plugin runtime",
|
|
17
17
|
type: "module",
|
|
18
18
|
bin: {
|
|
@@ -22,6 +22,7 @@ var package_default = {
|
|
|
22
22
|
"bin",
|
|
23
23
|
"dist/cli.js",
|
|
24
24
|
"dist/cli.js.map",
|
|
25
|
+
"runtime",
|
|
25
26
|
"capabilities.lock.json"
|
|
26
27
|
],
|
|
27
28
|
scripts: {
|
|
@@ -33,6 +34,7 @@ var package_default = {
|
|
|
33
34
|
"check:release": "tsx scripts/check-release.ts",
|
|
34
35
|
"test:package": "tsx scripts/test-packed-package.ts",
|
|
35
36
|
"release:prepare": "tsx scripts/prepare-release.ts",
|
|
37
|
+
"release:create": "tsx scripts/create-release.ts",
|
|
36
38
|
"package:agent-plugin": "node scripts/package-agent-plugin.mjs",
|
|
37
39
|
"package:codex-plugin": "node scripts/package-codex-plugin.mjs"
|
|
38
40
|
},
|
|
@@ -67,6 +69,16 @@ var TOOL_SCHEMA_VERSION = "1";
|
|
|
67
69
|
var SERVER_API_VERSION = "1";
|
|
68
70
|
|
|
69
71
|
// src/http.ts
|
|
72
|
+
var HttpError = class extends Error {
|
|
73
|
+
constructor(status, responseBody) {
|
|
74
|
+
super(`server returned ${status}: ${responseBody}`);
|
|
75
|
+
this.status = status;
|
|
76
|
+
this.responseBody = responseBody;
|
|
77
|
+
this.name = "HttpError";
|
|
78
|
+
}
|
|
79
|
+
status;
|
|
80
|
+
responseBody;
|
|
81
|
+
};
|
|
70
82
|
function unwrap(value) {
|
|
71
83
|
if (value && typeof value === "object" && Object.keys(value).length === 1 && "data" in value) return value.data;
|
|
72
84
|
return value;
|
|
@@ -86,10 +98,11 @@ async function request(session, path, init = {}, tenant = false) {
|
|
|
86
98
|
const response = await fetch(`${AUTH_SERVER_URL}${path}`, { ...init, headers, signal: AbortSignal.timeout(3e4) });
|
|
87
99
|
const text = await response.text();
|
|
88
100
|
const value = text ? JSON.parse(text) : void 0;
|
|
89
|
-
if (!response.ok) throw new
|
|
101
|
+
if (!response.ok) throw new HttpError(response.status, text);
|
|
90
102
|
return unwrap(value);
|
|
91
103
|
} catch (error) {
|
|
92
104
|
last = error;
|
|
105
|
+
if (error instanceof HttpError && error.status < 500) break;
|
|
93
106
|
if (attempt < 2) await new Promise((r) => setTimeout(r, 200 * 2 ** attempt));
|
|
94
107
|
}
|
|
95
108
|
}
|
|
@@ -101,9 +114,15 @@ var post = (session, path, body, tenant = false) => request(session, path, { met
|
|
|
101
114
|
import { AsyncEntry } from "@napi-rs/keyring";
|
|
102
115
|
var options = process.platform === "linux" ? { linux: { store: "secret-service" } } : void 0;
|
|
103
116
|
var entry = () => new AsyncEntry("com.solvea.cxclaw", "session", options);
|
|
117
|
+
var SessionNotFoundError = class extends Error {
|
|
118
|
+
constructor() {
|
|
119
|
+
super("\u672A\u767B\u5F55");
|
|
120
|
+
this.name = "SessionNotFoundError";
|
|
121
|
+
}
|
|
122
|
+
};
|
|
104
123
|
async function loadSession() {
|
|
105
124
|
const raw = await entry().getPassword();
|
|
106
|
-
if (!raw) throw new
|
|
125
|
+
if (!raw) throw new SessionNotFoundError();
|
|
107
126
|
return JSON.parse(raw);
|
|
108
127
|
}
|
|
109
128
|
async function saveSession(session) {
|
|
@@ -114,32 +133,88 @@ async function deleteSession() {
|
|
|
114
133
|
await entry().deleteCredential();
|
|
115
134
|
}
|
|
116
135
|
|
|
136
|
+
// runtime/auth-wait-state.mjs
|
|
137
|
+
import { createHash } from "crypto";
|
|
138
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
|
|
139
|
+
import { tmpdir } from "os";
|
|
140
|
+
import { join } from "path";
|
|
141
|
+
var markerRoot = join(tmpdir(), "cxclaw-auth-wait");
|
|
142
|
+
var markerTtlMs = 15 * 60 * 1e3;
|
|
143
|
+
var markerName = (sessionId, pid) => `${createHash("sha256").update(sessionId).digest("hex").slice(0, 24)}-${pid}.json`;
|
|
144
|
+
async function withAuthWaitMarker(operation, env = process.env) {
|
|
145
|
+
const sessionId = env.CODEX_SESSION_ID ?? env.CODEX_THREAD_ID;
|
|
146
|
+
if (!sessionId) return operation();
|
|
147
|
+
await mkdir(markerRoot, { recursive: true, mode: 448 });
|
|
148
|
+
const path = join(markerRoot, markerName(sessionId, process.pid));
|
|
149
|
+
await writeFile(path, JSON.stringify({
|
|
150
|
+
sessionId,
|
|
151
|
+
threadId: env.CODEX_THREAD_ID ?? null,
|
|
152
|
+
pid: process.pid,
|
|
153
|
+
expiresAt: Date.now() + markerTtlMs
|
|
154
|
+
}), { mode: 384 });
|
|
155
|
+
try {
|
|
156
|
+
return await operation();
|
|
157
|
+
} finally {
|
|
158
|
+
await rm(path, { force: true }).catch(() => void 0);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
117
162
|
// src/auth.ts
|
|
118
163
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
164
|
+
var loginInFlight;
|
|
119
165
|
function publicAuthStatus(session) {
|
|
120
166
|
return { ok: true, loggedIn: true, email: session.email, channel: session.channel, accessExpiresAt: session.accessExpiry };
|
|
121
167
|
}
|
|
122
|
-
async function
|
|
168
|
+
async function authorizeDevice(channel, runtime) {
|
|
123
169
|
const session = { channel, accessToken: "", refreshToken: "", accessExpiry: 0, employeeId: "", email: "" };
|
|
124
|
-
const start = await
|
|
125
|
-
|
|
170
|
+
const start = await runtime.start(session);
|
|
171
|
+
try {
|
|
172
|
+
await runtime.openBrowser(start.verificationUriComplete);
|
|
173
|
+
runtime.log("\u9489\u9489\u6388\u6743\u9875\u5DF2\u6253\u5F00\uFF0C\u5F53\u524D\u547D\u4EE4\u4F1A\u6301\u7EED\u7B49\u5F85\u6388\u6743\u7ED3\u679C\uFF0C\u65E0\u9700\u56DE\u590D Codex\u3002");
|
|
174
|
+
} catch {
|
|
175
|
+
runtime.log(`\u672A\u80FD\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u6253\u5F00\u4EE5\u4E0B\u5730\u5740\u5B8C\u6210\u9489\u9489\u6388\u6743\uFF1A
|
|
126
176
|
${start.verificationUriComplete}
|
|
127
177
|
\u8BBE\u5907\u7801\uFF1A${start.userCode}`);
|
|
128
|
-
|
|
129
|
-
const deadline =
|
|
130
|
-
|
|
131
|
-
|
|
178
|
+
}
|
|
179
|
+
const deadline = runtime.now() + start.expiresIn * 1e3;
|
|
180
|
+
let nextProgressAt = runtime.now() + 15e3;
|
|
181
|
+
while (runtime.now() < deadline) {
|
|
182
|
+
await runtime.sleep(Math.max(2, start.interval) * 1e3);
|
|
132
183
|
try {
|
|
133
|
-
const token = await
|
|
134
|
-
Object.assign(session, { accessToken: token.accessToken, refreshToken: token.refreshToken, accessExpiry: Math.floor(
|
|
135
|
-
await
|
|
184
|
+
const token = await runtime.poll(session, start.deviceCode);
|
|
185
|
+
Object.assign(session, { accessToken: token.accessToken, refreshToken: token.refreshToken, accessExpiry: Math.floor(runtime.now() / 1e3) + token.expiresIn, employeeId: token.employee.id, email: token.employee.email });
|
|
186
|
+
await runtime.save(session);
|
|
187
|
+
runtime.log("\u9489\u9489\u6388\u6743\u6210\u529F\uFF0C\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\u3002");
|
|
136
188
|
return session;
|
|
137
189
|
} catch (error) {
|
|
138
|
-
if (!
|
|
190
|
+
if (!(error instanceof HttpError) || error.status !== 428) throw error;
|
|
191
|
+
if (runtime.now() >= nextProgressAt) {
|
|
192
|
+
runtime.log("\u4ECD\u5728\u7B49\u5F85\u9489\u9489\u6388\u6743\u2026\u2026");
|
|
193
|
+
nextProgressAt = runtime.now() + 15e3;
|
|
194
|
+
}
|
|
139
195
|
}
|
|
140
196
|
}
|
|
141
197
|
throw new Error("device authorization expired");
|
|
142
198
|
}
|
|
199
|
+
async function runLogin(channel) {
|
|
200
|
+
return withAuthWaitMarker(() => authorizeDevice(channel, {
|
|
201
|
+
start: (session) => post(session, "/auth/device/start", { clientVersion: VERSION }),
|
|
202
|
+
poll: (session, deviceCode) => post(session, "/auth/device/token", { deviceCode, clientVersion: VERSION }),
|
|
203
|
+
openBrowser: async (url) => {
|
|
204
|
+
await open(url);
|
|
205
|
+
},
|
|
206
|
+
save: saveSession,
|
|
207
|
+
sleep,
|
|
208
|
+
now: Date.now,
|
|
209
|
+
log: (message) => console.error(message)
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
function login(channel) {
|
|
213
|
+
if (!loginInFlight) loginInFlight = runLogin(channel).finally(() => {
|
|
214
|
+
loginInFlight = void 0;
|
|
215
|
+
});
|
|
216
|
+
return loginInFlight;
|
|
217
|
+
}
|
|
143
218
|
async function ensureAccess(session) {
|
|
144
219
|
if (session.accessToken && Date.now() / 1e3 + 30 < session.accessExpiry) return session;
|
|
145
220
|
const token = await post(session, "/auth/refresh", { refreshToken: session.refreshToken, clientVersion: VERSION });
|
|
@@ -147,8 +222,24 @@ async function ensureAccess(session) {
|
|
|
147
222
|
await saveSession(session);
|
|
148
223
|
return session;
|
|
149
224
|
}
|
|
150
|
-
async function
|
|
151
|
-
|
|
225
|
+
async function resolveAuthenticated(runtime) {
|
|
226
|
+
try {
|
|
227
|
+
return await runtime.refresh(await runtime.load());
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error instanceof SessionNotFoundError) {
|
|
230
|
+
console.error("CXClaw \u5C1A\u672A\u6388\u6743\uFF0C\u6B63\u5728\u81EA\u52A8\u53D1\u8D77\u9489\u9489\u6388\u6743\u3002");
|
|
231
|
+
return runtime.authorize();
|
|
232
|
+
}
|
|
233
|
+
if (error instanceof HttpError && error.status === 401) {
|
|
234
|
+
console.error("CXClaw \u767B\u5F55\u5DF2\u5931\u6548\uFF0C\u6B63\u5728\u91CD\u65B0\u53D1\u8D77\u9489\u9489\u6388\u6743\u3002");
|
|
235
|
+
await runtime.remove().catch(() => void 0);
|
|
236
|
+
return runtime.authorize();
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function authenticated() {
|
|
242
|
+
return resolveAuthenticated({ load: loadSession, refresh: ensureAccess, remove: deleteSession, authorize: () => login("stable") });
|
|
152
243
|
}
|
|
153
244
|
async function logout() {
|
|
154
245
|
let session;
|
|
@@ -200,10 +291,10 @@ import { dirname as dirname2, resolve } from "path";
|
|
|
200
291
|
import { fileURLToPath } from "url";
|
|
201
292
|
|
|
202
293
|
// src/solvea/commands.ts
|
|
203
|
-
import { readFile as
|
|
294
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
204
295
|
|
|
205
296
|
// src/solvea/client.ts
|
|
206
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
297
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
207
298
|
import { basename, dirname, extname } from "path";
|
|
208
299
|
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
209
300
|
var KNOWLEDGE_TYPES = /* @__PURE__ */ new Set(["QA", "LEARN_SELF", "FILE", "WEBSITE", "PRODUCT"]);
|
|
@@ -651,7 +742,7 @@ var SolveaClient = class {
|
|
|
651
742
|
await client.send(new PutObjectCommand({
|
|
652
743
|
Bucket: String(credentials.bucketName),
|
|
653
744
|
Key: key,
|
|
654
|
-
Body: await
|
|
745
|
+
Body: await readFile2(filePath),
|
|
655
746
|
ContentType: contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
|
656
747
|
}));
|
|
657
748
|
return key;
|
|
@@ -668,8 +759,8 @@ var SolveaClient = class {
|
|
|
668
759
|
signal: AbortSignal.timeout(3e4)
|
|
669
760
|
});
|
|
670
761
|
if (!response.ok) throw new Error(`HTTP ${response.status} GET ${path}: ${await response.text()}`);
|
|
671
|
-
await
|
|
672
|
-
await
|
|
762
|
+
await mkdir2(dirname(target), { recursive: true });
|
|
763
|
+
await writeFile2(target, new Uint8Array(await response.arrayBuffer()));
|
|
673
764
|
return target;
|
|
674
765
|
}
|
|
675
766
|
async downloadLabelTemplate(templateType, target) {
|
|
@@ -911,10 +1002,10 @@ var SolveaClient = class {
|
|
|
911
1002
|
};
|
|
912
1003
|
|
|
913
1004
|
// src/solvea/input.ts
|
|
914
|
-
import { readFile as
|
|
1005
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
915
1006
|
async function jsonData(args, required = true) {
|
|
916
1007
|
const file = option(args, "--data-file");
|
|
917
|
-
const raw = file ? await
|
|
1008
|
+
const raw = file ? await readFile3(file, "utf8") : option(args, "--data");
|
|
918
1009
|
if (!raw) {
|
|
919
1010
|
if (required) throw new Error("--data or --data-file is required");
|
|
920
1011
|
return {};
|
|
@@ -925,7 +1016,7 @@ async function jsonData(args, required = true) {
|
|
|
925
1016
|
}
|
|
926
1017
|
async function jsonValue(args) {
|
|
927
1018
|
const file = option(args, "--data-file");
|
|
928
|
-
const raw = file ? await
|
|
1019
|
+
const raw = file ? await readFile3(file, "utf8") : option(args, "--data", true);
|
|
929
1020
|
return JSON.parse(raw);
|
|
930
1021
|
}
|
|
931
1022
|
function requireFlag(args, name, command) {
|
|
@@ -1108,7 +1199,7 @@ function enabled(args, command) {
|
|
|
1108
1199
|
}
|
|
1109
1200
|
async function data2(args) {
|
|
1110
1201
|
const file = option(args, "--data-file");
|
|
1111
|
-
const raw = file ? await
|
|
1202
|
+
const raw = file ? await readFile4(file, "utf8") : option(args, "--data");
|
|
1112
1203
|
if (!raw) throw new Error("--data or --data-file is required");
|
|
1113
1204
|
const parsed = JSON.parse(raw);
|
|
1114
1205
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("request data must be a JSON object");
|
|
@@ -1453,13 +1544,13 @@ async function runResourceCommand(session, resource, action2, args) {
|
|
|
1453
1544
|
|
|
1454
1545
|
// src/doctor.ts
|
|
1455
1546
|
import { access } from "fs/promises";
|
|
1456
|
-
import { join } from "path";
|
|
1547
|
+
import { join as join2 } from "path";
|
|
1457
1548
|
async function diagnose() {
|
|
1458
1549
|
const root2 = packageRoot();
|
|
1459
1550
|
const requiredFiles = [
|
|
1460
1551
|
"package.json"
|
|
1461
1552
|
];
|
|
1462
|
-
await Promise.all(requiredFiles.map((path) => access(
|
|
1553
|
+
await Promise.all(requiredFiles.map((path) => access(join2(root2, path))));
|
|
1463
1554
|
const expectedPluginVersion = process.env.CXCLAW_PLUGIN_VERSION || null;
|
|
1464
1555
|
if (expectedPluginVersion && expectedPluginVersion !== VERSION) {
|
|
1465
1556
|
throw new Error(
|
|
@@ -1511,9 +1602,9 @@ function publicTenant(t) {
|
|
|
1511
1602
|
|
|
1512
1603
|
// src/update.ts
|
|
1513
1604
|
import { spawn } from "child_process";
|
|
1514
|
-
import { mkdir as
|
|
1605
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
1515
1606
|
import { homedir, platform } from "os";
|
|
1516
|
-
import { isAbsolute, join as
|
|
1607
|
+
import { isAbsolute, join as join3 } from "path";
|
|
1517
1608
|
var exec = (command, args, options3 = {}) => new Promise((resolve5, reject) => {
|
|
1518
1609
|
const chunks = [];
|
|
1519
1610
|
const child = spawn(command, args, {
|
|
@@ -1586,11 +1677,11 @@ async function update() {
|
|
|
1586
1677
|
async function installScheduler() {
|
|
1587
1678
|
const invocation = schedulerInvocation();
|
|
1588
1679
|
if (platform() === "darwin") {
|
|
1589
|
-
const dir =
|
|
1590
|
-
const path =
|
|
1680
|
+
const dir = join3(homedir(), "Library/LaunchAgents");
|
|
1681
|
+
const path = join3(dir, "com.solvea.cxclaw.update.plist");
|
|
1591
1682
|
const programArguments = [invocation.command, ...invocation.args].map((value) => `<string>${xml(value)}</string>`).join("");
|
|
1592
|
-
await
|
|
1593
|
-
await
|
|
1683
|
+
await mkdir3(dir, { recursive: true });
|
|
1684
|
+
await writeFile3(
|
|
1594
1685
|
path,
|
|
1595
1686
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
1596
1687
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -1602,18 +1693,18 @@ async function installScheduler() {
|
|
|
1602
1693
|
return;
|
|
1603
1694
|
}
|
|
1604
1695
|
if (platform() === "linux") {
|
|
1605
|
-
const dir =
|
|
1696
|
+
const dir = join3(homedir(), ".config/systemd/user");
|
|
1606
1697
|
const command = [invocation.command, ...invocation.args].map(systemdArg).join(" ");
|
|
1607
|
-
await
|
|
1608
|
-
await
|
|
1609
|
-
|
|
1698
|
+
await mkdir3(dir, { recursive: true });
|
|
1699
|
+
await writeFile3(
|
|
1700
|
+
join3(dir, "cxclaw-update.service"),
|
|
1610
1701
|
`[Service]
|
|
1611
1702
|
Type=oneshot
|
|
1612
1703
|
ExecStart=${command}
|
|
1613
1704
|
`
|
|
1614
1705
|
);
|
|
1615
|
-
await
|
|
1616
|
-
|
|
1706
|
+
await writeFile3(
|
|
1707
|
+
join3(dir, "cxclaw-update.timer"),
|
|
1617
1708
|
"[Timer]\nOnBootSec=5m\nOnUnitActiveSec=6h\nPersistent=true\n[Install]\nWantedBy=timers.target\n"
|
|
1618
1709
|
);
|
|
1619
1710
|
await exec("systemctl", ["--user", "daemon-reload"]);
|
|
@@ -1652,12 +1743,12 @@ async function installMarketplace(channel) {
|
|
|
1652
1743
|
// src/workflows/runtime.ts
|
|
1653
1744
|
import { execFile } from "child_process";
|
|
1654
1745
|
import { promises as fs } from "fs";
|
|
1655
|
-
import { join as
|
|
1746
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
1656
1747
|
import { promisify } from "util";
|
|
1657
1748
|
var exec2 = promisify(execFile);
|
|
1658
|
-
var workspace = () => resolve2(process.env.WORKSPACE_DIR ??
|
|
1659
|
-
var root = () =>
|
|
1660
|
-
var dirs = () => ({ output:
|
|
1749
|
+
var workspace = () => resolve2(process.env.WORKSPACE_DIR ?? join4(process.cwd(), "workspace"));
|
|
1750
|
+
var root = () => join4(workspace(), "runtime-inspector");
|
|
1751
|
+
var dirs = () => ({ output: join4(root(), "outputs"), state: join4(root(), "state"), tmp: join4(root(), "tmp") });
|
|
1661
1752
|
var timestamp = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:TZ.]/g, "").slice(0, 15);
|
|
1662
1753
|
var readJson = async (path, fallback) => {
|
|
1663
1754
|
try {
|
|
@@ -1693,26 +1784,26 @@ async function prepare(args) {
|
|
|
1693
1784
|
await fs.mkdir(tmp, { recursive: true });
|
|
1694
1785
|
const batches = [];
|
|
1695
1786
|
for (let i = 0; i < records.length; i += batchSize) batches.push(records.slice(i, i + batchSize));
|
|
1696
|
-
await Promise.all(batches.map((batch, i) => writeJson(
|
|
1697
|
-
await writeJson(
|
|
1787
|
+
await Promise.all(batches.map((batch, i) => writeJson(join4(tmp, `batch_${String(i).padStart(3, "0")}.json`), batch)));
|
|
1788
|
+
await writeJson(join4(state2, "inspect.json"), { status: "batched", total_records: records.length, total_batches: batches.length, batch_size: batchSize, source: source ?? "auto-generated", written_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1698
1789
|
return { status: "batched", totalRecords: records.length, totalBatches: batches.length, tmp };
|
|
1699
1790
|
}
|
|
1700
1791
|
async function processBatches() {
|
|
1701
1792
|
const { tmp, output, state: state2 } = dirs();
|
|
1702
|
-
const current = await readJson(
|
|
1793
|
+
const current = await readJson(join4(state2, "inspect.json"), {});
|
|
1703
1794
|
if (current.status !== "batched") throw new Error(`runtime state must be batched, got ${String(current.status ?? "missing")}`);
|
|
1704
1795
|
const names = (await fs.readdir(tmp)).filter((name) => /^batch_\d+\.json$/.test(name)).sort();
|
|
1705
1796
|
if (!names.length) throw new Error(`no batch files found in ${tmp}`);
|
|
1706
1797
|
const records = [];
|
|
1707
|
-
for (const name of names) for (const row of await readJson(
|
|
1708
|
-
const path =
|
|
1798
|
+
for (const name of names) for (const row of await readJson(join4(tmp, name), [])) records.push({ ...row, _processed: true, _summary: `processed: ${JSON.stringify(row).slice(0, 60)}`, _at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1799
|
+
const path = join4(output, `results_${timestamp()}.json`);
|
|
1709
1800
|
await writeJson(path, { total: records.length, processed_at: (/* @__PURE__ */ new Date()).toISOString(), source: current.source, records });
|
|
1710
|
-
await writeJson(
|
|
1801
|
+
await writeJson(join4(state2, "inspect.json"), { status: "done", total_processed: records.length, results_path: path, done_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1711
1802
|
return { status: "done", total: records.length, resultsPath: path };
|
|
1712
1803
|
}
|
|
1713
1804
|
async function state() {
|
|
1714
1805
|
const { state: stateDir2, output, tmp } = dirs();
|
|
1715
|
-
return { state: await readJson(
|
|
1806
|
+
return { state: await readJson(join4(stateDir2, "inspect.json"), null), outputFiles: await fs.readdir(output).catch(() => []), tmpFiles: await fs.readdir(tmp).catch(() => []) };
|
|
1716
1807
|
}
|
|
1717
1808
|
async function verify() {
|
|
1718
1809
|
const snapshot = await state();
|
|
@@ -1739,7 +1830,7 @@ async function sandbox(args) {
|
|
|
1739
1830
|
}
|
|
1740
1831
|
async function model(args) {
|
|
1741
1832
|
if (option(args, "--scenario", true) !== "truncation") throw new Error("only --scenario truncation is supported");
|
|
1742
|
-
const target =
|
|
1833
|
+
const target = join4(dirs().tmp, "truncation_test.md");
|
|
1743
1834
|
return { scenario: "truncation", target, instruction: "Use the Codex write_file tool to write 60+ long technical paragraphs, then inspect SLG model_response stop_reason and output_tokens." };
|
|
1744
1835
|
}
|
|
1745
1836
|
async function runRuntime(action2, args) {
|
|
@@ -1752,9 +1843,9 @@ async function runRuntime(action2, args) {
|
|
|
1752
1843
|
if (action2 === "write-test") {
|
|
1753
1844
|
const { output, state: stateDir2 } = dirs();
|
|
1754
1845
|
const runId = timestamp();
|
|
1755
|
-
await writeJson(
|
|
1756
|
-
const current = await readJson(
|
|
1757
|
-
await writeJson(
|
|
1846
|
+
await writeJson(join4(output, `${runId}_test.json`), { run_id: runId, written_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1847
|
+
const current = await readJson(join4(stateDir2, "inspect.json"), {});
|
|
1848
|
+
await writeJson(join4(stateDir2, "inspect.json"), { ...current, last_run_id: runId, write_count: Number(current.write_count ?? 0) + 1 });
|
|
1758
1849
|
return { ok: true, runId };
|
|
1759
1850
|
}
|
|
1760
1851
|
throw new Error("runtime action must be prepare, process, state, verify, sandbox, model, or write-test");
|
|
@@ -1763,10 +1854,10 @@ async function runRuntime(action2, args) {
|
|
|
1763
1854
|
// src/workflows/solvea-kit.ts
|
|
1764
1855
|
import ExcelJS from "exceljs";
|
|
1765
1856
|
import { promises as fs2 } from "fs";
|
|
1766
|
-
import { dirname as dirname3, join as
|
|
1767
|
-
var workspace2 = () => resolve3(process.env.WORKSPACE_DIR ??
|
|
1768
|
-
var outputDir = (owner) =>
|
|
1769
|
-
var stateDir = (owner) =>
|
|
1857
|
+
import { dirname as dirname3, join as join5, resolve as resolve3 } from "path";
|
|
1858
|
+
var workspace2 = () => resolve3(process.env.WORKSPACE_DIR ?? join5(process.cwd(), "workspace"));
|
|
1859
|
+
var outputDir = (owner) => join5(workspace2(), "outputs", owner);
|
|
1860
|
+
var stateDir = (owner) => join5(workspace2(), "state", owner);
|
|
1770
1861
|
var now = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[-:TZ.]/g, "").slice(0, 15);
|
|
1771
1862
|
var readJson2 = async (path) => JSON.parse(await fs2.readFile(path, "utf8"));
|
|
1772
1863
|
var writeJson2 = async (path, value) => {
|
|
@@ -1796,7 +1887,7 @@ function labelsFrom(value) {
|
|
|
1796
1887
|
async function generateLabelImport(args) {
|
|
1797
1888
|
const input = option(args, "--input") ?? option(args, "--data-file", true);
|
|
1798
1889
|
const init = args.includes("--init");
|
|
1799
|
-
const output = option(args, "--output") ??
|
|
1890
|
+
const output = option(args, "--output") ?? join5(stateDir("solvea-kit"), init ? "labels_init.xlsx" : "labels_update.xlsx");
|
|
1800
1891
|
const labels = labelsFrom(await readJson2(input));
|
|
1801
1892
|
const workbook = new ExcelJS.Workbook();
|
|
1802
1893
|
const sheet = workbook.addWorksheet("labels");
|
|
@@ -1836,9 +1927,9 @@ async function classifyIntents(args) {
|
|
|
1836
1927
|
if (!noExcel && !option(args, "--xlsx")) throw new Error("provide --xlsx or --no-excel");
|
|
1837
1928
|
const intents = Object.entries(TAXONOMY).map(([name, l2s]) => ({ name, definition: `Customer requests and issues related to ${name}.`, count: 0, pct: 0, sub_intents: l2s.map((l2) => ({ name: l2, definition: "", count: 0, pct: 0, samples: [], is_new: false })), samples: [], is_new: false }));
|
|
1838
1929
|
const result = { meta: { run_id: runId, total: 0, classified: 0, n_known: intents.length, n_new: 0, mode: noExcel ? "no-excel" : "structured" }, intents, new_intents: [] };
|
|
1839
|
-
const resultPath =
|
|
1930
|
+
const resultPath = join5(outputDir(owner), `results_${runId}.json`);
|
|
1840
1931
|
await writeJson2(resultPath, result);
|
|
1841
|
-
await writeJson2(
|
|
1932
|
+
await writeJson2(join5(stateDir(owner), "progress.json"), { status: "ready", run_id: runId, results_path: resultPath, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1842
1933
|
return { ...result.meta, resultsPath: resultPath };
|
|
1843
1934
|
}
|
|
1844
1935
|
async function runSolveaKitWorkflow(action2, args) {
|
|
@@ -1848,7 +1939,7 @@ async function runSolveaKitWorkflow(action2, args) {
|
|
|
1848
1939
|
}
|
|
1849
1940
|
|
|
1850
1941
|
// src/workflows/support.ts
|
|
1851
|
-
import { readFile as
|
|
1942
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1852
1943
|
import { basename as basename3 } from "path";
|
|
1853
1944
|
var safe = (value) => encodeURIComponent(value);
|
|
1854
1945
|
var docsMcpUrl = process.env.CXCLAW_DOCS_MCP_URL ?? "https://solvea-39f11a36.mintlify.app/mcp";
|
|
@@ -1923,7 +2014,7 @@ async function runSupport(session, action2, args) {
|
|
|
1923
2014
|
const attachments = [];
|
|
1924
2015
|
for (const file of options2(args, "--attachment")) {
|
|
1925
2016
|
const form = new FormData();
|
|
1926
|
-
form.append("file", new Blob([await
|
|
2017
|
+
form.append("file", new Blob([await readFile5(file)]), basename3(file));
|
|
1927
2018
|
const tenant = session.tenant;
|
|
1928
2019
|
const base = tenant?.businessUrl?.replace(/\/$/, "");
|
|
1929
2020
|
if (!tenant || !base || !tenant.businessToken) throw new Error("no tenant selected");
|
|
@@ -1954,24 +2045,24 @@ async function runSupport(session, action2, args) {
|
|
|
1954
2045
|
}
|
|
1955
2046
|
|
|
1956
2047
|
// src/workflows/common.ts
|
|
1957
|
-
import { mkdir as
|
|
1958
|
-
import { dirname as dirname4, join as
|
|
2048
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
2049
|
+
import { dirname as dirname4, join as join6, resolve as resolve4 } from "path";
|
|
1959
2050
|
import { homedir as homedir2 } from "os";
|
|
1960
|
-
var workspaceRoot = () => resolve4(process.env.CXCLAW_WORKSPACE ??
|
|
1961
|
-
var stateRoot = (domain) => resolve4(process.env.CXCLAW_STATE_DIR ??
|
|
1962
|
-
var outputRoot = (domain) => resolve4(process.env.CXCLAW_OUTPUT_DIR ??
|
|
2051
|
+
var workspaceRoot = () => resolve4(process.env.CXCLAW_WORKSPACE ?? join6(homedir2(), ".cxclaw", "workspace"));
|
|
2052
|
+
var stateRoot = (domain) => resolve4(process.env.CXCLAW_STATE_DIR ?? join6(workspaceRoot(), "state"), domain);
|
|
2053
|
+
var outputRoot = (domain) => resolve4(process.env.CXCLAW_OUTPUT_DIR ?? join6(workspaceRoot(), "outputs"), domain);
|
|
1963
2054
|
async function ensureParent(file) {
|
|
1964
|
-
await
|
|
2055
|
+
await mkdir4(dirname4(file), { recursive: true });
|
|
1965
2056
|
}
|
|
1966
2057
|
async function ensureDir(directory) {
|
|
1967
|
-
await
|
|
2058
|
+
await mkdir4(directory, { recursive: true });
|
|
1968
2059
|
}
|
|
1969
2060
|
async function readJson3(file) {
|
|
1970
|
-
return JSON.parse(await
|
|
2061
|
+
return JSON.parse(await readFile6(file, "utf8"));
|
|
1971
2062
|
}
|
|
1972
2063
|
async function writeJson3(file, value) {
|
|
1973
2064
|
await ensureParent(file);
|
|
1974
|
-
await
|
|
2065
|
+
await writeFile4(file, `${JSON.stringify(value, null, 2)}
|
|
1975
2066
|
`, "utf8");
|
|
1976
2067
|
}
|
|
1977
2068
|
function intOption(args, name, fallback) {
|
|
@@ -1989,7 +2080,7 @@ function htmlEscape(value) {
|
|
|
1989
2080
|
}
|
|
1990
2081
|
async function writeHtml(file, title, body) {
|
|
1991
2082
|
await ensureParent(file);
|
|
1992
|
-
await
|
|
2083
|
+
await writeFile4(file, `<!doctype html><html><head><meta charset="utf-8"><title>${htmlEscape(title)}</title><style>body{font-family:system-ui,sans-serif;margin:2rem;line-height:1.5}pre{white-space:pre-wrap;background:#f6f8fa;padding:1rem;border-radius:8px}.card{border:1px solid #ddd;border-radius:8px;padding:1rem;margin:1rem 0}</style></head><body><h1>${htmlEscape(title)}</h1>${body}</body></html>`, "utf8");
|
|
1993
2084
|
}
|
|
1994
2085
|
function asRows(value) {
|
|
1995
2086
|
if (Array.isArray(value)) return value.filter((item) => item && typeof item === "object");
|
|
@@ -2196,13 +2287,13 @@ async function runLocalAgentWorkflow(action2, args) {
|
|
|
2196
2287
|
}
|
|
2197
2288
|
async function writeFileText(file, content) {
|
|
2198
2289
|
await ensureParent(file);
|
|
2199
|
-
const { writeFile:
|
|
2200
|
-
await
|
|
2290
|
+
const { writeFile: writeFile5 } = await import("fs/promises");
|
|
2291
|
+
await writeFile5(file, `${content}
|
|
2201
2292
|
`, "utf8");
|
|
2202
2293
|
}
|
|
2203
2294
|
|
|
2204
2295
|
// src/workflows/tester.ts
|
|
2205
|
-
import { join as
|
|
2296
|
+
import { join as join7 } from "path";
|
|
2206
2297
|
import ExcelJS2 from "exceljs";
|
|
2207
2298
|
async function casesFrom(file) {
|
|
2208
2299
|
if (file.endsWith(".json")) return asRows(await readJson3(file));
|
|
@@ -2224,7 +2315,7 @@ async function casesFrom(file) {
|
|
|
2224
2315
|
}
|
|
2225
2316
|
async function createSheet(args) {
|
|
2226
2317
|
const cases = option(args, "--cases-file") ? await casesFrom(option(args, "--cases-file")) : [];
|
|
2227
|
-
const output = option(args, "--output") ??
|
|
2318
|
+
const output = option(args, "--output") ?? join7(outputRoot("agent-tester"), `cases_${Date.now()}.xlsx`);
|
|
2228
2319
|
await ensureParent(output);
|
|
2229
2320
|
const workbook = new ExcelJS2.Workbook();
|
|
2230
2321
|
const sheet = workbook.addWorksheet("cases");
|
|
@@ -2250,7 +2341,7 @@ async function runAgentTester(session, action2, args) {
|
|
|
2250
2341
|
}
|
|
2251
2342
|
|
|
2252
2343
|
// src/workflows/delivery.ts
|
|
2253
|
-
import { join as
|
|
2344
|
+
import { join as join8 } from "path";
|
|
2254
2345
|
import ExcelJS3 from "exceljs";
|
|
2255
2346
|
var KNOWN_INTENTS = ["Product Recommendation", "Discount Inquiry", "Price Inquiry", "Stock Availability", "Payment Issues", "Request Invoice", "Modify Order", "Check Order Status", "Cancel Order", "Track Shipment", "Shipping Exception", "Missing Items", "Wrong Items Received", "Damaged Package", "Product Usage", "Return and exchange"];
|
|
2256
2347
|
async function loadRows(file) {
|
|
@@ -2284,7 +2375,7 @@ async function writeRows(file, rows) {
|
|
|
2284
2375
|
const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))];
|
|
2285
2376
|
sheet.addRow(keys);
|
|
2286
2377
|
rows.forEach((row) => sheet.addRow(keys.map((key) => row[key] ?? "")));
|
|
2287
|
-
await ensureDir(
|
|
2378
|
+
await ensureDir(join8(file, ".."));
|
|
2288
2379
|
await workbook.xlsx.writeFile(file);
|
|
2289
2380
|
}
|
|
2290
2381
|
function classify(text) {
|
|
@@ -2305,13 +2396,13 @@ async function classifyCases(args, domain) {
|
|
|
2305
2396
|
const intents = [...grouped.entries()].map(([name, items]) => ({ name, definition: `Customer requests and issues related to ${name}.`, count: items.length, pct: rows.length ? items.length / rows.length : 0, samples: items.slice(0, 5), is_new: name === "New Intent" }));
|
|
2306
2397
|
const runId = option(args, "--run-id") ?? Date.now().toString();
|
|
2307
2398
|
const result = { meta: { run_id: runId, total: rows.length, classified: rows.length, mode: noExcel ? "no-excel" : "structured", domain }, intents, new_intents: intents.filter((item) => item.is_new) };
|
|
2308
|
-
const out = option(args, "--results") ??
|
|
2399
|
+
const out = option(args, "--results") ?? join8(outputRoot(domain), `results_${runId}.json`);
|
|
2309
2400
|
await writeJson3(out, result);
|
|
2310
|
-
await writeJson3(
|
|
2401
|
+
await writeJson3(join8(stateRoot(domain), "progress.json"), { status: "classified", run_id: runId, results_path: out, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2311
2402
|
return { ...result.meta, resultsPath: out };
|
|
2312
2403
|
}
|
|
2313
2404
|
async function plan(domain, args) {
|
|
2314
|
-
const file =
|
|
2405
|
+
const file = join8(stateRoot(domain), "progress.json");
|
|
2315
2406
|
let current = {};
|
|
2316
2407
|
try {
|
|
2317
2408
|
current = await readJson3(file);
|
|
@@ -2335,7 +2426,7 @@ async function plan(domain, args) {
|
|
|
2335
2426
|
async function runBatch(client, args, domain) {
|
|
2336
2427
|
const input = option(args, "--input") ?? option(args, "--test-file", true);
|
|
2337
2428
|
const rows = await loadRows(input);
|
|
2338
|
-
const output = option(args, "--output") ??
|
|
2429
|
+
const output = option(args, "--output") ?? join8(outputRoot(domain), `test_results_${Date.now()}.json`);
|
|
2339
2430
|
const workers = Math.max(1, Math.min(intOption(args, "--workers", 4), 16));
|
|
2340
2431
|
const results = [];
|
|
2341
2432
|
let cursor = 0;
|
|
@@ -2359,7 +2450,7 @@ async function runBatch(client, args, domain) {
|
|
|
2359
2450
|
return { output, total: results.length, succeeded: results.filter((row) => row.ok).length, failed: results.filter((row) => !row.ok).length };
|
|
2360
2451
|
}
|
|
2361
2452
|
async function createAgents(client, args, domain) {
|
|
2362
|
-
const state2 = await readJson3(
|
|
2453
|
+
const state2 = await readJson3(join8(stateRoot(domain), "progress.json")).catch(() => ({}));
|
|
2363
2454
|
const resultsPath = option(args, "--results") ?? (typeof state2.results_path === "string" ? state2.results_path : void 0);
|
|
2364
2455
|
if (!resultsPath) throw new Error("classification results are required");
|
|
2365
2456
|
const result = await readJson3(resultsPath);
|
|
@@ -2370,12 +2461,12 @@ async function createAgents(client, args, domain) {
|
|
|
2370
2461
|
return { created, count: created.length };
|
|
2371
2462
|
}
|
|
2372
2463
|
async function renderReport(args, domain) {
|
|
2373
|
-
const progress = await readJson3(
|
|
2464
|
+
const progress = await readJson3(join8(stateRoot(domain), "progress.json")).catch(() => ({}));
|
|
2374
2465
|
const resultsFile = option(args, "--results") ?? option(args, "--input") ?? (typeof progress.results_path === "string" ? progress.results_path : void 0);
|
|
2375
2466
|
if (!resultsFile) throw new Error("--results/--input is required, or run classify first");
|
|
2376
2467
|
const value = await readJson3(resultsFile);
|
|
2377
2468
|
const rows = asRows(value.results ?? value.items ?? value);
|
|
2378
|
-
const output = option(args, "--html") ?? option(args, "--output") ??
|
|
2469
|
+
const output = option(args, "--html") ?? option(args, "--output") ?? join8(outputRoot(domain), `${domain}_report.html`);
|
|
2379
2470
|
const body = `<p>Total: ${rows.length}</p>${rows.map((row, index) => `<div class="card"><h2>${index + 1}. ${htmlEscape(row.intent ?? row.question ?? row.message ?? "Case")}</h2><pre>${htmlEscape(JSON.stringify(row, null, 2))}</pre></div>`).join("")}`;
|
|
2380
2471
|
await writeHtml(output, option(args, "--title") ?? `${domain} report`, body);
|
|
2381
2472
|
const md = option(args, "--md");
|
|
@@ -2386,12 +2477,12 @@ Total: ${rows.length}
|
|
|
2386
2477
|
return { output, count: rows.length };
|
|
2387
2478
|
}
|
|
2388
2479
|
async function genExcel(args, domain) {
|
|
2389
|
-
const progress = await readJson3(
|
|
2480
|
+
const progress = await readJson3(join8(stateRoot(domain), "progress.json")).catch(() => ({}));
|
|
2390
2481
|
const input = option(args, "--results") ?? option(args, "--items") ?? (typeof progress.results_path === "string" ? progress.results_path : void 0);
|
|
2391
2482
|
if (!input) throw new Error("--results or --items is required, or run classify first");
|
|
2392
2483
|
const value = await readJson3(input);
|
|
2393
2484
|
const rows = asRows(value);
|
|
2394
|
-
const output = option(args, "--output") ??
|
|
2485
|
+
const output = option(args, "--output") ?? join8(outputRoot(domain), `${domain}_results.xlsx`);
|
|
2395
2486
|
await writeRows(output, rows);
|
|
2396
2487
|
return { output, count: rows.length };
|
|
2397
2488
|
}
|
|
@@ -2431,7 +2522,7 @@ async function runDomain(session, domain, action2, args) {
|
|
|
2431
2522
|
}
|
|
2432
2523
|
|
|
2433
2524
|
// src/workflows/kb-builder.ts
|
|
2434
|
-
import { join as
|
|
2525
|
+
import { join as join9 } from "path";
|
|
2435
2526
|
import ExcelJS4 from "exceljs";
|
|
2436
2527
|
var clean = (value) => String(value ?? "").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
2437
2528
|
var absolute = (href, base) => {
|
|
@@ -2465,7 +2556,7 @@ async function crawl(args) {
|
|
|
2465
2556
|
}
|
|
2466
2557
|
}
|
|
2467
2558
|
const result = { url, mode: option(args, "--mode") ?? "auto", pages, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), total: pages.length };
|
|
2468
|
-
const output = option(args, "--output") ??
|
|
2559
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), "crawl_result.json");
|
|
2469
2560
|
await writeJson3(output, result);
|
|
2470
2561
|
return { ...result, output };
|
|
2471
2562
|
}
|
|
@@ -2480,13 +2571,13 @@ async function directoryPlan(args) {
|
|
|
2480
2571
|
}
|
|
2481
2572
|
const tree = [...groups.entries()].map(([name, pages]) => ({ name, children: pages.slice(0, 100).map((page2) => ({ name: String(page2.title ?? page2.url), sourceUrl: page2.url })) }));
|
|
2482
2573
|
const result = { source: option(args, "--crawl-result"), generatedAt: (/* @__PURE__ */ new Date()).toISOString(), tree, confirmed: false };
|
|
2483
|
-
const output = option(args, "--output") ??
|
|
2574
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), "directory_plan.json");
|
|
2484
2575
|
await writeJson3(output, result);
|
|
2485
|
-
if (flag(args, "--write-confirmed-tree")) await writeJson3(
|
|
2576
|
+
if (flag(args, "--write-confirmed-tree")) await writeJson3(join9(stateRoot("kb-builder"), "confirmed_tree.json"), { ...result, confirmed: true });
|
|
2486
2577
|
return { output, directories: tree.length, pages: rows.length, tree };
|
|
2487
2578
|
}
|
|
2488
2579
|
async function createDirectories(client, args) {
|
|
2489
|
-
const file = option(args, "--tree-file") ??
|
|
2580
|
+
const file = option(args, "--tree-file") ?? join9(stateRoot("kb-builder"), "confirmed_tree.json");
|
|
2490
2581
|
const value = await readJson3(file);
|
|
2491
2582
|
const tree = Array.isArray(value.tree) ? value.tree : Array.isArray(value) ? value : [];
|
|
2492
2583
|
if (!tree.length) throw new Error("confirmed directory tree is empty");
|
|
@@ -2509,12 +2600,12 @@ async function createDirectories(client, args) {
|
|
|
2509
2600
|
async function extractWebQa(args) {
|
|
2510
2601
|
const input = await readJson3(option(args, "--crawl-result", true));
|
|
2511
2602
|
const pages = asRows(input.pages ?? input).slice(0, intOption(args, "--max-pages", 80));
|
|
2512
|
-
const outputDir2 = option(args, "--output-dir") ??
|
|
2603
|
+
const outputDir2 = option(args, "--output-dir") ?? join9(outputRoot("kb-builder"), "qa_parts");
|
|
2513
2604
|
await ensureDir(outputDir2);
|
|
2514
2605
|
const parts = pages.map((page2, index) => ({ question: String(page2.title ?? page2.url), answer: String(page2.text ?? "").slice(0, intOption(args, "--answer-limit", 3500)), sourceUrl: page2.url, sourceTitle: page2.title, index }));
|
|
2515
2606
|
const files = [];
|
|
2516
2607
|
for (const [index, part] of parts.entries()) {
|
|
2517
|
-
const file =
|
|
2608
|
+
const file = join9(outputDir2, `part_${String(index + 1).padStart(4, "0")}.json`);
|
|
2518
2609
|
await writeJson3(file, { items: [part] });
|
|
2519
2610
|
files.push(file);
|
|
2520
2611
|
}
|
|
@@ -2580,9 +2671,9 @@ async function importQa(client, args) {
|
|
|
2580
2671
|
items.push(...Array.isArray(value) ? value : value.items && Array.isArray(value.items) ? value.items : [value]);
|
|
2581
2672
|
}
|
|
2582
2673
|
if (fromDir) {
|
|
2583
|
-
const { readdir } = await import("fs/promises");
|
|
2584
|
-
for (const file of (await
|
|
2585
|
-
const value = await readJson3(
|
|
2674
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
2675
|
+
for (const file of (await readdir2(fromDir)).filter((name) => name.endsWith(".json")).sort()) {
|
|
2676
|
+
const value = await readJson3(join9(fromDir, file));
|
|
2586
2677
|
items.push(...Array.isArray(value) ? value : value.items && Array.isArray(value.items) ? value.items : [value]);
|
|
2587
2678
|
}
|
|
2588
2679
|
}
|
|
@@ -2590,7 +2681,7 @@ async function importQa(client, args) {
|
|
|
2590
2681
|
if (!Number.isSafeInteger(maxDepth) || maxDepth <= 0) throw new Error("--max-depth must be a positive integer");
|
|
2591
2682
|
const normalized = validateQaItems(items, maxDepth);
|
|
2592
2683
|
const plan2 = { items, count: items.length, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2593
|
-
const planFile =
|
|
2684
|
+
const planFile = join9(outputRoot("kb-builder"), `qa_import_plan_${Date.now()}.json`);
|
|
2594
2685
|
await writeJson3(planFile, plan2);
|
|
2595
2686
|
if (flag(args, "--dry-run")) return { dryRun: true, planFile, count: items.length };
|
|
2596
2687
|
const directories = flattenDirectories(asRows(await client.listDirectories()));
|
|
@@ -2665,7 +2756,7 @@ async function importTemplates(client, args) {
|
|
|
2665
2756
|
});
|
|
2666
2757
|
}
|
|
2667
2758
|
const items = rows.map((row) => ({ name: String(row.name ?? row.Name ?? "").trim(), content: String(row.content ?? row.Content ?? row.template ?? ""), status: row.status ?? row.Status ?? "ENABLED", scene: row.scene ?? row.Scene })).filter((item) => item.name && item.content);
|
|
2668
|
-
const output = option(args, "--output") ??
|
|
2759
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), `template_import_plan_${Date.now()}.json`);
|
|
2669
2760
|
await writeJson3(output, { items, count: items.length });
|
|
2670
2761
|
if (flag(args, "--dry-run")) return { dryRun: true, output, count: items.length };
|
|
2671
2762
|
const results = [];
|
|
@@ -2680,13 +2771,13 @@ async function exportKnowledge(client, args) {
|
|
|
2680
2771
|
const value = await client.listKnowledge({ directoryId: id, keyword: option(args, "--keyword") ?? "", type: option(args, "--type") ?? "ALL", pageSize: intOption(args, "--page-size", 1e3) });
|
|
2681
2772
|
rows.push(...asRows(value).map((row) => ({ ...row, directoryId: id, directoryName: directories.find((dir) => Number(dir.id ?? dir.directoryId) === id)?.name ?? "" })));
|
|
2682
2773
|
}
|
|
2683
|
-
const output = option(args, "--output") ??
|
|
2774
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), `knowledge_${Date.now()}.xlsx`);
|
|
2684
2775
|
const workbook = new ExcelJS4.Workbook();
|
|
2685
2776
|
const sheet = workbook.addWorksheet("knowledge");
|
|
2686
2777
|
const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))];
|
|
2687
2778
|
sheet.addRow(keys);
|
|
2688
2779
|
for (const row of rows) sheet.addRow(keys.map((key) => row[key] ?? ""));
|
|
2689
|
-
await ensureDir(
|
|
2780
|
+
await ensureDir(join9(output, ".."));
|
|
2690
2781
|
await workbook.xlsx.writeFile(output);
|
|
2691
2782
|
return { output, count: rows.length };
|
|
2692
2783
|
}
|
|
@@ -2727,7 +2818,7 @@ async function extractTicketQa(args) {
|
|
|
2727
2818
|
const answer = String(record[replyCol] ?? "").trim();
|
|
2728
2819
|
if (question && question !== `${brand}:`) items.push({ question, answer, sourceRow: number });
|
|
2729
2820
|
});
|
|
2730
|
-
const output = option(args, "--output") ??
|
|
2821
|
+
const output = option(args, "--output") ?? join9(outputRoot("kb-builder"), `ticket_qa_${Date.now()}.json`);
|
|
2731
2822
|
const result = { brand, count: items.length, items };
|
|
2732
2823
|
await writeJson3(output, result);
|
|
2733
2824
|
if (flag(args, "--dry-run")) return { dryRun: true, output, count: items.length };
|
|
@@ -2735,12 +2826,12 @@ async function extractTicketQa(args) {
|
|
|
2735
2826
|
}
|
|
2736
2827
|
|
|
2737
2828
|
// src/workflows/desk.ts
|
|
2738
|
-
import { join as
|
|
2829
|
+
import { join as join10 } from "path";
|
|
2739
2830
|
import ExcelJS5 from "exceljs";
|
|
2740
2831
|
var taskId = (args) => option(args, "--task-id") ?? (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
2741
|
-
var taskDir = (args) =>
|
|
2832
|
+
var taskDir = (args) => join10(stateRoot("solvea-desk"), taskId(args));
|
|
2742
2833
|
async function saveDatasource(dir, name, rows) {
|
|
2743
|
-
const file =
|
|
2834
|
+
const file = join10(dir, name);
|
|
2744
2835
|
await ensureParent(file);
|
|
2745
2836
|
const workbook = new ExcelJS5.Workbook();
|
|
2746
2837
|
const sheet = workbook.addWorksheet("data");
|
|
@@ -2763,7 +2854,7 @@ async function fetchTickets(client, args) {
|
|
|
2763
2854
|
if (pageRows.length < pageSize2) break;
|
|
2764
2855
|
}
|
|
2765
2856
|
const output = await saveDatasource(dir, "datasource.xlsx", rows);
|
|
2766
|
-
await writeJson3(
|
|
2857
|
+
await writeJson3(join10(dir, "metadata.json"), { taskId: taskId(args), rows: rows.length, output });
|
|
2767
2858
|
return { taskId: taskId(args), output, count: rows.length };
|
|
2768
2859
|
}
|
|
2769
2860
|
async function fetchSources(client, args) {
|
|
@@ -2774,7 +2865,7 @@ async function fetchSources(client, args) {
|
|
|
2774
2865
|
const rows = asRows(value).slice(0, intOption(args, "--max-rows", 1e4));
|
|
2775
2866
|
sources.tickets = rows;
|
|
2776
2867
|
const output = await saveDatasource(dir, "datasource.xlsx", rows);
|
|
2777
|
-
await writeJson3(
|
|
2868
|
+
await writeJson3(join10(dir, "sources.json"), sources);
|
|
2778
2869
|
return { taskId: taskId(args), output, count: rows.length };
|
|
2779
2870
|
}
|
|
2780
2871
|
async function tags(client, action2, args) {
|
|
@@ -2801,10 +2892,10 @@ async function tags(client, action2, args) {
|
|
|
2801
2892
|
throw new Error(`unknown tag action: ${action2}`);
|
|
2802
2893
|
}
|
|
2803
2894
|
async function renderReport2(args) {
|
|
2804
|
-
const dir = taskDir(args), dataFile = option(args, "--report-data") ??
|
|
2895
|
+
const dir = taskDir(args), dataFile = option(args, "--report-data") ?? join10(dir, "report_data.json"), notesFile = option(args, "--notes") ?? join10(dir, "calculation_notes.json");
|
|
2805
2896
|
const data3 = await readJson3(dataFile);
|
|
2806
2897
|
if (flag(args, "--validate-only")) return { valid: true, dataFile };
|
|
2807
|
-
const output = option(args, "--output") ??
|
|
2898
|
+
const output = option(args, "--output") ?? join10(outputRoot("solvea-desk"), taskId(args), `${option(args, "--report-slug") ?? "report"}.html`);
|
|
2808
2899
|
const body = `<pre>${htmlEscape(JSON.stringify({ data: data3, notes: await readJson3(notesFile).catch(() => ({})) }, null, 2))}</pre>`;
|
|
2809
2900
|
await writeHtml(output, option(args, "--report-slug") ?? "Solvea Desk report", body);
|
|
2810
2901
|
return { output, valid: true };
|
|
@@ -2835,6 +2926,7 @@ async function runAuth(action2, args) {
|
|
|
2835
2926
|
const session = await login(option(args, "--channel") ?? "stable");
|
|
2836
2927
|
return print(publicAuthStatus(session));
|
|
2837
2928
|
}
|
|
2929
|
+
if (action2 === "ensure") return print(publicAuthStatus(await authenticated()));
|
|
2838
2930
|
if (action2 === "logout") {
|
|
2839
2931
|
await logout();
|
|
2840
2932
|
return print({ ok: true, loggedOut: true });
|
|
@@ -2850,7 +2942,7 @@ async function runAuth(action2, args) {
|
|
|
2850
2942
|
if (action2 === "refresh") {
|
|
2851
2943
|
return print(publicAuthStatus(await authenticated()));
|
|
2852
2944
|
}
|
|
2853
|
-
throw new Error("auth action must be login, logout, status, or refresh");
|
|
2945
|
+
throw new Error("auth action must be login, ensure, logout, status, or refresh");
|
|
2854
2946
|
}
|
|
2855
2947
|
async function runTenant(action2, args) {
|
|
2856
2948
|
const session = await authenticated();
|
|
@@ -2923,7 +3015,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
2923
3015
|
if (flag(args, "--scheduled")) await new Promise((resolve5) => setTimeout(resolve5, Math.random() * 18e5));
|
|
2924
3016
|
return print(await update());
|
|
2925
3017
|
}
|
|
2926
|
-
if (command === "commands" || command === "capabilities") console.log(await
|
|
3018
|
+
if (command === "commands" || command === "capabilities") console.log(await readFile7(new URL("../capabilities.lock.json", import.meta.url), "utf8"));
|
|
2927
3019
|
else throw new Error(`unknown command: ${command}`);
|
|
2928
3020
|
}
|
|
2929
3021
|
main().catch((error) => {
|