facult 2.5.2 → 2.7.0

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.
package/src/remote.ts CHANGED
@@ -11,6 +11,14 @@ import {
11
11
  } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { isCancel, multiselect, select, text } from "@clack/prompts";
14
+ import {
15
+ renderBullets,
16
+ renderCatalog,
17
+ renderCode,
18
+ renderKeyValue,
19
+ renderPage,
20
+ renderTable,
21
+ } from "./cli-ui";
14
22
  import { buildIndex } from "./index-builder";
15
23
  import { facultRootDir, readFacultConfig } from "./paths";
16
24
  import {
@@ -49,6 +57,8 @@ const REMOTE_STATE_VERSION = 1;
49
57
  const VERSION_TOKEN_RE = /[A-Za-z]+|[0-9]+/g;
50
58
  const QUERY_SPLIT_RE = /\s+/;
51
59
  const MD_EXT_RE = /\.md$/i;
60
+ const FILE_EXT_RE = /\.[A-Za-z0-9]+$/;
61
+ const TRAILING_SLASH_RE = /\/+$/;
52
62
  const PROMPT_PATH_SPLIT_RE = /[,\n]/;
53
63
  const GIT_WORKTREE_LINE_RE = /\r?\n/;
54
64
 
@@ -231,6 +241,38 @@ Use this skill when the task repeatedly follows a known workflow and you want co
231
241
  },
232
242
  },
233
243
  },
