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.
Files changed (67) hide show
  1. deepcell_cli/__init__.py +12 -0
  2. deepcell_cli/__main__.py +5 -0
  3. deepcell_cli/_findings.py +84 -0
  4. deepcell_cli/capabilities.py +560 -0
  5. deepcell_cli/capability-contract.json +15622 -0
  6. deepcell_cli/client.py +503 -0
  7. deepcell_cli/commands/__init__.py +1 -0
  8. deepcell_cli/commands/_batch_input.py +29 -0
  9. deepcell_cli/commands/_datatypes.py +56 -0
  10. deepcell_cli/commands/_negative_args.py +133 -0
  11. deepcell_cli/commands/_swapped_args.py +153 -0
  12. deepcell_cli/commands/_version_display.py +40 -0
  13. deepcell_cli/commands/_write_opts.py +139 -0
  14. deepcell_cli/commands/account.py +123 -0
  15. deepcell_cli/commands/auth.py +610 -0
  16. deepcell_cli/commands/changes.py +307 -0
  17. deepcell_cli/commands/deck.py +594 -0
  18. deepcell_cli/commands/defs.py +3890 -0
  19. deepcell_cli/commands/describe.py +902 -0
  20. deepcell_cli/commands/doc.py +529 -0
  21. deepcell_cli/commands/doctor.py +257 -0
  22. deepcell_cli/commands/download.py +36 -0
  23. deepcell_cli/commands/edit.py +384 -0
  24. deepcell_cli/commands/example.py +161 -0
  25. deepcell_cli/commands/export.py +81 -0
  26. deepcell_cli/commands/export_docx.py +57 -0
  27. deepcell_cli/commands/export_pdf.py +66 -0
  28. deepcell_cli/commands/export_pptx.py +45 -0
  29. deepcell_cli/commands/files.py +386 -0
  30. deepcell_cli/commands/grep.py +90 -0
  31. deepcell_cli/commands/guide.py +431 -0
  32. deepcell_cli/commands/help_cmd.py +348 -0
  33. deepcell_cli/commands/impact.py +382 -0
  34. deepcell_cli/commands/import_cmd.py +208 -0
  35. deepcell_cli/commands/ingest.py +110 -0
  36. deepcell_cli/commands/merge.py +399 -0
  37. deepcell_cli/commands/query.py +718 -0
  38. deepcell_cli/commands/reasoning.py +2981 -0
  39. deepcell_cli/commands/ref.py +279 -0
  40. deepcell_cli/commands/replace.py +326 -0
  41. deepcell_cli/commands/rules.py +206 -0
  42. deepcell_cli/commands/share.py +186 -0
  43. deepcell_cli/commands/sync.py +804 -0
  44. deepcell_cli/commands/upgrade.py +185 -0
  45. deepcell_cli/commands/variant.py +353 -0
  46. deepcell_cli/commands/version.py +445 -0
  47. deepcell_cli/commands/viewer.py +54 -0
  48. deepcell_cli/commands/workspace.py +101 -0
  49. deepcell_cli/config.py +352 -0
  50. deepcell_cli/context.py +187 -0
  51. deepcell_cli/errors.py +141 -0
  52. deepcell_cli/logging_setup.py +161 -0
  53. deepcell_cli/main.py +518 -0
  54. deepcell_cli/mcp_server.py +906 -0
  55. deepcell_cli/oauth_provider.py +580 -0
  56. deepcell_cli/output.py +503 -0
  57. deepcell_cli/revision.py +164 -0
  58. deepcell_cli/stages.py +223 -0
  59. deepcell_cli/surface.py +628 -0
  60. deepcell_cli/sync_state.py +120 -0
  61. deepcell_cli/upgrade_check.py +399 -0
  62. deepcell_cli/xml_replace.py +89 -0
  63. deepcell_cli-0.6.1.dist-info/METADATA +264 -0
  64. deepcell_cli-0.6.1.dist-info/RECORD +67 -0
  65. deepcell_cli-0.6.1.dist-info/WHEEL +5 -0
  66. deepcell_cli-0.6.1.dist-info/entry_points.txt +3 -0
  67. deepcell_cli-0.6.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,348 @@
