@remixmate/cli 0.9.30 → 0.9.31

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.9.30",
4
- "generatedAt": "2026-09-14T14:44:43.104Z",
3
+ "version": "0.9.31",
4
+ "generatedAt": "2026-09-14T15:38:04.716Z",
5
5
  "skills": [
6
6
  {
7
7
  "id": "export-jianying",
@@ -833,7 +833,7 @@
833
833
  "tier": "orchestration",
834
834
  "category": "authoring",
835
835
  "title": "Video Template Registry",
836
- "summary": "List all available video templates (templateId / name / aspect ratio / style tags). Template-to-DSL binding is no longer exposed as a separate step — once prepare_video_assets receives a template_id it runs the full DSL→RenderPlan pipeline internally.",
836
+ "summary": "List all available video templates (templateId / name / aspect ratio / style tags), or read one template's full definition with template_id. Listing returns summaries; pass template_id to get that template's authoring contract (llmHint / customPayloadSchema / slotMapping / variants). Template-to-DSL binding is no longer exposed as a separate step — once prepare_video_assets receives a template_id it runs the full DSL→RenderPlan pipeline internally.",
837
837
  "triggers": [
838
838
  "View available templates / list every template"
839
839
  ],
@@ -853,11 +853,19 @@
853
853
  "properties": {
854
854
  "list_templates": {
855
855
  "type": "boolean",
856
- "description": "List available templates (default behavior; also implied when other flags are passed)."
856
+ "description": "List available templates as summaries — templateId / name / description / aspect ratios / language / status / styleTags / variantIds / key capabilities / a truncated llmHint (default behavior; also implied when other flags are passed)."
857
+ },
858
+ "template_id": {
859
+ "type": "string",
860
+ "description": "Return this template's FULL definition instead of the summary list: llmHint in full, customPayloadSchema (every template-specific field incl. the legal slideId values), slotMapping, compositions, variants. This is the per-template authoring contract — read it before writing DSL for that template."
861
+ },
862
+ "full": {
863
+ "type": "boolean",
864
+ "description": "List mode only: emit full definitions for every listed template instead of summaries. Rejected when the result would be too large for one tool result — prefer template_id, or narrow with the filter_* parameters."
857
865
  },
858
866
  "list_examples": {
859
867
  "type": "boolean",
860
- "description": "List the *.dsl.json / *.binding.json reference examples shipped under template-registry/video_dsl/schema/examples/, grouped by templateId. Useful for downstream agents (e.g. a creation agent) that want to read a template's reference shape before producing new DSL."
868
+ "description": "List the *.dsl.json / *.binding.json reference examples under template-registry/video_dsl/schema/examples/, grouped by templateId. That directory is optional and is not part of the published package, so this is normally empty — to read a template's reference shape, use template_id instead (the registry's own contract)."
861
869
  },
862
870
  "filter_tag": {
863
871
  "type": "string",
@@ -877,7 +885,7 @@
877
885
  },
878
886
  "json_output": {
879
887
  "type": "boolean",
880
- "description": "Emit a JSON result ({ templates: [...] } or { examples: [...] }) instead of the human-readable table."
888
+ "description": "Emit a JSON result instead of the human-readable table: { templates: [...] } — summaries, or full definitions when template_id / full is set — or { examples: [...] }."
881
889
  }
882
890
  },
883
891
  "required": []
@@ -886,6 +894,8 @@
886
894
  "primary": [],
887
895
  "advanced": [
888
896
  "list_templates",
897
+ "template_id",
898
+ "full",
889
899
  "list_examples",
890
900
  "filter_tag",
891
901
  "filter_aspect",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixmate/cli",
3
- "version": "0.9.30",
3
+ "version": "0.9.31",
4
4
  "description": "AI media generation skills for Claude Code / Codex — 12 skills covering image, video, voice, digital human, web screenshot, web recording, script, template registry, rendering, Jianying export, and video deconstruction.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,7 +52,7 @@ The intent is to avoid the failure mode where "the generic DSL looks compatible
52
52
 
53
53
  ### Mandatory steps
54
54
 
55
- 1. Read the requested template's full definition from the registry — `template_registry` with `list_templates=true` and `json_output=true` emits each template whole. This is the shipping source of truth, and unlike the table view it truncates nothing.
55
+ 1. Read the requested template's full definition from the registry — `template_registry` with `template_id=<the id>` and `json_output=true` emits that one template whole. This is the shipping source of truth, and unlike the summary list it truncates nothing. Do **not** ask for every template's full definition (`list_templates=true` + `full=true`): the whole registry is far more JSON than one tool result can carry, and the call fails instead of returning the contract.
56
56
  2. Read its `llmHint` end to end — that is where the template states how its on-screen text and its layouts must be authored.
57
57
  3. Read its `customPayloadSchema`: every template-specific field lives there, including the enum of legal `customPayload.slideId` values for multi-layout templates.
58
58
  4. Combine that with the template's `slotMapping`, `requiredProps`, `optionalProps`, `propExtractors`, `assetRequirements`, `supportedAspectRatios`, `supportedDurations`, `constraints`, and `scenePatterns`.
@@ -77,8 +77,9 @@ the registry is loaded **only** from the ab-api HTTP endpoint — explicit `VIDE
77
77
  > The Python binding logic that maps DSL → TemplateBinding lives in `scripts/match_template.py` but is **not exposed as a CLI** — it is only consumed as a Python library by `render-video`'s `render_video.py` via `import match_template`.
78
78
 
79
79
  1. **List available templates**: run `remixmate template-registry --list-templates` to view the templates in the registry along with their supported aspect ratios / style tags, and decide which `templateId` to pick.
80
- 2. **Write the DSL**: when generating the Video DSL, put the chosen `templateId` into `meta.templateId` (the canonical location). Use `meta.templateVariant` / `renderHints.templateVariant` to explicitly select a variant. The legacy `renderHints.templatePreference[0]` is still tolerated by `match_template.py` and `dsl_validator._pick_template_id` during transition, but new authors should write `meta.templateId`.
81
- 3. **Produce the TemplateBinding**: there is no standalone CLI for DSL TemplateBinding; `prepare_video_assets` calls `match_template.build_binding(template, dsl)` inline during the asset-resolution pipeline and embeds the binding into the RenderPlan it hands to the renderer no separate `*.binding.json` file is written.
80
+ 2. **Read that template's contract**: run `remixmate template-registry --template-id <id> --json-output` for the full definition (llmHint, `customPayloadSchema` with the legal `slideId` values, `slotMapping`, `variants`). The list view only carries a 200-char llmHint preview, which is not enough to author against.
81
+ 3. **Write the DSL**: when generating the Video DSL, put the chosen `templateId` into `meta.templateId` (the canonical location). Use `meta.templateVariant` / `renderHints.templateVariant` to explicitly select a variant. The legacy `renderHints.templatePreference[0]` is still tolerated by `match_template.py` and `dsl_validator._pick_template_id` during transition, but new authors should write `meta.templateId`.
82
+ 4. **Produce the TemplateBinding**: there is no standalone CLI for DSL → TemplateBinding; `prepare_video_assets` calls `match_template.build_binding(template, dsl)` inline during the asset-resolution pipeline and embeds the binding into the RenderPlan it hands to the renderer — no separate `*.binding.json` file is written.
82
83
 
83
84
  > Design trade-off: collapsing the binding step into the asset-prep pipeline (no CLI, no on-disk artifact) avoids binding files drifting between the agent, the database, and the file system; any hand-edited `.binding.json` would never be consumed by the renderer anyway. For local debugging you can still `import match_template.build_binding` from Python.
84
85
 
@@ -119,12 +120,25 @@ print(json.dumps(binding, ensure_ascii=False, indent=2))
119
120
 
120
121
  | Flag | Description | Default |
121
122
  |------|-------------|---------|
122
- | `--list-templates` | List every available template (currently the only CLI verb). | — |
123
+ | `--list-templates` | List every available template as a **summary** (templateId / name / description / aspect ratios / language / status / styleTags / variantIds / the `capabilities` keys that drive authoring — `payloadStyle` / `needsNarration` / `durationStrategy` / `narrationDriver` — and llmHint truncated to 200 chars). | — |
124
+ | `--template-id <id>` | Print that template's **full definition** — llmHint in full, `customPayloadSchema`, `slotMapping`, `compositions`, `variants`. Repeatable. | — |
125
+ | `--full` | List mode: emit full definitions instead of summaries. Refuses when the result would exceed 60 K characters. | off |
123
126
  | `--filter-tag` | Keep only templates whose `styleTags` match this substring (case-insensitive). | none |
124
127
  | `--filter-aspect` | Keep only templates declaring this aspect ratio (e.g. `9:16`). | none |
125
128
  | `--filter-language` | Keep only templates whose `contentLanguage` includes this code (`zh`/`en`); language-agnostic templates always show. | none |
126
129
  | `--include-beta` | Also show `status: beta` templates (same effect as `ENABLE_BETA_TEMPLATES=1`). | off |
127
- | `--json-output` | Emit `{ "templates": [...] }` instead of the table. | off |
130
+ | `--json-output` | Emit `{ "templates": [...] }` instead of the table — summaries, or full definitions under `--template-id` / `--full`. | off |
131
+
132
+ ### List vs. detail
133
+
134
+ A registry row is 4–17 KB of JSON, so dumping every full definition at once overflows an LLM tool result (the caller sees "exceeds maximum allowed tokens" instead of the contract it asked for). The list verb therefore returns summaries — enough to *choose* a template — and `--template-id` returns the one definition you need to *author* for it:
135
+
136
+ ```bash
137
+ remixmate template-registry --list-templates # choose
138
+ remixmate template-registry --template-id html-slide --json-output # then read its contract
139
+ ```
140
+
141
+ This mirrors the list/detail split described in `scripts/registry_loader.py` (P1.2), realized CLI-side so it holds even while the backend still serves one merged registry payload.
128
142
 
129
143
  ## Props extraction rules
130
144
 
@@ -13,8 +13,32 @@ TS↔Python duplication; this is now the only place the list verb is realized.
13
13
  The DSL → TemplateBinding logic stays in ``match_template.py`` and is invoked
14
14
  in-process by ``render-video``; it is intentionally NOT exposed as a CLI verb.
15
15
 
16
+ List vs. detail (why ``--json-output`` no longer dumps everything):
17
+ A registry row is 4–17 KB of JSON (customPayloadSchema + slotMapping +
18
+ variants dominate), so ``--list-templates --json-output`` over a real
19
+ registry produced a six-figure-character blob. Every caller of this CLI is
20
+ an LLM tool call, and that blob overflowed the tool-result budget outright —
21
+ the agent got an "exceeds maximum allowed tokens" error instead of the
22
+ template definition it asked for, i.e. the mode that existed *to* read a
23
+ template's contract was the one mode that could never be read.
24
+
25
+ So the list verb emits **summaries** (identity + how to choose: name,
26
+ description, aspects, language, status, tags, variant ids, the capability
27
+ keys that drive authoring, a truncated llmHint) and ``--template-id <id>``
28
+ emits the **full definition** of the one
29
+ template the agent picked. That is the shape ``registry_loader``'s P1.2 note
30
+ already anticipated, realized CLI-side so it works before the backend
31
+ list/detail endpoints exist. ``--full`` still dumps whole definitions for
32
+ programmatic callers, guarded by a size check so it can't silently recreate
33
+ the overflow.
34
+
16
35
  Flags:
17
- --list-templates (accepted; listing is the only verb)
36
+ --list-templates (accepted; listing is the default verb)
37
+ --template-id <id> print the FULL definition of this template
38
+ (repeatable); this is the detail verb
39
+ --full in list mode, emit full definitions instead
40
+ of summaries (size-guarded; prefer
41
+ --template-id)
18
42
  --list-examples list *.dsl.json / *.binding.json reference
19
43
  examples grouped by templateId (consumed
20
44
  by downstream agents that want to read a
@@ -44,9 +68,19 @@ from registry_loader import ( # noqa: E402
44
68
  EXIT_NOT_AUTHENTICATED,
45
69
  RegistryAuthError,
46
70
  RegistryUnreachableError,
71
+ get_template,
47
72
  list_templates as load_visible_templates,
48
73
  )
49
74
 
75
+ # Summary llmHint budget: enough to tell templates apart when choosing, far
76
+ # short of the multi-KB authoring contract (read that with --template-id).
77
+ _SUMMARY_HINT_CHARS = 200
78
+
79
+ # Upper bound for a multi-template full dump (--full). ~60 K chars ≈ 20 K
80
+ # tokens: still large, but inside a tool-result budget, and the message it
81
+ # fails with names the flag that replaces it.
82
+ _MAX_FULL_DUMP_CHARS = 60_000
83
+
50
84
 
51
85
  def _matches(tpl: dict, tag: str | None, aspect: str | None, language: str | None) -> bool:
52
86
  if tag:
@@ -74,6 +108,57 @@ def _status_of(tpl: dict) -> str:
74
108
  return "stable"
75
109
 
76
110
 
111
+ def _truncate(text: object, limit: int) -> str | None:
112
+ if not isinstance(text, str) or not text.strip():
113
+ return None
114
+ text = text.strip()
115
+ return text if len(text) <= limit else text[: limit - 3] + "..."
116
+
117
+
118
+ def _summarize(tpl: dict) -> dict:
119
+ """The "choose a template" view: identity + selection criteria only.
120
+
121
+ Deliberately excludes the authoring contract (llmHint in full,
122
+ customPayloadSchema, slotMapping, compositions, variants' style bodies) —
123
+ that is what ``--template-id`` returns, one template at a time.
124
+ """
125
+ variants = tpl.get("variants")
126
+ # Only the scalar capability keys that change how a caller *drives* the
127
+ # template (narration vs typewriter caption, who decides duration). The
128
+ # rest of `capabilities` — payloadDefaults above all — is bulk that scales
129
+ # with the registry, so it stays in the detail view.
130
+ caps = tpl.get("capabilities") if isinstance(tpl.get("capabilities"), dict) else {}
131
+ caps_summary = {
132
+ k: caps[k]
133
+ for k in ("payloadStyle", "needsNarration", "durationStrategy", "narrationDriver")
134
+ if k in caps
135
+ }
136
+ summary = {
137
+ "templateId": tpl.get("templateId"),
138
+ "name": tpl.get("name"),
139
+ "description": tpl.get("description"),
140
+ "status": _status_of(tpl),
141
+ "supportedAspectRatios": tpl.get("supportedAspectRatios") or [],
142
+ "contentLanguage": tpl.get("contentLanguage") or [],
143
+ "styleTags": tpl.get("styleTags") or [],
144
+ "variantIds": sorted(variants.keys()) if isinstance(variants, dict) else [],
145
+ "capabilities": caps_summary,
146
+ "llmHintPreview": _truncate(tpl.get("llmHint"), _SUMMARY_HINT_CHARS),
147
+ }
148
+ hint = tpl.get("llmHint")
149
+ if isinstance(hint, str) and len(hint.strip()) > _SUMMARY_HINT_CHARS:
150
+ summary["llmHintTruncated"] = True
151
+ return summary
152
+
153
+
154
+ def _detail_hint(template_id: str = "<templateId>") -> str:
155
+ return (
156
+ f"Summaries only. Run --template-id {template_id} --json-output for one "
157
+ "template's full definition (llmHint / customPayloadSchema / slotMapping / "
158
+ "variants) — that is the per-template authoring contract."
159
+ )
160
+
161
+
77
162
  # ----- Examples discovery (--list-examples) ---------------------------------
78
163
  #
79
164
  # Examples live under <SkillDir>/video_dsl/schema/examples/ as `*.dsl.json` and
@@ -129,7 +214,12 @@ def main() -> None:
129
214
  description="List available video templates from the registry.",
130
215
  )
131
216
  ap.add_argument("--list-templates", action="store_true",
132
- help="List every available template (the only CLI verb).")
217
+ help="List every available template (summaries; the default verb).")
218
+ ap.add_argument("--template-id", action="append", dest="template_ids", metavar="ID",
219
+ help="Print this template's FULL definition (repeatable).")
220
+ ap.add_argument("--full", action="store_true",
221
+ help="List mode: emit full definitions instead of summaries "
222
+ "(size-guarded; prefer --template-id).")
133
223
  ap.add_argument("--list-examples", action="store_true",
134
224
  help="List reference examples (*.dsl.json / *.binding.json) grouped by templateId.")
135
225
  ap.add_argument("--filter-tag", help="Keep templates whose styleTags match this substring.")
@@ -169,8 +259,10 @@ def main() -> None:
169
259
  " • llmHint — how this template's on-screen text and layouts must be authored\n"
170
260
  " • customPayloadSchema — every template-specific field, incl. the legal slideId values\n"
171
261
  " • slotMapping — propExtractors / requiredProps / optionalProps\n"
172
- " Read it with `--list-templates --json-output` (that mode emits each "
173
- "template's full definition; the table view truncates llmHint to 200 chars).",
262
+ " Read it with `--template-id <id> --json-output` (one template's full "
263
+ "definition). Run `--list-templates` first if you need the ids;\n"
264
+ " that view is summaries only — pointing it at the whole registry "
265
+ "returns more JSON than a tool result can carry.",
174
266
  )
175
267
  return
176
268
  print(f"\n{'Template ID':<28} Files")
@@ -201,13 +293,74 @@ def main() -> None:
201
293
  print(f"❌ {exc}", file=sys.stderr)
202
294
  sys.exit(1)
203
295
 
296
+ # ----- detail mode (--template-id) ---------------------------------------
297
+ #
298
+ # Explicit ids win over --filter-*: the caller already chose, and silently
299
+ # returning nothing because a stale filter excluded the pick would read as
300
+ # "template does not exist".
301
+ if args.template_ids:
302
+ by_id = {t.get("templateId"): t for t in templates}
303
+ picked: list[dict] = []
304
+ missing: list[str] = []
305
+ for tid in args.template_ids:
306
+ tpl = by_id.get(tid)
307
+ if tpl is None:
308
+ missing.append(tid)
309
+ else:
310
+ picked.append(tpl)
311
+ if missing:
312
+ for tid in missing:
313
+ # Distinguish "gated out by status" from "does not exist" —
314
+ # otherwise a beta template reads as a typo and the caller
315
+ # retries the id instead of passing --include-beta.
316
+ gated = get_template(tid, include_all_statuses=True)
317
+ if gated is not None:
318
+ print(
319
+ f"❌ Template '{tid}' exists but its status is "
320
+ f"'{_status_of(gated)}' — pass --include-beta to read it.",
321
+ file=sys.stderr,
322
+ )
323
+ else:
324
+ print(f"❌ Unknown template id: '{tid}'", file=sys.stderr)
325
+ known = ", ".join(sorted(str(t.get("templateId")) for t in templates))
326
+ print(f" Available ids: {known}", file=sys.stderr)
327
+ sys.exit(1)
328
+ if args.json_output:
329
+ print(json.dumps({"templates": picked}, ensure_ascii=False))
330
+ else:
331
+ # Pretty-printed rather than tabular: the whole point of detail mode
332
+ # is the nested contract (customPayloadSchema / slotMapping), which
333
+ # a table cannot show.
334
+ print(json.dumps({"templates": picked}, ensure_ascii=False, indent=2))
335
+ return
336
+
204
337
  visible = [
205
338
  t for t in templates
206
339
  if _matches(t, args.filter_tag, args.filter_aspect, args.filter_language)
207
340
  ]
208
341
 
209
342
  if args.json_output:
210
- print(json.dumps({"templates": visible}, ensure_ascii=False))
343
+ if args.full:
344
+ payload = json.dumps({"templates": visible}, ensure_ascii=False)
345
+ if len(payload) > _MAX_FULL_DUMP_CHARS and len(visible) > 1:
346
+ print(
347
+ f"❌ Full definitions for {len(visible)} template(s) are "
348
+ f"{len(payload):,} characters — past the {_MAX_FULL_DUMP_CHARS:,}-char "
349
+ "cap, and past what a tool result can carry.\n"
350
+ " Read one template at a time with `--template-id <id> --json-output`, "
351
+ "or narrow with --filter-tag / --filter-aspect / --filter-language.",
352
+ file=sys.stderr,
353
+ )
354
+ sys.exit(1)
355
+ print(payload)
356
+ return
357
+ print(json.dumps(
358
+ {
359
+ "templates": [_summarize(t) for t in visible],
360
+ "detailHint": _detail_hint(),
361
+ },
362
+ ensure_ascii=False,
363
+ ))
211
364
  return
212
365
 
213
366
  if not visible:
@@ -233,12 +386,16 @@ def main() -> None:
233
386
  status = _status_of(tpl).ljust(8)
234
387
  tags = ", ".join((tpl.get("styleTags") or [])[:5])
235
388
  print(f"{tid} {name} {ratios} {lang} {status} {tags}")
236
- hint = tpl.get("llmHint")
389
+ hint = _truncate(tpl.get("llmHint"), _SUMMARY_HINT_CHARS)
237
390
  if hint:
238
- hint = hint if len(hint) <= 200 else hint[:197] + "..."
239
391
  print(f"{' ' * 22} ↳ {hint}")
240
392
 
241
393
  print(f"\n{len(visible)} template(s) shown")
394
+ first_id = str(visible[0].get("templateId", "<templateId>"))
395
+ print(
396
+ f"Full definition of one template: --template-id {first_id} --json-output "
397
+ "(llmHint / customPayloadSchema / slotMapping / variants)",
398
+ )
242
399
 
243
400
 
244
401
  if __name__ == "__main__":
@@ -4,20 +4,22 @@
4
4
  "tier": "orchestration",
5
5
  "category": "authoring",
6
6
  "title": "Video Template Registry",
7
- "description": "List all available video templates (templateId / name / aspect ratio / style tags). Template-to-DSL binding is no longer exposed as a separate step — once prepare_video_assets receives a template_id it runs the full DSL→RenderPlan pipeline internally.",
7
+ "description": "List all available video templates (templateId / name / aspect ratio / style tags), or read one template's full definition with template_id. Listing returns summaries; pass template_id to get that template's authoring contract (llmHint / customPayloadSchema / slotMapping / variants). Template-to-DSL binding is no longer exposed as a separate step — once prepare_video_assets receives a template_id it runs the full DSL→RenderPlan pipeline internally.",
8
8
  "auth": "required",
9
9
  "envVars": ["PRIV_TOKEN", "VIDEO_TEMPLATE_REGISTRY_URL", "MM_API_BASE_URL", "VIDEO_TEMPLATE_REGISTRY_HTTP_METHOD"],
10
10
  "entry": { "type": "python", "scriptPath": "scripts/list_templates.py" },
11
11
  "parameters": {
12
12
  "type": "object",
13
13
  "properties": {
14
- "list_templates": { "type": "boolean", "description": "List available templates (default behavior; also implied when other flags are passed)." },
15
- "list_examples": { "type": "boolean", "description": "List the *.dsl.json / *.binding.json reference examples shipped under template-registry/video_dsl/schema/examples/, grouped by templateId. Useful for downstream agents (e.g. a creation agent) that want to read a template's reference shape before producing new DSL." },
14
+ "list_templates": { "type": "boolean", "description": "List available templates as summaries — templateId / name / description / aspect ratios / language / status / styleTags / variantIds / key capabilities / a truncated llmHint (default behavior; also implied when other flags are passed)." },
15
+ "template_id": { "type": "string", "description": "Return this template's FULL definition instead of the summary list: llmHint in full, customPayloadSchema (every template-specific field incl. the legal slideId values), slotMapping, compositions, variants. This is the per-template authoring contract read it before writing DSL for that template." },
16
+ "full": { "type": "boolean", "description": "List mode only: emit full definitions for every listed template instead of summaries. Rejected when the result would be too large for one tool result — prefer template_id, or narrow with the filter_* parameters." },
17
+ "list_examples": { "type": "boolean", "description": "List the *.dsl.json / *.binding.json reference examples under template-registry/video_dsl/schema/examples/, grouped by templateId. That directory is optional and is not part of the published package, so this is normally empty — to read a template's reference shape, use template_id instead (the registry's own contract)." },
16
18
  "filter_tag": { "type": "string", "description": "Show only templates whose styleTags contain (or are contained in) this string. Case-insensitive. E.g. 'tech' / '科普' / 'walkthrough'." },
17
19
  "filter_aspect": { "type": "string", "description": "Show only templates that declare this aspect ratio. E.g. '9:16' / '16:9' / '1:1'." },
18
20
  "filter_language": { "type": "string", "description": "Show only templates whose contentLanguage includes this code ('zh' or 'en'). Templates with no declared language are always shown (treated as language-agnostic)." },
19
21
  "include_beta": { "type": "boolean", "description": "Also list templates with status='beta'. Default off (only 'stable' shows). The ENABLE_BETA_TEMPLATES env var has the same effect process-wide." },
20
- "json_output": { "type": "boolean", "description": "Emit a JSON result ({ templates: [...] } or { examples: [...] }) instead of the human-readable table." }
22
+ "json_output": { "type": "boolean", "description": "Emit a JSON result instead of the human-readable table: { templates: [...] } — summaries, or full definitions when template_id / full is set — or { examples: [...] }." }
21
23
  },
22
24
  "required": []
23
25
  }