Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ jobs:
run: |
$ErrorActionPreference = 'Stop'
$files = @(
'src/plaud_tools/scripts/update.ps1',
'src/plaud_tools/tray/scripts/update.ps1',
'scripts/install.ps1'
)
$failed = $false
Expand Down Expand Up @@ -210,7 +210,7 @@ jobs:
# Wave 2 / C4 — frozen psutil import proof.
#
# Build the tray bundle and run PlaudTools.exe --diagnose-enum. This flag
# imports plaud_tools.mcp_lifecycle (exercising all hiddenimports) and
# imports plaud_tools.cli.process_probe (exercising all hiddenimports) and
# prints "enumerator=psutil" when psutil is bundled correctly, then exits 0
# WITHOUT starting the tkinter/pystray GUI (critical: no display on CI).
#
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- **Internal: reorganized `src/plaud_tools/` by surface instead of a flat file list.**
New packages `core/` (shared client/session/auth/transport/etc., used by all
three surfaces), `cli/`, `mcp_pt/` (named to avoid shadowing the `mcp` SDK
package), and consolidated `assets/completions/scripts` PS1-template data
into `tray/` where they're actually consumed. `mcp_lifecycle.py` renamed to
`cli/process_probe.py` (its name had outlived a prior dead-code removal).
No behavior, CLI/MCP surface, or entry-point change — pure module reorg for
maintainability.

## [0.7.1] - 2026-07-07

Follow-up patch that formally closes the last open items from the #138–#164
Expand Down
10 changes: 5 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ This file provides working guidance for agents operating in this repository.

## Current State

The Python rewrite is complete. The active code lives under `src/plaud_tools/`:
The Python rewrite is complete. The active code lives under `src/plaud_tools/`, grouped by which surface owns each concern:

- `client.py` — Plaud domain/client layer (auth, session, all API flows)
- `cli.py` — Python CLI (`plaud-tools` / `pt` entry points)
- `mcp.py` — MCP handler functions (11 tools: browse_recordings, get_recording, mutate_recording, delete_recording, edit_transcript, upload_recording, process_recording, list_folders, merge_recordings, edit_summary, mutate_folder)
- `server.py` — Python MCP server process (`plaud-mcp` entry point, stdio transport)
- `core/` — shared Plaud domain/client layer (client, session, auth, transport, models, errors, appdata, layout, transcode, query, ai_clients) used by all three surfaces below
- `cli/` — Python CLI (`plaud-tools` / `pt` entry points) plus `doctor` diagnostics
- `mcp_pt/` — MCP handler functions (11 tools: browse_recordings, get_recording, mutate_recording, delete_recording, edit_transcript, upload_recording, process_recording, list_folders, merge_recordings, edit_summary, mutate_folder) and the MCP server process (`plaud-mcp` entry point, stdio transport). Named `mcp_pt` rather than `mcp` to avoid shadowing the `mcp` SDK package these modules import.
- `tray/` — Windows tray app, updater/uninstaller, and first-run setup (`plaud-tray` entry point)

The TypeScript prior art has been removed. The `har-captures/` directory contains live Plaud API traffic captures (gitignored — local reference only).

Expand Down
10 changes: 5 additions & 5 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ against this by raising `PlaudSessionExpiredError` when the UT is within
`TOKEN_REFRESH_BUFFER_SECONDS` (24 hours) of expiry — shrunk from an earlier
3-day buffer that burned 10% of every token's life. The tray/HomeWindow
separately warn starting `TRAY_EXPIRY_WARNING_DAYS` (5 days) before expiry,
so the warning always precedes the refusal (see `session.py`).
so the warning always precedes the refusal (see `core/session.py`).

If a future Plaud API endpoint requires a workspace-scoped token, this
model needs to grow: store all three tokens, exchange via the workspace
Expand All @@ -66,7 +66,7 @@ endpoint, refresh on demand. Until then, simplicity wins.

