agents-united 0.7.3 → 0.8.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/dist/cli.d.ts CHANGED
@@ -1,5 +1,27 @@
1
1
  type AgentHost = 'agents' | 'gemini' | 'claude' | 'cursor' | 'cline' | 'opencode' | 'codex';
2
+ type ProjectionKind = 'role' | 'skill' | 'rule' | 'team-manifest' | 'workflow' | 'bridge' | 'plugin-manifest';
3
+ interface ProjectionInfo {
4
+ host: string;
5
+ path: string;
6
+ kind?: ProjectionKind;
7
+ warnings: string[];
8
+ }
2
9
 
3
10
  declare function detectWorkspaceHosts(cwd?: string): AgentHost[];
11
+ declare function getTerminalContentWidth(): number;
12
+ declare function wrapText(text: string, maxLen?: number, indent?: string): string[];
13
+ declare function formatWrappedList(items: string[], indent?: string, maxLineLen?: number): string[];
14
+ interface InstallationSummaryOptions {
15
+ bundleName: string;
16
+ scope: string;
17
+ method: string;
18
+ hosts: string[];
19
+ agents: string[];
20
+ skills: string[];
21
+ targetDirs: string[];
22
+ projections?: ProjectionInfo[];
23
+ }
24
+ declare function formatInstallationSummary(options: InstallationSummaryOptions): string;
25
+ declare function renderProjections(projections: ProjectionInfo[]): string;
4
26
 
