@fairmint/captable-ai 0.1.34 → 0.1.35

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 (2) hide show
  1. package/dist/index.js +136 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15355,10 +15355,82 @@ function planImportStatus({ documents, sessionState, scopeUnknown = false }) {
15355
15355
  }
15356
15356
  }
15357
15357
  }
15358
- function applyDraftCount(plan, draftCount) {
15359
- if (plan.phase !== "complete" || draftCount !== 0)
15358
+ var IMPORT_REVIEW_MAX_IDS = 50;
15359
+ function reviewIsCut(action) {
15360
+ return action.reviewObjectIds !== void 0 && action.s3AssetIds?.length !== 1 && action.reviewObjectIds.length > IMPORT_REVIEW_MAX_IDS;
15361
+ }
15362
+ function isFileRecordRow(draft) {
15363
+ return draft.type === "DOCUMENT" || draft.subtype === "DOCUMENT";
15364
+ }
15365
+ function partitionImportDrafts(drafts, documentIds) {
15366
+ if (documentIds.length === 0)
15367
+ return null;
15368
+ const documents = new Set(documentIds.map((id) => id.toLowerCase()));
15369
+ const importDraftIds = [];
15370
+ const importReviewableIds = [];
15371
+ const portalIssuerDraftIds = [];
15372
+ let otherDrafts = false;
15373
+ for (const draft of drafts) {
15374
+ if (typeof draft.id !== "string" || draft.id.length === 0)
15375
+ return null;
15376
+ if (!("documentId" in draft))
15377
+ return null;
15378
+ const ours = typeof draft.documentId === "string" && documents.has(draft.documentId.toLowerCase());
15379
+ const selectable = !draft.id.includes(",") && draft.id === draft.id.trim();
15380
+ if (!ours) {
15381
+ if (draft.type === "ISSUER" && draft.documentId === null) {
15382
+ if (!selectable)
15383
+ return null;
15384
+ portalIssuerDraftIds.push(draft.id);
15385
+ } else {
15386
+ otherDrafts = true;
15387
+ }
15388
+ continue;
15389
+ }
15390
+ if (!selectable)
15391
+ return null;
15392
+ importDraftIds.push(draft.id);
15393
+ if (!isFileRecordRow(draft))
15394
+ importReviewableIds.push(draft.id);
15395
+ }
15396
+ return { importDraftIds, importReviewableIds, portalIssuerDraftIds, otherDrafts };
15397
+ }
15398
+ function applyImportDrafts(plan, scope, documentIds) {
15399
+ if (plan.phase !== "complete" || scope === null)
15400
+ return plan;
15401
+ if (scope.importReviewableIds.length === 0) {
15402
+ return {
15403
+ ...plan,
15404
+ state: "complete",
15405
+ actions: [],
15406
+ ...scope.otherDrafts ? { unrelatedDrafts: true } : {}
15407
+ };
15408
+ }
15409
+ if (!scope.otherDrafts)
15360
15410
  return plan;
15361
- return { ...plan, state: "complete", actions: [] };
15411
+ const selection = [...scope.importDraftIds, ...scope.portalIssuerDraftIds];
15412
+ const scoped = (action) => {
15413
+ const base = { ...action, objectIds: selection };
15414
+ switch (action.id) {
15415
+ case "review_drafts":
15416
+ return {
15417
+ ...base,
15418
+ label: "Review the drafts from this import's documents",
15419
+ s3AssetIds: [...documentIds],
15420
+ reviewObjectIds: scope.importReviewableIds
15421
+ };
15422
+ case "preview_commit":
15423
+ return { ...base, label: "Preview committing this import's drafts" };
15424
+ case "confirm_commit":
15425
+ return {
15426
+ ...base,
15427
+ label: "Commit this import's drafts with the preview's final IDs as objectIds. Not allDrafts: the portal also has pending drafts from other sources"
15428
+ };
15429
+ default:
15430
+ return action;
15431
+ }
15432
+ };
15433
+ return { ...plan, actions: plan.actions.map(scoped) };
15362
15434
  }
15363
15435
  var EXTRACTION_NOT_READY_REASONS = [
15364
15436
  "pending_upload",
@@ -23031,7 +23103,16 @@ async function runImportStart(args) {
23031
23103
  function scopeFlag(ids) {
23032
23104
  return ids && ids.length > 0 ? ` --s3-asset-ids ${ids.join(",")}` : "";
23033
23105
  }
23106
+ var CLI_SELECTION_MAX_BYTES = 1e5;
23034
23107
  function importStatusCommand(action, sessionId) {
23108
+ const tooManyToSelect = (action.id === "preview_commit" || action.id === "confirm_commit") && action.objectIds !== void 0 && Buffer.byteLength(`--objects=${action.objectIds.join(",")}`, "utf8") > CLI_SELECTION_MAX_BYTES;
23109
+ if (tooManyToSelect) {
23110
+ return {
23111
+ id: action.id,
23112
+ label: `This import has ${action.objectIds?.length ?? 0} drafts, too many to select on one command line. Commit them from the Studio timeline, or with the MCP. Not --all: the portal also has pending drafts from other sources.`,
23113
+ required: action.required
23114
+ };
23115
+ }
23035
23116
  const command = (() => {
23036
23117
  switch (action.id) {
23037
23118
  case "upload_documents":
@@ -23048,12 +23129,19 @@ function importStatusCommand(action, sessionId) {
23048
23129
  return "captable-ai documents list";
23049
23130
  case "review_failures":
23050
23131
  return action.statusFilter ? `captable-ai documents list --status ${action.statusFilter}` : "captable-ai documents list";
23051
- case "review_drafts":
23052
- return "captable-ai objects list --status draft --all";
23132
+ case "review_drafts": {
23133
+ if (!action.objectIds) return "captable-ai objects list --status draft --all";
23134
+ const [onlyDocument, ...moreDocuments] = action.s3AssetIds ?? [];
23135
+ if (onlyDocument !== void 0 && moreDocuments.length === 0) {
23136
+ return `captable-ai objects list --status draft --document ${shellQuote(onlyDocument)} --all`;
23137
+ }
23138
+ const reviewable = (action.reviewObjectIds ?? []).slice(0, IMPORT_REVIEW_MAX_IDS);
23139
+ return `captable-ai objects list --status draft --ids=${shellQuote(reviewable.join(","))}`;
23140
+ }
23053
23141
  case "preview_commit":
23054
- return "captable-ai commit --dry-run";
23142
+ return action.objectIds ? `captable-ai commit --dry-run --objects=${shellQuote(action.objectIds.join(","))}` : "captable-ai commit --dry-run";
23055
23143
  case "confirm_commit":
23056
- return "captable-ai commit --all";
23144
+ return action.objectIds ? "captable-ai commit --objects <final IDs from the dry run>" : "captable-ai commit --all";
23057
23145
  default: {
23058
23146
  const unhandled = action.id;
23059
23147
  throw new Error(`Unhandled import status action: ${String(unhandled)}`);
@@ -23063,35 +23151,40 @@ function importStatusCommand(action, sessionId) {
23063
23151
  const id = action.id === "upload_documents" ? "upload_cli" : action.id;
23064
23152
  return {
23065
23153
  id,
23066
- label: action.label,
23154
+ label: action.id === "review_drafts" && reviewIsCut(action) ? `${action.label} (first ${IMPORT_REVIEW_MAX_IDS} of ${action.reviewObjectIds?.length ?? 0})` : action.label,
23067
23155
  required: action.required,
23068
23156
  command,
23069
23157
  ...action.id === "review_failures" && action.s3AssetIds && { s3AssetIds: action.s3AssetIds }
23070
23158
  };
23071
23159
  }
23072
- var DRAFT_COUNT_MAX_PAGES = 100;
23073
- async function readDraftCount(portalId) {
23160
+ var DRAFT_SCOPE_MAX_PAGES = 100;
23161
+ async function readImportDraftScope(portalId, documentIds) {
23074
23162
  if (!portalId) return null;
23075
23163
  try {
23076
- let count = 0;
23164
+ const drafts = [];
23077
23165
  let cursor;
23078
- for (let page = 0; page < DRAFT_COUNT_MAX_PAGES; page += 1) {
23166
+ for (let page = 0; page < DRAFT_SCOPE_MAX_PAGES; page += 1) {
23079
23167
  const res = await apiRequest(
23080
23168
  `/${encodeURIComponent(portalId)}/objects`,
23081
23169
  {
23082
- query: { status: "draft", ...cursor ? { cursor } : {} },
23170
+ query: { status: "draft", limit: 250, ...cursor ? { cursor } : {} },
23083
23171
  // Optional read: throw (caught below) instead of exiting the process on a failure.
23084
23172
  softFail: true
23085
23173
  }
23086
23174
  );
23087
23175
  const objects = res.data?.objects;
23088
23176
  if (!Array.isArray(objects)) return null;
23089
- count += objects.filter(
23090
- (obj) => obj.type !== "DOCUMENT" && obj.subtype !== "DOCUMENT"
23091
- ).length;
23092
- if (count > 0) return count;
23177
+ for (const obj of objects) {
23178
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return null;
23179
+ drafts.push({
23180
+ id: Reflect.get(obj, "id"),
23181
+ type: Reflect.get(obj, "type"),
23182
+ subtype: Reflect.get(obj, "subtype"),
23183
+ ..."documentId" in obj ? { documentId: Reflect.get(obj, "documentId") } : {}
23184
+ });
23185
+ }
23186
+ if (!res.meta?.hasMore) return partitionImportDrafts(drafts, documentIds);
23093
23187
  const next = res.meta?.cursor;
23094
- if (!res.meta?.hasMore) return count;
23095
23188
  if (!next || next === cursor) return null;
23096
23189
  cursor = next;
23097
23190
  }
@@ -23114,12 +23207,19 @@ async function runImportStatus(args) {
23114
23207
  );
23115
23208
  const portalDocuments = response.data?.processing?.documents ?? [];
23116
23209
  const documents = documentsForImportSession(portalDocuments, response.data?.session?.createdAt);
23210
+ const documentIds = documents.flatMap(
23211
+ (document) => typeof document.s3AssetId === "string" ? [document.s3AssetId] : []
23212
+ );
23117
23213
  const documentPlan = planImportStatus({
23118
23214
  documents,
23119
23215
  sessionState: response.data?.session?.state,
23120
23216
  scopeUnknown: importSessionScopeUnknown(portalDocuments)
23121
23217
  });
23122
- const plan = documentPlan.phase === "complete" ? applyDraftCount(documentPlan, await readDraftCount(response.data?.session?.portalId)) : documentPlan;
23218
+ const plan = documentPlan.phase === "complete" ? applyImportDrafts(
23219
+ documentPlan,
23220
+ await readImportDraftScope(response.data?.session?.portalId, documentIds),
23221
+ documentIds
23222
+ ) : documentPlan;
23123
23223
  const { summary } = plan;
23124
23224
  if (plan.shouldComplete) {
23125
23225
  const completed = await apiRequest(
@@ -23141,6 +23241,7 @@ async function runImportStatus(args) {
23141
23241
  phase: plan.phase,
23142
23242
  summary,
23143
23243
  nextActions,
23244
+ ...plan.unrelatedDrafts && { unrelatedDrafts: true },
23144
23245
  ...suggestMonitoring && { monitoringHint: BACKGROUND_MONITORING_SUGGESTION }
23145
23246
  };
23146
23247
  if (isJsonMode()) {
@@ -23165,12 +23266,28 @@ async function runImportStatus(args) {
23165
23266
  " Some uploads never finished. Re-upload them; checking status again will not complete them."
23166
23267
  );
23167
23268
  }
23269
+ if (plan.unrelatedDrafts) {
23270
+ printLine(
23271
+ " The portal has other pending drafts that this import did not produce; leave them alone unless asked."
23272
+ );
23273
+ }
23274
+ const scopedCommit = plan.actions.some(
23275
+ (planned) => planned.id === "confirm_commit" && planned.objectIds !== void 0
23276
+ );
23168
23277
  for (const action of nextActions) {
23278
+ if (action.command === void 0) {
23279
+ printLine(` Note: ${action.label}`);
23280
+ continue;
23281
+ }
23169
23282
  printLine(` Next: ${action.command}`);
23170
23283
  if (action.id === "resolve_extraction_scope") printLine(` Note: ${action.label}`);
23171
23284
  if (action.id === "check_status" && plan.phase === "empty") {
23172
23285
  printLine(` Note: ${action.label}`);
23173
23286
  }
23287
+ if (action.id === "confirm_commit" && scopedCommit) printLine(` Note: ${action.label}`);
23288
+ if (action.id === "review_drafts" && action.label.includes("(first ")) {
23289
+ printLine(` Note: ${action.label}`);
23290
+ }
23174
23291
  if (action.id === "review_failures" && action.s3AssetIds) {
23175
23292
  printLine(` Documents: ${action.s3AssetIds.join(", ")}`);
23176
23293
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairmint/captable-ai",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "Agent-native CLI for Captable AI — manage companies, upload documents, run OCF extraction, and commit cap tables from the terminal.",
5
5
  "keywords": [
6
6
  "captable",