@akonwi/opencode-kit 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -5,7 +5,7 @@ OpenCode plugin package for local workflow notifications and runtime toggles.
5
5
  ## Features
6
6
 
7
7
  - Idle notification with terminal bell and optional speech.
8
- - Error notification with macOS Funk sound and optional speech.
8
+ - Error notification with macOS Funk sound only (no speech).
9
9
  - Runtime config reads from `~/.config/opencode/kit.json`.
10
10
  - Structured JSON-line logs at `~/.config/opencode/logs/opencode-kit.log`.
11
11
  - Local control CLI (no AI turn): `oc-kit`.
package/dist/plugin.js CHANGED
@@ -240,15 +240,11 @@ async function notifyIdle(lastAssistantText, config, logger) {
240
240
  speech: config.speech.enabled
241
241
  });
242
242
  }
243
- async function notifyError(message, config, logger) {
243
+ async function notifyError(config, logger) {
244
244
  await playErrorSound(config, logger);
245
- if (config.speech.enabled) {
246
- const safeMessage = message?.trim() || "Unknown error";
247
- await speak(`OpenCode error: ${safeMessage}`, config, logger);
248
- }
249
245
  logger.warn("error.notify", "Error notification processed", {
250
246
  bell: config.bells.enabled,
251
- speech: config.speech.enabled
247
+ speech: false
252
248
  });
253
249
  }
254
250
 
@@ -323,7 +319,10 @@ var OpencodeKit = async (input) => {
323
319
  await notifyIdle(lastText, config, logger);
324
320
  return;
325
321
  }
326
- await notifyError(getErrorMessage(event), config, logger);
322
+ logger.warn("error.detected", getErrorMessage(event), {
323
+ eventType: event.type
324
+ });
325
+ await notifyError(config, logger);
327
326
  }
328
327
  };
