Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`omni-dev drive sheets info` / `drive sheets read` — reading the *cells* of a Google Sheet** ([#1589](https://github.com/rust-works/omni-dev/issues/1589), [ADR-0073](docs/adrs/adr-0073.md)): the Drive API cannot do this at all — it treats a Sheet as an opaque native document with no notion of a range, a row or a cell, which is why `drive read --content` on a Sheet exports **the first sheet only** (Drive's export API has no multi-sheet CSV format). `drive sheets read` closes that gap: with no `--range`/`--sheet` it reads every tab (one `spreadsheets.get` for the titles, then `values.batchGet`), and narrows to a range or a named tab on request. Needs no new login flag — the Sheets API accepts the `drive.readonly` scope every account already has. `-o table` (the default) emits CSV, since a grid of cells is what a spreadsheet range *is*; multi-sheet output separates each block with a `# <title>` line. Two rendering rules are deliberate and documented: CSV **pads** rows to the widest row (the API truncates trailing empty cells, so raw rows are ragged and a ragged CSV is malformed) while `-o json`/`-o yaml` preserve the raggedness as the truthful shape; and cell values are emitted **verbatim** rather than stripped of control characters, because they are content — a multi-line cell survives intact as a quoted CSV field — while sheet *titles*, rendered as chrome, are sanitised. `--render formatted|unformatted|formula` selects locale-formatted strings, raw typed numbers, or formula text. Sheet titles are always quoted internally, so spaces, apostrophes and `!` need no special handling and a sheet literally titled `A1` is unambiguous; A1 *grammar* is deliberately left to the server, so unbounded forms (`A:A`, `1:2`, a bare defined name) pass through rather than being rejected by a local guess. Writing cells (`sheets write`/`append`/`clear`/`create`) is the next phase.
- Under the hood this adds the integration's **first second-host Google API**. Sheets lives on `sheets.googleapis.com`, which no base-URL tweak to `DriveClient` (built around `/drive/v3/...` on `www.googleapis.com`) could reach, so the host-agnostic half of that client — OAuth session, one-shot 401 refresh-and-retry, quota backoff, request logging, error-envelope parsing — moved into a shared `GoogleApiClient` that both clients wrap. They stay **distinct types** so a Sheets-hosted client cannot be handed to `FilesApi` and silently issue `/drive/v3/files` against the wrong host, and they **share one OAuth session**, so a command touching both APIs refreshes the token once rather than twice. `DriveClient`'s public API is unchanged.
- Fixes a latent bug the second host exposed: Drive v3 reports error codes at `error.errors[0].reason`, but newer Google services — Sheets v4 among them — return the `google.rpc` envelope with `error.status` and **no `errors[]` array at all**. Reading only the legacy shape yields no reason code, which silently disables both the `--write-file`/`--write-full` scope hint and the quota retry — neither fails loudly, they simply never fire. Both envelope shapes are now understood.
- **`drive_sheets_info` / `drive_sheets_read` MCP tools** ([#1614](https://github.com/rust-works/omni-dev/issues/1614)): the read-only half of the MCP surface ADR-0073 §12 deferred when `drive sheets` shipped CLI-first ([#1589](https://github.com/rust-works/omni-dev/issues/1589) above). Each is a thin wrapper over the same engine functions the CLI calls (`drive::sheets::api::SheetsApi::get_spreadsheet`, `drive::sheets::read::read`), returning YAML rather than the CLI's default CSV rendering — mirrors `drive_file_read`'s shape one-for-one, including the optional `account` parameter. Like the rest of the Drive read surface, these consult no permission gate and write no request-log record (a known, deliberate gap — ADR-0071 §11, ADR-0073 §5), not something specific to Sheets. The write verbs (`write`/`append`/`clear`) still have no MCP equivalent — deferred to a follow-up, since a model-driven caller for a data-mutating surface (particularly `sheets clear`) needs its own thought around a dry-run equivalent and how a folder-permission refusal is surfaced. See [docs/mcp.md](docs/mcp.md#drive-7-tools).
- **`omni-dev worktrees ui` remembers your layout, labels sessions readably, and clears all row colours (Phase 5 — completes issue [#1585](https://github.com/rust-works/omni-dev/issues/1585))**: the pane layout now survives a restart. Quitting records the shape of the workspace — how many pane groups, their relative sizes, which worktree each tab was opened in and which was active — to `~/.omni-dev/worktrees-ui-layout.yaml` (`0600` under a `0700` directory, atomic rename, beside the existing row-colour store), and the next run reopens it. What is restored is the *shape*, never a live process: PTYs are hosted in this process ([ADR-0072](docs/adrs/adr-0072.md) §2), so each restored tab is a **new** child in the same place. Restoration is best-effort and silent by design — a worktree deleted since, a shell that no longer exists, a file from a newer version: each is dropped rather than turned into a startup error, and a tab that will not spawn costs you that tab rather than the session (the count is reported in the status bar). Quitting with no tabs removes the file, so the next run starts clean. The Move/Copy Claude Session picker now labels each session with the **first user prompt from its transcript** instead of a bare UUID, which is what the VS Code companion's `readPreview` does and for the same reason: a UUID tells you nothing about which session you are about to move. The reader is deliberately tolerant of Claude's transcript schema — it scans only the head of the file for the first user message and gives up quietly on anything unexpected, rather than parsing a structure that is not ours to depend on — and no transcript content is logged or persisted. Finally `alt-⇧c` clears every row colour at once, retiring the last `#[allow(dead_code)]` placeholder in the module (`HubCommand::ClearAllRowColors` has been wired since Phase 1 with no key to reach it).
- **`omni-dev worktrees ui` gains the batch git actions (Phase 4d — completes Phase 4)** ([#1585](https://github.com/rust-works/omni-dev/issues/1585), [ADR-0072](docs/adrs/adr-0072.md) §9): **Rebase on main**, **Push (force-with-lease)** and **Add to Merge Queue** join the action menu's `4_git` group, completing the VS Code tree view's parity surface. Each drives the **daemon's** `rebase`/`push`/`merge-queue` op — the same op the companion extension calls, running the same engine — never the CLI's local-only path, and each is two-phase like `close`: the daemon plans, the plan is rendered verbatim into the confirm modal, and phase 2 re-plans from scratch so what was confirmed is advisory rather than a replayable token. The plans are specific: a rebase names each worktree, its branch and how far behind it is, and warns that a conflict leaves that worktree mid-rebase to resolve in place; a push separates plain fast-forwards from leased forces; a merge-queue check lists the eligible PRs and why each skipped one was skipped. **No force option exists anywhere in the UI** — every force the daemon issues is `--force-with-lease --force-if-includes`, a repository's default branch is never force-pushed, and a refused lease is reported as such with the fix named (fetch and rebase), since there is no harder push to reach for. A guard test greps the UI's action, client and wire modules so a `force` field cannot be added to a request without failing the build; the client also sends no `remote` or `onto` override, each of which would be a way around a guard that lives in the daemon.
- **`omni-dev worktrees ui` gains the glyph table, badge columns, scrollback search and a command palette (Phase 4c)** ([#1585](https://github.com/rust-works/omni-dev/issues/1585)): every row cue now comes from one table (`src/cli/worktrees/ui/glyph.rs`) with a unicode and an ASCII form, and **every form is asserted to be exactly one cell wide in both modes** — a glyph that measures two cells misaligns every column after it on that row alone, so the East-Asian *Ambiguous* characters the issue's mockups used (`✔ ⟳ ⚠ ▌ ⇊ ↑ ↓`) are deliberately excluded in favour of confirmed-Narrow substitutes. The new `--ascii` flag (or `OMNI_DEV_UI_ASCII=1`), resolved once at startup, switches the whole table over for terminals without a unicode font. Each worktree row gains a two-cell badge column — PR-check state and Claude-session state, one cell each — which is where a TUI is simply better than the VS Code companion: the extension negotiates two characters of `FileDecoration` between its two providers ([#1406](https://github.com/rust-works/omni-dev/issues/1406)), while a TUI owns every cell and gives each dimension its own. Long branch names are elided in the middle and padded to a fixed column, so paths and fields line up down the pane. `alt-f` searches the focused tab's scrollback case-insensitively and scrolls the match into view, stepping further back on each `Enter`; `:` opens a command palette that filters the same action set the menu uses, and opens the menu at the match rather than firing it, so destructive actions keep their confirm. The tree also now reports which rows are actually on screen (`SetVisibleRows`), so the per-worktree ahead/behind fetch — the dominant cost ([#1306](https://github.com/rust-works/omni-dev/issues/1306)) — covers the visible rows instead of every row in the snapshot, and is sent only when the set changes rather than every frame.
Expand Down
18 changes: 11 additions & 7 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,16 @@ valid names.
| `gmail_label_list` | List labels with unread/total counts. Label add/remove is CLI-only in this release |
| `gmail_account_list` | List configured Gmail accounts — name, cached email, scope, default. Never a secret |

### Drive (5 tools)
### Drive (7 tools)

Read-only access (search, dedupe, file metadata/content) via OAuth2, mirroring
the Gmail tool surface one-for-one. Authentication uses `DRIVE_CLIENT_ID` +
`DRIVE_CLIENT_SECRET` + a refresh token stored by `omni-dev drive auth login`.
`rename`/`move` (the CLI's write operations, gated behind the opt-in
`drive.metadata` scope) have no MCP equivalent. See [Drive Guide](drive.md)
and [ADR-0069](adrs/adr-0069.md).
Read-only access (search, dedupe, file metadata/content, Sheets info/read)
via OAuth2, mirroring the Gmail tool surface one-for-one. Authentication uses
`DRIVE_CLIENT_ID` + `DRIVE_CLIENT_SECRET` + a refresh token stored by
`omni-dev drive auth login`. `rename`/`move` (the CLI's write operations,
gated behind the opt-in `drive.metadata` scope) have no MCP equivalent, and
neither do the Sheets write verbs (`write`/`append`/`clear`) — deferred
pending their own design ([#1614](https://github.com/rust-works/omni-dev/issues/1614)).
See [Drive Guide](drive.md) and [ADR-0069](adrs/adr-0069.md).

Every tool below (except `drive_account_list`) takes an optional `account`
parameter selecting a named Drive account configured via `drive account`
Expand All @@ -269,6 +271,8 @@ parameter selecting a named Drive account configured via `drive account`
| `drive_search` | Search files (Drive query syntax); returns full metadata per hit, including checksums when present |
| `drive_dedupe` | Find files sharing the same content hash within a query's results, grouped by `md5Checksum` |
| `drive_file_read` | Read a file's metadata (default) or content (`format: "content"`); `output_file` writes binary content to disk; `verify: true` checks the fetched SHA-256 against Drive's reported checksum |
| `drive_sheets_info` | A spreadsheet's title and the sheets (tabs) it contains — id, title, index, hidden flag, grid dimensions |
| `drive_sheets_read` | Read cell values from one range, one sheet, or the whole workbook (capped at 200 sheets); `render` selects formatted/unformatted/formula values |
| `drive_account_list` | List configured Drive accounts — name, cached email, scope, default. Never a secret |

### AI / Config (5 tools)
Expand Down
6 changes: 4 additions & 2 deletions src/cli/drive/sheets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ pub struct SheetsCommand {
/// Sheets subcommands.
#[derive(Subcommand)]
pub enum SheetsSubcommands {
/// Shows a spreadsheet's title and the sheets (tabs) it contains.
/// Shows a spreadsheet's title and the sheets (tabs) it contains
/// (mirrors the `drive_sheets_info` MCP tool).
Info(info::InfoCommand),
/// Reads cell values from one range, or from every sheet.
/// Reads cell values from one range, or from every sheet (mirrors the
/// `drive_sheets_read` MCP tool).
Read(read::ReadCommand),
/// Overwrites the cells of a range, gated by the folder
/// write-permission rules (issue #1589). Requires the `drive.file` or
Expand Down
Loading
Loading