---
phase: 01-foundation-build-system
plan: 02
type: execute
wave: 2
depends_on:
  - 01-01
files_modified:
  - plugins/claude-tts-companion/Sources/claude-tts-companion/main.swift
  - plugins/claude-tts-companion/CLAUDE.md
  - .claude-plugin/marketplace.json
autonomous: true
requirements:
  - BUILD-01
  - BUILD-04
must_haves:
  truths:
    - "`swift build -c release` succeeds with zero errors in plugins/claude-tts-companion/"
    - "Release binary is a single file under 30MB"
    - "Binary launches, prints sherpa-onnx version, logs startup, and exits cleanly on SIGTERM"
    - "Plugin is registered in marketplace.json and passes validation"
  artifacts:
    - path: "plugins/claude-tts-companion/Sources/claude-tts-companion/main.swift"
      provides: "NSApplication accessory app entry point with SIGTERM handling and C interop verification"
      contains: "SherpaOnnxGetVersion"
      min_lines: 30
    - path: "plugins/claude-tts-companion/CLAUDE.md"
      provides: "Plugin documentation for cc-skills marketplace"
      contains: "claude-tts-companion"
    - path: ".claude-plugin/marketplace.json"
      provides: "Plugin registry with claude-tts-companion entry"
      contains: "claude-tts-companion"
  key_links:
    - from: "main.swift"
      to: "CSherpaOnnx"
      via: "import CSherpaOnnx + SherpaOnnxGetVersion() call"
      pattern: "import CSherpaOnnx"
    - from: "main.swift"
      to: "NSApplication"
      via: "app.setActivationPolicy(.accessory)"
      pattern: "setActivationPolicy.*accessory"
    - from: "main.swift"
      to: "SIGTERM handler"
      via: "DispatchSource.makeSignalSource"
      pattern: "makeSignalSource.*SIGTERM"
---

<objective>
Create the main.swift entry point (NSApplication accessory app with SIGTERM handling and sherpa-onnx C interop verification), register the plugin in marketplace.json, create CLAUDE.md, and verify the complete build produces a working binary under 30MB.

Purpose: This is the final integration step -- proving the entire build system works end-to-end from `swift build -c release` through binary execution. The sherpa-onnx version print confirms C interop is functional.
Output: A compilable, runnable binary + registered cc-skills plugin.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-foundation-build-system/01-RESEARCH.md
@.planning/phases/01-foundation-build-system/01-01-SUMMARY.md
</context>

<interfaces>
<!-- From Plan 01 artifacts the executor needs -->

From plugins/claude-tts-companion/Sources/CSherpaOnnx/include/shim.h:

```c
#include "sherpa-onnx/c-api/c-api.h"
// Exposes: SherpaOnnxGetVersion() -> const char*
```

From plugins/claude-tts-companion/Sources/claude-tts-companion/Config.swift:

```swift
enum Config {
    static let sherpaOnnxPath: String
    static let kokoroModelPath: String
    static let appName: String  // "claude-tts-companion"
    static let serviceLabel: String  // "com.terryli.claude-tts-companion"
}
```

From plugins/claude-tts-companion/Package.swift:

- CSherpaOnnx target available via `import CSherpaOnnx`
- SwiftTelegramSdk available via `import SwiftTelegramSdk` (Phase 5+)
- Logging available via `import Logging`
  </interfaces>

<tasks>

<task type="auto">
  <name>Task 1: Create main.swift entry point with NSApp accessory + SIGTERM + C interop</name>
  <files>
    plugins/claude-tts-companion/Sources/claude-tts-companion/main.swift
  </files>
  <read_first>
    .planning/phases/01-foundation-build-system/01-RESEARCH.md
    plugins/claude-tts-companion/Sources/claude-tts-companion/Config.swift
    plugins/claude-tts-companion/Package.swift
  </read_first>
  <action>
Create `plugins/claude-tts-companion/Sources/claude-tts-companion/main.swift` with this exact content:

```swift
import AppKit
import Foundation
import Logging
import CSherpaOnnx

// Unbuffer stdout/stderr for launchd (Pitfall 5)
setbuf(stdout, nil)
setbuf(stderr, nil)

// Configure logging first
LoggingSystem.bootstrap(StreamLogHandler.standardError)
let logger = Logger(label: Config.appName)

// Verify C interop works -- call a trivial sherpa-onnx function
let version = String(cString: SherpaOnnxGetVersion())
logger.info("sherpa-onnx C API version: \(version)")

// Set up NSApplication as accessory (no dock icon, no app switcher)
let app = NSApplication.shared
app.setActivationPolicy(.accessory)

// Set up SIGTERM handler using DispatchSource (not signal(), per research)
let sigSource = DispatchSource.makeSignalSource(signal: SIGTERM, queue: .main)
signal(SIGTERM, SIG_IGN)  // Let DispatchSource handle it
sigSource.setEventHandler {
    logger.info("SIGTERM received, shutting down")
    // Post dummy event to unblock RunLoop (Pitfall 4: NSApplication.stop requires event)
    let event = NSEvent.otherEvent(
        with: .applicationDefined, location: .zero,
        modifierFlags: [], timestamp: 0, windowNumber: 0,
        context: nil, subtype: 0, data1: 0, data2: 0
    )!
    app.postEvent(event, atStart: true)
    app.stop(nil)
}
sigSource.resume()

// Store sigSource globally to prevent ARC deallocation (Pitfall 3)
nonisolated(unsafe) var keepAlive: (any DispatchSourceSignal)? = sigSource

logger.info("Starting \(Config.appName)")

// Enter run loop (blocks forever until SIGTERM)
app.run()

logger.info("\(Config.appName) exited cleanly")
```

