oc-tweaks 0.1.0 → 0.1.2

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.
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/cli/init.ts
5
+ import { mkdir } from "fs/promises";
6
+ import { dirname } from "path";
7
+ var DEFAULT_CONFIG = {
8
+ notify: { enabled: true },
9
+ compaction: { enabled: true },
10
+ backgroundSubagent: { enabled: true },
11
+ leaderboard: { enabled: false },
12
+ logging: { enabled: false, maxLines: 200 }
13
+ };
14
+ async function initConfig() {
15
+ const home = Bun.env?.HOME ?? (globalThis?.process?.env?.HOME ?? "") ?? "";
16
+ const configPath = `${home}/.config/opencode/oc-tweaks.json`;
17
+ const file = Bun.file(configPath);
18
+ if (await file.exists()) {
19
+ return { created: false, path: configPath };
20
+ }
21
+ const json = JSON.stringify(DEFAULT_CONFIG, null, 2);
22
+ await mkdir(dirname(configPath), { recursive: true });
23
+ await Bun.write(configPath, json + `
24
+ `);
25
+ return { created: true, path: configPath };
26
+ }
27
+ var isMain = typeof Bun !== "undefined" && Bun.main === import.meta.path;
28
+ if (isMain) {
29
+ const result = await initConfig();
30
+ if (result.created) {
31
+ console.log(`Created: ${result.path}`);
32
+ console.log("All plugins configured. Edit the file to customize.");
33
+ } else {
34
+ console.log(`Config already exists: ${result.path}`);
35
+ console.log("Nothing changed. Edit the file manually to update your configuration.");
36
+ }
37
+ }
38
+ export {
39
+ initConfig,
40
+ DEFAULT_CONFIG
41
+ };
package/dist/index.js ADDED
@@ -0,0 +1,559 @@
1
+ // src/utils/logger.ts
2
+ import { mkdir } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ var DEFAULT_MAX_LINES = 100;
5
+ function getHome() {
6
+ return Bun.env?.HOME ?? (globalThis?.process?.env?.HOME ?? "") ?? "";
7
+ }
8
+ function getLogFilePath() {
9
+ return `${getHome()}/.config/opencode/plugins/oc-tweaks.log`;
10
+ }
11
+ async function log(config, level, message) {
12
+ if (!config?.enabled)
13
+ return;
14
+ try {
15
+ const logFile = getLogFilePath();
16
+ const file = Bun.file(logFile);
17
+ let content = "";
18
+ if (await file.exists()) {
19
+ content = await file.text();
20
+ }
21
+ const line = `[${new Date().toISOString()}] [${level}] ${message}
22
+ `;
23
+ content += line;
24
+ const maxLines = config.maxLines ?? DEFAULT_MAX_LINES;
25
+ const keepLines = Math.floor(maxLines / 2);
26
+ const lines = content.split(`
27
+ `).filter(Boolean);
28
+ if (lines.length > maxLines) {
29
+ content = lines.slice(-keepLines).join(`
30
+ `) + `
31
+ `;
32
+ }
33
+ await mkdir(dirname(logFile), { recursive: true });
34
+ await Bun.write(logFile, content);
35
+ } catch {}
36
+ }
37
+
38
+ // src/utils/safe-hook.ts
39
+ function safeHook(name, fn, loggerConfig) {
40
+ return async (...args) => {
41
+ try {
42
+ return await fn(...args);
43
+ } catch (err) {
44
+ await log(loggerConfig, "ERROR", `[oc-tweaks] ${name}: ${err}`);
45
+ }
46
+ };
47
+ }
48
+ // src/utils/config.ts
49
+ async function loadJsonConfig(path, defaults) {
50
+ try {
51
+ const file = Bun.file(path);
52
+ if (!await file.exists())
53
+ return defaults;
54
+ const parsed = await file.json();
55
+ return { ...defaults, ...parsed };
56
+ } catch {
57
+ return defaults;
58
+ }
59
+ }
60
+ var DEFAULT_CONFIG = {
61
+ compaction: {},
62
+ backgroundSubagent: {},
63
+ leaderboard: {},
64
+ notify: {}
65
+ };
66
+ async function loadOcTweaksConfig() {
67
+ const home = Bun.env?.HOME ?? (globalThis?.process?.env?.HOME ?? "") ?? "";
68
+ const path = `${home}/.config/opencode/oc-tweaks.json`;
69
+ try {
70
+ const file = Bun.file(path);
71
+ if (!await file.exists())
72
+ return null;
73
+ const parsed = await file.json();
74
+ return { ...DEFAULT_CONFIG, ...parsed };
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ // src/plugins/background-subagent.ts
80
+ var SUB_AGENT_DISPATCH_PROMPT = `
81
+ ## Sub-Agent Dispatch Policy
82
+
83
+ When calling \`task()\` to dispatch sub-agents, you should default to \`run_in_background=true\`.
84
+ This keeps the main conversation responsive while sub-agents work in the background.
85
+
86
+ Only use \`run_in_background=false\` when ALL of these conditions are met:
87
+ 1. The next step cannot easily proceed without the sub-agent's result
88
+ 2. There is NO other useful work to do while waiting
89
+ 3. The user is explicitly waiting for that specific result
90
+
91
+ When in doubt → background. Use \`background_output()\` to collect results later.
92
+ `;
93
+ var VIOLATION_WARNING = `\uD83D\uDCA1 [Reminder] Consider using background mode for better responsiveness.
94
+ You used foreground mode (run_in_background=false). Check the three conditions in the system prompt.
95
+ If not all three are met, consider run_in_background=true + background_output() for next time.`;
96
+ var backgroundSubagentPlugin = async () => {
97
+ const foregroundCalls = new Set;
98
+ return {
99
+ "experimental.chat.system.transform": safeHook("background-subagent:system.transform", async (_input, output) => {
100
+ const config = await loadOcTweaksConfig();
101
+ if (!config || config.backgroundSubagent?.enabled !== true)
102
+ return;
103
+ output.system.push(SUB_AGENT_DISPATCH_PROMPT);
104
+ }),
105
+ "tool.execute.before": safeHook("background-subagent:tool.execute.before", async (input, output) => {
106
+ const config = await loadOcTweaksConfig();
107
+ if (!config || config.backgroundSubagent?.enabled !== true)
108
+ return;
109
+ if (input.tool !== "task")
110
+ return;
111
+ if (!output.args?.run_in_background) {
112
+ foregroundCalls.add(input.callID);
113
+ }
114
+ }),
115
+ "tool.execute.after": safeHook("background-subagent:tool.execute.after", async (input, output) => {
116
+ if (!foregroundCalls.has(input.callID))
117
+ return;
118
+ foregroundCalls.delete(input.callID);
119
+ output.output += `
120
+
121
+ ${VIOLATION_WARNING}`;
122
+ })
123
+ };
124
+ };
125
+ // src/plugins/compaction.ts
126
+ var LANGUAGE_PREFERENCE_PROMPT = `
127
+ ## Language Preference
128
+
129
+ Important: Write the compaction summary in the user's preferred language(for example, if user prefers Chinese, then the compaction should be in Chinese as well).
130
+ All section titles, descriptions, analysis, and next-step suggestions should use the user's language.
131
+ Keep technical terms (filenames, variable names, commands, code snippets) in their original form.
132
+ `;
133
+ var compactionPlugin = async () => {
134
+ return {
135
+ "experimental.session.compacting": safeHook("compaction", async (_input, output) => {
136
+ const config = await loadOcTweaksConfig();
137
+ if (!config || config.compaction?.enabled !== true)
138
+ return;
139
+ output.context.push(LANGUAGE_PREFERENCE_PROMPT);
140
+ })
141
+ };
142
+ };
143
+ // src/plugins/leaderboard.ts
144
+ function getHome2() {
145
+ return Bun.env?.HOME ?? (globalThis?.process?.env?.HOME ?? "") ?? "";
146
+ }
147
+ var API_ENDPOINT = "https://api.claudecount.com/api/usage/hook";
148
+ var MODEL_MAP = {
149
+ "claude-opus-4.6": "claude-opus-4-20250514",
150
+ "claude-opus-4.5": "claude-opus-4-20250514",
151
+ "claude-sonnet-4.5": "claude-sonnet-4-20250514",
152
+ "claude-sonnet-4": "claude-sonnet-4-20250514",
153
+ "claude-haiku-4.5": "claude-3.5-haiku-20241022",
154
+ "gpt-5.2-codex": "claude-sonnet-4-20250514",
155
+ "gpt-5.1-codex": "claude-sonnet-4-20250514",
156
+ "gpt-5.1-codex-max": "claude-opus-4-20250514",
157
+ "gpt-5.2": "claude-sonnet-4-20250514",
158
+ "gpt-5.1": "claude-sonnet-4-20250514",
159
+ "gpt-5-mini": "claude-3.5-haiku-20241022",
160
+ "gpt-5": "claude-sonnet-4-20250514",
161
+ "gemini-3-pro-preview": "claude-sonnet-4-20250514",
162
+ "gemini-2.5-pro": "claude-sonnet-4-20250514",
163
+ "grok-code-fast-1": "claude-3.5-haiku-20241022"
164
+ };
165
+ var DEFAULT_MODEL = "claude-sonnet-4-20250514";
166
+ function mapModel(modelID) {
167
+ if (MODEL_MAP[modelID])
168
+ return MODEL_MAP[modelID];
169
+ if (/claude-.*-\d{8}$/.test(modelID))
170
+ return modelID;
171
+ return DEFAULT_MODEL;
172
+ }
173
+ function parseLeaderboardConfig(parsed) {
174
+ const handle = parsed.twitter_handle ?? parsed.twitterUrl;
175
+ const userId = parsed.twitter_user_id ?? parsed.twitterUserId ?? handle;
176
+ if (!handle || !userId)
177
+ return null;
178
+ return { twitter_handle: handle, twitter_user_id: userId };
179
+ }
180
+ async function readLeaderboardConfig(path) {
181
+ const parsed = await loadJsonConfig(path, {});
182
+ return parseLeaderboardConfig(parsed);
183
+ }
184
+ async function loadLeaderboardConfig(configPath) {
185
+ if (typeof configPath === "string") {
186
+ return readLeaderboardConfig(configPath);
187
+ }
188
+ const paths = [`${getHome2()}/.claude/leaderboard.json`, `${getHome2()}/.config/claude/leaderboard.json`];
189
+ for (const path of paths) {
190
+ const config = await readLeaderboardConfig(path);
191
+ if (config)
192
+ return config;
193
+ }
194
+ return null;
195
+ }
196
+ async function submitUsage(config, msg) {
197
+ const timestamp = new Date(msg.time.created).toISOString();
198
+ const hashInput = `${msg.time.created}${msg.id}${msg.sessionID}`;
199
+ const interactionHash = new Bun.CryptoHasher("sha256").update(hashInput).digest("hex");
200
+ const payload = {
201
+ twitter_handle: config.twitter_handle,
202
+ twitter_user_id: config.twitter_user_id,
203
+ timestamp,
204
+ tokens: {
205
+ input: msg.tokens.input,
206
+ output: msg.tokens.output,
207
+ cache_creation: msg.tokens.cache?.write ?? 0,
208
+ cache_read: msg.tokens.cache?.read ?? 0
209
+ },
210
+ model: mapModel(msg.modelID),
211
+ interaction_id: interactionHash,
212
+ interaction_hash: interactionHash
213
+ };
214
+ const res = await fetch(API_ENDPOINT, {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify(payload)
218
+ });
219
+ if (!res.ok) {
220
+ const body = await res.text().catch(() => "");
221
+ throw new Error(`API ${res.status}: ${body}`);
222
+ }
223
+ }
224
+ var leaderboardPlugin = async () => {
225
+ const submitted = new Set;
226
+ let loggedInit = false;
227
+ return {
228
+ event: safeHook("leaderboard:event", async ({ event }) => {
229
+ try {
230
+ if (!event || typeof event !== "object")
231
+ return;
232
+ const eventRecord = event;
233
+ if (eventRecord.type !== "message.updated")
234
+ return;
235
+ const ocTweaks = await loadOcTweaksConfig();
236
+ if (!ocTweaks || ocTweaks.leaderboard?.enabled !== true)
237
+ return;
238
+ const config = await loadLeaderboardConfig(ocTweaks.leaderboard?.configPath);
239
+ if (!config)
240
+ return;
241
+ if (!loggedInit) {
242
+ await log(ocTweaks.logging, "INFO", `Plugin loaded, handle=${config.twitter_handle}`);
243
+ loggedInit = true;
244
+ }
245
+ const properties = eventRecord.properties;
246
+ if (!properties || typeof properties !== "object")
247
+ return;
248
+ const infoUnknown = properties.info;
249
+ if (!infoUnknown || typeof infoUnknown !== "object")
250
+ return;
251
+ const info = infoUnknown;
252
+ if (info.role !== "assistant")
253
+ return;
254
+ if (!info.time?.completed)
255
+ return;
256
+ if (submitted.has(info.id))
257
+ return;
258
+ const msg = info;
259
+ if (!msg.tokens || msg.tokens.input === 0)
260
+ return;
261
+ submitted.add(msg.id);
262
+ await log(ocTweaks.logging, "INFO", `Submitting: msg=${msg.id.slice(0, 16)} model=${msg.modelID}→${mapModel(msg.modelID)} in=${msg.tokens.input} out=${msg.tokens.output} cache_r=${msg.tokens.cache?.read ?? 0} cache_w=${msg.tokens.cache?.write ?? 0}`);
263
+ await submitUsage(config, msg);
264
+ await log(ocTweaks.logging, "INFO", "Submitted OK");
265
+ } catch (err) {
266
+ const ocTweaks = await loadOcTweaksConfig().catch(() => null);
267
+ await log(ocTweaks?.logging, "ERROR", `Submit failed: ${err}`);
268
+ }
269
+ })
270
+ };
271
+ };
272
+ // src/plugins/notify.ts
273
+ var notifyPlugin = async ({ $, directory, client }) => {
274
+ let cachedSender = null;
275
+ return {
276
+ event: safeHook("notify:event", async ({ event }) => {
277
+ const config = await loadOcTweaksConfig();
278
+ if (!config || config.notify?.enabled !== true)
279
+ return;
280
+ const logConfig = config.logging;
281
+ const notifyOnIdle = config.notify?.notifyOnIdle !== false;
282
+ const notifyOnError = config.notify?.notifyOnError !== false;
283
+ const configuredCommand = typeof config.notify?.command === "string" && config.notify.command.trim().length > 0 ? config.notify.command.trim() : null;
284
+ const style = config.notify?.style;
285
+ const sender = configuredCommand ? { kind: "custom", commandTemplate: configuredCommand } : cachedSender ??= await detectNotifySender($, client, logConfig);
286
+ const sendToast = async (projectName, message, tag) => {
287
+ const title = `oc: ${projectName}`;
288
+ await notifyWithSender($, sender, title, message, tag, style, logConfig);
289
+ };
290
+ if (event?.type === "session.idle") {
291
+ if (!notifyOnIdle)
292
+ return;
293
+ const projectName = getProjectName(directory);
294
+ const sessionId = event.properties?.sessionID ?? event.properties?.sessionId;
295
+ const message = await extractIdleMessage(client, sessionId);
296
+ await sendToast(projectName, message, "Stop");
297
+ return;
298
+ }
299
+ if (event?.type === "session.error") {
300
+ if (!notifyOnError)
301
+ return;
302
+ const projectName = getProjectName(directory);
303
+ await sendToast(projectName, "❌ Session error", "Error");
304
+ }
305
+ })
306
+ };
307
+ };
308
+ function getProjectName(directory) {
309
+ const normalized = directory.replace(/\\/g, "/");
310
+ const segments = normalized.split("/").filter(Boolean);
311
+ return segments[segments.length - 1] || "opencode";
312
+ }
313
+ async function extractIdleMessage(client, sessionId) {
314
+ let message = "✓ Task completed";
315
+ if (!sessionId || !client?.session?.messages)
316
+ return message;
317
+ try {
318
+ const result = await client.session.messages({ path: { id: sessionId } });
319
+ const messages = result?.data;
320
+ if (!Array.isArray(messages))
321
+ return message;
322
+ for (let i = messages.length - 1;i >= 0; i -= 1) {
323
+ const msg = messages[i];
324
+ if (msg?.info?.role !== "assistant" || !Array.isArray(msg?.parts))
325
+ continue;
326
+ for (const part of msg.parts) {
327
+ if (part?.type === "text" && typeof part.text === "string") {
328
+ message = `✓ ${truncateText(cleanMarkdown(part.text), 400)}`;
329
+ return message;
330
+ }
331
+ }
332
+ }
333
+ return message;
334
+ } catch {
335
+ return message;
336
+ }
337
+ }
338
+ async function detectNotifySender($, client, logConfig) {
339
+ if (await commandExists($, "pwsh")) {
340
+ return { kind: "wpf", command: "pwsh" };
341
+ }
342
+ if (await commandExists($, "powershell.exe")) {
343
+ return { kind: "wpf", command: "powershell.exe" };
344
+ }
345
+ if (await commandExists($, "osascript")) {
346
+ return { kind: "osascript" };
347
+ }
348
+ if (await commandExists($, "notify-send")) {
349
+ return { kind: "notify-send" };
350
+ }
351
+ if (typeof client?.tui?.showToast === "function") {
352
+ return { kind: "tui", showToast: client.tui.showToast };
353
+ }
354
+ await log(logConfig, "WARN", "[oc-tweaks] notify: no available notifier, set notify.command to override");
355
+ return { kind: "none" };
356
+ }
357
+ async function commandExists($, command) {
358
+ try {
359
+ await $`which ${command}`;
360
+ return true;
361
+ } catch {
362
+ return false;
363
+ }
364
+ }
365
+ async function notifyWithSender($, sender, title, message, tag, style, logConfig) {
366
+ try {
367
+ if (sender.kind === "custom") {
368
+ const command = sender.commandTemplate.replace(/\$TITLE/g, title).replace(/\$MESSAGE/g, message);
369
+ await runCustomCommand($, command);
370
+ return;
371
+ }
372
+ if (sender.kind === "wpf") {
373
+ await runWpfNotification($, sender.command, title, message, tag, style);
374
+ return;
375
+ }
376
+ if (sender.kind === "osascript") {
377
+ const script = `display notification "${escapeAppleScript(message)}" with title "${escapeAppleScript(title)}"`;
378
+ await $`osascript -e ${script}`;
379
+ return;
380
+ }
381
+ if (sender.kind === "notify-send") {
382
+ await $`notify-send ${title} ${message}`;
383
+ return;
384
+ }
385
+ if (sender.kind === "tui") {
386
+ await showToastWithFallback(sender.showToast, title, message);
387
+ }
388
+ } catch {}
389
+ }
390
+ async function runCustomCommand($, command) {
391
+ const escaped = command.replace(/"/g, "\\\"");
392
+ await $`bun -e ${`const { exec } = require("node:child_process"); exec("${escaped}")`}`;
393
+ }
394
+ async function runWpfNotification($, shellCommand, title, message, tag, style) {
395
+ const backgroundColor = style?.backgroundColor ?? "#101018";
396
+ const backgroundOpacity = style?.backgroundOpacity ?? 0.95;
397
+ const textColor = style?.textColor ?? "#AAAAAA";
398
+ const borderRadius = style?.borderRadius ?? 14;
399
+ const colorBarWidth = style?.colorBarWidth ?? 5;
400
+ const width = style?.width ?? 420;
401
+ const height = style?.height ?? 105;
402
+ const titleFontSize = style?.titleFontSize ?? 14;
403
+ const contentFontSize = style?.contentFontSize ?? 11;
404
+ const iconFontSize = style?.iconFontSize ?? 30;
405
+ const duration = style?.duration ?? 1e4;
406
+ const position = style?.position ?? "center";
407
+ const shadow = style?.shadow !== false;
408
+ const idleColor = style?.idleColor ?? "#4ADE80";
409
+ const errorColor = style?.errorColor ?? "#EF4444";
410
+ const accentColor = tag === "Error" ? errorColor : idleColor;
411
+ const icon = tag === "Error" ? "❌" : "✅";
412
+ const startupLocation = position === "center" ? "CenterScreen" : position;
413
+ const psTitle = title.replace(/'/g, "''");
414
+ const psText = truncateText(cleanMarkdown(message), 400).replace(/'/g, "''");
415
+ const shadowXaml = shadow ? '<Border.Effect><DropShadowEffect BlurRadius="20" ShadowDepth="2" Opacity="0.7" Color="Black"/></Border.Effect>' : "";
416
+ const xaml = [
417
+ `<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"`,
418
+ ` xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"`,
419
+ ` WindowStyle="None" AllowsTransparency="True" Background="Transparent"`,
420
+ ` Topmost="True" ShowInTaskbar="False" ShowActivated="False"`,
421
+ ` WindowStartupLocation="${startupLocation}"`,
422
+ ` Width="${width}" Height="${height}">`,
423
+ ` <Border CornerRadius="${borderRadius}" Margin="10">`,
424
+ ` <Border.Background>`,
425
+ ` <SolidColorBrush Color="${backgroundColor}" Opacity="${backgroundOpacity}"/>`,
426
+ ` </Border.Background>`,
427
+ ` ${shadowXaml}`,
428
+ ` <Grid>`,
429
+ ` <Border CornerRadius="${borderRadius},0,0,${borderRadius}" Width="${colorBarWidth}" HorizontalAlignment="Left" Name="ColorBar"/>`,
430
+ ` <StackPanel Orientation="Horizontal" Margin="22,0,16,0" VerticalAlignment="Center">`,
431
+ ` <TextBlock Name="IconText" FontSize="${iconFontSize}" VerticalAlignment="Center" Margin="0,0,15,0" Foreground="White"/>`,
432
+ ` <StackPanel VerticalAlignment="Center" MaxWidth="320">`,
433
+ ` <TextBlock Name="TitleText" FontSize="${titleFontSize}" FontWeight="SemiBold"/>`,
434
+ ` <TextBlock Name="ContentText" Foreground="${textColor}" FontSize="${contentFontSize}" Margin="0,4,0,0" TextWrapping="Wrap"/>`,
435
+ ` <TextBlock Text="Click to dismiss" Foreground="#555555" FontSize="9" Margin="0,4,0,0"/>`,
436
+ ` </StackPanel>`,
437
+ ` </StackPanel>`,
438
+ ` </Grid>`,
439
+ ` </Border>`,
440
+ `</Window>`
441
+ ].join(`
442
+ `);
443
+ const psScript = [
444
+ "Add-Type -AssemblyName PresentationFramework",
445
+ "Add-Type -AssemblyName PresentationCore",
446
+ "Add-Type -AssemblyName WindowsBase",
447
+ "",
448
+ "Add-Type -TypeDefinition @'",
449
+ "using System;",
450
+ "using System.Runtime.InteropServices;",
451
+ "",
452
+ "public static class VDesktop {",
453
+ ' [DllImport("user32.dll", SetLastError = true)]',
454
+ " public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);",
455
+ "",
456
+ ' [DllImport("user32.dll", SetLastError = true)]',
457
+ " public static extern int GetWindowLong(IntPtr hWnd, int nIndex);",
458
+ "",
459
+ " public const int GWL_EXSTYLE = -20;",
460
+ " public const int WS_EX_TOOLWINDOW = 0x00000080;",
461
+ " public const int WS_EX_NOACTIVATE = 0x08000000;",
462
+ " public const int WS_EX_APPWINDOW = 0x00040000;",
463
+ "",
464
+ " public static void MakeGlobalWindow(IntPtr hwnd) {",
465
+ " int style = GetWindowLong(hwnd, GWL_EXSTYLE);",
466
+ " style = style | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE;",
467
+ " style = style & ~WS_EX_APPWINDOW;",
468
+ " SetWindowLong(hwnd, GWL_EXSTYLE, style);",
469
+ " }",
470
+ "}",
471
+ "'@ -ErrorAction SilentlyContinue",
472
+ "",
473
+ `$title = '${psTitle}'`,
474
+ `$text = '${psText}'`,
475
+ `$accentColor = '${accentColor}'`,
476
+ `$icon = '${icon}'`,
477
+ `$duration = ${duration}`,
478
+ "",
479
+ "[xml]$xaml = @'",
480
+ xaml,
481
+ "'@",
482
+ "",
483
+ "$reader = New-Object System.Xml.XmlNodeReader $xaml",
484
+ "$window = [Windows.Markup.XamlReader]::Load($reader)",
485
+ "",
486
+ "$colorBar = $window.FindName('ColorBar')",
487
+ "$iconText = $window.FindName('IconText')",
488
+ "$titleText = $window.FindName('TitleText')",
489
+ "$contentText = $window.FindName('ContentText')",
490
+ "",
491
+ "$colorBar.Background = [System.Windows.Media.BrushConverter]::new().ConvertFromString($accentColor)",
492
+ "$iconText.Text = $icon",
493
+ "$titleText.Text = $title",
494
+ "$titleText.Foreground = [System.Windows.Media.BrushConverter]::new().ConvertFromString($accentColor)",
495
+ "$contentText.Text = $text",
496
+ "",
497
+ "$window.Add_MouseLeftButtonDown({ $window.Close() })",
498
+ "",
499
+ "$window.Add_Loaded({",
500
+ " $hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle",
501
+ " [VDesktop]::MakeGlobalWindow($hwnd)",
502
+ "})",
503
+ "",
504
+ "if ($duration -gt 0) {",
505
+ " $timer = New-Object System.Windows.Threading.DispatcherTimer",
506
+ " $timer.Interval = [TimeSpan]::FromMilliseconds($duration)",
507
+ " $timer.Add_Tick({",
508
+ " $window.Close()",
509
+ " $timer.Stop()",
510
+ " })",
511
+ " $timer.Start()",
512
+ "}",
513
+ "",
514
+ "$window.ShowActivated = $false",
515
+ "$window.Show()",
516
+ "$frame = New-Object System.Windows.Threading.DispatcherFrame",
517
+ "$window.Add_Closed({ $frame.Continue = $false })",
518
+ "[System.Windows.Threading.Dispatcher]::PushFrame($frame)"
519
+ ].join(`
520
+ `);
521
+ const jsCode = [
522
+ "const proc = require('node:child_process').spawn(",
523
+ ` ${JSON.stringify(shellCommand)},`,
524
+ ` ['-NoProfile', '-Command', ${JSON.stringify(psScript)}],`,
525
+ " { detached: true, stdio: 'ignore' }",
526
+ ");",
527
+ "proc.unref();"
528
+ ].join(`
529
+ `);
530
+ await $`bun -e ${jsCode}`;
531
+ }
532
+ async function showToastWithFallback(showToast, title, message) {
533
+ try {
534
+ await Promise.resolve(showToast({ title, message }));
535
+ return;
536
+ } catch {}
537
+ try {
538
+ await Promise.resolve(showToast({ title, description: message }));
539
+ return;
540
+ } catch {}
541
+ await Promise.resolve(showToast(title, message));
542
+ }
543
+ function truncateText(text, maxChars) {
544
+ if (text.length <= maxChars)
545
+ return text;
546
+ return `${text.slice(0, maxChars)}...`;
547
+ }
548
+ function cleanMarkdown(text) {
549
+ return text.replace(/[`*#]/g, "").replace(/\n+/g, " ").replace(/\s+/g, " ").trim();
550
+ }
551
+ function escapeAppleScript(text) {
552
+ return text.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
553
+ }
554
+ export {
555
+ notifyPlugin,
556
+ leaderboardPlugin,
557
+ compactionPlugin,
558
+ backgroundSubagentPlugin
559
+ };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "oc-tweaks",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "exports": {
6
- ".": "./src/index.ts"
6
+ ".": "./dist/index.js"
7
7
  },
8
+ "main": "./dist/index.js",
8
9
  "peerDependencies": {
9
10
  "@opencode-ai/plugin": ">=1.0.0"
10
11
  },
@@ -12,14 +13,15 @@
12
13
  "@opencode-ai/plugin": "^1.2.15"
13
14
  },
14
15
  "bin": {
15
- "oc-tweaks": "./src/cli/init.ts"
16
+ "oc-tweaks": "./dist/cli/init.js"
16
17
  },
17
18
  "scripts": {
19
+ "build": "bun build src/index.ts --outdir=dist --target=node --format=esm --external @opencode-ai/plugin --external @opencode-ai/sdk && bun build src/cli/init.ts --outdir=dist/cli --target=node --format=esm",
18
20
  "test": "bun test",
19
21
  "smoke": "bun scripts/smoke-test.ts"
20
22
  },
21
23
  "files": [
22
- "src/"
24
+ "dist/"
23
25
  ],
24
26
  "publishConfig": {
25
27
  "access": "public"
File without changes