recappi 0.1.0 → 0.1.1
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/dist/index.js +377 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -692,6 +692,7 @@ var init_tui = __esm({
|
|
|
692
692
|
|
|
693
693
|
// src/cli.ts
|
|
694
694
|
import { Command, CommanderError, InvalidArgumentError } from "commander/esm.mjs";
|
|
695
|
+
import os3 from "os";
|
|
695
696
|
|
|
696
697
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
697
698
|
var external_exports = {};
|
|
@@ -15208,7 +15209,7 @@ function date4(params) {
|
|
|
15208
15209
|
config(en_default());
|
|
15209
15210
|
|
|
15210
15211
|
// ../../packages/contracts/src/index.ts
|
|
15211
|
-
var CLI_SCHEMA_VERSION = "2026-06-
|
|
15212
|
+
var CLI_SCHEMA_VERSION = "2026-06-25";
|
|
15212
15213
|
function toJsonSchema(schema) {
|
|
15213
15214
|
return external_exports.toJSONSchema(schema, { target: "draft-2020-12" });
|
|
15214
15215
|
}
|
|
@@ -15262,6 +15263,22 @@ var authStatusDataSchema = external_exports.object({
|
|
|
15262
15263
|
email: external_exports.string().optional(),
|
|
15263
15264
|
userId: external_exports.string().optional()
|
|
15264
15265
|
});
|
|
15266
|
+
var authLoginDataSchema = external_exports.object({
|
|
15267
|
+
loggedIn: external_exports.literal(true),
|
|
15268
|
+
origin: external_exports.string(),
|
|
15269
|
+
email: external_exports.string().optional(),
|
|
15270
|
+
userId: external_exports.string().optional()
|
|
15271
|
+
});
|
|
15272
|
+
var authLogoutDataSchema = external_exports.object({
|
|
15273
|
+
loggedIn: external_exports.literal(false),
|
|
15274
|
+
origin: external_exports.string(),
|
|
15275
|
+
cleared: external_exports.boolean()
|
|
15276
|
+
});
|
|
15277
|
+
var authImportDataSchema = external_exports.object({
|
|
15278
|
+
imported: external_exports.boolean(),
|
|
15279
|
+
origin: external_exports.string(),
|
|
15280
|
+
source: external_exports.literal("macos-keychain")
|
|
15281
|
+
});
|
|
15265
15282
|
var uploadSuccessSchema = external_exports.object({
|
|
15266
15283
|
filePath: external_exports.string(),
|
|
15267
15284
|
recordingId: external_exports.string(),
|
|
@@ -15543,17 +15560,19 @@ async function resolveAuthContext(opts = {}) {
|
|
|
15543
15560
|
token: config2.token,
|
|
15544
15561
|
source: "config"
|
|
15545
15562
|
};
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15563
|
+
if (opts.includeMacOSKeychain === true) {
|
|
15564
|
+
const keychain = await inspectMacOSAppKeychain({
|
|
15565
|
+
env,
|
|
15566
|
+
platform: opts.platform
|
|
15567
|
+
});
|
|
15568
|
+
if (keychain.token) return { origin, token: keychain.token, source: "macos-keychain" };
|
|
15550
15569
|
}
|
|
15551
15570
|
return { origin, token: null, source: "none" };
|
|
15552
15571
|
}
|
|
15553
15572
|
function requireToken(ctx) {
|
|
15554
15573
|
if (!ctx.token) {
|
|
15555
15574
|
throw cliError("auth.not_logged_in", "Not logged in to Recappi.", {
|
|
15556
|
-
hint: "
|
|
15575
|
+
hint: "Run recappi auth login, or set RECAPPI_AUTH_TOKEN for automation."
|
|
15557
15576
|
});
|
|
15558
15577
|
}
|
|
15559
15578
|
return ctx.token;
|
|
@@ -15579,14 +15598,61 @@ function validateOrigin(value) {
|
|
|
15579
15598
|
}
|
|
15580
15599
|
return url2.origin;
|
|
15581
15600
|
}
|
|
15601
|
+
async function saveAuthConfig(homeDir, config2) {
|
|
15602
|
+
const target = primaryConfigPath(homeDir);
|
|
15603
|
+
await fs.mkdir(path.dirname(target), { recursive: true, mode: 448 });
|
|
15604
|
+
const existing = await readConfigObject(target);
|
|
15605
|
+
const next = {
|
|
15606
|
+
...existing,
|
|
15607
|
+
origin: validateOrigin(config2.origin),
|
|
15608
|
+
authToken: config2.token
|
|
15609
|
+
};
|
|
15610
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
15611
|
+
await fs.writeFile(tmp, `${JSON.stringify(next, null, 2)}
|
|
15612
|
+
`, { mode: 384 });
|
|
15613
|
+
await fs.rename(tmp, target);
|
|
15614
|
+
await fs.chmod(target, 384).catch(() => {
|
|
15615
|
+
});
|
|
15616
|
+
}
|
|
15617
|
+
async function clearAuthConfig(homeDir) {
|
|
15618
|
+
const target = primaryConfigPath(homeDir);
|
|
15619
|
+
const existing = await readConfigObject(target);
|
|
15620
|
+
if (!("authToken" in existing)) return false;
|
|
15621
|
+
delete existing.authToken;
|
|
15622
|
+
await fs.mkdir(path.dirname(target), { recursive: true, mode: 448 });
|
|
15623
|
+
await fs.writeFile(target, `${JSON.stringify(existing, null, 2)}
|
|
15624
|
+
`, { mode: 384 });
|
|
15625
|
+
await fs.chmod(target, 384).catch(() => {
|
|
15626
|
+
});
|
|
15627
|
+
return true;
|
|
15628
|
+
}
|
|
15629
|
+
async function inspectMacOSAppKeychain(opts = {}) {
|
|
15630
|
+
const env = opts.env ?? process.env;
|
|
15631
|
+
if (env.RECAPPI_DISABLE_KEYCHAIN_AUTH === "1") {
|
|
15632
|
+
return {
|
|
15633
|
+
status: "disabled",
|
|
15634
|
+
token: null,
|
|
15635
|
+
message: "macOS app keychain lookup is disabled by RECAPPI_DISABLE_KEYCHAIN_AUTH=1."
|
|
15636
|
+
};
|
|
15637
|
+
}
|
|
15638
|
+
const platform = opts.platform ?? process.platform;
|
|
15639
|
+
if (platform !== "darwin") {
|
|
15640
|
+
return {
|
|
15641
|
+
status: "unsupported",
|
|
15642
|
+
token: null,
|
|
15643
|
+
message: "macOS app keychain lookup is only available on macOS."
|
|
15644
|
+
};
|
|
15645
|
+
}
|
|
15646
|
+
return readMacOSAppToken();
|
|
15647
|
+
}
|
|
15648
|
+
function primaryConfigPath(homeDir) {
|
|
15649
|
+
return path.join(homeDir, ".config", "recappi", "config.json");
|
|
15650
|
+
}
|
|
15582
15651
|
async function readConfig(homeDir) {
|
|
15583
|
-
const candidates = [
|
|
15584
|
-
path.join(homeDir, ".config", "recappi", "config.json"),
|
|
15585
|
-
path.join(homeDir, ".recappi", "config.json")
|
|
15586
|
-
];
|
|
15652
|
+
const candidates = [primaryConfigPath(homeDir), path.join(homeDir, ".recappi", "config.json")];
|
|
15587
15653
|
for (const candidate of candidates) {
|
|
15588
15654
|
try {
|
|
15589
|
-
const parsed =
|
|
15655
|
+
const parsed = await readConfigObject(candidate);
|
|
15590
15656
|
const token = typeof parsed.authToken === "string" ? parsed.authToken.trim() : void 0;
|
|
15591
15657
|
const origin = typeof parsed.origin === "string" ? parsed.origin.trim() : void 0;
|
|
15592
15658
|
if (token || origin) return { ...token ? { token } : {}, ...origin ? { origin } : {} };
|
|
@@ -15601,6 +15667,14 @@ async function readConfig(homeDir) {
|
|
|
15601
15667
|
}
|
|
15602
15668
|
return {};
|
|
15603
15669
|
}
|
|
15670
|
+
async function readConfigObject(candidate) {
|
|
15671
|
+
try {
|
|
15672
|
+
return JSON.parse(await fs.readFile(candidate, "utf8"));
|
|
15673
|
+
} catch (error51) {
|
|
15674
|
+
if (error51.code === "ENOENT") return {};
|
|
15675
|
+
throw error51;
|
|
15676
|
+
}
|
|
15677
|
+
}
|
|
15604
15678
|
async function readMacOSAppToken() {
|
|
15605
15679
|
try {
|
|
15606
15680
|
const { stdout } = await execFileAsync(
|
|
@@ -15609,10 +15683,165 @@ async function readMacOSAppToken() {
|
|
|
15609
15683
|
{ timeout: 2e3, maxBuffer: 1024 * 1024 }
|
|
15610
15684
|
);
|
|
15611
15685
|
const token = stdout.trim();
|
|
15612
|
-
|
|
15613
|
-
|
|
15614
|
-
|
|
15686
|
+
if (token.length > 0) {
|
|
15687
|
+
return {
|
|
15688
|
+
status: "ok",
|
|
15689
|
+
token,
|
|
15690
|
+
message: "Found a Recappi Mini app token in the macOS keychain."
|
|
15691
|
+
};
|
|
15692
|
+
}
|
|
15693
|
+
return {
|
|
15694
|
+
status: "missing",
|
|
15695
|
+
token: null,
|
|
15696
|
+
message: "Recappi Mini app keychain item was present but empty."
|
|
15697
|
+
};
|
|
15698
|
+
} catch (error51) {
|
|
15699
|
+
const err = error51;
|
|
15700
|
+
const timedOut = err.killed || err.signal === "SIGTERM";
|
|
15701
|
+
const stderr = typeof err.message === "string" ? err.message : "";
|
|
15702
|
+
if (!timedOut && /could not be found|The specified item could not be found/i.test(stderr)) {
|
|
15703
|
+
return {
|
|
15704
|
+
status: "missing",
|
|
15705
|
+
token: null,
|
|
15706
|
+
message: "No Recappi Mini app token was found in the macOS keychain."
|
|
15707
|
+
};
|
|
15708
|
+
}
|
|
15709
|
+
return {
|
|
15710
|
+
status: "error",
|
|
15711
|
+
token: null,
|
|
15712
|
+
message: timedOut ? "Timed out while reading the Recappi Mini app keychain token." : "Could not read the Recappi Mini app keychain token.",
|
|
15713
|
+
hint: "Use recappi auth login for the CLI, or run recappi auth import-macos explicitly if you want to import the app session."
|
|
15714
|
+
};
|
|
15715
|
+
}
|
|
15716
|
+
}
|
|
15717
|
+
|
|
15718
|
+
// src/auth-login.ts
|
|
15719
|
+
import { execFile as execFile2 } from "child_process";
|
|
15720
|
+
import os2 from "os";
|
|
15721
|
+
import { promisify as promisify2 } from "util";
|
|
15722
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
15723
|
+
async function loginWithDeviceCode(opts) {
|
|
15724
|
+
const origin = validateOrigin(opts.origin);
|
|
15725
|
+
const fetchImpl = opts.deps?.fetchImpl ?? fetch;
|
|
15726
|
+
const sleep = opts.deps?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
15727
|
+
const start = await startDeviceAuth(origin, fetchImpl);
|
|
15728
|
+
opts.onPrompt?.(
|
|
15729
|
+
[
|
|
15730
|
+
"To sign in to Recappi CLI:",
|
|
15731
|
+
` 1. Open ${start.verificationUri}`,
|
|
15732
|
+
` 2. Enter code: ${start.userCode}`,
|
|
15733
|
+
""
|
|
15734
|
+
].join("\n")
|
|
15735
|
+
);
|
|
15736
|
+
if (!opts.noOpen) {
|
|
15737
|
+
const openUrl2 = opts.deps?.openUrl ?? openUrlWithSystemBrowser;
|
|
15738
|
+
await openUrl2(start.verificationUriComplete).catch((error51) => {
|
|
15739
|
+
opts.onPrompt?.(
|
|
15740
|
+
`Could not open the browser automatically: ${error51 instanceof Error ? error51.message : String(error51)}
|
|
15741
|
+
`
|
|
15742
|
+
);
|
|
15743
|
+
});
|
|
15744
|
+
}
|
|
15745
|
+
let intervalMs = start.interval * 1e3;
|
|
15746
|
+
const expiresAt = Date.now() + start.expiresIn * 1e3;
|
|
15747
|
+
while (Date.now() < expiresAt) {
|
|
15748
|
+
await sleep(intervalMs);
|
|
15749
|
+
const poll = await pollDeviceAuth(origin, start.deviceCode, fetchImpl);
|
|
15750
|
+
if (poll.status === "pending") {
|
|
15751
|
+
if (typeof poll.interval === "number") intervalMs = poll.interval * 1e3;
|
|
15752
|
+
continue;
|
|
15753
|
+
}
|
|
15754
|
+
if (poll.status === "slow_down") {
|
|
15755
|
+
intervalMs = (typeof poll.interval === "number" ? poll.interval : intervalMs / 1e3 + 5) * 1e3;
|
|
15756
|
+
continue;
|
|
15757
|
+
}
|
|
15758
|
+
if (poll.status === "denied") {
|
|
15759
|
+
throw cliError("auth.unauthorized", "Recappi CLI sign-in was denied.");
|
|
15760
|
+
}
|
|
15761
|
+
if (poll.status === "expired") {
|
|
15762
|
+
throw cliError("auth.unauthorized", "Recappi CLI sign-in code expired.", {
|
|
15763
|
+
hint: "Run recappi auth login again."
|
|
15764
|
+
});
|
|
15765
|
+
}
|
|
15766
|
+
if (poll.status === "authorized") {
|
|
15767
|
+
const token = typeof poll.token === "string" ? poll.token.trim() : "";
|
|
15768
|
+
if (!token) {
|
|
15769
|
+
throw cliError("cloud.invalid_response", "Recappi device auth returned no token.");
|
|
15770
|
+
}
|
|
15771
|
+
const user = isRecord(poll.user) ? poll.user : {};
|
|
15772
|
+
await saveAuthConfig(opts.homeDir ?? os2.homedir(), { origin, token });
|
|
15773
|
+
return {
|
|
15774
|
+
loggedIn: true,
|
|
15775
|
+
origin,
|
|
15776
|
+
...typeof user.email === "string" ? { email: user.email } : {},
|
|
15777
|
+
...typeof user.id === "string" ? { userId: user.id } : {}
|
|
15778
|
+
};
|
|
15779
|
+
}
|
|
15780
|
+
throw cliError("cloud.invalid_response", "Recappi device auth returned an unknown status.");
|
|
15781
|
+
}
|
|
15782
|
+
throw cliError("auth.unauthorized", "Recappi CLI sign-in timed out.", {
|
|
15783
|
+
hint: "Run recappi auth login again."
|
|
15784
|
+
});
|
|
15785
|
+
}
|
|
15786
|
+
async function startDeviceAuth(origin, fetchImpl) {
|
|
15787
|
+
const response = await fetchImpl(new URL("/api/device-auth/start", origin), {
|
|
15788
|
+
method: "POST"
|
|
15789
|
+
});
|
|
15790
|
+
if (!response.ok) {
|
|
15791
|
+
throw cliError(
|
|
15792
|
+
"cloud.http_error",
|
|
15793
|
+
`Could not start Recappi device sign-in (${response.status}).`,
|
|
15794
|
+
{
|
|
15795
|
+
retryable: response.status >= 500
|
|
15796
|
+
}
|
|
15797
|
+
);
|
|
15798
|
+
}
|
|
15799
|
+
const body = await response.json();
|
|
15800
|
+
const deviceCode = stringField(body.device_code, "device_code");
|
|
15801
|
+
const userCode = stringField(body.user_code, "user_code");
|
|
15802
|
+
const verificationUri = stringField(body.verification_uri, "verification_uri");
|
|
15803
|
+
const verificationUriComplete = stringField(
|
|
15804
|
+
body.verification_uri_complete,
|
|
15805
|
+
"verification_uri_complete"
|
|
15806
|
+
);
|
|
15807
|
+
const expiresIn = numberField(body.expires_in, "expires_in");
|
|
15808
|
+
const interval = numberField(body.interval, "interval");
|
|
15809
|
+
return { deviceCode, userCode, verificationUri, verificationUriComplete, expiresIn, interval };
|
|
15810
|
+
}
|
|
15811
|
+
async function pollDeviceAuth(origin, deviceCode, fetchImpl) {
|
|
15812
|
+
const response = await fetchImpl(new URL("/api/device-auth/poll", origin), {
|
|
15813
|
+
method: "POST",
|
|
15814
|
+
headers: { "content-type": "application/json" },
|
|
15815
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
15816
|
+
});
|
|
15817
|
+
if (!response.ok) {
|
|
15818
|
+
throw cliError("cloud.http_error", `Recappi device sign-in poll failed (${response.status}).`, {
|
|
15819
|
+
retryable: response.status >= 500
|
|
15820
|
+
});
|
|
15615
15821
|
}
|
|
15822
|
+
return await response.json();
|
|
15823
|
+
}
|
|
15824
|
+
async function openUrlWithSystemBrowser(url2) {
|
|
15825
|
+
if (process.platform === "darwin") {
|
|
15826
|
+
await execFileAsync2("/usr/bin/open", [url2]);
|
|
15827
|
+
return;
|
|
15828
|
+
}
|
|
15829
|
+
if (process.platform === "win32") {
|
|
15830
|
+
await execFileAsync2("cmd", ["/c", "start", "", url2]);
|
|
15831
|
+
return;
|
|
15832
|
+
}
|
|
15833
|
+
await execFileAsync2("xdg-open", [url2]);
|
|
15834
|
+
}
|
|
15835
|
+
function stringField(value, name) {
|
|
15836
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
15837
|
+
throw cliError("cloud.invalid_response", `Recappi device auth response is missing ${name}.`);
|
|
15838
|
+
}
|
|
15839
|
+
function numberField(value, name) {
|
|
15840
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
|
|
15841
|
+
throw cliError("cloud.invalid_response", `Recappi device auth response is missing ${name}.`);
|
|
15842
|
+
}
|
|
15843
|
+
function isRecord(value) {
|
|
15844
|
+
return typeof value === "object" && value !== null;
|
|
15616
15845
|
}
|
|
15617
15846
|
|
|
15618
15847
|
// src/api.ts
|
|
@@ -15779,9 +16008,11 @@ var RecappiApiClient = class {
|
|
|
15779
16008
|
this.auth = auth;
|
|
15780
16009
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
15781
16010
|
this.sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
16011
|
+
this.env = opts.env ?? process.env;
|
|
15782
16012
|
}
|
|
15783
16013
|
fetchImpl;
|
|
15784
16014
|
sleep;
|
|
16015
|
+
env;
|
|
15785
16016
|
async authStatus() {
|
|
15786
16017
|
if (!this.auth.token) {
|
|
15787
16018
|
return { loggedIn: false, origin: this.auth.origin };
|
|
@@ -15793,7 +16024,7 @@ var RecappiApiClient = class {
|
|
|
15793
16024
|
return { loggedIn: false, origin: this.auth.origin };
|
|
15794
16025
|
}
|
|
15795
16026
|
const body = await parseJson(response);
|
|
15796
|
-
const user =
|
|
16027
|
+
const user = isRecord2(body) && isRecord2(body.user) ? body.user : void 0;
|
|
15797
16028
|
return {
|
|
15798
16029
|
loggedIn: Boolean(user),
|
|
15799
16030
|
origin: this.auth.origin,
|
|
@@ -15820,7 +16051,7 @@ var RecappiApiClient = class {
|
|
|
15820
16051
|
status: this.auth.token ? "ok" : "error",
|
|
15821
16052
|
message: this.auth.token ? `Found auth token from ${this.auth.source}.` : "No Recappi auth token found.",
|
|
15822
16053
|
...this.auth.token ? {} : {
|
|
15823
|
-
hint: "
|
|
16054
|
+
hint: "Run recappi auth login, or set RECAPPI_AUTH_TOKEN for automation."
|
|
15824
16055
|
}
|
|
15825
16056
|
});
|
|
15826
16057
|
if (this.auth.token) {
|
|
@@ -15830,7 +16061,9 @@ var RecappiApiClient = class {
|
|
|
15830
16061
|
name: "auth.session",
|
|
15831
16062
|
status: status2.loggedIn ? "ok" : "error",
|
|
15832
16063
|
message: status2.loggedIn ? `Cloud session is valid${status2.email ? ` for ${status2.email}` : ""}.` : "Cloud did not accept the configured auth token.",
|
|
15833
|
-
...status2.loggedIn ? {} : {
|
|
16064
|
+
...status2.loggedIn ? {} : {
|
|
16065
|
+
hint: `Run recappi auth login again. If you use --origin, make sure the token belongs to ${this.auth.origin}.`
|
|
16066
|
+
}
|
|
15834
16067
|
});
|
|
15835
16068
|
} catch (error51) {
|
|
15836
16069
|
const cli = toCliError(error51);
|
|
@@ -15842,6 +16075,13 @@ var RecappiApiClient = class {
|
|
|
15842
16075
|
});
|
|
15843
16076
|
}
|
|
15844
16077
|
}
|
|
16078
|
+
const keychain = await inspectMacOSAppKeychain({ env: this.env });
|
|
16079
|
+
checks.push({
|
|
16080
|
+
name: "auth.macos_keychain",
|
|
16081
|
+
status: keychain.status === "error" ? "warn" : "ok",
|
|
16082
|
+
message: keychain.message,
|
|
16083
|
+
...keychain.status === "ok" ? { hint: "Run recappi auth import-macos to copy this app session into CLI config." } : keychain.hint ? { hint: keychain.hint } : {}
|
|
16084
|
+
});
|
|
15845
16085
|
checks.push({
|
|
15846
16086
|
name: "audio.wav",
|
|
15847
16087
|
status: "ok",
|
|
@@ -15882,7 +16122,7 @@ var RecappiApiClient = class {
|
|
|
15882
16122
|
limit: String(opts.limit)
|
|
15883
16123
|
});
|
|
15884
16124
|
const parsed = await this.getJson(`/api/jobs?${params}`);
|
|
15885
|
-
const items = Array.isArray(parsed.items) ? parsed.items.filter(
|
|
16125
|
+
const items = Array.isArray(parsed.items) ? parsed.items.filter(isRecord2).map(mapJobListItem) : [];
|
|
15886
16126
|
return jobListDataSchema.parse({
|
|
15887
16127
|
items,
|
|
15888
16128
|
status: typeof parsed.status === "string" ? parsed.status : opts.status,
|
|
@@ -16035,7 +16275,7 @@ var RecappiApiClient = class {
|
|
|
16035
16275
|
}
|
|
16036
16276
|
);
|
|
16037
16277
|
const parsed = await parseJson(response);
|
|
16038
|
-
if (!
|
|
16278
|
+
if (!isRecord2(parsed) || typeof parsed.etag !== "string") {
|
|
16039
16279
|
throw cliError("cloud.invalid_response", "Upload part response was missing etag.");
|
|
16040
16280
|
}
|
|
16041
16281
|
parts.push({ partNumber, etag: parsed.etag });
|
|
@@ -16107,8 +16347,8 @@ async function parseJson(response) {
|
|
|
16107
16347
|
async function responseMessage(response) {
|
|
16108
16348
|
try {
|
|
16109
16349
|
const parsed = await response.clone().json();
|
|
16110
|
-
if (
|
|
16111
|
-
if (
|
|
16350
|
+
if (isRecord2(parsed) && typeof parsed.message === "string") return parsed.message;
|
|
16351
|
+
if (isRecord2(parsed) && typeof parsed.error === "string") return parsed.error;
|
|
16112
16352
|
} catch {
|
|
16113
16353
|
}
|
|
16114
16354
|
try {
|
|
@@ -16118,7 +16358,7 @@ async function responseMessage(response) {
|
|
|
16118
16358
|
return response.statusText;
|
|
16119
16359
|
}
|
|
16120
16360
|
}
|
|
16121
|
-
function
|
|
16361
|
+
function isRecord2(value) {
|
|
16122
16362
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16123
16363
|
}
|
|
16124
16364
|
function mapTranscript(row) {
|
|
@@ -16162,7 +16402,7 @@ function requiredNumber(row, key) {
|
|
|
16162
16402
|
}
|
|
16163
16403
|
function parseSegments(value, text, durationMs) {
|
|
16164
16404
|
const decoded = decodeJsonArray(value);
|
|
16165
|
-
const rawSegments = decoded.filter(
|
|
16405
|
+
const rawSegments = decoded.filter(isRecord2).map((segment) => {
|
|
16166
16406
|
const segmentText = typeof segment.text === "string" ? segment.text.trim() : "";
|
|
16167
16407
|
if (!segmentText) return null;
|
|
16168
16408
|
const start = typeof segment.start === "number" && segment.start >= 0 ? segment.start : 0;
|
|
@@ -16213,12 +16453,12 @@ function parseSummary(row) {
|
|
|
16213
16453
|
if (strings.length > 0) summary[key] = strings;
|
|
16214
16454
|
}
|
|
16215
16455
|
}
|
|
16216
|
-
const actionItems = Array.isArray(payload?.actionItems) ? payload.actionItems.filter(
|
|
16456
|
+
const actionItems = Array.isArray(payload?.actionItems) ? payload.actionItems.filter(isRecord2).flatMap((item) => {
|
|
16217
16457
|
if (typeof item.what !== "string" || !item.what.trim()) return [];
|
|
16218
16458
|
return [{ what: item.what, ...typeof item.who === "string" ? { who: item.who } : {} }];
|
|
16219
16459
|
}) : [];
|
|
16220
16460
|
if (actionItems.length > 0) summary.actionItems = actionItems;
|
|
16221
|
-
const quotes = Array.isArray(payload?.quotes) ? payload.quotes.filter(
|
|
16461
|
+
const quotes = Array.isArray(payload?.quotes) ? payload.quotes.filter(isRecord2).flatMap((item) => {
|
|
16222
16462
|
if (typeof item.text !== "string" || !item.text.trim()) return [];
|
|
16223
16463
|
return [
|
|
16224
16464
|
{
|
|
@@ -16228,7 +16468,7 @@ function parseSummary(row) {
|
|
|
16228
16468
|
];
|
|
16229
16469
|
}) : [];
|
|
16230
16470
|
if (quotes.length > 0) summary.quotes = quotes;
|
|
16231
|
-
const timeline = Array.isArray(payload?.timeline) ? payload.timeline.filter(
|
|
16471
|
+
const timeline = Array.isArray(payload?.timeline) ? payload.timeline.filter(isRecord2).flatMap((item) => {
|
|
16232
16472
|
if (typeof item.startMs !== "number" || typeof item.endMs !== "number" || typeof item.title !== "string" || typeof item.summary !== "string") {
|
|
16233
16473
|
return [];
|
|
16234
16474
|
}
|
|
@@ -16248,7 +16488,7 @@ function parseSummary(row) {
|
|
|
16248
16488
|
return summary;
|
|
16249
16489
|
}
|
|
16250
16490
|
function mapJobListItem(row) {
|
|
16251
|
-
const recording =
|
|
16491
|
+
const recording = isRecord2(row.recording) ? row.recording : {};
|
|
16252
16492
|
return {
|
|
16253
16493
|
jobId: stringValue(row.jobId) ?? stringValue(row.id) ?? "",
|
|
16254
16494
|
recordingId: stringValue(row.recordingId) ?? "",
|
|
@@ -16295,11 +16535,11 @@ function decodeJsonArray(value) {
|
|
|
16295
16535
|
}
|
|
16296
16536
|
}
|
|
16297
16537
|
function decodeJsonRecord(value) {
|
|
16298
|
-
if (
|
|
16538
|
+
if (isRecord2(value)) return value;
|
|
16299
16539
|
if (typeof value !== "string" || !value.trim()) return null;
|
|
16300
16540
|
try {
|
|
16301
16541
|
const decoded = JSON.parse(value);
|
|
16302
|
-
return
|
|
16542
|
+
return isRecord2(decoded) ? decoded : null;
|
|
16303
16543
|
} catch {
|
|
16304
16544
|
return null;
|
|
16305
16545
|
}
|
|
@@ -16385,7 +16625,20 @@ function renderEnvelope(envelope, opts) {
|
|
|
16385
16625
|
`);
|
|
16386
16626
|
}
|
|
16387
16627
|
function renderHumanSuccess(command, data, opts) {
|
|
16388
|
-
if (command === "auth
|
|
16628
|
+
if (command === "auth login" && isRecord3(data)) {
|
|
16629
|
+
opts.stdout(`Signed in${typeof data.email === "string" ? ` as ${data.email}` : ""}
|
|
16630
|
+
`);
|
|
16631
|
+
return;
|
|
16632
|
+
}
|
|
16633
|
+
if (command === "auth logout" && isRecord3(data)) {
|
|
16634
|
+
opts.stdout(data.cleared ? "Signed out of Recappi CLI\n" : "No Recappi CLI session to clear\n");
|
|
16635
|
+
return;
|
|
16636
|
+
}
|
|
16637
|
+
if (command === "auth import-macos" && isRecord3(data)) {
|
|
16638
|
+
opts.stdout("Imported the Recappi Mini app session into Recappi CLI\n");
|
|
16639
|
+
return;
|
|
16640
|
+
}
|
|
16641
|
+
if (command === "auth status" && isRecord3(data)) {
|
|
16389
16642
|
if (data.loggedIn) {
|
|
16390
16643
|
opts.stdout(`Signed in${typeof data.email === "string" ? ` as ${data.email}` : ""}
|
|
16391
16644
|
`);
|
|
@@ -16394,17 +16647,17 @@ function renderHumanSuccess(command, data, opts) {
|
|
|
16394
16647
|
opts.stdout("Not logged in\n");
|
|
16395
16648
|
return;
|
|
16396
16649
|
}
|
|
16397
|
-
if (command === "version" &&
|
|
16650
|
+
if (command === "version" && isRecord3(data) && typeof data.version === "string") {
|
|
16398
16651
|
opts.stdout(`${data.version}
|
|
16399
16652
|
`);
|
|
16400
16653
|
return;
|
|
16401
16654
|
}
|
|
16402
|
-
if (command === "doctor" &&
|
|
16655
|
+
if (command === "doctor" && isRecord3(data) && Array.isArray(data.checks)) {
|
|
16403
16656
|
const status = typeof data.status === "string" ? data.status : "unknown";
|
|
16404
16657
|
opts.stdout(`Doctor: ${status}
|
|
16405
16658
|
`);
|
|
16406
16659
|
for (const check2 of data.checks) {
|
|
16407
|
-
if (!
|
|
16660
|
+
if (!isRecord3(check2)) continue;
|
|
16408
16661
|
const checkStatus = typeof check2.status === "string" ? check2.status : "unknown";
|
|
16409
16662
|
const name = typeof check2.name === "string" ? check2.name : "check";
|
|
16410
16663
|
const message = typeof check2.message === "string" ? ` \u2014 ${check2.message}` : "";
|
|
@@ -16415,7 +16668,7 @@ function renderHumanSuccess(command, data, opts) {
|
|
|
16415
16668
|
}
|
|
16416
16669
|
return;
|
|
16417
16670
|
}
|
|
16418
|
-
if (command === "transcript get" &&
|
|
16671
|
+
if (command === "transcript get" && isRecord3(data)) {
|
|
16419
16672
|
renderTranscriptHuman(data, opts);
|
|
16420
16673
|
return;
|
|
16421
16674
|
}
|
|
@@ -16448,7 +16701,7 @@ Next:
|
|
|
16448
16701
|
}
|
|
16449
16702
|
return;
|
|
16450
16703
|
}
|
|
16451
|
-
if ((command === "jobs wait" || command === "upload") &&
|
|
16704
|
+
if ((command === "jobs wait" || command === "upload") && isRecord3(data)) {
|
|
16452
16705
|
if (typeof data.transcriptId === "string") {
|
|
16453
16706
|
opts.stdout("Transcription ready\n");
|
|
16454
16707
|
opts.stdout(` transcriptId: ${data.transcriptId}
|
|
@@ -16469,10 +16722,10 @@ Next:
|
|
|
16469
16722
|
}
|
|
16470
16723
|
return;
|
|
16471
16724
|
}
|
|
16472
|
-
if (command === "schema" &&
|
|
16725
|
+
if (command === "schema" && isRecord3(data) && Array.isArray(data.commands)) {
|
|
16473
16726
|
opts.stdout("Commands:\n");
|
|
16474
16727
|
for (const entry of data.commands) {
|
|
16475
|
-
if (!
|
|
16728
|
+
if (!isRecord3(entry) || typeof entry.name !== "string") continue;
|
|
16476
16729
|
const summary = typeof entry.summary === "string" ? ` \u2014 ${entry.summary}` : "";
|
|
16477
16730
|
opts.stdout(` ${entry.name}${summary}
|
|
16478
16731
|
`);
|
|
@@ -16562,7 +16815,7 @@ function renderTranscriptHuman(data, opts) {
|
|
|
16562
16815
|
const segments = Array.isArray(data.segments) ? data.segments : [];
|
|
16563
16816
|
let printedBody = false;
|
|
16564
16817
|
for (const segment of segments) {
|
|
16565
|
-
if (!
|
|
16818
|
+
if (!isRecord3(segment) || typeof segment.text !== "string") continue;
|
|
16566
16819
|
const clock = typeof segment.startMs === "number" ? `[${formatClock(segment.startMs / 1e3)}] ` : "";
|
|
16567
16820
|
const speaker = typeof segment.speaker === "string" ? `${segment.speaker}: ` : "";
|
|
16568
16821
|
opts.stdout(`${clock}${speaker}${segment.text}
|
|
@@ -16574,7 +16827,7 @@ function renderTranscriptHuman(data, opts) {
|
|
|
16574
16827
|
`);
|
|
16575
16828
|
printedBody = true;
|
|
16576
16829
|
}
|
|
16577
|
-
const summary =
|
|
16830
|
+
const summary = isRecord3(data.summary) ? data.summary : void 0;
|
|
16578
16831
|
if (!summary || summary.status !== "succeeded") return;
|
|
16579
16832
|
if (typeof summary.tldr === "string" && summary.tldr.length > 0) {
|
|
16580
16833
|
opts.stdout(`
|
|
@@ -16592,7 +16845,7 @@ Summary:
|
|
|
16592
16845
|
if (Array.isArray(summary.actionItems) && summary.actionItems.length > 0) {
|
|
16593
16846
|
opts.stdout("\nAction items:\n");
|
|
16594
16847
|
for (const item of summary.actionItems) {
|
|
16595
|
-
if (!
|
|
16848
|
+
if (!isRecord3(item) || typeof item.what !== "string") continue;
|
|
16596
16849
|
const who = typeof item.who === "string" ? `${item.who}: ` : "";
|
|
16597
16850
|
opts.stdout(` - ${who}${item.what}
|
|
16598
16851
|
`);
|
|
@@ -16627,7 +16880,7 @@ function applyFields(command, data, fields, compact) {
|
|
|
16627
16880
|
};
|
|
16628
16881
|
return compact ? compactData(filtered2) : filtered2;
|
|
16629
16882
|
}
|
|
16630
|
-
if (!
|
|
16883
|
+
if (!isRecord3(data)) return data;
|
|
16631
16884
|
const allowed = new Set(Object.keys(data));
|
|
16632
16885
|
assertKnownFields(fields, allowed);
|
|
16633
16886
|
const filtered = pickFields(data, fields);
|
|
@@ -16652,7 +16905,7 @@ function compactData(value) {
|
|
|
16652
16905
|
if (Array.isArray(value)) {
|
|
16653
16906
|
return value.map(compactData).filter((item) => item !== void 0);
|
|
16654
16907
|
}
|
|
16655
|
-
if (
|
|
16908
|
+
if (isRecord3(value)) {
|
|
16656
16909
|
const out = {};
|
|
16657
16910
|
for (const [key, child] of Object.entries(value)) {
|
|
16658
16911
|
const compacted = compactData(child);
|
|
@@ -16670,20 +16923,23 @@ function stableStringify(value, compact) {
|
|
|
16670
16923
|
}
|
|
16671
16924
|
function sortKeys(value) {
|
|
16672
16925
|
if (Array.isArray(value)) return value.map(sortKeys);
|
|
16673
|
-
if (!
|
|
16926
|
+
if (!isRecord3(value)) return value;
|
|
16674
16927
|
return Object.fromEntries(
|
|
16675
16928
|
Object.keys(value).sort().map((key) => [key, sortKeys(value[key])])
|
|
16676
16929
|
);
|
|
16677
16930
|
}
|
|
16678
|
-
function
|
|
16931
|
+
function isRecord3(value) {
|
|
16679
16932
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16680
16933
|
}
|
|
16681
16934
|
function isUploadBatch(value) {
|
|
16682
|
-
return
|
|
16935
|
+
return isRecord3(value) && Array.isArray(value.successes) && Array.isArray(value.failures);
|
|
16683
16936
|
}
|
|
16684
16937
|
|
|
16685
16938
|
// src/schema.ts
|
|
16686
16939
|
var COMMAND_DATA_SCHEMAS = {
|
|
16940
|
+
"auth login": authLoginDataSchema,
|
|
16941
|
+
"auth logout": authLogoutDataSchema,
|
|
16942
|
+
"auth import-macos": authImportDataSchema,
|
|
16687
16943
|
"auth status": authStatusDataSchema,
|
|
16688
16944
|
doctor: doctorDataSchema,
|
|
16689
16945
|
upload: uploadBatchDataSchema,
|
|
@@ -16814,8 +17070,16 @@ async function runCli(deps = {}) {
|
|
|
16814
17070
|
renderSuccess("version", { version: CLI_VERSION }, render2);
|
|
16815
17071
|
return 0;
|
|
16816
17072
|
}
|
|
16817
|
-
const auth = await resolveAuthContext({
|
|
16818
|
-
|
|
17073
|
+
const auth = await resolveAuthContext({
|
|
17074
|
+
origin: parsed.options.origin,
|
|
17075
|
+
env: deps.env,
|
|
17076
|
+
homeDir: deps.homeDir
|
|
17077
|
+
});
|
|
17078
|
+
const client = new RecappiApiClient(auth, {
|
|
17079
|
+
fetchImpl: deps.fetchImpl,
|
|
17080
|
+
sleep: deps.sleep,
|
|
17081
|
+
env: deps.env
|
|
17082
|
+
});
|
|
16819
17083
|
if (parsed.kind === "dashboard") {
|
|
16820
17084
|
const runDashboard2 = deps.runDashboard ?? (await Promise.resolve().then(() => (init_tui(), tui_exports))).runDashboard;
|
|
16821
17085
|
await runDashboard2({
|
|
@@ -16830,6 +17094,44 @@ async function runCli(deps = {}) {
|
|
|
16830
17094
|
renderSuccess("auth status", data, render2);
|
|
16831
17095
|
return data.loggedIn ? 0 : 3;
|
|
16832
17096
|
}
|
|
17097
|
+
if (parsed.kind === "auth-login") {
|
|
17098
|
+
const data = await loginWithDeviceCode({
|
|
17099
|
+
origin: auth.origin,
|
|
17100
|
+
homeDir: deps.homeDir,
|
|
17101
|
+
noOpen: parsed.noOpen,
|
|
17102
|
+
onPrompt: (message) => stderr(message),
|
|
17103
|
+
deps: {
|
|
17104
|
+
fetchImpl: deps.fetchImpl,
|
|
17105
|
+
openUrl: deps.openUrl,
|
|
17106
|
+
sleep: deps.sleep
|
|
17107
|
+
}
|
|
17108
|
+
});
|
|
17109
|
+
renderSuccess("auth login", data, render2);
|
|
17110
|
+
return 0;
|
|
17111
|
+
}
|
|
17112
|
+
if (parsed.kind === "auth-logout") {
|
|
17113
|
+
const cleared = await clearAuthConfig(deps.homeDir ?? os3.homedir());
|
|
17114
|
+
renderSuccess("auth logout", { loggedIn: false, origin: auth.origin, cleared }, render2);
|
|
17115
|
+
return 0;
|
|
17116
|
+
}
|
|
17117
|
+
if (parsed.kind === "auth-import-macos") {
|
|
17118
|
+
const keychain = await inspectMacOSAppKeychain({ env: deps.env });
|
|
17119
|
+
if (!keychain.token) {
|
|
17120
|
+
throw cliError("auth.not_logged_in", keychain.message, {
|
|
17121
|
+
hint: keychain.hint ?? "Run recappi auth login instead."
|
|
17122
|
+
});
|
|
17123
|
+
}
|
|
17124
|
+
await saveAuthConfig(deps.homeDir ?? os3.homedir(), {
|
|
17125
|
+
origin: auth.origin,
|
|
17126
|
+
token: keychain.token
|
|
17127
|
+
});
|
|
17128
|
+
renderSuccess(
|
|
17129
|
+
"auth import-macos",
|
|
17130
|
+
{ imported: true, origin: auth.origin, source: "macos-keychain" },
|
|
17131
|
+
render2
|
|
17132
|
+
);
|
|
17133
|
+
return 0;
|
|
17134
|
+
}
|
|
16833
17135
|
if (parsed.kind === "doctor") {
|
|
16834
17136
|
const data = await client.doctor();
|
|
16835
17137
|
renderSuccess("doctor", data, render2);
|
|
@@ -17027,6 +17329,34 @@ Agent mode:
|
|
|
17027
17329
|
addCommonOptions(program);
|
|
17028
17330
|
const auth = program.command("auth").description("Authentication commands");
|
|
17029
17331
|
addCommonOptions(auth);
|
|
17332
|
+
const authLogin = auth.command("login").description("Sign in to Recappi Cloud with a device code").option("--no-open", "print the device URL without opening a browser");
|
|
17333
|
+
addCommonOptions(authLogin);
|
|
17334
|
+
authLogin.action((opts, command) => {
|
|
17335
|
+
onSelect({
|
|
17336
|
+
kind: "auth-login",
|
|
17337
|
+
options: collectGlobalOptions(command),
|
|
17338
|
+
commandName: "auth login",
|
|
17339
|
+
...opts.noOpen === true ? { noOpen: true } : {}
|
|
17340
|
+
});
|
|
17341
|
+
});
|
|
17342
|
+
const authLogout = auth.command("logout").description("Remove the Recappi CLI sign-in token");
|
|
17343
|
+
addCommonOptions(authLogout);
|
|
17344
|
+
authLogout.action((_options, command) => {
|
|
17345
|
+
onSelect({
|
|
17346
|
+
kind: "auth-logout",
|
|
17347
|
+
options: collectGlobalOptions(command),
|
|
17348
|
+
commandName: "auth logout"
|
|
17349
|
+
});
|
|
17350
|
+
});
|
|
17351
|
+
const authImportMacOS = auth.command("import-macos").description("Copy the Recappi Mini macOS app session into CLI config");
|
|
17352
|
+
addCommonOptions(authImportMacOS);
|
|
17353
|
+
authImportMacOS.action((_options, command) => {
|
|
17354
|
+
onSelect({
|
|
17355
|
+
kind: "auth-import-macos",
|
|
17356
|
+
options: collectGlobalOptions(command),
|
|
17357
|
+
commandName: "auth import-macos"
|
|
17358
|
+
});
|
|
17359
|
+
});
|
|
17030
17360
|
const authStatus = auth.command("status").description("Show Recappi Cloud sign-in status");
|
|
17031
17361
|
addCommonOptions(authStatus);
|
|
17032
17362
|
authStatus.action((_options, command) => {
|