Skip to content

Commit fdc3de7

Browse files
authored
docs: add SKILL.md, CLAUDE.md, and package READMEs (#9)
* docs: add SKILL.md, CLAUDE.md, and package READMEs Add Claude Code skill file for agent integration, project-level CLAUDE.md quick-reference, and improved npm/PyPI README files matching the jira-cli-v2 documentation structure. * fix: resolve all golangci-lint and gosec warnings - Handle unchecked error returns (errcheck) across test and prod code - Remove unused newTestClient helper (unused) - Fix ineffectual assignment in preset_test.go (ineffassign) - Apply De Morgan's law in cache_test.go (staticcheck) - Add ReadHeaderTimeout to OAuth2 callback server (gosec G112) - Add #nosec annotations for intentional patterns (G107, G204, G117) * fix: suppress remaining gosec G101 and G117 false positives - G101: TokenURL is a well-known public OAuth endpoint, not a credential - G117: Move #nosec annotation to json.Marshal call site
1 parent fd3c7ec commit fdc3de7

27 files changed

Lines changed: 860 additions & 162 deletions

CLAUDE.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# cf — Agent-Friendly Confluence CLI
2+
3+
## Quick Start
4+
```
5+
cf configure --base-url https://yoursite.atlassian.net --token YOUR_API_TOKEN
6+
cf configure --test # test saved credentials
7+
cf configure --test --profile work # test a specific profile
8+
cf configure --profile myprofile --delete # remove a profile
9+
```
10+
11+
## Key Patterns
12+
13+
- **All output is JSON** on stdout. Errors are JSON on stderr.
14+
- **Exit codes are semantic**: 0=ok, 1=error, 2=auth, 3=not_found, 4=validation, 5=rate_limited, 6=permission
15+
- **Use `--preset`** for common field sets: `cf pages get --id 12345 --preset agent` (presets: agent, brief, titles, meta, tree, search, diff)
16+
- **Use `--jq`** to reduce output tokens: `cf pages get --id 12345 --jq '{id: .id, title: .title}'`
17+
- **Use `--fields`** to limit Confluence response fields: `cf pages get --id 12345 --fields id,title,status`
18+
- **Use `cf batch`** to run multiple operations in one call
19+
- **Use `cf raw`** for any API endpoint not covered by generated commands
20+
- **Content is XHTML** (Confluence storage format), not Markdown
21+
22+
## Common Operations
23+
24+
```bash
25+
# Get page
26+
cf pages get --id 12345
27+
28+
# Search content with CQL
29+
cf search search-content --cql "space = DEV AND type = page" --jq '.results[] | {id, title}'
30+
31+
# Create page (content in Confluence storage format — XHTML)
32+
cf pages create --spaceId 123456 --title "New Page" --body "<p>Content here</p>"
33+
34+
# Update page (requires current version number)
35+
cf pages update --id 12345 --version-number 3 --title "Updated" --body "<p>New content</p>"
36+
37+
# Delete page
38+
cf pages delete --id 12345
39+
40+
# List spaces
41+
cf spaces list --jq '.results[] | {id, key: .key, name: .name}'
42+
43+
# Blog posts
44+
cf blogposts create --spaceId 123456 --title "Sprint Recap" --body "<p>What we shipped</p>"
45+
cf blogposts list --jq '.results[] | {id, title}'
46+
47+
# Comments
48+
cf workflow comment --id 12345 --body "Reviewed and approved"
49+
50+
# Labels
51+
cf labels add --page-id 12345 --name "reviewed"
52+
cf labels remove --page-id 12345 --name "draft"
53+
54+
# Attachments
55+
cf attachments upload --page-id 12345 --file ./diagram.png
56+
cf attachments list --page-id 12345
57+
58+
# Workflow commands
59+
cf workflow move --id 12345 --target 67890 --position append
60+
cf workflow copy --id 12345 --target 67890 --title "Copy of Runbook"
61+
cf workflow archive --id 12345
62+
cf workflow restrict --id 12345 --user "john@company.com" --operation read
63+
64+
# Diff — structured version comparison
65+
cf diff --id 12345 # all changes
66+
cf diff --id 12345 --since 2h # changes in last 2 hours
67+
cf diff --id 12345 --since 2026-01-01 # changes since date
68+
69+
# Export — page content extraction
70+
cf export --id 12345 # single page body
71+
cf export --id 12345 --tree # page + all descendants
72+
cf export --id 12345 --format storage # raw storage format
73+
74+
# Raw API call (method is positional, not a flag; POST/PUT/PATCH require --body)
75+
cf raw GET /wiki/api/v2/pages/12345
76+
cf raw POST /wiki/api/v2/pages --body '{"spaceId":"123","title":"New"}'
77+
echo '{"spaceId":"123"}' | cf raw POST /wiki/api/v2/pages --body - # stdin
78+
79+
# Batch operations
80+
echo '[{"command":"pages get","args":{"id":"12345"},"jq":".title"},{"command":"pages get","args":{"id":"67890"},"jq":".title"}]' | cf batch
81+
82+
# Watch for changes (NDJSON stream — always use --max-events in automated contexts)
83+
cf watch --cql "space = DEV" --interval 30s --max-events 50
84+
85+
# Templates — create pages from predefined patterns
86+
cf templates list # list all templates
87+
cf templates show meeting-notes # show template definition
88+
cf pages create --template meeting-notes --var title="Q1 Review" --var date="2026-03-28"
89+
cf templates create my-template --from 12345 # create from existing page
90+
```
91+
92+
## Discovery
93+
94+
```bash
95+
cf schema # resource → verbs mapping (default)
96+
cf schema --list # all resource names only
97+
cf schema pages # all operations for 'pages'
98+
cf schema pages get # full schema with flags for one operation
99+
cf preset list # list available output presets
100+
cf templates list # list available templates
101+
cf templates show <name> # show a template's variables
102+
```
103+
104+
## Batch Command Names
105+
106+
In `cf batch`, use `"resource verb"` strings. Notable: `"diff diff"` (not just `"diff"`), `"export export"` (not just `"export"`).
107+
108+
```bash
109+
echo '[
110+
{"command": "pages get", "args": {"id": "12345"}, "jq": ".title"},
111+
{"command": "workflow comment", "args": {"id": "12345", "body": "Reviewed"}},
112+
{"command": "diff diff", "args": {"id": "12345", "since": "2h"}}
113+
]' | cf batch
114+
```
115+
116+
Batch exit code = highest-severity code from all operations.
117+
118+
## Global Flags
119+
120+
| Flag | Description |
121+
|------|-------------|
122+
| `--preset <name>` | named output preset (agent, brief, titles, meta, tree, search, diff) |
123+
| `--jq <expr>` | jq filter on response |
124+
| `--fields <list>` | comma-separated fields to return (GET only) |
125+
| `--cache <duration>` | cache GET responses (e.g. 5m, 1h) |
126+
| `--pretty` | pretty-print JSON |
127+
| `--no-paginate` | disable auto-pagination |
128+
| `--dry-run` | show request without executing |
129+
| `--verbose` | log HTTP details to stderr (JSON) |
130+
| `--timeout <duration>` | HTTP request timeout (default 30s) |
131+
| `--profile <name>` | use named config profile |
132+
| `--audit <path>` | NDJSON audit log file path |
133+
| `--max-batch <N>` | max operations per batch (default 50, batch command only) |
134+
135+
## Security
136+
137+
### Operation Policy (per profile)
138+
Restrict which operations a profile can execute:
139+
140+
```json
141+
{
142+
"profiles": {
143+
"agent": {
144+
"base_url": "...",
145+
"auth": {"type": "basic", "token": "..."},
146+
"allowed_operations": ["pages get", "search *", "workflow *"]
147+
},
148+
"readonly": {
149+
"base_url": "...",
150+
"auth": {"type": "basic", "token": "..."},
151+
"denied_operations": ["* delete*", "workflow *", "raw *"]
152+
}
153+
}
154+
}
155+
```
156+
157+
Rules:
158+
- Use `allowed_operations` OR `denied_operations`, not both
159+
- Patterns use glob matching: `*` matches any sequence
160+
- `allowed_operations`: implicit deny-all, only matching ops run
161+
- `denied_operations`: implicit allow-all, only matching ops blocked
162+
163+
### Batch Limits
164+
Default max batch size is 50. Override with `--max-batch N`.
165+
166+
### Audit Logging
167+
Enable per-invocation with `--audit <path>`. Logs NDJSON to the specified file.
168+
169+
## Development
170+
171+
```bash
172+
make generate # regenerate commands from OpenAPI spec
173+
make build # build binary
174+
make test # run tests
175+
make lint # run golangci-lint
176+
```

cmd/attachments.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ var attachments_workflow_upload = &cobra.Command{
128128
apiErr.WriteJSON(c.Stderr)
129129
return &cferrors.AlreadyWrittenError{Code: cferrors.ExitError}
130130
}
131-
writer.Close()
131+
_ = writer.Close()
132132

133133
// Create HTTP request.
134134
req, err := http.NewRequestWithContext(cmd.Context(), "POST", fullURL, &buf)

cmd/attachments_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func TestAttachmentsUpload_MultipartAndHeaders(t *testing.T) {
193193
t.Errorf("failed to read body: %v", err)
194194
}
195195
w.Header().Set("Content-Type", "application/json")
196-
w.Write([]byte(`[{"id":"att-1","title":"report.pdf"}]`))
196+
_, _ = w.Write([]byte(`[{"id":"att-1","title":"report.pdf"}]`))
197197
}))
198198
defer srv.Close()
199199
setupAttachmentEnv(t, srv.URL)
@@ -271,7 +271,7 @@ func TestAttachmentsUpload_UsesSearchV1Domain(t *testing.T) {
271271
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
272272
capturedHost = r.Host
273273
w.Header().Set("Content-Type", "application/json")
274-
w.Write([]byte(`[{"id":"att-1"}]`))
274+
_, _ = w.Write([]byte(`[{"id":"att-1"}]`))
275275
}))
276276
defer srv.Close()
277277
setupAttachmentEnv(t, srv.URL)

0 commit comments

Comments
 (0)