@fairmint/captable-ai 0.1.6 → 0.1.8

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 +266 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -602,7 +602,7 @@ var COMMAND_REGISTRY = [
602
602
  {
603
603
  name: "objects get",
604
604
  group: "captable",
605
- description: "Get a single OCF object with document and extraction details.",
605
+ description: "Get a single OCF object with document, extraction details, and a vesting summary when present.",
606
606
  usage: "captable-ai objects get <object-id> [--json]",
607
607
  requiresAuth: true,
608
608
  requiresPortal: true
@@ -1168,7 +1168,7 @@ var COMMAND_REGISTRY = [
1168
1168
  }
1169
1169
  ];
1170
1170
  async function runCommands(_args) {
1171
- printJson({ schemaVersion: "8", version: "0.0.1", commands: COMMAND_REGISTRY });
1171
+ printJson({ schemaVersion: "9", version: "0.0.1", commands: COMMAND_REGISTRY });
1172
1172
  }
1173
1173
 
1174
1174
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
@@ -15260,7 +15260,10 @@ var LEGAL_DOCUMENTS_MAPPING = {
15260
15260
  "Founder Stock Purchase Agreement",
15261
15261
  "Notice of Stock Purchase Exercise",
15262
15262
  "Notice of RSA Exercise",
15263
- "Share Grant Exercise"
15263
+ "Share Grant Exercise",
15264
+ "Restricted Stock Award Agreement",
15265
+ "Restricted Stock Award",
15266
+ "RSA Agreement"
15264
15267
  ]
15265
15268
  },
15266
15269
  "Rolling SAFE": {
@@ -16064,6 +16067,176 @@ var exportResultSchema = external_exports.object({
16064
16067
  }).optional().describe("Present when pro-forma sheets were requested but engine refused.")
16065
16068
  });
16066
16069
 
