deepcell-cli 0.6.1__py3-none-any.whl
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/__init__.py +12 -0
- deepcell_cli/__main__.py +5 -0
- deepcell_cli/_findings.py +84 -0
- deepcell_cli/capabilities.py +560 -0
- deepcell_cli/capability-contract.json +15622 -0
- deepcell_cli/client.py +503 -0
- deepcell_cli/commands/__init__.py +1 -0
- deepcell_cli/commands/_batch_input.py +29 -0
- deepcell_cli/commands/_datatypes.py +56 -0
- deepcell_cli/commands/_negative_args.py +133 -0
- deepcell_cli/commands/_swapped_args.py +153 -0
- deepcell_cli/commands/_version_display.py +40 -0
- deepcell_cli/commands/_write_opts.py +139 -0
- deepcell_cli/commands/account.py +123 -0
- deepcell_cli/commands/auth.py +610 -0
- deepcell_cli/commands/changes.py +307 -0
- deepcell_cli/commands/deck.py +594 -0
- deepcell_cli/commands/defs.py +3890 -0
- deepcell_cli/commands/describe.py +902 -0
- deepcell_cli/commands/doc.py +529 -0
- deepcell_cli/commands/doctor.py +257 -0
- deepcell_cli/commands/download.py +36 -0
- deepcell_cli/commands/edit.py +384 -0
- deepcell_cli/commands/example.py +161 -0
- deepcell_cli/commands/export.py +81 -0
- deepcell_cli/commands/export_docx.py +57 -0
- deepcell_cli/commands/export_pdf.py +66 -0
- deepcell_cli/commands/export_pptx.py +45 -0
- deepcell_cli/commands/files.py +386 -0
- deepcell_cli/commands/grep.py +90 -0
- deepcell_cli/commands/guide.py +431 -0
- deepcell_cli/commands/help_cmd.py +348 -0
- deepcell_cli/commands/impact.py +382 -0
- deepcell_cli/commands/import_cmd.py +208 -0
- deepcell_cli/commands/ingest.py +110 -0
- deepcell_cli/commands/merge.py +399 -0
- deepcell_cli/commands/query.py +718 -0
- deepcell_cli/commands/reasoning.py +2981 -0
- deepcell_cli/commands/ref.py +279 -0
- deepcell_cli/commands/replace.py +326 -0
- deepcell_cli/commands/rules.py +206 -0
- deepcell_cli/commands/share.py +186 -0
- deepcell_cli/commands/sync.py +804 -0
- deepcell_cli/commands/upgrade.py +185 -0
- deepcell_cli/commands/variant.py +353 -0
- deepcell_cli/commands/version.py +445 -0
- deepcell_cli/commands/viewer.py +54 -0
- deepcell_cli/commands/workspace.py +101 -0
- deepcell_cli/config.py +352 -0
- deepcell_cli/context.py +187 -0
- deepcell_cli/errors.py +141 -0
- deepcell_cli/logging_setup.py +161 -0
- deepcell_cli/main.py +518 -0
- deepcell_cli/mcp_server.py +906 -0
- deepcell_cli/oauth_provider.py +580 -0
- deepcell_cli/output.py +503 -0
- deepcell_cli/revision.py +164 -0
- deepcell_cli/stages.py +223 -0
- deepcell_cli/surface.py +628 -0
- deepcell_cli/sync_state.py +120 -0
- deepcell_cli/upgrade_check.py +399 -0
- deepcell_cli/xml_replace.py +89 -0
- deepcell_cli-0.6.1.dist-info/METADATA +264 -0
- deepcell_cli-0.6.1.dist-info/RECORD +67 -0
- deepcell_cli-0.6.1.dist-info/WHEEL +5 -0
- deepcell_cli-0.6.1.dist-info/entry_points.txt +3 -0
- deepcell_cli-0.6.1.dist-info/top_level.txt +1 -0
deepcell_cli/surface.py
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
"""The CLI surface — the command tree as data, with exit semantics and examples.
|
|
2
|
+
|
|
3
|
+
One module, two readers: ``deepcell help -f json`` serves it directly (no
|
|
4
|
+
network — the Click tree is local, and so is everything here), and
|
|
5
|
+
``scripts/gen_cli_surface.py`` imports it to emit ``docs/cli-surface.json``,
|
|
6
|
+
which the published doc renders. So the human page and the agent's ``help``
|
|
7
|
+
dump are not *kept in sync*; they are the same bytes rendered twice.
|
|
8
|
+
See ``docs/cli-agent-surface.md`` §3 and §9.
|
|
9
|
+
|
|
10
|
+
Three things this adds to what Click already knows:
|
|
11
|
+
|
|
12
|
+
**Exit semantics per command.** ``ref exit`` documents centrally, which
|
|
13
|
+
means an agent must know to go read a separate topic *before* it can interpret
|
|
14
|
+
a failure it has already hit. Exit 1 means five different things and the
|
|
15
|
+
difference is whether anything was written — so it belongs in the help of the
|
|
16
|
+
command that can return it. Expressed as ordered patterns rather than 131
|
|
17
|
+
entries: the taxonomy is genuinely per-*family*, and a per-command list would
|
|
18
|
+
be 131 chances to disagree with itself.
|
|
19
|
+
|
|
20
|
+
**One example per command.** Agents pattern-match on invocations far more
|
|
21
|
+
reliably than they parse flag tables. Examples are *derived* from the Click
|
|
22
|
+
signature by default and *authored* where a derived one would mislead; the
|
|
23
|
+
manifest marks which, so a derived example is never mistaken for a tested one.
|
|
24
|
+
|
|
25
|
+
**Typed ``see_also`` ids**, resolved by ``deepcell ref``.
|
|
26
|
+
|
|
27
|
+
⚠ Nothing here may name a defs-op kind. ``cli/tests/test_op_reachability.py``
|
|
28
|
+
scans this package for op-kind strings in real code positions and would read
|
|
29
|
+
them as "the CLI can emit this op", silently disabling the reachability guard.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import fnmatch
|
|
35
|
+
import inspect
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
import click
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Exit semantics
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
#: ``(glob, sense-id)``, first match wins. Sense ids are defined once, in the
|
|
45
|
+
#: backend's ``ref:exit`` namespace; ``test_cli_surface_codegen.py`` asserts
|
|
46
|
+
#: every id used here exists there, so the two halves cannot drift apart.
|
|
47
|
+
#:
|
|
48
|
+
#: Order matters: the more specific pattern must come first — ``merge *``
|
|
49
|
+
#: before a bare ``merge`` would swallow it, and so on.
|
|
50
|
+
#:
|
|
51
|
+
#: ⚠ These are command PATHS, so a sense that depends on a *flag* cannot be
|
|
52
|
+
#: expressed here at all. That constraint is why ``replace`` is a command and
|
|
53
|
+
#: not a mode: string replacement writes first and validates second
|
|
54
|
+
#: (``written-but-invalid``) while ``edit`` applies a batch row by row
|
|
55
|
+
#: (``partial``), and one command carrying two senses had nowhere to go in this
|
|
56
|
+
#: table. Splitting it made the taxonomy expressible with no schema change —
|
|
57
|
+
#: both are plain entries below. ``edit --replace`` survives as a deprecated
|
|
58
|
+
#: alias, so a caller who uses it reads ``edit``'s sense; the alias warns and
|
|
59
|
+
#: names the command whose exit codes actually describe it.
|
|
60
|
+
EXIT_SENSE_RULES: list[tuple[str, str]] = [
|
|
61
|
+
# Read-only checks — nothing was ever going to change.
|
|
62
|
+
("describe", "read-only-check"),
|
|
63
|
+
("reasoning lint", "read-only-check"),
|
|
64
|
+
("reasoning-diff", "read-only-check"),
|
|
65
|
+
# ⚠ Must precede `defs *`: the two defs READS (#1291) write nothing, so the
|
|
66
|
+
# write family's "nothing-changed, re-send the whole batch" sense would
|
|
67
|
+
# misdescribe them.
|
|
68
|
+
("defs list", "read-only-check"),
|
|
69
|
+
("defs show", "read-only-check"),
|
|
70
|
+
# Conflict — your state is preserved.
|
|
71
|
+
("pull", "conflict"),
|
|
72
|
+
("merge *", "conflict"),
|
|
73
|
+
("variant merge", "conflict"),
|
|
74
|
+
# Written, then validated: a failure means it is already on disk.
|
|
75
|
+
("write", "written-but-invalid"),
|
|
76
|
+
("replace", "written-but-invalid"),
|
|
77
|
+
("push", "written-but-invalid"),
|
|
78
|
+
("commit", "written-but-invalid"),
|
|
79
|
+
# ⚠ Glob-matched, first match wins — `reasoning add-*` does NOT cover
|
|
80
|
+
# `set-conclusion`, so without its own entry it would exit 1 with no sense
|
|
81
|
+
# attached. That is a silent quality loss, not a test failure.
|
|
82
|
+
("reasoning set-conclusion", "written-but-invalid"),
|
|
83
|
+
("reasoning add-*", "written-but-invalid"),
|
|
84
|
+
("reasoning update-*", "written-but-invalid"),
|
|
85
|
+
("reasoning supersede-*", "written-but-invalid"),
|
|
86
|
+
("reasoning delete-*", "written-but-invalid"),
|
|
87
|
+
# Whole-batch atomic: nothing was applied, re-send everything.
|
|
88
|
+
("defs *", "nothing-changed"),
|
|
89
|
+
# Partially applied — read results and errors.
|
|
90
|
+
("edit", "partial"),
|
|
91
|
+
("import", "partial"),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
#: Commands that exit 2 on an unparseable document, distinct from exit 1
|
|
95
|
+
#: ("parsed fine, id not found"). Everything else's exit 2 is Click's own
|
|
96
|
+
#: usage error, which needs no per-command note.
|
|
97
|
+
EXIT_TWO_ON_UNPARSEABLE = frozenset({
|
|
98
|
+
"claim history",
|
|
99
|
+
"claim variant",
|
|
100
|
+
"claim falsified",
|
|
101
|
+
"assumption impact",
|
|
102
|
+
"reasoning-diff",
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def exit_sense_for(command: str) -> str | None:
|
|
107
|
+
"""The exit-1 sense id for *command*, or ``None`` when no rule matches."""
|
|
108
|
+
for pattern, sense in EXIT_SENSE_RULES:
|
|
109
|
+
if fnmatch.fnmatch(command, pattern):
|
|
110
|
+
return sense
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# Examples and cross-references
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
#: Authored examples. ``docs/cli-agent-surface.md`` §3.2 asks for "one runnable
|
|
119
|
+
#: example per command, using a workspace file — **not a placeholder**. Agents
|
|
120
|
+
#: pattern-match on invocations far more reliably than they parse flag tables."
|
|
121
|
+
#:
|
|
122
|
+
#: A derived example satisfies the *shape* and not that requirement: an agent
|
|
123
|
+
#: reading ``--sheet <sheet_id> --member <member>`` learns nothing it could not
|
|
124
|
+
#: read off the flag table, so it falls back to `--help` anyway. The guide-eval
|
|
125
|
+
#: run measured what that costs — 87 of 161 commands rendered as placeholders,
|
|
126
|
+
#: and 108 of its 178 findings were some topic being blamed for not shipping
|
|
127
|
+
#: the example that belongs here.
|
|
128
|
+
#:
|
|
129
|
+
#: So: author one wherever an agent has to *choose* a value — a status
|
|
130
|
+
#: archetype, an axis, a rel, a target selector, a JSON payload. Leave it
|
|
131
|
+
#: derived only where the signature already says everything (``ls``, ``logout``).
|
|
132
|
+
#: Values are illustrative but must be **legal**: every flag exists on the
|
|
133
|
+
#: command and every enum value is one it accepts, pinned by
|
|
134
|
+
#: ``cli/tests/test_surface_examples.py`` against the live Click tree.
|
|
135
|
+
COMMAND_EXAMPLES: dict[str, str] = {
|
|
136
|
+
"doctor": "deepcell doctor -f json",
|
|
137
|
+
"ls": "deepcell ls",
|
|
138
|
+
"cat": "deepcell cat model.deepcell",
|
|
139
|
+
"write": "deepcell write model.deepcell --file ./model.deepcell",
|
|
140
|
+
"describe": "deepcell describe model.deepcell --lint",
|
|
141
|
+
"query": "deepcell query model.deepcell Revenue FY2026E",
|
|
142
|
+
"defs list": "deepcell defs list model.deepcell",
|
|
143
|
+
"defs show": "deepcell defs show model.deepcell Revenue",
|
|
144
|
+
"cell-meta": "deepcell cell-meta model.deepcell Revenue FY2026E",
|
|
145
|
+
"edit": "deepcell edit model.deepcell Growth_Rate FY2026E 0.12",
|
|
146
|
+
"replace": 'deepcell replace model.deepcell "<Old>text</Old>" "<New>text</New>"',
|
|
147
|
+
"grep": "deepcell grep Revenue",
|
|
148
|
+
# `orient/start` is the front door. It was `quick-start` for a while, on
|
|
149
|
+
# the strength of a description that read "Start here" — that topic was a
|
|
150
|
+
# finance template index and has since been deleted (`doctor.py` reached
|
|
151
|
+
# the same conclusion independently).
|
|
152
|
+
"guide": "deepcell guide orient/start",
|
|
153
|
+
"rules": "deepcell rules R2",
|
|
154
|
+
"ref": "deepcell ref lint/hardcoded_literal_in_calc",
|
|
155
|
+
"relationships": "deepcell relationships model.deepcell --type business",
|
|
156
|
+
"to-excel": "deepcell to-excel model.deepcell -o model.xlsx",
|
|
157
|
+
"share create": "deepcell share create model.deepcell --permission view",
|
|
158
|
+
"viewer": "deepcell viewer model.deepcell",
|
|
159
|
+
"reasoning lint": "deepcell reasoning lint model.deepcell --strict",
|
|
160
|
+
"reasoning graph": "deepcell reasoning graph model.deepcell --syntax mermaid",
|
|
161
|
+
"project use": "deepcell project use my-project",
|
|
162
|
+
|
|
163
|
+
# ---- Structure -------------------------------------------------------
|
|
164
|
+
"defs add-item": (
|
|
165
|
+
"deepcell defs add-item model.deepcell --name Revenue --label Revenue "
|
|
166
|
+
"--data-type monetary --scale 6 --currency USD --order-mode append"
|
|
167
|
+
),
|
|
168
|
+
"defs update-item": (
|
|
169
|
+
"deepcell defs update-item model.deepcell Revenue --scale 3 --currency USD"
|
|
170
|
+
),
|
|
171
|
+
"defs rename-item": "deepcell defs rename-item model.deepcell Revenue Total_Revenue",
|
|
172
|
+
"defs delete-item": "deepcell defs delete-item model.deepcell Revenue --cascade",
|
|
173
|
+
"defs reorder-item": "deepcell defs reorder-item model.deepcell Revenue --to-index 0",
|
|
174
|
+
"defs add-calc": (
|
|
175
|
+
"deepcell defs add-calc model.deepcell --item Revenue --calc-id calc_revenue "
|
|
176
|
+
"--formula 'Units_Sold[CURRENT] * Unit_Price[CURRENT]' --status projected"
|
|
177
|
+
),
|
|
178
|
+
"defs update-calc": (
|
|
179
|
+
"deepcell defs update-calc model.deepcell calc_revenue "
|
|
180
|
+
"--formula 'Units_Sold[CURRENT] * Unit_Price[CURRENT] * 1.02'"
|
|
181
|
+
),
|
|
182
|
+
# --archetype is what the status MEANS; omitting it leaves the meaning to
|
|
183
|
+
# be guessed from the id's spelling (house rule R13).
|
|
184
|
+
"defs add-status": (
|
|
185
|
+
"deepcell defs add-status model.deepcell --name Budget --ref budget "
|
|
186
|
+
"--archetype budget --authority derived"
|
|
187
|
+
),
|
|
188
|
+
"defs update-status": (
|
|
189
|
+
"deepcell defs update-status model.deepcell budget --archetype budget --label Budget"
|
|
190
|
+
),
|
|
191
|
+
# --state is what says whether the period is over — the question a trailing
|
|
192
|
+
# A/E on the id used to answer by accident.
|
|
193
|
+
"defs add-context": (
|
|
194
|
+
"deepcell defs add-context model.deepcell --name FY2025 --context FY2025 "
|
|
195
|
+
"--kind period --state closed --status actual"
|
|
196
|
+
),
|
|
197
|
+
"defs add-period": (
|
|
198
|
+
"deepcell defs add-period model.deepcell --name FY2026 --context FY2026 "
|
|
199
|
+
"--state future --status projected"
|
|
200
|
+
),
|
|
201
|
+
"defs update-context": (
|
|
202
|
+
"deepcell defs update-context model.deepcell FY2025 --state closed --as-of 2025-12-31"
|
|
203
|
+
),
|
|
204
|
+
# No --label here: `add-scenario` takes the id only, and the display label
|
|
205
|
+
# is set afterwards with `update-scenario --label`.
|
|
206
|
+
"defs add-scenario": "deepcell defs add-scenario model.deepcell --name downside",
|
|
207
|
+
# --name changes the id every scenarioRef points at; --label is display only.
|
|
208
|
+
"defs update-scenario": (
|
|
209
|
+
"deepcell defs update-scenario model.deepcell downside --label 'Bear case'"
|
|
210
|
+
),
|
|
211
|
+
"defs rename-scenario": "deepcell defs rename-scenario model.deepcell downside bear",
|
|
212
|
+
"defs apply": "deepcell defs apply model.deepcell --ops-file ops.json --dry-run",
|
|
213
|
+
|
|
214
|
+
# ---- Layout ----------------------------------------------------------
|
|
215
|
+
"defs add-sheet": "deepcell defs add-sheet model.deepcell --label 'Variance review' --index 1",
|
|
216
|
+
"defs add-block": (
|
|
217
|
+
"deepcell defs add-block model.deepcell --sheet variance --block-type table "
|
|
218
|
+
"--name 'Revenue vs budget' --item-orders Revenue,COGS --context-refs FY2025,FY2026"
|
|
219
|
+
),
|
|
220
|
+
# --member says what kind of id --ref is: 'item' with rows, 'context' with columns.
|
|
221
|
+
"defs add-axis-member": (
|
|
222
|
+
"deepcell defs add-axis-member model.deepcell --sheet variance --block revenue_table "
|
|
223
|
+
"--axis rows --member item --ref Gross_Profit"
|
|
224
|
+
),
|
|
225
|
+
"defs delete-axis-member": (
|
|
226
|
+
"deepcell defs delete-axis-member model.deepcell --sheet variance "
|
|
227
|
+
"--block revenue_table --axis rows --ref Gross_Profit"
|
|
228
|
+
),
|
|
229
|
+
"defs reorder-axis-member": (
|
|
230
|
+
"deepcell defs reorder-axis-member model.deepcell --sheet variance "
|
|
231
|
+
"--block revenue_table --axis rows --ref Gross_Profit --to-index 0"
|
|
232
|
+
),
|
|
233
|
+
"defs reorder-block": (
|
|
234
|
+
"deepcell defs reorder-block model.deepcell --sheet variance --block revenue_table --to-index 0"
|
|
235
|
+
),
|
|
236
|
+
"defs reorder-sheets": "deepcell defs reorder-sheets model.deepcell variance --to-index 0",
|
|
237
|
+
"defs set-block-attrs": (
|
|
238
|
+
"deepcell defs set-block-attrs model.deepcell --sheet variance --block revenue_table "
|
|
239
|
+
"--status-refs actual,budget --status-expansion columns"
|
|
240
|
+
),
|
|
241
|
+
"defs add-sensitivity": (
|
|
242
|
+
"deepcell defs add-sensitivity model.deepcell --sheet summary --name 'IRR sensitivity' "
|
|
243
|
+
"--spec-file sensitivity.json"
|
|
244
|
+
),
|
|
245
|
+
# --block-id scopes the format to one block; without it the rule is global
|
|
246
|
+
# to the sheet and every other block inherits it too.
|
|
247
|
+
"defs set-format": (
|
|
248
|
+
"deepcell defs set-format model.deepcell --sheet-id ic --block-id deal_summary "
|
|
249
|
+
"--item-ref Entry_Multiple --scope item --number-format '0.0x' --bold"
|
|
250
|
+
),
|
|
251
|
+
"defs add-format": (
|
|
252
|
+
"deepcell defs add-format model.deepcell fmt_statement --rule 'default:fontName=Arial'"
|
|
253
|
+
),
|
|
254
|
+
"defs add-rule": (
|
|
255
|
+
"deepcell defs add-rule model.deepcell fmt_statement --target 'level:0:item' "
|
|
256
|
+
"--font-weight bold --number-format '#,##0'"
|
|
257
|
+
),
|
|
258
|
+
|
|
259
|
+
# ---- Prose -----------------------------------------------------------
|
|
260
|
+
"defs add-doc": (
|
|
261
|
+
"deepcell defs add-doc model.deepcell --doc-id variance_note "
|
|
262
|
+
"--name 'July variance note' --lang en --body-file variance-note.md"
|
|
263
|
+
),
|
|
264
|
+
"doc set-body": (
|
|
265
|
+
"deepcell doc set-body model.deepcell --doc variance_note "
|
|
266
|
+
"--body-file variance-note.md -m 'Rewrite the July variance note'"
|
|
267
|
+
),
|
|
268
|
+
# --anchor must be an explicit {#id}, never a heading slug.
|
|
269
|
+
"doc patch-body": (
|
|
270
|
+
"deepcell doc patch-body model.deepcell --doc variance_note --anchor outlook "
|
|
271
|
+
"--markdown '## Outlook {#outlook}\\n\\nQ4 pipeline covers the gap.'"
|
|
272
|
+
),
|
|
273
|
+
"doc outline": "deepcell doc outline model.deepcell --doc variance_note",
|
|
274
|
+
"doc links": "deepcell doc links model.deepcell --doc variance_note --unresolved",
|
|
275
|
+
"doc show": "deepcell doc show model.deepcell --doc variance_note --as markdown",
|
|
276
|
+
"doc lint": "deepcell doc lint model.deepcell --doc variance_note --strict",
|
|
277
|
+
"to-docx": "deepcell to-docx model.deepcell --doc variance_note -o variance-note.docx",
|
|
278
|
+
"to-pptx": "deepcell to-pptx model.deepcell --deck board -o board.pptx",
|
|
279
|
+
|
|
280
|
+
# ---- Reasoning -------------------------------------------------------
|
|
281
|
+
"reasoning add-claim": (
|
|
282
|
+
"deepcell reasoning add-claim model.deepcell --id t_gm --kind thesis "
|
|
283
|
+
"--label 'Gross margin expands' --body 'Mix shift to subscription lifts GM 200bps.' "
|
|
284
|
+
"--item-refs Gross_Margin_Pct --confidence 0.62"
|
|
285
|
+
),
|
|
286
|
+
"reasoning add-assumption": (
|
|
287
|
+
"deepcell reasoning add-assumption model.deepcell --id a_hiring --label 'Hiring lands on plan' "
|
|
288
|
+
"--body 'Sales headcount reaches 40 by Q3.' --item-refs Headcount"
|
|
289
|
+
),
|
|
290
|
+
"reasoning add-evidence": (
|
|
291
|
+
"deepcell reasoning add-evidence model.deepcell --id e_10k --source-ref src_aapl_10k_fy25 "
|
|
292
|
+
"--excerpt 'Gross margin of 46.2%' --retrieved-at 2026-02-19T10:30:00Z"
|
|
293
|
+
),
|
|
294
|
+
# weight is qualitative confidence in the EDGE — never a probability, and
|
|
295
|
+
# never copied from the risk claim's @probability.
|
|
296
|
+
"reasoning add-argument": (
|
|
297
|
+
"deepcell reasoning add-argument model.deepcell --from-id r_pricing --to-id t_gm "
|
|
298
|
+
"--rel refutes --weight 0.6"
|
|
299
|
+
),
|
|
300
|
+
"reasoning set-conclusion": "deepcell reasoning set-conclusion model.deepcell t_gm",
|
|
301
|
+
"reasoning supersede-claim": (
|
|
302
|
+
"deepcell reasoning supersede-claim model.deepcell t_gm --id t_gm_v2 "
|
|
303
|
+
"--label 'Gross margin expands, slower' --body 'Mix shift lifts GM 120bps.' --confidence 0.55"
|
|
304
|
+
),
|
|
305
|
+
"reasoning supersede-assumption": (
|
|
306
|
+
"deepcell reasoning supersede-assumption model.deepcell a_hiring --id a_hiring_v2 "
|
|
307
|
+
"--label 'Hiring lands one quarter late'"
|
|
308
|
+
),
|
|
309
|
+
# A premise actuals disproved is marked broken in place, not rewritten —
|
|
310
|
+
# the wording is the record of what was believed.
|
|
311
|
+
"reasoning update-assumption": (
|
|
312
|
+
"deepcell reasoning update-assumption model.deepcell a_hiring --status broken --broken-at 2026-07-31"
|
|
313
|
+
),
|
|
314
|
+
"reasoning update-claim": (
|
|
315
|
+
"deepcell reasoning update-claim model.deepcell t_gm --status falsified"
|
|
316
|
+
),
|
|
317
|
+
"reasoning update-argument": (
|
|
318
|
+
"deepcell reasoning update-argument model.deepcell arg_pricing --weight 0.6"
|
|
319
|
+
),
|
|
320
|
+
# The edge's address is its id OR its triple; a bare edge has no id to type.
|
|
321
|
+
"reasoning delete-argument": (
|
|
322
|
+
"deepcell reasoning delete-argument model.deepcell --from-id r_pricing --edge-rel refutes --to-id t_gm"
|
|
323
|
+
),
|
|
324
|
+
# --cascade takes the edges with the node; --allow-dangling leaves them.
|
|
325
|
+
"reasoning delete-claim": "deepcell reasoning delete-claim model.deepcell t_gm --cascade",
|
|
326
|
+
"reasoning impact": "deepcell reasoning impact model.deepcell a_hiring",
|
|
327
|
+
"assumption impact": "deepcell assumption impact model.deepcell a_hiring",
|
|
328
|
+
|
|
329
|
+
# ---- Ingest ----------------------------------------------------------
|
|
330
|
+
# Items need id/name/level (+ row and sheet to auto-map cells); contexts
|
|
331
|
+
# need id/name/period_type (+ column). These are snake_case JSON keys, not
|
|
332
|
+
# the XML spellings (itemId / contextId / statusRef).
|
|
333
|
+
"import": (
|
|
334
|
+
"deepcell import variance.xlsx --name variance_import "
|
|
335
|
+
"""--items '[{"id":"Revenue","name":"Revenue","level":0,"row":2,"sheet":"Variance"}]' """
|
|
336
|
+
"""--contexts '[{"id":"FY25A","name":"FY25 actual","period_type":"annual","status":"actual","column":"B"}]'"""
|
|
337
|
+
),
|
|
338
|
+
"ingest cn extract": (
|
|
339
|
+
"deepcell ingest cn extract https://static.cninfo.com.cn/finalpage/2026-03-28/1224567890.PDF "
|
|
340
|
+
"--statement income --persist"
|
|
341
|
+
),
|
|
342
|
+
"ingest cn search": "deepcell ingest cn search 600519",
|
|
343
|
+
"ingest cn filings": "deepcell ingest cn filings 600519 --type annual --count 5",
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
#: Typed ids per command, resolved by ``deepcell ref``. Deliberately sparse:
|
|
347
|
+
#: a pointer is worth printing only where it answers the question the command's
|
|
348
|
+
#: own help cannot.
|
|
349
|
+
COMMAND_SEE_ALSO: dict[str, list[str]] = {
|
|
350
|
+
"write": ["guide:generate/whole-doc", "rule:R1", "ref:exit/1"],
|
|
351
|
+
"describe": ["ref:lint/unrendered_value", "rule:R9"],
|
|
352
|
+
"edit": ["guide:generate/values", "ref:exit/1"],
|
|
353
|
+
"replace": ["guide:revise/values", "ref:exit/1"],
|
|
354
|
+
"query": ["guide:verify/query-back", "rule:R4"],
|
|
355
|
+
"defs apply": ["guide:generate/calcs", "ref:exit/1"],
|
|
356
|
+
"defs list": ["guide:revise/structure", "rule:R4"],
|
|
357
|
+
"defs show": ["guide:revise/structure", "rule:R4"],
|
|
358
|
+
"defs add-calc": ["guide:generate/calcs", "rule:R2", "ref:function/NPV"],
|
|
359
|
+
"defs add-item": ["guide:generate/structure", "rule:R7"],
|
|
360
|
+
"defs add-sensitivity": ["ref:op/add_sensitivity_block", "rule:R9"],
|
|
361
|
+
"reasoning lint": ["rule:R8", "ref:lint/unanchored_claim"],
|
|
362
|
+
"reasoning add-claim": ["guide:revise/reasoning", "ref:claim", "rule:R8"],
|
|
363
|
+
"reasoning set-conclusion": ["rule:R14", "ref:lint/dangling_conclusion_ref"],
|
|
364
|
+
"reasoning set-key-question": [
|
|
365
|
+
"rule:R19",
|
|
366
|
+
"ref:lint/dangling_key_question_ref",
|
|
367
|
+
"ref:lint/ambiguous_key_question",
|
|
368
|
+
],
|
|
369
|
+
"to-excel": ["guide:present/layout", "rule:R9"],
|
|
370
|
+
"rules": ["guide:house-rules"],
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _derive_example(path: str, command: click.Command) -> str:
|
|
375
|
+
"""Build a runnable-shaped invocation from the command's own signature.
|
|
376
|
+
|
|
377
|
+
Uses the real required parameter names, so the shape is right even though
|
|
378
|
+
the values are illustrative. Marked ``derived`` in the manifest — an
|
|
379
|
+
example nobody ran should never look like one that was tested.
|
|
380
|
+
"""
|
|
381
|
+
parts = [f"deepcell {path}"]
|
|
382
|
+
for param in command.params:
|
|
383
|
+
if not isinstance(param, click.Argument) or not param.required:
|
|
384
|
+
continue
|
|
385
|
+
name = param.name or "arg"
|
|
386
|
+
if "file" in name or name in {"filename", "path"}:
|
|
387
|
+
parts.append("model.deepcell")
|
|
388
|
+
elif param.nargs == -1:
|
|
389
|
+
parts.append(f"<{name}...>")
|
|
390
|
+
else:
|
|
391
|
+
parts.append(f"<{name}>")
|
|
392
|
+
for param in command.params:
|
|
393
|
+
if isinstance(param, click.Option) and param.required:
|
|
394
|
+
flag = max(param.opts, key=len)
|
|
395
|
+
parts.append(f"{flag} <{param.name}>")
|
|
396
|
+
return " ".join(parts)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
# ---------------------------------------------------------------------------
|
|
400
|
+
# Walking the Click tree
|
|
401
|
+
# ---------------------------------------------------------------------------
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _json_safe(value: Any) -> Any:
|
|
405
|
+
"""Return *value* if it survives a JSON round trip, else ``None``.
|
|
406
|
+
|
|
407
|
+
Click represents "no default" with a sentinel object, and some defaults are
|
|
408
|
+
callables. The manifest is committed and byte-compared by a drift test, so
|
|
409
|
+
an unserialisable default must be dropped rather than stringified — a repr
|
|
410
|
+
would change between Python versions and show up as spurious drift.
|
|
411
|
+
"""
|
|
412
|
+
if isinstance(value, (str, int, float, bool)):
|
|
413
|
+
return value
|
|
414
|
+
if isinstance(value, (list, tuple)):
|
|
415
|
+
items = [_json_safe(item) for item in value]
|
|
416
|
+
return items if all(item is not None for item in items) else None
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _type_name(param: click.Parameter) -> str:
|
|
421
|
+
param_type = getattr(param, "type", None)
|
|
422
|
+
name = getattr(param_type, "name", None)
|
|
423
|
+
if name:
|
|
424
|
+
return str(name)
|
|
425
|
+
return type(param_type).__name__ if param_type else "text"
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _describe_param(param: click.Parameter) -> dict[str, Any]:
|
|
429
|
+
"""One argument or option as data.
|
|
430
|
+
|
|
431
|
+
For an **option**, ``name`` is the flag you would actually type
|
|
432
|
+
(``--order-mode``), not Click's Python identifier (``order_mode``). The
|
|
433
|
+
identifier is kept as ``param`` for anyone mapping back to the callback.
|
|
434
|
+
Reporting the identifier as ``name`` was a live trap: it looks like a flag,
|
|
435
|
+
so an agent reading the manifest would construct ``--order_mode`` and get a
|
|
436
|
+
usage error with no hint why.
|
|
437
|
+
"""
|
|
438
|
+
is_option = isinstance(param, click.Option)
|
|
439
|
+
entry: dict[str, Any] = {
|
|
440
|
+
"name": (max(param.opts, key=len) if is_option and param.opts else param.name),
|
|
441
|
+
"type": _type_name(param),
|
|
442
|
+
"required": bool(param.required),
|
|
443
|
+
}
|
|
444
|
+
if is_option:
|
|
445
|
+
entry["param"] = param.name
|
|
446
|
+
# `--inherit/--no-inherit` is one Click option with the off-switch in
|
|
447
|
+
# `secondary_opts`. Listing only `opts` published half of every boolean
|
|
448
|
+
# pair: `deepcell ref` printed `--inherit` and nothing else, and the
|
|
449
|
+
# guide-eval flag check called a reference command that used
|
|
450
|
+
# `--no-inherit` a defect in the task. The flag is real; the manifest
|
|
451
|
+
# was the thing that did not know about it.
|
|
452
|
+
entry["flags"] = list(param.opts) + list(getattr(param, "secondary_opts", []))
|
|
453
|
+
entry["is_flag"] = bool(getattr(param, "is_flag", False))
|
|
454
|
+
default = _json_safe(param.default)
|
|
455
|
+
if default is not None and not entry["is_flag"]:
|
|
456
|
+
entry["default"] = default
|
|
457
|
+
if param.help:
|
|
458
|
+
entry["help"] = " ".join(str(param.help).split())
|
|
459
|
+
choices = getattr(param.type, "choices", None)
|
|
460
|
+
if choices:
|
|
461
|
+
entry["choices"] = list(choices)
|
|
462
|
+
else:
|
|
463
|
+
entry["variadic"] = param.nargs == -1
|
|
464
|
+
return entry
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _summary(command: click.Command) -> str:
|
|
468
|
+
"""The first line of the help text — what the index shows."""
|
|
469
|
+
text = command.get_short_help_str(limit=200) or ""
|
|
470
|
+
return " ".join(str(text).split())
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _describe_command(path: str, command: click.Command) -> dict[str, Any]:
|
|
474
|
+
args = [p for p in command.params if isinstance(p, click.Argument)]
|
|
475
|
+
opts = [p for p in command.params if isinstance(p, click.Option)]
|
|
476
|
+
|
|
477
|
+
exit_codes: dict[str, str] = {"0": "completed"}
|
|
478
|
+
sense = exit_sense_for(path)
|
|
479
|
+
if sense:
|
|
480
|
+
exit_codes["1"] = sense
|
|
481
|
+
if path in EXIT_TWO_ON_UNPARSEABLE:
|
|
482
|
+
exit_codes["2"] = "unparseable-document"
|
|
483
|
+
|
|
484
|
+
authored = COMMAND_EXAMPLES.get(path)
|
|
485
|
+
# The stage is a property of the top-level command, not of each leaf:
|
|
486
|
+
# `defs add-calc` is generate because `defs` is. Carrying it per entry is
|
|
487
|
+
# what lets every other catalog — the MCP tool description, the published
|
|
488
|
+
# docs, `deepcell help` — group identically without restating the table.
|
|
489
|
+
from deepcell_cli.stages import tier_for
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
"id": f"cmd:{path}",
|
|
493
|
+
"name": path,
|
|
494
|
+
"stage": tier_for(path.split(" ", 1)[0]),
|
|
495
|
+
"summary": _summary(command),
|
|
496
|
+
"args": [_describe_param(p) for p in args],
|
|
497
|
+
"flags": [_describe_param(p) for p in opts],
|
|
498
|
+
"exit_codes": exit_codes,
|
|
499
|
+
"example": authored or _derive_example(path, command),
|
|
500
|
+
"example_source": "authored" if authored else "derived",
|
|
501
|
+
"see_also": COMMAND_SEE_ALSO.get(path, []),
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _walk(command: click.Command, path: str, out: dict[str, dict[str, Any]]) -> None:
|
|
506
|
+
if isinstance(command, click.Group):
|
|
507
|
+
# An `invoke_without_command=True` group is a command *as well as* a
|
|
508
|
+
# group: `deepcell example --pack finance` runs, and the `--pack` is
|
|
509
|
+
# declared on the group, not on any leaf. Walking only the leaves left
|
|
510
|
+
# it out of the manifest entirely — so `deepcell help` never mentioned
|
|
511
|
+
# it, `cmd:example` resolved to nothing, and the flag existed in the
|
|
512
|
+
# CLI while every generated surface said it did not.
|
|
513
|
+
if command.invoke_without_command:
|
|
514
|
+
out[path] = _describe_command(path, command)
|
|
515
|
+
ctx = click.Context(command)
|
|
516
|
+
for name in command.list_commands(ctx):
|
|
517
|
+
sub = command.get_command(ctx, name)
|
|
518
|
+
if sub is not None and not sub.hidden:
|
|
519
|
+
_walk(sub, f"{path} {name}".strip(), out)
|
|
520
|
+
return
|
|
521
|
+
out[path] = _describe_command(path, command)
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def build_commands() -> dict[str, dict[str, Any]]:
|
|
525
|
+
"""Every invocable command, keyed by its full invocation path.
|
|
526
|
+
|
|
527
|
+
Groups are walked rather than listed: ``defs`` is not a command, ``defs
|
|
528
|
+
add-calc`` is, and it is the leaf an agent actually invokes. A group that
|
|
529
|
+
is *itself* invocable appears too — see ``_walk``.
|
|
530
|
+
|
|
531
|
+
⚠ A group that declares options but is **not** invocable has nowhere to put
|
|
532
|
+
them: its options must be typed before the subcommand, so attributing them
|
|
533
|
+
to each leaf would misdescribe where they go, and the group has no entry of
|
|
534
|
+
its own. ``test_surface_group_options`` fails on that shape rather than
|
|
535
|
+
letting the option vanish the way ``example --pack`` did — the fix is to
|
|
536
|
+
move the option onto the leaves, or make the group invocable.
|
|
537
|
+
"""
|
|
538
|
+
from deepcell_cli.main import cli
|
|
539
|
+
|
|
540
|
+
out: dict[str, dict[str, Any]] = {}
|
|
541
|
+
ctx = click.Context(cli)
|
|
542
|
+
for name in cli.list_commands(ctx):
|
|
543
|
+
sub = cli.get_command(ctx, name)
|
|
544
|
+
if sub is not None and not sub.hidden:
|
|
545
|
+
_walk(sub, name, out)
|
|
546
|
+
return dict(sorted(out.items()))
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def build_stages() -> list[dict[str, str]]:
|
|
550
|
+
"""The tiers a command's ``stage`` names, in the order the work happens.
|
|
551
|
+
|
|
552
|
+
The order is the one thing a consumer *cannot* recover from the per-command
|
|
553
|
+
``stage`` field. ``commands`` is sorted by invocation path, so a renderer
|
|
554
|
+
grouping by first appearance would lead with ``cat``'s tier and print
|
|
555
|
+
``present`` before ``generate`` — teaching the wrong shape of the work with
|
|
556
|
+
no way to tell it had.
|
|
557
|
+
|
|
558
|
+
That gap is not hypothetical. The web CLI reference reads this manifest and
|
|
559
|
+
was the one catalog still ignoring the stage vocabulary, because grouping
|
|
560
|
+
by it would have meant hardcoding this list in TypeScript — a sixth private
|
|
561
|
+
copy of exactly the table ``stages.py`` exists to be the only one of.
|
|
562
|
+
"""
|
|
563
|
+
from deepcell_cli import stages
|
|
564
|
+
|
|
565
|
+
return [
|
|
566
|
+
{"name": tier, "summary": stages.TIER_SUMMARY[tier]}
|
|
567
|
+
for tier in stages.ALL_TIERS
|
|
568
|
+
]
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def build_root() -> dict[str, str]:
|
|
572
|
+
"""What ``deepcell --help`` prints around the command list.
|
|
573
|
+
|
|
574
|
+
The command *list* has always been derivable from ``build_commands()``, but
|
|
575
|
+
the prose either side of it was reachable only by running the CLI: the root
|
|
576
|
+
group's docstring above it, and ``_EPILOG`` — the getting-started,
|
|
577
|
+
file-handling, sync and learn-more recipes — below. Anything rendering a
|
|
578
|
+
root help pane therefore had to hand-copy them, which is the drift this
|
|
579
|
+
module exists to remove.
|
|
580
|
+
|
|
581
|
+
Both are static. The epilog is an f-string, but the only value it
|
|
582
|
+
interpolates is ``DEFAULT_API_URL``, a module constant — so the output is
|
|
583
|
+
deterministic and safe to bake into a byte-compared artifact. If a
|
|
584
|
+
*runtime* value is ever interpolated in here, this stops being generable
|
|
585
|
+
and the drift test will say so on the next unrelated commit.
|
|
586
|
+
"""
|
|
587
|
+
from deepcell_cli.main import cli
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
"help": _help_text(cli.help),
|
|
591
|
+
"epilog": _help_text(cli.epilog),
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _help_text(raw: str | None) -> str:
|
|
596
|
+
"""Dedent, and drop Click's ``\\b`` no-rewrap markers.
|
|
597
|
+
|
|
598
|
+
A lone ``\\b`` line tells Click's formatter not to re-wrap the paragraph
|
|
599
|
+
below it; Click consumes it and it never reaches the terminal. Carrying it
|
|
600
|
+
into the artifact would put a literal backspace control character in front
|
|
601
|
+
of five paragraphs of every consumer's output — so stripping it is what
|
|
602
|
+
keeps the artifact faithful, not a liberty taken with it.
|
|
603
|
+
"""
|
|
604
|
+
text = inspect.cleandoc(raw or "")
|
|
605
|
+
lines = [line for line in text.split("\n") if line.strip("\b \t") or not line.strip()]
|
|
606
|
+
return "\n".join(line for line in lines if line.strip() != "\b").strip("\n")
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def build_globals() -> list[dict[str, Any]]:
|
|
610
|
+
"""The root group's options — the ones that work on *every* command.
|
|
611
|
+
|
|
612
|
+
``-f/--format``, ``-v/--verbose``, ``--workspace`` and ``--version`` are
|
|
613
|
+
declared once, on the root group, and ``FlexibleGroup`` deliberately
|
|
614
|
+
accepts them *after* the subcommand too (`deepcell describe x -f json`).
|
|
615
|
+
They were the same blind spot as `example --pack`, one level up: real,
|
|
616
|
+
usable on all 139 commands, and absent from every generated surface.
|
|
617
|
+
|
|
618
|
+
Emitted once rather than copied onto each command — a per-command copy
|
|
619
|
+
would be 139 places for one fact, and would also imply they are
|
|
620
|
+
command-specific, which is the opposite of what they are.
|
|
621
|
+
"""
|
|
622
|
+
from deepcell_cli.main import cli
|
|
623
|
+
|
|
624
|
+
return [
|
|
625
|
+
_describe_param(param)
|
|
626
|
+
for param in cli.params
|
|
627
|
+
if isinstance(param, click.Option)
|
|
628
|
+
]
|