diff --git a/AGENTS.md b/AGENTS.md index 489fd7b..e9bdaaf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,16 +9,17 @@ Canonical Go client for OnlyOffice Workspace (Projects + Calendar + CRM) and the - `request.go` — `Request`, `Query`, `Time`, `Token`, `MetaResponse`, `Permissions`. - `auth.go` — `Authenticate`, `AuthenticateContext`, `InvalidateToken`, `Auth`, token lifecycle. - `http.go` — transport + DRY response decoders (`ResponseArray`/`ResponseObject`/`postFormObject`/`putFormObject`/`deleteObject`). - - `projects.go`, `tasks.go`, `users.go`, `calendar.go`, `crm.go`, `files.go`, `mails.go`, `invoices.go` — typed / untyped domain methods. **`files.go`** — CRM opportunity upload plus **project/task Documents**. **`mails.go`** — OnlyOffice Workspace Mail. **`invoices.go`** — CRM invoices, PDF regen/cleanup, status. Association rules: [`docs/crm-associations.md`](docs/crm-associations.md). + - `projects.go`, `tasks.go`, `users.go`, `calendar.go`, `crm.go`, `files.go`, `files_webdav.go`, `files_stem.go`, `retry.go`, `mails.go`, `invoices.go` — typed / untyped domain methods. **`files.go`** — CRM opportunity upload plus **project/task Documents** (`UpdateFile`, `UploadToFolderReplacing`). **`files_webdav.go`** — Documents module by id (`ListDavFolder`, `MoveDavItems`/`CopyDavItems` with per-operation error surfacing, `ListFileOps`). **`retry.go`** — `DoRetry`: deterministic linear backoff (no jitter) on 429/502/503/504; every bulk tool routes API calls through it. **`mails.go`** — OnlyOffice Workspace Mail. **`invoices.go`** — CRM invoices, PDF regen/cleanup, status. Association rules: [`docs/crm-associations.md`](docs/crm-associations.md). - Pure stdlib + `google/go-querystring`; no UI, no dotenv. - **CLI — `cmd/oo/` as `package main`.** Cobra wrapper that loads `.env` via `godotenv` at startup. **Subject-based command tree** mirroring [`tea`](https://gitea.com/gitea/tea): - `main.go` — entry point (docstring lists the command tree). - `common.go` — `rootCmd`, `newOO`, `printTable`/`printObject`, `--output table|json` flag. - - `calendar.go`, `projects.go`, `projects_files.go`, `tasks.go`, `tasks_files.go`, `users.go`, `contacts.go`, `opportunities.go`, `cases.go`, `crm_tasks.go`, `mails.go`, `invoices.go` — one file per subject (or per subject facet), each registers in `init()`. + - `calendar.go`, `projects.go`, `projects_files.go`, `tasks.go`, `tasks_files.go`, `users.go`, `contacts.go`, `opportunities.go`, `cases.go`, `crm.go`, `crm_tasks.go`, `catalog.go`, `docs.go`, `dav.go`, `mails.go`, `invoices.go` — one file per subject (or per subject facet), each registers in `init()`. `dav.go` exposes the Documents module by id (`oo dav ls|move|copy|mkdir|rename-file|rename-folder|download|fileops`). - CLI-only deps (`spf13/cobra`, `joho/godotenv`) stay out of the library. - **TUI — `cmd/office/` as `package main`.** Bubble Tea three-pane browser (module tree, selectable list, markdown preview). Reuses `cmd/internal/bootstrap` for env/auth and the root `onlyoffice` library for all API calls. UI logic in `cmd/office/ui/`; preview/formatting in `cmd/office/preview/`; list loaders in `cmd/office/fetch/`. - **List table (`DataTable`)** — `cmd/office/ui/table*.go`. Column layout policies live in `cmd/office/model/table_layout.go` (`TableFlexLayoutFor`); cell rendering uses the bubbles/table inline pattern in `table_render.go` (`renderTableCell`, `padANSIWidth`). See `.cursor/skills/office-tui-table/SKILL.md` before changing center-pane tables. - **Shared bootstrap — `cmd/internal/bootstrap/`.** `LoadEnv()` + `NewClient(ctx)` extracted from `oo`; both binaries import it. +- **Bulk Documents tools — `cmd/ooscan/`, `cmd/pdfamount/`, `cmd/kontoblatt/`, `cmd/kontolink/`.** Single-purpose binaries (folder index, PDF amounts, Kontoblatt summary/linking). Pace requests, route API calls through `DoRetry`; usage in README. - **Personal ops tooling** (disk inventory, dossier→CRM sync, SearXNG) lives in private [`eSlider/oo-workspace`](https://git.produktor.io/eSlider/oo-workspace) (`oow`), not in this public tree. ## Rules @@ -27,7 +28,7 @@ Canonical Go client for OnlyOffice Workspace (Projects + Calendar + CRM) and the - New endpoints go into the library first; CLI commands are thin wrappers. - Prefer `ResponseObject` / `postFormObject` / `putFormObject` / `deleteObject` over hand-rolled `json.Unmarshal(responseField(...))` blocks — they exist for DRY, use them. - Domain split is by file, **not** by subpackage. Don't introduce `internal/` or `pkg/*` subpackages inside the library — it flattens the `*Client` call surface for a reason. -- CLI commands follow **subject → verb** structure (`oo `), never `oo -`. Add new commands to the existing subject file if one fits; create a new `cmd/oo/.go` for a genuinely new domain. +- CLI commands follow **subject → verb** structure (`oo `), never `oo -`. Add new commands to the existing subject file if one fits; create a new `cmd/oo/.go` for a genuinely new domain. The subject→verb tree in `cmd/oo/main.go` and the README table are documentation — update them with the code. - **Documents for agents:** prefer Markdown in git; OnlyOffice UI is weak for `.md`/`.txt`. Use `oo docs put-md` (md→docx) and `oo docs put-txt` (txt→docx, preserves line breaks). All upload paths default to **upsert** by `stem|ext` (`--replace`, default true); `--no-replace` fails on conflict; `--allow-duplicate` opts into raw OO append. `oo projects files dedupe PROJECT_ID` reports/removes duplicate stem|ext copies (`--apply`, `--cross`; includes project root folder). - Every table output goes through `printTable(headers, rows)`; every single-object through `printObject(v)`. Do not `fmt.Println` rows ad-hoc or the `--output json` flag breaks for that command. - No secrets in the repo; use `.env` (gitignored). Commit `.env.example` only. diff --git a/README.md b/README.md index ba1b0b7..50870c4 100644 --- a/README.md +++ b/README.md @@ -503,6 +503,29 @@ type Task struct { |---|---| | `GetUsers()` | List all users with profiles | +### Documents Files + +| Method | Description | +|---|---| +| `ListDavFolder(ctx, id)` | List a Documents folder (`@root` for virtual sections) | +| `ListDavSections(ctx)` | Virtual sections (Documents, Projects, …) | +| `CreateDavFolder(ctx, parentID, title)` | Create a subfolder | +| `RenameDavFolder(ctx, id, title)` / `RenameDavFile(ctx, id, title)` | Rename folder / file | +| `DownloadFile(ctx, id, dst)` / `DownloadDavFile(ctx, id, w)` | Download file bytes | +| `UploadDavFile(ctx, folderID, fileName, src)` | Upload from a reader | +| `UploadToFolder(ctx, folderID, localPath)` | Upload a local file into a folder | +| `UploadToFolderReplacing(ctx, folderID, localPath)` | Upsert by `stem\|ext`; returns replaced ids | +| `UpdateFile(ctx, fileID, localPath)` | New version of an existing file (same id, no copy) | +| `MoveDavItems(ctx, folderIDs, fileIDs, dest)` | Move (`resolveType=Skip`); per-operation errors surfaced, not silent nil | +| `CopyDavItems(ctx, folderIDs, fileIDs, dest)` | Copy (`conflictResolveType=Skip`); errors surfaced | +| `MoveFiles(ctx, destFolderID, fileIDs)` | Move with `resolveType=Skip` + `holdResult`; errors surfaced | +| `ListFileOps(ctx)` | Active file operations (move/copy status polling) | +| `FolderFiles(ctx, folderID)` | Flat file list of a folder (stem helpers) | +| `DeleteFilesByStem(ctx, folderID, stem)` | Remove `stem\|ext` copies | +| `DoRetry(ctx, policy, fn)` | Deterministic linear backoff (N·Base, no jitter) on 429/502/503/504 | +| `DefaultRetryPolicy()` | 5 attempts, 1s·2s·3s·4s waits, 30s cap | +| `Transient(err)` | True for retriable OnlyOffice answers | + ### Helper Types | Type | Description | @@ -622,6 +645,8 @@ oo docs convert ./note.docx # → note.md oo docs ocr ./scan.jpg --md ./scan.md # searchable PDF + markdown oo docs hocr ./scan.jpg --lang spa --md ./scan.hocr.md --yaml ./scan.yml oo docs put-md 7 ./OO-HONDA-7-INDEX.md --folder 490 +oo docs put-txt 7 ./notes.txt --folder 490 +oo docs put-xlsx 7 ./table.xlsx --folder 490 oo docs as-md 2815 --to ./parte.md # download OO file as MD (OCR if needed) oo docs as-md 307 --hocr --lang spa # OO download via go-hocr structure oo projects files put-md 7 ./note.md # alias @@ -631,21 +656,64 @@ oo tasks files upload 208 ./notes.pdf oo tasks files detach 208 12345 ``` +### Documents module (`oo dav`) + +Direct access to the Documents module by folder/file id — the same calls that +back `oo-webdav` and the project/task file commands. `move` sends +`resolveType=Skip` + `holdResult=true`: without those params the legacy +`fileops/move` endpoint answers 200 without moving anything, and the library +surfaces such per-operation errors instead of a silent nil +(`MoveDavItems` / `CopyDavItems` / `MoveFiles`). + +```bash +oo dav ls 659 +oo dav ls @root # virtual sections (Documents, Projects, …) +oo dav mkdir 659 "2026 inbox" +oo dav move 659 22881 22882 # DEST_FOLDER_ID FILE_ID… +oo dav move 659 22881 --folders 670 # move folders along with files +oo dav copy 659 22881 +oo dav rename-file 22881 invoice-v2.pdf +oo dav rename-folder 671 o2-archive +oo dav download 22881 --to ./copy.pdf # default path: ./ +oo dav fileops # active move/copy operations (status polling) +``` + +### Bulk tools (`cmd/`) + +Small single-purpose binaries for bulk Documents work. All of them pace +requests and retry transient OnlyOffice answers (429/502/503/504) with a +deterministic linear backoff — no jitter, same waits on every run +(see `DoRetry` below). Build with `go build ./cmd/`. + +```bash +ooscan 659 # recursive index → TSV: file_id, folder_id, path, title +ooscan 659 666 > oo-index.tsv # several roots into one index +pdfamount 671 # "Zu zahlender Betrag" per PDF → TSV: file_id, title, amount +kontoblatt 3906 ./kontoblatt.xlsx # summary (Gegenkonto/Monat) uploaded next to source +kontolink IN.xlsx oo-index.tsv OUT.xlsx [FILE_ID] [AMOUNTS_TSV] +# kontolink writes DocEditor links into the Link column: Beleg → supplier+month +# → amount+date (5th arg = pdfamount output); with FILE_ID it updates the +# source file in place, else uploads an "(links)" copy next to it. +``` + | Subject | Verbs | |---|---| | `calendar` | `list`, `events`, `add`, `delete` | -| `projects` | `list`, `get`, `milestones`, `create`, `update`, `delete`, **`files`** (`list`, `upload`, `download`, `rename`, `delete`) | +| `projects` | `list`, `get`, `milestones`, `milestone-create`, `create`, `update`, `delete`, `contacts` (`add`, `remove`), `link-authors`, `link-git`, **`files`** (`list`, `upload`, `download`, `rename`, `delete`, `dedupe`, `as-md`, `put-md`, `put-txt`, `put-xlsx`) | | `tasks` | `list`, `get`, `create`, `update`, `delete`, `subtask add`, **`files`** (`list`, `upload`, `detach`) | | `users` | `list`, `self` (alias: `oo whoami`) | -| `contacts` | `list`, `get`, `delete`, `info-add`, `merge`, `dedupe-info` | +| `contacts` | `list`, `get`, `delete`, `info-add`, `merge`, `dedupe-info`, `tags`, `tag-add`, `tag-create`, `tag-remove` | | `persons` | `list`, `create`, `delete`, `dedupe` | | `companies` | `list`, `create`, `delete`, `dedupe`, `dedupe-persons` | -| `opportunities` | `list`, `get`, `create`, `delete`, `stages`, `member-add`, `dedupe`, `dedupe-members`, `fix-titles` | +| `opportunities` | `list`, `get`, `create`, `update`, `delete`, `stages`, `member-add`, `dedupe`, `dedupe-members`, `fix-titles` | | `invoices` | `list`, `get`, `create`, `update`, `pdf`, `pdf-cleanup`, `status`, `delete`, `items …` | | `crm` | `cleanup` | -| `mails` | `accounts`, `folders`, `list`, `get`, `draft`, `attach`, `draft-invoice`, `delete` | +| `mails` | `accounts`, `folders`, `list`, `get`, `download-attachment`, `draft`, `attach`, `draft-invoice`, `send`, `delete` | | `cases` | `list`, `create`, `delete`, `member-add` | -| `crm-tasks` | `list`, `create`, `delete`, `categories` | +| `crm-tasks` | `list`, `create`, `delete`, `categories`, `reassign-self` | +| `docs` | `tools`, `convert`, `optimize`, `ocr`, `hocr`, `as-md`, `put-md`, `put-txt`, `put-xlsx` | +| `catalog` | `match`, `merge`, `apply`, `scan-contacts`, `scan-projects`, `scan-thunderbird` | +| `dav` | `ls`, `move`, `copy`, `mkdir`, `rename-file`, `rename-folder`, `download`, `fileops` | The CLI reads only `.env` from the current working directory (godotenv is a CLI-only concern — the library itself never loads dotfiles). diff --git a/cmd/oo/main.go b/cmd/oo/main.go index 7422a2d..c91baaa 100644 --- a/cmd/oo/main.go +++ b/cmd/oo/main.go @@ -3,19 +3,20 @@ // Command tree is subject-based (mirrors the library split and the `tea` CLI): // // oo calendar list | events | add | delete -// oo projects list | get | milestones | create | update | delete | files (list|upload|download|rename|delete|as-md|put-md) +// oo projects list | get | milestones | milestone-create | create | update | delete | contacts (add|remove) | link-authors | link-git | files (list|upload|download|rename|delete|dedupe|as-md|put-md|put-txt|put-xlsx) // oo tasks list | get | create | update | delete | subtask add | files (list|upload|detach) // oo users list | self (alias: oo whoami) -// oo contacts list | get | delete | info-add | merge | dedupe-info +// oo contacts list | get | delete | info-add | merge | dedupe-info | tags | tag-add | tag-create | tag-remove // oo persons list | create | delete | dedupe // oo companies list | create | delete | dedupe | dedupe-persons -// oo opportunities list | get | create | delete | stages | member-add | dedupe | dedupe-members | fix-titles +// oo opportunities list | get | create | update | delete | stages | member-add | dedupe | dedupe-members | fix-titles // oo cases list | create | delete | member-add -// oo crm-tasks list | create | delete | categories +// oo crm-tasks list | create | delete | categories | reassign-self // oo crm cleanup -// oo mails accounts | folders | list | get | download-attachment | draft | attach | draft-invoice | delete +// oo mails accounts | folders | list | get | download-attachment | draft | attach | draft-invoice | send | delete // oo invoices list | get | create | update | pdf | pdf-cleanup | status | delete | items … -// oo docs tools | convert | ocr | as-md | put-md +// oo docs tools | convert | optimize | ocr | hocr | as-md | put-md | put-txt | put-xlsx +// oo catalog match | merge | apply | scan-contacts | scan-projects | scan-thunderbird // oo dav ls | move | copy | mkdir | rename-file | rename-folder | download | fileops // // CRM association rules: docs/crm-associations.md