@fairmint/captable-ai 0.1.33 → 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 +143 -20
  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",
@@ -15730,7 +15802,13 @@ var draftOperationDependencyIssueSchema = external_exports.object({
15730
15802
  expectedType: external_exports.string().describe("Expected OCF object category for the reference.")
15731
15803
  });
15732
15804
  var draftOperationPlanIssueSchema = external_exports.object({
15733
- code: external_exports.enum(["NO_DRAFTS", "REVERT_LIMIT_EXCEEDED", "ISSUER_REQUIRED", "ISSUER_INCOMPLETE"]).describe("Plan-level validation code."),
15805
+ code: external_exports.enum([
15806
+ "NO_DRAFTS",
15807
+ "REVERT_LIMIT_EXCEEDED",
15808
+ "ISSUER_REQUIRED",
15809
+ "ISSUER_INCOMPLETE",
15810
+ "DOCUMENT_INTEGRITY_FAILED"
15811
+ ]).describe("Plan-level validation code."),
15734
15812
  message: external_exports.string().describe("Human-readable plan validation message.")
15735
15813
  });
15736
15814
  var draftOperationPreviewInputSchema = external_exports.object({
@@ -23025,7 +23103,16 @@ async function runImportStart(args) {
23025
23103
  function scopeFlag(ids) {
23026
23104
  return ids && ids.length > 0 ? ` --s3-asset-ids ${ids.join(",")}` : "";
23027
23105
  }
23106
+ var CLI_SELECTION_MAX_BYTES = 1e5;
23028
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
+ }
23029
23116
  const command = (() => {
23030
23117
  switch (action.id) {
23031
23118
  case "upload_documents":
@@ -23042,12 +23129,19 @@ function importStatusCommand(action, sessionId) {
23042
23129
  return "captable-ai documents list";
23043
23130
  case "review_failures":
23044
23131
  return action.statusFilter ? `captable-ai documents list --status ${action.statusFilter}` : "captable-ai documents list";
23045
- case "review_drafts":
23046
- 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
+ }
23047
23141
  case "preview_commit":
23048
- 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";
23049
23143
  case "confirm_commit":
23050
- return "captable-ai commit --all";
23144
+ return action.objectIds ? "captable-ai commit --objects <final IDs from the dry run>" : "captable-ai commit --all";
23051
23145
  default: {
23052
23146
  const unhandled = action.id;
23053
23147
  throw new Error(`Unhandled import status action: ${String(unhandled)}`);
@@ -23057,35 +23151,40 @@ function importStatusCommand(action, sessionId) {
23057
23151
  const id = action.id === "upload_documents" ? "upload_cli" : action.id;
23058
23152
  return {
23059
23153
  id,
23060
- 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,
23061
23155
  required: action.required,
23062
23156
  command,
23063
23157
  ...action.id === "review_failures" && action.s3AssetIds && { s3AssetIds: action.s3AssetIds }
23064
23158
  };
23065
23159
  }
23066
- var DRAFT_COUNT_MAX_PAGES = 100;
23067
- async function readDraftCount(portalId) {
23160
+ var DRAFT_SCOPE_MAX_PAGES = 100;
23161
+ async function readImportDraftScope(portalId, documentIds) {
23068
23162
  if (!portalId) return null;
23069
23163
  try {
23070
- let count = 0;
23164
+ const drafts = [];
23071
23165
  let cursor;
23072
- for (let page = 0; page < DRAFT_COUNT_MAX_PAGES; page += 1) {
23166
+ for (let page = 0; page < DRAFT_SCOPE_MAX_PAGES; page += 1) {
23073
23167
  const res = await apiRequest(
23074
23168
  `/${encodeURIComponent(portalId)}/objects`,
23075
23169
  {
23076
- query: { status: "draft", ...cursor ? { cursor } : {} },
23170
+ query: { status: "draft", limit: 250, ...cursor ? { cursor } : {} },
23077
23171
  // Optional read: throw (caught below) instead of exiting the process on a failure.
23078
23172
  softFail: true
23079
23173
  }
23080
23174
  );
23081
23175
  const objects = res.data?.objects;
23082
23176
  if (!Array.isArray(objects)) return null;
23083
- count += objects.filter(
23084
- (obj) => obj.type !== "DOCUMENT" && obj.subtype !== "DOCUMENT"
23085
- ).length;
23086
- 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);
23087
23187
  const next = res.meta?.cursor;
23088
- if (!res.meta?.hasMore) return count;
23089
23188
  if (!next || next === cursor) return null;
23090
23189
  cursor = next;
23091
23190
  }
@@ -23108,12 +23207,19 @@ async function runImportStatus(args) {
23108
23207
  );
23109
23208
  const portalDocuments = response.data?.processing?.documents ?? [];
23110
23209
  const documents = documentsForImportSession(portalDocuments, response.data?.session?.createdAt);
23210
+ const documentIds = documents.flatMap(
23211
+ (document) => typeof document.s3AssetId === "string" ? [document.s3AssetId] : []
23212
+ );
23111
23213
  const documentPlan = planImportStatus({
23112
23214
  documents,
23113
23215
  sessionState: response.data?.session?.state,
23114
23216
  scopeUnknown: importSessionScopeUnknown(portalDocuments)
23115
23217
  });
23116
- 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;
23117
23223
  const { summary } = plan;
23118
23224
  if (plan.shouldComplete) {
23119
23225
  const completed = await apiRequest(
@@ -23135,6 +23241,7 @@ async function runImportStatus(args) {
23135
23241
  phase: plan.phase,
23136
23242
  summary,
23137
23243
  nextActions,
23244
+ ...plan.unrelatedDrafts && { unrelatedDrafts: true },
23138
23245
  ...suggestMonitoring && { monitoringHint: BACKGROUND_MONITORING_SUGGESTION }
23139
23246
  };
23140
23247
  if (isJsonMode()) {
@@ -23159,12 +23266,28 @@ async function runImportStatus(args) {
23159
23266
  " Some uploads never finished. Re-upload them; checking status again will not complete them."
23160
23267
  );
23161
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
+ );
23162
23277
  for (const action of nextActions) {
23278
+ if (action.command === void 0) {
23279
+ printLine(` Note: ${action.label}`);
23280
+ continue;
23281
+ }
23163
23282
  printLine(` Next: ${action.command}`);
23164
23283
  if (action.id === "resolve_extraction_scope") printLine(` Note: ${action.label}`);
23165
23284
  if (action.id === "check_status" && plan.phase === "empty") {
23166
23285
  printLine(` Note: ${action.label}`);
23167
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
+ }
23168
23291
  if (action.id === "review_failures" && action.s3AssetIds) {
23169
23292
  printLine(` Documents: ${action.s3AssetIds.join(", ")}`);
23170
23293
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairmint/captable-ai",
3
- "version": "0.1.33",
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",