jpskill.com
📦 その他 コミュニティ

openless-voice-input

OpenLess open-source voice input for macOS & Windows — press a hotkey, speak, get AI-polished text inserted at your cursor in any app.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o openless-voice-input.zip https://jpskill.com/download/23050.zip && unzip -o openless-voice-input.zip && rm openless-voice-input.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/23050.zip -OutFile "$d\openless-voice-input.zip"; Expand-Archive "$d\openless-voice-input.zip" -DestinationPath $d -Force; ri "$d\openless-voice-input.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して openless-voice-input.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → openless-voice-input フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1
📖 Claude が読む原文 SKILL.md(中身を展開)

この本文は AI(Claude)が読むための原文(英語または中国語)です。日本語訳は順次追加中。

OpenLess Voice Input

Skill by ara.so — Daily 2026 Skills collection.

OpenLess is a cross-platform (macOS 12+, Windows 10+) voice-input app built with Tauri 2 + Rust + React/TypeScript. Press a global hotkey, speak, release — the app records audio, transcribes via Volcengine streaming ASR or Whisper, polishes the transcript with an LLM, and inserts the result at the active cursor in any app. It is a fully open-source alternative to Typeless, Wispr Flow, and Superwhisper.


Installation (End Users)

macOS

  1. Download OpenLess_<version>_aarch64.dmg from Releases.
  2. Open the DMG, drag OpenLess.app to /Applications.
  3. Launch, grant Microphone and Accessibility permissions when prompted.
  4. Quit and reopen — Accessibility only takes effect after a restart.
  5. Open Settings → fill in ASR + LLM credentials.

Windows

  1. Download OpenLess_<version>_x64-setup.exe from Releases.
  2. Run the installer.
  3. Grant Microphone access when prompted.
  4. Open Settings → Permissions → verify the global hotkey listener is active.
  5. Fill in ASR + LLM credentials in Settings.

Build from Source (Developers)

Prerequisites

  • Node.js 18+, npm
  • Rust 1.77+ (rustup)
  • Tauri CLI v2 (cargo install tauri-cli --version "^2")
  • macOS: Xcode Command Line Tools
  • Windows: MSVC build tools or MinGW (see openless-all/README.md)

Steps

git clone https://github.com/appergb/openless.git
cd openless/openless-all/app

npm ci

# Development (Vite at :1420 + Tauri shell with hot reload)
npm run tauri dev

# Production build — macOS (signs, installs, resets TCC)
./scripts/build-mac.sh

# Build only, skip install step
INSTALL=0 ./scripts/build-mac.sh

# Rust type-check without full compile
cargo check --manifest-path src-tauri/Cargo.toml

# Frontend TypeScript type-check
npm run build

Log locations

  • macOS: ~/Library/Logs/OpenLess/openless.log
  • Windows: %LOCALAPPDATA%\OpenLess\Logs\openless.log

Configuration & Credentials

Credentials are stored in the platform Keychain (service = com.openless.app). A plaintext fallback is written to ~/.openless/credentials.json (mode 0600) when Keychain is unavailable in dev mode.

Never commit API keys. Reference them via environment variables or enter them in the Settings UI.

Required credentials

Key Where to get it
Volcengine ASR APP ID Volcengine console → Speech Recognition
Volcengine ASR Access Token Same console
Volcengine ASR Resource ID Same console
Ark/LLM API Key Volcengine Ark console or any OpenAI-compatible provider
Ark Model ID e.g. ep-XXXXXXXX or a DeepSeek model ID
Ark Endpoint Default: https://ark.cn-beijing.volces.com/api/v3/chat/completions

Setting credentials programmatically (Rust, for tests/CI)

// src-tauri/src/persistence.rs pattern
use crate::types::Credentials;

