@docmana/sdk 0.3.5 → 0.4.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.
Files changed (47) hide show
  1. package/README.md +115 -247
  2. package/dist/api/execution-result.d.ts +3 -0
  3. package/dist/api/execution-status.d.ts +6 -0
  4. package/dist/api/run-flow.d.ts +6 -0
  5. package/dist/cli.mjs +217 -257
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/client.d.ts +21 -0
  8. package/dist/config.d.ts +21 -0
  9. package/dist/errors.d.ts +55 -0
  10. package/dist/files/resolve-input.d.ts +11 -0
  11. package/dist/http/http-client.d.ts +26 -0
  12. package/dist/index.d.ts +4 -349
  13. package/dist/index.js +159 -298
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +159 -298
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/models/docmana-api-document-execution-result.model.d.ts +9 -0
  18. package/dist/models/docmana-api-document-request.model.d.ts +15 -0
  19. package/dist/models/docmana-api-execution-result.model.d.ts +24 -0
  20. package/dist/models/docmana-api-node-result.model.d.ts +28 -0
  21. package/dist/models/docmana-classification-result.model.d.ts +3 -0
  22. package/dist/models/docmana-conclusion-result.model.d.ts +3 -0
  23. package/dist/models/docmana-cross-validation-result.model.d.ts +3 -0
  24. package/dist/models/docmana-document-mapping.model.d.ts +4 -0
  25. package/dist/models/docmana-document-result.model.d.ts +11 -0
  26. package/dist/models/docmana-execution-result.model.d.ts +15 -0
  27. package/dist/models/docmana-extraction-result.model.d.ts +3 -0
  28. package/dist/models/docmana-metadata-extraction-result.model.d.ts +3 -0
  29. package/dist/models/docmana-node-result.model.d.ts +7 -0
  30. package/dist/models/docmana-score-node-result.model.d.ts +5 -0
  31. package/dist/models/docmana-validation-result.model.d.ts +3 -0
  32. package/dist/models/document-content.model.d.ts +4 -0
  33. package/dist/models/execution-progress.model.d.ts +4 -0
  34. package/dist/models/execution-status.enum.d.ts +1 -0
  35. package/dist/models/flow-configs.model.d.ts +16 -0
  36. package/dist/models/flow-edge.model.d.ts +4 -0
  37. package/dist/models/flow-node-type.enum.d.ts +1 -0
  38. package/dist/models/flow-node.model.d.ts +33 -0
  39. package/dist/models/flow.model.d.ts +11 -0
  40. package/dist/models/index.d.ts +26 -0
  41. package/dist/models/node-translation-result.model.d.ts +7 -0
  42. package/dist/models/physical-document.model.d.ts +9 -0
  43. package/dist/models/virtual-document.model.d.ts +10 -0
  44. package/dist/polling/poll.d.ts +15 -0
  45. package/dist/types.d.ts +76 -0
  46. package/package.json +17 -14
  47. package/dist/index.d.cts +0 -349
package/dist/cli.mjs CHANGED
@@ -61,7 +61,14 @@ var DocmanaAbortError = class extends DocmanaError {
61
61
  super(message, { code: "aborted" });
62
62
  }
63
63
  };