1
+ """``deepcell help`` — the whole command tree as one document.
2
+
3
+ ``--help`` answers a human's question, one command at a time. An agent
4
+ planning a twelve-step build would pay twelve ``--help`` round trips before it
5
+ could write step one; ``deepcell help -f json`` makes planning a single call,
6
+ and gives the agent something it can cache for the rest of the session.
7
+
8
+ Everything is local — the Click tree and the surface data both live in this
9
+ package — so this works with no network and no workspace, which is what makes
10
+ it usable as the *first* call rather than one that needs setup first.
11
+
12
+ See ``docs/cli-agent-surface.md`` §3.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from functools import lru_cache
19
+
20
+ import click
21
+
22
+ from deepcell_cli.capabilities import (
23
+ get_command_capability,
24
+ load_capability_contract,
25
+ )
26
+ from deepcell_cli.context import Ctx, pass_ctx
27
+ from deepcell_cli.output import output, print_plain
28
+ from deepcell_cli.surface import build_commands, build_globals
29
+
30
+ #: Exit 1 with no command-specific sense. Kept out of the manifest's
31
+ #: ``exit_codes`` map — an explicit "nothing special here" value would look
32
+ #: like a sixth sense of exit 1, and there are five.
33
+ _GENERIC_EXIT_1 = "the command failed"
34
+
35
+
36
+ def _render_command(entry: dict) -> str:
37
+ lines = [entry["name"], "=" * len(entry["name"]), ""]
38
+ if entry.get("summary"):
39
+ lines += [entry["summary"], ""]
40
+
41
+ lines.append(f"Usage: {entry['example']}")
42
+ if entry.get("example_source") == "derived":
43
+ # Never let a derived example pass for a tested one.
44
+ lines.append(" (shape derived from the signature, not a tested run)")
45
+ lines.append("")
46
+
47
+ if entry["args"]:
48
+ lines.append("Arguments:")
49
+ for arg in entry["args"]:
50
+ suffix = "..." if arg.get("variadic") else ""
51
+ need = "" if arg["required"] else " (optional)"
52
+ lines.append(f" {arg['name']}{suffix}: {arg['type']}{need}")
53
+ lines.append("")
54
+
55
+ if entry["flags"]:
56
+ lines.append("Flags:")
57
+ for flag in entry["flags"]:
58
+ names = ", ".join(flag["flags"])
59
+ bits = [flag["type"] if not flag["is_flag"] else "flag"]
60
+ if flag.get("choices"):
61
+ bits.append("one of: " + "|".join(map(str, flag["choices"])))
62
+ if "default" in flag:
63
+ bits.append(f"default {flag['default']}")
64
+ if flag["required"]:
65
+ bits.append("required")
66
+ lines.append(f" {names} ({'; '.join(bits)})")
67
+ if flag.get("help"):
68
+ lines.append(f" {flag['help']}")
69
+ lines.append("")
70
+
71
+ lines.append("Exit codes:")
72
+ for code in sorted(entry["exit_codes"]):
73
+ lines.append(f" {code} {entry['exit_codes'][code]}")
74
+ if "1" not in entry["exit_codes"]:
75
+ lines.append(f" 1 {_GENERIC_EXIT_1}")
76
+ lines.append(" 2 malformed input — the command was never attempted")
77
+
78
+ if entry["see_also"]:
79
+ lines += ["", "See also: " + " · ".join(entry["see_also"])]
80
+ lines.append("Read one: deepcell ref <id>")
81
+
82
+ capability = entry.get("capability")
83
+ if capability:
84
+ lines += ["", "Availability:"]
85
+ for transport in ("cli", "mcp", "in_process"):
86
+ policy = capability["transports"][transport]
87
+ label = transport.replace("_", "-")
88
+ if policy["available"]:
89
+ lines.append(f" {label}: available")
90
+ else:
91
+ lines.append(f" {label}: unavailable ({policy['reason']})")
92
+ endpoints = capability.get("endpoints") or []
93
+ if endpoints:
94
+ rendered = ", ".join(
95
+ f"{row['method']} {row['path']}" for row in endpoints
96
+ )
97
+ lines.append(f" Jingwei: {rendered}")
98
+ elif capability.get("endpoint_exception"):
99
+ lines.append(
100
+ " Jingwei: none "
101
+ f"({capability['endpoint_exception']['reason']})"
102
+ )
103
+
104
+ return "\n".join(lines)
105
+
106
+
107
+ def _render_group(
108
+ path: str, children: list[str], commands: dict, *, titled: bool = True
109
+ ) -> str:
110
+ """A group as an index of its subcommands, one summary line each.
111
+
112
+ ``titled=False`` when this is appended to the group's *own* command help —
113
+ the heading is already on the page, and repeating it reads as two answers
114
+ to one question.
115
+ """
116
+ lines = [path, "=" * len(path), ""] if titled else []
117
+ lines += [f"{len(children)} subcommand(s):", ""]
118
+ width = max(len(c) for c in children)
119
+ lines += [
120
+ f" {name:<{width}} {commands[name].get('summary', '')}".rstrip()
121
+ for name in children
122
+ ]
123
+ lines += ["", f"One command: deepcell help {children[0]}"]
124
+ return "\n".join(lines)
125
+
126
+
127
+ @lru_cache(maxsize=128)
128
+ def _needle_pattern(needle: str) -> "re.Pattern[str] | None":
129
+ """Whole-word, separator-flexible matcher — the CLI half of one rule.
130
+
131
+ The authority is ``src.core.search_text.needle_pattern``; this package
132
+ cannot import the backend, so the rule is restated here the same way the
133
+ two ``guide`` index renderers restate their headings. Keep them in step:
134
+ whole words because `lag` must not match *flag*, flexible separators
135
+ because "order mode" is spelled `--order-mode`, and the word guard applied
136
+ per end only where that end is a word character — `--order-mode` and
137
+ `.deepcell` start on punctuation that is its own boundary.
138
+ """
139
+ words = [p for p in re.split(r"[\s\-_]+", needle) if p]
140
+ if not words:
141
+ return None
142
+ core = r"[\s\-_]+".join(re.escape(w) for w in words)
143
+ head = r"(?<!\w)" if re.match(r"\w", words[0]) else ""
144
+ tail = r"(?!\w)" if re.search(r"\w\Z", words[-1]) else ""
145
+ return re.compile(head + core + tail, re.IGNORECASE)
146
+
147
+
148
+ def _detail_hit(text: str, needle: str) -> bool:
149
+ """Whether *needle* appears as a whole word in *text*."""
150
+ pattern = _needle_pattern(needle)
151
+ return bool(pattern and text and pattern.search(text))
152
+
153
+
154
+ def _detail_text(entry: dict) -> str:
155
+ """The parts of a command a name-and-summary search cannot see.
156
+
157
+ Flag names and their help, the worked example, and the argument names —
158
+ all already in memory from `build_commands()`, so widening the index here
159
+ costs no IO at all. This is where a caller's actual words live: nothing is
160
+ *summarised* as "sensitivity sweep", but a flag's help says it, and
161
+ `deepcell help --search` answering "No command matches" to a word the CLI
162
+ itself prints is the surface calling its own vocabulary unknown.
163
+ """
164
+ parts: list[str] = [str(entry.get("example") or "")]
165
+ for flag in entry.get("flags") or ():
166
+ parts.append(str(flag.get("name") or ""))
167
+ parts.append(str(flag.get("help") or ""))
168
+ for arg in entry.get("args") or ():
169
+ parts.append(str(arg.get("name") or ""))
170
+ return "\n".join(p for p in parts if p)
171
+
172
+
173
+ def _matching(commands: dict, search: str) -> dict:
174
+ """Commands matching *search*, name and summary hits first.
175
+
176
+ Ordered, not merely filtered: a detail hit may only ever append below the
177
+ name and summary hits, so widening the index cannot push the command you
178
+ typed the name of below one that merely mentions it in a flag's help. The
179
+ same invariant `ref search` and `guide --search` hold, for the same reason.
180
+ """
181
+ needle = search.lower()
182
+ named, detailed = {}, {}
183
+ for name, entry in commands.items():
184
+ if needle in name.lower() or needle in (entry.get("summary") or "").lower():
185
+ named[name] = entry
186
+ elif _detail_hit(_detail_text(entry), search):
187
+ detailed[name] = entry
188
+ return {**named, **detailed}
189
+
190
+
191
+ @click.command("help")
192
+ @click.argument("command", nargs=-1)
193
+ @click.option(
194
+ "--stage",
195
+ default=None,
196
+ metavar="NAME",
197
+ help="Only this stage's commands (e.g. generate). Same stages as `deepcell guide`.",
198
+ )
199
+ @click.option(
200
+ "--search",
201
+ default=None,
202
+ metavar="TEXT",
203
+ help="Commands whose name or summary matches TEXT.",
204
+ )
205
+ @pass_ctx
206
+ def help_cmd(
207
+ ctx: Ctx,
208
+ command: tuple[str, ...],
209
+ stage: str | None,
210
+ search: str | None,
211
+ ) -> None:
212
+ """Every command, flag, exit code and example as one document.
213
+
214
+ \b
215
+ The whole tree: deepcell help
216
+ One stage: deepcell help --stage generate
217
+ Find one: deepcell help --search sensitivity
218
+ One command: deepcell help defs add-calc
219
+ As data: deepcell help -f json
220
+
221
+ `-f json` is the form to parse: it is the same structure the published
222
+ command reference is rendered from, so what you read here and what the
223
+ docs say cannot disagree.
224
+
225
+ This answers what to TYPE. For what to DO next — the procedure, in the
226
+ order the work happens — run `deepcell guide`. The two use the same six
227
+ stage names, so a stage that reads well in one filters the other.
228
+ """
229
+ commands = {
230
+ name: {
231
+ **entry,
232
+ "capability": get_command_capability(name),
233
+ }
234
+ for name, entry in build_commands().items()
235
+ }
236
+
237
+ if (stage or search) and command:
238
+ raise click.ClickException(
239
+ "--stage/--search filter the listing; they do not apply when you "
240
+ "have already named a command. Drop the filter, or drop the "
241
+ f"command: deepcell help {' '.join(command)}"
242
+ )
243
+
244
+ if stage:
245
+ from deepcell_cli.stages import ALL_TIERS
246
+
247
+ if stage not in ALL_TIERS:
248
+ raise click.ClickException(
249
+ f"Unknown stage: {stage}. One of: {', '.join(ALL_TIERS)}."
250
+ )
251
+ commands = {n: e for n, e in commands.items() if e.get("stage") == stage}
252
+ if search:
253
+ commands = _matching(commands, search)
254
+ if not commands:
255
+ raise click.ClickException(f"No command matches: {search}")
256
+
257
+ if command:
258
+ path = " ".join(command)
259
+ entry = commands.get(path)
260
+ if entry is None:
261
+ # `build_commands()` keys leaf commands only, so a group path misses
262
+ # the table. Asking what is under `defs` is a well-formed question
263
+ # and answering it with a non-zero exit made an agent treat the group
264
+ # as nonexistent — so render the group's leaves instead. Kept here
265
+ # rather than in `build_commands()`, whose output is a codegen source
266
+ # for the surface manifest.
267
+ children = sorted(
268
+ name for name in commands if name.startswith(f"{path} ")
269
+ )
270
+ if children:
271
+ if ctx.fmt == "json":
272
+ output({"group": path, "commands": children}, ctx.fmt)
273
+ else:
274
+ print_plain(_render_group(path, children, commands))
275
+ return
276
+ near = [name for name in commands if name.startswith(path)]
277
+ hint = f" Did you mean: {', '.join(near[:5])}?" if near else ""
278
+ raise click.ClickException(f"Unknown command: {path}.{hint}")
279
+ # A command that is *also* a group (`example`) needs both halves: what
280
+ # running it bare does, and what lives under it. Rendering only the
281
+ # command would hide the subcommands that were the whole reason to
282
+ # look it up.
283
+ children = sorted(name for name in commands if name.startswith(f"{path} "))
284
+ if ctx.fmt == "json":
285
+ output({**entry, "commands": children} if children else entry, ctx.fmt)
286
+ else:
287
+ rendered = _render_command(entry)
288
+ if children:
289
+ rendered += "\n\n" + _render_group(
290
+ path, children, commands, titled=False
291
+ )
292
+ print_plain(rendered)
293
+ return
294
+
295
+ if ctx.fmt == "json":
296
+ contract = load_capability_contract()
297
+ output(
298
+ {
299
+ "commands": commands,
300
+ "globals": build_globals(),
301
+ "capabilities": {
302
+ "schema_version": contract["schema_version"],
303
+ "operation_count": len(contract["operations"]),
304
+ "reader": "deepcell guide --capabilities",
305
+ },
306
+ },
307
+ ctx.fmt,
308
+ )
309
+ return
310
+
311
+ # Grouped by stage, not alphabetical. 161 leaf commands in one A-Z run is
312
+ # not an index, it is a search problem — the same conclusion the guide
313
+ # restructure reached about 55 flat topics. The stage names are guide's,
314
+ # so a reader who oriented there lands in the right block here.
315
+ from deepcell_cli.stages import ALL_TIERS, TIER_SUMMARY
316
+
317
+ width = max(len(name) for name in commands)
318
+ lines: list[str] = []
319
+ for tier in ALL_TIERS:
320
+ block = [(n, e) for n, e in commands.items() if e.get("stage") == tier]
321
+ if not block:
322
+ continue
323
+ if lines:
324
+ lines.append("")
325
+ lines.append(f"{tier.upper()} — {TIER_SUMMARY[tier]}")
326
+ lines += [f" {name:<{width}} {entry['summary']}" for name, entry in block]
327
+ # A command whose top-level name is not staged cannot silently disappear
328
+ # from the catalog just because the index has no home for it.
329
+ unstaged = [(n, e) for n, e in commands.items() if e.get("stage") not in ALL_TIERS]
330
+ if unstaged:
331
+ lines += ["", "UNSTAGED — no stage declared in deepcell_cli.stages"]
332
+ lines += [f" {name:<{width}} {entry['summary']}" for name, entry in unstaged]
333
+ # Printed once, at the end, because they apply to every line above — and
334
+ # because they are declared on the root group, so they appear in no
335
+ # command's own flag list no matter which one you read.
336
+ lines += ["", "Global options — valid on any command, before or after it:"]
337
+ for option in build_globals():
338
+ names = ", ".join(option["flags"])
339
+ kind = "flag" if option["is_flag"] else option["type"]
340
+ lines.append(f" {names} ({kind})")
341
+ if option.get("help"):
342
+ lines.append(f" {option['help']}")
343
+ lines += [
344
+ "",
345
+ f"{len(commands)} commands. Read one: deepcell help <command>",
346
+ "As data (one call, cacheable): deepcell help -f json",
347
+ ]
348
+ print_plain("\n".join(lines))