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,161 @@
|
|
|
1
|
+
"""``deepcell example`` — complete, valid documents, indexed by mechanic.
|
|
2
|
+
|
|
3
|
+
The anti-guessing surface. Models are complicated to build, and an agent that
|
|
4
|
+
guesses at the format gets rejected by the parser; give it a shape that is
|
|
5
|
+
already valid and it copies that instead. See ``docs/cli-agent-surface.md`` §7.
|
|
6
|
+
|
|
7
|
+
Three layers per example, read in order — skeleton, full, transcript — and the
|
|
8
|
+
transcript is the point. Agents rarely get the XML wrong once they have seen
|
|
9
|
+
one; they routinely get the *verb order* wrong.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from deepcell_cli.context import Ctx, pass_ctx
|
|
19
|
+
from deepcell_cli.output import output, print_plain
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group(invoke_without_command=True)
|
|
23
|
+
@click.option("--pack", default=None, help="Core examples plus this pack's.")
|
|
24
|
+
@pass_ctx
|
|
25
|
+
def example(ctx: Ctx, pack: str | None) -> None:
|
|
26
|
+
"""Complete, valid documents to copy the shape from.
|
|
27
|
+
|
|
28
|
+
\b
|
|
29
|
+
List them: deepcell example
|
|
30
|
+
Read the build: deepcell example show ops/headcount-plan transcript
|
|
31
|
+
Seed a file: deepcell example get ops/headcount-plan --into plan.deepcell
|
|
32
|
+
|
|
33
|
+
Indexed by the hard feature each demonstrates, not by industry — one
|
|
34
|
+
example per mechanic, each in a different domain.
|
|
35
|
+
"""
|
|
36
|
+
if click.get_current_context().invoked_subcommand is not None:
|
|
37
|
+
return
|
|
38
|
+
_print_example_list(ctx, pack)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _print_example_list(ctx: Ctx, pack: str | None = None) -> None:
|
|
42
|
+
"""Print every example name and the mechanic it demonstrates."""
|
|
43
|
+
data = ctx.client.get("/examples", params={"pack": pack} if pack else None)
|
|
44
|
+
examples = data.get("examples", []) if isinstance(data, dict) else data
|
|
45
|
+
|
|
46
|
+
if ctx.fmt == "json":
|
|
47
|
+
output(data, ctx.fmt)
|
|
48
|
+
return
|
|
49
|
+
if not isinstance(examples, list) or not examples:
|
|
50
|
+
print_plain("No examples available.")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
width = max(len(str(e.get("name", ""))) for e in examples)
|
|
54
|
+
lines = [
|
|
55
|
+
f"{str(e.get('name', '')):<{width}} {e.get('mechanic', '')}"
|
|
56
|
+
f" [{' + '.join(str(s).title() for s in e.get('surfaces', []))}]"
|
|
57
|
+
f" [{','.join(e.get('journeys', []))}]"
|
|
58
|
+
f"{' [' + e['pack'] + ' pack]' if e.get('pack') else ''}"
|
|
59
|
+
for e in examples
|
|
60
|
+
]
|
|
61
|
+
lines += [
|
|
62
|
+
"",
|
|
63
|
+
"Read the build order: deepcell example show <name> transcript",
|
|
64
|
+
"Seed a file: deepcell example get <name> --into <file>",
|
|
65
|
+
]
|
|
66
|
+
print_plain("\n".join(lines))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@example.command("list")
|
|
70
|
+
@click.option("--pack", default=None, help="Core examples plus this pack's.")
|
|
71
|
+
@pass_ctx
|
|
72
|
+
def example_list(ctx: Ctx, pack: str | None) -> None:
|
|
73
|
+
"""List every example name — the same output as bare `deepcell example`.
|
|
74
|
+
|
|
75
|
+
An alias, and a deliberate one: every other surface with a listing has a
|
|
76
|
+
verb (`ref search`, `defs list`, `workspace list`), so `example list` is
|
|
77
|
+
the natural guess, and it used to fail with a bare `No such command`.
|
|
78
|
+
"""
|
|
79
|
+
_print_example_list(ctx, pack)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@example.command("show")
|
|
83
|
+
@click.argument("name", required=False)
|
|
84
|
+
@click.argument("layer", required=False, default="full")
|
|
85
|
+
@pass_ctx
|
|
86
|
+
def example_show(ctx: Ctx, name: str | None, layer: str) -> None:
|
|
87
|
+
"""Print one layer of an example: skeleton, full, or transcript.
|
|
88
|
+
|
|
89
|
+
Defaults to `full`. `transcript` is the one worth reading first — it is
|
|
90
|
+
the ordered command sequence that produced the document, which is the part
|
|
91
|
+
agents most need and never get.
|
|
92
|
+
|
|
93
|
+
With no NAME it lists the examples. Omitting the argument was the one case
|
|
94
|
+
that got *less* help than getting it wrong — a wrong name has always come
|
|
95
|
+
back with every legal name and a did-you-mean, while no name at all raised
|
|
96
|
+
a bare `Missing argument 'NAME'`. Two of eleven tasks in the 2026-08-02 CLI
|
|
97
|
+
eval hit that, and an empty argument is the clearer request for a list.
|
|
98
|
+
"""
|
|
99
|
+
if not name:
|
|
100
|
+
_print_example_list(ctx)
|
|
101
|
+
return
|
|
102
|
+
data = ctx.client.get(f"/examples/{name}/layer/{layer}")
|
|
103
|
+
if ctx.fmt == "json":
|
|
104
|
+
output(data, ctx.fmt)
|
|
105
|
+
return
|
|
106
|
+
content = data.get("content", "") if isinstance(data, dict) else str(data)
|
|
107
|
+
print_plain(content.rstrip())
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@example.command("get")
|
|
111
|
+
@click.argument("name")
|
|
112
|
+
@click.option(
|
|
113
|
+
"--into",
|
|
114
|
+
"into",
|
|
115
|
+
required=True,
|
|
116
|
+
type=click.Path(dir_okay=False, writable=True),
|
|
117
|
+
help="Local path to write the document to.",
|
|
118
|
+
)
|
|
119
|
+
@click.option(
|
|
120
|
+
"--layer",
|
|
121
|
+
default="full",
|
|
122
|
+
type=click.Choice(["skeleton", "full"]),
|
|
123
|
+
show_default=True,
|
|
124
|
+
help="Which document to seed from.",
|
|
125
|
+
)
|
|
126
|
+
@click.option("--force", is_flag=True, help="Overwrite an existing file.")
|
|
127
|
+
@pass_ctx
|
|
128
|
+
def example_get(ctx: Ctx, name: str, into: str, layer: str, force: bool) -> None:
|
|
129
|
+
"""Write an example document to a local file.
|
|
130
|
+
|
|
131
|
+
The file is guaranteed to parse — that is the whole point of seeding from
|
|
132
|
+
one rather than writing XML from memory. Upload it with
|
|
133
|
+
`deepcell write <workspace-file> --file <local-file>`, then edit it in
|
|
134
|
+
place.
|
|
135
|
+
"""
|
|
136
|
+
target = Path(into)
|
|
137
|
+
if target.exists() and not force:
|
|
138
|
+
# Refuse rather than clobber: `--into` is a local path and the caller
|
|
139
|
+
# may have pointed it at real work.
|
|
140
|
+
raise click.ClickException(
|
|
141
|
+
f"{target} already exists. Pass --force to overwrite it."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
data = ctx.client.get(f"/examples/{name}/layer/{layer}")
|
|
145
|
+
content = data.get("content", "") if isinstance(data, dict) else str(data)
|
|
146
|
+
if not content.strip():
|
|
147
|
+
raise click.ClickException(f"Example {name}/{layer} is empty.")
|
|
148
|
+
|
|
149
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
target.write_text(content, encoding="utf-8")
|
|
151
|
+
|
|
152
|
+
if ctx.fmt == "json":
|
|
153
|
+
output({"name": name, "layer": layer, "path": str(target),
|
|
154
|
+
"bytes": len(content.encode("utf-8"))}, ctx.fmt)
|
|
155
|
+
return
|
|
156
|
+
print_plain(
|
|
157
|
+
f"Wrote {target} ({len(content.encode('utf-8'))} bytes) from "
|
|
158
|
+
f"example {name}/{layer}.\n"
|
|
159
|
+
f"Upload it: deepcell write {target.name} --file {target}\n"
|
|
160
|
+
f"See how it was built: deepcell example show {name} transcript"
|
|
161
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""``deepcell to-excel`` — export .deepcell files to Excel."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from deepcell_cli.context import Ctx, pass_ctx
|
|
8
|
+
from deepcell_cli.output import echo_conversion_warnings, echo_success
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.argument("filename")
|
|
13
|
+
@click.option("-o", "--output", "out_path", default=None, help="Output file path (default: <filename>.xlsx).")
|
|
14
|
+
@click.option("--formulas", is_flag=True, help="Export with live Excel formulas.")
|
|
15
|
+
@click.option("--recalculate", is_flag=True, help="Recompute formula values via the LibreOffice service (requires --formulas).")
|
|
16
|
+
@click.option("--scenario", "scenario_id", default=None, help="Document scenario ID to export (applies its VariableOverrides; omit for the default scenario).")
|
|
17
|
+
@click.option(
|
|
18
|
+
"--variant",
|
|
19
|
+
"variant_id",
|
|
20
|
+
default=None,
|
|
21
|
+
hidden=True,
|
|
22
|
+
help="(removed) Never worked — see --scenario for document scenarios.",
|
|
23
|
+
)
|
|
24
|
+
@pass_ctx
|
|
25
|
+
def to_excel(ctx: Ctx, filename: str, out_path: str | None, formulas: bool, recalculate: bool, scenario_id: str | None, variant_id: str | None) -> None:
|
|
26
|
+
"""Export a .deepcell file to Excel format.
|
|
27
|
+
|
|
28
|
+
The exported workbook is shaped by two optional definition sections:
|
|
29
|
+
PresentationDefinitions (tab/block layout) and FormatDefinitions (cell
|
|
30
|
+
styling). Run `deepcell guide present/layout` or `deepcell ref format` for
|
|
31
|
+
details.
|
|
32
|
+
|
|
33
|
+
A workbook carries the MODEL: cells, formats and text blocks. It does not
|
|
34
|
+
carry charts or the SourceDefinitions provenance footnotes — the export
|
|
35
|
+
reports both as conversion warnings rather than dropping them silently.
|
|
36
|
+
Read sources with `deepcell ref source`, and take charts to a deck with
|
|
37
|
+
to-pptx or to the web viewer; see `deepcell guide present/deliver`.
|
|
38
|
+
"""
|
|
39
|
+
if variant_id is not None:
|
|
40
|
+
raise click.UsageError(
|
|
41
|
+
"--variant was removed: the backend's /to-excel has no variant field, "
|
|
42
|
+
"so the flag silently exported the base model. To export a document "
|
|
43
|
+
"scenario (Bull/Bear/... defined via `deepcell defs add-scenario`) use "
|
|
44
|
+
"--scenario SCENARIO_ID. Workspace variants are git branches "
|
|
45
|
+
"(`deepcell variant list`); to-excel always exports the file content "
|
|
46
|
+
"of the current variant."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if recalculate and not formulas:
|
|
50
|
+
raise click.UsageError(
|
|
51
|
+
"--recalculate requires --formulas: the LibreOffice pass recomputes "
|
|
52
|
+
"the workbook's live formulas, so without --formulas the server "
|
|
53
|
+
"silently skips it."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
slug = ctx.require_workspace()
|
|
57
|
+
|
|
58
|
+
# Determine output filename
|
|
59
|
+
if not out_path:
|
|
60
|
+
base = filename.rsplit(".", 1)[0] if "." in filename else filename
|
|
61
|
+
out_path = f"{base}.xlsx"
|
|
62
|
+
|
|
63
|
+
body: dict = {
|
|
64
|
+
"workspace_slug": slug,
|
|
65
|
+
"source_filename": filename,
|
|
66
|
+
"filename": out_path,
|
|
67
|
+
"export_formulas": formulas,
|
|
68
|
+
"should_recalculate": True,
|
|
69
|
+
"recalculate": recalculate,
|
|
70
|
+
}
|
|
71
|
+
if scenario_id:
|
|
72
|
+
body["scenario_id"] = scenario_id
|
|
73
|
+
|
|
74
|
+
# Server-side recalculation can exceed the default 30s client timeout.
|
|
75
|
+
resp = ctx.client.post_raw("/to-excel", json=body, timeout=300.0)
|
|
76
|
+
|
|
77
|
+
with open(out_path, "wb") as fh:
|
|
78
|
+
fh.write(resp.content)
|
|
79
|
+
|
|
80
|
+
echo_conversion_warnings(resp)
|
|
81
|
+
echo_success(f"Exported to {out_path} ({len(resp.content)} bytes)")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""``deepcell to-docx`` — export a `<Document>` prose section to Word."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from deepcell_cli.context import Ctx, pass_ctx
|
|
8
|
+
from deepcell_cli.output import echo_conversion_warnings, echo_success
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.argument("filename")
|
|
13
|
+
@click.option("--doc", "doc_id", default=None, help="Document ID (required when the file has multiple documents).")
|
|
14
|
+
@click.option("--scenario", "scenario_id", default=None, help="Document scenario ID to export.")
|
|
15
|
+
@click.option("-o", "--output", "out_path", default=None, help="Output path (default: <filename>.docx).")
|
|
16
|
+
@click.option("--bundle", "bundle", is_flag=True, default=False, help="Link to sibling .xlsx / .pptx exports written alongside this one.")
|
|
17
|
+
@pass_ctx
|
|
18
|
+
def to_docx(
|
|
19
|
+
ctx: Ctx,
|
|
20
|
+
filename: str,
|
|
21
|
+
doc_id: str | None,
|
|
22
|
+
scenario_id: str | None,
|
|
23
|
+
out_path: str | None,
|
|
24
|
+
bundle: bool,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Export one document's prose as a Word file.
|
|
27
|
+
|
|
28
|
+
Links resolve against what is actually in the export: a bookmark inside
|
|
29
|
+
this file, a relative link to a sibling artifact written alongside it, and
|
|
30
|
+
otherwise an absolute viewer URL. Pass ``--bundle`` when the .xlsx and
|
|
31
|
+
.pptx are being written next to this file, so a cell reference lands on the
|
|
32
|
+
workbook cell rather than the hosted viewer.
|
|
33
|
+
"""
|
|
34
|
+
slug = ctx.require_workspace()
|
|
35
|
+
base = filename.rsplit(".", 1)[0] if "." in filename else filename
|
|
36
|
+
if not out_path:
|
|
37
|
+
out_path = f"{base}.docx"
|
|
38
|
+
body: dict = {
|
|
39
|
+
"workspace_slug": slug,
|
|
40
|
+
"source_filename": filename,
|
|
41
|
+
"filename": out_path,
|
|
42
|
+
}
|
|
43
|
+
if doc_id:
|
|
44
|
+
body["doc_id"] = doc_id
|
|
45
|
+
if scenario_id:
|
|
46
|
+
body["scenario_id"] = scenario_id
|
|
47
|
+
if bundle:
|
|
48
|
+
xlsx_name = f"{base}.xlsx"
|
|
49
|
+
pptx_name = f"{base}.pptx"
|
|
50
|
+
body["bundle_files"] = [xlsx_name, pptx_name]
|
|
51
|
+
body["xlsx_name"] = xlsx_name
|
|
52
|
+
body["pptx_name"] = pptx_name
|
|
53
|
+
resp = ctx.client.post_raw("/to-docx", json=body, timeout=120.0)
|
|
54
|
+
with open(out_path, "wb") as fh:
|
|
55
|
+
fh.write(resp.content)
|
|
56
|
+
echo_conversion_warnings(resp)
|
|
57
|
+
echo_success(f"Exported to {out_path} ({len(resp.content)} bytes)")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""``deepcell to-pdf`` — export a document or a deck as PDF."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from deepcell_cli.context import Ctx, pass_ctx
|
|
8
|
+
from deepcell_cli.output import echo_conversion_warnings, echo_success
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.argument("filename")
|
|
13
|
+
@click.option(
|
|
14
|
+
"--from", "source", type=click.Choice(["deck", "doc"]), default="deck",
|
|
15
|
+
show_default=True,
|
|
16
|
+
help="Which surface to render: the presentation deck, or the prose document.",
|
|
17
|
+
)
|
|
18
|
+
@click.option("--deck", "deck_id", default=None, help="Deck ID (required when the file has multiple decks).")
|
|
19
|
+
@click.option("--doc", "doc_id", default=None, help="Document ID (required when the file has multiple documents).")
|
|
20
|
+
@click.option("--scenario", "scenario_id", default=None, help="Document scenario ID to export.")
|
|
21
|
+
@click.option("-o", "--output", "out_path", default=None, help="Output path (default: <filename>_<deck|doc>.pdf).")
|
|
22
|
+
@pass_ctx
|
|
23
|
+
def to_pdf(
|
|
24
|
+
ctx: Ctx,
|
|
25
|
+
filename: str,
|
|
26
|
+
source: str,
|
|
27
|
+
deck_id: str | None,
|
|
28
|
+
doc_id: str | None,
|
|
29
|
+
scenario_id: str | None,
|
|
30
|
+
out_path: str | None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Export a deck or a document as a PDF.
|
|
33
|
+
|
|
34
|
+
The PDF is the PowerPoint or Word export rendered one step further, so it
|
|
35
|
+
shows exactly what those files show — including anything wrong with them,
|
|
36
|
+
which is what makes it useful for checking an export you cannot open.
|
|
37
|
+
|
|
38
|
+
Needs a server with the LibreOffice service configured; without one the
|
|
39
|
+
command reports that PDF export is unavailable rather than writing a file.
|
|
40
|
+
"""
|
|
41
|
+
slug = ctx.require_workspace()
|
|
42
|
+
base = filename.rsplit(".", 1)[0] if "." in filename else filename
|
|
43
|
+
if not out_path:
|
|
44
|
+
# The surface is part of the default name: one file can yield both a deck
|
|
45
|
+
# PDF and a document PDF, and a shared default would make the second
|
|
46
|
+
# export silently overwrite the first.
|
|
47
|
+
out_path = f"{base}_{source}.pdf"
|
|
48
|
+
body: dict = {
|
|
49
|
+
"source": source,
|
|
50
|
+
"workspace_slug": slug,
|
|
51
|
+
"source_filename": filename,
|
|
52
|
+
"filename": out_path,
|
|
53
|
+
}
|
|
54
|
+
if deck_id:
|
|
55
|
+
body["deck_id"] = deck_id
|
|
56
|
+
if doc_id:
|
|
57
|
+
body["doc_id"] = doc_id
|
|
58
|
+
if scenario_id:
|
|
59
|
+
body["scenario_id"] = scenario_id
|
|
60
|
+
# Two services in series (deck export, then conversion), so the deadline is
|
|
61
|
+
# the sum rather than either one's.
|
|
62
|
+
resp = ctx.client.post_raw("/to-pdf", json=body, timeout=240.0)
|
|
63
|
+
with open(out_path, "wb") as fh:
|
|
64
|
+
fh.write(resp.content)
|
|
65
|
+
echo_conversion_warnings(resp)
|
|
66
|
+
echo_success(f"Exported to {out_path} ({len(resp.content)} bytes)")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""``deepcell to-pptx`` — export an HTML deck to editable PowerPoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from deepcell_cli.context import Ctx, pass_ctx
|
|
8
|
+
from deepcell_cli.output import echo_conversion_warnings, echo_success
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.argument("filename")
|
|
13
|
+
@click.option("--deck", "deck_id", default=None, help="Deck ID (required when the document has multiple decks).")
|
|
14
|
+
@click.option("--scenario", "scenario_id", default=None, help="Document scenario ID to export.")
|
|
15
|
+
@click.option("-o", "--output", "out_path", default=None, help="Output path (default: <filename>.pptx).")
|
|
16
|
+
@pass_ctx
|
|
17
|
+
def to_pptx(
|
|
18
|
+
ctx: Ctx,
|
|
19
|
+
filename: str,
|
|
20
|
+
deck_id: str | None,
|
|
21
|
+
scenario_id: str | None,
|
|
22
|
+
out_path: str | None,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Export one document-defined HTML deck as editable PowerPoint objects."""
|
|
25
|
+
slug = ctx.require_workspace()
|
|
26
|
+
if not out_path:
|
|
27
|
+
base = filename.rsplit(".", 1)[0] if "." in filename else filename
|
|
28
|
+
out_path = f"{base}.pptx"
|
|
29
|
+
body: dict = {
|
|
30
|
+
"workspace_slug": slug,
|
|
31
|
+
"source_filename": filename,
|
|
32
|
+
"filename": out_path,
|
|
33
|
+
"should_recalculate": True,
|
|
34
|
+
}
|
|
35
|
+
if deck_id:
|
|
36
|
+
body["deck_id"] = deck_id
|
|
37
|
+
if scenario_id:
|
|
38
|
+
body["scenario_id"] = scenario_id
|
|
39
|
+
# Export recalculates and renders every slide in headless Chromium
|
|
40
|
+
# server-side — far slower than the default 30s client timeout allows.
|
|
41
|
+
resp = ctx.client.post_raw("/to-pptx", json=body, timeout=300.0)
|
|
42
|
+
with open(out_path, "wb") as fh:
|
|
43
|
+
fh.write(resp.content)
|
|
44
|
+
echo_conversion_warnings(resp)
|
|
45
|
+
echo_success(f"Exported to {out_path} ({len(resp.content)} bytes)")
|