let creds = Credentials {
    asr_app_id: std::env::var("OPENLESS_ASR_APP_ID").unwrap(),
    asr_token: std::env::var("OPENLESS_ASR_TOKEN").unwrap(),
    asr_resource_id: std::env::var("OPENLESS_ASR_RESOURCE_ID").unwrap(),
    ark_api_key: std::env::var("OPENLESS_ARK_API_KEY").unwrap(),
    ark_model_id: std::env::var("OPENLESS_ARK_MODEL_ID").unwrap(),
    ark_endpoint: std::env::var("OPENLESS_ARK_ENDPOINT")
        .unwrap_or_else(|_| "https://ark.cn-beijing.volces.com/api/v3/chat/completions".to_string()),
};
// Pass to coordinator via Tauri state

Architecture Overview

openless-all/app/
├── src/                    # React/TypeScript frontend
│   ├── pages/
│   │   ├── _atoms.tsx      # Recoil global state atoms
│   │   ├── Home.tsx
│   │   ├── History.tsx
│   │   ├── Dictionary.tsx
│   │   └── Settings.tsx
│   └── lib/
│       └── ipc.ts          # All Tauri invoke() calls (IPC surface)
└── src-tauri/src/          # Rust backend
    ├── types.rs            # Value types: DictationSession, PolishMode, errors
    ├── hotkey.rs           # CGEventTap (macOS) / WH_KEYBOARD_LL (Windows)
    ├── recorder.rs         # Mic → 16 kHz mono Int16 PCM + RMS callback
    ├── asr/                # Volcengine WebSocket ASR + Whisper HTTP
    ├── polish.rs           # OpenAI-compatible chat-completions
    ├── insertion.rs        # AX focused-element → clipboard+paste → copy fallback
    ├── persistence.rs      # History / prefs / vocab JSON + Keychain
    ├── permissions.rs      # TCC checks
    ├── coordinator.rs      # State machine: Idle→Starting→Listening→Processing
    └── commands.rs         # Tauri #[tauri::command] IPC surface

Dictation pipeline

hotkey DOWN
  → coordinator: Idle → Starting → Listening
  → recorder.start() + asr.open_session()
  → [audio frames streamed to ASR via WebSocket]

hotkey UP
  → recorder.stop() + asr.send_last_frame()
  → coordinator: Listening → Processing
  → polish(transcript, mode) → LLM API call
  → insertion.insert_at_cursor(polished_text)
      ├─ AX focused element write (macOS Accessibility API)
      ├─ clipboard + Cmd+V / Ctrl+V paste
      └─ copy-only fallback (text in clipboard, user pastes manually)
  → history.save(session)
  → coordinator: Processing → Idle

Esc cancels at any phase including polish/insert.


Polish Modes

Mode Tauri enum Behaviour
Raw PolishMode::Raw Transcript verbatim, no LLM call
Light PolishMode::Light Remove filler words, fix punctuation
Structured PolishMode::Structured AI-prompt mode — reshapes speech into a structured, context-rich prompt
Formal PolishMode::Formal Formal prose, fixes grammar, organises paragraphs

Structured mode — what it does

Input (spoken): "uh so I want ChatGPT to write me a SQL query from the orders table get last month's orders group by customer sort by amount desc top ten"

Output inserted at cursor:

Please write a SQL query that:

- Pulls orders from last month from the `orders` table.
- Groups by customer.
- Sorts by total amount, descending.
- Returns the top 10 rows only.

Key invariant: the LLM only reshapes your text. It does not answer questions or execute commands. If you say "what features are missing?", the output is What features are missing? — not a feature list.


IPC Surface (Frontend → Backend)

All calls go through src/lib/ipc.ts using Tauri's invoke().

// src/lib/ipc.ts — representative subset

import { invoke } from "@tauri-apps/api/core";
import type { DictationSession, PolishMode, HotkeyBinding } from "./types";

// Save credentials (written to Keychain)
export async function saveCredentials(creds: {
  asrAppId: string;
  asrToken: string;
  asrResourceId: string;
  arkApiKey: string;
  arkModelId: string;
  arkEndpoint: string;
}): Promise<void> {
  return invoke("save_credentials", { creds });
}

// Load saved credentials (for Settings UI pre-fill)
export async function loadCredentials(): Promise<typeof creds | null> {
  return invoke("load_credentials");
}

