@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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +315 -0
  3. package/THIRD_PARTY_NOTICES.md +24 -0
  4. package/cordis.patch.yml +166 -0
  5. package/dist/bin.js +5358 -0
  6. package/dist/compat.js +40 -0
  7. package/dist/plugins/approval.js +147 -0
  8. package/dist/plugins/headless.js +236 -0
  9. package/dist/plugins/herdr.js +470 -0
  10. package/dist/plugins/mcp.js +275 -0
  11. package/dist/plugins/modes.js +850 -0
  12. package/dist/plugins/render.js +3723 -0
  13. package/dist/plugins/repl.js +9589 -0
  14. package/dist/plugins/silence.js +234 -0
  15. package/dist/plugins/startup.js +42 -0
  16. package/dist/plugins/web-search.js +138 -0
  17. package/package.json +93 -0
  18. package/skills/code-review/SKILL.md +30 -0
  19. package/skills/git-workflow/SKILL.md +31 -0
  20. package/skills/impeccable/LICENSE +191 -0
  21. package/skills/impeccable/NOTICE.md +11 -0
  22. package/skills/impeccable/SKILL.md +87 -0
  23. package/skills/impeccable/reference/adapt.md +318 -0
  24. package/skills/impeccable/reference/adapt.native.md +58 -0
  25. package/skills/impeccable/reference/android.md +46 -0
  26. package/skills/impeccable/reference/animate.md +89 -0
  27. package/skills/impeccable/reference/audit.md +137 -0
  28. package/skills/impeccable/reference/audit.native.md +139 -0
  29. package/skills/impeccable/reference/bolder.md +33 -0
  30. package/skills/impeccable/reference/clarify.md +94 -0
  31. package/skills/impeccable/reference/colorize.md +86 -0
  32. package/skills/impeccable/reference/component-review.md +63 -0
  33. package/skills/impeccable/reference/craft-floor.md +44 -0
  34. package/skills/impeccable/reference/craft.md +5 -0
  35. package/skills/impeccable/reference/critique.md +806 -0
  36. package/skills/impeccable/reference/degraded/asset-producer.md +42 -0
  37. package/skills/impeccable/reference/degraded/documenter.md +24 -0
  38. package/skills/impeccable/reference/degraded/finish-reviewer.md +38 -0
  39. package/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  40. package/skills/impeccable/reference/delight.md +70 -0
  41. package/skills/impeccable/reference/distill.md +111 -0
  42. package/skills/impeccable/reference/doctor.md +54 -0
  43. package/skills/impeccable/reference/document.md +416 -0
  44. package/skills/impeccable/reference/extract.md +69 -0
  45. package/skills/impeccable/reference/generate.md +101 -0
  46. package/skills/impeccable/reference/harden.md +345 -0
  47. package/skills/impeccable/reference/hooks.md +113 -0
  48. package/skills/impeccable/reference/init.md +131 -0
  49. package/skills/impeccable/reference/ios.md +51 -0
  50. package/skills/impeccable/reference/layout.md +84 -0
  51. package/skills/impeccable/reference/live-setup.md +104 -0
  52. package/skills/impeccable/reference/live.md +325 -0
  53. package/skills/impeccable/reference/mode-operate.md +21 -0
  54. package/skills/impeccable/reference/mode-persuade.md +19 -0
  55. package/skills/impeccable/reference/mode-read.md +21 -0
  56. package/skills/impeccable/reference/new-work.md +154 -0
  57. package/skills/impeccable/reference/onboard.md +234 -0
  58. package/skills/impeccable/reference/operate.md +61 -0
  59. package/skills/impeccable/reference/optimize.md +258 -0
  60. package/skills/impeccable/reference/overdrive.md +127 -0
  61. package/skills/impeccable/reference/polish.md +105 -0
  62. package/skills/impeccable/reference/quieter.md +99 -0
  63. package/skills/impeccable/reference/region-map.md +26 -0
  64. package/skills/impeccable/reference/routing.md +24 -0
  65. package/skills/impeccable/reference/shape.md +59 -0
  66. package/skills/impeccable/reference/typeset.md +80 -0
  67. package/skills/impeccable/reference/visualize.md +46 -0
  68. package/skills/impeccable/scripts/VERSION +1 -0
  69. package/skills/impeccable/scripts/command-metadata.json +98 -0
  70. package/skills/impeccable/scripts/data/font-index-failures.json +121 -0
  71. package/skills/impeccable/scripts/data/font-index.json +1 -0
  72. package/skills/impeccable/scripts/impeccable +206 -0
  73. package/skills/impeccable/scripts/impeccable.cmd +214 -0
  74. package/skills/impeccable/scripts/live-browser-dom.js +167 -0
  75. package/skills/impeccable/scripts/live-browser-ignores.js +242 -0
  76. package/skills/impeccable/scripts/live-browser-session.js +148 -0
  77. package/skills/impeccable/scripts/live-browser.js +13510 -0
  78. package/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  79. package/skills/make-interfaces-feel-better/LICENSE +21 -0
  80. package/skills/make-interfaces-feel-better/SKILL.md +187 -0
  81. package/skills/make-interfaces-feel-better/agents/openai.yaml +3 -0
  82. package/skills/make-interfaces-feel-better/animations.md +403 -0
  83. package/skills/make-interfaces-feel-better/icons.md +63 -0
  84. package/skills/make-interfaces-feel-better/performance.md +88 -0
  85. package/skills/make-interfaces-feel-better/surfaces.md +256 -0
  86. package/skills/make-interfaces-feel-better/typography.md +157 -0
  87. package/skills/playwright-cli/LICENSE +201 -0
  88. package/skills/playwright-cli/SKILL.md +489 -0
  89. package/skills/playwright-cli/references/element-attributes.md +23 -0
  90. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  91. package/skills/playwright-cli/references/pr-attachments.md +60 -0
  92. package/skills/playwright-cli/references/request-mocking.md +87 -0
  93. package/skills/playwright-cli/references/running-code.md +245 -0
  94. package/skills/playwright-cli/references/session-management.md +227 -0
  95. package/skills/playwright-cli/references/storage-state.md +275 -0
  96. package/skills/playwright-cli/references/test-generation.md +433 -0
  97. package/skills/playwright-cli/references/tracing.md +139 -0
  98. package/skills/playwright-cli/references/video-recording.md +216 -0
  99. package/skills/remotion/SKILL.md +42 -0
  100. package/skills/systematic-debugging/SKILL.md +26 -0
  101. package/skills/thermo-nuclear-code-quality-review/LICENSE +21 -0
  102. package/skills/thermo-nuclear-code-quality-review/SKILL.md +192 -0
  103. package/skills/write-tests/SKILL.md +35 -0
  104. package/skills/youtube-transcript/LICENSE +21 -0
  105. package/skills/youtube-transcript/SKILL.md +41 -0
  106. package/skills/youtube-transcript/package.json +8 -0
  107. package/skills/youtube-transcript/transcript.js +44 -0