16070
+ // ../captable-ai-core/dist/format-vesting-summary.js
16071
+ function asRecord(value) {
16072
+ if (typeof value !== "object" || value === null || Array.isArray(value))
16073
+ return null;
16074
+ const out = {};
16075
+ for (const [key, val] of Object.entries(value)) {
16076
+ out[key] = val;
16077
+ }
16078
+ return out;
16079
+ }
16080
+ function gcd(a, b) {
16081
+ let x = Math.abs(Math.trunc(a));
16082
+ let y = Math.abs(Math.trunc(b));
16083
+ while (y !== 0) {
16084
+ const t = y;
16085
+ y = x % y;
16086
+ x = t;
16087
+ }
16088
+ return x || 1;
16089
+ }
16090
+ function reduceFraction(numerator, denominator) {
16091
+ const divisor = gcd(numerator, denominator);
16092
+ return { numerator: numerator / divisor, denominator: denominator / divisor };
16093
+ }
16094
+ function asConditions(value) {
16095
+ if (!Array.isArray(value))
16096
+ return [];
16097
+ return value.filter((item) => asRecord(item) !== null);
16098
+ }
16099
+ function portionFraction(condition) {
16100
+ const portion = asRecord(condition.portion);
16101
+ if (!portion)
16102
+ return null;
16103
+ const numerator = Number(portion.numerator);
16104
+ const denominator = Number(portion.denominator);
16105
+ if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0)
16106
+ return null;
16107
+ if (numerator <= 0)
16108
+ return null;
16109
+ return { numerator, denominator };
16110
+ }
16111
+ function formatPercent(numerator, denominator) {
16112
+ const reduced = reduceFraction(numerator, denominator);
16113
+ const pct = reduced.numerator / reduced.denominator * 100;
16114
+ if (Number.isInteger(pct) && pct >= 1 && pct <= 100)
16115
+ return `${pct}%`;
16116
+ return `${reduced.numerator}/${reduced.denominator}`;
16117
+ }
16118
+ function periodLabel(period) {
16119
+ if (!period)
16120
+ return null;
16121
+ let occurrences = 1;
16122
+ if (period.occurrences != null && period.occurrences !== "") {
16123
+ occurrences = Number(period.occurrences);
16124
+ if (!Number.isInteger(occurrences) || occurrences <= 0)
16125
+ return null;
16126
+ }
16127
+ let length = 1;
16128
+ if (period.length != null && period.length !== "") {
16129
+ const suppliedLength = Number(period.length);
16130
+ if (!Number.isFinite(suppliedLength) || suppliedLength < 0)
16131
+ return null;
16132
+ length = suppliedLength;
16133
+ }
16134
+ if (length === 0)
16135
+ return { cadence: "immediately", occurrences };
16136
+ const unit = String(period.type ?? "").toUpperCase();
16137
+ if (unit === "MONTHS") {
16138
+ return { cadence: length === 1 ? "monthly" : `every ${length} months`, occurrences };
16139
+ }
16140
+ if (unit === "DAYS") {
16141
+ return { cadence: length === 1 ? "daily" : `every ${length} days`, occurrences };
16142
+ }
16143
+ if (unit === "WEEKS") {
16144
+ return { cadence: length === 1 ? "weekly" : `every ${length} weeks`, occurrences };
16145
+ }
16146
+ if (unit === "YEARS") {
16147
+ return { cadence: length === 1 ? "yearly" : `every ${length} years`, occurrences };
16148
+ }
16149
+ return null;
16150
+ }
16151
+ function quantityLabel(condition) {
16152
+ const raw = condition.quantity;
16153
+ if (raw == null || raw === "")
16154
+ return null;
16155
+ const quantity = Number(raw);
16156
+ if (!Number.isFinite(quantity) || quantity <= 0)
16157
+ return null;
16158
+ return quantity === 1 ? "1 share" : `${quantity} shares`;
16159
+ }
16160
+ function trancheAmount(condition) {
16161
+ const fraction = portionFraction(condition);
16162
+ if (fraction)
16163
+ return formatPercent(fraction.numerator, fraction.denominator);
16164
+ return quantityLabel(condition);
16165
+ }
16166
+ function formatVestingSummary(ocf) {
16167
+ const record2 = asRecord(ocf);
16168
+ const conditions = asConditions(record2?.vesting_conditions);
16169
+ if (conditions.length === 0)
16170
+ return null;
16171
+ const parts = [];
16172
+ for (const condition of conditions) {
16173
+ const trigger = asRecord(condition.trigger);
16174
+ const triggerType = String(trigger?.type ?? "").toUpperCase();
16175
+ if (triggerType === "VESTING_EVENT") {
16176
+ const portion = asRecord(condition.portion);
16177
+ const remainder = portion?.remainder === true || portion?.remainder === "true";
16178
+ const hasAllocation = Boolean(trancheAmount(condition) || remainder);
16179
+ if (portion && !hasAllocation)
16180
+ continue;
16181
+ parts.push("event-based (see conditions)");
16182
+ continue;
16183
+ }
16184
+ const amount = trancheAmount(condition);
16185
+ if (!amount)
16186
+ continue;
16187
+ if (triggerType === "VESTING_START_DATE") {
16188
+ parts.push(`${amount} at grant`);
16189
+ continue;
16190
+ }
16191
+ if (triggerType === "VESTING_SCHEDULE_ABSOLUTE") {
16192
+ const date5 = typeof trigger?.date === "string" && trigger.date.length > 0 ? trigger.date : null;
16193
+ parts.push(date5 ? `${amount} on ${date5}` : `${amount} on a fixed date`);
16194
+ continue;
16195
+ }
16196
+ if (triggerType === "VESTING_SCHEDULE_RELATIVE") {
16197
+ const period = periodLabel(asRecord(trigger?.period));
16198
+ if (!period)
16199
+ continue;
16200
+ if (period.cadence === "immediately" && period.occurrences === 1) {
16201
+ parts.push(`${amount} immediately`);
16202
+ continue;
16203
+ }
16204
+ const fraction = portionFraction(condition);
16205
+ if (fraction) {
16206
+ const reduced = reduceFraction(fraction.numerator, fraction.denominator);
16207
+ parts.push(`${reduced.numerator}/${reduced.denominator} ${period.cadence} \xD7 ${period.occurrences}`);
16208
+ continue;
16209
+ }
16210
+ parts.push(`${amount} ${period.cadence} \xD7 ${period.occurrences}`);
16211
+ }
16212
+ }
16213
+ if (parts.length === 0)
16214
+ return null;
16215
+ return `Vesting: ${parts.join(", then ")}`;
16216
+ }
16217
+ function unresolvedVestingTermsId(ocf) {
16218
+ const record2 = asRecord(ocf);
16219
+ if (!record2)
16220
+ return null;
16221
+ if (Array.isArray(record2.vesting_conditions) && record2.vesting_conditions.length > 0) {
16222
+ return null;
16223
+ }
16224
+ return typeof record2.vesting_terms_id === "string" && record2.vesting_terms_id.length > 0 ? record2.vesting_terms_id : null;
16225
+ }
16226
+ async function resolveVestingSummary(ocf, fetchTermsOcf) {
16227
+ const direct = formatVestingSummary(ocf);
16228
+ if (direct)
16229
+ return direct;
16230
+ const termsId = unresolvedVestingTermsId(ocf);
16231
+ if (!termsId)
16232
+ return null;
16233
+ try {
16234
+ return formatVestingSummary(await fetchTermsOcf(termsId));
16235
+ } catch {
16236
+ return null;
16237
+ }
16238
+ }
16239
+
16067
16240
  // ../captable-ai-core/dist/objects-dump.js
