@ziamana/bruine 0.1.0
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/LICENSE +21 -0
- package/README.md +315 -0
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/cordis.patch.yml +166 -0
- package/dist/bin.js +5358 -0
- package/dist/compat.js +40 -0
- package/dist/plugins/approval.js +147 -0
- package/dist/plugins/headless.js +236 -0
- package/dist/plugins/herdr.js +470 -0
- package/dist/plugins/mcp.js +275 -0
- package/dist/plugins/modes.js +850 -0
- package/dist/plugins/render.js +3723 -0
- package/dist/plugins/repl.js +9589 -0
- package/dist/plugins/silence.js +234 -0
- package/dist/plugins/startup.js +42 -0
- package/dist/plugins/web-search.js +138 -0
- package/package.json +93 -0
- package/skills/code-review/SKILL.md +30 -0
- package/skills/git-workflow/SKILL.md +31 -0
- package/skills/impeccable/LICENSE +191 -0
- package/skills/impeccable/NOTICE.md +11 -0
- package/skills/impeccable/SKILL.md +87 -0
- package/skills/impeccable/reference/adapt.md +318 -0
- package/skills/impeccable/reference/adapt.native.md +58 -0
- package/skills/impeccable/reference/android.md +46 -0
- package/skills/impeccable/reference/animate.md +89 -0
- package/skills/impeccable/reference/audit.md +137 -0
- package/skills/impeccable/reference/audit.native.md +139 -0
- package/skills/impeccable/reference/bolder.md +33 -0
- package/skills/impeccable/reference/clarify.md +94 -0
- package/skills/impeccable/reference/colorize.md +86 -0
- package/skills/impeccable/reference/component-review.md +63 -0
- package/skills/impeccable/reference/craft-floor.md +44 -0
- package/skills/impeccable/reference/craft.md +5 -0
- package/skills/impeccable/reference/critique.md +806 -0
- package/skills/impeccable/reference/degraded/asset-producer.md +42 -0
- package/skills/impeccable/reference/degraded/documenter.md +24 -0
- package/skills/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/skills/impeccable/reference/delight.md +70 -0
- package/skills/impeccable/reference/distill.md +111 -0
- package/skills/impeccable/reference/doctor.md +54 -0
- package/skills/impeccable/reference/document.md +416 -0
- package/skills/impeccable/reference/extract.md +69 -0
- package/skills/impeccable/reference/generate.md +101 -0
- package/skills/impeccable/reference/harden.md +345 -0
- package/skills/impeccable/reference/hooks.md +113 -0
- package/skills/impeccable/reference/init.md +131 -0
- package/skills/impeccable/reference/ios.md +51 -0
- package/skills/impeccable/reference/layout.md +84 -0
- package/skills/impeccable/reference/live-setup.md +104 -0
- package/skills/impeccable/reference/live.md +325 -0
- package/skills/impeccable/reference/mode-operate.md +21 -0
- package/skills/impeccable/reference/mode-persuade.md +19 -0
- package/skills/impeccable/reference/mode-read.md +21 -0
- package/skills/impeccable/reference/new-work.md +154 -0
- package/skills/impeccable/reference/onboard.md +234 -0
- package/skills/impeccable/reference/operate.md +61 -0
- package/skills/impeccable/reference/optimize.md +258 -0
- package/skills/impeccable/reference/overdrive.md +127 -0
- package/skills/impeccable/reference/polish.md +105 -0
- package/skills/impeccable/reference/quieter.md +99 -0
- package/skills/impeccable/reference/region-map.md +26 -0
- package/skills/impeccable/reference/routing.md +24 -0
- package/skills/impeccable/reference/shape.md +59 -0
- package/skills/impeccable/reference/typeset.md +80 -0
- package/skills/impeccable/reference/visualize.md +46 -0
- package/skills/impeccable/scripts/VERSION +1 -0
- package/skills/impeccable/scripts/command-metadata.json +98 -0
- package/skills/impeccable/scripts/data/font-index-failures.json +121 -0
- package/skills/impeccable/scripts/data/font-index.json +1 -0
- package/skills/impeccable/scripts/impeccable +206 -0
- package/skills/impeccable/scripts/impeccable.cmd +214 -0
- package/skills/impeccable/scripts/live-browser-dom.js +167 -0
- package/skills/impeccable/scripts/live-browser-ignores.js +242 -0
- package/skills/impeccable/scripts/live-browser-session.js +148 -0
- package/skills/impeccable/scripts/live-browser.js +13510 -0
- package/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/skills/make-interfaces-feel-better/LICENSE +21 -0
- package/skills/make-interfaces-feel-better/SKILL.md +187 -0
- package/skills/make-interfaces-feel-better/agents/openai.yaml +3 -0
- package/skills/make-interfaces-feel-better/animations.md +403 -0
- package/skills/make-interfaces-feel-better/icons.md +63 -0
- package/skills/make-interfaces-feel-better/performance.md +88 -0
- package/skills/make-interfaces-feel-better/surfaces.md +256 -0
- package/skills/make-interfaces-feel-better/typography.md +157 -0
- package/skills/playwright-cli/LICENSE +201 -0
- package/skills/playwright-cli/SKILL.md +489 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/pr-attachments.md +60 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +245 -0
- package/skills/playwright-cli/references/session-management.md +227 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +216 -0
- package/skills/remotion/SKILL.md +42 -0
- package/skills/systematic-debugging/SKILL.md +26 -0
- package/skills/thermo-nuclear-code-quality-review/LICENSE +21 -0
- package/skills/thermo-nuclear-code-quality-review/SKILL.md +192 -0
- package/skills/write-tests/SKILL.md +35 -0
- package/skills/youtube-transcript/LICENSE +21 -0
- package/skills/youtube-transcript/SKILL.md +41 -0
- package/skills/youtube-transcript/package.json +8 -0
- package/skills/youtube-transcript/transcript.js +44 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// src/compat.ts
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
function appEnv(suffix, env = process.env) {
|
|
6
|
+
return env[`BRUINE_${suffix}`] ?? env[`KUMO_${suffix}`];
|
|
7
|
+
}
|
|
8
|
+
function appHome(env = process.env, userHome = homedir(), pathMod = path, exists = existsSync) {
|
|
9
|
+
const explicit = appEnv("HOME", env);
|
|
10
|
+
if (explicit !== void 0) return explicit;
|
|
11
|
+
const current = pathMod.join(userHome, ".bruine");
|
|
12
|
+
const legacy = pathMod.join(userHome, ".kumo");
|
|
13
|
+
return exists(current) ? current : exists(legacy) ? legacy : current;
|
|
14
|
+
}
|
|
15
|
+
function runtimeHome(env = process.env, pathMod = path) {
|
|
16
|
+
return appEnv("HOME", env) ?? env.DSH_HOME ?? appHome(env, homedir(), pathMod);
|
|
17
|
+
}
|
|
18
|
+
function compatReadPath(currentPath, legacyPath) {
|
|
19
|
+
return existsSync(currentPath) || !existsSync(legacyPath) ? currentPath : legacyPath;
|
|
20
|
+
}
|
|
21
|
+
function configWritePath(home) {
|
|
22
|
+
return path.join(home, "bruine.json");
|
|
23
|
+
}
|
|
24
|
+
function configReadPath(home) {
|
|
25
|
+
return compatReadPath(configWritePath(home), path.join(home, "kumo.json"));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/mcp/config.ts
|
|
29
|
+
import { createHash } from "crypto";
|
|
30
|
+
import { readFileSync } from "fs";
|
|
31
|
+
import path2 from "path";
|
|
32
|
+
var PROJECT_MCP_FILE = ".mcp.json";
|
|
33
|
+
function expandEnv(text, env, missing) {
|
|
34
|
+
return text.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (_, name2, fallback) => {
|
|
35
|
+
const value = env[name2];
|
|
36
|
+
if (value !== void 0 && value !== "") return value;
|
|
37
|
+
if (fallback !== void 0) return fallback;
|
|
38
|
+
missing.add(name2);
|
|
39
|
+
return "";
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function serverId(name2) {
|
|
43
|
+
const safe = name2.replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
44
|
+
if (safe !== "" && safe.length <= 32 && safe === name2) return safe;
|
|
45
|
+
const hash = createHash("sha256").update(name2).digest("hex").slice(0, 6);
|
|
46
|
+
return `${(safe === "" ? "server" : safe).slice(0, 25)}_${hash}`;
|
|
47
|
+
}
|
|
48
|
+
var isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
49
|
+
function stringRecord(v) {
|
|
50
|
+
if (!isRecord(v)) return void 0;
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const [k, value] of Object.entries(v)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") out[k] = String(value);
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function parseMcpServers(raw, source, file, env = process.env) {
|
|
56
|
+
const servers = [];
|
|
57
|
+
const problems = [];
|
|
58
|
+
if (raw === void 0) return { servers, problems };
|
|
59
|
+
if (!isRecord(raw)) return { servers, problems: [`${file}: mcpServers must be an object of servers by name.`] };
|
|
60
|
+
for (const [name2, row] of Object.entries(raw)) {
|
|
61
|
+
if (!isRecord(row)) {
|
|
62
|
+
problems.push(`${name2} (${file}): not an object.`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const missing = /* @__PURE__ */ new Set();
|
|
66
|
+
const x = (s) => expandEnv(s, env, missing);
|
|
67
|
+
const xRecord = (r) => r === void 0 ? void 0 : Object.fromEntries(Object.entries(r).map(([k, v]) => [k, x(v)]));
|
|
68
|
+
const type = typeof row.type === "string" ? row.type.toLowerCase() : row.url !== void 0 ? "http" : "stdio";
|
|
69
|
+
const common = {
|
|
70
|
+
name: name2,
|
|
71
|
+
id: serverId(name2),
|
|
72
|
+
source,
|
|
73
|
+
alwaysAllow: Array.isArray(row.alwaysAllow) ? row.alwaysAllow.filter((t) => typeof t === "string") : [],
|
|
74
|
+
readOnly: row.readOnly === true,
|
|
75
|
+
disabled: row.disabled === true,
|
|
76
|
+
...typeof row.timeout === "number" && row.timeout > 0 ? { timeoutMs: Math.round(row.timeout) } : {}
|
|
77
|
+
};
|
|
78
|
+
if (type === "stdio") {
|
|
79
|
+
if (typeof row.command !== "string" || row.command.trim() === "") {
|
|
80
|
+
problems.push(`${name2} (${file}): a local server needs a "command".`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const args = Array.isArray(row.args) ? row.args.filter((a) => typeof a === "string").map(x) : [];
|
|
84
|
+
servers.push({
|
|
85
|
+
...common,
|
|
86
|
+
transport: "stdio",
|
|
87
|
+
command: x(row.command),
|
|
88
|
+
args,
|
|
89
|
+
...stringRecord(row.env) !== void 0 ? { env: xRecord(stringRecord(row.env)) } : {},
|
|
90
|
+
...typeof row.cwd === "string" ? { cwd: x(row.cwd) } : {}
|
|
91
|
+
});
|
|
92
|
+
} else if (type === "http" || type === "streamable-http") {
|
|
93
|
+
if (typeof row.url !== "string" || !/^https?:\/\//i.test(row.url)) {
|
|
94
|
+
problems.push(`${name2} (${file}): an HTTP server needs a "url" starting with http:// or https://.`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
servers.push({
|
|
98
|
+
...common,
|
|
99
|
+
transport: "streamable-http",
|
|
100
|
+
url: x(row.url),
|
|
101
|
+
...stringRecord(row.headers) !== void 0 ? { headers: xRecord(stringRecord(row.headers)) } : {}
|
|
102
|
+
});
|
|
103
|
+
} else if (type === "sse") {
|
|
104
|
+
problems.push(`${name2} (${file}): the old SSE transport is not supported; use the server's Streamable HTTP endpoint with "type": "http".`);
|
|
105
|
+
continue;
|
|
106
|
+
} else {
|
|
107
|
+
problems.push(`${name2} (${file}): unknown type "${String(row.type)}" (use "stdio" or "http").`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (missing.size > 0) problems.push(`${name2} (${file}): ${[...missing].join(", ")} ${missing.size === 1 ? "is" : "are"} not set in the environment.`);
|
|
111
|
+
}
|
|
112
|
+
return { servers, problems };
|
|
113
|
+
}
|
|
114
|
+
function readJson(file) {
|
|
115
|
+
let text;
|
|
116
|
+
try {
|
|
117
|
+
text = readFileSync(file, "utf8");
|
|
118
|
+
} catch {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return { value: JSON.parse(text) };
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return { error: `${file}: not valid JSON (${error.message}).` };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function readMcpServers(home, projectDir, env = process.env) {
|
|
128
|
+
const problems = [];
|
|
129
|
+
const userFile = configReadPath(home);
|
|
130
|
+
const user = readJson(userFile);
|
|
131
|
+
if (user.error !== void 0) problems.push(user.error);
|
|
132
|
+
const userRows = parseMcpServers(isRecord(user.value) ? user.value.mcpServers : void 0, "user", path2.basename(userFile), env);
|
|
133
|
+
const projectFile = path2.join(projectDir, PROJECT_MCP_FILE);
|
|
134
|
+
const project = readJson(projectFile);
|
|
135
|
+
if (project.error !== void 0) problems.push(project.error);
|
|
136
|
+
const projectRows = parseMcpServers(isRecord(project.value) ? project.value.mcpServers : void 0, "project", PROJECT_MCP_FILE, env);
|
|
137
|
+
problems.push(...userRows.problems, ...projectRows.problems);
|
|
138
|
+
const servers = [];
|
|
139
|
+
const names = /* @__PURE__ */ new Set();
|
|
140
|
+
const ids = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const server of [...userRows.servers, ...projectRows.servers]) {
|
|
142
|
+
if (names.has(server.name)) continue;
|
|
143
|
+
if (ids.has(server.id)) {
|
|
144
|
+
problems.push(`${server.name}: its tool namespace "${server.id}" is already taken by another server; rename one of them.`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
names.add(server.name);
|
|
148
|
+
ids.add(server.id);
|
|
149
|
+
servers.push(server);
|
|
150
|
+
}
|
|
151
|
+
return { servers, problems };
|
|
152
|
+
}
|
|
153
|
+
function fingerprint(server) {
|
|
154
|
+
const what = server.transport === "stdio" ? { t: "stdio", command: server.command, args: server.args ?? [], env: server.env ?? {}, cwd: server.cwd ?? "" } : { t: "http", url: server.url, headers: server.headers ?? {} };
|
|
155
|
+
return createHash("sha256").update(JSON.stringify(what)).digest("hex").slice(0, 16);
|
|
156
|
+
}
|
|
157
|
+
function toDshConfig(server) {
|
|
158
|
+
const common = {
|
|
159
|
+
serverName: server.id,
|
|
160
|
+
// A server that cannot start must say so: the failure is shown in /mcp, never swallowed.
|
|
161
|
+
failOnStartupError: true,
|
|
162
|
+
...server.timeoutMs !== void 0 ? { toolCallTimeoutMs: server.timeoutMs } : {}
|
|
163
|
+
};
|
|
164
|
+
if (server.transport === "stdio") {
|
|
165
|
+
return { ...common, transport: "stdio", command: server.command, args: server.args ?? [], env: server.env ?? {}, ...server.cwd !== void 0 ? { cwd: server.cwd } : {} };
|
|
166
|
+
}
|
|
167
|
+
return { ...common, transport: "streamable-http", url: server.url, headers: server.headers ?? {} };
|
|
168
|
+
}
|
|
169
|
+
var toolPrefix = (id) => `mcp__${id}__`;
|
|
170
|
+
|
|
171
|
+
// src/mcp/state.ts
|
|
172
|
+
import { mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
173
|
+
import path3 from "path";
|
|
174
|
+
var MCP_STATE_FILE = "mcp-state.json";
|
|
175
|
+
var empty = () => ({ user: { disabled: [] }, projects: {} });
|
|
176
|
+
function projectKey(dir, platform = process.platform) {
|
|
177
|
+
const abs = path3.resolve(dir);
|
|
178
|
+
return platform === "win32" ? abs.toLowerCase() : abs;
|
|
179
|
+
}
|
|
180
|
+
function readMcpState(home) {
|
|
181
|
+
try {
|
|
182
|
+
const doc = JSON.parse(readFileSync2(path3.join(home, MCP_STATE_FILE), "utf8"));
|
|
183
|
+
return {
|
|
184
|
+
user: { disabled: Array.isArray(doc.user?.disabled) ? doc.user.disabled.filter((s) => typeof s === "string") : [] },
|
|
185
|
+
projects: doc.projects !== null && typeof doc.projects === "object" ? doc.projects : {}
|
|
186
|
+
};
|
|
187
|
+
} catch {
|
|
188
|
+
return empty();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function isApproved(state, dir, server) {
|
|
192
|
+
if (server.source === "user") return true;
|
|
193
|
+
return state.projects[projectKey(dir)]?.approved?.[server.name] === fingerprint(server);
|
|
194
|
+
}
|
|
195
|
+
function isDisabled(state, dir, server) {
|
|
196
|
+
if (server.disabled) return true;
|
|
197
|
+
if (server.source === "user") return state.user.disabled.includes(server.name);
|
|
198
|
+
return state.projects[projectKey(dir)]?.disabled?.includes(server.name) ?? false;
|
|
199
|
+
}
|
|
200
|
+
function planMcp(home, dir, env = process.env) {
|
|
201
|
+
const { servers, problems } = readMcpServers(home, dir, env);
|
|
202
|
+
const state = readMcpState(home);
|
|
203
|
+
return {
|
|
204
|
+
problems,
|
|
205
|
+
plan: servers.map((server) => ({
|
|
206
|
+
server,
|
|
207
|
+
state: isDisabled(state, dir, server) ? "disabled" : isApproved(state, dir, server) ? "ready" : "unapproved"
|
|
208
|
+
}))
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/plugins/mcp.ts
|
|
213
|
+
var name = "bruine-mcp";
|
|
214
|
+
var BRUINE_MCP_SERVICE = "bruineMcp";
|
|
215
|
+
var STARTUP_BUDGET_MS = 2e4;
|
|
216
|
+
async function apply(ctx, config = {}) {
|
|
217
|
+
const planned = config.plan !== void 0 ? { plan: config.plan, problems: config.problems ?? [] } : planMcp(runtimeHome(), process.cwd());
|
|
218
|
+
const statuses = planned.plan.map(({ server, state }) => ({
|
|
219
|
+
server,
|
|
220
|
+
state: state === "ready" ? "starting" : state,
|
|
221
|
+
tools: []
|
|
222
|
+
}));
|
|
223
|
+
const toolsOf = (server) => {
|
|
224
|
+
try {
|
|
225
|
+
const schemas = ctx.get("tools")?.schemas?.() ?? [];
|
|
226
|
+
return schemas.map((s) => s.name ?? s.function?.name ?? "").filter((n) => n.startsWith(toolPrefix(server.id)));
|
|
227
|
+
} catch {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const service = {
|
|
232
|
+
problems: planned.problems,
|
|
233
|
+
status: () => statuses.map((s) => s.state === "connected" ? { ...s, tools: toolsOf(s.server) } : { ...s }),
|
|
234
|
+
serverOf: (tool) => planned.plan.find(({ server }) => tool.startsWith(toolPrefix(server.id)))?.server
|
|
235
|
+
};
|
|
236
|
+
ctx.provide(BRUINE_MCP_SERVICE, service);
|
|
237
|
+
for (const problem of planned.problems) ctx.logger?.warn?.(`mcp: ${problem}`);
|
|
238
|
+
const ready = statuses.filter((s) => s.state === "starting");
|
|
239
|
+
if (ready.length === 0) return;
|
|
240
|
+
if (typeof ctx.plugin !== "function") {
|
|
241
|
+
for (const s of ready) Object.assign(s, { state: "failed", error: "this dsh build cannot load plugins at runtime" });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
let client;
|
|
245
|
+
try {
|
|
246
|
+
client = await (config.load ?? (async () => await import("@deepseek-ai/dsh-mcp-client")))();
|
|
247
|
+
} catch (error) {
|
|
248
|
+
for (const s of ready) Object.assign(s, { state: "failed", error: `the MCP client could not be loaded: ${error.message}` });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const started = ready.map(async (status) => {
|
|
252
|
+
try {
|
|
253
|
+
await Promise.resolve(ctx.plugin(client, toDshConfig(status.server)));
|
|
254
|
+
status.state = "connected";
|
|
255
|
+
} catch (error) {
|
|
256
|
+
status.state = "failed";
|
|
257
|
+
status.error = (error?.message ?? String(error)).split("\n")[0].slice(0, 200);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
let timer;
|
|
261
|
+
await Promise.race([
|
|
262
|
+
Promise.allSettled(started),
|
|
263
|
+
new Promise((resolve) => {
|
|
264
|
+
timer = setTimeout(resolve, config.budgetMs ?? STARTUP_BUDGET_MS);
|
|
265
|
+
timer.unref?.();
|
|
266
|
+
})
|
|
267
|
+
]);
|
|
268
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
269
|
+
}
|
|
270
|
+
export {
|
|
271
|
+
BRUINE_MCP_SERVICE,
|
|
272
|
+
STARTUP_BUDGET_MS,
|
|
273
|
+
apply,
|
|
274
|
+
name
|
|
275
|
+
};
|