bluekiwi 0.3.18 → 0.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/accept.js +22 -24
- package/dist/commands/init.js +35 -28
- package/dist/commands/logout.js +23 -2
- package/dist/commands/profile.js +50 -0
- package/dist/commands/runtimes.js +9 -11
- package/dist/commands/status.js +7 -5
- package/dist/commands/upgrade.js +16 -15
- package/dist/config.js +156 -2
- package/dist/index.js +20 -3
- package/dist/runtime-sync.js +34 -0
- package/package.json +1 -1
package/dist/commands/accept.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import prompts from "prompts";
|
|
2
2
|
import pc from "picocolors";
|
|
3
3
|
import { BlueKiwiClient } from "../api-client.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { createEmptyConfig, loadConfig, normalizeProfileName, saveConfig, upsertProfile, } from "../config.js";
|
|
5
|
+
import { applyProfileToRuntimes } from "../runtime-sync.js";
|
|
6
6
|
import { detectInstalledAdapters, getAllAdapters } from "../runtimes/detect.js";
|
|
7
7
|
export async function acceptCommand(token, opts) {
|
|
8
|
+
const profileName = normalizeProfileName(opts.profile);
|
|
9
|
+
const currentConfig = loadConfig() ?? createEmptyConfig();
|
|
8
10
|
console.log(pc.cyan("→ Validating invite..."));
|
|
9
11
|
const validateRes = await fetch(`${opts.server}/api/invites/accept/${token}`);
|
|
10
12
|
if (!validateRes.ok) {
|
|
@@ -60,7 +62,8 @@ export async function acceptCommand(token, opts) {
|
|
|
60
62
|
const choices = all.map((adapter) => ({
|
|
61
63
|
title: adapter.displayName,
|
|
62
64
|
value: adapter.name,
|
|
63
|
-
selected:
|
|
65
|
+
selected: currentConfig.runtimes.includes(adapter.name) ||
|
|
66
|
+
detected.some((detectedAdapter) => detectedAdapter.name === adapter.name),
|
|
64
67
|
disabled: !adapter.isInstalled(),
|
|
65
68
|
}));
|
|
66
69
|
const { selected } = (await prompts({
|
|
@@ -72,31 +75,26 @@ export async function acceptCommand(token, opts) {
|
|
|
72
75
|
}));
|
|
73
76
|
chosen = selected ?? [];
|
|
74
77
|
}
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!adapter)
|
|
79
|
-
continue;
|
|
80
|
-
console.log(pc.cyan(`→ Installing to ${adapter.displayName}...`));
|
|
81
|
-
adapter.installSkills(BUNDLED_SKILLS);
|
|
82
|
-
adapter.installMcp({
|
|
83
|
-
command: "node",
|
|
84
|
-
args: [BUNDLED_MCP_PATH],
|
|
85
|
-
env: {
|
|
86
|
-
BLUEKIWI_API_URL: opts.server,
|
|
87
|
-
BLUEKIWI_API_KEY: result.api_key,
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
saveConfig({
|
|
92
|
-
version: "1.0.0",
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
const targetProfile = {
|
|
80
|
+
name: profileName,
|
|
93
81
|
server_url: opts.server,
|
|
94
82
|
api_key: result.api_key,
|
|
95
83
|
user: result.user,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
installed_at: now,
|
|
85
|
+
last_used: now,
|
|
86
|
+
};
|
|
87
|
+
const nextConfig = upsertProfile({
|
|
88
|
+
...currentConfig,
|
|
89
|
+
runtimes: Array.from(new Set([...currentConfig.runtimes, ...chosen])),
|
|
90
|
+
}, targetProfile, {
|
|
91
|
+
activate: chosen.length > 0 || Object.keys(currentConfig.profiles).length === 0,
|
|
99
92
|
});
|
|
93
|
+
if (chosen.length > 0) {
|
|
94
|
+
applyProfileToRuntimes(nextConfig, profileName, chosen);
|
|
95
|
+
}
|
|
96
|
+
saveConfig(nextConfig);
|
|
100
97
|
console.log(pc.green("\n✓ BlueKiwi installed successfully!"));
|
|
98
|
+
console.log(pc.dim(`Profile: ${profileName}`));
|
|
101
99
|
console.log(pc.dim("Try /bk-start in your agent runtime to begin."));
|
|
102
100
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import prompts from "prompts";
|
|
2
2
|
import pc from "picocolors";
|
|
3
3
|
import { BlueKiwiClient } from "../api-client.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { createEmptyConfig, getProfile, loadConfig, normalizeProfileName, saveConfig, upsertProfile, } from "../config.js";
|
|
5
|
+
import { applyProfileToRuntimes } from "../runtime-sync.js";
|
|
6
6
|
import { detectInstalledAdapters, getAllAdapters } from "../runtimes/detect.js";
|
|
7
7
|
function normalizeEnvValue(value) {
|
|
8
8
|
const trimmed = value?.trim();
|
|
@@ -37,8 +37,10 @@ function maskApiKey(key) {
|
|
|
37
37
|
}
|
|
38
38
|
export async function initCommand(options = {}) {
|
|
39
39
|
const isNonInteractive = options.yes === true || process.stdin.isTTY !== true;
|
|
40
|
+
const profileName = normalizeProfileName(options.profile);
|
|
40
41
|
// Load existing config for pre-filling prompts
|
|
41
|
-
const existingCfg = loadConfig();
|
|
42
|
+
const existingCfg = loadConfig() ?? createEmptyConfig();
|
|
43
|
+
const existingProfile = getProfile(existingCfg, profileName);
|
|
42
44
|
let server = normalizeEnvValue(options.server) ??
|
|
43
45
|
normalizeEnvValue(process.env.BLUEKIWI_API_URL) ??
|
|
44
46
|
normalizeEnvValue(process.env.BLUEKIWI_SERVER);
|
|
@@ -51,12 +53,12 @@ export async function initCommand(options = {}) {
|
|
|
51
53
|
type: "text",
|
|
52
54
|
name: "server",
|
|
53
55
|
message: "BlueKiwi server URL",
|
|
54
|
-
initial:
|
|
56
|
+
initial: existingProfile?.profile.server_url,
|
|
55
57
|
});
|
|
56
58
|
}
|
|
57
59
|
if (apiKey === undefined) {
|
|
58
|
-
const maskedKey =
|
|
59
|
-
? maskApiKey(
|
|
60
|
+
const maskedKey = existingProfile?.profile.api_key
|
|
61
|
+
? maskApiKey(existingProfile.profile.api_key)
|
|
60
62
|
: undefined;
|
|
61
63
|
questions.push({
|
|
62
64
|
type: "text",
|
|
@@ -72,11 +74,11 @@ export async function initCommand(options = {}) {
|
|
|
72
74
|
// If user kept the masked key (just pressed Enter), restore original
|
|
73
75
|
if (apiKey === undefined) {
|
|
74
76
|
const entered = answers.apiKey;
|
|
75
|
-
const maskedKey =
|
|
76
|
-
? maskApiKey(
|
|
77
|
+
const maskedKey = existingProfile?.profile.api_key
|
|
78
|
+
? maskApiKey(existingProfile.profile.api_key)
|
|
77
79
|
: undefined;
|
|
78
80
|
if (entered && maskedKey && entered === maskedKey) {
|
|
79
|
-
apiKey =
|
|
81
|
+
apiKey = existingProfile.profile.api_key;
|
|
80
82
|
}
|
|
81
83
|
else {
|
|
82
84
|
apiKey = entered;
|
|
@@ -91,7 +93,7 @@ export async function initCommand(options = {}) {
|
|
|
91
93
|
}
|
|
92
94
|
const client = new BlueKiwiClient(server, apiKey);
|
|
93
95
|
await client.request("GET", "/api/workflows");
|
|
94
|
-
const me = {
|
|
96
|
+
const me = existingProfile?.profile.user ?? {
|
|
95
97
|
id: 0,
|
|
96
98
|
username: "unknown",
|
|
97
99
|
email: "",
|
|
@@ -104,8 +106,9 @@ export async function initCommand(options = {}) {
|
|
|
104
106
|
const requestedFromFlags = uniquePreserveOrder(parseCommaSeparatedList(options.runtimes));
|
|
105
107
|
const requestedFromEnv = uniquePreserveOrder(parseCommaSeparatedList([process.env.BLUEKIWI_RUNTIMES ?? ""]));
|
|
106
108
|
const requestedRuntimeNames = requestedFromFlags.length > 0 ? requestedFromFlags : requestedFromEnv;
|
|
109
|
+
const hasExplicitRuntimeSelection = requestedRuntimeNames.length > 0;
|
|
107
110
|
let selectedRuntimeNames = [];
|
|
108
|
-
if (
|
|
111
|
+
if (hasExplicitRuntimeSelection) {
|
|
109
112
|
const unknown = requestedRuntimeNames.filter((name) => !adaptersByName.has(name));
|
|
110
113
|
if (unknown.length > 0) {
|
|
111
114
|
throw new Error(`Unknown runtime(s): ${unknown.join(", ")}. Valid runtimes: ${validRuntimeNames.join(", ")}`);
|
|
@@ -120,6 +123,9 @@ export async function initCommand(options = {}) {
|
|
|
120
123
|
}
|
|
121
124
|
selectedRuntimeNames = requestedRuntimeNames;
|
|
122
125
|
}
|
|
126
|
+
else if (existingCfg.runtimes.length > 0) {
|
|
127
|
+
selectedRuntimeNames = existingCfg.runtimes;
|
|
128
|
+
}
|
|
123
129
|
else if (isNonInteractive) {
|
|
124
130
|
if (detected.length === 0) {
|
|
125
131
|
throw new Error("Non-interactive mode: at least one runtime is required (--runtime <name>) or install a supported runtime");
|
|
@@ -140,25 +146,26 @@ export async function initCommand(options = {}) {
|
|
|
140
146
|
}));
|
|
141
147
|
selectedRuntimeNames = selected ?? [];
|
|
142
148
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
continue;
|
|
147
|
-
adapter.installSkills(BUNDLED_SKILLS);
|
|
148
|
-
adapter.installMcp({
|
|
149
|
-
command: "node",
|
|
150
|
-
args: [BUNDLED_MCP_PATH],
|
|
151
|
-
env: { BLUEKIWI_API_URL: server, BLUEKIWI_API_KEY: apiKey },
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
saveConfig({
|
|
155
|
-
version: "1.0.0",
|
|
149
|
+
const now = new Date().toISOString();
|
|
150
|
+
const targetProfile = {
|
|
151
|
+
name: profileName,
|
|
156
152
|
server_url: server,
|
|
157
153
|
api_key: apiKey,
|
|
158
154
|
user: me,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
155
|
+
installed_at: existingProfile?.profile.installed_at ?? now,
|
|
156
|
+
last_used: now,
|
|
157
|
+
};
|
|
158
|
+
const shouldActivate = hasExplicitRuntimeSelection ||
|
|
159
|
+
existingCfg.active_profile === profileName ||
|
|
160
|
+
Object.keys(existingCfg.profiles).length === 0;
|
|
161
|
+
const nextConfig = upsertProfile({
|
|
162
|
+
...existingCfg,
|
|
163
|
+
runtimes: Array.from(new Set([...existingCfg.runtimes, ...selectedRuntimeNames])),
|
|
164
|
+
}, targetProfile, { activate: shouldActivate });
|
|
165
|
+
if (shouldActivate && nextConfig.runtimes.length > 0) {
|
|
166
|
+
applyProfileToRuntimes(nextConfig, profileName, nextConfig.runtimes);
|
|
167
|
+
}
|
|
168
|
+
saveConfig(nextConfig);
|
|
163
169
|
console.log(pc.green("✓ BlueKiwi connected"));
|
|
170
|
+
console.log(pc.dim(`Profile: ${profileName}`));
|
|
164
171
|
}
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
|
-
import { clearConfig, loadConfig } from "../config.js";
|
|
2
|
+
import { clearConfig, loadConfig, removeProfile, saveConfig } from "../config.js";
|
|
3
3
|
import { getAllAdapters } from "../runtimes/detect.js";
|
|
4
|
-
|
|
4
|
+
import { applyProfileToRuntimes } from "../runtime-sync.js";
|
|
5
|
+
export async function logoutCommand(profileName) {
|
|
5
6
|
const cfg = loadConfig();
|
|
6
7
|
if (!cfg) {
|
|
7
8
|
console.log(pc.yellow("Already logged out."));
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
11
|
+
if (profileName) {
|
|
12
|
+
const next = removeProfile(cfg, profileName);
|
|
13
|
+
if (!next) {
|
|
14
|
+
for (const adapter of getAllAdapters()) {
|
|
15
|
+
if (cfg.runtimes.includes(adapter.name)) {
|
|
16
|
+
adapter.uninstall();
|
|
17
|
+
console.log(pc.dim(` removed ${adapter.displayName}`));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
clearConfig();
|
|
21
|
+
console.log(pc.green(`✓ Removed profile '${profileName}' and logged out.`));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
saveConfig(next);
|
|
25
|
+
if (profileName === cfg.active_profile && next.runtimes.length > 0) {
|
|
26
|
+
applyProfileToRuntimes(next, next.active_profile);
|
|
27
|
+
}
|
|
28
|
+
console.log(pc.green(`✓ Removed profile '${profileName}'. Active profile: ${next.active_profile}`));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
10
31
|
for (const adapter of getAllAdapters()) {
|
|
11
32
|
if (cfg.runtimes.includes(adapter.name)) {
|
|
12
33
|
adapter.uninstall();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { clearConfig, loadConfig, removeProfile, requireConfig, requireProfile, saveConfig, } from "../config.js";
|
|
3
|
+
import { getAllAdapters } from "../runtimes/detect.js";
|
|
4
|
+
import { applyProfileToRuntimes } from "../runtime-sync.js";
|
|
5
|
+
async function list() {
|
|
6
|
+
const cfg = loadConfig();
|
|
7
|
+
if (!cfg) {
|
|
8
|
+
console.log(pc.yellow("No profiles configured."));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
for (const [name, profile] of Object.entries(cfg.profiles)) {
|
|
12
|
+
const marker = name === cfg.active_profile ? pc.green("●") : pc.dim("○");
|
|
13
|
+
console.log(`${marker} ${name} ${pc.dim(profile.server_url)} ${profile.user.username} (${profile.user.role})`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function use(name) {
|
|
17
|
+
const cfg = requireConfig();
|
|
18
|
+
requireProfile(cfg, name);
|
|
19
|
+
const next = {
|
|
20
|
+
...cfg,
|
|
21
|
+
active_profile: name,
|
|
22
|
+
};
|
|
23
|
+
if (next.runtimes.length > 0) {
|
|
24
|
+
applyProfileToRuntimes(next, name);
|
|
25
|
+
}
|
|
26
|
+
saveConfig(next);
|
|
27
|
+
console.log(pc.green(`✓ Active profile switched to '${name}'`));
|
|
28
|
+
}
|
|
29
|
+
async function remove(name) {
|
|
30
|
+
const cfg = requireConfig();
|
|
31
|
+
requireProfile(cfg, name);
|
|
32
|
+
const next = removeProfile(cfg, name);
|
|
33
|
+
if (!next) {
|
|
34
|
+
for (const adapter of getAllAdapters()) {
|
|
35
|
+
if (cfg.runtimes.includes(adapter.name)) {
|
|
36
|
+
adapter.uninstall();
|
|
37
|
+
console.log(pc.dim(` removed ${adapter.displayName}`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
clearConfig();
|
|
41
|
+
console.log(pc.green(`✓ Removed profile '${name}' and cleared config.`));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
saveConfig(next);
|
|
45
|
+
if (cfg.active_profile === name && next.runtimes.length > 0) {
|
|
46
|
+
applyProfileToRuntimes(next, next.active_profile);
|
|
47
|
+
}
|
|
48
|
+
console.log(pc.green(`✓ Removed profile '${name}'`));
|
|
49
|
+
}
|
|
50
|
+
export const profileCommand = { list, use, remove };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getAllAdapters } from "../runtimes/detect.js";
|
|
2
2
|
import pc from "picocolors";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { loadConfig, requireConfig, requireProfile, saveConfig, } from "../config.js";
|
|
4
|
+
import { applyProfileToRuntimes } from "../runtime-sync.js";
|
|
5
5
|
async function list() {
|
|
6
6
|
const cfg = loadConfig();
|
|
7
7
|
const installed = new Set(cfg?.runtimes ?? []);
|
|
@@ -15,23 +15,21 @@ async function list() {
|
|
|
15
15
|
console.log(`${adapter.displayName.padEnd(14)} ${detected} ${active}`);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
async function add(name) {
|
|
18
|
+
async function add(name, profileName) {
|
|
19
19
|
const cfg = requireConfig();
|
|
20
|
+
const { name: resolvedProfile } = requireProfile(cfg, profileName);
|
|
20
21
|
const adapter = getAllAdapters().find((item) => item.name === name);
|
|
21
22
|
if (!adapter) {
|
|
22
23
|
console.error(`Unknown runtime: ${name}`);
|
|
23
24
|
process.exit(1);
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
adapter.installMcp({
|
|
27
|
-
command: "node",
|
|
28
|
-
args: [BUNDLED_MCP_PATH],
|
|
29
|
-
env: { BLUEKIWI_API_URL: cfg.server_url, BLUEKIWI_API_KEY: cfg.api_key },
|
|
30
|
-
});
|
|
31
|
-
saveConfig({
|
|
26
|
+
const next = {
|
|
32
27
|
...cfg,
|
|
28
|
+
active_profile: resolvedProfile,
|
|
33
29
|
runtimes: Array.from(new Set([...cfg.runtimes, name])),
|
|
34
|
-
}
|
|
30
|
+
};
|
|
31
|
+
applyProfileToRuntimes(next, resolvedProfile, [name]);
|
|
32
|
+
saveConfig(next);
|
|
35
33
|
console.log(pc.green(`✓ Installed to ${adapter.displayName}`));
|
|
36
34
|
}
|
|
37
35
|
async function remove(name) {
|
package/dist/commands/status.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import { BlueKiwiClient } from "../api-client.js";
|
|
3
|
-
import { CONFIG_PATH, loadConfig } from "../config.js";
|
|
4
|
-
export async function statusCommand() {
|
|
3
|
+
import { CONFIG_PATH, loadConfig, requireProfile } from "../config.js";
|
|
4
|
+
export async function statusCommand(profileName) {
|
|
5
5
|
const cfg = loadConfig();
|
|
6
6
|
if (!cfg) {
|
|
7
7
|
console.log(pc.yellow(`Not authenticated. No config at ${CONFIG_PATH}.`));
|
|
8
8
|
process.exit(1);
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
console.log(`${pc.bold("
|
|
10
|
+
const { name, profile } = requireProfile(cfg, profileName);
|
|
11
|
+
console.log(`${pc.bold("Profile:")} ${name}${name === cfg.active_profile ? " (active)" : ""}`);
|
|
12
|
+
console.log(`${pc.bold("Server:")} ${profile.server_url}`);
|
|
13
|
+
console.log(`${pc.bold("User:")} ${profile.user.username} (${profile.user.role})`);
|
|
12
14
|
console.log(`${pc.bold("Runtimes:")} ${cfg.runtimes.join(", ") || "(none)"}`);
|
|
13
15
|
try {
|
|
14
|
-
const client = new BlueKiwiClient(
|
|
16
|
+
const client = new BlueKiwiClient(profile.server_url, profile.api_key);
|
|
15
17
|
await client.request("GET", "/api/workflows");
|
|
16
18
|
console.log(pc.green("✓ Connection OK"));
|
|
17
19
|
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "child_process";
|
|
2
2
|
import pc from "picocolors";
|
|
3
|
-
import { BUNDLED_MCP_PATH, BUNDLED_SKILLS } from "../assets/index.js";
|
|
4
3
|
import { loadConfig, saveConfig } from "../config.js";
|
|
5
|
-
import {
|
|
4
|
+
import { applyProfileToRuntimes, pruneBundledSkills } from "../runtime-sync.js";
|
|
6
5
|
export async function upgradeCommand() {
|
|
7
6
|
console.log(pc.cyan("→ Upgrading bluekiwi..."));
|
|
8
7
|
execFileSync("npm", ["install", "-g", "bluekiwi@latest"], {
|
|
@@ -13,18 +12,20 @@ export async function upgradeCommand() {
|
|
|
13
12
|
console.log(pc.yellow("No config found. Run `bluekiwi accept` or `bluekiwi init` next."));
|
|
14
13
|
return;
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
pruneBundledSkills(cfg);
|
|
16
|
+
applyProfileToRuntimes(cfg, cfg.active_profile);
|
|
17
|
+
const active = cfg.profiles[cfg.active_profile];
|
|
18
|
+
saveConfig({
|
|
19
|
+
...cfg,
|
|
20
|
+
profiles: active
|
|
21
|
+
? {
|
|
22
|
+
...cfg.profiles,
|
|
23
|
+
[cfg.active_profile]: {
|
|
24
|
+
...active,
|
|
25
|
+
last_used: new Date().toISOString(),
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
: cfg.profiles,
|
|
29
|
+
});
|
|
29
30
|
console.log(pc.green("✓ Upgraded and reinstalled assets."));
|
|
30
31
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,20 +1,126 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, unlinkSync, } from "fs";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join, dirname } from "path";
|
|
4
|
+
const CONFIG_VERSION = "2.0.0";
|
|
5
|
+
export const DEFAULT_PROFILE = "default";
|
|
4
6
|
export const CONFIG_PATH = join(homedir(), ".bluekiwi", "config.json");
|
|
7
|
+
function nowIso() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
function coerceUser(value) {
|
|
11
|
+
const raw = (value ?? {});
|
|
12
|
+
return {
|
|
13
|
+
id: typeof raw.id === "number" ? raw.id : 0,
|
|
14
|
+
username: typeof raw.username === "string" ? raw.username : "unknown",
|
|
15
|
+
email: typeof raw.email === "string" ? raw.email : "",
|
|
16
|
+
role: typeof raw.role === "string" ? raw.role : "viewer",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function coerceRuntimes(value) {
|
|
20
|
+
if (!Array.isArray(value))
|
|
21
|
+
return [];
|
|
22
|
+
return value.filter((item) => typeof item === "string");
|
|
23
|
+
}
|
|
24
|
+
function normalizeProfileName(name) {
|
|
25
|
+
const trimmed = name?.trim();
|
|
26
|
+
return trimmed ? trimmed : DEFAULT_PROFILE;
|
|
27
|
+
}
|
|
28
|
+
function isLegacyConfig(value) {
|
|
29
|
+
const raw = value;
|
|
30
|
+
return !!raw && typeof raw.server_url === "string" && typeof raw.api_key === "string";
|
|
31
|
+
}
|
|
32
|
+
function normalizeProfile(name, raw) {
|
|
33
|
+
if (!raw ||
|
|
34
|
+
typeof raw.server_url !== "string" ||
|
|
35
|
+
typeof raw.api_key !== "string") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
server_url: raw.server_url,
|
|
41
|
+
api_key: raw.api_key,
|
|
42
|
+
user: coerceUser(raw.user),
|
|
43
|
+
installed_at: typeof raw.installed_at === "string" ? raw.installed_at : nowIso(),
|
|
44
|
+
last_used: typeof raw.last_used === "string" ? raw.last_used : nowIso(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function migrateLegacyConfig(raw) {
|
|
48
|
+
const profile = normalizeProfile(DEFAULT_PROFILE, {
|
|
49
|
+
name: DEFAULT_PROFILE,
|
|
50
|
+
server_url: raw.server_url,
|
|
51
|
+
api_key: raw.api_key,
|
|
52
|
+
user: raw.user,
|
|
53
|
+
installed_at: raw.installed_at,
|
|
54
|
+
last_used: raw.last_used,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
version: CONFIG_VERSION,
|
|
58
|
+
active_profile: DEFAULT_PROFILE,
|
|
59
|
+
profiles: profile ? { [DEFAULT_PROFILE]: profile } : {},
|
|
60
|
+
runtimes: coerceRuntimes(raw.runtimes),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function normalizeConfig(raw) {
|
|
64
|
+
if (isLegacyConfig(raw)) {
|
|
65
|
+
return migrateLegacyConfig(raw);
|
|
66
|
+
}
|
|
67
|
+
const value = raw;
|
|
68
|
+
if (!value || typeof value !== "object" || value.profiles == null) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const profiles = {};
|
|
72
|
+
for (const [name, profileValue] of Object.entries(value.profiles)) {
|
|
73
|
+
const normalized = normalizeProfile(name, profileValue);
|
|
74
|
+
if (normalized)
|
|
75
|
+
profiles[name] = normalized;
|
|
76
|
+
}
|
|
77
|
+
const profileNames = Object.keys(profiles);
|
|
78
|
+
if (profileNames.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
const requestedActive = typeof value.active_profile === "string" ? value.active_profile : undefined;
|
|
81
|
+
const activeProfile = profiles[requestedActive ?? ""]
|
|
82
|
+
? requestedActive
|
|
83
|
+
: profiles[DEFAULT_PROFILE]
|
|
84
|
+
? DEFAULT_PROFILE
|
|
85
|
+
: profileNames[0];
|
|
86
|
+
return {
|
|
87
|
+
version: typeof value.version === "string" ? value.version : CONFIG_VERSION,
|
|
88
|
+
active_profile: activeProfile,
|
|
89
|
+
profiles,
|
|
90
|
+
runtimes: coerceRuntimes(value.runtimes),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
5
93
|
export function loadConfig() {
|
|
6
94
|
if (!existsSync(CONFIG_PATH))
|
|
7
95
|
return null;
|
|
8
96
|
try {
|
|
9
|
-
|
|
97
|
+
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
98
|
+
const normalized = normalizeConfig(raw);
|
|
99
|
+
if (!normalized)
|
|
100
|
+
return null;
|
|
101
|
+
if (JSON.stringify(raw, null, 2) !== JSON.stringify(normalized, null, 2)) {
|
|
102
|
+
saveConfig(normalized);
|
|
103
|
+
}
|
|
104
|
+
return normalized;
|
|
10
105
|
}
|
|
11
106
|
catch {
|
|
12
107
|
return null;
|
|
13
108
|
}
|
|
14
109
|
}
|
|
110
|
+
export function createEmptyConfig() {
|
|
111
|
+
return {
|
|
112
|
+
version: CONFIG_VERSION,
|
|
113
|
+
active_profile: DEFAULT_PROFILE,
|
|
114
|
+
profiles: {},
|
|
115
|
+
runtimes: [],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
15
118
|
export function saveConfig(config) {
|
|
16
119
|
mkdirSync(dirname(CONFIG_PATH), { recursive: true, mode: 0o700 });
|
|
17
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(
|
|
120
|
+
writeFileSync(CONFIG_PATH, JSON.stringify({
|
|
121
|
+
...config,
|
|
122
|
+
version: CONFIG_VERSION,
|
|
123
|
+
}, null, 2), { mode: 0o600 });
|
|
18
124
|
chmodSync(CONFIG_PATH, 0o600);
|
|
19
125
|
}
|
|
20
126
|
export function clearConfig() {
|
|
@@ -28,3 +134,51 @@ export function requireConfig() {
|
|
|
28
134
|
}
|
|
29
135
|
return config;
|
|
30
136
|
}
|
|
137
|
+
export function getProfile(config, profileName) {
|
|
138
|
+
const resolved = normalizeProfileName(profileName ?? config.active_profile);
|
|
139
|
+
const profile = config.profiles[resolved];
|
|
140
|
+
if (!profile)
|
|
141
|
+
return null;
|
|
142
|
+
return { name: resolved, profile };
|
|
143
|
+
}
|
|
144
|
+
export function requireProfile(config, profileName) {
|
|
145
|
+
const resolved = getProfile(config, profileName);
|
|
146
|
+
if (!resolved) {
|
|
147
|
+
throw new Error(`Unknown profile '${normalizeProfileName(profileName)}'. Use \`bluekiwi profile list\` to see configured profiles.`);
|
|
148
|
+
}
|
|
149
|
+
return resolved;
|
|
150
|
+
}
|
|
151
|
+
export function upsertProfile(config, profile, options) {
|
|
152
|
+
const next = {
|
|
153
|
+
...config,
|
|
154
|
+
profiles: {
|
|
155
|
+
...config.profiles,
|
|
156
|
+
[profile.name]: profile,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
if (options?.activate) {
|
|
160
|
+
next.active_profile = profile.name;
|
|
161
|
+
}
|
|
162
|
+
else if (!next.active_profile) {
|
|
163
|
+
next.active_profile = profile.name;
|
|
164
|
+
}
|
|
165
|
+
return next;
|
|
166
|
+
}
|
|
167
|
+
export function removeProfile(config, profileName) {
|
|
168
|
+
const nextProfiles = { ...config.profiles };
|
|
169
|
+
delete nextProfiles[profileName];
|
|
170
|
+
const remaining = Object.keys(nextProfiles);
|
|
171
|
+
if (remaining.length === 0)
|
|
172
|
+
return null;
|
|
173
|
+
const nextActive = config.active_profile === profileName
|
|
174
|
+
? nextProfiles[DEFAULT_PROFILE]
|
|
175
|
+
? DEFAULT_PROFILE
|
|
176
|
+
: remaining[0]
|
|
177
|
+
: config.active_profile;
|
|
178
|
+
return {
|
|
179
|
+
...config,
|
|
180
|
+
active_profile: nextActive,
|
|
181
|
+
profiles: nextProfiles,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export { normalizeProfileName };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { upgradeCommand } from "./commands/upgrade.js";
|
|
|
8
8
|
import { logoutCommand } from "./commands/logout.js";
|
|
9
9
|
import { runtimesCommand } from "./commands/runtimes.js";
|
|
10
10
|
import { devLinkCommand } from "./commands/dev-link.js";
|
|
11
|
+
import { profileCommand } from "./commands/profile.js";
|
|
11
12
|
const require = createRequire(import.meta.url);
|
|
12
13
|
const pkg = require("../package.json");
|
|
13
14
|
function splitCommaSeparatedList(value) {
|
|
@@ -27,6 +28,7 @@ program
|
|
|
27
28
|
program
|
|
28
29
|
.command("accept <token>")
|
|
29
30
|
.requiredOption("--server <url>", "BlueKiwi server URL")
|
|
31
|
+
.option("--profile <name>", "Profile name (default: default)")
|
|
30
32
|
.option("--username <name>", "Username (non-interactive)")
|
|
31
33
|
.option("--password <pass>", "Password (non-interactive)")
|
|
32
34
|
.action(acceptCommand);
|
|
@@ -34,20 +36,35 @@ program
|
|
|
34
36
|
.command("init")
|
|
35
37
|
.option("--server <url>", "BlueKiwi server URL")
|
|
36
38
|
.option("--api-key <key>", "API key (bk_...)")
|
|
39
|
+
.option("--profile <name>", "Profile name (default: default)")
|
|
37
40
|
.option("--runtime <name>", "Runtime to install into (repeatable, or comma-separated)", collectRuntimes, [])
|
|
38
41
|
.option("--yes", "Suppress all prompts (non-interactive)")
|
|
39
42
|
.action((opts) => initCommand({
|
|
40
43
|
server: opts.server,
|
|
41
44
|
apiKey: opts.apiKey,
|
|
42
45
|
runtimes: opts.runtime?.length ? opts.runtime : undefined,
|
|
46
|
+
profile: opts.profile,
|
|
43
47
|
yes: opts.yes,
|
|
44
48
|
}));
|
|
45
|
-
program
|
|
49
|
+
program
|
|
50
|
+
.command("status")
|
|
51
|
+
.option("--profile <name>", "Profile name (default: active profile)")
|
|
52
|
+
.action((opts) => statusCommand(opts.profile));
|
|
46
53
|
program.command("upgrade").action(upgradeCommand);
|
|
47
|
-
program
|
|
54
|
+
program
|
|
55
|
+
.command("logout")
|
|
56
|
+
.option("--profile <name>", "Remove only one profile")
|
|
57
|
+
.action((opts) => logoutCommand(opts.profile));
|
|
48
58
|
program.command("runtimes").action(runtimesCommand.list);
|
|
49
|
-
program
|
|
59
|
+
program
|
|
60
|
+
.command("runtimes:add <name>")
|
|
61
|
+
.option("--profile <name>", "Profile to install into runtimes and set active")
|
|
62
|
+
.action((name, opts) => runtimesCommand.add(name, opts.profile));
|
|
50
63
|
program.command("runtimes:remove <name>").action(runtimesCommand.remove);
|
|
64
|
+
program.command("profile").action(profileCommand.list);
|
|
65
|
+
program.command("profile:list").action(profileCommand.list);
|
|
66
|
+
program.command("profile:use <name>").action(profileCommand.use);
|
|
67
|
+
program.command("profile:remove <name>").action(profileCommand.remove);
|
|
51
68
|
program.command("dev-link").action(devLinkCommand);
|
|
52
69
|
program.parseAsync(process.argv).catch((err) => {
|
|
53
70
|
console.error(err.message);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BUNDLED_MCP_PATH, BUNDLED_SKILLS } from "./assets/index.js";
|
|
2
|
+
import { requireProfile, } from "./config.js";
|
|
3
|
+
import { getAllAdapters } from "./runtimes/detect.js";
|
|
4
|
+
export function applyProfileToRuntimes(config, profileName, runtimeNames) {
|
|
5
|
+
const { profile } = requireProfile(config, profileName);
|
|
6
|
+
const targetNames = runtimeNames ?? config.runtimes;
|
|
7
|
+
const allAdapters = getAllAdapters();
|
|
8
|
+
const installed = [];
|
|
9
|
+
for (const name of targetNames) {
|
|
10
|
+
const adapter = allAdapters.find((item) => item.name === name);
|
|
11
|
+
if (!adapter)
|
|
12
|
+
continue;
|
|
13
|
+
adapter.installSkills(BUNDLED_SKILLS);
|
|
14
|
+
adapter.installMcp({
|
|
15
|
+
command: "node",
|
|
16
|
+
args: [BUNDLED_MCP_PATH],
|
|
17
|
+
env: {
|
|
18
|
+
BLUEKIWI_API_URL: profile.server_url,
|
|
19
|
+
BLUEKIWI_API_KEY: profile.api_key,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
installed.push(name);
|
|
23
|
+
}
|
|
24
|
+
return installed;
|
|
25
|
+
}
|
|
26
|
+
export function pruneBundledSkills(config) {
|
|
27
|
+
const bundledNames = new Set(BUNDLED_SKILLS.map((skill) => skill.name));
|
|
28
|
+
for (const adapter of getAllAdapters()) {
|
|
29
|
+
if (!config.runtimes.includes(adapter.name))
|
|
30
|
+
continue;
|
|
31
|
+
adapter.installSkills(BUNDLED_SKILLS);
|
|
32
|
+
adapter.pruneSkills(bundledNames);
|
|
33
|
+
}
|
|
34
|
+
}
|