**Install layout** — the on-disk arrangement of binaries for the *running*
plaud-tools install, derived from `sys.executable`. Distribution-channel
aware (bundle / pip / dev). Represented by `InstallLayout` in `layout.py`.
aware (bundle / pip / dev). Represented by `InstallLayout` in `core/layout.py`.
Tray uninstall, tray update, MCP-child process scoping, and AI-client wiring
all act on the *running* install, never a hardcoded canonical path. The
canonical install path (`%LOCALAPPDATA%\Programs\PlaudTools\`) is the
Expand All @@ -77,7 +77,7 @@ code.
(session storage, tray log, MCP log, events). Channel-agnostic,
platform-aware via `platformdirs`. On Windows: `%LOCALAPPDATA%\PlaudTools\`.
On macOS / Linux: per `platformdirs.user_data_dir` conventions. Lives in
`appdata.py`. All log and event files share the data directory; logs do
`core/appdata.py`. All log and event files share the data directory; logs do
not get a separate `user_log_dir` subtree (deliberate — preserves existing
Windows file locations as a no-op and keeps Mac/Linux conventions simple).

Expand All @@ -86,12 +86,12 @@ Windows file locations as a no-op and keeps Mac/Linux conventions simple).
The CLI and MCP surfaces apply different but complementary mechanisms to guard
destructive operations:

**CLI (`cli.py`)** — interactive sessions use a `--yes` / `-y` flag. Without
**CLI (`cli/cli.py`)** — interactive sessions use a `--yes` / `-y` flag. Without
the flag, a destructive subcommand (e.g. `plaud delete`) prints a confirmation
prompt and exits; with `--yes` it proceeds immediately. This is appropriate
for terminal users who can read and respond to stdout.

**MCP (`server.py` + `mcp.py`)** — the MCP server runs over stdio and cannot
**MCP (`mcp_pt/server.py` + `mcp_pt/mcp.py`)** — the MCP server runs over stdio and cannot
display interactive prompts. Safety is layered:

1. `ToolAnnotations` on every `types.Tool` entry in `_TOOLS` declare
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The default suite is offline and runs in a few seconds.

`tests/data/tool_descriptions.golden.json` is a snapshot of every MCP tool's
name, description, and `inputSchema`. If you intentionally change any tool
definition in `server.py`, regenerate it and commit the updated fixture
definition in `mcp_pt/server.py`, regenerate it and commit the updated fixture
alongside your code change:

```
Expand Down
3 changes: 2 additions & 1 deletion docs/adr/003-mcp-process-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ unexpected DLL load failure).
**CI frozen-import proof:** The `bundle-smoke` job in `.github/workflows/ci.yml`
now builds the tray spec and runs
`PlaudTools.exe --diagnose-enum`. This flag is implemented in
`scripts/plaud_tray_entry.py`; it imports `plaud_tools.mcp_lifecycle`, reports
`scripts/plaud_tray_entry.py`; it imports `plaud_tools.cli.process_probe` (renamed
from `plaud_tools.mcp_lifecycle` in the src/ package-by-surface reorg), reports
`enumerator=psutil` when psutil is importable in the frozen context, and exits 0
without starting the tkinter/pystray GUI (critical: CI runners have no display).
The CI step asserts the output contains `enumerator=psutil`; any regression
Expand Down
6 changes: 3 additions & 3 deletions pyinstaller/plaud-tray.spec
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ block_cipher = None
src = Path(SPECPATH).parent / 'src'

_assets = str(Path(SPECPATH).parent / 'src' / 'plaud_tools' / 'assets')
_completions = str(Path(SPECPATH).parent / 'src' / 'plaud_tools' / 'completions')
_scripts = str(Path(SPECPATH).parent / 'src' / 'plaud_tools' / 'scripts')
_completions = str(Path(SPECPATH).parent / 'src' / 'plaud_tools' / 'tray' / 'completions')
_scripts = str(Path(SPECPATH).parent / 'src' / 'plaud_tools' / 'tray' / 'scripts')
_icon = str(Path(SPECPATH).parent / 'src' / 'plaud_tools' / 'assets' / 'icon.ico')

