coho-cli 2026.1.0a21__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. coho_cli-2026.1.0a21/.github/workflows/ci.yml +198 -0
  2. coho_cli-2026.1.0a21/.gitignore +226 -0
  3. coho_cli-2026.1.0a21/CHANGELOG.md +146 -0
  4. coho_cli-2026.1.0a21/CONTRIBUTING.md +95 -0
  5. coho_cli-2026.1.0a21/LICENSE +201 -0
  6. coho_cli-2026.1.0a21/Makefile +30 -0
  7. coho_cli-2026.1.0a21/PKG-INFO +96 -0
  8. coho_cli-2026.1.0a21/README.md +66 -0
  9. coho_cli-2026.1.0a21/SECURITY.md +27 -0
  10. coho_cli-2026.1.0a21/docs/authentication.md +113 -0
  11. coho_cli-2026.1.0a21/docs/ci.md +82 -0
  12. coho_cli-2026.1.0a21/docs/commands/account.md +27 -0
  13. coho_cli-2026.1.0a21/docs/commands/asset.md +64 -0
  14. coho_cli-2026.1.0a21/docs/commands/auth.md +70 -0
  15. coho_cli-2026.1.0a21/docs/commands/branch.md +32 -0
  16. coho_cli-2026.1.0a21/docs/commands/context.md +33 -0
  17. coho_cli-2026.1.0a21/docs/commands/diff.md +47 -0
  18. coho_cli-2026.1.0a21/docs/commands/entry.md +89 -0
  19. coho_cli-2026.1.0a21/docs/commands/env.md +64 -0
  20. coho_cli-2026.1.0a21/docs/commands/export.md +25 -0
  21. coho_cli-2026.1.0a21/docs/commands/index.md +41 -0
  22. coho_cli-2026.1.0a21/docs/commands/invite.md +48 -0
  23. coho_cli-2026.1.0a21/docs/commands/key.md +44 -0
  24. coho_cli-2026.1.0a21/docs/commands/member.md +21 -0
  25. coho_cli-2026.1.0a21/docs/commands/merge.md +62 -0
  26. coho_cli-2026.1.0a21/docs/commands/preview.md +24 -0
  27. coho_cli-2026.1.0a21/docs/commands/project.md +73 -0
  28. coho_cli-2026.1.0a21/docs/commands/ref.md +47 -0
  29. coho_cli-2026.1.0a21/docs/commands/role.md +41 -0
  30. coho_cli-2026.1.0a21/docs/commands/tag.md +64 -0
  31. coho_cli-2026.1.0a21/docs/commands/tier.md +34 -0
  32. coho_cli-2026.1.0a21/docs/commands/token.md +24 -0
  33. coho_cli-2026.1.0a21/docs/commands/trait.md +35 -0
  34. coho_cli-2026.1.0a21/docs/commands/type.md +49 -0
  35. coho_cli-2026.1.0a21/docs/commands/user.md +12 -0
  36. coho_cli-2026.1.0a21/docs/concepts.md +120 -0
  37. coho_cli-2026.1.0a21/docs/configuration.md +119 -0
  38. coho_cli-2026.1.0a21/docs/content-model.md +146 -0
  39. coho_cli-2026.1.0a21/docs/errors.md +101 -0
  40. coho_cli-2026.1.0a21/docs/index.md +32 -0
  41. coho_cli-2026.1.0a21/docs/install.md +60 -0
  42. coho_cli-2026.1.0a21/docs/quickstart.md +170 -0
  43. coho_cli-2026.1.0a21/pyproject.toml +101 -0
  44. coho_cli-2026.1.0a21/src/coho_cli/__init__.py +8 -0
  45. coho_cli-2026.1.0a21/src/coho_cli/_entry_ids.py +164 -0
  46. coho_cli-2026.1.0a21/src/coho_cli/_io.py +55 -0
  47. coho_cli-2026.1.0a21/src/coho_cli/_output.py +103 -0
  48. coho_cli-2026.1.0a21/src/coho_cli/_slugs.py +59 -0
  49. coho_cli-2026.1.0a21/src/coho_cli/_state.py +205 -0
  50. coho_cli-2026.1.0a21/src/coho_cli/_usage.py +367 -0
  51. coho_cli-2026.1.0a21/src/coho_cli/commands/__init__.py +1 -0
  52. coho_cli-2026.1.0a21/src/coho_cli/commands/account.py +159 -0
  53. coho_cli-2026.1.0a21/src/coho_cli/commands/asset.py +325 -0
  54. coho_cli-2026.1.0a21/src/coho_cli/commands/auth.py +385 -0
  55. coho_cli-2026.1.0a21/src/coho_cli/commands/branch.py +74 -0
  56. coho_cli-2026.1.0a21/src/coho_cli/commands/context.py +119 -0
  57. coho_cli-2026.1.0a21/src/coho_cli/commands/diffmerge.py +159 -0
  58. coho_cli-2026.1.0a21/src/coho_cli/commands/entry.py +297 -0
  59. coho_cli-2026.1.0a21/src/coho_cli/commands/env.py +188 -0
  60. coho_cli-2026.1.0a21/src/coho_cli/commands/export.py +65 -0
  61. coho_cli-2026.1.0a21/src/coho_cli/commands/invite.py +135 -0
  62. coho_cli-2026.1.0a21/src/coho_cli/commands/key.py +92 -0
  63. coho_cli-2026.1.0a21/src/coho_cli/commands/member.py +79 -0
  64. coho_cli-2026.1.0a21/src/coho_cli/commands/preview.py +70 -0
  65. coho_cli-2026.1.0a21/src/coho_cli/commands/project.py +157 -0
  66. coho_cli-2026.1.0a21/src/coho_cli/commands/ref.py +81 -0
  67. coho_cli-2026.1.0a21/src/coho_cli/commands/role.py +95 -0
  68. coho_cli-2026.1.0a21/src/coho_cli/commands/tag.py +76 -0
  69. coho_cli-2026.1.0a21/src/coho_cli/commands/tier.py +87 -0
  70. coho_cli-2026.1.0a21/src/coho_cli/commands/token.py +81 -0
  71. coho_cli-2026.1.0a21/src/coho_cli/commands/trait.py +150 -0
  72. coho_cli-2026.1.0a21/src/coho_cli/commands/type_.py +159 -0
  73. coho_cli-2026.1.0a21/src/coho_cli/commands/user.py +50 -0
  74. coho_cli-2026.1.0a21/src/coho_cli/main.py +304 -0
  75. coho_cli-2026.1.0a21/src/coho_cli/py.typed +0 -0
  76. coho_cli-2026.1.0a21/tests/conftest.py +70 -0
  77. coho_cli-2026.1.0a21/tests/test_asset.py +166 -0
  78. coho_cli-2026.1.0a21/tests/test_cli.py +770 -0
  79. coho_cli-2026.1.0a21/tests/test_entry_ids.py +373 -0
  80. coho_cli-2026.1.0a21/tests/test_slug_generation.py +127 -0
  81. coho_cli-2026.1.0a21/tests/test_usage.py +79 -0
  82. coho_cli-2026.1.0a21/uv.lock +913 -0