244
+ {
245
+ id: "agent-template",
246
+ type: "agent",
247
+ title: "Canonical Agent Template",
248
+ description:
249
+ "Starter canonical subagent scaffold for focused, reviewable specialist behavior.",
250
+ version: "1.0.0",
251
+ tags: ["template", "dx", "agent"],
252
+ agent: {
253
+ fileName: "agent.toml",
254
+ content: `name = "{{name}}"
255
+ description = "Describe the focused responsibility for {{name}}."
256
+
257
+ developer_instructions = """
258
+ You are {{name}}.
259
+
260
+ Operate with a tight scope.
261
+
262
+ Prioritize:
263
+ - one clear responsibility
264
+ - concrete evidence over vague summaries
265
+ - explicit assumptions and blockers
266
+ - outputs that are easy for the calling agent to apply
267
+
268
+ Return:
269
+ - what you changed or found
270
+ - what you verified
271
+ - what still needs a decision
272
+ """
273
+ `,
274
+ },
275
+ },
234
276
  {
235
277
  id: "agents-md-template",
236
278
  type: "agent",
@@ -589,7 +631,11 @@ function normalizeCwdList(raw: string | null | undefined): string[] {
589
631
 
590
632
  function isInteractiveOutputRequested(args: string[]): boolean {
591
633
  return (
592
- !args.includes("--json") &&
634
+ !(
635
+ args.includes("--json") ||
636
+ args.includes("--yes") ||
637
+ args.includes("--non-interactive")
638
+ ) &&
593
639
  process.stdin.isTTY === true &&
594
640
  process.stdout.isTTY === true
595
641
  );
@@ -1895,6 +1941,18 @@ async function installMcpItem(args: {
1895
1941
  };
1896
1942
  }
1897
1943
 
1944
+ function deriveAgentTemplateName(fileName: string): string {
1945
+ const normalized = fileName.replaceAll("\\", "/");
1946
+ const base = basename(normalized);
1947
+ if (base.toLowerCase() === "agent.toml") {
1948
+ const parent = basename(dirname(normalized));
1949
+ if (parent && parent !== ".") {
1950
+ return parent;
1951
+ }
1952
+ }
1953
+ return base.replace(FILE_EXT_RE, "");
1954
+ }
1955
+
1898
1956
  async function installAgentItem(args: {
1899
1957
  item: RemoteAgentItem;
1900
1958
  installAs?: string;
@@ -1918,12 +1976,14 @@ async function installAgentItem(args: {
1918
1976
  );
1919
1977
  }
1920
1978
 
1979
+ const agentName = deriveAgentTemplateName(fileName);
1980
+
1921
1981
  if (!args.dryRun) {
1922
1982
  await mkdir(dirname(filePath), { recursive: true });
1923
1983
  await Bun.write(
1924
1984
  filePath,
1925
1985
  renderTemplate(args.item.agent.content, {
1926
- name: fileName.replace(MD_EXT_RE, ""),
1986
+ name: agentName,
1927
1987
  })
1928
1988
  );
1929
1989
  }
@@ -2488,84 +2548,159 @@ async function verifySource(args: {
2488
2548
  }
2489
2549
 
2490
2550
  function printSearchHelp() {
2491
- console.log(`fclt search — search configured remote indices
2492
-
2493
- Usage:
2494
- fclt search <query> [--index <name>] [--limit <n>] [--json]
2495
-
2496
- Notes:
2497
- - Builtin index "${BUILTIN_INDEX_NAME}" is always available.
2498
- - Builtin provider aliases: "${SMITHERY_INDEX_NAME}", "${GLAMA_INDEX_NAME}", "${SKILLS_SH_INDEX_NAME}", "${CLAWHUB_INDEX_NAME}".
2499
- - Optional custom indices can be configured in ~/.ai/.facult/indices.json.
2500
- `);
2551
+ console.log(
2552
+ renderPage({
2553
+ title: "fclt search",
2554
+ subtitle: "Search configured remote indices.",
2555
+ sections: [
2556
+ {
2557
+ title: "Usage",
2558
+ lines: renderBullets([
2559
+ renderCode(
2560
+ "fclt search <query> [--index <name>] [--limit <n>] [--json]"
2561
+ ),
2562
+ ]),
2563
+ },
2564
+ {
2565
+ title: "Notes",
2566
+ lines: renderBullets([
2567
+ `Builtin index ${renderCode(BUILTIN_INDEX_NAME)} is always available.`,
2568
+ `Builtin provider aliases: ${SMITHERY_INDEX_NAME}, ${GLAMA_INDEX_NAME}, ${SKILLS_SH_INDEX_NAME}, ${CLAWHUB_INDEX_NAME}.`,
2569
+ "Optional custom indices can be configured in ~/.ai/.facult/indices.json.",
2570
+ ]),
2571
+ },
2572
+ ],
2573
+ })
2574
+ );
2501
2575
  }
2502
2576
 
2503
2577
  function printInstallHelp() {
2504
- console.log(`fclt install — install an item from a remote index
2505
-
2506
- Usage:
2507
- fclt install <index:item> [--as <name>] [--dry-run] [--force] [--strict-source-trust] [--json]
2508
-
2509
- Examples:
2510
- fclt install facult:skill-template --as my-skill
2511
- fclt install facult:mcp-stdio-template --as github
2512
- fclt install smithery:github
2513
- fclt install glama:systeminit/si --as system-initiative
2514
- fclt install skills.sh:owner/repo --as my-skill
2515
- fclt install clawhub:my-skill
2516
- `);
2578
+ console.log(
2579
+ renderPage({
2580
+ title: "fclt install",
2581
+ subtitle: "Install an item from a remote index into canonical state.",
2582
+ sections: [
2583
+ {
2584
+ title: "Usage",
2585
+ lines: renderBullets([
2586
+ renderCode(
2587
+ "fclt install <index:item> [--as <name>] [--dry-run] [--force] [--strict-source-trust] [--json]"
2588
+ ),
2589
+ ]),
2590
+ },
2591
+ {
2592
+ title: "Examples",
2593
+ lines: renderBullets([
2594
+ renderCode("fclt install facult:skill-template --as my-skill"),
2595
+ renderCode(
2596
+ "fclt install facult:agent-template --as reviewer/agent.toml"
2597
+ ),
2598
+ renderCode("fclt install facult:mcp-stdio-template --as github"),
2599
+ renderCode("fclt install smithery:github"),
2600
+ ]),
2601
+ },
2602
+ ],
2603
+ })
2604
+ );
2517
2605
  }
2518
2606
 
2519
2607
  function printUpdateHelp() {
2520
- console.log(`fclt update — check for updates to remotely installed items
2521
-
2522
- Usage:
2523
- fclt update [--apply] [--strict-source-trust] [--json]
2524
-
2525
- Options:
2526
- --apply Install available updates
2527
- --strict-source-trust Block review-level sources unless explicitly trusted
2528
- `);
2608
+ console.log(
2609
+ renderPage({
2610
+ title: "fclt update",
2611
+ subtitle: "Check for updates to remotely installed items.",
2612
+ sections: [
2613
+ {
2614
+ title: "Usage",
2615
+ lines: renderBullets([
2616
+ renderCode(
2617
+ "fclt update [--apply] [--strict-source-trust] [--json]"
2618
+ ),
2619
+ ]),
2620
+ },
2621
+ {
2622
+ title: "Options",
2623
+ lines: renderTable({
2624
+ headers: ["Option", "Meaning"],
2625
+ rows: [
2626
+ ["--apply", "Install available updates"],
2627
+ [
2628
+ "--strict-source-trust",
2629
+ "Block review-level sources unless explicitly trusted",
2630
+ ],
2631
+ ],
2632
+ }),
2633
+ },
2634
+ ],
2635
+ })
2636
+ );
2529
2637
  }
2530
2638
 
2531
2639
  function printTemplatesHelp() {
2532
- console.log(`fclt templates — DX-first local scaffolding for skills/instructions/MCP/snippets
2533
-
2534
- Usage:
2535
- fclt templates list [--json]
2536
- fclt templates init skill <name> [--force] [--dry-run]
2537
- fclt templates init mcp <name> [--force] [--dry-run]
2538
- fclt templates init snippet <marker> [--force] [--dry-run]
2539
- fclt templates init agents [--force] [--dry-run]
2540
- fclt templates init claude [--force] [--dry-run]
2541
- fclt templates init project-ai [--force] [--dry-run]
2542
- fclt templates init automation <template-id> [--scope global|project|wide] [--name <name>] [--project-root <path>] [--cwds <path1,path2>] [--rrule <RRULE>]
2543
- --status [PAUSED|ACTIVE] [--force] [--dry-run]
2544
-
2545
- Notes:
2546
- - Templates are powered by the builtin remote index (${BUILTIN_INDEX_NAME}).
2547
- - Automation templates scaffold Codex automation files under ~/.codex/automations/.
2548
- - scope=global|wide creates a wide automation. Provide --cwds for explicit repo roots.
2549
- Without --cwds, wide/global automation has no cwds by default.
2550
- - scope=project creates an automation scoped to one project root.
2551
- - If --scope (or --project-root / --cwds) is omitted and the command runs in an
2552
- interactive terminal, you will be prompted to choose scope and candidate paths.
2553
- Without explicit answers, project scope falls back to current git project and
2554
- wide/global scope can be left empty.
2555
- `);
2640
+ console.log(
2641
+ renderPage({
2642
+ title: "fclt templates",
2643
+ subtitle:
2644
+ "Scaffold canonical skills, MCP, agents, snippets, docs, and automation.",
2645
+ sections: [
2646
+ {
2647
+ title: "Usage",
2648
+ lines: renderBullets([
2649
+ renderCode("fclt templates list [--json]"),
2650
+ renderCode(
2651
+ "fclt templates init skill <name> [--force] [--dry-run]"
2652
+ ),
2653
+ renderCode("fclt templates init mcp <name> [--force] [--dry-run]"),
2654
+ renderCode(
2655
+ "fclt templates init agent <name> [--force] [--dry-run]"
2656
+ ),
2657
+ renderCode(
2658
+ "fclt templates init snippet <marker> [--force] [--dry-run]"
2659
+ ),
2660
+ renderCode("fclt templates init agents [--force] [--dry-run]"),
2661
+ renderCode("fclt templates init project-ai [--force] [--dry-run]"),
2662
+ renderCode(
2663
+ "fclt templates init automation <template-id> [--scope global|project|wide] [--name <name>] [--project-root <path>] [--cwds <path1,path2>] [--rrule <RRULE>] [--status PAUSED|ACTIVE] [--yes] [--dry-run]"
2664
+ ),
2665
+ ]),
2666
+ },
2667
+ {
2668
+ title: "Notes",
2669
+ lines: renderBullets([
2670
+ `Templates are powered by the builtin ${renderCode(BUILTIN_INDEX_NAME)} index.`,
2671
+ "Automation templates scaffold Codex automation files under ~/.codex/automations/.",
2672
+ `${renderCode("--yes")} and ${renderCode("--non-interactive")} skip scope prompts and use inferred defaults when possible.`,
2673
+ "Use project scope for one repo root, wide/global scope for many explicit roots.",
2674
+ ]),
2675
+ },
2676
+ ],
2677
+ })
2678
+ );
2556
2679
  }
2557
2680
 
2558
2681
  function printVerifySourceHelp() {
2559
- console.log(`fclt verify-source — verify source trust/integrity/signature status
2560
-
2561
- Usage:
2562
- fclt verify-source <name> [--json]
2563
-
2564
- Examples:
2565
- fclt verify-source facult
2566
- fclt verify-source smithery
2567
- fclt verify-source local-index --json
2568
- `);
2682
+ console.log(
2683
+ renderPage({
2684
+ title: "fclt verify-source",
2685
+ subtitle: "Verify source trust, integrity, and signature status.",
2686
+ sections: [
2687
+ {
2688
+ title: "Usage",
2689
+ lines: renderBullets([
2690
+ renderCode("fclt verify-source <name> [--json]"),
2691
+ ]),
2692
+ },
2693
+ {
2694
+ title: "Examples",
2695
+ lines: renderBullets([
2696
+ renderCode("fclt verify-source facult"),
2697
+ renderCode("fclt verify-source smithery"),
2698
+ renderCode("fclt verify-source local-index --json"),
2699
+ ]),
2700
+ },
2701
+ ],
2702
+ })
2703
+ );
2569
2704
  }
2570
2705
 
2571
2706
  function parseLongFlag(argv: string[], flag: string): string | null {
@@ -2644,16 +2779,34 @@ export async function searchCommand(
2644
2779
  return;
2645
2780
  }
2646
2781
  if (!results.length) {
2647
- console.log("(no results)");
2648
- return;
2649
- }
2650
- for (const row of results) {
2651
- const version = row.item.version ?? "-";
2652
- const title = row.item.title ?? row.item.description ?? "";
2653
2782
  console.log(
2654
- `${row.index}:${row.item.id}\t${row.item.type}\t${version}\t${title}`
2783
+ renderPage({
2784
+ title: "fclt search",
2785
+ subtitle: `No remote results for "${query}".`,
2786
+ sections: [],
2787
+ })
2655
2788
  );
2789
+ return;
2656
2790
  }
2791
+ console.log(
2792
+ renderPage({
2793
+ title: "fclt search",
2794
+ subtitle: `${results.length} remote match${results.length === 1 ? "" : "es"} for "${query}"`,
2795
+ sections: [
2796
+ {
2797
+ title: "Results",
2798
+ lines: renderCatalog(
2799
+ results.map((row) => ({
2800
+ title: `${row.index}:${row.item.id}`,
2801
+ meta: `${row.item.type} • v${row.item.version ?? "-"}`,
2802
+ description:
2803
+ row.item.title ?? row.item.description ?? "No title.",
2804
+ }))
2805
+ ),
2806
+ },
2807
+ ],
2808
+ })
2809
+ );
2657
2810
  } catch (err) {
2658
2811
  console.error(err instanceof Error ? err.message : String(err));
2659
2812
  process.exitCode = 1;
@@ -2704,15 +2857,37 @@ export async function installCommand(
2704
2857
  return;
2705
2858
  }
2706
2859
  const action = dryRun ? "Would install" : "Installed";
2707
- console.log(`${action} ${result.ref} as ${result.installedAs}`);
2708
- if (result.sourceTrustLevel === "review" && !strictSourceTrust) {
2709
- console.log(
2710
- " ! source policy: review (use --strict-source-trust to enforce trust-only installs)"
2711
- );
2712
- }
2713
- for (const path of result.changedPaths) {
2714
- console.log(` - ${path}`);
2715
- }
2860
+ console.log(
2861
+ renderPage({
2862
+ title: "fclt install",
2863
+ subtitle: `${action} ${result.ref} as ${result.installedAs}`,
2864
+ sections: [
2865
+ {
2866
+ title: "Result",
2867
+ lines: renderKeyValue([
2868
+ ["type", result.type],
2869
+ [
2870
+ "source trust",
2871
+ result.sourceTrustLevel === "trusted"
2872
+ ? "trusted"
2873
+ : result.sourceTrustLevel,
2874
+ ],
2875
+ ["path", result.path],
2876
+ ]),
2877
+ },
2878
+ {
2879
+ title: "Changed Paths",
2880
+ lines: renderBullets(result.changedPaths),
2881
+ },
2882
+ ],
2883
+ footer:
2884
+ result.sourceTrustLevel === "review" && !strictSourceTrust
2885
+ ? [
2886
+ "Source policy is review. Use --strict-source-trust to require explicit trust.",
2887
+ ]
2888
+ : undefined,
2889
+ })
2890
+ );
2716
2891
  } catch (err) {
2717
2892
  console.error(err instanceof Error ? err.message : String(err));
2718
2893
  process.exitCode = 1;
@@ -2747,19 +2922,39 @@ export async function updateCommand(
2747
2922
  return;
2748
2923
  }
2749
2924
  if (!report.checks.length) {
2750
- console.log("No remotely installed items found.");
2751
- return;
2752
- }
2753
- for (const check of report.checks) {
2754
- const current = check.currentVersion ?? "-";
2755
- const latest = check.latestVersion ?? "-";
2756
2925
  console.log(
2757
- `${check.installed.ref} (${check.installed.installedAs})\t${check.status}\t${current} -> ${latest}`
2926
+ renderPage({
2927
+ title: "fclt update",
2928
+ subtitle: "No remotely installed items found.",
2929
+ sections: [],
2930
+ })
2758
2931
  );
2932
+ return;
2759
2933
  }
2760
- if (apply) {
2761
- console.log(`Applied ${report.applied.length} updates.`);
2762
- }
2934
+ console.log(
2935
+ renderPage({
2936
+ title: "fclt update",
2937
+ subtitle: `${report.checks.length} installed item${report.checks.length === 1 ? "" : "s"} checked`,
2938
+ sections: [
2939
+ {
2940
+ title: "Checks",
2941
+ lines: renderCatalog(
2942
+ report.checks.map((check) => ({
2943
+ title: check.installed.ref,
2944
+ meta: check.status,
2945
+ description: check.installed.installedAs,
2946
+ details: [
2947
+ `Version ${check.currentVersion ?? "-"} -> ${check.latestVersion ?? "-"}`,
2948
+ ],
2949
+ }))
2950
+ ),
2951
+ },
2952
+ ],
2953
+ footer: apply
2954
+ ? [`Applied ${report.applied.length} update(s).`]
2955
+ : undefined,
2956
+ })
2957
+ );
2763
2958
  } catch (err) {
2764
2959
  console.error(err instanceof Error ? err.message : String(err));
2765
2960
  process.exitCode = 1;
@@ -2802,14 +2997,31 @@ export async function verifySourceCommand(
2802
2997
  } else {
2803
2998
  const trustOrigin = report.trust.explicit ? "explicit" : "default";
2804
2999
  console.log(
2805
- `${report.source.name}\t${report.source.kind}\t${report.source.url}`
3000
+ renderPage({
3001
+ title: `fclt verify-source ${report.source.name}`,
3002
+ subtitle: report.source.url,
3003
+ sections: [
3004
+ {
3005
+ title: "Source",
3006
+ lines: renderKeyValue([
3007
+ ["kind", report.source.kind],
3008
+ ["trust", `${report.trust.level} (${trustOrigin})`],
3009
+ ["items", String(report.checks.items)],
3010
+ ]),
3011
+ },
3012
+ {
3013
+ title: "Checks",
3014
+ lines: renderKeyValue([
3015
+ ["fetch", report.checks.fetch],
3016
+ ["parse", report.checks.parse],
3017
+ ["integrity", report.checks.integrity],
3018
+ ["signature", report.checks.signature],
3019
+ ]),
3020
+ },
3021
+ ],
3022
+ footer: report.error ? [`error: ${report.error}`] : undefined,
3023
+ })
2806
3024
  );
2807
- console.log(
2808
- `trust=${report.trust.level} (${trustOrigin})\tfetch=${report.checks.fetch}\tparse=${report.checks.parse}\tintegrity=${report.checks.integrity}\tsignature=${report.checks.signature}\titems=${report.checks.items}`
2809
- );
2810
- if (report.error) {
2811
- console.log(`error: ${report.error}`);
2812
- }
2813
3025
  }
2814
3026
 
2815
3027
  if (report.error) {
@@ -2860,9 +3072,24 @@ export async function templatesCommand(
2860
3072
  console.log(JSON.stringify(rows, null, 2));
2861
3073
  return;
2862
3074
  }
2863
- for (const row of rows) {
2864
- console.log(`${row.id}\t${row.type}\t${row.version}\t${row.title}`);
2865
- }
3075
+ console.log(
3076
+ renderPage({
3077
+ title: "fclt templates list",
3078
+ subtitle: `${rows.length} available scaffold${rows.length === 1 ? "" : "s"}`,
3079
+ sections: [
3080
+ {
3081
+ title: "Templates",
3082
+ lines: renderCatalog(
3083
+ rows.map((row) => ({
3084
+ title: row.id,
3085
+ meta: `${row.type} • ${row.version}`,
3086
+ description: row.description || row.title,
3087
+ }))
3088
+ ),
3089
+ },
3090
+ ],
3091
+ })
3092
+ );
2866
3093
  return;
2867
3094
  }
2868
3095
  if (sub !== "init") {
@@ -2874,7 +3101,7 @@ export async function templatesCommand(
2874
3101
  const [kind, ...args] = rest;
2875
3102
  if (!kind) {
2876
3103
  console.error(
2877
- "templates init requires a kind (skill|mcp|snippet|agents|claude|project-ai|automation)"
3104
+ "templates init requires a kind (skill|mcp|agent|snippet|agents|claude|project-ai|automation)"
2878
3105
  );
2879
3106
  process.exitCode = 2;
2880
3107
  return;
@@ -2897,10 +3124,18 @@ export async function templatesCommand(
2897
3124
  return;
2898
3125
  }
2899
3126
  const action = dryRun ? "Would scaffold" : "Scaffolded";
2900
- console.log(`${action} ${kind} template as ${result.installedAs}`);
2901
- for (const path of result.changedPaths) {
2902
- console.log(` - ${path}`);
2903
- }
3127
+ console.log(
3128
+ renderPage({
3129
+ title: `fclt templates init ${kind}`,
3130
+ subtitle: `${action} ${result.installedAs}`,
3131
+ sections: [
3132
+ {
3133
+ title: "Changed Paths",
3134
+ lines: renderBullets(result.changedPaths),
3135
+ },
3136
+ ],
3137
+ })
3138
+ );
2904
3139
  return;
2905
3140
  } catch (err) {
2906
3141
  console.error(err instanceof Error ? err.message : String(err));
@@ -2927,6 +3162,20 @@ export async function templatesCommand(
2927
3162
  process.exitCode = 2;
2928
3163
  return;
2929
3164
  }
3165
+ } else if (kind === "agent") {
3166
+ ref = `${BUILTIN_INDEX_NAME}:agent-template`;
3167
+ const rawName = positional[0];
3168
+ if (!rawName) {
3169
+ console.error("templates init agent requires a <name>");
3170
+ process.exitCode = 2;
3171
+ return;
3172
+ }
3173
+ const normalizedName = rawName
3174
+ .replaceAll("\\", "/")
3175
+ .replace(TRAILING_SLASH_RE, "");
3176
+ as = normalizedName.endsWith(".toml")
3177
+ ? normalizedName
3178
+ : `${normalizedName}/agent.toml`;
2930
3179
  } else if (kind === "snippet") {
2931
3180
  ref = `${BUILTIN_INDEX_NAME}:snippet-template`;
2932
3181
  as = positional[0];
@@ -2999,11 +3248,24 @@ export async function templatesCommand(
2999
3248
  }
3000
3249
  const action = dryRun ? "Would scaffold" : "Scaffolded";
3001
3250
  console.log(
3002
- `${action} automation template as ${result.installedAs} (${result.path})`
3251
+ renderPage({
3252
+ title: "fclt templates init automation",
3253
+ subtitle: `${action} ${result.installedAs}`,
3254
+ sections: [
3255
+ {
3256
+ title: "Automation",
3257
+ lines: renderKeyValue([
3258
+ ["name", result.installedAs],
3259
+ ["path", result.path],
3260
+ ]),
3261
+ },
3262
+ {
3263
+ title: "Changed Paths",
3264
+ lines: renderBullets(result.changedPaths),
3265
+ },
3266
+ ],
3267
+ })
3003
3268
  );
3004
- for (const path of result.changedPaths) {
3005
- console.log(` - ${path}`);
3006
- }
3007
3269
  return;
3008
3270
  } catch (err) {
3009
3271
  console.error(err instanceof Error ? err.message : String(err));
@@ -3034,10 +3296,18 @@ export async function templatesCommand(
3034
3296
  return;
3035
3297
  }
3036
3298
  const action = dryRun ? "Would scaffold" : "Scaffolded";
3037
- console.log(`${action} ${kind} template as ${result.installedAs}`);
3038
- for (const path of result.changedPaths) {
3039
- console.log(` - ${path}`);
3040
- }
3299
+ console.log(
3300
+ renderPage({
3301
+ title: `fclt templates init ${kind}`,
3302
+ subtitle: `${action} ${result.installedAs}`,
3303
+ sections: [
3304
+ {
3305
+ title: "Changed Paths",
3306
+ lines: renderBullets(result.changedPaths),
3307
+ },
3308
+ ],
3309
+ })
3310
+ );
3041
3311
  } catch (err) {
3042
3312
  console.error(err instanceof Error ? err.message : String(err));
3043
3313
  process.exitCode = 1;