Skip to content

Add Ollama chat to the Windows and Linux app - #262

Closed
bagpotato wants to merge 1 commit into
Louis-CFM:mainfrom
bagpotato:ollama-windows-linux
Closed

bagpotato wants to merge 1 commit into
Louis-CFM:mainfrom
bagpotato:ollama-windows-linux

Conversation

@bagpotato

@bagpotato bagpotato commented Oct 6, 2026 •

Copy link
Copy Markdown

Ollama chat on Windows and Linux

Until now the local-model chat only existed in the macOS app. This adds it to the Tauri app (Windows, Linux).

What changes

  • New windows/src-tauri/src/ollama.rs: model list (/api/tags) and multi-turn chat (/api/chat), no API key needed.
  • Settings → Ollama (local): "Use for chat" switch, server URL, model picker with Refresh. The server is only probed automatically when the switch is already on.
  • Text files and images dropped on the island are sent to the model; PDFs return a clear error.
  • <think> blocks from reasoning models are hidden.
  • Readable errors: server down, model not installed (ollama pull …), timeout.
  • New settings fields provider, ollamaUrl, ollamaModel, all with defaults, so an existing settings.json still loads.

Windows specifics

  • localhost, 0.0.0.0 and ::1 are pinned to 127.0.0.1: Ollama listens on IPv4 only and localhost can resolve to ::1 first.
  • OLLAMA_HOST is honoured when the URL field is empty; values without a scheme, with /api or /v1, or with a trailing slash are accepted.
  • System proxy is bypassed for loopback.

Not in this PR

LM Studio, Gemini and OpenAI chat remain macOS only.

Testing

  • cargo test: 12/12 pass, including new tests for URL normalisation and think-block stripping.
  • npm run pack succeeds on Windows 11.
  • Manually checked chat against a local Ollama server with an installed model.

Screenshots

Captura de pantalla 2026-10-06 102558 Captura de pantalla 2026-10-06 102657

