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
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"""``deepcell guide`` — how DeepCell itself works, served from the topic registry.
|
|
2
|
+
|
|
3
|
+
The topic index is never restated here or in the command help: ``deepcell guide``
|
|
4
|
+
with no argument prints it from the registry. A hand-maintained copy would rot
|
|
5
|
+
the moment a topic is added. See docs/agent-skills-redesign.md.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from deepcell_cli.config import get_active_workspace
|
|
13
|
+
from deepcell_cli.context import Ctx, pass_ctx
|
|
14
|
+
from deepcell_cli.errors import APIError
|
|
15
|
+
from deepcell_cli.output import output, print_plain
|
|
16
|
+
from deepcell_cli.stages import STAGE_ORDER
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
#: Printed above `--all`'s listing, because `--all` is an INDEX and does not
|
|
20
|
+
#: read like one. The 2026-08-27 prompt_stock eval is the case in point: a
|
|
21
|
+
#: worker ran `guide --all`, got every topic name on one screen, and went
|
|
22
|
+
#: straight to writing calcs — never opening `calc-specificity`, the one page
|
|
23
|
+
#: carrying the selector syntax its task needed. The output is ~11k of one-line
|
|
24
|
+
#: descriptions, so an agent that has read it has plausibly "read the guidance"
|
|
25
|
+
#: by every signal except the one that matters.
|
|
26
|
+
#:
|
|
27
|
+
#: One string with real newlines rather than a list of fragments: the list form
|
|
28
|
+
#: needed two source lines per output line and so read as four elements with
|
|
29
|
+
#: missing commas — the exact shape of a real bug, and CodeQL flagged it as
|
|
30
|
+
#: one. Prose belongs in a block that looks like the prose it prints.
|
|
31
|
+
_ALL_TOPICS_BANNER = """\
|
|
32
|
+
{count} topics. These are one-line SUMMARIES, not the guidance itself —
|
|
33
|
+
the syntax, the procedure and the worked examples are in the page, not in
|
|
34
|
+
this index. Open the topics your task touches before you write calcs,
|
|
35
|
+
reasoning or a deck."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _resolve_frontend_url(content: str) -> str:
|
|
39
|
+
"""Replace ``${FRONTEND_URL}`` / ``${WORKSPACE_SLUG}`` placeholders.
|
|
40
|
+
|
|
41
|
+
The frontend URL comes from the ``FRONTEND_URL`` / ``DEEPCELL_FRONTEND_URL``
|
|
42
|
+
env vars when set, otherwise it is derived from the configured API URL.
|
|
43
|
+
"""
|
|
44
|
+
from deepcell_cli.config import frontend_base_url
|
|
45
|
+
|
|
46
|
+
url = frontend_base_url().rstrip("/")
|
|
47
|
+
content = content.replace("${FRONTEND_URL}", url).replace(
|
|
48
|
+
"${DEEPCELL_FRONTEND_URL}", url
|
|
49
|
+
)
|
|
50
|
+
return content.replace("${WORKSPACE_SLUG}", get_active_workspace() or "<slug>")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@click.command()
|
|
54
|
+
@click.argument("topic", required=False)
|
|
55
|
+
@click.option("--stage", default=None, help="Only this stage's topics (e.g. generate).")
|
|
56
|
+
@click.option(
|
|
57
|
+
"--pack",
|
|
58
|
+
default=None,
|
|
59
|
+
help="One domain pack: its vocabulary page, then its topics (e.g. finance).",
|
|
60
|
+
)
|
|
61
|
+
@click.option("--packs", "list_packs_flag", is_flag=True, help="List the domain packs.")
|
|
62
|
+
@click.option(
|
|
63
|
+
"--capabilities",
|
|
64
|
+
"capabilities_flag",
|
|
65
|
+
is_flag=True,
|
|
66
|
+
help="Show the Jingwei/CLI/reference/transport capability contract.",
|
|
67
|
+
)
|
|
68
|
+
@click.option("--all", "all_topics", is_flag=True,
|
|
69
|
+
help="Also show the long-form reference pages and domain packs.")
|
|
70
|
+
@click.option("--search", default=None, metavar="TEXT",
|
|
71
|
+
help="Topics whose name or description matches, across both catalogs.")
|
|
72
|
+
@pass_ctx
|
|
73
|
+
def guide(
|
|
74
|
+
ctx: Ctx,
|
|
75
|
+
topic: str | None,
|
|
76
|
+
stage: str | None,
|
|
77
|
+
pack: str | None,
|
|
78
|
+
list_packs_flag: bool,
|
|
79
|
+
capabilities_flag: bool,
|
|
80
|
+
all_topics: bool,
|
|
81
|
+
search: str | None,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Explain how DeepCell itself works.
|
|
84
|
+
|
|
85
|
+
Topics cover the .deepcell format, calc semantics, built-in functions,
|
|
86
|
+
layout and decks, and reasoning structure.
|
|
87
|
+
|
|
88
|
+
\b
|
|
89
|
+
List the topics: deepcell guide
|
|
90
|
+
One stage: deepcell guide --stage generate (a bare stage name works too)
|
|
91
|
+
Read one: deepcell guide <topic>
|
|
92
|
+
Find by keyword: deepcell guide --search scenario
|
|
93
|
+
Domain packs: deepcell guide --packs
|
|
94
|
+
Capability map: deepcell guide --capabilities
|
|
95
|
+
One pack: deepcell guide --pack finance
|
|
96
|
+
Everything: deepcell guide --all
|
|
97
|
+
|
|
98
|
+
The listing is generated from the topic registry, so it is always the
|
|
99
|
+
complete and current set — this help text deliberately does not repeat it.
|
|
100
|
+
It shows the staged index by default: the work in the order it happens,
|
|
101
|
+
one screen. `--all` adds two more catalogs — the long-form reference pages
|
|
102
|
+
behind each stage, and the domain packs.
|
|
103
|
+
"""
|
|
104
|
+
if capabilities_flag:
|
|
105
|
+
data = ctx.client.get("/guide/capabilities/index")
|
|
106
|
+
if ctx.fmt == "json":
|
|
107
|
+
output(data, ctx.fmt)
|
|
108
|
+
return
|
|
109
|
+
operations = data.get("operations", []) if isinstance(data, dict) else []
|
|
110
|
+
lines = [
|
|
111
|
+
"Jingwei endpoint -> CLI capability contract",
|
|
112
|
+
"",
|
|
113
|
+
]
|
|
114
|
+
for operation in operations:
|
|
115
|
+
commands = operation.get("commands") or []
|
|
116
|
+
if commands:
|
|
117
|
+
disposition = ", ".join(commands)
|
|
118
|
+
else:
|
|
119
|
+
exception = operation.get("exception") or {}
|
|
120
|
+
disposition = f"EXEMPT ({exception.get('reason', 'unclassified')})"
|
|
121
|
+
lines.append(
|
|
122
|
+
f"{operation.get('method', '?'):<6} "
|
|
123
|
+
f"{operation.get('path', ''):<72} {disposition}"
|
|
124
|
+
)
|
|
125
|
+
lines += [
|
|
126
|
+
"",
|
|
127
|
+
f"{len(operations)} operations. As data: deepcell guide "
|
|
128
|
+
"--capabilities -f json",
|
|
129
|
+
]
|
|
130
|
+
print_plain("\n".join(lines))
|
|
131
|
+
elif topic and _stage_alias(topic) in STAGE_ORDER and not stage:
|
|
132
|
+
# A bare stage name is the documented entry point half-typed:
|
|
133
|
+
# `guide orient` for `guide --stage orient`. Every transcript that
|
|
134
|
+
# tried it exited 1 with a did-you-mean naming `reit`. The stage
|
|
135
|
+
# names are the CLI's own (`deepcell_cli.stages` mirrors the guide's,
|
|
136
|
+
# and a test pins them equal), so no round trip decides this.
|
|
137
|
+
_list_topics(
|
|
138
|
+
ctx, stage=_stage_alias(topic), pack=pack,
|
|
139
|
+
all_topics=all_topics, search=search,
|
|
140
|
+
)
|
|
141
|
+
elif topic:
|
|
142
|
+
data = ctx.client.get(f"/guide/{topic}")
|
|
143
|
+
# The endpoint returns {"topic": "...", "content": "..."}
|
|
144
|
+
content = data.get("content", "") if isinstance(data, dict) else str(data)
|
|
145
|
+
content = _resolve_frontend_url(content)
|
|
146
|
+
if ctx.fmt == "plain":
|
|
147
|
+
# Human default: the Markdown body without a metadata wrapper.
|
|
148
|
+
print_plain(content)
|
|
149
|
+
else:
|
|
150
|
+
if isinstance(data, dict) and "content" in data:
|
|
151
|
+
data["content"] = content
|
|
152
|
+
output(data, ctx.fmt)
|
|
153
|
+
elif list_packs_flag:
|
|
154
|
+
data = ctx.client.get("/guide/packs/index")
|
|
155
|
+
packs = data.get("packs", []) if isinstance(data, dict) else data
|
|
156
|
+
if ctx.fmt == "json" or not isinstance(packs, list) or not packs:
|
|
157
|
+
output(data, ctx.fmt)
|
|
158
|
+
return
|
|
159
|
+
width = max(len(str(p.get("name", ""))) for p in packs)
|
|
160
|
+
lines = [
|
|
161
|
+
f"{str(p.get('name','')):<{width}} {p.get('summary','')}"
|
|
162
|
+
f" [{_pack_counts(p)}]"
|
|
163
|
+
for p in packs
|
|
164
|
+
]
|
|
165
|
+
lines += [
|
|
166
|
+
"",
|
|
167
|
+
"One pack: deepcell guide --pack <name>",
|
|
168
|
+
"A pack is a vocabulary over the format, not a different format.",
|
|
169
|
+
]
|
|
170
|
+
print_plain("\n".join(lines))
|
|
171
|
+
elif pack and not (search or stage or all_topics):
|
|
172
|
+
# Only the bare `--pack <name>`: combined with `--stage`, `--all` or
|
|
173
|
+
# `--search` it is a filter on the listing, as it always was.
|
|
174
|
+
_show_pack(ctx, pack)
|
|
175
|
+
else:
|
|
176
|
+
_list_topics(
|
|
177
|
+
ctx, stage=stage, pack=pack, all_topics=all_topics, search=search
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _stage_alias(topic: str) -> str:
|
|
182
|
+
"""`guide orient/` is `guide orient`: the staged topic names are
|
|
183
|
+
`orient/start`, so a reader who has seen them types the slash."""
|
|
184
|
+
return topic.strip().lower().rstrip("/")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _pack_counts(pack: dict) -> str:
|
|
188
|
+
"""`3 topics, 1 example` — so a reader knows a thin pack is thin before
|
|
189
|
+
opening it, rather than reading an empty listing as a broken command."""
|
|
190
|
+
topics = len(pack.get("topics") or [])
|
|
191
|
+
examples = len(pack.get("examples") or [])
|
|
192
|
+
return (
|
|
193
|
+
f"{topics} topic{'s' if topics != 1 else ''}, "
|
|
194
|
+
f"{examples} example{'s' if examples != 1 else ''}"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _show_pack(ctx: Ctx, pack: str) -> None:
|
|
199
|
+
"""`--pack <name>`: the vocabulary page, then the pack's topics.
|
|
200
|
+
|
|
201
|
+
Two reads, on purpose. The listing is what tells an unknown pack apart
|
|
202
|
+
from a thin one (404 naming the known packs, against a real answer), and
|
|
203
|
+
the page is the content: three of the four packs are *only* their
|
|
204
|
+
vocabulary page and one worked example, so a topic listing alone printed
|
|
205
|
+
`(empty)` for them and the reader stopped there. `-f json` keeps the bare
|
|
206
|
+
topic list the endpoint returns — the vocabulary row in it names the page
|
|
207
|
+
to read next.
|
|
208
|
+
"""
|
|
209
|
+
data = ctx.client.get("/guide", params={"pack": pack})
|
|
210
|
+
if ctx.fmt == "json":
|
|
211
|
+
output(data, ctx.fmt)
|
|
212
|
+
return
|
|
213
|
+
topics = data if isinstance(data, list) else data.get("topics", [])
|
|
214
|
+
topics = [t for t in topics if isinstance(t, dict)]
|
|
215
|
+
page_name = f"pack:{pack}"
|
|
216
|
+
try:
|
|
217
|
+
page = ctx.client.get(f"/guide/{page_name}")
|
|
218
|
+
except APIError as exc:
|
|
219
|
+
# A server without the `pack:<name>` route (older than the vocabulary
|
|
220
|
+
# page) answers "Unknown topic". The listing above already succeeded,
|
|
221
|
+
# so the pack is real: render what that server has — the topic rows
|
|
222
|
+
# — rather than failing a command that used to work against it.
|
|
223
|
+
if exc.status_code != 404:
|
|
224
|
+
raise
|
|
225
|
+
page = None
|
|
226
|
+
lines: list[str] = []
|
|
227
|
+
if page is not None:
|
|
228
|
+
content = page.get("content", "") if isinstance(page, dict) else str(page)
|
|
229
|
+
lines += [_resolve_frontend_url(content).rstrip("\n"), ""]
|
|
230
|
+
|
|
231
|
+
rows = [t for t in topics if t.get("name") != page_name]
|
|
232
|
+
if rows:
|
|
233
|
+
width = max(len(str(t.get("name", ""))) for t in rows)
|
|
234
|
+
lines.append(f"TOPICS — the {pack} pack's guide pages")
|
|
235
|
+
lines += [
|
|
236
|
+
f" {str(t['name']):<{width}} {_describe(t)}".rstrip() for t in rows
|
|
237
|
+
]
|
|
238
|
+
lines += ["", "Read a topic: deepcell guide <topic>"]
|
|
239
|
+
else:
|
|
240
|
+
# Not `(empty)`: the page above is the whole pack, and saying so is
|
|
241
|
+
# what stops the reader looking for a listing that does not exist.
|
|
242
|
+
examples = page.get("examples") if isinstance(page, dict) else None
|
|
243
|
+
if page is not None:
|
|
244
|
+
lines.append(
|
|
245
|
+
f"No guide topics of its own — the {pack} pack is this "
|
|
246
|
+
"vocabulary page plus its worked example."
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
lines.append(
|
|
250
|
+
f"No guide topics of its own — the {pack} pack is its worked "
|
|
251
|
+
"example."
|
|
252
|
+
)
|
|
253
|
+
if examples:
|
|
254
|
+
lines.append(
|
|
255
|
+
"Worked example: deepcell example show "
|
|
256
|
+
f"{examples[0]} transcript"
|
|
257
|
+
)
|
|
258
|
+
else:
|
|
259
|
+
lines.append(f"Worked examples: deepcell example --pack {pack}")
|
|
260
|
+
print_plain("\n".join(lines))
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _list_topics(
|
|
264
|
+
ctx: Ctx,
|
|
265
|
+
*,
|
|
266
|
+
stage: str | None,
|
|
267
|
+
pack: str | None,
|
|
268
|
+
all_topics: bool,
|
|
269
|
+
search: str | None,
|
|
270
|
+
) -> None:
|
|
271
|
+
"""The index, narrowed by whatever the caller passed."""
|
|
272
|
+
params: dict = {}
|
|
273
|
+
if pack:
|
|
274
|
+
params["pack"] = pack
|
|
275
|
+
if stage:
|
|
276
|
+
params["stage"] = stage
|
|
277
|
+
if all_topics:
|
|
278
|
+
params["all"] = "true"
|
|
279
|
+
if search:
|
|
280
|
+
params["search"] = search
|
|
281
|
+
data = ctx.client.get("/guide", params=params or None)
|
|
282
|
+
|
|
283
|
+
if ctx.fmt == "json":
|
|
284
|
+
output(data, ctx.fmt)
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
topics = data if isinstance(data, list) else data.get("topics", [])
|
|
288
|
+
if not isinstance(topics, list) or not topics:
|
|
289
|
+
if search:
|
|
290
|
+
# An empty list rendered as `[]` reads as a broken command
|
|
291
|
+
# rather than a real answer, and leaves the reader without the
|
|
292
|
+
# next thing to try — the guide searches names, not bodies.
|
|
293
|
+
print_plain(
|
|
294
|
+
f"No topic name or description matches '{search}'.\n"
|
|
295
|
+
"Browse the full index: deepcell guide --all\n"
|
|
296
|
+
"Legal values and enumerations: deepcell ref search "
|
|
297
|
+
f"{search}"
|
|
298
|
+
)
|
|
299
|
+
return
|
|
300
|
+
if stage:
|
|
301
|
+
# A stage the CLI knows and the server does not list is a
|
|
302
|
+
# version skew, not a typo; name the stages the server has.
|
|
303
|
+
print_plain(
|
|
304
|
+
f"No topics in stage '{stage}'.\n"
|
|
305
|
+
"Stages with topics: deepcell guide"
|
|
306
|
+
)
|
|
307
|
+
return
|
|
308
|
+
output(data, ctx.fmt)
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
# The generic plain formatter collapses {name, description} rows to
|
|
312
|
+
# names only — useless for picking a topic — so format it here.
|
|
313
|
+
width = max(len(str(t.get("name", ""))) for t in topics)
|
|
314
|
+
lines: list[str] = []
|
|
315
|
+
if all_topics:
|
|
316
|
+
# Before the names rather than after them: a reader who needs the
|
|
317
|
+
# warning is about to skim past the footer. No trailing blank —
|
|
318
|
+
# the stage loop below opens each band with one whenever `lines`
|
|
319
|
+
# is already non-empty.
|
|
320
|
+
lines += _ALL_TOPICS_BANNER.format(count=len(topics)).split("\n")
|
|
321
|
+
# Group by stage: the sections *are* the index's structure, and a flat
|
|
322
|
+
# list would throw away the ordering that makes the stages worth
|
|
323
|
+
# having. The server returns topics already in stage order, so first
|
|
324
|
+
# appearance is the right order to render them in.
|
|
325
|
+
current: str | None = "\x00"
|
|
326
|
+
for t in topics:
|
|
327
|
+
band = _band_of(t)
|
|
328
|
+
if band != current:
|
|
329
|
+
current = band
|
|
330
|
+
stage_name = t.get("stage")
|
|
331
|
+
if stage_name:
|
|
332
|
+
if lines:
|
|
333
|
+
lines.append("")
|
|
334
|
+
header = stage_name.upper()
|
|
335
|
+
summary = t.get("stage_summary") or ""
|
|
336
|
+
lines.append(f"{header} — {summary}" if summary else header)
|
|
337
|
+
elif all_topics or search:
|
|
338
|
+
# The appendix used to open with no header at all: 60
|
|
339
|
+
# unexplained names, which is what made `--all` read as a
|
|
340
|
+
# second competing catalog rather than an appendix. Say
|
|
341
|
+
# what it is once, at the top of it — and say it separately
|
|
342
|
+
# for the two catalogs that were sharing one heading, since
|
|
343
|
+
# "LEGACY" over `dcf` and `balance-sheet` advises a reader
|
|
344
|
+
# away from the only page that covers them.
|
|
345
|
+
if lines:
|
|
346
|
+
lines.append("")
|
|
347
|
+
lines.append(_appendix_header(band))
|
|
348
|
+
prefix = " " if t.get("stage") else ""
|
|
349
|
+
lines.append(
|
|
350
|
+
f"{prefix}{str(t['name']):<{width}} {_describe(t)}".rstrip()
|
|
351
|
+
)
|
|
352
|
+
# A snippet means the description above does NOT contain the search
|
|
353
|
+
# term — the match is in the topic's body. Showing the line it
|
|
354
|
+
# matched on is what makes such a row legible; without it the
|
|
355
|
+
# reader sees a description with none of their words in it.
|
|
356
|
+
if t.get("snippet"):
|
|
357
|
+
lines.append(f"{prefix}{'':<{width}} ↳ {t['snippet']}")
|
|
358
|
+
|
|
359
|
+
lines += ["", "Read a topic: deepcell guide <topic>"]
|
|
360
|
+
if all_topics:
|
|
361
|
+
# The other half of the same miss: the worker needed a formula
|
|
362
|
+
# SELECTOR, which is an enumeration and so has never lived in the
|
|
363
|
+
# guide at all. Naming `ref` here costs one line and covers the
|
|
364
|
+
# case where the reader is on the wrong surface entirely.
|
|
365
|
+
lines.append("Legal values and formula syntax: deepcell ref")
|
|
366
|
+
if not all_topics and not stage and not search:
|
|
367
|
+
lines.append(
|
|
368
|
+
"Long-form reference and domain packs: deepcell guide --all"
|
|
369
|
+
)
|
|
370
|
+
print_plain("\n".join(lines))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
#: The core reference band. Not `LEGACY`: these pages average 2.6x the bytes of
|
|
374
|
+
#: the staged topic that replaced their procedure (`reasoning` is 34,630
|
|
375
|
+
#: against `revise/reasoning`'s 5,122), and the redirect banner they serve says
|
|
376
|
+
#: so — "this page is the full reference and stays authoritative for the detail
|
|
377
|
+
#: below". A heading that called them legacy contradicted the banner printed
|
|
378
|
+
#: underneath them, and told a reader to prefer the shorter page.
|
|
379
|
+
_REFERENCE_HEADER = (
|
|
380
|
+
"REFERENCE — the long-form detail behind the staged procedure, still "
|
|
381
|
+
"authoritative; `→` names the staged topic that owns the procedure"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _band_of(topic: dict) -> str:
|
|
386
|
+
"""Which heading a row belongs under.
|
|
387
|
+
|
|
388
|
+
Bands, not stages, because the appendix holds two unrelated catalogs and
|
|
389
|
+
used to print one heading over both. A stage name is its own band so the
|
|
390
|
+
staged index still breaks per stage, and a pack is keyed by name so a
|
|
391
|
+
second pack with topics gets its own heading rather than being folded into
|
|
392
|
+
finance's.
|
|
393
|
+
"""
|
|
394
|
+
if topic.get("stage"):
|
|
395
|
+
return str(topic["stage"])
|
|
396
|
+
if topic.get("legacy"):
|
|
397
|
+
return "reference"
|
|
398
|
+
return f"pack:{topic.get('pack') or 'unknown'}"
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _appendix_header(band: str) -> str:
|
|
402
|
+
"""The heading for one appendix band."""
|
|
403
|
+
if band == "reference":
|
|
404
|
+
return _REFERENCE_HEADER
|
|
405
|
+
name = band.split(":", 1)[1]
|
|
406
|
+
return (
|
|
407
|
+
f"PACK: {name.upper()} — domain templates and vocabulary, not part of "
|
|
408
|
+
f"the core path; list them alone with `deepcell guide --pack {name}`"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _describe(topic: dict) -> str:
|
|
413
|
+
"""A row's description, plus where its procedure went if it moved.
|
|
414
|
+
|
|
415
|
+
A legacy topic and the staged topic that replaced it are the same subject
|
|
416
|
+
under two names, and `--all` listed both with nothing saying so — leaving
|
|
417
|
+
the reader to guess whether `items` or `orient/concepts` was the one to
|
|
418
|
+
open. The server already knows: `replaced_by` is the alias table it has
|
|
419
|
+
always had. Rendering it turns the appendix from a second, competing
|
|
420
|
+
catalog into a set of forwarding addresses.
|
|
421
|
+
|
|
422
|
+
`guide:` pointers print bare because they are the common case and the
|
|
423
|
+
prefix is noise; anything else keeps its namespace, since `ref function`
|
|
424
|
+
and `rules R1` are read with different commands.
|
|
425
|
+
"""
|
|
426
|
+
description = topic.get("description", "") or ""
|
|
427
|
+
pointer = topic.get("replaced_by")
|
|
428
|
+
if not pointer:
|
|
429
|
+
return description
|
|
430
|
+
shown = pointer[len("guide:"):] if pointer.startswith("guide:") else pointer
|
|
431
|
+
return f"{description} → {shown}".strip()
|