64
- function errorFromResponse(status, message, requestId) {
64
+ var DocmanaRateLimitError = class extends DocmanaError {
65
+ retryAfterMs;
66
+ constructor(message, opts = {}) {
67
+ super(message, { ...opts, code: "rate_limited" });
68
+ this.retryAfterMs = opts.retryAfterMs;
69
+ }
70
+ };
71
+ function errorFromResponse(status, message, requestId, retryAfterMs) {
65
72
  const opts = { status, requestId };
66
73
  switch (status) {
67
74
  case 400:
@@ -72,6 +79,8 @@ function errorFromResponse(status, message, requestId) {
72
79
  return new DocmanaPermissionError(message, opts);
73
80
  case 404:
74
81
  return new DocmanaNotFoundError(message, opts);
82
+ case 429:
83
+ return new DocmanaRateLimitError(message, { ...opts, retryAfterMs });
75
84
  default:
76
85
  return new DocmanaError(message, opts);
77
86
  }
@@ -159,29 +168,33 @@ var TokenManager = class {
159
168
 
160
169
  // src/config.ts
161
170
  var DEFAULTS = {
162
- apiBaseUrl: "https://api.docmana.ai",
163
171
  tokenEndpoint: "https://4fe70f5b-e013-4f65-9fa7-3109a33beba5.ciamlogin.com/4fe70f5b-e013-4f65-9fa7-3109a33beba5/oauth2/v2.0/token",
164
172
  scope: "api://d781e6ba-cc08-4618-8099-ad968abd2b9e/.default",
165
173
  timeoutMs: 3e5,
166
- pollIntervalMs: 2e3
174
+ pollIntervalMs: 2e3,
175
+ pollMaxIntervalMs: 1e4
167
176
  };
168
177
  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(/\/+$/, "");
178
+ if (!config.apiBaseUrl) throw new Error("DocmanaConfig.apiBaseUrl is required");
179
+ if (!config.accessToken && !config.getAccessToken) {
180
+ throw new Error("DocmanaConfig.accessToken or DocmanaConfig.getAccessToken is required");
181
+ }
182
+ const apiBaseUrl = normalizeApiBaseUrl(config.apiBaseUrl);
172
183
  return {
173
- clientId: config.clientId,
174
- clientSecret: config.clientSecret,
175
184
  apiBaseUrl,
176
- tokenEndpoint: config.tokenEndpoint ?? DEFAULTS.tokenEndpoint,
177
- scope: config.scope ?? DEFAULTS.scope,
185
+ accessToken: config.accessToken,
186
+ getAccessToken: config.getAccessToken,
178
187
  timeoutMs: config.timeoutMs ?? DEFAULTS.timeoutMs,
179
188
  pollIntervalMs: config.pollIntervalMs ?? DEFAULTS.pollIntervalMs,
189
+ pollMaxIntervalMs: config.pollMaxIntervalMs ?? DEFAULTS.pollMaxIntervalMs,
180
190
  fetchImpl: config.fetch ?? fetch,
181
- headers: config.headers ?? {},
182
- tokenCache: config.tokenCache
191
+ headers: config.headers ?? {}
183
192
  };
184
193
  }
194
+ function normalizeApiBaseUrl(value) {
195
+ const trimmed = value.replace(/\/+$/, "");
196
+ return /\/v\d+$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
197
+ }
185
198
 
186
199
  // src/http/http-client.ts
187
200
  var HttpClient = class {
@@ -201,19 +214,23 @@ var HttpClient = class {
201
214
  }
202
215
  async requestRaw(method, path, options = {}) {
203
216
  let res = await this.send(method, path, options);
204
- if (res.status === 401) {
205
- this.opts.tokenManager.invalidate();
206
- res = await this.send(method, path, options);
217
+ if (res.status === 401 && this.opts.getAccessToken) {
218
+ res = await this.send(method, path, options, true);
207
219
  }
208
220
  if (!res.ok) {
209
221
  const requestId = res.headers.get("x-request-id") ?? void 0;
210
222
  const message = await this.extractMessage(res);
211
- throw errorFromResponse(res.status, message, requestId);
223
+ throw errorFromResponse(
224
+ res.status,
225
+ message,
226
+ requestId,
227
+ parseRetryAfterMs(res.headers.get("retry-after"))
228
+ );
212
229
  }
213
230
  return res;
214
231
  }
215
- async send(method, path, options) {
216
- const token = await this.opts.tokenManager.getToken();
232
+ async send(method, path, options, forceRefresh = false) {
233
+ const token = await this.resolveToken(forceRefresh);
217
234
  const url = new URL(this.opts.apiBaseUrl + path);
218
235
  for (const [k, v] of Object.entries(options.query ?? {})) url.searchParams.set(k, v);
219
236
  const headers = {
@@ -235,6 +252,13 @@ var HttpClient = class {
235
252
  throw err;
236
253
  }
237
254
  }
255
+ async resolveToken(forceRefresh) {
256
+ const token = this.opts.getAccessToken ? await this.opts.getAccessToken(forceRefresh ? { forceRefresh: true } : void 0) : this.opts.accessToken;
257
+ if (!token) {
258
+ throw new DocmanaError("Missing Docmana access token", { code: "auth_missing" });
259
+ }
260
+ return token;
261
+ }
238
262
  async extractMessage(res) {
239
263
  try {
240
264
  const data = await res.clone().json();
@@ -244,6 +268,18 @@ var HttpClient = class {
244
268
  }
245
269
  }
246
270
  };
271
+ function parseRetryAfterMs(value) {
272
+ if (!value) return void 0;
273
+ const seconds = Number(value);
274
+ if (Number.isFinite(seconds) && seconds >= 0) {
275
+ return seconds * 1e3;
276
+ }
277
+ const retryAt = Date.parse(value);
278
+ if (!Number.isNaN(retryAt)) {
279
+ return Math.max(0, retryAt - Date.now());
280
+ }
281
+ return void 0;
282
+ }
247
283
 
248
284
  // src/files/resolve-input.ts
249
285
  import { readFile } from "fs/promises";
@@ -275,16 +311,19 @@ async function streamToBuffer(stream) {
275
311
  for await (const chunk of stream) chunks.push(Buffer.from(chunk));
276
312
  return Buffer.concat(chunks);
277
313
  }
314
+ function toBlobBytes(bytes) {
315
+ return new Uint8Array(bytes);
316
+ }
278
317
  async function resolveOne(input, fallbackName) {
279
318
  if (typeof input === "string" || isPathInput(input)) {
280
319
  const path = typeof input === "string" ? input : input.path;
281
320
  const data = await readFile(path);
282
321
  const filename = basename(path);
283
- return { data: new Blob([data]), filename, contentType: contentTypeFor(filename) };
322
+ return { data: new Blob([toBlobBytes(data)]), filename, contentType: contentTypeFor(filename) };
284
323
  }
285
324
  if (input instanceof Uint8Array) {
286
325
  return {
287
- data: new Blob([input]),
326
+ data: new Blob([toBlobBytes(input)]),
288
327
  filename: fallbackName,
289
328
  contentType: contentTypeFor(fallbackName)
290
329
  };
@@ -292,7 +331,7 @@ async function resolveOne(input, fallbackName) {
292
331
  if (isReadable(input)) {
293
332
  const buf = await streamToBuffer(input);
294
333
  return {
295
- data: new Blob([buf]),
334
+ data: new Blob([toBlobBytes(buf)]),
296
335
  filename: fallbackName,
297
336
  contentType: contentTypeFor(fallbackName)
298
337
  };
@@ -306,39 +345,43 @@ async function resolveInputs(input) {
306
345
  return Promise.all(list.map((item) => resolveOne(item, fallback)));
307
346
  }
308
347
 
309
- // src/api/upload.ts
310
- async function uploadFiles(http, parts, signal) {
348
+ // src/api/run-flow.ts
349
+ async function runFlow(http, flowId, parts, signal, useDraftVersion = false, context, callbackUrl, language) {
311
350
  const form = new FormData();
312
351
  for (const part of parts) {
313
352
  form.append("files", new File([part.data], part.filename, { type: part.contentType }));
314
353
  }
315
- const res = await http.requestJson("POST", "/upload", { body: form, signal });
316
- return [res.id];
317
- }
318
-
319
- // src/api/run-flow.ts
320
- async function runFlow(http, flowId, uuidFiles, signal, once = false, context, callbackUrl) {
321
- const path = once ? `/flows/run_once_flow/${flowId}` : `/flows/run_flow/${flowId}`;
322
- const body = { uuid_files: uuidFiles };
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),
329
- signal
330
- });
331
- return res.execution_result_id;
354
+ if (context !== void 0) form.append("json_context", JSON.stringify(context));
355
+ if (callbackUrl !== void 0) form.append("callback_url", callbackUrl);
356
+ if (language !== void 0) form.append("language", language);
357
+ if (useDraftVersion) form.append("useDraftVersion", "true");
358
+ return http.requestJson(
359
+ "POST",
360
+ `/flows/${flowId}/execute-async`,
361
+ { body: form, signal }
362
+ );
332
363
  }
333
364
 
334
365
  // src/api/execution-status.ts
335
366
  async function getStatus(http, executionResultId, signal) {
336
- return http.requestJson("GET", `/flows/execution-status/${executionResultId}`, { signal });
367
+ const res = await http.requestRaw("GET", `/executions/${executionResultId}/status`, { signal });
368
+ const text = await res.text();
369
+ const body = text ? JSON.parse(text) : { status: "" };
370
+ const pollAfterMs = parsePositiveInt(res.headers.get("x-poll-after"));
371
+ return pollAfterMs === void 0 ? body : { ...body, pollAfterMs };
372
+ }
373
+ function parsePositiveInt(value) {
374
+ if (!value) return void 0;
375
+ const parsed = Number(value);
376
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
337
377
  }
338
378
 
339
379
  // src/api/execution-result.ts
340
- async function getResult(http, executionResultId, signal) {
341
- return http.requestJson("GET", `/flows/execution-result/${executionResultId}`, { signal });
380
+ async function getResult(http, executionResultId, signal, language) {
381
+ return http.requestJson("GET", `/executions/${executionResultId}/result`, {
382
+ query: language ? { language } : void 0,
383
+ signal
384
+ });
342
385
  }
343
386
 
344
387
  // src/polling/poll.ts
@@ -348,166 +391,42 @@ async function pollUntilTerminal(opts) {
348
391
  const sleep = opts.sleep ?? defaultSleep;
349
392
  const start = Date.now();
350
393
  const elapsed = opts.elapsed ?? (() => Date.now() - start);
394
+ const random = opts.random ?? Math.random;
395
+ let fallbackAttempt = 0;
351
396
  for (; ; ) {
352
397
  if (opts.signal?.aborted) throw new DocmanaAbortError("Polling aborted");
353
- const status = await opts.check();
398
+ let result;
399
+ try {
400
+ result = normalizePollResult(await opts.check());
401
+ } catch (err) {
402
+ if (!(err instanceof DocmanaRateLimitError)) throw err;
403
+ if (elapsed() >= opts.timeoutMs) {
404
+ throw new DocmanaTimeoutError(`Execution did not finish within ${opts.timeoutMs}ms`);
405
+ }
406
+ await sleep(err.retryAfterMs ?? opts.intervalMs);
407
+ continue;
408
+ }
409
+ const { status } = result;
354
410
  if (TERMINAL.has(status)) return status;
355
411
  if (elapsed() >= opts.timeoutMs) {
356
412
  throw new DocmanaTimeoutError(`Execution did not finish within ${opts.timeoutMs}ms`);
357
413
  }
358
- await sleep(opts.intervalMs);
414
+ const fallbackDelay = fullJitterDelay(
415
+ opts.intervalMs,
416
+ opts.maxIntervalMs,
417
+ fallbackAttempt,
418
+ random
419
+ );
420
+ fallbackAttempt += 1;
421
+ await sleep(result.nextDelayMs ?? fallbackDelay);
359
422
  }
360
423
  }
361
-
362
- // src/mappers/mapper.ts
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;
424
+ function normalizePollResult(result) {
425
+ return typeof result === "string" ? { status: result } : result;
428
426
  }
429
- function mapExecutionResult(raw, targetLanguage) {
430
- const sdkResult = {
431
- status: raw.status || "Unknown",
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;
427
+ function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
428
+ const capDelay = Math.min(maxIntervalMs, intervalMs * 2 ** attempt);
429
+ return Math.round(intervalMs + random() * (capDelay - intervalMs));
511
430
  }
512
431
 
513
432
  // src/client.ts
@@ -516,60 +435,79 @@ var Docmana = class {
516
435
  http;
517
436
  constructor(config) {
518
437
  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
438
  this.http = new HttpClient({
528
439
  apiBaseUrl: this.config.apiBaseUrl,
529
440
  fetchImpl: this.config.fetchImpl,
530
- tokenManager,
441
+ accessToken: this.config.accessToken,
442
+ getAccessToken: this.config.getAccessToken,
531
443
  headers: this.config.headers
532
444
  });
533
445
  }
534
446
  async runFlowAsync(flowId, input) {
535
447
  const parts = await resolveInputs(input);
536
- const fileIds = await uploadFiles(this.http, parts, input.signal);
537
- const executionResultId = await runFlow(
448
+ return runFlow(
538
449
  this.http,
539
450
  flowId,
540
- fileIds,
451
+ parts,
541
452
  input.signal,
542
- input.once ?? false,
543
- input.context
453
+ input.useDraftVersion ?? false,
454
+ input.context,
455
+ void 0,
456
+ input.language
544
457
  );
545
- return { executionResultId, fileIds };
546
458
  }
547
459
  async runFlowWithCallback(flowId, input) {
548
460
  const parts = await resolveInputs(input);
549
- const fileIds = await uploadFiles(this.http, parts, input.signal);
550
- const executionResultId = await runFlow(
461
+ return runFlow(
551
462
  this.http,
552
463
  flowId,
553
- fileIds,
464
+ parts,
554
465
  input.signal,
555
- input.once ?? false,
466
+ input.useDraftVersion ?? false,
556
467
  input.context,
557
- input.callbackUrl
468
+ input.callbackUrl,
469
+ input.language
558
470
  );
559
- return { executionResultId, fileIds };
560
471
  }
561
472
  async getStatus(executionResultId, signal) {
562
473
  return getStatus(this.http, executionResultId, signal);
563
474
  }
564
475
  async getResult(executionResultId, signal, language) {
565
- const rawResult = await getResult(this.http, executionResultId, signal);
566
- return mapExecutionResult(rawResult, language);
476
+ return getResult(this.http, executionResultId, signal, language);
567
477
  }
568
478
  async runFlow(flowId, input) {
569
479
  const { executionResultId } = await this.runFlowAsync(flowId, input);
480
+ let attempt = 0;
481
+ const startedAt = Date.now();
570
482
  await pollUntilTerminal({
571
- check: async () => (await this.getStatus(executionResultId, input.signal)).status,
483
+ check: async () => {
484
+ attempt += 1;
485
+ try {
486
+ const statusResult = await this.getStatus(executionResultId, input.signal);
487
+ const status = statusResult.status;
488
+ const nextPollAfterMs = this.isTerminalStatus(status) ? void 0 : statusResult.pollAfterMs;
489
+ input.onPoll?.({
490
+ executionResultId,
491
+ status,
492
+ attempt,
493
+ elapsedMs: Date.now() - startedAt,
494
+ nextPollAfterMs
495
+ });
496
+ return { status, nextDelayMs: nextPollAfterMs };
497
+ } catch (err) {
498
+ if (err instanceof DocmanaRateLimitError) {
499
+ input.onRateLimit?.({
500
+ executionResultId,
501
+ attempt,
502
+ elapsedMs: Date.now() - startedAt,
503
+ retryAfterMs: err.retryAfterMs ?? input.pollIntervalMs ?? this.config.pollIntervalMs
504
+ });
505
+ }
506
+ throw err;
507
+ }
508
+ },
572
509
  intervalMs: input.pollIntervalMs ?? this.config.pollIntervalMs,
510
+ maxIntervalMs: input.pollMaxIntervalMs ?? this.config.pollMaxIntervalMs,
573
511
  timeoutMs: input.timeoutMs ?? this.config.timeoutMs,
574
512
  signal: input.signal
575
513
  });
@@ -583,6 +521,9 @@ var Docmana = class {
583
521
  }
584
522
  return result;
585
523
  }
524
+ isTerminalStatus(status) {
525
+ return status === "Completed" || status === "Failed";
526
+ }
586
527
  };
587
528
 
588
529
  // src/cli.ts
@@ -653,7 +594,7 @@ function buildProgram(env, io, clientFactory, deps) {
653
594
  const flow = program.command("flow").description(HELP.flow).action(() => {
654
595
  io.stdout(flow.helpInformation());
655
596
  });
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) => {
597
+ 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) => {
657
598
  await runFlowCommand(flowId, options, env, io, clientFactory, deps);
658
599
  });
659
600
  const config = program.command("config").description(HELP.config).action(() => {
@@ -668,46 +609,35 @@ async function runFlowCommand(flowId, options, env, io, clientFactory, deps) {
668
609
  try {
669
610
  const files = parseFiles(options.files);
670
611
  const cliConfig = await loadCliConfig(options.config, getCwd(deps), Boolean(options.config));
671
- const clientId = firstNonEmpty(options.clientId, env.DOCMANA_CLIENT_ID, cliConfig.clientId);
672
- const clientSecret = firstNonEmpty(
673
- options.clientSecret,
674
- env.DOCMANA_CLIENT_SECRET,
675
- cliConfig.clientSecret
676
- );
677
- const organisation = firstNonEmpty(
678
- options.organisation,
679
- env.DOCMANA_ORGANISATION,
680
- cliConfig.organisation
681
- );
682
612
  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.");
613
+ if (!apiBaseUrl) {
614
+ throw new CliUsageError("Missing API base URL. Use --api-url or DOCMANA_API_URL.");
698
615
  }
616
+ const tokenCache = createFileTokenCache(
617
+ resolveTokenCachePath(options.tokenCache, getCwd(deps))
618
+ );
619
+ const accessToken = firstNonEmpty(options.accessToken, env.DOCMANA_ACCESS_TOKEN) ?? await resolveFlowAccessToken(env, cliConfig, tokenCache, deps, io);
699
620
  const client = clientFactory({
700
- clientId,
701
- clientSecret,
702
621
  apiBaseUrl,
703
- tokenEndpoint,
704
- scope,
705
- headers: { "X-Selected-Organization": organisation },
706
- tokenCache: createFileTokenCache(resolveTokenCachePath(options.tokenCache, getCwd(deps)))
622
+ accessToken
707
623
  });
708
624
  const result = await client.runFlow(flowId, {
709
625
  files: files.map((path) => ({ path })),
710
- language: options.language
626
+ language: options.language,
627
+ useDraftVersion: options.draft === true,
628
+ onPoll: (event) => {
629
+ const nextPoll = event.nextPollAfterMs === void 0 ? "" : `, next poll in ${formatElapsedSeconds(event.nextPollAfterMs)}`;
630
+ io.stderr(
631
+ `Polling execution ${event.executionResultId}: ${event.status} (attempt ${event.attempt}, elapsed ${formatElapsedSeconds(event.elapsedMs)}${nextPoll})
632
+ `
633
+ );
634
+ },
635
+ onRateLimit: (event) => {
636
+ io.stderr(
637
+ `Polling execution ${event.executionResultId}: rate limited (attempt ${event.attempt}, retrying in ${formatElapsedSeconds(event.retryAfterMs)})
638
+ `
639
+ );
640
+ }
711
641
  });
712
642
  if (options.json) {
713
643
  io.stdout(`${JSON.stringify(result, null, 2)}
@@ -737,10 +667,38 @@ async function runFlowCommand(flowId, options, env, io, clientFactory, deps) {
737
667
  throw err;
738
668
  }
739
669
  }
670
+ async function resolveFlowAccessToken(env, cliConfig, tokenCache, deps, io) {
671
+ const cached = await readFreshCachedToken(tokenCache);
672
+ if (cached) return cached;
673
+ const clientId = firstNonEmpty(env.DOCMANA_CLIENT_ID, cliConfig.clientId);
674
+ const clientSecret = firstNonEmpty(env.DOCMANA_CLIENT_SECRET, cliConfig.clientSecret);
675
+ if (!clientId || !clientSecret) {
676
+ throw new CliUsageError(
677
+ "Missing access token. Use --access-token, DOCMANA_ACCESS_TOKEN, docmana login, or configure OAuth credentials."
678
+ );
679
+ }
680
+ const tokenManager = new TokenManager({
681
+ clientId,
682
+ clientSecret,
683
+ tokenEndpoint: firstNonEmpty(env.DOCMANA_TOKEN_URL, cliConfig.tokenEndpoint) ?? DEFAULTS.tokenEndpoint,
684
+ scope: firstNonEmpty(env.DOCMANA_SCOPE, cliConfig.scope) ?? DEFAULTS.scope,
685
+ fetchImpl: deps.fetch ?? fetch,
686
+ tokenCache
687
+ });
688
+ const token = await tokenManager.getToken();
689
+ io.stderr("Generated new access token and updated the token cache.\n");
690
+ return token;
691
+ }
692
+ async function readFreshCachedToken(tokenCache) {
693
+ const cached = await tokenCache.read().catch(() => null);
694
+ if (!cached || Date.now() >= cached.expiresAt) return void 0;
695
+ return cached.accessToken;
696
+ }
740
697
  async function loginCommand(options, env, io, deps) {
741
698
  const auth = await resolveAuthConfig(options, env, deps);
742
699
  const tokenCachePath = resolveTokenCachePath(options.tokenCache, getCwd(deps));
743
700
  const tokenCache = createFileTokenCache(tokenCachePath);
701
+ const hadFreshCachedToken = Boolean(await readFreshCachedToken(tokenCache));
744
702
  const tokenManager = new TokenManager({
745
703
  clientId: auth.clientId,
746
704
  clientSecret: auth.clientSecret,
@@ -751,6 +709,9 @@ async function loginCommand(options, env, io, deps) {
751
709
  });
752
710
  await tokenManager.getToken();
753
711
  const cached = await tokenCache.read();
712
+ if (!hadFreshCachedToken) {
713
+ io.stdout("Generated new access token and updated the token cache.\n");
714
+ }
754
715
  io.stdout(`Logged in. Token cached at ${tokenCachePath}
755
716
  `);
756
717
  if (cached) io.stdout(formatTokenExpiry(cached.expiresAt));
@@ -789,7 +750,7 @@ async function configInitCommand(options, io, deps) {
789
750
  try {
790
751
  const config = {
791
752
  apiBaseUrl: await prompt("API base URL", {
792
- defaultValue: existing.apiBaseUrl ?? DEFAULTS.apiBaseUrl
753
+ defaultValue: existing.apiBaseUrl
793
754
  }),
794
755
  tokenEndpoint: await prompt("OAuth2 token endpoint", {
795
756
  defaultValue: existing.tokenEndpoint ?? DEFAULTS.tokenEndpoint
@@ -797,9 +758,6 @@ async function configInitCommand(options, io, deps) {
797
758
  scope: await prompt("OAuth2 scope", {
798
759
  defaultValue: existing.scope ?? DEFAULTS.scope
799
760
  }),
800
- organisation: await prompt("Organisation", {
801
- defaultValue: existing.organisation
802
- }),
803
761
  clientId: await prompt("Client id", {
804
762
  defaultValue: existing.clientId
805
763
  }),
@@ -848,7 +806,6 @@ function normalizeCliConfig(config) {
848
806
  apiBaseUrl: stringValue(config.apiBaseUrl),
849
807
  tokenEndpoint: stringValue(config.tokenEndpoint),
850
808
  scope: stringValue(config.scope),
851
- organisation: stringValue(config.organisation),
852
809
  clientId: stringValue(config.clientId),
853
810
  clientSecret: stringValue(config.clientSecret)
854
811
  };
@@ -924,6 +881,9 @@ function formatPrompt(label, options) {
924
881
  const defaultHint = options.defaultValue && !options.secret ? ` [${options.defaultValue}]` : options.defaultValue && options.secret ? " [current value hidden]" : "";
925
882
  return `${label}${defaultHint}: `;
926
883
  }
884
+ function formatElapsedSeconds(elapsedMs) {
885
+ return `${Math.floor(elapsedMs / 1e3)}s`;
886
+ }
927
887
  async function readStdinLines() {
928
888
  let input = "";
929
889
  for await (const chunk of process.stdin) input += String(chunk);