@metabase/rde 0.1.4 → 0.1.6
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 +3 -3
- package/dist/cli.mjs +265 -205
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ The wizard shows its checklist first (`✓` done, `○` to do, `–` not applica
|
|
|
44
44
|
● Running npm install -g @metabase/cli@alpha-fix-transform-test-ee-path
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
The steps up to the license run without a question: `mb` is installed with the package manager that installed `rde`, the skill goes to
|
|
47
|
+
The steps up to the license run without a question, save one: `mb` is installed with the package manager that installed `rde`, the skill goes to the coding agents you pick from the ones found on the machine (at a terminal; without one, to every agent found), Metabase runs in Docker when Docker is there (the jar with your Java otherwise), on the first free port from 3100 up, with an admin user whose password is generated and kept in `~/.rde`, `mb` is logged in under its `default` profile (the one question here comes when `default` already points at another Metabase), and a Postgres sample database comes up in a second container, connected to Metabase as a place transforms can write. Each of those choices is printed as it is made and can be overridden with a flag. The prompts that remain are the license token (Enter skips it), a connection string for your own database (Enter skips it), and, with a license that includes remote sync, where the git repository lives and which remote it uses.
|
|
48
48
|
|
|
49
49
|
The databases step asks once. At a terminal, without `--yes` or `--skip-databases`, and while none of your own databases is connected, it lists what is connected (the sample database) and asks for a connection string: paste one and `rde` reads the engine from it (or asks which, when the string fits several, or shows the engine list when it fits none), then shows the connection for review, tests it, adds it, and offers another. Enter skips it, and the skip is remembered, so the next `rde init` does not ask again; `rde init --only databases` and `rde db add` still do. With `--yes` or without a terminal it asks nothing and the sample database stands in; `--database` connects yours ahead of time.
|
|
50
50
|
|
|
@@ -54,7 +54,7 @@ The git step reads `–` until a license with remote sync is active; it is check
|
|
|
54
54
|
| --- | --- |
|
|
55
55
|
| Check this machine | Platform, Node version, git, and a reachable Docker daemon or a Java 21+ runtime |
|
|
56
56
|
| Install the Metabase CLI | `@metabase/cli` (`mb`) at the `alpha-fix-transform-test-ee-path` dist-tag, with the package manager that installed `rde` |
|
|
57
|
-
| Install the rde skill | The `rde` skill from the `rde-skill` branch of `metabase/agent-skills` for
|
|
57
|
+
| Install the rde skill | The `rde` skill from the `rde-skill` branch of `metabase/agent-skills` for the coding agents you pick at a terminal, from the ones found on this machine (Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Windsurf) plus OpenCode, which is always offered: picked while missing, it is installed first (`opencode-ai` with the package manager that installed `rde`). Without a terminal, or with `--yes`, every agent found gets the skill; `--agents` picks a set ahead of time (`opencode` in it is installed when missing), `--skip-agents` none; `RDE_SKILL_SOURCE` installs it from another URL or a local directory |
|
|
58
58
|
| Install Metabase | **Docker** when it is available: pulls `metabase/metabase-enterprise-head:latest` (or `--image`) and creates the container. **Jar** otherwise, or with `--runtime jar`: downloads the latest enterprise release (or the tag or `.jar` path given as `--jar`) into `~/.rde/jars/` and runs it with your Java. Either way on port 3100, or the first free port above it, or `--port` |
|
|
59
59
|
| Start Metabase | Starts the container or the jar and waits for `/api/health`, showing Metabase's own progress |
|
|
60
60
|
| Create the admin user | `admin@metabase.local` with a generated password that `rde credentials` shows, or your own with `--admin prompted`, see [Credentials](#credentials) |
|
|
@@ -219,7 +219,7 @@ rde init --skip-license \
|
|
|
219
219
|
|
|
220
220
|
| Flag | Answers |
|
|
221
221
|
| --- | --- |
|
|
222
|
-
| `--agents claude-code,codex` / `--skip-agents` | Which agents get the skill, instead of the ones found on the machine |
|
|
222
|
+
| `--agents claude-code,codex` / `--skip-agents` | Which agents get the skill, instead of asking at a terminal or taking the ones found on the machine; a missing `opencode` is installed |
|
|
223
223
|
| `--runtime docker\|jar`, `--image`, `--jar`, `--port` | How Metabase runs, the Docker image or the jar (`latest`, a release tag, or a `.jar` path), and the host port, instead of Docker when available, `metabase/metabase-enterprise-head:latest`, the latest release, and the first free port from 3100; `--image` implies `docker` and `--jar` implies `jar` |
|
|
224
224
|
| `--admin generated\|prompted`, `--admin-email`, `--admin-password` | The admin user, `generated` unless given; an email or password implies `prompted` |
|
|
225
225
|
| `--license-token` / `--skip-license` | The license |
|
package/dist/cli.mjs
CHANGED
|
@@ -2229,7 +2229,6 @@ async function chooseAfterParseError(run, engine, uri, error) {
|
|
|
2229
2229
|
}
|
|
2230
2230
|
function toReview(run, model, connection) {
|
|
2231
2231
|
const mapped = mapDatabaseValues(connection.parsed);
|
|
2232
|
-
const aliased = applyHostAlias(mapped.details, connection.engine.key, run.deps.hostAlias);
|
|
2233
2232
|
const sameString = model.uri === connection.parsed.uri;
|
|
2234
2233
|
return {
|
|
2235
2234
|
type: "next",
|
|
@@ -2237,8 +2236,8 @@ function toReview(run, model, connection) {
|
|
|
2237
2236
|
model: {
|
|
2238
2237
|
engine: connection.engine,
|
|
2239
2238
|
uri: connection.parsed.uri,
|
|
2240
|
-
details:
|
|
2241
|
-
hostRewritten:
|
|
2239
|
+
details: mapped.details,
|
|
2240
|
+
hostRewritten: false,
|
|
2242
2241
|
suggestedName: defaultDisplayName(mapped, connection.parsed, connection.engine),
|
|
2243
2242
|
name: sameString ? model.name : null,
|
|
2244
2243
|
fieldErrors: null
|
|
@@ -2359,11 +2358,15 @@ function reviewScreen(run) {
|
|
|
2359
2358
|
let details = model.details;
|
|
2360
2359
|
if (model.fieldErrors !== null) details = await fixReportedFields(run, chosen.fields, details, model.fieldErrors);
|
|
2361
2360
|
let name = model.name;
|
|
2361
|
+
let hostRewritten = model.hostRewritten;
|
|
2362
2362
|
for (;;) {
|
|
2363
|
-
|
|
2363
|
+
const aliased = applyHostAlias(await fillRequiredFields(run.deps, chosen.fields, details), chosen.engine.key, run.deps.hostAlias);
|
|
2364
|
+
details = aliased.details;
|
|
2365
|
+
hostRewritten ||= aliased.rewritten;
|
|
2364
2366
|
const current = {
|
|
2365
2367
|
...model,
|
|
2366
2368
|
details,
|
|
2369
|
+
hostRewritten,
|
|
2367
2370
|
name,
|
|
2368
2371
|
fieldErrors: null
|
|
2369
2372
|
};
|
|
@@ -3847,134 +3850,36 @@ function sampleDatabaseContainer(state, docker) {
|
|
|
3847
3850
|
};
|
|
3848
3851
|
}
|
|
3849
3852
|
//#endregion
|
|
3850
|
-
//#region src/tools/agents.ts
|
|
3851
|
-
const AgentId = z.enum([
|
|
3852
|
-
"claude-code",
|
|
3853
|
-
"codex",
|
|
3854
|
-
"cursor",
|
|
3855
|
-
"gemini-cli",
|
|
3856
|
-
"opencode",
|
|
3857
|
-
"windsurf"
|
|
3858
|
-
]);
|
|
3859
|
-
const AGENTS = [
|
|
3860
|
-
{
|
|
3861
|
-
id: "claude-code",
|
|
3862
|
-
displayName: "Claude Code",
|
|
3863
|
-
skillsCliName: "claude-code",
|
|
3864
|
-
command: "claude",
|
|
3865
|
-
launch: "claude",
|
|
3866
|
-
configDir: (home) => join(home, ".claude")
|
|
3867
|
-
},
|
|
3868
|
-
{
|
|
3869
|
-
id: "codex",
|
|
3870
|
-
displayName: "Codex",
|
|
3871
|
-
skillsCliName: "codex",
|
|
3872
|
-
command: "codex",
|
|
3873
|
-
launch: "codex",
|
|
3874
|
-
configDir: (home) => join(home, ".codex")
|
|
3875
|
-
},
|
|
3876
|
-
{
|
|
3877
|
-
id: "cursor",
|
|
3878
|
-
displayName: "Cursor",
|
|
3879
|
-
skillsCliName: "cursor",
|
|
3880
|
-
command: "cursor",
|
|
3881
|
-
launch: "cursor .",
|
|
3882
|
-
configDir: (home) => join(home, ".cursor")
|
|
3883
|
-
},
|
|
3884
|
-
{
|
|
3885
|
-
id: "gemini-cli",
|
|
3886
|
-
displayName: "Gemini CLI",
|
|
3887
|
-
skillsCliName: "gemini-cli",
|
|
3888
|
-
command: "gemini",
|
|
3889
|
-
launch: "gemini",
|
|
3890
|
-
configDir: (home) => join(home, ".gemini")
|
|
3891
|
-
},
|
|
3892
|
-
{
|
|
3893
|
-
id: "opencode",
|
|
3894
|
-
displayName: "OpenCode",
|
|
3895
|
-
skillsCliName: "opencode",
|
|
3896
|
-
command: "opencode",
|
|
3897
|
-
launch: "opencode",
|
|
3898
|
-
configDir: (_home, configHome) => join(configHome, "opencode")
|
|
3899
|
-
},
|
|
3900
|
-
{
|
|
3901
|
-
id: "windsurf",
|
|
3902
|
-
displayName: "Windsurf",
|
|
3903
|
-
skillsCliName: "windsurf",
|
|
3904
|
-
command: null,
|
|
3905
|
-
launch: "windsurf .",
|
|
3906
|
-
configDir: (home) => join(home, ".codeium", "windsurf")
|
|
3907
|
-
}
|
|
3908
|
-
];
|
|
3909
|
-
async function directoryExists$1(path) {
|
|
3910
|
-
try {
|
|
3911
|
-
return (await promises.stat(path)).isDirectory();
|
|
3912
|
-
} catch {
|
|
3913
|
-
return false;
|
|
3914
|
-
}
|
|
3915
|
-
}
|
|
3916
|
-
function hostAgentProbe() {
|
|
3917
|
-
const home = homedir();
|
|
3918
|
-
return {
|
|
3919
|
-
commandExists: async (command) => await whichCommand(command) !== null,
|
|
3920
|
-
directoryExists: directoryExists$1,
|
|
3921
|
-
home,
|
|
3922
|
-
configHome: readEnv("XDG_CONFIG_HOME") ?? join(home, ".config")
|
|
3923
|
-
};
|
|
3924
|
-
}
|
|
3925
|
-
async function isDetected(agent, probe) {
|
|
3926
|
-
if (agent.command !== null && await probe.commandExists(agent.command)) return true;
|
|
3927
|
-
return probe.directoryExists(agent.configDir(probe.home, probe.configHome));
|
|
3928
|
-
}
|
|
3929
|
-
async function detectAgents(probe) {
|
|
3930
|
-
return Promise.all(AGENTS.map(async (agent) => ({
|
|
3931
|
-
id: agent.id,
|
|
3932
|
-
displayName: agent.displayName,
|
|
3933
|
-
skillsCliName: agent.skillsCliName,
|
|
3934
|
-
launch: agent.launch,
|
|
3935
|
-
detected: await isDetected(agent, probe)
|
|
3936
|
-
})));
|
|
3937
|
-
}
|
|
3938
|
-
//#endregion
|
|
3939
3853
|
//#region src/tools/mb.ts
|
|
3940
3854
|
const MB = "mb";
|
|
3941
3855
|
const MB_INSTALL_SPEC = `@metabase/cli@alpha-fix-transform-test-ee-path`;
|
|
3942
3856
|
const GLOBAL_INSTALL = {
|
|
3943
3857
|
npm: {
|
|
3944
3858
|
command: "npm",
|
|
3945
|
-
args: [
|
|
3946
|
-
"install",
|
|
3947
|
-
"-g",
|
|
3948
|
-
MB_INSTALL_SPEC
|
|
3949
|
-
]
|
|
3859
|
+
args: ["install", "-g"]
|
|
3950
3860
|
},
|
|
3951
3861
|
pnpm: {
|
|
3952
3862
|
command: "pnpm",
|
|
3953
|
-
args: [
|
|
3954
|
-
"add",
|
|
3955
|
-
"-g",
|
|
3956
|
-
MB_INSTALL_SPEC
|
|
3957
|
-
]
|
|
3863
|
+
args: ["add", "-g"]
|
|
3958
3864
|
},
|
|
3959
3865
|
yarn: {
|
|
3960
3866
|
command: "yarn",
|
|
3961
|
-
args: [
|
|
3962
|
-
"global",
|
|
3963
|
-
"add",
|
|
3964
|
-
MB_INSTALL_SPEC
|
|
3965
|
-
]
|
|
3867
|
+
args: ["global", "add"]
|
|
3966
3868
|
},
|
|
3967
3869
|
bun: {
|
|
3968
3870
|
command: "bun",
|
|
3969
|
-
args: [
|
|
3970
|
-
"add",
|
|
3971
|
-
"-g",
|
|
3972
|
-
MB_INSTALL_SPEC
|
|
3973
|
-
]
|
|
3871
|
+
args: ["add", "-g"]
|
|
3974
3872
|
}
|
|
3975
3873
|
};
|
|
3874
|
+
function globalInstallCommand(packageManager, spec) {
|
|
3875
|
+
const install = GLOBAL_INSTALL[packageManager];
|
|
3876
|
+
return {
|
|
3877
|
+
command: install.command,
|
|
3878
|
+
args: [...install.args, spec]
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3976
3881
|
function mbInstallCommand(packageManager) {
|
|
3977
|
-
return
|
|
3882
|
+
return globalInstallCommand(packageManager, MB_INSTALL_SPEC);
|
|
3978
3883
|
}
|
|
3979
3884
|
function ownerByMarker(path, markers) {
|
|
3980
3885
|
const normalized = path.replaceAll("\\", "/");
|
|
@@ -4258,6 +4163,99 @@ function mbTool(runner) {
|
|
|
4258
4163
|
};
|
|
4259
4164
|
}
|
|
4260
4165
|
//#endregion
|
|
4166
|
+
//#region src/tools/agents.ts
|
|
4167
|
+
const AgentId = z.enum([
|
|
4168
|
+
"claude-code",
|
|
4169
|
+
"codex",
|
|
4170
|
+
"cursor",
|
|
4171
|
+
"gemini-cli",
|
|
4172
|
+
"opencode",
|
|
4173
|
+
"windsurf"
|
|
4174
|
+
]);
|
|
4175
|
+
const OPENCODE_PACKAGE = "opencode-ai@latest";
|
|
4176
|
+
function opencodeInstallCommand(packageManager) {
|
|
4177
|
+
return globalInstallCommand(packageManager, OPENCODE_PACKAGE);
|
|
4178
|
+
}
|
|
4179
|
+
const AGENTS = [
|
|
4180
|
+
{
|
|
4181
|
+
id: "claude-code",
|
|
4182
|
+
displayName: "Claude Code",
|
|
4183
|
+
skillsCliName: "claude-code",
|
|
4184
|
+
command: "claude",
|
|
4185
|
+
launch: "claude",
|
|
4186
|
+
configDir: (home) => join(home, ".claude")
|
|
4187
|
+
},
|
|
4188
|
+
{
|
|
4189
|
+
id: "codex",
|
|
4190
|
+
displayName: "Codex",
|
|
4191
|
+
skillsCliName: "codex",
|
|
4192
|
+
command: "codex",
|
|
4193
|
+
launch: "codex",
|
|
4194
|
+
configDir: (home) => join(home, ".codex")
|
|
4195
|
+
},
|
|
4196
|
+
{
|
|
4197
|
+
id: "cursor",
|
|
4198
|
+
displayName: "Cursor",
|
|
4199
|
+
skillsCliName: "cursor",
|
|
4200
|
+
command: "cursor",
|
|
4201
|
+
launch: "cursor .",
|
|
4202
|
+
configDir: (home) => join(home, ".cursor")
|
|
4203
|
+
},
|
|
4204
|
+
{
|
|
4205
|
+
id: "gemini-cli",
|
|
4206
|
+
displayName: "Gemini CLI",
|
|
4207
|
+
skillsCliName: "gemini-cli",
|
|
4208
|
+
command: "gemini",
|
|
4209
|
+
launch: "gemini",
|
|
4210
|
+
configDir: (home) => join(home, ".gemini")
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
id: "opencode",
|
|
4214
|
+
displayName: "OpenCode",
|
|
4215
|
+
skillsCliName: "opencode",
|
|
4216
|
+
command: "opencode",
|
|
4217
|
+
launch: "opencode",
|
|
4218
|
+
configDir: (_home, configHome) => join(configHome, "opencode")
|
|
4219
|
+
},
|
|
4220
|
+
{
|
|
4221
|
+
id: "windsurf",
|
|
4222
|
+
displayName: "Windsurf",
|
|
4223
|
+
skillsCliName: "windsurf",
|
|
4224
|
+
command: null,
|
|
4225
|
+
launch: "windsurf .",
|
|
4226
|
+
configDir: (home) => join(home, ".codeium", "windsurf")
|
|
4227
|
+
}
|
|
4228
|
+
];
|
|
4229
|
+
async function directoryExists$1(path) {
|
|
4230
|
+
try {
|
|
4231
|
+
return (await promises.stat(path)).isDirectory();
|
|
4232
|
+
} catch {
|
|
4233
|
+
return false;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
function hostAgentProbe() {
|
|
4237
|
+
const home = homedir();
|
|
4238
|
+
return {
|
|
4239
|
+
commandExists: async (command) => await whichCommand(command) !== null,
|
|
4240
|
+
directoryExists: directoryExists$1,
|
|
4241
|
+
home,
|
|
4242
|
+
configHome: readEnv("XDG_CONFIG_HOME") ?? join(home, ".config")
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
async function isDetected(agent, probe) {
|
|
4246
|
+
if (agent.command !== null && await probe.commandExists(agent.command)) return true;
|
|
4247
|
+
return probe.directoryExists(agent.configDir(probe.home, probe.configHome));
|
|
4248
|
+
}
|
|
4249
|
+
async function detectAgents(probe) {
|
|
4250
|
+
return Promise.all(AGENTS.map(async (agent) => ({
|
|
4251
|
+
id: agent.id,
|
|
4252
|
+
displayName: agent.displayName,
|
|
4253
|
+
skillsCliName: agent.skillsCliName,
|
|
4254
|
+
launch: agent.launch,
|
|
4255
|
+
detected: await isDetected(agent, probe)
|
|
4256
|
+
})));
|
|
4257
|
+
}
|
|
4258
|
+
//#endregion
|
|
4261
4259
|
//#region src/tools/skills-cli.ts
|
|
4262
4260
|
const NPX = "npx";
|
|
4263
4261
|
const SKILLS_PACKAGE = "skills@latest";
|
|
@@ -4445,92 +4443,8 @@ async function runWizard(steps, ctx, options = {}) {
|
|
|
4445
4443
|
}
|
|
4446
4444
|
}
|
|
4447
4445
|
//#endregion
|
|
4448
|
-
//#region src/steps/agents-skills.ts
|
|
4449
|
-
const STEP_ID$8 = "agents-skills";
|
|
4450
|
-
const MECHANICS_NOTE = "The skill's mechanics come from `mb skills get <name>` at runtime, so nothing else needs installing.";
|
|
4451
|
-
const DETECT_AGENTS = { agents: null };
|
|
4452
|
-
function hostDeps$11(ctx) {
|
|
4453
|
-
return {
|
|
4454
|
-
runner: processRunner(ctx.signal),
|
|
4455
|
-
probe: hostAgentProbe(),
|
|
4456
|
-
skillSource: rdeSkillSource()
|
|
4457
|
-
};
|
|
4458
|
-
}
|
|
4459
|
-
function detectedOnly(agents) {
|
|
4460
|
-
return agents.filter((agent) => agent.detected);
|
|
4461
|
-
}
|
|
4462
|
-
function displayNames(agents) {
|
|
4463
|
-
return agents.map((agent) => agent.displayName).join(", ");
|
|
4464
|
-
}
|
|
4465
|
-
function skillCoversAgent(entry, agent) {
|
|
4466
|
-
return entry.agents.includes(agent.displayName);
|
|
4467
|
-
}
|
|
4468
|
-
function coversAny(entry, agents) {
|
|
4469
|
-
return agents.some((agent) => skillCoversAgent(entry, agent));
|
|
4470
|
-
}
|
|
4471
|
-
async function rdeSkillEntries(runner) {
|
|
4472
|
-
return (await skillsCli(runner).list()).filter((entry) => entry.name === "rde");
|
|
4473
|
-
}
|
|
4474
|
-
function installedLines(entries) {
|
|
4475
|
-
return entries.map((entry) => `${entry.agents.join(", ")}: ${entry.path}`).join("\n");
|
|
4476
|
-
}
|
|
4477
|
-
const SOURCE_NOTES = {
|
|
4478
|
-
override: "from --agents",
|
|
4479
|
-
detected: "detected on this machine"
|
|
4480
|
-
};
|
|
4481
|
-
function selectAgents(all, options) {
|
|
4482
|
-
if (options.agents === null) return {
|
|
4483
|
-
agents: detectedOnly(all),
|
|
4484
|
-
source: "detected"
|
|
4485
|
-
};
|
|
4486
|
-
const chosen = options.agents;
|
|
4487
|
-
return {
|
|
4488
|
-
agents: all.filter((agent) => chosen.includes(agent.id)),
|
|
4489
|
-
source: "override"
|
|
4490
|
-
};
|
|
4491
|
-
}
|
|
4492
|
-
function skipLine(all, source) {
|
|
4493
|
-
switch (source) {
|
|
4494
|
-
case "override": return `Skipping the rde skill; run \`rde init --only ${STEP_ID$8}\` to install it later`;
|
|
4495
|
-
case "detected": return `No coding agent found (${displayNames(all)}); run \`rde init --only ${STEP_ID$8} --agents <name>\` once one is installed`;
|
|
4496
|
-
}
|
|
4497
|
-
}
|
|
4498
|
-
function agentsSkillsStep(options, deps = hostDeps$11) {
|
|
4499
|
-
return {
|
|
4500
|
-
id: STEP_ID$8,
|
|
4501
|
-
title: "Install the rde skill for your coding agents",
|
|
4502
|
-
async detect(ctx) {
|
|
4503
|
-
const resolved = deps(ctx);
|
|
4504
|
-
const detected = detectedOnly(await detectAgents(resolved.probe));
|
|
4505
|
-
if (detected.length === 0) return ctx.state.steps[STEP_ID$8]?.status === "skipped" ? "not-applicable" : "todo";
|
|
4506
|
-
return (await rdeSkillEntries(resolved.runner)).some((entry) => coversAny(entry, detected)) ? "done" : "todo";
|
|
4507
|
-
},
|
|
4508
|
-
async run(ctx) {
|
|
4509
|
-
const resolved = deps(ctx);
|
|
4510
|
-
const all = await detectAgents(resolved.probe);
|
|
4511
|
-
const selection = selectAgents(all, options);
|
|
4512
|
-
if (selection.agents.length === 0) {
|
|
4513
|
-
ctx.ui.info(skipLine(all, selection.source));
|
|
4514
|
-
return "skipped";
|
|
4515
|
-
}
|
|
4516
|
-
const from = resolved.skillSource === "https://github.com/metabase/agent-skills/tree/rde-skill" ? "" : ` from ${resolved.skillSource}`;
|
|
4517
|
-
ctx.ui.info(`Installing the rde skill for ${displayNames(selection.agents)} (${SOURCE_NOTES[selection.source]})${from}`);
|
|
4518
|
-
await skillsCli(resolved.runner).add({
|
|
4519
|
-
source: resolved.skillSource,
|
|
4520
|
-
skill: "rde",
|
|
4521
|
-
agents: selection.agents.map((agent) => agent.skillsCliName)
|
|
4522
|
-
});
|
|
4523
|
-
const entries = await rdeSkillEntries(resolved.runner);
|
|
4524
|
-
if (entries.length === 0) throw new ConfigError(`the skills installer finished but \`skills list\` does not show the rde skill`);
|
|
4525
|
-
ctx.ui.note(installedLines(entries), "rde skill installed");
|
|
4526
|
-
ctx.ui.info(MECHANICS_NOTE);
|
|
4527
|
-
return "completed";
|
|
4528
|
-
}
|
|
4529
|
-
};
|
|
4530
|
-
}
|
|
4531
|
-
//#endregion
|
|
4532
4446
|
//#region src/steps/install-mb.ts
|
|
4533
|
-
const STEP_ID$
|
|
4447
|
+
const STEP_ID$8 = "install-mb";
|
|
4534
4448
|
const MIN_MB_VERSION = "0.3.0";
|
|
4535
4449
|
const MB_RANGE = `>=${MIN_MB_VERSION}`;
|
|
4536
4450
|
function installedWith(scriptPath) {
|
|
@@ -4539,7 +4453,7 @@ function installedWith(scriptPath) {
|
|
|
4539
4453
|
function rdeInstalledWith() {
|
|
4540
4454
|
return installedWith(process.argv[1]);
|
|
4541
4455
|
}
|
|
4542
|
-
function hostDeps$
|
|
4456
|
+
function hostDeps$11(ctx) {
|
|
4543
4457
|
return {
|
|
4544
4458
|
runner: processRunner(ctx.signal),
|
|
4545
4459
|
installedWith: rdeInstalledWith(),
|
|
@@ -4655,9 +4569,9 @@ async function stillWrong(install, after, deps) {
|
|
|
4655
4569
|
`${stillWrongAdvice(diagnosis)}.`
|
|
4656
4570
|
].join(" "));
|
|
4657
4571
|
}
|
|
4658
|
-
function installMbStep(deps = hostDeps$
|
|
4572
|
+
function installMbStep(deps = hostDeps$11) {
|
|
4659
4573
|
return {
|
|
4660
|
-
id: STEP_ID$
|
|
4574
|
+
id: STEP_ID$8,
|
|
4661
4575
|
title: "Install the Metabase CLI",
|
|
4662
4576
|
async detect(ctx) {
|
|
4663
4577
|
const found = await inspectMb(deps(ctx).runner);
|
|
@@ -4688,6 +4602,138 @@ function installMbStep(deps = hostDeps$10) {
|
|
|
4688
4602
|
};
|
|
4689
4603
|
}
|
|
4690
4604
|
//#endregion
|
|
4605
|
+
//#region src/steps/agents-skills.ts
|
|
4606
|
+
const STEP_ID$7 = "agents-skills";
|
|
4607
|
+
const AGENTS_SKILLS_KEYS = { agents: `${STEP_ID$7}.agents` };
|
|
4608
|
+
const MECHANICS_NOTE = "The skill's mechanics come from `mb skills get <name>` at runtime, so nothing else needs installing.";
|
|
4609
|
+
const DETECT_AGENTS = { kind: "detected" };
|
|
4610
|
+
function hostDeps$10(ctx) {
|
|
4611
|
+
return {
|
|
4612
|
+
runner: processRunner(ctx.signal),
|
|
4613
|
+
probe: hostAgentProbe(),
|
|
4614
|
+
skillSource: rdeSkillSource(),
|
|
4615
|
+
installedWith: rdeInstalledWith()
|
|
4616
|
+
};
|
|
4617
|
+
}
|
|
4618
|
+
function detectedOnly(agents) {
|
|
4619
|
+
return agents.filter((agent) => agent.detected);
|
|
4620
|
+
}
|
|
4621
|
+
function displayNames(agents) {
|
|
4622
|
+
return agents.map((agent) => agent.displayName).join(", ");
|
|
4623
|
+
}
|
|
4624
|
+
function skillCoversAgent(entry, agent) {
|
|
4625
|
+
return entry.agents.includes(agent.displayName);
|
|
4626
|
+
}
|
|
4627
|
+
function coversAny(entry, agents) {
|
|
4628
|
+
return agents.some((agent) => skillCoversAgent(entry, agent));
|
|
4629
|
+
}
|
|
4630
|
+
async function rdeSkillEntries(runner) {
|
|
4631
|
+
return (await skillsCli(runner).list()).filter((entry) => entry.name === "rde");
|
|
4632
|
+
}
|
|
4633
|
+
function installedLines(entries) {
|
|
4634
|
+
return entries.map((entry) => `${entry.agents.join(", ")}: ${entry.path}`).join("\n");
|
|
4635
|
+
}
|
|
4636
|
+
const SOURCE_NOTES = {
|
|
4637
|
+
override: "from --agents",
|
|
4638
|
+
detected: "detected on this machine",
|
|
4639
|
+
chosen: "as chosen"
|
|
4640
|
+
};
|
|
4641
|
+
const MISSING_OPENCODE_HINT = "not installed; rde installs it";
|
|
4642
|
+
function isMissingOpencode(agent) {
|
|
4643
|
+
return agent.id === "opencode" && !agent.detected;
|
|
4644
|
+
}
|
|
4645
|
+
function offeredChoice(agent) {
|
|
4646
|
+
return {
|
|
4647
|
+
value: agent.id,
|
|
4648
|
+
label: agent.displayName,
|
|
4649
|
+
...isMissingOpencode(agent) && { hint: MISSING_OPENCODE_HINT }
|
|
4650
|
+
};
|
|
4651
|
+
}
|
|
4652
|
+
async function askAgents(ctx, all) {
|
|
4653
|
+
const offered = all.filter((agent) => agent.detected || agent.id === "opencode");
|
|
4654
|
+
const picked = await ctx.prompts.multiselect({
|
|
4655
|
+
key: AGENTS_SKILLS_KEYS.agents,
|
|
4656
|
+
message: "Which coding agents should get the rde skill?",
|
|
4657
|
+
choices: offered.map(offeredChoice),
|
|
4658
|
+
initialValues: detectedOnly(all).map((agent) => agent.id),
|
|
4659
|
+
required: false
|
|
4660
|
+
});
|
|
4661
|
+
return offered.filter((agent) => picked.includes(agent.id));
|
|
4662
|
+
}
|
|
4663
|
+
async function selectAgents(ctx, all, options) {
|
|
4664
|
+
switch (options.kind) {
|
|
4665
|
+
case "detected": return {
|
|
4666
|
+
agents: detectedOnly(all),
|
|
4667
|
+
source: "detected"
|
|
4668
|
+
};
|
|
4669
|
+
case "listed": {
|
|
4670
|
+
const listed = options.agents;
|
|
4671
|
+
return {
|
|
4672
|
+
agents: all.filter((agent) => listed.includes(agent.id)),
|
|
4673
|
+
source: "override"
|
|
4674
|
+
};
|
|
4675
|
+
}
|
|
4676
|
+
case "ask": return {
|
|
4677
|
+
agents: await askAgents(ctx, all),
|
|
4678
|
+
source: "chosen"
|
|
4679
|
+
};
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
async function installOpencode(ctx, deps) {
|
|
4683
|
+
const install = opencodeInstallCommand(deps.installedWith);
|
|
4684
|
+
const commandLine = [install.command, ...install.args].join(" ");
|
|
4685
|
+
ctx.ui.info(`Running ${commandLine}`);
|
|
4686
|
+
const exitCode = await deps.runner.stream(install.command, install.args);
|
|
4687
|
+
if (exitCode !== 0) throw new ConfigError(`\`${commandLine}\` exited with ${exitCode}`);
|
|
4688
|
+
if (!(await detectAgents(deps.probe)).some((agent) => agent.id === "opencode" && agent.detected)) throw new ConfigError("OpenCode was installed but `opencode` is not on PATH; open a new terminal and run rde init again");
|
|
4689
|
+
}
|
|
4690
|
+
function listsMissingOpencode(all, options) {
|
|
4691
|
+
return options.kind === "listed" && options.agents.includes("opencode") && all.some(isMissingOpencode);
|
|
4692
|
+
}
|
|
4693
|
+
function skipLine(all, source) {
|
|
4694
|
+
switch (source) {
|
|
4695
|
+
case "override":
|
|
4696
|
+
case "chosen": return `Skipping the rde skill; run \`rde init --only ${STEP_ID$7}\` to install it later`;
|
|
4697
|
+
case "detected": return `No coding agent found (${displayNames(all)}); run \`rde init --only ${STEP_ID$7} --agents <name>\` once one is installed`;
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
function agentsSkillsStep(options, deps = hostDeps$10) {
|
|
4701
|
+
return {
|
|
4702
|
+
id: STEP_ID$7,
|
|
4703
|
+
title: "Install the rde skill for your coding agents",
|
|
4704
|
+
async detect(ctx) {
|
|
4705
|
+
const resolved = deps(ctx);
|
|
4706
|
+
const all = await detectAgents(resolved.probe);
|
|
4707
|
+
if (listsMissingOpencode(all, options)) return "todo";
|
|
4708
|
+
const detected = detectedOnly(all);
|
|
4709
|
+
if (detected.length === 0) return ctx.state.steps[STEP_ID$7]?.status === "skipped" ? "not-applicable" : "todo";
|
|
4710
|
+
return (await rdeSkillEntries(resolved.runner)).some((entry) => coversAny(entry, detected)) ? "done" : "todo";
|
|
4711
|
+
},
|
|
4712
|
+
async run(ctx) {
|
|
4713
|
+
const resolved = deps(ctx);
|
|
4714
|
+
const all = await detectAgents(resolved.probe);
|
|
4715
|
+
const selection = await selectAgents(ctx, all, options);
|
|
4716
|
+
if (selection.agents.length === 0) {
|
|
4717
|
+
ctx.ui.info(skipLine(all, selection.source));
|
|
4718
|
+
return "skipped";
|
|
4719
|
+
}
|
|
4720
|
+
if (selection.agents.some(isMissingOpencode)) await installOpencode(ctx, resolved);
|
|
4721
|
+
const from = resolved.skillSource === "https://github.com/metabase/agent-skills/tree/rde-skill" ? "" : ` from ${resolved.skillSource}`;
|
|
4722
|
+
ctx.ui.info(`Installing the rde skill for ${displayNames(selection.agents)} (${SOURCE_NOTES[selection.source]})${from}`);
|
|
4723
|
+
await skillsCli(resolved.runner).add({
|
|
4724
|
+
source: resolved.skillSource,
|
|
4725
|
+
skill: "rde",
|
|
4726
|
+
agents: selection.agents.map((agent) => agent.skillsCliName)
|
|
4727
|
+
});
|
|
4728
|
+
const entries = await rdeSkillEntries(resolved.runner);
|
|
4729
|
+
if (entries.length === 0) throw new ConfigError(`the skills installer finished but \`skills list\` does not show the rde skill`);
|
|
4730
|
+
ctx.ui.note(installedLines(entries), "rde skill installed");
|
|
4731
|
+
ctx.ui.info(MECHANICS_NOTE);
|
|
4732
|
+
return "completed";
|
|
4733
|
+
}
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4736
|
+
//#endregion
|
|
4691
4737
|
//#region src/tools/git.ts
|
|
4692
4738
|
const GIT = "git";
|
|
4693
4739
|
const FALLBACK_IDENTITY_NAME = "rde";
|
|
@@ -7446,6 +7492,13 @@ function databasesRequest(databases, run) {
|
|
|
7446
7492
|
if (databases !== void 0) return databases.length > 0 ? "named" : "sample-stands-in";
|
|
7447
7493
|
return run.interactive ? "ask-once" : "sample-stands-in";
|
|
7448
7494
|
}
|
|
7495
|
+
function agentsOption(agents, run) {
|
|
7496
|
+
if (agents !== void 0) return {
|
|
7497
|
+
kind: "listed",
|
|
7498
|
+
agents: agents.agents
|
|
7499
|
+
};
|
|
7500
|
+
return run.interactive ? { kind: "ask" } : DEFAULT_STEP_OPTIONS.agents;
|
|
7501
|
+
}
|
|
7449
7502
|
function stepOptionsFrom(answers, run) {
|
|
7450
7503
|
const runtime = answers.runtime;
|
|
7451
7504
|
const agents = answers["agents-skills"];
|
|
@@ -7458,7 +7511,7 @@ function stepOptionsFrom(answers, run) {
|
|
|
7458
7511
|
jar: runtime.jar ?? null,
|
|
7459
7512
|
port: runtime.port ?? null
|
|
7460
7513
|
},
|
|
7461
|
-
agents: agents
|
|
7514
|
+
agents: agentsOption(agents, run),
|
|
7462
7515
|
admin: admin === void 0 ? DEFAULT_STEP_OPTIONS.admin : { variant: admin.variant },
|
|
7463
7516
|
databases: { request: databasesRequest(databases, run) }
|
|
7464
7517
|
};
|
|
@@ -7807,12 +7860,19 @@ function applyGitRecord(draft, key, value) {
|
|
|
7807
7860
|
else if (key === GIT_SYNC_KEYS.url && value !== null) gitSync(draft).url = value;
|
|
7808
7861
|
else if (key === GIT_SYNC_KEYS.token) gitSync(draft).token = envReference(ENV_GIT_TOKEN);
|
|
7809
7862
|
}
|
|
7863
|
+
function agentIds(values) {
|
|
7864
|
+
return values.flatMap((value) => {
|
|
7865
|
+
const parsed = AgentId.safeParse(value);
|
|
7866
|
+
return parsed.success ? [parsed.data] : [];
|
|
7867
|
+
});
|
|
7868
|
+
}
|
|
7810
7869
|
function applyRecord(draft, record) {
|
|
7811
7870
|
const { key } = record;
|
|
7812
7871
|
const value = stringOf(record.answer);
|
|
7813
7872
|
if (key.startsWith("databases.")) applyDatabaseRecord(draft, record);
|
|
7814
7873
|
else if (key.startsWith("admin-user.")) applyAdminRecord(draft, key, value);
|
|
7815
7874
|
else if (key.startsWith("git-sync.")) applyGitRecord(draft, key, value);
|
|
7875
|
+
else if (key === AGENTS_SKILLS_KEYS.agents && record.answer.kind === "multiselect") draft.answers["agents-skills"] = { agents: agentIds(record.answer.values) };
|
|
7816
7876
|
else if (key === API_KEY_KEYS.profile && value !== null) {
|
|
7817
7877
|
const profile = MbProfileChoice.safeParse(value);
|
|
7818
7878
|
if (profile.success) draft.answers["api-key"] = { profile: profile.data };
|
|
@@ -8031,7 +8091,7 @@ const init = defineRdeCommand({
|
|
|
8031
8091
|
},
|
|
8032
8092
|
agents: {
|
|
8033
8093
|
type: "string",
|
|
8034
|
-
description: "Agents to install the skill for, comma-separated; the detected ones
|
|
8094
|
+
description: "Agents to install the skill for, comma-separated (a missing opencode is installed); asked at a terminal, the detected ones otherwise"
|
|
8035
8095
|
},
|
|
8036
8096
|
"skip-agents": {
|
|
8037
8097
|
type: "boolean",
|
|
@@ -9022,7 +9082,7 @@ const upgradeCommand = defineRdeCommand({
|
|
|
9022
9082
|
const main = defineCommand({
|
|
9023
9083
|
meta: {
|
|
9024
9084
|
name: "rde",
|
|
9025
|
-
version: "0.1.
|
|
9085
|
+
version: "0.1.6",
|
|
9026
9086
|
description: "Prepares a local machine for data-engineering work with Metabase and coding agents"
|
|
9027
9087
|
},
|
|
9028
9088
|
subCommands: {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metabase/rde",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Prepares a local machine for data-engineering work with Metabase and coding agents",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"bin": {
|
|
7
|
-
"rde": "
|
|
7
|
+
"rde": "dist/cli.mjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|