@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,234 @@
|
|
|
1
|
+
// src/plugins/silence.ts
|
|
2
|
+
import { existsSync as existsSync2, readFileSync, statSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
|
|
6
|
+
// src/compat.ts
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import path from "path";
|
|
10
|
+
function appEnv(suffix, env = process.env) {
|
|
11
|
+
return env[`BRUINE_${suffix}`] ?? env[`KUMO_${suffix}`];
|
|
12
|
+
}
|
|
13
|
+
function appHome(env = process.env, userHome = homedir(), pathMod = path, exists = existsSync) {
|
|
14
|
+
const explicit = appEnv("HOME", env);
|
|
15
|
+
if (explicit !== void 0) return explicit;
|
|
16
|
+
const current = pathMod.join(userHome, ".bruine");
|
|
17
|
+
const legacy = pathMod.join(userHome, ".kumo");
|
|
18
|
+
return exists(current) ? current : exists(legacy) ? legacy : current;
|
|
19
|
+
}
|
|
20
|
+
function runtimeHome(env = process.env, pathMod = path) {
|
|
21
|
+
return appEnv("HOME", env) ?? env.DSH_HOME ?? appHome(env, homedir(), pathMod);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/llm/silence.ts
|
|
25
|
+
var STREAM_CEILING_MS = 15 * 60 * 1e3;
|
|
26
|
+
var SECOND = 1e3;
|
|
27
|
+
var MINUTE = 60 * SECOND;
|
|
28
|
+
var PHASE_BUDGET_MS = {
|
|
29
|
+
waiting: 2 * MINUTE,
|
|
30
|
+
thinking: 90 * SECOND,
|
|
31
|
+
preparing: 3 * MINUTE,
|
|
32
|
+
answering: 60 * SECOND,
|
|
33
|
+
calling: 2 * MINUTE
|
|
34
|
+
};
|
|
35
|
+
var PER_THOUSAND_CHARS_MS = 6 * SECOND;
|
|
36
|
+
var MAX_SIZE_BONUS_MS = 10 * MINUTE;
|
|
37
|
+
var MIN_BUDGET_MS = 20 * SECOND;
|
|
38
|
+
function effortFactor(effort) {
|
|
39
|
+
switch (effort) {
|
|
40
|
+
case "high":
|
|
41
|
+
return 1.5;
|
|
42
|
+
case "xhigh":
|
|
43
|
+
return 2;
|
|
44
|
+
case "max":
|
|
45
|
+
return 2.5;
|
|
46
|
+
default:
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function silenceBudget(state, ceiling, scale = 1) {
|
|
51
|
+
let base = PHASE_BUDGET_MS[state.phase];
|
|
52
|
+
if (state.phase === "calling") base += Math.min(MAX_SIZE_BONUS_MS, state.toolChars / 1e3 * PER_THOUSAND_CHARS_MS);
|
|
53
|
+
const grown = base * effortFactor(state.effort) * 2 ** Math.max(0, state.retry);
|
|
54
|
+
const learned = state.survived * 1.5;
|
|
55
|
+
const budget = Math.max(grown * scale, learned, MIN_BUDGET_MS * scale);
|
|
56
|
+
return Math.max(1, Math.min(budget, ceiling));
|
|
57
|
+
}
|
|
58
|
+
function advance(state, chunk) {
|
|
59
|
+
switch (chunk.type) {
|
|
60
|
+
case "reasoning-delta":
|
|
61
|
+
return { ...state, phase: "thinking" };
|
|
62
|
+
case "text-delta":
|
|
63
|
+
return { ...state, phase: "answering" };
|
|
64
|
+
case "tool-call-delta": {
|
|
65
|
+
const fresh = chunk.name !== void 0 && chunk.name !== state.tool;
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
phase: "calling",
|
|
69
|
+
tool: chunk.name ?? state.tool,
|
|
70
|
+
toolChars: (fresh ? 0 : state.toolChars) + (chunk.argumentsDelta?.length ?? 0)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
case "block-end":
|
|
74
|
+
return chunk.block?.type === "reasoning" ? { ...state, phase: "preparing" } : state;
|
|
75
|
+
default:
|
|
76
|
+
return state;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function formatSilence(ms) {
|
|
80
|
+
const s = Math.round(ms / 1e3);
|
|
81
|
+
if (s < 60) return `${String(s)}s`;
|
|
82
|
+
const m = Math.floor(s / 60);
|
|
83
|
+
const rest = s % 60;
|
|
84
|
+
return rest === 0 ? `${String(m)}m` : `${String(m)}m ${String(rest)}s`;
|
|
85
|
+
}
|
|
86
|
+
var SILENCE_FAILURE_CODE = "TIMEOUT";
|
|
87
|
+
function silenceFinish(quietMs) {
|
|
88
|
+
return {
|
|
89
|
+
type: "finish",
|
|
90
|
+
reason: {
|
|
91
|
+
kind: "error",
|
|
92
|
+
failure: { code: SILENCE_FAILURE_CODE, message: `no answer from the model for ${formatSilence(quietMs)}`, silenceMs: quietMs }
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function* watchSilence(source, opts) {
|
|
97
|
+
const now = opts.now ?? Date.now;
|
|
98
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
99
|
+
let state = opts.start;
|
|
100
|
+
let quietSince = now();
|
|
101
|
+
let done = false;
|
|
102
|
+
try {
|
|
103
|
+
while (true) {
|
|
104
|
+
const budget = opts.budget(state);
|
|
105
|
+
opts.onState?.(state, quietSince, budget);
|
|
106
|
+
const left = Math.max(0, quietSince + budget - now());
|
|
107
|
+
let timer;
|
|
108
|
+
const timeout = new Promise((resolve) => {
|
|
109
|
+
timer = setTimeout(() => resolve("timeout"), left);
|
|
110
|
+
});
|
|
111
|
+
const next = iterator.next();
|
|
112
|
+
let result;
|
|
113
|
+
try {
|
|
114
|
+
result = await Promise.race([next, timeout]);
|
|
115
|
+
} finally {
|
|
116
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
if (result === "timeout") {
|
|
119
|
+
const quiet2 = now() - quietSince;
|
|
120
|
+
opts.onGiveUp?.(quiet2, state);
|
|
121
|
+
next.catch(() => void 0);
|
|
122
|
+
void Promise.resolve(iterator.return?.(void 0)).catch(() => void 0);
|
|
123
|
+
done = true;
|
|
124
|
+
yield silenceFinish(quiet2);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (result.done === true) {
|
|
128
|
+
done = true;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const at = now();
|
|
132
|
+
const quiet = at - quietSince;
|
|
133
|
+
if (quiet >= 1e3) opts.onResumed?.(quiet);
|
|
134
|
+
quietSince = at;
|
|
135
|
+
state = advance(state, result.value);
|
|
136
|
+
yield result.value;
|
|
137
|
+
}
|
|
138
|
+
} finally {
|
|
139
|
+
if (!done) await Promise.resolve(iterator.return?.(void 0)).catch(() => void 0);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/shared-state.ts
|
|
144
|
+
function sharedState(key, init) {
|
|
145
|
+
const store = globalThis;
|
|
146
|
+
const id = /* @__PURE__ */ Symbol.for(`bruine.${key}`);
|
|
147
|
+
return store[id] ??= init();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/ui/rain.ts
|
|
151
|
+
var effortState = sharedState("effort", () => ({ level: 0.45, effort: void 0 }));
|
|
152
|
+
function currentEffortName() {
|
|
153
|
+
return effortState.effort;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/plugins/silence.ts
|
|
157
|
+
var name = "bruine-silence";
|
|
158
|
+
var BRUINE_SILENCE_SERVICE = "bruineSilence";
|
|
159
|
+
var ADAPTER_DEFAULT_CEILING_MS = 3e5;
|
|
160
|
+
function ceilingReader(home) {
|
|
161
|
+
let cache;
|
|
162
|
+
return (provider) => {
|
|
163
|
+
try {
|
|
164
|
+
const file = join(home(), "settings.yaml");
|
|
165
|
+
if (!existsSync2(file)) return ADAPTER_DEFAULT_CEILING_MS;
|
|
166
|
+
const mtime = statSync(file).mtimeMs;
|
|
167
|
+
if (cache?.mtime !== mtime) cache = { mtime, doc: parseYaml(readFileSync(file, "utf8")) };
|
|
168
|
+
const doc = cache.doc;
|
|
169
|
+
const value = provider === void 0 ? void 0 : doc?.["llm-pi-ai"]?.providers?.[provider]?.streamIdleTimeoutMs ?? doc?.["llm-deepseek"]?.streamIdleTimeoutMs;
|
|
170
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : ADAPTER_DEFAULT_CEILING_MS;
|
|
171
|
+
} catch {
|
|
172
|
+
return ADAPTER_DEFAULT_CEILING_MS;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function apply(ctx) {
|
|
177
|
+
const enabled = appEnv("SILENCE") !== "off";
|
|
178
|
+
const scale = Number(appEnv("SILENCE_SCALE") ?? "1");
|
|
179
|
+
const factor = Number.isFinite(scale) && scale > 0 ? scale : 1;
|
|
180
|
+
const ceilingOf = ceilingReader(() => runtimeHome());
|
|
181
|
+
const retries = /* @__PURE__ */ new Map();
|
|
182
|
+
const readings = /* @__PURE__ */ new Map();
|
|
183
|
+
let survived = 0;
|
|
184
|
+
const service = {
|
|
185
|
+
reading: (sessionId) => sessionId === void 0 ? void 0 : readings.get(sessionId)
|
|
186
|
+
};
|
|
187
|
+
ctx.provide(BRUINE_SILENCE_SERVICE, service);
|
|
188
|
+
ctx.on("session/event", (session, event) => {
|
|
189
|
+
const id = session?.id === void 0 ? void 0 : String(session.id);
|
|
190
|
+
if (id === void 0) return;
|
|
191
|
+
if (event.type === "llm/retry" && typeof event.data?.retry === "number") retries.set(id, event.data.retry);
|
|
192
|
+
else if (event.type === "step/start" || event.type === "turn/end") retries.delete(id);
|
|
193
|
+
});
|
|
194
|
+
if (!enabled) return;
|
|
195
|
+
ctx.on(
|
|
196
|
+
"llm/stream",
|
|
197
|
+
(options, next) => {
|
|
198
|
+
const source = next();
|
|
199
|
+
const id = options.sessionId === void 0 ? void 0 : String(options.sessionId);
|
|
200
|
+
const ceiling = ceilingOf(options.provider);
|
|
201
|
+
const start = {
|
|
202
|
+
phase: "waiting",
|
|
203
|
+
toolChars: 0,
|
|
204
|
+
effort: options.reasoningEffort ?? currentEffortName(),
|
|
205
|
+
retry: id === void 0 ? 0 : retries.get(id) ?? 0,
|
|
206
|
+
survived
|
|
207
|
+
};
|
|
208
|
+
const watched = watchSilence(source, {
|
|
209
|
+
start,
|
|
210
|
+
budget: (state) => silenceBudget({ ...state, survived }, ceiling, factor),
|
|
211
|
+
onState: (state, quietSince, budget) => {
|
|
212
|
+
if (id !== void 0) readings.set(id, { quietSince, budget, state });
|
|
213
|
+
},
|
|
214
|
+
onResumed: (quiet) => {
|
|
215
|
+
survived = Math.max(survived, quiet);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return (async function* () {
|
|
219
|
+
try {
|
|
220
|
+
yield* watched;
|
|
221
|
+
} finally {
|
|
222
|
+
if (id !== void 0) readings.delete(id);
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
},
|
|
226
|
+
{ global: true }
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
export {
|
|
230
|
+
ADAPTER_DEFAULT_CEILING_MS,
|
|
231
|
+
BRUINE_SILENCE_SERVICE,
|
|
232
|
+
apply,
|
|
233
|
+
name
|
|
234
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/plugins/startup.ts
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { parseCmdline } from "@deepseek-ai/dsh-cmdline";
|
|
4
|
+
var name = "bruine-startup";
|
|
5
|
+
var inject = ["cmdlineArgs"];
|
|
6
|
+
var BRUINE_STARTUP_SERVICE = "bruineStartup";
|
|
7
|
+
function bruineCommand() {
|
|
8
|
+
const program = new Command().name("bruine").description(
|
|
9
|
+
"Interactive terminal agent. Type a message and press Enter; /exit or Ctrl+D quits."
|
|
10
|
+
).helpOption("-h, --help", "show this help").option("-p, --print <task>", "run a task and exit", (value, previous) => [...previous, value], []).option("-f, --output-format <format>", "headless output: text, json, or stream-json", "text").argument("[prompt...]", "optional first prompt, sent before the loop starts").addHelpText(
|
|
11
|
+
"after",
|
|
12
|
+
`
|
|
13
|
+
Examples:
|
|
14
|
+
bruine start an interactive session
|
|
15
|
+
bruine "fix the tests" start and send a first prompt
|
|
16
|
+
`
|
|
17
|
+
);
|
|
18
|
+
return program;
|
|
19
|
+
}
|
|
20
|
+
function apply(ctx) {
|
|
21
|
+
const program = bruineCommand();
|
|
22
|
+
program.action(() => {
|
|
23
|
+
const options = program.opts();
|
|
24
|
+
if (options.print.length > 0) {
|
|
25
|
+
if (!["text", "json", "stream-json"].includes(options.outputFormat)) {
|
|
26
|
+
throw new Error("--output-format must be one of: text, json, stream-json");
|
|
27
|
+
}
|
|
28
|
+
ctx.provide(BRUINE_STARTUP_SERVICE, { headless: { prompts: options.print, format: options.outputFormat } });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const prompt = program.args.join(" ").trim();
|
|
32
|
+
const startup = prompt === "" ? {} : { initialPrompt: prompt };
|
|
33
|
+
ctx.provide(BRUINE_STARTUP_SERVICE, startup);
|
|
34
|
+
});
|
|
35
|
+
parseCmdline(ctx, program);
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
BRUINE_STARTUP_SERVICE,
|
|
39
|
+
apply,
|
|
40
|
+
inject,
|
|
41
|
+
name
|
|
42
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
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/plugins/web-search.ts
|
|
29
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
30
|
+
var name = "bruine-web-search";
|
|
31
|
+
var BRUINE_SEARCH_SERVICE = "bruineSearch";
|
|
32
|
+
function readSearchConfig(env = process.env, read = (p) => readFileSync(p, "utf8"), has = existsSync2) {
|
|
33
|
+
const home = runtimeHome(env);
|
|
34
|
+
const path2 = configReadPath(home);
|
|
35
|
+
try {
|
|
36
|
+
if (!has(path2)) return { provider: "none" };
|
|
37
|
+
const raw = JSON.parse(read(path2));
|
|
38
|
+
const s = raw?.search;
|
|
39
|
+
if (s !== void 0 && typeof s.provider === "string" && s.provider !== "none") {
|
|
40
|
+
return s;
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
return { provider: "none" };
|
|
45
|
+
}
|
|
46
|
+
function clip(sources, max) {
|
|
47
|
+
if (max === void 0 || sources.length <= max) {
|
|
48
|
+
return { sources, truncated: false };
|
|
49
|
+
}
|
|
50
|
+
return { sources: sources.slice(0, max), truncated: true };
|
|
51
|
+
}
|
|
52
|
+
function makeSearchProvider(cfg, env, doFetch) {
|
|
53
|
+
return {
|
|
54
|
+
id: "bruine",
|
|
55
|
+
available() {
|
|
56
|
+
if (cfg.provider === "none") return false;
|
|
57
|
+
if (cfg.provider === "searxng") return cfg.url !== void 0 && cfg.url !== "";
|
|
58
|
+
const key = cfg.apiKeyEnv ?? "";
|
|
59
|
+
return env[key] !== void 0 && env[key] !== "";
|
|
60
|
+
},
|
|
61
|
+
async search(request, signal) {
|
|
62
|
+
const q = encodeURIComponent(request.query);
|
|
63
|
+
if (cfg.provider === "searxng") {
|
|
64
|
+
const res = await doFetch(`${(cfg.url ?? "").replace(/\/+$/, "")}/search?q=${q}&format=json`, { signal });
|
|
65
|
+
if (!res.ok) throw new Error(`searxng: HTTP ${res.status}`);
|
|
66
|
+
const body = await res.json();
|
|
67
|
+
const sources = (body?.results ?? []).map((r) => ({
|
|
68
|
+
url: String(r.url),
|
|
69
|
+
title: r.title === void 0 ? void 0 : String(r.title),
|
|
70
|
+
snippet: r.content === void 0 ? void 0 : String(r.content)
|
|
71
|
+
}));
|
|
72
|
+
return clip(sources, request.maxResults);
|
|
73
|
+
}
|
|
74
|
+
if (cfg.provider === "brave") {
|
|
75
|
+
const key = env[cfg.apiKeyEnv ?? "BRAVE_API_KEY"] ?? "";
|
|
76
|
+
const res = await doFetch(
|
|
77
|
+
`https://api.search.brave.com/res/v1/web/search?q=${q}${request.maxResults !== void 0 ? `&count=${request.maxResults}` : ""}`,
|
|
78
|
+
{ signal, headers: { "X-Subscription-Token": key, Accept: "application/json" } }
|
|
79
|
+
);
|
|
80
|
+
if (!res.ok) throw new Error(`brave: HTTP ${res.status}`);
|
|
81
|
+
const body = await res.json();
|
|
82
|
+
const sources = (body?.web?.results ?? []).map((r) => ({
|
|
83
|
+
url: String(r.url),
|
|
84
|
+
title: r.title === void 0 ? void 0 : String(r.title),
|
|
85
|
+
snippet: r.description === void 0 ? void 0 : String(r.description)
|
|
86
|
+
}));
|
|
87
|
+
return clip(sources, request.maxResults);
|
|
88
|
+
}
|
|
89
|
+
if (cfg.provider === "tavily") {
|
|
90
|
+
const key = env[cfg.apiKeyEnv ?? "TAVILY_API_KEY"] ?? "";
|
|
91
|
+
const res = await doFetch("https://api.tavily.com/search", {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
api_key: key,
|
|
96
|
+
query: request.query,
|
|
97
|
+
max_results: request.maxResults ?? 8
|
|
98
|
+
}),
|
|
99
|
+
signal
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok) throw new Error(`tavily: HTTP ${res.status}`);
|
|
102
|
+
const body = await res.json();
|
|
103
|
+
const sources = (body?.results ?? []).map((r) => ({
|
|
104
|
+
url: String(r.url),
|
|
105
|
+
title: r.title === void 0 ? void 0 : String(r.title),
|
|
106
|
+
snippet: r.content === void 0 ? void 0 : String(r.content)
|
|
107
|
+
}));
|
|
108
|
+
return clip(sources, request.maxResults);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
content: "Web search is not configured. Choose a provider in bruine setup (SearXNG, Brave, or Tavily).",
|
|
112
|
+
sources: [],
|
|
113
|
+
truncated: false
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function apply(ctx) {
|
|
119
|
+
ctx.provide(BRUINE_SEARCH_SERVICE, { id: "bruine" });
|
|
120
|
+
const provider = makeSearchProvider(readSearchConfig(), process.env, fetch);
|
|
121
|
+
let dispose;
|
|
122
|
+
ctx.inject(["web"], (c) => {
|
|
123
|
+
const web = c.web ?? ctx.get("web");
|
|
124
|
+
if (typeof web?.registerSearchProvider === "function") {
|
|
125
|
+
dispose = web.registerSearchProvider(provider);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return () => {
|
|
129
|
+
dispose?.();
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
BRUINE_SEARCH_SERVICE,
|
|
134
|
+
apply,
|
|
135
|
+
makeSearchProvider,
|
|
136
|
+
name,
|
|
137
|
+
readSearchConfig
|
|
138
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ziamana/bruine",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive terminal coding agent that runs your own models, local or OpenAI-compatible",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ziamana/bruine.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/ziamana/bruine#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/ziamana/bruine/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai",
|
|
17
|
+
"agent",
|
|
18
|
+
"cli",
|
|
19
|
+
"coding-assistant",
|
|
20
|
+
"llm",
|
|
21
|
+
"local-llm",
|
|
22
|
+
"llama.cpp",
|
|
23
|
+
"openai-compatible",
|
|
24
|
+
"terminal",
|
|
25
|
+
"tui"
|
|
26
|
+
],
|
|
27
|
+
"bin": {
|
|
28
|
+
"bruine": "dist/bin.js",
|
|
29
|
+
"kumo": "dist/bin.js"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
"./startup": "./dist/plugins/startup.js",
|
|
33
|
+
"./repl": "./dist/plugins/repl.js",
|
|
34
|
+
"./headless": "./dist/plugins/headless.js",
|
|
35
|
+
"./render": "./dist/plugins/render.js",
|
|
36
|
+
"./approval": "./dist/plugins/approval.js",
|
|
37
|
+
"./herdr": "./dist/plugins/herdr.js",
|
|
38
|
+
"./modes": "./dist/plugins/modes.js",
|
|
39
|
+
"./web-search": "./dist/plugins/web-search.js",
|
|
40
|
+
"./mcp": "./dist/plugins/mcp.js",
|
|
41
|
+
"./silence": "./dist/plugins/silence.js",
|
|
42
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
43
|
+
"./package.json": "./package.json",
|
|
44
|
+
"./compat": "./dist/compat.js"
|
|
45
|
+
},
|
|
46
|
+
"dsh": {
|
|
47
|
+
"bundle": {
|
|
48
|
+
"patch": "./cordis.patch.yml"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=22"
|
|
53
|
+
},
|
|
54
|
+
"packageManager": "pnpm@12.4.2",
|
|
55
|
+
"files": [
|
|
56
|
+
"dist",
|
|
57
|
+
"skills",
|
|
58
|
+
"THIRD_PARTY_NOTICES.md",
|
|
59
|
+
"cordis.patch.yml"
|
|
60
|
+
],
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsup",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"bench": "node --experimental-strip-types --import ./bench/ts-hooks.mjs bench/run.ts",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"test:e2e": "vitest run test/e2e"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/node": "^24.0.0",
|
|
70
|
+
"@xterm/headless": "^6.0.0",
|
|
71
|
+
"node-pty": "^1.1.0",
|
|
72
|
+
"tsup": "^8.0.0",
|
|
73
|
+
"typescript": "^5.5.0",
|
|
74
|
+
"vitest": "^3.0.0"
|
|
75
|
+
},
|
|
76
|
+
"dependencies": {
|
|
77
|
+
"@deepseek-ai/dsh": "0.1.5-rc.3",
|
|
78
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.3",
|
|
79
|
+
"@deepseek-ai/dsh-attachment": "0.1.5-rc.3",
|
|
80
|
+
"@deepseek-ai/dsh-cmdline": "0.1.5-rc.3",
|
|
81
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.3",
|
|
82
|
+
"@deepseek-ai/dsh-mcp-client": "0.1.5-rc.3",
|
|
83
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.3",
|
|
84
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
85
|
+
"@earendil-works/pi-tui": "0.85.1",
|
|
86
|
+
"commander": "^15.0.0",
|
|
87
|
+
"diff": "^9.0.0",
|
|
88
|
+
"highlight.js": "^11.12.0",
|
|
89
|
+
"jpeg-js": "0.4.4",
|
|
90
|
+
"string-width": "^8.3.0",
|
|
91
|
+
"yaml": "^2.9.1"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: Review diffs like the next reader matters: intent, correctness, edge cases, tests, and long-term cost.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Code review
|
|
7
|
+
|
|
8
|
+
Use this skill when asked to review a diff, a pull request, or your own change before shipping.
|
|
9
|
+
|
|
10
|
+
## Read in this order
|
|
11
|
+
1. **Intent**: what is this change for? Is it the smallest honest way to do that?
|
|
12
|
+
2. **Correctness**: trace the happy path once, then the boundaries: empty, single, max, wrong-type, concurrent, canceled, partially-failed.
|
|
13
|
+
3. **Contracts**: callers and callees: did any signature, ordering, error type, or default change silently?
|
|
14
|
+
4. **Tests**: does a test fail if the bug returns? Tests that only assert the code compiles are cost, not safety.
|
|
15
|
+
5. **Longevity**: will the next person understand this in six months? Is there now a second way to do the same thing?
|
|
16
|
+
|
|
17
|
+
## Severity when reporting
|
|
18
|
+
- **[block]** wrong, unsafe, or silently breaking a contract. Must fix.
|
|
19
|
+
- **[should]** real weakness with a concrete better shape. Fix unless out of scope.
|
|
20
|
+
- **[nit]** taste-level. Say it, then move on; never gate on a nit.
|
|
21
|
+
|
|
22
|
+
## Rules
|
|
23
|
+
- Comment on the code, not the person; every "why not X?" beats every "this is bad".
|
|
24
|
+
- One concrete suggestion per finding: show the shape of the fix, not just the flaw.
|
|
25
|
+
- Check the diff for things that must never appear: secrets, tokens, personal paths, debug prints, dead flags, commented-out code.
|
|
26
|
+
- Verify, don't trust: run the tests the diff claims are green; run the repro for a claimed fix.
|
|
27
|
+
- If the change is yours: re-read it as a stranger once, top to bottom, before presenting it.
|
|
28
|
+
|
|
29
|
+
## Sign off
|
|
30
|
+
Say what you actually checked and what you did not. "Tests pass on Linux, Windows CI pending" is a review; "looks good" is not.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-workflow
|
|
3
|
+
description: Keep git history clean and reviewable: focused branches, atomic commits, honest messages, safe pushes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git workflow
|
|
7
|
+
|
|
8
|
+
Use this skill whenever you are asked to commit, branch, rebase, or push work.
|
|
9
|
+
|
|
10
|
+
## Branches
|
|
11
|
+
- Work on a focused branch named `type/scope-short-title` (e.g. `feat/login`, `fix/cache-prefix`).
|
|
12
|
+
- Never commit directly to the default branch unless the user explicitly asked for it.
|
|
13
|
+
- Start from an up-to-date base: check the current branch and how far ahead/behind it is before making changes.
|
|
14
|
+
|
|
15
|
+
## Commits
|
|
16
|
+
- One logical change per commit. Stage deliberately (`git add <paths>`), never `git add -A` when unrelated files are dirty.
|
|
17
|
+
- Message format: a command-style subject under ~70 characters ("Fix cache prefix on mode flip"), then a blank line and a body explaining *why*, not *what*.
|
|
18
|
+
- No "WIP", no emoji, no "as requested". The diff says what; the message says why.
|
|
19
|
+
- If a commit would need "and also" in its subject, split it.
|
|
20
|
+
|
|
21
|
+
## Working with the user's state
|
|
22
|
+
- Check `git status --short` before touching files; never revert, stash, or overwrite changes you did not make.
|
|
23
|
+
- When rebasing or amending, prefer `--force-with-lease` over `--force` for pushes, and say plainly what rewrote history in your final message.
|
|
24
|
+
- Never push, tag, or open a PR unless the user asked.
|
|
25
|
+
|
|
26
|
+
## Recovery
|
|
27
|
+
- Losing work: check `git reflog` before declaring anything destroyed.
|
|
28
|
+
- Merge conflicts: resolve hunk by hunk, keep both intents when they are compatible, and run the tests before declaring the conflict solved.
|
|
29
|
+
|
|
30
|
+
## Verification
|
|
31
|
+
- Before the final commit of a task, run the project's checks (typecheck, build, tests) and report anything you could not run instead of silently skipping it.
|