Key patterns established:

- `setbuf` for unbuffered output in daemon context (Pitfall 5)
- `StreamLogHandler.standardError` for all logging (not print())
- CSherpaOnnx import + `SherpaOnnxGetVersion()` call proves C interop (BUILD-03)
- `NSApplication.accessory` -- no dock icon, no app switcher
- `DispatchSource.makeSignalSource` for SIGTERM (not raw signal handler, per research)
- `nonisolated(unsafe) var keepAlive` prevents ARC deallocation of DispatchSource (Pitfall 3)
- Dummy `NSEvent.otherEvent` after `app.stop()` to unblock RunLoop (Pitfall 4)

Do NOT use `@main` attribute -- it conflicts with custom main.swift entry point.
</action>
<verify>
<automated>cd plugins/claude-tts-companion && swift build -c release 2>&1 | tail -5 && echo "EXIT: $?" && test -f .build/release/claude-tts-companion && echo "BINARY_EXISTS" && SIZE=$(stat -f%z .build/release/claude-tts-companion) && echo "SIZE_BYTES: $SIZE" && test "$SIZE" -lt 31457280 && echo "UNDER_30MB: PASS" || echo "UNDER_30MB: FAIL"</automated>
</verify>
<acceptance_criteria> - main.swift contains `import CSherpaOnnx` and calls `SherpaOnnxGetVersion()` - main.swift contains `app.setActivationPolicy(.accessory)` - main.swift contains `DispatchSource.makeSignalSource(signal: SIGTERM` - main.swift contains `nonisolated(unsafe) var keepAlive` - main.swift contains `setbuf(stdout, nil)` and `setbuf(stderr, nil)` - main.swift does NOT contain `@main` - `swift build -c release` exits with code 0 - Binary exists at `.build/release/claude-tts-companion` - Binary size is under 30MB (31,457,280 bytes)
</acceptance_criteria>
<done>`swift build -c release` succeeds, binary exists under 30MB, main.swift has NSApp accessory + SIGTERM handler + C interop verification</done>
</task>

<task type="auto">
  <name>Task 2: Register plugin in marketplace.json and create CLAUDE.md</name>
  <files>
    plugins/claude-tts-companion/CLAUDE.md
    .claude-plugin/marketplace.json
  </files>
  <read_first>
    .claude-plugin/marketplace.json
    plugins/itp/CLAUDE.md
  </read_first>
  <action>
1. Create `plugins/claude-tts-companion/CLAUDE.md` with plugin documentation:
   - Title: `# claude-tts-companion`
   - Description: "Unified macOS accessory app for real-time karaoke subtitles synced with TTS playback. Replaces three separate processes (TypeScript Telegram bot + Python TTS server + Swift subtitle prototype) with a single Swift binary."
   - Hub navigation link back to `../../CLAUDE.md`
   - Sibling link to `../CLAUDE.md` (plugins directory)
   - Build section: `swift build -c release` in plugin directory
   - Architecture section: Brief mention of CSherpaOnnx C interop, swift-telegram-sdk, NSApp accessory pattern
   - Key files table listing Package.swift, main.swift, Config.swift, CSherpaOnnx module

1. Add plugin entry to `.claude-plugin/marketplace.json`:
   - Add to the `plugins` array (maintain alphabetical order by name if possible, or append at end)
   - Use this exact structure:

   ```json
   {
     "name": "claude-tts-companion",
     "description": "Real-time karaoke subtitles synced with TTS playback — unified macOS accessory app replacing telegram-bot + kokoro-tts-server + subtitle prototype",
     "version": "12.16.0",
     "source": "./plugins/claude-tts-companion",
     "category": "application",
     "author": {
       "name": "Terry Li",
       "url": "https://github.com/terrylica"
     },
     "keywords": [
       "tts",
       "subtitles",
       "karaoke",
       "macos",
       "swift",
       "telegram",
       "sherpa-onnx",
       "kokoro"
     ],
     "skills": []
   }
   ```

2. Run `bun scripts/validate-plugins.mjs` to verify the plugin registry is valid.
   </action>

  <verify>
    <automated>grep -q "claude-tts-companion" .claude-plugin/marketplace.json && test -f plugins/claude-tts-companion/CLAUDE.md && grep -q "claude-tts-companion" plugins/claude-tts-companion/CLAUDE.md && bun scripts/validate-plugins.mjs 2>&1 | tail -3 && echo "PASS" || echo "FAIL"</automated>
  </verify>
  <acceptance_criteria>
    - `plugins/claude-tts-companion/CLAUDE.md` exists with hub navigation link to `../../CLAUDE.md`
    - `.claude-plugin/marketplace.json` contains a `claude-tts-companion` entry with `source: "./plugins/claude-tts-companion"`
    - `bun scripts/validate-plugins.mjs` passes without errors
  </acceptance_criteria>
  <done>Plugin registered in marketplace.json and CLAUDE.md created; validation passes</done>
</task>

</tasks>

<verification>
- `swift build -c release` succeeds in `plugins/claude-tts-companion/`
- Binary at `.build/release/claude-tts-companion` is under 30MB
- Running the binary prints sherpa-onnx version and starts as accessory app
- `bun scripts/validate-plugins.mjs` passes
- Sending SIGTERM to the running binary causes clean shutdown
</verification>

<success_criteria>

- `swift build -c release` exits 0 with zero errors (BUILD-01)
- Release binary is a single file under 30MB (BUILD-04)
- Binary prints sherpa-onnx C API version on startup (confirms BUILD-03 end-to-end)
- Plugin is registered and passes validation
  </success_criteria>

<output>
After completion, create `.planning/phases/01-foundation-build-system/01-02-SUMMARY.md`
</output>
