Go client library for the OnlyOffice Project Management API — manage projects, tasks, subtasks, milestones, and users programmatically.
Pairs with go-gitea-helpers to bridge developer issue trackers with CRM-grade project management for Gantt charts, resource planning, and executive reporting.
graph TB
subgraph "Developer Tools"
GIT["Gitea / GitHub<br/>Issues, PRs, Milestones"]
end
subgraph "go-onlyoffice"
CL["Client"]
AUTH["Auth<br/>Token-based"]
PRJ["Projects"]
TSK["Tasks & Subtasks"]
MS["Milestones"]
USR["Users"]
end
subgraph "OnlyOffice CRM"
GANTT["Gantt Charts"]
PLAN["Project Planning"]
RPT["Reports & Dashboards"]
PM["PM Workflow"]
end
GIT -->|"sync issues"| CL
CL --> AUTH
AUTH --> PRJ
AUTH --> TSK
AUTH --> MS
AUTH --> USR
PRJ --> GANTT
TSK --> GANTT
MS --> PLAN
TSK --> RPT
PRJ --> PM
graph LR
subgraph "Engineering World"
DEV["Developers"]
GITEA["Gitea / GitHub<br/>Issues & PRs"]
CODE["Code Reviews"]
end
subgraph "Management World"
PM["Project Managers"]
OO["OnlyOffice CRM<br/>Gantt · Planning · Reports"]
EXEC["Executives<br/>Status Reports"]
end
DEV -->|"create issues"| GITEA
GITEA -.->|"❌ invisible"| PM
PM -->|"manual copy"| OO
OO --> EXEC
style GITEA fill:#f96,stroke:#333
style OO fill:#69f,stroke:#333
Without sync: Project managers manually copy issue titles, deadlines, and status from Gitea into OnlyOffice. Developers don't update the CRM. Gantt charts rot. Reports lie.
With sync: Issues flow automatically from Gitea to OnlyOffice with start dates, deadlines, and status. PMs get live Gantt charts. Developers keep working in Git.
graph LR
subgraph "Engineering World"
DEV["Developers"]
GITEA["Gitea / GitHub"]
end
subgraph "Sync Bridge"
SYNC["go-onlyoffice<br/>+ go-gitea-helpers"]
end
subgraph "Management World"
OO["OnlyOffice CRM"]
GANTT["Gantt Charts ✓"]
RPT["Reports ✓"]
end
DEV -->|"create/close issues"| GITEA
GITEA -->|"auto-sync"| SYNC
SYNC -->|"create/update tasks"| OO
OO --> GANTT
OO --> RPT
style SYNC fill:#4a4,stroke:#333,color:#fff
go get github.com/eslider/go-onlyofficeFor Gitea sync (optional):
go get github.com/eslider/go-gitea-helpersclient := onlyoffice.NewClient(onlyoffice.GetEnvironmentCredentials())
projects, _ := client.GetProjects()
for _, p := range projects {
fmt.Printf("[%d] %s — %d tasks\n", *p.ID, *p.Title, safeInt(p.TaskCountTotal))
}// Create a project
project, _ := client.CreateProject(onlyoffice.NewProjectRequest{
Title: "Q1 2026 Release",
Description: "Backend API v2 + mobile app redesign",
})
// Create tasks with start/end dates (for Gantt chart)
client.CreateProjectTask(onlyoffice.NewProjectTaskRequest{
ProjectId: *project.ID,
Title: "Design API schema",
Description: "OpenAPI 3.1 spec for all endpoints",
StartDate: onlyoffice.Time(time.Now()),
Deadline: onlyoffice.Time(time.Now().AddDate(0, 0, 14)),
Priority: 1, // High
})
client.CreateProjectTask(onlyoffice.NewProjectTaskRequest{
ProjectId: *project.ID,
Title: "Implement auth service",
Description: "JWT + OAuth2 + refresh tokens",
StartDate: onlyoffice.Time(time.Now().AddDate(0, 0, 14)),
Deadline: onlyoffice.Time(time.Now().AddDate(0, 1, 0)),
})// Get all tasks for a project
tasks, _ := client.GetTasks(onlyoffice.NewProjectGetTasksRequest(*project.ID))
for _, t := range tasks {
status := "open"
if t.Status != nil && *t.Status == onlyoffice.ProjectTaskStatusClosed {
status = "closed"
}
fmt.Printf(" [%s] %s", status, *t.Title)
if t.Deadline != nil {
fmt.Printf(" (due: %s)", t.Deadline.Format("2006-01-02"))
}
fmt.Println()
}// Close a task and set actual end date
client.UpdateProjectTask(onlyoffice.ProjectTaskUpdateRequest{
ID: taskID,
Title: "Design API schema",
Status: onlyoffice.ProjectTaskStatusClosed,
Deadline: &onlyoffice.Time(time.Now()),
})milestones, _ := client.GetProjectMilestones(project)
for _, ms := range milestones {
active := int64(0)
closed := int64(0)
if ms.ActiveTaskCount != nil { active = *ms.ActiveTaskCount }
if ms.ClosedTaskCount != nil { closed = *ms.ClosedTaskCount }
total := active + closed
fmt.Printf("Milestone: %s — %d/%d tasks done", *ms.Title, closed, total)
if ms.Deadline != nil {
fmt.Printf(" (deadline: %s)", ms.Deadline.Format("2006-01-02"))
}
fmt.Println()
}The primary use case is bridging developer workflows with project management. Developers create issues in Gitea; a sync job automatically mirrors them as OnlyOffice tasks with proper start/end dates, enabling PMs to work with Gantt charts without developers leaving their Git workflow.
sequenceDiagram
participant Dev as Developer
participant Gitea
participant Sync as Sync Job
participant OO as OnlyOffice
Dev->>Gitea: Create issue "Add OAuth2"
Note over Gitea: issue.Created = Feb 13<br/>issue.Deadline = Mar 1
Sync->>Gitea: GET /repos/{org}/*/issues
Gitea-->>Sync: issues list (paginated)
Sync->>OO: GET /api/2.0/project/filter.json
OO-->>Sync: projects list
Sync->>Sync: Match Gitea labels → OO projects
alt Issue not yet in OnlyOffice
Sync->>OO: POST /api/2.0/project/{id}/task.json
Note over OO: Task created:<br/>Start: Feb 13, End: Mar 1<br/>Description includes Gitea URL
else Issue already synced
Sync->>OO: PUT /api/2.0/project/task/{id}.json
Note over OO: Title, status, dates updated
end
Dev->>Gitea: Close issue "Add OAuth2"
Sync->>OO: PUT status → Closed
Note over OO: Gantt chart updates automatically
package main
import (
"fmt"
"log"
"os"
"strings"
gitea "github.com/eslider/go-gitea-helpers"
onlyoffice "github.com/eslider/go-onlyoffice"
)
func main() {
// Connect to both services
oo := onlyoffice.NewClient(onlyoffice.GetEnvironmentCredentials())
gc, _ := gitea.NewClient(gitea.GetEnvironmentConfig())
owner := os.Getenv("GITEA_OWNER")
// Load all Gitea issues and OnlyOffice projects
repos, _ := gc.GetAllReposIssues(owner)
projects, _ := oo.GetProjects()
for repoName, repo := range repos {
// Find matching OnlyOffice project by name
project := projects.Get(repoName)
if project == nil {
fmt.Printf("SKIP %s (no matching OO project)\n", repoName)
continue
}
// Load existing tasks
tasks, _ := oo.GetTasks(onlyoffice.NewProjectGetTasksRequest(*project.ID))
for _, issue := range repo.Issues {
// Check if issue is already synced (URL in description)
existing := findSyncedTask(tasks, issue.HTMLURL)
if existing != nil {
// Update existing task
status := onlyoffice.ProjectTaskStatusOpen
if issue.State == "closed" {
status = onlyoffice.ProjectTaskStatusClosed
}
oo.UpdateProjectTask(onlyoffice.ProjectTaskUpdateRequest{
ID: *existing.ID,
Title: issue.Title,
Status: status,
StartDate: timePtr(onlyoffice.Time(issue.Created)),
Deadline: deadlineFromIssue(issue),
})
fmt.Printf(" UPDATED: %s\n", issue.Title)
} else {
// Create new task
status := onlyoffice.ProjectTaskStatusOpen
if issue.State == "closed" {
status = onlyoffice.ProjectTaskStatusClosed
}
oo.CreateProjectTask(onlyoffice.NewProjectTaskRequest{
ProjectId: *project.ID,
Title: issue.Title,
Description: issue.Body + "\n\nURL:" + issue.HTMLURL,
StartDate: onlyoffice.Time(issue.Created),
Deadline: *deadlineFromIssue(issue),
Status: status,
})
fmt.Printf(" CREATED: %s\n", issue.Title)
}
}
}
}
// findSyncedTask checks task descriptions for the Gitea issue URL.
func findSyncedTask(tasks []*onlyoffice.Task, issueURL string) *onlyoffice.Task {
for _, t := range tasks {
if t.Description != nil && strings.Contains(*t.Description, issueURL) {
return t
}
}
return nil
}Once synced, OnlyOffice provides without any developer intervention:
| Feature | How It Works |
|---|---|
| Gantt Charts | Tasks have StartDate and Deadline from Gitea issue created/due dates |
| Status Tracking | Open/closed status mirrors Gitea issue state in real time |
| Milestone Planning | Gitea milestones map to OnlyOffice milestones with progress % |
| Resource Allocation | Task assignees sync so PMs see who's working on what |
| Sprint Reports | Filter by date range to generate sprint/release reports |
| Cross-Repo View | All repos' issues appear as tasks in a unified project board |
| Executive Dashboards | Project progress, overdue tasks, team workload at a glance |
graph TB
subgraph "Trigger Options"
CRON["Cron Job<br/>every 15 min"]
HOOK["Gitea Webhook<br/>on issue events"]
CLI["Manual CLI<br/>on-demand"]
end
subgraph "Sync Engine"
S["Sync Job"]
MAP["Label → Project<br/>Mapping"]
MATCH["URL-based Task<br/>Matching"]
DATE["Date Translation<br/>Created → Start<br/>Deadline → End<br/>Closed → Actual End"]
end
subgraph "Output"
OO["OnlyOffice Tasks"]
GANTT["Gantt Timeline"]
REP["PM Reports"]
end
CRON --> S
HOOK --> S
CLI --> S
S --> MAP
S --> MATCH
S --> DATE
MAP --> OO
MATCH --> OO
DATE --> OO
OO --> GANTT
OO --> REP
task, _ := client.CreateProjectTask(onlyoffice.NewProjectTaskRequest{
ProjectId: projectID,
Title: "Implement payment gateway",
Description: "Integrate Stripe API for subscription billing",
StartDate: onlyoffice.Time(time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)),
Deadline: onlyoffice.Time(time.Date(2026, 3, 15, 0, 0, 0, 0, time.UTC)),
Priority: 1, // High
MilestoneId: milestoneID,
Notify: true,
})tasks, _ := client.GetTasks(onlyoffice.NewProjectGetTasksRequest(projectID))
open, closed := 0, 0
var overdue []*onlyoffice.Task
for _, t := range tasks {
if t.Status != nil && *t.Status == onlyoffice.ProjectTaskStatusClosed {
closed++
} else {
open++
if t.Deadline != nil && t.Deadline.Before(time.Now()) {
overdue = append(overdue, t)
}
}
}
fmt.Printf("Progress: %d/%d done (%.0f%%)\n", closed, open+closed,
float64(closed)/float64(open+closed)*100)
if len(overdue) > 0 {
fmt.Printf("⚠ %d overdue tasks:\n", len(overdue))
for _, t := range overdue {
fmt.Printf(" - %s (due: %s)\n", *t.Title, t.Deadline.Format("2006-01-02"))
}
}Tasks support subtasks for breaking work into smaller pieces:
type Task struct {
ID *int `json:"id"`
Title *string `json:"title"`
StartDate *time.Time `json:"startDate"`
Deadline *time.Time `json:"deadline"`
Description *string `json:"description"`
Priority *int `json:"priority"` // High=1, Normal=0, Low=-1
Status *ProjectTaskStatus `json:"status"` // Open=1, Closed=2
Subtasks []any `json:"subtasks"`
MilestoneID *int64 `json:"milestoneId"`
Responsibles []*User `json:"responsibles"` // Assigned team members
// ... timestamps, permissions
}| Function | Description |
|---|---|
NewClient(credentials) |
Create a new API client |
GetEnvironmentCredentials() |
Load from ONLYOFFICE_* env vars |
Auth(credentials) |
Authenticate and get token |
Query(request, result) |
Execute raw API request |
| Method | Description |
|---|---|
GetProjects() |
List all projects |
CreateProject(req) |
Create a new project |
UpdateProject(req) |
Update project details |
DeleteProject(id) |
Delete a project |
GetProjectMilestones(project) |
Get milestones with task counts |
| Method | Description |
|---|---|
GetTasks(req) |
List tasks with filtering |
CreateProjectTask(req) |
Create task with dates, priority, milestone |
UpdateProjectTask(req) |
Update title, status, dates, priority |
| Field | Type | Purpose |
|---|---|---|
StartDate |
*time.Time |
Gantt bar start |
Deadline |
*time.Time |
Gantt bar end |
Status |
ProjectTaskStatus |
Open (1) / Closed (2) |
Priority |
*int |
High (1) / Normal (0) / Low (-1) |
MilestoneID |
*int64 |
Groups tasks under milestones |
Responsibles |
[]*User |
Assigned team members |
Subtasks |
[]any |
Sub-items within a task |
| Method | Description |
|---|---|
GetUsers() |
List all users with profiles |
| Type | Description |
|---|---|
Projects |
[]*Project with .Get(title) lookup |
Time |
time.Time wrapper with OnlyOffice JSON format |
Task.GetGiteaIssueLink() |
Extract Gitea URL from task description |
Since v0.2 the library also exposes OnlyOffice Workspace surfaces beyond
Projects: Calendar events, CRM (Contacts, Companies, Opportunities, Cases,
Tasks, History notes) and opportunity file uploads. These helpers return
untyped map[string]any for flexibility; callers that need typed structs
should use the typed Project/Task API above.
client := onlyoffice.NewClient(onlyoffice.GetEnvironmentCredentials())
client.SetDefaults(onlyoffice.GetEnvironmentDefaults()) // optional
ctx := context.Background()
// Calendar
events, _ := client.ListEvents(ctx, "2025-01-01", "2025-12-31")
client.AddEvent(ctx, "", "Interview", "2025-06-10T10:00:00Z", "2025-06-10T11:00:00Z", "", false)
// CRM
deals, total, _ := client.ListOpportunities(ctx, 50, 0)
company, _ := client.FindCompany(ctx, "ACME")
// Subtasks (form-encoded)
client.AddSubtask(ctx, "4242", "Prepare notes")A ready-to-use Cobra CLI wrapping the
library lives under cmd/oo. The command tree is subject-based,
mirroring the tea CLI:
go install github.com/eslider/go-onlyoffice/cmd/oo@latest
# Global: every list-style command takes -o table|json (default: table)
oo whoami
oo users list
oo calendar events --start 2026-04-24 --end 2026-05-01
oo projects list
oo projects get 33
oo tasks list --all --verbose
oo tasks subtask add 4242 "Prepare notes"
oo persons create --first Jane --last Doe --email jane@example.com
oo companies create --name "Acme GmbH" --website https://acme.com
oo opportunities list
oo opportunities stages
oo cases list
oo crm-tasks categoriesTerminal UI for browsing OnlyOffice Workspace — module tree (left), selectable
lists (center), and markdown preview (right). Uses the same .env credentials
as oo. Lives under cmd/office.
go install github.com/eslider/go-onlyoffice/cmd/office@latest
office| Key | Action |
|---|---|
Tab |
Next pane (menu → list → preview) |
Shift+Tab |
Previous pane |
↑↓ / j / k |
Navigate / scroll within focused pane |
Enter / → |
Expand tree branch or open leaf list |
Space |
Toggle multi-select on list row |
Enter / a |
Action menu (view, delete, download, …) |
r |
Refresh current list |
q |
Quit |
Navigate the tree on the left: expand modules (▸/▾), drill to a leaf (marked •) — the center list loads only at the last level. Under Projects → By project, live projects appear as subnodes with Tasks and Files leaves.
Optional env for DOCX preview via Document Server (see .env.example):
ONLYOFFICE_DOCS_URL=https://docs.example.com
ONLYOFFICE_DOCS_SECRET=… # when JWT signing is enabledSpreadsheet files: inline CSV/JSON preview in the right pane; install
vex on PATH for full-screen
xlsx/csv viewing (v on a file row — coming in next iteration).
Integration tests for list loaders and preview (live API only — no mocks):
go test -tags=integration ./cmd/office/fetch/... ./cmd/office/preview/...Project / task documents (oo):
# Project Documents (files module)
oo projects files list 33
oo projects files upload 33 ./notes.md
oo projects files download 12345 --to ./copy.md
oo projects files rename 12345 notes-v2.md
oo projects files delete 12345
oo tasks files list 208
oo tasks files upload 208 ./notes.pdf
oo tasks files detach 208 12345| Subject | Verbs |
|---|---|
calendar |
list, events, add, delete |
projects |
list, get, milestones, create, update, delete, files (list, upload, download, rename, delete) |
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 |
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 |
invoices |
list, get, create, update, pdf, pdf-cleanup, status, delete, items … |
crm |
cleanup |
mails |
accounts, folders, list, get, draft, attach, draft-invoice, delete |
cases |
list, create, delete, member-add |
crm-tasks |
list, create, delete, categories |
The CLI reads only .env from the current working directory (godotenv is a
CLI-only concern — the library itself never loads dotfiles).
Canonical ONLYOFFICE_* variables win over aliases. Optional CLI-only aliases:
OO_URL / OO_USER / OO_PASS → ONLYOFFICE_URL / ONLYOFFICE_USER / ONLYOFFICE_PASS.
Run oo --help or oo <subject> --help for the full command reference.
0.5.0 migration note: the command tree was flattened per-subject. Old flat names (
oo cal-events,oo task-list,oo crm-contacts, …) were replaced by subject-based equivalents (oo calendar events,oo tasks list,oo contacts list). Flags on leaf commands are unchanged.
The oo binary is the day-to-day operator interface. It loads credentials from
.env in the current working directory (copy from .env.example):
cp .env.example .env
# ONLYOFFICE_URL=https://office.example.com
# ONLYOFFICE_USER=you@example.com
# ONLYOFFICE_PASS=…
go install github.com/eslider/go-onlyoffice/cmd/oo@latest
oo whoamiEvery list command accepts -o table (default) or -o json for scripting.
Problem: Duplicate companies (Acme / ACME GmbH), persons created twice,
the same email on a contact three times, deals titled @ Acme, or the same
HR contact linked to a deal twice.
One-shot fix — runs every dedupe pass in order:
oo crm cleanup -o jsonSteps inside crm cleanup:
| Step | What it does |
|---|---|
companies |
Merge companies with the same normalized name (slogan variants like Affirm / Affirm — Fraud Engineering count as one) |
persons |
Merge duplicate persons globally (same first+last) |
company-persons |
Merge duplicate persons under each company |
contact-info |
Remove duplicate email/phone/website rows |
opportunity-members |
Drop duplicate contacts on the same deal |
opportunities |
Merge duplicate deals by title |
fix-titles |
Repair malformed titles ( @ Company → Company) |
Targeted passes when you only want one kind of fix:
# Duplicate company records
oo companies dedupe
# Same person entered twice under one employer
oo companies dedupe-persons
# Global person duplicates (same name, different ids)
oo persons dedupe
# Repeated email/phone rows on contacts
oo contacts dedupe-info
# Two deals with the same title
oo opportunities dedupe
# Same contact attached twice to one deal
oo opportunities dedupe-members
# Titles like " @ Acme" or extra whitespace
oo opportunities fix-titlesMerge two known company ids (keeps INTO):
oo contacts merge FROM_ID INTO_IDCompany ↔ person ↔ deal ↔ project ↔ invoice ↔ mail rules and OO quirks: docs/crm-associations.md.
Problem: Bill a client deal as Draft, regenerate PDF, prune duplicate PDF attachments, prepare OnlyOffice Mail — without inventing a second bill-to company.
# Always pass --opportunity at create (update --opportunity often HTTP 400)
oo invoices create --number INV-2026-01 --contact COMPANY_ID --item ITEM_ID \
--price 300 --opportunity DEAL_ID --language de-DE \
--line-description "…" --terms $'…' --description $'…' --po "Deal #DEAL_ID"
oo invoices pdf INVOICE_ID --force
oo invoices pdf-cleanup INVOICE_ID
oo invoices status INVOICE_ID --status draft
oo mails draft-invoice --invoice INVOICE_ID --to billing@example.comDeal grouping flag — when the same role at the same company created
separate deals (Engineer @ Acme vs Engineer):
oo opportunities dedupe --ignore-company-suffix
oo crm cleanup --ignore-company-suffixInspect before/after:
oo opportunities list --count 200
oo contacts get CONTACT_ID -o json
oo crm cleanup -o jsonProblem: Mail lives in OnlyOffice Mail (/addons/mail/#inbox), bound to your
portal account — not a separate archive service. You want to list, read, or
remove messages from the shell.
Uses the same ONLYOFFICE_* credentials as every other oo command.
# Which mailbox is linked?
oo mails accounts
# Folder counters (inbox unread, trash size, …)
oo mails folders
# Latest inbox messages (API returns 25 per page; --limit paginates automatically)
oo mails list --folder inbox --limit 50
# Page through older mail
oo mails list --folder inbox --limit 100 --offset 100
# Other folders
oo mails list --folder sent --limit 20
oo mails list --folder spam --limit 100
# Read full message (subject, htmlBody, attachments metadata)
oo mails get 5664 -o json | jq '{subject, from, to, date}'
# Remove one or more messages (server moves to trash or deletes per Mail rules)
oo mails delete 5664
oo mails delete 5664 5663 5661
# Create / update a draft (HTML body field is API "body"; plain text is wrapped)
oo mails draft --to client@example.com --subject "Rechnung INV-2026-01" \
--body "Guten Tag,\n\nanbei die Rechnung.\n\nMit freundlichen Grüßen"
# Attach an OnlyOffice Files document (e.g. invoice PDF file id) to a draft
oo mails attach 7301 --file-id 12345
# Regenerate invoice PDF (force) + draft + attach (does not send)
oo mails draft-invoice --invoice 16 --to info@example.comMatrix / chat URLs in signatures: use plain text
chat: https://matrix.to/#/@user:server — HTML <a href="…#…"> truncates at #.
Table output splits the from header into fromName and fromAddress
(e.g. Bitfinex + no-reply@bitfinex.com). JSON output returns the raw
API payload.
Scripting example — export today's inbox subjects:
oo mails list --folder inbox --limit 200 -o json \
| jq -r '.[] | "\(.id)\t\(.subject)"'# Search companies
oo companies list --search acme
# Create company + person with primary email
oo companies create --name "Acme GmbH" --website https://acme.com
oo persons create --first Jane --last Doe --email jane@acme.com --company-id 42
# Attach email or LinkedIn to existing contact
oo contacts info-add 42 --type Email --value jane@acme.com --primary
# Pipeline overview
oo opportunities list --count 100
oo opportunities stages
oo opportunities get 231 -o json# Next week's events
oo calendar events --start 2026-06-24 --end 2026-07-01
# Schedule interview block
oo calendar add "Technical interview" 2026-06-26T10:00:00Z 2026-06-26T11:00:00Z
# Attach a file to a task
oo tasks files upload 208 ./notes.pdf
oo projects files list 33| When | Command |
|---|---|
| After bulk CRM imports | oo crm cleanup |
| After bulk CSV import into CRM | oo crm cleanup |
| Weekly inbox triage | oo mails list --folder inbox --limit 100 |
| Before exec reporting | oo opportunities list + oo projects list |
| Variable | Description |
|---|---|
ONLYOFFICE_URL (or ONLYOFFICE_HOST) |
OnlyOffice instance URL |
ONLYOFFICE_USER (or ONLYOFFICE_NAME) |
Login email or username |
ONLYOFFICE_PASS (or ONLYOFFICE_PASSWORD) |
Password |
ONLYOFFICE_CALENDAR_ID |
Default calendar id used when omitted (default 1) |
ONLYOFFICE_PROJECT_ID |
Default project id used when omitted (default 33) |
OO_URL, OO_USER, OO_PASS |
Optional CLI-only aliases for ONLYOFFICE_* |
Mail and CRM cleanup are documented in oo CLI use cases above. Personal disk inventory / dossier sync lives in the private oo-workspace (oow) tooling.
GitHub Actions (pattern from eSlider/go-config):
| Workflow | Trigger | Purpose |
|---|---|---|
test.yml |
push / PR | go vet, unit tests, build oo + office |
release-please.yml |
push to main |
semver PR from conventional commits |
release.yml |
tag v* |
GoReleaser cross-platform oo + office binaries |
Repo setting required once: Settings → Actions → General → Allow GitHub Actions to create and approve pull requests.
Merge the release-please PR to tag a version; GoReleaser publishes assets to GitHub Releases.
| Example | Description |
|---|---|
| basic | List projects and users |
| calendar | List calendars and events, create a new event |
| crm | List contacts and opportunities, add company/deal/history note |
| subtasks | Create a parent task and attach subtasks |
cmd/oo |
Full-featured CLI using all modules |
cmd/office |
Terminal UI — browse Workspace with markdown preview |
| Library | Description |
|---|---|
| go-gitea-helpers | Gitea pagination helpers for issue/repo fetching |
| go-matrix-bot | Matrix bot with OnlyOffice task creation from chat |
| go-trade | Unified trade data model across exchanges |