16068
16241
  var DumpAssemblyError = class extends Error {
16069
16242
  constructor(message) {
@@ -16469,12 +16642,19 @@ function coerceDetails(raw) {
16469
16642
  return details.length > 0 ? details : void 0;
16470
16643
  }
16471
16644
  var REQUEST_TIMEOUT_MS2 = 3e4;
16472
- function exitForRefreshFailure(err) {
16645
+ function exitForRefreshFailure(err, softFail = false) {
16473
16646
  if (err instanceof Error && err.name === "AuthError") {
16474
- exitWithError("Session expired. Run: captable-ai login", "AUTH_EXPIRED", void 0, EXIT.AUTH);
16647
+ failRequest(
16648
+ softFail,
16649
+ "Session expired. Run: captable-ai login",
16650
+ "AUTH_EXPIRED",
16651
+ void 0,
16652
+ EXIT.AUTH
16653
+ );
16475
16654
  }
16476
16655
  if (err instanceof Error && (err.name === "NetworkError" || isTransportError(err))) {
16477
- exitWithError(
16656
+ failRequest(
16657
+ softFail,
16478
16658
  "Network error while refreshing session. Check your connection and try again.",
16479
16659
  "REFRESH_TRANSPORT",
16480
16660
  void 0,
@@ -16482,7 +16662,8 @@ function exitForRefreshFailure(err) {
16482
16662
  );
16483
16663
  }
16484
16664
  const detail = err instanceof Error ? err.message : String(err);
16485
- exitWithError(
16665
+ failRequest(
16666
+ softFail,
16486
16667
  `Token refresh failed: ${detail}. Run: captable-ai login`,
16487
16668
  "REFRESH_FAILED",
16488
16669
  void 0,
@@ -16496,6 +16677,22 @@ function isTransportError(err) {
16496
16677
  if (!(err instanceof Error)) return false;
16497
16678
  return err.name === "TimeoutError" || err.name === "AbortError" || err.name === "TypeError" || /network|timeout|fetch|ECONNRESET|ENOTFOUND|ETIMEDOUT/i.test(err.message);
16498
16679
  }
16680
+ var ApiRequestError = class extends Error {
16681
+ code;
16682
+ details;
16683
+ constructor(message, code, details) {
16684
+ super(message);
16685
+ this.name = "ApiRequestError";
16686
+ if (code !== void 0) this.code = code;
16687
+ if (details !== void 0) this.details = details;
16688
+ }
16689
+ };
16690
+ function failRequest(softFail, message, code, details, exitCode) {
16691
+ if (softFail) {
16692
+ throw new ApiRequestError(message, code, details);
16693
+ }
16694
+ exitWithError(message, code, details, exitCode);
16695
+ }
16499
16696
  async function refreshTokens(creds) {
16500
16697
  const baseUrl = getApiUrl();
16501
16698
  let res;
@@ -16539,20 +16736,20 @@ async function refreshTokens(creds) {
16539
16736
  await writeCredentials(updated);
16540
16737
  return updated;
16541
16738
  }
16542
- async function getAuthHeaders() {
16739
+ async function getAuthHeaders(softFail = false) {
16543
16740
  let creds = await readCredentials();
16544
16741
  if (!creds) return {};
16545
16742
  if (isTokenExpired(creds.accessToken)) {
16546
16743
  try {
16547
16744
  creds = await refreshTokens(creds);
16548
16745
  } catch (err) {
16549
- exitForRefreshFailure(err);
16746
+ exitForRefreshFailure(err, softFail);
16550
16747
  }
16551
16748
  }
16552
16749
  return { Authorization: `Bearer ${creds.accessToken}` };
16553
16750
  }
16554
16751
  async function apiRequest(path, opts = {}) {
16555
- const { method = "GET", body, query, skipAuth = false } = opts;
16752
+ const { method = "GET", body, query, skipAuth = false, softFail = false } = opts;
16556
16753
  const baseUrl = getApiUrl();
16557
16754
  const url2 = new URL(`${baseUrl}${path}`);
16558
16755
  if (query) {
@@ -16562,7 +16759,7 @@ async function apiRequest(path, opts = {}) {
16562
16759
  }
16563
16760
  const headers = { "Content-Type": "application/json" };
16564
16761
  if (!skipAuth) {
16565
- Object.assign(headers, await getAuthHeaders());
16762
+ Object.assign(headers, await getAuthHeaders(softFail));
16566
16763
  }
16567
16764
  const buildFetchOpts = () => ({
16568
16765
  method,
@@ -16575,14 +16772,16 @@ async function apiRequest(path, opts = {}) {
16575
16772
  res = await fetch(url2.toString(), buildFetchOpts());
16576
16773
  } catch (err) {
16577
16774
  if (err instanceof DOMException && err.name === "TimeoutError") {
16578
- exitWithError(
16775
+ failRequest(
16776
+ softFail,
16579
16777
  "Request timed out. Check your network connection and try again.",
16580
16778
  "TIMEOUT",
16581
16779
  void 0,
16582
16780
  EXIT.NETWORK
16583
16781
  );
16584
16782
  }
16585
- exitWithError(
16783
+ failRequest(
16784
+ softFail,
16586
16785
  `Network error: ${err instanceof Error ? err.message : "connection failed"}`,
16587
16786
  "NETWORK",
16588
16787
  void 0,
@@ -16596,23 +16795,25 @@ async function apiRequest(path, opts = {}) {
16596
16795
  try {
16597
16796
  refreshed = await refreshTokens(creds);
16598
16797
  } catch (refreshErr) {
16599
- exitForRefreshFailure(refreshErr);
16798
+ exitForRefreshFailure(refreshErr, softFail);
16600
16799
  }
16601
16800
  if (refreshed) {
16602
16801
  headers.Authorization = `Bearer ${refreshed.accessToken}`;
16603
16802
  try {
16604
16803
  const retryRes = await fetch(url2.toString(), buildFetchOpts());
16605
- return handleResponse(retryRes);
16804
+ return handleResponse(retryRes, softFail);
16606
16805
  } catch (retryErr) {
16607
16806
  if (retryErr instanceof DOMException && retryErr.name === "TimeoutError") {
16608
- exitWithError(
16807
+ failRequest(
16808
+ softFail,
16609
16809
  "Request timed out. Check your network connection and try again.",
16610
16810
  "TIMEOUT",
16611
16811
  void 0,
16612
16812
  EXIT.NETWORK
16613
16813
  );
16614
16814
  }
16615
- exitWithError(
16815
+ failRequest(
16816
+ softFail,
16616
16817
  `Network error: ${retryErr instanceof Error ? retryErr.message : "connection failed"}`,
16617
16818
  "NETWORK",
16618
16819
  void 0,
@@ -16622,7 +16823,7 @@ async function apiRequest(path, opts = {}) {
16622
16823
  }
16623
16824
  }
16624
16825
  }
16625
- return handleResponse(res);
16826
+ return handleResponse(res, softFail);
16626
16827
  }
16627
16828
  function exitCodeForStatus(status) {
16628
16829
  if (status === 400) return EXIT.VALIDATION;
@@ -16633,14 +16834,26 @@ function exitCodeForStatus(status) {
16633
16834
  if (status >= 500) return EXIT.SERVER_ERROR;
16634
16835
  return EXIT.SERVER_ERROR;
16635
16836
  }
16636
- async function handleResponse(res) {
16637
- const text = await res.text();
16837
+ async function handleResponse(res, softFail = false) {
16838
+ let text;
16839
+ try {
16840
+ text = await res.text();
16841
+ } catch (err) {
16842
+ failRequest(
16843
+ softFail,
16844
+ `Network error: ${err instanceof Error ? err.message : "connection failed"}`,
16845
+ "NETWORK",
16846
+ void 0,
16847
+ EXIT.NETWORK
16848
+ );
16849
+ }
16638
16850
  let parsed;
16639
16851
  try {
16640
16852
  parsed = JSON.parse(text);
16641
16853
  } catch {
16642
16854
  if (!res.ok) {
16643
- exitWithError(
16855
+ failRequest(
16856
+ softFail,
16644
16857
  `HTTP ${res.status}: ${text.slice(0, 200)}`,
16645
16858
  `HTTP_${res.status}`,
16646
16859
  void 0,
@@ -16656,14 +16869,26 @@ async function handleResponse(res) {
16656
16869
  if (isRecord2(nested)) {
16657
16870
  const code = typeof nested.code === "string" && nested.code.trim() ? nested.code : void 0;
16658
16871
  const details = coerceDetails(nested.details);
16659
- exitWithError(String(nested.message ?? `HTTP ${res.status}`), code, details, exitCode);
16872
+ failRequest(
16873
+ softFail,
16874
+ String(nested.message ?? `HTTP ${res.status}`),
16875
+ code,
16876
+ details,
16877
+ exitCode
16878
+ );
16660
16879
  }
16661
16880
  if (typeof nested === "string") {
16662
16881
  const code = typeof obj.code === "string" && obj.code.trim() ? obj.code : `HTTP_${res.status}`;
16663
16882
  const details = coerceDetails(obj.details);
16664
- exitWithError(nested, code, details, exitCode);
16883
+ failRequest(softFail, nested, code, details, exitCode);
16665
16884
  }
16666
- exitWithError(`HTTP ${res.status}: ${text.slice(0, 200)}`, void 0, void 0, exitCode);
16885
+ failRequest(
16886
+ softFail,
16887
+ `HTTP ${res.status}: ${text.slice(0, 200)}`,
16888
+ void 0,
16889
+ void 0,
16890
+ exitCode
16891
+ );
16667
16892
  }
16668
16893
  return parsed;
16669
16894
  }
@@ -16779,6 +17004,10 @@ Global:
16779
17004
  --json Machine-readable JSON output (API order, not confidence-sorted)`;
16780
17005
  var HELP_OBJECTS_GET = `objects get \u2014 Show one OCF object
16781
17006
 
17007
+ After the OCF payload, prints a one-line vesting summary when the object (or
17008
+ its vesting_terms_id target) has a condition graph. --json adds vestingSummary
17009
+ as a sibling of data \u2014 not inside ocf.
17010
+
16782
17011
  Usage:
16783
17012
  captable-ai objects get <object-id> [--json]`;
16784
17013
  var HELP_OBJECTS_DUMP = `objects dump \u2014 Engine-shaped ocf.json
@@ -19314,9 +19543,17 @@ async function runObjectsGet(args, rawArgv) {
19314
19543
  if (!obj) {
19315
19544
  exitWithError("Object not found", "OBJECT_NOT_FOUND", void 0, EXIT.NOT_FOUND);
19316
19545
  }
19546
+ const vestingSummary = await resolveVestingSummary(obj.ocf, async (termsId) => {
19547
+ const terms = await apiRequest(
19548
+ `/${portalId}/objects/${encodeURIComponent(termsId)}`,
19549
+ { softFail: true }
19550
+ );
19551
+ return terms.data?.ocf ?? null;
19552
+ });
19317
19553
  if (isJsonMode()) {
19318
19554
  const out = { data: obj };
19319
19555
  if (res.meta !== void 0) out.meta = res.meta;
19556
+ if (vestingSummary) out.vestingSummary = vestingSummary;
19320
19557
  printJson(out);
19321
19558
  return;
19322
19559
  }
@@ -19351,7 +19588,10 @@ Object: ${obj.id}
19351
19588
  }
19352
19589
  printLine("\nOCF data:\n");
19353
19590
  printJson(obj.ocf);
19354
- printLine();
19591
+ if (vestingSummary) printLine(`
19592
+ ${vestingSummary}
19593
+ `);
19594
+ else printLine();
19355
19595
  }
19356
19596
 
19357
19597
  // src/validate.ts
@@ -20990,7 +21230,7 @@ Cap Table (read):
20990
21230
  Review: --stakeholder, --document, --max-confidence N (requires --all)
20991
21231
  Pagination: --limit (1-250), --cursor, --all
20992
21232
  Dates: --from / --to as YYYY-MM-DD
20993
- objects get <id> Show a single OCF object
21233
+ objects get <id> Show a single OCF object (vesting summary when present)
20994
21234
  objects dump Engine-shaped ocf.json (default --status committed; --output path)
20995
21235
  schemas list List OCF object types
20996
21236
  schemas get <type> Show schema for an OCF type
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairmint/captable-ai",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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",