deepcell-cli 0.6.1__tar.gz → 0.6.3__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.
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/PKG-INFO +9 -7
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/README.md +8 -6
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/pyproject.toml +5 -7
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/__init__.py +1 -1
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/capabilities.py +21 -5
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/capability-contract.json +18 -18
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/client.py +10 -1
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/help_cmd.py +23 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/sync.py +6 -62
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/version.py +73 -2
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/config.py +1 -1
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/main.py +25 -4
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/mcp_server.py +8 -6
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/oauth_provider.py +12 -4
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/stages.py +4 -1
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/surface.py +17 -6
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/upgrade_check.py +1 -1
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli.egg-info/PKG-INFO +9 -7
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_config.py +1 -1
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_mcp_auth.py +11 -11
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_mcp_server.py +32 -3
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_stages.py +25 -6
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_upgrade_check.py +10 -10
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/setup.cfg +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/__main__.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/_findings.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/__init__.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/_batch_input.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/_datatypes.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/_negative_args.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/_swapped_args.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/_version_display.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/_write_opts.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/account.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/auth.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/changes.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/deck.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/defs.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/describe.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/doc.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/doctor.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/download.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/edit.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/example.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/export.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/export_docx.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/export_pdf.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/export_pptx.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/files.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/grep.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/guide.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/impact.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/import_cmd.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/ingest.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/merge.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/query.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/reasoning.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/ref.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/replace.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/rules.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/share.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/upgrade.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/variant.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/viewer.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/commands/workspace.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/context.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/errors.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/logging_setup.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/output.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/revision.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/sync_state.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli/xml_replace.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli.egg-info/SOURCES.txt +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli.egg-info/dependency_links.txt +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli.egg-info/entry_points.txt +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli.egg-info/requires.txt +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/src/deepcell_cli.egg-info/top_level.txt +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_anon.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_claim_strength.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_client.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_client_surface_header.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_conflict_reason.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_deck_help_contract.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_deck_op_shape.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_docs_catalog.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_doctor.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_download.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_findings_provenance.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_guide_eval_a4_a7.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_guide_flag_drift.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_help_flag_drift.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_impact_commands.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_ingest_command.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_logging_setup.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_op_reachability.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_openapi_route_parity.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_output_export_notes.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_output_keeps_the_answer.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_reasoning_commands.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_reasoning_diff_command.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_ref_flag_drift.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_round6_signal_regressions.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_search_reach.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_shed_retry.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_surface_examples.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_surface_walker.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_version_parity.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_write_message_rule.py +0 -0
- {deepcell_cli-0.6.1 → deepcell_cli-0.6.3}/tests/test_xml_replace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepcell-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: Build and revise .deepcell documents from a terminal — the reasoning, the calculations, the document and the deck, with the links between them
|
|
5
5
|
Author-email: DeepCell <hello@deepcell.net>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -36,12 +36,12 @@ Command-line interface for **DeepCell** — a versionable `.deepcell` document t
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
# macOS / Linux
|
|
39
|
-
curl -LsSf https://
|
|
39
|
+
curl -LsSf https://deepcell.net/install.sh | sh
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
```powershell
|
|
43
43
|
# Windows (PowerShell)
|
|
44
|
-
irm https://
|
|
44
|
+
irm https://deepcell.net/install.ps1 | iex
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
```bash
|
|
@@ -166,8 +166,8 @@ deepcell defs apply model.deepcell --ops ops.json # many ops, atomically
|
|
|
166
166
|
| Document surface | `doc` — outline, blocks, body, links, lint |
|
|
167
167
|
| Deck surface | `deck` — slides, ordering, binding, branding |
|
|
168
168
|
| Change review | `changes list / diff / revert`, `impact` |
|
|
169
|
-
| Version control | `log`, `diff`, `restore` |
|
|
170
|
-
| Sync | `clone`, `status`, `pull`, `push`, `
|
|
169
|
+
| Version control | `log`, `diff`, `restore`, `commit` |
|
|
170
|
+
| Sync (people, not agents) | `clone`, `status`, `pull`, `push`, `merge` |
|
|
171
171
|
| Import / export | `import`, `ingest`, `to-excel`, `to-docx`, `to-pptx`, `to-pdf` |
|
|
172
172
|
| Reasoning | `reasoning`, `reasoning-diff`, `claim`, `assumption` |
|
|
173
173
|
| Delivery | `viewer`, `share create / list / revoke` |
|
|
@@ -218,7 +218,9 @@ deepcell push -m "Updated assumptions" # upload
|
|
|
218
218
|
deepcell pull # fetch cloud changes
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
-
A `.deepcell/` metadata directory tracks the project link and sync state. `push` refuses to clobber cloud changes — it asks you to `pull` first. See `deepcell
|
|
221
|
+
A `.deepcell/` metadata directory tracks the project link and sync state. `push` refuses to clobber cloud changes — it asks you to `pull` first. See `deepcell help clone`.
|
|
222
|
+
|
|
223
|
+
Sync moves *bytes*, not the commands that read them: `query`, `describe --lint`, `cat` and every other read address the project on the server, so inside a clone they answer from the cloud's HEAD and not from the file you just edited. `push` first, then read back. Sync is a human workflow for this reason and is unavailable over MCP.
|
|
222
224
|
|
|
223
225
|
## Import & export
|
|
224
226
|
|
|
@@ -249,7 +251,7 @@ Interactive and sync commands (`login`, `clone`, `push`, …) are blocked inside
|
|
|
249
251
|
|
|
250
252
|
| Variable | Purpose | Default |
|
|
251
253
|
|----------|---------|---------|
|
|
252
|
-
| `DEEPCELL_API_URL` | API endpoint | `https://
|
|
254
|
+
| `DEEPCELL_API_URL` | API endpoint | `https://deepcell.net/api/jingwei` |
|
|
253
255
|
| `DEEPCELL_PROJECT` | Override active project | _(none)_ |
|
|
254
256
|
| `DEEPCELL_ACCESS_TOKEN` | Auth token for CI/headless use | _(none)_ |
|
|
255
257
|
| `DEEPCELL_CONFIG` | Alternate `config.json` path (session-scoped) | `~/.deepcell/config.json` |
|
|
@@ -4,12 +4,12 @@ Command-line interface for **DeepCell** — a versionable `.deepcell` document t
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
# macOS / Linux
|
|
7
|
-
curl -LsSf https://
|
|
7
|
+
curl -LsSf https://deepcell.net/install.sh | sh
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
```powershell
|
|
11
11
|
# Windows (PowerShell)
|
|
12
|
-
irm https://
|
|
12
|
+
irm https://deepcell.net/install.ps1 | iex
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
```bash
|
|
@@ -134,8 +134,8 @@ deepcell defs apply model.deepcell --ops ops.json # many ops, atomically
|
|
|
134
134
|
| Document surface | `doc` — outline, blocks, body, links, lint |
|
|
135
135
|
| Deck surface | `deck` — slides, ordering, binding, branding |
|
|
136
136
|
| Change review | `changes list / diff / revert`, `impact` |
|
|
137
|
-
| Version control | `log`, `diff`, `restore` |
|
|
138
|
-
| Sync | `clone`, `status`, `pull`, `push`, `
|
|
137
|
+
| Version control | `log`, `diff`, `restore`, `commit` |
|
|
138
|
+
| Sync (people, not agents) | `clone`, `status`, `pull`, `push`, `merge` |
|
|
139
139
|
| Import / export | `import`, `ingest`, `to-excel`, `to-docx`, `to-pptx`, `to-pdf` |
|
|
140
140
|
| Reasoning | `reasoning`, `reasoning-diff`, `claim`, `assumption` |
|
|
141
141
|
| Delivery | `viewer`, `share create / list / revoke` |
|
|
@@ -186,7 +186,9 @@ deepcell push -m "Updated assumptions" # upload
|
|
|
186
186
|
deepcell pull # fetch cloud changes
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
A `.deepcell/` metadata directory tracks the project link and sync state. `push` refuses to clobber cloud changes — it asks you to `pull` first. See `deepcell
|
|
189
|
+
A `.deepcell/` metadata directory tracks the project link and sync state. `push` refuses to clobber cloud changes — it asks you to `pull` first. See `deepcell help clone`.
|
|
190
|
+
|
|
191
|
+
Sync moves *bytes*, not the commands that read them: `query`, `describe --lint`, `cat` and every other read address the project on the server, so inside a clone they answer from the cloud's HEAD and not from the file you just edited. `push` first, then read back. Sync is a human workflow for this reason and is unavailable over MCP.
|
|
190
192
|
|
|
191
193
|
## Import & export
|
|
192
194
|
|
|
@@ -217,7 +219,7 @@ Interactive and sync commands (`login`, `clone`, `push`, …) are blocked inside
|
|
|
217
219
|
|
|
218
220
|
| Variable | Purpose | Default |
|
|
219
221
|
|----------|---------|---------|
|
|
220
|
-
| `DEEPCELL_API_URL` | API endpoint | `https://
|
|
222
|
+
| `DEEPCELL_API_URL` | API endpoint | `https://deepcell.net/api/jingwei` |
|
|
221
223
|
| `DEEPCELL_PROJECT` | Override active project | _(none)_ |
|
|
222
224
|
| `DEEPCELL_ACCESS_TOKEN` | Auth token for CI/headless use | _(none)_ |
|
|
223
225
|
| `DEEPCELL_CONFIG` | Alternate `config.json` path (session-scoped) | `~/.deepcell/config.json` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "deepcell-cli"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.3"
|
|
4
4
|
description = "Build and revise .deepcell documents from a terminal — the reasoning, the calculations, the document and the deck, with the links between them"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -42,12 +42,10 @@ deepcell-mcp = "deepcell_cli.mcp_server:main"
|
|
|
42
42
|
# deliberately no Repository or Issues entry: the CLI is developed in a private
|
|
43
43
|
# repository, and a link to one nobody can open is worse than no link.
|
|
44
44
|
#
|
|
45
|
-
# deepcell.net is the canonical
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# they stay on beta.deepcell.net, because a curl that 404s is a broken install
|
|
50
|
-
# today rather than a link that is briefly grey.
|
|
45
|
+
# deepcell.net is the canonical origin and serves the site. beta.deepcell.net
|
|
46
|
+
# answers the same deployment and is deliberately left un-redirected, because
|
|
47
|
+
# every CLI already installed has it baked in as its API default — but it is
|
|
48
|
+
# not what anything new should hand out.
|
|
51
49
|
[project.urls]
|
|
52
50
|
Homepage = "https://deepcell.net"
|
|
53
51
|
Documentation = "https://deepcell.net/product/cli"
|
|
@@ -33,6 +33,7 @@ MCP_ALLOWED_COMMANDS = frozenset(
|
|
|
33
33
|
"cell-meta",
|
|
34
34
|
"changes",
|
|
35
35
|
"claim",
|
|
36
|
+
"commit",
|
|
36
37
|
"deck",
|
|
37
38
|
"defs",
|
|
38
39
|
"describe",
|
|
@@ -61,13 +62,23 @@ MCP_ALLOWED_COMMANDS = frozenset(
|
|
|
61
62
|
"rm",
|
|
62
63
|
"rules",
|
|
63
64
|
"share",
|
|
64
|
-
"variant",
|
|
65
65
|
"viewer",
|
|
66
66
|
"whoami",
|
|
67
|
+
# `workspace` is the pre-rename spelling of `project` — a hidden alias
|
|
68
|
+
# of the same Group. Hidden is not unreachable: `_parse_command`
|
|
69
|
+
# dispatches through Click, which runs a hidden command like any other,
|
|
70
|
+
# so the alias needs a policy of its own or it is reachable with none
|
|
71
|
+
# declared. It carries `project`'s policy because it IS `project`.
|
|
72
|
+
"workspace",
|
|
67
73
|
"write",
|
|
68
74
|
}
|
|
69
75
|
)
|
|
70
76
|
|
|
77
|
+
#: Allowed spellings that are aliases of another allowed command. They need an
|
|
78
|
+
#: MCP policy (they run) but must NOT reach the tool catalog: documenting a
|
|
79
|
+
#: second spelling of `project` would put back the row the alias is hidden from.
|
|
80
|
+
MCP_ALIAS_COMMANDS = frozenset({"workspace"})
|
|
81
|
+
|
|
71
82
|
# Reason codes are stable data rendered into help, guide, MCP errors, and the
|
|
72
83
|
# for-agent page. The detail is for maintainers and structured clients; user
|
|
73
84
|
# prose remains translated at its own surface.
|
|
@@ -80,10 +91,6 @@ MCP_BLOCKED_COMMANDS: dict[str, dict[str, str]] = {
|
|
|
80
91
|
"reason": "local_checkout",
|
|
81
92
|
"detail": "creates and updates a local Git working copy",
|
|
82
93
|
},
|
|
83
|
-
"commit": {
|
|
84
|
-
"reason": "local_checkout",
|
|
85
|
-
"detail": "reads local sync state and commits a working copy",
|
|
86
|
-
},
|
|
87
94
|
"login": {
|
|
88
95
|
"reason": "interactive_auth",
|
|
89
96
|
"detail": "opens or polls an interactive browser authentication flow",
|
|
@@ -132,6 +139,15 @@ MCP_BLOCKED_COMMANDS: dict[str, dict[str, str]] = {
|
|
|
132
139
|
"reason": "shared_host_install",
|
|
133
140
|
"detail": "would inspect or replace the CLI installed on the MCP host",
|
|
134
141
|
},
|
|
142
|
+
"variant": {
|
|
143
|
+
"reason": "local_checkout",
|
|
144
|
+
"detail": (
|
|
145
|
+
"branches the whole file set, and only a working copy can use one: "
|
|
146
|
+
"`checkout` resets a local tree, and no write command takes a "
|
|
147
|
+
"`--variant`, so a caller with no clone cannot put anything on a "
|
|
148
|
+
"branch for `diff` or `merge` to act on"
|
|
149
|
+
),
|
|
150
|
+
},
|
|
135
151
|
"verify-email": {
|
|
136
152
|
"reason": "interactive_auth",
|
|
137
153
|
"detail": "prompts for a verification token sent out of band",
|
|
@@ -427,9 +427,9 @@
|
|
|
427
427
|
"reason": "not_dispatched"
|
|
428
428
|
},
|
|
429
429
|
"mcp": {
|
|
430
|
-
"available":
|
|
431
|
-
"detail":
|
|
432
|
-
"reason":
|
|
430
|
+
"available": true,
|
|
431
|
+
"detail": null,
|
|
432
|
+
"reason": null
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
},
|
|
@@ -5817,9 +5817,9 @@
|
|
|
5817
5817
|
"reason": "not_dispatched"
|
|
5818
5818
|
},
|
|
5819
5819
|
"mcp": {
|
|
5820
|
-
"available":
|
|
5821
|
-
"detail":
|
|
5822
|
-
"reason":
|
|
5820
|
+
"available": false,
|
|
5821
|
+
"detail": "branches the whole file set, and only a working copy can use one: `checkout` resets a local tree, and no write command takes a `--variant`, so a caller with no clone cannot put anything on a branch for `diff` or `merge` to act on",
|
|
5822
|
+
"reason": "local_checkout"
|
|
5823
5823
|
}
|
|
5824
5824
|
}
|
|
5825
5825
|
},
|
|
@@ -5848,9 +5848,9 @@
|
|
|
5848
5848
|
"reason": "not_dispatched"
|
|
5849
5849
|
},
|
|
5850
5850
|
"mcp": {
|
|
5851
|
-
"available":
|
|
5852
|
-
"detail":
|
|
5853
|
-
"reason":
|
|
5851
|
+
"available": false,
|
|
5852
|
+
"detail": "branches the whole file set, and only a working copy can use one: `checkout` resets a local tree, and no write command takes a `--variant`, so a caller with no clone cannot put anything on a branch for `diff` or `merge` to act on",
|
|
5853
|
+
"reason": "local_checkout"
|
|
5854
5854
|
}
|
|
5855
5855
|
}
|
|
5856
5856
|
},
|
|
@@ -5879,9 +5879,9 @@
|
|
|
5879
5879
|
"reason": "not_dispatched"
|
|
5880
5880
|
},
|
|
5881
5881
|
"mcp": {
|
|
5882
|
-
"available":
|
|
5883
|
-
"detail":
|
|
5884
|
-
"reason":
|
|
5882
|
+
"available": false,
|
|
5883
|
+
"detail": "branches the whole file set, and only a working copy can use one: `checkout` resets a local tree, and no write command takes a `--variant`, so a caller with no clone cannot put anything on a branch for `diff` or `merge` to act on",
|
|
5884
|
+
"reason": "local_checkout"
|
|
5885
5885
|
}
|
|
5886
5886
|
}
|
|
5887
5887
|
},
|
|
@@ -5910,9 +5910,9 @@
|
|
|
5910
5910
|
"reason": "not_dispatched"
|
|
5911
5911
|
},
|
|
5912
5912
|
"mcp": {
|
|
5913
|
-
"available":
|
|
5914
|
-
"detail":
|
|
5915
|
-
"reason":
|
|
5913
|
+
"available": false,
|
|
5914
|
+
"detail": "branches the whole file set, and only a working copy can use one: `checkout` resets a local tree, and no write command takes a `--variant`, so a caller with no clone cannot put anything on a branch for `diff` or `merge` to act on",
|
|
5915
|
+
"reason": "local_checkout"
|
|
5916
5916
|
}
|
|
5917
5917
|
}
|
|
5918
5918
|
},
|
|
@@ -5941,9 +5941,9 @@
|
|
|
5941
5941
|
"reason": "not_dispatched"
|
|
5942
5942
|
},
|
|
5943
5943
|
"mcp": {
|
|
5944
|
-
"available":
|
|
5945
|
-
"detail":
|
|
5946
|
-
"reason":
|
|
5944
|
+
"available": false,
|
|
5945
|
+
"detail": "branches the whole file set, and only a working copy can use one: `checkout` resets a local tree, and no write command takes a `--variant`, so a caller with no clone cannot put anything on a branch for `diff` or `merge` to act on",
|
|
5946
|
+
"reason": "local_checkout"
|
|
5947
5947
|
}
|
|
5948
5948
|
}
|
|
5949
5949
|
},
|
|
@@ -111,7 +111,16 @@ class DeepCellClient:
|
|
|
111
111
|
# which build their own per-request headers and would otherwise be the
|
|
112
112
|
# two calls a new user makes before anything else. httpx merges these
|
|
113
113
|
# with per-request headers rather than replacing them.
|
|
114
|
-
|
|
114
|
+
# `follow_redirects` because httpx defaults it to False, and an installed
|
|
115
|
+
# CLI outlives the hostname it was built against: every copy already in
|
|
116
|
+
# the wild has `https://beta.deepcell.net/api/jingwei` baked in as its
|
|
117
|
+
# default. The day that host answers a 301 instead of serving, a client
|
|
118
|
+
# that does not follow redirects reports the 3xx as a hard failure and
|
|
119
|
+
# every one of those installs breaks at once — which is why beta is kept
|
|
120
|
+
# serving rather than redirected. This makes the next move survivable.
|
|
121
|
+
self._http = httpx.Client(
|
|
122
|
+
timeout=_TIMEOUT, headers=client_headers(), follow_redirects=True
|
|
123
|
+
)
|
|
115
124
|
|
|
116
125
|
# ── Helpers ─────────────────────────────────────────────
|
|
117
126
|
|
|
@@ -257,6 +257,19 @@ def help_cmd(
|
|
|
257
257
|
if command:
|
|
258
258
|
path = " ".join(command)
|
|
259
259
|
entry = commands.get(path)
|
|
260
|
+
if entry is None:
|
|
261
|
+
# A HIDDEN command is absent from the listing on purpose but still
|
|
262
|
+
# runs, so asking for it by name must answer. `clone` is the case:
|
|
263
|
+
# `cli/README.md` points a person at `deepcell help clone`, and the
|
|
264
|
+
# listing it is hidden from is not the same question as the flags it
|
|
265
|
+
# takes. Only reached on an explicit name — the index, the stage
|
|
266
|
+
# filters and every generated surface still see the visible set.
|
|
267
|
+
hidden_entry = build_commands(include_hidden=True).get(path)
|
|
268
|
+
if hidden_entry is not None:
|
|
269
|
+
entry = {
|
|
270
|
+
**hidden_entry,
|
|
271
|
+
"capability": get_command_capability(path),
|
|
272
|
+
}
|
|
260
273
|
if entry is None:
|
|
261
274
|
# `build_commands()` keys leaf commands only, so a group path misses
|
|
262
275
|
# the table. Asking what is under `defs` is a well-formed question
|
|
@@ -267,6 +280,16 @@ def help_cmd(
|
|
|
267
280
|
children = sorted(
|
|
268
281
|
name for name in commands if name.startswith(f"{path} ")
|
|
269
282
|
)
|
|
283
|
+
if not children:
|
|
284
|
+
# A hidden GROUP is keyed nowhere — `build_commands` keys leaves
|
|
285
|
+
# — so `deepcell help variant` has to find its children the same
|
|
286
|
+
# way, in the hidden-inclusive tree.
|
|
287
|
+
all_commands = build_commands(include_hidden=True)
|
|
288
|
+
children = sorted(
|
|
289
|
+
name for name in all_commands if name.startswith(f"{path} ")
|
|
290
|
+
)
|
|
291
|
+
if children:
|
|
292
|
+
commands = {**all_commands, **commands}
|
|
270
293
|
if children:
|
|
271
294
|
if ctx.fmt == "json":
|
|
272
295
|
output({"group": path, "commands": children}, ctx.fmt)
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
"""Sync commands: clone, pull, push, status
|
|
1
|
+
"""Sync commands: clone, pull, push, status.
|
|
2
2
|
|
|
3
|
-
Git-like sync between a local folder and a cloud DeepCell workspace.
|
|
3
|
+
Git-like sync between a local folder and a cloud DeepCell workspace. Every
|
|
4
|
+
command here genuinely reads or writes a local working copy — that is the
|
|
5
|
+
membership rule, and `commit` failed it: it posts to
|
|
6
|
+
`/workspaces/{slug}/versions/commit` and touches no local file, so it lives in
|
|
7
|
+
`version.py` with the rest of the server-side history commands.
|
|
4
8
|
"""
|
|
5
9
|
|
|
6
10
|
from __future__ import annotations
|
|
@@ -742,63 +746,3 @@ def push(ctx: Ctx, message: str) -> None:
|
|
|
742
746
|
"push again"
|
|
743
747
|
)
|
|
744
748
|
raise click.exceptions.Exit(1)
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
# -- commit (server-side pending) ---------------------------------------------
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
@click.command()
|
|
751
|
-
@click.option("-m", "--message", default=None, help="Commit message.")
|
|
752
|
-
@pass_ctx
|
|
753
|
-
def commit(ctx: Ctx, message: str | None) -> None:
|
|
754
|
-
"""Commit changes staged on the server by something other than you.
|
|
755
|
-
|
|
756
|
-
You almost certainly do not need this. `defs`, `edit`, `write` and
|
|
757
|
-
`reasoning` each commit their own change with their own `-m` — there is no
|
|
758
|
-
staging area they queue into. This commits what a *different* actor left
|
|
759
|
-
pending on the server, such as files created by an agent session.
|
|
760
|
-
|
|
761
|
-
Nothing staged is a no-op, not an error.
|
|
762
|
-
"""
|
|
763
|
-
root, state = _require_sync_root(ctx)
|
|
764
|
-
slug = state.workspace_slug
|
|
765
|
-
|
|
766
|
-
# `-m` is no longer `required=True`, so this reaches the server even when
|
|
767
|
-
# the caller gave no message. The server checks "is anything staged"
|
|
768
|
-
# first and answers the no-op; a commit that will actually write still
|
|
769
|
-
# 422s without a message, and that is re-raised below as a usage error.
|
|
770
|
-
#
|
|
771
|
-
# It used to be rejected by Click during parsing, so a worker whose defs
|
|
772
|
-
# ops had each already committed and who reasonably tried to finalize got
|
|
773
|
-
# "Missing option '-m' / '--message'" — an argument-syntax error for a
|
|
774
|
-
# command with nothing to do. That is what the 2026-08-02 CLI eval hit.
|
|
775
|
-
data = ctx.client.post(
|
|
776
|
-
f"/workspaces/{slug}/versions/commit",
|
|
777
|
-
json={"message": message or ""},
|
|
778
|
-
)
|
|
779
|
-
|
|
780
|
-
# A commit with nothing staged is a benign no-op (the endpoint answers
|
|
781
|
-
# 200 with committed=false, sha=null) — report it instead of slicing a
|
|
782
|
-
# null sha.
|
|
783
|
-
if isinstance(data, dict) and data.get("committed") is False:
|
|
784
|
-
echo_info(
|
|
785
|
-
"Nothing to commit — no pending changes. `defs`, `edit`, `write` "
|
|
786
|
-
"and `reasoning` each commit their own change, so there is "
|
|
787
|
-
"normally nothing here to finalize."
|
|
788
|
-
)
|
|
789
|
-
return
|
|
790
|
-
|
|
791
|
-
sha = (data.get("sha") or "")[:8] if isinstance(data, dict) else ""
|
|
792
|
-
# Staged content is committed either way (the server persists invalid
|
|
793
|
-
# .deepcell on purpose), so report the errors rather than letting a clean
|
|
794
|
-
# "Committed" line stand in for them — parity with write/push.
|
|
795
|
-
invalid = echo_validation_map(
|
|
796
|
-
data.get("validation") if isinstance(data, dict) else None
|
|
797
|
-
)
|
|
798
|
-
if invalid:
|
|
799
|
-
echo_warning(
|
|
800
|
-
f"Committed ({sha}), but {len(invalid)} file(s) have validation "
|
|
801
|
-
f"error(s): {', '.join(invalid)} — fix the errors above"
|
|
802
|
-
)
|
|
803
|
-
raise click.exceptions.Exit(1)
|
|
804
|
-
echo_success(f"Committed ({sha}): {message}")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Version control commands: log, diff, restore."""
|
|
1
|
+
"""Version control commands: log, diff, restore, commit."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -7,7 +7,8 @@ import click
|
|
|
7
7
|
from deepcell_cli.errors import APIError
|
|
8
8
|
from deepcell_cli.context import Ctx, pass_ctx
|
|
9
9
|
from deepcell_cli.output import (
|
|
10
|
-
echo_error, echo_info, echo_success,
|
|
10
|
+
echo_error, echo_info, echo_success, echo_validation_map, echo_warning,
|
|
11
|
+
output, output_mutation, print_plain,
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
|
|
@@ -443,3 +444,73 @@ def restore(ctx: Ctx, revision: str, filename: str | None, yes: bool) -> None:
|
|
|
443
444
|
"Recover them from a newer revision (`deepcell log`, then "
|
|
444
445
|
"`deepcell download <file> --revision <sha>`)."
|
|
445
446
|
)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# -- commit (finalize what another actor staged server-side) -------------------
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@click.command()
|
|
453
|
+
@click.option("-m", "--message", default=None, help="Commit message.")
|
|
454
|
+
@pass_ctx
|
|
455
|
+
def commit(ctx: Ctx, message: str | None) -> None:
|
|
456
|
+
"""Commit changes staged on the server by something other than you.
|
|
457
|
+
|
|
458
|
+
You almost certainly do not need this. `defs`, `edit`, `write` and
|
|
459
|
+
`reasoning` each commit their own change with their own `-m` — there is no
|
|
460
|
+
staging area they queue into. This commits what a *different* actor left
|
|
461
|
+
pending on the server, such as files created by an agent session.
|
|
462
|
+
|
|
463
|
+
Nothing staged is a no-op, not an error.
|
|
464
|
+
"""
|
|
465
|
+
# Addressed by workspace, like everything else in this module. It used to
|
|
466
|
+
# live in `sync.py` and call `_require_sync_root`, which reads
|
|
467
|
+
# `.deepcell/sync.json` — for nothing but the slug, because the work is
|
|
468
|
+
# entirely server-side: `POST /workspaces/{slug}/versions/commit` stages
|
|
469
|
+
# nothing locally and reads no local file.
|
|
470
|
+
#
|
|
471
|
+
# That invented clone requirement cost the command the callers it exists
|
|
472
|
+
# for. It raised "Not inside a synced workspace" for anyone standing
|
|
473
|
+
# anywhere else, and it was blocked over MCP as `local_checkout` — a reason
|
|
474
|
+
# untrue of it. An agent, which is precisely who leaves changes staged,
|
|
475
|
+
# could not finalize them from any transport.
|
|
476
|
+
slug = ctx.require_workspace()
|
|
477
|
+
|
|
478
|
+
# `-m` is no longer `required=True`, so this reaches the server even when
|
|
479
|
+
# the caller gave no message. The server checks "is anything staged"
|
|
480
|
+
# first and answers the no-op; a commit that will actually write still
|
|
481
|
+
# 422s without a message, and that is re-raised below as a usage error.
|
|
482
|
+
#
|
|
483
|
+
# It used to be rejected by Click during parsing, so a worker whose defs
|
|
484
|
+
# ops had each already committed and who reasonably tried to finalize got
|
|
485
|
+
# "Missing option '-m' / '--message'" — an argument-syntax error for a
|
|
486
|
+
# command with nothing to do. That is what the 2026-08-02 CLI eval hit.
|
|
487
|
+
data = ctx.client.post(
|
|
488
|
+
f"/workspaces/{slug}/versions/commit",
|
|
489
|
+
json={"message": message or ""},
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# A commit with nothing staged is a benign no-op (the endpoint answers
|
|
493
|
+
# 200 with committed=false, sha=null) — report it instead of slicing a
|
|
494
|
+
# null sha.
|
|
495
|
+
if isinstance(data, dict) and data.get("committed") is False:
|
|
496
|
+
echo_info(
|
|
497
|
+
"Nothing to commit — no pending changes. `defs`, `edit`, `write` "
|
|
498
|
+
"and `reasoning` each commit their own change, so there is "
|
|
499
|
+
"normally nothing here to finalize."
|
|
500
|
+
)
|
|
501
|
+
return
|
|
502
|
+
|
|
503
|
+
sha = (data.get("sha") or "")[:8] if isinstance(data, dict) else ""
|
|
504
|
+
# Staged content is committed either way (the server persists invalid
|
|
505
|
+
# .deepcell on purpose), so report the errors rather than letting a clean
|
|
506
|
+
# "Committed" line stand in for them — parity with write/push.
|
|
507
|
+
invalid = echo_validation_map(
|
|
508
|
+
data.get("validation") if isinstance(data, dict) else None
|
|
509
|
+
)
|
|
510
|
+
if invalid:
|
|
511
|
+
echo_warning(
|
|
512
|
+
f"Committed ({sha}), but {len(invalid)} file(s) have validation "
|
|
513
|
+
f"error(s): {', '.join(invalid)} — fix the errors above"
|
|
514
|
+
)
|
|
515
|
+
raise click.exceptions.Exit(1)
|
|
516
|
+
echo_success(f"Committed ({sha}): {message}")
|
|
@@ -19,7 +19,7 @@ _CREDS_FILE = _DIR / "credentials.json"
|
|
|
19
19
|
|
|
20
20
|
# ── Defaults ────────────────────────────────────────────────
|
|
21
21
|
|
|
22
|
-
DEFAULT_API_URL = "https://
|
|
22
|
+
DEFAULT_API_URL = "https://deepcell.net/api/jingwei"
|
|
23
23
|
DEFAULT_FORMAT = "plain"
|
|
24
24
|
|
|
25
25
|
# ── Environment overrides ───────────────────────────────────
|
|
@@ -442,11 +442,11 @@ from deepcell_cli.commands.impact import impact # noqa: E402
|
|
|
442
442
|
from deepcell_cli.commands.export_docx import to_docx # noqa: E402
|
|
443
443
|
from deepcell_cli.commands.export_pdf import to_pdf # noqa: E402
|
|
444
444
|
from deepcell_cli.commands.export_pptx import to_pptx # noqa: E402
|
|
445
|
-
from deepcell_cli.commands.version import log, diff, restore # noqa: E402
|
|
445
|
+
from deepcell_cli.commands.version import log, diff, restore, commit # noqa: E402
|
|
446
446
|
from deepcell_cli.commands.changes import changes # noqa: E402
|
|
447
447
|
from deepcell_cli.commands.variant import variant # noqa: E402
|
|
448
448
|
from deepcell_cli.commands.grep import grep # noqa: E402
|
|
449
|
-
from deepcell_cli.commands.sync import clone, pull, push, sync_status
|
|
449
|
+
from deepcell_cli.commands.sync import clone, pull, push, sync_status # noqa: E402
|
|
450
450
|
from deepcell_cli.commands.merge import merge # noqa: E402
|
|
451
451
|
from deepcell_cli.commands.import_cmd import import_cmd # noqa: E402
|
|
452
452
|
from deepcell_cli.commands.ingest import ingest # noqa: E402
|
|
@@ -499,13 +499,34 @@ cli.add_command(log)
|
|
|
499
499
|
cli.add_command(diff)
|
|
500
500
|
cli.add_command(restore)
|
|
501
501
|
cli.add_command(changes)
|
|
502
|
-
cli.add_command(variant)
|
|
503
502
|
cli.add_command(grep)
|
|
503
|
+
cli.add_command(commit)
|
|
504
|
+
|
|
505
|
+
# Local sync and variants: registered, working, and HIDDEN. Both are a person's
|
|
506
|
+
# workflow at a terminal with a clone, and neither is discoverable any more —
|
|
507
|
+
# `surface.py`, `main`'s own help and `docs/cli-commands.md`'s coverage test all
|
|
508
|
+
# skip hidden commands, so they produce no help row and no entry in any
|
|
509
|
+
# generated surface. Muscle memory and existing scripts keep working, and
|
|
510
|
+
# `deepcell help <name>` still answers for each.
|
|
511
|
+
#
|
|
512
|
+
# Sync moves BYTES while every read addresses the project on the server, so a
|
|
513
|
+
# clone is a working copy the read path cannot see. Variants branch the whole
|
|
514
|
+
# file set, and no write command takes a variant, so a branch is only reachable
|
|
515
|
+
# by checking it out onto disk. Both are therefore clone-shaped all the way
|
|
516
|
+
# down, which is precisely what the agent surfaces do not have.
|
|
517
|
+
#
|
|
518
|
+
# The cost is deliberate and was weighed: the `variants` and `merge` domains
|
|
519
|
+
# leave `docs/backend-cli-semantic-contracts.yaml`, because a hidden command is
|
|
520
|
+
# absent from the capability contract the domain resolves through. Those two
|
|
521
|
+
# backend domains now have no taught CLI command.
|
|
522
|
+
for _person_only in (variant, clone, pull, push, sync_status, merge):
|
|
523
|
+
_person_only.hidden = True
|
|
524
|
+
|
|
525
|
+
cli.add_command(variant)
|
|
504
526
|
cli.add_command(clone)
|
|
505
527
|
cli.add_command(pull)
|
|
506
528
|
cli.add_command(push)
|
|
507
529
|
cli.add_command(sync_status, "status")
|
|
508
|
-
cli.add_command(commit)
|
|
509
530
|
cli.add_command(merge)
|
|
510
531
|
cli.add_command(import_cmd, "import")
|
|
511
532
|
cli.add_command(ingest)
|
|
@@ -24,6 +24,7 @@ from click.testing import CliRunner
|
|
|
24
24
|
from mcp.server.fastmcp import FastMCP
|
|
25
25
|
|
|
26
26
|
from deepcell_cli.capabilities import (
|
|
27
|
+
MCP_ALIAS_COMMANDS,
|
|
27
28
|
MCP_ALLOWED_COMMANDS,
|
|
28
29
|
MCP_BLOCKED_COMMANDS as MCP_BLOCK_POLICY,
|
|
29
30
|
blocked_mcp_commands,
|
|
@@ -48,7 +49,7 @@ AUTO_CONFIRM: dict[str, list[str]] = {
|
|
|
48
49
|
# Top-level commands documented in TOOL_DESCRIPTION. Must stay equal to the
|
|
49
50
|
# registered Click commands minus BLOCKED_COMMANDS — tests/test_mcp_server.py
|
|
50
51
|
# cross-checks both directions so the catalog can't silently drift.
|
|
51
|
-
DOCUMENTED_COMMANDS = frozenset(MCP_ALLOWED_COMMANDS)
|
|
52
|
+
DOCUMENTED_COMMANDS = frozenset(MCP_ALLOWED_COMMANDS) - MCP_ALIAS_COMMANDS
|
|
52
53
|
|
|
53
54
|
TOOL_DESCRIPTION = """\
|
|
54
55
|
Run a DeepCell CLI command. Structured results are JSON; `cat` prints raw XML
|
|
@@ -136,9 +137,6 @@ Available commands:
|
|
|
136
137
|
replace <file> <old> <new>
|
|
137
138
|
String-replace in XML (last resort — prefer defs;
|
|
138
139
|
`edit --replace` is a deprecated alias)
|
|
139
|
-
variant list List variants
|
|
140
|
-
variant create <name> Create a variant
|
|
141
|
-
(Note: variant checkout/diff/merge need a LOCAL clone — blocked here)
|
|
142
140
|
rm <file> Delete a file (auto-confirmed)
|
|
143
141
|
log [--file F] [-n N] Show version history
|
|
144
142
|
diff <rev_a> [rev_b] Diff between revisions
|
|
@@ -147,6 +145,9 @@ Available commands:
|
|
|
147
145
|
changes diff <base> [head] What one change did, semantically
|
|
148
146
|
changes revert <base> <head>
|
|
149
147
|
Undo one change as a new commit (auto-confirmed)
|
|
148
|
+
commit [-m MSG] Finalize changes another actor left staged
|
|
149
|
+
(Rare: every writer commits its own change.
|
|
150
|
+
Nothing staged is a no-op, not an error.)
|
|
150
151
|
|
|
151
152
|
VERIFY — change one thing, see everything it affects
|
|
152
153
|
relationships <file> Show relationship graph
|
|
@@ -182,7 +183,8 @@ Available commands:
|
|
|
182
183
|
|
|
183
184
|
SYNC — local <-> cloud
|
|
184
185
|
download <file> -o <path> Download file to local path
|
|
185
|
-
(
|
|
186
|
+
(Sync proper is blocked here — it needs a clone.
|
|
187
|
+
Note `commit` is NOT sync: see REVISE.)
|
|
186
188
|
|
|
187
189
|
LEARN — the five reference surfaces
|
|
188
190
|
help [command] Every command, flag, exit code and example.
|
|
@@ -385,7 +387,7 @@ def _config_path_for_token(token: str) -> str:
|
|
|
385
387
|
base config's ``api_url`` on creation: ``get_api_url()`` falls back to
|
|
386
388
|
DEFAULT_API_URL (the public beta) when the config it reads has no
|
|
387
389
|
``api_url``, so handing a caller an empty config silently repointed a
|
|
388
|
-
self-hosted deployment at
|
|
390
|
+
self-hosted deployment at deepcell.net. ``active_workspace`` is
|
|
389
391
|
deliberately NOT inherited — isolating it is the whole point of this file.
|
|
390
392
|
"""
|
|
391
393
|
import hashlib
|
|
@@ -275,7 +275,9 @@ class DeepCellOAuthProvider(
|
|
|
275
275
|
Returns ``None`` when the code is unknown, expired, or still pending.
|
|
276
276
|
"""
|
|
277
277
|
try:
|
|
278
|
-
async with httpx.AsyncClient(
|
|
278
|
+
async with httpx.AsyncClient(
|
|
279
|
+
timeout=10, headers=client_headers("mcp"), follow_redirects=True
|
|
280
|
+
) as client:
|
|
279
281
|
resp = await client.post(
|
|
280
282
|
f"{self.api_url}/auth/device/token",
|
|
281
283
|
json={"device_code": device_code},
|
|
@@ -436,7 +438,9 @@ class DeepCellOAuthProvider(
|
|
|
436
438
|
headers["Authorization"] = f"Bearer {jingwei_api_key}"
|
|
437
439
|
|
|
438
440
|
try:
|
|
439
|
-
async with httpx.AsyncClient(
|
|
441
|
+
async with httpx.AsyncClient(
|
|
442
|
+
timeout=10, headers=client_headers("mcp"), follow_redirects=True
|
|
443
|
+
) as client:
|
|
440
444
|
resp = await client.post(
|
|
441
445
|
f"{self.api_url}/auth/api-keys/verify",
|
|
442
446
|
json={"key": token},
|
|
@@ -514,7 +518,9 @@ class DeepCellOAuthProvider(
|
|
|
514
518
|
# so they simply expire (15 min).
|
|
515
519
|
if isinstance(token, DeepCellRefreshToken):
|
|
516
520
|
try:
|
|
517
|
-
async with httpx.AsyncClient(
|
|
521
|
+
async with httpx.AsyncClient(
|
|
522
|
+
timeout=10, headers=client_headers("mcp"), follow_redirects=True
|
|
523
|
+
) as client:
|
|
518
524
|
await client.post(
|
|
519
525
|
f"{self.api_url}/auth/revoke-token",
|
|
520
526
|
json={"refresh_token": token.token},
|
|
@@ -547,7 +553,9 @@ class DeepCellOAuthProvider(
|
|
|
547
553
|
grant may still be valid, so the client should retry and keep it.
|
|
548
554
|
"""
|
|
549
555
|
try:
|
|
550
|
-
async with httpx.AsyncClient(
|
|
556
|
+
async with httpx.AsyncClient(
|
|
557
|
+
timeout=10, headers=client_headers("mcp"), follow_redirects=True
|
|
558
|
+
) as client:
|
|
551
559
|
resp = await client.post(
|
|
552
560
|
f"{self.api_url}/auth/refresh",
|
|
553
561
|
json={"refresh_token": raw_refresh_token},
|