@fairmint/captable-ai 0.1.38 → 0.1.40
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 +6 -4
- package/dist/index.js +230 -203
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,16 +125,18 @@ captable-ai export convertibles --type SAFE --csv # SAFEs only, CSV on
|
|
|
125
125
|
captable-ai export convertibles --type NOTE --json --output notes.json
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
Every register
|
|
129
|
-
|
|
128
|
+
Every register carries the holder fields (name, email, address, country, state) and the
|
|
129
|
+
economics of that family. Grants lead with the grant date; convertibles put the signature
|
|
130
|
+
date, execution date, and status right after the holder. Without a format flag the result is one Excel sheet
|
|
130
131
|
returned as a download link. `--csv` prints RFC-4180 CSV whose header row is the JSON
|
|
131
132
|
keys; `--json` prints the rows as a JSON array. Pass one or neither. `--output <path>`
|
|
132
133
|
writes the CSV or JSON body to a file and, for these commands only, does not imply
|
|
133
134
|
`--json`.
|
|
134
135
|
|
|
135
136
|
By default a register lists the committed cap table only. `--include-pending` adds what
|
|
136
|
-
is not committed yet: draft OCF objects, plus `investment
|
|
137
|
-
have no OCF transaction. A row that already has
|
|
137
|
+
is not committed yet: draft OCF objects, plus `investment`, `equity_grant`, and
|
|
138
|
+
`equity_award` (Send One Grant) rows that have no OCF transaction. A row that already has
|
|
139
|
+
an OCF object appears once. Registers
|
|
138
140
|
are read-only and never commit drafts. You must be an owner or admin of the active
|
|
139
141
|
company.
|
|
140
142
|
|
package/dist/index.js
CHANGED
|
@@ -16151,21 +16151,27 @@ var exportResultSchema = external_exports.object({
|
|
|
16151
16151
|
// ../captable-ai-core/dist/export-registers.js
|
|
16152
16152
|
var EXPORT_REGISTERS = ["stock", "grants", "warrants", "convertibles"];
|
|
16153
16153
|
var CONVERTIBLE_TYPE_FILTERS = ["SAFE", "NOTE"];
|
|
16154
|
-
var REGISTER_ROW_SOURCES = [
|
|
16154
|
+
var REGISTER_ROW_SOURCES = [
|
|
16155
|
+
"committed",
|
|
16156
|
+
"draft",
|
|
16157
|
+
"investment",
|
|
16158
|
+
"equity_grant",
|
|
16159
|
+
"equity_award"
|
|
16160
|
+
];
|
|
16155
16161
|
var isoDateSchema = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Must be YYYY-MM-DD").refine((val) => {
|
|
16156
16162
|
const [y, m, d] = val.split("-").map(Number);
|
|
16157
16163
|
const parsed = new Date(Date.UTC(y, m - 1, d));
|
|
16158
16164
|
return parsed.toISOString().slice(0, 10) === val;
|
|
16159
16165
|
}, { message: "Invalid calendar date (e.g. Feb 30 does not exist)" });
|
|
16160
16166
|
var exportRegisterInputSchema = external_exports.object({
|
|
16161
|
-
includePending: external_exports.boolean().optional().default(false).describe("Add draft OCF objects and investment / equity_grant rows that have no OCF transaction yet."),
|
|
16167
|
+
includePending: external_exports.boolean().optional().default(false).describe("Add draft OCF objects and investment / equity_grant / equity_award rows that have no OCF transaction yet."),
|
|
16162
16168
|
asOf: isoDateSchema.optional().describe("Only securities issued on or before this date (YYYY-MM-DD)."),
|
|
16163
16169
|
convertibleType: external_exports.enum(CONVERTIBLE_TYPE_FILTERS).optional().describe("convertibles only: keep SAFE or NOTE rows."),
|
|
16164
16170
|
format: external_exports.enum(["rows", "xlsx"]).optional().default("rows").describe("rows returns the rows as JSON; xlsx also uploads a one-sheet workbook.")
|
|
16165
16171
|
});
|
|
16166
16172
|
var cell = external_exports.union([external_exports.string(), external_exports.number()]).nullable();
|
|
16167
16173
|
var holderShape = {
|
|
16168
|
-
source: external_exports.enum(REGISTER_ROW_SOURCES).describe("committed, draft, investment, or
|
|
16174
|
+
source: external_exports.enum(REGISTER_ROW_SOURCES).describe("committed, draft, investment, equity_grant, or equity_award."),
|
|
16169
16175
|
security_id: external_exports.string().nullable().describe("OCF security id, null for operational-only rows."),
|
|
16170
16176
|
ocf_object_id: external_exports.string().nullable().describe("OCF transaction object id."),
|
|
16171
16177
|
holder_name: cell.describe("Holder legal name."),
|
|
@@ -16187,11 +16193,13 @@ var grantRegisterRowSchema = external_exports.object({
|
|
|
16187
16193
|
award_type: cell.describe("ISO, NSO, RSU, or another OCF compensation type."),
|
|
16188
16194
|
quantity: cell.describe("Shares or units granted."),
|
|
16189
16195
|
dollar_amount: cell.describe("equity_grant.dollar_amount when a linked grant row exists."),
|
|
16190
|
-
status: cell.describe("equity_grant workflow status, or COMMITTED / DRAFT for OCF-only grants."),
|
|
16196
|
+
status: cell.describe("equity_grant / equity_award workflow status, or COMMITTED / DRAFT for OCF-only grants."),
|
|
16191
16197
|
accepted_on: cell.describe("Date the grantee signed (YYYY-MM-DD)."),
|
|
16198
|
+
vesting_schedule: cell.describe("Vesting schedule name, e.g. 4 years, 1-year cliff."),
|
|
16192
16199
|
cliff_months: cell.describe("Cliff length in months."),
|
|
16193
16200
|
vesting_months: cell.describe("Total vesting length in months."),
|
|
16194
16201
|
effective_date: cell.describe("Grant date (YYYY-MM-DD)."),
|
|
16202
|
+
vesting_start_date: cell.describe("Vesting commencement date (YYYY-MM-DD)."),
|
|
16195
16203
|
exercise_price: cell.describe("Exercise price per share, blank for RSUs."),
|
|
16196
16204
|
series: cell.describe("Series name (Hospitable's team / retention / community categories)."),
|
|
16197
16205
|
plan: cell.describe("Stock plan name.")
|
|
@@ -16207,6 +16215,9 @@ var warrantRegisterRowSchema = external_exports.object({
|
|
|
16207
16215
|
});
|
|
16208
16216
|
var convertibleRegisterRowSchema = external_exports.object({
|
|
16209
16217
|
...holderShape,
|
|
16218
|
+
signature_date: cell.describe("Date the investor signed (YYYY-MM-DD)."),
|
|
16219
|
+
execution_date: cell.describe("Date the agreement was fully executed (YYYY-MM-DD)."),
|
|
16220
|
+
status: cell.describe("investment workflow status, or COMMITTED / DRAFT for OCF-only rows."),
|
|
16210
16221
|
convertible_type: cell.describe("SAFE, NOTE, or SECURITY."),
|
|
16211
16222
|
date: cell.describe("Investment date (YYYY-MM-DD)."),
|
|
16212
16223
|
round: cell.describe("Round / series / offering name."),
|
|
@@ -16237,17 +16248,24 @@ var REGISTER_COLUMNS = {
|
|
|
16237
16248
|
{ key: "amount", header: "Amount", format: "money" },
|
|
16238
16249
|
...TRAILING_COLUMNS
|
|
16239
16250
|
],
|
|
16251
|
+
// Leading columns follow the grant file Hospitable asked for (ENG-2200).
|
|
16240
16252
|
grants: [
|
|
16241
|
-
|
|
16242
|
-
{ key: "
|
|
16253
|
+
{ key: "effective_date", header: "Grant Date", format: "date" },
|
|
16254
|
+
{ key: "holder_name", header: "Name", format: "text" },
|
|
16255
|
+
{ key: "holder_email", header: "Email", format: "text" },
|
|
16256
|
+
{ key: "award_type", header: "Type", format: "text" },
|
|
16243
16257
|
{ key: "quantity", header: "Quantity", format: "number" },
|
|
16244
|
-
{ key: "dollar_amount", header: "Dollar Amount", format: "money" },
|
|
16245
16258
|
{ key: "status", header: "Status", format: "text" },
|
|
16246
|
-
{ key: "
|
|
16247
|
-
{ key: "
|
|
16248
|
-
{ key: "
|
|
16249
|
-
{ key: "effective_date", header: "Effective Date", format: "date" },
|
|
16259
|
+
{ key: "vesting_schedule", header: "Vesting", format: "text" },
|
|
16260
|
+
{ key: "vesting_months", header: "Vesting Period (Months)", format: "number" },
|
|
16261
|
+
{ key: "cliff_months", header: "Cliff Months", format: "number" },
|
|
16250
16262
|
{ key: "exercise_price", header: "Exercise Price", format: "money" },
|
|
16263
|
+
{ key: "accepted_on", header: "Signed Date", format: "date" },
|
|
16264
|
+
{ key: "vesting_start_date", header: "Vesting Start Date", format: "date" },
|
|
16265
|
+
{ key: "holder_address", header: "Address", format: "text" },
|
|
16266
|
+
{ key: "holder_country", header: "Country", format: "text" },
|
|
16267
|
+
{ key: "holder_state", header: "State", format: "text" },
|
|
16268
|
+
{ key: "dollar_amount", header: "Dollar Amount", format: "money" },
|
|
16251
16269
|
{ key: "series", header: "Series", format: "text" },
|
|
16252
16270
|
{ key: "plan", header: "Plan", format: "text" },
|
|
16253
16271
|
...TRAILING_COLUMNS
|
|
@@ -16262,13 +16280,18 @@ var REGISTER_COLUMNS = {
|
|
|
16262
16280
|
{ key: "issue_date", header: "Issue Date", format: "date" },
|
|
16263
16281
|
...TRAILING_COLUMNS
|
|
16264
16282
|
],
|
|
16283
|
+
// Signature / execution / status / round / amount / funding follow the SAFE
|
|
16284
|
+
// file Hospitable asked for (ENG-2200).
|
|
16265
16285
|
convertibles: [
|
|
16266
16286
|
...HOLDER_COLUMNS,
|
|
16267
|
-
{ key: "
|
|
16268
|
-
{ key: "
|
|
16287
|
+
{ key: "signature_date", header: "Signature Date", format: "date" },
|
|
16288
|
+
{ key: "execution_date", header: "Execution Date", format: "date" },
|
|
16289
|
+
{ key: "status", header: "Status", format: "text" },
|
|
16269
16290
|
{ key: "round", header: "Round", format: "text" },
|
|
16270
16291
|
{ key: "amount", header: "Amount", format: "money" },
|
|
16271
16292
|
{ key: "funding_method", header: "Funding Method", format: "text" },
|
|
16293
|
+
{ key: "convertible_type", header: "Type", format: "text" },
|
|
16294
|
+
{ key: "date", header: "Investment Date", format: "date" },
|
|
16272
16295
|
{ key: "valuation_cap", header: "Valuation Cap", format: "money" },
|
|
16273
16296
|
{ key: "discount", header: "Discount (%)", format: "percent" },
|
|
16274
16297
|
...TRAILING_COLUMNS
|
|
@@ -17321,80 +17344,6 @@ async function requireAuth() {
|
|
|
17321
17344
|
return creds;
|
|
17322
17345
|
}
|
|
17323
17346
|
|
|
17324
|
-
// src/auth/refresh-portals.ts
|
|
17325
|
-
var PORTALS_TTL_MS = 3e5;
|
|
17326
|
-
var REQUEST_TIMEOUT_MS = 1e4;
|
|
17327
|
-
function isStoredPortal(v) {
|
|
17328
|
-
if (typeof v !== "object" || v === null) return false;
|
|
17329
|
-
const obj = v;
|
|
17330
|
-
return typeof obj.id === "string";
|
|
17331
|
-
}
|
|
17332
|
-
function parsePortals(body) {
|
|
17333
|
-
if (typeof body !== "object" || body === null) return null;
|
|
17334
|
-
if (!("portals" in body)) return null;
|
|
17335
|
-
const portals = body.portals;
|
|
17336
|
-
if (!Array.isArray(portals)) return null;
|
|
17337
|
-
if (!portals.every(isStoredPortal)) return null;
|
|
17338
|
-
return portals;
|
|
17339
|
-
}
|
|
17340
|
-
function cachedPortalsNeedNameRefresh(portals) {
|
|
17341
|
-
return portals.some((portal) => typeof portal.name !== "string" || portal.name.trim() === "");
|
|
17342
|
-
}
|
|
17343
|
-
async function refreshPortalsIfStale(creds, force = false) {
|
|
17344
|
-
if (!force && !cachedPortalsNeedNameRefresh(creds.portals) && creds.portalsLastFetchedAt && Date.now() - Date.parse(creds.portalsLastFetchedAt) < PORTALS_TTL_MS) {
|
|
17345
|
-
return creds;
|
|
17346
|
-
}
|
|
17347
|
-
if (isTokenExpired(creds.accessToken)) {
|
|
17348
|
-
return creds;
|
|
17349
|
-
}
|
|
17350
|
-
try {
|
|
17351
|
-
const res = await fetch(`${getApiUrl()}/auth/portals`, {
|
|
17352
|
-
headers: { Authorization: `Bearer ${creds.accessToken}` },
|
|
17353
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
17354
|
-
});
|
|
17355
|
-
if (!res.ok) {
|
|
17356
|
-
return creds;
|
|
17357
|
-
}
|
|
17358
|
-
const body = await res.json();
|
|
17359
|
-
const portals = parsePortals(body);
|
|
17360
|
-
if (portals) {
|
|
17361
|
-
const updated = {
|
|
17362
|
-
...creds,
|
|
17363
|
-
portals,
|
|
17364
|
-
portalsLastFetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17365
|
-
};
|
|
17366
|
-
await writeCredentials(updated);
|
|
17367
|
-
return updated;
|
|
17368
|
-
}
|
|
17369
|
-
} catch {
|
|
17370
|
-
process.stderr.write("Warning: Could not refresh company list (using cached data).\n");
|
|
17371
|
-
}
|
|
17372
|
-
return creds;
|
|
17373
|
-
}
|
|
17374
|
-
async function markPortalsFetched(creds) {
|
|
17375
|
-
creds.portalsLastFetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17376
|
-
await writeCredentials(creds);
|
|
17377
|
-
}
|
|
17378
|
-
async function refreshCachedPortalNames() {
|
|
17379
|
-
try {
|
|
17380
|
-
const creds = await readCredentials();
|
|
17381
|
-
if (!creds) return;
|
|
17382
|
-
const updated = await refreshPortalsIfStale(creds, true);
|
|
17383
|
-
const config2 = await readConfig();
|
|
17384
|
-
const activeId = config2.activePortal?.id;
|
|
17385
|
-
if (!activeId) return;
|
|
17386
|
-
const portal = updated.portals.find((candidate) => candidate.id === activeId);
|
|
17387
|
-
if (portal) await setActivePortal(portal);
|
|
17388
|
-
} catch {
|
|
17389
|
-
process.stderr.write(
|
|
17390
|
-
"Warning: Could not refresh company name after updating the company list.\n"
|
|
17391
|
-
);
|
|
17392
|
-
}
|
|
17393
|
-
}
|
|
17394
|
-
|
|
17395
|
-
// src/output.ts
|
|
17396
|
-
import { renameSync, rmSync, writeFileSync } from "fs";
|
|
17397
|
-
|
|
17398
17347
|
// src/exit-codes.ts
|
|
17399
17348
|
var EXIT = {
|
|
17400
17349
|
OK: 0,
|
|
@@ -17408,6 +17357,7 @@ var EXIT = {
|
|
|
17408
17357
|
};
|
|
17409
17358
|
|
|
17410
17359
|
// src/output.ts
|
|
17360
|
+
import { renameSync, rmSync, writeFileSync } from "fs";
|
|
17411
17361
|
function write(msg) {
|
|
17412
17362
|
process.stdout.write(`${msg}
|
|
17413
17363
|
`);
|
|
@@ -17513,111 +17463,6 @@ function exitWithUsage(message, usageLine, code = "USAGE") {
|
|
|
17513
17463
|
process.exit(EXIT.USAGE);
|
|
17514
17464
|
}
|
|
17515
17465
|
|
|
17516
|
-
// src/validate-flags.ts
|
|
17517
|
-
function restorePositional(args, value, originalIndex) {
|
|
17518
|
-
const insertAt = args.positionalIndexes.findIndex((index) => index > originalIndex);
|
|
17519
|
-
if (insertAt === -1) {
|
|
17520
|
-
args.positional.push(value);
|
|
17521
|
-
args.positionalIndexes.push(originalIndex);
|
|
17522
|
-
return;
|
|
17523
|
-
}
|
|
17524
|
-
args.positional.splice(insertAt, 0, value);
|
|
17525
|
-
args.positionalIndexes.splice(insertAt, 0, originalIndex);
|
|
17526
|
-
}
|
|
17527
|
-
function validateFlags(args, allowed) {
|
|
17528
|
-
const valueFlags = new Set(allowed.flags ?? []);
|
|
17529
|
-
const booleanFlags = new Set(allowed.booleans ?? []);
|
|
17530
|
-
const allAllowed = /* @__PURE__ */ new Set([...valueFlags, ...booleanFlags]);
|
|
17531
|
-
const allList = [...allAllowed].map((f) => `--${f}`).join(", ");
|
|
17532
|
-
for (const key of Object.keys(args.flags)) {
|
|
17533
|
-
if (!allAllowed.has(key)) {
|
|
17534
|
-
exitWithError(
|
|
17535
|
-
`Unknown flag: --${key}. Allowed flags: ${allList || "(none)"}`,
|
|
17536
|
-
"UNKNOWN_FLAG",
|
|
17537
|
-
void 0,
|
|
17538
|
-
EXIT.USAGE
|
|
17539
|
-
);
|
|
17540
|
-
}
|
|
17541
|
-
if (booleanFlags.has(key) && !valueFlags.has(key) && args.flagValueSources[key] === "next") {
|
|
17542
|
-
args.boolean.add(key);
|
|
17543
|
-
restorePositional(args, args.flags[key], args.flagValueIndexes[key]);
|
|
17544
|
-
delete args.flags[key];
|
|
17545
|
-
delete args.flagValueSources[key];
|
|
17546
|
-
delete args.flagValueIndexes[key];
|
|
17547
|
-
continue;
|
|
17548
|
-
}
|
|
17549
|
-
if (booleanFlags.has(key) && !valueFlags.has(key)) {
|
|
17550
|
-
exitWithError(
|
|
17551
|
-
`Flag --${key} does not take a value.`,
|
|
17552
|
-
"UNEXPECTED_FLAG_VALUE",
|
|
17553
|
-
void 0,
|
|
17554
|
-
EXIT.USAGE
|
|
17555
|
-
);
|
|
17556
|
-
}
|
|
17557
|
-
}
|
|
17558
|
-
for (const key of args.boolean) {
|
|
17559
|
-
if (valueFlags.has(key)) {
|
|
17560
|
-
exitWithError(`Flag --${key} requires a value.`, "MISSING_FLAG_VALUE", void 0, EXIT.USAGE);
|
|
17561
|
-
}
|
|
17562
|
-
if (!booleanFlags.has(key)) {
|
|
17563
|
-
exitWithError(
|
|
17564
|
-
`Unknown flag: --${key}. Allowed flags: ${allList || "(none)"}`,
|
|
17565
|
-
"UNKNOWN_FLAG",
|
|
17566
|
-
void 0,
|
|
17567
|
-
EXIT.USAGE
|
|
17568
|
-
);
|
|
17569
|
-
}
|
|
17570
|
-
}
|
|
17571
|
-
}
|
|
17572
|
-
|
|
17573
|
-
// src/commands/auth-status.ts
|
|
17574
|
-
async function runAuthStatus(args) {
|
|
17575
|
-
validateFlags(args, { flags: [], booleans: [] });
|
|
17576
|
-
let creds = await readCredentials();
|
|
17577
|
-
const config2 = await readConfig();
|
|
17578
|
-
if (creds) {
|
|
17579
|
-
creds = await refreshPortalsIfStale(creds);
|
|
17580
|
-
}
|
|
17581
|
-
if (!creds) {
|
|
17582
|
-
if (isJsonMode()) {
|
|
17583
|
-
printJson({ data: { authenticated: false } });
|
|
17584
|
-
} else {
|
|
17585
|
-
printLine("Not logged in. Run: captable-ai login");
|
|
17586
|
-
}
|
|
17587
|
-
return;
|
|
17588
|
-
}
|
|
17589
|
-
const expired = isTokenExpired(creds.accessToken);
|
|
17590
|
-
const payload = decodeJwtPayload(creds.accessToken);
|
|
17591
|
-
const expiresAt = typeof payload.exp === "number" ? new Date(payload.exp * 1e3).toISOString() : creds.expiresAt;
|
|
17592
|
-
const activeCompany = resolveActiveCompany(config2, creds.portals);
|
|
17593
|
-
const status = {
|
|
17594
|
-
authenticated: true,
|
|
17595
|
-
expired,
|
|
17596
|
-
user: creds.user,
|
|
17597
|
-
activeCompany,
|
|
17598
|
-
tokenExpiresAt: expiresAt,
|
|
17599
|
-
companyCount: creds.portals.length
|
|
17600
|
-
};
|
|
17601
|
-
if (isJsonMode()) {
|
|
17602
|
-
printJson({ data: status });
|
|
17603
|
-
return;
|
|
17604
|
-
}
|
|
17605
|
-
printLine(
|
|
17606
|
-
expired ? "Status: Expired (will auto-refresh on next command)" : "Status: Authenticated"
|
|
17607
|
-
);
|
|
17608
|
-
printLine();
|
|
17609
|
-
printKeyValue([
|
|
17610
|
-
["Email", creds.user.email],
|
|
17611
|
-
["User ID", creds.user.id],
|
|
17612
|
-
["Token expires", expiresAt],
|
|
17613
|
-
["Active company", activeCompany ? activeCompany.name ?? activeCompany.id : "(none)"],
|
|
17614
|
-
["Companies available", String(creds.portals.length)]
|
|
17615
|
-
]);
|
|
17616
|
-
}
|
|
17617
|
-
|
|
17618
|
-
// src/commands/export.ts
|
|
17619
|
-
import { renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
17620
|
-
|
|
17621
17466
|
// src/api.ts
|
|
17622
17467
|
function isRecord2(value) {
|
|
17623
17468
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -17634,7 +17479,7 @@ function coerceDetails(raw) {
|
|
|
17634
17479
|
}
|
|
17635
17480
|
return details.length > 0 ? details : void 0;
|
|
17636
17481
|
}
|
|
17637
|
-
var
|
|
17482
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
17638
17483
|
function exitForRefreshFailure(err, softFail = false) {
|
|
17639
17484
|
if (err instanceof Error && err.name === "AuthError") {
|
|
17640
17485
|
failRequest(
|
|
@@ -17694,7 +17539,7 @@ async function refreshTokens(creds) {
|
|
|
17694
17539
|
method: "POST",
|
|
17695
17540
|
headers: { "Content-Type": "application/json" },
|
|
17696
17541
|
body: JSON.stringify({ refreshToken: creds.refreshToken, clientType: "cli" }),
|
|
17697
|
-
signal: AbortSignal.timeout(
|
|
17542
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
17698
17543
|
});
|
|
17699
17544
|
} catch (fetchErr) {
|
|
17700
17545
|
const err = new Error(
|
|
@@ -17764,7 +17609,7 @@ async function apiRequest(path, opts = {}) {
|
|
|
17764
17609
|
const buildFetchOpts = () => ({
|
|
17765
17610
|
method,
|
|
17766
17611
|
headers: { ...headers },
|
|
17767
|
-
signal: AbortSignal.timeout(
|
|
17612
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
17768
17613
|
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
17769
17614
|
});
|
|
17770
17615
|
let res;
|
|
@@ -17893,6 +17738,182 @@ async function handleResponse(res, softFail = false) {
|
|
|
17893
17738
|
return parsed;
|
|
17894
17739
|
}
|
|
17895
17740
|
|
|
17741
|
+
// src/auth/refresh-portals.ts
|
|
17742
|
+
var PORTALS_TTL_MS = 3e5;
|
|
17743
|
+
var REQUEST_TIMEOUT_MS2 = 1e4;
|
|
17744
|
+
function isStoredPortal(v) {
|
|
17745
|
+
if (typeof v !== "object" || v === null) return false;
|
|
17746
|
+
const obj = v;
|
|
17747
|
+
return typeof obj.id === "string";
|
|
17748
|
+
}
|
|
17749
|
+
function parsePortals(body) {
|
|
17750
|
+
if (typeof body !== "object" || body === null) return null;
|
|
17751
|
+
if (!("portals" in body)) return null;
|
|
17752
|
+
const portals = body.portals;
|
|
17753
|
+
if (!Array.isArray(portals)) return null;
|
|
17754
|
+
if (!portals.every(isStoredPortal)) return null;
|
|
17755
|
+
return portals;
|
|
17756
|
+
}
|
|
17757
|
+
function cachedPortalsNeedNameRefresh(portals) {
|
|
17758
|
+
return portals.some((portal) => typeof portal.name !== "string" || portal.name.trim() === "");
|
|
17759
|
+
}
|
|
17760
|
+
async function refreshPortalsIfStale(creds, force = false) {
|
|
17761
|
+
if (!force && !cachedPortalsNeedNameRefresh(creds.portals) && creds.portalsLastFetchedAt && Date.now() - Date.parse(creds.portalsLastFetchedAt) < PORTALS_TTL_MS) {
|
|
17762
|
+
return creds;
|
|
17763
|
+
}
|
|
17764
|
+
if (isTokenExpired(creds.accessToken)) {
|
|
17765
|
+
return creds;
|
|
17766
|
+
}
|
|
17767
|
+
try {
|
|
17768
|
+
const res = await fetch(`${getApiUrl()}/auth/portals`, {
|
|
17769
|
+
headers: { Authorization: `Bearer ${creds.accessToken}` },
|
|
17770
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
17771
|
+
});
|
|
17772
|
+
if (!res.ok) {
|
|
17773
|
+
return creds;
|
|
17774
|
+
}
|
|
17775
|
+
const body = await res.json();
|
|
17776
|
+
const portals = parsePortals(body);
|
|
17777
|
+
if (portals) {
|
|
17778
|
+
const updated = {
|
|
17779
|
+
...creds,
|
|
17780
|
+
portals,
|
|
17781
|
+
portalsLastFetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17782
|
+
};
|
|
17783
|
+
await writeCredentials(updated);
|
|
17784
|
+
return updated;
|
|
17785
|
+
}
|
|
17786
|
+
} catch {
|
|
17787
|
+
process.stderr.write("Warning: Could not refresh company list (using cached data).\n");
|
|
17788
|
+
}
|
|
17789
|
+
return creds;
|
|
17790
|
+
}
|
|
17791
|
+
async function markPortalsFetched(creds) {
|
|
17792
|
+
creds.portalsLastFetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
17793
|
+
await writeCredentials(creds);
|
|
17794
|
+
}
|
|
17795
|
+
async function refreshCachedPortalNames() {
|
|
17796
|
+
try {
|
|
17797
|
+
const creds = await readCredentials();
|
|
17798
|
+
if (!creds) return;
|
|
17799
|
+
const updated = await refreshPortalsIfStale(creds, true);
|
|
17800
|
+
const config2 = await readConfig();
|
|
17801
|
+
const activeId = config2.activePortal?.id;
|
|
17802
|
+
if (!activeId) return;
|
|
17803
|
+
const portal = updated.portals.find((candidate) => candidate.id === activeId);
|
|
17804
|
+
if (portal) await setActivePortal(portal);
|
|
17805
|
+
} catch {
|
|
17806
|
+
process.stderr.write(
|
|
17807
|
+
"Warning: Could not refresh company name after updating the company list.\n"
|
|
17808
|
+
);
|
|
17809
|
+
}
|
|
17810
|
+
}
|
|
17811
|
+
|
|
17812
|
+
// src/validate-flags.ts
|
|
17813
|
+
function restorePositional(args, value, originalIndex) {
|
|
17814
|
+
const insertAt = args.positionalIndexes.findIndex((index) => index > originalIndex);
|
|
17815
|
+
if (insertAt === -1) {
|
|
17816
|
+
args.positional.push(value);
|
|
17817
|
+
args.positionalIndexes.push(originalIndex);
|
|
17818
|
+
return;
|
|
17819
|
+
}
|
|
17820
|
+
args.positional.splice(insertAt, 0, value);
|
|
17821
|
+
args.positionalIndexes.splice(insertAt, 0, originalIndex);
|
|
17822
|
+
}
|
|
17823
|
+
function validateFlags(args, allowed) {
|
|
17824
|
+
const valueFlags = new Set(allowed.flags ?? []);
|
|
17825
|
+
const booleanFlags = new Set(allowed.booleans ?? []);
|
|
17826
|
+
const allAllowed = /* @__PURE__ */ new Set([...valueFlags, ...booleanFlags]);
|
|
17827
|
+
const allList = [...allAllowed].map((f) => `--${f}`).join(", ");
|
|
17828
|
+
for (const key of Object.keys(args.flags)) {
|
|
17829
|
+
if (!allAllowed.has(key)) {
|
|
17830
|
+
exitWithError(
|
|
17831
|
+
`Unknown flag: --${key}. Allowed flags: ${allList || "(none)"}`,
|
|
17832
|
+
"UNKNOWN_FLAG",
|
|
17833
|
+
void 0,
|
|
17834
|
+
EXIT.USAGE
|
|
17835
|
+
);
|
|
17836
|
+
}
|
|
17837
|
+
if (booleanFlags.has(key) && !valueFlags.has(key) && args.flagValueSources[key] === "next") {
|
|
17838
|
+
args.boolean.add(key);
|
|
17839
|
+
restorePositional(args, args.flags[key], args.flagValueIndexes[key]);
|
|
17840
|
+
delete args.flags[key];
|
|
17841
|
+
delete args.flagValueSources[key];
|
|
17842
|
+
delete args.flagValueIndexes[key];
|
|
17843
|
+
continue;
|
|
17844
|
+
}
|
|
17845
|
+
if (booleanFlags.has(key) && !valueFlags.has(key)) {
|
|
17846
|
+
exitWithError(
|
|
17847
|
+
`Flag --${key} does not take a value.`,
|
|
17848
|
+
"UNEXPECTED_FLAG_VALUE",
|
|
17849
|
+
void 0,
|
|
17850
|
+
EXIT.USAGE
|
|
17851
|
+
);
|
|
17852
|
+
}
|
|
17853
|
+
}
|
|
17854
|
+
for (const key of args.boolean) {
|
|
17855
|
+
if (valueFlags.has(key)) {
|
|
17856
|
+
exitWithError(`Flag --${key} requires a value.`, "MISSING_FLAG_VALUE", void 0, EXIT.USAGE);
|
|
17857
|
+
}
|
|
17858
|
+
if (!booleanFlags.has(key)) {
|
|
17859
|
+
exitWithError(
|
|
17860
|
+
`Unknown flag: --${key}. Allowed flags: ${allList || "(none)"}`,
|
|
17861
|
+
"UNKNOWN_FLAG",
|
|
17862
|
+
void 0,
|
|
17863
|
+
EXIT.USAGE
|
|
17864
|
+
);
|
|
17865
|
+
}
|
|
17866
|
+
}
|
|
17867
|
+
}
|
|
17868
|
+
|
|
17869
|
+
// src/commands/auth-status.ts
|
|
17870
|
+
async function runAuthStatus(args) {
|
|
17871
|
+
validateFlags(args, { flags: [], booleans: [] });
|
|
17872
|
+
let creds = await readCredentials();
|
|
17873
|
+
const config2 = await readConfig();
|
|
17874
|
+
if (creds) {
|
|
17875
|
+
creds = await refreshPortalsIfStale(creds);
|
|
17876
|
+
}
|
|
17877
|
+
if (!creds) {
|
|
17878
|
+
if (isJsonMode()) {
|
|
17879
|
+
printJson({ data: { authenticated: false } });
|
|
17880
|
+
} else {
|
|
17881
|
+
printLine("Not logged in. Run: captable-ai login");
|
|
17882
|
+
}
|
|
17883
|
+
return;
|
|
17884
|
+
}
|
|
17885
|
+
const expired = isTokenExpired(creds.accessToken);
|
|
17886
|
+
const payload = decodeJwtPayload(creds.accessToken);
|
|
17887
|
+
const expiresAt = typeof payload.exp === "number" ? new Date(payload.exp * 1e3).toISOString() : creds.expiresAt;
|
|
17888
|
+
const activeCompany = resolveActiveCompany(config2, creds.portals);
|
|
17889
|
+
const status = {
|
|
17890
|
+
authenticated: true,
|
|
17891
|
+
expired,
|
|
17892
|
+
user: creds.user,
|
|
17893
|
+
activeCompany,
|
|
17894
|
+
tokenExpiresAt: expiresAt,
|
|
17895
|
+
companyCount: creds.portals.length
|
|
17896
|
+
};
|
|
17897
|
+
if (isJsonMode()) {
|
|
17898
|
+
printJson({ data: status });
|
|
17899
|
+
return;
|
|
17900
|
+
}
|
|
17901
|
+
printLine(
|
|
17902
|
+
expired ? "Status: Expired (will auto-refresh on next command)" : "Status: Authenticated"
|
|
17903
|
+
);
|
|
17904
|
+
printLine();
|
|
17905
|
+
printKeyValue([
|
|
17906
|
+
["Email", creds.user.email],
|
|
17907
|
+
["User ID", creds.user.id],
|
|
17908
|
+
["Token expires", expiresAt],
|
|
17909
|
+
["Active company", activeCompany ? activeCompany.name ?? activeCompany.id : "(none)"],
|
|
17910
|
+
["Companies available", String(creds.portals.length)]
|
|
17911
|
+
]);
|
|
17912
|
+
}
|
|
17913
|
+
|
|
17914
|
+
// src/commands/export.ts
|
|
17915
|
+
import { renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
17916
|
+
|
|
17896
17917
|
// src/flags.ts
|
|
17897
17918
|
function looksLikeFlag(value) {
|
|
17898
17919
|
if (!value.startsWith("-")) return false;
|
|
@@ -18174,7 +18195,7 @@ var REGISTER_DESCRIPTIONS = {
|
|
|
18174
18195
|
var REGISTER_FLAGS = [
|
|
18175
18196
|
{
|
|
18176
18197
|
name: "include-pending",
|
|
18177
|
-
description: "Add draft OCF objects and investment / equity_grant rows with no OCF transaction.",
|
|
18198
|
+
description: "Add draft OCF objects and investment / equity_grant / equity_award rows with no OCF transaction.",
|
|
18178
18199
|
type: "boolean"
|
|
18179
18200
|
},
|
|
18180
18201
|
{
|
|
@@ -19563,18 +19584,21 @@ Workbook options:
|
|
|
19563
19584
|
Registers (one row per security; holder name, email, address, country, state,
|
|
19564
19585
|
plus the economics of that instrument):
|
|
19565
19586
|
stock Stock issuances: class, quantity, issue date, price, amount
|
|
19566
|
-
grants Options, RSUs, other awards: award type
|
|
19567
|
-
|
|
19568
|
-
|
|
19587
|
+
grants Options, RSUs, other awards: grant date, award type
|
|
19588
|
+
(ISO/NSO/RSU), quantity, status, vesting schedule, vesting
|
|
19589
|
+
length, cliff, exercise price, signed date, vesting start
|
|
19590
|
+
date, dollar amount, series, plan
|
|
19569
19591
|
warrants Warrants: quantity, exercise price, purchase price, expiration,
|
|
19570
19592
|
underlying class, issue date
|
|
19571
|
-
convertibles SAFEs, notes:
|
|
19572
|
-
valuation cap,
|
|
19593
|
+
convertibles SAFEs, notes: signature date, execution date, status, round,
|
|
19594
|
+
amount, funding method, type, investment date, valuation cap,
|
|
19595
|
+
discount
|
|
19573
19596
|
|
|
19574
19597
|
Register options:
|
|
19575
19598
|
--include-pending Add what is not committed yet: draft OCF objects, and
|
|
19576
|
-
investment / equity_grant rows with no
|
|
19577
|
-
A row that already has an OCF object
|
|
19599
|
+
investment / equity_grant / equity_award rows with no
|
|
19600
|
+
OCF transaction. A row that already has an OCF object
|
|
19601
|
+
is listed once.
|
|
19578
19602
|
--as-of YYYY-MM-DD Only securities issued on or before this date
|
|
19579
19603
|
--type SAFE|NOTE convertibles only: keep SAFEs or notes
|
|
19580
19604
|
--csv Print CSV to stdout: RFC-4180 quoting, UTF-8, LF line
|
|
@@ -24522,6 +24546,9 @@ function exitForThrownError(err) {
|
|
|
24522
24546
|
if (err instanceof AuthRequiredError) {
|
|
24523
24547
|
exitWithError(err.message, "AUTH_REQUIRED", void 0, EXIT.AUTH);
|
|
24524
24548
|
}
|
|
24549
|
+
if (err instanceof ApiRequestError) {
|
|
24550
|
+
exitWithError(err.message, err.code ?? "ERROR", err.details, EXIT.SERVER_ERROR);
|
|
24551
|
+
}
|
|
24525
24552
|
exitWithError(err.message, "ERROR", void 0, EXIT.SERVER_ERROR);
|
|
24526
24553
|
}
|
|
24527
24554
|
async function main() {
|
package/package.json
CHANGED