# sv_ttk ships a Tcl theme file (sun-valley.tcl + sibling .tcl files) as
Expand Down Expand Up @@ -88,7 +88,7 @@ a = Analysis(
'keyring.backends.fail',
'keyring.core',
# ai_clients imported inside tray.background at module level
'plaud_tools.ai_clients',
'plaud_tools.core.ai_clients',
# sv_ttk applies a Tcl theme at runtime
'sv_ttk',
# pywin32 / win32 used by keyring and pystray on Windows
Expand Down
2 changes: 1 addition & 1 deletion pyinstaller/plaud.spec
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ a = Analysis(
datas=[*_plaud_metadata],
hiddenimports=[
# transcode is imported inside an if-branch; static analysis misses it
'plaud_tools.transcode',
'plaud_tools.core.transcode',
# keyring selects its backend at runtime
'keyring.backends.Windows',
'keyring.backends.fail',
Expand Down
14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ Issues = "https://github.com/massive-value/plaud-tools/issues"
Changelog = "https://github.com/massive-value/plaud-tools/blob/master/CHANGELOG.md"

[project.scripts]
plaud-tools = "plaud_tools.cli:main"
pt = "plaud_tools.cli:main"
plaud-mcp = "plaud_tools.server:main"
plaud-tools = "plaud_tools.cli.cli:main"
pt = "plaud_tools.cli.cli:main"
plaud-mcp = "plaud_tools.mcp_pt.server:main"
plaud-tray = "plaud_tools.tray.app:main"

[tool.setuptools]
Expand Down Expand Up @@ -160,10 +160,10 @@ disallow_untyped_defs = false
# package. Stricter checking here gives the most signal-to-noise benefit.
[[tool.mypy.overrides]]
module = [
"plaud_tools.session",
"plaud_tools.client",
"plaud_tools.layout",
"plaud_tools.appdata",
"plaud_tools.core.session",
"plaud_tools.core.client",
"plaud_tools.core.layout",
"plaud_tools.core.appdata",
]
disallow_untyped_defs = true
warn_return_any = true
Expand Down
2 changes: 1 addition & 1 deletion scripts/ffmpeg_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def main() -> int:

# 2. Transcode to MP3 via the real ffmpeg subprocess (path-in/path-out —
# the same function production upload flows use).
from plaud_tools.transcode import transcode_to_mp3_path
from plaud_tools.core.transcode import transcode_to_mp3_path

tmp_dir = Path(tempfile.mkdtemp(prefix="plaud-ffmpeg-smoke-"))
src_path = tmp_dir / "silence.wav"
Expand Down
2 changes: 1 addition & 1 deletion scripts/plaud_entry.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from plaud_tools.cli import main
from plaud_tools.cli.cli import main

raise SystemExit(main())
2 changes: 1 addition & 1 deletion scripts/plaud_mcp_entry.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from plaud_tools.server import main
from plaud_tools.mcp_pt.server import main

main()
10 changes: 5 additions & 5 deletions scripts/plaud_tray_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# ---------------------------------------------------------------------------
# --diagnose-enum: frozen-import proof for Wave 2 / C4 (psutil in bundle).
#
# When this flag is present the entry point imports plaud_tools.mcp_lifecycle
# When this flag is present the entry point imports plaud_tools.cli.process_probe
# and reports which process enumerator is active (psutil vs PowerShell
# fallback), then exits 0 WITHOUT importing pystray / PIL / tkinter or
# launching any GUI. This is intentional: CI runners have no display, so
Expand Down Expand Up @@ -31,12 +31,12 @@
_enumerator = "psutil" if _psutil_available else "powershell_fallback"
print(f"enumerator={_enumerator}", flush=True)

# Also import mcp_lifecycle to ensure its full import chain resolves
# Also import process_probe to ensure its full import chain resolves
# in the frozen build (exercises the hiddenimport entries in the spec).
try:
import plaud_tools.mcp_lifecycle as _ml # noqa: F401
import plaud_tools.cli.process_probe as _ml # noqa: F401
except Exception as _exc:
print(f"mcp_lifecycle import failed: {_exc}", flush=True)
print(f"process_probe import failed: {_exc}", flush=True)
sys.exit(1)

sys.exit(0)
Expand All @@ -55,7 +55,7 @@
# the tray.
if "--com-activate" not in sys.argv:
try:
from plaud_tools.session import SessionStore
from plaud_tools.core.session import SessionStore

SessionStore().prime_dpapi_shadow()
except Exception:
Expand Down
10 changes: 5 additions & 5 deletions src/plaud_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as _pkg_version

from .auth import PlaudAuth
from .client import PlaudClient, PlaudRecordingQuery
from .errors import PlaudApiError, PlaudSessionExpiredError
from .mcp import build_handlers
from .session import FileSessionStore, PlaudSession, SessionManager, SessionStore
from .core.auth import PlaudAuth
from .core.client import PlaudClient, PlaudRecordingQuery
from .core.errors import PlaudApiError, PlaudSessionExpiredError
from .core.session import FileSessionStore, PlaudSession, SessionManager, SessionStore
from .mcp_pt.mcp import build_handlers

try:
__version__ = _pkg_version("plaud-tools")
Expand Down
2 changes: 1 addition & 1 deletion src/plaud_tools/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .cli import main
from .cli.cli import main

raise SystemExit(main())
1 change: 1 addition & 0 deletions src/plaud_tools/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Python CLI (`plaud-tools` / `pt` entry points)."""
14 changes: 7 additions & 7 deletions src/plaud_tools/cli.py → src/plaud_tools/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@
from pathlib import Path
from typing import Any

from . import __version__
from .auth import PlaudAuth
from .client import PlaudClient, PlaudRecordingQuery
from .errors import PlaudApiError, PlaudSessionExpiredError
from .query import (
from .. import __version__
from ..core.auth import PlaudAuth
from ..core.client import PlaudClient, PlaudRecordingQuery
from ..core.errors import PlaudApiError, PlaudSessionExpiredError
from ..core.query import (
BROWSE_PAGE_SIZE,
collect_filtered_paged,
detail_summary_dict,
folder_dict,
parse_isoish,
summarize_recording,
)
from .session import PlaudSession, SessionManager, SessionStore
from ..core.session import PlaudSession, SessionManager, SessionStore


def build_parser() -> argparse.ArgumentParser:
Expand Down Expand Up @@ -625,7 +625,7 @@ def _handle_set_summary(args: argparse.Namespace, client: PlaudClient) -> str:


def _handle_upload(args: argparse.Namespace, client: PlaudClient) -> str:
from .transcode import upload_with_transcode
from ..core.transcode import upload_with_transcode

path = Path(args.file)
title = args.title or path.stem
Expand Down
16 changes: 8 additions & 8 deletions src/plaud_tools/doctor.py → src/plaud_tools/cli/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
from pathlib import Path
from typing import Any

from . import __version__
from . import ai_clients as _ai_clients_mod
from .ai_clients import CLIENTS
from .appdata import tray_log as _log_path
from .errors import PlaudSessionExpiredError
from .layout import InstallLayout
from .mcp_lifecycle import active_enumerator_name
from .session import SessionManager, SessionStore
from .. import __version__
from ..core import ai_clients as _ai_clients_mod
from ..core.ai_clients import CLIENTS
from ..core.appdata import tray_log as _log_path
from ..core.errors import PlaudSessionExpiredError
from ..core.layout import InstallLayout
from ..core.session import SessionManager, SessionStore
from .process_probe import active_enumerator_name

# ---------------------------------------------------------------------------
# Install-dir / executable resolution (delegated to InstallLayout)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""MCP process-enumerator introspection.
"""Process-enumerator introspection for `doctor` (formerly `mcp_lifecycle.py`).

Historically this module also contained a scoped-shutdown helper and two
PowerShell-snippet generators (ADR 003). An audit (2026-07-06) found those
Expand Down
1 change: 1 addition & 0 deletions src/plaud_tools/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Shared domain/client layer used by the cli, mcp_pt, and tray surfaces."""
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions src/plaud_tools/mcp_pt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""MCP server process and tool handlers (`plaud-mcp` entry point)."""
14 changes: 7 additions & 7 deletions src/plaud_tools/mcp.py → src/plaud_tools/mcp_pt/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
from pathlib import Path
from typing import Any

from .appdata import events_path as _events_path
from .client import PlaudClient, PlaudRecordingQuery
from .errors import PlaudApiError, PlaudSessionExpiredError
from .query import (
from ..core.appdata import events_path as _events_path
from ..core.client import PlaudClient, PlaudRecordingQuery
from ..core.errors import PlaudApiError, PlaudSessionExpiredError
from ..core.query import (
BROWSE_PAGE_SIZE,
collect_filtered_paged,
detail_summary_dict,
folder_dict,
parse_isoish,
summarize_recording,
)
from .session import SessionManager, SessionStore
from ..core.session import SessionManager, SessionStore

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -79,7 +79,7 @@ def _diagnose_session_state() -> dict[str, Any]:
"""
# Lazy import to avoid the circular import surfaced by
# ``plaud_tools/__init__.py`` re-exporting ``build_handlers`` from this module.
from . import __version__ as _app_version
from .. import __version__ as _app_version

diag: dict[str, Any] = {
"mcp_pid": os.getpid(),
Expand Down Expand Up @@ -611,7 +611,7 @@ def upload_recording(
timezone_offset: float | None = None,
) -> dict[str, Any]:
def inner(client: PlaudClient) -> dict[str, Any]:
from .transcode import upload_with_transcode
from ..core.transcode import upload_with_transcode

path = Path(file_path)
rec_title = title or path.stem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
from mcp.server.lowlevel import NotificationOptions, Server
from mcp.server.models import InitializationOptions

from . import __version__
from .appdata import mcp_log as _mcp_log_path
from .client import PlaudClient
from .. import __version__
from ..core.appdata import mcp_log as _mcp_log_path
from ..core.client import PlaudClient
from ..core.session import SessionManager, SessionStore
from .mcp import build_handlers
from .session import SessionManager, SessionStore


def _setup_mcp_logging() -> None:
Expand Down
8 changes: 4 additions & 4 deletions src/plaud_tools/tray/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
from PIL import Image

from .. import __version__ as APP_VERSION
from ..client import PlaudClient
from ..errors import PlaudSessionExpiredError
from ..session import TRAY_EXPIRY_WARNING_DAYS, PlaudSession, SessionManager, SessionStore
from ..core.client import PlaudClient
from ..core.errors import PlaudSessionExpiredError
from ..core.session import TRAY_EXPIRY_WARNING_DAYS, PlaudSession, SessionManager, SessionStore
from .background import _BackgroundMixin
from .icons import _load_icon, _load_icons
from .setup import (
Expand Down Expand Up @@ -293,7 +293,7 @@ def _open_repo(self) -> None:
self._open_url(REPO_URL)

def _open_log_folder(self) -> None:
from ..appdata import data_dir as _data_dir
from ..core.appdata import data_dir as _data_dir

log_dir = _data_dir()
log_dir.mkdir(parents=True, exist_ok=True)
Expand Down
Loading
Loading