Louis-CFM added a commit that referenced this pull request Oct 6, 2026
* feat: add GitHub Copilot CLI, Muse Code, OpenCode, Amp agents (#262)

- agent_copilot: writes ~/.copilot/hooks/coucou.json with camelCase
  Claude Code events; fail-closed — relay outputs
  {"permissionDecision":"ask"} on timeout so Copilot re-prompts
  instead of denying
- agent_muse: merges into ~/.config/muse/settings.json like Gemini;
  relay normalises snake_case events (pre_tool_use → PreToolUse, etc.)
- agent_opencode: writes ~/.config/opencode/plugins/coucou.js (JS
  plugin, fire-and-forget, never blocks OpenCode)
- agent_amp: writes ~/.config/amp/plugins/coucou.ts (TS plugin,
  display only, never blocks Amp)
- PillCatalog: agent_copilot #818CF8, agent_muse #38BDF8,
  agent_opencode #4ADE80, agent_amp #F59E0B — all githubOnly
- SettingsView: installer GroupBox + trigger/confirm functions for all 4
- IslandViewContent: pill-tap → terminal, isConfigured, isHooks, hints
- Python relay (both GitHub and App Store builds): normalize_event adds
  Muse snake_case mappings; fire-and-forget outputs {} for muse like
  gemini/antigravity; Copilot PermissionRequest timeout → ask JSON
- docs/AGENTS.md: entries for all 4 new agents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat/more-agents: fix Copilot, Muse, OpenCode and Amp end-to-end

Copilot CLI:
- Hook format: camelCase event keys, bash/timeoutSec, event name as
  positional arg, version:1 at root
- Uninstall deletes the file entirely instead of leaving {}
- copilotHooksInstalled() checks "bash" key
- processPermissionRequest shows real Allow/Deny card for copilot agent;
  relay outputs {"permissionDecision":"allow|deny"} directly

Muse Code:
- Hook format: PascalCase event keys (was snake_case)
- Add PermissionRequest hook (120 s)
- Add schema_version:1 when creating a new settings file
- processPermissionRequest shows real Allow/Deny card for muse agent;
  relay outputs {"permissionDecision":"allow|deny"} directly

OpenCode plugin:
- Rewritten to use CoucouPlugin = async (ctx) => ({ event: async }) API
- Maps OpenCode event types to canonical Coucou names in the plugin itself
- Error handlers on spawn and stdin

Amp plugin:
- Rewritten to use amp.on('event-name', handler) API
- tool.call handler returns { action: 'allow' } (required by Amp)
- Per-event handlers for SessionStart/UserPromptSubmit/PreToolUse/
  PostToolUse/Stop with correct session_id forwarding
- Error handlers on spawn and stdin

Python relays (both GitHub and App Store versions):
- normalize_event: add sessionStart, userPromptSubmitted, agentStop,
  notification, stop
- normalize_tool_fields: add toolName->tool_name, toolArgs->tool_input,
  workdir->cwd (Copilot CLI fields)
- PermissionRequest: copilot/muse output {"permissionDecision":"allow|deny"}
  instead of hookSpecificOutput; always → plain allow for copilot/muse
- Normal events: add copilot to agents that output {} on stdout
- Wrap main() in try/except at top level

Shell wrapper:
- Output {"permissionDecision":"ask"} when python3 unavailable and
  the call is copilot permissionRequest (fail-closed guarantee)

Plugin safety:
- removeOpenCodePlugin/removeAmpPlugin check "generated by Coucou"
  header before deleting
- previewOpenCodePlugin/previewAmpPlugin and their write counterparts
  now use SHA-256 fingerprint to detect file changes between preview
  and write

Docs: update AGENTS.md with correct event tables for all four agents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(agents): normalize_event, OpenCode hooks, Amp try/finally, Copilot/Muse Always button

- normalize_event: add preToolUse→PreToolUse, postToolUse→PostToolUse,
  permissionRequest→PermissionRequest, sessionEnd→SessionEnd (both relays)
- Wrapper shell: copilot without permissionRequest now returns {} (not nothing)
- OpenCode plugin: filter unknown event types, read from event.properties,
  use e.tool as string directly
- Amp plugin: surround forward() with try/finally so tool.call always returns
  {action:'allow'}; use typeof e.tool === 'string' check
- Move isCopilotRequest/isMuseRequest inside #if !APPSTORE guard
- Add handledNote cases for agent_copilot and agent_muse
- Reduce relay wait to 110s for Copilot/Muse
- Update docs/SPEC.md and windows/README.md with new agents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(agents): OpenCode separate tool hooks, README agent table

- Remove tool.execute.before/after from EVENT_MAP; add as top-level
  plugin object properties with correct async signatures
- Root README: agent list + config table with Copilot CLI, Muse Code,
  OpenCode (Mac-only), Amp (Mac-only)
- windows/README: Mac-only plugin installer note with workaround path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Louis-CFM added a commit that referenced this pull request Oct 6, 2026
* feat: add GitHub Copilot CLI, Muse Code, OpenCode, Amp agents (#262)

- agent_copilot: writes ~/.copilot/hooks/coucou.json with camelCase
  Claude Code events; fail-closed — relay outputs
  {"permissionDecision":"ask"} on timeout so Copilot re-prompts
  instead of denying
- agent_muse: merges into ~/.config/muse/settings.json like Gemini;
  relay normalises snake_case events (pre_tool_use → PreToolUse, etc.)
- agent_opencode: writes ~/.config/opencode/plugins/coucou.js (JS
  plugin, fire-and-forget, never blocks OpenCode)
- agent_amp: writes ~/.config/amp/plugins/coucou.ts (TS plugin,
  display only, never blocks Amp)
- PillCatalog: agent_copilot #818CF8, agent_muse #38BDF8,
  agent_opencode #4ADE80, agent_amp #F59E0B — all githubOnly
- SettingsView: installer GroupBox + trigger/confirm functions for all 4
- IslandViewContent: pill-tap → terminal, isConfigured, isHooks, hints
- Python relay (both GitHub and App Store builds): normalize_event adds
  Muse snake_case mappings; fire-and-forget outputs {} for muse like
  gemini/antigravity; Copilot PermissionRequest timeout → ask JSON
- docs/AGENTS.md: entries for all 4 new agents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Weekly recap: local history store + Spotify Wrapped-style card + share image

Adds a full weekly recap feature to Coucou Mac:

- RecapStore: @mainactor singleton that persists session history to
  ~/Library/Application Support/NotchBuddy/recap.json. Records turns
  (pill, project, start/end, files ±, commands, questions) and
  permission decisions. 12-week retention. Always active, independent
  of iPhone sync.

- WeeklyRecapCardView: notch card (.recap IslandView) with time,
  sessions, files and commands stats + "Share image" / "OK" buttons.

- RecapShareImageView: 1080×1920 SwiftUI view rendered with
  ImageRenderer. Shows Coucou branding, primary time stat, secondary
  stats, and badges (top agent, top project, busiest day, longest
  session). RecapSharePanel opens an NSPanel with copy/save/share.

- IslandTypes: .recap case added, ViewLayout matches .finished.

- HookServer: RecapStore calls on UserPromptSubmit, PreToolUse,
  PostToolUse (after buildFileDiff), Stop, SessionEnd, and
  sendApprovalDecision.

- AppState: recapEnabled + recapHideProjects (UserDefaults-backed).

- SettingsView: "Weekly recap" GroupBox in General with enable toggle,
  privacy toggle and clear button.

- AppDelegate: "Weekly recap" menu item + Monday ≥ 8am trigger after
  the greeting (shows once per week if there is activity).

- scripts/test-weekly-recap.swift: fills recap.json with a fake week
  of activity for manual testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat/more-agents: fix Copilot, Muse, OpenCode and Amp end-to-end

Copilot CLI:
- Hook format: camelCase event keys, bash/timeoutSec, event name as
  positional arg, version:1 at root
- Uninstall deletes the file entirely instead of leaving {}
- copilotHooksInstalled() checks "bash" key
- processPermissionRequest shows real Allow/Deny card for copilot agent;
  relay outputs {"permissionDecision":"allow|deny"} directly

Muse Code:
- Hook format: PascalCase event keys (was snake_case)
- Add PermissionRequest hook (120 s)
- Add schema_version:1 when creating a new settings file
- processPermissionRequest shows real Allow/Deny card for muse agent;
  relay outputs {"permissionDecision":"allow|deny"} directly

OpenCode plugin:
- Rewritten to use CoucouPlugin = async (ctx) => ({ event: async }) API
- Maps OpenCode event types to canonical Coucou names in the plugin itself
- Error handlers on spawn and stdin

Amp plugin:
- Rewritten to use amp.on('event-name', handler) API
- tool.call handler returns { action: 'allow' } (required by Amp)
- Per-event handlers for SessionStart/UserPromptSubmit/PreToolUse/
  PostToolUse/Stop with correct session_id forwarding
- Error handlers on spawn and stdin

Python relays (both GitHub and App Store versions):
- normalize_event: add sessionStart, userPromptSubmitted, agentStop,
  notification, stop
- normalize_tool_fields: add toolName->tool_name, toolArgs->tool_input,
  workdir->cwd (Copilot CLI fields)
- PermissionRequest: copilot/muse output {"permissionDecision":"allow|deny"}
  instead of hookSpecificOutput; always → plain allow for copilot/muse
- Normal events: add copilot to agents that output {} on stdout
- Wrap main() in try/except at top level

Shell wrapper:
- Output {"permissionDecision":"ask"} when python3 unavailable and
  the call is copilot permissionRequest (fail-closed guarantee)

Plugin safety:
- removeOpenCodePlugin/removeAmpPlugin check "generated by Coucou"
  header before deleting
- previewOpenCodePlugin/previewAmpPlugin and their write counterparts
  now use SHA-256 fingerprint to detect file changes between preview
  and write

Docs: update AGENTS.md with correct event tables for all four agents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: weekly recap fixes — sessionId keying, interval merge, Monday triggers, share image

- RecapStore: key drafts by sessionId (not pillId) to handle concurrent sessions; merge
  overlapping intervals for accurate "time coding"; Calendar(.iso8601) for Mon–Sun weeks;
  corrupt-file rename on load; pruneStale() drops drafts open > 2 h; clearHistory() always
  active; recordQuestionAnswered keyed by sessionId
- HookServer: update all RecapStore call sites to pass sessionId; add stop() in StopFailure
  and Interrupt; post .checkMondayRecap on SessionStart + UserPromptSubmit
- IslandWindowController: declare .checkMondayRecap notification name
- AppDelegate: checkMondayRecap() now internal, uses .iso8601 calendar, guards against
  pendingApproval/pendingQuestion; add NSWorkspace didWake + .checkMondayRecap observers
- WeeklyRecapView: draw Mochi via BotEngine Canvas in share image; add +/− lines stat;
  update footer to "Coucou · github.com/Louis-CFM/coucou"
- SettingsView: remove hardcoded path, always enable "Clear history" button
- test-weekly-recap.swift: remove .iso8601 date encoding (matches RecapStore default)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: Coucou in 10 languages

Add Localizable.xcstrings (Xcode 15 String Catalog) with ~90 strings
translated into en, zh-Hans, hi, es, ar, fr, bn, pt-BR, ru, id.

- frenchStep → localizedStep; all step labels use String(localized: "step.*")
- bashVerb returns use String(localized:)
- PrimaryButton/SecondaryButton/AgentWho use Text(LocalizedStringKey(title))
- SearchingView.label, IntegrationCardView.statusLabel, FinishedView fallback
  all use String(localized:) for proper runtime lookup
- sectionTitle computed var uses String(localized:)
- SettingsSidebarRow uses Text(LocalizedStringKey(title))
- Language GroupBox in Settings → General: System + 10 languages, applies via
  AppleLanguages UserDefaults, Restart Coucou button
- AppState.appLanguage property reads current AppleLanguages preference
- AppDelegate menu items use NSLocalizedString
- IslandWindowController: .environment(\.layoutDirection, .leftToRight) keeps
  notch window LTR even for Arabic
- README invites community translation PRs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(i18n): localize all visible text — 10 languages, 240 keys

- Add Localizable.xcstrings with 240 keys across ar, bn, es, fr, hi, id, pt-BR, ru, zh-Hans
- Localize SettingsView (agents, chat, integrations, iPhone, active pills, all status messages, NSAlert)
- Localize WeeklyRecapView (notch card + 1080×1920 share image)
- Localize ClaudePlanCardView (title, gauge labels, countdown/subtitle strings)
- Localize ShortcutLogic (displayName + islandShortcuts descriptions)
- Localize ShortcutsSettingsView (GroupBox titles, reset button, conflict tags)
- Add verbatim PrimaryButton/SecondaryButton inits for dynamic content (AskUserQuestion options, filenames)
- Fix AppState.appLanguage to read from persistentDomain so System is default
- Fix broken sentence fragments (file.ready %@, mail.with %@) into single format keys
- Fix "Active pills" translations (tabs not medicine pills)
- Fix "Too many hits at once." (clicking Mochi, not HTTP requests)
- Restart via Process sleep+open+NSApp.terminate so HookServer port clears
- project.yml: knownRegions + CFBundleLocalizations (10 codes) in NotchBuddy + CoucouAppStore
- Both NotchBuddy and CoucouAppStore schemes build clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(i18n): add en source values to all 246 keys, fix format-specifier key names, restart logic

- Add "en" source value to all 228 previously missing keys (was showing raw key in English)
- Rename 5 format-specifier key names to match code usage:
  settings.slots-used → settings.slots-used %lld
  status.local.{no-models,connected,connected.plural,unreachable} + specifiers
- Add 7 new keys: file.ready %@, mail.with %@, Weekly recap, Keep a history…,
  Stored locally…, Hide project names…, Clear history (all 10 languages)
- Remove orphan key settings.startup
- Fix restart logic: wait for PID to exit, use open bundleURL.path (not open -b)
- Fix Claude is reading interpolation to use String(format:) with %@ key
- Add defaultValue to all step.* String(localized:) calls
- Patch pbxproj knownRegions with all 10 locales

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(i18n): add 29 more notch card and action button translations (10 languages)

Activity, AUTO, Clear, Close, Copy, Deployments, Diff too large, Draft, Emails,
Loading…, Main, No calls scheduled, No changes, Nothing here, Open, Open Codex/Cursor/Music/Settings/VS Code,
Overview, Payments, Recent, Refresh, Resend, Save…, Schedule, Share…, Ask a question about it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(agents): normalize_event, OpenCode hooks, Amp try/finally, Copilot/Muse Always button

- normalize_event: add preToolUse→PreToolUse, postToolUse→PostToolUse,
  permissionRequest→PermissionRequest, sessionEnd→SessionEnd (both relays)
- Wrapper shell: copilot without permissionRequest now returns {} (not nothing)
- OpenCode plugin: filter unknown event types, read from event.properties,
  use e.tool as string directly
- Amp plugin: surround forward() with try/finally so tool.call always returns
  {action:'allow'}; use typeof e.tool === 'string' check
- Move isCopilotRequest/isMuseRequest inside #if !APPSTORE guard
- Add handledNote cases for agent_copilot and agent_muse
- Reduce relay wait to 110s for Copilot/Muse
- Update docs/SPEC.md and windows/README.md with new agents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(recap): onChange reload, pruneStale 2h, Monday re-check, card lines/questions, test PNG

- WeeklyRecapCardView: recalculate summary on onChange(state.view → .recap)
- RecapStore.pruneStale: track lastEvent on each event; sessions with no
  events for 2h become completed turns instead of being dropped silently
- AppDelegate Monday recap: re-check pendingApproval/pendingQuestion inside
  the delayed block before expanding to .recap
- Notch card: show +lines/-lines and answered questions when non-zero
- test-weekly-recap.swift: generate PNG of the share card

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat/languages: localize 180+ strings across 10 languages

- Add 293-key Localizable.xcstrings with translations for all 10 locales
  (ar, bn, es, fr, hi, id, pt-BR, ru, zh-Hans) and complete en source values
- Localize IslandViewContent, SettingsView: upload status, mail flow,
  chat placeholder, music player, GitHub sections, n8n detail, auto-close
- French tutoiement (ta réponse, appuie, Demande-moi…, Recherche)
  and Russian ты form throughout
- Plural catalog entry for "status.local.connected %lld" (one/other)
- Positional args %1$@ %2$@ in status.local.unreachable for reordering
- Fix restart to pass app path as $1 argument (no shell injection)
- Remove options.knownRegions from project.yml (ignored by xcodegen)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(agents): OpenCode separate tool hooks, README agent table

- Remove tool.execute.before/after from EVENT_MAP; add as top-level
  plugin object properties with correct async signatures
- Root README: agent list + config table with Copilot CLI, Muse Code,
  OpenCode (Mac-only), Amp (Mac-only)
- windows/README: Mac-only plugin installer note with workaround path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(recap): weekKey inside async block, persist() in pruneStale, Debug menu

- Record recapLastShownWeek inside asyncAfter after expand(to: .recap)
  so the week is only marked shown if the card actually appeared
- Extract persist() helper in RecapStore; pruneStale calls persist()
  directly (avoid save→pruneStale recursion)
- #if DEBUG menu item "Render recap image" using RecapShareImageView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
JhoanG956 pushed a commit to JhoanG956/coucou that referenced this pull request Oct 7, 2026
… server

As the Mac does since 0.1.3 (Louis-CFM#156, LocalChat.swift), plus a server of the
user's own that speaks the OpenAI API (vLLM, llama.cpp…), with an optional
key kept in the credential store (openai-compatible-key).

- local_connect checks that a server answers and lists its chat models
  (embedding and rerank models are left out). An empty address means the
  usual one on this computer; Ollama's OLLAMA_HOST is honoured.
- Addresses are validated and cleaned: http or https only, no
  user:password@, trailing /v1 and /api dropped, and the loopback names
  pinned to 127.0.0.1 (on Windows "localhost" can resolve to ::1 first while
  Ollama listens on IPv4 only). Requests to this computer skip any proxy.
- The answer streams: each step goes to the island as a chat-delta event
  with the text visible so far, at most 15 times a second. <think> blocks
  stay hidden while open and are dropped from the finished answer;
  reasoning-only deltas are ignored and an error event ends the turn.
- Every read is bounded: model lists and error bodies, one event line
  (1 MB) and the whole streamed answer (4 MB).
- A dropped text file goes along inline, 24 000 characters at most, read
  without loading more of the file than that; images and PDFs by name only.
- Settings: ollamaUrl, lmstudioUrl, customUrl (empty: not connected).

Based on Louis-CFM#173 by Justin Minkmar (local_chat.rs), re-applied on main
without the branches it was stacked on; ideas from Louis-CFM#262 by bagpotato94
(loopback pinning, no proxy for loopback, OLLAMA_HOST).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copy link
Copy Markdown
Owner

Thank you! Local chat landed in Coucou for Windows 0.2.0 (https://github.com/Louis-CFM/coucou/releases/tag/windows-v0.2.0) through the OpenAI-compatible design from #173, which also covers LM Studio and custom servers. Your loopback handling (127.0.0.1, no_proxy, OLLAMA_HOST) was taken into it. Details in #307.


Generated by Claude Code

@Louis-CFM Louis-CFM closed this Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants