@moda-ai/cli 1.13.0 → 1.13.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/dist/auth-storage.d.ts +35 -0
- package/dist/auth-storage.js +159 -0
- package/dist/auth.d.ts +17 -13
- package/dist/auth.js +350 -173
- package/dist/cli-session.d.ts +26 -0
- package/dist/cli-session.js +169 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +52 -0
- package/dist/command-context.d.ts +7 -0
- package/dist/command-context.js +12 -0
- package/dist/init/index.js +8 -3
- package/dist/init/tenant.d.ts +34 -2
- package/dist/init/tenant.js +218 -26
- package/dist/profile-commands.js +10 -1
- package/dist/provision.d.ts +28 -0
- package/dist/provision.js +104 -0
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +171 -4
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CliAuthError } from './errors.js';
|
|
2
|
+
import { type AuthSessionOptions, type StoredAuth } from './auth-storage.js';
|
|
3
|
+
/**
|
|
4
|
+
* Fire-and-forget sliding-expiry renewal, invoked on every authenticated
|
|
5
|
+
* request: a no-op unless the session is a moda_ct token inside the renewal
|
|
6
|
+
* window. Never awaited by callers, never throws, never holds the process
|
|
7
|
+
* open (unref'd socket); on success the stored expires_at is rewritten in
|
|
8
|
+
* place.
|
|
9
|
+
*/
|
|
10
|
+
export declare function scheduleRenewalIfNearExpiry(session: StoredAuth, authOptions: AuthSessionOptions): void;
|
|
11
|
+
/** The CliAuthError thrown whenever a stored moda_ct session has expired. */
|
|
12
|
+
export declare function cliSessionExpiredError(): CliAuthError;
|
|
13
|
+
/**
|
|
14
|
+
* Classify a 401 for a moda_ct session. Returns the CliAuthError to throw,
|
|
15
|
+
* or null when the renew probe says the token is actually alive (the caller
|
|
16
|
+
* then surfaces the original response unchanged).
|
|
17
|
+
*/
|
|
18
|
+
export declare function classifyModaCtUnauthorized(session: StoredAuth, authOptions: AuthSessionOptions): Promise<CliAuthError | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort server-side revocation of a stored moda_ct session token,
|
|
21
|
+
* called by `moda auth logout` before local deletion. No-op for legacy
|
|
22
|
+
* auth0-kind sessions; all failures are swallowed (the local deletion is
|
|
23
|
+
* the contract).
|
|
24
|
+
*/
|
|
25
|
+
export declare function revokeCliSessionToken(options?: AuthSessionOptions): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=cli-session.d.ts.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// src/cli-session.ts — moda_ct CLI session token lifecycle: renewal
|
|
2
|
+
// (sliding expiry), revocation, and 401 classification against the
|
|
3
|
+
// /api/cli/auth/* endpoints.
|
|
4
|
+
import { request as httpRequest } from 'node:http';
|
|
5
|
+
import { request as httpsRequest } from 'node:https';
|
|
6
|
+
import { resolveModaBaseUrl } from './config.js';
|
|
7
|
+
import { CliAuthError } from './errors.js';
|
|
8
|
+
import { isModaCtSession, loadAuthSession, persistAuthSession, } from './auth-storage.js';
|
|
9
|
+
// Background sliding-expiry renewal fires when a moda_ct session has less
|
|
10
|
+
// than this long left.
|
|
11
|
+
const RENEW_WINDOW_SECONDS = 7 * 86_400;
|
|
12
|
+
const RENEW_TIMEOUT_MS = 10_000;
|
|
13
|
+
// Synchronous renew probe used to classify a 401, and best-effort revoke on
|
|
14
|
+
// logout: both must never stall a command, so they get a tight budget.
|
|
15
|
+
const RENEW_PROBE_TIMEOUT_MS = 2_000;
|
|
16
|
+
const REVOKE_TIMEOUT_MS = 2_000;
|
|
17
|
+
/**
|
|
18
|
+
* POST an Authorization-bearer request to a CLI session lifecycle endpoint
|
|
19
|
+
* (/api/cli/auth/renew or /api/cli/auth/revoke) over raw node http/https.
|
|
20
|
+
* Not fetch: the background renewal must never delay process exit, and only
|
|
21
|
+
* a raw socket can be unref'd. Rejects on network failure or timeout.
|
|
22
|
+
*/
|
|
23
|
+
function postCliSessionEndpoint(path, token, timeoutMs, opts = {}) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let url;
|
|
26
|
+
try {
|
|
27
|
+
// Resolve the origin from the same profile/env context the session was
|
|
28
|
+
// loaded with: a `--profile staging` session's renew/revoke must hit the
|
|
29
|
+
// staging backend that minted it, not the active/default profile's.
|
|
30
|
+
url = new URL(`${resolveModaBaseUrl(opts.authOptions)}${path}`);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const requestFn = url.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
37
|
+
const req = requestFn(url, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
40
|
+
timeout: timeoutMs,
|
|
41
|
+
}, (res) => {
|
|
42
|
+
const chunks = [];
|
|
43
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
44
|
+
res.on('end', () => {
|
|
45
|
+
resolve({
|
|
46
|
+
status: res.statusCode ?? 0,
|
|
47
|
+
body: Buffer.concat(chunks).toString('utf8'),
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
res.on('error', reject);
|
|
51
|
+
});
|
|
52
|
+
if (opts.unrefSocket) {
|
|
53
|
+
req.on('socket', (socket) => {
|
|
54
|
+
socket.unref();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
req.on('timeout', () => {
|
|
58
|
+
req.destroy(new Error(`CLI session request timed out after ${timeoutMs}ms`));
|
|
59
|
+
});
|
|
60
|
+
req.on('error', reject);
|
|
61
|
+
req.end();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parse a renew response body and rewrite the stored session's expiry in
|
|
66
|
+
* place, preserving the profile scoping the session was loaded with.
|
|
67
|
+
* Best-effort: an unreadable or incomplete body is simply ignored.
|
|
68
|
+
*/
|
|
69
|
+
function persistRenewedExpiry(session, body, authOptions) {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(body);
|
|
72
|
+
const expiresAtMs = typeof parsed.expires_at === 'string' ? Date.parse(parsed.expires_at) : NaN;
|
|
73
|
+
if (!Number.isFinite(expiresAtMs))
|
|
74
|
+
return;
|
|
75
|
+
persistAuthSession({ ...session, expires_at: Math.floor(expiresAtMs / 1000) }, authOptions);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Best-effort persistence only.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Stampede guard: at most one background renewal in flight per process.
|
|
82
|
+
let renewInFlight = false;
|
|
83
|
+
/**
|
|
84
|
+
* Fire-and-forget sliding-expiry renewal, invoked on every authenticated
|
|
85
|
+
* request: a no-op unless the session is a moda_ct token inside the renewal
|
|
86
|
+
* window. Never awaited by callers, never throws, never holds the process
|
|
87
|
+
* open (unref'd socket); on success the stored expires_at is rewritten in
|
|
88
|
+
* place.
|
|
89
|
+
*/
|
|
90
|
+
export function scheduleRenewalIfNearExpiry(session, authOptions) {
|
|
91
|
+
if (!isModaCtSession(session))
|
|
92
|
+
return;
|
|
93
|
+
if (session.expires_at - Date.now() / 1000 >= RENEW_WINDOW_SECONDS)
|
|
94
|
+
return;
|
|
95
|
+
if (renewInFlight)
|
|
96
|
+
return;
|
|
97
|
+
renewInFlight = true;
|
|
98
|
+
void (async () => {
|
|
99
|
+
try {
|
|
100
|
+
const result = await postCliSessionEndpoint('/api/cli/auth/renew', session.access_token, RENEW_TIMEOUT_MS, { unrefSocket: true, authOptions });
|
|
101
|
+
if (result.status !== 200)
|
|
102
|
+
return;
|
|
103
|
+
persistRenewedExpiry(session, result.body, authOptions);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Silent by design: renewal is opportunistic, the command result must
|
|
107
|
+
// not depend on it.
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
renewInFlight = false;
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
}
|
|
114
|
+
/** The CliAuthError thrown whenever a stored moda_ct session has expired. */
|
|
115
|
+
export function cliSessionExpiredError() {
|
|
116
|
+
return new CliAuthError('The stored CLI session token has expired. Run "moda auth login" to sign in again.', { reason: 'token_expired', login_command: ['moda', 'auth', 'login'] });
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Classify a 401 for a moda_ct session. Returns the CliAuthError to throw,
|
|
120
|
+
* or null when the renew probe says the token is actually alive (the caller
|
|
121
|
+
* then surfaces the original response unchanged).
|
|
122
|
+
*/
|
|
123
|
+
export async function classifyModaCtUnauthorized(session, authOptions) {
|
|
124
|
+
const loginCommand = ['moda', 'auth', 'login'];
|
|
125
|
+
if (Date.now() / 1000 > session.expires_at) {
|
|
126
|
+
return cliSessionExpiredError();
|
|
127
|
+
}
|
|
128
|
+
let probe;
|
|
129
|
+
try {
|
|
130
|
+
probe = await postCliSessionEndpoint('/api/cli/auth/renew', session.access_token, RENEW_PROBE_TIMEOUT_MS, { authOptions });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return new CliAuthError('The Moda API rejected the stored CLI session token, and the renewal check could not reach the server. ' +
|
|
134
|
+
'The token has not expired, so this is likely transient: retry the command instead of logging in again.', { reason: 'renew_failed', login_command: loginCommand });
|
|
135
|
+
}
|
|
136
|
+
if (probe.status === 401) {
|
|
137
|
+
return new CliAuthError('The stored CLI session token has been revoked. Run "moda auth login" to sign in again.', { reason: 'token_revoked', login_command: loginCommand });
|
|
138
|
+
}
|
|
139
|
+
if (probe.status === 200) {
|
|
140
|
+
// The token is demonstrably alive (the probe just slid its expiry) but
|
|
141
|
+
// the original request still 401'd; persist the fresh expiry and let the
|
|
142
|
+
// caller see the original response.
|
|
143
|
+
persistRenewedExpiry(session, probe.body, authOptions);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
// Any other renew status (e.g. a 5xx) is indistinguishable from a
|
|
147
|
+
// transient server problem: same retry guidance as a network failure.
|
|
148
|
+
return new CliAuthError('The Moda API rejected the stored CLI session token, and the renewal check failed ' +
|
|
149
|
+
`(HTTP ${probe.status}). The token has not expired, so this is likely transient: ` +
|
|
150
|
+
'retry the command instead of logging in again.', { reason: 'renew_failed', login_command: loginCommand });
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Best-effort server-side revocation of a stored moda_ct session token,
|
|
154
|
+
* called by `moda auth logout` before local deletion. No-op for legacy
|
|
155
|
+
* auth0-kind sessions; all failures are swallowed (the local deletion is
|
|
156
|
+
* the contract).
|
|
157
|
+
*/
|
|
158
|
+
export async function revokeCliSessionToken(options = {}) {
|
|
159
|
+
const session = loadAuthSession({ ...options, includeExpired: true });
|
|
160
|
+
if (!session || !isModaCtSession(session))
|
|
161
|
+
return;
|
|
162
|
+
try {
|
|
163
|
+
await postCliSessionEndpoint('/api/cli/auth/revoke', session.access_token, REVOKE_TIMEOUT_MS, { authOptions: options });
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Best-effort: logout proceeds with local deletion regardless.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=cli-session.js.map
|
package/dist/cli.d.ts
CHANGED
|
@@ -11,6 +11,16 @@ export interface ParsedArgs {
|
|
|
11
11
|
}
|
|
12
12
|
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
13
13
|
export declare function flagsToArgs(flags: Record<string, string>, positionalKey?: string, positionalValue?: string): Record<string, unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* Build the argv the caller should re-run to resume an interrupted command,
|
|
16
|
+
* echoing back the raw flags exactly as parsed (so per-invocation opt-outs
|
|
17
|
+
* like --no-telemetry survive). Treats `--flag` and `--flag=true` identically.
|
|
18
|
+
*
|
|
19
|
+
* Limitation: positional arguments are not included — the current callers
|
|
20
|
+
* (`moda init`, `moda provision`) take none. Extend the signature before
|
|
21
|
+
* reusing this for a command with positionals.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildResumeCommand(command: string, flags: Record<string, string>): string[];
|
|
14
24
|
export declare function runCommand(command: string, positional: string | undefined, flags: Record<string, string>, positionals?: string[], context?: CommandContext): Promise<number>;
|
|
15
25
|
interface CommandDispatchResult {
|
|
16
26
|
exitCode: number;
|
package/dist/cli.js
CHANGED
|
@@ -101,12 +101,29 @@ export function flagsToArgs(flags, positionalKey, positionalValue) {
|
|
|
101
101
|
}
|
|
102
102
|
return result;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Build the argv the caller should re-run to resume an interrupted command,
|
|
106
|
+
* echoing back the raw flags exactly as parsed (so per-invocation opt-outs
|
|
107
|
+
* like --no-telemetry survive). Treats `--flag` and `--flag=true` identically.
|
|
108
|
+
*
|
|
109
|
+
* Limitation: positional arguments are not included — the current callers
|
|
110
|
+
* (`moda init`, `moda provision`) take none. Extend the signature before
|
|
111
|
+
* reusing this for a command with positionals.
|
|
112
|
+
*/
|
|
113
|
+
export function buildResumeCommand(command, flags) {
|
|
114
|
+
return [
|
|
115
|
+
'moda',
|
|
116
|
+
command,
|
|
117
|
+
...Object.entries(flags).map(([key, value]) => value === 'true' ? `--${key}` : `--${key}=${value}`),
|
|
118
|
+
];
|
|
119
|
+
}
|
|
104
120
|
// --- Help ---
|
|
105
121
|
function printHelp() {
|
|
106
122
|
console.error(`Usage: moda <command> [options]
|
|
107
123
|
|
|
108
124
|
Commands:
|
|
109
125
|
init Set up Moda in your project
|
|
126
|
+
provision Headless tenant + API key provisioning (JSON credentials on stdout)
|
|
110
127
|
status Show concise local Moda setup status
|
|
111
128
|
manifest Describe the CLI machine protocol and commands
|
|
112
129
|
doctor Diagnose local Moda setup and data flow
|
|
@@ -234,8 +251,17 @@ Init flags:
|
|
|
234
251
|
--no-autosync Skip autosync workflow creation
|
|
235
252
|
--offline Run final validation offline (skip the online telemetry check)
|
|
236
253
|
|
|
254
|
+
Provision flags (headless; requires an existing "moda auth login" session):
|
|
255
|
+
--tenant-id=ID Organization to provision (required with multiple
|
|
256
|
+
organizations; MODA_TENANT_ID env is also honored,
|
|
257
|
+
flag wins). Never prompts, even on a TTY.
|
|
258
|
+
--label=TEXT API key label (default: machine hostname)
|
|
259
|
+
--save Also persist the key to ~/.moda/config.json
|
|
260
|
+
(default: print only; headless callers own storage)
|
|
261
|
+
|
|
237
262
|
Examples:
|
|
238
263
|
moda init
|
|
264
|
+
moda provision --tenant-id=<org-id>
|
|
239
265
|
moda status
|
|
240
266
|
moda manifest --json
|
|
241
267
|
moda doctor --json
|
|
@@ -734,6 +760,32 @@ export const commandRegistry = createCommandRegistry([
|
|
|
734
760
|
return { exitCode: 0, apiKey: result.apiKey };
|
|
735
761
|
},
|
|
736
762
|
},
|
|
763
|
+
{
|
|
764
|
+
name: 'provision',
|
|
765
|
+
description: 'Headless tenant + API key provisioning for CI and agents (JSON credentials on stdout)',
|
|
766
|
+
examples: [
|
|
767
|
+
'moda provision',
|
|
768
|
+
'moda provision --tenant-id=<org-id>',
|
|
769
|
+
'moda provision --tenant-id=<org-id> --label=ci-runner --save',
|
|
770
|
+
'KEY=$(moda provision --tenant-id <org-id> | jq -r .apiKey)',
|
|
771
|
+
],
|
|
772
|
+
auth: 'none',
|
|
773
|
+
outputModes: ['human', 'json', 'agent'],
|
|
774
|
+
mutability: 'write',
|
|
775
|
+
validateConfigBeforeRun: false,
|
|
776
|
+
resetApiRequestCountBeforeRun: true,
|
|
777
|
+
// result-and-error like init: recoverable failures (auth_required /
|
|
778
|
+
// input_required / access_error) are recorded, not just successes.
|
|
779
|
+
telemetry: 'result-and-error',
|
|
780
|
+
handler: async (context) => {
|
|
781
|
+
const { runProvision } = await import('./provision.js');
|
|
782
|
+
return runProvision(context, {
|
|
783
|
+
// rawFlags (not context.flags) so per-invocation opt-outs like
|
|
784
|
+
// --no-telemetry survive into the resume command.
|
|
785
|
+
resumeCommand: buildResumeCommand('provision', context.rawFlags),
|
|
786
|
+
});
|
|
787
|
+
},
|
|
788
|
+
},
|
|
737
789
|
{
|
|
738
790
|
name: 'manifest',
|
|
739
791
|
description: 'Describe the CLI machine protocol, commands, events, and exit codes',
|
|
@@ -38,6 +38,13 @@ export interface CommandContext {
|
|
|
38
38
|
/** Snapshot of telemetry recorded so far by the handler. */
|
|
39
39
|
getTelemetryMeta(): CommandTelemetryMeta;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Normalize a raw flag/env value to a trimmed non-empty string. Accepts
|
|
43
|
+
* non-string values because flagsToArgs number-coerces numeric-looking flag
|
|
44
|
+
* values (which must never happen to tenant ids, profile names, or labels);
|
|
45
|
+
* empty values (`--label=`) become undefined so defaults apply.
|
|
46
|
+
*/
|
|
47
|
+
export declare function stringOption(value: unknown): string | undefined;
|
|
41
48
|
export declare function createCommandContext(input: CommandContextInput, options?: {
|
|
42
49
|
commandName?: string;
|
|
43
50
|
commandFlags?: Record<string, string>;
|
package/dist/command-context.js
CHANGED
|
@@ -2,6 +2,18 @@ import { createCliOutput, outputModeFromFlags } from './output.js';
|
|
|
2
2
|
function flagEnabled(flags, key) {
|
|
3
3
|
return flags[key] === 'true';
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Normalize a raw flag/env value to a trimmed non-empty string. Accepts
|
|
7
|
+
* non-string values because flagsToArgs number-coerces numeric-looking flag
|
|
8
|
+
* values (which must never happen to tenant ids, profile names, or labels);
|
|
9
|
+
* empty values (`--label=`) become undefined so defaults apply.
|
|
10
|
+
*/
|
|
11
|
+
export function stringOption(value) {
|
|
12
|
+
if (value === undefined || value === null)
|
|
13
|
+
return undefined;
|
|
14
|
+
const text = String(value).trim();
|
|
15
|
+
return text.length > 0 ? text : undefined;
|
|
16
|
+
}
|
|
5
17
|
export function createCommandContext(input, options = {}) {
|
|
6
18
|
const flags = options.commandFlags ?? input.flags;
|
|
7
19
|
const rawFlags = options.rawFlags ?? input.flags;
|
package/dist/init/index.js
CHANGED
|
@@ -44,8 +44,13 @@ export async function runInit(flags, options = {}) {
|
|
|
44
44
|
emit('welcome', 'Onboarding this project as a Moda agent harness.', 'completed');
|
|
45
45
|
phases.push({ id: 'welcome', title: 'Welcome', status: 'completed' });
|
|
46
46
|
// Step 2: Authentication
|
|
47
|
+
// Every auth touchpoint below (cached-token check, browser login, tenant
|
|
48
|
+
// setup) must share the same --profile context, or `moda init --profile X`
|
|
49
|
+
// logs in / stores under the default profile while the rest of init reads
|
|
50
|
+
// from X — the session then looks unauthenticated or uses the wrong key.
|
|
51
|
+
const profile = stringFlag(flags.profile);
|
|
47
52
|
log(renderInitStep(2, 7, 'Authentication'));
|
|
48
|
-
let token = loadToken();
|
|
53
|
+
let token = loadToken({ profile });
|
|
49
54
|
if (!token) {
|
|
50
55
|
if (json || agent) {
|
|
51
56
|
throw new CliAuthError('Browser authentication is required before non-interactive init can continue.', {
|
|
@@ -66,7 +71,7 @@ export async function runInit(flags, options = {}) {
|
|
|
66
71
|
}
|
|
67
72
|
log(renderInitNote(`Opening browser to ${action === 'signup' ? 'sign up' : 'log in'}...`, 'warning'));
|
|
68
73
|
emit('auth', `Opening browser to ${action}.`);
|
|
69
|
-
token = await login(action);
|
|
74
|
+
token = await login(action, { profile });
|
|
70
75
|
log(renderInitNote('Authentication successful'));
|
|
71
76
|
}
|
|
72
77
|
else {
|
|
@@ -87,7 +92,7 @@ export async function runInit(flags, options = {}) {
|
|
|
87
92
|
nonInteractive: json || agent,
|
|
88
93
|
silent: !human,
|
|
89
94
|
resumeCommand: ['moda', 'init', ...resumeFlagsForInit(flags)],
|
|
90
|
-
profile
|
|
95
|
+
profile,
|
|
91
96
|
});
|
|
92
97
|
log(renderInitNote(`Active workspace: ${tenantName}`));
|
|
93
98
|
emit('workspace', `Active workspace: ${tenantName}`, 'completed');
|
package/dist/init/tenant.d.ts
CHANGED
|
@@ -4,17 +4,39 @@ interface BootstrapResult {
|
|
|
4
4
|
tenantName: string;
|
|
5
5
|
tenantSlug: string;
|
|
6
6
|
}
|
|
7
|
+
export interface TenantKeyResult extends BootstrapResult {
|
|
8
|
+
/**
|
|
9
|
+
* True when a previously stored key for the selected tenant passed the
|
|
10
|
+
* liveness probe and was reused instead of minting a new one.
|
|
11
|
+
*/
|
|
12
|
+
reused: boolean;
|
|
13
|
+
}
|
|
7
14
|
export interface SelectTenantAndCreateKeyOptions {
|
|
8
15
|
/** Tenant id preselected via --tenant-id (flag) or MODA_TENANT_ID (env). */
|
|
9
16
|
tenantId?: string;
|
|
10
17
|
/** Where the preselected tenant id came from; drives the env-shadow warning. */
|
|
11
18
|
tenantIdSource?: 'flag' | 'env';
|
|
12
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Non-interactive runs must never open the selection prompt. Headless-by-
|
|
21
|
+
* contract callers (`moda provision`) pass true unconditionally — regardless
|
|
22
|
+
* of terminal state.
|
|
23
|
+
*/
|
|
13
24
|
nonInteractive?: boolean;
|
|
14
25
|
/** Suppress human prose so it never interleaves with JSON envelopes on stdout. */
|
|
15
26
|
silent?: boolean;
|
|
16
27
|
/** Resume command echoed into the needs_input event for multi-tenant runs. */
|
|
17
28
|
resumeCommand?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Bootstrap key label sent to the backend (clipped to its 60-char cap).
|
|
31
|
+
* Defaults to the machine hostname.
|
|
32
|
+
*/
|
|
33
|
+
label?: string;
|
|
34
|
+
/**
|
|
35
|
+
* When false, skip the ~/.moda/config.json persistence entirely —
|
|
36
|
+
* headless callers (`moda provision`) own their credential storage.
|
|
37
|
+
* Defaults to true (init behavior).
|
|
38
|
+
*/
|
|
39
|
+
persistConfig?: boolean;
|
|
18
40
|
/**
|
|
19
41
|
* Profile context (--profile) threaded from init: scopes the auth-session
|
|
20
42
|
* reads and request origin behind authFetch, the stored-key candidate
|
|
@@ -22,7 +44,17 @@ export interface SelectTenantAndCreateKeyOptions {
|
|
|
22
44
|
*/
|
|
23
45
|
profile?: string;
|
|
24
46
|
env?: NodeJS.ProcessEnv;
|
|
47
|
+
/**
|
|
48
|
+
* Test seam for the post-bootstrap validate-retry: override the retry
|
|
49
|
+
* delays and/or the sleep implementation so tests never wait on real
|
|
50
|
+
* timers. Production callers leave this unset (defaults:
|
|
51
|
+
* POST_BOOTSTRAP_VALIDATE_DELAYS_MS via setTimeout).
|
|
52
|
+
*/
|
|
53
|
+
validateRetry?: {
|
|
54
|
+
delaysMs?: readonly number[];
|
|
55
|
+
sleep?: (ms: number) => Promise<void>;
|
|
56
|
+
};
|
|
25
57
|
}
|
|
26
|
-
export declare function selectTenantAndCreateKey(options?: SelectTenantAndCreateKeyOptions): Promise<
|
|
58
|
+
export declare function selectTenantAndCreateKey(options?: SelectTenantAndCreateKeyOptions): Promise<TenantKeyResult>;
|
|
27
59
|
export {};
|
|
28
60
|
//# sourceMappingURL=tenant.d.ts.map
|