// Get dictation history
export async function getHistory(): Promise<DictationSession[]> {
  return invoke("get_history");
}

// Set active polish mode
export async function setPolishMode(mode: PolishMode): Promise<void> {
  return invoke("set_polish_mode", { mode });
}

// Update hotkey binding
export async function setHotkey(binding: HotkeyBinding): Promise<void> {
  return invoke("set_hotkey", { binding });
}

// Check platform permissions
export async function checkPermissions(): Promise<{
  microphone: boolean;
  accessibility: boolean;
}> {
  return invoke("check_permissions");
}

// Add a vocabulary/dictionary entry
export async function addVocabEntry(entry: {
  word: string;
  category: string;
  notes: string;
}): Promise<void> {
  return invoke("add_vocab_entry", { entry });
}

Recoil State Atoms

// src/pages/_atoms.tsx — key atoms

import { atom, selector } from "recoil";
import type { DictationSession, PolishMode } from "../lib/types";

export const polishModeAtom = atom<PolishMode>({
  key: "polishMode",
  default: "Structured",
});

export const historyAtom = atom<DictationSession[]>({
  key: "history",
  default: [],
});

export const recordingStateAtom = atom<
  "idle" | "starting" | "listening" | "processing"
>({
  key: "recordingState",
  default: "idle",
});

export const rmsLevelAtom = atom<number>({
  key: "rmsLevel",
  default: 0,
});

Adding a Custom Polish Mode (Rust)

// src-tauri/src/types.rs
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum PolishMode {
    Raw,
    Light,
    Structured,
    Formal,
    // Add your mode:
    Technical,
}

// src-tauri/src/polish.rs
fn build_system_prompt(mode: &PolishMode) -> &'static str {
    match mode {
        PolishMode::Raw => "",
        PolishMode::Light => LIGHT_PROMPT,
        PolishMode::Structured => STRUCTURED_PROMPT,
        PolishMode::Formal => FORMAL_PROMPT,
        PolishMode::Technical => {
            "You are a technical writer. Convert the spoken transcript into \
             precise technical documentation prose. Use correct terminology. \
             Do not answer questions — output the cleaned text only, no preamble."
        }
    }
}

Dictionary / Vocabulary

Dictionary entries are sent as Volcengine ASR context.hotwords (improving transcription accuracy) and injected into the polish prompt (the LLM applies context-aware substitution).

Adding entries via UI

Settings → Dictionary tab → New button → fill Word, Category, Notes → Save.

Adding entries via IPC (programmatically)

import { addVocabEntry } from "../lib/ipc";

await addVocabEntry({
  word: "OpenLess",
  category: "product",
  notes: "Open-source voice input app, not 'open list' or 'open less'",
});

How hotword injection works (Rust)

// src-tauri/src/asr/volcengine.rs (simplified)
async fn open_session(
    &self,
    vocab: &[VocabEntry],
    config: &AsrConfig,
) -> Result<AsrSession> {
    let hotwords: Vec<String> = vocab
        .iter()
        .filter(|e| e.enabled)
        .map(|e| e.word.clone())
        .collect();

    let payload = serde_json::json!({
        "app": { "appid": config.app_id, "token": config.token },
        "audio": { "format": "pcm", "sample_rate": 16000, "bits": 16, "channel": 1 },
        "request": {
            "model_name": config.resource_id,
            "context": { "hotwords": hotwords }
        }
    });
    // open WebSocket, send payload ...
}

Hotkey Configuration

Recording modes

  • Push-to-talk: hold hotkey → recording → release → process
  • Toggle: press once to start, press again to stop

Changing the hotkey (TypeScript)

import { setHotkey } from "../lib/ipc";
import type { HotkeyBinding } from "../lib/types";

// Example: Right Option key, push-to-talk mode
const binding: HotkeyBinding = {
  key: "AltRight",
  modifiers: [],
  mode: "PushToTalk",
};
await setHotkey(binding);

Platform hotkey internals (Rust)

