@qodercn-ai/qmind-cli 3.0.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/CHANGELOG.md +70 -0
- package/CHANGELOG.zh-CN.md +70 -0
- package/README.md +91 -0
- package/README.zh-CN.md +91 -0
- package/bin/cli.js +8 -0
- package/dist/cards-2J6ebaI0.js +75 -0
- package/dist/imports-Da88W09Q.js +126 -0
- package/dist/management-xYuUDFtg.js +312 -0
- package/dist/qmind.js +4696 -0
- package/dist/shared-DwycozMA.js +1147 -0
- package/package.json +44 -0
package/dist/qmind.js
ADDED
|
@@ -0,0 +1,4696 @@
|
|
|
1
|
+
import { $ as executeStream, A as retrieveResultSchema, B as sourceTreeSchema, C as chatSessionListSchema, E as imageUrlsSchema, F as sourceImportResultsSchema, G as streamWireEventSchema, H as sourceUploadTicketSchema, I as sourceListSchema, J as voidResponseSchema, K as taskRunListSchema, L as sourcePreviewSchema, M as sourceActionAccessSchema, N as sourceChildrenCountsSchema, O as notebookListSchema, P as sourceContentSchema, Q as assertCapability, R as sourceRawContentSchema, S as chatMessageListSchema, T as imageUploadTicketSchema, U as sourceWebOfficeSessionSchema, V as sourceUploadCapabilitiesSchema, W as sourceWebOfficeTokenSchema, X as QMIND_MULTIPART_UPLOAD_MAX_BYTES, Y as wikiBranchListSchema, Z as QMIND_SOURCE_UPLOAD_MAX_BYTES, a as invalidArgument$1, c as optionalRecord, d as pagination$1, et as executeUnary, f as positiveInteger$1, h as stringList, i as httpUrl, j as sharedNotebookListSchema, k as notebookSchema, l as optionalString$1, m as requiredString$2, n as call, o as nonNegativeInteger$1, p as queryPath, q as taskRunSchema, r as compactUndefined, s as optionalBoolean, t as assertNonEmptyPatch, tt as QMindError, u as optionalTrimmedString, v as batchMutationResultSchema, w as chatSessionSchema, y as cancelQuestionResultSchema, z as sourceSchema } from "./shared-DwycozMA.js";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
6
|
+
import { chmod, copyFile, link, lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import lockfile from "proper-lockfile";
|
|
9
|
+
import open from "open";
|
|
10
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
11
|
+
import { Readable } from "node:stream";
|
|
12
|
+
import { pipeline } from "node:stream/promises";
|
|
13
|
+
import { createParser } from "eventsource-parser";
|
|
14
|
+
import { Agent, EnvHttpProxyAgent, request } from "undici";
|
|
15
|
+
import { Command, CommanderError, Option } from "commander";
|
|
16
|
+
import pLimit from "p-limit";
|
|
17
|
+
import picomatch from "picomatch";
|
|
18
|
+
import { decodeHTML } from "entities";
|
|
19
|
+
//#region ../sdk/src/operations/agents.ts
|
|
20
|
+
const workflowPrompt = "Follow the system prompt strictly: use the provided tools to discover, process and prepare the result, then emit the final report. Do NOT greet, do NOT ask for clarification — act immediately.";
|
|
21
|
+
function isRecord$3(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function finiteNumber(value, field) {
|
|
25
|
+
if (value === void 0) return void 0;
|
|
26
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw invalidArgument$1(`${field} must be a finite number`, field);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function queryHints(value) {
|
|
30
|
+
const input = optionalRecord(value, "queryHints");
|
|
31
|
+
if (input === void 0) return void 0;
|
|
32
|
+
return compactUndefined({
|
|
33
|
+
intent: optionalTrimmedString(input.intent, "queryHints.intent"),
|
|
34
|
+
keywords: input.keywords === void 0 ? void 0 : stringList(input.keywords, "queryHints.keywords"),
|
|
35
|
+
language: optionalTrimmedString(input.language, "queryHints.language"),
|
|
36
|
+
queryVariants: input.queryVariants === void 0 ? void 0 : stringList(input.queryVariants, "queryHints.queryVariants"),
|
|
37
|
+
rewrittenQuery: optionalTrimmedString(input.rewrittenQuery, "queryHints.rewrittenQuery")
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function businessContext(value) {
|
|
41
|
+
const input = optionalRecord(value, "billingContext.business");
|
|
42
|
+
if (input === void 0) return void 0;
|
|
43
|
+
const beginAt = input.beginAt;
|
|
44
|
+
if (beginAt !== void 0 && (typeof beginAt !== "number" || !Number.isSafeInteger(beginAt))) throw invalidArgument$1("billingContext.business.beginAt must be a safe integer", "beginAt");
|
|
45
|
+
return compactUndefined({
|
|
46
|
+
beginAt,
|
|
47
|
+
id: optionalTrimmedString(input.id, "billingContext.business.id"),
|
|
48
|
+
name: optionalTrimmedString(input.name, "billingContext.business.name"),
|
|
49
|
+
product: optionalTrimmedString(input.product, "billingContext.business.product"),
|
|
50
|
+
stage: optionalTrimmedString(input.stage, "billingContext.business.stage"),
|
|
51
|
+
type: optionalTrimmedString(input.type, "billingContext.business.type"),
|
|
52
|
+
version: optionalTrimmedString(input.version, "billingContext.business.version")
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function billingContext(value) {
|
|
56
|
+
const input = optionalRecord(value, "billingContext");
|
|
57
|
+
if (input === void 0) return void 0;
|
|
58
|
+
return compactUndefined({
|
|
59
|
+
business: businessContext(input.business),
|
|
60
|
+
modelKey: optionalTrimmedString(input.modelKey, "billingContext.modelKey"),
|
|
61
|
+
sessionId: optionalTrimmedString(input.sessionId, "billingContext.sessionId")
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function retrieveBody(input) {
|
|
65
|
+
if (!isRecord$3(input)) throw invalidArgument$1("input must be an object", "input");
|
|
66
|
+
return compactUndefined({
|
|
67
|
+
billingContext: billingContext(input.billingContext),
|
|
68
|
+
maxResults: nonNegativeInteger$1(input.maxResults, "maxResults"),
|
|
69
|
+
query: requiredString$2(input.query, "query"),
|
|
70
|
+
queryHints: queryHints(input.queryHints),
|
|
71
|
+
scoreThreshold: finiteNumber(input.scoreThreshold, "scoreThreshold"),
|
|
72
|
+
sourceIds: stringList(input.sourceIds ?? [], "sourceIds"),
|
|
73
|
+
topK: nonNegativeInteger$1(input.topK, "topK")
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function chatSessionBody(input = {}) {
|
|
77
|
+
if (!isRecord$3(input)) throw invalidArgument$1("input must be an object", "input");
|
|
78
|
+
return compactUndefined({
|
|
79
|
+
metadata: optionalRecord(input.metadata, "metadata"),
|
|
80
|
+
modelOverride: optionalTrimmedString(input.modelOverride, "modelOverride"),
|
|
81
|
+
sceneType: optionalTrimmedString(input.sceneType, "sceneType") ?? "rag",
|
|
82
|
+
status: optionalTrimmedString(input.status, "status"),
|
|
83
|
+
title: optionalString$1(input.title, "title")
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function streamBody(input) {
|
|
87
|
+
if (!isRecord$3(input)) throw invalidArgument$1("input must be an object", "input");
|
|
88
|
+
return compactUndefined({
|
|
89
|
+
content: requiredString$2(input.content, "content"),
|
|
90
|
+
maxResults: nonNegativeInteger$1(input.maxResults, "maxResults"),
|
|
91
|
+
modelOverride: optionalTrimmedString(input.modelOverride, "modelOverride"),
|
|
92
|
+
sceneType: optionalTrimmedString(input.sceneType, "sceneType"),
|
|
93
|
+
sourceIds: stringList(input.sourceIds ?? [], "sourceIds"),
|
|
94
|
+
topK: nonNegativeInteger$1(input.topK, "topK")
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function taskRunBody(input) {
|
|
98
|
+
if (!isRecord$3(input)) throw invalidArgument$1("input must be an object", "input");
|
|
99
|
+
return compactUndefined({
|
|
100
|
+
chatSessionId: optionalTrimmedString(input.chatSessionId, "chatSessionId"),
|
|
101
|
+
config: optionalRecord(input.config, "config"),
|
|
102
|
+
parentRunId: optionalTrimmedString(input.parentRunId, "parentRunId"),
|
|
103
|
+
sceneType: requiredString$2(input.sceneType, "sceneType"),
|
|
104
|
+
settings: optionalRecord(input.settings, "settings"),
|
|
105
|
+
sourceId: optionalTrimmedString(input.sourceId, "sourceId"),
|
|
106
|
+
scheduledTaskId: optionalTrimmedString(input.scheduledTaskId, "scheduledTaskId"),
|
|
107
|
+
trigger: optionalTrimmedString(input.trigger, "trigger")
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function taskRunListPath(context, notebookId, input = {}) {
|
|
111
|
+
if (!isRecord$3(input)) throw invalidArgument$1("input must be an object", "input");
|
|
112
|
+
const normalizedPage = pagination$1(input);
|
|
113
|
+
const status = optionalTrimmedString(input.status, "status");
|
|
114
|
+
if (status !== void 0 && ![
|
|
115
|
+
"pending",
|
|
116
|
+
"running",
|
|
117
|
+
"success",
|
|
118
|
+
"partial",
|
|
119
|
+
"failed",
|
|
120
|
+
"canceled"
|
|
121
|
+
].includes(status)) throw invalidArgument$1("status must be a known task run status", "status");
|
|
122
|
+
return queryPath(context.profile.apiPath("notebooks", notebookId, "task-runs"), [
|
|
123
|
+
["scheduled_task_id", optionalTrimmedString(input.scheduledTaskId, "scheduledTaskId")],
|
|
124
|
+
["parent_run_id", optionalTrimmedString(input.parentRunId, "parentRunId")],
|
|
125
|
+
["scene_type", optionalTrimmedString(input.sceneType, "sceneType")],
|
|
126
|
+
["source_id", optionalTrimmedString(input.sourceId, "sourceId")],
|
|
127
|
+
["trigger", optionalTrimmedString(input.trigger, "trigger")],
|
|
128
|
+
["status", status],
|
|
129
|
+
["page", normalizedPage.page],
|
|
130
|
+
["page_size", normalizedPage.pageSize]
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
function agentContent(input) {
|
|
134
|
+
if (input.scene === "rag") return {
|
|
135
|
+
content: requiredString$2(input.question, "question"),
|
|
136
|
+
maxResults: input.maxResults,
|
|
137
|
+
modelOverride: input.modelOverride,
|
|
138
|
+
sceneType: "rag",
|
|
139
|
+
sourceIds: input.sourceIds,
|
|
140
|
+
topK: input.topK
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
content: `Begin the ${input.scene} workflow now for this notebook. ${workflowPrompt}`,
|
|
144
|
+
modelOverride: input.modelOverride,
|
|
145
|
+
sceneType: input.scene
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function callOptions(options) {
|
|
149
|
+
return {
|
|
150
|
+
...options?.signal === void 0 ? {} : { signal: options.signal },
|
|
151
|
+
...options?.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function streamCallOptions(options) {
|
|
155
|
+
return {
|
|
156
|
+
...callOptions(options),
|
|
157
|
+
...options?.connectTimeoutMs === void 0 ? {} : { connectTimeoutMs: options.connectTimeoutMs },
|
|
158
|
+
...options?.idleTimeoutMs === void 0 ? {} : { idleTimeoutMs: options.idleTimeoutMs }
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function createAgentOperations(context) {
|
|
162
|
+
const createChatSessionFor = (notebookId, input, options, featured) => {
|
|
163
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
164
|
+
return call(context, {
|
|
165
|
+
body: chatSessionBody(input),
|
|
166
|
+
callOptions: options,
|
|
167
|
+
capability: featured ? "featuredNotebooks.read" : "chat",
|
|
168
|
+
idempotent: false,
|
|
169
|
+
method: "POST",
|
|
170
|
+
operation: featured ? "featuredChatSessions.create" : "chatSessions.create",
|
|
171
|
+
path: context.profile.apiPath(featured ? "featured-notebooks" : "notebooks", notebook, "chat-sessions"),
|
|
172
|
+
schema: chatSessionSchema
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
const createChatSession = (notebookId, input, options) => createChatSessionFor(notebookId, input, options, false);
|
|
176
|
+
const createFeaturedChatSession = (notebookId, input, options) => createChatSessionFor(notebookId, input, options, true);
|
|
177
|
+
const streamQuestionFor = (notebookId, sessionId, input, options, featured) => {
|
|
178
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
179
|
+
const session = requiredString$2(sessionId, "sessionId");
|
|
180
|
+
return executeStream({
|
|
181
|
+
capability: featured ? "featuredNotebooks.read" : "chat",
|
|
182
|
+
operation: featured ? "featuredChat.stream" : "chat.stream",
|
|
183
|
+
profile: context.profile,
|
|
184
|
+
request: {
|
|
185
|
+
body: {
|
|
186
|
+
kind: "json",
|
|
187
|
+
value: streamBody(input)
|
|
188
|
+
},
|
|
189
|
+
destination: {
|
|
190
|
+
kind: "host",
|
|
191
|
+
path: queryPath(context.profile.apiPath(featured ? "featured-notebooks" : "notebooks", notebook, "chat-sessions", session, "messages", "stream"), [["client", context.client]])
|
|
192
|
+
},
|
|
193
|
+
idempotent: false,
|
|
194
|
+
method: "POST",
|
|
195
|
+
...streamCallOptions(options)
|
|
196
|
+
},
|
|
197
|
+
transport: context.transport
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
const streamQuestion = (notebookId, sessionId, input, options) => streamQuestionFor(notebookId, sessionId, input, options, false);
|
|
201
|
+
const streamFeaturedQuestion = (notebookId, sessionId, input, options) => streamQuestionFor(notebookId, sessionId, input, options, true);
|
|
202
|
+
const cancelQuestionFor = (notebookId, sessionId, input = {}, options, featured = false) => {
|
|
203
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
204
|
+
const session = requiredString$2(sessionId, "sessionId");
|
|
205
|
+
if (!isRecord$3(input)) throw invalidArgument$1("input must be an object", "input");
|
|
206
|
+
return call(context, {
|
|
207
|
+
body: compactUndefined({ reason: optionalTrimmedString(input.reason, "reason") }),
|
|
208
|
+
callOptions: options,
|
|
209
|
+
capability: featured ? "featuredNotebooks.read" : "chat",
|
|
210
|
+
idempotent: false,
|
|
211
|
+
method: "POST",
|
|
212
|
+
operation: featured ? "featuredChat.cancel" : "chat.cancel",
|
|
213
|
+
path: context.profile.apiPath(featured ? "featured-notebooks" : "notebooks", notebook, "chat-sessions", session, "messages", "cancel"),
|
|
214
|
+
schema: cancelQuestionResultSchema
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
const listChatSessionsFor = (notebookId, input = {}, options, featured = false) => {
|
|
218
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
219
|
+
const normalizedPage = pagination$1({
|
|
220
|
+
page: input.page ?? 1,
|
|
221
|
+
pageSize: input.pageSize ?? 20
|
|
222
|
+
});
|
|
223
|
+
return call(context, {
|
|
224
|
+
callOptions: options,
|
|
225
|
+
capability: featured ? "featuredNotebooks.read" : "chat",
|
|
226
|
+
idempotent: true,
|
|
227
|
+
method: "GET",
|
|
228
|
+
operation: featured ? "featuredChatSessions.list" : "chatSessions.list",
|
|
229
|
+
path: queryPath(context.profile.apiPath(featured ? "featured-notebooks" : "notebooks", notebook, "chat-sessions"), [["page", normalizedPage.page], ["page_size", normalizedPage.pageSize]]),
|
|
230
|
+
schema: chatSessionListSchema
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
const listChatMessagesFor = (notebookId, sessionId, input = {}, options, featured = false) => {
|
|
234
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
235
|
+
const session = requiredString$2(sessionId, "sessionId");
|
|
236
|
+
const normalizedPage = pagination$1({
|
|
237
|
+
page: input.page ?? 1,
|
|
238
|
+
pageSize: input.pageSize ?? 100
|
|
239
|
+
});
|
|
240
|
+
return call(context, {
|
|
241
|
+
callOptions: options,
|
|
242
|
+
capability: featured ? "featuredNotebooks.read" : "chat",
|
|
243
|
+
idempotent: true,
|
|
244
|
+
method: "GET",
|
|
245
|
+
operation: featured ? "featuredChatMessages.list" : "chatMessages.list",
|
|
246
|
+
path: queryPath(context.profile.apiPath(featured ? "featured-notebooks" : "notebooks", notebook, "chat-sessions", session, "messages"), [["page", normalizedPage.page], ["page_size", normalizedPage.pageSize]]),
|
|
247
|
+
schema: chatMessageListSchema
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
const deleteChatSessionFor = (notebookId, sessionId, options, featured = false) => {
|
|
251
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
252
|
+
const session = requiredString$2(sessionId, "sessionId");
|
|
253
|
+
return call(context, {
|
|
254
|
+
callOptions: options,
|
|
255
|
+
capability: featured ? "featuredNotebooks.read" : "chat",
|
|
256
|
+
idempotent: false,
|
|
257
|
+
method: "DELETE",
|
|
258
|
+
operation: featured ? "featuredChatSessions.delete" : "chatSessions.delete",
|
|
259
|
+
path: context.profile.apiPath(featured ? "featured-notebooks" : "notebooks", notebook, "chat-sessions", session),
|
|
260
|
+
schema: voidResponseSchema
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
const startAgentRunFor = async (notebookId, input, options, featured) => {
|
|
264
|
+
if (!isRecord$3(input)) throw invalidArgument$1("input must be an object", "input");
|
|
265
|
+
if (input.scene !== "rag" && input.scene !== "compilation" && input.scene !== "lint") throw invalidArgument$1("scene must be rag, compilation or lint", "scene");
|
|
266
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
267
|
+
const scene = input.scene;
|
|
268
|
+
const modelOverride = optionalTrimmedString(input.modelOverride, "modelOverride");
|
|
269
|
+
const title = optionalString$1(input.title, "title");
|
|
270
|
+
const session = await (featured ? createFeaturedChatSession : createChatSession)(notebook, {
|
|
271
|
+
...modelOverride === void 0 ? {} : { modelOverride },
|
|
272
|
+
sceneType: scene,
|
|
273
|
+
...title === void 0 ? {} : { title }
|
|
274
|
+
}, options);
|
|
275
|
+
return {
|
|
276
|
+
events: (featured ? streamFeaturedQuestion : streamQuestion)(notebook, session.id, agentContent(input), options),
|
|
277
|
+
scene,
|
|
278
|
+
session
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
return {
|
|
282
|
+
cancelFeaturedQuestion: (notebookId, sessionId, input, options) => cancelQuestionFor(notebookId, sessionId, input, options, true),
|
|
283
|
+
cancelQuestion: (notebookId, sessionId, input, options) => cancelQuestionFor(notebookId, sessionId, input, options),
|
|
284
|
+
createChatSession,
|
|
285
|
+
createFeaturedChatSession,
|
|
286
|
+
deleteChatSession: (notebookId, sessionId, options) => deleteChatSessionFor(notebookId, sessionId, options),
|
|
287
|
+
deleteFeaturedChatSession: (notebookId, sessionId, options) => deleteChatSessionFor(notebookId, sessionId, options, true),
|
|
288
|
+
async createTaskRun(notebookId, input, options) {
|
|
289
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
290
|
+
return call(context, {
|
|
291
|
+
body: taskRunBody(input),
|
|
292
|
+
callOptions: options,
|
|
293
|
+
capability: "taskRuns.write",
|
|
294
|
+
idempotent: false,
|
|
295
|
+
method: "POST",
|
|
296
|
+
operation: "taskRuns.create",
|
|
297
|
+
path: context.profile.apiPath("notebooks", notebook, "task-runs"),
|
|
298
|
+
schema: taskRunSchema
|
|
299
|
+
});
|
|
300
|
+
},
|
|
301
|
+
async getTaskRun(notebookId, runId, options) {
|
|
302
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
303
|
+
const run = requiredString$2(runId, "runId");
|
|
304
|
+
return call(context, {
|
|
305
|
+
callOptions: options,
|
|
306
|
+
capability: "taskRuns.read",
|
|
307
|
+
idempotent: true,
|
|
308
|
+
method: "GET",
|
|
309
|
+
operation: "taskRuns.get",
|
|
310
|
+
path: context.profile.apiPath("notebooks", notebook, "task-runs", run),
|
|
311
|
+
schema: taskRunSchema
|
|
312
|
+
});
|
|
313
|
+
},
|
|
314
|
+
async listTaskRuns(notebookId, input, options) {
|
|
315
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
316
|
+
return call(context, {
|
|
317
|
+
callOptions: options,
|
|
318
|
+
capability: "taskRuns.read",
|
|
319
|
+
idempotent: true,
|
|
320
|
+
method: "GET",
|
|
321
|
+
operation: "taskRuns.list",
|
|
322
|
+
path: taskRunListPath(context, notebook, input),
|
|
323
|
+
schema: taskRunListSchema
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
listChatMessages: (notebookId, sessionId, input, options) => listChatMessagesFor(notebookId, sessionId, input, options),
|
|
327
|
+
listChatSessions: (notebookId, input, options) => listChatSessionsFor(notebookId, input, options),
|
|
328
|
+
listFeaturedChatMessages: (notebookId, sessionId, input, options) => listChatMessagesFor(notebookId, sessionId, input, options, true),
|
|
329
|
+
listFeaturedChatSessions: (notebookId, input, options) => listChatSessionsFor(notebookId, input, options, true),
|
|
330
|
+
async retrieve(notebookId, input, options) {
|
|
331
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
332
|
+
return call(context, {
|
|
333
|
+
body: retrieveBody(input),
|
|
334
|
+
callOptions: options,
|
|
335
|
+
capability: "retrieval",
|
|
336
|
+
idempotent: false,
|
|
337
|
+
method: "POST",
|
|
338
|
+
operation: "retrieval.retrieve",
|
|
339
|
+
path: context.profile.apiPath("notebooks", notebook, "retrieve"),
|
|
340
|
+
schema: retrieveResultSchema
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
startAgentRun: (notebookId, input, options) => startAgentRunFor(notebookId, input, options, false),
|
|
344
|
+
startFeaturedAgentRun: (notebookId, input, options) => startAgentRunFor(notebookId, input, options, true),
|
|
345
|
+
streamFeaturedQuestion,
|
|
346
|
+
streamQuestion
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region ../sdk/src/operations/notebooks.ts
|
|
351
|
+
function createNotebookOperations(context) {
|
|
352
|
+
return {
|
|
353
|
+
async createNotebook(input, options) {
|
|
354
|
+
const title = requiredString$2(input?.title, "title");
|
|
355
|
+
const description = optionalString$1(input?.description, "description");
|
|
356
|
+
const settings = optionalRecord(input?.settings, "settings");
|
|
357
|
+
return call(context, {
|
|
358
|
+
body: compactUndefined({
|
|
359
|
+
client: context.client,
|
|
360
|
+
description,
|
|
361
|
+
settings,
|
|
362
|
+
title
|
|
363
|
+
}),
|
|
364
|
+
callOptions: options,
|
|
365
|
+
capability: "notebooks.write",
|
|
366
|
+
idempotent: false,
|
|
367
|
+
method: "POST",
|
|
368
|
+
operation: "notebooks.create",
|
|
369
|
+
path: context.profile.apiPath("notebooks"),
|
|
370
|
+
schema: notebookSchema
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
async deleteNotebook(notebookId, options) {
|
|
374
|
+
const id = requiredString$2(notebookId, "notebookId");
|
|
375
|
+
return call(context, {
|
|
376
|
+
callOptions: options,
|
|
377
|
+
capability: "notebooks.write",
|
|
378
|
+
idempotent: false,
|
|
379
|
+
method: "DELETE",
|
|
380
|
+
operation: "notebooks.delete",
|
|
381
|
+
path: context.profile.apiPath("notebooks", id),
|
|
382
|
+
schema: voidResponseSchema
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
async getNotebook(notebookId, options) {
|
|
386
|
+
const id = requiredString$2(notebookId, "notebookId");
|
|
387
|
+
return call(context, {
|
|
388
|
+
callOptions: options,
|
|
389
|
+
capability: "notebooks.read",
|
|
390
|
+
idempotent: true,
|
|
391
|
+
method: "GET",
|
|
392
|
+
operation: "notebooks.get",
|
|
393
|
+
path: context.profile.apiPath("notebooks", id),
|
|
394
|
+
schema: notebookSchema
|
|
395
|
+
});
|
|
396
|
+
},
|
|
397
|
+
async getFeaturedNotebook(notebookId, options) {
|
|
398
|
+
const id = requiredString$2(notebookId, "notebookId");
|
|
399
|
+
return call(context, {
|
|
400
|
+
callOptions: options,
|
|
401
|
+
capability: "featuredNotebooks.read",
|
|
402
|
+
idempotent: true,
|
|
403
|
+
method: "GET",
|
|
404
|
+
operation: "featuredNotebooks.get",
|
|
405
|
+
path: context.profile.featuredNotebookPath(id),
|
|
406
|
+
schema: notebookSchema
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
async listFeaturedNotebooks(input = {}, options) {
|
|
410
|
+
const normalized = pagination$1(input);
|
|
411
|
+
return call(context, {
|
|
412
|
+
callOptions: options,
|
|
413
|
+
capability: "featuredNotebooks.read",
|
|
414
|
+
idempotent: true,
|
|
415
|
+
method: "GET",
|
|
416
|
+
operation: "featuredNotebooks.list",
|
|
417
|
+
path: context.profile.listFeaturedNotebooksPath(normalized),
|
|
418
|
+
schema: notebookListSchema
|
|
419
|
+
});
|
|
420
|
+
},
|
|
421
|
+
async listNotebooks(input = {}, options) {
|
|
422
|
+
const normalizedPage = pagination$1(input);
|
|
423
|
+
const client = optionalTrimmedString(input.client, "client");
|
|
424
|
+
if (client !== void 0 && !context.profile.capabilities["notebooks.filterByClient"]) assertCapability(context.profile, "notebooks.filterByClient", "notebooks.list");
|
|
425
|
+
const normalized = {
|
|
426
|
+
...normalizedPage,
|
|
427
|
+
...client === void 0 ? {} : { client }
|
|
428
|
+
};
|
|
429
|
+
return call(context, {
|
|
430
|
+
callOptions: options,
|
|
431
|
+
capability: "notebooks.read",
|
|
432
|
+
idempotent: true,
|
|
433
|
+
method: "GET",
|
|
434
|
+
operation: "notebooks.list",
|
|
435
|
+
path: context.profile.listNotebooksPath(normalized),
|
|
436
|
+
schema: notebookListSchema
|
|
437
|
+
});
|
|
438
|
+
},
|
|
439
|
+
async listSharedNotebooks(input = {}, options) {
|
|
440
|
+
const normalized = pagination$1(input);
|
|
441
|
+
return call(context, {
|
|
442
|
+
callOptions: options,
|
|
443
|
+
capability: "sharedNotebooks.read",
|
|
444
|
+
idempotent: true,
|
|
445
|
+
method: "GET",
|
|
446
|
+
operation: "sharedNotebooks.list",
|
|
447
|
+
path: context.profile.listSharedNotebooksPath(normalized),
|
|
448
|
+
schema: sharedNotebookListSchema
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
async listWikiBranches(notebookId, options) {
|
|
452
|
+
const id = requiredString$2(notebookId, "notebookId");
|
|
453
|
+
return (await call(context, {
|
|
454
|
+
callOptions: options,
|
|
455
|
+
capability: "wikiBranches.read",
|
|
456
|
+
idempotent: true,
|
|
457
|
+
method: "GET",
|
|
458
|
+
operation: "wikiBranches.list",
|
|
459
|
+
path: context.profile.apiPath("notebooks", id, "wiki-branches"),
|
|
460
|
+
schema: wikiBranchListSchema
|
|
461
|
+
})).branches;
|
|
462
|
+
},
|
|
463
|
+
async updateNotebook(notebookId, input, options) {
|
|
464
|
+
const id = requiredString$2(notebookId, "notebookId");
|
|
465
|
+
const body = compactUndefined({
|
|
466
|
+
description: optionalString$1(input?.description, "description"),
|
|
467
|
+
settings: optionalRecord(input?.settings, "settings"),
|
|
468
|
+
status: optionalString$1(input?.status, "status"),
|
|
469
|
+
title: input?.title === void 0 ? void 0 : requiredString$2(input.title, "title")
|
|
470
|
+
});
|
|
471
|
+
assertNonEmptyPatch(body, "input");
|
|
472
|
+
return call(context, {
|
|
473
|
+
body,
|
|
474
|
+
callOptions: options,
|
|
475
|
+
capability: "notebooks.write",
|
|
476
|
+
idempotent: false,
|
|
477
|
+
method: "PUT",
|
|
478
|
+
operation: "notebooks.update",
|
|
479
|
+
path: context.profile.apiPath("notebooks", id),
|
|
480
|
+
schema: notebookSchema
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region ../sdk/src/operations/sources.ts
|
|
487
|
+
const maxImageBytes = 52428800;
|
|
488
|
+
function sourceTreeNodeOrder(left, right) {
|
|
489
|
+
if (left.type !== right.type) return left.type === "directory" ? -1 : 1;
|
|
490
|
+
if (left.name !== right.name) return left.name < right.name ? -1 : 1;
|
|
491
|
+
return left.id < right.id ? -1 : left.id === right.id ? 0 : 1;
|
|
492
|
+
}
|
|
493
|
+
function sortSourceTree(nodes) {
|
|
494
|
+
nodes.sort(sourceTreeNodeOrder);
|
|
495
|
+
for (const node of nodes) sortSourceTree(node.files);
|
|
496
|
+
}
|
|
497
|
+
function createsSourceCycle(source, sourcesById) {
|
|
498
|
+
const seen = /* @__PURE__ */ new Set([source.id]);
|
|
499
|
+
let parentId = source.parentSourceId;
|
|
500
|
+
while (parentId.length > 0) {
|
|
501
|
+
if (seen.has(parentId)) return true;
|
|
502
|
+
seen.add(parentId);
|
|
503
|
+
const parent = sourcesById.get(parentId);
|
|
504
|
+
if (parent === void 0) return false;
|
|
505
|
+
parentId = parent.parentSourceId;
|
|
506
|
+
}
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
function buildSourceTree(sources) {
|
|
510
|
+
const sourcesById = new Map(sources.map((source) => [source.id, source]));
|
|
511
|
+
const parentIds = new Set(sources.filter((source) => sourcesById.has(source.parentSourceId) && !createsSourceCycle(source, sourcesById)).map((source) => source.parentSourceId));
|
|
512
|
+
const nodesById = /* @__PURE__ */ new Map();
|
|
513
|
+
for (const source of sources) nodesById.set(source.id, {
|
|
514
|
+
files: [],
|
|
515
|
+
id: source.id,
|
|
516
|
+
name: source.title,
|
|
517
|
+
path: source.path,
|
|
518
|
+
sourceType: source.sourceType,
|
|
519
|
+
status: source.status,
|
|
520
|
+
type: source.isDir || parentIds.has(source.id) ? "directory" : "file"
|
|
521
|
+
});
|
|
522
|
+
const tree = [];
|
|
523
|
+
for (const source of sources) {
|
|
524
|
+
const node = nodesById.get(source.id);
|
|
525
|
+
if (node === void 0) continue;
|
|
526
|
+
const parent = nodesById.get(source.parentSourceId);
|
|
527
|
+
if (parent === void 0 || createsSourceCycle(source, sourcesById)) tree.push(node);
|
|
528
|
+
else parent.files.push(node);
|
|
529
|
+
}
|
|
530
|
+
sortSourceTree(tree);
|
|
531
|
+
return {
|
|
532
|
+
totalSize: sources.length,
|
|
533
|
+
tree
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function sourceBody(input) {
|
|
537
|
+
return compactUndefined({
|
|
538
|
+
isDir: optionalBoolean(input?.isDir, "isDir"),
|
|
539
|
+
metadata: optionalRecord(input?.metadata, "metadata"),
|
|
540
|
+
parentSourceId: optionalString$1(input?.parentSourceId, "parentSourceId"),
|
|
541
|
+
path: optionalString$1(input?.path, "path"),
|
|
542
|
+
sourceType: optionalTrimmedString(input?.sourceType, "sourceType"),
|
|
543
|
+
status: optionalTrimmedString(input?.status, "status"),
|
|
544
|
+
title: requiredString$2(input?.title, "title"),
|
|
545
|
+
uri: optionalString$1(input?.uri, "uri")
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
function sourcePatch(input) {
|
|
549
|
+
const patch = compactUndefined({
|
|
550
|
+
content: optionalString$1(input?.content, "content"),
|
|
551
|
+
errorMessage: optionalString$1(input?.errorMessage, "errorMessage"),
|
|
552
|
+
isDir: optionalBoolean(input?.isDir, "isDir"),
|
|
553
|
+
metadata: optionalRecord(input?.metadata, "metadata"),
|
|
554
|
+
parentSourceId: optionalString$1(input?.parentSourceId, "parentSourceId"),
|
|
555
|
+
path: optionalString$1(input?.path, "path"),
|
|
556
|
+
sourceType: optionalString$1(input?.sourceType, "sourceType"),
|
|
557
|
+
status: optionalString$1(input?.status, "status"),
|
|
558
|
+
title: optionalString$1(input?.title, "title"),
|
|
559
|
+
uri: optionalString$1(input?.uri, "uri")
|
|
560
|
+
});
|
|
561
|
+
assertNonEmptyPatch(patch, "input");
|
|
562
|
+
return patch;
|
|
563
|
+
}
|
|
564
|
+
function siteBody(input) {
|
|
565
|
+
const maxPages = positiveInteger$1(input?.maxPages, "maxPages") ?? 50;
|
|
566
|
+
if (maxPages > 200) throw invalidArgument$1("maxPages must not exceed 200", "maxPages");
|
|
567
|
+
const includePaths = stringList(input?.includePaths ?? [], "includePaths");
|
|
568
|
+
const excludePaths = stringList(input?.excludePaths ?? [], "excludePaths");
|
|
569
|
+
const sitemap = optionalTrimmedString(input?.sitemapUrl, "sitemapUrl");
|
|
570
|
+
const compileAfterRefresh = optionalBoolean(input?.compileAfterRefresh, "compileAfterRefresh");
|
|
571
|
+
return compactUndefined({
|
|
572
|
+
config: compactUndefined({
|
|
573
|
+
compileAfterRefresh,
|
|
574
|
+
excludePaths,
|
|
575
|
+
includePaths,
|
|
576
|
+
maxPages,
|
|
577
|
+
rootUrl: httpUrl(input?.rootUrl, "rootUrl"),
|
|
578
|
+
sitemapUrl: sitemap === void 0 ? void 0 : httpUrl(sitemap, "sitemapUrl")
|
|
579
|
+
}),
|
|
580
|
+
parentSourceId: optionalString$1(input?.parentSourceId, "parentSourceId"),
|
|
581
|
+
path: optionalString$1(input?.pathPrefix, "pathPrefix"),
|
|
582
|
+
sourceType: "site_import",
|
|
583
|
+
title: optionalTrimmedString(input?.title, "title")
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
function createSourceOperations(context) {
|
|
587
|
+
const createSource = async (notebookId, input, options) => {
|
|
588
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
589
|
+
return call(context, {
|
|
590
|
+
body: sourceBody(input),
|
|
591
|
+
callOptions: options,
|
|
592
|
+
capability: "sources.write",
|
|
593
|
+
idempotent: false,
|
|
594
|
+
method: "POST",
|
|
595
|
+
operation: "sources.create",
|
|
596
|
+
path: context.profile.apiPath("notebooks", notebook, "sources"),
|
|
597
|
+
schema: sourceSchema
|
|
598
|
+
});
|
|
599
|
+
};
|
|
600
|
+
const updateSource = async (notebookId, sourceId, input, options) => {
|
|
601
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
602
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
603
|
+
return call(context, {
|
|
604
|
+
body: sourcePatch(input),
|
|
605
|
+
callOptions: options,
|
|
606
|
+
capability: "sources.write",
|
|
607
|
+
idempotent: false,
|
|
608
|
+
method: "PUT",
|
|
609
|
+
operation: "sources.update",
|
|
610
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source),
|
|
611
|
+
schema: sourceSchema
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
const readSourcePage = async (notebookId, input = {}, options, operation = "sources.list") => {
|
|
615
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
616
|
+
const normalizedPage = pagination$1(input);
|
|
617
|
+
const isDir = optionalBoolean(input.isDir, "isDir");
|
|
618
|
+
const view = input.view ?? "list";
|
|
619
|
+
if (view !== "list" && view !== "children") throw invalidArgument$1("view must be either \"list\" or \"children\"", "view");
|
|
620
|
+
const parentSourceId = optionalString$1(input.parentSourceId, "parentSourceId");
|
|
621
|
+
const query = optionalTrimmedString(input.query, "query");
|
|
622
|
+
if (view === "children") assertCapability(context.profile, "sources.children", "sources.list");
|
|
623
|
+
if (query !== void 0) assertCapability(context.profile, "sources.search", "sources.list");
|
|
624
|
+
if (parentSourceId !== void 0 && view !== "children") throw invalidArgument$1("parentSourceId requires view=\"children\"", "parentSourceId");
|
|
625
|
+
const path = queryPath(context.profile.apiPath("notebooks", notebook, "sources"), [
|
|
626
|
+
["view", view === "list" ? void 0 : view],
|
|
627
|
+
["is_dir", isDir === void 0 ? void 0 : String(isDir)],
|
|
628
|
+
["parent_source_id", parentSourceId],
|
|
629
|
+
["page", normalizedPage.page],
|
|
630
|
+
["page_size", normalizedPage.pageSize],
|
|
631
|
+
["query", query]
|
|
632
|
+
]);
|
|
633
|
+
return call(context, {
|
|
634
|
+
callOptions: options,
|
|
635
|
+
capability: "sources.read",
|
|
636
|
+
idempotent: true,
|
|
637
|
+
method: "GET",
|
|
638
|
+
operation,
|
|
639
|
+
path,
|
|
640
|
+
schema: sourceListSchema
|
|
641
|
+
});
|
|
642
|
+
};
|
|
643
|
+
const listSources = (notebookId, input, options) => readSourcePage(notebookId, input, options);
|
|
644
|
+
const listFeaturedSources = async (notebookId, input = {}, options) => {
|
|
645
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
646
|
+
const normalizedPage = pagination$1({
|
|
647
|
+
page: input.page ?? 1,
|
|
648
|
+
pageSize: input.pageSize ?? 200
|
|
649
|
+
});
|
|
650
|
+
const parentSourceId = optionalString$1(input.parentSourceId, "parentSourceId");
|
|
651
|
+
const view = input.view ?? "list";
|
|
652
|
+
if (view !== "list" && view !== "children") throw invalidArgument$1("view must be either \"list\" or \"children\"", "view");
|
|
653
|
+
return call(context, {
|
|
654
|
+
callOptions: options,
|
|
655
|
+
capability: "featuredNotebooks.read",
|
|
656
|
+
idempotent: true,
|
|
657
|
+
method: "GET",
|
|
658
|
+
operation: "featuredSources.list",
|
|
659
|
+
path: queryPath(context.profile.apiPath("featured-notebooks", notebook, "sources"), [
|
|
660
|
+
["view", view === "list" ? void 0 : view],
|
|
661
|
+
["parent_source_id", parentSourceId],
|
|
662
|
+
["page", normalizedPage.page],
|
|
663
|
+
["page_size", normalizedPage.pageSize]
|
|
664
|
+
]),
|
|
665
|
+
schema: sourceListSchema
|
|
666
|
+
});
|
|
667
|
+
};
|
|
668
|
+
const collectAllSources = async (notebookId, input = {}, options, operation) => {
|
|
669
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
670
|
+
const pageSize = positiveInteger$1(input.pageSize, "pageSize") ?? 200;
|
|
671
|
+
const maxPages = positiveInteger$1(input.maxPages, "maxPages") ?? 1e4;
|
|
672
|
+
const sources = [];
|
|
673
|
+
for (let page = 1; page <= maxPages; page += 1) {
|
|
674
|
+
const result = await readSourcePage(notebook, {
|
|
675
|
+
page,
|
|
676
|
+
pageSize,
|
|
677
|
+
view: "list"
|
|
678
|
+
}, options, operation);
|
|
679
|
+
sources.push(...result.sources);
|
|
680
|
+
const lastPage = result.pages?.lastPage;
|
|
681
|
+
if (result.sources.length < pageSize || lastPage !== void 0 && page >= lastPage) return sources;
|
|
682
|
+
}
|
|
683
|
+
throw new QMindError("PROTOCOL_ERROR", `source pagination exceeded ${maxPages} pages`, {
|
|
684
|
+
access: context.profile.access,
|
|
685
|
+
details: {
|
|
686
|
+
maxPages,
|
|
687
|
+
pageSize
|
|
688
|
+
},
|
|
689
|
+
operation
|
|
690
|
+
});
|
|
691
|
+
};
|
|
692
|
+
const listAllSources = (notebookId, input, options) => collectAllSources(notebookId, input, options, "sources.list");
|
|
693
|
+
const getSourceTree = async (notebookId, options) => {
|
|
694
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
695
|
+
assertCapability(context.profile, "sources.tree", "sources.tree");
|
|
696
|
+
if (context.profile.access === "sash") return buildSourceTree(await collectAllSources(notebook, {}, options, "sources.tree"));
|
|
697
|
+
return call(context, {
|
|
698
|
+
callOptions: options,
|
|
699
|
+
capability: "sources.tree",
|
|
700
|
+
idempotent: true,
|
|
701
|
+
method: "GET",
|
|
702
|
+
operation: "sources.tree",
|
|
703
|
+
path: queryPath(context.profile.apiPath("notebooks", notebook, "sources"), [["view", "filetree"]]),
|
|
704
|
+
schema: sourceTreeSchema
|
|
705
|
+
});
|
|
706
|
+
};
|
|
707
|
+
return {
|
|
708
|
+
async batchDeleteSources(notebookId, sourceIds, options) {
|
|
709
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
710
|
+
const ids = stringList(sourceIds, "sourceIds", { min: 1 });
|
|
711
|
+
return call(context, {
|
|
712
|
+
body: { sourceIds: ids },
|
|
713
|
+
callOptions: options,
|
|
714
|
+
capability: "sources.write",
|
|
715
|
+
idempotent: false,
|
|
716
|
+
method: "POST",
|
|
717
|
+
operation: "sources.batchDelete",
|
|
718
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", "batch-delete"),
|
|
719
|
+
schema: batchMutationResultSchema
|
|
720
|
+
});
|
|
721
|
+
},
|
|
722
|
+
async countSourceChildren(notebookId, parentIds, options) {
|
|
723
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
724
|
+
const ids = stringList(parentIds, "parentIds", {
|
|
725
|
+
max: 200,
|
|
726
|
+
min: 1
|
|
727
|
+
});
|
|
728
|
+
assertCapability(context.profile, "sources.childrenCount", "sources.childrenCount");
|
|
729
|
+
const path = context.profile.mindPath("notebooks", notebook, "sources", "children-count");
|
|
730
|
+
const query = new URLSearchParams();
|
|
731
|
+
for (const id of ids) query.append("parent_ids", id);
|
|
732
|
+
return call(context, {
|
|
733
|
+
callOptions: options,
|
|
734
|
+
capability: "sources.childrenCount",
|
|
735
|
+
idempotent: true,
|
|
736
|
+
method: "GET",
|
|
737
|
+
operation: "sources.childrenCount",
|
|
738
|
+
path: `${path}?${query.toString()}`,
|
|
739
|
+
schema: sourceChildrenCountsSchema
|
|
740
|
+
});
|
|
741
|
+
},
|
|
742
|
+
async createDirectory(notebookId, input, options) {
|
|
743
|
+
const title = requiredString$2(input?.title, "title");
|
|
744
|
+
return createSource(notebookId, {
|
|
745
|
+
isDir: true,
|
|
746
|
+
parentSourceId: input?.parentSourceId,
|
|
747
|
+
path: input?.path,
|
|
748
|
+
status: "ready",
|
|
749
|
+
title
|
|
750
|
+
}, options);
|
|
751
|
+
},
|
|
752
|
+
createSource,
|
|
753
|
+
async createSourceWebOfficeSession(notebookId, sourceId, options) {
|
|
754
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
755
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
756
|
+
assertCapability(context.profile, "sources.webOffice", "sources.webOffice.create");
|
|
757
|
+
return call(context, {
|
|
758
|
+
callOptions: options,
|
|
759
|
+
capability: "sources.webOffice",
|
|
760
|
+
idempotent: false,
|
|
761
|
+
method: "POST",
|
|
762
|
+
operation: "sources.webOffice.create",
|
|
763
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source, "weboffice", "session"),
|
|
764
|
+
schema: sourceWebOfficeSessionSchema
|
|
765
|
+
});
|
|
766
|
+
},
|
|
767
|
+
async deleteSource(notebookId, sourceId, options) {
|
|
768
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
769
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
770
|
+
return call(context, {
|
|
771
|
+
callOptions: options,
|
|
772
|
+
capability: "sources.write",
|
|
773
|
+
idempotent: false,
|
|
774
|
+
method: "DELETE",
|
|
775
|
+
operation: "sources.delete",
|
|
776
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source),
|
|
777
|
+
schema: voidResponseSchema
|
|
778
|
+
});
|
|
779
|
+
},
|
|
780
|
+
async getFeaturedSource(notebookId, sourceId, options) {
|
|
781
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
782
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
783
|
+
return call(context, {
|
|
784
|
+
callOptions: options,
|
|
785
|
+
capability: "featuredNotebooks.read",
|
|
786
|
+
idempotent: true,
|
|
787
|
+
method: "GET",
|
|
788
|
+
operation: "featuredSources.get",
|
|
789
|
+
path: context.profile.apiPath("featured-notebooks", notebook, "sources", source),
|
|
790
|
+
schema: sourceSchema
|
|
791
|
+
});
|
|
792
|
+
},
|
|
793
|
+
async getFeaturedSourceContent(notebookId, sourceId, options) {
|
|
794
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
795
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
796
|
+
return call(context, {
|
|
797
|
+
callOptions: options,
|
|
798
|
+
capability: "featuredNotebooks.read",
|
|
799
|
+
idempotent: true,
|
|
800
|
+
method: "GET",
|
|
801
|
+
operation: "featuredSources.content",
|
|
802
|
+
path: context.profile.apiPath("featured-notebooks", notebook, "sources", source, "content"),
|
|
803
|
+
schema: sourceContentSchema
|
|
804
|
+
});
|
|
805
|
+
},
|
|
806
|
+
async getFeaturedSourcePreview(notebookId, sourceId, options) {
|
|
807
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
808
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
809
|
+
return call(context, {
|
|
810
|
+
callOptions: options,
|
|
811
|
+
capability: "featuredNotebooks.read",
|
|
812
|
+
idempotent: true,
|
|
813
|
+
method: "GET",
|
|
814
|
+
operation: "featuredSources.preview",
|
|
815
|
+
path: context.profile.apiPath("featured-notebooks", notebook, "sources", source, "preview"),
|
|
816
|
+
schema: sourcePreviewSchema
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
async getFeaturedSourceRawContent(notebookId, sourceId, options) {
|
|
820
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
821
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
822
|
+
return call(context, {
|
|
823
|
+
callOptions: options,
|
|
824
|
+
capability: "featuredNotebooks.read",
|
|
825
|
+
idempotent: true,
|
|
826
|
+
method: "GET",
|
|
827
|
+
operation: "featuredSources.rawContent",
|
|
828
|
+
path: context.profile.apiPath("featured-notebooks", notebook, "sources", source, "raw-content"),
|
|
829
|
+
schema: sourceRawContentSchema
|
|
830
|
+
});
|
|
831
|
+
},
|
|
832
|
+
async getSource(notebookId, sourceId, options) {
|
|
833
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
834
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
835
|
+
return call(context, {
|
|
836
|
+
callOptions: options,
|
|
837
|
+
capability: "sources.read",
|
|
838
|
+
idempotent: true,
|
|
839
|
+
method: "GET",
|
|
840
|
+
operation: "sources.get",
|
|
841
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source),
|
|
842
|
+
schema: sourceSchema
|
|
843
|
+
});
|
|
844
|
+
},
|
|
845
|
+
async getSourceContent(notebookId, sourceId, options) {
|
|
846
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
847
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
848
|
+
return call(context, {
|
|
849
|
+
callOptions: options,
|
|
850
|
+
capability: "sources.read",
|
|
851
|
+
idempotent: true,
|
|
852
|
+
method: "GET",
|
|
853
|
+
operation: "sources.content",
|
|
854
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source, "content"),
|
|
855
|
+
schema: sourceContentSchema
|
|
856
|
+
});
|
|
857
|
+
},
|
|
858
|
+
async getSourceActionAccess(options) {
|
|
859
|
+
return call(context, {
|
|
860
|
+
callOptions: options,
|
|
861
|
+
capability: "sources.actionAccess",
|
|
862
|
+
idempotent: true,
|
|
863
|
+
method: "GET",
|
|
864
|
+
operation: "sources.actionAccess",
|
|
865
|
+
path: context.profile.mindPath("source-actions", "access"),
|
|
866
|
+
schema: sourceActionAccessSchema
|
|
867
|
+
});
|
|
868
|
+
},
|
|
869
|
+
async getSourcePreview(notebookId, sourceId, options) {
|
|
870
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
871
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
872
|
+
return call(context, {
|
|
873
|
+
callOptions: options,
|
|
874
|
+
capability: "sources.preview",
|
|
875
|
+
idempotent: true,
|
|
876
|
+
method: "GET",
|
|
877
|
+
operation: "sources.preview",
|
|
878
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source, "preview"),
|
|
879
|
+
schema: sourcePreviewSchema
|
|
880
|
+
});
|
|
881
|
+
},
|
|
882
|
+
async getSourceRawContent(notebookId, sourceId, options) {
|
|
883
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
884
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
885
|
+
return call(context, {
|
|
886
|
+
callOptions: options,
|
|
887
|
+
capability: "sources.rawContent",
|
|
888
|
+
idempotent: true,
|
|
889
|
+
method: "GET",
|
|
890
|
+
operation: "sources.rawContent",
|
|
891
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source, "raw-content"),
|
|
892
|
+
schema: sourceRawContentSchema
|
|
893
|
+
});
|
|
894
|
+
},
|
|
895
|
+
async getSourceUploadCapabilities(options) {
|
|
896
|
+
return call(context, {
|
|
897
|
+
callOptions: options,
|
|
898
|
+
capability: "sources.write",
|
|
899
|
+
idempotent: true,
|
|
900
|
+
method: "GET",
|
|
901
|
+
operation: "sources.uploadCapabilities",
|
|
902
|
+
path: context.profile.mindPath("source-upload", "capabilities"),
|
|
903
|
+
schema: sourceUploadCapabilitiesSchema
|
|
904
|
+
});
|
|
905
|
+
},
|
|
906
|
+
async getFeaturedSourceTree(notebookId, options) {
|
|
907
|
+
return buildSourceTree((await listFeaturedSources(notebookId, {
|
|
908
|
+
page: 1,
|
|
909
|
+
pageSize: 200
|
|
910
|
+
}, options)).sources);
|
|
911
|
+
},
|
|
912
|
+
getSourceTree,
|
|
913
|
+
async importSite(notebookId, input, options) {
|
|
914
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
915
|
+
return call(context, {
|
|
916
|
+
body: siteBody(input),
|
|
917
|
+
callOptions: options,
|
|
918
|
+
capability: "sources.write",
|
|
919
|
+
idempotent: false,
|
|
920
|
+
method: "POST",
|
|
921
|
+
operation: "sources.importSite",
|
|
922
|
+
path: context.profile.apiPath("notebooks", notebook, "sources"),
|
|
923
|
+
schema: sourceSchema
|
|
924
|
+
});
|
|
925
|
+
},
|
|
926
|
+
async importWebLinks(notebookId, input, options) {
|
|
927
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
928
|
+
const urls = stringList(input?.urls ?? [], "urls", {
|
|
929
|
+
max: 5,
|
|
930
|
+
min: 1
|
|
931
|
+
}).map((url) => httpUrl(url, "urls"));
|
|
932
|
+
return call(context, {
|
|
933
|
+
body: compactUndefined({
|
|
934
|
+
parentSourceId: optionalString$1(input?.parentSourceId, "parentSourceId"),
|
|
935
|
+
path: optionalString$1(input?.path, "path"),
|
|
936
|
+
sourceType: "url",
|
|
937
|
+
urls
|
|
938
|
+
}),
|
|
939
|
+
callOptions: options,
|
|
940
|
+
capability: "sources.importWeb",
|
|
941
|
+
idempotent: false,
|
|
942
|
+
method: "POST",
|
|
943
|
+
operation: "sources.importWeb",
|
|
944
|
+
path: context.profile.apiPath("notebooks", notebook, "sources"),
|
|
945
|
+
schema: sourceImportResultsSchema
|
|
946
|
+
});
|
|
947
|
+
},
|
|
948
|
+
async importWikiDocuments(notebookId, input, options) {
|
|
949
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
950
|
+
const urls = stringList(input?.urls ?? [], "urls", {
|
|
951
|
+
max: 5,
|
|
952
|
+
min: 1
|
|
953
|
+
});
|
|
954
|
+
return call(context, {
|
|
955
|
+
body: compactUndefined({
|
|
956
|
+
parentSourceId: optionalString$1(input?.parentSourceId, "parentSourceId"),
|
|
957
|
+
path: "/repowiki",
|
|
958
|
+
sourceType: "repowiki",
|
|
959
|
+
urls
|
|
960
|
+
}),
|
|
961
|
+
callOptions: options,
|
|
962
|
+
capability: "sources.write",
|
|
963
|
+
idempotent: false,
|
|
964
|
+
method: "POST",
|
|
965
|
+
operation: "sources.importWiki",
|
|
966
|
+
path: context.profile.apiPath("notebooks", notebook, "sources"),
|
|
967
|
+
schema: sourceImportResultsSchema
|
|
968
|
+
});
|
|
969
|
+
},
|
|
970
|
+
listAllSources,
|
|
971
|
+
listFeaturedSources,
|
|
972
|
+
listSources,
|
|
973
|
+
async moveSource(notebookId, sourceId, input, options) {
|
|
974
|
+
const parentSourceId = optionalString$1(input?.parentSourceId, "parentSourceId");
|
|
975
|
+
const path = optionalString$1(input?.path, "path");
|
|
976
|
+
const status = optionalString$1(input?.status, "status");
|
|
977
|
+
const title = optionalString$1(input?.title, "title");
|
|
978
|
+
const patch = {
|
|
979
|
+
...parentSourceId === void 0 ? {} : { parentSourceId },
|
|
980
|
+
...path === void 0 ? {} : { path },
|
|
981
|
+
...status === void 0 ? {} : { status },
|
|
982
|
+
...title === void 0 ? {} : { title }
|
|
983
|
+
};
|
|
984
|
+
assertNonEmptyPatch(patch, "input");
|
|
985
|
+
return updateSource(notebookId, sourceId, patch, options);
|
|
986
|
+
},
|
|
987
|
+
async presignImageUpload(notebookId, sourceId, input, options) {
|
|
988
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
989
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
990
|
+
const filename = requiredString$2(input?.filename, "filename");
|
|
991
|
+
const mimeType = requiredString$2(input?.mimeType, "mimeType");
|
|
992
|
+
const size = nonNegativeInteger$1(input?.size, "size");
|
|
993
|
+
if (size === void 0) throw invalidArgument$1("size must be a non-negative integer", "size");
|
|
994
|
+
if (size > maxImageBytes) throw invalidArgument$1(`size must not exceed ${maxImageBytes} bytes`, "size");
|
|
995
|
+
return call(context, {
|
|
996
|
+
body: compactUndefined({
|
|
997
|
+
filename,
|
|
998
|
+
mimeType,
|
|
999
|
+
size
|
|
1000
|
+
}),
|
|
1001
|
+
callOptions: options,
|
|
1002
|
+
capability: "images.presignUpload",
|
|
1003
|
+
idempotent: false,
|
|
1004
|
+
method: "POST",
|
|
1005
|
+
operation: "images.presignUpload",
|
|
1006
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source, "images", "presign"),
|
|
1007
|
+
schema: imageUploadTicketSchema
|
|
1008
|
+
});
|
|
1009
|
+
},
|
|
1010
|
+
async refreshSourceWebOfficeSession(notebookId, sourceId, input, options) {
|
|
1011
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
1012
|
+
const source = requiredString$2(sourceId, "sourceId");
|
|
1013
|
+
const sessionId = requiredString$2(input?.sessionId, "sessionId");
|
|
1014
|
+
const tokenVersion = nonNegativeInteger$1(input?.tokenVersion, "tokenVersion") ?? 0;
|
|
1015
|
+
assertCapability(context.profile, "sources.webOffice", "sources.webOffice.refresh");
|
|
1016
|
+
return call(context, {
|
|
1017
|
+
body: { tokenVersion },
|
|
1018
|
+
callOptions: options,
|
|
1019
|
+
capability: "sources.webOffice",
|
|
1020
|
+
idempotent: false,
|
|
1021
|
+
method: "POST",
|
|
1022
|
+
operation: "sources.webOffice.refresh",
|
|
1023
|
+
path: context.profile.apiPath("notebooks", notebook, "sources", source, "weboffice", "session", sessionId, "refresh"),
|
|
1024
|
+
schema: sourceWebOfficeTokenSchema
|
|
1025
|
+
});
|
|
1026
|
+
},
|
|
1027
|
+
async presignImageUrls(notebookId, ossKeys, options) {
|
|
1028
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
1029
|
+
const keys = stringList(ossKeys, "ossKeys", {
|
|
1030
|
+
max: 100,
|
|
1031
|
+
min: 1
|
|
1032
|
+
});
|
|
1033
|
+
return call(context, {
|
|
1034
|
+
body: { ossKeys: keys },
|
|
1035
|
+
callOptions: options,
|
|
1036
|
+
capability: "images.presignUrls",
|
|
1037
|
+
idempotent: false,
|
|
1038
|
+
method: "POST",
|
|
1039
|
+
operation: "images.presignUrls",
|
|
1040
|
+
path: context.profile.apiPath("notebooks", notebook, "images", "presign-urls"),
|
|
1041
|
+
schema: imageUrlsSchema
|
|
1042
|
+
});
|
|
1043
|
+
},
|
|
1044
|
+
updateSource
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
//#endregion
|
|
1048
|
+
//#region ../sdk/src/operations/transfers.ts
|
|
1049
|
+
function isRecord$2(value) {
|
|
1050
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1051
|
+
}
|
|
1052
|
+
function binarySource(value) {
|
|
1053
|
+
if (!isRecord$2(value)) throw invalidArgument$1("source must be a QMindBinarySource", "source");
|
|
1054
|
+
const filename = requiredString$2(value.filename, "source.filename");
|
|
1055
|
+
const mimeType = requiredString$2(value.mimeType, "source.mimeType");
|
|
1056
|
+
const size = value.size;
|
|
1057
|
+
if (typeof size !== "number" || !Number.isSafeInteger(size) || size <= 0) throw invalidArgument$1("source.size must be a positive safe integer", "source.size");
|
|
1058
|
+
if (typeof value.open !== "function") throw invalidArgument$1("source.open must be a function", "source.open");
|
|
1059
|
+
const source = value;
|
|
1060
|
+
return {
|
|
1061
|
+
filename,
|
|
1062
|
+
mimeType,
|
|
1063
|
+
open: () => source.open(),
|
|
1064
|
+
size
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function transferOptions(options) {
|
|
1068
|
+
if (options === void 0) return {};
|
|
1069
|
+
if (!isRecord$2(options)) throw invalidArgument$1("options must be an object", "options");
|
|
1070
|
+
if (options.onProgress !== void 0 && typeof options.onProgress !== "function") throw invalidArgument$1("onProgress must be a function", "onProgress");
|
|
1071
|
+
return options;
|
|
1072
|
+
}
|
|
1073
|
+
function uploadMetadata(value) {
|
|
1074
|
+
if (value === void 0) return {};
|
|
1075
|
+
if (!isRecord$2(value)) throw invalidArgument$1("metadata must be an object", "metadata");
|
|
1076
|
+
return {
|
|
1077
|
+
parentSourceId: optionalString$1(value.parentSourceId, "parentSourceId"),
|
|
1078
|
+
path: optionalString$1(value.path, "path"),
|
|
1079
|
+
sha256: optionalTrimmedString(value.sha256, "sha256"),
|
|
1080
|
+
sourceType: optionalTrimmedString(value.sourceType, "sourceType"),
|
|
1081
|
+
title: optionalTrimmedString(value.title, "title")
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function progressReporter(context, operation, strategy, totalBytes, options) {
|
|
1085
|
+
let lastUploadedBytes = 0;
|
|
1086
|
+
const notify = (phase, uploadedBytes) => {
|
|
1087
|
+
if (!Number.isSafeInteger(uploadedBytes) || uploadedBytes < lastUploadedBytes) throw new QMindError("HOST_POLICY_ERROR", "upload progress must be a monotonic integer", {
|
|
1088
|
+
access: context.profile.access,
|
|
1089
|
+
details: {
|
|
1090
|
+
lastUploadedBytes,
|
|
1091
|
+
totalBytes,
|
|
1092
|
+
uploadedBytes
|
|
1093
|
+
},
|
|
1094
|
+
operation
|
|
1095
|
+
});
|
|
1096
|
+
if (uploadedBytes > totalBytes) throw new QMindError("HOST_POLICY_ERROR", "upload progress exceeds source size", {
|
|
1097
|
+
access: context.profile.access,
|
|
1098
|
+
details: {
|
|
1099
|
+
totalBytes,
|
|
1100
|
+
uploadedBytes
|
|
1101
|
+
},
|
|
1102
|
+
operation
|
|
1103
|
+
});
|
|
1104
|
+
lastUploadedBytes = uploadedBytes;
|
|
1105
|
+
const progress = {
|
|
1106
|
+
phase,
|
|
1107
|
+
strategy,
|
|
1108
|
+
totalBytes,
|
|
1109
|
+
uploadedBytes
|
|
1110
|
+
};
|
|
1111
|
+
options.onProgress?.(progress);
|
|
1112
|
+
};
|
|
1113
|
+
return {
|
|
1114
|
+
complete: () => notify("uploading", totalBytes),
|
|
1115
|
+
registering: () => notify("registering", totalBytes),
|
|
1116
|
+
start: () => notify("uploading", 0),
|
|
1117
|
+
update: (uploadedBytes) => notify("uploading", uploadedBytes)
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function requestOptions(options) {
|
|
1121
|
+
return {
|
|
1122
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
1123
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function transferCall(context, operation, request, schema) {
|
|
1127
|
+
return executeUnary({
|
|
1128
|
+
capability: operation.startsWith("images.") ? "images.presignUpload" : "transfers",
|
|
1129
|
+
operation,
|
|
1130
|
+
profile: context.profile,
|
|
1131
|
+
request,
|
|
1132
|
+
schema,
|
|
1133
|
+
transport: context.transport
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
function hostRequest(path, method, idempotent, options, body) {
|
|
1137
|
+
const authorization = options.authorization?.trim();
|
|
1138
|
+
return {
|
|
1139
|
+
body,
|
|
1140
|
+
destination: {
|
|
1141
|
+
kind: "host",
|
|
1142
|
+
path
|
|
1143
|
+
},
|
|
1144
|
+
...authorization === void 0 || authorization.length === 0 ? {} : { headers: { authorization: `Bearer ${authorization}` } },
|
|
1145
|
+
idempotent,
|
|
1146
|
+
method,
|
|
1147
|
+
...requestOptions(options)
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function signedUploadRequest(ticket, source, progress, options) {
|
|
1151
|
+
return {
|
|
1152
|
+
body: {
|
|
1153
|
+
kind: "binary",
|
|
1154
|
+
onProgress: progress.update,
|
|
1155
|
+
source
|
|
1156
|
+
},
|
|
1157
|
+
destination: {
|
|
1158
|
+
kind: "signed",
|
|
1159
|
+
url: ticket.uploadUrl
|
|
1160
|
+
},
|
|
1161
|
+
headers: ticket.headers,
|
|
1162
|
+
idempotent: true,
|
|
1163
|
+
method: "PUT",
|
|
1164
|
+
...requestOptions(options)
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function createTransferOperations(context) {
|
|
1168
|
+
return {
|
|
1169
|
+
async uploadImage(notebookId, sourceId, rawSource, rawOptions) {
|
|
1170
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
1171
|
+
const sourceIdValue = requiredString$2(sourceId, "sourceId");
|
|
1172
|
+
const source = binarySource(rawSource);
|
|
1173
|
+
const options = transferOptions(rawOptions);
|
|
1174
|
+
if (source.size > 52428800) throw new QMindError("FILE_TOO_LARGE", "image exceeds the 50 MiB upload limit", {
|
|
1175
|
+
access: context.profile.access,
|
|
1176
|
+
details: {
|
|
1177
|
+
limitBytes: QMIND_MULTIPART_UPLOAD_MAX_BYTES,
|
|
1178
|
+
size: source.size
|
|
1179
|
+
},
|
|
1180
|
+
operation: "images.upload"
|
|
1181
|
+
});
|
|
1182
|
+
const ticket = await transferCall(context, "images.presignUpload", hostRequest(context.profile.apiPath("notebooks", notebook, "sources", sourceIdValue, "images", "presign"), "POST", false, options, {
|
|
1183
|
+
kind: "json",
|
|
1184
|
+
value: {
|
|
1185
|
+
filename: source.filename,
|
|
1186
|
+
mimeType: source.mimeType,
|
|
1187
|
+
size: source.size
|
|
1188
|
+
}
|
|
1189
|
+
}), imageUploadTicketSchema);
|
|
1190
|
+
const progress = progressReporter(context, "images.upload", "presigned", source.size, options);
|
|
1191
|
+
progress.start();
|
|
1192
|
+
await transferCall(context, "images.upload", signedUploadRequest(ticket, source, progress, options), voidResponseSchema);
|
|
1193
|
+
progress.complete();
|
|
1194
|
+
return ticket;
|
|
1195
|
+
},
|
|
1196
|
+
async uploadSource(notebookId, rawSource, rawMetadata, rawOptions) {
|
|
1197
|
+
const notebook = requiredString$2(notebookId, "notebookId");
|
|
1198
|
+
const source = binarySource(rawSource);
|
|
1199
|
+
const metadata = uploadMetadata(rawMetadata);
|
|
1200
|
+
const options = transferOptions(rawOptions);
|
|
1201
|
+
if (source.size > 524288e3) throw new QMindError("FILE_TOO_LARGE", "file exceeds the 500 MiB upload limit", {
|
|
1202
|
+
access: context.profile.access,
|
|
1203
|
+
details: {
|
|
1204
|
+
limitBytes: QMIND_SOURCE_UPLOAD_MAX_BYTES,
|
|
1205
|
+
size: source.size
|
|
1206
|
+
},
|
|
1207
|
+
operation: "transfers.source.multipart"
|
|
1208
|
+
});
|
|
1209
|
+
if (source.size <= 52428800) {
|
|
1210
|
+
const progress = progressReporter(context, "transfers.source.multipart", "multipart", source.size, options);
|
|
1211
|
+
progress.start();
|
|
1212
|
+
const fields = compactUndefined({
|
|
1213
|
+
parent_source_id: metadata.parentSourceId,
|
|
1214
|
+
path: metadata.path,
|
|
1215
|
+
retain_original_file: "true",
|
|
1216
|
+
source_type: metadata.sourceType,
|
|
1217
|
+
title: metadata.title
|
|
1218
|
+
});
|
|
1219
|
+
const result = await transferCall(context, "transfers.source.multipart", hostRequest(queryPath(context.profile.apiPath("notebooks", notebook, "sources", "upload"), [["client", context.client]]), "POST", false, options, {
|
|
1220
|
+
file: source,
|
|
1221
|
+
fields,
|
|
1222
|
+
kind: "multipart",
|
|
1223
|
+
onProgress: progress.update
|
|
1224
|
+
}), sourceSchema);
|
|
1225
|
+
progress.complete();
|
|
1226
|
+
return result;
|
|
1227
|
+
}
|
|
1228
|
+
const ticket = await transferCall(context, "transfers.source.presign", hostRequest(context.profile.apiPath("notebooks", notebook, "sources", "presign"), "POST", false, options, {
|
|
1229
|
+
kind: "json",
|
|
1230
|
+
value: {
|
|
1231
|
+
filename: source.filename,
|
|
1232
|
+
mimeType: source.mimeType,
|
|
1233
|
+
size: source.size
|
|
1234
|
+
}
|
|
1235
|
+
}), sourceUploadTicketSchema);
|
|
1236
|
+
const progress = progressReporter(context, "transfers.source.upload", "presigned", source.size, options);
|
|
1237
|
+
progress.start();
|
|
1238
|
+
await transferCall(context, "transfers.source.upload", signedUploadRequest(ticket, source, progress, options), voidResponseSchema);
|
|
1239
|
+
progress.complete();
|
|
1240
|
+
progress.registering();
|
|
1241
|
+
return transferCall(context, "transfers.source.register", hostRequest(context.profile.apiPath("notebooks", notebook, "sources", "register"), "POST", false, options, {
|
|
1242
|
+
kind: "json",
|
|
1243
|
+
value: compactUndefined({
|
|
1244
|
+
filename: source.filename,
|
|
1245
|
+
mimeType: source.mimeType,
|
|
1246
|
+
parentSourceId: metadata.parentSourceId,
|
|
1247
|
+
path: metadata.path,
|
|
1248
|
+
sha256: metadata.sha256,
|
|
1249
|
+
size: source.size,
|
|
1250
|
+
sourceType: metadata.sourceType,
|
|
1251
|
+
title: metadata.title,
|
|
1252
|
+
uri: ticket.uri
|
|
1253
|
+
})
|
|
1254
|
+
}), sourceSchema);
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
//#endregion
|
|
1259
|
+
//#region ../sdk/src/capabilities.ts
|
|
1260
|
+
const dashboardCapabilities = Object.freeze({
|
|
1261
|
+
botGateway: false,
|
|
1262
|
+
"cards.read": true,
|
|
1263
|
+
"cards.write": true,
|
|
1264
|
+
chat: true,
|
|
1265
|
+
compilation: true,
|
|
1266
|
+
"featuredNotebooks.read": true,
|
|
1267
|
+
"images.presignUpload": false,
|
|
1268
|
+
"images.presignUrls": false,
|
|
1269
|
+
"imports.aliDing": true,
|
|
1270
|
+
"imports.discover": true,
|
|
1271
|
+
members: true,
|
|
1272
|
+
"notebooks.filterByClient": false,
|
|
1273
|
+
"notebooks.read": true,
|
|
1274
|
+
"notebooks.write": true,
|
|
1275
|
+
"notes.read": false,
|
|
1276
|
+
"notes.write": false,
|
|
1277
|
+
retrieval: true,
|
|
1278
|
+
scheduledTasks: true,
|
|
1279
|
+
"sharedNotebooks.read": true,
|
|
1280
|
+
"sources.children": true,
|
|
1281
|
+
"sources.childrenCount": true,
|
|
1282
|
+
"sources.actionAccess": true,
|
|
1283
|
+
"sources.importWeb": true,
|
|
1284
|
+
"sources.preview": true,
|
|
1285
|
+
"sources.rawContent": true,
|
|
1286
|
+
"sources.read": true,
|
|
1287
|
+
"sources.search": true,
|
|
1288
|
+
"sources.tree": true,
|
|
1289
|
+
"sources.webOffice": true,
|
|
1290
|
+
"sources.write": true,
|
|
1291
|
+
"taskRuns.read": true,
|
|
1292
|
+
"taskRuns.write": true,
|
|
1293
|
+
transfers: true,
|
|
1294
|
+
"wikiBranches.read": true
|
|
1295
|
+
});
|
|
1296
|
+
const sashCapabilities = Object.freeze({
|
|
1297
|
+
botGateway: false,
|
|
1298
|
+
"cards.read": true,
|
|
1299
|
+
"cards.write": true,
|
|
1300
|
+
chat: true,
|
|
1301
|
+
compilation: false,
|
|
1302
|
+
"featuredNotebooks.read": false,
|
|
1303
|
+
"images.presignUpload": true,
|
|
1304
|
+
"images.presignUrls": true,
|
|
1305
|
+
"imports.aliDing": false,
|
|
1306
|
+
"imports.discover": false,
|
|
1307
|
+
members: false,
|
|
1308
|
+
"notebooks.filterByClient": true,
|
|
1309
|
+
"notebooks.read": true,
|
|
1310
|
+
"notebooks.write": true,
|
|
1311
|
+
"notes.read": false,
|
|
1312
|
+
"notes.write": false,
|
|
1313
|
+
retrieval: true,
|
|
1314
|
+
scheduledTasks: false,
|
|
1315
|
+
"sharedNotebooks.read": false,
|
|
1316
|
+
"sources.children": true,
|
|
1317
|
+
"sources.childrenCount": false,
|
|
1318
|
+
"sources.actionAccess": true,
|
|
1319
|
+
"sources.importWeb": true,
|
|
1320
|
+
"sources.preview": true,
|
|
1321
|
+
"sources.rawContent": true,
|
|
1322
|
+
"sources.read": true,
|
|
1323
|
+
"sources.search": false,
|
|
1324
|
+
"sources.tree": true,
|
|
1325
|
+
"sources.webOffice": true,
|
|
1326
|
+
"sources.write": true,
|
|
1327
|
+
"taskRuns.read": true,
|
|
1328
|
+
"taskRuns.write": true,
|
|
1329
|
+
transfers: true,
|
|
1330
|
+
"wikiBranches.read": true
|
|
1331
|
+
});
|
|
1332
|
+
function capabilitiesFor(access) {
|
|
1333
|
+
return access === "sash" ? sashCapabilities : dashboardCapabilities;
|
|
1334
|
+
}
|
|
1335
|
+
//#endregion
|
|
1336
|
+
//#region ../sdk/src/profiles.ts
|
|
1337
|
+
function isRecord$1(value) {
|
|
1338
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1339
|
+
}
|
|
1340
|
+
function encodeId(value) {
|
|
1341
|
+
return encodeURIComponent(value);
|
|
1342
|
+
}
|
|
1343
|
+
function appendQuery(path, entries) {
|
|
1344
|
+
const query = new URLSearchParams();
|
|
1345
|
+
for (const [key, value] of entries) if (value !== void 0 && value !== "") query.set(key, String(value));
|
|
1346
|
+
const suffix = query.toString();
|
|
1347
|
+
return suffix ? `${path}?${suffix}` : path;
|
|
1348
|
+
}
|
|
1349
|
+
function dashboardEnvelope(body) {
|
|
1350
|
+
if (!isRecord$1(body) || typeof body.status !== "number" || !("data" in body)) return body;
|
|
1351
|
+
return body.data;
|
|
1352
|
+
}
|
|
1353
|
+
function firstResultSource(access, operation, body) {
|
|
1354
|
+
if (!isRecord$1(body) || !Array.isArray(body.results)) return body;
|
|
1355
|
+
const first = body.results[0];
|
|
1356
|
+
if (!isRecord$1(first)) return void 0;
|
|
1357
|
+
if ("source" in first) return first.source;
|
|
1358
|
+
if (typeof first.error === "string" && first.error.length > 0) throw new QMindError("REMOTE_ERROR", first.error, {
|
|
1359
|
+
access,
|
|
1360
|
+
details: first,
|
|
1361
|
+
operation,
|
|
1362
|
+
status: 200
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
function dashboardBatchMutation(body) {
|
|
1366
|
+
if (!isRecord$1(body)) return body;
|
|
1367
|
+
const successCount = typeof body.successCount === "number" ? body.successCount : body.success_count;
|
|
1368
|
+
const failureCount = typeof body.failureCount === "number" ? body.failureCount : body.failure_count;
|
|
1369
|
+
if (typeof successCount !== "number" && typeof failureCount !== "number") return body;
|
|
1370
|
+
return {
|
|
1371
|
+
...body,
|
|
1372
|
+
failureCount: typeof failureCount === "number" ? failureCount : 0,
|
|
1373
|
+
successCount: typeof successCount === "number" ? successCount : 0
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
function normalizeSuccess(access, operation, body) {
|
|
1377
|
+
const unwrapped = access === "dashboard" ? dashboardEnvelope(body) : body;
|
|
1378
|
+
if (access !== "dashboard") return unwrapped;
|
|
1379
|
+
if (operation === "cards.get" && isRecord$1(unwrapped) && "card" in unwrapped) return unwrapped.card;
|
|
1380
|
+
if (operation === "cards.delete" || operation === "cards.batchDelete") return dashboardBatchMutation(unwrapped);
|
|
1381
|
+
if (operation === "sources.create" || operation === "sources.importSite") return firstResultSource(access, operation, unwrapped);
|
|
1382
|
+
if (operation === "transfers.source.multipart" || operation === "transfers.source.register") return firstResultSource(access, operation, unwrapped);
|
|
1383
|
+
return unwrapped;
|
|
1384
|
+
}
|
|
1385
|
+
function streamError(access, message, details) {
|
|
1386
|
+
return new QMindError("STREAM_ERROR", message, {
|
|
1387
|
+
access,
|
|
1388
|
+
...details === void 0 ? {} : { details },
|
|
1389
|
+
operation: "chat.stream"
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
function requiredString$1(access, value, field) {
|
|
1393
|
+
if (value !== void 0 && value.length > 0) return value;
|
|
1394
|
+
throw streamError(access, `stream event is missing ${field}`, { field });
|
|
1395
|
+
}
|
|
1396
|
+
function stringValue$1(value) {
|
|
1397
|
+
return value ?? "";
|
|
1398
|
+
}
|
|
1399
|
+
function presentString(access, value, field) {
|
|
1400
|
+
if (value !== void 0) return value;
|
|
1401
|
+
throw streamError(access, `stream event is missing ${field}`, { field });
|
|
1402
|
+
}
|
|
1403
|
+
function decodeStreamEvent(access, rawBody) {
|
|
1404
|
+
const candidate = access === "dashboard" ? dashboardEnvelope(rawBody) : rawBody;
|
|
1405
|
+
const parsed = streamWireEventSchema.safeParse(candidate);
|
|
1406
|
+
if (!parsed.success) throw streamError(access, "stream event does not match the QMind contract", { issues: parsed.error.issues });
|
|
1407
|
+
const event = parsed.data;
|
|
1408
|
+
const eventType = event.eventType ?? event.event_type ?? event.type;
|
|
1409
|
+
switch (eventType) {
|
|
1410
|
+
case "message_start": return {
|
|
1411
|
+
citations: event.citations ?? [],
|
|
1412
|
+
reranked: event.reranked ?? false,
|
|
1413
|
+
totalContextChunks: event.totalContextChunks ?? 0,
|
|
1414
|
+
type: "messageStart",
|
|
1415
|
+
...event.assistantMessageId === void 0 ? {} : { assistantMessageId: event.assistantMessageId },
|
|
1416
|
+
...event.userMessage === void 0 ? {} : { userMessage: event.userMessage }
|
|
1417
|
+
};
|
|
1418
|
+
case "tool_call_start": return {
|
|
1419
|
+
step: event.step ?? 0,
|
|
1420
|
+
toolArguments: stringValue$1(event.toolArguments),
|
|
1421
|
+
toolName: requiredString$1(access, event.toolName, "toolName"),
|
|
1422
|
+
type: "toolCallStart",
|
|
1423
|
+
...event.toolDisplayName === void 0 ? {} : { toolDisplayName: event.toolDisplayName }
|
|
1424
|
+
};
|
|
1425
|
+
case "tool_call_result": return {
|
|
1426
|
+
step: event.step ?? 0,
|
|
1427
|
+
toolName: requiredString$1(access, event.toolName, "toolName"),
|
|
1428
|
+
toolResult: stringValue$1(event.toolResult),
|
|
1429
|
+
type: "toolCallResult",
|
|
1430
|
+
...event.toolDisplayName === void 0 ? {} : { toolDisplayName: event.toolDisplayName }
|
|
1431
|
+
};
|
|
1432
|
+
case "delta": return {
|
|
1433
|
+
delta: presentString(access, event.delta, "delta"),
|
|
1434
|
+
type: "delta"
|
|
1435
|
+
};
|
|
1436
|
+
case "batch_progress": return {
|
|
1437
|
+
metadata: event.metadata ?? {},
|
|
1438
|
+
type: "batchProgress"
|
|
1439
|
+
};
|
|
1440
|
+
case "heartbeat": return {
|
|
1441
|
+
metadata: event.metadata ?? {},
|
|
1442
|
+
type: "heartbeat"
|
|
1443
|
+
};
|
|
1444
|
+
case "cancel": return {
|
|
1445
|
+
canceled: true,
|
|
1446
|
+
type: "cancel",
|
|
1447
|
+
...event.cancelReason === void 0 ? {} : { cancelReason: event.cancelReason }
|
|
1448
|
+
};
|
|
1449
|
+
case "message_complete": return {
|
|
1450
|
+
canceled: event.canceled ?? false,
|
|
1451
|
+
citations: event.citations ?? [],
|
|
1452
|
+
metadata: event.metadata ?? {},
|
|
1453
|
+
promptContext: event.promptContext ?? "",
|
|
1454
|
+
reranked: event.reranked ?? false,
|
|
1455
|
+
totalContextChunks: event.totalContextChunks ?? 0,
|
|
1456
|
+
type: "messageComplete",
|
|
1457
|
+
...event.assistantMessage === void 0 ? {} : { assistantMessage: event.assistantMessage },
|
|
1458
|
+
...event.cancelReason === void 0 ? {} : { cancelReason: event.cancelReason }
|
|
1459
|
+
};
|
|
1460
|
+
case "error": return {
|
|
1461
|
+
message: requiredString$1(access, event.errorMessage ?? event.error, "errorMessage"),
|
|
1462
|
+
type: "error"
|
|
1463
|
+
};
|
|
1464
|
+
default: throw streamError(access, "stream event has an unsupported event type", { eventType });
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
function decodeStreamPayload(access, payload) {
|
|
1468
|
+
let value;
|
|
1469
|
+
try {
|
|
1470
|
+
value = JSON.parse(payload);
|
|
1471
|
+
} catch (cause) {
|
|
1472
|
+
throw streamError(access, "stream event is not valid JSON", { cause });
|
|
1473
|
+
}
|
|
1474
|
+
return decodeStreamEvent(access, value);
|
|
1475
|
+
}
|
|
1476
|
+
function createProfile(access, apiPrefix) {
|
|
1477
|
+
const capabilities = capabilitiesFor(access);
|
|
1478
|
+
const apiPath = (...segments) => {
|
|
1479
|
+
return `${apiPrefix}/${segments.map((segment) => encodeId(segment)).join("/")}`;
|
|
1480
|
+
};
|
|
1481
|
+
return Object.freeze({
|
|
1482
|
+
access,
|
|
1483
|
+
capabilities,
|
|
1484
|
+
apiPath,
|
|
1485
|
+
decodeStreamPayload(payload) {
|
|
1486
|
+
return decodeStreamPayload(access, payload);
|
|
1487
|
+
},
|
|
1488
|
+
featuredNotebookPath(notebookId) {
|
|
1489
|
+
return notebookId === void 0 ? apiPath("featured-notebooks") : apiPath("featured-notebooks", notebookId);
|
|
1490
|
+
},
|
|
1491
|
+
listFeaturedNotebooksPath(input) {
|
|
1492
|
+
return appendQuery(`${apiPrefix}/featured-notebooks`, [["page", input.page], ["page_size", input.pageSize]]);
|
|
1493
|
+
},
|
|
1494
|
+
listNotebooksPath(input) {
|
|
1495
|
+
return appendQuery(`${apiPrefix}/notebooks`, [
|
|
1496
|
+
["page", input.page],
|
|
1497
|
+
["page_size", input.pageSize],
|
|
1498
|
+
["client", input.client]
|
|
1499
|
+
]);
|
|
1500
|
+
},
|
|
1501
|
+
listSharedNotebooksPath(input) {
|
|
1502
|
+
return appendQuery(`${apiPrefix}/notebooks/shared`, [["page", input.page], ["page_size", input.pageSize]]);
|
|
1503
|
+
},
|
|
1504
|
+
mindPath(...segments) {
|
|
1505
|
+
return `/api/v1/mind/${segments.map((segment) => encodeId(segment)).join("/")}`;
|
|
1506
|
+
},
|
|
1507
|
+
notebookPath(notebookId) {
|
|
1508
|
+
return notebookId === void 0 ? apiPath("notebooks") : apiPath("notebooks", notebookId);
|
|
1509
|
+
},
|
|
1510
|
+
unwrapError(body) {
|
|
1511
|
+
return access === "dashboard" ? dashboardEnvelope(body) : body;
|
|
1512
|
+
},
|
|
1513
|
+
unwrapSuccess(operation, body) {
|
|
1514
|
+
return normalizeSuccess(access, operation, body);
|
|
1515
|
+
},
|
|
1516
|
+
wikiBranchesPath(notebookId) {
|
|
1517
|
+
return apiPath("notebooks", notebookId, "wiki-branches");
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
const dashboardProfile = createProfile("dashboard", "/api/v1");
|
|
1522
|
+
const sashProfile = createProfile("sash", "/sash/api/v1");
|
|
1523
|
+
function profileFor(access) {
|
|
1524
|
+
return access === "sash" ? sashProfile : dashboardProfile;
|
|
1525
|
+
}
|
|
1526
|
+
//#endregion
|
|
1527
|
+
//#region ../sdk/src/client.ts
|
|
1528
|
+
function isRecord(value) {
|
|
1529
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1530
|
+
}
|
|
1531
|
+
function isQMindAccess(value) {
|
|
1532
|
+
return value === "sash" || value === "dashboard";
|
|
1533
|
+
}
|
|
1534
|
+
function isQMindTransport(value) {
|
|
1535
|
+
if (!isRecord(value)) return false;
|
|
1536
|
+
return typeof value.request === "function" && typeof value.stream === "function";
|
|
1537
|
+
}
|
|
1538
|
+
const managementOperationNames = [
|
|
1539
|
+
"addMember",
|
|
1540
|
+
"createScheduledTask",
|
|
1541
|
+
"deleteScheduledTask",
|
|
1542
|
+
"getCompilationSettings",
|
|
1543
|
+
"getCompilationStatus",
|
|
1544
|
+
"listMembers",
|
|
1545
|
+
"listScheduledTasks",
|
|
1546
|
+
"removeMember",
|
|
1547
|
+
"saveCompilationSettings",
|
|
1548
|
+
"startCompilation",
|
|
1549
|
+
"updateMemberPermission",
|
|
1550
|
+
"updateScheduledTask"
|
|
1551
|
+
];
|
|
1552
|
+
const cardOperationNames = [
|
|
1553
|
+
"batchDeleteCards",
|
|
1554
|
+
"deleteCard",
|
|
1555
|
+
"getCard",
|
|
1556
|
+
"listCards",
|
|
1557
|
+
"listFeaturedCards"
|
|
1558
|
+
];
|
|
1559
|
+
const importOperationNames = [
|
|
1560
|
+
"getAliDingImportRun",
|
|
1561
|
+
"listAliDingImportRunItems",
|
|
1562
|
+
"listImportProviders",
|
|
1563
|
+
"startAliDingKnowledgeBaseImport",
|
|
1564
|
+
"startAliDingNodeSubtreeImport"
|
|
1565
|
+
];
|
|
1566
|
+
function createLazyCardOperations(context) {
|
|
1567
|
+
let operations;
|
|
1568
|
+
const load = () => {
|
|
1569
|
+
operations ??= import("./cards-2J6ebaI0.js").then(({ createCardOperations }) => createCardOperations(context));
|
|
1570
|
+
return operations;
|
|
1571
|
+
};
|
|
1572
|
+
return Object.fromEntries(cardOperationNames.map((name) => [name, (...args) => load().then((loaded) => Reflect.apply(loaded[name], loaded, args))]));
|
|
1573
|
+
}
|
|
1574
|
+
function createLazyImportOperations(context) {
|
|
1575
|
+
let operations;
|
|
1576
|
+
const load = () => {
|
|
1577
|
+
operations ??= import("./imports-Da88W09Q.js").then(({ createImportOperations }) => createImportOperations(context));
|
|
1578
|
+
return operations;
|
|
1579
|
+
};
|
|
1580
|
+
return Object.fromEntries(importOperationNames.map((name) => [name, (...args) => load().then((loaded) => Reflect.apply(loaded[name], loaded, args))]));
|
|
1581
|
+
}
|
|
1582
|
+
function createLazyManagementOperations(context) {
|
|
1583
|
+
let operations;
|
|
1584
|
+
const load = () => {
|
|
1585
|
+
operations ??= import("./management-xYuUDFtg.js").then(({ createManagementOperations }) => createManagementOperations(context));
|
|
1586
|
+
return operations;
|
|
1587
|
+
};
|
|
1588
|
+
return Object.fromEntries(managementOperationNames.map((name) => [name, (...args) => load().then((loaded) => Reflect.apply(loaded[name], loaded, args))]));
|
|
1589
|
+
}
|
|
1590
|
+
function createQMindClient(options) {
|
|
1591
|
+
if (!isRecord(options)) throw invalidArgument$1("client options must be an object", "options");
|
|
1592
|
+
if (!isQMindAccess(options.access)) throw invalidArgument$1("access must be either \"sash\" or \"dashboard\"", "access");
|
|
1593
|
+
if (!isQMindTransport(options.transport)) throw invalidArgument$1("transport must implement request() and stream()", "transport");
|
|
1594
|
+
const profile = profileFor(options.access);
|
|
1595
|
+
const context = {
|
|
1596
|
+
client: optionalTrimmedString(options.client, "client") ?? "qmind",
|
|
1597
|
+
profile,
|
|
1598
|
+
transport: options.transport
|
|
1599
|
+
};
|
|
1600
|
+
return Object.freeze({
|
|
1601
|
+
access: profile.access,
|
|
1602
|
+
capabilities: profile.capabilities,
|
|
1603
|
+
...createNotebookOperations(context),
|
|
1604
|
+
...createLazyCardOperations(context),
|
|
1605
|
+
...createSourceOperations(context),
|
|
1606
|
+
...createLazyImportOperations(context),
|
|
1607
|
+
...createLazyManagementOperations(context),
|
|
1608
|
+
...createTransferOperations(context),
|
|
1609
|
+
...createAgentOperations(context),
|
|
1610
|
+
supports(capability) {
|
|
1611
|
+
return profile.capabilities[capability] === true;
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
//#endregion
|
|
1616
|
+
//#region ../node-host/src/auth.ts
|
|
1617
|
+
const responseRecordSchema = z.record(z.string(), z.unknown());
|
|
1618
|
+
const personalTokenExchangeSchema = z.object({ token: z.string().min(1) });
|
|
1619
|
+
const devicePollSchema = z.object({
|
|
1620
|
+
expires_at: z.string().default(""),
|
|
1621
|
+
refresh_token: z.string().default(""),
|
|
1622
|
+
refresh_token_expires_at: z.string().default(""),
|
|
1623
|
+
token: z.string().min(1)
|
|
1624
|
+
});
|
|
1625
|
+
const refreshSchema = z.object({
|
|
1626
|
+
device_token: z.string().min(1),
|
|
1627
|
+
expires_at: z.string().default(""),
|
|
1628
|
+
refresh_token: z.string().optional(),
|
|
1629
|
+
refresh_token_expires_at: z.string().optional()
|
|
1630
|
+
});
|
|
1631
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
1632
|
+
const LOGIN_TIMEOUT_MS = 3e5;
|
|
1633
|
+
const EXPIRY_SKEW_MS = 3e4;
|
|
1634
|
+
function base64Url(bytes) {
|
|
1635
|
+
return Buffer.from(bytes).toString("base64url");
|
|
1636
|
+
}
|
|
1637
|
+
function createPkce() {
|
|
1638
|
+
const verifier = base64Url(randomBytes(32));
|
|
1639
|
+
return {
|
|
1640
|
+
challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
1641
|
+
nonce: base64Url(randomBytes(16)),
|
|
1642
|
+
verifier
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
function errorCodeForStatus(status) {
|
|
1646
|
+
if (status === 401 || status === 403) return "AUTH_REQUIRED";
|
|
1647
|
+
if (status === 404) return "NOT_FOUND";
|
|
1648
|
+
if (status === 409) return "CONFLICT";
|
|
1649
|
+
if (status === 429) return "RATE_LIMITED";
|
|
1650
|
+
if (status >= 400 && status < 500) return "INVALID_ARGUMENT";
|
|
1651
|
+
return "REMOTE_ERROR";
|
|
1652
|
+
}
|
|
1653
|
+
function record$1(value) {
|
|
1654
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1655
|
+
}
|
|
1656
|
+
function responseString(response, ...keys) {
|
|
1657
|
+
const body = record$1(response.body);
|
|
1658
|
+
const nestedError = record$1(body?.error);
|
|
1659
|
+
for (const source of [body, nestedError]) for (const key of keys) {
|
|
1660
|
+
const value = source?.[key];
|
|
1661
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
function header(response, name) {
|
|
1665
|
+
const normalized = name.toLowerCase();
|
|
1666
|
+
for (const [key, value] of Object.entries(response.headers)) if (key.toLowerCase() === normalized && value.length > 0) return value;
|
|
1667
|
+
}
|
|
1668
|
+
function authResponseError(response, operation) {
|
|
1669
|
+
const requestId = responseString(response, "requestId", "request_id") ?? header(response, "x-request-id");
|
|
1670
|
+
const upstreamCode = responseString(response, "errorCode", "error_code", "code");
|
|
1671
|
+
return new QMindError(errorCodeForStatus(response.status), `${operation} failed`, {
|
|
1672
|
+
details: { operation },
|
|
1673
|
+
...requestId === void 0 ? {} : { requestId },
|
|
1674
|
+
status: response.status,
|
|
1675
|
+
...upstreamCode === void 0 ? {} : { upstreamCode }
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
async function authRequest(transport, method, path, operation, body) {
|
|
1679
|
+
const request = {
|
|
1680
|
+
...body === void 0 ? {} : { body: {
|
|
1681
|
+
kind: "json",
|
|
1682
|
+
value: body
|
|
1683
|
+
} },
|
|
1684
|
+
destination: {
|
|
1685
|
+
kind: "host",
|
|
1686
|
+
path
|
|
1687
|
+
},
|
|
1688
|
+
idempotent: method === "GET",
|
|
1689
|
+
method
|
|
1690
|
+
};
|
|
1691
|
+
let response;
|
|
1692
|
+
try {
|
|
1693
|
+
response = await transport.request(request);
|
|
1694
|
+
} catch (cause) {
|
|
1695
|
+
if (cause instanceof QMindError) throw cause;
|
|
1696
|
+
throw new QMindError("NETWORK_ERROR", `${operation} failed`);
|
|
1697
|
+
}
|
|
1698
|
+
if (response.status < 200 || response.status >= 300) throw authResponseError(response, operation);
|
|
1699
|
+
const parsed = responseRecordSchema.safeParse(response.body);
|
|
1700
|
+
if (!parsed.success) throw new QMindError("PROTOCOL_ERROR", `${operation} returned an invalid response`, {
|
|
1701
|
+
details: { operation },
|
|
1702
|
+
status: response.status
|
|
1703
|
+
});
|
|
1704
|
+
return parsed.data;
|
|
1705
|
+
}
|
|
1706
|
+
function credentialsFromLogin(value, sashOrigin) {
|
|
1707
|
+
return {
|
|
1708
|
+
deviceToken: value.token,
|
|
1709
|
+
expiresAt: value.expires_at,
|
|
1710
|
+
refreshToken: value.refresh_token,
|
|
1711
|
+
refreshTokenExpiresAt: value.refresh_token_expires_at,
|
|
1712
|
+
sashOrigin,
|
|
1713
|
+
savedAt: "",
|
|
1714
|
+
schemaVersion: 2
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
function isExpired(timestamp, now, skew = 0) {
|
|
1718
|
+
if (timestamp.length === 0) return false;
|
|
1719
|
+
const value = Date.parse(timestamp);
|
|
1720
|
+
return Number.isFinite(value) && value <= now + skew;
|
|
1721
|
+
}
|
|
1722
|
+
function aborted(signal) {
|
|
1723
|
+
return signal?.aborted === true ? new QMindError("ABORTED", "QMind authorization was aborted") : void 0;
|
|
1724
|
+
}
|
|
1725
|
+
function createSystemClock() {
|
|
1726
|
+
return Object.freeze({
|
|
1727
|
+
now: Date.now,
|
|
1728
|
+
sleep(milliseconds, signal) {
|
|
1729
|
+
return new Promise((resolve, reject) => {
|
|
1730
|
+
const alreadyAborted = aborted(signal);
|
|
1731
|
+
if (alreadyAborted !== void 0) {
|
|
1732
|
+
reject(alreadyAborted);
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
const onAbort = () => {
|
|
1736
|
+
clearTimeout(timer);
|
|
1737
|
+
reject(new QMindError("ABORTED", "QMind authorization was aborted"));
|
|
1738
|
+
};
|
|
1739
|
+
const timer = setTimeout(() => {
|
|
1740
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1741
|
+
resolve();
|
|
1742
|
+
}, milliseconds);
|
|
1743
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
function createQMindCliAuthManager(options) {
|
|
1749
|
+
let credentials = options.credentials;
|
|
1750
|
+
let explicitToken = options.explicitToken;
|
|
1751
|
+
let token = "";
|
|
1752
|
+
let refreshPromise;
|
|
1753
|
+
const login = async (loginOptions = {}) => {
|
|
1754
|
+
if (options.config.nonInteractive) throw new QMindError("AUTH_REQUIRED", "login requires an interactive terminal");
|
|
1755
|
+
const pkce = createPkce();
|
|
1756
|
+
const browserUrl = new URL("/device/selectAccounts", options.config.dashboardOrigin);
|
|
1757
|
+
browserUrl.searchParams.set("nonce", pkce.nonce);
|
|
1758
|
+
browserUrl.searchParams.set("challenge", pkce.challenge);
|
|
1759
|
+
browserUrl.searchParams.set("challenge_method", "S256");
|
|
1760
|
+
browserUrl.searchParams.set("machine_id", pkce.nonce);
|
|
1761
|
+
if (loginOptions.clientId !== void 0 && loginOptions.clientId.trim().length > 0) browserUrl.searchParams.set("client_id", loginOptions.clientId.trim());
|
|
1762
|
+
options.reporter.info(`Opening browser for authorization. If it does not open, visit:\n${browserUrl.toString()}`);
|
|
1763
|
+
if (loginOptions.openBrowser !== false) try {
|
|
1764
|
+
await options.browser.open(browserUrl.toString());
|
|
1765
|
+
} catch {
|
|
1766
|
+
options.reporter.warn("warning: unable to open a browser; use the authorization URL above");
|
|
1767
|
+
}
|
|
1768
|
+
options.reporter.info("Waiting for authorization (timeout: 5m)...");
|
|
1769
|
+
const deadline = options.clock.now() + LOGIN_TIMEOUT_MS;
|
|
1770
|
+
while (options.clock.now() < deadline) {
|
|
1771
|
+
const abortError = aborted(loginOptions.signal);
|
|
1772
|
+
if (abortError !== void 0) throw abortError;
|
|
1773
|
+
await options.clock.sleep(POLL_INTERVAL_MS, loginOptions.signal);
|
|
1774
|
+
const pollPath = `/api/v1/deviceToken/poll?${new URLSearchParams({
|
|
1775
|
+
challenge_method: "S256",
|
|
1776
|
+
nonce: pkce.nonce,
|
|
1777
|
+
verifier: pkce.verifier
|
|
1778
|
+
}).toString()}`;
|
|
1779
|
+
try {
|
|
1780
|
+
const raw = await authRequest(options.transport, "GET", pollPath, "device authorization polling");
|
|
1781
|
+
const parsed = devicePollSchema.safeParse(raw);
|
|
1782
|
+
if (!parsed.success) throw new QMindError("PROTOCOL_ERROR", "device authorization returned an invalid response");
|
|
1783
|
+
credentials = credentialsFromLogin(parsed.data, options.config.sashOrigin);
|
|
1784
|
+
await options.vault.save(credentials);
|
|
1785
|
+
explicitToken = "";
|
|
1786
|
+
token = credentials.deviceToken;
|
|
1787
|
+
return {
|
|
1788
|
+
expiresAt: credentials.expiresAt,
|
|
1789
|
+
sashOrigin: credentials.sashOrigin
|
|
1790
|
+
};
|
|
1791
|
+
} catch (error) {
|
|
1792
|
+
if (error instanceof QMindError && error.status === 404) continue;
|
|
1793
|
+
throw error;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
throw new QMindError("AUTH_REQUIRED", "authorization timed out after 5m");
|
|
1797
|
+
};
|
|
1798
|
+
const refreshInternal = async (reason) => {
|
|
1799
|
+
if (credentials === void 0 || credentials.refreshToken.length === 0) {
|
|
1800
|
+
token = "";
|
|
1801
|
+
throw new QMindError("AUTH_REQUIRED", `credentials cannot be refreshed after ${reason}`);
|
|
1802
|
+
}
|
|
1803
|
+
if (isExpired(credentials.refreshTokenExpiresAt, options.clock.now())) {
|
|
1804
|
+
await options.vault.deleteIf(credentials);
|
|
1805
|
+
credentials = void 0;
|
|
1806
|
+
token = "";
|
|
1807
|
+
throw new QMindError("AUTH_REQUIRED", "stored refresh credentials have expired");
|
|
1808
|
+
}
|
|
1809
|
+
const raw = await authRequest(options.transport, "POST", "/api/v1/deviceToken/refresh", "credential refresh", { refresh_token: credentials.refreshToken });
|
|
1810
|
+
const parsed = refreshSchema.safeParse(raw);
|
|
1811
|
+
if (!parsed.success) throw new QMindError("PROTOCOL_ERROR", "credential refresh returned an invalid response");
|
|
1812
|
+
const basis = credentials;
|
|
1813
|
+
const legacy = basis.schemaVersion !== 2;
|
|
1814
|
+
const refreshed = {
|
|
1815
|
+
...basis,
|
|
1816
|
+
deviceToken: parsed.data.device_token,
|
|
1817
|
+
expiresAt: parsed.data.expires_at,
|
|
1818
|
+
refreshToken: parsed.data.refresh_token ?? basis.refreshToken,
|
|
1819
|
+
refreshTokenExpiresAt: parsed.data.refresh_token_expires_at ?? basis.refreshTokenExpiresAt,
|
|
1820
|
+
sashOrigin: options.config.sashOrigin,
|
|
1821
|
+
schemaVersion: 2
|
|
1822
|
+
};
|
|
1823
|
+
const current = await options.vault.compareAndSwap(basis, refreshed, { migrateLegacy: legacy }) ? refreshed : await options.vault.load();
|
|
1824
|
+
if (current === void 0) {
|
|
1825
|
+
credentials = void 0;
|
|
1826
|
+
token = "";
|
|
1827
|
+
throw new QMindError("AUTH_REQUIRED", "credentials changed during refresh");
|
|
1828
|
+
}
|
|
1829
|
+
credentials = current;
|
|
1830
|
+
token = current.deviceToken;
|
|
1831
|
+
return token;
|
|
1832
|
+
};
|
|
1833
|
+
const manager = {
|
|
1834
|
+
async getToken() {
|
|
1835
|
+
if (token.length > 0) return token;
|
|
1836
|
+
const candidate = explicitToken || credentials?.deviceToken || "";
|
|
1837
|
+
if (candidate.length === 0) {
|
|
1838
|
+
if (options.config.nonInteractive) throw new QMindError("AUTH_REQUIRED", `no credentials found; run '${options.loginCommand ?? "qmind login"}' in an interactive terminal`);
|
|
1839
|
+
await login();
|
|
1840
|
+
return token;
|
|
1841
|
+
}
|
|
1842
|
+
if (candidate.startsWith("pt-")) {
|
|
1843
|
+
const raw = await authRequest(options.transport, "POST", "/api/v1/jobToken/exchange", "personal token exchange", { personal_token: candidate });
|
|
1844
|
+
const parsed = personalTokenExchangeSchema.safeParse(raw);
|
|
1845
|
+
if (!parsed.success) throw new QMindError("PROTOCOL_ERROR", "personal token exchange returned an invalid response");
|
|
1846
|
+
token = parsed.data.token;
|
|
1847
|
+
return token;
|
|
1848
|
+
}
|
|
1849
|
+
if (credentials !== void 0 && candidate === credentials.deviceToken && isExpired(credentials.expiresAt, options.clock.now(), EXPIRY_SKEW_MS)) return manager.refreshToken("expired", candidate);
|
|
1850
|
+
token = candidate;
|
|
1851
|
+
return token;
|
|
1852
|
+
},
|
|
1853
|
+
login,
|
|
1854
|
+
async logout() {
|
|
1855
|
+
await options.vault.delete();
|
|
1856
|
+
credentials = void 0;
|
|
1857
|
+
explicitToken = "";
|
|
1858
|
+
token = "";
|
|
1859
|
+
refreshPromise = void 0;
|
|
1860
|
+
},
|
|
1861
|
+
async refreshToken(reason, rejectedToken) {
|
|
1862
|
+
if (rejectedToken !== void 0 && token.length > 0 && token !== rejectedToken) return token;
|
|
1863
|
+
refreshPromise ??= refreshInternal(reason).finally(() => {
|
|
1864
|
+
refreshPromise = void 0;
|
|
1865
|
+
});
|
|
1866
|
+
return await refreshPromise;
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
return Object.freeze(manager);
|
|
1870
|
+
}
|
|
1871
|
+
//#endregion
|
|
1872
|
+
//#region ../node-host/src/authenticated-transport.ts
|
|
1873
|
+
function withBearer(request, token) {
|
|
1874
|
+
const headers = Object.fromEntries(Object.entries(request.headers ?? {}).filter(([name]) => name.toLowerCase() !== "authorization"));
|
|
1875
|
+
return {
|
|
1876
|
+
...request,
|
|
1877
|
+
headers: {
|
|
1878
|
+
...headers,
|
|
1879
|
+
authorization: `Bearer ${token}`
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
/** A caller-supplied per-request bearer wins over process-level auth and skips refresh. */
|
|
1884
|
+
function hasRequestAuthorization(request) {
|
|
1885
|
+
const headers = request.headers;
|
|
1886
|
+
if (headers === void 0) return false;
|
|
1887
|
+
return Object.keys(headers).some((name) => name.toLowerCase() === "authorization");
|
|
1888
|
+
}
|
|
1889
|
+
function hostCredentialRequired() {
|
|
1890
|
+
return new QMindError("AUTH_REQUIRED", "The embedding host did not provide a credential for this request.");
|
|
1891
|
+
}
|
|
1892
|
+
function createAuthenticatedQMindTransport(transport, auth, requestCredential) {
|
|
1893
|
+
return Object.freeze({
|
|
1894
|
+
async request(request) {
|
|
1895
|
+
if (request.destination.kind === "signed") return await transport.request(request);
|
|
1896
|
+
const providedToken = (await requestCredential?.())?.trim();
|
|
1897
|
+
if (providedToken) return await transport.request(withBearer(request, providedToken));
|
|
1898
|
+
if (hasRequestAuthorization(request)) return await transport.request(request);
|
|
1899
|
+
if (requestCredential !== void 0) throw hostCredentialRequired();
|
|
1900
|
+
const token = await auth.getToken();
|
|
1901
|
+
const response = await transport.request(withBearer(request, token));
|
|
1902
|
+
if (response.status !== 401) return response;
|
|
1903
|
+
const refreshed = await auth.refreshToken("unauthorized", token);
|
|
1904
|
+
return await transport.request(withBearer(request, refreshed));
|
|
1905
|
+
},
|
|
1906
|
+
async stream(request) {
|
|
1907
|
+
const providedToken = (await requestCredential?.())?.trim();
|
|
1908
|
+
if (providedToken) return await transport.stream(withBearer(request, providedToken));
|
|
1909
|
+
if (hasRequestAuthorization(request)) return await transport.stream(request);
|
|
1910
|
+
if (requestCredential !== void 0) throw hostCredentialRequired();
|
|
1911
|
+
const token = await auth.getToken();
|
|
1912
|
+
const response = await transport.stream(withBearer(request, token));
|
|
1913
|
+
if (response.kind !== "response" || response.status !== 401) return response;
|
|
1914
|
+
const refreshed = await auth.refreshToken("unauthorized", token);
|
|
1915
|
+
return await transport.stream(withBearer(request, refreshed));
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
//#endregion
|
|
1920
|
+
//#region ../node-host/src/config.ts
|
|
1921
|
+
const fileConfigSchema = z.object({
|
|
1922
|
+
client: z.string().optional(),
|
|
1923
|
+
dashboard_url: z.string().optional(),
|
|
1924
|
+
proxy_from_env: z.boolean().optional(),
|
|
1925
|
+
request_timeout_ms: z.number().int().positive().optional(),
|
|
1926
|
+
sash_url: z.string().optional(),
|
|
1927
|
+
stream_idle_timeout_ms: z.number().int().positive().optional(),
|
|
1928
|
+
tls_ca_file: z.string().optional(),
|
|
1929
|
+
tls_reject_unauthorized: z.boolean().optional(),
|
|
1930
|
+
update_public_key: z.string().optional()
|
|
1931
|
+
}).passthrough();
|
|
1932
|
+
const PRESETS = Object.freeze({
|
|
1933
|
+
daily: {
|
|
1934
|
+
dashboardOrigin: "https://daily.qoder.ai",
|
|
1935
|
+
sashOrigin: "https://daily-openapi.qoder.sh"
|
|
1936
|
+
},
|
|
1937
|
+
prod: {
|
|
1938
|
+
dashboardOrigin: "https://qoder.com",
|
|
1939
|
+
sashOrigin: "https://openapi.qoder.sh"
|
|
1940
|
+
},
|
|
1941
|
+
test: {
|
|
1942
|
+
dashboardOrigin: "https://test.qoder.ai",
|
|
1943
|
+
sashOrigin: "https://test-openapi.qoder.sh"
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
/**
|
|
1947
|
+
* Embedding product identity → that product's backends per environment. Two products share neither
|
|
1948
|
+
* dashboard nor Sash, so a host that declares its identity must be routed by it; falling back to the
|
|
1949
|
+
* environment table alone would send the second product's requests to the first product's backend,
|
|
1950
|
+
* which is indistinguishable from a correct call on the wire.
|
|
1951
|
+
*
|
|
1952
|
+
* Cells that no backend exists for yet are absent rather than borrowed: resolving one throws.
|
|
1953
|
+
*/
|
|
1954
|
+
const PRODUCT_PRESETS = Object.freeze({
|
|
1955
|
+
qoder: PRESETS,
|
|
1956
|
+
"qoder-cn": Object.freeze({
|
|
1957
|
+
prod: {
|
|
1958
|
+
dashboardOrigin: "https://qoder.cn",
|
|
1959
|
+
sashOrigin: "https://openapi.qoder.com.cn"
|
|
1960
|
+
},
|
|
1961
|
+
test: {
|
|
1962
|
+
dashboardOrigin: "https://test.qoder.com.cn",
|
|
1963
|
+
sashOrigin: "https://test-openapi.qoder.com.cn"
|
|
1964
|
+
}
|
|
1965
|
+
})
|
|
1966
|
+
});
|
|
1967
|
+
function invalidConfig(message, field) {
|
|
1968
|
+
return new QMindError("INVALID_ARGUMENT", message, { ...field === void 0 ? {} : { details: { field } } });
|
|
1969
|
+
}
|
|
1970
|
+
function normalizeQMindOrigin(rawValue, field) {
|
|
1971
|
+
let url;
|
|
1972
|
+
try {
|
|
1973
|
+
url = new URL(rawValue);
|
|
1974
|
+
} catch {
|
|
1975
|
+
throw invalidConfig(`${field} must be a valid HTTP(S) origin`, field);
|
|
1976
|
+
}
|
|
1977
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0 || url.pathname !== "/" || url.search.length > 0 || url.hash.length > 0) throw invalidConfig(`${field} must contain only an HTTP(S) scheme and authority`, field);
|
|
1978
|
+
return url.origin;
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Resolves one registered product/environment without fallback. The default Host config loader
|
|
1982
|
+
* invokes this lazily after explicit overrides; fixed-product callers select the target up front.
|
|
1983
|
+
*/
|
|
1984
|
+
function resolveQMindProductEndpoints(productId, presetName, field = "env") {
|
|
1985
|
+
const presets = Object.hasOwn(PRODUCT_PRESETS, productId) ? PRODUCT_PRESETS[productId] : void 0;
|
|
1986
|
+
if (presets === void 0) throw invalidConfig(`no QMind endpoints are registered for product ${JSON.stringify(productId)}`, field);
|
|
1987
|
+
const preset = Object.hasOwn(presets, presetName) ? presets[presetName] : void 0;
|
|
1988
|
+
if (preset === void 0) throw invalidConfig(`product ${JSON.stringify(productId)} has no ${JSON.stringify(presetName)} endpoints; set ${field} explicitly`, field);
|
|
1989
|
+
return { ...preset };
|
|
1990
|
+
}
|
|
1991
|
+
function optionalTrimmed(value) {
|
|
1992
|
+
const trimmed = value?.trim();
|
|
1993
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
1994
|
+
}
|
|
1995
|
+
function parseBoolean(value, field) {
|
|
1996
|
+
const normalized = optionalTrimmed(value)?.toLowerCase();
|
|
1997
|
+
if (normalized === void 0) return void 0;
|
|
1998
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
1999
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
2000
|
+
throw invalidConfig(`${field} must be 1, 0, true, or false`, field);
|
|
2001
|
+
}
|
|
2002
|
+
function parsePositiveInteger(value, field) {
|
|
2003
|
+
const normalized = optionalTrimmed(value);
|
|
2004
|
+
if (normalized === void 0) return void 0;
|
|
2005
|
+
const parsed = Number(normalized);
|
|
2006
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) throw invalidConfig(`${field} must be a positive safe integer`, field);
|
|
2007
|
+
return parsed;
|
|
2008
|
+
}
|
|
2009
|
+
function qmindEnvironmentAssignment(line) {
|
|
2010
|
+
const trimmed = line.trim();
|
|
2011
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) return void 0;
|
|
2012
|
+
const assignment = trimmed.startsWith("export ") ? trimmed.slice(7) : trimmed;
|
|
2013
|
+
const separator = assignment.indexOf("=");
|
|
2014
|
+
if (separator < 1) return void 0;
|
|
2015
|
+
const key = assignment.slice(0, separator).trim();
|
|
2016
|
+
if (!/^QMIND_[A-Z0-9_]+$/.test(key)) return void 0;
|
|
2017
|
+
let value = assignment.slice(separator + 1).trim();
|
|
2018
|
+
if (value.length >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
|
|
2019
|
+
return [key, value];
|
|
2020
|
+
}
|
|
2021
|
+
async function mergedEnvironment(environment, legacyEnvironmentPath) {
|
|
2022
|
+
const merged = { ...environment };
|
|
2023
|
+
if (legacyEnvironmentPath === false) return merged;
|
|
2024
|
+
let content;
|
|
2025
|
+
try {
|
|
2026
|
+
content = await readFile(legacyEnvironmentPath, "utf8");
|
|
2027
|
+
} catch (error) {
|
|
2028
|
+
if (error.code === "ENOENT") return merged;
|
|
2029
|
+
throw invalidConfig("unable to read the legacy QMind environment file");
|
|
2030
|
+
}
|
|
2031
|
+
for (const line of content.split(/\r?\n/)) {
|
|
2032
|
+
const parsed = qmindEnvironmentAssignment(line);
|
|
2033
|
+
if (parsed === void 0) continue;
|
|
2034
|
+
const [key, value] = parsed;
|
|
2035
|
+
if (merged[key] === void 0) merged[key] = value;
|
|
2036
|
+
}
|
|
2037
|
+
return merged;
|
|
2038
|
+
}
|
|
2039
|
+
async function fileConfiguration(homeDirectory, fixedEndpoints = false) {
|
|
2040
|
+
try {
|
|
2041
|
+
return fileConfigSchema.parse(JSON.parse(await readFile(join(homeDirectory, "config.json"), "utf8")));
|
|
2042
|
+
} catch (error) {
|
|
2043
|
+
if (error.code === "ENOENT") return {};
|
|
2044
|
+
if (fixedEndpoints && error instanceof z.ZodError) {
|
|
2045
|
+
const field = error.issues.find((issue) => issue.path[0] === "sash_url" || issue.path[0] === "dashboard_url")?.path[0];
|
|
2046
|
+
if (field !== void 0) throw invalidEndpointHint(`config.json ${String(field)}`);
|
|
2047
|
+
}
|
|
2048
|
+
throw invalidConfig("stored QMind configuration is invalid");
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
function defaultProcessEnvironment() {
|
|
2052
|
+
return process.env;
|
|
2053
|
+
}
|
|
2054
|
+
function invalidEndpointHint(field) {
|
|
2055
|
+
return invalidConfig(`${field} is invalid or conflicts with the selected target; remove ${field} and select the target with --env or paired --sash/--dashboard`, field);
|
|
2056
|
+
}
|
|
2057
|
+
function validateFixedEndpoints(options, environment, file) {
|
|
2058
|
+
const target = options.fixedEndpoints;
|
|
2059
|
+
if (target === void 0) return;
|
|
2060
|
+
const checkOrigin = (raw, expected, field, deprecated = true) => {
|
|
2061
|
+
if (raw === void 0) return;
|
|
2062
|
+
let normalized;
|
|
2063
|
+
try {
|
|
2064
|
+
normalized = normalizeQMindOrigin(raw.trim(), field);
|
|
2065
|
+
} catch {
|
|
2066
|
+
throw invalidEndpointHint(field);
|
|
2067
|
+
}
|
|
2068
|
+
if (normalized !== expected) throw invalidEndpointHint(field);
|
|
2069
|
+
if (deprecated) options.reporter?.warn(`warning: ${field} is deprecated and does not select a target; remove ${field}`);
|
|
2070
|
+
};
|
|
2071
|
+
checkOrigin(environment.QMIND_SASH_URL, target.sashOrigin, "QMIND_SASH_URL");
|
|
2072
|
+
checkOrigin(environment.QMIND_DASHBOARD_URL, target.dashboardOrigin, "QMIND_DASHBOARD_URL");
|
|
2073
|
+
checkOrigin(file.sash_url, target.sashOrigin, "config.json sash_url");
|
|
2074
|
+
checkOrigin(file.dashboard_url, target.dashboardOrigin, "config.json dashboard_url");
|
|
2075
|
+
checkOrigin(options.credentialSashOrigin, target.sashOrigin, "credentials.json sash_url", false);
|
|
2076
|
+
if (environment.QMIND_ENV !== void 0) {
|
|
2077
|
+
let legacy;
|
|
2078
|
+
try {
|
|
2079
|
+
legacy = resolveQMindProductEndpoints(options.productId ?? "", environment.QMIND_ENV.trim());
|
|
2080
|
+
} catch {
|
|
2081
|
+
throw invalidEndpointHint("QMIND_ENV");
|
|
2082
|
+
}
|
|
2083
|
+
if (legacy.sashOrigin !== target.sashOrigin || legacy.dashboardOrigin !== target.dashboardOrigin) throw invalidEndpointHint("QMIND_ENV");
|
|
2084
|
+
options.reporter?.warn("warning: QMIND_ENV is deprecated and does not select a target; remove QMIND_ENV");
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
async function loadQMindCliConfig(options = {}) {
|
|
2088
|
+
const initialEnvironment = options.environment ?? defaultProcessEnvironment();
|
|
2089
|
+
const baseHome = options.homeDirectory ?? homedir();
|
|
2090
|
+
const environment = await mergedEnvironment(initialEnvironment, options.legacyEnvironmentPath === void 0 ? join(baseHome, ".qmind-env") : options.legacyEnvironmentPath);
|
|
2091
|
+
const configuredHome = optionalTrimmed(environment.QMIND_HOME);
|
|
2092
|
+
const homeDirectory = resolve(options.homeDirectory ?? configuredHome ?? join(baseHome, ".qmind"));
|
|
2093
|
+
const file = await fileConfiguration(homeDirectory, options.fixedEndpoints !== void 0);
|
|
2094
|
+
validateFixedEndpoints(options, environment, file);
|
|
2095
|
+
const flags = options.flags ?? {};
|
|
2096
|
+
const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
2097
|
+
const presetName = optionalTrimmed(environment.QMIND_ENV) ?? "prod";
|
|
2098
|
+
const productId = optionalTrimmed(options.productId);
|
|
2099
|
+
const defaultOrigin = (field) => {
|
|
2100
|
+
const preset = productId === void 0 ? PRESETS[presetName] ?? PRESETS.prod : resolveQMindProductEndpoints(productId, presetName, field);
|
|
2101
|
+
return field === "sash" ? preset.sashOrigin : preset.dashboardOrigin;
|
|
2102
|
+
};
|
|
2103
|
+
if (options.fixedEndpoints === void 0 && productId === void 0 && !(presetName in PRESETS)) options.reporter?.warn(`warning: unknown QMIND_ENV ${JSON.stringify(presetName)}, falling back to prod`);
|
|
2104
|
+
const sashOrigin = normalizeQMindOrigin(options.fixedEndpoints?.sashOrigin ?? optionalTrimmed(flags.sash) ?? optionalTrimmed(environment.QMIND_SASH_URL) ?? optionalTrimmed(file.sash_url) ?? optionalTrimmed(options.credentialSashOrigin) ?? defaultOrigin("sash"), "sash");
|
|
2105
|
+
const dashboardOrigin = normalizeQMindOrigin(options.fixedEndpoints?.dashboardOrigin ?? optionalTrimmed(flags.dashboard) ?? optionalTrimmed(environment.QMIND_DASHBOARD_URL) ?? optionalTrimmed(file.dashboard_url) ?? defaultOrigin("dashboard"), "dashboard");
|
|
2106
|
+
const runtime = Object.freeze({
|
|
2107
|
+
client: optionalTrimmed(flags.client) ?? optionalTrimmed(environment.QMIND_CLIENT) ?? optionalTrimmed(file.client) ?? "qmind",
|
|
2108
|
+
dashboardOrigin,
|
|
2109
|
+
homeDirectory,
|
|
2110
|
+
nonInteractive: flags.nonInteractive === true || parseBoolean(environment.QMIND_NON_INTERACTIVE, "QMIND_NON_INTERACTIVE") === true || !stdinIsTTY,
|
|
2111
|
+
sashOrigin,
|
|
2112
|
+
updatePublicKey: optionalTrimmed(environment.QMIND_UPDATE_PUBLIC_KEY) ?? optionalTrimmed(file.update_public_key) ?? ""
|
|
2113
|
+
});
|
|
2114
|
+
return {
|
|
2115
|
+
debug: parseBoolean(environment.QMIND_DEBUG, "QMIND_DEBUG") ?? false,
|
|
2116
|
+
environment,
|
|
2117
|
+
proxyFromEnvironment: parseBoolean(environment.QMIND_PROXY_FROM_ENV, "QMIND_PROXY_FROM_ENV") ?? file.proxy_from_env ?? true,
|
|
2118
|
+
requestTimeoutMs: parsePositiveInteger(environment.QMIND_REQUEST_TIMEOUT_MS, "QMIND_REQUEST_TIMEOUT_MS") ?? file.request_timeout_ms ?? 3e4,
|
|
2119
|
+
runtime,
|
|
2120
|
+
streamIdleTimeoutMs: parsePositiveInteger(environment.QMIND_STREAM_IDLE_TIMEOUT_MS, "QMIND_STREAM_IDLE_TIMEOUT_MS") ?? file.stream_idle_timeout_ms ?? 12e4,
|
|
2121
|
+
...(() => {
|
|
2122
|
+
const tlsCaFile = optionalTrimmed(environment.QMIND_TLS_CA_FILE) ?? optionalTrimmed(file.tls_ca_file);
|
|
2123
|
+
return tlsCaFile === void 0 ? {} : { tlsCaFile };
|
|
2124
|
+
})(),
|
|
2125
|
+
tlsRejectUnauthorized: parseBoolean(environment.QMIND_TLS_REJECT_UNAUTHORIZED, "QMIND_TLS_REJECT_UNAUTHORIZED") ?? file.tls_reject_unauthorized ?? true,
|
|
2126
|
+
token: optionalTrimmed(flags.token) ?? optionalTrimmed(environment.QMIND_TOKEN) ?? ""
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
//#endregion
|
|
2130
|
+
//#region ../node-host/src/credentials.ts
|
|
2131
|
+
const serializedCredentialsSchema = z.object({
|
|
2132
|
+
device_token: z.string().default(""),
|
|
2133
|
+
expires_at: z.string().default(""),
|
|
2134
|
+
refresh_token: z.string().default(""),
|
|
2135
|
+
refresh_token_expires_at: z.string().default(""),
|
|
2136
|
+
sash_url: z.string().default(""),
|
|
2137
|
+
saved_at: z.string().default(""),
|
|
2138
|
+
schema_version: z.number().int().positive().optional()
|
|
2139
|
+
}).passthrough();
|
|
2140
|
+
function storedCredentialsInvalid() {
|
|
2141
|
+
return new QMindError("AUTH_REQUIRED", "stored QMind credentials are invalid");
|
|
2142
|
+
}
|
|
2143
|
+
function isMissing(error) {
|
|
2144
|
+
return error.code === "ENOENT";
|
|
2145
|
+
}
|
|
2146
|
+
function isValidOptionalTimestamp(value) {
|
|
2147
|
+
return value.length === 0 || Number.isFinite(Date.parse(value));
|
|
2148
|
+
}
|
|
2149
|
+
function normalizeStoredCredentials(value) {
|
|
2150
|
+
let parsed;
|
|
2151
|
+
try {
|
|
2152
|
+
parsed = serializedCredentialsSchema.parse(value);
|
|
2153
|
+
} catch {
|
|
2154
|
+
throw storedCredentialsInvalid();
|
|
2155
|
+
}
|
|
2156
|
+
if (parsed.schema_version !== void 0 && parsed.schema_version > 2 || parsed.device_token.length === 0 && parsed.refresh_token.length === 0 || !isValidOptionalTimestamp(parsed.expires_at) || !isValidOptionalTimestamp(parsed.refresh_token_expires_at)) throw storedCredentialsInvalid();
|
|
2157
|
+
return {
|
|
2158
|
+
deviceToken: parsed.device_token,
|
|
2159
|
+
expiresAt: parsed.expires_at,
|
|
2160
|
+
refreshToken: parsed.refresh_token,
|
|
2161
|
+
refreshTokenExpiresAt: parsed.refresh_token_expires_at,
|
|
2162
|
+
sashOrigin: parsed.sash_url,
|
|
2163
|
+
savedAt: parsed.saved_at,
|
|
2164
|
+
schemaVersion: parsed.schema_version ?? 1
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
function serializedCredentials(credentials, now) {
|
|
2168
|
+
return `${JSON.stringify({
|
|
2169
|
+
device_token: credentials.deviceToken,
|
|
2170
|
+
expires_at: credentials.expiresAt,
|
|
2171
|
+
refresh_token: credentials.refreshToken,
|
|
2172
|
+
refresh_token_expires_at: credentials.refreshTokenExpiresAt,
|
|
2173
|
+
sash_url: credentials.sashOrigin,
|
|
2174
|
+
saved_at: new Date(now).toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
2175
|
+
schema_version: 2
|
|
2176
|
+
}, null, 2)}\n`;
|
|
2177
|
+
}
|
|
2178
|
+
function sameCredentials(left, right) {
|
|
2179
|
+
return left !== void 0 && right !== void 0 && left.deviceToken === right.deviceToken && left.expiresAt === right.expiresAt && left.refreshToken === right.refreshToken && left.refreshTokenExpiresAt === right.refreshTokenExpiresAt && left.sashOrigin === right.sashOrigin && left.schemaVersion === right.schemaVersion;
|
|
2180
|
+
}
|
|
2181
|
+
async function secureDirectory(path) {
|
|
2182
|
+
await mkdir(path, {
|
|
2183
|
+
mode: 448,
|
|
2184
|
+
recursive: true
|
|
2185
|
+
});
|
|
2186
|
+
const info = await lstat(path);
|
|
2187
|
+
if (!info.isDirectory() || info.isSymbolicLink()) throw storedCredentialsInvalid();
|
|
2188
|
+
if (process.platform !== "win32") await chmod(path, 448);
|
|
2189
|
+
}
|
|
2190
|
+
async function secureRegularFile(path) {
|
|
2191
|
+
const info = await lstat(path);
|
|
2192
|
+
if (!info.isFile() || info.isSymbolicLink()) throw storedCredentialsInvalid();
|
|
2193
|
+
if (process.platform !== "win32" && (info.mode & 63) !== 0) await chmod(path, 384);
|
|
2194
|
+
}
|
|
2195
|
+
async function replaceFileAtomically(tempPath, targetPath) {
|
|
2196
|
+
try {
|
|
2197
|
+
await rename(tempPath, targetPath);
|
|
2198
|
+
return;
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
if (process.platform !== "win32") throw error;
|
|
2201
|
+
}
|
|
2202
|
+
const backupPath = `${targetPath}.${randomBytes(6).toString("hex")}.old`;
|
|
2203
|
+
let backedUp = false;
|
|
2204
|
+
try {
|
|
2205
|
+
try {
|
|
2206
|
+
await rename(targetPath, backupPath);
|
|
2207
|
+
backedUp = true;
|
|
2208
|
+
} catch (error) {
|
|
2209
|
+
if (!isMissing(error)) throw error;
|
|
2210
|
+
}
|
|
2211
|
+
await rename(tempPath, targetPath);
|
|
2212
|
+
if (backedUp) await rm(backupPath, { force: true });
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
if (backedUp) await rename(backupPath, targetPath).catch(() => void 0);
|
|
2215
|
+
throw error;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
async function optionalSecret(operation, fallback) {
|
|
2219
|
+
try {
|
|
2220
|
+
return await operation();
|
|
2221
|
+
} catch {
|
|
2222
|
+
return fallback;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function createNoopQMindSecretStore() {
|
|
2226
|
+
return Object.freeze({
|
|
2227
|
+
async delete() {},
|
|
2228
|
+
async get() {},
|
|
2229
|
+
async set(_token) {}
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
function createFileCredentialVault(options) {
|
|
2233
|
+
const credentialsPath = join(options.homeDirectory, "credentials.json");
|
|
2234
|
+
const legacyBackupPath = `${credentialsPath}.v1.bak`;
|
|
2235
|
+
const secretStore = options.secretStore ?? createNoopQMindSecretStore();
|
|
2236
|
+
const now = options.now ?? Date.now;
|
|
2237
|
+
const loadUnlocked = async () => {
|
|
2238
|
+
let serialized;
|
|
2239
|
+
try {
|
|
2240
|
+
await secureRegularFile(credentialsPath);
|
|
2241
|
+
serialized = JSON.parse(await readFile(credentialsPath, "utf8"));
|
|
2242
|
+
} catch (error) {
|
|
2243
|
+
if (isMissing(error)) return void 0;
|
|
2244
|
+
if (error instanceof QMindError) throw error;
|
|
2245
|
+
throw storedCredentialsInvalid();
|
|
2246
|
+
}
|
|
2247
|
+
const credentials = normalizeStoredCredentials(serialized);
|
|
2248
|
+
const keychainToken = await optionalSecret(() => secretStore.get(), void 0);
|
|
2249
|
+
return keychainToken === void 0 || keychainToken.length === 0 ? credentials : {
|
|
2250
|
+
...credentials,
|
|
2251
|
+
deviceToken: keychainToken
|
|
2252
|
+
};
|
|
2253
|
+
};
|
|
2254
|
+
const withLock = async (operation) => {
|
|
2255
|
+
await secureDirectory(options.homeDirectory);
|
|
2256
|
+
const release = await lockfile.lock(options.homeDirectory, {
|
|
2257
|
+
lockfilePath: join(options.homeDirectory, ".credentials.lock"),
|
|
2258
|
+
realpath: false,
|
|
2259
|
+
retries: {
|
|
2260
|
+
factor: 1.5,
|
|
2261
|
+
maxTimeout: 100,
|
|
2262
|
+
minTimeout: 20,
|
|
2263
|
+
retries: 8
|
|
2264
|
+
},
|
|
2265
|
+
stale: 1e4
|
|
2266
|
+
});
|
|
2267
|
+
try {
|
|
2268
|
+
return await operation();
|
|
2269
|
+
} finally {
|
|
2270
|
+
await release();
|
|
2271
|
+
}
|
|
2272
|
+
};
|
|
2273
|
+
const persist = async (credentials, saveOptions = {}) => {
|
|
2274
|
+
const tempPath = `${credentialsPath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
2275
|
+
try {
|
|
2276
|
+
if (saveOptions.migrateLegacy === true) try {
|
|
2277
|
+
await secureRegularFile(credentialsPath);
|
|
2278
|
+
await copyFile(credentialsPath, legacyBackupPath);
|
|
2279
|
+
if (process.platform !== "win32") await chmod(legacyBackupPath, 384);
|
|
2280
|
+
} catch (error) {
|
|
2281
|
+
if (!isMissing(error)) throw error;
|
|
2282
|
+
}
|
|
2283
|
+
await writeFile(tempPath, serializedCredentials(credentials, now()), {
|
|
2284
|
+
flag: "wx",
|
|
2285
|
+
mode: 384
|
|
2286
|
+
});
|
|
2287
|
+
if (process.platform !== "win32") await chmod(tempPath, 384);
|
|
2288
|
+
await replaceFileAtomically(tempPath, credentialsPath);
|
|
2289
|
+
if (process.platform !== "win32") await chmod(credentialsPath, 384);
|
|
2290
|
+
await optionalSecret(() => secretStore.set(credentials.deviceToken), void 0);
|
|
2291
|
+
} finally {
|
|
2292
|
+
await rm(tempPath, { force: true });
|
|
2293
|
+
}
|
|
2294
|
+
};
|
|
2295
|
+
const deleteUnlocked = async () => {
|
|
2296
|
+
await Promise.all([rm(credentialsPath, { force: true }), rm(legacyBackupPath, { force: true })]);
|
|
2297
|
+
await optionalSecret(() => secretStore.delete(), void 0);
|
|
2298
|
+
};
|
|
2299
|
+
return Object.freeze({
|
|
2300
|
+
async compareAndSwap(expected, credentials, saveOptions = {}) {
|
|
2301
|
+
return await withLock(async () => {
|
|
2302
|
+
if (!sameCredentials(await loadUnlocked(), expected)) return false;
|
|
2303
|
+
await persist(credentials, saveOptions);
|
|
2304
|
+
return true;
|
|
2305
|
+
});
|
|
2306
|
+
},
|
|
2307
|
+
async delete() {
|
|
2308
|
+
await withLock(deleteUnlocked);
|
|
2309
|
+
},
|
|
2310
|
+
async deleteIf(expected) {
|
|
2311
|
+
return await withLock(async () => {
|
|
2312
|
+
if (!sameCredentials(await loadUnlocked(), expected)) return false;
|
|
2313
|
+
await deleteUnlocked();
|
|
2314
|
+
return true;
|
|
2315
|
+
});
|
|
2316
|
+
},
|
|
2317
|
+
async load() {
|
|
2318
|
+
return await loadUnlocked();
|
|
2319
|
+
},
|
|
2320
|
+
async save(credentials, saveOptions = {}) {
|
|
2321
|
+
await withLock(async () => persist(credentials, saveOptions));
|
|
2322
|
+
}
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
//#endregion
|
|
2326
|
+
//#region ../node-host/src/files.ts
|
|
2327
|
+
function safeQMindDownloadName(title, fallback) {
|
|
2328
|
+
const normalized = [...title.normalize("NFKC")].map((character) => {
|
|
2329
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
2330
|
+
return codePoint <= 31 || codePoint === 127 ? "_" : character;
|
|
2331
|
+
}).join("").replace(/[\\/]/g, "_").replace(/^\.+$/, "").trim().slice(0, 240);
|
|
2332
|
+
const safe = basename(normalized);
|
|
2333
|
+
if (safe.length > 0 && safe !== "." && safe !== "..") return safe;
|
|
2334
|
+
return basename(fallback.replace(/[\\/]/g, "_")) || "download";
|
|
2335
|
+
}
|
|
2336
|
+
async function commitQMindTemporaryFile(temporary, path, overwrite) {
|
|
2337
|
+
if (!overwrite) {
|
|
2338
|
+
await link(temporary, path).catch((error) => {
|
|
2339
|
+
if (error.code === "EEXIST") throw new QMindError("CONFLICT", `target already exists: ${path}`);
|
|
2340
|
+
throw error;
|
|
2341
|
+
});
|
|
2342
|
+
await rm(temporary, { force: true });
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
if (process.platform !== "win32") {
|
|
2346
|
+
await rename(temporary, path);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
const backup = `${path}.${randomBytes(8).toString("hex")}.old`;
|
|
2350
|
+
let backedUp = false;
|
|
2351
|
+
try {
|
|
2352
|
+
try {
|
|
2353
|
+
await rename(path, backup);
|
|
2354
|
+
backedUp = true;
|
|
2355
|
+
} catch (error) {
|
|
2356
|
+
if (error.code !== "ENOENT") throw error;
|
|
2357
|
+
}
|
|
2358
|
+
await rename(temporary, path);
|
|
2359
|
+
if (backedUp) await rm(backup, { force: true });
|
|
2360
|
+
} catch (error) {
|
|
2361
|
+
if (backedUp) await rename(backup, path).catch(() => void 0);
|
|
2362
|
+
throw error;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
/** Atomic write with a no-clobber default. Temporary files are always cleaned. */
|
|
2366
|
+
async function atomicQMindWrite(path, content, overwrite = false) {
|
|
2367
|
+
await mkdir(dirname(path), { recursive: true });
|
|
2368
|
+
const temporary = join(dirname(path), `.${basename(path)}.${randomBytes(8).toString("hex")}.tmp`);
|
|
2369
|
+
try {
|
|
2370
|
+
await writeFile(temporary, content, {
|
|
2371
|
+
flag: "wx",
|
|
2372
|
+
mode: 420
|
|
2373
|
+
});
|
|
2374
|
+
await commitQMindTemporaryFile(temporary, path, overwrite);
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
await rm(temporary, { force: true });
|
|
2377
|
+
throw error;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
//#endregion
|
|
2381
|
+
//#region ../node-host/src/node-transport.ts
|
|
2382
|
+
const RETRYABLE_STATUS = /* @__PURE__ */ new Set([
|
|
2383
|
+
408,
|
|
2384
|
+
429,
|
|
2385
|
+
500,
|
|
2386
|
+
502,
|
|
2387
|
+
503,
|
|
2388
|
+
504
|
|
2389
|
+
]);
|
|
2390
|
+
const CREDENTIAL_HEADERS = /* @__PURE__ */ new Set([
|
|
2391
|
+
"authorization",
|
|
2392
|
+
"cookie",
|
|
2393
|
+
"proxy-authorization",
|
|
2394
|
+
"x-csrf-token",
|
|
2395
|
+
"x-xsrf-token"
|
|
2396
|
+
]);
|
|
2397
|
+
const SSE_MAX_BUFFER_SIZE = 8388608;
|
|
2398
|
+
function invalidArgument(message, field) {
|
|
2399
|
+
return new QMindError("INVALID_ARGUMENT", message, { details: { field } });
|
|
2400
|
+
}
|
|
2401
|
+
function positiveInteger(value, fallback, field) {
|
|
2402
|
+
if (value === void 0) return fallback;
|
|
2403
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw invalidArgument(`${field} must be a positive safe integer`, field);
|
|
2404
|
+
return value;
|
|
2405
|
+
}
|
|
2406
|
+
function nonNegativeInteger(value, fallback, field) {
|
|
2407
|
+
if (value === void 0) return fallback;
|
|
2408
|
+
if (!Number.isSafeInteger(value) || value < 0) throw invalidArgument(`${field} must be a non-negative safe integer`, field);
|
|
2409
|
+
return value;
|
|
2410
|
+
}
|
|
2411
|
+
function normalizeOrigin(value) {
|
|
2412
|
+
let url;
|
|
2413
|
+
try {
|
|
2414
|
+
url = new URL(value);
|
|
2415
|
+
} catch {
|
|
2416
|
+
throw invalidArgument("origin must be a valid HTTP(S) origin", "origin");
|
|
2417
|
+
}
|
|
2418
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0 || url.pathname !== "/" || url.search.length > 0 || url.hash.length > 0) throw invalidArgument("origin must contain only an HTTP(S) scheme and authority", "origin");
|
|
2419
|
+
return url;
|
|
2420
|
+
}
|
|
2421
|
+
function environmentValue(environment, ...keys) {
|
|
2422
|
+
for (const key of keys) {
|
|
2423
|
+
const value = environment[key]?.trim();
|
|
2424
|
+
if (value !== void 0 && value.length > 0) return value;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
function tlsConnectOptions(options, withTls) {
|
|
2428
|
+
const timeout = positiveInteger(options.connectTimeoutMs, 1e4, "connectTimeoutMs");
|
|
2429
|
+
if (!withTls) return { timeout };
|
|
2430
|
+
const ca = options.tls?.ca;
|
|
2431
|
+
return {
|
|
2432
|
+
...ca === void 0 ? {} : { ca: typeof ca === "string" ? ca : [...ca] },
|
|
2433
|
+
rejectUnauthorized: options.tls?.rejectUnauthorized ?? true,
|
|
2434
|
+
timeout
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
function createDispatchers(options) {
|
|
2438
|
+
const environment = options.environment ?? process.env;
|
|
2439
|
+
if (options.proxyFromEnvironment === false) return {
|
|
2440
|
+
host: new Agent({ connect: tlsConnectOptions(options, true) }),
|
|
2441
|
+
signed: new Agent({ connect: tlsConnectOptions(options, true) })
|
|
2442
|
+
};
|
|
2443
|
+
const httpProxy = environmentValue(environment, "HTTP_PROXY", "http_proxy");
|
|
2444
|
+
const httpsProxy = environmentValue(environment, "HTTPS_PROXY", "https_proxy");
|
|
2445
|
+
const noProxy = environmentValue(environment, "NO_PROXY", "no_proxy");
|
|
2446
|
+
if (httpProxy === void 0 && httpsProxy === void 0) return {
|
|
2447
|
+
host: new Agent({ connect: tlsConnectOptions(options, true) }),
|
|
2448
|
+
signed: new Agent({ connect: tlsConnectOptions(options, true) })
|
|
2449
|
+
};
|
|
2450
|
+
const proxy = {
|
|
2451
|
+
httpProxy: httpProxy ?? "",
|
|
2452
|
+
httpsProxy: httpsProxy ?? "",
|
|
2453
|
+
noProxy: noProxy ?? ""
|
|
2454
|
+
};
|
|
2455
|
+
return {
|
|
2456
|
+
host: new EnvHttpProxyAgent({
|
|
2457
|
+
...proxy,
|
|
2458
|
+
requestTls: tlsConnectOptions(options, true)
|
|
2459
|
+
}),
|
|
2460
|
+
signed: new EnvHttpProxyAgent({
|
|
2461
|
+
...proxy,
|
|
2462
|
+
requestTls: tlsConnectOptions(options, true)
|
|
2463
|
+
})
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
function headersToRecord(headers) {
|
|
2467
|
+
const result = {};
|
|
2468
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
2469
|
+
if (value === void 0) continue;
|
|
2470
|
+
result[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
|
|
2471
|
+
}
|
|
2472
|
+
return result;
|
|
2473
|
+
}
|
|
2474
|
+
function normalizedRequestHeaders(headers) {
|
|
2475
|
+
const result = {};
|
|
2476
|
+
for (const [name, value] of Object.entries(headers ?? {})) result[name.toLowerCase()] = value;
|
|
2477
|
+
return result;
|
|
2478
|
+
}
|
|
2479
|
+
function parseRawBody(rawBody) {
|
|
2480
|
+
if (rawBody.trim().length === 0) return void 0;
|
|
2481
|
+
try {
|
|
2482
|
+
return JSON.parse(rawBody);
|
|
2483
|
+
} catch {
|
|
2484
|
+
return rawBody;
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
async function wireResponse(response) {
|
|
2488
|
+
const rawBody = await response.body.text();
|
|
2489
|
+
return {
|
|
2490
|
+
body: parseRawBody(rawBody),
|
|
2491
|
+
headers: headersToRecord(response.headers),
|
|
2492
|
+
rawBody,
|
|
2493
|
+
status: response.statusCode
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
function assertWireRequest(request) {
|
|
2497
|
+
if (request.timeoutMs !== void 0) positiveInteger(request.timeoutMs, request.timeoutMs, "timeoutMs");
|
|
2498
|
+
if (request.destination.kind === "host") {
|
|
2499
|
+
if (!request.destination.path.startsWith("/") || request.destination.path.startsWith("//")) throw new QMindError("HOST_POLICY_ERROR", "host destination must be root-relative");
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
let url;
|
|
2503
|
+
try {
|
|
2504
|
+
url = new URL(request.destination.url);
|
|
2505
|
+
} catch {
|
|
2506
|
+
throw new QMindError("HOST_POLICY_ERROR", "signed destination must be valid HTTPS");
|
|
2507
|
+
}
|
|
2508
|
+
if (url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0) throw new QMindError("HOST_POLICY_ERROR", "signed destination must be credential-free HTTPS");
|
|
2509
|
+
for (const name of Object.keys(request.headers ?? {})) if (CREDENTIAL_HEADERS.has(name.toLowerCase())) throw new QMindError("HOST_POLICY_ERROR", "host credentials cannot reach a signed URL", { details: { header: name.toLowerCase() } });
|
|
2510
|
+
}
|
|
2511
|
+
function sourceFailure(message, source, yieldedBytes) {
|
|
2512
|
+
return new QMindError("HOST_POLICY_ERROR", message, { details: {
|
|
2513
|
+
filename: source.filename,
|
|
2514
|
+
size: source.size,
|
|
2515
|
+
...yieldedBytes === void 0 ? {} : { yieldedBytes }
|
|
2516
|
+
} });
|
|
2517
|
+
}
|
|
2518
|
+
function assertSource(source) {
|
|
2519
|
+
if (!Number.isSafeInteger(source.size) || source.size <= 0 || typeof source.open !== "function") throw sourceFailure("binary source metadata is invalid", source);
|
|
2520
|
+
}
|
|
2521
|
+
async function* sourceBytes(source, signal, onProgress) {
|
|
2522
|
+
assertSource(source);
|
|
2523
|
+
let bytes = 0;
|
|
2524
|
+
for await (const chunk of source.open()) {
|
|
2525
|
+
if (signal?.aborted === true) throw signal.reason;
|
|
2526
|
+
if (!(chunk instanceof Uint8Array)) throw sourceFailure("binary source yielded a non-Uint8Array chunk", source, bytes);
|
|
2527
|
+
bytes += chunk.byteLength;
|
|
2528
|
+
if (bytes > source.size) throw sourceFailure("binary source yielded more bytes than declared", source, bytes);
|
|
2529
|
+
yield chunk;
|
|
2530
|
+
onProgress?.(bytes);
|
|
2531
|
+
}
|
|
2532
|
+
if (bytes !== source.size) throw sourceFailure("binary source yielded fewer bytes than declared", source, bytes);
|
|
2533
|
+
}
|
|
2534
|
+
function quoteDisposition(value) {
|
|
2535
|
+
return value.replace(/["\r\n]/g, "_");
|
|
2536
|
+
}
|
|
2537
|
+
function multipartPart(name, value, boundary) {
|
|
2538
|
+
return `--${boundary}\r\nContent-Disposition: form-data; name="${quoteDisposition(name)}"\r\n\r\n${value}\r\n`;
|
|
2539
|
+
}
|
|
2540
|
+
function multipartPreamble(source, boundary) {
|
|
2541
|
+
return `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${quoteDisposition(source.filename)}"\r\nContent-Type: ${source.mimeType}\r\n\r\n`;
|
|
2542
|
+
}
|
|
2543
|
+
function byteLength(value) {
|
|
2544
|
+
return Buffer.byteLength(value, "utf8");
|
|
2545
|
+
}
|
|
2546
|
+
function encodeBody(request, boundary) {
|
|
2547
|
+
const initialHeaders = normalizedRequestHeaders(request.headers);
|
|
2548
|
+
const body = request.body;
|
|
2549
|
+
if (body === void 0) return { headers: initialHeaders };
|
|
2550
|
+
if (body.kind === "json") {
|
|
2551
|
+
initialHeaders["content-type"] ??= "application/json";
|
|
2552
|
+
return {
|
|
2553
|
+
body: JSON.stringify(body.value),
|
|
2554
|
+
headers: initialHeaders
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
if (body.kind === "binary") {
|
|
2558
|
+
assertSource(body.source);
|
|
2559
|
+
initialHeaders["content-type"] ??= body.source.mimeType;
|
|
2560
|
+
initialHeaders["content-length"] = String(body.source.size);
|
|
2561
|
+
return {
|
|
2562
|
+
body: Readable.from(sourceBytes(body.source, request.signal, body.onProgress)),
|
|
2563
|
+
headers: initialHeaders
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
const multipartBody = body;
|
|
2567
|
+
assertSource(multipartBody.file);
|
|
2568
|
+
const fieldParts = Object.entries(multipartBody.fields).map(([name, value]) => multipartPart(name, value, boundary));
|
|
2569
|
+
const preamble = multipartPreamble(multipartBody.file, boundary);
|
|
2570
|
+
const closing = `\r\n--${boundary}--\r\n`;
|
|
2571
|
+
const contentLength = fieldParts.reduce((total, part) => total + byteLength(part), 0) + byteLength(preamble) + multipartBody.file.size + byteLength(closing);
|
|
2572
|
+
async function* multipart() {
|
|
2573
|
+
for (const part of fieldParts) yield Buffer.from(part);
|
|
2574
|
+
yield Buffer.from(preamble);
|
|
2575
|
+
yield* sourceBytes(multipartBody.file, request.signal, multipartBody.onProgress);
|
|
2576
|
+
yield Buffer.from(closing);
|
|
2577
|
+
}
|
|
2578
|
+
initialHeaders["content-type"] = `multipart/form-data; boundary=${boundary}`;
|
|
2579
|
+
initialHeaders["content-length"] = String(contentLength);
|
|
2580
|
+
return {
|
|
2581
|
+
body: Readable.from(multipart()),
|
|
2582
|
+
headers: initialHeaders
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
function isTimeoutCause(cause) {
|
|
2586
|
+
const code = cause.code;
|
|
2587
|
+
const name = cause.name;
|
|
2588
|
+
return code === "QMIND_REQUEST_TIMEOUT" || code === "UND_ERR_CONNECT_TIMEOUT" || code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_BODY_TIMEOUT" || name === "TimeoutError";
|
|
2589
|
+
}
|
|
2590
|
+
function safeCause(cause) {
|
|
2591
|
+
const record = cause;
|
|
2592
|
+
return {
|
|
2593
|
+
...typeof record.name === "string" ? { name: record.name } : {},
|
|
2594
|
+
...typeof record.code === "string" ? { code: record.code } : {}
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
function transportFailure(request, cause, deadlineExpired = false) {
|
|
2598
|
+
if (cause instanceof QMindError) return cause;
|
|
2599
|
+
if (request.signal?.aborted === true) return new QMindError("ABORTED", "QMind request was aborted");
|
|
2600
|
+
if (deadlineExpired || isTimeoutCause(cause)) return new QMindError("TIMEOUT", "QMind request timed out", { cause: safeCause(cause) });
|
|
2601
|
+
return new QMindError("NETWORK_ERROR", "QMind network request failed", { cause: safeCause(cause) });
|
|
2602
|
+
}
|
|
2603
|
+
function requestDeadline(signal, milliseconds) {
|
|
2604
|
+
const controller = new AbortController();
|
|
2605
|
+
let timedOut = false;
|
|
2606
|
+
const timeout = setTimeout(() => {
|
|
2607
|
+
timedOut = true;
|
|
2608
|
+
const error = Object.assign(/* @__PURE__ */ new Error("QMind request deadline expired"), {
|
|
2609
|
+
code: "QMIND_REQUEST_TIMEOUT",
|
|
2610
|
+
name: "TimeoutError"
|
|
2611
|
+
});
|
|
2612
|
+
controller.abort(error);
|
|
2613
|
+
}, milliseconds);
|
|
2614
|
+
timeout.unref();
|
|
2615
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
2616
|
+
if (signal?.aborted === true) onAbort();
|
|
2617
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
2618
|
+
const clearDeadline = () => clearTimeout(timeout);
|
|
2619
|
+
return {
|
|
2620
|
+
clearTimeout: clearDeadline,
|
|
2621
|
+
dispose() {
|
|
2622
|
+
clearDeadline();
|
|
2623
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2624
|
+
},
|
|
2625
|
+
signal: controller.signal,
|
|
2626
|
+
get timedOut() {
|
|
2627
|
+
return timedOut;
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
function abortableSleep(milliseconds, signal) {
|
|
2632
|
+
return new Promise((resolve, reject) => {
|
|
2633
|
+
if (signal?.aborted === true) {
|
|
2634
|
+
reject(signal.reason);
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
const onAbort = () => {
|
|
2638
|
+
clearTimeout(timer);
|
|
2639
|
+
reject(signal?.reason);
|
|
2640
|
+
};
|
|
2641
|
+
const timer = setTimeout(() => {
|
|
2642
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2643
|
+
resolve();
|
|
2644
|
+
}, milliseconds);
|
|
2645
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
function requestUrl(origin, request) {
|
|
2649
|
+
return request.destination.kind === "host" ? new URL(request.destination.path, origin) : new URL(request.destination.url);
|
|
2650
|
+
}
|
|
2651
|
+
function debugRequest(options, request, url) {
|
|
2652
|
+
options.debugLog?.(`[qmind-debug] ${request.method} ${url.origin}${url.pathname}`);
|
|
2653
|
+
}
|
|
2654
|
+
async function* decodedEvents(body, request, disposeSignal) {
|
|
2655
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
2656
|
+
const pending = [];
|
|
2657
|
+
let done = false;
|
|
2658
|
+
let parserFailure;
|
|
2659
|
+
const parser = createParser({
|
|
2660
|
+
maxBufferSize: SSE_MAX_BUFFER_SIZE,
|
|
2661
|
+
onError(error) {
|
|
2662
|
+
if (error.type === "max-buffer-size-exceeded") parserFailure = error;
|
|
2663
|
+
},
|
|
2664
|
+
onEvent(event) {
|
|
2665
|
+
if (event.data.trim() === "[DONE]") done = true;
|
|
2666
|
+
else pending.push(event.data);
|
|
2667
|
+
}
|
|
2668
|
+
});
|
|
2669
|
+
const drain = function* () {
|
|
2670
|
+
while (pending.length > 0) {
|
|
2671
|
+
const value = pending.shift();
|
|
2672
|
+
if (value !== void 0) yield value;
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
try {
|
|
2676
|
+
for await (const chunk of body) {
|
|
2677
|
+
parser.feed(decoder.decode(chunk, { stream: true }));
|
|
2678
|
+
if (parserFailure !== void 0) throw new QMindError("STREAM_ERROR", "SSE parser buffer limit exceeded", { details: { limitBytes: SSE_MAX_BUFFER_SIZE } });
|
|
2679
|
+
yield* drain();
|
|
2680
|
+
if (done) return;
|
|
2681
|
+
}
|
|
2682
|
+
parser.feed(decoder.decode());
|
|
2683
|
+
parser.reset({ consume: true });
|
|
2684
|
+
if (parserFailure !== void 0) throw new QMindError("STREAM_ERROR", "SSE parser buffer limit exceeded", { details: { limitBytes: SSE_MAX_BUFFER_SIZE } });
|
|
2685
|
+
yield* drain();
|
|
2686
|
+
} catch (cause) {
|
|
2687
|
+
if (cause instanceof QMindError) throw cause;
|
|
2688
|
+
if (request.signal?.aborted === true) throw new QMindError("ABORTED", "QMind stream was aborted");
|
|
2689
|
+
if (isTimeoutCause(cause)) throw new QMindError("TIMEOUT", "QMind stream timed out", { cause: safeCause(cause) });
|
|
2690
|
+
throw new QMindError("STREAM_ERROR", "QMind SSE stream failed", { cause: safeCause(cause) });
|
|
2691
|
+
} finally {
|
|
2692
|
+
disposeSignal();
|
|
2693
|
+
body.destroy();
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
function createQMindNodeTransport(options) {
|
|
2697
|
+
const origin = normalizeOrigin(options.origin);
|
|
2698
|
+
const requestTimeoutMs = positiveInteger(options.requestTimeoutMs, 3e4, "requestTimeoutMs");
|
|
2699
|
+
const streamIdleTimeoutMs = positiveInteger(options.streamIdleTimeoutMs, 12e4, "streamIdleTimeoutMs");
|
|
2700
|
+
const maxRetries = nonNegativeInteger(options.maxRetries, 2, "maxRetries");
|
|
2701
|
+
const dispatchers = createDispatchers(options);
|
|
2702
|
+
const dispatcherFor = (request) => request.destination.kind === "host" ? dispatchers.host : dispatchers.signed;
|
|
2703
|
+
return Object.freeze({
|
|
2704
|
+
async close() {
|
|
2705
|
+
const unique = /* @__PURE__ */ new Set([dispatchers.host, dispatchers.signed]);
|
|
2706
|
+
await Promise.all([...unique].map(async (dispatcher) => dispatcher.close()));
|
|
2707
|
+
},
|
|
2708
|
+
async downloadToFile(download) {
|
|
2709
|
+
const request$1 = {
|
|
2710
|
+
destination: {
|
|
2711
|
+
kind: "signed",
|
|
2712
|
+
url: download.url
|
|
2713
|
+
},
|
|
2714
|
+
idempotent: true,
|
|
2715
|
+
method: "GET",
|
|
2716
|
+
...download.signal === void 0 ? {} : { signal: download.signal }
|
|
2717
|
+
};
|
|
2718
|
+
assertWireRequest(request$1);
|
|
2719
|
+
const url = requestUrl(origin, request$1);
|
|
2720
|
+
await mkdir(dirname(download.targetPath), { recursive: true });
|
|
2721
|
+
if (download.overwrite !== true) try {
|
|
2722
|
+
await stat(download.targetPath);
|
|
2723
|
+
throw new QMindError("CONFLICT", `target already exists: ${download.targetPath}`);
|
|
2724
|
+
} catch (error) {
|
|
2725
|
+
if (error.code !== "ENOENT") throw error;
|
|
2726
|
+
}
|
|
2727
|
+
const temporary = join(dirname(download.targetPath), `.${basename(download.targetPath)}.${randomBytes(8).toString("hex")}.tmp`);
|
|
2728
|
+
let response;
|
|
2729
|
+
try {
|
|
2730
|
+
response = await request(url, {
|
|
2731
|
+
bodyTimeout: requestTimeoutMs,
|
|
2732
|
+
dispatcher: dispatchers.signed,
|
|
2733
|
+
headersTimeout: requestTimeoutMs,
|
|
2734
|
+
idempotent: true,
|
|
2735
|
+
method: "GET",
|
|
2736
|
+
...download.signal === void 0 ? {} : { signal: download.signal }
|
|
2737
|
+
});
|
|
2738
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
2739
|
+
const decoded = await wireResponse(response);
|
|
2740
|
+
throw new QMindError("REMOTE_ERROR", "download request failed", {
|
|
2741
|
+
...decoded.rawBody === void 0 ? {} : { rawBody: decoded.rawBody },
|
|
2742
|
+
status: decoded.status
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2745
|
+
await pipeline(response.body, createWriteStream(temporary, {
|
|
2746
|
+
flags: "wx",
|
|
2747
|
+
mode: 384
|
|
2748
|
+
}));
|
|
2749
|
+
const size = (await stat(temporary)).size;
|
|
2750
|
+
await chmod(temporary, 420);
|
|
2751
|
+
await commitQMindTemporaryFile(temporary, download.targetPath, download.overwrite === true);
|
|
2752
|
+
return size;
|
|
2753
|
+
} catch (cause) {
|
|
2754
|
+
response?.body.destroy();
|
|
2755
|
+
await rm(temporary, { force: true });
|
|
2756
|
+
if (cause instanceof QMindError) throw cause;
|
|
2757
|
+
throw transportFailure(request$1, cause);
|
|
2758
|
+
}
|
|
2759
|
+
},
|
|
2760
|
+
async request(request$2) {
|
|
2761
|
+
assertWireRequest(request$2);
|
|
2762
|
+
const url = requestUrl(origin, request$2);
|
|
2763
|
+
const attempts = request$2.idempotent ? maxRetries + 1 : 1;
|
|
2764
|
+
let lastFailure;
|
|
2765
|
+
let lastDeadlineExpired = false;
|
|
2766
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
2767
|
+
const deadline = requestDeadline(request$2.signal, request$2.timeoutMs ?? requestTimeoutMs);
|
|
2768
|
+
try {
|
|
2769
|
+
debugRequest(options, request$2, url);
|
|
2770
|
+
const encoded = encodeBody(request$2, `----qmind-${randomBytes(18).toString("hex")}`);
|
|
2771
|
+
const response = await request(url, {
|
|
2772
|
+
...encoded.body === void 0 ? {} : { body: encoded.body },
|
|
2773
|
+
bodyTimeout: request$2.timeoutMs ?? requestTimeoutMs,
|
|
2774
|
+
dispatcher: dispatcherFor(request$2),
|
|
2775
|
+
headers: encoded.headers,
|
|
2776
|
+
headersTimeout: request$2.timeoutMs ?? requestTimeoutMs,
|
|
2777
|
+
idempotent: request$2.idempotent,
|
|
2778
|
+
method: request$2.method,
|
|
2779
|
+
signal: deadline.signal
|
|
2780
|
+
});
|
|
2781
|
+
if (attempt + 1 < attempts && RETRYABLE_STATUS.has(response.statusCode)) {
|
|
2782
|
+
await response.body.dump();
|
|
2783
|
+
await abortableSleep(250 * 2 ** attempt, request$2.signal);
|
|
2784
|
+
continue;
|
|
2785
|
+
}
|
|
2786
|
+
return await wireResponse(response);
|
|
2787
|
+
} catch (cause) {
|
|
2788
|
+
lastFailure = cause;
|
|
2789
|
+
lastDeadlineExpired = deadline.timedOut;
|
|
2790
|
+
if (attempt + 1 < attempts && request$2.signal?.aborted !== true) {
|
|
2791
|
+
await abortableSleep(250 * 2 ** attempt, request$2.signal);
|
|
2792
|
+
continue;
|
|
2793
|
+
}
|
|
2794
|
+
} finally {
|
|
2795
|
+
deadline.dispose();
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
throw transportFailure(request$2, lastFailure, lastDeadlineExpired);
|
|
2799
|
+
},
|
|
2800
|
+
async stream(request$3) {
|
|
2801
|
+
assertWireRequest(request$3);
|
|
2802
|
+
const url = requestUrl(origin, request$3);
|
|
2803
|
+
debugRequest(options, request$3, url);
|
|
2804
|
+
const encoded = encodeBody(request$3, `----qmind-${randomBytes(18).toString("hex")}`);
|
|
2805
|
+
let response;
|
|
2806
|
+
const deadline = requestDeadline(request$3.signal, request$3.timeoutMs ?? requestTimeoutMs);
|
|
2807
|
+
try {
|
|
2808
|
+
response = await request(url, {
|
|
2809
|
+
...encoded.body === void 0 ? {} : { body: encoded.body },
|
|
2810
|
+
bodyTimeout: streamIdleTimeoutMs,
|
|
2811
|
+
dispatcher: dispatchers.host,
|
|
2812
|
+
headers: {
|
|
2813
|
+
accept: "text/event-stream",
|
|
2814
|
+
...encoded.headers
|
|
2815
|
+
},
|
|
2816
|
+
headersTimeout: request$3.timeoutMs ?? requestTimeoutMs,
|
|
2817
|
+
idempotent: request$3.idempotent,
|
|
2818
|
+
method: request$3.method,
|
|
2819
|
+
signal: deadline.signal
|
|
2820
|
+
});
|
|
2821
|
+
} catch (cause) {
|
|
2822
|
+
deadline.dispose();
|
|
2823
|
+
throw transportFailure(request$3, cause, deadline.timedOut);
|
|
2824
|
+
}
|
|
2825
|
+
deadline.clearTimeout();
|
|
2826
|
+
const headers = headersToRecord(response.headers);
|
|
2827
|
+
if (response.statusCode < 200 || response.statusCode >= 300 || !headers["content-type"]?.toLowerCase().includes("text/event-stream")) try {
|
|
2828
|
+
return {
|
|
2829
|
+
...await wireResponse(response),
|
|
2830
|
+
kind: "response"
|
|
2831
|
+
};
|
|
2832
|
+
} finally {
|
|
2833
|
+
deadline.dispose();
|
|
2834
|
+
}
|
|
2835
|
+
return {
|
|
2836
|
+
events: decodedEvents(response.body, request$3, deadline.dispose),
|
|
2837
|
+
headers,
|
|
2838
|
+
kind: "events",
|
|
2839
|
+
status: response.statusCode
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
const MIME_TYPES = Object.freeze({
|
|
2845
|
+
".csv": "text/csv",
|
|
2846
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
2847
|
+
".gif": "image/gif",
|
|
2848
|
+
".jpeg": "image/jpeg",
|
|
2849
|
+
".jpg": "image/jpeg",
|
|
2850
|
+
".md": "text/markdown",
|
|
2851
|
+
".mdx": "text/markdown",
|
|
2852
|
+
".pdf": "application/pdf",
|
|
2853
|
+
".png": "image/png",
|
|
2854
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
2855
|
+
".svg": "image/svg+xml",
|
|
2856
|
+
".txt": "text/plain",
|
|
2857
|
+
".webp": "image/webp",
|
|
2858
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
2859
|
+
});
|
|
2860
|
+
function detectQMindMimeType(filename) {
|
|
2861
|
+
return MIME_TYPES[extname(filename).toLowerCase()] ?? "application/octet-stream";
|
|
2862
|
+
}
|
|
2863
|
+
async function createQMindFileSource(path, options = {}) {
|
|
2864
|
+
let info;
|
|
2865
|
+
try {
|
|
2866
|
+
info = await stat(path);
|
|
2867
|
+
} catch {
|
|
2868
|
+
throw invalidArgument("source path must reference a readable file", "path");
|
|
2869
|
+
}
|
|
2870
|
+
if (!info.isFile() || info.size <= 0) throw invalidArgument("source path must reference a non-empty regular file", "path");
|
|
2871
|
+
const filename = options.filename?.trim() || basename(path);
|
|
2872
|
+
const mimeType = options.mimeType?.trim() || detectQMindMimeType(filename);
|
|
2873
|
+
return Object.freeze({
|
|
2874
|
+
filename,
|
|
2875
|
+
mimeType,
|
|
2876
|
+
async *open() {
|
|
2877
|
+
for await (const chunk of createReadStream(path)) yield chunk;
|
|
2878
|
+
},
|
|
2879
|
+
size: info.size
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
//#endregion
|
|
2883
|
+
//#region ../node-host/src/host.ts
|
|
2884
|
+
function defaultReporter() {
|
|
2885
|
+
return Object.freeze({
|
|
2886
|
+
info(message) {
|
|
2887
|
+
console.error(message);
|
|
2888
|
+
},
|
|
2889
|
+
warn(message) {
|
|
2890
|
+
console.error(message);
|
|
2891
|
+
}
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
function defaultBrowser() {
|
|
2895
|
+
return Object.freeze({ async open(url) {
|
|
2896
|
+
(await open(url, { wait: false })).unref();
|
|
2897
|
+
} });
|
|
2898
|
+
}
|
|
2899
|
+
async function tlsAuthority(path) {
|
|
2900
|
+
if (path === void 0) return void 0;
|
|
2901
|
+
try {
|
|
2902
|
+
return await readFile(path, "utf8");
|
|
2903
|
+
} catch {
|
|
2904
|
+
throw new QMindError("HOST_POLICY_ERROR", "unable to read the configured TLS CA file", { details: { field: "tlsCaFile" } });
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
function configurationOptions(options, reporter) {
|
|
2908
|
+
return {
|
|
2909
|
+
...options.credentialSashOrigin === void 0 ? {} : { credentialSashOrigin: options.credentialSashOrigin },
|
|
2910
|
+
...options.environment === void 0 ? {} : { environment: options.environment },
|
|
2911
|
+
...options.flags === void 0 ? {} : { flags: options.flags },
|
|
2912
|
+
...options.fixedEndpoints === void 0 ? {} : { fixedEndpoints: options.fixedEndpoints },
|
|
2913
|
+
...options.homeDirectory === void 0 ? {} : { homeDirectory: options.homeDirectory },
|
|
2914
|
+
...options.legacyEnvironmentPath === void 0 ? {} : { legacyEnvironmentPath: options.legacyEnvironmentPath },
|
|
2915
|
+
...options.productId === void 0 ? {} : { productId: options.productId },
|
|
2916
|
+
...reporter === void 0 ? {} : { reporter },
|
|
2917
|
+
...options.stdinIsTTY === void 0 ? {} : { stdinIsTTY: options.stdinIsTTY }
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
async function createQMindCliHost(options = {}) {
|
|
2921
|
+
const reporter = options.adapters?.reporter ?? defaultReporter();
|
|
2922
|
+
const clock = options.adapters?.clock ?? createSystemClock();
|
|
2923
|
+
const vault = createFileCredentialVault({
|
|
2924
|
+
homeDirectory: (await loadQMindCliConfig(configurationOptions(options))).runtime.homeDirectory,
|
|
2925
|
+
now: () => clock.now(),
|
|
2926
|
+
secretStore: options.adapters?.secretStore ?? createNoopQMindSecretStore()
|
|
2927
|
+
});
|
|
2928
|
+
let credentials;
|
|
2929
|
+
try {
|
|
2930
|
+
credentials = await vault.load();
|
|
2931
|
+
} catch (error) {
|
|
2932
|
+
if (options.credentialReadFailure !== "unauthenticated") throw error;
|
|
2933
|
+
reporter.warn("QMind credential vault is unavailable; authentication is required.");
|
|
2934
|
+
credentials = void 0;
|
|
2935
|
+
}
|
|
2936
|
+
const resolved = await loadQMindCliConfig({
|
|
2937
|
+
...configurationOptions(options, reporter),
|
|
2938
|
+
...options.adapters?.requestCredential === void 0 && credentials?.sashOrigin ? { credentialSashOrigin: credentials.sashOrigin } : {},
|
|
2939
|
+
reporter
|
|
2940
|
+
});
|
|
2941
|
+
const ca = await tlsAuthority(resolved.tlsCaFile);
|
|
2942
|
+
const nodeOptions = {
|
|
2943
|
+
...options.connectTimeoutMs === void 0 ? {} : { connectTimeoutMs: options.connectTimeoutMs },
|
|
2944
|
+
...resolved.debug ? { debugLog: (message) => reporter.info(message) } : {},
|
|
2945
|
+
environment: resolved.environment,
|
|
2946
|
+
...options.maxRetries === void 0 ? {} : { maxRetries: options.maxRetries },
|
|
2947
|
+
origin: resolved.runtime.sashOrigin,
|
|
2948
|
+
proxyFromEnvironment: resolved.proxyFromEnvironment,
|
|
2949
|
+
requestTimeoutMs: resolved.requestTimeoutMs,
|
|
2950
|
+
streamIdleTimeoutMs: resolved.streamIdleTimeoutMs,
|
|
2951
|
+
tls: {
|
|
2952
|
+
...ca === void 0 ? {} : { ca },
|
|
2953
|
+
rejectUnauthorized: resolved.tlsRejectUnauthorized
|
|
2954
|
+
}
|
|
2955
|
+
};
|
|
2956
|
+
const ownedTransport = options.adapters?.transport === void 0 ? createQMindNodeTransport(nodeOptions) : void 0;
|
|
2957
|
+
const rawTransport = options.adapters?.transport ?? ownedTransport;
|
|
2958
|
+
if (rawTransport === void 0) throw new QMindError("HOST_POLICY_ERROR", "unable to initialize the QMind transport");
|
|
2959
|
+
const auth = createQMindCliAuthManager({
|
|
2960
|
+
browser: options.adapters?.browser ?? defaultBrowser(),
|
|
2961
|
+
clock,
|
|
2962
|
+
config: resolved.runtime,
|
|
2963
|
+
credentials,
|
|
2964
|
+
explicitToken: resolved.token,
|
|
2965
|
+
...options.loginCommand === void 0 ? {} : { loginCommand: options.loginCommand },
|
|
2966
|
+
reporter,
|
|
2967
|
+
transport: rawTransport,
|
|
2968
|
+
vault
|
|
2969
|
+
});
|
|
2970
|
+
const client = createQMindClient({
|
|
2971
|
+
access: "sash",
|
|
2972
|
+
client: resolved.runtime.client,
|
|
2973
|
+
transport: createAuthenticatedQMindTransport(rawTransport, auth, options.adapters?.requestCredential)
|
|
2974
|
+
});
|
|
2975
|
+
return Object.freeze({
|
|
2976
|
+
client,
|
|
2977
|
+
async close() {
|
|
2978
|
+
await ownedTransport?.close();
|
|
2979
|
+
},
|
|
2980
|
+
config: resolved.runtime,
|
|
2981
|
+
createFileSource: createQMindFileSource,
|
|
2982
|
+
async downloadToFile(downloadOptions) {
|
|
2983
|
+
const downloader = options.adapters?.downloader ?? ownedTransport;
|
|
2984
|
+
if (downloader === void 0) throw new QMindError("UNSUPPORTED_OPERATION", "the injected CLI transport does not provide file downloads");
|
|
2985
|
+
return await downloader.downloadToFile(downloadOptions);
|
|
2986
|
+
},
|
|
2987
|
+
login: auth.login,
|
|
2988
|
+
logout: auth.logout
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
//#endregion
|
|
2992
|
+
//#region src/argv.ts
|
|
2993
|
+
const SHORT_OPTIONS = /* @__PURE__ */ new Set([
|
|
2994
|
+
"-h",
|
|
2995
|
+
"-V",
|
|
2996
|
+
"-o",
|
|
2997
|
+
"-q"
|
|
2998
|
+
]);
|
|
2999
|
+
/**
|
|
3000
|
+
* Preserve the historical Go CLI spelling where long options used one dash.
|
|
3001
|
+
* The delimiter is an intentional hard boundary: everything after `--` is a
|
|
3002
|
+
* positional value and must remain byte-for-byte unchanged.
|
|
3003
|
+
*/
|
|
3004
|
+
function normalizeLegacyArgv(argv) {
|
|
3005
|
+
let positionalOnly = false;
|
|
3006
|
+
return argv.map((argument) => {
|
|
3007
|
+
if (argument === "--") {
|
|
3008
|
+
positionalOnly = true;
|
|
3009
|
+
return argument;
|
|
3010
|
+
}
|
|
3011
|
+
if (positionalOnly || SHORT_OPTIONS.has(argument) || argument.startsWith("--")) return argument;
|
|
3012
|
+
if (/^-[A-Za-z][A-Za-z0-9-]+(?:=.*)?$/.test(argument)) return `-${argument}`;
|
|
3013
|
+
return argument;
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
/** Runtime errors use the JSON error envelope only when the caller explicitly
|
|
3017
|
+
* selected JSON, except folder sync whose compatibility default is JSON. */
|
|
3018
|
+
function requestsJsonOutput(argv) {
|
|
3019
|
+
let explicitFormat;
|
|
3020
|
+
let positionalOnly = false;
|
|
3021
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
3022
|
+
const argument = argv[index];
|
|
3023
|
+
if (argument === "--") {
|
|
3024
|
+
positionalOnly = true;
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
if (positionalOnly) continue;
|
|
3028
|
+
if (argument === "--format" || argument === "-format") {
|
|
3029
|
+
explicitFormat = argv[index + 1];
|
|
3030
|
+
index += 1;
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
3033
|
+
const match = argument?.match(/^--?format=(.*)$/);
|
|
3034
|
+
if (match !== null && match !== void 0) explicitFormat = match[1];
|
|
3035
|
+
}
|
|
3036
|
+
if (explicitFormat !== void 0) return explicitFormat === "json";
|
|
3037
|
+
return argv[0] === "upload-folder" || argv[0] === "sync";
|
|
3038
|
+
}
|
|
3039
|
+
//#endregion
|
|
3040
|
+
//#region src/build-info.ts
|
|
3041
|
+
const QMIND_CLI_VERSION = "3.0.0";
|
|
3042
|
+
const QMIND_CLI_BUILD_TIME = "2026-09-01T07:33:25.000Z";
|
|
3043
|
+
function qmindCliPlatform() {
|
|
3044
|
+
return process.platform === "win32" ? "windows" : process.platform;
|
|
3045
|
+
}
|
|
3046
|
+
function qmindCliArchitecture() {
|
|
3047
|
+
return process.arch === "x64" ? "amd64" : process.arch;
|
|
3048
|
+
}
|
|
3049
|
+
//#endregion
|
|
3050
|
+
//#region src/distributions.ts
|
|
3051
|
+
/** Canonical runtime and release identity for every public CLI distribution. */
|
|
3052
|
+
const CLI_DISTRIBUTIONS = Object.freeze({
|
|
3053
|
+
global: {
|
|
3054
|
+
id: "global",
|
|
3055
|
+
command: "qmind",
|
|
3056
|
+
package: "@qoder-ai/qmind-cli",
|
|
3057
|
+
productId: "qoder",
|
|
3058
|
+
gitTagSlug: "qmind-cli",
|
|
3059
|
+
readmeDirectory: ".",
|
|
3060
|
+
productNames: {
|
|
3061
|
+
en: "Qoder Global",
|
|
3062
|
+
zhCN: "Qoder Global"
|
|
3063
|
+
}
|
|
3064
|
+
},
|
|
3065
|
+
cn: {
|
|
3066
|
+
id: "cn",
|
|
3067
|
+
command: "qmind-cn",
|
|
3068
|
+
package: "@qodercn-ai/qmind-cli",
|
|
3069
|
+
productId: "qoder-cn",
|
|
3070
|
+
gitTagSlug: "qmind-cli-cn",
|
|
3071
|
+
readmeDirectory: "readmes/cn",
|
|
3072
|
+
productNames: {
|
|
3073
|
+
en: "Qoder China",
|
|
3074
|
+
zhCN: "Qoder 中国版"
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
});
|
|
3078
|
+
//#endregion
|
|
3079
|
+
//#region src/config.ts
|
|
3080
|
+
function cliHostConfiguration(distribution, flags, environment, stateRoot) {
|
|
3081
|
+
const { productId, command } = CLI_DISTRIBUTIONS[distribution];
|
|
3082
|
+
const custom = flags.sash !== void 0 || flags.dashboard !== void 0;
|
|
3083
|
+
if (custom && (typeof flags.sash !== "string" || typeof flags.dashboard !== "string" || flags.env !== void 0)) throw new QMindError("INVALID_ARGUMENT", "--sash and --dashboard must be supplied together and cannot be combined with --env");
|
|
3084
|
+
const preset = typeof flags.env === "string" ? flags.env.trim() : "prod";
|
|
3085
|
+
const endpoints = custom ? {
|
|
3086
|
+
dashboardOrigin: normalizeQMindOrigin(String(flags.dashboard).trim(), "--dashboard"),
|
|
3087
|
+
sashOrigin: normalizeQMindOrigin(String(flags.sash).trim(), "--sash")
|
|
3088
|
+
} : resolveQMindProductEndpoints(productId, preset);
|
|
3089
|
+
const target = custom ? `custom-${createHash("sha256").update(JSON.stringify([endpoints.dashboardOrigin, endpoints.sashOrigin])).digest("hex")}` : preset;
|
|
3090
|
+
return {
|
|
3091
|
+
environment: {
|
|
3092
|
+
...environment,
|
|
3093
|
+
QMIND_TOKEN: target === "prod" ? environment.QMIND_TOKEN : environment.QMIND_DEBUG_TOKEN
|
|
3094
|
+
},
|
|
3095
|
+
flags,
|
|
3096
|
+
fixedEndpoints: endpoints,
|
|
3097
|
+
homeDirectory: join(resolve(stateRoot ?? (environment.QMIND_HOME?.trim() || join(homedir(), ".qmind"))), "cli", distribution, target),
|
|
3098
|
+
legacyEnvironmentPath: false,
|
|
3099
|
+
loginCommand: `${command} login${custom ? ` --sash ${endpoints.sashOrigin} --dashboard ${endpoints.dashboardOrigin}` : target === "prod" ? "" : ` --env ${target}`}`,
|
|
3100
|
+
productId
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
//#endregion
|
|
3104
|
+
//#region src/folder-sync.ts
|
|
3105
|
+
const QMIND_FOLDER_SYNC_DEFAULT_EXTENSIONS = Object.freeze([
|
|
3106
|
+
".mdx",
|
|
3107
|
+
".md",
|
|
3108
|
+
".pdf",
|
|
3109
|
+
".txt",
|
|
3110
|
+
".docx",
|
|
3111
|
+
".pptx",
|
|
3112
|
+
".png",
|
|
3113
|
+
".jpg",
|
|
3114
|
+
".jpeg",
|
|
3115
|
+
".gif",
|
|
3116
|
+
".svg",
|
|
3117
|
+
".webp"
|
|
3118
|
+
]);
|
|
3119
|
+
function posix(path) {
|
|
3120
|
+
return path.split(sep).join("/");
|
|
3121
|
+
}
|
|
3122
|
+
function qmindFolderSyncIndexKey(parentId, title) {
|
|
3123
|
+
return `${parentId}\u0000${title}`;
|
|
3124
|
+
}
|
|
3125
|
+
function normalizeExtensions(values) {
|
|
3126
|
+
return new Set(values.map((value) => value.trim().toLowerCase()).filter((value) => value.length > 0).map((value) => value.startsWith(".") ? value : `.${value}`));
|
|
3127
|
+
}
|
|
3128
|
+
function validateMapping(value) {
|
|
3129
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new QMindError("INVALID_ARGUMENT", "directory ID mapping must be a JSON object");
|
|
3130
|
+
const result = {};
|
|
3131
|
+
for (const [path, id] of Object.entries(value)) {
|
|
3132
|
+
if (typeof id !== "string" || id.trim().length === 0) throw new QMindError("INVALID_ARGUMENT", "directory ID mapping values must be non-empty strings");
|
|
3133
|
+
result[posix(path)] = id.trim();
|
|
3134
|
+
}
|
|
3135
|
+
return result;
|
|
3136
|
+
}
|
|
3137
|
+
async function sha256(path) {
|
|
3138
|
+
const hash = createHash("sha256");
|
|
3139
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
3140
|
+
return hash.digest("hex");
|
|
3141
|
+
}
|
|
3142
|
+
function createNodeQMindFolderSyncFileSystem() {
|
|
3143
|
+
return Object.freeze({
|
|
3144
|
+
async isDirectory(path) {
|
|
3145
|
+
return (await stat(path).catch(() => void 0))?.isDirectory() === true;
|
|
3146
|
+
},
|
|
3147
|
+
async readMapping(path) {
|
|
3148
|
+
try {
|
|
3149
|
+
return validateMapping(JSON.parse(await readFile(path, "utf8")));
|
|
3150
|
+
} catch (error) {
|
|
3151
|
+
if (error instanceof QMindError) throw error;
|
|
3152
|
+
throw new QMindError("INVALID_ARGUMENT", `unable to read directory ID mapping: ${path}`);
|
|
3153
|
+
}
|
|
3154
|
+
},
|
|
3155
|
+
async scan(baseDirectory, options) {
|
|
3156
|
+
const directories = [];
|
|
3157
|
+
const files = [];
|
|
3158
|
+
let skipped = 0;
|
|
3159
|
+
const isIgnored = options.ignorePatterns.length === 0 ? () => false : picomatch([...options.ignorePatterns], { dot: true });
|
|
3160
|
+
const visit = async (directory) => {
|
|
3161
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
3162
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
3163
|
+
for (const entry of entries) {
|
|
3164
|
+
const absolute = join(directory, entry.name);
|
|
3165
|
+
const path = posix(relative(baseDirectory, absolute));
|
|
3166
|
+
if (entry.name.startsWith(".") || isIgnored(path)) {
|
|
3167
|
+
skipped += entry.isFile() ? 1 : 0;
|
|
3168
|
+
continue;
|
|
3169
|
+
}
|
|
3170
|
+
if (entry.isDirectory()) {
|
|
3171
|
+
directories.push(path);
|
|
3172
|
+
await visit(absolute);
|
|
3173
|
+
} else if (entry.isFile()) {
|
|
3174
|
+
if (options.extensions.has(extname(entry.name).toLowerCase())) files.push(path);
|
|
3175
|
+
else skipped += 1;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
await visit(baseDirectory);
|
|
3180
|
+
directories.sort((left, right) => left.split("/").length - right.split("/").length || left.localeCompare(right));
|
|
3181
|
+
files.sort((left, right) => left.localeCompare(right));
|
|
3182
|
+
return {
|
|
3183
|
+
directories,
|
|
3184
|
+
files,
|
|
3185
|
+
skipped
|
|
3186
|
+
};
|
|
3187
|
+
},
|
|
3188
|
+
sha256,
|
|
3189
|
+
async writeMapping(path, mapping) {
|
|
3190
|
+
await mkdir(dirname(path), { recursive: true });
|
|
3191
|
+
await writeFile(path, `${JSON.stringify(mapping, null, 2)}\n`, { mode: 384 });
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
async function shouldReplaceQMindFolderFile(mode, existing, localPath, fileSystem = createNodeQMindFolderSyncFileSystem()) {
|
|
3196
|
+
if (mode === "skip") return false;
|
|
3197
|
+
if (mode === "overwrite") return true;
|
|
3198
|
+
const metadata = existing.metadata ?? {};
|
|
3199
|
+
const remoteHash = metadata.fileSha256 ?? metadata.rawFileSha256 ?? metadata.sha256;
|
|
3200
|
+
if (typeof remoteHash !== "string" || !/^[a-f0-9]{64}$/i.test(remoteHash)) return true;
|
|
3201
|
+
return (await fileSystem.sha256(localPath)).toLowerCase() !== remoteHash.toLowerCase();
|
|
3202
|
+
}
|
|
3203
|
+
function retryable(error) {
|
|
3204
|
+
if (!(error instanceof QMindError)) return true;
|
|
3205
|
+
if (error.code === "FILE_TOO_LARGE" || error.code === "INVALID_ARGUMENT" || error.code === "AUTH_REQUIRED" || error.code === "FORBIDDEN") return false;
|
|
3206
|
+
return error.status === void 0 || error.status === 408 || error.status === 429 || error.status >= 500;
|
|
3207
|
+
}
|
|
3208
|
+
async function withUploadRetry(task, sleep) {
|
|
3209
|
+
let lastFailure;
|
|
3210
|
+
for (let attempt = 0; attempt < 3; attempt += 1) try {
|
|
3211
|
+
return await task();
|
|
3212
|
+
} catch (error) {
|
|
3213
|
+
lastFailure = error;
|
|
3214
|
+
if (!retryable(error) || attempt === 2) break;
|
|
3215
|
+
await sleep(250 * 2 ** attempt);
|
|
3216
|
+
}
|
|
3217
|
+
throw lastFailure;
|
|
3218
|
+
}
|
|
3219
|
+
function defaultSleep(milliseconds) {
|
|
3220
|
+
return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
3221
|
+
}
|
|
3222
|
+
function mappingPath(baseDirectory, cacheDirectory) {
|
|
3223
|
+
const cache = cacheDirectory ?? process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
|
|
3224
|
+
const name = createHash("sha1").update(baseDirectory).digest("hex").slice(0, 16);
|
|
3225
|
+
return join(cache, "qmind", "dir-ids", `${name}.json`);
|
|
3226
|
+
}
|
|
3227
|
+
function addFailure(stats, field, path) {
|
|
3228
|
+
(stats[field] ??= []).push(path);
|
|
3229
|
+
}
|
|
3230
|
+
function publicStats(stats) {
|
|
3231
|
+
return Object.freeze({
|
|
3232
|
+
...stats,
|
|
3233
|
+
...stats.failed_dirs === void 0 ? {} : { failed_dirs: Object.freeze(stats.failed_dirs) },
|
|
3234
|
+
...stats.failed_files === void 0 ? {} : { failed_files: Object.freeze(stats.failed_files) },
|
|
3235
|
+
...stats.failed_deletes === void 0 ? {} : { failed_deletes: Object.freeze(stats.failed_deletes) }
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
async function synchronizeQMindFolder(host, options) {
|
|
3239
|
+
const notebookId = options.notebookId.trim();
|
|
3240
|
+
if (notebookId.length === 0) throw new QMindError("INVALID_ARGUMENT", "notebookId is required");
|
|
3241
|
+
const fileSystem = options.fileSystem ?? createNodeQMindFolderSyncFileSystem();
|
|
3242
|
+
const baseDirectory = resolve(options.baseDirectory);
|
|
3243
|
+
if (!await fileSystem.isDirectory(baseDirectory)) throw new QMindError("INVALID_ARGUMENT", `${baseDirectory} is not a directory`);
|
|
3244
|
+
const mode = options.mode ?? "skip";
|
|
3245
|
+
if (options.skipUpload === true && options.delete === true) throw new QMindError("INVALID_ARGUMENT", "delete cannot be combined with skipUpload because local files would not enter the mirror plan");
|
|
3246
|
+
const requestedConcurrency = options.concurrency ?? 3;
|
|
3247
|
+
if (!Number.isSafeInteger(requestedConcurrency) || requestedConcurrency < 1) throw new QMindError("INVALID_ARGUMENT", "concurrency must be a positive integer");
|
|
3248
|
+
const concurrency = Math.min(10, requestedConcurrency);
|
|
3249
|
+
const diagnostics = [];
|
|
3250
|
+
if (requestedConcurrency > 10) diagnostics.push(`warning: concurrency ${requestedConcurrency} exceeds maximum (10); clamped to 10`);
|
|
3251
|
+
const extensions = normalizeExtensions(options.extensions ?? QMIND_FOLDER_SYNC_DEFAULT_EXTENSIONS);
|
|
3252
|
+
const tree = await fileSystem.scan(baseDirectory, {
|
|
3253
|
+
extensions,
|
|
3254
|
+
ignorePatterns: options.ignorePatterns ?? []
|
|
3255
|
+
});
|
|
3256
|
+
const remote = await host.client.listAllSources(notebookId);
|
|
3257
|
+
const remoteIndex = new Map(remote.map((source) => [qmindFolderSyncIndexKey(source.parentSourceId, source.title), source]));
|
|
3258
|
+
const remoteById = new Map(remote.map((source) => [source.id, source]));
|
|
3259
|
+
const stats = {
|
|
3260
|
+
dirs_created: 0,
|
|
3261
|
+
dirs_reused: 0,
|
|
3262
|
+
dirs_failed: 0,
|
|
3263
|
+
dirs_refresh_failed: 0,
|
|
3264
|
+
files_uploaded: 0,
|
|
3265
|
+
files_skipped: options.skipUpload === true ? 0 : tree.skipped,
|
|
3266
|
+
files_already_exist: 0,
|
|
3267
|
+
files_failed: 0,
|
|
3268
|
+
...options.delete === true ? {
|
|
3269
|
+
delete_failed: 0,
|
|
3270
|
+
dirs_deleted: 0,
|
|
3271
|
+
files_deleted: 0
|
|
3272
|
+
} : {},
|
|
3273
|
+
...options.dryRun === true ? { dry_run: true } : {}
|
|
3274
|
+
};
|
|
3275
|
+
const configuredMapping = options.directoryIdsPath === void 0 ? {} : await fileSystem.readMapping(options.directoryIdsPath);
|
|
3276
|
+
const directoryIds = {};
|
|
3277
|
+
const desiredKeys = /* @__PURE__ */ new Set();
|
|
3278
|
+
for (const directory of tree.directories) {
|
|
3279
|
+
const parentPath = posix(dirname(directory)) === "." ? "" : posix(dirname(directory));
|
|
3280
|
+
const parentId = directoryIds[parentPath] ?? "";
|
|
3281
|
+
if (parentPath.length > 0 && parentId.length === 0) {
|
|
3282
|
+
stats.dirs_failed += 1;
|
|
3283
|
+
addFailure(stats, "failed_dirs", directory);
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3286
|
+
const title = basename(directory);
|
|
3287
|
+
const key = qmindFolderSyncIndexKey(parentId, title);
|
|
3288
|
+
desiredKeys.add(key);
|
|
3289
|
+
const mappedId = configuredMapping[directory];
|
|
3290
|
+
const mapped = mappedId === void 0 ? void 0 : remoteById.get(mappedId);
|
|
3291
|
+
const existing = mappedId === void 0 || mapped === void 0 ? remoteIndex.get(key) : mapped;
|
|
3292
|
+
if (existing?.isDir === true && existing.parentSourceId === parentId && existing.title === title) {
|
|
3293
|
+
directoryIds[directory] = existing.id;
|
|
3294
|
+
stats.dirs_reused += 1;
|
|
3295
|
+
continue;
|
|
3296
|
+
}
|
|
3297
|
+
if (existing !== void 0) {
|
|
3298
|
+
stats.dirs_failed += 1;
|
|
3299
|
+
addFailure(stats, "failed_dirs", directory);
|
|
3300
|
+
continue;
|
|
3301
|
+
}
|
|
3302
|
+
if (options.dryRun === true) {
|
|
3303
|
+
directoryIds[directory] = `dry-run:${directory}`;
|
|
3304
|
+
stats.dirs_created += 1;
|
|
3305
|
+
continue;
|
|
3306
|
+
}
|
|
3307
|
+
try {
|
|
3308
|
+
const created = await host.client.createDirectory(notebookId, {
|
|
3309
|
+
parentSourceId: parentId,
|
|
3310
|
+
path: title,
|
|
3311
|
+
title
|
|
3312
|
+
});
|
|
3313
|
+
directoryIds[directory] = created.id;
|
|
3314
|
+
remoteIndex.set(key, created);
|
|
3315
|
+
remoteById.set(created.id, created);
|
|
3316
|
+
stats.dirs_created += 1;
|
|
3317
|
+
} catch {
|
|
3318
|
+
stats.dirs_failed += 1;
|
|
3319
|
+
addFailure(stats, "failed_dirs", directory);
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
let savedMappingPath;
|
|
3323
|
+
if (options.directoryIdsPath === void 0 && options.dryRun !== true) {
|
|
3324
|
+
savedMappingPath = options.saveDirectoryIdsPath ?? mappingPath(baseDirectory, options.cacheDirectory);
|
|
3325
|
+
await fileSystem.writeMapping(savedMappingPath, directoryIds);
|
|
3326
|
+
diagnostics.push(`directory mapping saved: ${savedMappingPath}`);
|
|
3327
|
+
}
|
|
3328
|
+
if (options.skipUpload !== true) {
|
|
3329
|
+
const limit = pLimit(concurrency);
|
|
3330
|
+
await Promise.all(tree.files.map((file) => limit(async () => {
|
|
3331
|
+
const absolute = join(baseDirectory, file);
|
|
3332
|
+
const parentPath = posix(dirname(file)) === "." ? "" : posix(dirname(file));
|
|
3333
|
+
const parentId = directoryIds[parentPath] ?? "";
|
|
3334
|
+
if (parentPath.length > 0 && parentId.length === 0) {
|
|
3335
|
+
stats.files_failed += 1;
|
|
3336
|
+
addFailure(stats, "failed_files", file);
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3339
|
+
const title = basename(file);
|
|
3340
|
+
const key = qmindFolderSyncIndexKey(parentId, title);
|
|
3341
|
+
desiredKeys.add(key);
|
|
3342
|
+
const existing = remoteIndex.get(key);
|
|
3343
|
+
if (existing?.isDir === true) {
|
|
3344
|
+
stats.files_failed += 1;
|
|
3345
|
+
addFailure(stats, "failed_files", file);
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
if (existing !== void 0 && !await shouldReplaceQMindFolderFile(mode, existing, absolute, fileSystem)) {
|
|
3349
|
+
stats.files_already_exist += 1;
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
if (options.dryRun === true) {
|
|
3353
|
+
stats.files_uploaded += 1;
|
|
3354
|
+
return;
|
|
3355
|
+
}
|
|
3356
|
+
try {
|
|
3357
|
+
const hash = await fileSystem.sha256(absolute);
|
|
3358
|
+
const uploaded = await withUploadRetry(async () => {
|
|
3359
|
+
const binary = await host.createFileSource(absolute);
|
|
3360
|
+
return await host.client.uploadSource(notebookId, binary, {
|
|
3361
|
+
parentSourceId: parentId,
|
|
3362
|
+
path: file,
|
|
3363
|
+
sha256: hash,
|
|
3364
|
+
title
|
|
3365
|
+
});
|
|
3366
|
+
}, options.sleep ?? defaultSleep);
|
|
3367
|
+
if (existing !== void 0) await host.client.deleteSource(notebookId, existing.id);
|
|
3368
|
+
remoteIndex.set(key, uploaded);
|
|
3369
|
+
remoteById.set(uploaded.id, uploaded);
|
|
3370
|
+
stats.files_uploaded += 1;
|
|
3371
|
+
} catch {
|
|
3372
|
+
stats.files_failed += 1;
|
|
3373
|
+
addFailure(stats, "failed_files", file);
|
|
3374
|
+
}
|
|
3375
|
+
})));
|
|
3376
|
+
}
|
|
3377
|
+
if (options.dryRun !== true) for (const [directory, id] of Object.entries(directoryIds)) {
|
|
3378
|
+
if (id.startsWith("dry-run:")) continue;
|
|
3379
|
+
try {
|
|
3380
|
+
await host.client.updateSource(notebookId, id, {
|
|
3381
|
+
isDir: true,
|
|
3382
|
+
title: basename(directory)
|
|
3383
|
+
});
|
|
3384
|
+
} catch {
|
|
3385
|
+
stats.dirs_refresh_failed += 1;
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
const preDeleteFailures = stats.files_failed + stats.dirs_failed;
|
|
3389
|
+
if (options.delete === true && preDeleteFailures === 0) {
|
|
3390
|
+
const deletions = remote.filter((source) => !desiredKeys.has(qmindFolderSyncIndexKey(source.parentSourceId, source.title))).sort((left, right) => {
|
|
3391
|
+
if (left.isDir !== right.isDir) return left.isDir ? 1 : -1;
|
|
3392
|
+
return right.path.split("/").length - left.path.split("/").length;
|
|
3393
|
+
});
|
|
3394
|
+
for (const source of deletions) try {
|
|
3395
|
+
if (options.dryRun !== true) await host.client.deleteSource(notebookId, source.id);
|
|
3396
|
+
if (source.isDir) stats.dirs_deleted = (stats.dirs_deleted ?? 0) + 1;
|
|
3397
|
+
else stats.files_deleted = (stats.files_deleted ?? 0) + 1;
|
|
3398
|
+
} catch {
|
|
3399
|
+
stats.delete_failed = (stats.delete_failed ?? 0) + 1;
|
|
3400
|
+
(stats.failed_deletes ??= []).push(source.path || source.title);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
const exitCode = stats.files_failed > 0 || stats.dirs_failed > 0 || stats.dirs_refresh_failed > 0 || (stats.delete_failed ?? 0) > 0 ? 1 : 0;
|
|
3404
|
+
return {
|
|
3405
|
+
diagnostics: Object.freeze(diagnostics),
|
|
3406
|
+
exitCode,
|
|
3407
|
+
...savedMappingPath === void 0 ? {} : { mappingPath: savedMappingPath },
|
|
3408
|
+
stats: publicStats(stats)
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
//#endregion
|
|
3412
|
+
//#region src/output.ts
|
|
3413
|
+
var TextWriter = class {
|
|
3414
|
+
#chunks = [];
|
|
3415
|
+
line(value = "") {
|
|
3416
|
+
this.#chunks.push(`${value}\n`);
|
|
3417
|
+
}
|
|
3418
|
+
write(value) {
|
|
3419
|
+
this.#chunks.push(value);
|
|
3420
|
+
}
|
|
3421
|
+
toString() {
|
|
3422
|
+
return this.#chunks.join("");
|
|
3423
|
+
}
|
|
3424
|
+
};
|
|
3425
|
+
function parseQMindCliOutputFormat(value) {
|
|
3426
|
+
if (value === "json" || value === "agent" || value === "ndjson") return value;
|
|
3427
|
+
return "table";
|
|
3428
|
+
}
|
|
3429
|
+
function record(value) {
|
|
3430
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3431
|
+
}
|
|
3432
|
+
function stringValue(value) {
|
|
3433
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
3434
|
+
}
|
|
3435
|
+
function numberValue(value) {
|
|
3436
|
+
const parsed = Number(value ?? 0);
|
|
3437
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
3438
|
+
}
|
|
3439
|
+
function boolValue(value) {
|
|
3440
|
+
return value === true;
|
|
3441
|
+
}
|
|
3442
|
+
function oneLine(value) {
|
|
3443
|
+
return stringValue(value).replace(/[\n\t]/g, " ").trim().split(/\s+/).filter(Boolean).join(" ");
|
|
3444
|
+
}
|
|
3445
|
+
function unescapeText(value) {
|
|
3446
|
+
return decodeHTML(stringValue(value));
|
|
3447
|
+
}
|
|
3448
|
+
function truncate(value, width) {
|
|
3449
|
+
const characters = [...stringValue(value)];
|
|
3450
|
+
if (characters.length <= width) return characters.join("");
|
|
3451
|
+
if (width <= 3) return characters.slice(0, width).join("");
|
|
3452
|
+
return `${characters.slice(0, width - 3).join("")}...`;
|
|
3453
|
+
}
|
|
3454
|
+
function pad(value, width) {
|
|
3455
|
+
const text = stringValue(value);
|
|
3456
|
+
return `${text}${" ".repeat(Math.max(0, width - [...text].length))}`;
|
|
3457
|
+
}
|
|
3458
|
+
function table(writer, headers, rows, widths, separatorWidth) {
|
|
3459
|
+
writer.line(headers.map((value, index) => pad(value, widths[index] ?? 0)).join(" "));
|
|
3460
|
+
writer.line("-".repeat(separatorWidth));
|
|
3461
|
+
for (const values of rows) writer.line(values.map((value, index) => pad(value, widths[index] ?? 0)).join(" "));
|
|
3462
|
+
}
|
|
3463
|
+
function goTimestamp(value) {
|
|
3464
|
+
if (value === void 0 || value === null || value === "") return null;
|
|
3465
|
+
const text = stringValue(value);
|
|
3466
|
+
const milliseconds = Date.parse(text);
|
|
3467
|
+
if (!Number.isFinite(milliseconds)) return null;
|
|
3468
|
+
const fraction = text.match(/\.(\d{1,9})/)?.[1] ?? "";
|
|
3469
|
+
return {
|
|
3470
|
+
nanos: fraction.length > 0 ? Number(fraction.padEnd(9, "0")) : milliseconds % 1e3 * 1e6,
|
|
3471
|
+
seconds: Math.floor(milliseconds / 1e3)
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
function timestamp(value) {
|
|
3475
|
+
const parsed = goTimestamp(value);
|
|
3476
|
+
if (parsed === null || parsed.seconds === 0) return "-";
|
|
3477
|
+
return (/* @__PURE__ */ new Date(parsed.seconds * 1e3)).toISOString().replace(".000Z", "Z");
|
|
3478
|
+
}
|
|
3479
|
+
function optionalMap(value) {
|
|
3480
|
+
const result = record(value);
|
|
3481
|
+
return Object.keys(result).length === 0 ? void 0 : result;
|
|
3482
|
+
}
|
|
3483
|
+
function optionalStrings(value) {
|
|
3484
|
+
return Array.isArray(value) && value.length > 0 ? value.map(stringValue) : void 0;
|
|
3485
|
+
}
|
|
3486
|
+
function pagination(value) {
|
|
3487
|
+
if (value === void 0 || value === null) return "";
|
|
3488
|
+
const page = record(value);
|
|
3489
|
+
if ([
|
|
3490
|
+
page.totalSize,
|
|
3491
|
+
page.currentPage,
|
|
3492
|
+
page.lastPage,
|
|
3493
|
+
page.pageSize
|
|
3494
|
+
].every((item) => numberValue(item) === 0)) return "";
|
|
3495
|
+
return ` (server totalSize=${numberValue(page.totalSize)}, page=${numberValue(page.currentPage)}/${numberValue(page.lastPage)}, pageSize=${numberValue(page.pageSize)})`;
|
|
3496
|
+
}
|
|
3497
|
+
function goNotebook(value) {
|
|
3498
|
+
const source = record(value);
|
|
3499
|
+
const settings = optionalMap(source.settings);
|
|
3500
|
+
return {
|
|
3501
|
+
id: stringValue(source.id),
|
|
3502
|
+
orgId: stringValue(source.orgId),
|
|
3503
|
+
userId: stringValue(source.userId),
|
|
3504
|
+
title: stringValue(source.title),
|
|
3505
|
+
description: stringValue(source.description),
|
|
3506
|
+
client: stringValue(source.client),
|
|
3507
|
+
status: stringValue(source.status),
|
|
3508
|
+
...settings === void 0 ? {} : { settings },
|
|
3509
|
+
createdAt: goTimestamp(source.createdAt),
|
|
3510
|
+
updatedAt: goTimestamp(source.updatedAt)
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
function goNotebookList(value) {
|
|
3514
|
+
return {
|
|
3515
|
+
notebooks: value.notebooks.map(goNotebook),
|
|
3516
|
+
totalSize: numberValue(value.totalSize),
|
|
3517
|
+
currentPage: numberValue(value.currentPage),
|
|
3518
|
+
pageSize: numberValue(value.pageSize)
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
function goPage(value) {
|
|
3522
|
+
if (value === void 0 || value === null) return void 0;
|
|
3523
|
+
const source = record(value);
|
|
3524
|
+
return {
|
|
3525
|
+
prevPage: numberValue(source.prevPage),
|
|
3526
|
+
currentPage: numberValue(source.currentPage),
|
|
3527
|
+
nextPage: numberValue(source.nextPage),
|
|
3528
|
+
lastPage: numberValue(source.lastPage),
|
|
3529
|
+
pageSize: numberValue(source.pageSize),
|
|
3530
|
+
totalSize: numberValue(source.totalSize),
|
|
3531
|
+
...source.nextToken ? { nextToken: stringValue(source.nextToken) } : {}
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
function goSource(value) {
|
|
3535
|
+
const source = record(value);
|
|
3536
|
+
const metadata = optionalMap(source.metadata);
|
|
3537
|
+
return {
|
|
3538
|
+
id: stringValue(source.id),
|
|
3539
|
+
orgId: stringValue(source.orgId),
|
|
3540
|
+
userId: stringValue(source.userId),
|
|
3541
|
+
notebookId: stringValue(source.notebookId),
|
|
3542
|
+
title: stringValue(source.title),
|
|
3543
|
+
sourceType: stringValue(source.sourceType),
|
|
3544
|
+
uri: stringValue(source.uri),
|
|
3545
|
+
status: stringValue(source.status),
|
|
3546
|
+
errorMessage: stringValue(source.errorMessage),
|
|
3547
|
+
parentSourceId: stringValue(source.parentSourceId),
|
|
3548
|
+
path: stringValue(source.path),
|
|
3549
|
+
isDir: boolValue(source.isDir),
|
|
3550
|
+
...metadata === void 0 ? {} : { metadata },
|
|
3551
|
+
originUrl: stringValue(source.originUrl),
|
|
3552
|
+
createdAt: goTimestamp(source.createdAt),
|
|
3553
|
+
updatedAt: goTimestamp(source.updatedAt)
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
function goSourceList(value) {
|
|
3557
|
+
const pages = goPage(value.pages);
|
|
3558
|
+
return {
|
|
3559
|
+
sources: value.sources.map(goSource),
|
|
3560
|
+
...pages === void 0 ? {} : { pages },
|
|
3561
|
+
totalSize: numberValue(value.totalSize),
|
|
3562
|
+
currentPage: numberValue(value.currentPage),
|
|
3563
|
+
pageSize: numberValue(value.pageSize)
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
function goCardLink(value) {
|
|
3567
|
+
const source = record(value);
|
|
3568
|
+
return {
|
|
3569
|
+
linkType: stringValue(source.linkType),
|
|
3570
|
+
targetId: stringValue(source.targetId),
|
|
3571
|
+
...source.targetTitle ? { targetTitle: stringValue(source.targetTitle) } : {},
|
|
3572
|
+
...source.description ? { description: stringValue(source.description) } : {}
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3575
|
+
function goCard(value) {
|
|
3576
|
+
const source = record(value);
|
|
3577
|
+
const keywords = optionalStrings(source.keywords);
|
|
3578
|
+
const links = Array.isArray(source.links) && source.links.length > 0 ? source.links.map(goCardLink) : void 0;
|
|
3579
|
+
return {
|
|
3580
|
+
id: stringValue(source.id),
|
|
3581
|
+
...source.knowledgeId ? { knowledgeId: stringValue(source.knowledgeId) } : {},
|
|
3582
|
+
orgId: stringValue(source.orgId),
|
|
3583
|
+
...source.notebookId ? { notebookId: stringValue(source.notebookId) } : {},
|
|
3584
|
+
source: stringValue(source.source),
|
|
3585
|
+
...source.cardType ? { cardType: stringValue(source.cardType) } : {},
|
|
3586
|
+
...source.category ? { category: stringValue(source.category) } : {},
|
|
3587
|
+
...source.repo ? { repo: stringValue(source.repo) } : {},
|
|
3588
|
+
title: stringValue(source.title),
|
|
3589
|
+
content: stringValue(source.content),
|
|
3590
|
+
...keywords === void 0 ? {} : { keywords },
|
|
3591
|
+
...links === void 0 ? {} : { links },
|
|
3592
|
+
...source.extra ? { extra: stringValue(source.extra) } : {},
|
|
3593
|
+
createdAt: goTimestamp(source.createdAt),
|
|
3594
|
+
updatedAt: goTimestamp(source.updatedAt)
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
function goCardList(value) {
|
|
3598
|
+
return {
|
|
3599
|
+
cards: value.cards.map(goCard),
|
|
3600
|
+
totalSize: numberValue(value.totalSize),
|
|
3601
|
+
currentPage: numberValue(value.currentPage),
|
|
3602
|
+
pageSize: numberValue(value.pageSize)
|
|
3603
|
+
};
|
|
3604
|
+
}
|
|
3605
|
+
function goRetrieve(value) {
|
|
3606
|
+
return {
|
|
3607
|
+
chunks: value.chunks.map((chunk) => ({
|
|
3608
|
+
sourceId: chunk.sourceId,
|
|
3609
|
+
sourceTitle: chunk.sourceTitle,
|
|
3610
|
+
sourceUri: chunk.sourceUri,
|
|
3611
|
+
chunkId: chunk.chunkId,
|
|
3612
|
+
chunkIndex: chunk.chunkIndex,
|
|
3613
|
+
content: chunk.content,
|
|
3614
|
+
tokenCount: chunk.tokenCount,
|
|
3615
|
+
...chunk.metadata === void 0 ? {} : { metadata: chunk.metadata },
|
|
3616
|
+
score: chunk.score,
|
|
3617
|
+
...chunk.originUrl === void 0 ? {} : { originUrl: chunk.originUrl }
|
|
3618
|
+
})),
|
|
3619
|
+
citations: value.citations.map((citation) => ({
|
|
3620
|
+
sourceId: citation.sourceId,
|
|
3621
|
+
sourceTitle: citation.sourceTitle,
|
|
3622
|
+
sourceUri: citation.sourceUri,
|
|
3623
|
+
chunkId: citation.chunkId,
|
|
3624
|
+
chunkIndex: citation.chunkIndex,
|
|
3625
|
+
snippet: citation.snippet,
|
|
3626
|
+
...citation.metadata === void 0 ? {} : { metadata: citation.metadata },
|
|
3627
|
+
...citation.originUrl === void 0 ? {} : { originUrl: citation.originUrl }
|
|
3628
|
+
})),
|
|
3629
|
+
promptContext: value.promptContext,
|
|
3630
|
+
total: value.total,
|
|
3631
|
+
reranked: value.reranked,
|
|
3632
|
+
cards: value.cards.map((card) => ({
|
|
3633
|
+
id: card.id,
|
|
3634
|
+
title: card.title,
|
|
3635
|
+
content: card.content,
|
|
3636
|
+
...card.category === void 0 ? {} : { category: card.category },
|
|
3637
|
+
...card.cardType === void 0 ? {} : { cardType: card.cardType },
|
|
3638
|
+
keywords: card.keywords,
|
|
3639
|
+
...card.links.length === 0 ? {} : { links: card.links },
|
|
3640
|
+
score: card.score,
|
|
3641
|
+
depth: card.depth
|
|
3642
|
+
}))
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
function agentField(writer, name, value) {
|
|
3646
|
+
if (value === void 0 || value === null) return;
|
|
3647
|
+
const text = stringValue(value);
|
|
3648
|
+
if (text === "" || text === "-") return;
|
|
3649
|
+
writer.line(`${name}: ${oneLine(text)}`);
|
|
3650
|
+
}
|
|
3651
|
+
function agentText(writer, name, value) {
|
|
3652
|
+
const text = stringValue(value);
|
|
3653
|
+
if (text.length === 0) return;
|
|
3654
|
+
writer.line(`${name}: |-`);
|
|
3655
|
+
for (const line of text.split("\n")) writer.line(` ${line}`);
|
|
3656
|
+
}
|
|
3657
|
+
function agentHeader(writer, kind, total) {
|
|
3658
|
+
writer.line(`kind: ${kind}`);
|
|
3659
|
+
writer.line(`total: ${total}`);
|
|
3660
|
+
}
|
|
3661
|
+
function renderAgentNotebook(writer, value, index = 0) {
|
|
3662
|
+
writer.line("---");
|
|
3663
|
+
agentField(writer, "type", "notebook");
|
|
3664
|
+
if (index > 0) agentField(writer, "index", index);
|
|
3665
|
+
agentField(writer, "id", value.id);
|
|
3666
|
+
agentField(writer, "orgId", value.orgId);
|
|
3667
|
+
agentField(writer, "userId", value.userId);
|
|
3668
|
+
agentField(writer, "status", value.status);
|
|
3669
|
+
agentField(writer, "title", value.title);
|
|
3670
|
+
agentField(writer, "createdAt", timestamp(value.createdAt));
|
|
3671
|
+
agentField(writer, "updatedAt", timestamp(value.updatedAt));
|
|
3672
|
+
agentText(writer, "description", value.description);
|
|
3673
|
+
}
|
|
3674
|
+
function renderAgentSource(writer, value, index = 0) {
|
|
3675
|
+
writer.line("---");
|
|
3676
|
+
agentField(writer, "type", "source");
|
|
3677
|
+
if (index > 0) agentField(writer, "index", index);
|
|
3678
|
+
agentField(writer, "id", value.id);
|
|
3679
|
+
agentField(writer, "notebookId", value.notebookId);
|
|
3680
|
+
agentField(writer, "sourceType", value.sourceType);
|
|
3681
|
+
agentField(writer, "status", value.status);
|
|
3682
|
+
agentField(writer, "title", value.title);
|
|
3683
|
+
agentField(writer, "uri", value.uri);
|
|
3684
|
+
agentField(writer, "path", value.path);
|
|
3685
|
+
agentField(writer, "isDir", value.isDir);
|
|
3686
|
+
agentField(writer, "parentSourceId", value.parentSourceId);
|
|
3687
|
+
agentField(writer, "createdAt", timestamp(value.createdAt));
|
|
3688
|
+
agentField(writer, "updatedAt", timestamp(value.updatedAt));
|
|
3689
|
+
agentText(writer, "error", value.errorMessage);
|
|
3690
|
+
}
|
|
3691
|
+
function renderAgentCard(writer, value, index, score) {
|
|
3692
|
+
writer.line("---");
|
|
3693
|
+
agentField(writer, "type", "card");
|
|
3694
|
+
if (index > 0) agentField(writer, "index", index);
|
|
3695
|
+
if (score !== void 0) agentField(writer, "score", score.toFixed(4));
|
|
3696
|
+
agentField(writer, "id", value.id);
|
|
3697
|
+
agentField(writer, "knowledgeId", value.knowledgeId);
|
|
3698
|
+
agentField(writer, "notebookId", value.notebookId);
|
|
3699
|
+
agentField(writer, "source", value.source);
|
|
3700
|
+
agentField(writer, "cardType", value.cardType);
|
|
3701
|
+
agentField(writer, "category", value.category);
|
|
3702
|
+
agentField(writer, "repo", value.repo);
|
|
3703
|
+
agentField(writer, "title", value.title);
|
|
3704
|
+
if (value.keywords.length > 0) agentField(writer, "keywords", value.keywords.join(", "));
|
|
3705
|
+
agentField(writer, "createdAt", timestamp(value.createdAt));
|
|
3706
|
+
agentField(writer, "updatedAt", timestamp(value.updatedAt));
|
|
3707
|
+
agentText(writer, "content", value.content);
|
|
3708
|
+
if (value.links.length > 0) {
|
|
3709
|
+
writer.line("links:");
|
|
3710
|
+
for (const link of value.links) {
|
|
3711
|
+
writer.line(` - targetId: ${link.targetId}`);
|
|
3712
|
+
agentField(writer, " linkType", link.linkType);
|
|
3713
|
+
agentField(writer, " description", link.description);
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
function json(value, pretty) {
|
|
3718
|
+
return `${JSON.stringify(value, null, pretty ? 2 : 0)}\n`;
|
|
3719
|
+
}
|
|
3720
|
+
function renderNotebook(value, format) {
|
|
3721
|
+
if (format === "json") return json(goNotebook(value), true);
|
|
3722
|
+
if (format === "ndjson") return json(goNotebook(value), false);
|
|
3723
|
+
const writer = new TextWriter();
|
|
3724
|
+
if (format === "agent") renderAgentNotebook(writer, value);
|
|
3725
|
+
else {
|
|
3726
|
+
writer.line(`ID: ${value.id}`);
|
|
3727
|
+
writer.line(`Title: ${value.title}`);
|
|
3728
|
+
writer.line(`Status: ${value.status}`);
|
|
3729
|
+
writer.line(`OrgID: ${value.orgId}`);
|
|
3730
|
+
writer.line(`UserID: ${value.userId}`);
|
|
3731
|
+
writer.line(`CreatedAt: ${timestamp(value.createdAt)}`);
|
|
3732
|
+
writer.line(`UpdatedAt: ${timestamp(value.updatedAt)}`);
|
|
3733
|
+
if (value.description.length > 0) writer.line(`\n${value.description}`);
|
|
3734
|
+
}
|
|
3735
|
+
return writer.toString();
|
|
3736
|
+
}
|
|
3737
|
+
function renderNotebookList(value, format) {
|
|
3738
|
+
if (format === "json") return json(goNotebookList(value), true);
|
|
3739
|
+
if (format === "ndjson") return value.notebooks.map((item) => json(goNotebook(item), false)).join("");
|
|
3740
|
+
const writer = new TextWriter();
|
|
3741
|
+
if (format === "agent") {
|
|
3742
|
+
agentHeader(writer, "notebooks", value.notebooks.length);
|
|
3743
|
+
value.notebooks.forEach((item, index) => renderAgentNotebook(writer, item, index + 1));
|
|
3744
|
+
} else if (value.notebooks.length === 0) writer.line("No notebooks found.");
|
|
3745
|
+
else {
|
|
3746
|
+
writer.line(`Total: ${value.notebooks.length}`);
|
|
3747
|
+
writer.line();
|
|
3748
|
+
table(writer, [
|
|
3749
|
+
"ID",
|
|
3750
|
+
"STATUS",
|
|
3751
|
+
"TITLE",
|
|
3752
|
+
"USER",
|
|
3753
|
+
"UPDATED"
|
|
3754
|
+
], value.notebooks.map((item) => [
|
|
3755
|
+
truncate(item.id, 36),
|
|
3756
|
+
truncate(item.status, 10),
|
|
3757
|
+
truncate(item.title, 28),
|
|
3758
|
+
truncate(item.userId, 18),
|
|
3759
|
+
timestamp(item.updatedAt)
|
|
3760
|
+
]), [
|
|
3761
|
+
36,
|
|
3762
|
+
10,
|
|
3763
|
+
28,
|
|
3764
|
+
18,
|
|
3765
|
+
20
|
|
3766
|
+
], 120);
|
|
3767
|
+
}
|
|
3768
|
+
return writer.toString();
|
|
3769
|
+
}
|
|
3770
|
+
function renderSource(value, format) {
|
|
3771
|
+
if (format === "json") return json(goSource(value), true);
|
|
3772
|
+
if (format === "ndjson") return json(goSource(value), false);
|
|
3773
|
+
const writer = new TextWriter();
|
|
3774
|
+
if (format === "agent") renderAgentSource(writer, value);
|
|
3775
|
+
else {
|
|
3776
|
+
writer.line(`ID: ${value.id}`);
|
|
3777
|
+
writer.line(`NotebookID: ${value.notebookId}`);
|
|
3778
|
+
writer.line(`Title: ${value.title}`);
|
|
3779
|
+
writer.line(`Type: ${value.sourceType}`);
|
|
3780
|
+
writer.line(`Status: ${value.status}`);
|
|
3781
|
+
writer.line(`URI: ${value.uri}`);
|
|
3782
|
+
if (value.path.length > 0) writer.line(`Path: ${value.path}`);
|
|
3783
|
+
if (value.errorMessage.length > 0) writer.line(`Error: ${value.errorMessage}`);
|
|
3784
|
+
writer.line(`UpdatedAt: ${timestamp(value.updatedAt)}`);
|
|
3785
|
+
}
|
|
3786
|
+
return writer.toString();
|
|
3787
|
+
}
|
|
3788
|
+
function renderSourceList(value, format) {
|
|
3789
|
+
if (format === "json") return json(goSourceList(value), true);
|
|
3790
|
+
if (format === "ndjson") return value.sources.map((item) => json(goSource(item), false)).join("");
|
|
3791
|
+
const writer = new TextWriter();
|
|
3792
|
+
if (format === "agent") {
|
|
3793
|
+
agentHeader(writer, "sources", value.sources.length);
|
|
3794
|
+
const summary = pagination(value.pages);
|
|
3795
|
+
if (summary.length > 0) writer.line(`pagination:${summary}`);
|
|
3796
|
+
value.sources.forEach((item, index) => renderAgentSource(writer, item, index + 1));
|
|
3797
|
+
} else if (value.sources.length === 0) writer.line("No sources found.");
|
|
3798
|
+
else {
|
|
3799
|
+
writer.line(`Total: ${value.sources.length}${pagination(value.pages)}`);
|
|
3800
|
+
writer.line();
|
|
3801
|
+
table(writer, [
|
|
3802
|
+
"ID",
|
|
3803
|
+
"TYPE",
|
|
3804
|
+
"STATUS",
|
|
3805
|
+
"TITLE",
|
|
3806
|
+
"URI"
|
|
3807
|
+
], value.sources.map((item) => [
|
|
3808
|
+
truncate(item.id, 36),
|
|
3809
|
+
truncate(item.sourceType, 10),
|
|
3810
|
+
truncate(item.status, 10),
|
|
3811
|
+
truncate(item.title, 30),
|
|
3812
|
+
truncate(item.uri || item.path, 28)
|
|
3813
|
+
]), [
|
|
3814
|
+
36,
|
|
3815
|
+
10,
|
|
3816
|
+
10,
|
|
3817
|
+
30,
|
|
3818
|
+
28
|
|
3819
|
+
], 120);
|
|
3820
|
+
}
|
|
3821
|
+
return writer.toString();
|
|
3822
|
+
}
|
|
3823
|
+
function renderCardList(value, format) {
|
|
3824
|
+
if (format === "json") return json(goCardList(value), true);
|
|
3825
|
+
if (format === "ndjson") return value.cards.map((item) => json(goCard(item), false)).join("");
|
|
3826
|
+
const writer = new TextWriter();
|
|
3827
|
+
if (format === "agent") {
|
|
3828
|
+
agentHeader(writer, "cards", value.totalSize || value.cards.length);
|
|
3829
|
+
value.cards.forEach((item, index) => renderAgentCard(writer, item, index + 1));
|
|
3830
|
+
} else if (value.cards.length === 0) writer.line("No cards found.");
|
|
3831
|
+
else {
|
|
3832
|
+
writer.line(`Total: ${value.totalSize || value.cards.length}`);
|
|
3833
|
+
writer.line();
|
|
3834
|
+
table(writer, [
|
|
3835
|
+
"ID",
|
|
3836
|
+
"TYPE",
|
|
3837
|
+
"CATEGORY",
|
|
3838
|
+
"TITLE",
|
|
3839
|
+
"LINKS",
|
|
3840
|
+
"UPDATED"
|
|
3841
|
+
], value.cards.map((item) => [
|
|
3842
|
+
truncate(item.id, 36),
|
|
3843
|
+
truncate(item.cardType, 8),
|
|
3844
|
+
truncate(item.category, 16),
|
|
3845
|
+
truncate(item.title, 30),
|
|
3846
|
+
item.links.length,
|
|
3847
|
+
timestamp(item.updatedAt)
|
|
3848
|
+
]), [
|
|
3849
|
+
36,
|
|
3850
|
+
8,
|
|
3851
|
+
16,
|
|
3852
|
+
30,
|
|
3853
|
+
5,
|
|
3854
|
+
20
|
|
3855
|
+
], 125);
|
|
3856
|
+
}
|
|
3857
|
+
return writer.toString();
|
|
3858
|
+
}
|
|
3859
|
+
function renderRetrieve(value, format) {
|
|
3860
|
+
if (format === "json") return json(goRetrieve(value), true);
|
|
3861
|
+
if (format === "ndjson") return [...value.chunks.map((item) => ({
|
|
3862
|
+
kind: "chunk",
|
|
3863
|
+
...item
|
|
3864
|
+
})), ...value.cards.map((item) => ({
|
|
3865
|
+
kind: "card",
|
|
3866
|
+
...item
|
|
3867
|
+
}))].map((item) => json(item, false)).join("");
|
|
3868
|
+
const writer = new TextWriter();
|
|
3869
|
+
if (format === "agent") {
|
|
3870
|
+
agentHeader(writer, "notebookContext", value.total);
|
|
3871
|
+
agentField(writer, "reranked", value.reranked);
|
|
3872
|
+
value.chunks.forEach((chunk, index) => {
|
|
3873
|
+
writer.line("---");
|
|
3874
|
+
agentField(writer, "type", "chunk");
|
|
3875
|
+
agentField(writer, "index", index + 1);
|
|
3876
|
+
agentField(writer, "chunkId", chunk.chunkId);
|
|
3877
|
+
agentField(writer, "chunkIndex", chunk.chunkIndex);
|
|
3878
|
+
agentField(writer, "sourceId", chunk.sourceId);
|
|
3879
|
+
agentField(writer, "sourceTitle", chunk.sourceTitle);
|
|
3880
|
+
agentField(writer, "sourceUri", chunk.sourceUri);
|
|
3881
|
+
agentField(writer, "score", chunk.score.toFixed(4));
|
|
3882
|
+
agentField(writer, "tokenCount", chunk.tokenCount);
|
|
3883
|
+
agentText(writer, "content", unescapeText(chunk.content));
|
|
3884
|
+
});
|
|
3885
|
+
value.cards.forEach((card, index) => {
|
|
3886
|
+
writer.line("---");
|
|
3887
|
+
agentField(writer, "type", "retrievedCard");
|
|
3888
|
+
agentField(writer, "index", index + 1);
|
|
3889
|
+
agentField(writer, "id", card.id);
|
|
3890
|
+
agentField(writer, "title", card.title);
|
|
3891
|
+
agentField(writer, "category", card.category);
|
|
3892
|
+
agentField(writer, "cardType", card.cardType);
|
|
3893
|
+
agentField(writer, "score", card.score.toFixed(4));
|
|
3894
|
+
agentField(writer, "depth", card.depth);
|
|
3895
|
+
if (card.keywords.length > 0) agentField(writer, "keywords", card.keywords.join(", "));
|
|
3896
|
+
agentText(writer, "content", unescapeText(card.content));
|
|
3897
|
+
});
|
|
3898
|
+
value.citations.forEach((citation, index) => {
|
|
3899
|
+
writer.line("---");
|
|
3900
|
+
agentField(writer, "type", "citation");
|
|
3901
|
+
agentField(writer, "index", index + 1);
|
|
3902
|
+
agentField(writer, "sourceId", citation.sourceId);
|
|
3903
|
+
agentField(writer, "sourceTitle", citation.sourceTitle);
|
|
3904
|
+
agentField(writer, "chunkId", citation.chunkId);
|
|
3905
|
+
agentText(writer, "snippet", unescapeText(citation.snippet));
|
|
3906
|
+
});
|
|
3907
|
+
agentText(writer, "promptContext", unescapeText(value.promptContext));
|
|
3908
|
+
return writer.toString();
|
|
3909
|
+
}
|
|
3910
|
+
writer.line(`Total chunks: ${value.total} Reranked: ${value.reranked}`);
|
|
3911
|
+
writer.line();
|
|
3912
|
+
if (value.chunks.length > 0) {
|
|
3913
|
+
writer.line("== Chunks ==");
|
|
3914
|
+
table(writer, [
|
|
3915
|
+
"SCORE",
|
|
3916
|
+
"CHUNK_ID",
|
|
3917
|
+
"IDX",
|
|
3918
|
+
"SOURCE",
|
|
3919
|
+
"CONTENT"
|
|
3920
|
+
], value.chunks.map((chunk) => [
|
|
3921
|
+
chunk.score.toFixed(2),
|
|
3922
|
+
truncate(chunk.chunkId, 36),
|
|
3923
|
+
chunk.chunkIndex,
|
|
3924
|
+
truncate(chunk.sourceTitle, 30),
|
|
3925
|
+
truncate(unescapeText(chunk.content).replace(/\n/g, " "), 50)
|
|
3926
|
+
]), [
|
|
3927
|
+
6,
|
|
3928
|
+
36,
|
|
3929
|
+
6,
|
|
3930
|
+
30,
|
|
3931
|
+
50
|
|
3932
|
+
], 130);
|
|
3933
|
+
writer.line();
|
|
3934
|
+
value.chunks.forEach((chunk, index) => {
|
|
3935
|
+
writer.line(`[${index + 1}] chunkId=${chunk.chunkId} sourceId=${chunk.sourceId} sourceTitle=${chunk.sourceTitle} score=${chunk.score.toFixed(4)} tokens=${chunk.tokenCount}`);
|
|
3936
|
+
if (chunk.sourceUri.length > 0) writer.line(` uri: ${chunk.sourceUri}`);
|
|
3937
|
+
if (chunk.content.length > 0) writer.line(` content: ${unescapeText(chunk.content)}`);
|
|
3938
|
+
writer.line();
|
|
3939
|
+
});
|
|
3940
|
+
}
|
|
3941
|
+
if (value.cards.length > 0) {
|
|
3942
|
+
writer.line("== Cards ==");
|
|
3943
|
+
table(writer, [
|
|
3944
|
+
"SCORE",
|
|
3945
|
+
"DEPTH",
|
|
3946
|
+
"ID",
|
|
3947
|
+
"CATEGORY",
|
|
3948
|
+
"TITLE",
|
|
3949
|
+
"CONTENT"
|
|
3950
|
+
], value.cards.map((card) => [
|
|
3951
|
+
card.score.toFixed(2),
|
|
3952
|
+
card.depth,
|
|
3953
|
+
truncate(card.id, 36),
|
|
3954
|
+
truncate(card.category, 20),
|
|
3955
|
+
truncate(card.title, 30),
|
|
3956
|
+
truncate(unescapeText(card.content).replace(/\n/g, " "), 30)
|
|
3957
|
+
]), [
|
|
3958
|
+
6,
|
|
3959
|
+
5,
|
|
3960
|
+
36,
|
|
3961
|
+
20,
|
|
3962
|
+
30,
|
|
3963
|
+
30
|
|
3964
|
+
], 130);
|
|
3965
|
+
writer.line();
|
|
3966
|
+
}
|
|
3967
|
+
if (value.citations.length > 0) {
|
|
3968
|
+
writer.line("== Citations ==");
|
|
3969
|
+
value.citations.forEach((citation, index) => {
|
|
3970
|
+
writer.line(`[${index + 1}] sourceId=${citation.sourceId} sourceTitle=${citation.sourceTitle} chunkId=${citation.chunkId}`);
|
|
3971
|
+
if (citation.snippet.length > 0) writer.line(` snippet: ${oneLine(unescapeText(citation.snippet))}`);
|
|
3972
|
+
});
|
|
3973
|
+
writer.line();
|
|
3974
|
+
}
|
|
3975
|
+
if (value.promptContext.length > 0) writer.line(`== Prompt Context ==\n${unescapeText(value.promptContext)}`);
|
|
3976
|
+
return writer.toString();
|
|
3977
|
+
}
|
|
3978
|
+
function renderQMindCliPresentation(presentation, format) {
|
|
3979
|
+
switch (presentation.kind) {
|
|
3980
|
+
case "notebook": return renderNotebook(presentation.value, format);
|
|
3981
|
+
case "notebookList": return renderNotebookList(presentation.value, format);
|
|
3982
|
+
case "source": return renderSource(presentation.value, format);
|
|
3983
|
+
case "sourceList": return renderSourceList(presentation.value, format);
|
|
3984
|
+
case "cardList": return renderCardList(presentation.value, format);
|
|
3985
|
+
case "retrieve": return renderRetrieve(presentation.value, format);
|
|
3986
|
+
case "imageUpload": return json({
|
|
3987
|
+
uploadUrl: presentation.value.uploadUrl,
|
|
3988
|
+
method: presentation.value.method,
|
|
3989
|
+
...Object.keys(presentation.value.headers).length === 0 ? {} : { headers: presentation.value.headers },
|
|
3990
|
+
ossKey: presentation.value.ossKey,
|
|
3991
|
+
...presentation.value.expiresAt === void 0 ? {} : { expiresAt: presentation.value.expiresAt }
|
|
3992
|
+
}, format !== "ndjson");
|
|
3993
|
+
case "imageUrls": return json(presentation.value, format !== "ndjson");
|
|
3994
|
+
case "json": return json(presentation.value, format !== "ndjson");
|
|
3995
|
+
case "text": return presentation.value;
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
function renderQMindCliError(error, jsonOutput) {
|
|
3999
|
+
const source = error instanceof QMindError ? {
|
|
4000
|
+
code: error.code,
|
|
4001
|
+
details: error.details,
|
|
4002
|
+
message: error.message,
|
|
4003
|
+
rawBody: error.rawBody,
|
|
4004
|
+
requestId: error.requestId,
|
|
4005
|
+
status: error.status,
|
|
4006
|
+
upstreamCode: error.upstreamCode
|
|
4007
|
+
} : {
|
|
4008
|
+
code: "INTERNAL_ERROR",
|
|
4009
|
+
details: void 0,
|
|
4010
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4011
|
+
rawBody: void 0,
|
|
4012
|
+
requestId: void 0,
|
|
4013
|
+
status: void 0,
|
|
4014
|
+
upstreamCode: void 0
|
|
4015
|
+
};
|
|
4016
|
+
const exitCode = source.code === "INVALID_ARGUMENT" ? 2 : 1;
|
|
4017
|
+
if (!jsonOutput) return {
|
|
4018
|
+
exitCode,
|
|
4019
|
+
stderr: `error: ${source.message}\n`,
|
|
4020
|
+
stdout: ""
|
|
4021
|
+
};
|
|
4022
|
+
return {
|
|
4023
|
+
exitCode,
|
|
4024
|
+
stderr: "",
|
|
4025
|
+
stdout: json({ error: {
|
|
4026
|
+
code: source.code,
|
|
4027
|
+
message: source.message,
|
|
4028
|
+
status: source.status,
|
|
4029
|
+
errorCode: source.upstreamCode,
|
|
4030
|
+
requestId: source.requestId,
|
|
4031
|
+
rawBody: source.rawBody,
|
|
4032
|
+
details: source.details
|
|
4033
|
+
} }, true)
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
function compatibilityQuestionEvent(event) {
|
|
4037
|
+
const goMessage = (message) => ({
|
|
4038
|
+
id: message.id,
|
|
4039
|
+
orgId: "",
|
|
4040
|
+
userId: "",
|
|
4041
|
+
notebookId: "",
|
|
4042
|
+
chatSessionId: "",
|
|
4043
|
+
role: message.role,
|
|
4044
|
+
content: message.content,
|
|
4045
|
+
sequence: 0,
|
|
4046
|
+
model: "",
|
|
4047
|
+
createdAt: null,
|
|
4048
|
+
updatedAt: null,
|
|
4049
|
+
sceneType: ""
|
|
4050
|
+
});
|
|
4051
|
+
switch (event.type) {
|
|
4052
|
+
case "messageStart": return {
|
|
4053
|
+
eventType: "message_start",
|
|
4054
|
+
...event.userMessage === void 0 ? {} : { userMessage: goMessage(event.userMessage) },
|
|
4055
|
+
...event.assistantMessageId === void 0 ? {} : { assistantMessageId: event.assistantMessageId },
|
|
4056
|
+
...event.citations.length === 0 ? {} : { citations: event.citations },
|
|
4057
|
+
...event.totalContextChunks === 0 ? {} : { totalContextChunks: event.totalContextChunks },
|
|
4058
|
+
...event.reranked ? { reranked: true } : {}
|
|
4059
|
+
};
|
|
4060
|
+
case "toolCallStart": return {
|
|
4061
|
+
eventType: "tool_call_start",
|
|
4062
|
+
toolName: event.toolName,
|
|
4063
|
+
toolArguments: event.toolArguments,
|
|
4064
|
+
step: event.step
|
|
4065
|
+
};
|
|
4066
|
+
case "toolCallResult": return {
|
|
4067
|
+
eventType: "tool_call_result",
|
|
4068
|
+
toolName: event.toolName,
|
|
4069
|
+
toolResult: event.toolResult,
|
|
4070
|
+
step: event.step
|
|
4071
|
+
};
|
|
4072
|
+
case "delta": return {
|
|
4073
|
+
eventType: "delta",
|
|
4074
|
+
delta: event.delta
|
|
4075
|
+
};
|
|
4076
|
+
case "batchProgress": return {
|
|
4077
|
+
eventType: "batch_progress",
|
|
4078
|
+
metadata: event.metadata
|
|
4079
|
+
};
|
|
4080
|
+
case "heartbeat": return {
|
|
4081
|
+
eventType: "heartbeat",
|
|
4082
|
+
metadata: event.metadata
|
|
4083
|
+
};
|
|
4084
|
+
case "cancel": return {
|
|
4085
|
+
eventType: "cancel",
|
|
4086
|
+
canceled: true,
|
|
4087
|
+
...event.cancelReason === void 0 ? {} : { cancelReason: event.cancelReason }
|
|
4088
|
+
};
|
|
4089
|
+
case "messageComplete": return {
|
|
4090
|
+
eventType: "message_complete",
|
|
4091
|
+
...event.assistantMessage === void 0 ? {} : { assistantMessage: goMessage(event.assistantMessage) },
|
|
4092
|
+
...event.citations.length === 0 ? {} : { citations: event.citations },
|
|
4093
|
+
...event.promptContext.length === 0 ? {} : { promptContext: event.promptContext },
|
|
4094
|
+
...event.totalContextChunks === 0 ? {} : { totalContextChunks: event.totalContextChunks },
|
|
4095
|
+
...event.reranked ? { reranked: true } : {},
|
|
4096
|
+
...event.canceled ? { canceled: true } : {},
|
|
4097
|
+
...event.cancelReason === void 0 ? {} : { cancelReason: event.cancelReason },
|
|
4098
|
+
...Object.keys(event.metadata).length === 0 ? {} : { metadata: event.metadata }
|
|
4099
|
+
};
|
|
4100
|
+
case "error": return {
|
|
4101
|
+
eventType: "error",
|
|
4102
|
+
errorMessage: event.message
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
function batchProgress(metadata) {
|
|
4107
|
+
const value = (key, fallback = "?") => metadata[key] || fallback;
|
|
4108
|
+
const index = () => {
|
|
4109
|
+
const raw = metadata.batchIndex;
|
|
4110
|
+
const parsed = Number(raw);
|
|
4111
|
+
return Number.isInteger(parsed) ? String(parsed + 1) : raw || "?";
|
|
4112
|
+
};
|
|
4113
|
+
switch (metadata.phase) {
|
|
4114
|
+
case "plan": return `[compile] 📋 plan: ${value("totalBatches")} batches, ${value("totalSources")} sources, ${value("totalBytes")} bytes`;
|
|
4115
|
+
case "batch_start": return `[compile] ▶ batch ${index()}/${value("totalBatches")} start (sources=${value("batchSources")}, bytes=${value("batchBytes")}, prepared=${value("preparedCardsTotal", "0")})`;
|
|
4116
|
+
case "batch_done": return `[compile] ✔ batch ${index()}/${value("totalBatches")} done (+${value("preparedCardsDelta", "0")} cards, total=${value("preparedCardsTotal", "0")}, steps=${value("batchStepsUsed")}, ${value("elapsedMs")}ms)`;
|
|
4117
|
+
case "persist_start": return `[compile] 💾 persist start (drafts=${value("draftsTotal")})`;
|
|
4118
|
+
case "persist_done": return metadata.saved === "true" ? `[compile] ✅ persisted ${value("upsertedCards", "0")} cards (failed=${value("failedCards", "0")})` : `[compile] ⚠️ persist failed: ${metadata.persistError || "not persisted"} (drafts retained for fallback save)`;
|
|
4119
|
+
default: return;
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
/** Stateful only for terminal layout; it never owns the SDK stream or writes I/O. */
|
|
4123
|
+
function createQMindCliStreamRenderer(format) {
|
|
4124
|
+
let hasDelta = false;
|
|
4125
|
+
return {
|
|
4126
|
+
finish() {
|
|
4127
|
+
if (!hasDelta) return "";
|
|
4128
|
+
hasDelta = false;
|
|
4129
|
+
return "\n";
|
|
4130
|
+
},
|
|
4131
|
+
push(event) {
|
|
4132
|
+
const compatible = compatibilityQuestionEvent(event);
|
|
4133
|
+
if (format === "json") return json(compatible, true);
|
|
4134
|
+
if (format === "ndjson") return json(compatible, false);
|
|
4135
|
+
const writer = new TextWriter();
|
|
4136
|
+
const breakDelta = () => {
|
|
4137
|
+
if (hasDelta) writer.line();
|
|
4138
|
+
hasDelta = false;
|
|
4139
|
+
};
|
|
4140
|
+
switch (event.type) {
|
|
4141
|
+
case "toolCallStart":
|
|
4142
|
+
breakDelta();
|
|
4143
|
+
if (format === "agent") writer.line(`[tool] ${event.toolName}`);
|
|
4144
|
+
else {
|
|
4145
|
+
writer.line(`[step ${event.step}] tool: ${event.toolName}`);
|
|
4146
|
+
if (event.toolArguments.length > 0) writer.line(` args: ${event.toolArguments}`);
|
|
4147
|
+
}
|
|
4148
|
+
break;
|
|
4149
|
+
case "toolCallResult":
|
|
4150
|
+
if (format !== "agent") writer.line(` result: ${truncate(event.toolResult, 120)}`);
|
|
4151
|
+
break;
|
|
4152
|
+
case "batchProgress": {
|
|
4153
|
+
breakDelta();
|
|
4154
|
+
const line = batchProgress(event.metadata);
|
|
4155
|
+
if (line !== void 0) writer.line(line);
|
|
4156
|
+
break;
|
|
4157
|
+
}
|
|
4158
|
+
case "delta":
|
|
4159
|
+
writer.write(event.delta);
|
|
4160
|
+
hasDelta = true;
|
|
4161
|
+
break;
|
|
4162
|
+
case "messageComplete":
|
|
4163
|
+
breakDelta();
|
|
4164
|
+
if (format === "agent" && event.assistantMessage?.content !== void 0) writer.line(event.assistantMessage.content);
|
|
4165
|
+
if (format !== "agent" && event.citations.length > 0) {
|
|
4166
|
+
writer.line();
|
|
4167
|
+
writer.line("Citations:");
|
|
4168
|
+
event.citations.forEach((citation, index) => writer.line(` [${index + 1}] ${citation}`));
|
|
4169
|
+
}
|
|
4170
|
+
break;
|
|
4171
|
+
case "cancel":
|
|
4172
|
+
breakDelta();
|
|
4173
|
+
writer.line(`[canceled] ${event.cancelReason ?? ""}`.trimEnd());
|
|
4174
|
+
break;
|
|
4175
|
+
case "error":
|
|
4176
|
+
breakDelta();
|
|
4177
|
+
writer.line(`error: ${event.message}`);
|
|
4178
|
+
}
|
|
4179
|
+
return writer.toString();
|
|
4180
|
+
}
|
|
4181
|
+
};
|
|
4182
|
+
}
|
|
4183
|
+
//#endregion
|
|
4184
|
+
//#region src/application.ts
|
|
4185
|
+
const distribution = CLI_DISTRIBUTIONS["cn"];
|
|
4186
|
+
function processIO() {
|
|
4187
|
+
return Object.freeze({
|
|
4188
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
4189
|
+
stderrIsTTY: process.stderr.isTTY === true,
|
|
4190
|
+
stderr(value) {
|
|
4191
|
+
process.stderr.write(value);
|
|
4192
|
+
},
|
|
4193
|
+
stdout(value) {
|
|
4194
|
+
process.stdout.write(value);
|
|
4195
|
+
}
|
|
4196
|
+
});
|
|
4197
|
+
}
|
|
4198
|
+
function interactiveConfirm(io) {
|
|
4199
|
+
return Object.freeze({ async confirm(message) {
|
|
4200
|
+
if (!io.stdinIsTTY || !io.stderrIsTTY) return false;
|
|
4201
|
+
const prompt = createInterface({
|
|
4202
|
+
input: process.stdin,
|
|
4203
|
+
output: process.stderr
|
|
4204
|
+
});
|
|
4205
|
+
try {
|
|
4206
|
+
return (await prompt.question(message)).trim().toLowerCase() === "y";
|
|
4207
|
+
} finally {
|
|
4208
|
+
prompt.close();
|
|
4209
|
+
}
|
|
4210
|
+
} });
|
|
4211
|
+
}
|
|
4212
|
+
function stringOption(options, key) {
|
|
4213
|
+
const value = options[key];
|
|
4214
|
+
return typeof value === "string" ? value : void 0;
|
|
4215
|
+
}
|
|
4216
|
+
function optionalString(options, key) {
|
|
4217
|
+
const value = stringOption(options, key)?.trim();
|
|
4218
|
+
return value === void 0 || value.length === 0 ? void 0 : value;
|
|
4219
|
+
}
|
|
4220
|
+
function requiredString(value, label) {
|
|
4221
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new QMindError("INVALID_ARGUMENT", `${label} is required`);
|
|
4222
|
+
return value;
|
|
4223
|
+
}
|
|
4224
|
+
function integer(value) {
|
|
4225
|
+
if (!/^[+-]?\d+$/.test(value)) throw new QMindError("INVALID_ARGUMENT", `invalid integer value: ${JSON.stringify(value)}`);
|
|
4226
|
+
const parsed = Number(value);
|
|
4227
|
+
if (!Number.isSafeInteger(parsed)) throw new QMindError("INVALID_ARGUMENT", `integer value is out of range: ${value}`);
|
|
4228
|
+
return parsed;
|
|
4229
|
+
}
|
|
4230
|
+
function numberOption(options, key, fallback) {
|
|
4231
|
+
const value = options[key];
|
|
4232
|
+
return typeof value === "number" ? value : fallback;
|
|
4233
|
+
}
|
|
4234
|
+
function positiveNumberOption(options, key, fallback) {
|
|
4235
|
+
const value = numberOption(options, key, fallback);
|
|
4236
|
+
return value > 0 ? value : void 0;
|
|
4237
|
+
}
|
|
4238
|
+
function splitCsv(value) {
|
|
4239
|
+
return value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
|
|
4240
|
+
}
|
|
4241
|
+
function formatOf(options) {
|
|
4242
|
+
return parseQMindCliOutputFormat(String(options.format ?? "table"));
|
|
4243
|
+
}
|
|
4244
|
+
function commonFlags(options) {
|
|
4245
|
+
const client = optionalString(options, "client");
|
|
4246
|
+
const dashboard = stringOption(options, "dashboard");
|
|
4247
|
+
const sash = stringOption(options, "sash");
|
|
4248
|
+
const env = stringOption(options, "env");
|
|
4249
|
+
const token = optionalString(options, "token");
|
|
4250
|
+
return {
|
|
4251
|
+
...client === void 0 ? {} : { client },
|
|
4252
|
+
...env === void 0 ? {} : { env },
|
|
4253
|
+
...dashboard === void 0 ? {} : { dashboard },
|
|
4254
|
+
...options.nonInteractive === true ? { nonInteractive: true } : {},
|
|
4255
|
+
...sash === void 0 ? {} : { sash },
|
|
4256
|
+
...token === void 0 ? {} : { token }
|
|
4257
|
+
};
|
|
4258
|
+
}
|
|
4259
|
+
async function hostFor(context, options) {
|
|
4260
|
+
if (context.host !== void 0) return context.host;
|
|
4261
|
+
const configuration = cliHostConfiguration("cn", commonFlags(options), context.hostOptions.environment ?? process.env, context.hostOptions.homeDirectory);
|
|
4262
|
+
const adapters = {
|
|
4263
|
+
...context.hostOptions.adapters,
|
|
4264
|
+
secretStore: createNoopQMindSecretStore(),
|
|
4265
|
+
reporter: context.hostOptions.adapters?.reporter ?? {
|
|
4266
|
+
info(message) {
|
|
4267
|
+
context.io.stderr(`${message}\n`);
|
|
4268
|
+
},
|
|
4269
|
+
warn(message) {
|
|
4270
|
+
context.io.stderr(`${message}\n`);
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
};
|
|
4274
|
+
context.host = await context.hostFactory({
|
|
4275
|
+
...context.hostOptions,
|
|
4276
|
+
...configuration,
|
|
4277
|
+
adapters,
|
|
4278
|
+
stdinIsTTY: context.io.stdinIsTTY
|
|
4279
|
+
});
|
|
4280
|
+
return context.host;
|
|
4281
|
+
}
|
|
4282
|
+
function render(context, options, presentation) {
|
|
4283
|
+
context.io.stdout(renderQMindCliPresentation(presentation, formatOf(options)));
|
|
4284
|
+
}
|
|
4285
|
+
function withCommon(command, output = true) {
|
|
4286
|
+
command.option("--token <token>", "Bearer access token").option("--env <name>", "product environment: prod (default), test, or daily where supported").option("--sash <url>", "custom debug Sash origin (requires --dashboard; excludes --env)").option("--dashboard <url>", "custom debug Dashboard origin (requires --sash; excludes --env)").option("--org <id>", "organization ID (deprecated and ignored)").option("--client <name>", "originating client identifier").option("--non-interactive", "never open a browser when credentials are missing");
|
|
4287
|
+
if (output) command.option("--format <format>", "output format: table | json | agent | ndjson", "table");
|
|
4288
|
+
return command;
|
|
4289
|
+
}
|
|
4290
|
+
function registerCardQueryCommand(context, command) {
|
|
4291
|
+
withCommon(command).option("--nb <id>", "notebook ID (required)").option("-q, --query <query>", "search query", "").option("--cat <category>", "category filter", "").option("--page <number>", "page number", integer, 0).option("--page-size <number>", "page size", integer, 20).action(async (raw) => {
|
|
4292
|
+
const host = await hostFor(context, raw);
|
|
4293
|
+
const page = positiveNumberOption(raw, "page", 0);
|
|
4294
|
+
const pageSize = positiveNumberOption(raw, "pageSize", 20);
|
|
4295
|
+
render(context, raw, {
|
|
4296
|
+
kind: "cardList",
|
|
4297
|
+
value: await host.client.listCards(requiredString(raw.nb, "--nb"), {
|
|
4298
|
+
category: String(raw.cat ?? ""),
|
|
4299
|
+
...page === void 0 ? {} : { page },
|
|
4300
|
+
...pageSize === void 0 ? {} : { pageSize },
|
|
4301
|
+
query: String(raw.query ?? "")
|
|
4302
|
+
})
|
|
4303
|
+
});
|
|
4304
|
+
});
|
|
4305
|
+
}
|
|
4306
|
+
function registerCoreCommands(context, program) {
|
|
4307
|
+
withCommon(program.command("login").description("Authorize via browser and save credentials"), false).option("--client-id <id>", "OAuth App client_id").action(async (raw) => {
|
|
4308
|
+
const host = await hostFor(context, raw);
|
|
4309
|
+
const clientId = optionalString(raw, "clientId");
|
|
4310
|
+
const result = await host.login({ ...clientId === void 0 ? {} : { clientId } });
|
|
4311
|
+
context.io.stdout(`Logged in successfully [${result.sashOrigin} -> ${host.config.dashboardOrigin}]\nToken saved to ${host.config.homeDirectory}/credentials.json\nDevice token expires at: ${result.expiresAt}\n`);
|
|
4312
|
+
});
|
|
4313
|
+
withCommon(program.command("logout").description("Remove saved credentials"), false).action(async (raw) => {
|
|
4314
|
+
await (await hostFor(context, raw)).logout();
|
|
4315
|
+
context.io.stdout("Logged out. Credentials removed.\n");
|
|
4316
|
+
});
|
|
4317
|
+
const notebook = program.command("notebook").description("Manage notebooks");
|
|
4318
|
+
withCommon(notebook.command("create").description("Create a notebook")).option("--title <title>", "notebook title (required)").option("--desc <description>", "notebook description", "").action(async (raw) => {
|
|
4319
|
+
render(context, raw, {
|
|
4320
|
+
kind: "notebook",
|
|
4321
|
+
value: await (await hostFor(context, raw)).client.createNotebook({
|
|
4322
|
+
description: String(raw.desc ?? ""),
|
|
4323
|
+
title: requiredString(raw.title, "--title")
|
|
4324
|
+
})
|
|
4325
|
+
});
|
|
4326
|
+
});
|
|
4327
|
+
withCommon(notebook.command("list").description("List notebooks")).option("--filter-client <name>", "filter by originating client", "").action(async (raw) => {
|
|
4328
|
+
const host = await hostFor(context, raw);
|
|
4329
|
+
const client = optionalString(raw, "filterClient");
|
|
4330
|
+
render(context, raw, {
|
|
4331
|
+
kind: "notebookList",
|
|
4332
|
+
value: await host.client.listNotebooks(client === void 0 ? {} : { client })
|
|
4333
|
+
});
|
|
4334
|
+
});
|
|
4335
|
+
withCommon(notebook.command("get <notebook_id>").description("Get a notebook")).action(async (notebookId, raw) => {
|
|
4336
|
+
render(context, raw, {
|
|
4337
|
+
kind: "notebook",
|
|
4338
|
+
value: await (await hostFor(context, raw)).client.getNotebook(notebookId)
|
|
4339
|
+
});
|
|
4340
|
+
});
|
|
4341
|
+
withCommon(notebook.command("delete <notebook_id>").description("Delete a notebook"), false).action(async (notebookId, raw) => {
|
|
4342
|
+
await (await hostFor(context, raw)).client.deleteNotebook(notebookId);
|
|
4343
|
+
context.io.stdout("deleted\n");
|
|
4344
|
+
});
|
|
4345
|
+
const cards = program.command("cards").description("Manage notebook cards");
|
|
4346
|
+
for (const action of ["list", "search"]) {
|
|
4347
|
+
const description = action === "search" ? "Search notebook cards" : "List notebook cards";
|
|
4348
|
+
registerCardQueryCommand(context, cards.command(action).description(description));
|
|
4349
|
+
registerCardQueryCommand(context, program.command(action).description(`${description} (alias for cards ${action})`));
|
|
4350
|
+
}
|
|
4351
|
+
withCommon(program.command("retrieve [query]").description("Retrieve notebook context without an LLM call")).option("--nb <id>", "notebook ID (required)").option("-q, --query <query>", "retrieval query").option("--sources <ids>", "comma-separated source IDs", "").option("--top-k <number>", "top-k for retrieval", integer, 0).option("--max-results <number>", "maximum context results", integer, 0).action(async (query, raw) => {
|
|
4352
|
+
render(context, raw, {
|
|
4353
|
+
kind: "retrieve",
|
|
4354
|
+
value: await (await hostFor(context, raw)).client.retrieve(requiredString(raw.nb, "--nb"), {
|
|
4355
|
+
maxResults: numberOption(raw, "maxResults", 0),
|
|
4356
|
+
query: requiredString(raw.query || query, "--query or positional query"),
|
|
4357
|
+
sourceIds: splitCsv(stringOption(raw, "sources")),
|
|
4358
|
+
topK: numberOption(raw, "topK", 0)
|
|
4359
|
+
})
|
|
4360
|
+
});
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
4363
|
+
function registerSourceCommands(context, program) {
|
|
4364
|
+
const source = program.command("source").description("Manage notebook sources");
|
|
4365
|
+
withCommon(source.command("create").description("Create a source record")).option("--nb <id>", "notebook ID (required)").option("--title <title>", "source title (required)").option("--type <type>", "source type", "").option("--parent <id>", "parent source ID", "").option("--path <path>", "source path", "").option("--is-dir", "create a directory source").option("--status <status>", "source status", "").action(async (raw) => {
|
|
4366
|
+
render(context, raw, {
|
|
4367
|
+
kind: "source",
|
|
4368
|
+
value: await (await hostFor(context, raw)).client.createSource(requiredString(raw.nb, "--nb"), {
|
|
4369
|
+
isDir: raw.isDir === true ? true : void 0,
|
|
4370
|
+
parentSourceId: String(raw.parent ?? ""),
|
|
4371
|
+
path: String(raw.path ?? ""),
|
|
4372
|
+
sourceType: String(raw.type ?? ""),
|
|
4373
|
+
status: String(raw.status ?? ""),
|
|
4374
|
+
title: requiredString(raw.title, "--title")
|
|
4375
|
+
})
|
|
4376
|
+
});
|
|
4377
|
+
});
|
|
4378
|
+
withCommon(source.command("mkdir").description("Create a directory source")).option("--nb <id>", "notebook ID (required)").option("--title <title>", "folder name (required)").option("--parent <id>", "parent source ID", "").option("--path <path>", "source path", "").action(async (raw) => {
|
|
4379
|
+
render(context, raw, {
|
|
4380
|
+
kind: "source",
|
|
4381
|
+
value: await (await hostFor(context, raw)).client.createDirectory(requiredString(raw.nb, "--nb"), {
|
|
4382
|
+
parentSourceId: String(raw.parent ?? ""),
|
|
4383
|
+
path: String(raw.path ?? ""),
|
|
4384
|
+
title: requiredString(raw.title, "--title")
|
|
4385
|
+
})
|
|
4386
|
+
});
|
|
4387
|
+
});
|
|
4388
|
+
withCommon(source.command("list").description("List sources")).option("--nb <id>", "notebook ID (required)").option("--page <number>", "page number", integer, 0).option("--page-size <number>", "page size", integer, 0).option("--all", "fetch and merge every page").option("--max-pages <number>", "all-pages safety limit", integer, 1e4).action(async (raw) => {
|
|
4389
|
+
const host = await hostFor(context, raw);
|
|
4390
|
+
const notebookId = requiredString(raw.nb, "--nb");
|
|
4391
|
+
let value;
|
|
4392
|
+
if (raw.all === true) {
|
|
4393
|
+
const pageSize = positiveNumberOption(raw, "pageSize", 0) ?? 100;
|
|
4394
|
+
value = { sources: await host.client.listAllSources(notebookId, {
|
|
4395
|
+
maxPages: numberOption(raw, "maxPages", 1e4),
|
|
4396
|
+
pageSize
|
|
4397
|
+
}) };
|
|
4398
|
+
} else {
|
|
4399
|
+
const page = positiveNumberOption(raw, "page", 0);
|
|
4400
|
+
const pageSize = positiveNumberOption(raw, "pageSize", 0);
|
|
4401
|
+
value = await host.client.listSources(notebookId, {
|
|
4402
|
+
...page === void 0 ? {} : { page },
|
|
4403
|
+
...pageSize === void 0 ? {} : { pageSize }
|
|
4404
|
+
});
|
|
4405
|
+
}
|
|
4406
|
+
render(context, raw, {
|
|
4407
|
+
kind: "sourceList",
|
|
4408
|
+
value
|
|
4409
|
+
});
|
|
4410
|
+
});
|
|
4411
|
+
withCommon(source.command("get <source_id>").description("Get a source")).option("--nb <id>", "notebook ID (required)").action(async (sourceId, raw) => {
|
|
4412
|
+
render(context, raw, {
|
|
4413
|
+
kind: "source",
|
|
4414
|
+
value: await (await hostFor(context, raw)).client.getSource(requiredString(raw.nb, "--nb"), sourceId)
|
|
4415
|
+
});
|
|
4416
|
+
});
|
|
4417
|
+
withCommon(source.command("upload").description("Upload a local file")).option("--nb <id>", "notebook ID (required)").option("--file <path>", "local file path (required)").option("--title <title>", "source title", "").option("--type <type>", "source type", "").option("--parent <id>", "parent source ID", "").option("--path <path>", "source path", "").action(async (raw) => {
|
|
4418
|
+
const host = await hostFor(context, raw);
|
|
4419
|
+
const file = requiredString(raw.file, "--file");
|
|
4420
|
+
render(context, raw, {
|
|
4421
|
+
kind: "source",
|
|
4422
|
+
value: await host.client.uploadSource(requiredString(raw.nb, "--nb"), await host.createFileSource(file), {
|
|
4423
|
+
...optionalString(raw, "title") === void 0 ? {} : { title: optionalString(raw, "title") },
|
|
4424
|
+
...optionalString(raw, "type") === void 0 ? {} : { sourceType: optionalString(raw, "type") },
|
|
4425
|
+
...optionalString(raw, "parent") === void 0 ? {} : { parentSourceId: optionalString(raw, "parent") },
|
|
4426
|
+
...optionalString(raw, "path") === void 0 ? {} : { path: optionalString(raw, "path") }
|
|
4427
|
+
})
|
|
4428
|
+
});
|
|
4429
|
+
});
|
|
4430
|
+
withCommon(source.command("download <source_id>").description("Download the original source file"), false).option("--nb <id>", "notebook ID (required)").option("-o, --output <path>", "output file path").option("--overwrite", "replace an existing target").action(async (sourceId, raw) => {
|
|
4431
|
+
const host = await hostFor(context, raw);
|
|
4432
|
+
const record = await host.client.getSource(requiredString(raw.nb, "--nb"), sourceId);
|
|
4433
|
+
if (record.originUrl === void 0 || record.originUrl.length === 0) throw new QMindError("NOT_FOUND", "source has no downloadable original file");
|
|
4434
|
+
const output = optionalString(raw, "output") ?? safeQMindDownloadName(record.title, sourceId);
|
|
4435
|
+
const bytes = await host.downloadToFile({
|
|
4436
|
+
overwrite: raw.overwrite === true,
|
|
4437
|
+
targetPath: output,
|
|
4438
|
+
url: record.originUrl
|
|
4439
|
+
});
|
|
4440
|
+
context.io.stderr(`Downloaded ${output} (${bytes} bytes)\n`);
|
|
4441
|
+
});
|
|
4442
|
+
withCommon(source.command("mv <source_id>").alias("move").description("Move or rename a source")).option("--nb <id>", "notebook ID (required)").option("--parent <id>", "new parent; pass an empty string for root").option("--path <path>", "new source path").option("--rename <title>", "new source title").option("--status <status>", "new source status").action(async (sourceId, raw) => {
|
|
4443
|
+
const patch = {
|
|
4444
|
+
...raw.parent === void 0 ? {} : { parentSourceId: String(raw.parent) },
|
|
4445
|
+
...raw.path === void 0 ? {} : { path: String(raw.path) },
|
|
4446
|
+
...raw.rename === void 0 ? {} : { title: String(raw.rename) },
|
|
4447
|
+
...raw.status === void 0 ? {} : { status: String(raw.status) }
|
|
4448
|
+
};
|
|
4449
|
+
if (Object.keys(patch).length === 0) throw new QMindError("INVALID_ARGUMENT", "at least one of --parent, --path, --rename or --status is required");
|
|
4450
|
+
render(context, raw, {
|
|
4451
|
+
kind: "source",
|
|
4452
|
+
value: await (await hostFor(context, raw)).client.moveSource(requiredString(raw.nb, "--nb"), sourceId, patch)
|
|
4453
|
+
});
|
|
4454
|
+
});
|
|
4455
|
+
withCommon(source.command("content <source_id>").description("Read parsed source content"), false).option("--nb <id>", "notebook ID (required)").option("--download <path>", "save content to a file").option("--no-overwrite", "refuse to replace an existing file").action(async (sourceId, raw) => {
|
|
4456
|
+
const content = await (await hostFor(context, raw)).client.getSourceContent(requiredString(raw.nb, "--nb"), sourceId);
|
|
4457
|
+
const path = optionalString(raw, "download");
|
|
4458
|
+
if (path === void 0) context.io.stdout(content.content);
|
|
4459
|
+
else {
|
|
4460
|
+
await atomicQMindWrite(path, content.content, raw.overwrite !== false);
|
|
4461
|
+
context.io.stdout(`content saved to ${path}\n`);
|
|
4462
|
+
}
|
|
4463
|
+
});
|
|
4464
|
+
withCommon(source.command("delete <source_id>").description("Delete a source"), false).option("--nb <id>", "notebook ID (required)").option("--force", "skip directory cascade confirmation").action(async (sourceId, raw) => {
|
|
4465
|
+
const host = await hostFor(context, raw);
|
|
4466
|
+
const notebookId = requiredString(raw.nb, "--nb");
|
|
4467
|
+
if (raw.force !== true) {
|
|
4468
|
+
const record = await host.client.getSource(notebookId, sourceId).catch(() => void 0);
|
|
4469
|
+
if (record?.isDir === true && !await context.confirm.confirm(`warning: source ${JSON.stringify(record.title)} is a directory. Deleting it will cascade-delete ALL children.\nType 'y' to proceed: `)) {
|
|
4470
|
+
context.io.stderr("aborted\n");
|
|
4471
|
+
context.exitCode = 1;
|
|
4472
|
+
return;
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
await host.client.deleteSource(notebookId, sourceId);
|
|
4476
|
+
context.io.stdout("deleted\n");
|
|
4477
|
+
});
|
|
4478
|
+
withCommon(source.command("import-site").description("Import a complete web site")).option("--nb <id>", "notebook ID (required)").option("--url <url>", "site root URL (required)").option("--title <title>", "source title").option("--parent <id>", "parent source ID", "").option("--path-prefix <path>", "path prefix", "").option("--max-pages <number>", "maximum page count", integer, 50).option("--sitemap <url>", "sitemap URL", "").option("--include <patterns>", "comma-separated include patterns", "").option("--exclude <patterns>", "comma-separated exclude patterns", "").option("--compile-after-refresh", "enqueue compilation after import").action(async (raw) => {
|
|
4479
|
+
const host = await hostFor(context, raw);
|
|
4480
|
+
const rootUrl = requiredString(raw.url, "--url");
|
|
4481
|
+
const value = await host.client.importSite(requiredString(raw.nb, "--nb"), {
|
|
4482
|
+
compileAfterRefresh: raw.compileAfterRefresh === true,
|
|
4483
|
+
excludePaths: splitCsv(stringOption(raw, "exclude")),
|
|
4484
|
+
includePaths: splitCsv(stringOption(raw, "include")),
|
|
4485
|
+
maxPages: numberOption(raw, "maxPages", 50),
|
|
4486
|
+
parentSourceId: String(raw.parent ?? ""),
|
|
4487
|
+
pathPrefix: String(raw.pathPrefix ?? ""),
|
|
4488
|
+
rootUrl,
|
|
4489
|
+
sitemapUrl: optionalString(raw, "sitemap"),
|
|
4490
|
+
title: optionalString(raw, "title")
|
|
4491
|
+
});
|
|
4492
|
+
const format = formatOf(raw);
|
|
4493
|
+
if (format === "json" || format === "ndjson") render(context, raw, {
|
|
4494
|
+
kind: "source",
|
|
4495
|
+
value
|
|
4496
|
+
});
|
|
4497
|
+
else context.io.stdout(`Site source created: ${value.id}\n title : ${value.title}\n status: ${value.status}\n site : ${rootUrl}\n\nSite import is running asynchronously. Use '${distribution.command} source list' to check imported pages.\n`);
|
|
4498
|
+
});
|
|
4499
|
+
const image = program.command("image").description("Notebook image presign operations");
|
|
4500
|
+
withCommon(image.command("presign-upload").description("Presign or upload a notebook image"), false).option("--nb <id>", "notebook ID (required)").option("--source <id>", "source ID (required)").option("--filename <name>", "image filename (required unless --file is set)").option("--mime <type>", "MIME type (required unless --file is set)").option("--size <bytes>", "file size", integer, 0).option("--file <path>", "local image to upload").action(async (raw) => {
|
|
4501
|
+
const host = await hostFor(context, raw);
|
|
4502
|
+
const notebookId = requiredString(raw.nb, "--nb");
|
|
4503
|
+
const sourceId = requiredString(raw.source, "--source");
|
|
4504
|
+
const file = optionalString(raw, "file");
|
|
4505
|
+
const filename = optionalString(raw, "filename");
|
|
4506
|
+
const mimeType = optionalString(raw, "mime");
|
|
4507
|
+
const value = file === void 0 ? await host.client.presignImageUpload(notebookId, sourceId, {
|
|
4508
|
+
filename: requiredString(filename, "--filename"),
|
|
4509
|
+
mimeType: requiredString(mimeType, "--mime"),
|
|
4510
|
+
size: numberOption(raw, "size", 0)
|
|
4511
|
+
}) : await host.client.uploadImage(notebookId, sourceId, await host.createFileSource(file, {
|
|
4512
|
+
...filename === void 0 ? {} : { filename },
|
|
4513
|
+
...mimeType === void 0 ? {} : { mimeType }
|
|
4514
|
+
}));
|
|
4515
|
+
if (file !== void 0) context.io.stderr(`uploaded ${file} -> ossKey=${value.ossKey}\n`);
|
|
4516
|
+
context.io.stdout(renderQMindCliPresentation({
|
|
4517
|
+
kind: "imageUpload",
|
|
4518
|
+
value
|
|
4519
|
+
}, "json"));
|
|
4520
|
+
});
|
|
4521
|
+
withCommon(image.command("presign-urls").description("Create inline preview URLs"), false).option("--nb <id>", "notebook ID (required)").option("--keys <keys>", "comma-separated OSS keys (required)").action(async (raw) => {
|
|
4522
|
+
const host = await hostFor(context, raw);
|
|
4523
|
+
const keys = splitCsv(requiredString(raw.keys, "--keys"));
|
|
4524
|
+
const value = await host.client.presignImageUrls(requiredString(raw.nb, "--nb"), keys);
|
|
4525
|
+
context.io.stdout(renderQMindCliPresentation({
|
|
4526
|
+
kind: "imageUrls",
|
|
4527
|
+
value
|
|
4528
|
+
}, "json"));
|
|
4529
|
+
});
|
|
4530
|
+
}
|
|
4531
|
+
async function renderAgentRun(context, host, notebookId, raw, input) {
|
|
4532
|
+
const run = await host.client.startAgentRun(notebookId, input);
|
|
4533
|
+
const renderer = createQMindCliStreamRenderer(formatOf(raw));
|
|
4534
|
+
for await (const event of run.events) {
|
|
4535
|
+
if (event.type === "error") throw new QMindError("STREAM_ERROR", event.message);
|
|
4536
|
+
context.io.stdout(renderer.push(event));
|
|
4537
|
+
}
|
|
4538
|
+
context.io.stdout(renderer.finish());
|
|
4539
|
+
}
|
|
4540
|
+
function registerSceneCommands(context, program) {
|
|
4541
|
+
withCommon(program.command("rag [question]").description("Ask a question using notebook RAG")).option("--nb <id>", "notebook ID (required)").option("-q, --query <query>", "question").option("--top-k <number>", "top-k for retrieval", integer, 0).option("--max-results <number>", "maximum context results", integer, 0).action(async (question, raw) => {
|
|
4542
|
+
await renderAgentRun(context, await hostFor(context, raw), requiredString(raw.nb, "--nb"), raw, {
|
|
4543
|
+
maxResults: numberOption(raw, "maxResults", 0),
|
|
4544
|
+
question: requiredString(raw.query || question, "--query or positional question"),
|
|
4545
|
+
scene: "rag",
|
|
4546
|
+
topK: numberOption(raw, "topK", 0)
|
|
4547
|
+
});
|
|
4548
|
+
});
|
|
4549
|
+
for (const [name, scene] of [["compile", "compilation"], ["lint", "lint"]]) withCommon(program.command(name).description(`Run notebook ${scene}`)).option("--nb <id>", "notebook ID (required)").action(async (raw) => {
|
|
4550
|
+
await renderAgentRun(context, await hostFor(context, raw), requiredString(raw.nb, "--nb"), raw, { scene });
|
|
4551
|
+
});
|
|
4552
|
+
}
|
|
4553
|
+
function registerTaskCommands(context, program) {
|
|
4554
|
+
const task = program.command("task").description("Manage notebook task runs");
|
|
4555
|
+
withCommon(task.command("create").description("Create a notebook task run")).option("--nb <id>", "notebook ID (required)").option("--scene <scene>", "task scene: compilation | source_refresh | condensation | lint").option("--trigger <trigger>", "task trigger", "manual").option("--sources <ids>", "comma-separated source IDs", "").action(async (raw) => {
|
|
4556
|
+
const host = await hostFor(context, raw);
|
|
4557
|
+
const sourceIds = splitCsv(stringOption(raw, "sources"));
|
|
4558
|
+
render(context, raw, {
|
|
4559
|
+
kind: "json",
|
|
4560
|
+
value: await host.client.createTaskRun(requiredString(raw.nb, "--nb"), {
|
|
4561
|
+
...sourceIds.length === 0 ? {} : { config: { sourceIds } },
|
|
4562
|
+
sceneType: requiredString(raw.scene, "--scene"),
|
|
4563
|
+
trigger: String(raw.trigger ?? "manual")
|
|
4564
|
+
})
|
|
4565
|
+
});
|
|
4566
|
+
});
|
|
4567
|
+
withCommon(task.command("list").description("List notebook task runs")).option("--nb <id>", "notebook ID (required)").option("--scene <scene>", "filter by scene", "").option("--trigger <trigger>", "filter by trigger", "").option("--status <status>", "filter by status", "").option("--parent-run <id>", "filter by parent run ID", "").option("--scheduled-task <id>", "filter by scheduled task ID", "").option("--page <number>", "page number", integer, 0).option("--page-size <number>", "page size", integer, 20).action(async (raw) => {
|
|
4568
|
+
const host = await hostFor(context, raw);
|
|
4569
|
+
const status = optionalString(raw, "status");
|
|
4570
|
+
render(context, raw, {
|
|
4571
|
+
kind: "json",
|
|
4572
|
+
value: await host.client.listTaskRuns(requiredString(raw.nb, "--nb"), {
|
|
4573
|
+
page: numberOption(raw, "page", 0),
|
|
4574
|
+
pageSize: numberOption(raw, "pageSize", 20),
|
|
4575
|
+
parentRunId: String(raw.parentRun ?? ""),
|
|
4576
|
+
sceneType: String(raw.scene ?? ""),
|
|
4577
|
+
scheduledTaskId: String(raw.scheduledTask ?? ""),
|
|
4578
|
+
...status === void 0 ? {} : { status },
|
|
4579
|
+
trigger: String(raw.trigger ?? "")
|
|
4580
|
+
})
|
|
4581
|
+
});
|
|
4582
|
+
});
|
|
4583
|
+
withCommon(task.command("get <run_id>").description("Get a notebook task run")).option("--nb <id>", "notebook ID (required)").action(async (runId, raw) => {
|
|
4584
|
+
render(context, raw, {
|
|
4585
|
+
kind: "json",
|
|
4586
|
+
value: await (await hostFor(context, raw)).client.getTaskRun(requiredString(raw.nb, "--nb"), runId)
|
|
4587
|
+
});
|
|
4588
|
+
});
|
|
4589
|
+
}
|
|
4590
|
+
function syncText(stats) {
|
|
4591
|
+
return `[upload-folder] 完成: 创建目录 ${stats.dirs_created} (复用 ${stats.dirs_reused}, 失败 ${stats.dirs_failed}), 上传文件 ${stats.files_uploaded}, 已存在跳过 ${stats.files_already_exist}, 不支持或忽略 ${stats.files_skipped}, 失败 ${stats.files_failed}, 时间戳刷新失败 ${stats.dirs_refresh_failed}` + (stats.files_deleted === void 0 ? "" : `, 删除文件 ${stats.files_deleted}, 删除目录 ${stats.dirs_deleted ?? 0}, 删除失败 ${stats.delete_failed ?? 0}`) + "\n";
|
|
4592
|
+
}
|
|
4593
|
+
function registerSyncCommand(context, program) {
|
|
4594
|
+
withCommon(program.command("upload-folder").alias("sync").description("Synchronize a local folder"), false).option("--nb <id>", "notebook ID (required)").option("--dir <path>", "local directory (required)").option("--extensions <list>", "comma-separated extensions").option("--dir-ids <path>", "existing directory ID mapping").option("--skip-upload", "only refresh directory timestamps").option("--concurrency <number>", "parallel uploads", integer, 3).option("--save-dir-ids <path>", "directory ID mapping output").option("--ignore <patterns...>", "glob patterns to exclude").option("--delete", "delete remote entries absent from the local mirror").option("--dry-run", "calculate changes without mutating remote or local state").option("--format <format>", "summary output format: json | text", "json").addOption(new Option("--mode <mode>", "existing-file behavior").choices([
|
|
4595
|
+
"skip",
|
|
4596
|
+
"overwrite",
|
|
4597
|
+
"if-changed"
|
|
4598
|
+
]).default("skip")).action(async (raw) => {
|
|
4599
|
+
const host = await hostFor(context, raw);
|
|
4600
|
+
const ignoreValues = Array.isArray(raw.ignore) ? raw.ignore.flatMap((value) => splitCsv(String(value))) : [];
|
|
4601
|
+
const directoryIdsPath = optionalString(raw, "dirIds");
|
|
4602
|
+
const saveDirectoryIdsPath = optionalString(raw, "saveDirIds");
|
|
4603
|
+
const extensions = splitCsv(optionalString(raw, "extensions"));
|
|
4604
|
+
const result = await synchronizeQMindFolder(host, {
|
|
4605
|
+
baseDirectory: requiredString(raw.dir, "--dir"),
|
|
4606
|
+
cacheDirectory: join(host.config.homeDirectory, "cache"),
|
|
4607
|
+
concurrency: Math.max(1, numberOption(raw, "concurrency", 3)),
|
|
4608
|
+
delete: raw.delete === true,
|
|
4609
|
+
dryRun: raw.dryRun === true,
|
|
4610
|
+
...extensions.length === 0 ? {} : { extensions },
|
|
4611
|
+
ignorePatterns: ignoreValues,
|
|
4612
|
+
mode: String(raw.mode ?? "skip"),
|
|
4613
|
+
notebookId: requiredString(raw.nb, "--nb"),
|
|
4614
|
+
skipUpload: raw.skipUpload === true,
|
|
4615
|
+
...directoryIdsPath === void 0 ? {} : { directoryIdsPath },
|
|
4616
|
+
...saveDirectoryIdsPath === void 0 ? {} : { saveDirectoryIdsPath }
|
|
4617
|
+
});
|
|
4618
|
+
result.diagnostics.forEach((message) => context.io.stderr(`[upload-folder] ${message}\n`));
|
|
4619
|
+
if (String(raw.format ?? "json") === "text") context.io.stderr(syncText(result.stats));
|
|
4620
|
+
else context.io.stdout(renderQMindCliPresentation({
|
|
4621
|
+
kind: "json",
|
|
4622
|
+
value: result.stats
|
|
4623
|
+
}, "json"));
|
|
4624
|
+
if (result.exitCode === 1) context.exitCode = 1;
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
4627
|
+
function registerUpdateCommand(program) {
|
|
4628
|
+
program.command("self-update").description("Show the supported qmind CLI upgrade path").option("--url <url>", "legacy update URL (ignored)").addOption(new Option("--channel <channel>", "legacy release channel (ignored)").choices([
|
|
4629
|
+
"stable",
|
|
4630
|
+
"beta",
|
|
4631
|
+
"daily"
|
|
4632
|
+
]).default("stable")).option("--public-key <key>", "legacy public key (ignored)").option("--check", "legacy check mode (ignored)").option("--force", "legacy reinstall mode (ignored)").option("--allow-downgrade", "legacy downgrade mode (ignored)").action(() => {
|
|
4633
|
+
throw new QMindError("UNSUPPORTED_OPERATION", `self-update is unavailable for npm installs; run npm update --global ${distribution.package}`);
|
|
4634
|
+
});
|
|
4635
|
+
}
|
|
4636
|
+
/** Internal parser adapter. Product consumers should call runQMindCli(). */
|
|
4637
|
+
function createQMindCliProgram(context) {
|
|
4638
|
+
const program = new Command().name(distribution.command).description("QMind knowledge CLI").version(QMIND_CLI_VERSION).showHelpAfterError().exitOverride().configureOutput({
|
|
4639
|
+
writeErr: (value) => context.io.stderr(value),
|
|
4640
|
+
writeOut: (value) => context.io.stdout(value)
|
|
4641
|
+
});
|
|
4642
|
+
program.command("version").description("Print version and build information").action(() => {
|
|
4643
|
+
context.io.stdout(`${distribution.command} ${QMIND_CLI_VERSION} (built ${QMIND_CLI_BUILD_TIME}, ${qmindCliPlatform()}/${qmindCliArchitecture()})\n`);
|
|
4644
|
+
});
|
|
4645
|
+
registerCoreCommands(context, program);
|
|
4646
|
+
registerSourceCommands(context, program);
|
|
4647
|
+
registerSceneCommands(context, program);
|
|
4648
|
+
registerTaskCommands(context, program);
|
|
4649
|
+
registerSyncCommand(context, program);
|
|
4650
|
+
registerUpdateCommand(program);
|
|
4651
|
+
return program;
|
|
4652
|
+
}
|
|
4653
|
+
async function runQMindCli(argv, options = {}) {
|
|
4654
|
+
const io = options.io ?? processIO();
|
|
4655
|
+
const context = {
|
|
4656
|
+
confirm: options.confirm ?? interactiveConfirm(io),
|
|
4657
|
+
exitCode: 0,
|
|
4658
|
+
host: void 0,
|
|
4659
|
+
hostFactory: options.hostFactory ?? createQMindCliHost,
|
|
4660
|
+
hostOptions: options.hostOptions ?? {},
|
|
4661
|
+
io
|
|
4662
|
+
};
|
|
4663
|
+
const normalized = normalizeLegacyArgv(argv);
|
|
4664
|
+
const requiresLegacyAction = normalized.length === 0 || normalized.length === 1 && (/* @__PURE__ */ new Set([
|
|
4665
|
+
"notebook",
|
|
4666
|
+
"cards",
|
|
4667
|
+
"source",
|
|
4668
|
+
"image"
|
|
4669
|
+
])).has(normalized[0] ?? "");
|
|
4670
|
+
let exitCode;
|
|
4671
|
+
try {
|
|
4672
|
+
await createQMindCliProgram(context).parseAsync(normalized, { from: "user" });
|
|
4673
|
+
exitCode = context.exitCode;
|
|
4674
|
+
} catch (error) {
|
|
4675
|
+
if (error instanceof CommanderError) exitCode = error.code === "commander.help" || error.code === "commander.helpDisplayed" || error.code === "commander.version" ? 0 : 2;
|
|
4676
|
+
else {
|
|
4677
|
+
const rendered = renderQMindCliError(error, requestsJsonOutput(normalized));
|
|
4678
|
+
if (rendered.stdout.length > 0) io.stdout(rendered.stdout);
|
|
4679
|
+
if (rendered.stderr.length > 0) io.stderr(rendered.stderr);
|
|
4680
|
+
exitCode = rendered.exitCode;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
if (exitCode === 0 && requiresLegacyAction) exitCode = 2;
|
|
4684
|
+
try {
|
|
4685
|
+
await context.host?.close();
|
|
4686
|
+
} catch {
|
|
4687
|
+
io.stderr("error: failed to close the QMind CLI host\n");
|
|
4688
|
+
if (exitCode === 0) exitCode = 1;
|
|
4689
|
+
}
|
|
4690
|
+
return exitCode;
|
|
4691
|
+
}
|
|
4692
|
+
//#endregion
|
|
4693
|
+
//#region src/qmind.ts
|
|
4694
|
+
process.exitCode = await runQMindCli(process.argv.slice(2));
|
|
4695
|
+
//#endregion
|
|
4696
|
+
export {};
|