@vigilbase/cli 0.2.0 → 0.2.2
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/CLOUDFLARE.md +9 -12
- package/COMMANDS.md +192 -111
- package/LOCAL.md +77 -84
- package/ONE.md +5 -6
- package/README.md +349 -173
- package/SECURITY.md +6 -4
- package/dist/auth/device-auth.d.ts +0 -1
- package/dist/auth/device-auth.js +6 -5
- package/dist/auth/index.d.ts +1 -2
- package/dist/auth/index.js +1 -2
- package/dist/auth/provider.d.ts +1 -1
- package/dist/auth/provider.js +16 -7
- package/dist/auth/refresh.d.ts +23 -4
- package/dist/auth/refresh.js +78 -22
- package/dist/auth/store.d.ts +78 -4
- package/dist/auth/store.js +385 -74
- package/dist/auth/token-metadata.d.ts +0 -1
- package/dist/auth/token-metadata.js +0 -1
- package/dist/auth/types.d.ts +0 -1
- package/dist/auth/types.js +0 -1
- package/dist/bin.d.ts +0 -1
- package/dist/bin.js +0 -1
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +13 -11
- package/dist/commands/account.d.ts +0 -1
- package/dist/commands/account.js +1 -2
- package/dist/commands/auth.d.ts +0 -1
- package/dist/commands/auth.js +71 -22
- package/dist/commands/billing.d.ts +0 -1
- package/dist/commands/billing.js +0 -1
- package/dist/commands/cloudflare.d.ts +1 -2
- package/dist/commands/cloudflare.js +14 -9
- package/dist/commands/helpers.d.ts +0 -1
- package/dist/commands/helpers.js +0 -1
- package/dist/commands/index.d.ts +0 -1
- package/dist/commands/index.js +0 -1
- package/dist/commands/legacy-validation.d.ts +7 -2
- package/dist/commands/legacy-validation.js +15 -3
- package/dist/commands/local.d.ts +6 -1
- package/dist/commands/local.js +36 -12
- package/dist/commands/one.d.ts +0 -1
- package/dist/commands/one.js +39 -13
- package/dist/commands/public-tools.d.ts +0 -1
- package/dist/commands/public-tools.js +63 -29
- package/dist/commands/runs.d.ts +0 -1
- package/dist/commands/runs.js +1 -2
- package/dist/commands/security.d.ts +1 -2
- package/dist/commands/security.js +34 -23
- package/dist/commands/watchdog.d.ts +0 -1
- package/dist/commands/watchdog.js +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- package/dist/lib/banner.d.ts +0 -1
- package/dist/lib/banner.js +0 -1
- package/dist/lib/client.d.ts +9 -2
- package/dist/lib/client.js +49 -20
- package/dist/lib/cloudflare-client.d.ts +0 -1
- package/dist/lib/cloudflare-client.js +1 -2
- package/dist/lib/config.d.ts +16 -3
- package/dist/lib/config.js +41 -14
- package/dist/lib/format.d.ts +2 -1
- package/dist/lib/format.js +19 -8
- package/dist/lib/help.d.ts +4 -2
- package/dist/lib/help.js +72 -23
- package/dist/lib/network.d.ts +10 -1
- package/dist/lib/network.js +31 -4
- package/dist/lib/one-client.d.ts +4 -1
- package/dist/lib/one-client.js +9 -3
- package/dist/lib/one-error-hints.d.ts +19 -0
- package/dist/lib/one-error-hints.js +47 -0
- package/dist/lib/one-operations.d.ts +0 -1
- package/dist/lib/one-operations.js +0 -1
- package/dist/lib/open-url.d.ts +0 -1
- package/dist/lib/open-url.js +0 -1
- package/dist/lib/platform.d.ts +2 -0
- package/dist/lib/platform.js +4 -0
- package/dist/lib/progress.d.ts +0 -1
- package/dist/lib/progress.js +0 -1
- package/dist/lib/security-client.d.ts +0 -1
- package/dist/lib/security-client.js +0 -1
- package/dist/lib/skill-fetch.d.ts +0 -1
- package/dist/lib/skill-fetch.js +0 -1
- package/dist/lib/system-trust.d.ts +0 -1
- package/dist/lib/system-trust.js +0 -1
- package/dist/lib/tui.d.ts +0 -1
- package/dist/lib/tui.js +0 -1
- package/dist/local/cloudflare-checkup/cf/allowlist.d.ts +0 -1
- package/dist/local/cloudflare-checkup/cf/allowlist.js +0 -1
- package/dist/local/cloudflare-checkup/cf/auth-lifecycle.d.ts +0 -1
- package/dist/local/cloudflare-checkup/cf/auth-lifecycle.js +0 -1
- package/dist/local/cloudflare-checkup/cf/child-env.d.ts +0 -1
- package/dist/local/cloudflare-checkup/cf/child-env.js +0 -1
- package/dist/local/cloudflare-checkup/cf/install.d.ts +0 -1
- package/dist/local/cloudflare-checkup/cf/install.js +0 -1
- package/dist/local/cloudflare-checkup/cf/locate.d.ts +0 -1
- package/dist/local/cloudflare-checkup/cf/locate.js +0 -1
- package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +0 -1
- package/dist/local/cloudflare-checkup/cf/run-cf.js +0 -1
- package/dist/local/cloudflare-checkup/collector.d.ts +0 -1
- package/dist/local/cloudflare-checkup/collector.js +0 -1
- package/dist/local/cloudflare-checkup/contract.d.ts +14 -3
- package/dist/local/cloudflare-checkup/contract.js +9 -3
- package/dist/local/cloudflare-checkup/email-verification.d.ts +0 -1
- package/dist/local/cloudflare-checkup/email-verification.js +0 -1
- package/dist/local/cloudflare-checkup/errors.d.ts +4 -1
- package/dist/local/cloudflare-checkup/errors.js +8 -2
- package/dist/local/cloudflare-checkup/output.d.ts +0 -1
- package/dist/local/cloudflare-checkup/output.js +0 -1
- package/dist/local/cloudflare-checkup/prompts.d.ts +0 -1
- package/dist/local/cloudflare-checkup/prompts.js +0 -1
- package/dist/local/cloudflare-checkup/rate-budget.d.ts +0 -1
- package/dist/local/cloudflare-checkup/rate-budget.js +0 -1
- package/dist/local/cloudflare-checkup/snapshot-file.d.ts +0 -1
- package/dist/local/cloudflare-checkup/snapshot-file.js +5 -4
- package/dist/local/cloudflare-checkup/submit-client.d.ts +19 -5
- package/dist/local/cloudflare-checkup/submit-client.js +63 -17
- package/dist/local/cloudflare-checkup/terminal-ui.d.ts +0 -1
- package/dist/local/cloudflare-checkup/terminal-ui.js +0 -1
- package/dist/types.d.ts +25 -3
- package/dist/types.js +16 -1
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/package.json +5 -9
- package/dist/auth/device-auth.d.ts.map +0 -1
- package/dist/auth/device-auth.js.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/provider.d.ts.map +0 -1
- package/dist/auth/provider.js.map +0 -1
- package/dist/auth/refresh.d.ts.map +0 -1
- package/dist/auth/refresh.js.map +0 -1
- package/dist/auth/store.d.ts.map +0 -1
- package/dist/auth/store.js.map +0 -1
- package/dist/auth/token-metadata.d.ts.map +0 -1
- package/dist/auth/token-metadata.js.map +0 -1
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/account.d.ts.map +0 -1
- package/dist/commands/account.js.map +0 -1
- package/dist/commands/auth.d.ts.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/billing.d.ts.map +0 -1
- package/dist/commands/billing.js.map +0 -1
- package/dist/commands/cloudflare.d.ts.map +0 -1
- package/dist/commands/cloudflare.js.map +0 -1
- package/dist/commands/helpers.d.ts.map +0 -1
- package/dist/commands/helpers.js.map +0 -1
- package/dist/commands/index.d.ts.map +0 -1
- package/dist/commands/index.js.map +0 -1
- package/dist/commands/legacy-validation.d.ts.map +0 -1
- package/dist/commands/legacy-validation.js.map +0 -1
- package/dist/commands/local.d.ts.map +0 -1
- package/dist/commands/local.js.map +0 -1
- package/dist/commands/one.d.ts.map +0 -1
- package/dist/commands/one.js.map +0 -1
- package/dist/commands/public-tools.d.ts.map +0 -1
- package/dist/commands/public-tools.js.map +0 -1
- package/dist/commands/runs.d.ts.map +0 -1
- package/dist/commands/runs.js.map +0 -1
- package/dist/commands/security.d.ts.map +0 -1
- package/dist/commands/security.js.map +0 -1
- package/dist/commands/watchdog.d.ts.map +0 -1
- package/dist/commands/watchdog.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/banner.d.ts.map +0 -1
- package/dist/lib/banner.js.map +0 -1
- package/dist/lib/client.d.ts.map +0 -1
- package/dist/lib/client.js.map +0 -1
- package/dist/lib/cloudflare-client.d.ts.map +0 -1
- package/dist/lib/cloudflare-client.js.map +0 -1
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/format.d.ts.map +0 -1
- package/dist/lib/format.js.map +0 -1
- package/dist/lib/help.d.ts.map +0 -1
- package/dist/lib/help.js.map +0 -1
- package/dist/lib/network.d.ts.map +0 -1
- package/dist/lib/network.js.map +0 -1
- package/dist/lib/one-client.d.ts.map +0 -1
- package/dist/lib/one-client.js.map +0 -1
- package/dist/lib/one-operations.d.ts.map +0 -1
- package/dist/lib/one-operations.js.map +0 -1
- package/dist/lib/open-url.d.ts.map +0 -1
- package/dist/lib/open-url.js.map +0 -1
- package/dist/lib/progress.d.ts.map +0 -1
- package/dist/lib/progress.js.map +0 -1
- package/dist/lib/security-client.d.ts.map +0 -1
- package/dist/lib/security-client.js.map +0 -1
- package/dist/lib/skill-fetch.d.ts.map +0 -1
- package/dist/lib/skill-fetch.js.map +0 -1
- package/dist/lib/system-trust.d.ts.map +0 -1
- package/dist/lib/system-trust.js.map +0 -1
- package/dist/lib/tui.d.ts.map +0 -1
- package/dist/lib/tui.js.map +0 -1
- package/dist/local/cloudflare-checkup/cf/allowlist.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/cf/allowlist.js.map +0 -1
- package/dist/local/cloudflare-checkup/cf/auth-lifecycle.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/cf/auth-lifecycle.js.map +0 -1
- package/dist/local/cloudflare-checkup/cf/child-env.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/cf/child-env.js.map +0 -1
- package/dist/local/cloudflare-checkup/cf/install.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/cf/install.js.map +0 -1
- package/dist/local/cloudflare-checkup/cf/locate.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/cf/locate.js.map +0 -1
- package/dist/local/cloudflare-checkup/cf/run-cf.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/cf/run-cf.js.map +0 -1
- package/dist/local/cloudflare-checkup/collector.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/collector.js.map +0 -1
- package/dist/local/cloudflare-checkup/contract.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/contract.js.map +0 -1
- package/dist/local/cloudflare-checkup/email-verification.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/email-verification.js.map +0 -1
- package/dist/local/cloudflare-checkup/errors.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/errors.js.map +0 -1
- package/dist/local/cloudflare-checkup/output.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/output.js.map +0 -1
- package/dist/local/cloudflare-checkup/prompts.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/prompts.js.map +0 -1
- package/dist/local/cloudflare-checkup/rate-budget.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/rate-budget.js.map +0 -1
- package/dist/local/cloudflare-checkup/snapshot-file.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/snapshot-file.js.map +0 -1
- package/dist/local/cloudflare-checkup/submit-client.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/submit-client.js.map +0 -1
- package/dist/local/cloudflare-checkup/terminal-ui.d.ts.map +0 -1
- package/dist/local/cloudflare-checkup/terminal-ui.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
package/dist/auth/store.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
3
|
import { lstat, mkdir, open, rename, rm } from 'node:fs/promises';
|
|
4
|
-
import { dirname, isAbsolute, join, parse, resolve } from 'node:path';
|
|
4
|
+
import { basename, dirname, isAbsolute, join, parse, resolve } from 'node:path';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { DEFAULT_TIMEOUT_MS } from '../lib/config.js';
|
|
7
|
+
import { platform } from '../lib/platform.js';
|
|
7
8
|
const FILE_NAME = 'credentials.json';
|
|
9
|
+
/** CLI 0.1.x rejects a version 2 credentials.json and deletes it unrevoked, so Identity grants live apart. */
|
|
10
|
+
const IDENTITY_FILE_NAME = 'identity-credentials.json';
|
|
8
11
|
const MODE_DIR = 0o700;
|
|
9
12
|
const MODE_FILE = 0o600;
|
|
10
|
-
const DEFAULT_ONE_ORIGIN = 'https://one.vigilbase.com';
|
|
13
|
+
export const DEFAULT_ONE_ORIGIN = 'https://one.vigilbase.com';
|
|
14
|
+
const INVALID_ONE_CREDENTIALS = 'Saved One credentials are invalid. Run `vigilbase logout`, then `vigilbase login` to replace them.';
|
|
15
|
+
const INVALID_IDENTITY_CREDENTIALS = 'Saved Identity credentials are invalid. Run `vigilbase login` to replace them.';
|
|
16
|
+
export const UNCERTAIN_REFRESH = 'A previous refresh has an uncertain result. Run `vigilbase login` again; its token cannot be retried safely.';
|
|
17
|
+
const SAVED_FOR_ANOTHER_ORIGIN = 'Saved for a different One origin, Identity issuer or client ID.';
|
|
11
18
|
export const OneCredentialsSchema = z.object({
|
|
12
19
|
issuer: z.string().optional(), clientId: z.string().optional(), resource: z.string().optional(), refreshToken: z.string().min(1).optional(), refreshTokenExpiresAt: z.string().optional(), refreshPending: z.boolean().optional(), orgId: z.string().optional(),
|
|
13
20
|
oneBaseUrl: z.string().optional(), accessToken: z.string().min(1), tokenType: z.string().optional(),
|
|
@@ -15,72 +22,126 @@ export const OneCredentialsSchema = z.object({
|
|
|
15
22
|
user: z.object({ id: z.string().optional(), email: z.string().optional(), name: z.string().optional() }).optional(),
|
|
16
23
|
});
|
|
17
24
|
const CredentialsSchema = z.object({ version: z.union([z.literal(1), z.literal(2)]), one: OneCredentialsSchema.nullable().optional() });
|
|
18
|
-
|
|
25
|
+
function configFilePath(config, name) {
|
|
19
26
|
if (!isAbsolute(config.configDir))
|
|
20
|
-
throw
|
|
21
|
-
return join(resolve(config.configDir),
|
|
27
|
+
throw relativeDirectoryError(config.configDir);
|
|
28
|
+
return join(resolve(config.configDir), name);
|
|
29
|
+
}
|
|
30
|
+
/** Version 1 One session credentials, shared with CLI 0.1.x. */
|
|
31
|
+
export function credentialsPath(config) {
|
|
32
|
+
return configFilePath(config, FILE_NAME);
|
|
33
|
+
}
|
|
34
|
+
/** Version 2 Identity credentials with the rotating refresh grant. */
|
|
35
|
+
export function identityCredentialsPath(config) {
|
|
36
|
+
return configFilePath(config, IDENTITY_FILE_NAME);
|
|
22
37
|
}
|
|
23
38
|
function isMissing(error) {
|
|
24
39
|
return error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
25
40
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
function isOwnedByOtherUser(info) {
|
|
42
|
+
return typeof process.getuid === 'function' && info.uid !== process.getuid();
|
|
43
|
+
}
|
|
44
|
+
/** A credentials file that passed the safety checks but cannot be parsed or is incomplete. */
|
|
45
|
+
export class InvalidCredentialsError extends Error {
|
|
46
|
+
constructor(message) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = 'InvalidCredentialsError';
|
|
29
49
|
}
|
|
30
50
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
51
|
+
/** Credentials cannot be protected by modes and owners on Windows yet. */
|
|
52
|
+
export class SignInUnsupportedError extends Error {
|
|
53
|
+
code = 'SIGN_IN_UNSUPPORTED';
|
|
54
|
+
constructor() {
|
|
55
|
+
super("Signing in isn't supported on Windows yet; public tools and cloudflare-checkup still work.");
|
|
56
|
+
this.name = 'SignInUnsupportedError';
|
|
34
57
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A credentials directory or file the CLI refuses to use. `reason` names the path that failed and `remedy`
|
|
61
|
+
* says how to fix it; `vigilbase login` can't, because it runs the same checks.
|
|
62
|
+
*/
|
|
63
|
+
export class CredentialsPathError extends Error {
|
|
64
|
+
reason;
|
|
65
|
+
remedy;
|
|
66
|
+
constructor(reason, remedy, options) {
|
|
67
|
+
super(`${reason}. ${remedy}`, options);
|
|
68
|
+
this.reason = reason;
|
|
69
|
+
this.remedy = remedy;
|
|
70
|
+
this.name = 'CredentialsPathError';
|
|
38
71
|
}
|
|
39
72
|
}
|
|
40
|
-
|
|
73
|
+
const REMOVE_FILE = 'Remove it, or set VIGILBASE_CONFIG_DIR to another directory.';
|
|
74
|
+
export function assertFile(info, path) {
|
|
75
|
+
if (info.isSymbolicLink())
|
|
76
|
+
throw new CredentialsPathError(`${path} is a symbolic link`, REMOVE_FILE);
|
|
77
|
+
if (!info.isFile())
|
|
78
|
+
throw new CredentialsPathError(`${path} is not a regular file`, REMOVE_FILE);
|
|
79
|
+
if (info.nlink !== 1)
|
|
80
|
+
throw new CredentialsPathError(`${path} has more than one hard link`, REMOVE_FILE);
|
|
81
|
+
if (isOwnedByOtherUser(info))
|
|
82
|
+
throw new CredentialsPathError(`${path} is owned by another user`, REMOVE_FILE);
|
|
83
|
+
if ((info.mode & 0o777) !== MODE_FILE)
|
|
84
|
+
throw new CredentialsPathError(`${path} must have mode 0600`, `Run chmod 600 ${path}.`);
|
|
85
|
+
}
|
|
86
|
+
const USE_ANOTHER_DIRECTORY = 'Set VIGILBASE_CONFIG_DIR to a private directory without symbolic links in its path.';
|
|
87
|
+
function relativeDirectoryError(configDir) {
|
|
88
|
+
return new CredentialsPathError(`${configDir} is not an absolute path`, 'Set VIGILBASE_CONFIG_DIR to an absolute path.');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check every ancestor before traversing it; a writable non-sticky parent permits replacement.
|
|
92
|
+
* Every credentials read or write starts here, so on Windows they fail before touching the filesystem.
|
|
93
|
+
*/
|
|
41
94
|
export async function checkDirectory(configDir, isCreate = false) {
|
|
95
|
+
if (platform() === 'win32')
|
|
96
|
+
throw new SignInUnsupportedError();
|
|
42
97
|
if (!isAbsolute(configDir))
|
|
43
|
-
throw
|
|
98
|
+
throw relativeDirectoryError(configDir);
|
|
44
99
|
const absolute = resolve(configDir);
|
|
45
|
-
const root = parse(absolute)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
100
|
+
const { root } = parse(absolute);
|
|
101
|
+
if (absolute === root)
|
|
102
|
+
throw new CredentialsPathError('The filesystem root cannot be the credentials directory', USE_ANOTHER_DIRECTORY);
|
|
103
|
+
// Each ancestor below the root, ending with the credentials directory itself.
|
|
104
|
+
const paths = [];
|
|
105
|
+
for (let path = absolute; path !== root; path = dirname(path))
|
|
106
|
+
paths.unshift(path);
|
|
107
|
+
for (const [index, path] of paths.entries()) {
|
|
50
108
|
let info;
|
|
51
109
|
try {
|
|
52
|
-
info = await lstat(
|
|
110
|
+
info = await lstat(path);
|
|
53
111
|
}
|
|
54
112
|
catch (error) {
|
|
55
|
-
if (!isMissing(error))
|
|
56
|
-
|
|
113
|
+
if (!isMissing(error)) {
|
|
114
|
+
const code = error instanceof Error && 'code' in error ? ` (${String(error.code)})` : '';
|
|
115
|
+
throw new CredentialsPathError(`${path} cannot be checked${code}`, USE_ANOTHER_DIRECTORY, { cause: error });
|
|
116
|
+
}
|
|
57
117
|
if (!isCreate)
|
|
58
118
|
return false;
|
|
59
|
-
await mkdir(
|
|
60
|
-
info = await lstat(
|
|
119
|
+
await mkdir(path, { mode: MODE_DIR });
|
|
120
|
+
info = await lstat(path);
|
|
61
121
|
}
|
|
62
|
-
if (
|
|
63
|
-
throw new
|
|
64
|
-
if (
|
|
65
|
-
throw new
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
if (index === components.length - 1) {
|
|
71
|
-
assertOwner(info, 'Credentials directory');
|
|
122
|
+
if (info.isSymbolicLink())
|
|
123
|
+
throw new CredentialsPathError(`${path} is a symbolic link`, USE_ANOTHER_DIRECTORY);
|
|
124
|
+
if (!info.isDirectory())
|
|
125
|
+
throw new CredentialsPathError(`${path} is not a directory`, USE_ANOTHER_DIRECTORY);
|
|
126
|
+
if (index === paths.length - 1) {
|
|
127
|
+
if (isOwnedByOtherUser(info))
|
|
128
|
+
throw new CredentialsPathError(`${path} is owned by another user`, USE_ANOTHER_DIRECTORY);
|
|
72
129
|
if ((info.mode & 0o777) !== MODE_DIR) {
|
|
73
|
-
throw new
|
|
130
|
+
throw new CredentialsPathError(`${path} must have mode 0700`, `Run chmod 700 ${path}, or set VIGILBASE_CONFIG_DIR to another directory.`);
|
|
74
131
|
}
|
|
75
132
|
}
|
|
133
|
+
else {
|
|
134
|
+
if ((info.mode & 0o022) !== 0 && (info.mode & 0o1000) === 0)
|
|
135
|
+
throw new CredentialsPathError(`${path} is writable by other users`, USE_ANOTHER_DIRECTORY);
|
|
136
|
+
if (isOwnedByOtherUser(info) && info.uid !== 0)
|
|
137
|
+
throw new CredentialsPathError(`${path} is owned by another user`, USE_ANOTHER_DIRECTORY);
|
|
138
|
+
}
|
|
76
139
|
}
|
|
77
|
-
if (!components.length)
|
|
78
|
-
throw new Error('The filesystem root cannot be a credentials directory.');
|
|
79
140
|
return true;
|
|
80
141
|
}
|
|
81
142
|
async function checkExistingFile(path) {
|
|
82
143
|
try {
|
|
83
|
-
assertFile(await lstat(path));
|
|
144
|
+
assertFile(await lstat(path), path);
|
|
84
145
|
return true;
|
|
85
146
|
}
|
|
86
147
|
catch (error) {
|
|
@@ -89,46 +150,173 @@ async function checkExistingFile(path) {
|
|
|
89
150
|
throw error;
|
|
90
151
|
}
|
|
91
152
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (!await checkDirectory(dirname(path)))
|
|
95
|
-
return { version: 1, one: null };
|
|
153
|
+
/** Parse one credentials file; the Identity file must hold a complete version 2 grant. */
|
|
154
|
+
async function readCredentialsFile(path) {
|
|
96
155
|
if (!await checkExistingFile(path))
|
|
97
|
-
return
|
|
156
|
+
return null;
|
|
157
|
+
const isIdentityFile = basename(path) === IDENTITY_FILE_NAME;
|
|
158
|
+
const invalid = () => new InvalidCredentialsError(isIdentityFile ? INVALID_IDENTITY_CREDENTIALS : INVALID_ONE_CREDENTIALS);
|
|
98
159
|
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
99
160
|
try {
|
|
100
|
-
assertFile(await handle.stat());
|
|
161
|
+
assertFile(await handle.stat(), path);
|
|
101
162
|
const parsed = CredentialsSchema.safeParse(JSON.parse(await handle.readFile('utf8')));
|
|
102
163
|
if (!parsed.success)
|
|
103
|
-
throw
|
|
164
|
+
throw invalid();
|
|
104
165
|
// Keep legacy session credentials separate from rotating Identity grants.
|
|
105
166
|
if (parsed.data.version === 1 && parsed.data.one)
|
|
106
167
|
delete parsed.data.one.refreshToken;
|
|
107
|
-
if (parsed.data.version === 2 && (!parsed.data.one?.issuer || !parsed.data.one.clientId || !parsed.data.one.resource || !parsed.data.one.refreshToken))
|
|
108
|
-
throw new
|
|
168
|
+
if ((parsed.data.version === 2 || isIdentityFile) && (parsed.data.version !== 2 || !parsed.data.one?.issuer || !parsed.data.one.clientId || !parsed.data.one.resource || !parsed.data.one.refreshToken))
|
|
169
|
+
throw new InvalidCredentialsError(INVALID_IDENTITY_CREDENTIALS);
|
|
109
170
|
return parsed.data;
|
|
110
171
|
}
|
|
111
172
|
catch (error) {
|
|
112
173
|
if (error instanceof SyntaxError)
|
|
113
|
-
throw
|
|
174
|
+
throw invalid();
|
|
114
175
|
throw error;
|
|
115
176
|
}
|
|
116
177
|
finally {
|
|
117
178
|
await handle.close();
|
|
118
179
|
}
|
|
119
180
|
}
|
|
120
|
-
|
|
181
|
+
/**
|
|
182
|
+
* With `onInvalid`, a safe file that cannot be parsed reads as absent and is reported; unsafe files always throw.
|
|
183
|
+
* Without it, an unparseable credentials.json reads as absent when identity-credentials.json holds a sign-in, which wins anyway.
|
|
184
|
+
*/
|
|
185
|
+
export async function readStoredCredentials(config, onInvalid) {
|
|
186
|
+
if (!await checkDirectory(dirname(credentialsPath(config))))
|
|
187
|
+
return { identity: null, shared: null };
|
|
188
|
+
const read = async (path) => {
|
|
189
|
+
try {
|
|
190
|
+
return await readCredentialsFile(path);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (!onInvalid || !(error instanceof InvalidCredentialsError))
|
|
194
|
+
throw error;
|
|
195
|
+
onInvalid(path);
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
// Read credentials.json first: a concurrent migration renames it to identity-credentials.json.
|
|
200
|
+
let shared = null;
|
|
201
|
+
let invalidShared = null;
|
|
202
|
+
try {
|
|
203
|
+
shared = await read(credentialsPath(config));
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (!(error instanceof InvalidCredentialsError))
|
|
207
|
+
throw error;
|
|
208
|
+
invalidShared = error;
|
|
209
|
+
}
|
|
210
|
+
const identity = await read(identityCredentialsPath(config));
|
|
211
|
+
if (invalidShared && !identity)
|
|
212
|
+
throw invalidShared;
|
|
213
|
+
return { identity, shared };
|
|
214
|
+
}
|
|
215
|
+
/** Identity credentials win over credentials.json everywhere tokens are read. */
|
|
216
|
+
function currentCredentials({ identity, shared }) {
|
|
217
|
+
return identity ?? shared ?? { version: 1, one: null };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* CLI 0.2.0 kept its Identity grant in credentials.json, which CLI 0.1.x rejects.
|
|
221
|
+
* Move it to identity-credentials.json unless that file exists. Callers hold credentials.json.lock.
|
|
222
|
+
*/
|
|
223
|
+
async function migrateIdentityCredentials(config) {
|
|
121
224
|
const path = credentialsPath(config);
|
|
225
|
+
const identityPath = identityCredentialsPath(config);
|
|
226
|
+
if (!await checkDirectory(dirname(path)) || await checkExistingFile(identityPath))
|
|
227
|
+
return;
|
|
228
|
+
// An unparseable credentials.json holds no grant to move.
|
|
229
|
+
const shared = await readCredentialsFile(path).catch((error) => {
|
|
230
|
+
if (error instanceof InvalidCredentialsError)
|
|
231
|
+
return null;
|
|
232
|
+
throw error;
|
|
233
|
+
});
|
|
234
|
+
if (shared?.version !== 2)
|
|
235
|
+
return;
|
|
236
|
+
// Re-check both paths immediately before the rename, as the write path does.
|
|
237
|
+
await checkDirectory(dirname(path));
|
|
238
|
+
if (!await checkExistingFile(path) || await checkExistingFile(identityPath))
|
|
239
|
+
return;
|
|
240
|
+
await rename(path, identityPath);
|
|
241
|
+
}
|
|
242
|
+
/** Identity credentials win. The first read moves a CLI 0.2.0 credentials.json aside under the lock. */
|
|
243
|
+
export async function readCredentials(config) {
|
|
244
|
+
const stored = await readStoredCredentials(config);
|
|
245
|
+
if (stored.identity || stored.shared?.version !== 2)
|
|
246
|
+
return currentCredentials(stored);
|
|
247
|
+
const { withCredentialsLock } = await import('./refresh.js');
|
|
248
|
+
return withCredentialsLock(config, () => readLockedCredentials(config));
|
|
249
|
+
}
|
|
250
|
+
/** readCredentials that never waits: while another command holds the lock, a CLI 0.2.0 credentials.json is read in place. */
|
|
251
|
+
async function readCredentialsWithoutWaiting(config) {
|
|
252
|
+
const stored = await readStoredCredentials(config);
|
|
253
|
+
if (stored.identity || stored.shared?.version !== 2)
|
|
254
|
+
return currentCredentials(stored);
|
|
255
|
+
const { CredentialsLockedError, withCredentialsLock } = await import('./refresh.js');
|
|
256
|
+
try {
|
|
257
|
+
return await withCredentialsLock(config, () => readLockedCredentials(config), false, false);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (error instanceof CredentialsLockedError)
|
|
261
|
+
return currentCredentials(stored);
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** readCredentials for callers that already hold credentials.json.lock. */
|
|
266
|
+
export async function readLockedCredentials(config) {
|
|
267
|
+
await migrateIdentityCredentials(config);
|
|
268
|
+
return currentCredentials(await readStoredCredentials(config));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The Identity grant that saving a new one replaces: identity-credentials.json's, or that of a CLI 0.2.0
|
|
272
|
+
* credentials.json which saving moves there. An unparseable file has no grant to revoke. Callers hold the lock.
|
|
273
|
+
*/
|
|
274
|
+
export async function readReplacedIdentityGrant(config) {
|
|
275
|
+
if (!await checkDirectory(dirname(credentialsPath(config))))
|
|
276
|
+
return null;
|
|
277
|
+
for (const path of [identityCredentialsPath(config), credentialsPath(config)]) {
|
|
278
|
+
let saved;
|
|
279
|
+
try {
|
|
280
|
+
saved = await readCredentialsFile(path);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
if (error instanceof InvalidCredentialsError)
|
|
284
|
+
return null;
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
if (saved)
|
|
288
|
+
return saved.version === 2 ? saved.one ?? null : null;
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Fail before a login starts, rather than after it was approved, when saving would fail: Windows, or an unsafe
|
|
294
|
+
* credentials directory or file. Unparseable files pass; login replaces them.
|
|
295
|
+
*/
|
|
296
|
+
export async function checkCredentialsLocation(config) {
|
|
297
|
+
if (!await checkDirectory(dirname(credentialsPath(config))))
|
|
298
|
+
return;
|
|
299
|
+
for (const path of [credentialsPath(config), identityCredentialsPath(config)])
|
|
300
|
+
await checkExistingFile(path);
|
|
301
|
+
}
|
|
302
|
+
/** CLI 0.2.0 can write credentials.json again after migration; that grant is ignored until logout revokes it. */
|
|
303
|
+
export async function hasIgnoredIdentityCredentials(config) {
|
|
304
|
+
const { identity, shared } = await readStoredCredentials(config);
|
|
305
|
+
return Boolean(identity && shared?.version === 2);
|
|
306
|
+
}
|
|
307
|
+
export async function writeOneCredentials(config, one) {
|
|
308
|
+
// Version 2 stores the rotating refresh grant in its own file; credentials.json stays version 1.
|
|
309
|
+
const version = one.issuer ? 2 : 1;
|
|
310
|
+
const path = version === 2 ? identityCredentialsPath(config) : credentialsPath(config);
|
|
122
311
|
await checkDirectory(dirname(path), true);
|
|
312
|
+
// A new Identity grant also replaces one CLI 0.2.0 left in credentials.json.
|
|
313
|
+
if (version === 2)
|
|
314
|
+
await migrateIdentityCredentials(config);
|
|
123
315
|
await checkExistingFile(path);
|
|
124
316
|
const parsedOne = OneCredentialsSchema.parse({ ...one, oneBaseUrl: new URL(config.oneBaseUrl ?? DEFAULT_ONE_ORIGIN).origin });
|
|
125
317
|
if (!one.issuer)
|
|
126
318
|
delete parsedOne.refreshToken;
|
|
127
|
-
const next = {
|
|
128
|
-
version: one.issuer ? 2 : 1,
|
|
129
|
-
// Version 2 stores the rotating refresh grant; legacy files stay version 1.
|
|
130
|
-
one: parsedOne,
|
|
131
|
-
};
|
|
319
|
+
const next = { version, one: parsedOne };
|
|
132
320
|
const temporaryPath = `${path}.${randomUUID()}.tmp`;
|
|
133
321
|
const handle = await open(temporaryPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, MODE_FILE);
|
|
134
322
|
try {
|
|
@@ -145,38 +333,161 @@ export async function writeOneCredentials(config, one) {
|
|
|
145
333
|
await rm(temporaryPath, { force: true });
|
|
146
334
|
}
|
|
147
335
|
}
|
|
336
|
+
/** Remove every local credentials file without revoking anything. */
|
|
148
337
|
export async function clearOneCredentials(config) {
|
|
149
|
-
const
|
|
338
|
+
const paths = [credentialsPath(config), identityCredentialsPath(config)];
|
|
339
|
+
if (!await checkDirectory(dirname(paths[0])))
|
|
340
|
+
return false;
|
|
341
|
+
// Check both files before removing either, so an unsafe file leaves both in place.
|
|
342
|
+
const existing = [];
|
|
343
|
+
for (const path of paths)
|
|
344
|
+
if (await checkExistingFile(path))
|
|
345
|
+
existing.push(path);
|
|
346
|
+
for (const path of existing)
|
|
347
|
+
await rm(path);
|
|
348
|
+
return existing.length > 0;
|
|
349
|
+
}
|
|
350
|
+
/** Remove identity-credentials.json only; credentials.json stays for CLI 0.1.x. */
|
|
351
|
+
export async function clearIdentityCredentials(config) {
|
|
352
|
+
const path = identityCredentialsPath(config);
|
|
150
353
|
if (!await checkDirectory(dirname(path)) || !await checkExistingFile(path))
|
|
151
354
|
return false;
|
|
152
355
|
await rm(path);
|
|
153
356
|
return true;
|
|
154
357
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const token = credentials.one?.accessToken;
|
|
158
|
-
if (typeof token !== 'string' || !token.trim())
|
|
159
|
-
return null;
|
|
358
|
+
/** Saved tokens are only sent to the One origin that issued them. */
|
|
359
|
+
function isForOneOrigin(one, config) {
|
|
160
360
|
try {
|
|
161
|
-
|
|
162
|
-
const target = new URL(config.oneBaseUrl ?? DEFAULT_ONE_ORIGIN).origin;
|
|
163
|
-
if (issuer !== target)
|
|
164
|
-
return null;
|
|
361
|
+
return new URL(one.oneBaseUrl ?? DEFAULT_ONE_ORIGIN).origin === new URL(config.oneBaseUrl ?? DEFAULT_ONE_ORIGIN).origin;
|
|
165
362
|
}
|
|
166
363
|
catch {
|
|
167
|
-
return
|
|
364
|
+
return false;
|
|
168
365
|
}
|
|
169
|
-
|
|
366
|
+
}
|
|
367
|
+
function hasExpired(one) {
|
|
368
|
+
if (one.expiresAt === undefined)
|
|
369
|
+
return false;
|
|
370
|
+
const expires = Date.parse(one.expiresAt);
|
|
371
|
+
return !Number.isFinite(expires) || expires <= Date.now();
|
|
372
|
+
}
|
|
373
|
+
export async function getOneAccessToken(config) {
|
|
374
|
+
const { version, one } = await readCredentials(config);
|
|
375
|
+
if (!one?.accessToken.trim() || !isForOneOrigin(one, config))
|
|
376
|
+
return null;
|
|
377
|
+
if (version === 2) {
|
|
170
378
|
const { managedAccessToken } = await import('./refresh.js');
|
|
171
379
|
// Library callers may pass a partial config; never forward an undefined timeout.
|
|
172
380
|
const timeoutMs = typeof config.timeoutMs === 'number' && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
173
381
|
return managedAccessToken({ ...config, timeoutMs });
|
|
174
382
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
383
|
+
return hasExpired(one) ? null : one.accessToken;
|
|
384
|
+
}
|
|
385
|
+
let hasWarnedAboutSignIn = false;
|
|
386
|
+
function isAbsent(error) {
|
|
387
|
+
return error instanceof Error && 'code' in error && (error.code === 'ENOENT' || error.code === 'ENOTDIR');
|
|
388
|
+
}
|
|
389
|
+
/** lstat() only, never open(): whether a credentials file exists, or may exist, in a directory that failed the checks. */
|
|
390
|
+
async function mayHaveCredentials(config) {
|
|
391
|
+
// The CLI never saves credentials under a relative path.
|
|
392
|
+
if (!isAbsolute(config.configDir))
|
|
393
|
+
return false;
|
|
394
|
+
for (const path of [credentialsPath, identityCredentialsPath]) {
|
|
395
|
+
try {
|
|
396
|
+
await lstat(path(config));
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
if (!isAbsent(error))
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
function messageOf(error) {
|
|
407
|
+
return error instanceof Error ? error.message : String(error);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* One stderr line per process. The reason is the first sentence of the problem, and the fix is
|
|
411
|
+
* `vigilbase login`, except for an unsafe credentials path, whose own remedy is kept: login would stop on it too.
|
|
412
|
+
*/
|
|
413
|
+
function warnOnce(warn, error) {
|
|
414
|
+
if (hasWarnedAboutSignIn)
|
|
415
|
+
return;
|
|
416
|
+
hasWarnedAboutSignIn = true;
|
|
417
|
+
if (error instanceof CredentialsPathError) {
|
|
418
|
+
warn(`Couldn't use your saved sign-in (${error.reason}); running anonymously. ${error.remedy}`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const problem = messageOf(error);
|
|
422
|
+
const reason = problem.split(/\.(?:\s|$)/)[0] || problem;
|
|
423
|
+
warn(`Couldn't use your saved sign-in (${reason}); running anonymously. Run \`vigilbase login\` to fix.`);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The soft directory check for list and doctor: whether the credentials directory exists and passes the checks,
|
|
427
|
+
* and the error to report when it doesn't. A directory that fails them is never written to or opened, and is
|
|
428
|
+
* only a problem when a credentials file may exist there.
|
|
429
|
+
*/
|
|
430
|
+
async function checkDirectoryQuietly(config) {
|
|
431
|
+
try {
|
|
432
|
+
return { isUsable: await checkDirectory(dirname(credentialsPath(config))), problem: null };
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
return { isUsable: false, problem: await mayHaveCredentials(config) ? error : null };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* getOneAccessToken for doctor: any problem with the saved sign-in is returned, not thrown.
|
|
440
|
+
* Lock waits, discovery and refresh use the configured timeout. Windows skips the credential store entirely.
|
|
441
|
+
* With no saved sign-in there is no problem, even under a config path that fails the directory checks.
|
|
442
|
+
*/
|
|
443
|
+
export async function getOptionalOneAccessToken(config) {
|
|
444
|
+
if (platform() === 'win32')
|
|
445
|
+
return { accessToken: null, problem: null };
|
|
446
|
+
const directory = await checkDirectoryQuietly(config);
|
|
447
|
+
if (!directory.isUsable)
|
|
448
|
+
return { accessToken: null, problem: directory.problem === null ? null : messageOf(directory.problem) };
|
|
449
|
+
try {
|
|
450
|
+
return { accessToken: await getOneAccessToken(config), problem: null };
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
return { accessToken: null, problem: messageOf(error) };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Whether list can show the saved sign-in as usable, judged from the files alone: no refresh, no network
|
|
458
|
+
* and no wait for the credentials lock. A legacy token must be unexpired; an Identity grant may hold an
|
|
459
|
+
* expired access token, which the next One command refreshes. Windows skips the credential store entirely.
|
|
460
|
+
* Any problem means signed out and one `warn` per process; an expired legacy token is not a problem.
|
|
461
|
+
* A credentials directory that fails the strict checks is never written to or opened, and is only warned
|
|
462
|
+
* about when a credentials file may exist there. Never throws.
|
|
463
|
+
*/
|
|
464
|
+
export async function hasUsableSignIn(config, warn) {
|
|
465
|
+
if (platform() === 'win32')
|
|
466
|
+
return false;
|
|
467
|
+
const directory = await checkDirectoryQuietly(config);
|
|
468
|
+
if (!directory.isUsable) {
|
|
469
|
+
if (directory.problem !== null)
|
|
470
|
+
warnOnce(warn, directory.problem);
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const { version, one } = await readCredentialsWithoutWaiting(config);
|
|
475
|
+
if (!one?.accessToken.trim())
|
|
476
|
+
return false;
|
|
477
|
+
if (version === 2) {
|
|
478
|
+
const { boundIdentityCredentials } = await import('./refresh.js');
|
|
479
|
+
if (!boundIdentityCredentials(config, one))
|
|
480
|
+
throw new Error(SAVED_FOR_ANOTHER_ORIGIN);
|
|
481
|
+
if (one.refreshPending)
|
|
482
|
+
throw new Error(UNCERTAIN_REFRESH);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
if (!isForOneOrigin(one, config))
|
|
486
|
+
throw new Error(SAVED_FOR_ANOTHER_ORIGIN);
|
|
487
|
+
return !hasExpired(one);
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
warnOnce(warn, error);
|
|
491
|
+
return false;
|
|
179
492
|
}
|
|
180
|
-
return token;
|
|
181
493
|
}
|
|
182
|
-
//# sourceMappingURL=store.js.map
|
package/dist/auth/types.d.ts
CHANGED
package/dist/auth/types.js
CHANGED
package/dist/bin.d.ts
CHANGED
package/dist/bin.js
CHANGED
package/dist/cli.d.ts
CHANGED
|
@@ -4,11 +4,13 @@ import { CLI_VERSION } from './version.js';
|
|
|
4
4
|
export { CLI_VERSION };
|
|
5
5
|
export type RunCliOptions = {
|
|
6
6
|
argv?: string[];
|
|
7
|
-
|
|
7
|
+
/** Its `isTTY` decides between the banner and the bare version for `--version`. */
|
|
8
|
+
stdout?: NodeJS.WritableStream & {
|
|
9
|
+
isTTY?: boolean;
|
|
10
|
+
};
|
|
8
11
|
stderr?: NodeJS.WritableStream;
|
|
9
12
|
stdin?: NodeJS.ReadableStream;
|
|
10
13
|
env?: NodeJS.ProcessEnv;
|
|
11
14
|
loginHandler?: (config: ReturnType<typeof createConfig>) => Promise<ExitCode>;
|
|
12
15
|
};
|
|
13
16
|
export declare function runCli(options?: RunCliOptions): Promise<ExitCode>;
|
|
14
|
-
//# sourceMappingURL=cli.d.ts.map
|