Files
2026-08-22 10:12:22 +02:00

223 lines
7.4 KiB
Markdown

# cmsctl
`cmsctl` is a dependency-free Go CLI for the platform's backoffice APIs. It
uses the existing RFC 8628 device-login flow and stores the resulting bearer
token **together with the instance base URL** in:
- `$CMSCTL_CONFIG`, when set
- `$XDG_CONFIG_HOME/cmsctl/config.json`, when set
- the platform config directory from Go's `os.UserConfigDir()` otherwise:
- Unix/Linux: `~/.config/cmsctl/config.json`
- macOS: `~/Library/Application Support/cmsctl/config.json`
- Windows: `%AppData%\\cmsctl\\config.json`
The persisted JSON uses `base_url` for the instance URL and `access_token` for
the device token.
The file and its parent directory are created with restrictive permissions
(`0700`/`0600`). The token is never printed during login; login progress goes to
stderr so stdout remains pipe-safe.
## Build and run
From `platform/`:
```bash
go build -o cmsctl ./cmd/cmsctl
go run ./cmd/cmsctl --help
```
The default backoffice URL is `http://localhost:8080`.
## Authenticate
```bash
cmsctl auth login --scope read,write
# Open the displayed verification URL and approve the code as an admin.
cmsctl auth status
cmsctl auth token --raw | secret-tool store --label=cmsctl token
cmsctl auth logout
```
The token can also be supplied per invocation with `--token` or
`CMSCTL_TOKEN`; a minted API key can be supplied with `--api-key` or
`CMSCTL_API_KEY`.
Global connection flags must appear before the command:
```bash
cmsctl --base-url https://shop.example cms nodes list --type page
cmsctl --finder-url http://finder:8080 finder search --q drill
cmsctl finder import inventory.json
```
When `--finder-url` is omitted, finder reads use `<base-url>/find`, which is the
backoffice proxy. Finder administration always uses `<base-url>/catalog/admin`.
The CLI intentionally has no separate finder-admin URL: the backoffice is the
writeradmin boundary.
## Todos and suggestions
List, filter, and complete improvement notes filed via the Suggest button or
API:
```bash
cmsctl todos list # all todos in triage sort (open first)
cmsctl todos list --open # open / uncompleted only (--done=false)
cmsctl todos list --done # completed only (--done=true)
cmsctl todos list --prio high --q "checkout" # filter by priority and search text
cmsctl todos done NODE_ID # mark completed (sets data.done = true)
cmsctl todos reopen NODE_ID # reopen (sets data.done = false)
cmsctl todos get NODE_ID # get todo node
cmsctl todos create --note "Fix cart layout" --prio high --location "Commerce · Cart"
cmsctl todos delete NODE_ID --yes
```
## CMS
```bash
cmsctl cms nodes list --type page --limit 100
cmsctl cms nodes get NODE_ID
cmsctl cms search --q "summer sale" --type page
cmsctl cms types
cmsctl cms nodes create \
--type page \
--data '{"title":"From the CLI"}' \
--tags published,homepage
cmsctl cms nodes update NODE_ID \
--data @replacement-data.json \
--tags published
```
`PUT /api/nodes/{id}` replaces the full `data` object. Use `cmsctl cms nodes
get` first and merge fields before updating, or use `--input` with a complete
`{"data":...,"tags":...}` request body. For a safe partial update, use the
native merge endpoint:
```bash
cmsctl cms nodes patch NODE_ID --data '{"data":{"title":"New"}}'
cmsctl cms nodes delete NODE_ID --yes
cmsctl cms nodes retype NODE_ID --type article
```
Graph and editorial operations are available directly:
```bash
cmsctl cms edges get NODE_ID
cmsctl cms edges create --from PAGE_ID --to BLOCK_ID --type child --sort 0
cmsctl cms edges reorder PAGE_ID --order EDGE_ID_1,EDGE_ID_2
cmsctl cms tree PAGE_ID --depth 3
cmsctl cms nav --data-type page
cmsctl cms graph --root PAGE_ID --budget 250 --edges child,ref
cmsctl cms publish PAGE_ID OTHER_PAGE_ID
cmsctl cms regenerate --type page
```
Schemas and templates can be inspected or edited in development mode. Writes
accept a file or `-` on stdin; destructive deletes require `--yes`:
```bash
cmsctl cms schemas list
cmsctl cms schemas get page > page.schema.json
cmsctl cms schemas put page page.schema.json
cmsctl cms templates get page.tmpl
cat page.tmpl | cmsctl cms templates put page -
cmsctl cms blueprints list
cmsctl cms blueprints create --blueprint landing --name "New landing"
```
The script command exposes the server-side CMS scripting API for atomic or
multi-step operations while retaining pipe-safe JSON output:
```bash
cmsctl cms script --script 'result(cms.listNodes({type:"todo",limit:0}))'
cat script-request.json | cmsctl cms script --input -
```
Bulk CMS import accepts the native `{nodes,edges}` request shape:
```bash
cmsctl cms import data/import.json
cat data/import.json | cmsctl cms import -
```
## Finder and inventory
Finder search emits the reader's JSONL response unchanged: product lines are
followed by the final search metadata line. This makes it easy to use with
`jq`, `awk`, or another streaming consumer.
```bash
cmsctl finder search --q drill --limit 20 --sort price_asc | jq -c .
cmsctl finder search --q window --filters '{"string":[{"id":"brand","value":["Dala"]}]}'
cmsctl finder get 861201
cmsctl finder get ABC-123 # SKU lookup is supported by finder
cmsctl finder config --stock in_stock
cmsctl finder facets --q drill
cmsctl finder facet-list
cmsctl finder values brand
cmsctl finder suggest --q dr
cmsctl finder popular
cmsctl finder related 861201
cmsctl finder compatible 861201 --limit 20
cmsctl finder compatible 861201 --cart-input cart-item-ids.json
cmsctl finder similar 861201
cmsctl finder relation-groups
cmsctl finder facet-groups
```
To materialize a bounded list of catalog items, pipe one numeric ID per line:
```bash
printf '861201\\n861202\\n' | cmsctl finder stream-items -
```
`finder search` also exposes the reader's important request controls:
`--filters`, `--stock`, `--sort`, `--mode`, `--disable-grouping`,
`--skip-facets`, and `--skip-tracking`. `--filters` is the JSON object sent as
the reader's `string`/`range` filter fields.
Writer administration is explicitly routed through the backoffice writeradmin
boundary (`<base-url>/catalog/admin`), never directly through `--finder-url`:
```bash
cmsctl finder admin settings get
cmsctl finder admin settings put settings.json
cmsctl finder admin facets list
cmsctl finder admin facets create facet.json
cmsctl finder admin facets update brand facet.json
cmsctl finder admin facets delete obsolete --yes
cmsctl finder admin update-by-filter update-request.json
cmsctl finder admin delete-by-filter filter.json --yes
```
The filter update body follows the writer contract:
`{"filter":{"string":[...] ,"range":[...]},"update":{...}}`. Filter
operations reject empty filters server-side; deletion also requires `--yes` in
the CLI.
Inventory/catalog imports accept either the writer's JSON array or one partial
item update per JSONL line. The CLI normalizes JSONL to the writer's batch
request before sending it to `POST /catalog/admin/items/batch`:
```bash
cat inventory.json | cmsctl finder import -
cat inventory.jsonl | cmsctl inventory import -
jq -c '.[]' products.json | cmsctl finder import -
```
Each finder import item follows the writer contract. For example:
```json
{"id":861201,"title":"New title","values":{"2":"Intel"}}
{"id":99000001,"buyable":true,"saleStatus":"ACT","values":{"4":50000}}
```
Updates are partial and merged by the finder writer; a `delete: true` item is a
catalog deletion. The persisted device token needs write authority for CMS and
finder mutations.