@@ -0,0 +1,198 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: ci-${{ github.ref }}
12
+ cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ env:
18
+ FORCE_COLOR: "1"
19
+
20
+ jobs:
21
+ test:
22
+ name: test · ${{ matrix.os }} · py${{ matrix.python }}
23
+ runs-on: ${{ matrix.os }}
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ os: [ubuntu-latest, macos-latest]
28
+ python: ["3.11", "3.12", "3.13"]
29
+ include:
30
+ - os: windows-latest
31
+ python: "3.13"
32
+ defaults:
33
+ run:
34
+ working-directory: coho-cli
35
+ steps:
36
+ - uses: actions/checkout@v7
37
+ with:
38
+ path: coho-cli
39
+ # Every commit and tag: the version is computed from them, and a shallow
40
+ # clone makes it a meaningless 0.1.dev1.
41
+ fetch-depth: 0
42
+
43
+ # The CLI develops against a sibling checkout of the library, which is what
44
+ # `[tool.uv.sources]` in pyproject.toml points at. A public repository needs no
45
+ # extra credential; a private one would need a PAT in `token:`.
46
+ - uses: actions/checkout@v7
47
+ with:
48
+ repository: coho-cms/coho-management-sdk-python
49
+ path: coho-management-sdk-python
50
+ # The library's release branch, not its default branch: this CLI needs
51
+ # the 2026.1 line, which lives there. Spelled out as a branch because the
52
+ # release tag v2026.1.0 will share its name.
53
+ ref: refs/heads/v2026.1.0
54
+ # The library's version comes from its tags. A shallow clone has none and
55
+ # builds 0.1.dev1, which the version range above refuses.
56
+ fetch-depth: 0
57
+
58
+ - name: Install uv
59
+ # An exact release: setup-uv stopped publishing floating major tags at v8,
60
+ # so `@v10` would not resolve.
61
+ uses: astral-sh/setup-uv@v10.2.0
62
+ with:
63
+ python-version: ${{ matrix.python }} # without this every job used the same Python
64
+ enable-cache: true
65
+ cache-dependency-glob: coho-cli/uv.lock
66
+
67
+ - name: Install the project
68
+ run: uv sync --locked
69
+
70
+ - name: Lint
71
+ run: uv run ruff check .
72
+
73
+ - name: Check formatting
74
+ run: uv run ruff format --check .
75
+
76
+ - name: Type check
77
+ run: uv run mypy
78
+
79
+ - name: Test
80
+ run: uv run pytest
81
+
82
+ build:
83
+ name: build distributions
84
+ needs: test
85
+ runs-on: ubuntu-latest
86
+ steps:
87
+ - uses: actions/checkout@v7
88
+ with:
89
+ # Every commit and tag: the version is computed from them, and a shallow
90
+ # clone makes it a meaningless 0.1.dev1.
91
+ fetch-depth: 0
92
+
93
+ - name: Install uv
94
+ # An exact release: setup-uv stopped publishing floating major tags at v8,
95
+ # so `@v10` would not resolve.
96
+ uses: astral-sh/setup-uv@v10.2.0
97
+ with:
98
+ enable-cache: true
99
+ cache-dependency-glob: uv.lock
100
+
101
+ - name: Refuse a dev-version tag
102
+ # Dev versions come from commits after a release (2026.1.0a2.dev3), never
103
+ # from tags. A tag like v2026.1.0.dev1 also makes every later build fail,
104
+ # because setuptools-scm cannot number a dev build of a dev build.
105
+ if: startsWith(github.ref, 'refs/tags/v')
106
+ run: |
107
+ if [[ "$GITHUB_REF_NAME" =~ [.-]?dev[0-9]*$ ]]; then
108
+ echo "::error::Don't tag dev versions: every commit after a release already builds one."
109
+ echo "Delete this tag (git push --delete origin $GITHUB_REF_NAME), or every later build fails."
110
+ exit 1
111
+ fi
112
+
113
+ # Build metadata comes from [project], never from [tool.uv.sources], so the
114
+ # wheel depends on the published coho-management-sdk even though development does not.
115
+ - name: Build the wheel and the sdist
116
+ run: uv build --out-dir dist
117
+
118
+ - name: Refuse a tag that is not a version in its standard form
119
+ # The version comes from the tag, but normalized to PEP 440: `v2026.1.0-rc.1`
120
+ # builds 2026.1.0rc1. A tag that does not match its own build would mislead
121
+ # anyone comparing the two, so the wheel's filename is the check.
122
+ if: startsWith(github.ref, 'refs/tags/v')
123
+ run: |
124
+ expected="dist/coho_cli-${GITHUB_REF_NAME#v}-py3-none-any.whl"
125
+ if [ ! -f "$expected" ]; then
126
+ echo "::error::tag $GITHUB_REF_NAME is not a version in PEP 440's standard form."
127
+ echo "Use forms like v2026.1.0, v2026.1.0a1, v2026.1.0b1 or v2026.1.0rc1. Built instead:"
128
+ ls -1 dist
129
+ exit 1
130
+ fi
131
+
132
+ - name: Check the packaging metadata
133
+ run: uvx twine check --strict dist/*.whl dist/*.tar.gz
134
+
135
+ - name: Upload the distributions
136
+ uses: actions/upload-artifact@v7
137
+ with:
138
+ name: coho-cli-dist
139
+ path: |
140
+ dist/*.whl
141
+ dist/*.tar.gz
142
+ if-no-files-found: error
143
+ retention-days: 90
144
+
145
+ release:
146
+ name: attach to the GitHub release
147
+ if: startsWith(github.ref, 'refs/tags/v')
148
+ needs: build
149
+ runs-on: ubuntu-latest
150
+ permissions:
151
+ contents: write
152
+ steps:
153
+ - name: Download the distributions
154
+ uses: actions/download-artifact@v8
155
+ with:
156
+ name: coho-cli-dist
157
+ path: dist
158
+
159
+ - name: Create or update the release
160
+ env:
161
+ GH_TOKEN: ${{ github.token }}
162
+ run: |
163
+ # An alpha, beta or release candidate is a pre-release on GitHub too, so
164
+ # it never shows as the latest release.
165
+ prerelease=""
166
+ if [[ "$GITHUB_REF_NAME" =~ (a|b|rc)[0-9]+$ ]]; then
167
+ prerelease="--prerelease"
168
+ fi
169
+ if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
170
+ gh release upload "$GITHUB_REF_NAME" dist/*.whl dist/*.tar.gz --repo "$GITHUB_REPOSITORY" --clobber
171
+ else
172
+ gh release create "$GITHUB_REF_NAME" dist/*.whl dist/*.tar.gz \
173
+ --repo "$GITHUB_REPOSITORY" \
174
+ --title "coho-cli $GITHUB_REF_NAME" \
175
+ --generate-notes \
176
+ --verify-tag $prerelease
177
+ fi
178
+
179
+ publish-pypi:
180
+ name: publish to PyPI
181
+ # Skipped unless the repository variable PUBLISH_TO_PYPI is "true". See CONTRIBUTING.md.
182
+ if: startsWith(github.ref, 'refs/tags/v') && vars.PUBLISH_TO_PYPI == 'true'
183
+ needs: build
184
+ runs-on: ubuntu-latest
185
+ environment:
186
+ name: pypi
187
+ url: https://pypi.org/project/coho-cli/
188
+ permissions:
189
+ id-token: write
190
+ steps:
191
+ - name: Download the distributions
192
+ uses: actions/download-artifact@v8
193
+ with:
194
+ name: coho-cli-dist
195
+ path: dist
196
+
197
+ - name: Publish
198
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,226 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # coho-cli
221
+ .venv/
222
+ .mypy_cache/
223
+ .ruff_cache/
224
+ .pytest_cache/
225
+ *.ndjson
226
+ *.tar
@@ -0,0 +1,146 @@
1
+ # Changelog
2
+
3
+ All notable changes to `coho-cli`. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses
5
+ [semantic versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - `coho tier move ID --ord N` (coho-data doc 11, M6-15): moves a tier in the pipeline, and
12
+ the tiers it passes shift by one. Needs coho-management-sdk with `tiers.move`.
13
+ - `coho trait list`, `get`, `put` and `delete` (coho-data doc 05 §10): traits, named groups
14
+ of fields that types take by naming them in `traits`. `trait list` shows the types that
15
+ take each. A refused trait or type change now prints what it reaches, `reaches: page,
16
+ servicePage, and 12 entries`, and a trait in use is refused with the types that take it.
17
+ - `coho type get` shows the type it extends, the traits it takes, and whether it is
18
+ abstract (coho-data doc 05 §9).
19
+
20
+ ### Changed
21
+
22
+ - `coho tier create --ord N` inserts: a position another tier holds moves that tier and the
23
+ ones after it along, where it was refused with `TIER_ORDER_TAKEN`, and anything past the
24
+ end is the end (coho-data doc 11, M6-15). `coho tier delete` closes the gap it leaves.
25
+
26
+ ### Fixed
27
+
28
+ - An `@slug` in a field a type inherits, or takes from a trait, is written as the id it
29
+ names: `coho entry create -t photoGallery --set author=@tom-okafor` stored the text
30
+ `@tom-okafor`, because the slug was looked up only in the fields the type declares itself.
31
+ - Component types (coho-data doc 40 §14.10): `coho type put` takes a definition with
32
+ `"kind": "component"`, and `type list`, `get` and `put` show each type's `KIND`. A
33
+ repeater takes its items as a JSON list, `--set 'citations=[{…}]'`, and the server
34
+ assigns each item's `_id` and, where the repeater holds one component, its `_type`.
35
+ Needs coho-management-sdk with `ContentType.kind` and a server with component types.
36
+
37
+ - `coho project usage [PROJECT]` and `coho account usage`: metered usage (coho-data doc
38
+ 41), as a table or, with `-o json`, the report exactly as the server sent it. A
39
+ project's shows delivery by key with requests per distinct URL (the client that is not
40
+ caching), management by people and project tokens and by class, content, history and
41
+ assets; anyone who can read the project may, project tokens included. The account's
42
+ sums every project and lists each one's totals, for admins: a member's `FORBIDDEN`
43
+ says to use `coho project usage` instead. Both take `--grain hour|day`, `--from`,
44
+ `--to`, `--meter` (repeatable) and `--periods` for a line per hour or day. Needs
45
+ coho-management-sdk with `usage()` and a server with the usage routes.
46
+
47
+ - `coho preview entries` ends the table with `more: --offset N` when another page
48
+ follows, as `entry list` does. It has no `--all`, because each live page resolves the
49
+ whole ref.
50
+ - `@SLUG` wherever a command takes an entry id: `entry get`, `put`, `delete`, `history`,
51
+ `references` and `preview entry`. The slug is looked up on the same ref with one
52
+ request (the BFF's live `preview/{ref}/entries-by-slug/{slug}`), then the command runs
53
+ as if the id had been typed; a plain id is used as given. A slug nothing has is
54
+ `ENTRY_NOT_FOUND` (exit 5); one that names a type or an asset, or `@` before something
55
+ that is not a slug, is a usage error. `entry delete @SLUG` asks about both the slug and
56
+ the id it found. `entry list` and `entry get` show the slug from `_slug` while the
57
+ server's entry responses leave out `slug`.
58
+ - `@SLUG` in a reference field's `--set` value, on `entry create` and `entry put`:
59
+ `--set author=@priya-shah --set 'tags=["@traffic"]'` writes the ids those slugs name
60
+ on the same ref. Only fields the content type declares as references are looked up, so
61
+ text keeps a leading `@`; the type is read only when a value starts with `@`.
62
+ - `@SLUG` in an asset field's `--set` value too: `--set image=@culvert` writes the id of
63
+ the asset with that slug, from one listing of the ref's assets. A slug no asset has
64
+ is `ASSET_NOT_FOUND` (exit 5).
65
+ - `coho ref switch NAME`: makes a branch, tag or environment of the current project the
66
+ current ref, after checking it exists, keeping the account and project. It and
67
+ `coho branch create` warn when `--ref` or `COHO_REF` still overrides the saved ref.
68
+ - `coho asset`: files on the current ref (coho-data doc 40). `asset upload FILE…` hashes
69
+ each file, sends it straight to object storage on a presigned URL and writes it as an
70
+ asset, building the slug from the filename (`Hero Shot.jpg` → `hero-shot`) unless
71
+ `--slug` is given for a single file; `--title`, `--alt`, `--focal x,y` and `--tag` set
72
+ its metadata. Bytes the project already holds are not sent again. Also `asset list`,
73
+ `get`, `url` (prints the original, or a resized image URL with `--width`, `--height`,
74
+ `--fit`, `--format`, `--quality`, centring fills on the focal point), `put` and
75
+ `delete`, each taking an id or a slug. Upload warns on stderr that asset URLs are
76
+ public to anyone holding them. Needs the `coho-management-sdk` that adds `ref.assets`,
77
+ and a server with the asset routes.
78
+ - On PyPI: `uv tool install coho-cli` or `pipx install coho-cli`.
79
+ - `coho login` finishes setting up an identity that has no organization yet: it opens the
80
+ BFF's sign-in in the browser, which asks for the organization's name, and waits until the
81
+ account exists. That sign-in asks for the password once more, because the CLI's own sign-in
82
+ never reaches the BFF. `coho login --token` with such an identity says where to go instead.
83
+ - An account saved with `coho use` that is not one of yours any more — deleted, or
84
+ remembered from before an environment was reset — gives way to your only account, which
85
+ is saved in its place (with the stale project and ref cleared) and named in a one-time
86
+ warning. With several accounts it asks which. Costs one `/me` per command while an
87
+ account is saved. `--account` and `COHO_ACCOUNT` are used as given.
88
+ - `coho type put` and `coho entry create` build the slug from the name when none is given:
89
+ camelCase for a type (`Blog post` → `blogPost`), kebab-case for an entry (`Hello, World!` →
90
+ `hello-world`). An explicit slug is sent unchanged.
91
+ - `coho tag create --validate` has the server check every entry at the tag point before
92
+ cutting the tag. If any fails, it is refused with `TAG_CONTENT_INVALID` and nothing is
93
+ tagged: the CLI prints how many entries fail, then each listed one's slug, type, id and
94
+ field errors, says how many went unlisted past the server's 100, and exits 1. With
95
+ `-o json` the problem document goes to stdout. Without the flag, tagging is unchanged.
96
+ Needs the `coho-management-sdk` that adds `tags.create(validate=…)`, and a server that
97
+ accepts `validate`.
98
+
99
+ ### Changed
100
+
101
+ - `coho branch create` makes the new branch the current ref, as `coho project create`
102
+ makes the new project current; `--no-use` keeps the current ref. When `--ref` or
103
+ `COHO_REF` is set, it warns that the override still wins over the saved ref.
104
+ - `coho signup` opens the BFF's sign-up page in the browser, as `coho login` opens the
105
+ sign-in page, and makes no API call itself. Sign-up stays an interactive session;
106
+ before release that page verifies a captcha.
107
+ - `coho login` uses `http://localhost:8765/callback`, matching the CLI's Cognito app
108
+ client, and answers on both loopback addresses.
109
+
110
+ - The library moved to its own repository and distribution,
111
+ [coho-management-sdk-python](https://github.com/coho-cms/coho-management-sdk-python), along with the API
112
+ contracts and the library reference. This repository is now the command alone, and
113
+ depends on `coho-management-sdk` as an ordinary package. Development uses a sibling checkout;
114
+ see [CONTRIBUTING.md](CONTRIBUTING.md).
115
+
116
+ ### Fixed
117
+
118
+ - `coho preview entries` crashed with `AttributeError: 'str' object has no attribute
119
+ 'get'`, in table and JSON output both. It read the body as a list of entries, but
120
+ delivery answers with an `EntryGraph`: it now lists `roots` in order, looked up in
121
+ `entries`.
122
+ - `coho signup`, `coho invite lookup` and `coho invite accept` failed with
123
+ `NOT_LOGGED_IN` for anyone without a login, who are exactly the people they are for.
124
+ - Errors printed their code twice, as in `error NOT_LOGGED_IN: NOT_LOGGED_IN: …`.
125
+ - URLs printed by `--no-browser` were wrapped at the terminal's width, so a copied URL
126
+ came out broken.
127
+ - `coho login` followed a successful sign-in with a bare `UNAUTHENTICATED` when the
128
+ identity had no Coho account yet. It now says so, and what to run.
129
+
130
+ ## [0.1.0] — unreleased
131
+
132
+ Initial version.
133
+
134
+ - Commands: `login`, `logout`, `whoami`, `signup`, `configure`, `profile`, `use`,
135
+ `status`, `account`, `member`, `invite`, `user`, `project`, `role`, `ref`, `branch`,
136
+ `tag`, `type`, `entry`, `diff`, `merge`, `env`, `promote`, `rollback`, `tier`, `key`,
137
+ `export`, `preview`.
138
+ - A sticky context (account, project, ref) per profile, verified against the server
139
+ before it is saved, and overridable by flag or environment variable.
140
+ - `--output json` prints the contract's response verbatim, so `jq` works against the
141
+ documented shapes. Exit codes distinguish usage (2), authentication (3), a failed
142
+ precondition worth retrying (4) and not-found (5).
143
+ - A local project registry, because the server has no project listing yet.
144
+
145
+ [Unreleased]: https://github.com/coho-cms/coho-cli/compare/v0.1.0...HEAD
146
+ [0.1.0]: https://github.com/coho-cms/coho-cli/releases/tag/v0.1.0
@@ -0,0 +1,95 @@
1
+ # Contributing
2
+
3
+ ## Setup
4
+
5
+ The CLI is a thin layer over [`coho-management-sdk`](https://github.com/coho-cms/coho-management-sdk-python),
6
+ and develops against a sibling checkout of it:
7
+
8
+ ```bash
9
+ git clone https://github.com/coho-cms/coho-management-sdk-python
10
+ git clone https://github.com/coho-cms/coho-cli
11
+ cd coho-cli
12
+ uv sync # resolves coho-management-sdk from ../coho-management-sdk-python
13
+ uv run coho --help
14
+ uv run pytest # 22 tests, no network, no server
15
+ uv run ruff check . && uv run ruff format --check .
16
+ uv run mypy # strict
17
+ ```
18
+
19
+ Or `make check` for all of it.
20
+
21
+ ### `coho` as a real command
22
+
23
+ To use your working copy as the installed `coho`, from any directory:
24
+
25
+ ```bash
26
+ uv tool install --editable . --with-editable ../coho-management-sdk-python
27
+ ```
28
+
29
+ The command lands in `~/.local/bin` and runs the source in both repositories, so
30
+ edits take effect at once. Reinstall, by adding `--force`, only when dependencies or
31
+ the entry point change; `uv tool uninstall coho-cli` removes it. It shares the
32
+ profiles and stored logins in `~/.config/coho` with `uv run coho`. `uv sync --no-sources` builds against the published
33
+ `coho-management-sdk` instead, which is what a user gets; see `[tool.uv.sources]` in
34
+ `pyproject.toml`.
35
+
36
+ ## Layout
37
+
38
+ ```
39
+ src/coho_cli/
40
+ main.py the typer app, global options, error → exit code
41
+ _state.py config/profile/client/context resolution for commands
42
+ _output.py tables, JSON, shown-once secrets
43
+ _io.py --file and --set parsing
44
+ commands/ one module per noun
45
+ tests/ the whole command tree against the SDK's FakeBff
46
+ docs/ guides and the command reference
47
+ ```
48
+
49
+ ⚠️ **Push the SDK first.** CI builds the CLI against the SDK's `v2026.1.0` branch on GitHub,
50
+ not your local copy. A CLI change that uses new SDK code fails CI with
51
+ `"Coho" has no attribute …` until that SDK code is pushed; then re-run the CLI's
52
+ workflow.
53
+
54
+ The library, the API contracts and the library reference live in
55
+ [coho-management-sdk-python](https://github.com/coho-cms/coho-management-sdk-python).
56
+
57
+ ## Rules of the road
58
+
59
+ 1. **The library owns behaviour; the CLI owns presentation.** If a command needs logic
60
+ that is not printing or argument parsing, it belongs in `coho_management_sdk` — open a pull
61
+ request there and call it from here. Resist the urge to reimplement it locally.
62
+ 2. **Errors print their `code`.** `main.py` maps `CohoError` to an exit code and a
63
+ one-line message; never catch an error just to reword it.
64
+ 3. **Secrets go to stdout alone**, with the warning on stderr, so
65
+ `coho key create > key.txt` captures the key and nothing else.
66
+ 4. **`--output json` prints the contract's response**, not a reshaped version of it.
67
+ Every model in the SDK keeps `.raw` for exactly this.
68
+ 5. **Tests run against `coho_management_sdk.testing.FakeBff`**, never a live server. A behaviour
69
+ that needs a new route belongs in the fake, which means a pull request against the
70
+ library.
71
+
72
+ ## Releasing
73
+
74
+ The version comes from git tags (`hatch-vcs`); nothing in the source holds it.
75
+ Tag `v2026.1.0` to release, `v2026.1.0a1`, `b1` or `rc1` for a pre-release. Every
76
+ commit after a tag builds a dev version such as `2026.1.0a2.dev3`. ⚠️ Never tag a dev
77
+ version: CI refuses it, and every later build would fail.
78
+
79
+ 1. Update the `coho-management-sdk` constraint if the CLI needs a newer library, and add
80
+ a `CHANGELOG.md` entry.
81
+ 2. Commit and push, then tag: `git tag v2026.1.0a4 && git push origin v2026.1.0a4`.
82
+ 3. CI tests, builds a wheel and an sdist, fails if the tag is not a version in its
83
+ standard form, uploads both as a build artifact, attaches them to a GitHub
84
+ Release (a pre-release for an alpha, beta or release candidate), and publishes
85
+ them to [PyPI](https://pypi.org/project/coho-cli/).
86
+
87
+ ⚠️ **Release `coho-management-sdk` first**, and wait until it is on PyPI: a `coho-cli` that
88
+ depends on a library version nobody can install is not installable either. PyPI never
89
+ takes the same version's files twice, even after a delete, so a broken release is fixed
90
+ by the next tag.
91
+
92
+ The `publish-pypi` job authenticates as a [trusted publisher](https://docs.pypi.org/trusted-publishers/)
93
+ with a short-lived OIDC token, so no API token is stored anywhere, and deploys through
94
+ the `pypi` environment, which only `v*` tags can use. Setting the repository variable
95
+ `PUBLISH_TO_PYPI` to anything but `true` turns it off.