329
328
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/features/logging.ts","../src/features/sounds.ts","../src/plugin.ts"],"sourcesContent":["import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface KitConfig {\n bells: {\n enabled: boolean;\n errorSound: \"Funk\";\n };\n speech: {\n enabled: boolean;\n maxChars: number;\n voice: string | null;\n };\n debug: {\n logLevel: LogLevel;\n };\n}\n\nexport const CONFIG_PATH = path.join(homedir(), \".config\", \"opencode\", \"kit.json\");\n\nconst VALID_LOG_LEVELS: ReadonlySet<string> = new Set([\"debug\", \"info\", \"warn\", \"error\"]);\n\nconst DEFAULT_CONFIG: KitConfig = {\n bells: {\n enabled: true,\n errorSound: \"Funk\",\n },\n speech: {\n enabled: true,\n maxChars: 220,\n voice: null,\n },\n debug: {\n logLevel: \"info\",\n },\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction asBoolean(value: unknown, fallback: boolean): boolean {\n return typeof value === \"boolean\" ? value : fallback;\n}\n\nfunction asErrorSound(value: unknown, fallback: \"Funk\"): \"Funk\" {\n return value === \"Funk\" ? \"Funk\" : fallback;\n}\n\nfunction asOptionalString(value: unknown, fallback: string | null): string | null {\n if (value === null) {\n return null;\n }\n\n return typeof value === \"string\" && value.trim() !== \"\" ? value : fallback;\n}\n\nfunction asIntInRange(value: unknown, fallback: number, min: number, max: number): number {\n if (typeof value !== \"number\" || !Number.isInteger(value)) {\n return fallback;\n }\n\n if (value < min || value > max) {\n return fallback;\n }\n\n return value;\n}\n\nfunction asLogLevel(value: unknown, fallback: LogLevel): LogLevel {\n if (typeof value !== \"string\") {\n return fallback;\n }\n\n return VALID_LOG_LEVELS.has(value) ? (value as LogLevel) : fallback;\n}\n\nexport function sanitizeConfig(input: unknown): KitConfig {\n const raw = isRecord(input) ? input : {};\n const rawBells = isRecord(raw.bells) ? raw.bells : {};\n const rawSpeech = isRecord(raw.speech) ? raw.speech : {};\n const rawDebug = isRecord(raw.debug) ? raw.debug : {};\n\n return {\n bells: {\n enabled: asBoolean(rawBells.enabled, DEFAULT_CONFIG.bells.enabled),\n errorSound: asErrorSound(rawBells.errorSound, DEFAULT_CONFIG.bells.errorSound),\n },\n speech: {\n enabled: asBoolean(rawSpeech.enabled, DEFAULT_CONFIG.speech.enabled),\n maxChars: asIntInRange(rawSpeech.maxChars, DEFAULT_CONFIG.speech.maxChars, 20, 2000),\n voice: asOptionalString(rawSpeech.voice, DEFAULT_CONFIG.speech.voice),\n },\n debug: {\n logLevel: asLogLevel(rawDebug.logLevel, DEFAULT_CONFIG.debug.logLevel),\n },\n };\n}\n\nexport async function readConfig(): Promise<KitConfig> {\n try {\n const content = await readFile(CONFIG_PATH, \"utf8\");\n const parsed = JSON.parse(content) as unknown;\n return sanitizeConfig(parsed);\n } catch {\n return sanitizeConfig(DEFAULT_CONFIG);\n }\n}\n\nasync function ensureConfigDirectory(): Promise<void> {\n await mkdir(path.dirname(CONFIG_PATH), { recursive: true });\n}\n\nexport async function writeConfig(config: KitConfig): Promise<void> {\n const safeConfig = sanitizeConfig(config);\n const serialized = `${JSON.stringify(safeConfig, null, 2)}\\n`;\n const tempPath = `${CONFIG_PATH}.tmp`;\n\n await ensureConfigDirectory();\n await writeFile(tempPath, serialized, \"utf8\");\n await rename(tempPath, CONFIG_PATH);\n}\n\nexport async function updateConfig(mutator: (current: KitConfig) => KitConfig): Promise<KitConfig> {\n const current = await readConfig();\n const next = sanitizeConfig(mutator(current));\n await writeConfig(next);\n return next;\n}\n\nexport function defaultConfig(): KitConfig {\n return sanitizeConfig(DEFAULT_CONFIG);\n}\n","import { appendFile, mkdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nimport type { LogLevel } from \"../config\";\n\nconst LOG_DIR = path.join(homedir(), \".config\", \"opencode\", \"logs\");\nexport const LOG_PATH = path.join(LOG_DIR, \"opencode-kit.log\");\n\nconst LOG_WEIGHT: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nexport interface LogContext {\n [key: string]: unknown;\n}\n\nexport interface Logger {\n setLevel: (level: LogLevel) => void;\n debug: (event: string, message: string, context?: LogContext) => void;\n info: (event: string, message: string, context?: LogContext) => void;\n warn: (event: string, message: string, context?: LogContext) => void;\n error: (event: string, message: string, context?: LogContext) => void;\n}\n\ninterface LogLine {\n timestamp: string;\n level: LogLevel;\n event: string;\n message: string;\n context?: LogContext;\n}\n\nfunction shouldLog(minLevel: LogLevel, level: LogLevel): boolean {\n return LOG_WEIGHT[level] >= LOG_WEIGHT[minLevel];\n}\n\nasync function appendLogLine(line: LogLine): Promise<void> {\n try {\n await mkdir(LOG_DIR, { recursive: true });\n await appendFile(LOG_PATH, `${JSON.stringify(line)}\\n`, \"utf8\");\n } catch {\n // Best effort logging only.\n }\n}\n\nexport function createLogger(initialLevel: LogLevel = \"info\"): Logger {\n let minLevel = initialLevel;\n\n const write = (level: LogLevel, event: string, message: string, context?: LogContext): void => {\n if (!shouldLog(minLevel, level)) {\n return;\n }\n\n void appendLogLine({\n timestamp: new Date().toISOString(),\n level,\n event,\n message,\n context,\n });\n };\n\n return {\n setLevel: (level: LogLevel) => {\n minLevel = level;\n },\n debug: (event, message, context) => {\n write(\"debug\", event, message, context);\n },\n info: (event, message, context) => {\n write(\"info\", event, message, context);\n },\n warn: (event, message, context) => {\n write(\"warn\", event, message, context);\n },\n error: (event, message, context) => {\n write(\"error\", event, message, context);\n },\n };\n}\n","import { spawn } from \"node:child_process\";\n\nimport type { KitConfig } from \"../config\";\nimport type { Logger } from \"./logging\";\n\nconst FUNK_SOUND_PATH = \"/System/Library/Sounds/Funk.aiff\";\n\nfunction runCommand(command: string, args: string[], logger: Logger): Promise<void> {\n return new Promise((resolve) => {\n const child = spawn(command, args, {\n stdio: \"ignore\",\n });\n\n child.on(\"error\", (error) => {\n logger.warn(\"command.error\", \"Command failed to launch\", {\n command,\n args,\n error: error.message,\n });\n resolve();\n });\n\n child.on(\"exit\", (code) => {\n if (code !== 0) {\n logger.warn(\"command.nonzero\", \"Command exited non-zero\", {\n command,\n args,\n code,\n });\n }\n\n resolve();\n });\n });\n}\n\nfunction truncateSummary(text: string, maxChars: number): string {\n const trimmed = text.trim();\n if (trimmed.length <= maxChars) {\n return trimmed;\n }\n\n return `${trimmed.slice(0, maxChars - 3)}...`;\n}\n\nfunction cleanTextForSpeech(text: string): string {\n return text\n .replace(/```[\\s\\S]*?```/g, \" code block omitted \")\n .replace(/`([^`]+)`/g, \"$1\")\n .replace(/\\[(.*?)\\]\\((.*?)\\)/g, \"$1\")\n .replace(/[*_~#>]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction shortSpeech(text: string, maxChars: number): string {\n const cleaned = cleanTextForSpeech(text);\n if (!cleaned) {\n return \"\";\n }\n\n if (cleaned.length <= maxChars) {\n return cleaned;\n }\n\n const sentence = cleaned.match(/(.+?[.!?])(\\s|$)/)?.[1]?.trim();\n if (sentence && sentence.length <= maxChars) {\n return sentence;\n }\n\n return truncateSummary(cleaned, maxChars);\n}\n\nfunction writeTerminalBell(logger: Logger): void {\n try {\n process.stdout.write(\"\\u0007\");\n } catch (error) {\n logger.warn(\"bell.error\", \"Failed to write terminal bell\", {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nasync function speak(text: string, config: KitConfig, logger: Logger): Promise<void> {\n if (!config.speech.enabled) {\n return;\n }\n\n if (process.platform !== \"darwin\") {\n logger.info(\"speech.unsupported\", \"Speech skipped: platform unsupported\", {\n platform: process.platform,\n });\n return;\n }\n\n const clipped = shortSpeech(text, config.speech.maxChars);\n if (!clipped) {\n return;\n }\n\n const args: string[] = [];\n if (config.speech.voice) {\n args.push(\"-v\", config.speech.voice);\n }\n args.push(clipped);\n\n await runCommand(\"say\", args, logger);\n}\n\nasync function playErrorSound(config: KitConfig, logger: Logger): Promise<void> {\n if (!config.bells.enabled) {\n return;\n }\n\n if (process.platform !== \"darwin\") {\n writeTerminalBell(logger);\n return;\n }\n\n if (config.bells.errorSound === \"Funk\") {\n await runCommand(\"afplay\", [FUNK_SOUND_PATH], logger);\n }\n}\n\nexport async function notifyIdle(\n lastAssistantText: string,\n config: KitConfig,\n logger: Logger,\n): Promise<void> {\n if (config.bells.enabled) {\n writeTerminalBell(logger);\n }\n\n if (config.speech.enabled && lastAssistantText.trim()) {\n await speak(lastAssistantText, config, logger);\n }\n\n logger.debug(\"idle.notify\", \"Idle notification processed\", {\n bell: config.bells.enabled,\n speech: config.speech.enabled,\n });\n}\n\nexport async function notifyError(\n message: string,\n config: KitConfig,\n logger: Logger,\n): Promise<void> {\n await playErrorSound(config, logger);\n\n if (config.speech.enabled) {\n const safeMessage = message?.trim() || \"Unknown error\";\n await speak(`OpenCode error: ${safeMessage}`, config, logger);\n }\n\n logger.warn(\"error.notify\", \"Error notification processed\", {\n bell: config.bells.enabled,\n speech: config.speech.enabled,\n });\n}\n","import type { Plugin } from \"@opencode-ai/plugin\";\nimport type { Event } from \"@opencode-ai/sdk\";\n\nimport { readConfig } from \"./config\";\nimport { createLogger } from \"./features/logging\";\nimport { notifyError, notifyIdle } from \"./features/sounds\";\n\nfunction getErrorMessage(event: Event): string {\n if (event.type !== \"session.error\") {\n return \"Unknown error\";\n }\n\n const rawError = event.properties.error;\n if (!rawError) {\n return \"Agent encountered an error.\";\n }\n\n const errorData = rawError.data;\n if (errorData && typeof errorData === \"object\") {\n if (\n \"message\" in errorData &&\n typeof errorData.message === \"string\" &&\n errorData.message.trim()\n ) {\n return `Agent encountered an error: ${errorData.message.trim()}`;\n }\n }\n\n if (typeof rawError.name === \"string\" && rawError.name.trim()) {\n return `Agent encountered an error: ${rawError.name.trim()}`;\n }\n\n return \"Agent encountered an error.\";\n}\n\nexport const OpencodeKit: Plugin = async (input) => {\n const logger = createLogger();\n const latestAssistantMessageBySession = new Map<string, string>();\n const latestAssistantTextByMessage = new Map<string, string>();\n const lastSpokenMessageBySession = new Map<string, string>();\n\n return {\n event: async ({ event }): Promise<void> => {\n if (event.type === \"message.updated\") {\n const info = event.properties.info;\n if (info.role === \"assistant\") {\n latestAssistantMessageBySession.set(info.sessionID, info.id);\n }\n return;\n }\n\n if (event.type === \"message.part.updated\") {\n const part = event.properties.part;\n if (\n part.type === \"text\" &&\n typeof part.messageID === \"string\" &&\n typeof part.text === \"string\"\n ) {\n latestAssistantTextByMessage.set(part.messageID, part.text);\n }\n return;\n }\n\n if (event.type !== \"session.idle\" && event.type !== \"session.error\") {\n return;\n }\n\n const config = await readConfig();\n logger.setLevel(config.debug.logLevel);\n\n if (event.type === \"session.idle\") {\n const sessionID = event.properties.sessionID;\n const latestMessageID = latestAssistantMessageBySession.get(sessionID);\n const lastText = latestMessageID\n ? (latestAssistantTextByMessage.get(latestMessageID) ?? \"\")\n : \"\";\n const previouslySpoken = lastSpokenMessageBySession.get(sessionID);\n\n if (latestMessageID && previouslySpoken === latestMessageID) {\n logger.debug(\"idle.skip_duplicate\", \"Skipping duplicate idle speech\", {\n sessionID,\n messageID: latestMessageID,\n });\n return;\n }\n\n if (!latestMessageID || !lastText) {\n logger.warn(\"idle.no_summary\", \"No cached assistant summary for idle event\", {\n sessionID,\n hasMessageID: Boolean(latestMessageID),\n hasText: Boolean(lastText),\n });\n await notifyIdle(\"\", config, logger);\n return;\n }\n\n lastSpokenMessageBySession.set(sessionID, latestMessageID);\n await notifyIdle(lastText, config, logger);\n return;\n }\n\n await notifyError(getErrorMessage(event), config, logger);\n },\n };\n};\n\nexport default OpencodeKit;\n"],"mappings":";AAAA,SAAS,OAAO,UAAU,QAAQ,iBAAiB;AACnD,SAAS,eAAe;AACxB,OAAO,UAAU;AAmBV,IAAM,cAAc,KAAK,KAAK,QAAQ,GAAG,WAAW,YAAY,UAAU;AAEjF,IAAM,mBAAwC,oBAAI,IAAI,CAAC,SAAS,QAAQ,QAAQ,OAAO,CAAC;AAExF,IAAM,iBAA4B;AAAA,EAChC,OAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAY;AAAA,EACd;AAAA,EACA,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,OAAO;AAAA,IACL,UAAU;AAAA,EACZ;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,UAAU,OAAgB,UAA4B;AAC7D,SAAO,OAAO,UAAU,YAAY,QAAQ;AAC9C;AAEA,SAAS,aAAa,OAAgB,UAA0B;AAC9D,SAAO,UAAU,SAAS,SAAS;AACrC;AAEA,SAAS,iBAAiB,OAAgB,UAAwC;AAChF,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,KAAK,QAAQ;AACpE;AAEA,SAAS,aAAa,OAAgB,UAAkB,KAAa,KAAqB;AACxF,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,KAAK,GAAG;AACzD,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,OAAO,QAAQ,KAAK;AAC9B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,OAAgB,UAA8B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO,iBAAiB,IAAI,KAAK,IAAK,QAAqB;AAC7D;AAEO,SAAS,eAAe,OAA2B;AACxD,QAAM,MAAM,SAAS,KAAK,IAAI,QAAQ,CAAC;AACvC,QAAM,WAAW,SAAS,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC;AACpD,QAAM,YAAY,SAAS,IAAI,MAAM,IAAI,IAAI,SAAS,CAAC;AACvD,QAAM,WAAW,SAAS,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC;AAEpD,SAAO;AAAA,IACL,OAAO;AAAA,MACL,SAAS,UAAU,SAAS,SAAS,eAAe,MAAM,OAAO;AAAA,MACjE,YAAY,aAAa,SAAS,YAAY,eAAe,MAAM,UAAU;AAAA,IAC/E;AAAA,IACA,QAAQ;AAAA,MACN,SAAS,UAAU,UAAU,SAAS,eAAe,OAAO,OAAO;AAAA,MACnE,UAAU,aAAa,UAAU,UAAU,eAAe,OAAO,UAAU,IAAI,GAAI;AAAA,MACnF,OAAO,iBAAiB,UAAU,OAAO,eAAe,OAAO,KAAK;AAAA,IACtE;AAAA,IACA,OAAO;AAAA,MACL,UAAU,WAAW,SAAS,UAAU,eAAe,MAAM,QAAQ;AAAA,IACvE;AAAA,EACF;AACF;AAEA,eAAsB,aAAiC;AACrD,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,aAAa,MAAM;AAClD,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO,eAAe,MAAM;AAAA,EAC9B,QAAQ;AACN,WAAO,eAAe,cAAc;AAAA,EACtC;AACF;;;AC9GA,SAAS,YAAY,SAAAA,cAAa;AAClC,SAAS,WAAAC,gBAAe;AACxB,OAAOC,WAAU;AAIjB,IAAM,UAAUA,MAAK,KAAKD,SAAQ,GAAG,WAAW,YAAY,MAAM;AAC3D,IAAM,WAAWC,MAAK,KAAK,SAAS,kBAAkB;AAE7D,IAAM,aAAuC;AAAA,EAC3C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAsBA,SAAS,UAAU,UAAoB,OAA0B;AAC/D,SAAO,WAAW,KAAK,KAAK,WAAW,QAAQ;AACjD;AAEA,eAAe,cAAc,MAA8B;AACzD,MAAI;AACF,UAAMF,OAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AACxC,UAAM,WAAW,UAAU,GAAG,KAAK,UAAU,IAAI,CAAC;AAAA,GAAM,MAAM;AAAA,EAChE,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,aAAa,eAAyB,QAAgB;AACpE,MAAI,WAAW;AAEf,QAAM,QAAQ,CAAC,OAAiB,OAAe,SAAiB,YAA+B;AAC7F,QAAI,CAAC,UAAU,UAAU,KAAK,GAAG;AAC/B;AAAA,IACF;AAEA,SAAK,cAAc;AAAA,MACjB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,UAAU,CAAC,UAAoB;AAC7B,iBAAW;AAAA,IACb;AAAA,IACA,OAAO,CAAC,OAAO,SAAS,YAAY;AAClC,YAAM,SAAS,OAAO,SAAS,OAAO;AAAA,IACxC;AAAA,IACA,MAAM,CAAC,OAAO,SAAS,YAAY;AACjC,YAAM,QAAQ,OAAO,SAAS,OAAO;AAAA,IACvC;AAAA,IACA,MAAM,CAAC,OAAO,SAAS,YAAY;AACjC,YAAM,QAAQ,OAAO,SAAS,OAAO;AAAA,IACvC;AAAA,IACA,OAAO,CAAC,OAAO,SAAS,YAAY;AAClC,YAAM,SAAS,OAAO,SAAS,OAAO;AAAA,IACxC;AAAA,EACF;AACF;;;ACnFA,SAAS,aAAa;AAKtB,IAAM,kBAAkB;AAExB,SAAS,WAAW,SAAiB,MAAgB,QAA+B;AAClF,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,MAAM,SAAS,MAAM;AAAA,MACjC,OAAO;AAAA,IACT,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,UAAU;AAC3B,aAAO,KAAK,iBAAiB,4BAA4B;AAAA,QACvD;AAAA,QACA;AAAA,QACA,OAAO,MAAM;AAAA,MACf,CAAC;AACD,cAAQ;AAAA,IACV,CAAC;AAED,UAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,UAAI,SAAS,GAAG;AACd,eAAO,KAAK,mBAAmB,2BAA2B;AAAA,UACxD;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAEA,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,gBAAgB,MAAc,UAA0B;AAC/D,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,QAAQ,UAAU,UAAU;AAC9B,WAAO;AAAA,EACT;AAEA,SAAO,GAAG,QAAQ,MAAM,GAAG,WAAW,CAAC,CAAC;AAC1C;AAEA,SAAS,mBAAmB,MAAsB;AAChD,SAAO,KACJ,QAAQ,mBAAmB,sBAAsB,EACjD,QAAQ,cAAc,IAAI,EAC1B,QAAQ,uBAAuB,IAAI,EACnC,QAAQ,YAAY,EAAE,EACtB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEA,SAAS,YAAY,MAAc,UAA0B;AAC3D,QAAM,UAAU,mBAAmB,IAAI;AACvC,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,UAAU,UAAU;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,QAAQ,MAAM,kBAAkB,IAAI,CAAC,GAAG,KAAK;AAC9D,MAAI,YAAY,SAAS,UAAU,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO,gBAAgB,SAAS,QAAQ;AAC1C;AAEA,SAAS,kBAAkB,QAAsB;AAC/C,MAAI;AACF,YAAQ,OAAO,MAAM,MAAQ;AAAA,EAC/B,SAAS,OAAO;AACd,WAAO,KAAK,cAAc,iCAAiC;AAAA,MACzD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D,CAAC;AAAA,EACH;AACF;AAEA,eAAe,MAAM,MAAc,QAAmB,QAA+B;AACnF,MAAI,CAAC,OAAO,OAAO,SAAS;AAC1B;AAAA,EACF;AAEA,MAAI,QAAQ,aAAa,UAAU;AACjC,WAAO,KAAK,sBAAsB,wCAAwC;AAAA,MACxE,UAAU,QAAQ;AAAA,IACpB,CAAC;AACD;AAAA,EACF;AAEA,QAAM,UAAU,YAAY,MAAM,OAAO,OAAO,QAAQ;AACxD,MAAI,CAAC,SAAS;AACZ;AAAA,EACF;AAEA,QAAM,OAAiB,CAAC;AACxB,MAAI,OAAO,OAAO,OAAO;AACvB,SAAK,KAAK,MAAM,OAAO,OAAO,KAAK;AAAA,EACrC;AACA,OAAK,KAAK,OAAO;AAEjB,QAAM,WAAW,OAAO,MAAM,MAAM;AACtC;AAEA,eAAe,eAAe,QAAmB,QAA+B;AAC9E,MAAI,CAAC,OAAO,MAAM,SAAS;AACzB;AAAA,EACF;AAEA,MAAI,QAAQ,aAAa,UAAU;AACjC,sBAAkB,MAAM;AACxB;AAAA,EACF;AAEA,MAAI,OAAO,MAAM,eAAe,QAAQ;AACtC,UAAM,WAAW,UAAU,CAAC,eAAe,GAAG,MAAM;AAAA,EACtD;AACF;AAEA,eAAsB,WACpB,mBACA,QACA,QACe;AACf,MAAI,OAAO,MAAM,SAAS;AACxB,sBAAkB,MAAM;AAAA,EAC1B;AAEA,MAAI,OAAO,OAAO,WAAW,kBAAkB,KAAK,GAAG;AACrD,UAAM,MAAM,mBAAmB,QAAQ,MAAM;AAAA,EAC/C;AAEA,SAAO,MAAM,eAAe,+BAA+B;AAAA,IACzD,MAAM,OAAO,MAAM;AAAA,IACnB,QAAQ,OAAO,OAAO;AAAA,EACxB,CAAC;AACH;AAEA,eAAsB,YACpB,SACA,QACA,QACe;AACf,QAAM,eAAe,QAAQ,MAAM;AAEnC,MAAI,OAAO,OAAO,SAAS;AACzB,UAAM,cAAc,SAAS,KAAK,KAAK;AACvC,UAAM,MAAM,mBAAmB,WAAW,IAAI,QAAQ,MAAM;AAAA,EAC9D;AAEA,SAAO,KAAK,gBAAgB,gCAAgC;AAAA,IAC1D,MAAM,OAAO,MAAM;AAAA,IACnB,QAAQ,OAAO,OAAO;AAAA,EACxB,CAAC;AACH;;;ACxJA,SAAS,gBAAgB,OAAsB;AAC7C,MAAI,MAAM,SAAS,iBAAiB;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,MAAM,WAAW;AAClC,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,SAAS;AAC3B,MAAI,aAAa,OAAO,cAAc,UAAU;AAC9C,QACE,aAAa,aACb,OAAO,UAAU,YAAY,YAC7B,UAAU,QAAQ,KAAK,GACvB;AACA,aAAO,+BAA+B,UAAU,QAAQ,KAAK,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,SAAS,YAAY,SAAS,KAAK,KAAK,GAAG;AAC7D,WAAO,+BAA+B,SAAS,KAAK,KAAK,CAAC;AAAA,EAC5D;AAEA,SAAO;AACT;AAEO,IAAM,cAAsB,OAAO,UAAU;AAClD,QAAM,SAAS,aAAa;AAC5B,QAAM,kCAAkC,oBAAI,IAAoB;AAChE,QAAM,+BAA+B,oBAAI,IAAoB;AAC7D,QAAM,6BAA6B,oBAAI,IAAoB;AAE3D,SAAO;AAAA,IACL,OAAO,OAAO,EAAE,MAAM,MAAqB;AACzC,UAAI,MAAM,SAAS,mBAAmB;AACpC,cAAM,OAAO,MAAM,WAAW;AAC9B,YAAI,KAAK,SAAS,aAAa;AAC7B,0CAAgC,IAAI,KAAK,WAAW,KAAK,EAAE;AAAA,QAC7D;AACA;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,wBAAwB;AACzC,cAAM,OAAO,MAAM,WAAW;AAC9B,YACE,KAAK,SAAS,UACd,OAAO,KAAK,cAAc,YAC1B,OAAO,KAAK,SAAS,UACrB;AACA,uCAA6B,IAAI,KAAK,WAAW,KAAK,IAAI;AAAA,QAC5D;AACA;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,iBAAiB;AACnE;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,WAAW;AAChC,aAAO,SAAS,OAAO,MAAM,QAAQ;AAErC,UAAI,MAAM,SAAS,gBAAgB;AACjC,cAAM,YAAY,MAAM,WAAW;AACnC,cAAM,kBAAkB,gCAAgC,IAAI,SAAS;AACrE,cAAM,WAAW,kBACZ,6BAA6B,IAAI,eAAe,KAAK,KACtD;AACJ,cAAM,mBAAmB,2BAA2B,IAAI,SAAS;AAEjE,YAAI,mBAAmB,qBAAqB,iBAAiB;AAC3D,iBAAO,MAAM,uBAAuB,kCAAkC;AAAA,YACpE;AAAA,YACA,WAAW;AAAA,UACb,CAAC;AACD;AAAA,QACF;AAEA,YAAI,CAAC,mBAAmB,CAAC,UAAU;AACjC,iBAAO,KAAK,mBAAmB,8CAA8C;AAAA,YAC3E;AAAA,YACA,cAAc,QAAQ,eAAe;AAAA,YACrC,SAAS,QAAQ,QAAQ;AAAA,UAC3B,CAAC;AACD,gBAAM,WAAW,IAAI,QAAQ,MAAM;AACnC;AAAA,QACF;AAEA,mCAA2B,IAAI,WAAW,eAAe;AACzD,cAAM,WAAW,UAAU,QAAQ,MAAM;AACzC;AAAA,MACF;AAEA,YAAM,YAAY,gBAAgB,KAAK,GAAG,QAAQ,MAAM;AAAA,IAC1D;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;","names":["mkdir","homedir","path"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/features/logging.ts","../src/features/sounds.ts","../src/plugin.ts"],"sourcesContent":["import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface KitConfig {\n bells: {\n enabled: boolean;\n errorSound: \"Funk\";\n };\n speech: {\n enabled: boolean;\n maxChars: number;\n voice: string | null;\n };\n debug: {\n logLevel: LogLevel;\n };\n}\n\nexport const CONFIG_PATH = path.join(homedir(), \".config\", \"opencode\", \"kit.json\");\n\nconst VALID_LOG_LEVELS: ReadonlySet<string> = new Set([\"debug\", \"info\", \"warn\", \"error\"]);\n\nconst DEFAULT_CONFIG: KitConfig = {\n bells: {\n enabled: true,\n errorSound: \"Funk\",\n },\n speech: {\n enabled: true,\n maxChars: 220,\n voice: null,\n },\n debug: {\n logLevel: \"info\",\n },\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction asBoolean(value: unknown, fallback: boolean): boolean {\n return typeof value === \"boolean\" ? value : fallback;\n}\n\nfunction asErrorSound(value: unknown, fallback: \"Funk\"): \"Funk\" {\n return value === \"Funk\" ? \"Funk\" : fallback;\n}\n\nfunction asOptionalString(value: unknown, fallback: string | null): string | null {\n if (value === null) {\n return null;\n }\n\n return typeof value === \"string\" && value.trim() !== \"\" ? value : fallback;\n}\n\nfunction asIntInRange(value: unknown, fallback: number, min: number, max: number): number {\n if (typeof value !== \"number\" || !Number.isInteger(value)) {\n return fallback;\n }\n\n if (value < min || value > max) {\n return fallback;\n }\n\n return value;\n}\n\nfunction asLogLevel(value: unknown, fallback: LogLevel): LogLevel {\n if (typeof value !== \"string\") {\n return fallback;\n }\n\n return VALID_LOG_LEVELS.has(value) ? (value as LogLevel) : fallback;\n}\n\nexport function sanitizeConfig(input: unknown): KitConfig {\n const raw = isRecord(input) ? input : {};\n const rawBells = isRecord(raw.bells) ? raw.bells : {};\n const rawSpeech = isRecord(raw.speech) ? raw.speech : {};\n const rawDebug = isRecord(raw.debug) ? raw.debug : {};\n\n return {\n bells: {\n enabled: asBoolean(rawBells.enabled, DEFAULT_CONFIG.bells.enabled),\n errorSound: asErrorSound(rawBells.errorSound, DEFAULT_CONFIG.bells.errorSound),\n },\n speech: {\n enabled: asBoolean(rawSpeech.enabled, DEFAULT_CONFIG.speech.enabled),\n maxChars: asIntInRange(rawSpeech.maxChars, DEFAULT_CONFIG.speech.maxChars, 20, 2000),\n voice: asOptionalString(rawSpeech.voice, DEFAULT_CONFIG.speech.voice),\n },\n debug: {\n logLevel: asLogLevel(rawDebug.logLevel, DEFAULT_CONFIG.debug.logLevel),\n },\n };\n}\n\nexport async function readConfig(): Promise<KitConfig> {\n try {\n const content = await readFile(CONFIG_PATH, \"utf8\");\n const parsed = JSON.parse(content) as unknown;\n return sanitizeConfig(parsed);\n } catch {\n return sanitizeConfig(DEFAULT_CONFIG);\n }\n}\n\nasync function ensureConfigDirectory(): Promise<void> {\n await mkdir(path.dirname(CONFIG_PATH), { recursive: true });\n}\n\nexport async function writeConfig(config: KitConfig): Promise<void> {\n const safeConfig = sanitizeConfig(config);\n const serialized = `${JSON.stringify(safeConfig, null, 2)}\\n`;\n const tempPath = `${CONFIG_PATH}.tmp`;\n\n await ensureConfigDirectory();\n await writeFile(tempPath, serialized, \"utf8\");\n await rename(tempPath, CONFIG_PATH);\n}\n\nexport async function updateConfig(mutator: (current: KitConfig) => KitConfig): Promise<KitConfig> {\n const current = await readConfig();\n const next = sanitizeConfig(mutator(current));\n await writeConfig(next);\n return next;\n}\n\nexport function defaultConfig(): KitConfig {\n return sanitizeConfig(DEFAULT_CONFIG);\n}\n","import { appendFile, mkdir } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nimport type { LogLevel } from \"../config\";\n\nconst LOG_DIR = path.join(homedir(), \".config\", \"opencode\", \"logs\");\nexport const LOG_PATH = path.join(LOG_DIR, \"opencode-kit.log\");\n\nconst LOG_WEIGHT: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nexport interface LogContext {\n [key: string]: unknown;\n}\n\nexport interface Logger {\n setLevel: (level: LogLevel) => void;\n debug: (event: string, message: string, context?: LogContext) => void;\n info: (event: string, message: string, context?: LogContext) => void;\n warn: (event: string, message: string, context?: LogContext) => void;\n error: (event: string, message: string, context?: LogContext) => void;\n}\n\ninterface LogLine {\n timestamp: string;\n level: LogLevel;\n event: string;\n message: string;\n context?: LogContext;\n}\n\nfunction shouldLog(minLevel: LogLevel, level: LogLevel): boolean {\n return LOG_WEIGHT[level] >= LOG_WEIGHT[minLevel];\n}\n\nasync function appendLogLine(line: LogLine): Promise<void> {\n try {\n await mkdir(LOG_DIR, { recursive: true });\n await appendFile(LOG_PATH, `${JSON.stringify(line)}\\n`, \"utf8\");\n } catch {\n // Best effort logging only.\n }\n}\n\nexport function createLogger(initialLevel: LogLevel = \"info\"): Logger {\n let minLevel = initialLevel;\n\n const write = (level: LogLevel, event: string, message: string, context?: LogContext): void => {\n if (!shouldLog(minLevel, level)) {\n return;\n }\n\n void appendLogLine({\n timestamp: new Date().toISOString(),\n level,\n event,\n message,\n context,\n });\n };\n\n return {\n setLevel: (level: LogLevel) => {\n minLevel = level;\n },\n debug: (event, message, context) => {\n write(\"debug\", event, message, context);\n },\n info: (event, message, context) => {\n write(\"info\", event, message, context);\n },\n warn: (event, message, context) => {\n write(\"warn\", event, message, context);\n },\n error: (event, message, context) => {\n write(\"error\", event, message, context);\n },\n };\n}\n","import { spawn } from \"node:child_process\";\n\nimport type { KitConfig } from \"../config\";\nimport type { Logger } from \"./logging\";\n\nconst FUNK_SOUND_PATH = \"/System/Library/Sounds/Funk.aiff\";\n\nfunction runCommand(command: string, args: string[], logger: Logger): Promise<void> {\n return new Promise((resolve) => {\n const child = spawn(command, args, {\n stdio: \"ignore\",\n });\n\n child.on(\"error\", (error) => {\n logger.warn(\"command.error\", \"Command failed to launch\", {\n command,\n args,\n error: error.message,\n });\n resolve();\n });\n\n child.on(\"exit\", (code) => {\n if (code !== 0) {\n logger.warn(\"command.nonzero\", \"Command exited non-zero\", {\n command,\n args,\n code,\n });\n }\n\n resolve();\n });\n });\n}\n\nfunction truncateSummary(text: string, maxChars: number): string {\n const trimmed = text.trim();\n if (trimmed.length <= maxChars) {\n return trimmed;\n }\n\n return `${trimmed.slice(0, maxChars - 3)}...`;\n}\n\nfunction cleanTextForSpeech(text: string): string {\n return text\n .replace(/```[\\s\\S]*?```/g, \" code block omitted \")\n .replace(/`([^`]+)`/g, \"$1\")\n .replace(/\\[(.*?)\\]\\((.*?)\\)/g, \"$1\")\n .replace(/[*_~#>]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nfunction shortSpeech(text: string, maxChars: number): string {\n const cleaned = cleanTextForSpeech(text);\n if (!cleaned) {\n return \"\";\n }\n\n if (cleaned.length <= maxChars) {\n return cleaned;\n }\n\n const sentence = cleaned.match(/(.+?[.!?])(\\s|$)/)?.[1]?.trim();\n if (sentence && sentence.length <= maxChars) {\n return sentence;\n }\n\n return truncateSummary(cleaned, maxChars);\n}\n\nfunction writeTerminalBell(logger: Logger): void {\n try {\n process.stdout.write(\"\\u0007\");\n } catch (error) {\n logger.warn(\"bell.error\", \"Failed to write terminal bell\", {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nasync function speak(text: string, config: KitConfig, logger: Logger): Promise<void> {\n if (!config.speech.enabled) {\n return;\n }\n\n if (process.platform !== \"darwin\") {\n logger.info(\"speech.unsupported\", \"Speech skipped: platform unsupported\", {\n platform: process.platform,\n });\n return;\n }\n\n const clipped = shortSpeech(text, config.speech.maxChars);\n if (!clipped) {\n return;\n }\n\n const args: string[] = [];\n if (config.speech.voice) {\n args.push(\"-v\", config.speech.voice);\n }\n args.push(clipped);\n\n await runCommand(\"say\", args, logger);\n}\n\nasync function playErrorSound(config: KitConfig, logger: Logger): Promise<void> {\n if (!config.bells.enabled) {\n return;\n }\n\n if (process.platform !== \"darwin\") {\n writeTerminalBell(logger);\n return;\n }\n\n if (config.bells.errorSound === \"Funk\") {\n await runCommand(\"afplay\", [FUNK_SOUND_PATH], logger);\n }\n}\n\nexport async function notifyIdle(\n lastAssistantText: string,\n config: KitConfig,\n logger: Logger,\n): Promise<void> {\n if (config.bells.enabled) {\n writeTerminalBell(logger);\n }\n\n if (config.speech.enabled && lastAssistantText.trim()) {\n await speak(lastAssistantText, config, logger);\n }\n\n logger.debug(\"idle.notify\", \"Idle notification processed\", {\n bell: config.bells.enabled,\n speech: config.speech.enabled,\n });\n}\n\nexport async function notifyError(config: KitConfig, logger: Logger): Promise<void> {\n await playErrorSound(config, logger);\n\n logger.warn(\"error.notify\", \"Error notification processed\", {\n bell: config.bells.enabled,\n speech: false,\n });\n}\n","import type { Plugin } from \"@opencode-ai/plugin\";\nimport type { Event } from \"@opencode-ai/sdk\";\n\nimport { readConfig } from \"./config\";\nimport { createLogger } from \"./features/logging\";\nimport { notifyError, notifyIdle } from \"./features/sounds\";\n\nfunction getErrorMessage(event: Event): string {\n if (event.type !== \"session.error\") {\n return \"Unknown error\";\n }\n\n const rawError = event.properties.error;\n if (!rawError) {\n return \"Agent encountered an error.\";\n }\n\n const errorData = rawError.data;\n if (errorData && typeof errorData === \"object\") {\n if (\n \"message\" in errorData &&\n typeof errorData.message === \"string\" &&\n errorData.message.trim()\n ) {\n return `Agent encountered an error: ${errorData.message.trim()}`;\n }\n }\n\n if (typeof rawError.name === \"string\" && rawError.name.trim()) {\n return `Agent encountered an error: ${rawError.name.trim()}`;\n }\n\n return \"Agent encountered an error.\";\n}\n\nexport const OpencodeKit: Plugin = async (input) => {\n const logger = createLogger();\n const latestAssistantMessageBySession = new Map<string, string>();\n const latestAssistantTextByMessage = new Map<string, string>();\n const lastSpokenMessageBySession = new Map<string, string>();\n\n return {\n event: async ({ event }): Promise<void> => {\n if (event.type === \"message.updated\") {\n const info = event.properties.info;\n if (info.role === \"assistant\") {\n latestAssistantMessageBySession.set(info.sessionID, info.id);\n }\n return;\n }\n\n if (event.type === \"message.part.updated\") {\n const part = event.properties.part;\n if (\n part.type === \"text\" &&\n typeof part.messageID === \"string\" &&\n typeof part.text === \"string\"\n ) {\n latestAssistantTextByMessage.set(part.messageID, part.text);\n }\n return;\n }\n\n if (event.type !== \"session.idle\" && event.type !== \"session.error\") {\n return;\n }\n\n const config = await readConfig();\n logger.setLevel(config.debug.logLevel);\n\n if (event.type === \"session.idle\") {\n const sessionID = event.properties.sessionID;\n const latestMessageID = latestAssistantMessageBySession.get(sessionID);\n const lastText = latestMessageID\n ? (latestAssistantTextByMessage.get(latestMessageID) ?? \"\")\n : \"\";\n const previouslySpoken = lastSpokenMessageBySession.get(sessionID);\n\n if (latestMessageID && previouslySpoken === latestMessageID) {\n logger.debug(\"idle.skip_duplicate\", \"Skipping duplicate idle speech\", {\n sessionID,\n messageID: latestMessageID,\n });\n return;\n }\n\n if (!latestMessageID || !lastText) {\n logger.warn(\"idle.no_summary\", \"No cached assistant summary for idle event\", {\n sessionID,\n hasMessageID: Boolean(latestMessageID),\n hasText: Boolean(lastText),\n });\n await notifyIdle(\"\", config, logger);\n return;\n }\n\n lastSpokenMessageBySession.set(sessionID, latestMessageID);\n await notifyIdle(lastText, config, logger);\n return;\n }\n\n logger.warn(\"error.detected\", getErrorMessage(event), {\n eventType: event.type,\n });\n await notifyError(config, logger);\n },\n };\n};\n\nexport default OpencodeKit;\n"],"mappings":";AAAA,SAAS,OAAO,UAAU,QAAQ,iBAAiB;AACnD,SAAS,eAAe;AACxB,OAAO,UAAU;AAmBV,IAAM,cAAc,KAAK,KAAK,QAAQ,GAAG,WAAW,YAAY,UAAU;AAEjF,IAAM,mBAAwC,oBAAI,IAAI,CAAC,SAAS,QAAQ,QAAQ,OAAO,CAAC;AAExF,IAAM,iBAA4B;AAAA,EAChC,OAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAY;AAAA,EACd;AAAA,EACA,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AAAA,EACA,OAAO;AAAA,IACL,UAAU;AAAA,EACZ;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,UAAU,OAAgB,UAA4B;AAC7D,SAAO,OAAO,UAAU,YAAY,QAAQ;AAC9C;AAEA,SAAS,aAAa,OAAgB,UAA0B;AAC9D,SAAO,UAAU,SAAS,SAAS;AACrC;AAEA,SAAS,iBAAiB,OAAgB,UAAwC;AAChF,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,KAAK,QAAQ;AACpE;AAEA,SAAS,aAAa,OAAgB,UAAkB,KAAa,KAAqB;AACxF,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,KAAK,GAAG;AACzD,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,OAAO,QAAQ,KAAK;AAC9B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,OAAgB,UAA8B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO,iBAAiB,IAAI,KAAK,IAAK,QAAqB;AAC7D;AAEO,SAAS,eAAe,OAA2B;AACxD,QAAM,MAAM,SAAS,KAAK,IAAI,QAAQ,CAAC;AACvC,QAAM,WAAW,SAAS,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC;AACpD,QAAM,YAAY,SAAS,IAAI,MAAM,IAAI,IAAI,SAAS,CAAC;AACvD,QAAM,WAAW,SAAS,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC;AAEpD,SAAO;AAAA,IACL,OAAO;AAAA,MACL,SAAS,UAAU,SAAS,SAAS,eAAe,MAAM,OAAO;AAAA,MACjE,YAAY,aAAa,SAAS,YAAY,eAAe,MAAM,UAAU;AAAA,IAC/E;AAAA,IACA,QAAQ;AAAA,MACN,SAAS,UAAU,UAAU,SAAS,eAAe,OAAO,OAAO;AAAA,MACnE,UAAU,aAAa,UAAU,UAAU,eAAe,OAAO,UAAU,IAAI,GAAI;AAAA,MACnF,OAAO,iBAAiB,UAAU,OAAO,eAAe,OAAO,KAAK;AAAA,IACtE;AAAA,IACA,OAAO;AAAA,MACL,UAAU,WAAW,SAAS,UAAU,eAAe,MAAM,QAAQ;AAAA,IACvE;AAAA,EACF;AACF;AAEA,eAAsB,aAAiC;AACrD,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,aAAa,MAAM;AAClD,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO,eAAe,MAAM;AAAA,EAC9B,QAAQ;AACN,WAAO,eAAe,cAAc;AAAA,EACtC;AACF;;;AC9GA,SAAS,YAAY,SAAAA,cAAa;AAClC,SAAS,WAAAC,gBAAe;AACxB,OAAOC,WAAU;AAIjB,IAAM,UAAUA,MAAK,KAAKD,SAAQ,GAAG,WAAW,YAAY,MAAM;AAC3D,IAAM,WAAWC,MAAK,KAAK,SAAS,kBAAkB;AAE7D,IAAM,aAAuC;AAAA,EAC3C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAsBA,SAAS,UAAU,UAAoB,OAA0B;AAC/D,SAAO,WAAW,KAAK,KAAK,WAAW,QAAQ;AACjD;AAEA,eAAe,cAAc,MAA8B;AACzD,MAAI;AACF,UAAMF,OAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AACxC,UAAM,WAAW,UAAU,GAAG,KAAK,UAAU,IAAI,CAAC;AAAA,GAAM,MAAM;AAAA,EAChE,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,aAAa,eAAyB,QAAgB;AACpE,MAAI,WAAW;AAEf,QAAM,QAAQ,CAAC,OAAiB,OAAe,SAAiB,YAA+B;AAC7F,QAAI,CAAC,UAAU,UAAU,KAAK,GAAG;AAC/B;AAAA,IACF;AAEA,SAAK,cAAc;AAAA,MACjB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,UAAU,CAAC,UAAoB;AAC7B,iBAAW;AAAA,IACb;AAAA,IACA,OAAO,CAAC,OAAO,SAAS,YAAY;AAClC,YAAM,SAAS,OAAO,SAAS,OAAO;AAAA,IACxC;AAAA,IACA,MAAM,CAAC,OAAO,SAAS,YAAY;AACjC,YAAM,QAAQ,OAAO,SAAS,OAAO;AAAA,IACvC;AAAA,IACA,MAAM,CAAC,OAAO,SAAS,YAAY;AACjC,YAAM,QAAQ,OAAO,SAAS,OAAO;AAAA,IACvC;AAAA,IACA,OAAO,CAAC,OAAO,SAAS,YAAY;AAClC,YAAM,SAAS,OAAO,SAAS,OAAO;AAAA,IACxC;AAAA,EACF;AACF;;;ACnFA,SAAS,aAAa;AAKtB,IAAM,kBAAkB;AAExB,SAAS,WAAW,SAAiB,MAAgB,QAA+B;AAClF,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,MAAM,SAAS,MAAM;AAAA,MACjC,OAAO;AAAA,IACT,CAAC;AAED,UAAM,GAAG,SAAS,CAAC,UAAU;AAC3B,aAAO,KAAK,iBAAiB,4BAA4B;AAAA,QACvD;AAAA,QACA;AAAA,QACA,OAAO,MAAM;AAAA,MACf,CAAC;AACD,cAAQ;AAAA,IACV,CAAC;AAED,UAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,UAAI,SAAS,GAAG;AACd,eAAO,KAAK,mBAAmB,2BAA2B;AAAA,UACxD;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAEA,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,gBAAgB,MAAc,UAA0B;AAC/D,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,QAAQ,UAAU,UAAU;AAC9B,WAAO;AAAA,EACT;AAEA,SAAO,GAAG,QAAQ,MAAM,GAAG,WAAW,CAAC,CAAC;AAC1C;AAEA,SAAS,mBAAmB,MAAsB;AAChD,SAAO,KACJ,QAAQ,mBAAmB,sBAAsB,EACjD,QAAQ,cAAc,IAAI,EAC1B,QAAQ,uBAAuB,IAAI,EACnC,QAAQ,YAAY,EAAE,EACtB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEA,SAAS,YAAY,MAAc,UAA0B;AAC3D,QAAM,UAAU,mBAAmB,IAAI;AACvC,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,UAAU,UAAU;AAC9B,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,QAAQ,MAAM,kBAAkB,IAAI,CAAC,GAAG,KAAK;AAC9D,MAAI,YAAY,SAAS,UAAU,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO,gBAAgB,SAAS,QAAQ;AAC1C;AAEA,SAAS,kBAAkB,QAAsB;AAC/C,MAAI;AACF,YAAQ,OAAO,MAAM,MAAQ;AAAA,EAC/B,SAAS,OAAO;AACd,WAAO,KAAK,cAAc,iCAAiC;AAAA,MACzD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D,CAAC;AAAA,EACH;AACF;AAEA,eAAe,MAAM,MAAc,QAAmB,QAA+B;AACnF,MAAI,CAAC,OAAO,OAAO,SAAS;AAC1B;AAAA,EACF;AAEA,MAAI,QAAQ,aAAa,UAAU;AACjC,WAAO,KAAK,sBAAsB,wCAAwC;AAAA,MACxE,UAAU,QAAQ;AAAA,IACpB,CAAC;AACD;AAAA,EACF;AAEA,QAAM,UAAU,YAAY,MAAM,OAAO,OAAO,QAAQ;AACxD,MAAI,CAAC,SAAS;AACZ;AAAA,EACF;AAEA,QAAM,OAAiB,CAAC;AACxB,MAAI,OAAO,OAAO,OAAO;AACvB,SAAK,KAAK,MAAM,OAAO,OAAO,KAAK;AAAA,EACrC;AACA,OAAK,KAAK,OAAO;AAEjB,QAAM,WAAW,OAAO,MAAM,MAAM;AACtC;AAEA,eAAe,eAAe,QAAmB,QAA+B;AAC9E,MAAI,CAAC,OAAO,MAAM,SAAS;AACzB;AAAA,EACF;AAEA,MAAI,QAAQ,aAAa,UAAU;AACjC,sBAAkB,MAAM;AACxB;AAAA,EACF;AAEA,MAAI,OAAO,MAAM,eAAe,QAAQ;AACtC,UAAM,WAAW,UAAU,CAAC,eAAe,GAAG,MAAM;AAAA,EACtD;AACF;AAEA,eAAsB,WACpB,mBACA,QACA,QACe;AACf,MAAI,OAAO,MAAM,SAAS;AACxB,sBAAkB,MAAM;AAAA,EAC1B;AAEA,MAAI,OAAO,OAAO,WAAW,kBAAkB,KAAK,GAAG;AACrD,UAAM,MAAM,mBAAmB,QAAQ,MAAM;AAAA,EAC/C;AAEA,SAAO,MAAM,eAAe,+BAA+B;AAAA,IACzD,MAAM,OAAO,MAAM;AAAA,IACnB,QAAQ,OAAO,OAAO;AAAA,EACxB,CAAC;AACH;AAEA,eAAsB,YAAY,QAAmB,QAA+B;AAClF,QAAM,eAAe,QAAQ,MAAM;AAEnC,SAAO,KAAK,gBAAgB,gCAAgC;AAAA,IAC1D,MAAM,OAAO,MAAM;AAAA,IACnB,QAAQ;AAAA,EACV,CAAC;AACH;;;AC/IA,SAAS,gBAAgB,OAAsB;AAC7C,MAAI,MAAM,SAAS,iBAAiB;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,MAAM,WAAW;AAClC,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,SAAS;AAC3B,MAAI,aAAa,OAAO,cAAc,UAAU;AAC9C,QACE,aAAa,aACb,OAAO,UAAU,YAAY,YAC7B,UAAU,QAAQ,KAAK,GACvB;AACA,aAAO,+BAA+B,UAAU,QAAQ,KAAK,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,SAAS,YAAY,SAAS,KAAK,KAAK,GAAG;AAC7D,WAAO,+BAA+B,SAAS,KAAK,KAAK,CAAC;AAAA,EAC5D;AAEA,SAAO;AACT;AAEO,IAAM,cAAsB,OAAO,UAAU;AAClD,QAAM,SAAS,aAAa;AAC5B,QAAM,kCAAkC,oBAAI,IAAoB;AAChE,QAAM,+BAA+B,oBAAI,IAAoB;AAC7D,QAAM,6BAA6B,oBAAI,IAAoB;AAE3D,SAAO;AAAA,IACL,OAAO,OAAO,EAAE,MAAM,MAAqB;AACzC,UAAI,MAAM,SAAS,mBAAmB;AACpC,cAAM,OAAO,MAAM,WAAW;AAC9B,YAAI,KAAK,SAAS,aAAa;AAC7B,0CAAgC,IAAI,KAAK,WAAW,KAAK,EAAE;AAAA,QAC7D;AACA;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,wBAAwB;AACzC,cAAM,OAAO,MAAM,WAAW;AAC9B,YACE,KAAK,SAAS,UACd,OAAO,KAAK,cAAc,YAC1B,OAAO,KAAK,SAAS,UACrB;AACA,uCAA6B,IAAI,KAAK,WAAW,KAAK,IAAI;AAAA,QAC5D;AACA;AAAA,MACF;AAEA,UAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,iBAAiB;AACnE;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,WAAW;AAChC,aAAO,SAAS,OAAO,MAAM,QAAQ;AAErC,UAAI,MAAM,SAAS,gBAAgB;AACjC,cAAM,YAAY,MAAM,WAAW;AACnC,cAAM,kBAAkB,gCAAgC,IAAI,SAAS;AACrE,cAAM,WAAW,kBACZ,6BAA6B,IAAI,eAAe,KAAK,KACtD;AACJ,cAAM,mBAAmB,2BAA2B,IAAI,SAAS;AAEjE,YAAI,mBAAmB,qBAAqB,iBAAiB;AAC3D,iBAAO,MAAM,uBAAuB,kCAAkC;AAAA,YACpE;AAAA,YACA,WAAW;AAAA,UACb,CAAC;AACD;AAAA,QACF;AAEA,YAAI,CAAC,mBAAmB,CAAC,UAAU;AACjC,iBAAO,KAAK,mBAAmB,8CAA8C;AAAA,YAC3E;AAAA,YACA,cAAc,QAAQ,eAAe;AAAA,YACrC,SAAS,QAAQ,QAAQ;AAAA,UAC3B,CAAC;AACD,gBAAM,WAAW,IAAI,QAAQ,MAAM;AACnC;AAAA,QACF;AAEA,mCAA2B,IAAI,WAAW,eAAe;AACzD,cAAM,WAAW,UAAU,QAAQ,MAAM;AACzC;AAAA,MACF;AAEA,aAAO,KAAK,kBAAkB,gBAAgB,KAAK,GAAG;AAAA,QACpD,WAAW,MAAM;AAAA,MACnB,CAAC;AACD,YAAM,YAAY,QAAQ,MAAM;AAAA,IAClC;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;","names":["mkdir","homedir","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akonwi/opencode-kit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Personal OpenCode workflow plugin with bells, speech, and runtime toggles",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
@@ -22,6 +22,9 @@
22
22
  "format": "biome format --write .",
23
23
  "check": "bun run lint && bun run build"
24
24
  },
25
+ "repository": {
26
+ "url": "https://github.com/akonwi/opencode-kit"
27
+ },
25
28
  "keywords": ["opencode", "plugin", "notifications", "speech"],
26
29
  "license": "MIT",
27
30
  "publishConfig": {