@remixmate/cli 0.9.63 → 0.9.64

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.63",
4
- "generatedAt": "2026-10-04T06:46:47.501Z",
3
+ "version": "0.9.64",
4
+ "generatedAt": "2026-10-04T11:52:39.175Z",
5
5
  "skills": [
6
6
  {
7
7
  "id": "doc-parse",
@@ -994,7 +994,7 @@
994
994
  },
995
995
  "json_output": {
996
996
  "type": "boolean",
997
- "description": "Emit a JSON result instead of the human-readable table: { templates: [...] } — summaries, or full definitions when template_id / full is set — or { examples: [...] }. When the backend serves samples, list summaries carry showcases { count, pageUrl, items } and the template_id view carries a top-level showcases map."
997
+ "description": "Emit a JSON result instead of the human-readable table: { templates: [...] } — summaries, or full definitions when template_id / full is set — or { examples: [...] }. List summaries carry pageUrl (the template's web detail page — show it to the user as a link) and, when the backend serves samples, showcases { count, pageUrl, items }; the template_id view carries top-level pageUrls / showcases maps."
998
998
  },
999
999
  "open": {
1000
1000
  "type": "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixmate/cli",
3
- "version": "0.9.63",
3
+ "version": "0.9.64",
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",
@@ -78,7 +78,7 @@ remixmate template-registry --list-templates
78
78
 
79
79
  ### Show templates to the user
80
80
 
81
- When the user asks to see / browse / pick templates, they decide by *watching* samples, not by reading definitions. The list adds, per template, the number of samples and a link to its web detail page (playable samples, layouts, a copy-ready prompt) whenever the backend serves them (`↳ samples: N · <url>` in the table; `showcases.count` / `showcases.pageUrl` in JSON).
81
+ When the user asks to see / browse / pick templates, they decide by *watching* samples, not by reading definitions. The list adds, per template, a link to its web detail page (playable samples, layouts, a copy-ready prompt) and the number of samples whenever the backend serves them — table: `↳ detail: <url>` and `↳ samples: N`; JSON: each summary's `pageUrl` and `showcases.count`; `--template-id`: top-level `pageUrls`.
82
82
 
83
83
  - Present each template as: name, aspect ratios, sample count, and its `pageUrl` as a clickable Markdown link. Keep the URL exactly as printed.
84
84
  - When the user wants to see what one template looks like, give its link, or run `remixmate template-registry --open <templateId>` (prints the URL; opens the browser only from an interactive terminal — relay the printed URL either way).
@@ -111,7 +111,7 @@ print(json.dumps(binding, ensure_ascii=False, indent=2))
111
111
  | `--filter-aspect` | Keep only templates declaring this aspect ratio (e.g. `9:16`). | none |
112
112
  | `--filter-language` | Keep only templates whose `contentLanguage` includes this code (`zh`/`en`); language-agnostic templates always show. | none |
113
113
  | `--include-beta` | Also show `status: beta` templates (same effect as `ENABLE_BETA_TEMPLATES=1`). | off |
114
- | `--json-output` | List mode: emit `{ "templates": [...] }` summaries (or full definitions with `--full`) instead of the table. Detail mode (`--template-id`) is always compact JSON. Summaries carry `showcases: { count, pageUrl, items }` and detail mode a top-level `showcases` map when the backend serves samples (absent otherwise). | off |
114
+ | `--json-output` | List mode: emit `{ "templates": [...] }` summaries (or full definitions with `--full`) instead of the table. Detail mode (`--template-id`) is always compact JSON. Summaries carry `pageUrl` (web detail page) and `showcases: { count, pageUrl, items }`; detail mode carries top-level `pageUrls` and `showcases` maps. Each is absent when the backend does not serve it. | off |
115
115
  | `--open <id>` | Print the template's web detail page URL (samples, layouts) and open the browser when run from an interactive terminal. Exits 1 when the backend has no link for it. Cannot be combined with `--template-id`. | — |
116
116
 
117
117
  ### List vs. detail
@@ -72,9 +72,10 @@ Flags:
72
72
  (samples, layouts); also opens the browser
73
73
  when stdout is an interactive terminal
74
74
 
75
- Samples (showcase_loader.py): list and detail views add each template's sample
76
- count and web detail-page link when ab-api serves them; when it does not, the
77
- views are unchanged apart from one stderr warning.
75
+ Links & samples: list and detail views add each template's web detail-page
76
+ link (registry document-level ``pageUrls``, falling back to the samples
77
+ endpoint) and sample count (showcase_loader.py) when ab-api serves them; when
78
+ it does not, the views are unchanged apart from one stderr warning.
78
79
  """
79
80
 
80
81
  from __future__ import annotations
@@ -94,6 +95,7 @@ from registry_loader import ( # noqa: E402
94
95
  RegistryUnreachableError,
95
96
  get_template,
96
97
  list_templates as load_visible_templates,
98
+ registry_page_urls,
97
99
  )
98
100
  from showcase_loader import load_showcases, summarize_group # noqa: E402
99
101
 
@@ -198,6 +200,23 @@ def _open_in_browser(url: str) -> None:
198
200
  pass
199
201
 
200
202
 
203
+ def _page_url(template_id: object, page_urls: dict[str, str], groups: dict | None) -> str | None:
204
+ """Web detail page link for one template.
205
+
206
+ The registry's document-level ``pageUrls`` comes first: it rides on the call
207
+ every list makes anyway, so the link survives the samples endpoint being
208
+ down. The samples group's ``pageUrl`` is the fallback (same URL, built by
209
+ the same backend helper).
210
+ """
211
+ if not isinstance(template_id, str):
212
+ return None
213
+ if page_urls.get(template_id):
214
+ return page_urls[template_id]
215
+ group = (groups or {}).get(template_id)
216
+ url = group.get("pageUrl") if isinstance(group, dict) else None
217
+ return url if isinstance(url, str) and url else None
218
+
219
+
201
220
  def _report_unknown_ids(missing: list[str], templates: list[dict]) -> None:
202
221
  for tid in missing:
203
222
  # Distinguish "gated out by status" from "does not exist" —
@@ -459,6 +478,7 @@ def main() -> None:
459
478
  sys.exit(1)
460
479
 
461
480
  by_id = {t.get("templateId"): t for t in templates}
481
+ page_urls = registry_page_urls()
462
482
 
463
483
  # ----- open mode (--open) -------------------------------------------------
464
484
  #
@@ -468,8 +488,9 @@ def main() -> None:
468
488
  if args.open_id not in by_id:
469
489
  _report_unknown_ids([args.open_id], templates)
470
490
  sys.exit(1)
471
- groups = load_showcases([args.open_id], 1)
472
- page_url = (groups or {}).get(args.open_id, {}).get("pageUrl")
491
+ page_url = _page_url(args.open_id, page_urls, None)
492
+ if not page_url:
493
+ page_url = _page_url(args.open_id, {}, load_showcases([args.open_id], 1))
473
494
  if not page_url:
474
495
  print(
475
496
  "❌ No web detail page link for this template: the backend does not "
@@ -520,9 +541,16 @@ def main() -> None:
520
541
  # chars), which alone pushed the plain call past the tool-result cap and
521
542
  # got it spilled to a file the agent cannot read. Humans can `| jq`.
522
543
  detail: dict = {"templates": picked}
523
- # Samples sit beside the definitions, never inside them: the definition
524
- # is the template's authoring contract, samples are web content.
544
+ # Samples and links sit beside the definitions, never inside them: the
545
+ # definition is the template's authoring contract, these are web content.
525
546
  groups = load_showcases([str(t.get("templateId")) for t in picked], _DETAIL_SHOWCASES)
547
+ links = {
548
+ str(t.get("templateId")): url
549
+ for t in picked
550
+ if (url := _page_url(t.get("templateId"), page_urls, groups))
551
+ }
552
+ if links:
553
+ detail["pageUrls"] = links
526
554
  if groups is not None:
527
555
  detail["showcases"] = {
528
556
  tid: summarize_group(g, _DETAIL_SHOWCASES) for tid, g in groups.items()
@@ -554,6 +582,8 @@ def main() -> None:
554
582
  summaries = []
555
583
  for t in visible:
556
584
  s = _summarize(t)
585
+ if url := _page_url(t.get("templateId"), page_urls, groups):
586
+ s["pageUrl"] = url
557
587
  # Absent (not null) when the endpoint is unavailable, so a consumer
558
588
  # never mistakes "unknown" for "zero samples".
559
589
  if groups is not None and t.get("templateId") in groups:
@@ -576,6 +606,7 @@ def main() -> None:
576
606
  return
577
607
 
578
608
  groups = load_showcases([str(t.get("templateId")) for t in visible], _LIST_SHOWCASES)
609
+ any_link = False
579
610
 
580
611
  header = (
581
612
  f"\n{'Template ID':<22} {'Name':<18} {'Aspect':<12} "
@@ -596,12 +627,14 @@ def main() -> None:
596
627
  hint = _truncate(tpl.get("llmHint"), _SUMMARY_HINT_CHARS)
597
628
  if hint:
598
629
  print(f"{' ' * 22} ↳ {hint}")
630
+ # Two lines, link on its own: an agent summarizing the table keeps a
631
+ # labelled `detail:` line, a URL trailing a count reads as an aside.
599
632
  group = (groups or {}).get(tpl.get("templateId"))
600
633
  if group is not None:
601
- line = f"samples: {int(group.get('showcaseCount') or 0)}"
602
- if group.get("pageUrl"):
603
- line += f" · {group['pageUrl']}"
604
- print(f"{' ' * 22} ↳ {line}")
634
+ print(f"{' ' * 22} ↳ samples: {int(group.get('showcaseCount') or 0)}")
635
+ if url := _page_url(tpl.get("templateId"), page_urls, groups):
636
+ print(f"{' ' * 22} ↳ detail: {url}")
637
+ any_link = True
605
638
 
606
639
  print(f"\n{len(visible)} template(s) shown")
607
640
  first_id = str(visible[0].get("templateId", "<templateId>"))
@@ -609,8 +642,8 @@ def main() -> None:
609
642
  f"Full definition of one template: --template-id {first_id} --json-output "
610
643
  "(llmHint / customPayloadSchema / slideSchemas / slotMapping / variants)",
611
644
  )
612
- if groups:
613
- print(f"Watch samples in the browser: --open {first_id}")
645
+ if any_link:
646
+ print(f"Watch samples / layouts in the browser: open a detail link, or --open {first_id}")
614
647
 
615
648
 
616
649
  if __name__ == "__main__":
@@ -306,6 +306,23 @@ def load_registry_data(
306
306
  return _load_from_url(url, ttl=ttl_seconds, timeout=http_timeout)
307
307
 
308
308
 
309
+ def registry_page_urls() -> dict[str, str]:
310
+ """Document-level ``pageUrls`` (templateId → web detail page), built by ab-api.
311
+
312
+ Kept beside ``templates`` rather than inside each definition: a definition is
313
+ the template's authoring contract, a link is site information. Absent on
314
+ older backends, offline registries, or when the backend has no site address
315
+ configured — returns ``{}`` then (never raises: it is display-only).
316
+ """
317
+ try:
318
+ raw = load_registry_data().get("pageUrls")
319
+ except Exception: # noqa: BLE001 — the caller already loaded the registry once
320
+ return {}
321
+ if not isinstance(raw, dict):
322
+ return {}
323
+ return {k: v for k, v in raw.items() if isinstance(k, str) and isinstance(v, str) and v}
324
+
325
+
309
326
  # ════════════════════════════════════════════════════════════════════════════
310
327
  # Indexed views — O(1) template-id lookup + status / beta gating
311
328
  #
@@ -19,7 +19,7 @@
19
19
  "filter_aspect": { "type": "string", "description": "Show only templates that declare this aspect ratio. E.g. '9:16' / '16:9' / '1:1'." },
20
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)." },
21
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." },
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: [...] }. When the backend serves samples, list summaries carry showcases { count, pageUrl, items } and the template_id view carries a top-level showcases map." },
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: [...] }. List summaries carry pageUrl (the template's web detail page — show it to the user as a link) and, when the backend serves samples, showcases { count, pageUrl, items }; the template_id view carries top-level pageUrls / showcases maps." },
23
23
  "open": { "type": "string", "description": "Print the web detail page URL of <templateId> (playable samples, layouts, copy-ready prompt) and open it in the browser when run from an interactive terminal. Relay the printed URL to the user." }
24
24
  },
25
25
  "required": []