// src-tauri/src/hotkey.rs (macOS path — CGEventTap)
#[cfg(target_os = "macos")]
pub fn register_global_hotkey(
    binding: HotkeyBinding,
    on_press: impl Fn() + Send + 'static,
    on_release: impl Fn() + Send + 'static,
) -> Result<HotkeyHandle> {
    // Uses CGEventTap — requires Accessibility permission
    // Spawns a dedicated CFRunLoop thread
    // Sends Tauri events: "hotkey-press" / "hotkey-release"
    todo!("see hotkey.rs for full implementation")
}

#[cfg(target_os = "windows")]
pub fn register_global_hotkey(/* ... */) -> Result<HotkeyHandle> {
    // Uses SetWindowsHookExA(WH_KEYBOARD_LL, ...)
    todo!("see hotkey.rs for full implementation")
}

Coordinator State Machine

// src-tauri/src/coordinator.rs
#[derive(Debug, Clone, PartialEq)]
pub enum RecordingState {
    Idle,
    Starting,
    Listening,
    Processing,
}

// Transitions:
// Idle      --[hotkey press]-->  Starting
// Starting  --[ASR ready]---->  Listening
// Listening --[hotkey release]-> Processing
// Listening --[Esc]----------->  Idle        (cancel)
// Processing--[done / Esc]---->  Idle

Listen for state changes on the frontend:

import { listen } from "@tauri-apps/api/event";
import { useSetRecoilState } from "recoil";
import { recordingStateAtom } from "./_atoms";

const setRecordingState = useSetRecoilState(recordingStateAtom);

useEffect(() => {
  const unlisten = listen<string>("recording-state-changed", (event) => {
    setRecordingState(event.payload as any);
  });
  return () => { unlisten.then(fn => fn()); };
}, []);

Troubleshooting

Text not being inserted at cursor

  1. macOS: Accessibility permission not granted or not restarted after granting.
    • System Settings → Privacy & Security → Accessibility → enable OpenLess → quit and reopen.
  2. Windows: Some apps (e.g. UWP, sandboxed Electron) block synthetic input — OpenLess falls back to clipboard copy. Check the tray notification.
  3. Verify insertion fallback chain: AX write → clipboard+paste → copy-only.

Hotkey not responding

  • macOS: Ensure Accessibility permission is active. CGEventTap silently fails without it.
  • Windows: Another app may have grabbed the same key combo. Change the hotkey in Settings.
  • Only one OpenLess instance can run (single-instance lock). Check Activity Monitor / Task Manager.

ASR returns empty / garbled transcript

  • Verify Volcengine ASR credentials: APP ID, Access Token, Resource ID all correct.
  • Check microphone sample rate — OpenLess records at 16 kHz mono Int16 PCM. Some USB mics need explicit configuration.
  • Check openless.log for WebSocket errors.

LLM polish not working / timeout

  • Verify Ark API Key and Model ID.
  • Confirm endpoint is reachable: curl -s $OPENLESS_ARK_ENDPOINT (should return 401, not timeout).
  • DeepSeek / OpenAI-compatible endpoints work — set the endpoint URL in Settings.

Build fails on macOS: codesign error

# Skip signing for local dev build
CODESIGN_IDENTITY="" INSTALL=0 ./scripts/build-mac.sh

Cargo check errors after pulling

cd openless-all/app
cargo check --manifest-path src-tauri/Cargo.toml
# Look for changed feature flags in Cargo.toml
# Run `cargo update` if lock file is stale

Key Files Reference

File Purpose
src-tauri/src/coordinator.rs Master state machine — start here to understand the flow
src-tauri/src/commands.rs All #[tauri::command] IPC endpoints
src-tauri/src/polish.rs LLM prompt construction and API calls
src-tauri/src/asr/ Volcengine WebSocket ASR + Whisper HTTP
src-tauri/src/insertion.rs Cursor insertion + clipboard fallback
src/lib/ipc.ts Frontend IPC surface — all invoke() calls
src/pages/_atoms.tsx Recoil global state
CLAUDE.md Module-wiring invariants for AI coding agents
docs/polish-reference-corpus.md Polish example corpus design
USAGE.md Full end-user walkthrough