@fairmint/captable-ai 0.1.25 → 0.1.26
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/dist/index.js +587 -142
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15483,6 +15483,344 @@ function buildClassificationTypes() {
|
|
|
15483
15483
|
}
|
|
15484
15484
|
var CLASSIFICATION_TYPES_LIST = buildClassificationTypes();
|
|
15485
15485
|
|
|
15486
|
+
// ../captable-ai-core/dist/extraction-readiness.js
|
|
15487
|
+
var STATUS_TO_CATEGORY = {
|
|
15488
|
+
PENDING_UPLOAD: "pendingUpload",
|
|
15489
|
+
NEW: "preclassification",
|
|
15490
|
+
EXTRACTION: "preclassification",
|
|
15491
|
+
CLASSIFICATION: "preclassification",
|
|
15492
|
+
CLASSIFIED: "classified",
|
|
15493
|
+
OCF_EXTRACTION: "extracting",
|
|
15494
|
+
CREATION: "extracting",
|
|
15495
|
+
VALIDATION: "validating",
|
|
15496
|
+
FINALIZATION: "validating",
|
|
15497
|
+
PROCESSED: "complete",
|
|
15498
|
+
COMMITTED: "complete",
|
|
15499
|
+
FAILED: "failed",
|
|
15500
|
+
CANCELLED: "failed"
|
|
15501
|
+
};
|
|
15502
|
+
function categorizeDocumentStatus(status) {
|
|
15503
|
+
if (typeof status !== "string")
|
|
15504
|
+
return "unknown";
|
|
15505
|
+
return STATUS_TO_CATEGORY[status.trim().toUpperCase()] ?? "unknown";
|
|
15506
|
+
}
|
|
15507
|
+
var extractionReadinessSummarySchema = external_exports.object({
|
|
15508
|
+
total: external_exports.number().int().nonnegative().describe("Documents in scope."),
|
|
15509
|
+
pendingUpload: external_exports.number().int().nonnegative().describe("Documents whose upload never completed (PENDING_UPLOAD)."),
|
|
15510
|
+
preclassification: external_exports.number().int().nonnegative().describe("Documents still in OCR/classification (NEW, EXTRACTION, CLASSIFICATION)."),
|
|
15511
|
+
classified: external_exports.number().int().nonnegative().describe("Documents classified and waiting for an extraction trigger."),
|
|
15512
|
+
extracting: external_exports.number().int().nonnegative().describe("Documents in OCF extraction (OCF_EXTRACTION, CREATION)."),
|
|
15513
|
+
validating: external_exports.number().int().nonnegative().describe("Documents waiting on portal validation (VALIDATION, FINALIZATION)."),
|
|
15514
|
+
complete: external_exports.number().int().nonnegative().describe("Documents PROCESSED or COMMITTED."),
|
|
15515
|
+
failed: external_exports.number().int().nonnegative().describe("Documents FAILED or CANCELLED."),
|
|
15516
|
+
unknown: external_exports.number().int().nonnegative().describe("Documents with an unrecognized status."),
|
|
15517
|
+
processing: external_exports.number().int().nonnegative().describe("Backward-compatible total of preclassification + extracting + validating + unknown.")
|
|
15518
|
+
});
|
|
15519
|
+
function summarizeExtractionReadiness(documents) {
|
|
15520
|
+
const counts = {
|
|
15521
|
+
pendingUpload: 0,
|
|
15522
|
+
preclassification: 0,
|
|
15523
|
+
classified: 0,
|
|
15524
|
+
extracting: 0,
|
|
15525
|
+
validating: 0,
|
|
15526
|
+
complete: 0,
|
|
15527
|
+
failed: 0,
|
|
15528
|
+
unknown: 0
|
|
15529
|
+
};
|
|
15530
|
+
for (const document of documents) {
|
|
15531
|
+
counts[categorizeDocumentStatus(document.status)] += 1;
|
|
15532
|
+
}
|
|
15533
|
+
return {
|
|
15534
|
+
total: documents.length,
|
|
15535
|
+
...counts,
|
|
15536
|
+
processing: counts.preclassification + counts.extracting + counts.validating + counts.unknown
|
|
15537
|
+
};
|
|
15538
|
+
}
|
|
15539
|
+
function deriveExtractionReadinessPhase(summary) {
|
|
15540
|
+
if (summary.total === 0)
|
|
15541
|
+
return "empty";
|
|
15542
|
+
if (summary.pendingUpload > 0)
|
|
15543
|
+
return "awaiting_upload";
|
|
15544
|
+
if (summary.preclassification > 0)
|
|
15545
|
+
return "classifying";
|
|
15546
|
+
if (summary.classified > 0)
|
|
15547
|
+
return "ready_to_extract";
|
|
15548
|
+
if (summary.extracting > 0 || summary.validating > 0 || summary.unknown > 0) {
|
|
15549
|
+
return "in_progress";
|
|
15550
|
+
}
|
|
15551
|
+
if (summary.failed > 0)
|
|
15552
|
+
return "failed";
|
|
15553
|
+
return "complete";
|
|
15554
|
+
}
|
|
15555
|
+
var IMPORT_SESSION_CLOCK_SKEW_MS = 5e3;
|
|
15556
|
+
function documentsForImportSession(documents, sessionCreatedAt) {
|
|
15557
|
+
if (typeof sessionCreatedAt !== "string")
|
|
15558
|
+
return [];
|
|
15559
|
+
const sessionMs = parseTimestampMs(sessionCreatedAt);
|
|
15560
|
+
if (sessionMs === null)
|
|
15561
|
+
return [];
|
|
15562
|
+
const cutoffMs = sessionMs - IMPORT_SESSION_CLOCK_SKEW_MS;
|
|
15563
|
+
return documents.filter((document) => {
|
|
15564
|
+
if (typeof document.createdAt !== "string")
|
|
15565
|
+
return false;
|
|
15566
|
+
const createdMs = parseTimestampMs(document.createdAt);
|
|
15567
|
+
return createdMs !== null && createdMs >= cutoffMs;
|
|
15568
|
+
});
|
|
15569
|
+
}
|
|
15570
|
+
function importSessionScopeUnknown(documents) {
|
|
15571
|
+
return documents.length > 0 && documents.every((document) => typeof document.createdAt !== "string" || parseTimestampMs(document.createdAt) === null);
|
|
15572
|
+
}
|
|
15573
|
+
var HAS_ZONE = /(?:Z|[+-]\d{2}(?::?\d{2})?)$/i;
|
|
15574
|
+
function parseTimestampMs(value) {
|
|
15575
|
+
let normalized = value.trim().replace(" ", "T").replace(/([+-]\d{2})$/, "$1:00");
|
|
15576
|
+
if (!HAS_ZONE.test(normalized))
|
|
15577
|
+
normalized += "Z";
|
|
15578
|
+
const parsed = Date.parse(normalized);
|
|
15579
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
15580
|
+
}
|
|
15581
|
+
var TRIGGER_EXTRACTION_MAX_IDS = 50;
|
|
15582
|
+
function idsInCategory(documents, category, predicate = () => true) {
|
|
15583
|
+
return documents.flatMap((document) => categorizeDocumentStatus(document.status) === category && typeof document.s3AssetId === "string" && predicate(document) ? [document.s3AssetId] : []);
|
|
15584
|
+
}
|
|
15585
|
+
var isRetryableFailure = (document) => (document.status ?? "").trim().toUpperCase() === "FAILED";
|
|
15586
|
+
function sessionTriggerScope(documents) {
|
|
15587
|
+
const eligible = documents.filter((document) => {
|
|
15588
|
+
const category = categorizeDocumentStatus(document.status);
|
|
15589
|
+
return category === "classified" || category === "failed" && isRetryableFailure(document);
|
|
15590
|
+
});
|
|
15591
|
+
const ids = eligible.flatMap((document) => typeof document.s3AssetId === "string" ? [document.s3AssetId] : []);
|
|
15592
|
+
if (ids.length === 0 || ids.length !== eligible.length) {
|
|
15593
|
+
return { kind: "unsafe", reason: "missing_ids" };
|
|
15594
|
+
}
|
|
15595
|
+
if (ids.length > TRIGGER_EXTRACTION_MAX_IDS)
|
|
15596
|
+
return { kind: "unsafe", reason: "too_many_ids" };
|
|
15597
|
+
const includesFailed = eligible.some((document) => categorizeDocumentStatus(document.status) === "failed");
|
|
15598
|
+
return { kind: "explicit", ids, includesFailed };
|
|
15599
|
+
}
|
|
15600
|
+
var BACKGROUND_MONITORING_SUGGESTION = "Processing can take several minutes. If you can run subagents or background tasks, delegate a monitor that re-checks document status every 30-60 seconds for up to 30 minutes. When the phase changes it may run only the returned nextActions, exactly as scoped (for example, trigger extraction with the listed s3AssetIds once every document is classified; never widen the scope to the whole company). It stops and reports back when the import is ready for review, needs the user (failures, uploads, scope questions), or the time box ends. Otherwise, check again periodically and tell the user if the import is still processing after about 30 minutes.";
|
|
15601
|
+
function shouldSuggestBackgroundMonitoring(phase) {
|
|
15602
|
+
switch (phase) {
|
|
15603
|
+
case "classifying":
|
|
15604
|
+
case "in_progress":
|
|
15605
|
+
return true;
|
|
15606
|
+
case "empty":
|
|
15607
|
+
case "awaiting_upload":
|
|
15608
|
+
case "ready_to_extract":
|
|
15609
|
+
case "failed":
|
|
15610
|
+
case "complete":
|
|
15611
|
+
return false;
|
|
15612
|
+
default: {
|
|
15613
|
+
const unhandled = phase;
|
|
15614
|
+
throw new Error(`Unhandled extraction readiness phase: ${String(unhandled)}`);
|
|
15615
|
+
}
|
|
15616
|
+
}
|
|
15617
|
+
}
|
|
15618
|
+
function reviewFailuresAction(documents) {
|
|
15619
|
+
const statusSet = /* @__PURE__ */ new Set();
|
|
15620
|
+
const ids = [];
|
|
15621
|
+
for (const document of documents) {
|
|
15622
|
+
if (categorizeDocumentStatus(document.status) !== "failed")
|
|
15623
|
+
continue;
|
|
15624
|
+
statusSet.add((document.status ?? "").trim().toUpperCase());
|
|
15625
|
+
if (typeof document.s3AssetId === "string")
|
|
15626
|
+
ids.push(document.s3AssetId);
|
|
15627
|
+
}
|
|
15628
|
+
const statuses = [...statusSet];
|
|
15629
|
+
return {
|
|
15630
|
+
id: "review_failures",
|
|
15631
|
+
label: "Review failed documents",
|
|
15632
|
+
required: true,
|
|
15633
|
+
...ids.length > 0 && { s3AssetIds: ids },
|
|
15634
|
+
...statuses.length === 1 && { statusFilter: statuses[0] }
|
|
15635
|
+
};
|
|
15636
|
+
}
|
|
15637
|
+
function triggerOrResolve(trigger, scope) {
|
|
15638
|
+
switch (scope.kind) {
|
|
15639
|
+
case "explicit":
|
|
15640
|
+
return {
|
|
15641
|
+
...trigger,
|
|
15642
|
+
...scope.includesFailed && {
|
|
15643
|
+
label: "Start OCF extraction for classified documents and retry failed ones"
|
|
15644
|
+
},
|
|
15645
|
+
s3AssetIds: scope.ids
|
|
15646
|
+
};
|
|
15647
|
+
case "unsafe":
|
|
15648
|
+
return {
|
|
15649
|
+
id: "resolve_extraction_scope",
|
|
15650
|
+
label: scope.reason === "too_many_ids" ? `This import has more classified documents than one scoped trigger accepts (${TRIGGER_EXTRACTION_MAX_IDS}), so extraction cannot be started safely for this import alone. Do not trigger extraction for the whole company on this import's behalf; ask the user how to proceed after reviewing the company's documents.` : "Some classified documents in this import have no document ID, so extraction cannot be scoped to this import. Review the company's documents, then check again.",
|
|
15651
|
+
required: trigger.required
|
|
15652
|
+
};
|
|
15653
|
+
default: {
|
|
15654
|
+
const unhandled = scope;
|
|
15655
|
+
throw new Error(`Unhandled trigger scope: ${String(unhandled)}`);
|
|
15656
|
+
}
|
|
15657
|
+
}
|
|
15658
|
+
}
|
|
15659
|
+
function stallRecoveryActions(documents) {
|
|
15660
|
+
const isStalled = (document) => document.health === "stalled";
|
|
15661
|
+
const isStalledOcr = (document) => isStalled(document) && (document.status ?? "").trim().toUpperCase() === "EXTRACTION";
|
|
15662
|
+
const stalledRestartable = [
|
|
15663
|
+
...idsInCategory(documents, "preclassification", isStalledOcr),
|
|
15664
|
+
...idsInCategory(documents, "extracting", isStalled)
|
|
15665
|
+
];
|
|
15666
|
+
const actions = [];
|
|
15667
|
+
if (stalledRestartable.length > TRIGGER_EXTRACTION_MAX_IDS) {
|
|
15668
|
+
actions.push({
|
|
15669
|
+
id: "resolve_extraction_scope",
|
|
15670
|
+
label: `${stalledRestartable.length} documents in this import look stalled, more than one scoped restart accepts (${TRIGGER_EXTRACTION_MAX_IDS}). Do not restart extraction for the whole company on this import's behalf; ask the user how to proceed after reviewing the company's documents.`,
|
|
15671
|
+
required: false
|
|
15672
|
+
});
|
|
15673
|
+
} else if (stalledRestartable.length > 0) {
|
|
15674
|
+
actions.push({
|
|
15675
|
+
id: "restart_extraction",
|
|
15676
|
+
label: "Restart the stalled extraction",
|
|
15677
|
+
required: false,
|
|
15678
|
+
s3AssetIds: stalledRestartable
|
|
15679
|
+
});
|
|
15680
|
+
}
|
|
15681
|
+
return actions;
|
|
15682
|
+
}
|
|
15683
|
+
function planImportStatus({ documents, sessionState, scopeUnknown = false }) {
|
|
15684
|
+
const summary = summarizeExtractionReadiness(documents);
|
|
15685
|
+
const phase = deriveExtractionReadinessPhase(summary);
|
|
15686
|
+
if (scopeUnknown && phase === "empty") {
|
|
15687
|
+
return {
|
|
15688
|
+
phase,
|
|
15689
|
+
state: "processing",
|
|
15690
|
+
summary,
|
|
15691
|
+
shouldComplete: false,
|
|
15692
|
+
actions: [
|
|
15693
|
+
{
|
|
15694
|
+
id: "check_status",
|
|
15695
|
+
label: "This server does not report document creation times, so this import's documents can't be identified. Do not re-upload; check the company's documents, or retry after the server is updated.",
|
|
15696
|
+
required: true
|
|
15697
|
+
}
|
|
15698
|
+
]
|
|
15699
|
+
};
|
|
15700
|
+
}
|
|
15701
|
+
const upload = {
|
|
15702
|
+
id: "upload_documents",
|
|
15703
|
+
label: "Upload the documents",
|
|
15704
|
+
required: true
|
|
15705
|
+
};
|
|
15706
|
+
const checkStatus = {
|
|
15707
|
+
id: "check_status",
|
|
15708
|
+
label: "Check import progress again",
|
|
15709
|
+
required: true
|
|
15710
|
+
};
|
|
15711
|
+
const trigger = {
|
|
15712
|
+
id: "trigger_extraction",
|
|
15713
|
+
label: "Start OCF extraction for classified documents",
|
|
15714
|
+
required: true
|
|
15715
|
+
};
|
|
15716
|
+
const triggerScope = sessionTriggerScope(documents);
|
|
15717
|
+
switch (phase) {
|
|
15718
|
+
case "empty":
|
|
15719
|
+
return { phase, state: "ready_to_import", summary, shouldComplete: false, actions: [upload] };
|
|
15720
|
+
case "awaiting_upload":
|
|
15721
|
+
return {
|
|
15722
|
+
phase,
|
|
15723
|
+
state: "ready_to_import",
|
|
15724
|
+
summary,
|
|
15725
|
+
shouldComplete: false,
|
|
15726
|
+
actions: [
|
|
15727
|
+
{
|
|
15728
|
+
...upload,
|
|
15729
|
+
label: "Re-upload (or delete) the documents that did not finish uploading"
|
|
15730
|
+
}
|
|
15731
|
+
]
|
|
15732
|
+
};
|
|
15733
|
+
case "classifying":
|
|
15734
|
+
return {
|
|
15735
|
+
phase,
|
|
15736
|
+
state: "processing",
|
|
15737
|
+
summary,
|
|
15738
|
+
shouldComplete: false,
|
|
15739
|
+
actions: [
|
|
15740
|
+
{ ...checkStatus, label: "Wait for classification, then check again" },
|
|
15741
|
+
...stallRecoveryActions(documents)
|
|
15742
|
+
]
|
|
15743
|
+
};
|
|
15744
|
+
case "ready_to_extract":
|
|
15745
|
+
return {
|
|
15746
|
+
phase,
|
|
15747
|
+
state: "ready_to_import",
|
|
15748
|
+
summary,
|
|
15749
|
+
shouldComplete: false,
|
|
15750
|
+
actions: [
|
|
15751
|
+
triggerOrResolve(trigger, triggerScope),
|
|
15752
|
+
// CLASSIFIED documents take priority over in-flight ones in the
|
|
15753
|
+
// phase, so a stall elsewhere in the import would otherwise get no
|
|
15754
|
+
// recovery action: triggering only queues CLASSIFIED/FAILED docs.
|
|
15755
|
+
...stallRecoveryActions(documents)
|
|
15756
|
+
]
|
|
15757
|
+
};
|
|
15758
|
+
case "in_progress":
|
|
15759
|
+
return {
|
|
15760
|
+
phase,
|
|
15761
|
+
state: "processing",
|
|
15762
|
+
summary,
|
|
15763
|
+
shouldComplete: false,
|
|
15764
|
+
actions: [
|
|
15765
|
+
checkStatus,
|
|
15766
|
+
...stallRecoveryActions(documents),
|
|
15767
|
+
// Surface failures now rather than after the last in-flight document
|
|
15768
|
+
// settles; optional because processing is still moving.
|
|
15769
|
+
...summary.failed > 0 ? [{ ...reviewFailuresAction(documents), required: false }] : []
|
|
15770
|
+
]
|
|
15771
|
+
};
|
|
15772
|
+
case "failed":
|
|
15773
|
+
return {
|
|
15774
|
+
phase,
|
|
15775
|
+
state: "blocked",
|
|
15776
|
+
summary,
|
|
15777
|
+
shouldComplete: false,
|
|
15778
|
+
actions: [reviewFailuresAction(documents)]
|
|
15779
|
+
};
|
|
15780
|
+
case "complete":
|
|
15781
|
+
return {
|
|
15782
|
+
phase,
|
|
15783
|
+
state: "needs_review",
|
|
15784
|
+
summary,
|
|
15785
|
+
shouldComplete: sessionState !== "completed",
|
|
15786
|
+
actions: [
|
|
15787
|
+
{ id: "review_drafts", label: "Review extracted cap table drafts", required: true },
|
|
15788
|
+
{ id: "preview_commit", label: "Preview the first commit", required: true },
|
|
15789
|
+
{ id: "confirm_commit", label: "Commit reviewed drafts", required: true }
|
|
15790
|
+
]
|
|
15791
|
+
};
|
|
15792
|
+
default: {
|
|
15793
|
+
const unhandled = phase;
|
|
15794
|
+
throw new Error(`Unhandled extraction readiness phase: ${String(unhandled)}`);
|
|
15795
|
+
}
|
|
15796
|
+
}
|
|
15797
|
+
}
|
|
15798
|
+
var EXTRACTION_NOT_READY_REASONS = [
|
|
15799
|
+
"pending_upload",
|
|
15800
|
+
"classification_in_progress"
|
|
15801
|
+
];
|
|
15802
|
+
var TRIGGER_EXTRACTION_OUTCOMES = [
|
|
15803
|
+
"queued",
|
|
15804
|
+
"partially_queued",
|
|
15805
|
+
"queue_failed",
|
|
15806
|
+
"not_ready",
|
|
15807
|
+
"no_eligible_documents"
|
|
15808
|
+
];
|
|
15809
|
+
function resolveTriggerExtractionOutcome(result) {
|
|
15810
|
+
const declared = TRIGGER_EXTRACTION_OUTCOMES.find((value) => value === result.outcome);
|
|
15811
|
+
if (declared)
|
|
15812
|
+
return declared;
|
|
15813
|
+
const queued = result.queuedCount ?? 0;
|
|
15814
|
+
const failed = result.failedCount ?? 0;
|
|
15815
|
+
if (queued > 0)
|
|
15816
|
+
return failed > 0 ? "partially_queued" : "queued";
|
|
15817
|
+
if (failed > 0)
|
|
15818
|
+
return "queue_failed";
|
|
15819
|
+
if (result.notReadyDocuments?.some((d) => d.blocking === true))
|
|
15820
|
+
return "not_ready";
|
|
15821
|
+
return "no_eligible_documents";
|
|
15822
|
+
}
|
|
15823
|
+
|
|
15486
15824
|
// ../captable-ai-core/dist/documents.js
|
|
15487
15825
|
var documentPrepareFileSchema = external_exports.object({
|
|
15488
15826
|
name: external_exports.string().min(1).describe("Original filename."),
|
|
@@ -15563,16 +15901,31 @@ var documentContextResultSchema = external_exports.object({
|
|
|
15563
15901
|
existing: external_exports.record(external_exports.string(), external_exports.unknown()).describe("Compact existing portal context. Empty when includeExisting=false.")
|
|
15564
15902
|
});
|
|
15565
15903
|
var triggerExtractionInputSchema = external_exports.object({
|
|
15566
|
-
s3AssetIds: external_exports.array(external_exports.string().uuid()).min(1).max(
|
|
15904
|
+
s3AssetIds: external_exports.array(external_exports.string().uuid()).min(1).max(TRIGGER_EXTRACTION_MAX_IDS).optional().describe(`Optional filter to specific document IDs (1-${TRIGGER_EXTRACTION_MAX_IDS}).`)
|
|
15567
15905
|
});
|
|
15568
15906
|
var triggerExtractionDocSchema = external_exports.object({
|
|
15569
15907
|
s3AssetId: external_exports.string().describe("S3 asset ID."),
|
|
15570
15908
|
date: external_exports.string().nullable().describe("Document date.")
|
|
15571
15909
|
});
|
|
15910
|
+
var triggerExtractionNotReadyDocSchema = external_exports.object({
|
|
15911
|
+
s3AssetId: external_exports.string().describe("S3 asset ID."),
|
|
15912
|
+
status: external_exports.string().describe("Current document status."),
|
|
15913
|
+
reason: external_exports.enum(EXTRACTION_NOT_READY_REASONS).describe("pending_upload: upload never completed, re-upload or delete it. classification_in_progress: OCR/classification still running, wait and retry."),
|
|
15914
|
+
blocking: external_exports.boolean().describe("True when this document prevented the batch from being queued.")
|
|
15915
|
+
});
|
|
15916
|
+
var triggerExtractionSkippedDocSchema = external_exports.object({
|
|
15917
|
+
s3AssetId: external_exports.string().describe("S3 asset ID."),
|
|
15918
|
+
status: external_exports.string().describe("Current document status (already extracting, validated, etc.).")
|
|
15919
|
+
});
|
|
15572
15920
|
var triggerExtractionResultSchema = external_exports.object({
|
|
15921
|
+
outcome: external_exports.enum(TRIGGER_EXTRACTION_OUTCOMES).describe("queued | partially_queued | queue_failed | not_ready (nothing queued because in-scope documents are still classifying or uploading) | no_eligible_documents."),
|
|
15573
15922
|
queuedCount: external_exports.number().describe("Number of documents queued for extraction."),
|
|
15574
15923
|
failedCount: external_exports.number().describe("Number of documents that failed to queue."),
|
|
15575
|
-
documents: external_exports.array(triggerExtractionDocSchema).describe("Documents
|
|
15924
|
+
documents: external_exports.array(triggerExtractionDocSchema).describe("Documents queued, in queue order."),
|
|
15925
|
+
notReadyCount: external_exports.number().int().nonnegative().describe("Number of in-scope documents not ready for extraction."),
|
|
15926
|
+
notReadyDocuments: external_exports.array(triggerExtractionNotReadyDocSchema).describe("In-scope documents not ready for extraction."),
|
|
15927
|
+
skippedDocuments: external_exports.array(triggerExtractionSkippedDocSchema).describe("Explicitly requested documents that are not in an extractable status."),
|
|
15928
|
+
missingIds: external_exports.array(external_exports.string()).describe("Explicitly requested document IDs not found in this portal.")
|
|
15576
15929
|
});
|
|
15577
15930
|
var cancelExtractionInputSchema = external_exports.object({
|
|
15578
15931
|
documentId: external_exports.string().uuid().describe("The s3_asset_id of the document to cancel.")
|
|
@@ -15615,6 +15968,7 @@ var DOCUMENT_HEALTH_VALUES = [
|
|
|
15615
15968
|
var documentProcessingStatusItemSchema = external_exports.object({
|
|
15616
15969
|
s3AssetId: external_exports.string().describe("S3 asset / document ID."),
|
|
15617
15970
|
status: external_exports.string().describe("Document processing status."),
|
|
15971
|
+
createdAt: external_exports.string().nullable().optional().describe("Immutable document creation timestamp; absent on older servers."),
|
|
15618
15972
|
lastActivityAt: external_exports.string().nullable().describe("Last backend activity timestamp (updated_at / heartbeat); null if unknown."),
|
|
15619
15973
|
queuePosition: external_exports.number().int().min(0).nullable().describe("0 = at the head (processing); >0 = waiting behind others; null = not in pipeline."),
|
|
15620
15974
|
health: external_exports.enum(DOCUMENT_HEALTH_VALUES).describe("Derived health from backend heartbeat + queue position.")
|
|
@@ -17547,7 +17901,7 @@ var COMMAND_REGISTRY = [
|
|
|
17547
17901
|
{
|
|
17548
17902
|
name: "import status",
|
|
17549
17903
|
group: "onboarding",
|
|
17550
|
-
description: "Show import
|
|
17904
|
+
description: "Show import progress by phase (uploading, classifying, ready to extract, extracting, validating) and the next action. Classified documents are actionable even while others validate; unfinished uploads need a re-upload, not more polling.",
|
|
17551
17905
|
usage: "captable-ai import status <session-id> [--json]",
|
|
17552
17906
|
requiresAuth: true,
|
|
17553
17907
|
requiresPortal: false
|
|
@@ -17932,7 +18286,7 @@ var COMMAND_REGISTRY = [
|
|
|
17932
18286
|
{
|
|
17933
18287
|
name: "documents trigger-extraction",
|
|
17934
18288
|
group: "captable",
|
|
17935
|
-
description: "Trigger OCF extraction for classified documents.
|
|
18289
|
+
description: "Trigger OCF extraction for classified documents in chronological order. Optionally filter to specific document IDs. Queues nothing (outcome not_ready, exit 5) while a document in scope is still classifying or a requested document never finished uploading; wait or re-upload, then retry. Only queued batches are tracked as jobs.",
|
|
17936
18290
|
usage: "captable-ai documents trigger-extraction [--s3-asset-ids ID1,ID2,...] [--json]",
|
|
17937
18291
|
requiresAuth: true,
|
|
17938
18292
|
requiresPortal: true,
|
|
@@ -18824,11 +19178,11 @@ async function runCommit(args, rawArgv) {
|
|
|
18824
19178
|
process.exit(0);
|
|
18825
19179
|
}
|
|
18826
19180
|
validateFlags(args, { flags: ["objects"], booleans: ["all", "dry-run"] });
|
|
18827
|
-
const
|
|
19181
|
+
const USAGE2 = "captable-ai commit --dry-run [--objects id1,id2,...] [--json]\ncaptable-ai commit (--objects id1,id2,... | --all) [--json]";
|
|
18828
19182
|
if (args.positional.length > 0) {
|
|
18829
19183
|
exitWithUsage(
|
|
18830
19184
|
`Unexpected argument: ${args.positional[0]}. This command takes no positional arguments.`,
|
|
18831
|
-
|
|
19185
|
+
USAGE2
|
|
18832
19186
|
);
|
|
18833
19187
|
}
|
|
18834
19188
|
const dryRun = hasFlag(args, "dry-run");
|
|
@@ -18837,10 +19191,10 @@ async function runCommit(args, rawArgv) {
|
|
|
18837
19191
|
const objectIds = parseObjectIdsList(objectIdsRaw);
|
|
18838
19192
|
const hasExplicitObjectIds = hasObjectIds(objectIdsRaw, objectIds);
|
|
18839
19193
|
if (objectIdsRaw !== void 0 && !hasExplicitObjectIds) {
|
|
18840
|
-
exitWithUsage("The --objects flag must include at least one object ID.",
|
|
19194
|
+
exitWithUsage("The --objects flag must include at least one object ID.", USAGE2);
|
|
18841
19195
|
}
|
|
18842
19196
|
if (all && objectIds) {
|
|
18843
|
-
exitWithUsage("Specify either --objects or --all, not both.",
|
|
19197
|
+
exitWithUsage("Specify either --objects or --all, not both.", USAGE2);
|
|
18844
19198
|
}
|
|
18845
19199
|
if (dryRun) {
|
|
18846
19200
|
const portalId2 = await getActivePortalId();
|
|
@@ -18867,7 +19221,7 @@ async function runCommit(args, rawArgv) {
|
|
|
18867
19221
|
{ objectIdsLabel: "--objects id1,id2,...", allDraftsLabel: "--all" }
|
|
18868
19222
|
);
|
|
18869
19223
|
if (!selection.ok) {
|
|
18870
|
-
exitWithUsage(`${selection.message} Preview first with --dry-run.`,
|
|
19224
|
+
exitWithUsage(`${selection.message} Preview first with --dry-run.`, USAGE2);
|
|
18871
19225
|
}
|
|
18872
19226
|
const portalId = await getActivePortalId();
|
|
18873
19227
|
if (!isJsonMode()) {
|
|
@@ -19715,81 +20069,174 @@ async function atomicWriteJobs(content) {
|
|
|
19715
20069
|
}
|
|
19716
20070
|
|
|
19717
20071
|
// src/commands/documents-trigger-extraction.ts
|
|
20072
|
+
var USAGE = "Usage: captable-ai documents trigger-extraction [--s3-asset-ids ID1,ID2,...] [--json]";
|
|
20073
|
+
var HELP = `documents trigger-extraction \u2014 Trigger OCF extraction
|
|
20074
|
+
|
|
20075
|
+
${USAGE}
|
|
20076
|
+
|
|
20077
|
+
Queues CLASSIFIED (and previously FAILED) documents in chronological order.
|
|
20078
|
+
Nothing is queued while a document in scope is still being classified, or an
|
|
20079
|
+
explicitly requested document never finished uploading; the command then exits
|
|
20080
|
+
with code 5 (outcome not_ready) and lists those documents. Wait for classification
|
|
20081
|
+
(captable-ai documents processing-status) or re-upload, then retry.
|
|
20082
|
+
`;
|
|
20083
|
+
function normalize(data) {
|
|
20084
|
+
const d = data ?? {};
|
|
20085
|
+
return { ...d, outcome: resolveTriggerExtractionOutcome(d) };
|
|
20086
|
+
}
|
|
19718
20087
|
async function runDocumentsTriggerExtraction(args, rawArgv) {
|
|
19719
20088
|
if (rawArgv.includes("--help") || rawArgv.includes("-h")) {
|
|
19720
|
-
process.stdout.write(
|
|
19721
|
-
"documents trigger-extraction \u2014 Trigger OCF extraction\n\nUsage: captable-ai documents trigger-extraction [--s3-asset-ids ID1,ID2,...] [--json]\n"
|
|
19722
|
-
);
|
|
20089
|
+
process.stdout.write(HELP);
|
|
19723
20090
|
process.exit(0);
|
|
19724
20091
|
}
|
|
19725
20092
|
validateFlags(args, { flags: ["s3-asset-ids"], booleans: [] });
|
|
19726
20093
|
if (args.positional.length > 0) {
|
|
19727
20094
|
exitWithUsage(
|
|
19728
20095
|
"Unexpected positional argument(s). Use --s3-asset-ids to filter specific documents.",
|
|
19729
|
-
|
|
20096
|
+
USAGE
|
|
19730
20097
|
);
|
|
19731
20098
|
}
|
|
19732
20099
|
const portalId = await getActivePortalId();
|
|
19733
20100
|
const body = {};
|
|
19734
20101
|
const rawIds = getFlagValue(args, "s3-asset-ids");
|
|
19735
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
20102
|
+
const requestedIds = rawIds === void 0 ? [] : rawIds.split(",").map((id) => id.trim()).filter(Boolean);
|
|
20103
|
+
if (rawIds !== void 0 && requestedIds.length === 0) {
|
|
20104
|
+
exitWithUsage("--s3-asset-ids needs at least one document ID.", USAGE);
|
|
19738
20105
|
}
|
|
19739
|
-
|
|
19740
|
-
|
|
19741
|
-
|
|
19742
|
-
|
|
19743
|
-
|
|
20106
|
+
if (requestedIds.length > 0) body.s3AssetIds = requestedIds;
|
|
20107
|
+
const retryCommand = "captable-ai documents trigger-extraction" + (requestedIds.length > 0 ? ` --s3-asset-ids ${requestedIds.join(",")}` : "");
|
|
20108
|
+
const res = await apiRequest(`/${portalId}/documents/trigger-extraction`, {
|
|
20109
|
+
method: "POST",
|
|
20110
|
+
body
|
|
20111
|
+
});
|
|
20112
|
+
const d = normalize(res.data);
|
|
20113
|
+
const queuedIds = d.documents?.map((doc) => doc.s3AssetId) ?? [];
|
|
20114
|
+
const notReady = d.notReadyDocuments ?? [];
|
|
20115
|
+
const blocking = notReady.filter((doc) => doc.blocking);
|
|
20116
|
+
let jobId = null;
|
|
20117
|
+
let trackerFailed = false;
|
|
20118
|
+
if (queuedIds.length > 0) {
|
|
20119
|
+
jobId = generateJobId();
|
|
20120
|
+
try {
|
|
20121
|
+
await appendJob({
|
|
20122
|
+
id: jobId,
|
|
20123
|
+
kind: "extraction",
|
|
20124
|
+
portalId,
|
|
20125
|
+
s3AssetIds: queuedIds,
|
|
20126
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20127
|
+
status: "submitted",
|
|
20128
|
+
lastCheckedAt: null,
|
|
20129
|
+
completedAt: null,
|
|
20130
|
+
result: null
|
|
20131
|
+
});
|
|
20132
|
+
} catch (trackErr) {
|
|
20133
|
+
trackerFailed = true;
|
|
20134
|
+
jobId = null;
|
|
20135
|
+
printWarning(
|
|
20136
|
+
`Job tracker write failed: ${trackErr instanceof Error ? trackErr.message : String(trackErr)}`
|
|
20137
|
+
);
|
|
19744
20138
|
}
|
|
19745
|
-
);
|
|
19746
|
-
const d = res.data ?? { queuedCount: 0, failedCount: 0, documents: [] };
|
|
19747
|
-
const jobId = generateJobId();
|
|
19748
|
-
const s3AssetIds = d.documents?.map((doc) => doc.s3AssetId) ?? [];
|
|
19749
|
-
let tracked = true;
|
|
19750
|
-
try {
|
|
19751
|
-
await appendJob({
|
|
19752
|
-
id: jobId,
|
|
19753
|
-
kind: "extraction",
|
|
19754
|
-
portalId,
|
|
19755
|
-
s3AssetIds,
|
|
19756
|
-
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19757
|
-
status: d.queuedCount > 0 ? "submitted" : "failed",
|
|
19758
|
-
lastCheckedAt: null,
|
|
19759
|
-
completedAt: d.queuedCount === 0 ? (/* @__PURE__ */ new Date()).toISOString() : null,
|
|
19760
|
-
result: null
|
|
19761
|
-
});
|
|
19762
|
-
} catch (trackErr) {
|
|
19763
|
-
tracked = false;
|
|
19764
|
-
printWarning(
|
|
19765
|
-
`Job tracker write failed: ${trackErr instanceof Error ? trackErr.message : String(trackErr)}`
|
|
19766
|
-
);
|
|
19767
20139
|
}
|
|
19768
20140
|
if (isJsonMode()) {
|
|
19769
20141
|
printJson({
|
|
19770
|
-
data: res.data,
|
|
19771
|
-
...
|
|
20142
|
+
data: { ...res.data, outcome: d.outcome },
|
|
20143
|
+
...jobId && {
|
|
19772
20144
|
jobId,
|
|
19773
|
-
hint: `Extraction takes 5-30+ min. Check progress: captable-ai jobs get ${jobId}
|
|
20145
|
+
hint: `Extraction takes 5-30+ min. Check progress: captable-ai jobs get ${jobId}`,
|
|
20146
|
+
monitoringHint: BACKGROUND_MONITORING_SUGGESTION
|
|
19774
20147
|
},
|
|
19775
|
-
|
|
20148
|
+
...trackerFailed && { trackerWarning: "Local job tracking failed." },
|
|
20149
|
+
...d.outcome === "not_ready" && {
|
|
20150
|
+
hint: "Nothing was queued. Wait for classification to finish (or re-upload unfinished uploads), then retry."
|
|
20151
|
+
}
|
|
19776
20152
|
});
|
|
20153
|
+
if (d.outcome === "not_ready") process.exitCode = EXIT.CONFLICT;
|
|
20154
|
+
if (d.outcome === "queue_failed" || d.outcome === "partially_queued") {
|
|
20155
|
+
process.exitCode = EXIT.SERVER_ERROR;
|
|
20156
|
+
}
|
|
20157
|
+
flagMissingIds(d.missingIds);
|
|
19777
20158
|
return;
|
|
19778
20159
|
}
|
|
19779
|
-
|
|
19780
|
-
|
|
19781
|
-
|
|
19782
|
-
"
|
|
19783
|
-
|
|
19784
|
-
|
|
19785
|
-
|
|
20160
|
+
switch (d.outcome) {
|
|
20161
|
+
case "not_ready": {
|
|
20162
|
+
const uploads = blocking.filter((doc) => doc.reason === "pending_upload");
|
|
20163
|
+
const classifying = blocking.filter((doc) => doc.reason === "classification_in_progress");
|
|
20164
|
+
const details = blocking.map((doc) => ({
|
|
20165
|
+
path: doc.s3AssetId,
|
|
20166
|
+
message: `${doc.status}: ${doc.reason}`
|
|
20167
|
+
}));
|
|
20168
|
+
const parts = [];
|
|
20169
|
+
if (classifying.length > 0) {
|
|
20170
|
+
parts.push(
|
|
20171
|
+
`${classifying.length} document(s) are still being classified; wait and retry (captable-ai documents processing-status). If one stays stuck, retry with --s3-asset-ids listing only the documents you intend to extract`
|
|
20172
|
+
);
|
|
20173
|
+
}
|
|
20174
|
+
if (uploads.length > 0) {
|
|
20175
|
+
parts.push(
|
|
20176
|
+
`${uploads.length} requested document(s) never finished uploading; re-upload or delete them`
|
|
20177
|
+
);
|
|
20178
|
+
}
|
|
20179
|
+
exitWithError(
|
|
20180
|
+
`No documents were queued. ${parts.join(". ")}.`,
|
|
20181
|
+
"EXTRACTION_NOT_READY",
|
|
20182
|
+
details,
|
|
20183
|
+
EXIT.CONFLICT
|
|
20184
|
+
);
|
|
20185
|
+
break;
|
|
20186
|
+
}
|
|
20187
|
+
case "queue_failed":
|
|
20188
|
+
exitWithError(
|
|
20189
|
+
`All ${d.failedCount ?? 0} document(s) failed to queue for extraction. Retry shortly: ${retryCommand}`,
|
|
20190
|
+
"EXTRACTION_QUEUE_FAILED",
|
|
20191
|
+
void 0,
|
|
20192
|
+
EXIT.SERVER_ERROR
|
|
20193
|
+
);
|
|
20194
|
+
break;
|
|
20195
|
+
case "no_eligible_documents":
|
|
20196
|
+
printLine("No CLASSIFIED or FAILED documents in scope; nothing was queued.");
|
|
20197
|
+
break;
|
|
20198
|
+
case "queued":
|
|
20199
|
+
case "partially_queued":
|
|
20200
|
+
printSuccess(
|
|
20201
|
+
`Queued ${d.queuedCount ?? queuedIds.length} document(s) for extraction${(d.failedCount ?? 0) > 0 ? ` (${d.failedCount} failed to queue)` : ""}.`
|
|
20202
|
+
);
|
|
20203
|
+
if (jobId) {
|
|
20204
|
+
printSuccess(`Job tracked: ${jobId}. Check progress: captable-ai jobs get ${jobId}`);
|
|
20205
|
+
printLine(`Tip: ${BACKGROUND_MONITORING_SUGGESTION}`);
|
|
20206
|
+
}
|
|
20207
|
+
if (d.outcome === "partially_queued") {
|
|
20208
|
+
printWarning(
|
|
20209
|
+
`${d.failedCount ?? 0} document(s) failed to queue and are still waiting for extraction. Re-run ${retryCommand} to queue them.`
|
|
20210
|
+
);
|
|
20211
|
+
process.exitCode = EXIT.SERVER_ERROR;
|
|
20212
|
+
}
|
|
20213
|
+
break;
|
|
20214
|
+
default: {
|
|
20215
|
+
const unhandled = d.outcome;
|
|
20216
|
+
throw new Error(`Unhandled trigger outcome: ${String(unhandled)}`);
|
|
20217
|
+
}
|
|
19786
20218
|
}
|
|
19787
|
-
|
|
19788
|
-
|
|
20219
|
+
const nonBlockingUploads = notReady.filter(
|
|
20220
|
+
(doc) => !doc.blocking && doc.reason === "pending_upload"
|
|
19789
20221
|
);
|
|
19790
|
-
if (
|
|
19791
|
-
|
|
20222
|
+
if (nonBlockingUploads.length > 0) {
|
|
20223
|
+
printWarning(
|
|
20224
|
+
`${nonBlockingUploads.length} document(s) never finished uploading and were not queued. Re-upload or delete them.`
|
|
20225
|
+
);
|
|
20226
|
+
}
|
|
20227
|
+
const skipped = d.skippedDocuments ?? [];
|
|
20228
|
+
if (skipped.length > 0) {
|
|
20229
|
+
printWarning(
|
|
20230
|
+
`Not queued (already in progress or complete): ${skipped.map((doc) => `${doc.s3AssetId} (${doc.status})`).join(", ")}.`
|
|
20231
|
+
);
|
|
20232
|
+
}
|
|
20233
|
+
if ((d.missingIds?.length ?? 0) > 0) {
|
|
20234
|
+
printWarning(`Not found in this company: ${d.missingIds?.join(", ")}`);
|
|
19792
20235
|
}
|
|
20236
|
+
flagMissingIds(d.missingIds);
|
|
20237
|
+
}
|
|
20238
|
+
function flagMissingIds(missingIds) {
|
|
20239
|
+
if ((missingIds?.length ?? 0) > 0 && !process.exitCode) process.exitCode = EXIT.NOT_FOUND;
|
|
19793
20240
|
}
|
|
19794
20241
|
|
|
19795
20242
|
// src/commands/documents-upload.ts
|
|
@@ -21475,22 +21922,22 @@ async function runObjectsRevert(args, rawArgv) {
|
|
|
21475
21922
|
const objectIdsRaw = getFlagValue(args, "objects");
|
|
21476
21923
|
const objectIdsFromFlag = parseObjectIdsList(objectIdsRaw);
|
|
21477
21924
|
const hasExplicitObjectIds = hasObjectIds(objectIdsRaw, objectIdsFromFlag);
|
|
21478
|
-
const
|
|
21925
|
+
const USAGE2 = "captable-ai objects revert --dry-run [<object-id> | --objects id1,id2,... | --all] [--json]\ncaptable-ai objects revert (<object-id> | --objects id1,id2,... | --all) [--yes] [--json]";
|
|
21479
21926
|
if (args.positional.length > 1) {
|
|
21480
21927
|
exitWithUsage(
|
|
21481
21928
|
`Too many arguments: expected one object ID, got ${args.positional.length}.`,
|
|
21482
|
-
|
|
21929
|
+
USAGE2
|
|
21483
21930
|
);
|
|
21484
21931
|
}
|
|
21485
21932
|
if (objectIdRaw !== void 0 && !objectId) {
|
|
21486
|
-
exitWithUsage("Object ID must be a non-empty string.",
|
|
21933
|
+
exitWithUsage("Object ID must be a non-empty string.", USAGE2);
|
|
21487
21934
|
}
|
|
21488
21935
|
if (objectIdsRaw !== void 0 && !hasExplicitObjectIds) {
|
|
21489
|
-
exitWithUsage("The --objects flag must include at least one object ID.",
|
|
21936
|
+
exitWithUsage("The --objects flag must include at least one object ID.", USAGE2);
|
|
21490
21937
|
}
|
|
21491
21938
|
const selectionSources = [Boolean(objectId), Boolean(objectIdsFromFlag), all].filter(Boolean);
|
|
21492
21939
|
if (selectionSources.length > 1) {
|
|
21493
|
-
exitWithUsage("Specify only one of an object ID, --objects, or --all.",
|
|
21940
|
+
exitWithUsage("Specify only one of an object ID, --objects, or --all.", USAGE2);
|
|
21494
21941
|
}
|
|
21495
21942
|
const objectIds = objectId ? [objectId] : objectIdsFromFlag;
|
|
21496
21943
|
if (dryRun) {
|
|
@@ -21520,7 +21967,7 @@ async function runObjectsRevert(args, rawArgv) {
|
|
|
21520
21967
|
{ objectIdsLabel: "<object-id> or --objects id1,id2,...", allDraftsLabel: "--all" }
|
|
21521
21968
|
);
|
|
21522
21969
|
if (!selection.ok) {
|
|
21523
|
-
exitWithUsage(`${selection.message} Preview first with --dry-run.`,
|
|
21970
|
+
exitWithUsage(`${selection.message} Preview first with --dry-run.`, USAGE2);
|
|
21524
21971
|
}
|
|
21525
21972
|
const portalId = await getActivePortalId();
|
|
21526
21973
|
if (selection.allDrafts) {
|
|
@@ -22160,28 +22607,45 @@ async function runImportStart(args) {
|
|
|
22160
22607
|
printLine("Upload documents: captable-ai documents upload <file...>");
|
|
22161
22608
|
printLine(`Check progress: captable-ai import status ${session.id}`);
|
|
22162
22609
|
}
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
|
|
22166
|
-
|
|
22167
|
-
|
|
22168
|
-
|
|
22169
|
-
|
|
22170
|
-
|
|
22171
|
-
|
|
22172
|
-
|
|
22173
|
-
|
|
22174
|
-
|
|
22175
|
-
|
|
22176
|
-
|
|
22177
|
-
|
|
22610
|
+
function scopeFlag(ids) {
|
|
22611
|
+
return ids && ids.length > 0 ? ` --s3-asset-ids ${ids.join(",")}` : "";
|
|
22612
|
+
}
|
|
22613
|
+
function importStatusCommand(action, sessionId) {
|
|
22614
|
+
const command = (() => {
|
|
22615
|
+
switch (action.id) {
|
|
22616
|
+
case "upload_documents":
|
|
22617
|
+
return "captable-ai documents upload <file...>";
|
|
22618
|
+
case "trigger_extraction":
|
|
22619
|
+
return `captable-ai documents trigger-extraction${scopeFlag(action.s3AssetIds)}`;
|
|
22620
|
+
case "check_status":
|
|
22621
|
+
return `captable-ai import status ${sessionId}`;
|
|
22622
|
+
case "restart_extraction":
|
|
22623
|
+
return `captable-ai documents restart-extraction${scopeFlag(action.s3AssetIds)}`;
|
|
22624
|
+
case "retrigger_validation":
|
|
22625
|
+
return "captable-ai retrigger-validation";
|
|
22626
|
+
case "resolve_extraction_scope":
|
|
22627
|
+
return "captable-ai documents list";
|
|
22628
|
+
case "review_failures":
|
|
22629
|
+
return action.statusFilter ? `captable-ai documents list --status ${action.statusFilter}` : "captable-ai documents list";
|
|
22630
|
+
case "review_drafts":
|
|
22631
|
+
return "captable-ai objects list --status draft --all";
|
|
22632
|
+
case "preview_commit":
|
|
22633
|
+
return "captable-ai commit --dry-run";
|
|
22634
|
+
case "confirm_commit":
|
|
22635
|
+
return "captable-ai commit --all";
|
|
22636
|
+
default: {
|
|
22637
|
+
const unhandled = action.id;
|
|
22638
|
+
throw new Error(`Unhandled import status action: ${String(unhandled)}`);
|
|
22639
|
+
}
|
|
22640
|
+
}
|
|
22641
|
+
})();
|
|
22642
|
+
const id = action.id === "upload_documents" ? "upload_cli" : action.id;
|
|
22178
22643
|
return {
|
|
22179
|
-
|
|
22180
|
-
|
|
22181
|
-
|
|
22182
|
-
|
|
22183
|
-
|
|
22184
|
-
complete: documents.length - failed - processing - classified - pendingUpload
|
|
22644
|
+
id,
|
|
22645
|
+
label: action.label,
|
|
22646
|
+
required: action.required,
|
|
22647
|
+
command,
|
|
22648
|
+
...action.id === "review_failures" && action.s3AssetIds && { s3AssetIds: action.s3AssetIds }
|
|
22185
22649
|
};
|
|
22186
22650
|
}
|
|
22187
22651
|
async function runImportStatus(args) {
|
|
@@ -22196,13 +22660,15 @@ async function runImportStatus(args) {
|
|
|
22196
22660
|
const response = await apiRequest(
|
|
22197
22661
|
`/import-sessions/${encodeURIComponent(sessionId)}/status`
|
|
22198
22662
|
);
|
|
22199
|
-
const
|
|
22200
|
-
|
|
22201
|
-
|
|
22202
|
-
|
|
22203
|
-
|
|
22204
|
-
|
|
22205
|
-
|
|
22663
|
+
const portalDocuments = response.data?.processing?.documents ?? [];
|
|
22664
|
+
const documents = documentsForImportSession(portalDocuments, response.data?.session?.createdAt);
|
|
22665
|
+
const plan = planImportStatus({
|
|
22666
|
+
documents,
|
|
22667
|
+
sessionState: response.data?.session?.state,
|
|
22668
|
+
scopeUnknown: importSessionScopeUnknown(portalDocuments)
|
|
22669
|
+
});
|
|
22670
|
+
const { summary } = plan;
|
|
22671
|
+
if (plan.shouldComplete) {
|
|
22206
22672
|
const completed = await apiRequest(
|
|
22207
22673
|
`/import-sessions/${encodeURIComponent(sessionId)}/complete`,
|
|
22208
22674
|
{
|
|
@@ -22214,56 +22680,15 @@ async function runImportStatus(args) {
|
|
|
22214
22680
|
response.data.session = { ...response.data.session, ...completed.data.session };
|
|
22215
22681
|
}
|
|
22216
22682
|
}
|
|
22217
|
-
const
|
|
22218
|
-
const
|
|
22219
|
-
nextAction(
|
|
22220
|
-
"upload_cli",
|
|
22221
|
-
"Upload local documents directly to S3",
|
|
22222
|
-
"captable-ai documents upload <file...>"
|
|
22223
|
-
),
|
|
22224
|
-
...summary.classified > 0 ? [
|
|
22225
|
-
nextAction(
|
|
22226
|
-
"trigger_extraction",
|
|
22227
|
-
"Start OCF extraction for classified documents",
|
|
22228
|
-
"captable-ai documents trigger-extraction"
|
|
22229
|
-
)
|
|
22230
|
-
] : []
|
|
22231
|
-
] : summary.classified > 0 ? [
|
|
22232
|
-
nextAction(
|
|
22233
|
-
"trigger_extraction",
|
|
22234
|
-
"Start OCF extraction for classified documents",
|
|
22235
|
-
"captable-ai documents trigger-extraction"
|
|
22236
|
-
)
|
|
22237
|
-
] : summary.processing > 0 ? [
|
|
22238
|
-
nextAction(
|
|
22239
|
-
"check_status",
|
|
22240
|
-
"Check import progress again",
|
|
22241
|
-
`captable-ai import status ${sessionId}`
|
|
22242
|
-
)
|
|
22243
|
-
] : summary.failed > 0 ? [
|
|
22244
|
-
nextAction(
|
|
22245
|
-
"review_failures",
|
|
22246
|
-
"Review failed documents",
|
|
22247
|
-
"captable-ai documents list --status FAILED"
|
|
22248
|
-
)
|
|
22249
|
-
] : [
|
|
22250
|
-
nextAction(
|
|
22251
|
-
"review_drafts",
|
|
22252
|
-
"Review extracted drafts",
|
|
22253
|
-
"captable-ai objects list --status draft --all"
|
|
22254
|
-
),
|
|
22255
|
-
nextAction(
|
|
22256
|
-
"preview_commit",
|
|
22257
|
-
"Preview the first commit",
|
|
22258
|
-
"captable-ai commit --dry-run"
|
|
22259
|
-
),
|
|
22260
|
-
nextAction("confirm_commit", "Commit reviewed drafts", "captable-ai commit --all")
|
|
22261
|
-
];
|
|
22683
|
+
const nextActions = plan.actions.map((action) => importStatusCommand(action, sessionId));
|
|
22684
|
+
const suggestMonitoring = shouldSuggestBackgroundMonitoring(plan.phase);
|
|
22262
22685
|
const data = {
|
|
22263
22686
|
...response.data,
|
|
22264
|
-
state,
|
|
22687
|
+
state: plan.state,
|
|
22688
|
+
phase: plan.phase,
|
|
22265
22689
|
summary,
|
|
22266
|
-
nextActions
|
|
22690
|
+
nextActions,
|
|
22691
|
+
...suggestMonitoring && { monitoringHint: BACKGROUND_MONITORING_SUGGESTION }
|
|
22267
22692
|
};
|
|
22268
22693
|
if (isJsonMode()) {
|
|
22269
22694
|
printJson({ data });
|
|
@@ -22272,14 +22697,33 @@ async function runImportStatus(args) {
|
|
|
22272
22697
|
printKeyValue([
|
|
22273
22698
|
["Session", sessionId],
|
|
22274
22699
|
["State", data.state],
|
|
22700
|
+
["Phase", plan.phase],
|
|
22275
22701
|
["Documents", summary.total],
|
|
22702
|
+
["Pending upload", summary.pendingUpload],
|
|
22703
|
+
["Classifying", summary.preclassification],
|
|
22276
22704
|
["Classified", summary.classified],
|
|
22277
|
-
["
|
|
22705
|
+
["Extracting", summary.extracting],
|
|
22706
|
+
["Validating", summary.validating],
|
|
22278
22707
|
["Complete", summary.complete],
|
|
22279
22708
|
["Failed", summary.failed]
|
|
22280
22709
|
]);
|
|
22710
|
+
if (plan.phase === "awaiting_upload") {
|
|
22711
|
+
printLine(
|
|
22712
|
+
" Some uploads never finished. Re-upload them; checking status again will not complete them."
|
|
22713
|
+
);
|
|
22714
|
+
}
|
|
22281
22715
|
for (const action of nextActions) {
|
|
22282
|
-
printLine(` Next: ${
|
|
22716
|
+
printLine(` Next: ${action.command}`);
|
|
22717
|
+
if (action.id === "resolve_extraction_scope") printLine(` Note: ${action.label}`);
|
|
22718
|
+
if (action.id === "check_status" && plan.phase === "empty") {
|
|
22719
|
+
printLine(` Note: ${action.label}`);
|
|
22720
|
+
}
|
|
22721
|
+
if (action.id === "review_failures" && action.s3AssetIds) {
|
|
22722
|
+
printLine(` Documents: ${action.s3AssetIds.join(", ")}`);
|
|
22723
|
+
}
|
|
22724
|
+
}
|
|
22725
|
+
if (suggestMonitoring) {
|
|
22726
|
+
printLine(` Tip: ${BACKGROUND_MONITORING_SUGGESTION}`);
|
|
22283
22727
|
}
|
|
22284
22728
|
}
|
|
22285
22729
|
|
|
@@ -23107,7 +23551,7 @@ Onboarding:
|
|
|
23107
23551
|
onboarding status Show the next setup/import action (works with zero companies)
|
|
23108
23552
|
onboarding setup Create the first production workspace
|
|
23109
23553
|
import start Start or reuse a document import session
|
|
23110
|
-
import status <id>
|
|
23554
|
+
import status <id> Show import phase and the next action (upload, wait, extract, review)
|
|
23111
23555
|
|
|
23112
23556
|
Cap Table (read):
|
|
23113
23557
|
summary Full cap table snapshot (capitalization, stakeholders, metrics)
|
|
@@ -23164,6 +23608,7 @@ Agent Logs (Fairmint super-admin only):
|
|
|
23164
23608
|
|
|
23165
23609
|
Extraction Pipeline:
|
|
23166
23610
|
documents trigger-extraction Trigger OCF extraction for classified documents
|
|
23611
|
+
Queues nothing while documents are still classifying
|
|
23167
23612
|
Filter: --s3-asset-ids ID1,ID2,...
|
|
23168
23613
|
documents cancel-extraction <s3AssetId> Cancel a stuck extraction
|
|
23169
23614
|
documents restart-extraction Atomically restart stuck extractions (cancel + re-enqueue)
|
package/package.json
CHANGED