@docmana/sdk 0.3.5 → 0.8.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 +33 -4
- package/README.md +175 -232
- package/dist/api/execution-result.d.ts +3 -0
- package/dist/api/execution-status.d.ts +6 -0
- package/dist/api/flow-definition.d.ts +3 -0
- package/dist/api/flow-metadata.d.ts +3 -0
- package/dist/api/list-flows.d.ts +3 -0
- package/dist/api/list-organizations.d.ts +3 -0
- package/dist/api/organization-documents.d.ts +7 -0
- package/dist/api/run-flow.d.ts +6 -0
- package/dist/cli.mjs +1022 -695
- package/dist/cli.mjs.map +1 -1
- package/dist/client.d.ts +26 -0
- package/dist/config.d.ts +21 -0
- package/dist/errors.d.ts +55 -0
- package/dist/files/resolve-input.d.ts +9 -0
- package/dist/http/http-client.d.ts +31 -0
- package/dist/index.d.ts +4 -349
- package/dist/index.js +323 -339
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +319 -335
- package/dist/index.mjs.map +1 -1
- package/dist/models/docmana-api-document-execution-result.model.d.ts +9 -0
- package/dist/models/docmana-api-document-request.model.d.ts +15 -0
- package/dist/models/docmana-api-execution-result.model.d.ts +24 -0
- package/dist/models/docmana-api-node-result.model.d.ts +28 -0
- package/dist/models/docmana-classification-result.model.d.ts +3 -0
- package/dist/models/docmana-conclusion-result.model.d.ts +3 -0
- package/dist/models/docmana-cross-validation-result.model.d.ts +3 -0
- package/dist/models/docmana-document-mapping.model.d.ts +4 -0
- package/dist/models/docmana-document-result.model.d.ts +11 -0
- package/dist/models/docmana-execution-result.model.d.ts +15 -0
- package/dist/models/docmana-extraction-result.model.d.ts +3 -0
- package/dist/models/docmana-metadata-extraction-result.model.d.ts +3 -0
- package/dist/models/docmana-node-result.model.d.ts +7 -0
- package/dist/models/docmana-score-node-result.model.d.ts +5 -0
- package/dist/models/docmana-validation-result.model.d.ts +3 -0
- package/dist/models/document-content.model.d.ts +4 -0
- package/dist/models/execution-progress.model.d.ts +4 -0
- package/dist/models/execution-status.enum.d.ts +1 -0
- package/dist/models/flow-configs.model.d.ts +16 -0
- package/dist/models/flow-edge.model.d.ts +4 -0
- package/dist/models/flow-node-type.enum.d.ts +1 -0
- package/dist/models/flow-node.model.d.ts +33 -0
- package/dist/models/flow.model.d.ts +11 -0
- package/dist/models/index.d.ts +26 -0
- package/dist/models/node-translation-result.model.d.ts +7 -0
- package/dist/models/physical-document.model.d.ts +9 -0
- package/dist/models/virtual-document.model.d.ts +10 -0
- package/dist/polling/poll.d.ts +15 -0
- package/dist/types.d.ts +145 -0
- package/package.json +17 -14
- package/dist/index.d.cts +0 -349
package/dist/cli.mjs
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/cli.ts
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { dirname, resolve } from "path";
|
|
7
|
-
import { createInterface } from "readline/promises";
|
|
3
|
+
// src/cli/cli.ts
|
|
4
|
+
import { realpathSync } from "fs";
|
|
5
|
+
import { resolve as resolve2 } from "path";
|
|
8
6
|
import { fileURLToPath } from "url";
|
|
9
7
|
import { Command, CommanderError } from "commander";
|
|
10
8
|
|
|
11
|
-
// src/
|
|
9
|
+
// src/sdk/config.ts
|
|
10
|
+
var DEFAULTS = {
|
|
11
|
+
tokenEndpoint: "https://4fe70f5b-e013-4f65-9fa7-3109a33beba5.ciamlogin.com/4fe70f5b-e013-4f65-9fa7-3109a33beba5/oauth2/v2.0/token",
|
|
12
|
+
scope: "api://d781e6ba-cc08-4618-8099-ad968abd2b9e/.default",
|
|
13
|
+
timeoutMs: 3e5,
|
|
14
|
+
pollIntervalMs: 2e3,
|
|
15
|
+
pollMaxIntervalMs: 1e4
|
|
16
|
+
};
|
|
17
|
+
function resolveConfig(config) {
|
|
18
|
+
if (!config.apiBaseUrl) throw new Error("DocmanaConfig.apiBaseUrl is required");
|
|
19
|
+
if (!config.accessToken && !config.getAccessToken) {
|
|
20
|
+
throw new Error("DocmanaConfig.accessToken or DocmanaConfig.getAccessToken is required");
|
|
21
|
+
}
|
|
22
|
+
const apiBaseUrl = normalizeApiBaseUrl(config.apiBaseUrl);
|
|
23
|
+
return {
|
|
24
|
+
apiBaseUrl,
|
|
25
|
+
accessToken: config.accessToken,
|
|
26
|
+
getAccessToken: config.getAccessToken,
|
|
27
|
+
timeoutMs: config.timeoutMs ?? DEFAULTS.timeoutMs,
|
|
28
|
+
pollIntervalMs: config.pollIntervalMs ?? DEFAULTS.pollIntervalMs,
|
|
29
|
+
pollMaxIntervalMs: config.pollMaxIntervalMs ?? DEFAULTS.pollMaxIntervalMs,
|
|
30
|
+
fetchImpl: config.fetch ?? fetch,
|
|
31
|
+
headers: config.headers ?? {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function normalizeApiBaseUrl(value) {
|
|
35
|
+
const trimmed = value.replace(/\/+$/, "");
|
|
36
|
+
return /\/v\d+$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/sdk/errors.ts
|
|
12
40
|
var DocmanaError = class extends Error {
|
|
13
41
|
status;
|
|
14
42
|
requestId;
|
|
@@ -61,7 +89,14 @@ var DocmanaAbortError = class extends DocmanaError {
|
|
|
61
89
|
super(message, { code: "aborted" });
|
|
62
90
|
}
|
|
63
91
|
};
|
|
64
|
-
|
|
92
|
+
var DocmanaRateLimitError = class extends DocmanaError {
|
|
93
|
+
retryAfterMs;
|
|
94
|
+
constructor(message, opts = {}) {
|
|
95
|
+
super(message, { ...opts, code: "rate_limited" });
|
|
96
|
+
this.retryAfterMs = opts.retryAfterMs;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function errorFromResponse(status, message, requestId, retryAfterMs) {
|
|
65
100
|
const opts = { status, requestId };
|
|
66
101
|
switch (status) {
|
|
67
102
|
case 400:
|
|
@@ -72,118 +107,14 @@ function errorFromResponse(status, message, requestId) {
|
|
|
72
107
|
return new DocmanaPermissionError(message, opts);
|
|
73
108
|
case 404:
|
|
74
109
|
return new DocmanaNotFoundError(message, opts);
|
|
110
|
+
case 429:
|
|
111
|
+
return new DocmanaRateLimitError(message, { ...opts, retryAfterMs });
|
|
75
112
|
default:
|
|
76
113
|
return new DocmanaError(message, opts);
|
|
77
114
|
}
|
|
78
115
|
}
|
|
79
116
|
|
|
80
|
-
// src/
|
|
81
|
-
var SAFETY_MARGIN_MS = 6e4;
|
|
82
|
-
var TokenManager = class {
|
|
83
|
-
constructor(opts) {
|
|
84
|
-
this.opts = opts;
|
|
85
|
-
}
|
|
86
|
-
opts;
|
|
87
|
-
token = null;
|
|
88
|
-
expiresAt = 0;
|
|
89
|
-
inflight = null;
|
|
90
|
-
invalidate() {
|
|
91
|
-
this.token = null;
|
|
92
|
-
this.expiresAt = 0;
|
|
93
|
-
void this.opts.tokenCache?.clear().catch(() => void 0);
|
|
94
|
-
}
|
|
95
|
-
async getToken() {
|
|
96
|
-
if (this.token && Date.now() < this.expiresAt) return this.token;
|
|
97
|
-
const cached = await this.readCachedToken();
|
|
98
|
-
if (cached) return cached;
|
|
99
|
-
if (this.inflight) return this.inflight;
|
|
100
|
-
this.inflight = this.acquire().finally(() => {
|
|
101
|
-
this.inflight = null;
|
|
102
|
-
});
|
|
103
|
-
return this.inflight;
|
|
104
|
-
}
|
|
105
|
-
async acquire() {
|
|
106
|
-
const body = new URLSearchParams({
|
|
107
|
-
grant_type: "client_credentials",
|
|
108
|
-
client_id: this.opts.clientId,
|
|
109
|
-
client_secret: this.opts.clientSecret,
|
|
110
|
-
scope: this.opts.scope
|
|
111
|
-
});
|
|
112
|
-
const res = await this.opts.fetchImpl(this.opts.tokenEndpoint, {
|
|
113
|
-
method: "POST",
|
|
114
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
115
|
-
body: body.toString()
|
|
116
|
-
});
|
|
117
|
-
if (!res.ok) {
|
|
118
|
-
throw new DocmanaAuthError("Failed to acquire Docmana access token", { status: res.status });
|
|
119
|
-
}
|
|
120
|
-
const json = await res.json();
|
|
121
|
-
if (typeof json.access_token !== "string" || json.access_token.length === 0 || typeof json.expires_in !== "number" || !Number.isFinite(json.expires_in)) {
|
|
122
|
-
throw new DocmanaAuthError("Malformed token response from Docmana CIAM", {
|
|
123
|
-
status: res.status
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
this.token = json.access_token;
|
|
127
|
-
this.expiresAt = Date.now() + json.expires_in * 1e3 - SAFETY_MARGIN_MS;
|
|
128
|
-
await this.writeCachedToken().catch(() => void 0);
|
|
129
|
-
return this.token;
|
|
130
|
-
}
|
|
131
|
-
async readCachedToken() {
|
|
132
|
-
if (!this.opts.tokenCache) return null;
|
|
133
|
-
let entry;
|
|
134
|
-
try {
|
|
135
|
-
entry = await this.opts.tokenCache.read();
|
|
136
|
-
} catch {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
if (!entry) return null;
|
|
140
|
-
if (entry.clientId !== this.opts.clientId || entry.tokenEndpoint !== this.opts.tokenEndpoint || entry.scope !== this.opts.scope || Date.now() >= entry.expiresAt) {
|
|
141
|
-
await this.opts.tokenCache.clear().catch(() => void 0);
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
this.token = entry.accessToken;
|
|
145
|
-
this.expiresAt = entry.expiresAt;
|
|
146
|
-
return this.token;
|
|
147
|
-
}
|
|
148
|
-
async writeCachedToken() {
|
|
149
|
-
if (!this.opts.tokenCache || !this.token) return;
|
|
150
|
-
await this.opts.tokenCache.write({
|
|
151
|
-
accessToken: this.token,
|
|
152
|
-
expiresAt: this.expiresAt,
|
|
153
|
-
clientId: this.opts.clientId,
|
|
154
|
-
tokenEndpoint: this.opts.tokenEndpoint,
|
|
155
|
-
scope: this.opts.scope
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// src/config.ts
|
|
161
|
-
var DEFAULTS = {
|
|
162
|
-
apiBaseUrl: "https://api.docmana.ai",
|
|
163
|
-
tokenEndpoint: "https://4fe70f5b-e013-4f65-9fa7-3109a33beba5.ciamlogin.com/4fe70f5b-e013-4f65-9fa7-3109a33beba5/oauth2/v2.0/token",
|
|
164
|
-
scope: "api://d781e6ba-cc08-4618-8099-ad968abd2b9e/.default",
|
|
165
|
-
timeoutMs: 3e5,
|
|
166
|
-
pollIntervalMs: 2e3
|
|
167
|
-
};
|
|
168
|
-
function resolveConfig(config) {
|
|
169
|
-
if (!config.clientId) throw new Error("DocmanaConfig.clientId is required");
|
|
170
|
-
if (!config.clientSecret) throw new Error("DocmanaConfig.clientSecret is required");
|
|
171
|
-
const apiBaseUrl = (config.apiBaseUrl ?? DEFAULTS.apiBaseUrl).replace(/\/+$/, "");
|
|
172
|
-
return {
|
|
173
|
-
clientId: config.clientId,
|
|
174
|
-
clientSecret: config.clientSecret,
|
|
175
|
-
apiBaseUrl,
|
|
176
|
-
tokenEndpoint: config.tokenEndpoint ?? DEFAULTS.tokenEndpoint,
|
|
177
|
-
scope: config.scope ?? DEFAULTS.scope,
|
|
178
|
-
timeoutMs: config.timeoutMs ?? DEFAULTS.timeoutMs,
|
|
179
|
-
pollIntervalMs: config.pollIntervalMs ?? DEFAULTS.pollIntervalMs,
|
|
180
|
-
fetchImpl: config.fetch ?? fetch,
|
|
181
|
-
headers: config.headers ?? {},
|
|
182
|
-
tokenCache: config.tokenCache
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// src/http/http-client.ts
|
|
117
|
+
// src/sdk/http/http-client.ts
|
|
187
118
|
var HttpClient = class {
|
|
188
119
|
constructor(opts) {
|
|
189
120
|
this.opts = opts;
|
|
@@ -199,21 +130,29 @@ var HttpClient = class {
|
|
|
199
130
|
throw new DocmanaError("Invalid JSON response from Docmana", { status: res.status });
|
|
200
131
|
}
|
|
201
132
|
}
|
|
133
|
+
async requestBytes(method, path, options = {}) {
|
|
134
|
+
const res = await this.requestRaw(method, path, options);
|
|
135
|
+
return { bytes: new Uint8Array(await res.arrayBuffer()), headers: res.headers };
|
|
136
|
+
}
|
|
202
137
|
async requestRaw(method, path, options = {}) {
|
|
203
138
|
let res = await this.send(method, path, options);
|
|
204
|
-
if (res.status === 401) {
|
|
205
|
-
this.
|
|
206
|
-
res = await this.send(method, path, options);
|
|
139
|
+
if (res.status === 401 && this.opts.getAccessToken) {
|
|
140
|
+
res = await this.send(method, path, options, true);
|
|
207
141
|
}
|
|
208
142
|
if (!res.ok) {
|
|
209
143
|
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
210
144
|
const message = await this.extractMessage(res);
|
|
211
|
-
throw errorFromResponse(
|
|
145
|
+
throw errorFromResponse(
|
|
146
|
+
res.status,
|
|
147
|
+
message,
|
|
148
|
+
requestId,
|
|
149
|
+
parseRetryAfterMs(res.headers.get("retry-after"))
|
|
150
|
+
);
|
|
212
151
|
}
|
|
213
152
|
return res;
|
|
214
153
|
}
|
|
215
|
-
async send(method, path, options) {
|
|
216
|
-
const token = await this.
|
|
154
|
+
async send(method, path, options, forceRefresh = false) {
|
|
155
|
+
const token = await this.resolveToken(forceRefresh);
|
|
217
156
|
const url = new URL(this.opts.apiBaseUrl + path);
|
|
218
157
|
for (const [k, v] of Object.entries(options.query ?? {})) url.searchParams.set(k, v);
|
|
219
158
|
const headers = {
|
|
@@ -235,6 +174,13 @@ var HttpClient = class {
|
|
|
235
174
|
throw err;
|
|
236
175
|
}
|
|
237
176
|
}
|
|
177
|
+
async resolveToken(forceRefresh) {
|
|
178
|
+
const token = this.opts.getAccessToken ? await this.opts.getAccessToken(forceRefresh ? { forceRefresh: true } : void 0) : this.opts.accessToken;
|
|
179
|
+
if (!token) {
|
|
180
|
+
throw new DocmanaError("Missing Docmana access token", { code: "auth_missing" });
|
|
181
|
+
}
|
|
182
|
+
return token;
|
|
183
|
+
}
|
|
238
184
|
async extractMessage(res) {
|
|
239
185
|
try {
|
|
240
186
|
const data = await res.clone().json();
|
|
@@ -244,8 +190,20 @@ var HttpClient = class {
|
|
|
244
190
|
}
|
|
245
191
|
}
|
|
246
192
|
};
|
|
193
|
+
function parseRetryAfterMs(value) {
|
|
194
|
+
if (!value) return void 0;
|
|
195
|
+
const seconds = Number(value);
|
|
196
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
197
|
+
return seconds * 1e3;
|
|
198
|
+
}
|
|
199
|
+
const retryAt = Date.parse(value);
|
|
200
|
+
if (!Number.isNaN(retryAt)) {
|
|
201
|
+
return Math.max(0, retryAt - Date.now());
|
|
202
|
+
}
|
|
203
|
+
return void 0;
|
|
204
|
+
}
|
|
247
205
|
|
|
248
|
-
// src/files/resolve-input.ts
|
|
206
|
+
// src/sdk/files/resolve-input.ts
|
|
249
207
|
import { readFile } from "fs/promises";
|
|
250
208
|
import { basename } from "path";
|
|
251
209
|
var CONTENT_TYPES = {
|
|
@@ -270,310 +228,310 @@ function isReadable(x) {
|
|
|
270
228
|
function isPathInput(x) {
|
|
271
229
|
return typeof x === "object" && x !== null && typeof x.path === "string";
|
|
272
230
|
}
|
|
231
|
+
function isNamedFileInput(x) {
|
|
232
|
+
if (typeof x !== "object" || x === null) return false;
|
|
233
|
+
const value = x;
|
|
234
|
+
return typeof value.filename === "string" && (value.data instanceof Uint8Array || isReadable(value.data));
|
|
235
|
+
}
|
|
273
236
|
async function streamToBuffer(stream) {
|
|
274
237
|
const chunks = [];
|
|
275
238
|
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
276
239
|
return Buffer.concat(chunks);
|
|
277
240
|
}
|
|
278
|
-
|
|
241
|
+
function toBlobBytes(bytes) {
|
|
242
|
+
return new Uint8Array(bytes);
|
|
243
|
+
}
|
|
244
|
+
async function resolveOne(input) {
|
|
279
245
|
if (typeof input === "string" || isPathInput(input)) {
|
|
280
246
|
const path = typeof input === "string" ? input : input.path;
|
|
281
247
|
const data = await readFile(path);
|
|
282
248
|
const filename = basename(path);
|
|
283
|
-
return { data: new Blob([data]), filename, contentType: contentTypeFor(filename) };
|
|
284
|
-
}
|
|
285
|
-
if (input instanceof Uint8Array) {
|
|
286
|
-
return {
|
|
287
|
-
data: new Blob([input]),
|
|
288
|
-
filename: fallbackName,
|
|
289
|
-
contentType: contentTypeFor(fallbackName)
|
|
290
|
-
};
|
|
249
|
+
return { data: new Blob([toBlobBytes(data)]), filename, contentType: contentTypeFor(filename) };
|
|
291
250
|
}
|
|
292
|
-
if (
|
|
293
|
-
const
|
|
251
|
+
if (isNamedFileInput(input)) {
|
|
252
|
+
const bytes = input.data instanceof Uint8Array ? input.data : await streamToBuffer(input.data);
|
|
294
253
|
return {
|
|
295
|
-
data: new Blob([
|
|
296
|
-
filename:
|
|
297
|
-
contentType: contentTypeFor(
|
|
254
|
+
data: new Blob([toBlobBytes(bytes)]),
|
|
255
|
+
filename: input.filename,
|
|
256
|
+
contentType: input.contentType ?? contentTypeFor(input.filename)
|
|
298
257
|
};
|
|
299
258
|
}
|
|
300
259
|
throw new Error("Unsupported file input type");
|
|
301
260
|
}
|
|
302
261
|
async function resolveInputs(input) {
|
|
303
|
-
const list = input.files
|
|
304
|
-
if (list.length === 0) throw new Error("runFlow requires `
|
|
305
|
-
|
|
306
|
-
return Promise.all(list.map((item) => resolveOne(item, fallback)));
|
|
262
|
+
const list = input.files;
|
|
263
|
+
if (list.length === 0) throw new Error("runFlow requires `files`");
|
|
264
|
+
return Promise.all(list.map((item) => resolveOne(item)));
|
|
307
265
|
}
|
|
308
266
|
|
|
309
|
-
// src/api/
|
|
310
|
-
async function
|
|
267
|
+
// src/sdk/api/run-flow.ts
|
|
268
|
+
async function runFlow(http, flowId, parts, signal, useDraftVersion = false, context, callbackUrl, language) {
|
|
311
269
|
const form = new FormData();
|
|
312
270
|
for (const part of parts) {
|
|
313
271
|
form.append("files", new File([part.data], part.filename, { type: part.contentType }));
|
|
314
272
|
}
|
|
315
|
-
|
|
316
|
-
|
|
273
|
+
if (context !== void 0) form.append("json_context", JSON.stringify(context));
|
|
274
|
+
if (callbackUrl !== void 0) form.append("callback_url", callbackUrl);
|
|
275
|
+
if (language !== void 0) form.append("language", language);
|
|
276
|
+
if (useDraftVersion) form.append("useDraftVersion", "true");
|
|
277
|
+
return http.requestJson(
|
|
278
|
+
"POST",
|
|
279
|
+
`/flows/${flowId}/execute-async`,
|
|
280
|
+
{ body: form, signal }
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/sdk/api/execution-status.ts
|
|
285
|
+
async function getStatus(http, executionResultId, signal) {
|
|
286
|
+
const res = await http.requestRaw("GET", `/executions/${executionResultId}/status`, { signal });
|
|
287
|
+
const text = await res.text();
|
|
288
|
+
const body = text ? JSON.parse(text) : { status: "" };
|
|
289
|
+
const pollAfterMs = parsePositiveInt(res.headers.get("x-poll-after"));
|
|
290
|
+
return pollAfterMs === void 0 ? body : { ...body, pollAfterMs };
|
|
291
|
+
}
|
|
292
|
+
function parsePositiveInt(value) {
|
|
293
|
+
if (!value) return void 0;
|
|
294
|
+
const parsed = Number(value);
|
|
295
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
317
296
|
}
|
|
318
297
|
|
|
319
|
-
// src/api/
|
|
320
|
-
async function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (context !== void 0) body.json_context = context;
|
|
324
|
-
if (callbackUrl !== void 0) body.callback_url = callbackUrl;
|
|
325
|
-
const res = await http.requestJson("POST", path, {
|
|
326
|
-
query: { async_mode: "true" },
|
|
327
|
-
headers: { "content-type": "application/json" },
|
|
328
|
-
body: JSON.stringify(body),
|
|
298
|
+
// src/sdk/api/execution-result.ts
|
|
299
|
+
async function getResult(http, executionResultId, signal, language) {
|
|
300
|
+
return http.requestJson("GET", `/executions/${executionResultId}/result`, {
|
|
301
|
+
query: language ? { language } : void 0,
|
|
329
302
|
signal
|
|
330
303
|
});
|
|
331
|
-
return res.execution_result_id;
|
|
332
304
|
}
|
|
333
305
|
|
|
334
|
-
// src/api/
|
|
335
|
-
async function
|
|
336
|
-
|
|
306
|
+
// src/sdk/api/list-flows.ts
|
|
307
|
+
async function listFlows(http) {
|
|
308
|
+
const flows = await http.requestJson("GET", "/flows");
|
|
309
|
+
return Array.isArray(flows) ? flows.map(toFlowListItem) : [];
|
|
310
|
+
}
|
|
311
|
+
function toFlowListItem(flow) {
|
|
312
|
+
const value = flow && typeof flow === "object" ? flow : {};
|
|
313
|
+
return {
|
|
314
|
+
id: toStringValue(value.id),
|
|
315
|
+
name: toStringValue(value.name),
|
|
316
|
+
state: toStateValue(value.state)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function toStringValue(value) {
|
|
320
|
+
return typeof value === "string" ? value : "";
|
|
321
|
+
}
|
|
322
|
+
function toStateValue(value) {
|
|
323
|
+
return typeof value === "string" && value.trim() ? value : "unknown";
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/sdk/api/list-organizations.ts
|
|
327
|
+
async function listOrganizations(http) {
|
|
328
|
+
const organizations = await http.requestJson("GET", "/organizations");
|
|
329
|
+
return Array.isArray(organizations) ? organizations.map(toAccessibleOrganization).filter(isAccessibleOrganization) : [];
|
|
330
|
+
}
|
|
331
|
+
function toAccessibleOrganization(organization) {
|
|
332
|
+
const value = organization && typeof organization === "object" ? organization : {};
|
|
333
|
+
const id = toStringValue2(value.id).trim();
|
|
334
|
+
if (!id) return void 0;
|
|
335
|
+
const name = toStringValue2(value.name).trim() || id;
|
|
336
|
+
return {
|
|
337
|
+
id,
|
|
338
|
+
name
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function isAccessibleOrganization(organization) {
|
|
342
|
+
return organization !== void 0;
|
|
343
|
+
}
|
|
344
|
+
function toStringValue2(value) {
|
|
345
|
+
return typeof value === "string" ? value : "";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/sdk/api/flow-metadata.ts
|
|
349
|
+
async function getFlowMetadata(http, flowId) {
|
|
350
|
+
return http.requestJson("GET", `/flows/${encodeURIComponent(flowId)}/metadata`);
|
|
337
351
|
}
|
|
338
352
|
|
|
339
|
-
// src/api/
|
|
340
|
-
async function
|
|
341
|
-
return http.requestJson("GET", `/flows
|
|
353
|
+
// src/sdk/api/flow-definition.ts
|
|
354
|
+
async function getFlowDefinition(http, flowId) {
|
|
355
|
+
return http.requestJson("GET", `/flows/${encodeURIComponent(flowId)}/definition`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/sdk/api/organization-documents.ts
|
|
359
|
+
async function listOrganizationDocuments(http, options = {}) {
|
|
360
|
+
const result = await http.requestJson("GET", "/organization-documents", {
|
|
361
|
+
query: options.prefix ? { prefix: options.prefix } : void 0
|
|
362
|
+
});
|
|
363
|
+
const value = result && typeof result === "object" ? result : {};
|
|
364
|
+
return {
|
|
365
|
+
configured: value.configured === true,
|
|
366
|
+
documents: Array.isArray(value.documents) ? value.documents.map(toOrganizationDocument).filter(isOrganizationDocument) : []
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
async function downloadOrganizationDocument(http, path) {
|
|
370
|
+
const result = await http.requestBytes("GET", "/organization-documents/download", {
|
|
371
|
+
query: { path }
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
bytes: result.bytes,
|
|
375
|
+
filename: filenameFromHeaders(result.headers) ?? filenameFromPath(path),
|
|
376
|
+
contentType: result.headers.get("content-type") || void 0
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
async function downloadOrganizationDocuments(http, paths) {
|
|
380
|
+
return Promise.all(paths.map((path) => downloadOrganizationDocument(http, path)));
|
|
381
|
+
}
|
|
382
|
+
function toOrganizationDocument(document) {
|
|
383
|
+
const value = document && typeof document === "object" ? document : {};
|
|
384
|
+
const path = toStringValue3(value.path).trim();
|
|
385
|
+
if (!path) return void 0;
|
|
386
|
+
const contentType = toStringValue3(value.contentType).trim();
|
|
387
|
+
return {
|
|
388
|
+
path,
|
|
389
|
+
name: toStringValue3(value.name).trim() || filenameFromPath(path),
|
|
390
|
+
size: toNumberValue(value.size),
|
|
391
|
+
lastModified: toStringValue3(value.lastModified).trim(),
|
|
392
|
+
...contentType ? { contentType } : {}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function isOrganizationDocument(document) {
|
|
396
|
+
return document !== void 0;
|
|
397
|
+
}
|
|
398
|
+
function filenameFromHeaders(headers) {
|
|
399
|
+
const disposition = headers.get("content-disposition");
|
|
400
|
+
if (!disposition) return void 0;
|
|
401
|
+
const utf8 = /filename\*=UTF-8''([^;]+)/i.exec(disposition);
|
|
402
|
+
if (utf8?.[1]) return decodeURIComponent(utf8[1]);
|
|
403
|
+
const quoted = /filename="([^"]+)"/i.exec(disposition);
|
|
404
|
+
if (quoted?.[1]) return quoted[1];
|
|
405
|
+
const plain = /filename=([^;]+)/i.exec(disposition);
|
|
406
|
+
return plain?.[1]?.trim();
|
|
407
|
+
}
|
|
408
|
+
function filenameFromPath(path) {
|
|
409
|
+
return path.replace(/\\/g, "/").split("/").filter(Boolean).pop() || path;
|
|
410
|
+
}
|
|
411
|
+
function toStringValue3(value) {
|
|
412
|
+
return typeof value === "string" ? value : "";
|
|
413
|
+
}
|
|
414
|
+
function toNumberValue(value) {
|
|
415
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
342
416
|
}
|
|
343
417
|
|
|
344
|
-
// src/polling/poll.ts
|
|
418
|
+
// src/sdk/polling/poll.ts
|
|
345
419
|
var TERMINAL = /* @__PURE__ */ new Set(["Completed", "Failed"]);
|
|
346
420
|
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
347
421
|
async function pollUntilTerminal(opts) {
|
|
348
422
|
const sleep = opts.sleep ?? defaultSleep;
|
|
349
423
|
const start = Date.now();
|
|
350
424
|
const elapsed = opts.elapsed ?? (() => Date.now() - start);
|
|
425
|
+
const random = opts.random ?? Math.random;
|
|
426
|
+
let fallbackAttempt = 0;
|
|
351
427
|
for (; ; ) {
|
|
352
428
|
if (opts.signal?.aborted) throw new DocmanaAbortError("Polling aborted");
|
|
353
|
-
|
|
429
|
+
let result;
|
|
430
|
+
try {
|
|
431
|
+
result = normalizePollResult(await opts.check());
|
|
432
|
+
} catch (err) {
|
|
433
|
+
if (!(err instanceof DocmanaRateLimitError)) throw err;
|
|
434
|
+
if (elapsed() >= opts.timeoutMs) {
|
|
435
|
+
throw new DocmanaTimeoutError(`Execution did not finish within ${opts.timeoutMs}ms`);
|
|
436
|
+
}
|
|
437
|
+
await sleep(err.retryAfterMs ?? opts.intervalMs);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const { status } = result;
|
|
354
441
|
if (TERMINAL.has(status)) return status;
|
|
355
442
|
if (elapsed() >= opts.timeoutMs) {
|
|
356
443
|
throw new DocmanaTimeoutError(`Execution did not finish within ${opts.timeoutMs}ms`);
|
|
357
444
|
}
|
|
358
|
-
|
|
445
|
+
const fallbackDelay = fullJitterDelay(
|
|
446
|
+
opts.intervalMs,
|
|
447
|
+
opts.maxIntervalMs,
|
|
448
|
+
fallbackAttempt,
|
|
449
|
+
random
|
|
450
|
+
);
|
|
451
|
+
fallbackAttempt += 1;
|
|
452
|
+
await sleep(result.nextDelayMs ?? fallbackDelay);
|
|
359
453
|
}
|
|
360
454
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
function extractNodeFields(rawNode, targetLanguage) {
|
|
364
|
-
const metadataFields = /* @__PURE__ */ new Set([
|
|
365
|
-
"node_id",
|
|
366
|
-
"node_name",
|
|
367
|
-
"node_type",
|
|
368
|
-
"feature_name",
|
|
369
|
-
"running_time",
|
|
370
|
-
"status",
|
|
371
|
-
"score",
|
|
372
|
-
"feedback",
|
|
373
|
-
"translations",
|
|
374
|
-
"tokens",
|
|
375
|
-
"input_tokens",
|
|
376
|
-
"output_tokens",
|
|
377
|
-
"errors",
|
|
378
|
-
"mana",
|
|
379
|
-
"model",
|
|
380
|
-
"tool_calls",
|
|
381
|
-
"prompt_id",
|
|
382
|
-
"prompt",
|
|
383
|
-
"prompt_name",
|
|
384
|
-
"label",
|
|
385
|
-
"scoreDescription",
|
|
386
|
-
"score_description",
|
|
387
|
-
"result",
|
|
388
|
-
"documentType"
|
|
389
|
-
]);
|
|
390
|
-
let sourceObj = rawNode;
|
|
391
|
-
if (targetLanguage) {
|
|
392
|
-
const translations = rawNode.translations;
|
|
393
|
-
if (!translations || !translations[targetLanguage]) {
|
|
394
|
-
throw new DocmanaError(
|
|
395
|
-
`Translation for language '${targetLanguage}' is missing on node '${rawNode.node_name || rawNode.node_id}'`,
|
|
396
|
-
{ code: "translation_missing", status: 400 }
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
sourceObj = translations[targetLanguage];
|
|
400
|
-
}
|
|
401
|
-
const dynamicFields = {};
|
|
402
|
-
for (const [key, value] of Object.entries(sourceObj)) {
|
|
403
|
-
if (!metadataFields.has(key)) {
|
|
404
|
-
dynamicFields[key] = value;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
const sdkNode = {
|
|
408
|
-
status: rawNode.status || "Unknown"
|
|
409
|
-
};
|
|
410
|
-
if (rawNode.errors && rawNode.errors.length > 0) {
|
|
411
|
-
sdkNode.errors = rawNode.errors;
|
|
412
|
-
}
|
|
413
|
-
const nodeType = rawNode.node_type || "";
|
|
414
|
-
const isScoreNode = /^(Validation|Classification|CrossValidation|Conclusion|Finish)$/i.test(
|
|
415
|
-
nodeType
|
|
416
|
-
);
|
|
417
|
-
if (isScoreNode && rawNode.score !== void 0 && rawNode.score !== null) {
|
|
418
|
-
sdkNode.score = rawNode.score;
|
|
419
|
-
}
|
|
420
|
-
const feedback = sourceObj.feedback ?? rawNode.feedback;
|
|
421
|
-
if (feedback !== void 0 && feedback !== null) {
|
|
422
|
-
sdkNode.feedback = feedback;
|
|
423
|
-
}
|
|
424
|
-
if (Object.keys(dynamicFields).length > 0) {
|
|
425
|
-
sdkNode.data = dynamicFields;
|
|
426
|
-
}
|
|
427
|
-
return sdkNode;
|
|
455
|
+
function normalizePollResult(result) {
|
|
456
|
+
return typeof result === "string" ? { status: result } : result;
|
|
428
457
|
}
|
|
429
|
-
function
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
executionId: String(raw.execution_id || raw.executionResultId || raw.execution_result_id || ""),
|
|
433
|
-
documents: {
|
|
434
|
-
mappings: []
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
if (raw.errors && raw.errors.length > 0) {
|
|
438
|
-
sdkResult.errors = raw.errors;
|
|
439
|
-
}
|
|
440
|
-
const results = raw.results || [];
|
|
441
|
-
for (const item of results) {
|
|
442
|
-
const isDocSpecific = !!item.document;
|
|
443
|
-
if (isDocSpecific && item.document) {
|
|
444
|
-
const docReq = item.document;
|
|
445
|
-
const docKey = docReq.doc_key || "unknown_doc";
|
|
446
|
-
const docName = docReq.name || "";
|
|
447
|
-
sdkResult.documents.mappings.push({
|
|
448
|
-
name: docName,
|
|
449
|
-
reference: docKey
|
|
450
|
-
});
|
|
451
|
-
const docResult = {
|
|
452
|
-
status: item.status || docReq.status || "Unknown",
|
|
453
|
-
extractions: {},
|
|
454
|
-
validations: {}
|
|
455
|
-
};
|
|
456
|
-
const nodeResults = item.node_results || [];
|
|
457
|
-
for (const node of nodeResults) {
|
|
458
|
-
const nodeType = node.node_type || "";
|
|
459
|
-
const nodeName = node.node_name || node.node_id || "unknown_node";
|
|
460
|
-
if (/^Classification$/i.test(nodeType)) {
|
|
461
|
-
const mappedNode = extractNodeFields(node, targetLanguage);
|
|
462
|
-
if (!docResult.classifications) {
|
|
463
|
-
docResult.classifications = {};
|
|
464
|
-
}
|
|
465
|
-
docResult.classifications[nodeName] = mappedNode;
|
|
466
|
-
} else if (/^MetadataExtraction$/i.test(nodeType)) {
|
|
467
|
-
docResult.metadataExtraction = extractNodeFields(node, targetLanguage);
|
|
468
|
-
} else if (/^Extraction$/i.test(nodeType)) {
|
|
469
|
-
if (docResult.extractions) {
|
|
470
|
-
docResult.extractions[nodeName] = extractNodeFields(node, targetLanguage);
|
|
471
|
-
}
|
|
472
|
-
} else if (/^Validation$/i.test(nodeType)) {
|
|
473
|
-
if (docResult.validations) {
|
|
474
|
-
docResult.validations[nodeName] = extractNodeFields(node, targetLanguage);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
if (docResult.extractions && Object.keys(docResult.extractions).length === 0) {
|
|
479
|
-
delete docResult.extractions;
|
|
480
|
-
}
|
|
481
|
-
if (docResult.validations && Object.keys(docResult.validations).length === 0) {
|
|
482
|
-
delete docResult.validations;
|
|
483
|
-
}
|
|
484
|
-
sdkResult.documents[docKey] = docResult;
|
|
485
|
-
} else {
|
|
486
|
-
const nodeResults = item.node_results || [];
|
|
487
|
-
for (const node of nodeResults) {
|
|
488
|
-
const nodeType = node.node_type || "";
|
|
489
|
-
const nodeName = node.node_name || node.node_id || "unknown_node";
|
|
490
|
-
if (/^CrossValidation$/i.test(nodeType)) {
|
|
491
|
-
if (!sdkResult.crossValidations) {
|
|
492
|
-
sdkResult.crossValidations = {};
|
|
493
|
-
}
|
|
494
|
-
sdkResult.crossValidations[nodeName] = extractNodeFields(
|
|
495
|
-
node,
|
|
496
|
-
targetLanguage
|
|
497
|
-
);
|
|
498
|
-
} else if (/^(Conclusion|Finish)$/i.test(nodeType)) {
|
|
499
|
-
if (!sdkResult.conclusions) {
|
|
500
|
-
sdkResult.conclusions = {};
|
|
501
|
-
}
|
|
502
|
-
sdkResult.conclusions[nodeName] = extractNodeFields(
|
|
503
|
-
node,
|
|
504
|
-
targetLanguage
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
return sdkResult;
|
|
458
|
+
function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
|
|
459
|
+
const capDelay = Math.min(maxIntervalMs, intervalMs * 2 ** attempt);
|
|
460
|
+
return Math.round(intervalMs + random() * (capDelay - intervalMs));
|
|
511
461
|
}
|
|
512
462
|
|
|
513
|
-
// src/client.ts
|
|
463
|
+
// src/sdk/client.ts
|
|
514
464
|
var Docmana = class {
|
|
515
465
|
config;
|
|
516
466
|
http;
|
|
517
467
|
constructor(config) {
|
|
518
468
|
this.config = resolveConfig(config);
|
|
519
|
-
const tokenManager = new TokenManager({
|
|
520
|
-
clientId: this.config.clientId,
|
|
521
|
-
clientSecret: this.config.clientSecret,
|
|
522
|
-
tokenEndpoint: this.config.tokenEndpoint,
|
|
523
|
-
scope: this.config.scope,
|
|
524
|
-
fetchImpl: this.config.fetchImpl,
|
|
525
|
-
tokenCache: this.config.tokenCache
|
|
526
|
-
});
|
|
527
469
|
this.http = new HttpClient({
|
|
528
470
|
apiBaseUrl: this.config.apiBaseUrl,
|
|
529
471
|
fetchImpl: this.config.fetchImpl,
|
|
530
|
-
|
|
472
|
+
accessToken: this.config.accessToken,
|
|
473
|
+
getAccessToken: this.config.getAccessToken,
|
|
531
474
|
headers: this.config.headers
|
|
532
475
|
});
|
|
533
476
|
}
|
|
534
|
-
async runFlowAsync(flowId,
|
|
535
|
-
const parts = await resolveInputs(
|
|
536
|
-
|
|
537
|
-
const executionResultId = await runFlow(
|
|
538
|
-
this.http,
|
|
539
|
-
flowId,
|
|
540
|
-
fileIds,
|
|
541
|
-
input.signal,
|
|
542
|
-
input.once ?? false,
|
|
543
|
-
input.context
|
|
544
|
-
);
|
|
545
|
-
return { executionResultId, fileIds };
|
|
546
|
-
}
|
|
547
|
-
async runFlowWithCallback(flowId, input) {
|
|
548
|
-
const parts = await resolveInputs(input);
|
|
549
|
-
const fileIds = await uploadFiles(this.http, parts, input.signal);
|
|
550
|
-
const executionResultId = await runFlow(
|
|
477
|
+
async runFlowAsync(flowId, params, options = {}) {
|
|
478
|
+
const parts = await resolveInputs(params);
|
|
479
|
+
return runFlow(
|
|
551
480
|
this.http,
|
|
552
481
|
flowId,
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
482
|
+
parts,
|
|
483
|
+
options.signal,
|
|
484
|
+
params.useDraftVersion ?? false,
|
|
485
|
+
params.context,
|
|
486
|
+
params.callbackUrl,
|
|
487
|
+
params.language
|
|
558
488
|
);
|
|
559
|
-
return { executionResultId, fileIds };
|
|
560
489
|
}
|
|
561
490
|
async getStatus(executionResultId, signal) {
|
|
562
491
|
return getStatus(this.http, executionResultId, signal);
|
|
563
492
|
}
|
|
564
493
|
async getResult(executionResultId, signal, language) {
|
|
565
|
-
|
|
566
|
-
return mapExecutionResult(rawResult, language);
|
|
494
|
+
return getResult(this.http, executionResultId, signal, language);
|
|
567
495
|
}
|
|
568
|
-
async runFlow(flowId,
|
|
569
|
-
const { executionResultId } = await this.runFlowAsync(flowId,
|
|
496
|
+
async runFlow(flowId, params, options = {}) {
|
|
497
|
+
const { executionResultId } = await this.runFlowAsync(flowId, params, {
|
|
498
|
+
signal: options.signal
|
|
499
|
+
});
|
|
500
|
+
let attempt = 0;
|
|
501
|
+
const startedAt = Date.now();
|
|
570
502
|
await pollUntilTerminal({
|
|
571
|
-
check: async () =>
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
503
|
+
check: async () => {
|
|
504
|
+
attempt += 1;
|
|
505
|
+
try {
|
|
506
|
+
const statusResult = await this.getStatus(executionResultId, options.signal);
|
|
507
|
+
const status = statusResult.status;
|
|
508
|
+
const nextPollAfterMs = this.isTerminalStatus(status) ? void 0 : statusResult.pollAfterMs;
|
|
509
|
+
options.onPoll?.({
|
|
510
|
+
executionResultId,
|
|
511
|
+
status,
|
|
512
|
+
attempt,
|
|
513
|
+
elapsedMs: Date.now() - startedAt,
|
|
514
|
+
nextPollAfterMs
|
|
515
|
+
});
|
|
516
|
+
return { status, nextDelayMs: nextPollAfterMs };
|
|
517
|
+
} catch (err) {
|
|
518
|
+
if (err instanceof DocmanaRateLimitError) {
|
|
519
|
+
options.onRateLimit?.({
|
|
520
|
+
executionResultId,
|
|
521
|
+
attempt,
|
|
522
|
+
elapsedMs: Date.now() - startedAt,
|
|
523
|
+
retryAfterMs: err.retryAfterMs ?? options.pollIntervalMs ?? this.config.pollIntervalMs
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
throw err;
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
intervalMs: options.pollIntervalMs ?? this.config.pollIntervalMs,
|
|
530
|
+
maxIntervalMs: options.pollMaxIntervalMs ?? this.config.pollMaxIntervalMs,
|
|
531
|
+
timeoutMs: options.timeoutMs ?? this.config.timeoutMs,
|
|
532
|
+
signal: options.signal
|
|
575
533
|
});
|
|
576
|
-
const result = await this.getResult(executionResultId,
|
|
534
|
+
const result = await this.getResult(executionResultId, options.signal, params.language);
|
|
577
535
|
if (result.status === "Failed") {
|
|
578
536
|
throw new DocmanaExecutionError(
|
|
579
537
|
`Flow ${flowId} execution failed`,
|
|
@@ -583,9 +541,33 @@ var Docmana = class {
|
|
|
583
541
|
}
|
|
584
542
|
return result;
|
|
585
543
|
}
|
|
544
|
+
async listFlows() {
|
|
545
|
+
return listFlows(this.http);
|
|
546
|
+
}
|
|
547
|
+
async listOrganizations() {
|
|
548
|
+
return listOrganizations(this.http);
|
|
549
|
+
}
|
|
550
|
+
async getFlowMetadata(flowId) {
|
|
551
|
+
return getFlowMetadata(this.http, flowId);
|
|
552
|
+
}
|
|
553
|
+
async getFlowDefinition(flowId) {
|
|
554
|
+
return getFlowDefinition(this.http, flowId);
|
|
555
|
+
}
|
|
556
|
+
async listOrganizationDocuments(options = {}) {
|
|
557
|
+
return listOrganizationDocuments(this.http, options);
|
|
558
|
+
}
|
|
559
|
+
async downloadOrganizationDocument(path) {
|
|
560
|
+
return downloadOrganizationDocument(this.http, path);
|
|
561
|
+
}
|
|
562
|
+
async downloadOrganizationDocuments(paths) {
|
|
563
|
+
return downloadOrganizationDocuments(this.http, paths);
|
|
564
|
+
}
|
|
565
|
+
isTerminalStatus(status) {
|
|
566
|
+
return status === "Completed" || status === "Failed";
|
|
567
|
+
}
|
|
586
568
|
};
|
|
587
569
|
|
|
588
|
-
// src/cli.ts
|
|
570
|
+
// src/cli/errors.ts
|
|
589
571
|
var CliUsageError = class extends Error {
|
|
590
572
|
constructor(message) {
|
|
591
573
|
super(message);
|
|
@@ -601,226 +583,212 @@ var CliSilentError = class extends Error {
|
|
|
601
583
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
602
584
|
}
|
|
603
585
|
};
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
async function runCli(argv = process.argv.slice(2), env = process.env, io = {
|
|
615
|
-
stdout: (text) => process.stdout.write(text),
|
|
616
|
-
stderr: (text) => process.stderr.write(text)
|
|
617
|
-
}, clientFactory = (config) => new Docmana(config), deps = {}) {
|
|
618
|
-
const program = buildProgram(env, io, clientFactory, deps);
|
|
619
|
-
if (argv.length === 0) {
|
|
620
|
-
io.stdout(program.helpInformation());
|
|
621
|
-
return 0;
|
|
586
|
+
|
|
587
|
+
// src/cli/formatting.ts
|
|
588
|
+
function formatFlowList(flows) {
|
|
589
|
+
if (flows.length === 0) return "No flows found.\n";
|
|
590
|
+
const lines = ["Docmana flows"];
|
|
591
|
+
for (const flow of flows) {
|
|
592
|
+
const id = flow.id || "(no id)";
|
|
593
|
+
const name = flow.name || "(no name)";
|
|
594
|
+
const state = flow.state ? ` [${flow.state}]` : "";
|
|
595
|
+
lines.push(`- ${id} - ${name}${state}`);
|
|
622
596
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
`);
|
|
633
|
-
return 2;
|
|
634
|
-
}
|
|
635
|
-
if (err instanceof CommanderError) {
|
|
636
|
-
return err.exitCode === 0 ? 0 : 2;
|
|
637
|
-
}
|
|
638
|
-
io.stderr(`${formatRuntimeError(err)}
|
|
639
|
-
`);
|
|
640
|
-
return 1;
|
|
597
|
+
return `${lines.join("\n")}
|
|
598
|
+
`;
|
|
599
|
+
}
|
|
600
|
+
function formatOrganizationList(organizations) {
|
|
601
|
+
if (organizations.length === 0) return "No organizations found.\n";
|
|
602
|
+
const lines = ["Docmana organizations"];
|
|
603
|
+
for (const organization of organizations) {
|
|
604
|
+
const id = organization.id || "(no id)";
|
|
605
|
+
const name = organization.name || "(no name)";
|
|
606
|
+
lines.push(`- ${id} - ${name}`);
|
|
641
607
|
}
|
|
608
|
+
return `${lines.join("\n")}
|
|
609
|
+
`;
|
|
642
610
|
}
|
|
643
|
-
function
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
});
|
|
653
|
-
const flow = program.command("flow").description(HELP.flow).action(() => {
|
|
654
|
-
io.stdout(flow.helpInformation());
|
|
655
|
-
});
|
|
656
|
-
flow.command("run").description(HELP.run).argument("<flow-id>", "Docmana flow id").requiredOption("--files <csv>", "Comma-separated file paths to upload").option("--client-id <id>", "OAuth2 client id; defaults to DOCMANA_CLIENT_ID").option("--client-secret <secret>", "OAuth2 client secret; defaults to DOCMANA_CLIENT_SECRET").option("--api-url <url>", "Docmana API base URL; defaults to DOCMANA_API_URL").option("--token-url <url>", "OAuth2 token endpoint; defaults to DOCMANA_TOKEN_URL").option("--scope <scope>", "OAuth2 scope; defaults to DOCMANA_SCOPE").option("--organisation <id>", "Docmana organisation id; defaults to DOCMANA_ORGANISATION").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).option("--json", "Print the raw JSON result").option("--language <lang>", "Translate output to specified language").action(async (flowId, options) => {
|
|
657
|
-
await runFlowCommand(flowId, options, env, io, clientFactory, deps);
|
|
658
|
-
});
|
|
659
|
-
const config = program.command("config").description(HELP.config).action(() => {
|
|
660
|
-
io.stdout(config.helpInformation());
|
|
661
|
-
});
|
|
662
|
-
config.command("init").description(HELP.configInit).option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).action(async (options) => {
|
|
663
|
-
await configInitCommand(options, io, deps);
|
|
664
|
-
});
|
|
665
|
-
return program;
|
|
611
|
+
function formatOrganizationDocumentList(documents) {
|
|
612
|
+
if (documents.length === 0) return "No organization documents found.\n";
|
|
613
|
+
const lines = ["Docmana organization documents"];
|
|
614
|
+
for (const document of documents) {
|
|
615
|
+
const contentType = document.contentType ? ` [${document.contentType}]` : "";
|
|
616
|
+
lines.push(`- ${document.path} - ${document.name} (${document.size} bytes)${contentType}`);
|
|
617
|
+
}
|
|
618
|
+
return `${lines.join("\n")}
|
|
619
|
+
`;
|
|
666
620
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const apiBaseUrl = firstNonEmpty(options.apiUrl, env.DOCMANA_API_URL, cliConfig.apiBaseUrl);
|
|
683
|
-
const tokenEndpoint = firstNonEmpty(
|
|
684
|
-
options.tokenUrl,
|
|
685
|
-
env.DOCMANA_TOKEN_URL,
|
|
686
|
-
cliConfig.tokenEndpoint
|
|
687
|
-
);
|
|
688
|
-
const scope = firstNonEmpty(options.scope, env.DOCMANA_SCOPE, cliConfig.scope);
|
|
689
|
-
if (!clientId)
|
|
690
|
-
throw new CliUsageError("Missing client id. Use --client-id or DOCMANA_CLIENT_ID.");
|
|
691
|
-
if (!clientSecret) {
|
|
692
|
-
throw new CliUsageError(
|
|
693
|
-
"Missing client secret. Use --client-secret or DOCMANA_CLIENT_SECRET."
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
-
if (!organisation) {
|
|
697
|
-
throw new CliUsageError("Missing organisation. Use --organisation or DOCMANA_ORGANISATION.");
|
|
621
|
+
function formatHumanResult(result) {
|
|
622
|
+
const lines = ["Docmana flow completed", `Status: ${String(result.status ?? "Unknown")}`];
|
|
623
|
+
const executionId = result.executionId;
|
|
624
|
+
if (executionId) lines.push(`Execution id: ${String(executionId)}`);
|
|
625
|
+
if (result.documents && result.documents.mappings) {
|
|
626
|
+
lines.push(`Results: ${result.documents.mappings.length}`);
|
|
627
|
+
}
|
|
628
|
+
if (result.errors && result.errors.length > 0) {
|
|
629
|
+
lines.push(`Errors: ${result.errors.length}`);
|
|
630
|
+
}
|
|
631
|
+
const previewData = {};
|
|
632
|
+
if (result.documents && result.documents.mappings) {
|
|
633
|
+
for (const mapping of result.documents.mappings.slice(0, 3)) {
|
|
634
|
+
const docKey = mapping.reference;
|
|
635
|
+
previewData[mapping.name] = result.documents[docKey];
|
|
698
636
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
if (
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
637
|
+
}
|
|
638
|
+
const preview = formatResultsPreview(previewData);
|
|
639
|
+
if (preview) lines.push("", "Result preview:", preview);
|
|
640
|
+
lines.push("", "Use --json to print the complete result payload.");
|
|
641
|
+
return `${lines.join("\n")}
|
|
642
|
+
`;
|
|
643
|
+
}
|
|
644
|
+
function formatRuntimeError(err) {
|
|
645
|
+
return formatHumanError(err);
|
|
646
|
+
}
|
|
647
|
+
function formatHumanError(err) {
|
|
648
|
+
if (err instanceof DocmanaExecutionError) {
|
|
649
|
+
const lines = [`Error: ${err.message}`];
|
|
650
|
+
if (err.errors && err.errors.length > 0) {
|
|
651
|
+
lines.push("Global errors:");
|
|
652
|
+
for (const e of err.errors) {
|
|
653
|
+
lines.push(` - ${formatSingleError(e)}`);
|
|
654
|
+
}
|
|
716
655
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
656
|
+
const result = err.result;
|
|
657
|
+
if (result?.documents?.mappings && result.documents.mappings.length > 0) {
|
|
658
|
+
lines.push("Document execution results:");
|
|
659
|
+
for (const mapping of result.documents.mappings) {
|
|
660
|
+
const docKey = mapping.reference;
|
|
661
|
+
const docName = mapping.name;
|
|
662
|
+
const docResult = result.documents[docKey];
|
|
663
|
+
if (!docResult) {
|
|
664
|
+
lines.push(` - Document: ${docName} (Status: Unknown)`);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
const status = docResult.status || "Unknown";
|
|
668
|
+
const nodes = [];
|
|
669
|
+
if (docResult.classifications) nodes.push(...Object.values(docResult.classifications));
|
|
670
|
+
if (docResult.metadataExtraction) nodes.push(docResult.metadataExtraction);
|
|
671
|
+
if (docResult.extractions) nodes.push(...Object.values(docResult.extractions));
|
|
672
|
+
if (docResult.validations) nodes.push(...Object.values(docResult.validations));
|
|
673
|
+
const hasNodeFailures = nodes.some(
|
|
674
|
+
(n) => n.status === "Failed" || n.errors && n.errors.length > 0
|
|
675
|
+
);
|
|
676
|
+
lines.push(` - Document: ${docName} (Status: ${status})`);
|
|
677
|
+
if (docResult.status !== "Failed" && !hasNodeFailures) continue;
|
|
678
|
+
if (docResult.classifications) {
|
|
679
|
+
for (const [nodeName, node] of Object.entries(docResult.classifications)) {
|
|
680
|
+
if (node.errors && node.errors.length > 0) {
|
|
681
|
+
lines.push(` Classification: ${nodeName} (Status: ${node.status})`);
|
|
682
|
+
for (const nodeErr of node.errors) lines.push(` - ${nodeErr}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (docResult.metadataExtraction?.errors?.length) {
|
|
687
|
+
lines.push(` MetadataExtraction: (Status: ${docResult.metadataExtraction.status})`);
|
|
688
|
+
for (const nodeErr of docResult.metadataExtraction.errors) {
|
|
689
|
+
lines.push(` - ${nodeErr}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (docResult.extractions) {
|
|
693
|
+
for (const [nodeName, node] of Object.entries(docResult.extractions)) {
|
|
694
|
+
if (node.status === "Failed" || node.errors && node.errors.length > 0) {
|
|
695
|
+
lines.push(` Extraction: ${nodeName} (Status: ${node.status})`);
|
|
696
|
+
if (node.errors) {
|
|
697
|
+
for (const nodeErr of node.errors) lines.push(` - ${nodeErr}`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (docResult.validations) {
|
|
703
|
+
for (const [nodeName, node] of Object.entries(docResult.validations)) {
|
|
704
|
+
if (node.status === "Failed" || node.errors && node.errors.length > 0) {
|
|
705
|
+
lines.push(` Validation: ${nodeName} (Status: ${node.status})`);
|
|
706
|
+
if (node.errors) {
|
|
707
|
+
for (const nodeErr of node.errors) lines.push(` - ${nodeErr}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
731
711
|
}
|
|
732
|
-
io.stdout(`${JSON.stringify(errorPayload, null, 2)}
|
|
733
|
-
`);
|
|
734
712
|
}
|
|
735
|
-
throw new CliSilentError(err instanceof CliUsageError ? 2 : 1);
|
|
736
713
|
}
|
|
737
|
-
|
|
714
|
+
return lines.join("\n");
|
|
738
715
|
}
|
|
716
|
+
if (err instanceof DocmanaError) {
|
|
717
|
+
return `Error: ${err.message} (Code: ${err.code})`;
|
|
718
|
+
}
|
|
719
|
+
if (err instanceof Error) {
|
|
720
|
+
return `Error: ${err.message}`;
|
|
721
|
+
}
|
|
722
|
+
return `Error: ${String(err)}`;
|
|
739
723
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
clientSecret: auth.clientSecret,
|
|
747
|
-
tokenEndpoint: auth.tokenEndpoint,
|
|
748
|
-
scope: auth.scope,
|
|
749
|
-
fetchImpl: deps.fetch ?? fetch,
|
|
750
|
-
tokenCache
|
|
751
|
-
});
|
|
752
|
-
await tokenManager.getToken();
|
|
753
|
-
const cached = await tokenCache.read();
|
|
754
|
-
io.stdout(`Logged in. Token cached at ${tokenCachePath}
|
|
755
|
-
`);
|
|
756
|
-
if (cached) io.stdout(formatTokenExpiry(cached.expiresAt));
|
|
757
|
-
}
|
|
758
|
-
function formatTokenExpiry(expiresAt) {
|
|
759
|
-
const expiresAtDate = new Date(expiresAt);
|
|
760
|
-
return [
|
|
761
|
-
`Token expires at local time ${expiresAtDate.toLocaleString()}`,
|
|
762
|
-
`Token expires at UTC ${expiresAtDate.toISOString()}`
|
|
763
|
-
].join("\n") + "\n";
|
|
724
|
+
function formatResultsPreview(results) {
|
|
725
|
+
if (!results) return void 0;
|
|
726
|
+
const preview = JSON.stringify(results, null, 2);
|
|
727
|
+
if (!preview) return void 0;
|
|
728
|
+
return preview.length > 2e3 ? `${preview.slice(0, 2e3)}
|
|
729
|
+
...` : preview;
|
|
764
730
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (!clientSecret) {
|
|
778
|
-
throw new CliUsageError("Missing client secret. Use --client-secret or DOCMANA_CLIENT_SECRET.");
|
|
731
|
+
function formatSingleError(e) {
|
|
732
|
+
if (typeof e === "string") return e;
|
|
733
|
+
if (e && typeof e === "object") {
|
|
734
|
+
const obj = e;
|
|
735
|
+
if (typeof obj.msg === "string") return obj.msg;
|
|
736
|
+
if (typeof obj.message === "string") return obj.message;
|
|
737
|
+
if (typeof obj.error === "string") return obj.error;
|
|
738
|
+
try {
|
|
739
|
+
return JSON.stringify(e);
|
|
740
|
+
} catch {
|
|
741
|
+
return String(e);
|
|
742
|
+
}
|
|
779
743
|
}
|
|
780
|
-
return
|
|
744
|
+
return String(e);
|
|
781
745
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
);
|
|
820
|
-
} finally {
|
|
821
|
-
defaultPrompt?.close();
|
|
746
|
+
|
|
747
|
+
// src/cli/types.ts
|
|
748
|
+
var DEFAULT_CONFIG_FILE = "docmana.config.json";
|
|
749
|
+
var DEFAULT_TOKEN_CACHE_FILE = "docmana.token.json";
|
|
750
|
+
var HELP = {
|
|
751
|
+
root: "Run Docmana document-analysis flows.",
|
|
752
|
+
flow: "Manage and run Docmana flows.",
|
|
753
|
+
organization: "Manage Docmana organizations.",
|
|
754
|
+
organizationDocument: "Manage organization documents.",
|
|
755
|
+
config: "Manage Docmana CLI configuration.",
|
|
756
|
+
configInit: "Create or update a local Docmana CLI config file.",
|
|
757
|
+
login: "Acquire and cache a Docmana OAuth2 access token.",
|
|
758
|
+
run: "Upload documents, run a Docmana flow, wait for completion, and print the result.",
|
|
759
|
+
list: "List Docmana flows available to the Bearer token.",
|
|
760
|
+
metadata: "Print Docmana flow runtime metadata as JSON.",
|
|
761
|
+
definition: "Print Docmana flow definition as JSON.",
|
|
762
|
+
organizationList: "List Docmana organizations available to the Bearer token.",
|
|
763
|
+
organizationDocumentList: "List organization documents available to the Bearer token.",
|
|
764
|
+
organizationDocumentDownload: "Download an organization document."
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// src/cli/config-store.ts
|
|
768
|
+
import { existsSync } from "fs";
|
|
769
|
+
import { mkdir, readFile as readFile2, rm, writeFile } from "fs/promises";
|
|
770
|
+
import { dirname, resolve } from "path";
|
|
771
|
+
import { createInterface } from "readline/promises";
|
|
772
|
+
|
|
773
|
+
// src/cli/utils.ts
|
|
774
|
+
function getCwd(deps) {
|
|
775
|
+
return deps.cwd ?? process.cwd();
|
|
776
|
+
}
|
|
777
|
+
function firstNonEmpty(...values) {
|
|
778
|
+
return values.map((value) => value?.trim()).find(Boolean);
|
|
779
|
+
}
|
|
780
|
+
function parseFiles(filesCsv) {
|
|
781
|
+
const files = filesCsv?.split(",").map((file) => file.trim()).filter(Boolean) ?? [];
|
|
782
|
+
if (files.length === 0) {
|
|
783
|
+
throw new CliUsageError("Missing files. Use --files <path[,path]>.");
|
|
822
784
|
}
|
|
785
|
+
return files;
|
|
786
|
+
}
|
|
787
|
+
function formatElapsedSeconds(elapsedMs) {
|
|
788
|
+
return `${Math.floor(elapsedMs / 1e3)}s`;
|
|
823
789
|
}
|
|
790
|
+
|
|
791
|
+
// src/cli/config-store.ts
|
|
824
792
|
async function loadCliConfig(configPathOption, cwd, requireExisting) {
|
|
825
793
|
const configPath = resolveConfigPath(configPathOption, cwd);
|
|
826
794
|
if (!existsSync(configPath)) {
|
|
@@ -848,23 +816,16 @@ function normalizeCliConfig(config) {
|
|
|
848
816
|
apiBaseUrl: stringValue(config.apiBaseUrl),
|
|
849
817
|
tokenEndpoint: stringValue(config.tokenEndpoint),
|
|
850
818
|
scope: stringValue(config.scope),
|
|
851
|
-
organisation: stringValue(config.organisation),
|
|
852
819
|
clientId: stringValue(config.clientId),
|
|
853
820
|
clientSecret: stringValue(config.clientSecret)
|
|
854
821
|
};
|
|
855
822
|
}
|
|
856
|
-
function stringValue(value) {
|
|
857
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
858
|
-
}
|
|
859
823
|
function resolveConfigPath(configPathOption, cwd) {
|
|
860
824
|
return resolve(cwd, configPathOption ?? DEFAULT_CONFIG_FILE);
|
|
861
825
|
}
|
|
862
826
|
function resolveTokenCachePath(tokenCachePathOption, cwd) {
|
|
863
827
|
return resolve(cwd, tokenCachePathOption ?? DEFAULT_TOKEN_CACHE_FILE);
|
|
864
828
|
}
|
|
865
|
-
function getCwd(deps) {
|
|
866
|
-
return deps.cwd ?? process.cwd();
|
|
867
|
-
}
|
|
868
829
|
function createFileTokenCache(path) {
|
|
869
830
|
return {
|
|
870
831
|
async read() {
|
|
@@ -897,6 +858,11 @@ function createFileTokenCache(path) {
|
|
|
897
858
|
}
|
|
898
859
|
};
|
|
899
860
|
}
|
|
861
|
+
async function writeCliConfig(configPath, config) {
|
|
862
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
863
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
864
|
+
`, "utf8");
|
|
865
|
+
}
|
|
900
866
|
function createDefaultPrompt() {
|
|
901
867
|
if (!process.stdin.isTTY) {
|
|
902
868
|
let index = 0;
|
|
@@ -920,6 +886,9 @@ function createDefaultPrompt() {
|
|
|
920
886
|
close: () => rl.close()
|
|
921
887
|
};
|
|
922
888
|
}
|
|
889
|
+
function stringValue(value) {
|
|
890
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
891
|
+
}
|
|
923
892
|
function formatPrompt(label, options) {
|
|
924
893
|
const defaultHint = options.defaultValue && !options.secret ? ` [${options.defaultValue}]` : options.defaultValue && options.secret ? " [current value hidden]" : "";
|
|
925
894
|
return `${label}${defaultHint}: `;
|
|
@@ -929,157 +898,515 @@ async function readStdinLines() {
|
|
|
929
898
|
for await (const chunk of process.stdin) input += String(chunk);
|
|
930
899
|
return input.split(/\r?\n/);
|
|
931
900
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
901
|
+
|
|
902
|
+
// src/cli/commands/config-init.ts
|
|
903
|
+
async function configInitCommand(options, io, deps) {
|
|
904
|
+
const cwd = getCwd(deps);
|
|
905
|
+
const configPath = resolveConfigPath(options.config, cwd);
|
|
906
|
+
const existing = await loadCliConfig(options.config, cwd, false);
|
|
907
|
+
const defaultPrompt = deps.prompt ? void 0 : createDefaultPrompt();
|
|
908
|
+
const prompt = deps.prompt ?? defaultPrompt?.prompt;
|
|
909
|
+
if (!prompt) throw new CliUsageError("Unable to initialize interactive prompt");
|
|
910
|
+
try {
|
|
911
|
+
const config = {
|
|
912
|
+
apiBaseUrl: await prompt("API base URL", {
|
|
913
|
+
defaultValue: existing.apiBaseUrl
|
|
914
|
+
}),
|
|
915
|
+
tokenEndpoint: await prompt("OAuth2 token endpoint", {
|
|
916
|
+
defaultValue: existing.tokenEndpoint ?? DEFAULTS.tokenEndpoint
|
|
917
|
+
}),
|
|
918
|
+
scope: await prompt("OAuth2 scope", {
|
|
919
|
+
defaultValue: existing.scope ?? DEFAULTS.scope
|
|
920
|
+
}),
|
|
921
|
+
clientId: await prompt("Client id", {
|
|
922
|
+
defaultValue: existing.clientId
|
|
923
|
+
}),
|
|
924
|
+
clientSecret: await prompt("Client secret", {
|
|
925
|
+
defaultValue: existing.clientSecret,
|
|
926
|
+
secret: true
|
|
927
|
+
})
|
|
928
|
+
};
|
|
929
|
+
await writeCliConfig(configPath, config);
|
|
930
|
+
io.stdout(`Wrote ${configPath}
|
|
931
|
+
`);
|
|
932
|
+
io.stdout(
|
|
933
|
+
`Warning: ${DEFAULT_CONFIG_FILE} contains credentials and should stay ignored by Git.
|
|
934
|
+
`
|
|
935
|
+
);
|
|
936
|
+
} finally {
|
|
937
|
+
defaultPrompt?.close();
|
|
946
938
|
}
|
|
947
|
-
|
|
948
|
-
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/cli/auth/token-manager.ts
|
|
942
|
+
var SAFETY_MARGIN_MS = 6e4;
|
|
943
|
+
var TokenManager = class {
|
|
944
|
+
constructor(opts) {
|
|
945
|
+
this.opts = opts;
|
|
949
946
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
947
|
+
opts;
|
|
948
|
+
token = null;
|
|
949
|
+
expiresAt = 0;
|
|
950
|
+
inflight = null;
|
|
951
|
+
invalidate() {
|
|
952
|
+
this.token = null;
|
|
953
|
+
this.expiresAt = 0;
|
|
954
|
+
void this.opts.tokenCache?.clear().catch(() => void 0);
|
|
955
|
+
}
|
|
956
|
+
async getToken() {
|
|
957
|
+
if (this.token && Date.now() < this.expiresAt) return this.token;
|
|
958
|
+
const cached = await this.readCachedToken();
|
|
959
|
+
if (cached) return cached;
|
|
960
|
+
if (this.inflight) return this.inflight;
|
|
961
|
+
this.inflight = this.acquire().finally(() => {
|
|
962
|
+
this.inflight = null;
|
|
963
|
+
});
|
|
964
|
+
return this.inflight;
|
|
965
|
+
}
|
|
966
|
+
async acquire() {
|
|
967
|
+
const body = new URLSearchParams({
|
|
968
|
+
grant_type: "client_credentials",
|
|
969
|
+
client_id: this.opts.clientId,
|
|
970
|
+
client_secret: this.opts.clientSecret,
|
|
971
|
+
scope: this.opts.scope
|
|
972
|
+
});
|
|
973
|
+
const res = await this.opts.fetchImpl(this.opts.tokenEndpoint, {
|
|
974
|
+
method: "POST",
|
|
975
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
976
|
+
body: body.toString()
|
|
977
|
+
});
|
|
978
|
+
if (!res.ok) {
|
|
979
|
+
throw new DocmanaAuthError("Failed to acquire Docmana access token", { status: res.status });
|
|
980
|
+
}
|
|
981
|
+
const json = await res.json();
|
|
982
|
+
if (typeof json.access_token !== "string" || json.access_token.length === 0 || typeof json.expires_in !== "number" || !Number.isFinite(json.expires_in)) {
|
|
983
|
+
throw new DocmanaAuthError("Malformed token response from Docmana CIAM", {
|
|
984
|
+
status: res.status
|
|
985
|
+
});
|
|
955
986
|
}
|
|
987
|
+
this.token = json.access_token;
|
|
988
|
+
this.expiresAt = Date.now() + json.expires_in * 1e3 - SAFETY_MARGIN_MS;
|
|
989
|
+
await this.writeCachedToken().catch(() => void 0);
|
|
990
|
+
return this.token;
|
|
956
991
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
992
|
+
async readCachedToken() {
|
|
993
|
+
if (!this.opts.tokenCache) return null;
|
|
994
|
+
let entry;
|
|
995
|
+
try {
|
|
996
|
+
entry = await this.opts.tokenCache.read();
|
|
997
|
+
} catch {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
if (!entry) return null;
|
|
1001
|
+
if (entry.clientId !== this.opts.clientId || entry.tokenEndpoint !== this.opts.tokenEndpoint || entry.scope !== this.opts.scope || Date.now() >= entry.expiresAt) {
|
|
1002
|
+
await this.opts.tokenCache.clear().catch(() => void 0);
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
this.token = entry.accessToken;
|
|
1006
|
+
this.expiresAt = entry.expiresAt;
|
|
1007
|
+
return this.token;
|
|
1008
|
+
}
|
|
1009
|
+
async writeCachedToken() {
|
|
1010
|
+
if (!this.opts.tokenCache || !this.token) return;
|
|
1011
|
+
await this.opts.tokenCache.write({
|
|
1012
|
+
accessToken: this.token,
|
|
1013
|
+
expiresAt: this.expiresAt,
|
|
1014
|
+
clientId: this.opts.clientId,
|
|
1015
|
+
tokenEndpoint: this.opts.tokenEndpoint,
|
|
1016
|
+
scope: this.opts.scope
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
// src/cli/auth/access-token.ts
|
|
1022
|
+
async function resolveFlowAccessToken(env, cliConfig, tokenCache, deps, io) {
|
|
1023
|
+
const cached = await readFreshCachedToken(tokenCache);
|
|
1024
|
+
if (cached) return cached;
|
|
1025
|
+
const clientId = firstNonEmpty(env.DOCMANA_CLIENT_ID, cliConfig.clientId);
|
|
1026
|
+
const clientSecret = firstNonEmpty(env.DOCMANA_CLIENT_SECRET, cliConfig.clientSecret);
|
|
1027
|
+
if (!clientId || !clientSecret) {
|
|
1028
|
+
throw new CliUsageError(
|
|
1029
|
+
"Missing access token. Use --access-token, DOCMANA_ACCESS_TOKEN, docmana login, or configure OAuth credentials."
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
const tokenManager = new TokenManager({
|
|
1033
|
+
clientId,
|
|
1034
|
+
clientSecret,
|
|
1035
|
+
tokenEndpoint: firstNonEmpty(env.DOCMANA_TOKEN_URL, cliConfig.tokenEndpoint) ?? DEFAULTS.tokenEndpoint,
|
|
1036
|
+
scope: firstNonEmpty(env.DOCMANA_SCOPE, cliConfig.scope) ?? DEFAULTS.scope,
|
|
1037
|
+
fetchImpl: deps.fetch ?? fetch,
|
|
1038
|
+
tokenCache
|
|
1039
|
+
});
|
|
1040
|
+
const token = await tokenManager.getToken();
|
|
1041
|
+
io.stderr("Generated new access token and updated the token cache.\n");
|
|
1042
|
+
return token;
|
|
962
1043
|
}
|
|
963
|
-
function
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
return preview.length > 2e3 ? `${preview.slice(0, 2e3)}
|
|
968
|
-
...` : preview;
|
|
1044
|
+
async function readFreshCachedToken(tokenCache) {
|
|
1045
|
+
const cached = await tokenCache.read().catch(() => null);
|
|
1046
|
+
if (!cached || Date.now() >= cached.expiresAt) return void 0;
|
|
1047
|
+
return cached.accessToken;
|
|
969
1048
|
}
|
|
970
|
-
function
|
|
971
|
-
|
|
1049
|
+
async function resolveAuthConfig(options, env, deps) {
|
|
1050
|
+
const cliConfig = await loadCliConfig(options.config, getCwd(deps), Boolean(options.config));
|
|
1051
|
+
const clientId = firstNonEmpty(options.clientId, env.DOCMANA_CLIENT_ID, cliConfig.clientId);
|
|
1052
|
+
const clientSecret = firstNonEmpty(
|
|
1053
|
+
options.clientSecret,
|
|
1054
|
+
env.DOCMANA_CLIENT_SECRET,
|
|
1055
|
+
cliConfig.clientSecret
|
|
1056
|
+
);
|
|
1057
|
+
const tokenEndpoint = firstNonEmpty(options.tokenUrl, env.DOCMANA_TOKEN_URL, cliConfig.tokenEndpoint) ?? DEFAULTS.tokenEndpoint;
|
|
1058
|
+
const scope = firstNonEmpty(options.scope, env.DOCMANA_SCOPE, cliConfig.scope) ?? DEFAULTS.scope;
|
|
1059
|
+
if (!clientId) {
|
|
1060
|
+
throw new CliUsageError("Missing client id. Use --client-id or DOCMANA_CLIENT_ID.");
|
|
1061
|
+
}
|
|
1062
|
+
if (!clientSecret) {
|
|
1063
|
+
throw new CliUsageError("Missing client secret. Use --client-secret or DOCMANA_CLIENT_SECRET.");
|
|
1064
|
+
}
|
|
1065
|
+
return { clientId, clientSecret, tokenEndpoint, scope };
|
|
972
1066
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1067
|
+
|
|
1068
|
+
// src/cli/commands/api-client.ts
|
|
1069
|
+
async function resolveApiClient(options, env, io, clientFactory, deps) {
|
|
1070
|
+
const cliConfig = await loadCliConfig(options.config, getCwd(deps), Boolean(options.config));
|
|
1071
|
+
const apiBaseUrl = firstNonEmpty(options.apiUrl, env.DOCMANA_API_URL, cliConfig.apiBaseUrl);
|
|
1072
|
+
if (!apiBaseUrl) {
|
|
1073
|
+
throw new CliUsageError("Missing API base URL. Use --api-url or DOCMANA_API_URL.");
|
|
1074
|
+
}
|
|
1075
|
+
const tokenCache = createFileTokenCache(resolveTokenCachePath(options.tokenCache, getCwd(deps)));
|
|
1076
|
+
const accessToken = firstNonEmpty(options.accessToken, env.DOCMANA_ACCESS_TOKEN) ?? await resolveFlowAccessToken(env, cliConfig, tokenCache, deps, io);
|
|
1077
|
+
return clientFactory({ apiBaseUrl, accessToken });
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/cli/commands/flow-definition.ts
|
|
1081
|
+
async function flowDefinitionCommand(flowId, options, env, io, clientFactory, deps) {
|
|
1082
|
+
try {
|
|
1083
|
+
const client = await resolveApiClient(options, env, io, clientFactory, deps);
|
|
1084
|
+
const definition = await client.getFlowDefinition(flowId);
|
|
1085
|
+
io.stdout(`${JSON.stringify(definition, null, 2)}
|
|
1086
|
+
`);
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
const errorPayload = {
|
|
1089
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1090
|
+
};
|
|
1091
|
+
if (err instanceof DocmanaError) {
|
|
1092
|
+
errorPayload.code = err.code;
|
|
1093
|
+
if (err.status !== void 0) errorPayload.status = err.status;
|
|
1094
|
+
if (err.requestId !== void 0) errorPayload.requestId = err.requestId;
|
|
1095
|
+
}
|
|
1096
|
+
io.stdout(`${JSON.stringify(errorPayload, null, 2)}
|
|
1097
|
+
`);
|
|
1098
|
+
throw new CliSilentError(err instanceof CliUsageError ? 2 : 1);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/cli/commands/flow-list.ts
|
|
1103
|
+
async function listFlowsCommand(options, env, io, clientFactory, deps) {
|
|
1104
|
+
try {
|
|
1105
|
+
const client = await resolveApiClient(options, env, io, clientFactory, deps);
|
|
1106
|
+
const flows = await client.listFlows();
|
|
1107
|
+
if (options.json) {
|
|
1108
|
+
io.stdout(`${JSON.stringify(flows, null, 2)}
|
|
1109
|
+
`);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
io.stdout(formatFlowList(flows));
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
if (options.json) {
|
|
1115
|
+
const errorPayload = {
|
|
1116
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1117
|
+
};
|
|
1118
|
+
if (err instanceof DocmanaError) {
|
|
1119
|
+
errorPayload.code = err.code;
|
|
1120
|
+
if (err.status !== void 0) errorPayload.status = err.status;
|
|
1121
|
+
if (err.requestId !== void 0) errorPayload.requestId = err.requestId;
|
|
980
1122
|
}
|
|
1123
|
+
io.stdout(`${JSON.stringify(errorPayload, null, 2)}
|
|
1124
|
+
`);
|
|
1125
|
+
throw new CliSilentError(err instanceof CliUsageError ? 2 : 1);
|
|
981
1126
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1127
|
+
throw err;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/cli/commands/flow-metadata.ts
|
|
1132
|
+
async function flowMetadataCommand(flowId, options, env, io, clientFactory, deps) {
|
|
1133
|
+
try {
|
|
1134
|
+
const client = await resolveApiClient(options, env, io, clientFactory, deps);
|
|
1135
|
+
const metadata = await client.getFlowMetadata(flowId);
|
|
1136
|
+
io.stdout(`${JSON.stringify(metadata, null, 2)}
|
|
1137
|
+
`);
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
const errorPayload = {
|
|
1140
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1141
|
+
};
|
|
1142
|
+
if (err instanceof DocmanaError) {
|
|
1143
|
+
errorPayload.code = err.code;
|
|
1144
|
+
if (err.status !== void 0) errorPayload.status = err.status;
|
|
1145
|
+
if (err.requestId !== void 0) errorPayload.requestId = err.requestId;
|
|
1146
|
+
}
|
|
1147
|
+
io.stdout(`${JSON.stringify(errorPayload, null, 2)}
|
|
1148
|
+
`);
|
|
1149
|
+
throw new CliSilentError(err instanceof CliUsageError ? 2 : 1);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/cli/commands/organization-document-download.ts
|
|
1154
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
1155
|
+
import { dirname as dirname2, join } from "path";
|
|
1156
|
+
async function organizationDocumentDownloadCommand(path, options, env, io, clientFactory, deps) {
|
|
1157
|
+
try {
|
|
1158
|
+
const client = await resolveApiClient(options, env, io, clientFactory, deps);
|
|
1159
|
+
const document = await client.downloadOrganizationDocument(path);
|
|
1160
|
+
const outputPath = options.output ?? join(getCwd(deps), path);
|
|
1161
|
+
await mkdir2(dirname2(outputPath), { recursive: true });
|
|
1162
|
+
await writeFile2(outputPath, document.bytes);
|
|
1163
|
+
io.stdout(`Downloaded ${document.filename} to ${outputPath}
|
|
1164
|
+
`);
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
const errorPayload = {
|
|
1167
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1168
|
+
};
|
|
1169
|
+
if (err instanceof DocmanaError) {
|
|
1170
|
+
errorPayload.code = err.code;
|
|
1171
|
+
if (err.status !== void 0) errorPayload.status = err.status;
|
|
1172
|
+
if (err.requestId !== void 0) errorPayload.requestId = err.requestId;
|
|
1173
|
+
}
|
|
1174
|
+
io.stdout(`${JSON.stringify(errorPayload, null, 2)}
|
|
1175
|
+
`);
|
|
1176
|
+
throw new CliSilentError(err instanceof CliUsageError ? 2 : 1);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/cli/commands/organization-document-list.ts
|
|
1181
|
+
async function organizationDocumentListCommand(options, env, io, clientFactory, deps) {
|
|
1182
|
+
try {
|
|
1183
|
+
const client = await resolveApiClient(options, env, io, clientFactory, deps);
|
|
1184
|
+
const payload = await client.listOrganizationDocuments({ prefix: options.prefix });
|
|
1185
|
+
if (options.json) {
|
|
1186
|
+
io.stdout(`${JSON.stringify(payload, null, 2)}
|
|
1187
|
+
`);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (!payload.configured) {
|
|
1191
|
+
io.stdout("Organization document depot is not configured.\n");
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
io.stdout(formatOrganizationDocumentList(payload.documents));
|
|
1195
|
+
} catch (err) {
|
|
1196
|
+
const errorPayload = {
|
|
1197
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1198
|
+
};
|
|
1199
|
+
if (err instanceof DocmanaError) {
|
|
1200
|
+
errorPayload.code = err.code;
|
|
1201
|
+
if (err.status !== void 0) errorPayload.status = err.status;
|
|
1202
|
+
if (err.requestId !== void 0) errorPayload.requestId = err.requestId;
|
|
1203
|
+
}
|
|
1204
|
+
io.stdout(`${JSON.stringify(errorPayload, null, 2)}
|
|
1205
|
+
`);
|
|
1206
|
+
throw new CliSilentError(err instanceof CliUsageError ? 2 : 1);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// src/cli/commands/organization-list.ts
|
|
1211
|
+
async function listOrganizationsCommand(options, env, io, clientFactory, deps) {
|
|
1212
|
+
try {
|
|
1213
|
+
const client = await resolveApiClient(options, env, io, clientFactory, deps);
|
|
1214
|
+
const organizations = await client.listOrganizations();
|
|
1215
|
+
if (options.json) {
|
|
1216
|
+
io.stdout(`${JSON.stringify(organizations, null, 2)}
|
|
1217
|
+
`);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
io.stdout(formatOrganizationList(organizations));
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
if (options.json) {
|
|
1223
|
+
const errorPayload = {
|
|
1224
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1225
|
+
};
|
|
1226
|
+
if (err instanceof DocmanaError) {
|
|
1227
|
+
errorPayload.code = err.code;
|
|
1228
|
+
if (err.status !== void 0) errorPayload.status = err.status;
|
|
1229
|
+
if (err.requestId !== void 0) errorPayload.requestId = err.requestId;
|
|
1051
1230
|
}
|
|
1231
|
+
io.stdout(`${JSON.stringify(errorPayload, null, 2)}
|
|
1232
|
+
`);
|
|
1233
|
+
throw new CliSilentError(err instanceof CliUsageError ? 2 : 1);
|
|
1052
1234
|
}
|
|
1053
|
-
|
|
1235
|
+
throw err;
|
|
1054
1236
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/cli/commands/flow-run.ts
|
|
1240
|
+
async function runFlowCommand(flowId, options, env, io, clientFactory, deps) {
|
|
1241
|
+
try {
|
|
1242
|
+
const files = parseFiles(options.files);
|
|
1243
|
+
const client = await resolveApiClient(options, env, io, clientFactory, deps);
|
|
1244
|
+
const result = await client.runFlow(
|
|
1245
|
+
flowId,
|
|
1246
|
+
{
|
|
1247
|
+
files: files.map((path) => ({ path })),
|
|
1248
|
+
language: options.language,
|
|
1249
|
+
useDraftVersion: options.draft === true
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
onPoll: (event) => {
|
|
1253
|
+
const nextPoll = event.nextPollAfterMs === void 0 ? "" : `, next poll in ${formatElapsedSeconds(event.nextPollAfterMs)}`;
|
|
1254
|
+
io.stderr(
|
|
1255
|
+
`Polling execution ${event.executionResultId}: ${event.status} (attempt ${event.attempt}, elapsed ${formatElapsedSeconds(event.elapsedMs)}${nextPoll})
|
|
1256
|
+
`
|
|
1257
|
+
);
|
|
1258
|
+
},
|
|
1259
|
+
onRateLimit: (event) => {
|
|
1260
|
+
io.stderr(
|
|
1261
|
+
`Polling execution ${event.executionResultId}: rate limited (attempt ${event.attempt}, retrying in ${formatElapsedSeconds(event.retryAfterMs)})
|
|
1262
|
+
`
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
);
|
|
1267
|
+
if (options.json) {
|
|
1268
|
+
io.stdout(`${JSON.stringify(result, null, 2)}
|
|
1269
|
+
`);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
io.stdout(formatHumanResult(result));
|
|
1273
|
+
} catch (err) {
|
|
1274
|
+
if (options.json) {
|
|
1275
|
+
if (err instanceof DocmanaExecutionError && err.result) {
|
|
1276
|
+
io.stdout(`${JSON.stringify(err.result, null, 2)}
|
|
1277
|
+
`);
|
|
1278
|
+
} else {
|
|
1279
|
+
const errorPayload = {
|
|
1280
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1281
|
+
};
|
|
1282
|
+
if (err instanceof DocmanaError) {
|
|
1283
|
+
errorPayload.code = err.code;
|
|
1284
|
+
if (err.status !== void 0) errorPayload.status = err.status;
|
|
1285
|
+
if (err.requestId !== void 0) errorPayload.requestId = err.requestId;
|
|
1286
|
+
}
|
|
1287
|
+
io.stdout(`${JSON.stringify(errorPayload, null, 2)}
|
|
1288
|
+
`);
|
|
1289
|
+
}
|
|
1290
|
+
throw new CliSilentError(err instanceof CliUsageError ? 2 : 1);
|
|
1291
|
+
}
|
|
1292
|
+
throw err;
|
|
1057
1293
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// src/cli/commands/login.ts
|
|
1297
|
+
async function loginCommand(options, env, io, deps) {
|
|
1298
|
+
const auth = await resolveAuthConfig(options, env, deps);
|
|
1299
|
+
const tokenCachePath = resolveTokenCachePath(options.tokenCache, getCwd(deps));
|
|
1300
|
+
const tokenCache = createFileTokenCache(tokenCachePath);
|
|
1301
|
+
const hadFreshCachedToken = Boolean(await readFreshCachedToken(tokenCache));
|
|
1302
|
+
const tokenManager = new TokenManager({
|
|
1303
|
+
clientId: auth.clientId,
|
|
1304
|
+
clientSecret: auth.clientSecret,
|
|
1305
|
+
tokenEndpoint: auth.tokenEndpoint,
|
|
1306
|
+
scope: auth.scope,
|
|
1307
|
+
fetchImpl: deps.fetch ?? fetch,
|
|
1308
|
+
tokenCache
|
|
1309
|
+
});
|
|
1310
|
+
await tokenManager.getToken();
|
|
1311
|
+
const cached = await tokenCache.read();
|
|
1312
|
+
if (!hadFreshCachedToken) {
|
|
1313
|
+
io.stdout("Generated new access token and updated the token cache.\n");
|
|
1060
1314
|
}
|
|
1061
|
-
|
|
1315
|
+
io.stdout(`Logged in. Token cached at ${tokenCachePath}
|
|
1316
|
+
`);
|
|
1317
|
+
if (cached) io.stdout(formatTokenExpiry(cached.expiresAt));
|
|
1062
1318
|
}
|
|
1063
|
-
function
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1319
|
+
function formatTokenExpiry(expiresAt) {
|
|
1320
|
+
const expiresAtDate = new Date(expiresAt);
|
|
1321
|
+
return [
|
|
1322
|
+
`Token expires at local time ${expiresAtDate.toLocaleString()}`,
|
|
1323
|
+
`Token expires at UTC ${expiresAtDate.toISOString()}`
|
|
1324
|
+
].join("\n") + "\n";
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// src/cli/cli.ts
|
|
1328
|
+
async function runCli(argv = process.argv.slice(2), env = process.env, io = {
|
|
1329
|
+
stdout: (text) => process.stdout.write(text),
|
|
1330
|
+
stderr: (text) => process.stderr.write(text)
|
|
1331
|
+
}, clientFactory = (config) => new Docmana(config), deps = {}) {
|
|
1332
|
+
const program = buildProgram(env, io, clientFactory, deps);
|
|
1333
|
+
if (argv.length === 0) {
|
|
1334
|
+
io.stdout(program.helpInformation());
|
|
1335
|
+
return 0;
|
|
1336
|
+
}
|
|
1337
|
+
try {
|
|
1338
|
+
await program.parseAsync(argv, { from: "user" });
|
|
1339
|
+
return 0;
|
|
1340
|
+
} catch (err) {
|
|
1341
|
+
if (err instanceof CliSilentError) {
|
|
1342
|
+
return err.exitCode;
|
|
1343
|
+
}
|
|
1344
|
+
if (err instanceof CliUsageError) {
|
|
1345
|
+
io.stderr(`Error: ${err.message}
|
|
1346
|
+
`);
|
|
1347
|
+
return 2;
|
|
1348
|
+
}
|
|
1349
|
+
if (err instanceof CommanderError) {
|
|
1350
|
+
return err.exitCode === 0 ? 0 : 2;
|
|
1074
1351
|
}
|
|
1352
|
+
io.stderr(`${formatRuntimeError(err)}
|
|
1353
|
+
`);
|
|
1354
|
+
return 1;
|
|
1075
1355
|
}
|
|
1076
|
-
|
|
1356
|
+
}
|
|
1357
|
+
function buildProgram(env, io, clientFactory, deps) {
|
|
1358
|
+
const program = new Command();
|
|
1359
|
+
program.name("docmana").description(HELP.root).showHelpAfterError().configureOutput({
|
|
1360
|
+
writeOut: io.stdout,
|
|
1361
|
+
writeErr: io.stderr,
|
|
1362
|
+
outputError: (str, write) => write(str)
|
|
1363
|
+
}).exitOverride();
|
|
1364
|
+
program.command("login").description(HELP.login).option("--client-id <id>", "OAuth2 client id; defaults to DOCMANA_CLIENT_ID").option("--client-secret <secret>", "OAuth2 client secret; defaults to DOCMANA_CLIENT_SECRET").option("--token-url <url>", "OAuth2 token endpoint; defaults to DOCMANA_TOKEN_URL").option("--scope <scope>", "OAuth2 scope; defaults to DOCMANA_SCOPE").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).action(async (options) => {
|
|
1365
|
+
await loginCommand(options, env, io, deps);
|
|
1366
|
+
});
|
|
1367
|
+
const flow = program.command("flow").description(HELP.flow).action(() => {
|
|
1368
|
+
io.stdout(flow.helpInformation());
|
|
1369
|
+
});
|
|
1370
|
+
flow.command("list").description(HELP.list).option("--api-url <url>", "Consumer API base URL; defaults to DOCMANA_API_URL").option("--access-token <token>", "Bearer token; defaults to DOCMANA_ACCESS_TOKEN").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).option("--json", "Print the raw JSON flow list").action(async (options) => {
|
|
1371
|
+
await listFlowsCommand(options, env, io, clientFactory, deps);
|
|
1372
|
+
});
|
|
1373
|
+
flow.command("run").description(HELP.run).argument("<flow-id>", "Docmana flow id").requiredOption("--files <csv>", "Comma-separated file paths to upload").option("--api-url <url>", "Consumer API base URL; defaults to DOCMANA_API_URL").option("--access-token <token>", "Bearer token; defaults to DOCMANA_ACCESS_TOKEN").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).option("--json", "Print the raw JSON result").option("--language <lang>", "Translate output to specified language").option("--draft", "Run the current draft flow version").action(async (flowId, options) => {
|
|
1374
|
+
await runFlowCommand(flowId, options, env, io, clientFactory, deps);
|
|
1375
|
+
});
|
|
1376
|
+
flow.command("metadata").description(HELP.metadata).argument("<flow-id>", "Docmana flow id").option("--api-url <url>", "Consumer API base URL; defaults to DOCMANA_API_URL").option("--access-token <token>", "Bearer token; defaults to DOCMANA_ACCESS_TOKEN").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).action(async (flowId, options) => {
|
|
1377
|
+
await flowMetadataCommand(flowId, options, env, io, clientFactory, deps);
|
|
1378
|
+
});
|
|
1379
|
+
flow.command("definition").description(HELP.definition).argument("<flow-id>", "Docmana flow id").option("--api-url <url>", "Consumer API base URL; defaults to DOCMANA_API_URL").option("--access-token <token>", "Bearer token; defaults to DOCMANA_ACCESS_TOKEN").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).action(async (flowId, options) => {
|
|
1380
|
+
await flowDefinitionCommand(flowId, options, env, io, clientFactory, deps);
|
|
1381
|
+
});
|
|
1382
|
+
const organization = program.command("organization").description(HELP.organization).action(() => {
|
|
1383
|
+
io.stdout(organization.helpInformation());
|
|
1384
|
+
});
|
|
1385
|
+
organization.command("list").description(HELP.organizationList).option("--api-url <url>", "Consumer API base URL; defaults to DOCMANA_API_URL").option("--access-token <token>", "Bearer token; defaults to DOCMANA_ACCESS_TOKEN").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).option("--json", "Print the raw JSON organization list").action(async (options) => {
|
|
1386
|
+
await listOrganizationsCommand(options, env, io, clientFactory, deps);
|
|
1387
|
+
});
|
|
1388
|
+
const organizationDocument = organization.command("document").description(HELP.organizationDocument).action(() => {
|
|
1389
|
+
io.stdout(organizationDocument.helpInformation());
|
|
1390
|
+
});
|
|
1391
|
+
organizationDocument.command("list").description(HELP.organizationDocumentList).option("--api-url <url>", "Consumer API base URL; defaults to DOCMANA_API_URL").option("--access-token <token>", "Bearer token; defaults to DOCMANA_ACCESS_TOKEN").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).option("--prefix <prefix>", "Filter documents by path prefix").option("--json", "Print the raw JSON organization document list").action(async (options) => {
|
|
1392
|
+
await organizationDocumentListCommand(options, env, io, clientFactory, deps);
|
|
1393
|
+
});
|
|
1394
|
+
organizationDocument.command("download").description(HELP.organizationDocumentDownload).argument("<path>", "Organization document path").option("--output <file>", "Output file path; defaults to ./{path}").option("--api-url <url>", "Consumer API base URL; defaults to DOCMANA_API_URL").option("--access-token <token>", "Bearer token; defaults to DOCMANA_ACCESS_TOKEN").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).action(async (path, options) => {
|
|
1395
|
+
await organizationDocumentDownloadCommand(path, options, env, io, clientFactory, deps);
|
|
1396
|
+
});
|
|
1397
|
+
const config = program.command("config").description(HELP.config).action(() => {
|
|
1398
|
+
io.stdout(config.helpInformation());
|
|
1399
|
+
});
|
|
1400
|
+
config.command("init").description(HELP.configInit).option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).action(async (options) => {
|
|
1401
|
+
await configInitCommand(options, io, deps);
|
|
1402
|
+
});
|
|
1403
|
+
return program;
|
|
1077
1404
|
}
|
|
1078
1405
|
function isDirectRun() {
|
|
1079
1406
|
const entry = process.argv[1];
|
|
1080
1407
|
if (!entry) return false;
|
|
1081
1408
|
try {
|
|
1082
|
-
return realpathSync(
|
|
1409
|
+
return realpathSync(resolve2(entry)) === realpathSync(fileURLToPath(import.meta.url));
|
|
1083
1410
|
} catch {
|
|
1084
1411
|
return false;
|
|
1085
1412
|
}
|