@fairmint/captable-ai 0.1.13 → 0.1.15
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/README.md +7 -1
- package/dist/index.js +635 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,8 +30,12 @@ the login code back into the terminal. Credentials are stored in `~/.captable-ai
|
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
captable-ai login
|
|
33
|
-
captable-ai
|
|
33
|
+
captable-ai onboarding status
|
|
34
|
+
captable-ai onboarding setup ... # only when you have no workspace
|
|
35
|
+
captable-ai company use "Acme Inc" # if you have more than one
|
|
36
|
+
captable-ai import start
|
|
34
37
|
captable-ai documents upload ./docs/*.pdf
|
|
38
|
+
captable-ai import status <session-id>
|
|
35
39
|
captable-ai documents trigger-extraction # once all docs are CLASSIFIED
|
|
36
40
|
captable-ai documents list # poll until PROCESSED
|
|
37
41
|
captable-ai commit --dry-run # preview
|
|
@@ -84,6 +88,8 @@ automated, but a few habits make extraction faster and more accurate:
|
|
|
84
88
|
|---------|-------------|
|
|
85
89
|
| `login` / `logout` | Authenticate / clear stored credentials |
|
|
86
90
|
| `auth status` | Show the current session |
|
|
91
|
+
| `onboarding status\|setup` | Inspect onboarding or create the first workspace |
|
|
92
|
+
| `import start\|status` | Start an import session and poll existing document processing |
|
|
87
93
|
| `company list\|use\|current\|create\|delete` | Manage and select the active company |
|
|
88
94
|
| `documents upload\|list\|download\|delete` | Manage source documents |
|
|
89
95
|
| `documents trigger-extraction\|cancel-extraction\|restart-extraction\|reprocess` | Control the OCF extraction pipeline |
|
package/dist/index.js
CHANGED
|
@@ -170,8 +170,8 @@ function parsePortals(body) {
|
|
|
170
170
|
if (!portals.every(isStoredPortal)) return null;
|
|
171
171
|
return portals;
|
|
172
172
|
}
|
|
173
|
-
async function refreshPortalsIfStale(creds) {
|
|
174
|
-
if (creds.portalsLastFetchedAt && Date.now() - Date.parse(creds.portalsLastFetchedAt) < PORTALS_TTL_MS) {
|
|
173
|
+
async function refreshPortalsIfStale(creds, force = false) {
|
|
174
|
+
if (!force && creds.portalsLastFetchedAt && Date.now() - Date.parse(creds.portalsLastFetchedAt) < PORTALS_TTL_MS) {
|
|
175
175
|
return creds;
|
|
176
176
|
}
|
|
177
177
|
if (isTokenExpired(creds.accessToken)) {
|
|
@@ -508,6 +508,108 @@ var COMMAND_REGISTRY = [
|
|
|
508
508
|
requiresPortal: false,
|
|
509
509
|
flags: [{ name: "yes", description: "Skip the confirmation prompt.", type: "boolean" }]
|
|
510
510
|
},
|
|
511
|
+
{
|
|
512
|
+
name: "onboarding status",
|
|
513
|
+
group: "onboarding",
|
|
514
|
+
description: "Show the next first-workspace or import action. Works for authenticated users with zero companies.",
|
|
515
|
+
usage: "captable-ai onboarding status [--session <import-session-id>] [--json]",
|
|
516
|
+
requiresAuth: true,
|
|
517
|
+
requiresPortal: false,
|
|
518
|
+
flags: [
|
|
519
|
+
{
|
|
520
|
+
name: "session",
|
|
521
|
+
description: "Inspect this import session instead of local workspace state.",
|
|
522
|
+
type: "string"
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: "onboarding setup",
|
|
528
|
+
group: "onboarding",
|
|
529
|
+
description: "Idempotently create the authenticated user's first production workspace. Only --accept-terms is required; the company profile is read from uploaded documents.",
|
|
530
|
+
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
|
+
requiresAuth: true,
|
|
532
|
+
requiresPortal: false,
|
|
533
|
+
flags: [
|
|
534
|
+
{
|
|
535
|
+
name: "accept-terms",
|
|
536
|
+
description: "Confirm acceptance of Fairmint's Terms of Service (required).",
|
|
537
|
+
type: "boolean"
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: "legal-name",
|
|
541
|
+
description: "Company name. Left unset until you or document extraction supplies one.",
|
|
542
|
+
type: "string"
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "company-type",
|
|
546
|
+
description: "Legal entity type. Left unrecorded when omitted.",
|
|
547
|
+
type: "string",
|
|
548
|
+
enum: ["us_c_corporation", "us_llc", "foreign_entity", "not_incorporated"]
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: "country",
|
|
552
|
+
description: "ISO 3166-1 alpha-2 code. Only with --company-type foreign_entity.",
|
|
553
|
+
type: "string"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "role",
|
|
557
|
+
description: "Your role at the company.",
|
|
558
|
+
type: "string",
|
|
559
|
+
enum: [
|
|
560
|
+
"CEO",
|
|
561
|
+
"COO",
|
|
562
|
+
"CMO",
|
|
563
|
+
"CFO",
|
|
564
|
+
"CTO",
|
|
565
|
+
"CRO",
|
|
566
|
+
"Director",
|
|
567
|
+
"Founder",
|
|
568
|
+
"co-founder",
|
|
569
|
+
"co-CEO",
|
|
570
|
+
"other"
|
|
571
|
+
]
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "other-role",
|
|
575
|
+
description: "Free-text role. Required when --role other.",
|
|
576
|
+
type: "string"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: "cap-table-source",
|
|
580
|
+
description: "Where the cap table is maintained today.",
|
|
581
|
+
type: "string",
|
|
582
|
+
enum: ["none", "spreadsheet", "carta", "pulley", "angellist", "other"]
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "other-cap-table-source",
|
|
586
|
+
description: "Free-text source. Required when --cap-table-source other.",
|
|
587
|
+
type: "string"
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: "stakeholder-count",
|
|
591
|
+
description: "Approximate number of stakeholders on the cap table.",
|
|
592
|
+
type: "string",
|
|
593
|
+
enum: ["1_10", "11_25", "26_50", "50_100", "100_plus"]
|
|
594
|
+
}
|
|
595
|
+
]
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: "import start",
|
|
599
|
+
group: "onboarding",
|
|
600
|
+
description: "Start or reuse a secure import session for existing document upload APIs.",
|
|
601
|
+
usage: "captable-ai import start [--json]",
|
|
602
|
+
requiresAuth: true,
|
|
603
|
+
requiresPortal: true
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
name: "import status",
|
|
607
|
+
group: "onboarding",
|
|
608
|
+
description: "Show import processing progress and the next existing-pipeline action.",
|
|
609
|
+
usage: "captable-ai import status <session-id> [--json]",
|
|
610
|
+
requiresAuth: true,
|
|
611
|
+
requiresPortal: false
|
|
612
|
+
},
|
|
511
613
|
{
|
|
512
614
|
name: "summary",
|
|
513
615
|
group: "captable",
|
|
@@ -1184,7 +1286,7 @@ var COMMAND_REGISTRY = [
|
|
|
1184
1286
|
}
|
|
1185
1287
|
];
|
|
1186
1288
|
async function runCommands(_args) {
|
|
1187
|
-
printJson({ schemaVersion: "
|
|
1289
|
+
printJson({ schemaVersion: "10", version: "0.0.1", commands: COMMAND_REGISTRY });
|
|
1188
1290
|
}
|
|
1189
1291
|
|
|
1190
1292
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
@@ -16598,60 +16700,109 @@ var hasControlCharacter = (value) => Array.from(value).some((character) => {
|
|
|
16598
16700
|
});
|
|
16599
16701
|
var legalNameSchema = shortTextSchema(255).refine((value) => !hasControlCharacter(value), "Legal name cannot contain control characters").describe("Confirmed legal name of the company.");
|
|
16600
16702
|
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.");
|
|
16601
|
-
var
|
|
16602
|
-
external_exports.
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
external_exports.
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
|
|
16626
|
-
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
}
|
|
16632
|
-
|
|
16633
|
-
|
|
16634
|
-
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
|
|
16638
|
-
|
|
16639
|
-
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16647
|
-
|
|
16648
|
-
|
|
16703
|
+
var onboardingWorkspaceSetupInputSchema = external_exports.object({
|
|
16704
|
+
termsAccepted: external_exports.literal(true).describe("Whether the user accepted Fairmint's Terms of Service; must be true."),
|
|
16705
|
+
legalName: legalNameSchema.optional(),
|
|
16706
|
+
companyType: external_exports.enum([
|
|
16707
|
+
ONBOARDING_COMPANY_TYPES.US_C_CORPORATION,
|
|
16708
|
+
ONBOARDING_COMPANY_TYPES.US_LLC,
|
|
16709
|
+
ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY,
|
|
16710
|
+
ONBOARDING_COMPANY_TYPES.NOT_INCORPORATED
|
|
16711
|
+
]).optional().describe("Confirmed company type. Left unrecorded when omitted."),
|
|
16712
|
+
country: countryCodeSchema.optional(),
|
|
16713
|
+
role: external_exports.enum([...ONBOARDING_ROLES, ONBOARDING_OTHER_ROLE]).optional().describe("Founder's role at the company."),
|
|
16714
|
+
otherRole: shortTextSchema(100).optional().describe("Free-text role when role is other."),
|
|
16715
|
+
capTableSource: external_exports.enum([
|
|
16716
|
+
ONBOARDING_CAP_TABLE_SOURCES.NONE,
|
|
16717
|
+
ONBOARDING_CAP_TABLE_SOURCES.SPREADSHEET,
|
|
16718
|
+
ONBOARDING_CAP_TABLE_SOURCES.CARTA,
|
|
16719
|
+
ONBOARDING_CAP_TABLE_SOURCES.PULLEY,
|
|
16720
|
+
ONBOARDING_CAP_TABLE_SOURCES.ANGELLIST,
|
|
16721
|
+
ONBOARDING_CAP_TABLE_SOURCES.OTHER
|
|
16722
|
+
]).optional().describe("Where the current cap table is maintained."),
|
|
16723
|
+
otherCapTableSource: shortTextSchema(100).optional().describe("Free-text cap table tool when capTableSource is other."),
|
|
16724
|
+
stakeholderCount: external_exports.enum(ONBOARDING_STAKEHOLDER_COUNT_BUCKETS).optional().describe("Approximate number of stakeholders on the cap table.")
|
|
16725
|
+
}).superRefine((value, ctx) => {
|
|
16726
|
+
if (value.companyType === ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY) {
|
|
16727
|
+
if (value.country === void 0) {
|
|
16728
|
+
ctx.addIssue({
|
|
16729
|
+
code: "custom",
|
|
16730
|
+
path: ["country"],
|
|
16731
|
+
message: "Foreign entities must provide a country of incorporation"
|
|
16732
|
+
});
|
|
16733
|
+
} else if (value.country === "US") {
|
|
16734
|
+
ctx.addIssue({
|
|
16735
|
+
code: "custom",
|
|
16736
|
+
path: ["country"],
|
|
16737
|
+
message: "Foreign entities must use a non-US country"
|
|
16738
|
+
});
|
|
16739
|
+
}
|
|
16740
|
+
} else if (value.country !== void 0) {
|
|
16741
|
+
ctx.addIssue({
|
|
16742
|
+
code: "custom",
|
|
16743
|
+
path: ["country"],
|
|
16744
|
+
message: "Omit country unless companyType is foreign_entity"
|
|
16745
|
+
});
|
|
16746
|
+
}
|
|
16747
|
+
if (value.role === ONBOARDING_OTHER_ROLE) {
|
|
16748
|
+
if (value.otherRole === void 0) {
|
|
16749
|
+
ctx.addIssue({
|
|
16750
|
+
code: "custom",
|
|
16751
|
+
path: ["otherRole"],
|
|
16752
|
+
message: "Describe the role when role is other"
|
|
16753
|
+
});
|
|
16754
|
+
}
|
|
16755
|
+
} else if (value.otherRole !== void 0) {
|
|
16756
|
+
ctx.addIssue({
|
|
16757
|
+
code: "custom",
|
|
16758
|
+
path: ["otherRole"],
|
|
16759
|
+
message: "Omit otherRole unless role is other"
|
|
16760
|
+
});
|
|
16761
|
+
}
|
|
16762
|
+
if (value.capTableSource === ONBOARDING_CAP_TABLE_SOURCES.OTHER) {
|
|
16763
|
+
if (value.otherCapTableSource === void 0) {
|
|
16764
|
+
ctx.addIssue({
|
|
16765
|
+
code: "custom",
|
|
16766
|
+
path: ["otherCapTableSource"],
|
|
16767
|
+
message: "Describe the cap table source when capTableSource is other"
|
|
16768
|
+
});
|
|
16769
|
+
}
|
|
16770
|
+
} else if (value.otherCapTableSource !== void 0) {
|
|
16771
|
+
ctx.addIssue({
|
|
16772
|
+
code: "custom",
|
|
16773
|
+
path: ["otherCapTableSource"],
|
|
16774
|
+
message: "Omit otherCapTableSource unless capTableSource is other"
|
|
16775
|
+
});
|
|
16776
|
+
}
|
|
16777
|
+
});
|
|
16778
|
+
function dropOrphanOnboardingSetupDetails(input) {
|
|
16779
|
+
const next = { ...input };
|
|
16780
|
+
if (next.companyType !== void 0 && next.companyType !== ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY) {
|
|
16781
|
+
delete next.country;
|
|
16782
|
+
}
|
|
16783
|
+
if (next.role !== void 0 && next.role !== ONBOARDING_OTHER_ROLE) {
|
|
16784
|
+
delete next.otherRole;
|
|
16785
|
+
}
|
|
16786
|
+
if (next.capTableSource !== void 0 && next.capTableSource !== ONBOARDING_CAP_TABLE_SOURCES.OTHER) {
|
|
16787
|
+
delete next.otherCapTableSource;
|
|
16788
|
+
}
|
|
16789
|
+
return next;
|
|
16790
|
+
}
|
|
16791
|
+
function resolveOnboardingCompanyProfile(input) {
|
|
16792
|
+
const companyType = input.companyType ?? null;
|
|
16793
|
+
const country = companyType === null ? null : companyType === ONBOARDING_COMPANY_TYPES.FOREIGN_ENTITY ? input.country ?? null : ONBOARDING_COMPANY_TYPE_TO_ISSUER[companyType].countryOfFormation;
|
|
16794
|
+
return {
|
|
16795
|
+
legalName: input.legalName ?? null,
|
|
16796
|
+
companyType,
|
|
16797
|
+
country
|
|
16798
|
+
};
|
|
16799
|
+
}
|
|
16649
16800
|
var onboardingWorkspaceSetupResultSchema = external_exports.object({
|
|
16650
16801
|
portalId: external_exports.uuid().describe("ID of the user's first production workspace."),
|
|
16651
16802
|
state: external_exports.literal(ONBOARDING_WORKSPACE_STATES.WORKSPACE_READY).describe("Workspace is created or reused and ready for import."),
|
|
16652
16803
|
created: external_exports.boolean().describe("True when this request inserted a new portal."),
|
|
16653
|
-
commitReady: external_exports.boolean().describe("True when the draft issuer has formation date and country of formation."),
|
|
16654
|
-
commitBlockers: external_exports.array(external_exports.enum(["formation_date", "country_of_formation"])).describe("Issuer fields that still block a live commit."),
|
|
16804
|
+
commitReady: external_exports.boolean().describe("True when the draft issuer has legal name, formation date, and country of formation."),
|
|
16805
|
+
commitBlockers: external_exports.array(external_exports.enum(["legal_name", "formation_date", "country_of_formation"])).describe("Issuer fields that still block a live commit."),
|
|
16655
16806
|
session: external_exports.object({
|
|
16656
16807
|
action: external_exports.enum([
|
|
16657
16808
|
ONBOARDING_SESSION_ACTIONS.NONE,
|
|
@@ -16982,6 +17133,13 @@ async function refreshTokens(creds) {
|
|
|
16982
17133
|
await writeCredentials(updated);
|
|
16983
17134
|
return updated;
|
|
16984
17135
|
}
|
|
17136
|
+
async function refreshSession() {
|
|
17137
|
+
const credentials = await readCredentials();
|
|
17138
|
+
if (!credentials) {
|
|
17139
|
+
exitWithError("Not logged in. Run: captable-ai login", "AUTH_REQUIRED", void 0, EXIT.AUTH);
|
|
17140
|
+
}
|
|
17141
|
+
return refreshTokens(credentials);
|
|
17142
|
+
}
|
|
16985
17143
|
async function getAuthHeaders(softFail = false) {
|
|
16986
17144
|
let creds = await readCredentials();
|
|
16987
17145
|
if (!creds) return {};
|
|
@@ -17194,6 +17352,40 @@ function argvRequestsHelp(argv) {
|
|
|
17194
17352
|
}
|
|
17195
17353
|
|
|
17196
17354
|
// src/help-text.ts
|
|
17355
|
+
var HELP_ONBOARDING = `onboarding \u2014 Create a first workspace and inspect next steps
|
|
17356
|
+
|
|
17357
|
+
Usage:
|
|
17358
|
+
captable-ai onboarding status [--session <import-session-id>] [--json]
|
|
17359
|
+
captable-ai onboarding setup --accept-terms [--legal-name <name>] [options] [--json]
|
|
17360
|
+
|
|
17361
|
+
Accepting the Terms is all that is required. The legal name, entity type, and
|
|
17362
|
+
country are read from the documents you upload, so you can skip them here and
|
|
17363
|
+
start importing right away.
|
|
17364
|
+
|
|
17365
|
+
Setup values:
|
|
17366
|
+
--accept-terms Confirm acceptance of Fairmint's Terms of Service (required)
|
|
17367
|
+
--legal-name Company name. Left unset until you or document
|
|
17368
|
+
extraction supplies one; commit stays blocked until then.
|
|
17369
|
+
--company-type us_c_corporation | us_llc | foreign_entity | not_incorporated
|
|
17370
|
+
--country ISO country code (only with --company-type foreign_entity)
|
|
17371
|
+
--role CEO | COO | CMO | CFO | CTO | CRO | Director | Founder |
|
|
17372
|
+
co-founder | co-CEO | other
|
|
17373
|
+
--other-role Required when --role other
|
|
17374
|
+
--cap-table-source none | spreadsheet | carta | pulley | angellist | other
|
|
17375
|
+
--other-cap-table-source Required when --cap-table-source other
|
|
17376
|
+
--stakeholder-count 1_10 | 11_25 | 26_50 | 50_100 | 100_plus`;
|
|
17377
|
+
var HELP_IMPORT = `import \u2014 Start and inspect a secure document-import session
|
|
17378
|
+
|
|
17379
|
+
Usage:
|
|
17380
|
+
captable-ai import start [--json]
|
|
17381
|
+
captable-ai import status <session-id> [--json]
|
|
17382
|
+
|
|
17383
|
+
For local files, the recommended data path remains:
|
|
17384
|
+
captable-ai documents upload <file...>
|
|
17385
|
+
|
|
17386
|
+
It performs prepare \u2192 direct presigned S3 PUT \u2192 confirm without routing file bytes
|
|
17387
|
+
through the CaptableAI API. After processing finishes, review drafts with
|
|
17388
|
+
\`objects list\` and commit with the existing \`commit --dry-run\` / \`commit --all\` commands.`;
|
|
17197
17389
|
var HELP_AUTH = `auth \u2014 Session information
|
|
17198
17390
|
|
|
17199
17391
|
Usage:
|
|
@@ -20767,6 +20959,344 @@ async function runObjectsVersions(args, rawArgv) {
|
|
|
20767
20959
|
}
|
|
20768
20960
|
}
|
|
20769
20961
|
|
|
20962
|
+
// src/commands/onboarding.ts
|
|
20963
|
+
function nextAction(id, label, command) {
|
|
20964
|
+
return command ? { id, label, required: true, command } : { id, label, required: true };
|
|
20965
|
+
}
|
|
20966
|
+
async function runOnboardingStatus(args) {
|
|
20967
|
+
validateFlags(args, { flags: ["session"], booleans: [] });
|
|
20968
|
+
const sessionId = typeof args.flags.session === "string" ? args.flags.session : void 0;
|
|
20969
|
+
if (sessionId) {
|
|
20970
|
+
await runImportStatus(parseArgs([sessionId]));
|
|
20971
|
+
return;
|
|
20972
|
+
}
|
|
20973
|
+
const credentials = await readCredentials();
|
|
20974
|
+
if (!credentials) {
|
|
20975
|
+
exitWithError("Not logged in. Run: captable-ai login", "AUTH_REQUIRED", void 0, EXIT.AUTH);
|
|
20976
|
+
}
|
|
20977
|
+
const active = resolveActiveCompany(await readConfig(), credentials.portals);
|
|
20978
|
+
const data = credentials.portals.length === 0 ? {
|
|
20979
|
+
state: ONBOARDING_STATES.NEEDS_COMPANY_SETUP,
|
|
20980
|
+
nextActions: [
|
|
20981
|
+
nextAction(
|
|
20982
|
+
"setup_workspace",
|
|
20983
|
+
"Create your first workspace",
|
|
20984
|
+
"captable-ai onboarding setup --accept-terms"
|
|
20985
|
+
)
|
|
20986
|
+
],
|
|
20987
|
+
question: "Do you accept Fairmint's Terms of Service? That is all we need to open your workspace."
|
|
20988
|
+
} : !active ? {
|
|
20989
|
+
state: ONBOARDING_STATES.NEEDS_COMPANY_SELECTION,
|
|
20990
|
+
companies: credentials.portals,
|
|
20991
|
+
nextActions: [
|
|
20992
|
+
nextAction(
|
|
20993
|
+
"select_company",
|
|
20994
|
+
"Choose the company to import into",
|
|
20995
|
+
"captable-ai company use <id-or-name>"
|
|
20996
|
+
)
|
|
20997
|
+
],
|
|
20998
|
+
question: "Which company should receive this cap table import?"
|
|
20999
|
+
} : {
|
|
21000
|
+
state: ONBOARDING_STATES.READY_TO_IMPORT,
|
|
21001
|
+
company: active,
|
|
21002
|
+
nextActions: [
|
|
21003
|
+
nextAction(
|
|
21004
|
+
"start_import",
|
|
21005
|
+
"Start a secure import session",
|
|
21006
|
+
"captable-ai import start"
|
|
21007
|
+
),
|
|
21008
|
+
nextAction(
|
|
21009
|
+
"upload_cli",
|
|
21010
|
+
"Upload local documents directly to S3",
|
|
21011
|
+
"captable-ai documents upload <file...>"
|
|
21012
|
+
)
|
|
21013
|
+
],
|
|
21014
|
+
question: `Should I start a secure import session for ${active.name ?? active.id}?`
|
|
21015
|
+
};
|
|
21016
|
+
if (isJsonMode()) {
|
|
21017
|
+
printJson({ data });
|
|
21018
|
+
return;
|
|
21019
|
+
}
|
|
21020
|
+
printKeyValue([
|
|
21021
|
+
["State", data.state],
|
|
21022
|
+
["Question", data.question]
|
|
21023
|
+
]);
|
|
21024
|
+
for (const action of data.nextActions) {
|
|
21025
|
+
printLine(` Next: ${String(action.command)}`);
|
|
21026
|
+
}
|
|
21027
|
+
}
|
|
21028
|
+
function setupInput(args) {
|
|
21029
|
+
const flagMap = {
|
|
21030
|
+
legalName: "legal-name",
|
|
21031
|
+
companyType: "company-type",
|
|
21032
|
+
country: "country",
|
|
21033
|
+
role: "role",
|
|
21034
|
+
otherRole: "other-role",
|
|
21035
|
+
capTableSource: "cap-table-source",
|
|
21036
|
+
otherCapTableSource: "other-cap-table-source",
|
|
21037
|
+
stakeholderCount: "stakeholder-count"
|
|
21038
|
+
};
|
|
21039
|
+
const input = {
|
|
21040
|
+
termsAccepted: hasFlag(args, "accept-terms")
|
|
21041
|
+
};
|
|
21042
|
+
for (const [field, flag] of Object.entries(flagMap)) {
|
|
21043
|
+
const value = args.flags[flag];
|
|
21044
|
+
if (typeof value === "string") input[field] = value;
|
|
21045
|
+
}
|
|
21046
|
+
return input;
|
|
21047
|
+
}
|
|
21048
|
+
async function runOnboardingSetup(args) {
|
|
21049
|
+
validateFlags(args, {
|
|
21050
|
+
flags: [
|
|
21051
|
+
"legal-name",
|
|
21052
|
+
"company-type",
|
|
21053
|
+
"country",
|
|
21054
|
+
"role",
|
|
21055
|
+
"other-role",
|
|
21056
|
+
"cap-table-source",
|
|
21057
|
+
"other-cap-table-source",
|
|
21058
|
+
"stakeholder-count"
|
|
21059
|
+
],
|
|
21060
|
+
booleans: ["accept-terms"]
|
|
21061
|
+
});
|
|
21062
|
+
const parsed = onboardingWorkspaceSetupInputSchema.safeParse(
|
|
21063
|
+
dropOrphanOnboardingSetupDetails(setupInput(args))
|
|
21064
|
+
);
|
|
21065
|
+
if (!parsed.success) {
|
|
21066
|
+
const details = parsed.error.issues.map((issue2) => ({
|
|
21067
|
+
path: issue2.path.join("."),
|
|
21068
|
+
message: issue2.message
|
|
21069
|
+
}));
|
|
21070
|
+
exitWithError(
|
|
21071
|
+
"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.",
|
|
21072
|
+
"VALIDATION_FAILED",
|
|
21073
|
+
details,
|
|
21074
|
+
EXIT.VALIDATION
|
|
21075
|
+
);
|
|
21076
|
+
}
|
|
21077
|
+
const input = parsed.data;
|
|
21078
|
+
const { legalName } = resolveOnboardingCompanyProfile(input);
|
|
21079
|
+
const workspaceLabel = legalName ?? "your new workspace";
|
|
21080
|
+
const response = await apiRequest("/onboarding/workspace", { method: "POST", body: input });
|
|
21081
|
+
const result = onboardingWorkspaceSetupResultSchema.safeParse(response.data ?? response);
|
|
21082
|
+
if (!result.success) {
|
|
21083
|
+
exitWithError(
|
|
21084
|
+
"Unexpected onboarding setup response from server.",
|
|
21085
|
+
"SERVER_ERROR",
|
|
21086
|
+
void 0,
|
|
21087
|
+
EXIT.SERVER_ERROR
|
|
21088
|
+
);
|
|
21089
|
+
}
|
|
21090
|
+
if (result.data.session.action === ONBOARDING_SESSION_ACTIONS.REFRESH) {
|
|
21091
|
+
const refreshed = await refreshSession();
|
|
21092
|
+
const withPortals = await refreshPortalsIfStale(refreshed, true);
|
|
21093
|
+
const portal = withPortals.portals.find(({ id }) => id === result.data.portalId);
|
|
21094
|
+
if (!portal) {
|
|
21095
|
+
exitWithError(
|
|
21096
|
+
"Workspace was created, but the refreshed session did not include it.",
|
|
21097
|
+
"SESSION_REFRESH_FAILED",
|
|
21098
|
+
void 0,
|
|
21099
|
+
EXIT.SERVER_ERROR
|
|
21100
|
+
);
|
|
21101
|
+
}
|
|
21102
|
+
await setActivePortal(portal);
|
|
21103
|
+
const data2 = {
|
|
21104
|
+
...result.data,
|
|
21105
|
+
state: ONBOARDING_STATES.READY_TO_IMPORT,
|
|
21106
|
+
nextActions: [
|
|
21107
|
+
nextAction("start_import", "Start the secure document import", "captable-ai import start")
|
|
21108
|
+
],
|
|
21109
|
+
question: "Your workspace is ready. Start the document import now?"
|
|
21110
|
+
};
|
|
21111
|
+
if (isJsonMode()) {
|
|
21112
|
+
printJson({ data: data2 });
|
|
21113
|
+
return;
|
|
21114
|
+
}
|
|
21115
|
+
printSuccess(`Workspace ready: ${workspaceLabel} (${result.data.portalId})`);
|
|
21116
|
+
printLine("Start the import: captable-ai import start");
|
|
21117
|
+
return;
|
|
21118
|
+
}
|
|
21119
|
+
const data = {
|
|
21120
|
+
...result.data,
|
|
21121
|
+
nextActions: [
|
|
21122
|
+
nextAction("reconnect", "Reconnect to activate the workspace", "captable-ai login"),
|
|
21123
|
+
nextAction("start_import", "Start an import after reconnecting", "captable-ai import start")
|
|
21124
|
+
],
|
|
21125
|
+
question: "Your workspace is ready. Please run `captable-ai login` again, then start the import."
|
|
21126
|
+
};
|
|
21127
|
+
if (isJsonMode()) {
|
|
21128
|
+
printJson({ data });
|
|
21129
|
+
return;
|
|
21130
|
+
}
|
|
21131
|
+
printSuccess(`Workspace ready: ${workspaceLabel} (${result.data.portalId})`);
|
|
21132
|
+
printLine("Reconnect so your session can access it: captable-ai login");
|
|
21133
|
+
}
|
|
21134
|
+
async function runImportStart(args) {
|
|
21135
|
+
validateFlags(args, { flags: [], booleans: [] });
|
|
21136
|
+
const portalId = await getActivePortalId();
|
|
21137
|
+
const response = await apiRequest(
|
|
21138
|
+
"/import-sessions",
|
|
21139
|
+
{
|
|
21140
|
+
method: "POST",
|
|
21141
|
+
body: { portalId }
|
|
21142
|
+
}
|
|
21143
|
+
);
|
|
21144
|
+
const session = response.data?.session;
|
|
21145
|
+
if (!session?.id) {
|
|
21146
|
+
exitWithError(
|
|
21147
|
+
"Unexpected import session response from server.",
|
|
21148
|
+
"SERVER_ERROR",
|
|
21149
|
+
void 0,
|
|
21150
|
+
EXIT.SERVER_ERROR
|
|
21151
|
+
);
|
|
21152
|
+
}
|
|
21153
|
+
const data = {
|
|
21154
|
+
...response.data,
|
|
21155
|
+
nextActions: [
|
|
21156
|
+
nextAction(
|
|
21157
|
+
"upload_cli",
|
|
21158
|
+
"Upload local documents directly to S3",
|
|
21159
|
+
"captable-ai documents upload <file...>"
|
|
21160
|
+
),
|
|
21161
|
+
nextAction(
|
|
21162
|
+
"check_status",
|
|
21163
|
+
"Check import progress",
|
|
21164
|
+
`captable-ai import status ${session.id}`
|
|
21165
|
+
)
|
|
21166
|
+
],
|
|
21167
|
+
question: "Which cap table documents should be uploaded with `documents upload`?"
|
|
21168
|
+
};
|
|
21169
|
+
if (isJsonMode()) {
|
|
21170
|
+
printJson({ data });
|
|
21171
|
+
return;
|
|
21172
|
+
}
|
|
21173
|
+
printSuccess(`Import session ${response.data?.reused ? "reused" : "started"}: ${session.id}`);
|
|
21174
|
+
printLine("Upload documents: captable-ai documents upload <file...>");
|
|
21175
|
+
printLine(`Check progress: captable-ai import status ${session.id}`);
|
|
21176
|
+
}
|
|
21177
|
+
var TERMINAL_DOCUMENT_STATUSES = /* @__PURE__ */ new Set(["PROCESSED", "COMMITTED", "FAILED", "CANCELLED"]);
|
|
21178
|
+
function documentsForImportSession(documents, sessionCreatedAt) {
|
|
21179
|
+
if (typeof sessionCreatedAt !== "string" || sessionCreatedAt.length === 0) return [];
|
|
21180
|
+
return documents.filter(
|
|
21181
|
+
(document) => typeof document.lastActivityAt === "string" && document.lastActivityAt >= sessionCreatedAt
|
|
21182
|
+
);
|
|
21183
|
+
}
|
|
21184
|
+
function summarizeImportDocuments(documents) {
|
|
21185
|
+
const statuses = documents.map((document) => (document.status ?? "").toUpperCase());
|
|
21186
|
+
const failed = statuses.filter((status) => ["FAILED", "CANCELLED"].includes(status)).length;
|
|
21187
|
+
const classified = statuses.filter((status) => status === "CLASSIFIED").length;
|
|
21188
|
+
const pendingUpload = statuses.filter((status) => status === "PENDING_UPLOAD").length;
|
|
21189
|
+
const processing = statuses.filter(
|
|
21190
|
+
(status) => status !== "" && !TERMINAL_DOCUMENT_STATUSES.has(status) && status !== "CLASSIFIED" && status !== "PENDING_UPLOAD"
|
|
21191
|
+
).length;
|
|
21192
|
+
return {
|
|
21193
|
+
total: documents.length,
|
|
21194
|
+
processing,
|
|
21195
|
+
classified,
|
|
21196
|
+
pendingUpload,
|
|
21197
|
+
failed,
|
|
21198
|
+
complete: documents.length - failed - processing - classified - pendingUpload
|
|
21199
|
+
};
|
|
21200
|
+
}
|
|
21201
|
+
async function runImportStatus(args) {
|
|
21202
|
+
validateFlags(args, { flags: [], booleans: [] });
|
|
21203
|
+
const sessionId = args.positional[0];
|
|
21204
|
+
if (!sessionId) {
|
|
21205
|
+
exitWithUsage(
|
|
21206
|
+
"Import session ID is required.",
|
|
21207
|
+
"Usage: captable-ai import status <session-id> [--json]"
|
|
21208
|
+
);
|
|
21209
|
+
}
|
|
21210
|
+
const response = await apiRequest(
|
|
21211
|
+
`/import-sessions/${encodeURIComponent(sessionId)}/status`
|
|
21212
|
+
);
|
|
21213
|
+
const documents = documentsForImportSession(
|
|
21214
|
+
response.data?.processing?.documents ?? [],
|
|
21215
|
+
response.data?.session?.createdAt
|
|
21216
|
+
);
|
|
21217
|
+
const summary = summarizeImportDocuments(documents);
|
|
21218
|
+
const sessionState = response.data?.session?.state;
|
|
21219
|
+
if (documents.length > 0 && summary.processing === 0 && summary.classified === 0 && summary.pendingUpload === 0 && summary.failed === 0 && sessionState !== "completed") {
|
|
21220
|
+
const completed = await apiRequest(
|
|
21221
|
+
`/import-sessions/${encodeURIComponent(sessionId)}/complete`,
|
|
21222
|
+
{
|
|
21223
|
+
method: "POST",
|
|
21224
|
+
body: {}
|
|
21225
|
+
}
|
|
21226
|
+
);
|
|
21227
|
+
if (response.data && completed.data?.session) {
|
|
21228
|
+
response.data.session = { ...response.data.session, ...completed.data.session };
|
|
21229
|
+
}
|
|
21230
|
+
}
|
|
21231
|
+
const state = documents.length === 0 ? ONBOARDING_STATES.READY_TO_IMPORT : summary.processing > 0 ? ONBOARDING_STATES.PROCESSING : summary.classified > 0 || summary.pendingUpload > 0 ? ONBOARDING_STATES.READY_TO_IMPORT : summary.failed > 0 ? ONBOARDING_STATES.BLOCKED : ONBOARDING_STATES.NEEDS_REVIEW;
|
|
21232
|
+
const nextActions = documents.length === 0 || summary.pendingUpload > 0 ? [
|
|
21233
|
+
nextAction(
|
|
21234
|
+
"upload_cli",
|
|
21235
|
+
"Upload local documents directly to S3",
|
|
21236
|
+
"captable-ai documents upload <file...>"
|
|
21237
|
+
),
|
|
21238
|
+
...summary.classified > 0 ? [
|
|
21239
|
+
nextAction(
|
|
21240
|
+
"trigger_extraction",
|
|
21241
|
+
"Start OCF extraction for classified documents",
|
|
21242
|
+
"captable-ai documents trigger-extraction"
|
|
21243
|
+
)
|
|
21244
|
+
] : []
|
|
21245
|
+
] : summary.classified > 0 ? [
|
|
21246
|
+
nextAction(
|
|
21247
|
+
"trigger_extraction",
|
|
21248
|
+
"Start OCF extraction for classified documents",
|
|
21249
|
+
"captable-ai documents trigger-extraction"
|
|
21250
|
+
)
|
|
21251
|
+
] : summary.processing > 0 ? [
|
|
21252
|
+
nextAction(
|
|
21253
|
+
"check_status",
|
|
21254
|
+
"Check import progress again",
|
|
21255
|
+
`captable-ai import status ${sessionId}`
|
|
21256
|
+
)
|
|
21257
|
+
] : summary.failed > 0 ? [
|
|
21258
|
+
nextAction(
|
|
21259
|
+
"review_failures",
|
|
21260
|
+
"Review failed documents",
|
|
21261
|
+
"captable-ai documents list --status FAILED"
|
|
21262
|
+
)
|
|
21263
|
+
] : [
|
|
21264
|
+
nextAction(
|
|
21265
|
+
"review_drafts",
|
|
21266
|
+
"Review extracted drafts",
|
|
21267
|
+
"captable-ai objects list --status draft --all"
|
|
21268
|
+
),
|
|
21269
|
+
nextAction(
|
|
21270
|
+
"preview_commit",
|
|
21271
|
+
"Preview the first commit",
|
|
21272
|
+
"captable-ai commit --dry-run"
|
|
21273
|
+
),
|
|
21274
|
+
nextAction("confirm_commit", "Commit reviewed drafts", "captable-ai commit --all")
|
|
21275
|
+
];
|
|
21276
|
+
const data = {
|
|
21277
|
+
...response.data,
|
|
21278
|
+
state,
|
|
21279
|
+
summary,
|
|
21280
|
+
nextActions
|
|
21281
|
+
};
|
|
21282
|
+
if (isJsonMode()) {
|
|
21283
|
+
printJson({ data });
|
|
21284
|
+
return;
|
|
21285
|
+
}
|
|
21286
|
+
printKeyValue([
|
|
21287
|
+
["Session", sessionId],
|
|
21288
|
+
["State", data.state],
|
|
21289
|
+
["Documents", summary.total],
|
|
21290
|
+
["Classified", summary.classified],
|
|
21291
|
+
["Processing", summary.processing],
|
|
21292
|
+
["Complete", summary.complete],
|
|
21293
|
+
["Failed", summary.failed]
|
|
21294
|
+
]);
|
|
21295
|
+
for (const action of nextActions) {
|
|
21296
|
+
printLine(` Next: ${String(action.command)}`);
|
|
21297
|
+
}
|
|
21298
|
+
}
|
|
21299
|
+
|
|
20770
21300
|
// src/commands/pro-forma.ts
|
|
20771
21301
|
var SCENARIO_TYPES2 = ["financing", "convertible", "warrant", "option-pool"];
|
|
20772
21302
|
function buildScenarioBody(type, args) {
|
|
@@ -21597,7 +22127,9 @@ var PROFILE_AWARE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
21597
22127
|
"company",
|
|
21598
22128
|
"documents",
|
|
21599
22129
|
"export",
|
|
22130
|
+
"import",
|
|
21600
22131
|
"objects",
|
|
22132
|
+
"onboarding",
|
|
21601
22133
|
"pro-forma",
|
|
21602
22134
|
"register",
|
|
21603
22135
|
"logs",
|
|
@@ -21621,6 +22153,12 @@ Company:
|
|
|
21621
22153
|
company create Create a test portal (super admin)
|
|
21622
22154
|
company delete <id> Delete a test portal (dev only, super admin)
|
|
21623
22155
|
|
|
22156
|
+
Onboarding:
|
|
22157
|
+
onboarding status Show the next setup/import action (works with zero companies)
|
|
22158
|
+
onboarding setup Create the first production workspace
|
|
22159
|
+
import start Start or reuse a document import session
|
|
22160
|
+
import status <id> Check import processing via existing document APIs
|
|
22161
|
+
|
|
21624
22162
|
Cap Table (read):
|
|
21625
22163
|
summary Full cap table snapshot (capitalization, stakeholders, metrics)
|
|
21626
22164
|
Options: --as-of, --include-pending, --skip-stakeholders, --page
|
|
@@ -21897,6 +22435,50 @@ async function main() {
|
|
|
21897
22435
|
case "company":
|
|
21898
22436
|
await routeCompany(positionalArgs);
|
|
21899
22437
|
break;
|
|
22438
|
+
case "onboarding":
|
|
22439
|
+
if (isHelpToken(sub) || argvRequestsHelp(positionalArgs.slice(2))) {
|
|
22440
|
+
process.stdout.write(`${HELP_ONBOARDING}
|
|
22441
|
+
`);
|
|
22442
|
+
process.exit(0);
|
|
22443
|
+
}
|
|
22444
|
+
switch (sub) {
|
|
22445
|
+
case "status":
|
|
22446
|
+
await runOnboardingStatus(parseArgs(positionalArgs.slice(2)));
|
|
22447
|
+
break;
|
|
22448
|
+
case "setup":
|
|
22449
|
+
await runOnboardingSetup(parseArgs(positionalArgs.slice(2)));
|
|
22450
|
+
break;
|
|
22451
|
+
default:
|
|
22452
|
+
exitWithError(
|
|
22453
|
+
`Unknown onboarding command: ${sub ?? "(none)"}. Try: onboarding status | setup`,
|
|
22454
|
+
"UNKNOWN_COMMAND",
|
|
22455
|
+
void 0,
|
|
22456
|
+
EXIT.USAGE
|
|
22457
|
+
);
|
|
22458
|
+
}
|
|
22459
|
+
break;
|
|
22460
|
+
case "import":
|
|
22461
|
+
if (isHelpToken(sub) || argvRequestsHelp(positionalArgs.slice(2))) {
|
|
22462
|
+
process.stdout.write(`${HELP_IMPORT}
|
|
22463
|
+
`);
|
|
22464
|
+
process.exit(0);
|
|
22465
|
+
}
|
|
22466
|
+
switch (sub) {
|
|
22467
|
+
case "start":
|
|
22468
|
+
await runImportStart(parseArgs(positionalArgs.slice(2)));
|
|
22469
|
+
break;
|
|
22470
|
+
case "status":
|
|
22471
|
+
await runImportStatus(parseArgs(positionalArgs.slice(2)));
|
|
22472
|
+
break;
|
|
22473
|
+
default:
|
|
22474
|
+
exitWithError(
|
|
22475
|
+
`Unknown import command: ${sub ?? "(none)"}. Try: import start | status`,
|
|
22476
|
+
"UNKNOWN_COMMAND",
|
|
22477
|
+
void 0,
|
|
22478
|
+
EXIT.USAGE
|
|
22479
|
+
);
|
|
22480
|
+
}
|
|
22481
|
+
break;
|
|
21900
22482
|
case "summary":
|
|
21901
22483
|
if (argvRequestsHelp(positionalArgs.slice(1))) {
|
|
21902
22484
|
process.stdout.write(`${HELP_SUMMARY}
|
package/package.json
CHANGED