5
- export { detectWorkspaceHosts };
27
+ export { type InstallationSummaryOptions, detectWorkspaceHosts, formatInstallationSummary, formatWrappedList, getTerminalContentWidth, renderProjections, wrapText };
package/dist/cli.js CHANGED
@@ -1274,7 +1274,18 @@ var InstallEngine = class _InstallEngine {
1274
1274
  const hasCanonicalAgents = hosts.includes("agents");
1275
1275
  const fanoutHosts = (options.fanout || []).map((h) => typeof h === "string" ? h.trim().toLowerCase() : h).filter((h) => isKnownHost(h) && HOST_REGISTRY[h]?.projectionCapable);
1276
1276
  if (options.dryRun) {
1277
- const projections2 = hasCanonicalAgents ? await this.buildProjections(fanoutHosts, resolved, registryDir, scope, options.targetDir) : [];
1277
+ let effectiveDryFanout = fanoutHosts;
1278
+ if (options.fanout === void 0 && hasCanonicalAgents) {
1279
+ const agentsTarget2 = AgentHostAdapter.resolveHostDir(scope, "agents", options.targetDir);
1280
+ const lockfilePath = path5.join(agentsTarget2, "agents-united.json");
1281
+ if (await fs3.pathExists(lockfilePath)) {
1282
+ const lockfile = await fs3.readJson(lockfilePath).catch(() => null);
1283
+ if (lockfile?.fanout) {
1284
+ effectiveDryFanout = lockfile.fanout.filter((h) => isKnownHost(h) && HOST_REGISTRY[h]?.projectionCapable);
1285
+ }
1286
+ }
1287
+ }
1288
+ const projections2 = hasCanonicalAgents ? await this.buildProjections(effectiveDryFanout, resolved, registryDir, scope, options.targetDir) : [];
1278
1289
  return { installed: resolved, targetDirs, dryRun: true, method, projections: projections2 };
1279
1290
  }
1280
1291
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1710,7 +1721,7 @@ var InventoryScanner = class _InventoryScanner {
1710
1721
  constructor(registry2) {
1711
1722
  this.registry = registry2 || new RegistryResolver();
1712
1723
  }
1713
- static formatDisplayLocation(scope, host, targetDir) {
1724
+ static formatDisplayLocation(scope, host, targetDir, fanout) {
1714
1725
  const home = os2.homedir();
1715
1726
  const cwd = process.cwd();
1716
1727
  let prettyPath = targetDir;
@@ -1721,7 +1732,8 @@ var InventoryScanner = class _InventoryScanner {
1721
1732
  const rel = path7.relative(home, targetDir);
1722
1733
  prettyPath = `~/${rel.replace(/\\/g, "/")}`;
1723
1734
  }
1724
- return `[${scope}: ${prettyPath}]`;
1735
+ const fanoutSuffix = fanout && fanout.length > 0 ? ` + ${fanout.join(", ")}` : "";
1736
+ return `[${scope}: ${prettyPath}${fanoutSuffix}]`;
1725
1737
  }
1726
1738
  parseHosts(options) {
1727
1739
  if (options.hosts && options.hosts.length > 0) return options.hosts;
@@ -1763,6 +1775,26 @@ var InventoryScanner = class _InventoryScanner {
1763
1775
  const bundleRecords = [];
1764
1776
  const standaloneRecords = [];
1765
1777
  const scannedTargetDirs = /* @__PURE__ */ new Set();
1778
+ const scannedLocationsMap = /* @__PURE__ */ new Map();
1779
+ const home = os2.homedir();
1780
+ for (const scope of scopes) {
1781
+ const canonicalDir = AgentHostAdapter.resolveHostDir(scope, "agents", options.targetDir);
1782
+ let displayLoc = canonicalDir;
1783
+ if (canonicalDir.startsWith(cwd)) {
1784
+ const rel = path7.relative(cwd, canonicalDir);
1785
+ displayLoc = rel ? `./${rel.replace(/\\/g, "/")}` : ".";
1786
+ } else if (canonicalDir.startsWith(home)) {
1787
+ const rel = path7.relative(home, canonicalDir);
1788
+ displayLoc = `~/${rel.replace(/\\/g, "/")}`;
1789
+ }
1790
+ scannedLocationsMap.set(`${scope}:${canonicalDir}`, {
1791
+ scope,
1792
+ targetDir: canonicalDir,
1793
+ displayLocation: displayLoc,
1794
+ packageCount: 0,
1795
+ fanout: []
1796
+ });
1797
+ }
1766
1798
  for (const item of candidateDirs) {
1767
1799
  const lockfilePath = path7.join(item.dir, "agents-united.json");
1768
1800
  if (!await fs5.pathExists(lockfilePath)) {
@@ -1775,7 +1807,22 @@ var InventoryScanner = class _InventoryScanner {
1775
1807
  } catch {
1776
1808
  continue;
1777
1809
  }
1778
- const displayLocation = _InventoryScanner.formatDisplayLocation(item.scope, item.host, item.dir);
1810
+ const lockfileFanout = lockfile.fanout || [];
1811
+ const displayLocation = _InventoryScanner.formatDisplayLocation(item.scope, item.host, item.dir, lockfileFanout);
1812
+ const locKey = `${item.scope}:${item.dir}`;
1813
+ const locSummary = scannedLocationsMap.get(locKey) || {
1814
+ scope: item.scope,
1815
+ targetDir: item.dir,
1816
+ displayLocation: _InventoryScanner.formatDisplayLocation(item.scope, item.host, item.dir),
1817
+ packageCount: 0,
1818
+ fanout: []
1819
+ };
1820
+ for (const f of lockfileFanout) {
1821
+ if (!locSummary.fanout.includes(f)) {
1822
+ locSummary.fanout.push(f);
1823
+ }
1824
+ }
1825
+ scannedLocationsMap.set(locKey, locSummary);
1779
1826
  const bundleOwnedSkills = /* @__PURE__ */ new Set();
1780
1827
  const bundleOwnedAgents = /* @__PURE__ */ new Set();
1781
1828
  const bundleOwnedWorkflows = /* @__PURE__ */ new Set();
@@ -1819,6 +1866,7 @@ var InventoryScanner = class _InventoryScanner {
1819
1866
  bundleOwnedWorkflows.add(w.replace(/\.md$/, ""));
1820
1867
  });
1821
1868
  }
1869
+ locSummary.packageCount++;
1822
1870
  const record = {
1823
1871
  id: `${bundleName}@${item.scope}:${item.host}`,
1824
1872
  name: bundleName,
@@ -1833,7 +1881,9 @@ var InventoryScanner = class _InventoryScanner {
1833
1881
  method: lockfile.method || "symlink",
1834
1882
  fileCount,
1835
1883
  title: bundleDef?.name || bundleName,
1836
- description: bundleDef?.description
1884
+ description: bundleDef?.description,
1885
+ fanout: lockfileFanout,
1886
+ projections: lockfileFanout
1837
1887
  };
1838
1888
  allRecords.push(record);
1839
1889
  bundleRecords.push(record);
@@ -1841,6 +1891,7 @@ var InventoryScanner = class _InventoryScanner {
1841
1891
  const installedSkills = lockfile.installed?.skills || [];
1842
1892
  for (const skillName of installedSkills) {
1843
1893
  if (bundleOwnedSkills.has(skillName)) continue;
1894
+ locSummary.packageCount++;
1844
1895
  const record = {
1845
1896
  id: `skill:${skillName}@${item.scope}:${item.host}`,
1846
1897
  name: skillName,
@@ -1855,7 +1906,9 @@ var InventoryScanner = class _InventoryScanner {
1855
1906
  method: lockfile.method || "symlink",
1856
1907
  fileCount: 1,
1857
1908
  title: skillName,
1858
- description: `Standalone skill (${skillName})`
1909
+ description: `Standalone skill (${skillName})`,
1910
+ fanout: lockfileFanout,
1911
+ projections: lockfileFanout
1859
1912
  };
1860
1913
  allRecords.push(record);
1861
1914
  standaloneRecords.push(record);
@@ -1864,6 +1917,7 @@ var InventoryScanner = class _InventoryScanner {
1864
1917
  for (const agentFile of installedAgents) {
1865
1918
  if (bundleOwnedAgents.has(agentFile)) continue;
1866
1919
  const agentName = agentFile.replace(/\.md$/, "");
1920
+ locSummary.packageCount++;
1867
1921
  const record = {
1868
1922
  id: `agent:${agentName}@${item.scope}:${item.host}`,
1869
1923
  name: agentName,
@@ -1878,7 +1932,9 @@ var InventoryScanner = class _InventoryScanner {
1878
1932
  method: lockfile.method || "symlink",
1879
1933
  fileCount: 1,
1880
1934
  title: agentName,
1881
- description: `Standalone agent (${agentFile})`
1935
+ description: `Standalone agent (${agentFile})`,
1936
+ fanout: lockfileFanout,
1937
+ projections: lockfileFanout
1882
1938
  };
1883
1939
  allRecords.push(record);
1884
1940
  standaloneRecords.push(record);
@@ -1888,7 +1944,9 @@ var InventoryScanner = class _InventoryScanner {
1888
1944
  records: allRecords,
1889
1945
  bundles: bundleRecords,
1890
1946
  standaloneItems: standaloneRecords,
1891
- targetDirs: Array.from(scannedTargetDirs)
1947
+ targetDirs: Array.from(scannedTargetDirs),
1948
+ scannedScopes: scopes,
1949
+ scannedLocations: Array.from(scannedLocationsMap.values())
1892
1950
  };
1893
1951
  }
1894
1952
  };
@@ -1906,6 +1964,18 @@ var UpdateEngine = class {
1906
1964
  this.scanner = scanner2 || new InventoryScanner(this.registry);
1907
1965
  this.installer = installer2 || new InstallEngine(this.registry);
1908
1966
  }
1967
+ deduplicateProjections(projections) {
1968
+ const seen = /* @__PURE__ */ new Set();
1969
+ const deduplicated = [];
1970
+ for (const p of projections) {
1971
+ const key = `${p.host}:${p.path}`;
1972
+ if (!seen.has(key)) {
1973
+ seen.add(key);
1974
+ deduplicated.push(p);
1975
+ }
1976
+ }
1977
+ return deduplicated;
1978
+ }
1909
1979
  async calculateHash(filePath) {
1910
1980
  const buffer = await fs6.readFile(filePath);
1911
1981
  return "sha256:" + crypto3.createHash("sha256").update(buffer).digest("hex");
@@ -1962,7 +2032,9 @@ var UpdateEngine = class {
1962
2032
  items,
1963
2033
  outdatedCount,
1964
2034
  upToDateCount,
1965
- totalCount: items.length
2035
+ totalCount: items.length,
2036
+ scannedScopes: inventory.scannedScopes,
2037
+ scannedLocations: inventory.scannedLocations
1966
2038
  };
1967
2039
  }
1968
2040
  async update(targets, options = {}) {
@@ -1985,19 +2057,39 @@ var UpdateEngine = class {
1985
2057
  updated: [],
1986
2058
  skipped: [],
1987
2059
  targetDirs: inventory.targetDirs,
1988
- dryRun: options.dryRun || false
2060
+ dryRun: options.dryRun || false,
2061
+ projections: []
1989
2062
  };
1990
2063
  }
1991
2064
  if (options.dryRun) {
2065
+ const dryProjections = [];
2066
+ for (const record of recordsToUpdate) {
2067
+ const lockfilePath = path8.join(record.targetDir, "agents-united.json");
2068
+ if (await fs6.pathExists(lockfilePath)) {
2069
+ const dryResult = await this.installer.install(record.name, {
2070
+ scope: record.scope,
2071
+ method: record.method,
2072
+ hosts: [record.host],
2073
+ targetDir: record.targetDir,
2074
+ fanout: options.fanout,
2075
+ dryRun: true
2076
+ });
2077
+ if (dryResult.projections) {
2078
+ dryProjections.push(...dryResult.projections);
2079
+ }
2080
+ }
2081
+ }
1992
2082
  return {
1993
2083
  updated: recordsToUpdate,
1994
2084
  skipped: [],
1995
2085
  targetDirs: inventory.targetDirs,
1996
- dryRun: true
2086
+ dryRun: true,
2087
+ projections: this.deduplicateProjections(dryProjections)
1997
2088
  };
1998
2089
  }
1999
2090
  const updated = [];
2000
2091
  const skipped = [];
2092
+ const collectedProjections = [];
2001
2093
  for (const record of recordsToUpdate) {
2002
2094
  const lockfilePath = path8.join(record.targetDir, "agents-united.json");
2003
2095
  if (!await fs6.pathExists(lockfilePath)) {
@@ -2038,7 +2130,7 @@ var UpdateEngine = class {
2038
2130
  continue;
2039
2131
  }
2040
2132
  }
2041
- await this.installer.install(record.name, {
2133
+ const installResult = await this.installer.install(record.name, {
2042
2134
  scope: record.scope,
2043
2135
  method: record.method,
2044
2136
  hosts: [record.host],
@@ -2047,11 +2139,24 @@ var UpdateEngine = class {
2047
2139
  force: true
2048
2140
  // force overwrite since we passed collision check above
2049
2141
  });
2142
+ if (installResult.projections) {
2143
+ collectedProjections.push(...installResult.projections);
2144
+ }
2050
2145
  if (await fs6.pathExists(lockfilePath)) {
2051
2146
  const updatedLockfile = await fs6.readJson(lockfilePath);
2052
2147
  updatedLockfile.bundleVersions = updatedLockfile.bundleVersions || {};
2053
2148
  updatedLockfile.bundleVersions[record.name] = record.upstreamVersion;
2054
2149
  await fs6.writeJson(lockfilePath, updatedLockfile, { spaces: 2 });
2150
+ if (updatedLockfile.fanout) {
2151
+ record.fanout = updatedLockfile.fanout;
2152
+ record.projections = updatedLockfile.fanout;
2153
+ record.displayLocation = InventoryScanner.formatDisplayLocation(
2154
+ record.scope,
2155
+ record.host,
2156
+ record.targetDir,
2157
+ updatedLockfile.fanout
2158
+ );
2159
+ }
2055
2160
  }
2056
2161
  record.installedVersion = record.upstreamVersion;
2057
2162
  record.driftStatus = "up-to-date";
@@ -2061,7 +2166,8 @@ var UpdateEngine = class {
2061
2166
  updated,
2062
2167
  skipped,
2063
2168
  targetDirs: inventory.targetDirs,
2064
- dryRun: false
2169
+ dryRun: false,
2170
+ projections: this.deduplicateProjections(collectedProjections)
2065
2171
  };
2066
2172
  }
2067
2173
  };
@@ -3304,6 +3410,112 @@ function detectWorkspaceHosts(cwd = process.cwd()) {
3304
3410
  }
3305
3411
  return detected;
3306
3412
  }
3413
+ function getTerminalContentWidth() {
3414
+ const cols = typeof process !== "undefined" && process.stdout?.columns ? process.stdout.columns : 80;
3415
+ return Math.max(40, Math.min(cols - 8, 68));
3416
+ }
3417
+ function wrapText(text2, maxLen, indent = "") {
3418
+ if (!text2) return [];
3419
+ const limit = maxLen ?? getTerminalContentWidth();
3420
+ const words = text2.split(/\s+/);
3421
+ const lines = [];
3422
+ let current = indent;
3423
+ for (const word of words) {
3424
+ if (!word) continue;
3425
+ if (current.length > indent.length && current.length + 1 + word.length > limit) {
3426
+ lines.push(current);
3427
+ current = indent + word;
3428
+ } else {
3429
+ current = current.length === indent.length ? current + word : current + " " + word;
3430
+ }
3431
+ }
3432
+ if (current.trim().length > 0) {
3433
+ lines.push(current);
3434
+ }
3435
+ return lines;
3436
+ }
3437
+ function formatWrappedList(items, indent = " ", maxLineLen) {
3438
+ if (!items || items.length === 0) return [];
3439
+ const limit = maxLineLen ?? getTerminalContentWidth();
3440
+ const lines = [];
3441
+ let currentLine = indent;
3442
+ for (let i = 0; i < items.length; i++) {
3443
+ const item = items[i];
3444
+ const isLast = i === items.length - 1;
3445
+ const token = isLast ? item : `${item}, `;
3446
+ if (currentLine.length > indent.length && currentLine.length + token.length > limit) {
3447
+ lines.push(currentLine.trimEnd());
3448
+ currentLine = indent + token;
3449
+ } else {
3450
+ currentLine += token;
3451
+ }
3452
+ }
3453
+ if (currentLine.trim().length > 0) {
3454
+ lines.push(currentLine.trimEnd());
3455
+ }
3456
+ return lines;
3457
+ }
3458
+ function formatInstallationSummary(options) {
3459
+ const { bundleName, scope, method, hosts, agents, skills, targetDirs, projections } = options;
3460
+ const limit = getTerminalContentWidth();
3461
+ const lines = [
3462
+ `Bundle: ${bundleName}`,
3463
+ `Scope: ${scope}`,
3464
+ `Method: ${method}`,
3465
+ `Targets: ${hosts.join(", ")}`
3466
+ ];
3467
+ if (agents.length === 0) {
3468
+ lines.push(`Agents: None`);
3469
+ } else {
3470
+ const inlineAgents = `Agents (${agents.length}): ${agents.join(", ")}`;
3471
+ if (inlineAgents.length <= limit) {
3472
+ lines.push(inlineAgents);
3473
+ } else {
3474
+ lines.push(`Agents (${agents.length}):`);
3475
+ lines.push(...formatWrappedList(agents, " ", limit));
3476
+ }
3477
+ }
3478
+ if (skills.length === 0) {
3479
+ lines.push(`Skills: None`);
3480
+ } else {
3481
+ const inlineSkills = `Skills (${skills.length}): ${skills.join(", ")}`;
3482
+ if (inlineSkills.length <= limit) {
3483
+ lines.push(inlineSkills);
3484
+ } else {
3485
+ lines.push(`Skills (${skills.length}):`);
3486
+ lines.push(...formatWrappedList(skills, " ", limit));
3487
+ }
3488
+ }
3489
+ if (targetDirs.length === 0) {
3490
+ lines.push("Target Directories: None");
3491
+ } else if (targetDirs.length === 1) {
3492
+ const singleDirLine = `Target Directories: ${targetDirs[0]}`;
3493
+ if (singleDirLine.length <= limit) {
3494
+ lines.push(singleDirLine);
3495
+ } else {
3496
+ lines.push("Target Directories:");
3497
+ lines.push(` ${targetDirs[0]}`);
3498
+ }
3499
+ } else {
3500
+ lines.push(`Target Directories (${targetDirs.length}):`);
3501
+ for (const dir of targetDirs) {
3502
+ lines.push(` ${dir}`);
3503
+ }
3504
+ }
3505
+ if (projections && projections.length > 0) {
3506
+ const byHost = /* @__PURE__ */ new Map();
3507
+ for (const p of projections) {
3508
+ byHost.set(p.host, (byHost.get(p.host) ?? 0) + 1);
3509
+ }
3510
+ const hostSummaries = [];
3511
+ for (const [host, count] of byHost) {
3512
+ const label = HOST_REGISTRY[host]?.label ?? host;
3513
+ hostSummaries.push(`${label} (${count} file${count > 1 ? "s" : ""})`);
3514
+ }
3515
+ lines.push(`Projections: ${hostSummaries.join(", ")}`);
3516
+ }
3517
+ return lines.join("\n");
3518
+ }
3307
3519
  function renderProjections(projections) {
3308
3520
  const byHost = /* @__PURE__ */ new Map();
3309
3521
  for (const p of projections) {
@@ -3316,7 +3528,12 @@ function renderProjections(projections) {
3316
3528
  lines.push(`${HOST_REGISTRY[host]?.label ?? host} \u2014 ${items.length} file${items.length > 1 ? "s" : ""}`);
3317
3529
  for (const p of items) {
3318
3530
  lines.push(` \u2192 ${p.path}`);
3319
- for (const w of p.warnings) lines.push(pc.yellow(` \u26A0 ${w}`));
3531
+ for (const w of p.warnings) {
3532
+ const wrapped = wrapText(`\u26A0 ${w}`, 68, " ");
3533
+ for (const wl of wrapped) {
3534
+ lines.push(pc.yellow(wl));
3535
+ }
3536
+ }
3320
3537
  }
3321
3538
  }
3322
3539
  return lines.join("\n");
@@ -3826,22 +4043,28 @@ To bypass this gate and install draft assets anyway, pass: --allow-under-constru
3826
4043
  return;
3827
4044
  }
3828
4045
  note(
3829
- `Bundle: ${result.installed.targetBundle || "Single Item"}
3830
- Scope: ${scope}
3831
- Method: ${result.method}
3832
- Targets: ${hosts.join(", ")}
3833
- Agents: ${result.installed.agents.join(", ") || "None"}
3834
- Skills: ${result.installed.skills.join(", ") || "None"}
3835
- Target Directories: ${result.targetDirs.join("\n ")}` + (result.projections.length > 0 ? `
3836
-
3837
- Projections:
3838
- ${renderProjections(result.projections)}` : ""),
4046
+ formatInstallationSummary({
4047
+ bundleName: result.installed.targetBundle || "Single Item",
4048
+ scope,
4049
+ method: result.method,
4050
+ hosts,
4051
+ agents: result.installed.agents,
4052
+ skills: result.installed.skills,
4053
+ targetDirs: result.targetDirs,
4054
+ projections: result.projections
4055
+ }),
3839
4056
  "Installation Success"
3840
4057
  );
4058
+ if (result.projections.length > 0) {
4059
+ note(renderProjections(result.projections), "Installed Projections");
4060
+ }
3841
4061
  const hasClineProjection = result.projections.some((p) => p.host === "cline");
3842
4062
  if (hasClineProjection && !options.dryRun) {
3843
4063
  note(
3844
- pc.green(`Already active: any Cline session in this workspace now sees this bundle's skills, subagent_* agent tools, rules & workflow commands (ADR 0013 native discovery).`),
4064
+ pc.green(
4065
+ `Already active: any Cline session in this workspace now sees this bundle's
4066
+ skills, subagent_* agent tools, rules & workflow commands (ADR 0013 native discovery).`
4067
+ ),
3845
4068
  "Native Activation"
3846
4069
  );
3847
4070
  }
@@ -3919,7 +4142,7 @@ rules & workflows run: agents update ${identifier} --fanout cline
3919
4142
  if (!options.dryRun && targetBundleDef?.tier === "organization") {
3920
4143
  note(
3921
4144
  pc.cyan(
3922
- "\u{1F4A1} In-Session MCP Setup: Your Lead Orchestrator will automatically check your runtime tools\nand guide configuration of live browser automation, design tokens, or cloud APIs on demand."
4145
+ "\u{1F4A1} In-Session MCP Setup: Your Lead Orchestrator will automatically check\nyour runtime tools and guide configuration of live browser automation,\ndesign tokens, or cloud APIs on demand."
3923
4146
  ),
3924
4147
  "Adaptive Tooling"
3925
4148
  );
@@ -4036,7 +4259,28 @@ cli.command("update [identifier]", "Update installed packages to upstream regist
4036
4259
  } else if (options.all || options.yes || !isInteractive) {
4037
4260
  targetsToUpdate = "__all__";
4038
4261
  } else {
4039
- const statusMessage = `Total Installed Packages: ${report.totalCount}
4262
+ const locationLines = [];
4263
+ if (report.scannedLocations && report.scannedLocations.length > 0) {
4264
+ for (const loc of report.scannedLocations) {
4265
+ const scopeLabel = loc.scope === "project" ? "Project" : "Global";
4266
+ const fanoutStr = loc.fanout.length > 0 ? ` ${pc.magenta(`(projections: ${loc.fanout.join(", ")})`)}` : "";
4267
+ locationLines.push(` \u2022 ${scopeLabel} (${loc.displayLocation}): ${pc.bold(loc.packageCount)} package${loc.packageCount !== 1 ? "s" : ""}${fanoutStr}`);
4268
+ }
4269
+ } else {
4270
+ const projectCount = report.items.filter((i) => i.record.scope === "project").length;
4271
+ const globalCount = report.items.filter((i) => i.record.scope === "global").length;
4272
+ if (!options.global) {
4273
+ locationLines.push(` \u2022 Project (./.agents): ${pc.bold(projectCount)} package${projectCount !== 1 ? "s" : ""}`);
4274
+ }
4275
+ locationLines.push(` \u2022 Global (~/.agents): ${pc.bold(globalCount)} package${globalCount !== 1 ? "s" : ""}`);
4276
+ }
4277
+ const allDetectedFanouts = Array.from(new Set(report.items.flatMap((i) => i.record.fanout || [])));
4278
+ const fanoutSummary = allDetectedFanouts.length > 0 ? `
4279
+ Active Projections: ${allDetectedFanouts.map((h) => `${pc.cyan(h)} (./.${h}/)`).join(", ")}` : "";
4280
+ const statusMessage = `Locations Checked:
4281
+ ${locationLines.join("\n")}${fanoutSummary}
4282
+
4283
+ Total Installed Packages: ${report.totalCount}
4040
4284
  Updates Available: ${report.outdatedCount > 0 ? pc.yellow(pc.bold(`${report.outdatedCount} package${report.outdatedCount > 1 ? "s" : ""} can be updated`)) : pc.green("0 (All up to date)")}`;
4041
4285
  note(statusMessage, "Inventory Status");
4042
4286
  const menuOptions = [];
@@ -4108,6 +4352,9 @@ Updates Available: ${report.outdatedCount > 0 ? pc.yellow(pc.bold(`${report.outd
4108
4352
  updateSpinner.stop("Update completed");
4109
4353
  if (options.dryRun) {
4110
4354
  outro(pc.yellow(`[DRY RUN] Would update ${result.updated.length} packages in ${result.targetDirs.join(", ")}`));
4355
+ if (result.projections && result.projections.length > 0) {
4356
+ note(renderProjections(result.projections), "Projection Plan (dry run)");
4357
+ }
4111
4358
  return;
4112
4359
  }
4113
4360
  if (result.updated.length > 0) {
@@ -4130,6 +4377,9 @@ For Cline configured agents, rules & workflows: agents update ${unprojected[0]}
4130
4377
  }
4131
4378
  const updatedList = result.updated.map((u) => ` \u2714 ${pc.bold(u.name)} ${pc.cyan(u.displayLocation)} ${pc.green(`(v${u.installedVersion})`)}`).join("\n");
4132
4379
  note(updatedList, "Updated Packages");
4380
+ if (result.projections && result.projections.length > 0) {
4381
+ note(renderProjections(result.projections), "Updated Projections");
4382
+ }
4133
4383
  }
4134
4384
  if (result.skipped.length > 0) {
4135
4385
  const skippedList = result.skipped.map((s2) => ` \u26A0 ${pc.yellow(s2.record.name)}: ${s2.reason}`).join("\n");
@@ -4534,21 +4784,28 @@ Planned Capabilities:
4534
4784
  });
4535
4785
  installSpinner.stop(pc.green(`\u2714 Successfully installed ${bundle.name}!`));
4536
4786
  note(
4537
- `Bundle: ${result.installed.targetBundle || bundle.name}
4538
- Scope: project
4539
- Method: ${result.method}
4540
- Targets: ${installHosts.join(", ")}
4541
- Agents: ${result.installed.agents.join(", ") || "None"}
4542
- Skills: ${result.installed.skills.join(", ") || "None"}
4543
- Target Directories: ${result.targetDirs.join("\n ")}` + (result.projections.length > 0 ? `
4544
-
4545
- Projections:
4546
- ${renderProjections(result.projections)}` : ""),
4787
+ formatInstallationSummary({
4788
+ bundleName: result.installed.targetBundle || bundle.name,
4789
+ scope: "project",
4790
+ method: result.method,
4791
+ hosts: installHosts,
4792
+ agents: result.installed.agents,
4793
+ skills: result.installed.skills,
4794
+ targetDirs: result.targetDirs,
4795
+ projections: result.projections
4796
+ }),
4547
4797
  "Installation Success"
4548
4798
  );
4799
+ if (result.projections.length > 0) {
4800
+ note(renderProjections(result.projections), "Installed Projections");
4801
+ }
4549
4802
  if (result.projections.some((p) => p.host === "cline")) {
4550
4803
  note(
4551
- pc.green(`Already active: any Cline session in this workspace now sees this bundle's skills, subagent_* agent tools, rules & workflow commands (ADR 0013 native discovery). Use "agents start ${bundle.name}" for a pre-seeded team session.`),
4804
+ pc.green(
4805
+ `Already active: any Cline session in this workspace now sees this bundle's
4806
+ skills, subagent_* agent tools, rules & workflow commands (ADR 0013 native discovery).
4807
+ Use "agents start ${bundle.name}" for a pre-seeded team session.`
4808
+ ),
4552
4809
  "Native Activation"
4553
4810
  );
4554
4811
  }
@@ -4939,6 +5196,11 @@ cli.help();
4939
5196
  cli.version("1.0.0");
4940
5197
  cli.parse();
4941
5198
  export {
4942
- detectWorkspaceHosts
5199
+ detectWorkspaceHosts,
5200
+ formatInstallationSummary,
5201
+ formatWrappedList,
5202
+ getTerminalContentWidth,
5203
+ renderProjections,
5204
+ wrapText
4943
5205
  };
4944
5206
  //# sourceMappingURL=cli.js.map