package/dist/compat.js ADDED
@@ -0,0 +1,40 @@
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
+ var SKILLS_MANIFEST_NAME = ".bruine-installed.json";
28
+ function manifestReadPath(skillsDir, pathMod = path) {
29
+ return compatReadPath(pathMod.join(skillsDir, SKILLS_MANIFEST_NAME), pathMod.join(skillsDir, ".kumo-installed.json"));
30
+ }
31
+ export {
32
+ SKILLS_MANIFEST_NAME,
33
+ appEnv,
34
+ appHome,
35
+ compatReadPath,
36
+ configReadPath,
37
+ configWritePath,
38
+ manifestReadPath,
39
+ runtimeHome
40
+ };
@@ -0,0 +1,147 @@
1
+ // src/plugins/approval.ts
2
+ var name = "bruine-approval";
3
+ function approvalTitle(request, describe) {
4
+ let detail = "";
5
+ if (request.callId !== void 0) {
6
+ const info = describe?.(request.callId);
7
+ if (info !== void 0 && info.summary !== "") detail = info.summary;
8
+ }
9
+ if (detail === "" && request.reason !== void 0 && request.reason !== "") {
10
+ detail = request.reason;
11
+ }
12
+ const target = detail === "" ? request.toolName : `${request.toolName}: ${detail}`;
13
+ return `? Allow ${target}`;
14
+ }
15
+ function buildQuestion(request, describe) {
16
+ return `${approvalTitle(request, describe)} ? [y/N] `;
17
+ }
18
+ function alwaysLabel(scope, toolName) {
19
+ if (scope === void 0) return "Always for this session";
20
+ const at = scope.indexOf(":");
21
+ if (at > 0) {
22
+ const command = scope.slice(at + 1).replace(/\s+/g, " ").trim();
23
+ const shown = command.length > 48 ? `${command.slice(0, 47)}\u2026` : command;
24
+ return `Always allow "${shown}" this session`;
25
+ }
26
+ return `Always allow every ${toolName === "edit" || toolName === "write" ? toolName : `${toolName} call`} this session`;
27
+ }
28
+ function parseAnswer(line) {
29
+ return /^y(es)?$/i.test(line.trim());
30
+ }
31
+ function apply(ctx) {
32
+ let repl;
33
+ let render;
34
+ let modes;
35
+ ctx.inject(["bruineRepl"], (c) => {
36
+ repl = c.bruineRepl;
37
+ });
38
+ ctx.inject(["bruineRender"], (c) => {
39
+ render = c.bruineRender;
40
+ });
41
+ ctx.inject(["bruineModes"], (c) => {
42
+ modes = c.bruineModes;
43
+ });
44
+ ctx.on(
45
+ "user-questions/request",
46
+ (request, next) => {
47
+ if (repl === void 0 || !(modes?.governs(request.agent) ?? request.agent === repl.agent)) return next();
48
+ if (request.signal?.aborted) {
49
+ return { answers: request.questions.map((q) => ({ id: q.id, selected: [], custom: "skipped by the user" })) };
50
+ }
51
+ const ui = repl.ui;
52
+ if (ui !== void 0 && typeof ui.askQuestions === "function") {
53
+ return ui.askQuestions(request.questions).then((answers) => {
54
+ const resolved = answers ?? request.questions.map((q) => ({ id: q.id, selected: [], custom: "skipped by the user" }));
55
+ return { answers: resolved };
56
+ });
57
+ }
58
+ const ask = repl.ask;
59
+ if (ask === void 0) return next();
60
+ return (async () => {
61
+ const answers = [];
62
+ for (const q of request.questions) {
63
+ const opts = [...q.options ?? [], { label: "Other\u2026" }];
64
+ const lines = [
65
+ q.header !== void 0 ? `${q.header}: ${q.question}` : q.question,
66
+ ...opts.map((o, i) => `${String(i + 1)}) ${o.label}${o.description !== void 0 ? ` (${o.description})` : ""}`)
67
+ ].join("\n");
68
+ const raw = (await ask(`${lines}
69
+ [1]`)).trim();
70
+ const n = Number.parseInt(raw, 10);
71
+ if (Number.isInteger(n) && n >= 1 && n <= opts.length) {
72
+ const chosen = opts[n - 1];
73
+ if (chosen.label === "Other\u2026") {
74
+ const custom = (await ask("Other answer: ")).trim();
75
+ answers.push({ id: q.id, selected: [], ...custom !== "" ? { custom } : {} });
76
+ } else {
77
+ answers.push({ id: q.id, selected: [chosen.label] });
78
+ }
79
+ } else if (raw !== "") {
80
+ answers.push({ id: q.id, selected: [], custom: raw });
81
+ } else {
82
+ answers.push({ id: q.id, selected: opts[0] !== void 0 && opts[0].label !== "Other\u2026" ? [opts[0].label] : [], custom: void 0 });
83
+ }
84
+ }
85
+ return { answers };
86
+ })();
87
+ }
88
+ );
89
+ ctx.on(
90
+ "approval/request",
91
+ (request, next) => {
92
+ if (!(modes?.governs(request.agent) ?? request.agent === repl?.agent)) return next();
93
+ if (request.signal?.aborted) return "cancelled";
94
+ if (modes !== void 0) {
95
+ const decision = modes.decisionFor(request.callId);
96
+ if (decision === "allow") return "allowed-once";
97
+ if (decision === "deny") return "rejected";
98
+ if (decision !== "ask") return repl === void 0 ? "rejected" : next();
99
+ }
100
+ if (repl === void 0) return "rejected";
101
+ const ui = repl.ui;
102
+ if (ui !== void 0) {
103
+ const title = approvalTitle(request, (id) => render?.describe?.(id));
104
+ return ui.askChoice(
105
+ title,
106
+ [
107
+ { value: "allow", label: "Allow once" },
108
+ { value: "always", label: alwaysLabel(modes?.alwaysScope?.(request.callId), request.toolName) },
109
+ { value: "reject", label: "Reject" }
110
+ ],
111
+ { keys: ["y", "a", "n"] }
112
+ ).then((choice) => {
113
+ if (choice === 0) return "allowed-once";
114
+ if (choice === 1) {
115
+ modes?.rememberFor(request.callId);
116
+ return "allowed-once";
117
+ }
118
+ return "rejected";
119
+ });
120
+ }
121
+ const screen = render?.screen;
122
+ const question = buildQuestion(request, (id) => screen?.tools.describe(id));
123
+ screen?.reasoning.end();
124
+ const ask = repl.ask;
125
+ if (ask === void 0) return next();
126
+ return ask(question).then((answer) => {
127
+ const always = /^a(lways?)?$/i.test(answer.trim());
128
+ const ok = always || parseAnswer(answer);
129
+ if (always) modes?.rememberFor(request.callId);
130
+ if (screen !== void 0) {
131
+ const mark = ok ? screen.icons.ok : screen.icons.fail;
132
+ screen.screen.write(`${mark} ${ok ? always ? "allowed for session" : "allowed" : "rejected"}
133
+ `);
134
+ }
135
+ return ok ? "allowed-once" : "rejected";
136
+ });
137
+ }
138
+ );
139
+ }
140
+ export {
141
+ alwaysLabel,
142
+ apply,
143
+ approvalTitle,
144
+ buildQuestion,
145
+ name,
146
+ parseAnswer
147
+ };
@@ -0,0 +1,236 @@
1
+ // src/plugins/headless.ts
2
+ import { randomUUID } from "crypto";
3
+ import { SessionId } from "@deepseek-ai/dsh-session";
4
+ import { installModelSelection } from "@deepseek-ai/dsh-agent";
5
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
6
+ import { join as join2 } from "path";
7
+
8
+ // src/compat.ts
9
+ import { existsSync } from "fs";
10
+ import { homedir } from "os";
11
+ import path from "path";
12
+ function appEnv(suffix, env = process.env) {
13
+ return env[`BRUINE_${suffix}`] ?? env[`KUMO_${suffix}`];
14
+ }
15
+ function appHome(env = process.env, userHome = homedir(), pathMod = path, exists = existsSync) {
16
+ const explicit = appEnv("HOME", env);
17
+ if (explicit !== void 0) return explicit;
18
+ const current = pathMod.join(userHome, ".bruine");
19
+ const legacy = pathMod.join(userHome, ".kumo");
20
+ return exists(current) ? current : exists(legacy) ? legacy : current;
21
+ }
22
+ function runtimeHome(env = process.env, pathMod = path) {
23
+ return appEnv("HOME", env) ?? env.DSH_HOME ?? appHome(env, homedir(), pathMod);
24
+ }
25
+
26
+ // src/setup/skills.ts
27
+ import { cp, lstat, mkdir, readFile, readdir, rename, rm, symlink, writeFile } from "fs/promises";
28
+ import { existsSync as existsSync2 } from "fs";
29
+ import { homedir as homedir2 } from "os";
30
+ import path2, { dirname, join } from "path";
31
+ var SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
32
+ function parseSkillFrontmatter(md) {
33
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(md);
34
+ if (m === null || m[1] === void 0) return {};
35
+ const lines = m[1].split(/\r?\n/);
36
+ const out = {};
37
+ const unquote = (v) => v.replaceAll(/^["']|["']$/g, "");
38
+ for (let i = 0; i < lines.length; i += 1) {
39
+ const kv = /^([A-Za-z_][\w-]*)\s*:\s*(.*)$/.exec(lines[i].trimEnd());
40
+ if (kv === null || kv[1] === void 0 || kv[2] === void 0) continue;
41
+ const key = kv[1];
42
+ let value = kv[2].trim();
43
+ const rest = [];
44
+ let j = i + 1;
45
+ while (j < lines.length && (lines[j].trim() === "" || /^\s+\S/.test(lines[j]))) {
46
+ rest.push(lines[j]);
47
+ j += 1;
48
+ }
49
+ while (rest.length > 0 && rest[rest.length - 1].trim() === "") rest.pop();
50
+ const block = /^([>|])[+-]?\d*$/.exec(value);
51
+ if (block !== null) {
52
+ const body = rest.map((l) => l.trim());
53
+ value = block[1] === "|" ? body.join("\n") : body.join(" ").replaceAll(/\s+/g, " ").trim();
54
+ i = j - 1;
55
+ } else if (rest.length > 0 && key !== "name") {
56
+ value = [value, ...rest.map((l) => l.trim())].filter((l) => l !== "").join(" ");
57
+ i = j - 1;
58
+ }
59
+ if (key === "name") out.name = unquote(value);
60
+ if (key === "description") out.description = unquote(value);
61
+ }
62
+ return out;
63
+ }
64
+ async function readSkillsUnder(root) {
65
+ const out = [];
66
+ if (!existsSync2(root)) return out;
67
+ for (const entry of await readdir(root, { withFileTypes: true })) {
68
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
69
+ const skillPath = join(root, entry.name, "SKILL.md");
70
+ if (!existsSync2(skillPath)) continue;
71
+ const fm = parseSkillFrontmatter(await readFile(skillPath, "utf8"));
72
+ if (fm.name === void 0 || !SAFE_NAME.test(fm.name)) continue;
73
+ out.push({ name: fm.name, description: fm.description ?? "", dir: join(root, entry.name) });
74
+ }
75
+ return out;
76
+ }
77
+ async function scanProjectSkills(cwd, pathMod = path2) {
78
+ const out = [];
79
+ const seen = /* @__PURE__ */ new Set();
80
+ for (const rel of [
81
+ [".agents", "skills"],
82
+ [".dsh", "skills"]
83
+ ]) {
84
+ for (const skill of await readSkillsUnder(pathMod.join(cwd, ...rel))) {
85
+ if (seen.has(skill.name)) continue;
86
+ seen.add(skill.name);
87
+ out.push(skill);
88
+ }
89
+ }
90
+ return out.sort((a, b) => a.name.localeCompare(b.name));
91
+ }
92
+ async function readAvailableSkills(homeSkillsDir, cwd) {
93
+ const byName = /* @__PURE__ */ new Map();
94
+ for (const skill of await readSkillsUnder(homeSkillsDir)) {
95
+ byName.set(skill.name, { ...skill, scope: "user" });
96
+ }
97
+ for (const skill of await scanProjectSkills(cwd)) {
98
+ byName.set(skill.name, { ...skill, scope: "project" });
99
+ }
100
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
101
+ }
102
+
103
+ // src/plugins/ui-skills.ts
104
+ var UI_SKILLS = ["impeccable", "make-interfaces-feel-better"];
105
+ var WEB_TASK = /\b(?:sites?(?:\s+(?:web|internet))?|web\s?sites?|web\s?apps?|landing(?:\s+page)?|home\s?page|page\s+(?:web|d['’]accueil)|front[- ]?end|dashboard|tableau\s+de\s+bord|html|css|tailwind|maquette|mock-?ups?|portfolio|boutique(?:\s+en\s+ligne)?|e-?commerce|interface\s+(?:utilisateur|graphique|web)|user\s+interface|ui|ux|gui|web\s+design|design\s+system|re-?design)\b/i;
106
+ function isUiTask(text) {
107
+ return WEB_TASK.test(text);
108
+ }
109
+ function uiSkillsHint(text, available) {
110
+ if (!isUiTask(text)) return void 0;
111
+ const names = UI_SKILLS.filter((name2) => available.includes(name2));
112
+ if (names.length === 0) return void 0;
113
+ const list = names.map((name2) => `\`${name2}\``).join(" and ");
114
+ return [
115
+ "<system-reminder>",
116
+ `If this task builds or changes a web page, a site or an interface: call the \`skill\` tool for ${list} first, read what they say, and apply it to the design and the details. Do this before writing any code.`,
117
+ "Where the page needs pictures, draw them yourself as detailed inline SVG with depth, light, shadow and texture; never grey boxes, emoji or placeholders.",
118
+ "When the user asks for a complete or professional site, plan all of it first with todo_write: every page a real site of that kind has, and for each page the features people expect (a shop: a home with many sections, a catalogue with filters, sort and search, a product page, a cart, a checkout, an account, a journal, legal pages, a 404 and a contact page). Build all of it. A first version that works is not the end: go through the pages again to add detail, content and polish.",
119
+ "Invent the brand name and the content for the business the user describes. The name of the working folder or of this tool is not the brand.",
120
+ "Before you say it is done, open every page in the browser at desktop and at phone width, look at the screenshots, and fix whatever is cut off, overlapping, low in contrast or empty.",
121
+ "</system-reminder>"
122
+ ].join("\n");
123
+ }
124
+ function withUiSkillsHint(text, parts, available) {
125
+ const hint = uiSkillsHint(text, available);
126
+ return hint === void 0 ? parts : [...parts, { type: "text", text: hint }];
127
+ }
128
+ async function availableSkillNames(homeSkillsDir, cwd) {
129
+ try {
130
+ return (await readAvailableSkills(homeSkillsDir, cwd)).map((skill) => skill.name);
131
+ } catch {
132
+ return [];
133
+ }
134
+ }
135
+
136
+ // src/plugins/headless.ts
137
+ var name = "bruine-headless";
138
+ var inject = ["agentDefaultModel", "agents", "sessions", "bruineStartup", "bruineModes"];
139
+ async function stdinTask() {
140
+ let task = "";
141
+ for await (const chunk of process.stdin) task += String(chunk);
142
+ return task;
143
+ }
144
+ function messageText(event) {
145
+ const blocks = event?.data?.message?.content;
146
+ return Array.isArray(blocks) ? blocks.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n") : "";
147
+ }
148
+ async function run(ctx) {
149
+ await ctx.get("loader")?.await();
150
+ const startup = ctx.get("bruineStartup");
151
+ const task = startup?.headless;
152
+ if (task === void 0 || task.prompts.length === 0) throw new Error("headless task is missing");
153
+ const modes = ctx.get("bruineModes");
154
+ if (modes === void 0) throw new Error("permission gate is unavailable");
155
+ const agents = ctx.get("agents");
156
+ const sessions = ctx.get("sessions");
157
+ const selection = ctx.get("agentDefaultModel")?.currentSelection();
158
+ if (agents === void 0 || sessions === void 0 || selection === void 0) throw new Error("agent services are unavailable");
159
+ const selectionRef = { current: selection, assembled: void 0 };
160
+ const handle = await agents.create({
161
+ sessionId: SessionId(`session-${randomUUID()}`),
162
+ meta: { cwd: process.cwd() },
163
+ agentOptions: { provider: selection.provider, model: selection.model },
164
+ setup: (agentCtx) => {
165
+ installModelSelection(agentCtx, selectionRef);
166
+ }
167
+ });
168
+ const { agent } = handle;
169
+ modes.govern(agent);
170
+ await agent.whenIdle();
171
+ const started = Date.now();
172
+ let text = "";
173
+ let reason = "error";
174
+ let streamed = "";
175
+ const writeEvent = (event) => {
176
+ if (task.format === "stream-json") process.stdout.write(`${JSON.stringify(event)}
177
+ `);
178
+ };
179
+ const offStream = ctx.on("agent/assistant-stream", ({ agent: subject, frame }) => {
180
+ if (subject !== agent || task.format !== "stream-json") return;
181
+ if (frame?.type === "chunk" && frame.chunk?.type === "text-delta") {
182
+ streamed += String(frame.chunk.text ?? "");
183
+ writeEvent({ type: "text", text: frame.chunk.text });
184
+ }
185
+ });
186
+ const offSession = ctx.on("session/event", (session, event) => {
187
+ if (session !== agent.session) return;
188
+ if (event?.type === "assistant/message") text = messageText(event);
189
+ if (event?.type === "turn/end") reason = String(event.data?.reason?.kind ?? "error");
190
+ if (event?.type === "tool/call") writeEvent({ type: "tool", name: event.data?.name, callId: event.data?.callId });
191
+ });
192
+ try {
193
+ for (const raw of task.prompts) {
194
+ const prompt = raw === "-" ? await stdinTask() : raw;
195
+ if (prompt.trim() === "") throw new Error("-p needs a non-empty task");
196
+ text = "";
197
+ streamed = "";
198
+ reason = "error";
199
+ const skills = await availableSkillNames(join2(runtimeHome(), "skills"), process.cwd());
200
+ agent.followup(createUserMessage({ content: withUiSkillsHint(prompt, [{ type: "text", text: prompt }], skills), source: { kind: "user" } }));
201
+ await agent.whenIdle();
202
+ if (text === "") text = streamed;
203
+ if (reason !== "completed") break;
204
+ }
205
+ await sessions.flush(agent.session);
206
+ const result = {
207
+ ok: reason === "completed",
208
+ text,
209
+ reason,
210
+ sessionId: String(agent.session.id),
211
+ durationMs: Date.now() - started,
212
+ tools: modes.log.map(({ tool, decision }) => ({ name: tool, decision }))
213
+ };
214
+ if (task.format === "text") process.stdout.write(result.text);
215
+ else process.stdout.write(`${JSON.stringify(result)}
216
+ `);
217
+ return result.ok ? 0 : 1;
218
+ } finally {
219
+ offStream();
220
+ offSession();
221
+ await handle.dispose();
222
+ }
223
+ }
224
+ function apply(ctx) {
225
+ const exit = ctx.get("appExit");
226
+ if (exit === void 0) throw new Error("bruine-headless: appExit is unavailable");
227
+ void run(ctx).then(exit, (error) => {
228
+ console.error(`bruine: ${error instanceof Error ? error.message : String(error)}`);
229
+ exit(1);
230
+ });
231
+ }
232
+ export {
233
+ apply,
234
+ inject,
235
+ name
236
+ };