@metabase/rde 0.1.3-alpha.rde-fixes.59b956e → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -69
- package/dist/cli.mjs +377 -1114
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { defineCommand, runMain, showUsage } from "citty";
|
|
3
3
|
import { createWriteStream, promises } from "node:fs";
|
|
4
|
-
import { delimiter, dirname,
|
|
4
|
+
import { delimiter, dirname, join, resolve } from "node:path";
|
|
5
5
|
import { ZodError, z } from "zod";
|
|
6
6
|
import { homedir, tmpdir, userInfo } from "node:os";
|
|
7
7
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -11,7 +11,6 @@ import { confirm, intro, isCancel, log, multiselect, note, outro, password, sele
|
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
12
12
|
import { connect, createServer } from "node:net";
|
|
13
13
|
import semver from "semver";
|
|
14
|
-
import { randomBytes } from "node:crypto";
|
|
15
14
|
//#region src/errors.ts
|
|
16
15
|
var AbortError = class extends Error {
|
|
17
16
|
constructor(message = "interrupted") {
|
|
@@ -57,10 +56,6 @@ var MetabaseApiError = class extends Error {
|
|
|
57
56
|
this.errors = failure.errors;
|
|
58
57
|
}
|
|
59
58
|
};
|
|
60
|
-
const UNAUTHORIZED_STATUS = 401;
|
|
61
|
-
function isRejectedCredential(error) {
|
|
62
|
-
return error instanceof MetabaseApiError && error.status === UNAUTHORIZED_STATUS;
|
|
63
|
-
}
|
|
64
59
|
var MetabaseInitializingError = class extends Error {
|
|
65
60
|
progress;
|
|
66
61
|
constructor(progress) {
|
|
@@ -122,17 +117,6 @@ function installInterruptHandler(exit) {
|
|
|
122
117
|
setTimeout(() => exit(130), FORCED_EXIT_GRACE_MS).unref();
|
|
123
118
|
});
|
|
124
119
|
}
|
|
125
|
-
//#endregion
|
|
126
|
-
//#region src/output/print.ts
|
|
127
|
-
function printLines(lines) {
|
|
128
|
-
process.stdout.write(lines.join("\n") + "\n");
|
|
129
|
-
}
|
|
130
|
-
function stdoutIsTty() {
|
|
131
|
-
return process.stdout.isTTY === true;
|
|
132
|
-
}
|
|
133
|
-
function printJson(value) {
|
|
134
|
-
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
135
|
-
}
|
|
136
120
|
const StepId = z.enum([
|
|
137
121
|
"preflight",
|
|
138
122
|
"install-mb",
|
|
@@ -173,36 +157,14 @@ const JarRuntimeState = z.object({
|
|
|
173
157
|
});
|
|
174
158
|
const RuntimeState = z.discriminatedUnion("kind", [DockerRuntimeState, JarRuntimeState]);
|
|
175
159
|
const RuntimeKind = z.enum(["docker", "jar"]);
|
|
176
|
-
const AdminCredentials = z.discriminatedUnion("kind", [
|
|
177
|
-
z.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
z.object({
|
|
182
|
-
kind: z.literal("prompted"),
|
|
183
|
-
email: z.string()
|
|
184
|
-
}),
|
|
185
|
-
z.object({
|
|
186
|
-
kind: z.literal("legacy-default"),
|
|
187
|
-
email: z.string()
|
|
188
|
-
})
|
|
189
|
-
]);
|
|
190
|
-
const AdminLogin = z.object({
|
|
160
|
+
const AdminCredentials = z.discriminatedUnion("kind", [z.object({
|
|
161
|
+
kind: z.literal("prompted"),
|
|
162
|
+
email: z.string()
|
|
163
|
+
}), z.object({
|
|
164
|
+
kind: z.literal("default"),
|
|
191
165
|
email: z.string(),
|
|
192
166
|
password: z.string()
|
|
193
|
-
});
|
|
194
|
-
const InstanceSecrets = z.object({
|
|
195
|
-
schemaVersion: z.literal(1),
|
|
196
|
-
admin: AdminLogin.nullable(),
|
|
197
|
-
apiKey: z.string().nullable()
|
|
198
|
-
});
|
|
199
|
-
function emptyInstanceSecrets() {
|
|
200
|
-
return {
|
|
201
|
-
schemaVersion: 1,
|
|
202
|
-
admin: null,
|
|
203
|
-
apiKey: null
|
|
204
|
-
};
|
|
205
|
-
}
|
|
167
|
+
})]);
|
|
206
168
|
const LicenseState = z.discriminatedUnion("activated", [z.object({ activated: z.literal(false) }), z.object({
|
|
207
169
|
activated: z.literal(true),
|
|
208
170
|
features: z.array(z.string()),
|
|
@@ -240,12 +202,13 @@ const StepRecord = z.object({
|
|
|
240
202
|
completedAt: z.string()
|
|
241
203
|
});
|
|
242
204
|
const InstanceState = z.object({
|
|
243
|
-
schemaVersion: z.literal(
|
|
205
|
+
schemaVersion: z.literal(3),
|
|
244
206
|
name: z.string(),
|
|
245
207
|
runtime: RuntimeState.nullable(),
|
|
246
208
|
url: z.string().nullable(),
|
|
247
209
|
version: z.string().nullable(),
|
|
248
210
|
admin: AdminCredentials.nullable(),
|
|
211
|
+
apiKey: z.string().nullable(),
|
|
249
212
|
mbProfile: z.string(),
|
|
250
213
|
license: LicenseState,
|
|
251
214
|
databases: z.array(DatabaseRecord),
|
|
@@ -267,22 +230,20 @@ function requireConfiguredRuntime(state) {
|
|
|
267
230
|
}
|
|
268
231
|
const DEFAULT_INSTANCE_NAME = "default";
|
|
269
232
|
const DEFAULT_MB_PROFILE = "default";
|
|
270
|
-
const
|
|
271
|
-
function mbProfileFor(instance) {
|
|
272
|
-
return instance === "default" ? RDE_MB_PROFILE : `${RDE_MB_PROFILE}-${instance}`;
|
|
273
|
-
}
|
|
233
|
+
const MbProfileChoice = z.enum([DEFAULT_MB_PROFILE, "rde"]);
|
|
274
234
|
function mbCommand(profile) {
|
|
275
235
|
return profile === "default" ? "mb" : `mb --profile ${profile}`;
|
|
276
236
|
}
|
|
277
237
|
function emptyInstanceState(name) {
|
|
278
238
|
return {
|
|
279
|
-
schemaVersion:
|
|
239
|
+
schemaVersion: 3,
|
|
280
240
|
name,
|
|
281
241
|
runtime: null,
|
|
282
242
|
url: null,
|
|
283
243
|
version: null,
|
|
284
244
|
admin: null,
|
|
285
|
-
|
|
245
|
+
apiKey: null,
|
|
246
|
+
mbProfile: DEFAULT_MB_PROFILE,
|
|
286
247
|
license: { activated: false },
|
|
287
248
|
databases: [],
|
|
288
249
|
sampleDatabase: null,
|
|
@@ -295,22 +256,8 @@ function emptyInstanceState(name) {
|
|
|
295
256
|
const Versioned = z.object({ schemaVersion: z.int() });
|
|
296
257
|
const V1 = 1;
|
|
297
258
|
const V2 = 2;
|
|
298
|
-
const V3 = 3;
|
|
299
259
|
const StateV1 = z.object({ schemaVersion: z.literal(V1) }).loose();
|
|
300
260
|
const StateV2 = z.object({ schemaVersion: z.literal(V2) }).loose();
|
|
301
|
-
const AdminV3 = z.discriminatedUnion("kind", [z.object({
|
|
302
|
-
kind: z.literal("prompted"),
|
|
303
|
-
email: z.string()
|
|
304
|
-
}), z.object({
|
|
305
|
-
kind: z.literal("default"),
|
|
306
|
-
email: z.string(),
|
|
307
|
-
password: z.string()
|
|
308
|
-
})]);
|
|
309
|
-
const StateV3 = z.object({
|
|
310
|
-
schemaVersion: z.literal(V3),
|
|
311
|
-
admin: AdminV3.nullable(),
|
|
312
|
-
apiKey: z.string().nullable()
|
|
313
|
-
}).loose();
|
|
314
261
|
function liftV1(raw) {
|
|
315
262
|
return {
|
|
316
263
|
...StateV1.parse(raw),
|
|
@@ -321,67 +268,27 @@ function liftV1(raw) {
|
|
|
321
268
|
function liftV2(raw) {
|
|
322
269
|
return {
|
|
323
270
|
...StateV2.parse(raw),
|
|
324
|
-
schemaVersion:
|
|
271
|
+
schemaVersion: 3,
|
|
325
272
|
sampleDatabase: null
|
|
326
273
|
};
|
|
327
274
|
}
|
|
328
|
-
function adminV4(admin) {
|
|
329
|
-
switch (admin.kind) {
|
|
330
|
-
case "default": return {
|
|
331
|
-
kind: "legacy-default",
|
|
332
|
-
email: admin.email
|
|
333
|
-
};
|
|
334
|
-
case "prompted": return {
|
|
335
|
-
kind: "prompted",
|
|
336
|
-
email: admin.email
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function liftV3(raw) {
|
|
341
|
-
const { admin, apiKey, ...rest } = StateV3.parse(raw);
|
|
342
|
-
const login = admin?.kind === "default" ? {
|
|
343
|
-
email: admin.email,
|
|
344
|
-
password: admin.password
|
|
345
|
-
} : null;
|
|
346
|
-
return {
|
|
347
|
-
state: InstanceState.parse({
|
|
348
|
-
...rest,
|
|
349
|
-
schemaVersion: 4,
|
|
350
|
-
admin: admin === null ? null : adminV4(admin)
|
|
351
|
-
}),
|
|
352
|
-
movedSecrets: {
|
|
353
|
-
schemaVersion: 1,
|
|
354
|
-
admin: login,
|
|
355
|
-
apiKey
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
275
|
function migrateInstanceState(raw) {
|
|
360
276
|
const { schemaVersion } = Versioned.parse(raw);
|
|
361
277
|
switch (schemaVersion) {
|
|
362
278
|
case V1: return migrateInstanceState(liftV1(raw));
|
|
363
279
|
case V2: return migrateInstanceState(liftV2(raw));
|
|
364
|
-
case
|
|
365
|
-
|
|
366
|
-
state: InstanceState.parse(raw),
|
|
367
|
-
movedSecrets: null
|
|
368
|
-
};
|
|
369
|
-
default: throw new ConfigError(`state schemaVersion ${schemaVersion} is not supported by this build (expected 4)`);
|
|
280
|
+
case 3: return InstanceState.parse(raw);
|
|
281
|
+
default: throw new ConfigError(`state schemaVersion ${schemaVersion} is not supported by this build (expected 3)`);
|
|
370
282
|
}
|
|
371
283
|
}
|
|
372
284
|
//#endregion
|
|
373
285
|
//#region src/env.ts
|
|
374
286
|
const ENV_RDE_HOME = "RDE_HOME";
|
|
375
|
-
const ENV_RDE_SKILL_SOURCE = "RDE_SKILL_SOURCE";
|
|
376
287
|
function readEnv(name) {
|
|
377
288
|
return process.env[name];
|
|
378
289
|
}
|
|
379
290
|
const ENV_PATH = "PATH";
|
|
380
|
-
const ENV_XDG_STATE_HOME = "XDG_STATE_HOME";
|
|
381
291
|
const ENV_JAVA_HOME = "JAVA_HOME";
|
|
382
|
-
const ENV_MB_PROFILE = "MB_PROFILE";
|
|
383
|
-
const ENV_MB_URL = "MB_URL";
|
|
384
|
-
const ENV_MB_API_KEY = "MB_API_KEY";
|
|
385
292
|
function environmentWith(overrides) {
|
|
386
293
|
return {
|
|
387
294
|
...process.env,
|
|
@@ -395,7 +302,6 @@ const INSTANCES_DIR = "instances";
|
|
|
395
302
|
const DATA_DIR = "data";
|
|
396
303
|
const BACKUPS_DIR = "backups";
|
|
397
304
|
const STATE_FILE = "state.json";
|
|
398
|
-
const SECRETS_FILE = "secrets.json";
|
|
399
305
|
const GLOBAL_CONFIG_FILE = "config.json";
|
|
400
306
|
const JARS_DIR = "jars";
|
|
401
307
|
const JAR_FILE$1 = "metabase.jar";
|
|
@@ -416,17 +322,8 @@ function instanceDataDir(name) {
|
|
|
416
322
|
function instanceBackupsDir(name) {
|
|
417
323
|
return join(instanceDir(name), BACKUPS_DIR);
|
|
418
324
|
}
|
|
419
|
-
function stateFileIn(dir) {
|
|
420
|
-
return join(dir, STATE_FILE);
|
|
421
|
-
}
|
|
422
|
-
function secretsFileIn(dir) {
|
|
423
|
-
return join(dir, SECRETS_FILE);
|
|
424
|
-
}
|
|
425
325
|
function instanceStatePath(name) {
|
|
426
|
-
return
|
|
427
|
-
}
|
|
428
|
-
function instanceSecretsPath(name) {
|
|
429
|
-
return secretsFileIn(instanceDir(name));
|
|
326
|
+
return join(instanceDir(name), STATE_FILE);
|
|
430
327
|
}
|
|
431
328
|
function globalConfigPath() {
|
|
432
329
|
return join(rdeHome(), GLOBAL_CONFIG_FILE);
|
|
@@ -440,21 +337,21 @@ function jarReleaseDir(version) {
|
|
|
440
337
|
function releaseJarIn(jarsDirectory, version) {
|
|
441
338
|
return join(jarsDirectory, version, JAR_FILE$1);
|
|
442
339
|
}
|
|
443
|
-
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/state/store.ts
|
|
342
|
+
const FILE_MODE$1 = 384;
|
|
343
|
+
const DIR_MODE = 448;
|
|
444
344
|
function isFileNotFound$1(error) {
|
|
445
345
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
446
346
|
}
|
|
447
|
-
async function
|
|
347
|
+
async function readJsonFile(path) {
|
|
348
|
+
let raw;
|
|
448
349
|
try {
|
|
449
|
-
|
|
350
|
+
raw = await promises.readFile(path, "utf8");
|
|
450
351
|
} catch (error) {
|
|
451
352
|
if (isFileNotFound$1(error)) return null;
|
|
452
353
|
throw error;
|
|
453
354
|
}
|
|
454
|
-
}
|
|
455
|
-
async function readJsonFile(path) {
|
|
456
|
-
const raw = await readTextFile(path);
|
|
457
|
-
if (raw === null) return null;
|
|
458
355
|
try {
|
|
459
356
|
return JSON.parse(raw);
|
|
460
357
|
} catch (error) {
|
|
@@ -473,11 +370,11 @@ function parseAt(path, parse) {
|
|
|
473
370
|
async function writeJsonFile(path, value) {
|
|
474
371
|
await promises.mkdir(dirname(path), {
|
|
475
372
|
recursive: true,
|
|
476
|
-
mode:
|
|
373
|
+
mode: DIR_MODE
|
|
477
374
|
});
|
|
478
375
|
const tmpPath = `${path}.${process.pid}.tmp`;
|
|
479
|
-
await promises.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", { mode:
|
|
480
|
-
await promises.chmod(tmpPath,
|
|
376
|
+
await promises.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", { mode: FILE_MODE$1 });
|
|
377
|
+
await promises.chmod(tmpPath, FILE_MODE$1);
|
|
481
378
|
try {
|
|
482
379
|
await promises.rename(tmpPath, path);
|
|
483
380
|
} catch (error) {
|
|
@@ -489,12 +386,7 @@ async function readInstanceState(name) {
|
|
|
489
386
|
const path = instanceStatePath(name);
|
|
490
387
|
const raw = await readJsonFile(path);
|
|
491
388
|
if (raw === null) return null;
|
|
492
|
-
|
|
493
|
-
if (migrated.movedSecrets !== null) {
|
|
494
|
-
await writeInstanceSecrets(name, migrated.movedSecrets);
|
|
495
|
-
await writeInstanceState(migrated.state);
|
|
496
|
-
}
|
|
497
|
-
return migrated.state;
|
|
389
|
+
return parseAt(path, () => migrateInstanceState(raw));
|
|
498
390
|
}
|
|
499
391
|
async function writeInstanceState(state) {
|
|
500
392
|
await writeJsonFile(instanceStatePath(state.name), state);
|
|
@@ -504,35 +396,6 @@ async function requireInstanceState(name) {
|
|
|
504
396
|
if (state === null) throw new ConfigError(`no instance named ${name}; run \`rde init\` first`);
|
|
505
397
|
return state;
|
|
506
398
|
}
|
|
507
|
-
async function readInstanceSecrets(name) {
|
|
508
|
-
const path = instanceSecretsPath(name);
|
|
509
|
-
const raw = await readJsonFile(path);
|
|
510
|
-
if (raw === null) return null;
|
|
511
|
-
return parseAt(path, () => InstanceSecrets.parse(raw));
|
|
512
|
-
}
|
|
513
|
-
async function writeInstanceSecrets(name, secrets) {
|
|
514
|
-
await writeJsonFile(instanceSecretsPath(name), secrets);
|
|
515
|
-
await promises.chmod(instanceDir(name), 448);
|
|
516
|
-
}
|
|
517
|
-
async function modeOf(path) {
|
|
518
|
-
try {
|
|
519
|
-
return (await promises.stat(path)).mode & PERMISSION_BITS;
|
|
520
|
-
} catch (error) {
|
|
521
|
-
if (isFileNotFound$1(error)) return null;
|
|
522
|
-
throw error;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
const StateWithApiKey = z.object({ apiKey: z.string() }).loose();
|
|
526
|
-
const StateWithPassword = z.object({ admin: z.object({ password: z.string() }).loose() }).loose();
|
|
527
|
-
async function inspectCredentialFiles(dir) {
|
|
528
|
-
const raw = await readJsonFile(stateFileIn(dir));
|
|
529
|
-
const secretsInState = [...StateWithApiKey.safeParse(raw).success ? ["apiKey"] : [], ...StateWithPassword.safeParse(raw).success ? ["admin.password"] : []];
|
|
530
|
-
return {
|
|
531
|
-
dirMode: await modeOf(dir),
|
|
532
|
-
secretsMode: await modeOf(secretsFileIn(dir)),
|
|
533
|
-
secretsInState
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
399
|
async function readGlobalConfig() {
|
|
537
400
|
const path = globalConfigPath();
|
|
538
401
|
const raw = await readJsonFile(path);
|
|
@@ -543,91 +406,6 @@ async function writeGlobalConfig(config) {
|
|
|
543
406
|
await writeJsonFile(globalConfigPath(), config);
|
|
544
407
|
}
|
|
545
408
|
//#endregion
|
|
546
|
-
//#region src/steps/credentials.ts
|
|
547
|
-
const TERMINAL_ONLY = "rde credentials prints only to a terminal; run it in your own terminal";
|
|
548
|
-
function storedPasswordLines(state, secrets, email) {
|
|
549
|
-
if (secrets === null || secrets.admin === null) throw new ConfigError(`the secrets file of instance ${state.name} holds no admin password; \`rde doctor\` says what to do`);
|
|
550
|
-
return [`Email: ${email}`, `Password: ${secrets.admin.password}`];
|
|
551
|
-
}
|
|
552
|
-
function credentialsLines(state, secrets) {
|
|
553
|
-
const admin = state.admin;
|
|
554
|
-
if (admin === null) throw new ConfigError(`instance ${state.name} has no admin user yet; run \`rde init\``);
|
|
555
|
-
const url = state.url === null ? [] : [`Metabase: ${state.url}`];
|
|
556
|
-
switch (admin.kind) {
|
|
557
|
-
case "generated": return [...url, ...storedPasswordLines(state, secrets, admin.email)];
|
|
558
|
-
case "legacy-default": return [
|
|
559
|
-
...url,
|
|
560
|
-
...storedPasswordLines(state, secrets, admin.email),
|
|
561
|
-
"This is the public default password; when the instance holds nothing you need, `rde uninstall` then `rde init` recreates it with a generated one"
|
|
562
|
-
];
|
|
563
|
-
case "prompted": return [
|
|
564
|
-
...url,
|
|
565
|
-
`Email: ${admin.email}`,
|
|
566
|
-
"Password: the one you entered during `rde init`; rde never stored it"
|
|
567
|
-
];
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
//#endregion
|
|
571
|
-
//#region src/commands/define.ts
|
|
572
|
-
function repeatedValues(rawArgs, flag) {
|
|
573
|
-
const values = [];
|
|
574
|
-
const inline = `${flag}=`;
|
|
575
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
576
|
-
const arg = rawArgs[index];
|
|
577
|
-
if (arg === void 0) continue;
|
|
578
|
-
if (arg.startsWith(inline)) values.push(arg.slice(inline.length));
|
|
579
|
-
else if (arg === flag) {
|
|
580
|
-
const next = rawArgs[index + 1];
|
|
581
|
-
if (next !== void 0) {
|
|
582
|
-
values.push(next);
|
|
583
|
-
index += 1;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return values;
|
|
588
|
-
}
|
|
589
|
-
function defineRdeCommand(def) {
|
|
590
|
-
return defineCommand({
|
|
591
|
-
meta: def.meta,
|
|
592
|
-
...def.args !== void 0 && { args: def.args },
|
|
593
|
-
async run({ args, rawArgs }) {
|
|
594
|
-
try {
|
|
595
|
-
await def.run(args, rawArgs);
|
|
596
|
-
} catch (error) {
|
|
597
|
-
reportError(error);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
//#endregion
|
|
603
|
-
//#region src/commands/instance.ts
|
|
604
|
-
async function defaultInstanceName() {
|
|
605
|
-
const config = await readGlobalConfig();
|
|
606
|
-
if (config === null) throw new ConfigError("no Metabase instance yet; run `rde init` first");
|
|
607
|
-
return config.defaultInstance;
|
|
608
|
-
}
|
|
609
|
-
async function instanceNameOrDefault() {
|
|
610
|
-
try {
|
|
611
|
-
return (await readGlobalConfig())?.defaultInstance ?? "default";
|
|
612
|
-
} catch (error) {
|
|
613
|
-
if (error instanceof ConfigError) return DEFAULT_INSTANCE_NAME;
|
|
614
|
-
throw error;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
//#endregion
|
|
618
|
-
//#region src/commands/credentials.ts
|
|
619
|
-
const credentials = defineRdeCommand({
|
|
620
|
-
meta: {
|
|
621
|
-
name: "credentials",
|
|
622
|
-
description: "Print the admin login of the instance; only to a terminal"
|
|
623
|
-
},
|
|
624
|
-
async run() {
|
|
625
|
-
if (!stdoutIsTty()) throw new ConfigError(TERMINAL_ONLY);
|
|
626
|
-
const name = await defaultInstanceName();
|
|
627
|
-
printLines(credentialsLines(await requireInstanceState(name), await readInstanceSecrets(name)));
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
//#endregion
|
|
631
409
|
//#region src/connections/parse-uri.ts
|
|
632
410
|
var UriParseError = class extends Error {
|
|
633
411
|
engine;
|
|
@@ -1294,18 +1072,6 @@ function tokenStatusResource(http) {
|
|
|
1294
1072
|
return { get };
|
|
1295
1073
|
}
|
|
1296
1074
|
//#endregion
|
|
1297
|
-
//#region src/metabase/domain/user.ts
|
|
1298
|
-
const CurrentUser = z.object({ id: z.int() }).loose();
|
|
1299
|
-
//#endregion
|
|
1300
|
-
//#region src/metabase/resources/user.ts
|
|
1301
|
-
function userResource(http) {
|
|
1302
|
-
/** GET /api/user/current */
|
|
1303
|
-
async function current() {
|
|
1304
|
-
return http.requestJson("GET", "/api/user/current", { schema: CurrentUser });
|
|
1305
|
-
}
|
|
1306
|
-
return { current };
|
|
1307
|
-
}
|
|
1308
|
-
//#endregion
|
|
1309
1075
|
//#region src/metabase/client.ts
|
|
1310
1076
|
const HEALTH_POLL_INTERVAL_MS = 2e3;
|
|
1311
1077
|
function createMetabaseClient(baseUrl, auth, signal, options = {}) {
|
|
@@ -1326,8 +1092,7 @@ function createMetabaseClient(baseUrl, auth, signal, options = {}) {
|
|
|
1326
1092
|
setting: settingResource(http),
|
|
1327
1093
|
tokenStatus: tokenStatusResource(http),
|
|
1328
1094
|
database: databaseResource(http),
|
|
1329
|
-
remoteSync: remoteSyncResource(http)
|
|
1330
|
-
user: userResource(http)
|
|
1095
|
+
remoteSync: remoteSyncResource(http)
|
|
1331
1096
|
};
|
|
1332
1097
|
}
|
|
1333
1098
|
function isNotReadyYet(error) {
|
|
@@ -2449,9 +2214,9 @@ function hostDatabasesDeps(signal) {
|
|
|
2449
2214
|
function isNotServingYet$1(error) {
|
|
2450
2215
|
return error instanceof MetabaseUnreachableError || error instanceof MetabaseInitializingError;
|
|
2451
2216
|
}
|
|
2452
|
-
function requireApiKey$2(state
|
|
2453
|
-
if (apiKey === null) throw new ConfigError(`instance ${state.name} has no API key yet; run \`rde init\``);
|
|
2454
|
-
return apiKey;
|
|
2217
|
+
function requireApiKey$2(state) {
|
|
2218
|
+
if (state.apiKey === null) throw new ConfigError(`instance ${state.name} has no API key yet; run \`rde init\``);
|
|
2219
|
+
return state.apiKey;
|
|
2455
2220
|
}
|
|
2456
2221
|
function hostAliasOf(runtime) {
|
|
2457
2222
|
switch (runtime.kind) {
|
|
@@ -2467,15 +2232,15 @@ function toRecord$1(database, createdAt) {
|
|
|
2467
2232
|
createdAt
|
|
2468
2233
|
};
|
|
2469
2234
|
}
|
|
2470
|
-
function authenticatedClient(state,
|
|
2235
|
+
function authenticatedClient(state, deps) {
|
|
2471
2236
|
const configured = requireConfiguredRuntime(state);
|
|
2472
2237
|
return deps.client(configured.url, {
|
|
2473
2238
|
kind: "api-key",
|
|
2474
|
-
key: requireApiKey$2(state
|
|
2239
|
+
key: requireApiKey$2(state)
|
|
2475
2240
|
});
|
|
2476
2241
|
}
|
|
2477
|
-
async function listDatabases(state,
|
|
2478
|
-
const listed = await authenticatedClient(state,
|
|
2242
|
+
async function listDatabases(state, deps) {
|
|
2243
|
+
const listed = await authenticatedClient(state, deps).database.list();
|
|
2479
2244
|
const records = new Map(state.databases.map((record) => [record.id, record]));
|
|
2480
2245
|
const onServer = listed.data.map((database) => ({
|
|
2481
2246
|
id: database.id,
|
|
@@ -2496,7 +2261,7 @@ async function listDatabases(state, apiKey, deps) {
|
|
|
2496
2261
|
}
|
|
2497
2262
|
async function addDatabases(ctx, deps) {
|
|
2498
2263
|
const configured = requireConfiguredRuntime(ctx.state);
|
|
2499
|
-
const client = authenticatedClient(ctx.state,
|
|
2264
|
+
const client = authenticatedClient(ctx.state, deps);
|
|
2500
2265
|
const properties = await client.session.properties();
|
|
2501
2266
|
const existing = await client.database.list();
|
|
2502
2267
|
if (existing.data.length > 0) ctx.ui.note(existingDatabaseLines(existing.data).join("\n"), "Connected databases");
|
|
@@ -2534,17 +2299,17 @@ function databasesStep(options, deps = hostDeps$12) {
|
|
|
2534
2299
|
title: "Connect your databases",
|
|
2535
2300
|
async detect(ctx) {
|
|
2536
2301
|
const configured = configuredRuntime(ctx.state);
|
|
2537
|
-
if (configured === null || ctx.
|
|
2302
|
+
if (configured === null || ctx.state.apiKey === null) return "todo";
|
|
2538
2303
|
const own = ownDatabaseIds(ctx.state);
|
|
2539
2304
|
if (own.size === 0) return sampleDatabaseId(ctx.state) !== null && !options.requested ? "done" : "todo";
|
|
2540
2305
|
const client = deps(ctx).client(configured.url, {
|
|
2541
2306
|
kind: "api-key",
|
|
2542
|
-
key: ctx.
|
|
2307
|
+
key: ctx.state.apiKey
|
|
2543
2308
|
});
|
|
2544
2309
|
try {
|
|
2545
2310
|
return (await client.database.list()).data.some((database) => own.has(database.id)) ? "done" : "todo";
|
|
2546
2311
|
} catch (error) {
|
|
2547
|
-
if (isNotServingYet$1(error)
|
|
2312
|
+
if (isNotServingYet$1(error)) return "todo";
|
|
2548
2313
|
throw error;
|
|
2549
2314
|
}
|
|
2550
2315
|
},
|
|
@@ -2626,11 +2391,6 @@ async function createWizardContext(instanceName, options) {
|
|
|
2626
2391
|
await writeInstanceState(next);
|
|
2627
2392
|
ctx.state = next;
|
|
2628
2393
|
},
|
|
2629
|
-
secrets: await readInstanceSecrets(instanceName) ?? emptyInstanceSecrets(),
|
|
2630
|
-
saveSecrets: async (next) => {
|
|
2631
|
-
await writeInstanceSecrets(instanceName, next);
|
|
2632
|
-
ctx.secrets = next;
|
|
2633
|
-
},
|
|
2634
2394
|
prompts: options.prompts ?? clackPrompts(),
|
|
2635
2395
|
ui: clackUi(),
|
|
2636
2396
|
signal: options.signal,
|
|
@@ -2639,6 +2399,53 @@ async function createWizardContext(instanceName, options) {
|
|
|
2639
2399
|
return ctx;
|
|
2640
2400
|
}
|
|
2641
2401
|
//#endregion
|
|
2402
|
+
//#region src/commands/define.ts
|
|
2403
|
+
function repeatedValues(rawArgs, flag) {
|
|
2404
|
+
const values = [];
|
|
2405
|
+
const inline = `${flag}=`;
|
|
2406
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
2407
|
+
const arg = rawArgs[index];
|
|
2408
|
+
if (arg === void 0) continue;
|
|
2409
|
+
if (arg.startsWith(inline)) values.push(arg.slice(inline.length));
|
|
2410
|
+
else if (arg === flag) {
|
|
2411
|
+
const next = rawArgs[index + 1];
|
|
2412
|
+
if (next !== void 0) {
|
|
2413
|
+
values.push(next);
|
|
2414
|
+
index += 1;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
return values;
|
|
2419
|
+
}
|
|
2420
|
+
function defineRdeCommand(def) {
|
|
2421
|
+
return defineCommand({
|
|
2422
|
+
meta: def.meta,
|
|
2423
|
+
...def.args !== void 0 && { args: def.args },
|
|
2424
|
+
async run({ args, rawArgs }) {
|
|
2425
|
+
try {
|
|
2426
|
+
await def.run(args, rawArgs);
|
|
2427
|
+
} catch (error) {
|
|
2428
|
+
reportError(error);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
//#endregion
|
|
2434
|
+
//#region src/commands/instance.ts
|
|
2435
|
+
async function defaultInstanceName() {
|
|
2436
|
+
const config = await readGlobalConfig();
|
|
2437
|
+
if (config === null) throw new ConfigError("no Metabase instance yet; run `rde init` first");
|
|
2438
|
+
return config.defaultInstance;
|
|
2439
|
+
}
|
|
2440
|
+
async function instanceNameOrDefault() {
|
|
2441
|
+
try {
|
|
2442
|
+
return (await readGlobalConfig())?.defaultInstance ?? "default";
|
|
2443
|
+
} catch (error) {
|
|
2444
|
+
if (error instanceof ConfigError) return DEFAULT_INSTANCE_NAME;
|
|
2445
|
+
throw error;
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
//#endregion
|
|
2642
2449
|
//#region src/commands/db/add.ts
|
|
2643
2450
|
const dbAdd = defineRdeCommand({
|
|
2644
2451
|
meta: {
|
|
@@ -2653,6 +2460,14 @@ const dbAdd = defineRdeCommand({
|
|
|
2653
2460
|
}
|
|
2654
2461
|
});
|
|
2655
2462
|
//#endregion
|
|
2463
|
+
//#region src/output/print.ts
|
|
2464
|
+
function printLines(lines) {
|
|
2465
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
2466
|
+
}
|
|
2467
|
+
function printJson(value) {
|
|
2468
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
2469
|
+
}
|
|
2470
|
+
//#endregion
|
|
2656
2471
|
//#region src/commands/db/list.ts
|
|
2657
2472
|
const dbList = defineRdeCommand({
|
|
2658
2473
|
meta: {
|
|
@@ -2665,10 +2480,7 @@ const dbList = defineRdeCommand({
|
|
|
2665
2480
|
default: false
|
|
2666
2481
|
} },
|
|
2667
2482
|
async run(args) {
|
|
2668
|
-
const
|
|
2669
|
-
const state = await requireInstanceState(name);
|
|
2670
|
-
const secrets = await readInstanceSecrets(name);
|
|
2671
|
-
const listing = await listDatabases(state, secrets === null ? null : secrets.apiKey, hostDatabasesDeps(interruptSignal));
|
|
2483
|
+
const listing = await listDatabases(await requireInstanceState(await defaultInstanceName()), hostDatabasesDeps(interruptSignal));
|
|
2672
2484
|
if (args.json) {
|
|
2673
2485
|
printJson(listing);
|
|
2674
2486
|
return;
|
|
@@ -2997,20 +2809,15 @@ async function isExecutable(path) {
|
|
|
2997
2809
|
return false;
|
|
2998
2810
|
}
|
|
2999
2811
|
}
|
|
3000
|
-
async function
|
|
2812
|
+
async function whichCommand(name) {
|
|
3001
2813
|
const searchPath = readEnv(ENV_PATH);
|
|
3002
|
-
if (searchPath === void 0) return
|
|
3003
|
-
const
|
|
3004
|
-
for (const dir of new Set(searchPath.split(delimiter))) {
|
|
2814
|
+
if (searchPath === void 0) return null;
|
|
2815
|
+
for (const dir of searchPath.split(delimiter)) {
|
|
3005
2816
|
if (dir.length === 0) continue;
|
|
3006
2817
|
const candidate = join(dir, name);
|
|
3007
|
-
if (await isExecutable(candidate))
|
|
2818
|
+
if (await isExecutable(candidate)) return candidate;
|
|
3008
2819
|
}
|
|
3009
|
-
return
|
|
3010
|
-
}
|
|
3011
|
-
async function whichCommand(name) {
|
|
3012
|
-
const [first] = await whichAll(name);
|
|
3013
|
-
return first ?? null;
|
|
2820
|
+
return null;
|
|
3014
2821
|
}
|
|
3015
2822
|
//#endregion
|
|
3016
2823
|
//#region src/tools/java.ts
|
|
@@ -3854,117 +3661,44 @@ const GLOBAL_INSTALL = {
|
|
|
3854
3661
|
function mbInstallCommand(packageManager) {
|
|
3855
3662
|
return GLOBAL_INSTALL[packageManager];
|
|
3856
3663
|
}
|
|
3857
|
-
function ownerByMarker(path, markers) {
|
|
3858
|
-
const normalized = path.replaceAll("\\", "/");
|
|
3859
|
-
const found = markers.find(({ marker }) => normalized.includes(marker));
|
|
3860
|
-
return found === void 0 ? null : found.owner;
|
|
3861
|
-
}
|
|
3862
3664
|
const GLOBAL_MARKERS = [
|
|
3863
3665
|
{
|
|
3864
3666
|
marker: "/.bun/install/global/node_modules/",
|
|
3865
|
-
|
|
3667
|
+
packageManager: "bun"
|
|
3866
3668
|
},
|
|
3867
3669
|
{
|
|
3868
3670
|
marker: "/Library/pnpm/global/",
|
|
3869
|
-
|
|
3671
|
+
packageManager: "pnpm"
|
|
3870
3672
|
},
|
|
3871
3673
|
{
|
|
3872
3674
|
marker: "/.local/share/pnpm/global/",
|
|
3873
|
-
|
|
3675
|
+
packageManager: "pnpm"
|
|
3874
3676
|
},
|
|
3875
3677
|
{
|
|
3876
3678
|
marker: "/share/pnpm/global/",
|
|
3877
|
-
|
|
3679
|
+
packageManager: "pnpm"
|
|
3878
3680
|
},
|
|
3879
3681
|
{
|
|
3880
3682
|
marker: "/pnpm-global/node_modules/",
|
|
3881
|
-
|
|
3683
|
+
packageManager: "pnpm"
|
|
3882
3684
|
},
|
|
3883
3685
|
{
|
|
3884
3686
|
marker: "/.config/yarn/global/node_modules/",
|
|
3885
|
-
|
|
3687
|
+
packageManager: "yarn"
|
|
3886
3688
|
},
|
|
3887
3689
|
{
|
|
3888
3690
|
marker: "/lib/node_modules/",
|
|
3889
|
-
|
|
3691
|
+
packageManager: "npm"
|
|
3890
3692
|
},
|
|
3891
3693
|
{
|
|
3892
3694
|
marker: "/.npm-global/node_modules/",
|
|
3893
|
-
|
|
3695
|
+
packageManager: "npm"
|
|
3894
3696
|
}
|
|
3895
3697
|
];
|
|
3896
3698
|
function packageManagerFor(scriptPath) {
|
|
3897
|
-
|
|
3898
|
-
}
|
|
3899
|
-
|
|
3900
|
-
npm: "npm",
|
|
3901
|
-
pnpm: "pnpm",
|
|
3902
|
-
yarn: "Yarn",
|
|
3903
|
-
bun: "Bun",
|
|
3904
|
-
homebrew: "Homebrew"
|
|
3905
|
-
};
|
|
3906
|
-
const CELLAR = "/Cellar/";
|
|
3907
|
-
const NODE_MODULES = "/node_modules/";
|
|
3908
|
-
const OWNER_MARKERS = [
|
|
3909
|
-
...GLOBAL_MARKERS,
|
|
3910
|
-
{
|
|
3911
|
-
marker: CELLAR,
|
|
3912
|
-
owner: "homebrew"
|
|
3913
|
-
},
|
|
3914
|
-
{
|
|
3915
|
-
marker: "/Library/pnpm/",
|
|
3916
|
-
owner: "pnpm"
|
|
3917
|
-
},
|
|
3918
|
-
{
|
|
3919
|
-
marker: "/.local/share/pnpm/",
|
|
3920
|
-
owner: "pnpm"
|
|
3921
|
-
}
|
|
3922
|
-
];
|
|
3923
|
-
async function locateMbOnPath() {
|
|
3924
|
-
const paths = await whichAll(MB);
|
|
3925
|
-
return Promise.all(paths.map(async (path) => ({
|
|
3926
|
-
path,
|
|
3927
|
-
resolved: await promises.realpath(path)
|
|
3928
|
-
})));
|
|
3929
|
-
}
|
|
3930
|
-
function mbOwner(binary) {
|
|
3931
|
-
return ownerByMarker(binary.resolved, OWNER_MARKERS) ?? ownerByMarker(binary.path, OWNER_MARKERS);
|
|
3932
|
-
}
|
|
3933
|
-
function firstSegmentAfter(path, marker) {
|
|
3934
|
-
const at = path.lastIndexOf(marker);
|
|
3935
|
-
if (at === -1) return null;
|
|
3936
|
-
const [first] = path.slice(at + marker.length).split("/");
|
|
3937
|
-
return first === void 0 || first.length === 0 ? null : first;
|
|
3938
|
-
}
|
|
3939
|
-
function packageName(resolved) {
|
|
3940
|
-
const scopeOrName = firstSegmentAfter(resolved, NODE_MODULES);
|
|
3941
|
-
if (scopeOrName === null || !scopeOrName.startsWith("@")) return scopeOrName;
|
|
3942
|
-
const name = firstSegmentAfter(resolved, `${NODE_MODULES}${scopeOrName}/`);
|
|
3943
|
-
return name === null ? null : `${scopeOrName}/${name}`;
|
|
3944
|
-
}
|
|
3945
|
-
const REMOVE_COMMANDS = {
|
|
3946
|
-
npm: (name) => `npm uninstall -g ${name}`,
|
|
3947
|
-
pnpm: (name) => `pnpm remove -g ${name}`,
|
|
3948
|
-
yarn: (name) => `yarn global remove ${name}`,
|
|
3949
|
-
bun: (name) => `bun remove -g ${name}`,
|
|
3950
|
-
homebrew: (name) => `brew uninstall ${name}`
|
|
3951
|
-
};
|
|
3952
|
-
function mbRemoveCommand(binary) {
|
|
3953
|
-
const owner = mbOwner(binary);
|
|
3954
|
-
if (owner === null) return null;
|
|
3955
|
-
const name = owner === "homebrew" ? firstSegmentAfter(binary.resolved, CELLAR) : packageName(binary.resolved);
|
|
3956
|
-
return name === null ? null : REMOVE_COMMANDS[owner](name);
|
|
3957
|
-
}
|
|
3958
|
-
function setValue(name) {
|
|
3959
|
-
const value = readEnv(name);
|
|
3960
|
-
return value === void 0 || value.length === 0 ? null : value;
|
|
3961
|
-
}
|
|
3962
|
-
function mbEnvironment() {
|
|
3963
|
-
return {
|
|
3964
|
-
profile: setValue(ENV_MB_PROFILE),
|
|
3965
|
-
url: setValue(ENV_MB_URL),
|
|
3966
|
-
apiKeySet: setValue(ENV_MB_API_KEY) !== null
|
|
3967
|
-
};
|
|
3699
|
+
const normalized = scriptPath.replaceAll("\\", "/");
|
|
3700
|
+
for (const { marker, packageManager } of GLOBAL_MARKERS) if (normalized.includes(marker)) return packageManager;
|
|
3701
|
+
return "npm";
|
|
3968
3702
|
}
|
|
3969
3703
|
const ProbedUser = z.object({
|
|
3970
3704
|
id: z.int(),
|
|
@@ -4041,16 +3775,16 @@ function mbTool(runner) {
|
|
|
4041
3775
|
if (result.exitCode !== 0) throw toolFailure(MB, argv, result);
|
|
4042
3776
|
return parseJsonOutput(MB, argv, result.stdout, schema);
|
|
4043
3777
|
}
|
|
4044
|
-
async function version(
|
|
3778
|
+
async function version() {
|
|
4045
3779
|
const args = ["--version"];
|
|
4046
3780
|
let result;
|
|
4047
3781
|
try {
|
|
4048
|
-
result = await runner.run(
|
|
3782
|
+
result = await runner.run(MB, args);
|
|
4049
3783
|
} catch (error) {
|
|
4050
3784
|
if (error instanceof ProcessNotFoundError) return null;
|
|
4051
3785
|
throw error;
|
|
4052
3786
|
}
|
|
4053
|
-
if (result.exitCode !== 0) throw toolFailure(
|
|
3787
|
+
if (result.exitCode !== 0) throw toolFailure(MB, args, result);
|
|
4054
3788
|
return result.stdout.trim();
|
|
4055
3789
|
}
|
|
4056
3790
|
async function authLogin(options) {
|
|
@@ -4136,65 +3870,73 @@ function mbTool(runner) {
|
|
|
4136
3870
|
};
|
|
4137
3871
|
}
|
|
4138
3872
|
//#endregion
|
|
4139
|
-
//#region src/
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
function rdeSkillSource() {
|
|
4144
|
-
const override = readEnv(ENV_RDE_SKILL_SOURCE);
|
|
4145
|
-
return override === void 0 || override.length === 0 ? DEFAULT_RDE_SKILL_SOURCE : override;
|
|
4146
|
-
}
|
|
4147
|
-
const InstalledSkill = z.object({
|
|
4148
|
-
name: z.string(),
|
|
4149
|
-
path: z.string(),
|
|
4150
|
-
scope: z.string(),
|
|
4151
|
-
agents: z.array(z.string()),
|
|
4152
|
-
source: z.string().nullable()
|
|
4153
|
-
}).loose();
|
|
4154
|
-
const InstalledSkillList = z.array(InstalledSkill);
|
|
4155
|
-
const SkillLock = z.object({ skills: z.record(z.string(), z.object({ ref: z.string().nullish() }).loose()) }).loose();
|
|
4156
|
-
const GLOBAL_LOCK_FILE = ".skill-lock.json";
|
|
4157
|
-
const PROJECT_LOCK_FILE = "skills-lock.json";
|
|
4158
|
-
function globalSkillLockPath(home, stateHome) {
|
|
4159
|
-
return stateHome === null ? join(home, ".agents", GLOBAL_LOCK_FILE) : join(stateHome, "skills", GLOBAL_LOCK_FILE);
|
|
4160
|
-
}
|
|
4161
|
-
function projectSkillLockPath(project) {
|
|
4162
|
-
return join(project, PROJECT_LOCK_FILE);
|
|
3873
|
+
//#region src/wizard/engine.ts
|
|
3874
|
+
function requireKnownStep(steps, id, flag) {
|
|
3875
|
+
if (id === void 0) return;
|
|
3876
|
+
if (!steps.some((step) => step.id === id)) throw new ConfigError(`${flag} ${id} is not one of the steps: ${steps.map((step) => step.id).join(", ")}`);
|
|
4163
3877
|
}
|
|
4164
|
-
function
|
|
4165
|
-
const
|
|
4166
|
-
|
|
3878
|
+
async function detectAll(steps, ctx, options) {
|
|
3879
|
+
const detected = [];
|
|
3880
|
+
let fromHere = false;
|
|
3881
|
+
for (const step of steps) {
|
|
3882
|
+
if (step.id === options.from) fromHere = true;
|
|
3883
|
+
const forced = options.only !== void 0 ? step.id === options.only : fromHere;
|
|
3884
|
+
detected.push({
|
|
3885
|
+
step,
|
|
3886
|
+
status: forced ? "todo" : await step.detect(ctx),
|
|
3887
|
+
forced
|
|
3888
|
+
});
|
|
3889
|
+
}
|
|
3890
|
+
return detected;
|
|
4167
3891
|
}
|
|
4168
|
-
function
|
|
4169
|
-
if (
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
return parsed.data.skills[skill]?.ref ?? null;
|
|
3892
|
+
async function shouldRun(entry, ctx, options) {
|
|
3893
|
+
if (entry.forced) return true;
|
|
3894
|
+
if (options.only !== void 0 || entry.status === "done") return false;
|
|
3895
|
+
return await entry.step.detect(ctx) === "todo";
|
|
4173
3896
|
}
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
const GITHUB_URL = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+)(?:\/.*)?)?\/?$/;
|
|
4179
|
-
function skillOrigin(source, cwd) {
|
|
4180
|
-
const github = GITHUB_URL.exec(source);
|
|
4181
|
-
if (github !== null) {
|
|
4182
|
-
const [, owner, repo, ref] = github;
|
|
4183
|
-
return {
|
|
4184
|
-
kind: "github",
|
|
4185
|
-
repository: `${owner}/${repo}`,
|
|
4186
|
-
ref: ref ?? null
|
|
4187
|
-
};
|
|
4188
|
-
}
|
|
4189
|
-
if (isAbsolute(source) || source.startsWith(".")) return {
|
|
4190
|
-
kind: "local",
|
|
4191
|
-
path: resolve(cwd, source)
|
|
3897
|
+
function recordFor(outcome, at) {
|
|
3898
|
+
return {
|
|
3899
|
+
status: outcome === "completed" ? "done" : "skipped",
|
|
3900
|
+
completedAt: at.toISOString()
|
|
4192
3901
|
};
|
|
3902
|
+
}
|
|
3903
|
+
function withStepRecord(state, id, record) {
|
|
4193
3904
|
return {
|
|
4194
|
-
|
|
4195
|
-
|
|
3905
|
+
...state,
|
|
3906
|
+
steps: {
|
|
3907
|
+
...state.steps,
|
|
3908
|
+
[id]: record
|
|
3909
|
+
}
|
|
4196
3910
|
};
|
|
4197
3911
|
}
|
|
3912
|
+
async function runWizard(steps, ctx, options = {}) {
|
|
3913
|
+
requireKnownStep(steps, options.from, "--from");
|
|
3914
|
+
requireKnownStep(steps, options.only, "--only");
|
|
3915
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3916
|
+
const detected = await detectAll(steps, ctx, options);
|
|
3917
|
+
ctx.ui.checklist(detected.map(({ step, status }) => ({
|
|
3918
|
+
title: step.title,
|
|
3919
|
+
status
|
|
3920
|
+
})));
|
|
3921
|
+
for (const entry of detected) {
|
|
3922
|
+
if (!await shouldRun(entry, ctx, options)) continue;
|
|
3923
|
+
const outcome = await entry.step.run(ctx);
|
|
3924
|
+
await ctx.saveState(withStepRecord(ctx.state, entry.step.id, recordFor(outcome, now())));
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
//#endregion
|
|
3928
|
+
//#region src/tools/skills-cli.ts
|
|
3929
|
+
const NPX = "npx";
|
|
3930
|
+
const SKILLS_PACKAGE = "skills@latest";
|
|
3931
|
+
const RDE_SKILL_SOURCE = "https://github.com/metabase/agent-skills/tree/rde-generic-skills";
|
|
3932
|
+
const InstalledSkill = z.object({
|
|
3933
|
+
name: z.string(),
|
|
3934
|
+
path: z.string(),
|
|
3935
|
+
scope: z.string(),
|
|
3936
|
+
agents: z.array(z.string()),
|
|
3937
|
+
source: z.string().nullable()
|
|
3938
|
+
}).loose();
|
|
3939
|
+
const InstalledSkillList = z.array(InstalledSkill);
|
|
4198
3940
|
function skillsCli(runner) {
|
|
4199
3941
|
async function add(options) {
|
|
4200
3942
|
const argv = [
|
|
@@ -4246,82 +3988,12 @@ function skillsCli(runner) {
|
|
|
4246
3988
|
if (result.exitCode !== 0) throw toolFailure(NPX, argv, result);
|
|
4247
3989
|
return parseJsonOutput(NPX, argv, result.stdout, InstalledSkillList);
|
|
4248
3990
|
}
|
|
4249
|
-
async function listProject(cwd) {
|
|
4250
|
-
const argv = [
|
|
4251
|
-
"-y",
|
|
4252
|
-
SKILLS_PACKAGE,
|
|
4253
|
-
"list",
|
|
4254
|
-
"--json"
|
|
4255
|
-
];
|
|
4256
|
-
const result = await runner.run(NPX, argv, {
|
|
4257
|
-
cwd,
|
|
4258
|
-
stdoutToFile: true
|
|
4259
|
-
});
|
|
4260
|
-
if (result.exitCode !== 0) throw toolFailure(NPX, argv, result);
|
|
4261
|
-
return parseJsonOutput(NPX, argv, result.stdout, InstalledSkillList);
|
|
4262
|
-
}
|
|
4263
3991
|
return {
|
|
4264
3992
|
add,
|
|
4265
3993
|
remove,
|
|
4266
|
-
list
|
|
4267
|
-
listProject
|
|
4268
|
-
};
|
|
4269
|
-
}
|
|
4270
|
-
//#endregion
|
|
4271
|
-
//#region src/wizard/engine.ts
|
|
4272
|
-
function requireKnownStep(steps, id, flag) {
|
|
4273
|
-
if (id === void 0) return;
|
|
4274
|
-
if (!steps.some((step) => step.id === id)) throw new ConfigError(`${flag} ${id} is not one of the steps: ${steps.map((step) => step.id).join(", ")}`);
|
|
4275
|
-
}
|
|
4276
|
-
async function detectAll(steps, ctx, options) {
|
|
4277
|
-
const detected = [];
|
|
4278
|
-
let fromHere = false;
|
|
4279
|
-
for (const step of steps) {
|
|
4280
|
-
if (step.id === options.from) fromHere = true;
|
|
4281
|
-
const forced = options.only !== void 0 ? step.id === options.only : fromHere;
|
|
4282
|
-
detected.push({
|
|
4283
|
-
step,
|
|
4284
|
-
status: forced ? "todo" : await step.detect(ctx),
|
|
4285
|
-
forced
|
|
4286
|
-
});
|
|
4287
|
-
}
|
|
4288
|
-
return detected;
|
|
4289
|
-
}
|
|
4290
|
-
async function shouldRun(entry, ctx, options) {
|
|
4291
|
-
if (entry.forced) return true;
|
|
4292
|
-
if (options.only !== void 0 || entry.status === "done") return false;
|
|
4293
|
-
return await entry.step.detect(ctx) === "todo";
|
|
4294
|
-
}
|
|
4295
|
-
function recordFor(outcome, at) {
|
|
4296
|
-
return {
|
|
4297
|
-
status: outcome === "completed" ? "done" : "skipped",
|
|
4298
|
-
completedAt: at.toISOString()
|
|
4299
|
-
};
|
|
4300
|
-
}
|
|
4301
|
-
function withStepRecord(state, id, record) {
|
|
4302
|
-
return {
|
|
4303
|
-
...state,
|
|
4304
|
-
steps: {
|
|
4305
|
-
...state.steps,
|
|
4306
|
-
[id]: record
|
|
4307
|
-
}
|
|
3994
|
+
list
|
|
4308
3995
|
};
|
|
4309
3996
|
}
|
|
4310
|
-
async function runWizard(steps, ctx, options = {}) {
|
|
4311
|
-
requireKnownStep(steps, options.from, "--from");
|
|
4312
|
-
requireKnownStep(steps, options.only, "--only");
|
|
4313
|
-
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
4314
|
-
const detected = await detectAll(steps, ctx, options);
|
|
4315
|
-
ctx.ui.checklist(detected.map(({ step, status }) => ({
|
|
4316
|
-
title: step.title,
|
|
4317
|
-
status
|
|
4318
|
-
})));
|
|
4319
|
-
for (const entry of detected) {
|
|
4320
|
-
if (!await shouldRun(entry, ctx, options)) continue;
|
|
4321
|
-
const outcome = await entry.step.run(ctx);
|
|
4322
|
-
await ctx.saveState(withStepRecord(ctx.state, entry.step.id, recordFor(outcome, now())));
|
|
4323
|
-
}
|
|
4324
|
-
}
|
|
4325
3997
|
//#endregion
|
|
4326
3998
|
//#region src/steps/agents-skills.ts
|
|
4327
3999
|
const STEP_ID$8 = "agents-skills";
|
|
@@ -4330,8 +4002,7 @@ const DETECT_AGENTS = { agents: null };
|
|
|
4330
4002
|
function hostDeps$11(ctx) {
|
|
4331
4003
|
return {
|
|
4332
4004
|
runner: processRunner(ctx.signal),
|
|
4333
|
-
probe: hostAgentProbe()
|
|
4334
|
-
skillSource: rdeSkillSource()
|
|
4005
|
+
probe: hostAgentProbe()
|
|
4335
4006
|
};
|
|
4336
4007
|
}
|
|
4337
4008
|
function detectedOnly(agents) {
|
|
@@ -4391,10 +4062,9 @@ function agentsSkillsStep(options, deps = hostDeps$11) {
|
|
|
4391
4062
|
ctx.ui.info(skipLine(all, selection.source));
|
|
4392
4063
|
return "skipped";
|
|
4393
4064
|
}
|
|
4394
|
-
|
|
4395
|
-
ctx.ui.info(`Installing the rde skill for ${displayNames(selection.agents)} (${SOURCE_NOTES[selection.source]})${from}`);
|
|
4065
|
+
ctx.ui.info(`Installing the rde skill for ${displayNames(selection.agents)} (${SOURCE_NOTES[selection.source]})`);
|
|
4396
4066
|
await skillsCli(resolved.runner).add({
|
|
4397
|
-
source:
|
|
4067
|
+
source: RDE_SKILL_SOURCE,
|
|
4398
4068
|
skill: "rde",
|
|
4399
4069
|
agents: selection.agents.map((agent) => agent.skillsCliName)
|
|
4400
4070
|
});
|
|
@@ -4420,8 +4090,7 @@ function rdeInstalledWith() {
|
|
|
4420
4090
|
function hostDeps$10(ctx) {
|
|
4421
4091
|
return {
|
|
4422
4092
|
runner: processRunner(ctx.signal),
|
|
4423
|
-
installedWith: rdeInstalledWith()
|
|
4424
|
-
locate: locateMbOnPath
|
|
4093
|
+
installedWith: rdeInstalledWith()
|
|
4425
4094
|
};
|
|
4426
4095
|
}
|
|
4427
4096
|
async function inspectMb(runner) {
|
|
@@ -4438,100 +4107,14 @@ async function inspectMb(runner) {
|
|
|
4438
4107
|
function displayCommand(install) {
|
|
4439
4108
|
return [install.command, ...install.args].join(" ");
|
|
4440
4109
|
}
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
return raw === null ? null : semver.valid(raw);
|
|
4445
|
-
} catch (error) {
|
|
4446
|
-
if (error instanceof ExternalToolError) return null;
|
|
4447
|
-
throw error;
|
|
4448
|
-
}
|
|
4449
|
-
}
|
|
4450
|
-
async function inspectMbOnPath(runner, locate) {
|
|
4451
|
-
const found = [];
|
|
4452
|
-
for (const binary of await locate()) found.push({
|
|
4453
|
-
path: binary.path,
|
|
4454
|
-
version: await versionAt(runner, binary.path),
|
|
4455
|
-
owner: mbOwner(binary),
|
|
4456
|
-
removeCommand: mbRemoveCommand(binary)
|
|
4457
|
-
});
|
|
4458
|
-
return found;
|
|
4459
|
-
}
|
|
4460
|
-
function satisfiesMinimum(version) {
|
|
4461
|
-
return version !== null && semver.satisfies(version, MB_RANGE, { includePrerelease: true });
|
|
4462
|
-
}
|
|
4463
|
-
function isNewer(candidate, than) {
|
|
4464
|
-
if (candidate === null) return false;
|
|
4465
|
-
return than === null || semver.gt(candidate, than);
|
|
4466
|
-
}
|
|
4467
|
-
function diagnoseMb(found, installer) {
|
|
4468
|
-
const [running, ...later] = found;
|
|
4469
|
-
if (running === void 0) return { kind: "missing" };
|
|
4470
|
-
const sufficient = satisfiesMinimum(running.version);
|
|
4471
|
-
const newer = later.find((candidate) => satisfiesMinimum(candidate.version) && isNewer(candidate.version, running.version));
|
|
4472
|
-
if (newer !== void 0 && (!sufficient || running.owner !== installer)) return {
|
|
4473
|
-
kind: "shadowed",
|
|
4474
|
-
running,
|
|
4475
|
-
newer
|
|
4476
|
-
};
|
|
4477
|
-
return sufficient ? {
|
|
4478
|
-
kind: "current",
|
|
4479
|
-
running
|
|
4480
|
-
} : {
|
|
4481
|
-
kind: "old",
|
|
4482
|
-
running
|
|
4483
|
-
};
|
|
4484
|
-
}
|
|
4485
|
-
function runningMbSuffices(diagnosis) {
|
|
4486
|
-
return diagnosis.kind !== "missing" && satisfiesMinimum(diagnosis.running.version);
|
|
4487
|
-
}
|
|
4488
|
-
function versionText(entry) {
|
|
4489
|
-
return entry.version ?? "an unknown version";
|
|
4490
|
-
}
|
|
4491
|
-
function describeEntry(entry) {
|
|
4492
|
-
const owner = entry.owner === null ? "" : ` (${MB_OWNER_NAMES[entry.owner]})`;
|
|
4493
|
-
return `${versionText(entry)} at ${entry.path}${owner}`;
|
|
4494
|
-
}
|
|
4495
|
-
function mbPathLines(found) {
|
|
4496
|
-
return found.map((entry, index) => `${index === 0 ? "" : "then "}${describeEntry(entry)}`);
|
|
4497
|
-
}
|
|
4498
|
-
function removeClause(entry) {
|
|
4499
|
-
if (entry.removeCommand !== null) return `remove the old one with \`${entry.removeCommand}\``;
|
|
4500
|
-
if (entry.owner !== null) return `remove the old one with ${MB_OWNER_NAMES[entry.owner]}`;
|
|
4501
|
-
return `remove ${entry.path}`;
|
|
4502
|
-
}
|
|
4503
|
-
function shadowedHint(diagnosis) {
|
|
4504
|
-
const { running, newer } = diagnosis;
|
|
4505
|
-
return [
|
|
4506
|
-
`${running.path} (${versionText(running)}) runs before ${newer.path} (${versionText(newer)});`,
|
|
4507
|
-
`${removeClause(running)}, or put ${dirname(newer.path)} before ${dirname(running.path)} on PATH,`,
|
|
4508
|
-
"then open a new terminal"
|
|
4509
|
-
].join(" ");
|
|
4510
|
-
}
|
|
4511
|
-
const NEW_TERMINAL = "open a new terminal and run rde init again";
|
|
4512
|
-
function stillWrongAdvice(diagnosis) {
|
|
4513
|
-
switch (diagnosis.kind) {
|
|
4514
|
-
case "shadowed": return `${shadowedHint(diagnosis)} and run rde init again`;
|
|
4515
|
-
case "old": return `The install put no newer mb on PATH; ${removeClause(diagnosis.running)}, or ${NEW_TERMINAL}`;
|
|
4516
|
-
case "missing":
|
|
4517
|
-
case "current": return "Open a new terminal and run rde init again";
|
|
4518
|
-
}
|
|
4519
|
-
}
|
|
4520
|
-
async function stillWrong(install, after, deps) {
|
|
4521
|
-
const fail = (stderrTail) => new ExternalToolError({
|
|
4110
|
+
function stillWrong(install, after) {
|
|
4111
|
+
const found = after.installed ? `mb ${after.version} is still what is on PATH (${MIN_MB_VERSION} or newer is required)` : "mb is not on PATH after the install";
|
|
4112
|
+
return new ExternalToolError({
|
|
4522
4113
|
tool: install.command,
|
|
4523
4114
|
argv: install.args,
|
|
4524
4115
|
exitCode: 0,
|
|
4525
|
-
stderrTail
|
|
4116
|
+
stderrTail: `${found}; open a new terminal and run rde init again`
|
|
4526
4117
|
});
|
|
4527
|
-
if (!after.installed) return fail(`mb is not on PATH after the install; ${NEW_TERMINAL}`);
|
|
4528
|
-
const found = await inspectMbOnPath(deps.runner, deps.locate);
|
|
4529
|
-
const diagnosis = diagnoseMb(found, deps.installedWith);
|
|
4530
|
-
return fail([
|
|
4531
|
-
`mb ${after.version} still runs first (${MIN_MB_VERSION} or newer is required).`,
|
|
4532
|
-
`Every mb on PATH, in order: ${mbPathLines(found).join("; ")}.`,
|
|
4533
|
-
`${stillWrongAdvice(diagnosis)}.`
|
|
4534
|
-
].join(" "));
|
|
4535
4118
|
}
|
|
4536
4119
|
function installMbStep(deps = hostDeps$10) {
|
|
4537
4120
|
return {
|
|
@@ -4559,7 +4142,7 @@ function installMbStep(deps = hostDeps$10) {
|
|
|
4559
4142
|
stderrTail: ""
|
|
4560
4143
|
});
|
|
4561
4144
|
const after = await inspectMb(resolved.runner);
|
|
4562
|
-
if (!after.installed || !after.satisfies) throw
|
|
4145
|
+
if (!after.installed || !after.satisfies) throw stillWrong(install, after);
|
|
4563
4146
|
ctx.ui.success(`mb ${after.version} is installed`);
|
|
4564
4147
|
return "completed";
|
|
4565
4148
|
}
|
|
@@ -4880,15 +4463,7 @@ function hostDoctorDeps(instanceName, signal) {
|
|
|
4880
4463
|
runtime,
|
|
4881
4464
|
ports: hostPortProbe,
|
|
4882
4465
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, signal),
|
|
4883
|
-
probe: hostAgentProbe()
|
|
4884
|
-
locateMb: locateMbOnPath,
|
|
4885
|
-
environment: mbEnvironment(),
|
|
4886
|
-
skills: {
|
|
4887
|
-
source: rdeSkillSource(),
|
|
4888
|
-
cwd: process.cwd(),
|
|
4889
|
-
stateHome: xdgStateHome(),
|
|
4890
|
-
files: hostSkillFiles
|
|
4891
|
-
}
|
|
4466
|
+
probe: hostAgentProbe()
|
|
4892
4467
|
};
|
|
4893
4468
|
}
|
|
4894
4469
|
const NOT_FOUND_STATUS$2 = 404;
|
|
@@ -4901,17 +4476,7 @@ function ok(id, label, detail) {
|
|
|
4901
4476
|
status: "ok",
|
|
4902
4477
|
detail,
|
|
4903
4478
|
hint: null,
|
|
4904
|
-
|
|
4905
|
-
};
|
|
4906
|
-
}
|
|
4907
|
-
function note$1(id, label, detail, hint) {
|
|
4908
|
-
return {
|
|
4909
|
-
id,
|
|
4910
|
-
label,
|
|
4911
|
-
status: "ok",
|
|
4912
|
-
detail,
|
|
4913
|
-
hint,
|
|
4914
|
-
step: null
|
|
4479
|
+
fix: null
|
|
4915
4480
|
};
|
|
4916
4481
|
}
|
|
4917
4482
|
function skipped(id, label, detail) {
|
|
@@ -4921,7 +4486,7 @@ function skipped(id, label, detail) {
|
|
|
4921
4486
|
status: "skipped",
|
|
4922
4487
|
detail,
|
|
4923
4488
|
hint: null,
|
|
4924
|
-
|
|
4489
|
+
fix: null
|
|
4925
4490
|
};
|
|
4926
4491
|
}
|
|
4927
4492
|
function failed(id, label, failure) {
|
|
@@ -4942,7 +4507,7 @@ function machineRow(check, needed) {
|
|
|
4942
4507
|
return failed(check.id, check.id, {
|
|
4943
4508
|
detail: check.label,
|
|
4944
4509
|
hint: check.hint,
|
|
4945
|
-
|
|
4510
|
+
fix: "preflight"
|
|
4946
4511
|
});
|
|
4947
4512
|
}
|
|
4948
4513
|
async function machineRows(deps, runtime) {
|
|
@@ -4954,48 +4519,19 @@ function installHint(installedWith) {
|
|
|
4954
4519
|
const install = mbInstallCommand(installedWith);
|
|
4955
4520
|
return `\`${[install.command, ...install.args].join(" ")}\``;
|
|
4956
4521
|
}
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
if (
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
const required = lines.map((line, index) => index === 0 ? `${line}; ${MIN_MB_VERSION} or newer is required` : line);
|
|
4971
|
-
return failed("mb", MB_LABEL, {
|
|
4972
|
-
detail: required.join("\n"),
|
|
4973
|
-
hint: installHint(deps.installedWith),
|
|
4974
|
-
step: "install-mb"
|
|
4975
|
-
});
|
|
4976
|
-
}
|
|
4977
|
-
case "shadowed": return failed("mb", MB_LABEL, {
|
|
4978
|
-
detail: lines.join("\n"),
|
|
4979
|
-
hint: shadowedHint(diagnosis),
|
|
4980
|
-
step: null
|
|
4981
|
-
});
|
|
4982
|
-
case "current": return ok("mb", MB_LABEL, lines.join("\n"));
|
|
4983
|
-
}
|
|
4984
|
-
}
|
|
4985
|
-
async function mbFinding(deps) {
|
|
4986
|
-
const found = await inspectMbOnPath(deps.runner, deps.locateMb);
|
|
4987
|
-
const diagnosis = diagnoseMb(found, deps.installedWith);
|
|
4988
|
-
return {
|
|
4989
|
-
row: mbRow(diagnosis, mbPathLines(found), deps),
|
|
4990
|
-
unusable: mbUnusable(diagnosis)
|
|
4991
|
-
};
|
|
4992
|
-
}
|
|
4993
|
-
function sharedProfileList(deps) {
|
|
4994
|
-
let listed = null;
|
|
4995
|
-
return () => {
|
|
4996
|
-
listed ??= mbTool(deps.runner).authList();
|
|
4997
|
-
return listed;
|
|
4998
|
-
};
|
|
4522
|
+
async function mbRow(deps) {
|
|
4523
|
+
const found = await inspectMb(deps.runner);
|
|
4524
|
+
if (!found.installed) return failed("mb", "mb", {
|
|
4525
|
+
detail: "not installed",
|
|
4526
|
+
hint: installHint(deps.installedWith),
|
|
4527
|
+
fix: "install-mb"
|
|
4528
|
+
});
|
|
4529
|
+
if (!found.satisfies) return failed("mb", "mb", {
|
|
4530
|
+
detail: `${found.version} (${MIN_MB_VERSION} or newer is required)`,
|
|
4531
|
+
hint: installHint(deps.installedWith),
|
|
4532
|
+
fix: "install-mb"
|
|
4533
|
+
});
|
|
4534
|
+
return ok("mb", "mb", found.version);
|
|
4999
4535
|
}
|
|
5000
4536
|
function publishesOnLoopback(container, port) {
|
|
5001
4537
|
return container.ports.some((mapping) => mapping.host === port && mapping.hostIp === "127.0.0.1");
|
|
@@ -5013,7 +4549,7 @@ async function containerRow(runtime, deps, dockerOk) {
|
|
|
5013
4549
|
row: failed("container", label, {
|
|
5014
4550
|
detail: `${runtime.containerName} does not exist`,
|
|
5015
4551
|
hint: "`rde init` recreates it from the saved choices",
|
|
5016
|
-
|
|
4552
|
+
fix: "runtime"
|
|
5017
4553
|
}),
|
|
5018
4554
|
running: false,
|
|
5019
4555
|
holder: CONTAINER
|
|
@@ -5026,7 +4562,7 @@ async function containerRow(runtime, deps, dockerOk) {
|
|
|
5026
4562
|
row: failed("container", label, {
|
|
5027
4563
|
detail: `${runtime.containerName} runs ${inspection.image}, the state says ${runtime.image}`,
|
|
5028
4564
|
hint: `remove it with \`docker rm -f ${runtime.containerName}\` and run \`rde init\``,
|
|
5029
|
-
|
|
4565
|
+
fix: null
|
|
5030
4566
|
}),
|
|
5031
4567
|
...found
|
|
5032
4568
|
};
|
|
@@ -5034,7 +4570,7 @@ async function containerRow(runtime, deps, dockerOk) {
|
|
|
5034
4570
|
row: failed("container", label, {
|
|
5035
4571
|
detail: `${runtime.containerName} does not publish port ${runtime.port} on ${LOOPBACK_HOST}`,
|
|
5036
4572
|
hint: `remove it with \`docker rm -f ${runtime.containerName}\` and run \`rde init\``,
|
|
5037
|
-
|
|
4573
|
+
fix: null
|
|
5038
4574
|
}),
|
|
5039
4575
|
...found
|
|
5040
4576
|
};
|
|
@@ -5055,7 +4591,7 @@ async function processRow(runtime, deps) {
|
|
|
5055
4591
|
row: failed("process", label, {
|
|
5056
4592
|
detail: `${inspection.jarPath} does not exist`,
|
|
5057
4593
|
hint,
|
|
5058
|
-
|
|
4594
|
+
fix: "runtime"
|
|
5059
4595
|
}),
|
|
5060
4596
|
running: false,
|
|
5061
4597
|
holder: JAR_PROCESS
|
|
@@ -5082,13 +4618,13 @@ async function portRow(port, finding, ports) {
|
|
|
5082
4618
|
if (finding.running) return failed("port", "port", {
|
|
5083
4619
|
detail: `${port} is not answering although ${finding.holder} runs`,
|
|
5084
4620
|
hint: "`rde logs` shows what Metabase is doing",
|
|
5085
|
-
|
|
4621
|
+
fix: null
|
|
5086
4622
|
});
|
|
5087
4623
|
if (free) return ok("port", "port", `${port} is free`);
|
|
5088
4624
|
return failed("port", "port", {
|
|
5089
4625
|
detail: `${port} is in use by another program`,
|
|
5090
4626
|
hint: "stop that program before `rde start`",
|
|
5091
|
-
|
|
4627
|
+
fix: null
|
|
5092
4628
|
});
|
|
5093
4629
|
}
|
|
5094
4630
|
async function healthRow(url, deps) {
|
|
@@ -5103,7 +4639,7 @@ async function healthRow(url, deps) {
|
|
|
5103
4639
|
row: failed("health", "health", {
|
|
5104
4640
|
detail: `initializing (${Math.round(error.progress * 100)}%)`,
|
|
5105
4641
|
hint: "wait for it; `rde logs -f` shows progress",
|
|
5106
|
-
|
|
4642
|
+
fix: "start-metabase"
|
|
5107
4643
|
}),
|
|
5108
4644
|
healthy: false
|
|
5109
4645
|
};
|
|
@@ -5111,7 +4647,7 @@ async function healthRow(url, deps) {
|
|
|
5111
4647
|
row: failed("health", "health", {
|
|
5112
4648
|
detail: "unreachable",
|
|
5113
4649
|
hint: "`rde start`",
|
|
5114
|
-
|
|
4650
|
+
fix: "start-metabase"
|
|
5115
4651
|
}),
|
|
5116
4652
|
healthy: false
|
|
5117
4653
|
};
|
|
@@ -5119,7 +4655,7 @@ async function healthRow(url, deps) {
|
|
|
5119
4655
|
row: failed("health", "health", {
|
|
5120
4656
|
detail: `something other than Metabase answers at ${url} (HTTP ${error.status})`,
|
|
5121
4657
|
hint: "another program holds the port; stop it before `rde start`",
|
|
5122
|
-
|
|
4658
|
+
fix: null
|
|
5123
4659
|
}),
|
|
5124
4660
|
healthy: false
|
|
5125
4661
|
};
|
|
@@ -5130,7 +4666,7 @@ function adminRow(state, properties) {
|
|
|
5130
4666
|
if (!properties["has-user-setup"]) return failed("admin", "admin user", {
|
|
5131
4667
|
detail: "Metabase has no admin user yet",
|
|
5132
4668
|
hint: "`rde init --only admin-user`",
|
|
5133
|
-
|
|
4669
|
+
fix: "admin-user"
|
|
5134
4670
|
});
|
|
5135
4671
|
return ok("admin", "admin user", state.admin?.email ?? "created outside rde");
|
|
5136
4672
|
}
|
|
@@ -5142,204 +4678,36 @@ function versionRow(state, properties) {
|
|
|
5142
4678
|
if (state.version !== running) return failed("version", label, {
|
|
5143
4679
|
detail: `running ${running}, the state file says ${state.version}`,
|
|
5144
4680
|
hint: "`rde start` records the running version",
|
|
5145
|
-
|
|
4681
|
+
fix: VERSION_STEP
|
|
5146
4682
|
});
|
|
5147
4683
|
return ok("version", label, running);
|
|
5148
4684
|
}
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
const admin = state.admin;
|
|
5155
|
-
if (admin === null) return skipped(id, label, "no admin user created by rde yet");
|
|
5156
|
-
switch (admin.kind) {
|
|
5157
|
-
case "prompted": return ok(id, label, "yours, entered during `rde init` and never stored");
|
|
5158
|
-
case "generated":
|
|
5159
|
-
if (secrets === null || secrets.admin === null) return failed(id, label, {
|
|
5160
|
-
detail: "generated, but missing from the secrets file",
|
|
5161
|
-
hint: `it cannot be recovered; ${RECREATE_HINT}`,
|
|
5162
|
-
step: null
|
|
5163
|
-
});
|
|
5164
|
-
return ok(id, label, "generated and stored; `rde credentials` shows it in your terminal");
|
|
5165
|
-
case "legacy-default": return failed(id, label, {
|
|
5166
|
-
detail: "the public default password from an earlier rde (legacy-default)",
|
|
5167
|
-
hint: `anyone who knows the default can log in; ${RECREATE_HINT} with a generated one`,
|
|
5168
|
-
step: null
|
|
5169
|
-
});
|
|
5170
|
-
}
|
|
5171
|
-
}
|
|
5172
|
-
async function apiKeyFinding(secrets, url, healthy, deps) {
|
|
5173
|
-
const id = "api-key";
|
|
5174
|
-
const label = "API key";
|
|
5175
|
-
const apiKey = secrets === null ? null : secrets.apiKey;
|
|
5176
|
-
if (apiKey === null) return {
|
|
5177
|
-
row: failed(id, label, {
|
|
5178
|
-
detail: "none in the secrets file",
|
|
5179
|
-
hint: "`rde init --only api-key`",
|
|
5180
|
-
step: API_KEY_STEP
|
|
5181
|
-
}),
|
|
5182
|
-
access: {
|
|
5183
|
-
kind: "unusable",
|
|
5184
|
-
reason: "no API key"
|
|
5185
|
-
}
|
|
5186
|
-
};
|
|
5187
|
-
if (url === null || !healthy) return {
|
|
5188
|
-
row: ok(id, label, "in the secrets file"),
|
|
5189
|
-
access: {
|
|
5190
|
-
kind: "unusable",
|
|
5191
|
-
reason: NOT_HEALTHY
|
|
5192
|
-
}
|
|
5193
|
-
};
|
|
5194
|
-
const client = deps.client(url, {
|
|
5195
|
-
kind: "api-key",
|
|
5196
|
-
key: apiKey
|
|
5197
|
-
});
|
|
5198
|
-
try {
|
|
5199
|
-
await client.user.current();
|
|
5200
|
-
} catch (error) {
|
|
5201
|
-
if (!isRejectedCredential(error)) throw error;
|
|
5202
|
-
return {
|
|
5203
|
-
row: failed(id, label, {
|
|
5204
|
-
detail: "in the secrets file, but Metabase rejects it",
|
|
5205
|
-
hint: "`rde init --only api-key` replaces it",
|
|
5206
|
-
step: API_KEY_STEP
|
|
5207
|
-
}),
|
|
5208
|
-
access: {
|
|
5209
|
-
kind: "unusable",
|
|
5210
|
-
reason: "Metabase rejects the API key"
|
|
5211
|
-
}
|
|
5212
|
-
};
|
|
5213
|
-
}
|
|
5214
|
-
return {
|
|
5215
|
-
row: ok(id, label, "in the secrets file, accepted by Metabase"),
|
|
5216
|
-
access: {
|
|
5217
|
-
kind: "accepted",
|
|
5218
|
-
client
|
|
5219
|
-
}
|
|
5220
|
-
};
|
|
5221
|
-
}
|
|
5222
|
-
function octal(mode) {
|
|
5223
|
-
return `0${mode.toString(8)}`;
|
|
5224
|
-
}
|
|
5225
|
-
function expectsSecrets(state) {
|
|
5226
|
-
return state.admin !== null && state.admin.kind !== "prompted" || state.steps[API_KEY_STEP]?.status === "done";
|
|
5227
|
-
}
|
|
5228
|
-
function secretsFileRow(state, files, dir) {
|
|
5229
|
-
const id = "secrets-file";
|
|
5230
|
-
const label = "secrets file";
|
|
5231
|
-
const path = secretsFileIn(dir);
|
|
5232
|
-
if (files.secretsMode === null) {
|
|
5233
|
-
if (!expectsSecrets(state)) return skipped(id, label, "nothing stored yet");
|
|
5234
|
-
return failed(id, label, {
|
|
5235
|
-
detail: `${path} is missing`,
|
|
5236
|
-
hint: "`rde init --only api-key` stores a new API key",
|
|
5237
|
-
step: API_KEY_STEP
|
|
5238
|
-
});
|
|
5239
|
-
}
|
|
5240
|
-
if (files.secretsMode !== 384 || files.dirMode !== 448) {
|
|
5241
|
-
const dirMode = files.dirMode === null ? "missing" : octal(files.dirMode);
|
|
5242
|
-
return failed(id, label, {
|
|
5243
|
-
detail: `${path} is ${octal(files.secretsMode)} in a ${dirMode} directory`,
|
|
5244
|
-
hint: `\`chmod ${octal(384)} ${path} && chmod ${octal(448)} ${dir}\``,
|
|
5245
|
-
step: null
|
|
5246
|
-
});
|
|
5247
|
-
}
|
|
5248
|
-
return ok(id, label, `${path}, ${octal(384)} in a ${octal(448)} directory`);
|
|
5249
|
-
}
|
|
5250
|
-
function stateFileRow(files) {
|
|
5251
|
-
const id = "state-file";
|
|
5252
|
-
const label = "state file";
|
|
5253
|
-
if (files.secretsInState.length > 0) return failed(id, label, {
|
|
5254
|
-
detail: `carries ${files.secretsInState.join(", ")}`,
|
|
5255
|
-
hint: "`rde init --only api-key` rewrites it without them",
|
|
5256
|
-
step: API_KEY_STEP
|
|
4685
|
+
function apiKeyRow(state) {
|
|
4686
|
+
if (state.apiKey === null) return failed("api-key", "API key", {
|
|
4687
|
+
detail: "none in the state file",
|
|
4688
|
+
hint: "`rde init --only api-key`",
|
|
4689
|
+
fix: "api-key"
|
|
5257
4690
|
});
|
|
5258
|
-
return ok(
|
|
4691
|
+
return ok("api-key", "API key", "in the state file");
|
|
5259
4692
|
}
|
|
5260
|
-
async function
|
|
5261
|
-
const dir = deps.runtime.instanceDir;
|
|
5262
|
-
const files = await inspectCredentialFiles(dir);
|
|
5263
|
-
return [
|
|
5264
|
-
adminPasswordRow(state, secrets),
|
|
5265
|
-
apiKey.row,
|
|
5266
|
-
secretsFileRow(state, files, dir),
|
|
5267
|
-
stateFileRow(files)
|
|
5268
|
-
];
|
|
5269
|
-
}
|
|
5270
|
-
function withoutTrailingSlash(url) {
|
|
5271
|
-
return url.replace(/\/+$/, "");
|
|
5272
|
-
}
|
|
5273
|
-
async function mbProfileRow(state, url, profiles, mbUnusableReason, healthy) {
|
|
4693
|
+
async function mbProfileRow(state, deps, mbInstalled, healthy) {
|
|
5274
4694
|
const label = "mb profile";
|
|
5275
|
-
if (
|
|
4695
|
+
if (!mbInstalled) return skipped("mb-profile", label, "mb is not installed");
|
|
5276
4696
|
if (!healthy) return skipped("mb-profile", label, NOT_HEALTHY);
|
|
5277
|
-
const entry = (await
|
|
4697
|
+
const entry = (await mbTool(deps.runner).authList()).find((candidate) => candidate.profile === state.mbProfile);
|
|
5278
4698
|
if (entry === void 0) return failed("mb-profile", label, {
|
|
5279
4699
|
detail: `${state.mbProfile} is not configured`,
|
|
5280
4700
|
hint: "`rde init --only api-key`",
|
|
5281
|
-
|
|
5282
|
-
});
|
|
5283
|
-
if (withoutTrailingSlash(entry.url) !== withoutTrailingSlash(url)) return failed("mb-profile", label, {
|
|
5284
|
-
detail: `${state.mbProfile} points at ${entry.url}, not at this instance (${url})`,
|
|
5285
|
-
hint: "`rde init --only api-key` logs it back in against the instance",
|
|
5286
|
-
step: "api-key"
|
|
4701
|
+
fix: "api-key"
|
|
5287
4702
|
});
|
|
5288
4703
|
if (entry.status !== "ok") return failed("mb-profile", label, {
|
|
5289
4704
|
detail: `${state.mbProfile}: ${entry.status}`,
|
|
5290
4705
|
hint: "`rde init --only api-key` logs it in again",
|
|
5291
|
-
|
|
4706
|
+
fix: "api-key"
|
|
5292
4707
|
});
|
|
5293
4708
|
const user = entry.user === null ? "" : ` as ${entry.user.name}`;
|
|
5294
4709
|
return ok("mb-profile", label, `${state.mbProfile}: ok${user}`);
|
|
5295
4710
|
}
|
|
5296
|
-
function hostOf(url) {
|
|
5297
|
-
return URL.canParse(url) ? new URL(url).host : "an address that is not a URL";
|
|
5298
|
-
}
|
|
5299
|
-
function unsetHint(name) {
|
|
5300
|
-
return `\`unset ${name}\`, remove it from your shell profile if it is exported there, then restart the agent session`;
|
|
5301
|
-
}
|
|
5302
|
-
async function mbProfileEnvRow(name, rdeProfile, profiles, mbUnusableReason) {
|
|
5303
|
-
const id = `env.${ENV_MB_PROFILE}`;
|
|
5304
|
-
if (name === rdeProfile) return ok(id, ENV_MB_PROFILE, `${name}: rde's own profile, so plain \`mb\` uses this instance`);
|
|
5305
|
-
const rde = rdeProfile === null ? "rde's" : `rde's (${rdeProfile})`;
|
|
5306
|
-
const detail = (where) => `${name}: \`mb\` and the rde skill will use profile ${name} (${where}) instead of ${rde}`;
|
|
5307
|
-
if (mbUnusableReason !== null) return note$1(id, ENV_MB_PROFILE, detail(mbUnusableReason), unsetHint(ENV_MB_PROFILE));
|
|
5308
|
-
const entry = (await profiles()).find((candidate) => candidate.profile === name);
|
|
5309
|
-
const where = entry === void 0 ? "not configured" : hostOf(entry.url);
|
|
5310
|
-
return note$1(id, ENV_MB_PROFILE, detail(where), unsetHint(ENV_MB_PROFILE));
|
|
5311
|
-
}
|
|
5312
|
-
async function environmentRows(env, rdeProfile, profiles, mbUnusableReason) {
|
|
5313
|
-
const rows = [];
|
|
5314
|
-
if (env.profile !== null) rows.push(await mbProfileEnvRow(env.profile, rdeProfile, profiles, mbUnusableReason));
|
|
5315
|
-
if (env.url !== null) rows.push(note$1(`env.${ENV_MB_URL}`, ENV_MB_URL, `every \`mb\` call goes to ${hostOf(env.url)}, whatever \`--profile\` says, rde's own included`, unsetHint(ENV_MB_URL)));
|
|
5316
|
-
if (env.apiKeySet) rows.push(note$1(`env.${ENV_MB_API_KEY}`, ENV_MB_API_KEY, "set (value not shown): every `mb` call sends it, whatever `--profile` says, rde's own included", unsetHint(ENV_MB_API_KEY)));
|
|
5317
|
-
return rows;
|
|
5318
|
-
}
|
|
5319
|
-
const PROFILE_STATUS_WORDS = {
|
|
5320
|
-
ok: "authenticates",
|
|
5321
|
-
"auth-failed": "login rejected",
|
|
5322
|
-
"network-error": "not answering",
|
|
5323
|
-
"server-error": "answers with a server error",
|
|
5324
|
-
"not-probed": "not checked"
|
|
5325
|
-
};
|
|
5326
|
-
function plainMbLine(env, rdeProfile, profiles) {
|
|
5327
|
-
if (env.url !== null) return `plain \`mb\` goes to ${hostOf(env.url)} (${ENV_MB_URL})`;
|
|
5328
|
-
const name = env.profile ?? "default";
|
|
5329
|
-
const via = env.profile === null ? "" : ` (${ENV_MB_PROFILE})`;
|
|
5330
|
-
const entry = profiles.find((candidate) => candidate.profile === name);
|
|
5331
|
-
if (entry === void 0) return `plain \`mb\` uses ${name}${via}, which is not configured`;
|
|
5332
|
-
const instance = name === rdeProfile ? ", this instance" : "";
|
|
5333
|
-
return `plain \`mb\` uses ${name}${via}: ${hostOf(entry.url)}${instance}`;
|
|
5334
|
-
}
|
|
5335
|
-
async function otherMetabasesRow(env, rdeProfile, profiles, mbUnusableReason) {
|
|
5336
|
-
const id = "other-metabases";
|
|
5337
|
-
const label = "other Metabases";
|
|
5338
|
-
if (mbUnusableReason !== null) return skipped(id, label, mbUnusableReason);
|
|
5339
|
-
const all = await profiles();
|
|
5340
|
-
const others = all.filter((entry) => entry.profile !== rdeProfile);
|
|
5341
|
-
return ok(id, label, [...others.length === 0 ? ["none"] : others.map((entry) => `${entry.profile}: ${hostOf(entry.url)}, ${PROFILE_STATUS_WORDS[entry.status]}`), plainMbLine(env, rdeProfile, all)].join("\n"));
|
|
5342
|
-
}
|
|
5343
4711
|
function isNotFound$2(error) {
|
|
5344
4712
|
return error instanceof MetabaseApiError && error.status === NOT_FOUND_STATUS$2;
|
|
5345
4713
|
}
|
|
@@ -5350,7 +4718,7 @@ async function licenseRow(client) {
|
|
|
5350
4718
|
if (!status.valid) return failed("license", label, {
|
|
5351
4719
|
detail: `invalid: ${status.status}`,
|
|
5352
4720
|
hint: "`rde init --from license` with a valid token",
|
|
5353
|
-
|
|
4721
|
+
fix: "license"
|
|
5354
4722
|
});
|
|
5355
4723
|
return ok("license", label, `valid, ${status.features.includes("remote-sync") ? `with ${REMOTE_SYNC_FEATURE}` : `without ${REMOTE_SYNC_FEATURE}, so git sync is unavailable`}`);
|
|
5356
4724
|
} catch (error) {
|
|
@@ -5367,47 +4735,47 @@ async function databasesRow(state, client) {
|
|
|
5367
4735
|
if (missing.length > 0) return failed("databases", label, {
|
|
5368
4736
|
detail: `not on the server any more: ${missing.map((record) => record.name).join(", ")}`,
|
|
5369
4737
|
hint: "`rde db add` connects them again",
|
|
5370
|
-
|
|
4738
|
+
fix: "databases"
|
|
5371
4739
|
});
|
|
5372
4740
|
const names = state.databases.map((record) => record.name).join(", ");
|
|
5373
4741
|
return ok("databases", label, `${state.databases.length} connected: ${names}`);
|
|
5374
4742
|
}
|
|
5375
4743
|
const SAMPLE_DATABASE_STEP = "sample-database";
|
|
5376
4744
|
const SAMPLE_LABEL = "sample database";
|
|
5377
|
-
async function sampleDatabaseRow(record, deps, dockerOk,
|
|
4745
|
+
async function sampleDatabaseRow(record, deps, dockerOk, client) {
|
|
5378
4746
|
const id = "sample-database";
|
|
5379
4747
|
if (!dockerOk) return skipped(id, SAMPLE_LABEL, "Docker is not available");
|
|
5380
4748
|
const status = await sampleDatabaseContainer(record, deps.runtime.docker).status();
|
|
5381
4749
|
if (!status.installed) return failed(id, SAMPLE_LABEL, {
|
|
5382
4750
|
detail: `${record.containerName} does not exist`,
|
|
5383
4751
|
hint: "`rde init` recreates it",
|
|
5384
|
-
|
|
4752
|
+
fix: SAMPLE_DATABASE_STEP
|
|
5385
4753
|
});
|
|
5386
4754
|
if (!status.running) return failed(id, SAMPLE_LABEL, {
|
|
5387
4755
|
detail: `${record.containerName} is stopped`,
|
|
5388
4756
|
hint: "`rde start`",
|
|
5389
|
-
|
|
4757
|
+
fix: "start-metabase"
|
|
5390
4758
|
});
|
|
5391
4759
|
if (await deps.ports.isFree(record.port)) return failed(id, SAMPLE_LABEL, {
|
|
5392
4760
|
detail: `${record.containerName} runs but port ${record.port} is not answering`,
|
|
5393
4761
|
hint: `\`docker logs ${record.containerName}\` shows what Postgres is doing`,
|
|
5394
|
-
|
|
4762
|
+
fix: null
|
|
5395
4763
|
});
|
|
5396
4764
|
const where = `${record.containerName} running on ${LOOPBACK_HOST}:${record.port}`;
|
|
5397
4765
|
if (record.databaseId === null) return failed(id, SAMPLE_LABEL, {
|
|
5398
4766
|
detail: `${where}; not connected to Metabase yet`,
|
|
5399
4767
|
hint: "`rde init` connects it",
|
|
5400
|
-
|
|
4768
|
+
fix: SAMPLE_DATABASE_STEP
|
|
5401
4769
|
});
|
|
5402
|
-
if (
|
|
4770
|
+
if (client === null) return skipped(id, SAMPLE_LABEL, `${where}; ${NOT_HEALTHY}, so the connection is unchecked`);
|
|
5403
4771
|
try {
|
|
5404
|
-
const database = await
|
|
4772
|
+
const database = await client.database.get(record.databaseId);
|
|
5405
4773
|
return ok(id, SAMPLE_LABEL, `${where}, database ${database.id} sync ${database.initial_sync_status}`);
|
|
5406
4774
|
} catch (error) {
|
|
5407
4775
|
if (isNotFound$2(error)) return failed(id, SAMPLE_LABEL, {
|
|
5408
4776
|
detail: `${where}; database ${record.databaseId} is gone from Metabase`,
|
|
5409
4777
|
hint: "`rde init` connects it again",
|
|
5410
|
-
|
|
4778
|
+
fix: SAMPLE_DATABASE_STEP
|
|
5411
4779
|
});
|
|
5412
4780
|
throw error;
|
|
5413
4781
|
}
|
|
@@ -5424,7 +4792,7 @@ async function gitSyncRow(state, deps) {
|
|
|
5424
4792
|
if (status.branch === null) return failed("git-sync", label, {
|
|
5425
4793
|
detail: "Metabase reports no branch",
|
|
5426
4794
|
hint: "`rde init --only git-sync`",
|
|
5427
|
-
|
|
4795
|
+
fix: "git-sync"
|
|
5428
4796
|
});
|
|
5429
4797
|
const dirty = status.is_dirty ? "unsynced changes" : "in sync";
|
|
5430
4798
|
return ok("git-sync", label, `branch ${status.branch}, ${dirty}, ${state.gitRemote.workdir}`);
|
|
@@ -5432,100 +4800,15 @@ async function gitSyncRow(state, deps) {
|
|
|
5432
4800
|
if (isMbFailure$1(error)) return failed("git-sync", label, {
|
|
5433
4801
|
detail: error.message,
|
|
5434
4802
|
hint: `\`${mbCommand(state.mbProfile)} git-sync status\` shows the full answer`,
|
|
5435
|
-
|
|
4803
|
+
fix: null
|
|
5436
4804
|
});
|
|
5437
4805
|
throw error;
|
|
5438
4806
|
}
|
|
5439
4807
|
}
|
|
5440
4808
|
const SKILL_STEP = "agents-skills";
|
|
5441
|
-
const GLOBAL_SCOPE = "global";
|
|
5442
|
-
const SKILL_FILE = "SKILL.md";
|
|
5443
|
-
const CLAUDE_CODE = "Claude Code";
|
|
5444
|
-
const CLAUDE_CODE_SKILL_RULE = "a personal skill wins over a project one: https://code.claude.com/docs/en/skills";
|
|
5445
4809
|
function skillRowId(agent) {
|
|
5446
4810
|
return `skill.${agent.id}`;
|
|
5447
4811
|
}
|
|
5448
|
-
function withRef(source, ref) {
|
|
5449
|
-
return ref === null ? source : `${source}#${ref}`;
|
|
5450
|
-
}
|
|
5451
|
-
async function copyRef(entry, deps) {
|
|
5452
|
-
const path = entry.scope === GLOBAL_SCOPE ? globalSkillLockPath(deps.probe.home, deps.skills.stateHome) : projectSkillLockPath(deps.skills.cwd);
|
|
5453
|
-
return lockedRef(path, await deps.skills.files.readJson(path), entry.name);
|
|
5454
|
-
}
|
|
5455
|
-
async function judgeCopy(entry, origin, deps) {
|
|
5456
|
-
const where = `${entry.scope} ${entry.path}`;
|
|
5457
|
-
switch (origin.kind) {
|
|
5458
|
-
case "local": {
|
|
5459
|
-
const installed = await deps.skills.files.readText(join(entry.path, SKILL_FILE));
|
|
5460
|
-
const wanted = await deps.skills.files.readText(join(origin.path, SKILL_FILE));
|
|
5461
|
-
const current = installed !== null && installed === wanted;
|
|
5462
|
-
return {
|
|
5463
|
-
entry,
|
|
5464
|
-
current,
|
|
5465
|
-
line: `${where}: ${current ? "same as" : "differs from"} ${origin.path}`
|
|
5466
|
-
};
|
|
5467
|
-
}
|
|
5468
|
-
case "github": {
|
|
5469
|
-
const wanted = withRef(origin.repository, origin.ref);
|
|
5470
|
-
if (entry.source === null) return {
|
|
5471
|
-
entry,
|
|
5472
|
-
current: false,
|
|
5473
|
-
line: `${where}: no recorded source, not ${wanted}`
|
|
5474
|
-
};
|
|
5475
|
-
const found = withRef(entry.source, await copyRef(entry, deps));
|
|
5476
|
-
const current = found === wanted;
|
|
5477
|
-
return {
|
|
5478
|
-
entry,
|
|
5479
|
-
current,
|
|
5480
|
-
line: `${where}: from ${found}${current ? "" : `, not ${wanted}`}`
|
|
5481
|
-
};
|
|
5482
|
-
}
|
|
5483
|
-
case "other": {
|
|
5484
|
-
const current = entry.source === origin.source;
|
|
5485
|
-
return {
|
|
5486
|
-
entry,
|
|
5487
|
-
current,
|
|
5488
|
-
line: `${where}: from ${entry.source ?? "no recorded source"}${current ? "" : `, not ${origin.source}`}`
|
|
5489
|
-
};
|
|
5490
|
-
}
|
|
5491
|
-
}
|
|
5492
|
-
}
|
|
5493
|
-
function loadedCopyLine(entries, cwd) {
|
|
5494
|
-
if (entries.length < 2) return null;
|
|
5495
|
-
const count = `${entries.length} copies are installed`;
|
|
5496
|
-
const forClaude = entries.filter((entry) => entry.agents.includes(CLAUDE_CODE));
|
|
5497
|
-
const loaded = forClaude.find((entry) => entry.scope === GLOBAL_SCOPE) ?? forClaude[0];
|
|
5498
|
-
if (loaded === void 0) return count;
|
|
5499
|
-
return `${count}; ${CLAUDE_CODE} started in ${cwd} loads ${loaded.path} (${CLAUDE_CODE_SKILL_RULE})`;
|
|
5500
|
-
}
|
|
5501
|
-
function staleCopiesRow(stale, detail, deps) {
|
|
5502
|
-
const globalStale = stale.some((copy) => copy.entry.scope === GLOBAL_SCOPE);
|
|
5503
|
-
const projectStale = stale.some((copy) => copy.entry.scope !== GLOBAL_SCOPE);
|
|
5504
|
-
const repairs = [];
|
|
5505
|
-
if (globalStale) repairs.push(`\`rde init --only ${SKILL_STEP}\` reinstalls the global copy from ${deps.skills.source}`);
|
|
5506
|
-
if (projectStale) repairs.push(`remove the project copy with \`npx -y skills@latest remove rde -y\` in ${deps.skills.cwd}`);
|
|
5507
|
-
return failed("skill-copies", "rde skill copies", {
|
|
5508
|
-
detail,
|
|
5509
|
-
hint: `${repairs.join("; ")}; then restart the agent session`,
|
|
5510
|
-
step: globalStale ? SKILL_STEP : null
|
|
5511
|
-
});
|
|
5512
|
-
}
|
|
5513
|
-
async function skillCopiesRow(globalEntries, deps) {
|
|
5514
|
-
const id = "skill-copies";
|
|
5515
|
-
const label = "rde skill copies";
|
|
5516
|
-
const cwd = deps.skills.cwd;
|
|
5517
|
-
const projectEntries = cwd === deps.probe.home ? [] : (await skillsCli(deps.runner).listProject(cwd)).filter((entry) => entry.name === "rde");
|
|
5518
|
-
const entries = [...globalEntries, ...projectEntries];
|
|
5519
|
-
if (entries.length === 0) return skipped(id, label, "no copy installed");
|
|
5520
|
-
const origin = skillOrigin(deps.skills.source, cwd);
|
|
5521
|
-
const judged = [];
|
|
5522
|
-
for (const entry of entries) judged.push(await judgeCopy(entry, origin, deps));
|
|
5523
|
-
const loaded = loadedCopyLine(entries, cwd);
|
|
5524
|
-
const lines = judged.map((copy) => copy.line);
|
|
5525
|
-
const detail = (loaded === null ? lines : [...lines, loaded]).join("\n");
|
|
5526
|
-
const stale = judged.filter((copy) => !copy.current);
|
|
5527
|
-
return stale.length === 0 ? ok(id, label, detail) : staleCopiesRow(stale, detail, deps);
|
|
5528
|
-
}
|
|
5529
4812
|
async function skillRows(state, deps) {
|
|
5530
4813
|
const label = "rde skill";
|
|
5531
4814
|
if (state === null) return [skipped("skill", label, NO_INSTANCE)];
|
|
@@ -5533,18 +4816,17 @@ async function skillRows(state, deps) {
|
|
|
5533
4816
|
const detected = (await detectAgents(deps.probe)).filter((agent) => agent.detected);
|
|
5534
4817
|
if (detected.length === 0) return [skipped("skill", label, "no coding agents detected")];
|
|
5535
4818
|
const entries = await rdeSkillEntries(deps.runner);
|
|
5536
|
-
const copies = await skillCopiesRow(entries, deps);
|
|
5537
4819
|
if (detected.filter((agent) => entries.some((entry) => skillCoversAgent(entry, agent))).length === 0) return [failed("skill", label, {
|
|
5538
4820
|
detail: `not installed for ${detected.map((agent) => agent.displayName).join(", ")}`,
|
|
5539
4821
|
hint: `\`rde init --only ${SKILL_STEP}\``,
|
|
5540
|
-
|
|
5541
|
-
})
|
|
5542
|
-
return
|
|
4822
|
+
fix: SKILL_STEP
|
|
4823
|
+
})];
|
|
4824
|
+
return detected.map((agent) => {
|
|
5543
4825
|
const entry = entries.find((candidate) => skillCoversAgent(candidate, agent));
|
|
5544
4826
|
const agentLabel = `${label} (${agent.displayName})`;
|
|
5545
4827
|
if (entry === void 0) return skipped(skillRowId(agent), agentLabel, `not installed; \`rde init --only ${SKILL_STEP}\` adds it`);
|
|
5546
4828
|
return ok(skillRowId(agent), agentLabel, entry.path);
|
|
5547
|
-
})
|
|
4829
|
+
});
|
|
5548
4830
|
}
|
|
5549
4831
|
function noRuntimeRows() {
|
|
5550
4832
|
return [
|
|
@@ -5553,13 +4835,13 @@ function noRuntimeRows() {
|
|
|
5553
4835
|
"health"
|
|
5554
4836
|
].map((id) => skipped(id, id, NO_INSTANCE));
|
|
5555
4837
|
}
|
|
5556
|
-
async function instanceRows(state,
|
|
4838
|
+
async function instanceRows(state, deps, dockerOk, mbInstalled) {
|
|
5557
4839
|
const configured = configuredRuntime(state);
|
|
5558
4840
|
if (configured === null) return [
|
|
5559
4841
|
...noRuntimeRows(),
|
|
5560
4842
|
skipped("version", "Metabase version", NO_INSTANCE),
|
|
5561
4843
|
skipped("admin", "admin user", NO_INSTANCE),
|
|
5562
|
-
|
|
4844
|
+
apiKeyRow(state),
|
|
5563
4845
|
skipped("mb-profile", "mb profile", NO_INSTANCE),
|
|
5564
4846
|
skipped("license", "license", NO_INSTANCE),
|
|
5565
4847
|
skipped("databases", "databases", NO_INSTANCE),
|
|
@@ -5569,33 +4851,35 @@ async function instanceRows(state, secrets, deps, dockerOk, profiles, mbUnusable
|
|
|
5569
4851
|
const runtime = await runtimeRow(configured.runtime, deps, dockerOk);
|
|
5570
4852
|
const { row: health, healthy } = await healthRow(configured.url, deps);
|
|
5571
4853
|
const properties = healthy ? await deps.client(configured.url, NO_AUTH).session.properties() : null;
|
|
5572
|
-
const apiKey = await apiKeyFinding(secrets, configured.url, healthy, deps);
|
|
5573
4854
|
const rows = [
|
|
5574
4855
|
runtime.row,
|
|
5575
4856
|
await portRow(configured.runtime.port, runtime, deps.ports),
|
|
5576
4857
|
health,
|
|
5577
4858
|
properties === null ? skipped("version", "Metabase version", NOT_HEALTHY) : versionRow(state, properties),
|
|
5578
4859
|
properties === null ? skipped("admin", "admin user", NOT_HEALTHY) : adminRow(state, properties),
|
|
5579
|
-
|
|
5580
|
-
await mbProfileRow(state,
|
|
4860
|
+
apiKeyRow(state),
|
|
4861
|
+
await mbProfileRow(state, deps, mbInstalled, healthy)
|
|
5581
4862
|
];
|
|
5582
|
-
const
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
4863
|
+
const client = healthy && state.apiKey !== null ? deps.client(configured.url, {
|
|
4864
|
+
kind: "api-key",
|
|
4865
|
+
key: state.apiKey
|
|
4866
|
+
}) : null;
|
|
4867
|
+
if (client === null) {
|
|
4868
|
+
const reason = healthy ? "no API key" : NOT_HEALTHY;
|
|
4869
|
+
rows.push(skipped("license", "license", reason), skipped("databases", "databases", reason));
|
|
4870
|
+
} else rows.push(await licenseRow(client), await databasesRow(state, client));
|
|
4871
|
+
rows.push(state.sampleDatabase === null ? skipped("sample-database", SAMPLE_LABEL, "not connected; `rde init` adds one") : await sampleDatabaseRow(state.sampleDatabase, deps, dockerOk, client));
|
|
5586
4872
|
rows.push(await gitSyncRow(state, deps));
|
|
5587
4873
|
return rows;
|
|
5588
4874
|
}
|
|
5589
|
-
async function collectDoctor(instance, state,
|
|
4875
|
+
async function collectDoctor(instance, state, deps) {
|
|
5590
4876
|
const machine = await machineRows(deps, state?.runtime ?? null);
|
|
5591
4877
|
const dockerOk = machine.some((row) => row.id === "docker" && row.status === "ok");
|
|
5592
|
-
const mb = await
|
|
5593
|
-
const
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
const rdeProfile = state === null ? null : state.mbProfile;
|
|
5598
|
-
checks.push(...await environmentRows(deps.environment, rdeProfile, profiles, mb.unusable), await otherMetabasesRow(deps.environment, rdeProfile, profiles, mb.unusable), ...await skillRows(state, deps));
|
|
4878
|
+
const mb = await mbRow(deps);
|
|
4879
|
+
const checks = [...machine, mb];
|
|
4880
|
+
if (state === null) checks.push(...noRuntimeRows(), skipped("version", "Metabase version", NO_INSTANCE), skipped("admin", "admin user", NO_INSTANCE), skipped("api-key", "API key", NO_INSTANCE), skipped("mb-profile", "mb profile", NO_INSTANCE), skipped("license", "license", NO_INSTANCE), skipped("databases", "databases", NO_INSTANCE), skipped("sample-database", SAMPLE_LABEL, NO_INSTANCE), skipped("git-sync", "remote sync", NO_INSTANCE));
|
|
4881
|
+
else checks.push(...await instanceRows(state, deps, dockerOk, mb.status === "ok"));
|
|
4882
|
+
checks.push(...await skillRows(state, deps));
|
|
5599
4883
|
return {
|
|
5600
4884
|
instance,
|
|
5601
4885
|
checks,
|
|
@@ -5607,38 +4891,18 @@ const MARK = {
|
|
|
5607
4891
|
failed: "✗",
|
|
5608
4892
|
skipped: "–"
|
|
5609
4893
|
};
|
|
5610
|
-
const NOTE_MARK = "!";
|
|
5611
4894
|
const HINT_ARROW = "→";
|
|
5612
|
-
function markFor(row) {
|
|
5613
|
-
return row.status === "ok" && row.hint !== null ? NOTE_MARK : MARK[row.status];
|
|
5614
|
-
}
|
|
5615
4895
|
function doctorLines(report) {
|
|
5616
4896
|
const width = Math.max(...report.checks.map((row) => row.label.length));
|
|
5617
|
-
const indent = ` ${" ".repeat(width)} `;
|
|
5618
4897
|
const lines = [];
|
|
5619
4898
|
for (const row of report.checks) {
|
|
5620
|
-
|
|
5621
|
-
lines.push(
|
|
5622
|
-
lines.push(...rest.map((line) => `${indent}${line}`));
|
|
5623
|
-
if (row.hint !== null) lines.push(`${indent}${HINT_ARROW} ${row.hint}`);
|
|
4899
|
+
lines.push(`${MARK[row.status]} ${row.label.padEnd(width)} ${row.detail}`);
|
|
4900
|
+
if (row.hint !== null) lines.push(` ${" ".repeat(width)} ${HINT_ARROW} ${row.hint}`);
|
|
5624
4901
|
}
|
|
5625
4902
|
return lines;
|
|
5626
4903
|
}
|
|
5627
|
-
function fixCommand(step) {
|
|
5628
|
-
return step === null ? null : `rde init --only ${step}`;
|
|
5629
|
-
}
|
|
5630
|
-
function doctorJson(report) {
|
|
5631
|
-
return {
|
|
5632
|
-
instance: report.instance,
|
|
5633
|
-
checks: report.checks.map(({ step, ...row }) => ({
|
|
5634
|
-
...row,
|
|
5635
|
-
fix: fixCommand(step)
|
|
5636
|
-
})),
|
|
5637
|
-
failed: report.failed
|
|
5638
|
-
};
|
|
5639
|
-
}
|
|
5640
4904
|
function fixableSteps(report) {
|
|
5641
|
-
const wanted = new Set(report.checks.flatMap((row) => row.status === "failed" && row.
|
|
4905
|
+
const wanted = new Set(report.checks.flatMap((row) => row.status === "failed" && row.fix !== null ? [row.fix] : []));
|
|
5642
4906
|
return StepId.options.filter((id) => wanted.has(id));
|
|
5643
4907
|
}
|
|
5644
4908
|
function doctorSummary(report) {
|
|
@@ -5677,24 +4941,15 @@ const ADMIN_USER_KEYS = {
|
|
|
5677
4941
|
firstName: `${STEP_ID$6}.first-name`,
|
|
5678
4942
|
lastName: `${STEP_ID$6}.last-name`
|
|
5679
4943
|
};
|
|
5680
|
-
const
|
|
5681
|
-
const
|
|
4944
|
+
const DEFAULT_ADMIN_EMAIL = "admin@metabase.com";
|
|
4945
|
+
const DEFAULT_ADMIN_PASSWORD = "metabase123";
|
|
5682
4946
|
const DEFAULT_SITE_NAME = "rde";
|
|
5683
4947
|
const MIN_PASSWORD_LENGTH = 6;
|
|
5684
4948
|
const DIGIT = /\d/;
|
|
5685
4949
|
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
5686
|
-
const DEFAULT_ADMIN = { variant: "
|
|
5687
|
-
function generatePassword(random) {
|
|
5688
|
-
for (;;) {
|
|
5689
|
-
const candidate = random(GENERATED_PASSWORD_BYTES).toString("base64url");
|
|
5690
|
-
if (DIGIT.test(candidate)) return candidate;
|
|
5691
|
-
}
|
|
5692
|
-
}
|
|
4950
|
+
const DEFAULT_ADMIN = { variant: "default" };
|
|
5693
4951
|
function hostDeps$8(ctx) {
|
|
5694
|
-
return {
|
|
5695
|
-
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
5696
|
-
password: () => generatePassword(randomBytes)
|
|
5697
|
-
};
|
|
4952
|
+
return { client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal) };
|
|
5698
4953
|
}
|
|
5699
4954
|
function isNotReady(error) {
|
|
5700
4955
|
return error instanceof MetabaseUnreachableError || error instanceof MetabaseInitializingError;
|
|
@@ -5733,22 +4988,26 @@ async function promptedUser(ctx) {
|
|
|
5733
4988
|
...lastName.length > 0 && { last_name: lastName }
|
|
5734
4989
|
};
|
|
5735
4990
|
}
|
|
5736
|
-
async function chooseUser(ctx,
|
|
5737
|
-
switch (
|
|
5738
|
-
case "
|
|
5739
|
-
email:
|
|
5740
|
-
password:
|
|
4991
|
+
async function chooseUser(ctx, kind) {
|
|
4992
|
+
switch (kind) {
|
|
4993
|
+
case "default": return {
|
|
4994
|
+
email: DEFAULT_ADMIN_EMAIL,
|
|
4995
|
+
password: DEFAULT_ADMIN_PASSWORD
|
|
5741
4996
|
};
|
|
5742
4997
|
case "prompted": return promptedUser(ctx);
|
|
5743
4998
|
}
|
|
5744
4999
|
}
|
|
5745
|
-
function
|
|
5746
|
-
switch (
|
|
5747
|
-
case "
|
|
5000
|
+
function credentialsRecord(kind, user) {
|
|
5001
|
+
switch (kind) {
|
|
5002
|
+
case "default": return {
|
|
5003
|
+
kind,
|
|
5748
5004
|
email: user.email,
|
|
5749
5005
|
password: user.password
|
|
5750
5006
|
};
|
|
5751
|
-
case "prompted": return
|
|
5007
|
+
case "prompted": return {
|
|
5008
|
+
kind,
|
|
5009
|
+
email: user.email
|
|
5010
|
+
};
|
|
5752
5011
|
}
|
|
5753
5012
|
}
|
|
5754
5013
|
function adminUserStep(options, deps = hostDeps$8) {
|
|
@@ -5767,8 +5026,7 @@ function adminUserStep(options, deps = hostDeps$8) {
|
|
|
5767
5026
|
},
|
|
5768
5027
|
async run(ctx) {
|
|
5769
5028
|
const configured = requireConfiguredRuntime(ctx.state);
|
|
5770
|
-
const
|
|
5771
|
-
const client = resolved.client(configured.url, NO_AUTH);
|
|
5029
|
+
const client = deps(ctx).client(configured.url, NO_AUTH);
|
|
5772
5030
|
const properties = await client.session.properties();
|
|
5773
5031
|
if (properties["has-user-setup"]) {
|
|
5774
5032
|
ctx.ui.info("Metabase already has an admin user");
|
|
@@ -5776,11 +5034,7 @@ function adminUserStep(options, deps = hostDeps$8) {
|
|
|
5776
5034
|
}
|
|
5777
5035
|
const token = properties["setup-token"];
|
|
5778
5036
|
if (token === null) throw new ConfigError("Metabase has no admin user yet but offers no setup token");
|
|
5779
|
-
const user = await chooseUser(ctx, options.variant
|
|
5780
|
-
await ctx.saveSecrets({
|
|
5781
|
-
...ctx.secrets,
|
|
5782
|
-
admin: storedLogin(options.variant, user)
|
|
5783
|
-
});
|
|
5037
|
+
const user = await chooseUser(ctx, options.variant);
|
|
5784
5038
|
const session = await client.setup.create({
|
|
5785
5039
|
token,
|
|
5786
5040
|
user,
|
|
@@ -5789,10 +5043,7 @@ function adminUserStep(options, deps = hostDeps$8) {
|
|
|
5789
5043
|
ctx.session.metabaseSession.set(session.id);
|
|
5790
5044
|
await ctx.saveState({
|
|
5791
5045
|
...ctx.state,
|
|
5792
|
-
admin:
|
|
5793
|
-
kind: options.variant,
|
|
5794
|
-
email: user.email
|
|
5795
|
-
}
|
|
5046
|
+
admin: credentialsRecord(options.variant, user)
|
|
5796
5047
|
});
|
|
5797
5048
|
ctx.ui.success(`Admin user ${user.email} created`);
|
|
5798
5049
|
return "completed";
|
|
@@ -5804,7 +5055,8 @@ function adminUserStep(options, deps = hostDeps$8) {
|
|
|
5804
5055
|
const STEP_ID$5 = "api-key";
|
|
5805
5056
|
const API_KEY_KEYS = {
|
|
5806
5057
|
email: `${STEP_ID$5}.email`,
|
|
5807
|
-
password: `${STEP_ID$5}.password
|
|
5058
|
+
password: `${STEP_ID$5}.password`,
|
|
5059
|
+
profile: `${STEP_ID$5}.profile`
|
|
5808
5060
|
};
|
|
5809
5061
|
const ADMINISTRATORS_GROUP_NAME = "Administrators";
|
|
5810
5062
|
function hostDeps$7(ctx) {
|
|
@@ -5814,12 +5066,11 @@ function hostDeps$7(ctx) {
|
|
|
5814
5066
|
};
|
|
5815
5067
|
}
|
|
5816
5068
|
async function adminCredentials(ctx) {
|
|
5817
|
-
const stored = ctx.secrets.admin;
|
|
5818
|
-
if (stored !== null) return {
|
|
5819
|
-
username: stored.email,
|
|
5820
|
-
password: stored.password
|
|
5821
|
-
};
|
|
5822
5069
|
const admin = ctx.state.admin;
|
|
5070
|
+
if (admin?.kind === "default") return {
|
|
5071
|
+
username: admin.email,
|
|
5072
|
+
password: admin.password
|
|
5073
|
+
};
|
|
5823
5074
|
ctx.ui.info("An admin login is needed once to create the API key; it is not saved");
|
|
5824
5075
|
return {
|
|
5825
5076
|
username: await ctx.prompts.text({
|
|
@@ -5857,35 +5108,47 @@ async function freshApiKey(ctx, client) {
|
|
|
5857
5108
|
group_id: groupId
|
|
5858
5109
|
})).unmasked_key;
|
|
5859
5110
|
}
|
|
5860
|
-
async function
|
|
5861
|
-
|
|
5862
|
-
await client.user.current();
|
|
5863
|
-
return true;
|
|
5864
|
-
} catch (error) {
|
|
5865
|
-
if (isRejectedCredential(error)) return false;
|
|
5866
|
-
throw error;
|
|
5867
|
-
}
|
|
5868
|
-
}
|
|
5869
|
-
async function workingApiKey(ctx, deps, url) {
|
|
5870
|
-
const stored = ctx.secrets.apiKey;
|
|
5871
|
-
if (stored !== null) {
|
|
5872
|
-
if (await authenticates(deps.client(url, {
|
|
5873
|
-
kind: "api-key",
|
|
5874
|
-
key: stored
|
|
5875
|
-
}))) return stored;
|
|
5876
|
-
ctx.ui.warn("Metabase rejects the stored API key; replacing it");
|
|
5877
|
-
}
|
|
5111
|
+
async function persistedApiKey(ctx, deps, url) {
|
|
5112
|
+
if (ctx.state.apiKey !== null) return ctx.state.apiKey;
|
|
5878
5113
|
const sessionId = await superuserSession(ctx, deps, url);
|
|
5879
5114
|
const apiKey = await freshApiKey(ctx, deps.client(url, {
|
|
5880
5115
|
kind: "session",
|
|
5881
5116
|
id: sessionId
|
|
5882
5117
|
}));
|
|
5883
|
-
await ctx.
|
|
5884
|
-
...ctx.
|
|
5118
|
+
await ctx.saveState({
|
|
5119
|
+
...ctx.state,
|
|
5885
5120
|
apiKey
|
|
5886
5121
|
});
|
|
5887
5122
|
return apiKey;
|
|
5888
5123
|
}
|
|
5124
|
+
async function chooseProfile(ctx, mb, url) {
|
|
5125
|
+
const profile = ctx.state.mbProfile;
|
|
5126
|
+
if (profile !== "default") return profile;
|
|
5127
|
+
let existing;
|
|
5128
|
+
try {
|
|
5129
|
+
existing = await mb.authStatus(DEFAULT_MB_PROFILE);
|
|
5130
|
+
} catch (error) {
|
|
5131
|
+
if (error instanceof ProcessNotFoundError) return profile;
|
|
5132
|
+
throw error;
|
|
5133
|
+
}
|
|
5134
|
+
if (!existing.present || existing.url === url) return profile;
|
|
5135
|
+
const chosen = await ctx.prompts.select({
|
|
5136
|
+
key: API_KEY_KEYS.profile,
|
|
5137
|
+
message: `mb profile ${DEFAULT_MB_PROFILE} already points at ${existing.url}; which profile should this instance use?`,
|
|
5138
|
+
choices: [{
|
|
5139
|
+
value: "rde",
|
|
5140
|
+
label: `rde, and leave ${DEFAULT_MB_PROFILE} alone`
|
|
5141
|
+
}, {
|
|
5142
|
+
value: DEFAULT_MB_PROFILE,
|
|
5143
|
+
label: `${DEFAULT_MB_PROFILE}, replacing it`
|
|
5144
|
+
}]
|
|
5145
|
+
});
|
|
5146
|
+
if (chosen !== profile) await ctx.saveState({
|
|
5147
|
+
...ctx.state,
|
|
5148
|
+
mbProfile: chosen
|
|
5149
|
+
});
|
|
5150
|
+
return chosen;
|
|
5151
|
+
}
|
|
5889
5152
|
function usageNote(profile) {
|
|
5890
5153
|
if (profile === "default") return `${mbCommand(profile)} <command> uses this instance`;
|
|
5891
5154
|
return `${mbCommand(profile)} <command>\nMB_PROFILE=${profile} for agents and scripts`;
|
|
@@ -5895,7 +5158,7 @@ function apiKeyStep(deps = hostDeps$7) {
|
|
|
5895
5158
|
id: STEP_ID$5,
|
|
5896
5159
|
title: "Connect the Metabase CLI",
|
|
5897
5160
|
async detect(ctx) {
|
|
5898
|
-
if (ctx.
|
|
5161
|
+
if (ctx.state.apiKey === null) return "todo";
|
|
5899
5162
|
const mb = mbTool(deps(ctx).runner);
|
|
5900
5163
|
const profile = ctx.state.mbProfile;
|
|
5901
5164
|
try {
|
|
@@ -5909,9 +5172,9 @@ function apiKeyStep(deps = hostDeps$7) {
|
|
|
5909
5172
|
async run(ctx) {
|
|
5910
5173
|
const resolved = deps(ctx);
|
|
5911
5174
|
const configured = requireConfiguredRuntime(ctx.state);
|
|
5912
|
-
const apiKey = await
|
|
5175
|
+
const apiKey = await persistedApiKey(ctx, resolved, configured.url);
|
|
5913
5176
|
const mb = mbTool(resolved.runner);
|
|
5914
|
-
const profile = ctx.
|
|
5177
|
+
const profile = await chooseProfile(ctx, mb, configured.url);
|
|
5915
5178
|
await mb.authLogin({
|
|
5916
5179
|
url: configured.url,
|
|
5917
5180
|
profile,
|
|
@@ -6311,14 +5574,14 @@ function isMbUnavailable(error) {
|
|
|
6311
5574
|
}
|
|
6312
5575
|
async function runGitSync(ctx, deps) {
|
|
6313
5576
|
const configured = requireConfiguredRuntime(ctx.state);
|
|
6314
|
-
if (ctx.
|
|
5577
|
+
if (ctx.state.apiKey === null) throw new ConfigError("no API key yet; the api-key step comes first");
|
|
6315
5578
|
const run = {
|
|
6316
5579
|
ctx,
|
|
6317
5580
|
deps,
|
|
6318
5581
|
git: gitTool(deps.runner),
|
|
6319
5582
|
client: deps.client(configured.url, {
|
|
6320
5583
|
kind: "api-key",
|
|
6321
|
-
key: ctx.
|
|
5584
|
+
key: ctx.state.apiKey
|
|
6322
5585
|
})
|
|
6323
5586
|
};
|
|
6324
5587
|
const flow = await runFlow({
|
|
@@ -6374,8 +5637,8 @@ function validateToken(value) {
|
|
|
6374
5637
|
return "A license token is 64 hexadecimal characters";
|
|
6375
5638
|
}
|
|
6376
5639
|
function requireApiKey$1(ctx) {
|
|
6377
|
-
if (ctx.
|
|
6378
|
-
return ctx.
|
|
5640
|
+
if (ctx.state.apiKey === null) throw new ConfigError("no API key yet; the api-key step comes first");
|
|
5641
|
+
return ctx.state.apiKey;
|
|
6379
5642
|
}
|
|
6380
5643
|
function activeLabel(status) {
|
|
6381
5644
|
const plan = status["plan-alias"];
|
|
@@ -6388,15 +5651,15 @@ function licenseStep(deps = hostDeps$5) {
|
|
|
6388
5651
|
async detect(ctx) {
|
|
6389
5652
|
if (ctx.state.steps[STEP_ID$3]?.status === "skipped") return "not-applicable";
|
|
6390
5653
|
const configured = configuredRuntime(ctx.state);
|
|
6391
|
-
if (configured === null || ctx.
|
|
5654
|
+
if (configured === null || ctx.state.apiKey === null) return "todo";
|
|
6392
5655
|
const client = deps(ctx).client(configured.url, {
|
|
6393
5656
|
kind: "api-key",
|
|
6394
|
-
key: ctx.
|
|
5657
|
+
key: ctx.state.apiKey
|
|
6395
5658
|
});
|
|
6396
5659
|
try {
|
|
6397
5660
|
return (await client.tokenStatus.get()).valid ? "done" : "todo";
|
|
6398
5661
|
} catch (error) {
|
|
6399
|
-
if (isNoTokenYet(error)
|
|
5662
|
+
if (isNoTokenYet(error)) return "todo";
|
|
6400
5663
|
throw error;
|
|
6401
5664
|
}
|
|
6402
5665
|
},
|
|
@@ -6631,9 +5894,9 @@ function isNotServingYet(error) {
|
|
|
6631
5894
|
function isNotFound$1(error) {
|
|
6632
5895
|
return error instanceof MetabaseApiError && error.status === NOT_FOUND_STATUS;
|
|
6633
5896
|
}
|
|
6634
|
-
function requireApiKey(
|
|
6635
|
-
if (
|
|
6636
|
-
return
|
|
5897
|
+
function requireApiKey(state) {
|
|
5898
|
+
if (state.apiKey === null) throw new ConfigError(`instance ${state.name} has no API key yet; run \`rde init\``);
|
|
5899
|
+
return state.apiKey;
|
|
6637
5900
|
}
|
|
6638
5901
|
async function recordedDatabase(record, client) {
|
|
6639
5902
|
if (record.databaseId === null) return null;
|
|
@@ -6755,23 +6018,23 @@ function sampleDatabaseStep(deps = hostDeps$3) {
|
|
|
6755
6018
|
if (!(await resolved.runtime.docker.available()).available) return "not-applicable";
|
|
6756
6019
|
const record = ctx.state.sampleDatabase;
|
|
6757
6020
|
const configured = configuredRuntime(ctx.state);
|
|
6758
|
-
if (record === null || configured === null || ctx.
|
|
6021
|
+
if (record === null || configured === null || ctx.state.apiKey === null) return "todo";
|
|
6759
6022
|
if (!(await sampleDatabaseContainer(record, resolved.runtime.docker).status()).installed) return "todo";
|
|
6760
6023
|
const client = resolved.client(configured.url, {
|
|
6761
6024
|
kind: "api-key",
|
|
6762
|
-
key: ctx.
|
|
6025
|
+
key: ctx.state.apiKey
|
|
6763
6026
|
});
|
|
6764
6027
|
try {
|
|
6765
6028
|
return await recordedDatabase(record, client) === null ? "todo" : "done";
|
|
6766
6029
|
} catch (error) {
|
|
6767
|
-
if (isNotServingYet(error)
|
|
6030
|
+
if (isNotServingYet(error)) return "todo";
|
|
6768
6031
|
throw error;
|
|
6769
6032
|
}
|
|
6770
6033
|
},
|
|
6771
6034
|
async run(ctx) {
|
|
6772
6035
|
const resolved = deps(ctx);
|
|
6773
6036
|
const configured = requireConfiguredRuntime(ctx.state);
|
|
6774
|
-
const apiKey = requireApiKey(ctx);
|
|
6037
|
+
const apiKey = requireApiKey(ctx.state);
|
|
6775
6038
|
const docker = await resolved.runtime.docker.available();
|
|
6776
6039
|
if (!docker.available) throw new ConfigError(`the sample database runs in Docker, which is not available: ${docker.reason}`);
|
|
6777
6040
|
const choice = await persistedRecord(ctx, resolved);
|
|
@@ -6886,24 +6149,23 @@ function hostDeps$1(ctx) {
|
|
|
6886
6149
|
function isNotServing$1(error) {
|
|
6887
6150
|
return error instanceof MetabaseUnreachableError || error instanceof MetabaseInitializingError;
|
|
6888
6151
|
}
|
|
6889
|
-
function loginLine(
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
case "
|
|
6893
|
-
case "prompted": return `Login: ${email} with the password you entered`;
|
|
6152
|
+
function loginLine(admin) {
|
|
6153
|
+
if (admin === null) return null;
|
|
6154
|
+
switch (admin.kind) {
|
|
6155
|
+
case "default": return `Login: ${admin.email} / ${admin.password}`;
|
|
6156
|
+
case "prompted": return `Login: ${admin.email} with the password you entered`;
|
|
6894
6157
|
}
|
|
6895
6158
|
}
|
|
6896
|
-
async function databaseLines$1(state,
|
|
6897
|
-
if (state.url === null || apiKey === null) return ["Databases: none connected yet"];
|
|
6159
|
+
async function databaseLines$1(state, deps) {
|
|
6160
|
+
if (state.url === null || state.apiKey === null) return ["Databases: none connected yet"];
|
|
6898
6161
|
let databases;
|
|
6899
6162
|
try {
|
|
6900
6163
|
databases = (await deps.client(state.url, {
|
|
6901
6164
|
kind: "api-key",
|
|
6902
|
-
key: apiKey
|
|
6165
|
+
key: state.apiKey
|
|
6903
6166
|
}).database.list()).data;
|
|
6904
6167
|
} catch (error) {
|
|
6905
6168
|
if (isNotServing$1(error)) return ["Databases: Metabase is not running; `rde start`"];
|
|
6906
|
-
if (isRejectedCredential(error)) return ["Databases: Metabase rejects the stored API key; `rde init --only api-key`"];
|
|
6907
6169
|
throw error;
|
|
6908
6170
|
}
|
|
6909
6171
|
if (databases.length === 0) return ["Databases: none connected yet; `rde db add`"];
|
|
@@ -6937,13 +6199,14 @@ function launchLines(agents, workdir) {
|
|
|
6937
6199
|
const prefix = workdir === null ? "" : `cd ${workdir} && `;
|
|
6938
6200
|
return ["Start working:", ...detected.map((agent) => ` ${prefix}${agent.launch} (${agent.displayName})`)];
|
|
6939
6201
|
}
|
|
6940
|
-
async function summaryLines(state,
|
|
6202
|
+
async function summaryLines(state, deps) {
|
|
6941
6203
|
const profile = state.mbProfile;
|
|
6942
6204
|
const lines = [];
|
|
6943
6205
|
if (state.url !== null) lines.push(`Metabase: ${state.url}`);
|
|
6944
|
-
|
|
6206
|
+
const login = loginLine(state.admin);
|
|
6207
|
+
if (login !== null) lines.push(login);
|
|
6945
6208
|
lines.push(profile === "default" ? `Metabase CLI: ${mbCommand(profile)} <command>` : `Metabase CLI: ${mbCommand(profile)} <command> or export MB_PROFILE=${profile}`);
|
|
6946
|
-
lines.push(...await databaseLines$1(state,
|
|
6209
|
+
lines.push(...await databaseLines$1(state, deps));
|
|
6947
6210
|
if (state.sampleDatabase !== null) lines.push(`Sample database: psql "${sampleDatabaseUrl(state.sampleDatabase.port)}"`);
|
|
6948
6211
|
lines.push(...gitLines(state));
|
|
6949
6212
|
const installed = await skillsCli(deps.runner).list();
|
|
@@ -6960,8 +6223,7 @@ function summaryStep(deps = hostDeps$1) {
|
|
|
6960
6223
|
return "todo";
|
|
6961
6224
|
},
|
|
6962
6225
|
async run(ctx) {
|
|
6963
|
-
|
|
6964
|
-
ctx.ui.note(lines.join("\n"), "Your environment");
|
|
6226
|
+
ctx.ui.note((await summaryLines(ctx.state, deps(ctx))).join("\n"), "Your environment");
|
|
6965
6227
|
return "completed";
|
|
6966
6228
|
}
|
|
6967
6229
|
};
|
|
@@ -6995,7 +6257,7 @@ function initSteps(options) {
|
|
|
6995
6257
|
//#region src/commands/doctor.ts
|
|
6996
6258
|
function print(report, json) {
|
|
6997
6259
|
if (json) {
|
|
6998
|
-
printJson(
|
|
6260
|
+
printJson(report);
|
|
6999
6261
|
return;
|
|
7000
6262
|
}
|
|
7001
6263
|
printLines(doctorLines(report));
|
|
@@ -7026,12 +6288,12 @@ const doctor = defineRdeCommand({
|
|
|
7026
6288
|
const name = await instanceNameOrDefault();
|
|
7027
6289
|
const state = await readInstanceState(name);
|
|
7028
6290
|
const deps = hostDoctorDeps(name, interruptSignal);
|
|
7029
|
-
let report = await collectDoctor(name, state,
|
|
6291
|
+
let report = await collectDoctor(name, state, deps);
|
|
7030
6292
|
if (args.fix && report.failed > 0) {
|
|
7031
6293
|
if (state === null) throw new ConfigError("no instance to fix yet; run `rde init`");
|
|
7032
6294
|
print(report, args.json);
|
|
7033
6295
|
const ctx = await createWizardContext(name, { signal: interruptSignal });
|
|
7034
|
-
if (await fixDoctor(report, initSteps(DEFAULT_STEP_OPTIONS), ctx, { yes: args.yes })) report = await collectDoctor(name, ctx.state,
|
|
6296
|
+
if (await fixDoctor(report, initSteps(DEFAULT_STEP_OPTIONS), ctx, { yes: args.yes })) report = await collectDoctor(name, ctx.state, deps);
|
|
7035
6297
|
}
|
|
7036
6298
|
print(report, args.json);
|
|
7037
6299
|
if (report.failed > 0) throw new Error(doctorSummary(report));
|
|
@@ -7056,7 +6318,7 @@ const EnvString = z.string().transform((value, ctx) => {
|
|
|
7056
6318
|
}
|
|
7057
6319
|
return resolved;
|
|
7058
6320
|
});
|
|
7059
|
-
const AdminVariant = z.enum(["
|
|
6321
|
+
const AdminVariant = z.enum(["default", "prompted"]);
|
|
7060
6322
|
const RemoteKind = z.enum(["local-bare", "hosted"]);
|
|
7061
6323
|
const FieldValue = z.union([
|
|
7062
6324
|
EnvString,
|
|
@@ -7085,6 +6347,7 @@ const SECTIONS = {
|
|
|
7085
6347
|
"first-name": z.string().optional(),
|
|
7086
6348
|
"last-name": z.string().optional()
|
|
7087
6349
|
}).optional(),
|
|
6350
|
+
"api-key": z.strictObject({ profile: MbProfileChoice }).optional(),
|
|
7088
6351
|
license: z.strictObject({ token: EnvString.nullable() }).optional(),
|
|
7089
6352
|
databases: z.array(DatabaseEntry).optional(),
|
|
7090
6353
|
"git-sync": z.strictObject({
|
|
@@ -7180,6 +6443,8 @@ function keyedAnswers(answers) {
|
|
|
7180
6443
|
const map = /* @__PURE__ */ new Map();
|
|
7181
6444
|
const admin = answers["admin-user"];
|
|
7182
6445
|
if (admin !== void 0) addAdmin(map, admin);
|
|
6446
|
+
const apiKey = answers["api-key"];
|
|
6447
|
+
if (apiKey !== void 0) add(map, API_KEY_KEYS.profile, select$1(apiKey.profile));
|
|
7183
6448
|
const license = answers.license;
|
|
7184
6449
|
if (license !== void 0) add(map, LICENSE_KEYS.token, password$1(license.token ?? SKIP_LICENSE));
|
|
7185
6450
|
const databases = answers.databases;
|
|
@@ -7235,6 +6500,7 @@ const FLAG_FOR_KEY = {
|
|
|
7235
6500
|
[ADMIN_USER_KEYS.password]: "--admin-password",
|
|
7236
6501
|
[API_KEY_KEYS.email]: "--admin-email",
|
|
7237
6502
|
[API_KEY_KEYS.password]: "--admin-password",
|
|
6503
|
+
[API_KEY_KEYS.profile]: "--mb-profile",
|
|
7238
6504
|
[LICENSE_KEYS.token]: "--license-token or --skip-license",
|
|
7239
6505
|
[DATABASE_KEYS.engine]: "--database or --skip-databases",
|
|
7240
6506
|
[DATABASE_KEYS.uri]: "--database",
|
|
@@ -7288,7 +6554,7 @@ function parseDatabase(spec) {
|
|
|
7288
6554
|
function adminSection(flags, secrets) {
|
|
7289
6555
|
const prompted = flags.adminEmail !== void 0 || secrets.has("--admin-password");
|
|
7290
6556
|
if (flags.admin === void 0 && !prompted) return;
|
|
7291
|
-
if (flags.admin === "
|
|
6557
|
+
if (flags.admin === "default" && prompted) throw conflict("--admin default", "--admin-email/--admin-password");
|
|
7292
6558
|
return {
|
|
7293
6559
|
variant: flags.admin ?? "prompted",
|
|
7294
6560
|
...flags.adminEmail !== void 0 && { email: flags.adminEmail },
|
|
@@ -7391,7 +6657,8 @@ async function answersFromFlags(flags, readStdin) {
|
|
|
7391
6657
|
license: licenseSection(flags, secrets),
|
|
7392
6658
|
databases: databasesSection(flags),
|
|
7393
6659
|
"git-sync": gitSection(flags, secrets),
|
|
7394
|
-
"agents-skills": agentsSection(flags)
|
|
6660
|
+
"agents-skills": agentsSection(flags),
|
|
6661
|
+
"api-key": flags.mbProfile === void 0 ? void 0 : { profile: flags.mbProfile }
|
|
7395
6662
|
};
|
|
7396
6663
|
const present = Object.entries(sections).filter(([, section]) => section !== void 0);
|
|
7397
6664
|
return parseInitAnswers(Object.fromEntries(present), "the init flags");
|
|
@@ -7485,7 +6752,10 @@ function applyRecord(draft, record) {
|
|
|
7485
6752
|
if (key.startsWith("databases.")) applyDatabaseRecord(draft, record);
|
|
7486
6753
|
else if (key.startsWith("admin-user.")) applyAdminRecord(draft, key, value);
|
|
7487
6754
|
else if (key.startsWith("git-sync.")) applyGitRecord(draft, key, value);
|
|
7488
|
-
else if (key ===
|
|
6755
|
+
else if (key === API_KEY_KEYS.profile && value !== null) {
|
|
6756
|
+
const profile = MbProfileChoice.safeParse(value);
|
|
6757
|
+
if (profile.success) draft.answers["api-key"] = { profile: profile.data };
|
|
6758
|
+
} else if (key === LICENSE_KEYS.token && value !== null) draft.answers.license = { token: value.length === 0 ? null : envReference(ENV_LICENSE_TOKEN) };
|
|
7489
6759
|
}
|
|
7490
6760
|
function writableUri(uri, index) {
|
|
7491
6761
|
return URI_WITH_PASSWORD.test(uri) ? envReference(databaseEnv(index, "uri")) : uri;
|
|
@@ -7757,7 +7027,7 @@ const init = defineRdeCommand({
|
|
|
7757
7027
|
},
|
|
7758
7028
|
admin: {
|
|
7759
7029
|
type: "string",
|
|
7760
|
-
description: "Admin credentials:
|
|
7030
|
+
description: "Admin credentials: default (admin@metabase.com / metabase123) or prompted"
|
|
7761
7031
|
},
|
|
7762
7032
|
"admin-email": {
|
|
7763
7033
|
type: "string",
|
|
@@ -7815,6 +7085,10 @@ const init = defineRdeCommand({
|
|
|
7815
7085
|
description: "Install the skill for no agent",
|
|
7816
7086
|
default: false
|
|
7817
7087
|
},
|
|
7088
|
+
"mb-profile": {
|
|
7089
|
+
type: "string",
|
|
7090
|
+
description: "mb profile for this instance when `default` already points elsewhere: default or rde"
|
|
7091
|
+
},
|
|
7818
7092
|
yes: {
|
|
7819
7093
|
type: "boolean",
|
|
7820
7094
|
description: "Answer yes to every confirmation (skipping databases)",
|
|
@@ -7849,7 +7123,8 @@ const init = defineRdeCommand({
|
|
|
7849
7123
|
gitToken: args["git-token"],
|
|
7850
7124
|
skipGit: args["skip-git"],
|
|
7851
7125
|
agents: args.agents,
|
|
7852
|
-
skipAgents: args["skip-agents"]
|
|
7126
|
+
skipAgents: args["skip-agents"],
|
|
7127
|
+
mbProfile: args["mb-profile"]
|
|
7853
7128
|
}
|
|
7854
7129
|
};
|
|
7855
7130
|
const printTo = args["print-answers"];
|
|
@@ -7968,11 +7243,11 @@ function isNotServing(error) {
|
|
|
7968
7243
|
return error instanceof MetabaseUnreachableError || error instanceof MetabaseInitializingError;
|
|
7969
7244
|
}
|
|
7970
7245
|
async function databasesOf(ctx, deps, health) {
|
|
7971
|
-
if (health !== "healthy" || ctx.
|
|
7246
|
+
if (health !== "healthy" || ctx.state.apiKey === null) return null;
|
|
7972
7247
|
try {
|
|
7973
|
-
return await listDatabases(ctx.state,
|
|
7248
|
+
return await listDatabases(ctx.state, { client: deps.client });
|
|
7974
7249
|
} catch (error) {
|
|
7975
|
-
if (isNotServing(error)
|
|
7250
|
+
if (isNotServing(error)) return null;
|
|
7976
7251
|
throw error;
|
|
7977
7252
|
}
|
|
7978
7253
|
}
|
|
@@ -8015,13 +7290,9 @@ async function collectStatus(ctx, deps) {
|
|
|
8015
7290
|
const runtime = configured === null ? null : await runtimeFromState(configured.runtime, deps.runtime).status();
|
|
8016
7291
|
const health = configured === null ? null : await healthOf(configured.url, deps);
|
|
8017
7292
|
const databases = health === null ? null : await databasesOf(ctx, deps, health);
|
|
8018
|
-
const admin = ctx.state.admin;
|
|
8019
7293
|
return {
|
|
8020
7294
|
instance: ctx.state.name,
|
|
8021
7295
|
url: configured?.url ?? null,
|
|
8022
|
-
mbProfile: ctx.state.mbProfile,
|
|
8023
|
-
adminEmail: admin === null ? null : admin.email,
|
|
8024
|
-
adminVariant: admin === null ? null : admin.kind,
|
|
8025
7296
|
runtime: configured === null || runtime === null ? null : {
|
|
8026
7297
|
kind: configured.runtime.kind,
|
|
8027
7298
|
installed: runtime.installed,
|
|
@@ -8075,8 +7346,6 @@ function databaseLines(report) {
|
|
|
8075
7346
|
function statusLines(report) {
|
|
8076
7347
|
return [
|
|
8077
7348
|
report.url === null ? `Instance ${report.instance}` : `Instance ${report.instance} — ${report.url}`,
|
|
8078
|
-
...report.adminVariant === null || report.adminEmail === null ? [] : [loginLine(report.adminVariant, report.adminEmail)],
|
|
8079
|
-
`Metabase CLI: ${mbCommand(report.mbProfile)} <command>`,
|
|
8080
7349
|
runtimeLine(report),
|
|
8081
7350
|
versionLine(report),
|
|
8082
7351
|
`Health: ${report.health ?? "unknown"}`,
|
|
@@ -8379,7 +7648,7 @@ async function planUninstall(options, deps) {
|
|
|
8379
7648
|
if (jar.removal !== null) removals.push(jar.removal);
|
|
8380
7649
|
if (jar.kept !== null) kept.push(jar.kept);
|
|
8381
7650
|
}
|
|
8382
|
-
const profiles = state === null ? await findMbProfiles([DEFAULT_MB_PROFILE,
|
|
7651
|
+
const profiles = state === null ? await findMbProfiles([DEFAULT_MB_PROFILE, "rde"], null, deps.runner) : await findMbProfiles([state.mbProfile], state.url, deps.runner);
|
|
8383
7652
|
removals.push(...profiles);
|
|
8384
7653
|
if (options.removeSkill) {
|
|
8385
7654
|
const skill = await findSkill(deps.runner);
|
|
@@ -8388,11 +7657,6 @@ async function planUninstall(options, deps) {
|
|
|
8388
7657
|
const dir = instanceDir(options.instance);
|
|
8389
7658
|
const dirPresent = await pathExists(dir);
|
|
8390
7659
|
if (!options.keepData) {
|
|
8391
|
-
const secrets = instanceSecretsPath(options.instance);
|
|
8392
|
-
if (await pathExists(secrets)) removals.push({
|
|
8393
|
-
label: `Stored credentials ${secrets} (the admin password and the API key)`,
|
|
8394
|
-
remove: () => promises.rm(secrets, { force: true })
|
|
8395
|
-
});
|
|
8396
7660
|
if (dirPresent) removals.push({
|
|
8397
7661
|
label: instanceDirLabel(dir, state),
|
|
8398
7662
|
remove: () => promises.rm(dir, {
|
|
@@ -8794,7 +8058,7 @@ const upgradeCommand = defineRdeCommand({
|
|
|
8794
8058
|
const main = defineCommand({
|
|
8795
8059
|
meta: {
|
|
8796
8060
|
name: "rde",
|
|
8797
|
-
version: "0.1.3
|
|
8061
|
+
version: "0.1.3",
|
|
8798
8062
|
description: "Prepares a local machine for data-engineering work with Metabase and coding agents"
|
|
8799
8063
|
},
|
|
8800
8064
|
subCommands: {
|
|
@@ -8802,7 +8066,6 @@ const main = defineCommand({
|
|
|
8802
8066
|
start,
|
|
8803
8067
|
stop,
|
|
8804
8068
|
status,
|
|
8805
|
-
credentials,
|
|
8806
8069
|
logs,
|
|
8807
8070
|
open,
|
|
8808
8071
|
db,
|