@fairmint/captable-ai 0.1.14 → 0.1.16

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 +205 -79
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -170,8 +170,11 @@ function parsePortals(body) {
170
170
  if (!portals.every(isStoredPortal)) return null;
171
171
  return portals;
172
172
  }
173
+ function cachedPortalsNeedNameRefresh(portals) {
174
+ return portals.some((portal) => typeof portal.name !== "string" || portal.name.trim() === "");
175
+ }
173
176
  async function refreshPortalsIfStale(creds, force = false) {
174
- if (!force && creds.portalsLastFetchedAt && Date.now() - Date.parse(creds.portalsLastFetchedAt) < PORTALS_TTL_MS) {
177
+ if (!force && !cachedPortalsNeedNameRefresh(creds.portals) && creds.portalsLastFetchedAt && Date.now() - Date.parse(creds.portalsLastFetchedAt) < PORTALS_TTL_MS) {
175
178
  return creds;
176
179
  }
177
180
  if (isTokenExpired(creds.accessToken)) {
@@ -205,6 +208,22 @@ async function markPortalsFetched(creds) {
205
208
  creds.portalsLastFetchedAt = (/* @__PURE__ */ new Date()).toISOString();
206
209
  await writeCredentials(creds);
207
210
  }
211
+ async function refreshCachedPortalNames() {
212
+ try {
213
+ const creds = await readCredentials();
214
+ if (!creds) return;
215
+ const updated = await refreshPortalsIfStale(creds, true);
216
+ const config2 = await readConfig();
217
+ const activeId = config2.activePortal?.id;
218
+ if (!activeId) return;
219
+ const portal = updated.portals.find((candidate) => candidate.id === activeId);
220
+ if (portal) await setActivePortal(portal);
221
+ } catch {
222
+ process.stderr.write(
223
+ "Warning: Could not refresh company name after updating the company list.\n"
224
+ );
225
+ }
226
+ }
208
227
 
209
228
  // src/output.ts
210
229
  import { renameSync, rmSync, writeFileSync } from "fs";
@@ -526,10 +545,73 @@ var COMMAND_REGISTRY = [
526
545
  {
527
546
  name: "onboarding setup",
528
547
  group: "onboarding",
529
- description: "Idempotently create the authenticated user's first production workspace.",
530
- usage: "captable-ai onboarding setup --legal-name <name> --company-type <type> --role <role> --cap-table-source <source> --stakeholder-count <bucket> --accept-terms [--country <code>] [--other-role <role>] [--other-cap-table-source <source>] [--json]",
548
+ description: "Idempotently create the authenticated user's first production workspace. Only --accept-terms is required; the company profile is read from uploaded documents.",
549
+ usage: "captable-ai onboarding setup --accept-terms [--legal-name <name>] [--company-type <type>] [--country <code>] [--role <role>] [--other-role <role>] [--cap-table-source <source>] [--other-cap-table-source <source>] [--stakeholder-count <bucket>] [--json]",
531
550
  requiresAuth: true,
532
- requiresPortal: false
551
+ requiresPortal: false,
552
+ flags: [
553
+ {
554
+ name: "accept-terms",
555
+ description: "Confirm acceptance of Fairmint's Terms of Service (required).",
556
+ type: "boolean"
557
+ },
558
+ {
559
+ name: "legal-name",
560
+ description: "Company name. Left unset until you or document extraction supplies one.",
561
+ type: "string"
562
+ },
563
+ {
564
+ name: "company-type",
565
+ description: "Legal entity type. Left unrecorded when omitted.",
566
+ type: "string",
567
+ enum: ["us_c_corporation", "us_llc", "foreign_entity", "not_incorporated"]
568
+ },
569
+ {
570
+ name: "country",
571
+ description: "ISO 3166-1 alpha-2 code. Only with --company-type foreign_entity.",
572
+ type: "string"
573
+ },
574
+ {
575
+ name: "role",
576
+ description: "Your role at the company.",
577
+ type: "string",
578
+ enum: [
579
+ "CEO",
580
+ "COO",
581
+ "CMO",
582
+ "CFO",
583
+ "CTO",
584
+ "CRO",
585
+ "Director",
586
+ "Founder",
587
+ "co-founder",
588
+ "co-CEO",
589
+ "other"
590
+ ]
591
+ },
592
+ {
593
+ name: "other-role",
594
+ description: "Free-text role. Required when --role other.",
595
+ type: "string"
596
+ },
597
+ {
598
+ name: "cap-table-source",
599
+ description: "Where the cap table is maintained today.",
600
+ type: "string",
601
+ enum: ["none", "spreadsheet", "carta", "pulley", "angellist", "other"]
602
+ },
603
+ {
604
+ name: "other-cap-table-source",
605
+ description: "Free-text source. Required when --cap-table-source other.",
606
+ type: "string"
607
+ },
608
+ {
609
+ name: "stakeholder-count",
610
+ description: "Approximate number of stakeholders on the cap table.",
611
+ type: "string",
612
+ enum: ["1_10", "11_25", "26_50", "50_100", "100_plus"]
613
+ }
614
+ ]
533
615
  },
534
616
  {
535
617
  name: "import start",
@@ -16637,60 +16719,109 @@ var hasControlCharacter = (value) => Array.from(value).some((character) => {
16637
16719
  });
16638
16720
  var legalNameSchema = shortTextSchema(255).refine((value) => !hasControlCharacter(value), "Legal name cannot contain control characters").describe("Confirmed legal name of the company.");
16639
16721
  var countryCodeSchema = external_exports.string().trim().transform((value) => value.toUpperCase()).pipe(external_exports.string().regex(/^[A-Z]{2}$/, "Must be an ISO 3166-1 alpha-2 country code")).describe("ISO 3166-1 alpha-2 country of incorporation. Required for foreign entities.");
16640
- var roleSchema = external_exports.discriminatedUnion("role", [
16641
- external_exports.object({
16642
- role: external_exports.enum(ONBOARDING_ROLES).describe("Founder's role at the company."),
16643
- otherRole: external_exports.never().optional().describe("Omit unless role is other.")
16644
- }),
16645
- external_exports.object({
16646
- role: external_exports.literal(ONBOARDING_OTHER_ROLE).describe("Founder's role at the company."),
16647
- otherRole: shortTextSchema(100).describe("Free-text role when role is other.")
16648
- })
16649
- ]);
16650
- var capTableSourceSchema = external_exports.discriminatedUnion("capTableSource", [
16651
- external_exports.object({
16652
- capTableSource: external_exports.enum([
16653
- ONBOARDING_CAP_TABLE_SOURCES.NONE,
16654
- ONBOARDING_CAP_TABLE_SOURCES.SPREADSHEET,
16655
- ONBOARDING_CAP_TABLE_SOURCES.CARTA,
16656
- ONBOARDING_CAP_TABLE_SOURCES.PULLEY,
16657
- ONBOARDING_CAP_TABLE_SOURCES.ANGELLIST
16658
- ]).describe("Where the current cap table is maintained."),
16659
- otherCapTableSource: external_exports.never().optional().describe("Omit unless capTableSource is other.")
16660
- }),
16661
- external_exports.object({
16662
- capTableSource: external_exports.literal(ONBOARDING_CAP_TABLE_SOURCES.OTHER).describe("Where the current cap table is maintained."),
16663
- otherCapTableSource: shortTextSchema(100).describe("Free-text cap table tool when capTableSource is other.")
16664
- })
16665
- ]);
16666
- var commonSetupSchema = external_exports.object({
16667
- legalName: legalNameSchema,
16668
- stakeholderCount: external_exports.enum(ONBOARDING_STAKEHOLDER_COUNT_BUCKETS).describe("Approximate number of stakeholders on the cap table."),
16669
- termsAccepted: external_exports.literal(true).describe("Whether the user accepted Fairmint's Terms of Service; must be true.")
16670
- }).and(roleSchema).and(capTableSourceSchema);
16671
- var companySchema = external_exports.discriminatedUnion("companyType", [
16672
- external_exports.object({
16673
- companyType: external_exports.enum([ONBOARDING_COMPANY_TYPES.US_C_CORPORATION, ONBOARDING_COMPANY_TYPES.US_LLC]).describe("Confirmed company type."),
16674
- country: external_exports.never().optional().describe("Omit for US companies.")
16675
- }),
16676
- external_exports.object({
16677
- companyType: external_exports.literal(ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY).describe("Confirmed company type."),
16678
- country: countryCodeSchema.refine((country) => country !== "US", {
16679
- message: "Foreign entities must use a non-US country"
16680
- }).describe("ISO 3166-1 alpha-2 country of incorporation. Required for foreign entities.")
16681
- }),
16682
- external_exports.object({
16683
- companyType: external_exports.literal(ONBOARDING_COMPANY_TYPES.NOT_INCORPORATED).describe("Confirmed company type."),
16684
- country: external_exports.never().optional().describe("Omit until the company is incorporated.")
16685
- })
16686
- ]);
16687
- var onboardingWorkspaceSetupInputSchema = commonSetupSchema.and(companySchema);
16722
+ var onboardingWorkspaceSetupInputSchema = external_exports.object({
16723
+ termsAccepted: external_exports.literal(true).describe("Whether the user accepted Fairmint's Terms of Service; must be true."),
16724
+ legalName: legalNameSchema.optional(),
16725
+ companyType: external_exports.enum([
16726
+ ONBOARDING_COMPANY_TYPES.US_C_CORPORATION,
16727
+ ONBOARDING_COMPANY_TYPES.US_LLC,
16728
+ ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY,
16729
+ ONBOARDING_COMPANY_TYPES.NOT_INCORPORATED
16730
+ ]).optional().describe("Confirmed company type. Left unrecorded when omitted."),
16731
+ country: countryCodeSchema.optional(),
16732
+ role: external_exports.enum([...ONBOARDING_ROLES, ONBOARDING_OTHER_ROLE]).optional().describe("Founder's role at the company."),
16733
+ otherRole: shortTextSchema(100).optional().describe("Free-text role when role is other."),
16734
+ capTableSource: external_exports.enum([
16735
+ ONBOARDING_CAP_TABLE_SOURCES.NONE,
16736
+ ONBOARDING_CAP_TABLE_SOURCES.SPREADSHEET,
16737
+ ONBOARDING_CAP_TABLE_SOURCES.CARTA,
16738
+ ONBOARDING_CAP_TABLE_SOURCES.PULLEY,
16739
+ ONBOARDING_CAP_TABLE_SOURCES.ANGELLIST,
16740
+ ONBOARDING_CAP_TABLE_SOURCES.OTHER
16741
+ ]).optional().describe("Where the current cap table is maintained."),
16742
+ otherCapTableSource: shortTextSchema(100).optional().describe("Free-text cap table tool when capTableSource is other."),
16743
+ stakeholderCount: external_exports.enum(ONBOARDING_STAKEHOLDER_COUNT_BUCKETS).optional().describe("Approximate number of stakeholders on the cap table.")
16744
+ }).superRefine((value, ctx) => {
16745
+ if (value.companyType === ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY) {
16746
+ if (value.country === void 0) {
16747
+ ctx.addIssue({
16748
+ code: "custom",
16749
+ path: ["country"],
16750
+ message: "Foreign entities must provide a country of incorporation"
16751
+ });
16752
+ } else if (value.country === "US") {
16753
+ ctx.addIssue({
16754
+ code: "custom",
16755
+ path: ["country"],
16756
+ message: "Foreign entities must use a non-US country"
16757
+ });
16758
+ }
16759
+ } else if (value.country !== void 0) {
16760
+ ctx.addIssue({
16761
+ code: "custom",
16762
+ path: ["country"],
16763
+ message: "Omit country unless companyType is foreign_entity"
16764
+ });
16765
+ }
16766
+ if (value.role === ONBOARDING_OTHER_ROLE) {
16767
+ if (value.otherRole === void 0) {
16768
+ ctx.addIssue({
16769
+ code: "custom",
16770
+ path: ["otherRole"],
16771
+ message: "Describe the role when role is other"
16772
+ });
16773
+ }
16774
+ } else if (value.otherRole !== void 0) {
16775
+ ctx.addIssue({
16776
+ code: "custom",
16777
+ path: ["otherRole"],
16778
+ message: "Omit otherRole unless role is other"
16779
+ });
16780
+ }
16781
+ if (value.capTableSource === ONBOARDING_CAP_TABLE_SOURCES.OTHER) {
16782
+ if (value.otherCapTableSource === void 0) {
16783
+ ctx.addIssue({
16784
+ code: "custom",
16785
+ path: ["otherCapTableSource"],
16786
+ message: "Describe the cap table source when capTableSource is other"
16787
+ });
16788
+ }
16789
+ } else if (value.otherCapTableSource !== void 0) {
16790
+ ctx.addIssue({
16791
+ code: "custom",
16792
+ path: ["otherCapTableSource"],
16793
+ message: "Omit otherCapTableSource unless capTableSource is other"
16794
+ });
16795
+ }
16796
+ });
16797
+ function dropOrphanOnboardingSetupDetails(input) {
16798
+ const next = { ...input };
16799
+ if (next.companyType !== void 0 && next.companyType !== ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY) {
16800
+ delete next.country;
16801
+ }
16802
+ if (next.role !== void 0 && next.role !== ONBOARDING_OTHER_ROLE) {
16803
+ delete next.otherRole;
16804
+ }
16805
+ if (next.capTableSource !== void 0 && next.capTableSource !== ONBOARDING_CAP_TABLE_SOURCES.OTHER) {
16806
+ delete next.otherCapTableSource;
16807
+ }
16808
+ return next;
16809
+ }
16810
+ function resolveOnboardingCompanyProfile(input) {
16811
+ const companyType = input.companyType ?? null;
16812
+ const country = companyType === null ? null : companyType === ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY ? input.country ?? null : ONBOARDING_COMPANY_TYPE_TO_ISSUER[companyType].countryOfFormation;
16813
+ return {
16814
+ legalName: input.legalName ?? null,
16815
+ companyType,
16816
+ country
16817
+ };
16818
+ }
16688
16819
  var onboardingWorkspaceSetupResultSchema = external_exports.object({
16689
16820
  portalId: external_exports.uuid().describe("ID of the user's first production workspace."),
16690
16821
  state: external_exports.literal(ONBOARDING_WORKSPACE_STATES.WORKSPACE_READY).describe("Workspace is created or reused and ready for import."),
16691
16822
  created: external_exports.boolean().describe("True when this request inserted a new portal."),
16692
- commitReady: external_exports.boolean().describe("True when the draft issuer has formation date and country of formation."),
16693
- commitBlockers: external_exports.array(external_exports.enum(["formation_date", "country_of_formation"])).describe("Issuer fields that still block a live commit."),
16823
+ commitReady: external_exports.boolean().describe("True when the draft issuer has legal name, formation date, and country of formation."),
16824
+ commitBlockers: external_exports.array(external_exports.enum(["legal_name", "formation_date", "country_of_formation"])).describe("Issuer fields that still block a live commit."),
16694
16825
  session: external_exports.object({
16695
16826
  action: external_exports.enum([
16696
16827
  ONBOARDING_SESSION_ACTIONS.NONE,
@@ -17244,20 +17375,24 @@ var HELP_ONBOARDING = `onboarding \u2014 Create a first workspace and inspect ne
17244
17375
 
17245
17376
  Usage:
17246
17377
  captable-ai onboarding status [--session <import-session-id>] [--json]
17247
- captable-ai onboarding setup --legal-name <name> --company-type <type>
17248
- --role <role> --cap-table-source <source> --stakeholder-count <bucket>
17249
- --accept-terms [options] [--json]
17378
+ captable-ai onboarding setup --accept-terms [--legal-name <name>] [options] [--json]
17379
+
17380
+ Accepting the Terms is all that is required. The legal name, entity type, and
17381
+ country are read from the documents you upload, so you can skip them here and
17382
+ start importing right away.
17250
17383
 
17251
17384
  Setup values:
17385
+ --accept-terms Confirm acceptance of Fairmint's Terms of Service (required)
17386
+ --legal-name Company name. Left unset until you or document
17387
+ extraction supplies one; commit stays blocked until then.
17252
17388
  --company-type us_c_corporation | us_llc | foreign_entity | not_incorporated
17253
- --country ISO country code (required for foreign_entity)
17389
+ --country ISO country code (only with --company-type foreign_entity)
17254
17390
  --role CEO | COO | CMO | CFO | CTO | CRO | Director | Founder |
17255
17391
  co-founder | co-CEO | other
17256
17392
  --other-role Required when --role other
17257
17393
  --cap-table-source none | spreadsheet | carta | pulley | angellist | other
17258
17394
  --other-cap-table-source Required when --cap-table-source other
17259
- --stakeholder-count 1_10 | 11_25 | 26_50 | 50_100 | 100_plus
17260
- --accept-terms Confirm acceptance of Fairmint's Terms of Service`;
17395
+ --stakeholder-count 1_10 | 11_25 | 26_50 | 50_100 | 100_plus`;
17261
17396
  var HELP_IMPORT = `import \u2014 Start and inspect a secure document-import session
17262
17397
 
17263
17398
  Usage:
@@ -17855,6 +17990,7 @@ Committing ${countCopy}...
17855
17990
  exitWithError("Unexpected empty response from server.");
17856
17991
  }
17857
17992
  const data = res.data;
17993
+ await refreshCachedPortalNames();
17858
17994
  if (isJsonMode()) {
17859
17995
  printJson({ data });
17860
17996
  return;
@@ -20476,6 +20612,7 @@ Reverting ${selection.objectIds.length} selected draft object(s)...
20476
20612
  if (!res.data) {
20477
20613
  exitWithError("Unexpected empty response from server.");
20478
20614
  }
20615
+ await refreshCachedPortalNames();
20479
20616
  if (isJsonMode()) {
20480
20617
  printJson({ data: res.data });
20481
20618
  return;
@@ -20865,10 +21002,10 @@ async function runOnboardingStatus(args) {
20865
21002
  nextAction(
20866
21003
  "setup_workspace",
20867
21004
  "Create your first workspace",
20868
- "captable-ai onboarding setup"
21005
+ "captable-ai onboarding setup --accept-terms"
20869
21006
  )
20870
21007
  ],
20871
- question: "What is your company's full legal name?"
21008
+ question: "Do you accept Fairmint's Terms of Service? That is all we need to open your workspace."
20872
21009
  } : !active ? {
20873
21010
  state: ONBOARDING_STATES.NEEDS_COMPANY_SELECTION,
20874
21011
  companies: credentials.portals,
@@ -20929,19 +21066,6 @@ function setupInput(args) {
20929
21066
  }
20930
21067
  return input;
20931
21068
  }
20932
- function omitInapplicableOnboardingSetupFields(input) {
20933
- const next = { ...input };
20934
- if (next.companyType !== ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY) {
20935
- delete next.country;
20936
- }
20937
- if (next.role !== ONBOARDING_OTHER_ROLE) {
20938
- delete next.otherRole;
20939
- }
20940
- if (next.capTableSource !== ONBOARDING_CAP_TABLE_SOURCES.OTHER) {
20941
- delete next.otherCapTableSource;
20942
- }
20943
- return next;
20944
- }
20945
21069
  async function runOnboardingSetup(args) {
20946
21070
  validateFlags(args, {
20947
21071
  flags: [
@@ -20957,7 +21081,7 @@ async function runOnboardingSetup(args) {
20957
21081
  booleans: ["accept-terms"]
20958
21082
  });
20959
21083
  const parsed = onboardingWorkspaceSetupInputSchema.safeParse(
20960
- omitInapplicableOnboardingSetupFields(setupInput(args))
21084
+ dropOrphanOnboardingSetupDetails(setupInput(args))
20961
21085
  );
20962
21086
  if (!parsed.success) {
20963
21087
  const details = parsed.error.issues.map((issue2) => ({
@@ -20965,13 +21089,15 @@ async function runOnboardingSetup(args) {
20965
21089
  message: issue2.message
20966
21090
  }));
20967
21091
  exitWithError(
20968
- "Company setup answers are incomplete or invalid. Run `captable-ai onboarding setup --help` for accepted values.",
21092
+ "Company setup answers are invalid. Only `--accept-terms` is required on its own, but some answers require a partner flag: `--company-type foreign_entity` needs `--country`, `--role other` needs `--other-role`, and `--cap-table-source other` needs `--other-cap-table-source`. Run `captable-ai onboarding setup --help` for accepted values.",
20969
21093
  "VALIDATION_FAILED",
20970
21094
  details,
20971
21095
  EXIT.VALIDATION
20972
21096
  );
20973
21097
  }
20974
21098
  const input = parsed.data;
21099
+ const { legalName } = resolveOnboardingCompanyProfile(input);
21100
+ const workspaceLabel = legalName ?? "your new workspace";
20975
21101
  const response = await apiRequest("/onboarding/workspace", { method: "POST", body: input });
20976
21102
  const result = onboardingWorkspaceSetupResultSchema.safeParse(response.data ?? response);
20977
21103
  if (!result.success) {
@@ -21007,7 +21133,7 @@ async function runOnboardingSetup(args) {
21007
21133
  printJson({ data: data2 });
21008
21134
  return;
21009
21135
  }
21010
- printSuccess(`Workspace ready: ${input.legalName} (${result.data.portalId})`);
21136
+ printSuccess(`Workspace ready: ${workspaceLabel} (${result.data.portalId})`);
21011
21137
  printLine("Start the import: captable-ai import start");
21012
21138
  return;
21013
21139
  }
@@ -21023,7 +21149,7 @@ async function runOnboardingSetup(args) {
21023
21149
  printJson({ data });
21024
21150
  return;
21025
21151
  }
21026
- printSuccess(`Workspace ready: ${input.legalName} (${result.data.portalId})`);
21152
+ printSuccess(`Workspace ready: ${workspaceLabel} (${result.data.portalId})`);
21027
21153
  printLine("Reconnect so your session can access it: captable-ai login");
21028
21154
  }
21029
21155
  async function runImportStart(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairmint/captable-ai",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
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",