@moda-ai/cli 1.22.0 → 1.22.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/cli-g8qs2tj9.js +9777 -0
- package/dist/cli-pwbvtes8.js +2312 -0
- package/dist/cli-qszcpda3.js +284 -0
- package/dist/cli.js +4775 -1159
- package/dist/index-6h0nc1r5.js +2765 -0
- package/dist/provision-j5gq8cvx.js +72 -0
- package/package.json +3 -2
- package/skills/integration/index.json +2 -2
- package/dist/agent-schemas.d.ts +0 -3
- package/dist/agent-schemas.js +0 -358
- package/dist/agent.d.ts +0 -5
- package/dist/agent.js +0 -477
- package/dist/analyst-tui.d.ts +0 -81
- package/dist/analyst-tui.js +0 -314
- package/dist/api-client.d.ts +0 -46
- package/dist/api-client.js +0 -120
- package/dist/ask-stream.d.ts +0 -146
- package/dist/ask-stream.js +0 -473
- package/dist/auth-storage.d.ts +0 -35
- package/dist/auth-storage.js +0 -159
- package/dist/auth.d.ts +0 -50
- package/dist/auth.js +0 -694
- package/dist/cli-session.d.ts +0 -26
- package/dist/cli-session.js +0 -169
- package/dist/cli.d.ts +0 -44
- package/dist/command-context.d.ts +0 -62
- package/dist/command-context.js +0 -57
- package/dist/command-registry.d.ts +0 -22
- package/dist/command-registry.js +0 -15
- package/dist/config.d.ts +0 -60
- package/dist/config.js +0 -110
- package/dist/doctor.d.ts +0 -79
- package/dist/doctor.js +0 -1182
- package/dist/errors.d.ts +0 -50
- package/dist/errors.js +0 -64
- package/dist/exit-funnel.d.ts +0 -14
- package/dist/exit-funnel.js +0 -53
- package/dist/harness-artifact-bodies.d.ts +0 -28
- package/dist/harness-artifact-bodies.js +0 -276
- package/dist/harness-candidates.d.ts +0 -77
- package/dist/harness-candidates.js +0 -482
- package/dist/harness-remote.d.ts +0 -135
- package/dist/harness-remote.js +0 -540
- package/dist/harness-report.d.ts +0 -145
- package/dist/harness-report.js +0 -1579
- package/dist/harness-sync-limits.d.ts +0 -8
- package/dist/harness-sync-limits.js +0 -21
- package/dist/harness.d.ts +0 -463
- package/dist/harness.js +0 -4009
- package/dist/init/index.d.ts +0 -78
- package/dist/init/index.js +0 -1442
- package/dist/init/mcp.d.ts +0 -2
- package/dist/init/mcp.js +0 -27
- package/dist/init/progress-board.d.ts +0 -80
- package/dist/init/progress-board.js +0 -225
- package/dist/init/rules.d.ts +0 -6
- package/dist/init/rules.js +0 -171
- package/dist/init/sdk-catalog.d.ts +0 -10
- package/dist/init/sdk-catalog.js +0 -80
- package/dist/init/sdk-guide.d.ts +0 -19
- package/dist/init/sdk-guide.js +0 -185
- package/dist/init/sdk-integration.d.ts +0 -53
- package/dist/init/sdk-integration.js +0 -835
- package/dist/init/sdk-protocol.d.ts +0 -135
- package/dist/init/sdk-protocol.js +0 -152
- package/dist/init/sdk-verification.d.ts +0 -27
- package/dist/init/sdk-verification.js +0 -123
- package/dist/init/tenant.d.ts +0 -67
- package/dist/init/tenant.js +0 -421
- package/dist/intelligence-schema.d.ts +0 -349
- package/dist/intelligence-schema.js +0 -174
- package/dist/keys.d.ts +0 -47
- package/dist/keys.js +0 -136
- package/dist/manifest.d.ts +0 -5
- package/dist/manifest.js +0 -114
- package/dist/markdown-terminal.d.ts +0 -49
- package/dist/markdown-terminal.js +0 -212
- package/dist/output.d.ts +0 -191
- package/dist/output.js +0 -624
- package/dist/production-intelligence.d.ts +0 -175
- package/dist/production-intelligence.js +0 -1430
- package/dist/profile-commands.d.ts +0 -10
- package/dist/profile-commands.js +0 -342
- package/dist/profiles.d.ts +0 -112
- package/dist/profiles.js +0 -573
- package/dist/prompts-ab.d.ts +0 -71
- package/dist/prompts-ab.js +0 -313
- package/dist/prompts-propose.d.ts +0 -15
- package/dist/prompts-propose.js +0 -212
- package/dist/prompts.d.ts +0 -58
- package/dist/prompts.js +0 -418
- package/dist/provision.d.ts +0 -28
- package/dist/provision.js +0 -104
- package/dist/redact.d.ts +0 -2
- package/dist/redact.js +0 -90
- package/dist/schemas.d.ts +0 -225
- package/dist/schemas.js +0 -126
- package/dist/skills-bundled.d.ts +0 -35
- package/dist/skills-bundled.js +0 -157
- package/dist/skills-manifest.d.ts +0 -33
- package/dist/skills-manifest.js +0 -244
- package/dist/skills.d.ts +0 -57
- package/dist/skills.js +0 -777
- package/dist/sync.d.ts +0 -5
- package/dist/sync.js +0 -36
- package/dist/telemetry.d.ts +0 -56
- package/dist/telemetry.js +0 -167
- package/dist/transforms.d.ts +0 -25
- package/dist/transforms.js +0 -102
- package/dist/tui.d.ts +0 -48
- package/dist/tui.js +0 -393
- package/dist/update-check.d.ts +0 -31
- package/dist/update-check.js +0 -141
- package/dist/workspace.d.ts +0 -12
- package/dist/workspace.js +0 -1294
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliAccessError,
|
|
3
|
+
CliInputError,
|
|
4
|
+
CliInputRequiredError,
|
|
5
|
+
authFetch,
|
|
6
|
+
embeddedTenantIdFromApiKey,
|
|
7
|
+
getCachedLoginContext,
|
|
8
|
+
getModaDir,
|
|
9
|
+
loadCliConfig,
|
|
10
|
+
resolveApiKeyFromActiveProfile,
|
|
11
|
+
resolveIngestUrl,
|
|
12
|
+
resolveModaBaseUrl,
|
|
13
|
+
resolveTenantIdFromActiveProfile,
|
|
14
|
+
saveCliConfig,
|
|
15
|
+
writeSecretJsonFile
|
|
16
|
+
} from "./cli-pwbvtes8.js";
|
|
17
|
+
|
|
18
|
+
// src/init/tenant.ts
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
20
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
21
|
+
import { hostname } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { select } from "@inquirer/prompts";
|
|
24
|
+
function formatControlPlaneFetchError(action, status, body) {
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(body);
|
|
28
|
+
} catch {}
|
|
29
|
+
const isDbUnavailable = status === 503 && (parsed?.error === "database_unavailable" || parsed?.retryable === true || /database_unavailable|too many database connections|rds_reserved|connection pool/i.test(body));
|
|
30
|
+
if (isDbUnavailable) {
|
|
31
|
+
return `Failed to ${action} (${status}): Moda control-plane database is temporarily unavailable ` + `(connection pool exhausted). This is not an authentication failure — your session token ` + `is fine. Retry in a few seconds.` + (parsed?.message ? ` (${parsed.message})` : "");
|
|
32
|
+
}
|
|
33
|
+
if (status >= 500 && /too many database connections|rds_reserved|remaining connection slots/i.test(body)) {
|
|
34
|
+
return `Failed to ${action} (${status}): Moda control-plane database ran out of connections. ` + `This is a retryable infra outage, not an auth failure. Retry in a few seconds. Body: ${body}`;
|
|
35
|
+
}
|
|
36
|
+
return `Failed to ${action} (${status}): ${body}`;
|
|
37
|
+
}
|
|
38
|
+
var ADMIN_ACCESS_MESSAGE = "You need admin or owner access to create API keys.";
|
|
39
|
+
var ADMIN_ACCESS_GUIDANCE = "Ask your organization owner to upgrade your role.";
|
|
40
|
+
var VALIDATE_TIMEOUT_MS = 3000;
|
|
41
|
+
var POST_BOOTSTRAP_VALIDATE_DELAYS_MS = [
|
|
42
|
+
500,
|
|
43
|
+
1000,
|
|
44
|
+
2000
|
|
45
|
+
];
|
|
46
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
var MAX_LABEL_LENGTH = 60;
|
|
48
|
+
var IDEMPOTENCY_KEY_PATTERN = /^[A-Za-z0-9_-]{16,128}$/;
|
|
49
|
+
function pendingBootstrapPath(options = {}) {
|
|
50
|
+
return join(getModaDir(options), "pending-bootstrap.json");
|
|
51
|
+
}
|
|
52
|
+
function loadPendingBootstrapState(options = {}) {
|
|
53
|
+
try {
|
|
54
|
+
const path = pendingBootstrapPath(options);
|
|
55
|
+
if (!existsSync(path))
|
|
56
|
+
return {};
|
|
57
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
58
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
const state = {};
|
|
62
|
+
for (const [tenantId, entry] of Object.entries(parsed)) {
|
|
63
|
+
if (entry && typeof entry === "object" && typeof entry.idempotency_key === "string") {
|
|
64
|
+
state[tenantId] = entry;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return state;
|
|
68
|
+
} catch {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function reserveBootstrapIdempotencyKey(tenantId, options = {}) {
|
|
73
|
+
const state = loadPendingBootstrapState(options);
|
|
74
|
+
const existing = state[tenantId]?.idempotency_key;
|
|
75
|
+
if (existing && IDEMPOTENCY_KEY_PATTERN.test(existing)) {
|
|
76
|
+
return existing;
|
|
77
|
+
}
|
|
78
|
+
const key = randomUUID();
|
|
79
|
+
state[tenantId] = {
|
|
80
|
+
idempotency_key: key,
|
|
81
|
+
created_at: new Date().toISOString()
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
writeSecretJsonFile(pendingBootstrapPath(options), state);
|
|
85
|
+
} catch {}
|
|
86
|
+
return key;
|
|
87
|
+
}
|
|
88
|
+
function clearBootstrapIdempotencyKey(tenantId, options = {}) {
|
|
89
|
+
try {
|
|
90
|
+
const state = loadPendingBootstrapState(options);
|
|
91
|
+
if (!(tenantId in state))
|
|
92
|
+
return;
|
|
93
|
+
delete state[tenantId];
|
|
94
|
+
const path = pendingBootstrapPath(options);
|
|
95
|
+
if (Object.keys(state).length === 0) {
|
|
96
|
+
rmSync(path, { force: true });
|
|
97
|
+
} else {
|
|
98
|
+
writeSecretJsonFile(path, state);
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
function bootstrapLabel(custom) {
|
|
103
|
+
const clipped = custom?.trim().slice(0, MAX_LABEL_LENGTH);
|
|
104
|
+
if (clipped)
|
|
105
|
+
return clipped;
|
|
106
|
+
try {
|
|
107
|
+
return hostname().slice(0, MAX_LABEL_LENGTH) || "moda init";
|
|
108
|
+
} catch {
|
|
109
|
+
return "moda init";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
var embeddedTenantId = embeddedTenantIdFromApiKey;
|
|
113
|
+
async function isStoredKeyLive(key, options = {}) {
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`${resolveModaBaseUrl(options)}/api/v1/data/overview`, {
|
|
116
|
+
method: "GET",
|
|
117
|
+
headers: { "x-api-key": key },
|
|
118
|
+
signal: AbortSignal.timeout(VALIDATE_TIMEOUT_MS)
|
|
119
|
+
});
|
|
120
|
+
if (response.status !== 200)
|
|
121
|
+
return false;
|
|
122
|
+
await response.json();
|
|
123
|
+
return true;
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function findStoredKeyForTenant(tenantId, options = {}) {
|
|
129
|
+
const legacy = loadCliConfig(options);
|
|
130
|
+
if (legacy.api_key && legacy.tenant_id === tenantId) {
|
|
131
|
+
return legacy.api_key;
|
|
132
|
+
}
|
|
133
|
+
if (resolveTenantIdFromActiveProfile(options) === tenantId) {
|
|
134
|
+
const profileKey = resolveApiKeyFromActiveProfile(options);
|
|
135
|
+
if (profileKey)
|
|
136
|
+
return profileKey;
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
async function verifyFreshKeyLive(key, probeOptions, overrides) {
|
|
141
|
+
const delays = overrides?.delaysMs ?? POST_BOOTSTRAP_VALIDATE_DELAYS_MS;
|
|
142
|
+
const sleep = overrides?.sleep ?? defaultSleep;
|
|
143
|
+
if (await isStoredKeyLive(key, probeOptions))
|
|
144
|
+
return;
|
|
145
|
+
for (const delayMs of delays) {
|
|
146
|
+
await sleep(delayMs);
|
|
147
|
+
if (await isStoredKeyLive(key, probeOptions))
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.error(" Warning: API key created but not yet active; propagation can take a few seconds. moda doctor --online will confirm.");
|
|
151
|
+
}
|
|
152
|
+
async function selectTenantAndCreateKey(options = {}) {
|
|
153
|
+
const log = (message) => {
|
|
154
|
+
if (!options.silent)
|
|
155
|
+
console.log(message);
|
|
156
|
+
};
|
|
157
|
+
const authOptions = {
|
|
158
|
+
profile: options.profile,
|
|
159
|
+
env: options.env
|
|
160
|
+
};
|
|
161
|
+
let ctx;
|
|
162
|
+
const cachedContext = getCachedLoginContext(authOptions);
|
|
163
|
+
if (cachedContext) {
|
|
164
|
+
ctx = cachedContext;
|
|
165
|
+
} else {
|
|
166
|
+
const contextRes = await authFetch("/api/cli/context", {}, authOptions);
|
|
167
|
+
if (!contextRes.ok) {
|
|
168
|
+
const body = await contextRes.text();
|
|
169
|
+
throw new Error(formatControlPlaneFetchError("fetch context", contextRes.status, body));
|
|
170
|
+
}
|
|
171
|
+
ctx = await contextRes.json();
|
|
172
|
+
}
|
|
173
|
+
log(` Authenticated as ${ctx.user.email}`);
|
|
174
|
+
if (ctx.tenants.length === 0) {
|
|
175
|
+
throw new CliAccessError("No organizations found. Create one at https://moda.dev");
|
|
176
|
+
}
|
|
177
|
+
let selected;
|
|
178
|
+
if (options.tenantId !== undefined) {
|
|
179
|
+
const match = ctx.tenants.find((tenant) => tenant.id === options.tenantId);
|
|
180
|
+
if (!match) {
|
|
181
|
+
const valid = ctx.tenants.map((tenant) => ` ${tenant.id} ${tenant.name} (${tenant.role})`).join(`
|
|
182
|
+
`);
|
|
183
|
+
throw new CliInputError(`No organization with id '${options.tenantId}'. Valid organizations:
|
|
184
|
+
${valid}`, "Re-run with --tenant-id=<id> using one of the ids listed above.");
|
|
185
|
+
}
|
|
186
|
+
selected = match;
|
|
187
|
+
log(` Organization: ${selected.name}`);
|
|
188
|
+
} else if (ctx.tenants.length === 1) {
|
|
189
|
+
selected = ctx.tenants[0];
|
|
190
|
+
log(` Organization: ${selected.name}`);
|
|
191
|
+
} else if (options.nonInteractive) {
|
|
192
|
+
throw new CliInputRequiredError("Multiple Moda organizations are available; choose one and re-run with --tenant-id.", {
|
|
193
|
+
id: "init_tenant_selection",
|
|
194
|
+
question: "Select organization",
|
|
195
|
+
kind: "select",
|
|
196
|
+
choices: ctx.tenants.map((tenant) => ({
|
|
197
|
+
value: tenant.id,
|
|
198
|
+
label: `${tenant.name} (${tenant.role})`,
|
|
199
|
+
resume_flags: ["--tenant-id", tenant.id]
|
|
200
|
+
})),
|
|
201
|
+
resume_command: options.resumeCommand
|
|
202
|
+
}, "Re-run `moda init --tenant-id <tenant_id>`.");
|
|
203
|
+
} else {
|
|
204
|
+
selected = await select({
|
|
205
|
+
message: "Select organization",
|
|
206
|
+
choices: ctx.tenants.map((tenant) => ({
|
|
207
|
+
name: `${tenant.name} (${tenant.role})`,
|
|
208
|
+
value: tenant
|
|
209
|
+
}))
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (selected.role.toLowerCase() === "viewer") {
|
|
213
|
+
throw new CliAccessError(ADMIN_ACCESS_MESSAGE, { guidance: ADMIN_ACCESS_GUIDANCE });
|
|
214
|
+
}
|
|
215
|
+
let result;
|
|
216
|
+
let reused = false;
|
|
217
|
+
const storedKey = findStoredKeyForTenant(selected.id, authOptions);
|
|
218
|
+
if (storedKey && embeddedTenantId(storedKey) === selected.id && await isStoredKeyLive(storedKey, authOptions)) {
|
|
219
|
+
log(` Using existing CLI key for ${selected.name} (created earlier)`);
|
|
220
|
+
clearBootstrapIdempotencyKey(selected.id, authOptions);
|
|
221
|
+
reused = true;
|
|
222
|
+
result = {
|
|
223
|
+
apiKey: storedKey,
|
|
224
|
+
tenantId: selected.id,
|
|
225
|
+
tenantName: selected.name,
|
|
226
|
+
tenantSlug: selected.slug
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (!result) {
|
|
230
|
+
const idempotencyKey = reserveBootstrapIdempotencyKey(selected.id, authOptions);
|
|
231
|
+
const bootstrapRes = await authFetch("/api/cli/bootstrap", {
|
|
232
|
+
method: "POST",
|
|
233
|
+
headers: {
|
|
234
|
+
"Content-Type": "application/json",
|
|
235
|
+
"Idempotency-Key": idempotencyKey
|
|
236
|
+
},
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
tenantId: selected.id,
|
|
239
|
+
label: bootstrapLabel(options.label)
|
|
240
|
+
})
|
|
241
|
+
}, authOptions);
|
|
242
|
+
if (bootstrapRes.status === 403) {
|
|
243
|
+
throw new CliAccessError(ADMIN_ACCESS_MESSAGE, { guidance: ADMIN_ACCESS_GUIDANCE });
|
|
244
|
+
}
|
|
245
|
+
if (bootstrapRes.status === 409) {
|
|
246
|
+
const body = await bootstrapRes.text();
|
|
247
|
+
let parsed;
|
|
248
|
+
try {
|
|
249
|
+
parsed = JSON.parse(body);
|
|
250
|
+
} catch {}
|
|
251
|
+
if (parsed?.error === "cli_key_cap_reached") {
|
|
252
|
+
throw new CliAccessError(parsed.message || "This organization has reached its CLI API key limit.", {
|
|
253
|
+
guidance: "Ask your organization admin to raise the CLI key limit in the dashboard " + '(Settings → API Keys → CLI key limit), or revoke unused "CLI - ..." keys, ' + "or reuse the existing key already saved in ~/.moda/config.json from a previous " + "moda init / moda provision --save, then retry."
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
throw new Error(`Failed to create API key (409): ${body}`);
|
|
257
|
+
}
|
|
258
|
+
if (!bootstrapRes.ok) {
|
|
259
|
+
const body = await bootstrapRes.text();
|
|
260
|
+
throw new Error(formatControlPlaneFetchError("create API key", bootstrapRes.status, body));
|
|
261
|
+
}
|
|
262
|
+
result = await bootstrapRes.json();
|
|
263
|
+
clearBootstrapIdempotencyKey(selected.id, authOptions);
|
|
264
|
+
await verifyFreshKeyLive(result.apiKey, authOptions, options.validateRetry);
|
|
265
|
+
}
|
|
266
|
+
if (options.persistConfig !== false) {
|
|
267
|
+
saveCliConfig({
|
|
268
|
+
api_key: result.apiKey,
|
|
269
|
+
tenant_id: result.tenantId,
|
|
270
|
+
ingest_url: resolveIngestUrl(authOptions)
|
|
271
|
+
}, authOptions);
|
|
272
|
+
const envTenantId = (options.env ?? process.env).MODA_TENANT_ID;
|
|
273
|
+
if (envTenantId && options.tenantIdSource !== "env" && envTenantId !== result.tenantId) {
|
|
274
|
+
console.error(` Warning: MODA_TENANT_ID is set to a different organization id and will shadow the saved selection (${result.tenantId}) for later commands. Unset it or set it to the selected organization id.`);
|
|
275
|
+
}
|
|
276
|
+
log(` API key + tenant saved to ~/.moda/config.json
|
|
277
|
+
`);
|
|
278
|
+
log(` (Set MODA_API_KEY / MODA_TENANT_ID to override for scripts/CI.)
|
|
279
|
+
`);
|
|
280
|
+
}
|
|
281
|
+
return { ...result, reused };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export { selectTenantAndCreateKey };
|