@skaile/workspaces 0.50.0 → 0.53.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.
Files changed (181) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/dist/asset-feeds-OEGJ6NOF.js +205 -0
  3. package/dist/asset-feeds-OEGJ6NOF.js.map +1 -0
  4. package/dist/asset-manager/index.js +8 -8
  5. package/dist/asset-manager/installer.js +7 -7
  6. package/dist/asset-manager/src/index.d.ts +83 -7
  7. package/dist/asset-manager/src/index.d.ts.map +1 -1
  8. package/dist/asset-manager/src/installer.d.ts.map +1 -1
  9. package/dist/{chunk-P4BDTJMI.js → chunk-2D5YE3FL.js} +4 -4
  10. package/dist/{chunk-P4BDTJMI.js.map → chunk-2D5YE3FL.js.map} +1 -1
  11. package/dist/{chunk-4IGNQG6E.js → chunk-2ZK7M2SG.js} +3 -3
  12. package/dist/{chunk-4IGNQG6E.js.map → chunk-2ZK7M2SG.js.map} +1 -1
  13. package/dist/{chunk-SXT6BAQJ.js → chunk-3IHBGDCG.js} +2 -2
  14. package/dist/{chunk-SXT6BAQJ.js.map → chunk-3IHBGDCG.js.map} +1 -1
  15. package/dist/{chunk-SDAKQCOB.js → chunk-3KPNDTMR.js} +3 -3
  16. package/dist/{chunk-SDAKQCOB.js.map → chunk-3KPNDTMR.js.map} +1 -1
  17. package/dist/{chunk-2Y4HIITD.js → chunk-7ZWLZ5H3.js} +13 -9
  18. package/dist/chunk-7ZWLZ5H3.js.map +1 -0
  19. package/dist/{chunk-6IDBWPK2.js → chunk-BQCGFPHW.js} +4 -3
  20. package/dist/chunk-BQCGFPHW.js.map +1 -0
  21. package/dist/chunk-BXC5547R.js +377 -0
  22. package/dist/chunk-BXC5547R.js.map +1 -0
  23. package/dist/{chunk-3A3OACI2.js → chunk-EOINYN5T.js} +2 -2
  24. package/dist/{chunk-3A3OACI2.js.map → chunk-EOINYN5T.js.map} +1 -1
  25. package/dist/{chunk-XGCXFX7X.js → chunk-F3HNW354.js} +3 -3
  26. package/dist/{chunk-XGCXFX7X.js.map → chunk-F3HNW354.js.map} +1 -1
  27. package/dist/{chunk-33WB7VFV.js → chunk-LKOMH3QC.js} +3 -3
  28. package/dist/{chunk-33WB7VFV.js.map → chunk-LKOMH3QC.js.map} +1 -1
  29. package/dist/{chunk-O4JH3KUE.js → chunk-MENCWIHW.js} +11 -3
  30. package/dist/chunk-MENCWIHW.js.map +1 -0
  31. package/dist/{chunk-PBO6SCT2.js → chunk-NXC6IEL4.js} +37 -14
  32. package/dist/chunk-NXC6IEL4.js.map +1 -0
  33. package/dist/{chunk-EBSIZYM6.js → chunk-QLLFCQU5.js} +12 -12
  34. package/dist/chunk-QLLFCQU5.js.map +1 -0
  35. package/dist/{chunk-X46FY45B.js → chunk-SKADPHAM.js} +12 -8
  36. package/dist/chunk-SKADPHAM.js.map +1 -0
  37. package/dist/{chunk-C5RYGUF4.js → chunk-TCNYSHPM.js} +157 -31
  38. package/dist/chunk-TCNYSHPM.js.map +1 -0
  39. package/dist/{chunk-JXHULIOA.js → chunk-UTHEIPEY.js} +387 -42
  40. package/dist/chunk-UTHEIPEY.js.map +1 -0
  41. package/dist/{chunk-LV73DB4G.js → chunk-V4BGVOJC.js} +26 -22
  42. package/dist/chunk-V4BGVOJC.js.map +1 -0
  43. package/dist/{chunk-ZJ3VRPOI.js → chunk-VE2UIJ7Y.js} +11 -15
  44. package/dist/chunk-VE2UIJ7Y.js.map +1 -0
  45. package/dist/{chunk-H74X4TSR.js → chunk-VGT2R662.js} +6 -6
  46. package/dist/{chunk-H74X4TSR.js.map → chunk-VGT2R662.js.map} +1 -1
  47. package/dist/{chunk-WBF7JLTY.js → chunk-W53D5Q52.js} +4 -4
  48. package/dist/{chunk-WBF7JLTY.js.map → chunk-W53D5Q52.js.map} +1 -1
  49. package/dist/{chunk-HNORRENR.js → chunk-WD2NRI3T.js} +5 -5
  50. package/dist/{chunk-HNORRENR.js.map → chunk-WD2NRI3T.js.map} +1 -1
  51. package/dist/cli/index.js +679 -477
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/cli/src/asset-feeds.d.ts +17 -3
  54. package/dist/cli/src/asset-feeds.d.ts.map +1 -1
  55. package/dist/cli/src/commands/catalog.d.ts +3 -2
  56. package/dist/cli/src/commands/catalog.d.ts.map +1 -1
  57. package/dist/cli/src/commands/manage.d.ts +47 -8
  58. package/dist/cli/src/commands/manage.d.ts.map +1 -1
  59. package/dist/cli/src/commands/project.d.ts.map +1 -1
  60. package/dist/cli/src/commands/source.d.ts +32 -0
  61. package/dist/cli/src/commands/source.d.ts.map +1 -1
  62. package/dist/cli/src/commands/update.d.ts +7 -0
  63. package/dist/cli/src/commands/update.d.ts.map +1 -1
  64. package/dist/connectors/config.js +7 -7
  65. package/dist/connectors/index.js +9 -9
  66. package/dist/connectors/src/config.d.ts +5 -13
  67. package/dist/connectors/src/config.d.ts.map +1 -1
  68. package/dist/connectors/src/index.d.ts +1 -1
  69. package/dist/connectors/src/index.d.ts.map +1 -1
  70. package/dist/core/driver-targets.js +1 -1
  71. package/dist/core/index.js +6 -6
  72. package/dist/core/manifest.js +1 -1
  73. package/dist/core/runtime-assets.js +4 -3
  74. package/dist/core/src/cache-paths.d.ts +6 -0
  75. package/dist/core/src/cache-paths.d.ts.map +1 -1
  76. package/dist/core/src/driver-targets.d.ts +12 -7
  77. package/dist/core/src/driver-targets.d.ts.map +1 -1
  78. package/dist/core/src/index.d.ts +3 -3
  79. package/dist/core/src/index.d.ts.map +1 -1
  80. package/dist/core/src/manifest.d.ts +6 -6
  81. package/dist/core/src/manifest.d.ts.map +1 -1
  82. package/dist/core/src/workspace-config.d.ts +36 -0
  83. package/dist/core/src/workspace-config.d.ts.map +1 -1
  84. package/dist/core/store.js +2 -2
  85. package/dist/core/workspace-config.js +3 -2
  86. package/dist/deploy/index.js +6 -6
  87. package/dist/discovery/index.js +2 -2
  88. package/dist/discovery/src/atomization-conventions.d.ts +30 -0
  89. package/dist/discovery/src/atomization-conventions.d.ts.map +1 -0
  90. package/dist/discovery/src/builtin-providers.d.ts.map +1 -1
  91. package/dist/discovery/src/discover-manifest.d.ts.map +1 -1
  92. package/dist/discovery/src/discover.d.ts +38 -0
  93. package/dist/discovery/src/discover.d.ts.map +1 -1
  94. package/dist/discovery/src/index.d.ts +5 -2
  95. package/dist/discovery/src/index.d.ts.map +1 -1
  96. package/dist/discovery/src/reference-lint.d.ts +112 -0
  97. package/dist/discovery/src/reference-lint.d.ts.map +1 -0
  98. package/dist/discovery/src/requires-graph.d.ts +1 -1
  99. package/dist/discovery/src/tree-entries.d.ts +9 -1
  100. package/dist/discovery/src/tree-entries.d.ts.map +1 -1
  101. package/dist/{ensure-sources-EE7Z2HKS.js → ensure-sources-GJOXWVWO.js} +8 -8
  102. package/dist/{ensure-sources-EE7Z2HKS.js.map → ensure-sources-GJOXWVWO.js.map} +1 -1
  103. package/dist/factory-assets/connectors/deploy.js +9 -9
  104. package/dist/factory-assets/connectors/devserver.js +9 -9
  105. package/dist/factory-assets/connectors/flow/adapter.js +9 -9
  106. package/dist/factory-assets/connectors/flow/engine/flow-kind-provider.d.ts +1 -1
  107. package/dist/factory-assets/connectors/flow/engine/flow-kind-provider.d.ts.map +1 -1
  108. package/dist/factory-assets/connectors/flow/engine.js +7 -7
  109. package/dist/factory-assets/connectors/flow/run-flow.js +10 -10
  110. package/dist/factory-assets/connectors/flow.js +9 -9
  111. package/dist/factory-assets/connectors/git.js +9 -9
  112. package/dist/factory-assets/connectors/gmail/adapter.d.ts.map +1 -1
  113. package/dist/factory-assets/connectors/gmail.js +9 -9
  114. package/dist/factory-assets/connectors/googledrive.js +9 -9
  115. package/dist/factory-assets/connectors/local.js +9 -9
  116. package/dist/factory-assets/connectors/mattermost/adapter.d.ts.map +1 -1
  117. package/dist/factory-assets/connectors/mattermost.js +9 -9
  118. package/dist/factory-assets/connectors/memory.js +9 -9
  119. package/dist/factory-assets/connectors/minio/adapter.d.ts.map +1 -1
  120. package/dist/factory-assets/connectors/minio.js +9 -9
  121. package/dist/factory-assets/connectors/postgres/adapter.d.ts.map +1 -1
  122. package/dist/factory-assets/connectors/postgres.js +9 -9
  123. package/dist/factory-assets/connectors/s3/driver.d.ts.map +1 -1
  124. package/dist/factory-assets/connectors/s3.js +9 -9
  125. package/dist/factory-assets/connectors/sharepoint.js +9 -9
  126. package/dist/factory-assets/connectors/sqlite/adapter.d.ts.map +1 -1
  127. package/dist/factory-assets/connectors/sqlite.js +9 -9
  128. package/dist/factory-assets/connectors/static-server.js +9 -9
  129. package/dist/factory-assets/connectors/tunnel.js +9 -9
  130. package/dist/factory-assets/connectors/webdav.js +9 -9
  131. package/dist/factory-assets/connectors/xstate-store.js +9 -9
  132. package/dist/factory-assets/connectors/xstate.js +9 -9
  133. package/dist/{flows-AAHXXXCH.js → flows-AGPIE6JM.js} +3 -3
  134. package/dist/{flows-AAHXXXCH.js.map → flows-AGPIE6JM.js.map} +1 -1
  135. package/dist/library/index.js +8 -8
  136. package/dist/library/install.js +6 -6
  137. package/dist/open-library-ERKRGH4B.js +23 -0
  138. package/dist/{open-library-TXJMFG34.js.map → open-library-ERKRGH4B.js.map} +1 -1
  139. package/dist/paths-D6RUQ522.js +15 -0
  140. package/dist/{paths-OMVC4UWY.js.map → paths-D6RUQ522.js.map} +1 -1
  141. package/dist/{plugin-store-JWJHIPUT.js → plugin-store-SGNJTBYS.js} +8 -8
  142. package/dist/{plugin-store-JWJHIPUT.js.map → plugin-store-SGNJTBYS.js.map} +1 -1
  143. package/dist/runner/index.js +11 -11
  144. package/dist/runner/src/serve.d.ts +6 -4
  145. package/dist/runner/src/serve.d.ts.map +1 -1
  146. package/dist/sdk/asset-manager.js +8 -8
  147. package/dist/sdk/core.js +6 -6
  148. package/dist/sdk/flow.js +7 -7
  149. package/dist/sdk/index.js +17 -11
  150. package/dist/sdk/index.js.map +1 -1
  151. package/dist/sdk/runner.js +11 -11
  152. package/dist/sdk/src/transport.d.ts +2 -0
  153. package/dist/sdk/src/transport.d.ts.map +1 -1
  154. package/dist/{setup-ETNBVZQA.js → setup-IV2JFW3T.js} +8 -8
  155. package/dist/{setup-ETNBVZQA.js.map → setup-IV2JFW3T.js.map} +1 -1
  156. package/dist/source-FGOMQMZT.js +24 -0
  157. package/dist/source-FGOMQMZT.js.map +1 -0
  158. package/dist/store-client-GJT2XQ5Z.js +15 -0
  159. package/dist/{store-client-SLFAAKSV.js.map → store-client-GJT2XQ5Z.js.map} +1 -1
  160. package/dist/tui/index.js +11 -11
  161. package/dist/types/src/transport.d.ts +12 -0
  162. package/dist/types/src/transport.d.ts.map +1 -1
  163. package/dist/workspace-plugin/adapters/mcp.js +1 -1
  164. package/dist/workspace-plugin/adapters/omp.js +3 -3
  165. package/dist/workspace-plugin/index.js +1 -1
  166. package/package.json +1 -1
  167. package/dist/asset-feeds-O6R6J4RI.js +0 -128
  168. package/dist/asset-feeds-O6R6J4RI.js.map +0 -1
  169. package/dist/chunk-2Y4HIITD.js.map +0 -1
  170. package/dist/chunk-6IDBWPK2.js.map +0 -1
  171. package/dist/chunk-C5RYGUF4.js.map +0 -1
  172. package/dist/chunk-EBSIZYM6.js.map +0 -1
  173. package/dist/chunk-JXHULIOA.js.map +0 -1
  174. package/dist/chunk-LV73DB4G.js.map +0 -1
  175. package/dist/chunk-O4JH3KUE.js.map +0 -1
  176. package/dist/chunk-PBO6SCT2.js.map +0 -1
  177. package/dist/chunk-X46FY45B.js.map +0 -1
  178. package/dist/chunk-ZJ3VRPOI.js.map +0 -1
  179. package/dist/open-library-TXJMFG34.js +0 -23
  180. package/dist/paths-OMVC4UWY.js +0 -15
  181. package/dist/store-client-SLFAAKSV.js +0 -15
package/dist/cli/index.js CHANGED
@@ -1,19 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import { openCatalogSource, openLibraryManager, createFullRegistry, openLibrary } from '../chunk-HNORRENR.js';
2
+ import { AI_RESOURCES, aiResourceRoots } from '../chunk-LKOMH3QC.js';
3
+ import { makeSourceCommand, syncProjectSources } from '../chunk-BXC5547R.js';
4
+ import { openCatalogSource, openLibraryManager, createFullRegistry, openLibrary } from '../chunk-WD2NRI3T.js';
3
5
  import { logErr, S, logOk, logInfo, colorRef, logWarn, kindColorPad, kindColor, formatRelativeTime, fitWidth } from '../chunk-GYPA2Q7F.js';
4
- import { getStoreConfig, storeFetch, saveStoreTokens, clearStoreTokens, isStoreAuthenticated } from '../chunk-4IGNQG6E.js';
5
- import { AI_RESOURCES, aiResourceRoots } from '../chunk-33WB7VFV.js';
6
+ import { getStoreConfig, storeFetch, saveStoreTokens, clearStoreTokens, isStoreAuthenticated } from '../chunk-2ZK7M2SG.js';
6
7
  import { LocalSecretsProvider } from '../chunk-6BEGKO2Q.js';
7
- import { resolveLibraryDir, LocalCatalogSource } from '../chunk-P4BDTJMI.js';
8
- import '../chunk-JXHULIOA.js';
8
+ import { resolveLibraryDir } from '../chunk-2D5YE3FL.js';
9
+ import '../chunk-UTHEIPEY.js';
9
10
  import '../chunk-JXT46QFC.js';
10
11
  import '../chunk-UKJMQZV6.js';
11
12
  import '../chunk-R7FOF242.js';
12
- import { runFlow, resumeFlow } from '../chunk-H74X4TSR.js';
13
+ import { runFlow, resumeFlow } from '../chunk-VGT2R662.js';
13
14
  import '../chunk-4R6DROGN.js';
14
15
  import { validateFlowVersions, parseSkillFrontmatter } from '../chunk-GXC3TYMQ.js';
15
- import { runAgentChat, loadSessionById, loadSession, listSessions, setCurrentSession, deleteSession, clearSession, loadAgentManifest, compileComposition, MarkdownStreamer, resolveMixin } from '../chunk-LV73DB4G.js';
16
- import { buildClaudePluginFiles } from '../chunk-SXT6BAQJ.js';
16
+ import { runAgentChat, loadSessionById, loadSession, listSessions, setCurrentSession, deleteSession, clearSession, loadAgentManifest, compileComposition, MarkdownStreamer, resolveMixin } from '../chunk-V4BGVOJC.js';
17
+ import { buildClaudePluginFiles } from '../chunk-3IHBGDCG.js';
17
18
  import '../chunk-X5YPJV4N.js';
18
19
  import '../chunk-O7SG5PC2.js';
19
20
  import '../chunk-7QBNJTTQ.js';
@@ -28,25 +29,25 @@ import '../chunk-BMQKMOQY.js';
28
29
  import '../chunk-GAH4YRCI.js';
29
30
  import '../chunk-N5AE5SOC.js';
30
31
  import '../chunk-LV2HPH3C.js';
31
- import '../chunk-EBSIZYM6.js';
32
+ import '../chunk-QLLFCQU5.js';
32
33
  import '../chunk-OVRSNIKJ.js';
33
- import '../chunk-ZJ3VRPOI.js';
34
+ import '../chunk-VE2UIJ7Y.js';
34
35
  import '../chunk-RAIRWFAN.js';
35
36
  import '../chunk-6E6PKKAD.js';
36
- import { loadFlow, loadAllFlows } from '../chunk-X46FY45B.js';
37
+ import { loadFlow, loadAllFlows } from '../chunk-SKADPHAM.js';
37
38
  import '../chunk-DEYYRFQU.js';
38
39
  import '../chunk-IFGTRL2I.js';
39
- import { AssetManager, NON_DEPLOYABLE_KINDS, MissingManifestError } from '../chunk-C5RYGUF4.js';
40
- import '../chunk-2Y4HIITD.js';
41
- import { readLock, resolveSettings, globalSettingsPath, projectSettingsPath, loadSettings, saveSettings, portableSpawn, portableSpawnSync, WorkspaceYamlEditor, checkAgentSkillConformance } from '../chunk-SDAKQCOB.js';
40
+ import { AssetManager, NON_DEPLOYABLE_KINDS, MissingManifestError } from '../chunk-TCNYSHPM.js';
41
+ import '../chunk-7ZWLZ5H3.js';
42
+ import { readLock, resolveSettings, globalSettingsPath, projectSettingsPath, loadSettings, saveSettings, portableSpawn, portableSpawnSync, WorkspaceYamlEditor, checkAgentSkillConformance } from '../chunk-3KPNDTMR.js';
42
43
  import { assetSearchDirs, detectDomains } from '../chunk-ZAP2BDHG.js';
43
- import '../chunk-WBF7JLTY.js';
44
- import { globalCacheRoot, sourceCacheKey, getGlobalCacheDir, resolveAgentDir, sourceShortName, findWorkspaceRoot, workspaceConfigFilename, resolveSkWorkspaceConfig } from '../chunk-PBO6SCT2.js';
44
+ import '../chunk-W53D5Q52.js';
45
+ import { globalCacheRoot, sourceCacheKey, getGlobalCacheDir, resolveAgentDir, resolveSkWorkspaceConfig } from '../chunk-NXC6IEL4.js';
45
46
  import { SKAILE_MANIFEST_FILENAME, decodeSkaileManifest, loadSkPublishManifest, saveSkPublishManifest } from '../chunk-DZCK7GRW.js';
46
- import { parseFrontmatter } from '../chunk-6IDBWPK2.js';
47
+ import { parseFrontmatter } from '../chunk-BQCGFPHW.js';
47
48
  import { parseAssetRef, ASSET_KINDS, isValidAssetName, slugifyAssetName, ASSET_NAME_HINT, assetRefToStr } from '../chunk-GEEW6SY2.js';
48
- import '../chunk-XGCXFX7X.js';
49
- import { SUPPORTED_DRIVER_TARGETS } from '../chunk-O4JH3KUE.js';
49
+ import '../chunk-F3HNW354.js';
50
+ import { SUPPORTED_DRIVER_TARGETS } from '../chunk-MENCWIHW.js';
50
51
  import { DRIVER_DEFAULTS } from '../chunk-K5GBV4SA.js';
51
52
  import { openSqlite } from '../chunk-JB35MVBN.js';
52
53
  import '../chunk-KTBKW2FI.js';
@@ -70,7 +71,7 @@ import * as readline from 'readline';
70
71
  import { createInterface } from 'readline';
71
72
  import fs6 from 'fs/promises';
72
73
  import { parse as parse$1, stringify as stringify$1 } from 'smol-toml';
73
- import { execSync, spawnSync } from 'child_process';
74
+ import { execSync } from 'child_process';
74
75
  import { createTRPCUntypedClient, httpLink, httpSubscriptionLink } from '@trpc/client';
75
76
 
76
77
  async function runSearch(kind, query, opts) {
@@ -80,16 +81,24 @@ async function runSearch(kind, query, opts) {
80
81
  }
81
82
  const showLocal = !opts.store;
82
83
  const showStore = !opts.local;
84
+ const { resolveConfig } = await import('../library/index.js');
85
+ const airGapped = resolveConfig({ projectDir: path14__default.resolve(opts.projectDir) }).catalog.cache_ttl === 0;
83
86
  if (showLocal) {
84
- searchLocal(query, kind, opts, showStore);
87
+ await searchLocal(query, kind, opts, showStore, airGapped);
85
88
  }
86
89
  if (showStore) {
87
- await searchStore(query, kind, opts);
90
+ await searchStore(query, kind, opts, airGapped);
88
91
  }
89
92
  }
90
- function searchLocal(query, kind, opts, showStore) {
91
- const am2 = new AssetManager({ projectDir: path14__default.resolve(opts.projectDir) });
92
- const entries = am2.search(query, kind);
93
+ async function searchLocal(query, kind, opts, showStore, airGapped) {
94
+ const projectDir = path14__default.resolve(opts.projectDir);
95
+ let entries = new AssetManager({ projectDir }).search(query, kind);
96
+ if (entries.length === 0 && !query && !kind && !airGapped) {
97
+ const { autoSyncForSearch } = await import('../source-FGOMQMZT.js');
98
+ if (await autoSyncForSearch(projectDir)) {
99
+ entries = new AssetManager({ projectDir }).search(query, kind);
100
+ }
101
+ }
93
102
  if (entries.length === 0) {
94
103
  if (!showStore) {
95
104
  logInfo(
@@ -126,9 +135,19 @@ function printLocalEntries(entries) {
126
135
  ${S.dim(`${entries.length} total`)}
127
136
  `);
128
137
  }
129
- async function searchStore(query, kind, opts) {
138
+ async function searchStore(query, kind, opts, airGapped) {
139
+ if (airGapped) {
140
+ if (opts.store) {
141
+ logErr(
142
+ "air-gapped (catalog.cache_ttl: 0) \u2014 store search is disabled. Set catalog.cache_ttl > 0 to query the store."
143
+ );
144
+ process.exit(1);
145
+ }
146
+ logInfo("air-gapped (cache_ttl: 0) \u2014 local sources only");
147
+ return;
148
+ }
130
149
  try {
131
- const { getStoreConfig: getStoreConfig2, storeFetch: storeFetch2 } = await import('../store-client-SLFAAKSV.js');
150
+ const { getStoreConfig: getStoreConfig2, storeFetch: storeFetch2 } = await import('../store-client-GJT2XQ5Z.js');
132
151
  const config = getStoreConfig2();
133
152
  const params = {};
134
153
  if (query) params.q = query;
@@ -149,6 +168,8 @@ async function searchStore(query, kind, opts) {
149
168
  } catch (e) {
150
169
  if (opts.store) {
151
170
  logErr(`Store search failed: ${e.message}`);
171
+ } else {
172
+ logInfo("store unavailable \u2014 showing local results only (run with --store to see the error)");
152
173
  }
153
174
  }
154
175
  }
@@ -157,14 +178,32 @@ function makeSearchCommand() {
157
178
  await runSearch(kind, query, opts);
158
179
  });
159
180
  }
181
+ function parseBackendFilter(raw) {
182
+ if (!raw) return void 0;
183
+ const ids = raw.split(",").map((s) => s.trim()).filter(Boolean);
184
+ const out = [];
185
+ for (const id of ids) {
186
+ if (!SUPPORTED_DRIVER_TARGETS.includes(id)) {
187
+ logErr(`unknown backend '${id}' \u2014 expected one of: ${SUPPORTED_DRIVER_TARGETS.join(", ")}`);
188
+ process.exit(1);
189
+ }
190
+ out.push(id);
191
+ }
192
+ return out.length > 0 ? out : void 0;
193
+ }
160
194
  function makeAddCommand() {
161
- return new Command("add").description("Add to skaile.yaml + deploy").argument("<ref>", "Asset reference (kind:@<publisher>/name#<version>)").option("--project-dir <path>", "Project directory", process.cwd()).option("--global", "Deploy to global dir (~/.claude)").option("--target <agent>", "Driver target", "claude-code").option(
195
+ return new Command("add").description("Add to skaile.yaml + deploy").argument("<ref>", "Asset reference (kind:@<publisher>/name#<version>)").option("--project-dir <path>", "Project directory", process.cwd()).option("--global", "Record in ~/.skaile/skaile.yaml + deploy to every global backend").option(
196
+ "--backend <ids>",
197
+ "Comma-separated global backends to deploy into (default: all resolved); implies --global"
198
+ ).option("--target <agent>", "Driver target", "claude-code").option(
162
199
  "--link",
163
200
  "symlink local-path assets instead of copying (dev loop; machine-local, not for committing)"
164
201
  ).action(async (ref, opts) => {
202
+ const backends = parseBackendFilter(opts.backend);
165
203
  const am2 = new AssetManager({
166
204
  projectDir: path14__default.resolve(opts.projectDir),
167
- global: opts.global,
205
+ global: opts.global || backends !== void 0,
206
+ backends,
168
207
  driverTarget: opts.target
169
208
  });
170
209
  const spinner5 = p6.spinner();
@@ -188,10 +227,15 @@ function makeAddCommand() {
188
227
  });
189
228
  }
190
229
  function makeRemoveCommand() {
191
- return new Command("remove").description("Remove from skaile.yaml + undeploy").argument("<ref>", "Asset reference (kind:name)").option("--project-dir <path>", "Project directory", process.cwd()).option("--global", "Remove from global dir").option("--target <agent>", "Driver target", "claude-code").action((ref, opts) => {
230
+ return new Command("remove").description("Remove from skaile.yaml + undeploy").argument("<ref>", "Asset reference (kind:name)").option("--project-dir <path>", "Project directory", process.cwd()).option("--global", "Remove from ~/.skaile/skaile.yaml + undeploy from every global backend").option(
231
+ "--backend <ids>",
232
+ "Comma-separated global backends to undeploy from (default: all resolved); implies --global"
233
+ ).option("--target <agent>", "Driver target", "claude-code").action((ref, opts) => {
234
+ const backends = parseBackendFilter(opts.backend);
192
235
  const am2 = new AssetManager({
193
236
  projectDir: path14__default.resolve(opts.projectDir),
194
- global: opts.global,
237
+ global: opts.global || backends !== void 0,
238
+ backends,
195
239
  driverTarget: opts.target
196
240
  });
197
241
  const ok = am2.remove(ref);
@@ -206,12 +250,19 @@ function stateLabel(s) {
206
250
  return S.dim(s);
207
251
  }
208
252
  function makeListCommand() {
209
- return new Command("list").description("List declared dependencies from skaile.yaml with state").argument("[kind]", "Filter by asset kind").option("--project-dir <path>", "Project directory", process.cwd()).option("-g, --global", "Inspect the global deployment dir for state").option("--json", "Output as JSON").addOption(
253
+ return new Command("list").description("List declared dependencies from skaile.yaml with state").argument("[kind]", "Filter by asset kind").option("--project-dir <path>", "Project directory", process.cwd()).option(
254
+ "-g, --global",
255
+ "Read the global SSOT (~/.skaile/skaile.yaml); report state per backend"
256
+ ).option("--backend <ids>", "Comma-separated global backends to report (implies --global)").option("--json", "Output as JSON").addOption(
210
257
  new Option("--target <agent>", "Driver target").default("claude-code").choices(SUPPORTED_DRIVER_TARGETS)
211
258
  ).action((kind, opts) => {
259
+ const backends = parseBackendFilter(opts.backend);
260
+ if (opts.global || backends) {
261
+ runGlobalList(kind, opts, backends);
262
+ return;
263
+ }
212
264
  const am2 = new AssetManager({
213
265
  projectDir: path14__default.resolve(opts.projectDir),
214
- global: opts.global,
215
266
  driverTarget: opts.target
216
267
  });
217
268
  let declared;
@@ -254,6 +305,47 @@ function makeListCommand() {
254
305
  console.log();
255
306
  });
256
307
  }
308
+ function runGlobalList(kind, opts, backends) {
309
+ const am2 = new AssetManager({
310
+ projectDir: path14__default.resolve(opts.projectDir),
311
+ global: true,
312
+ backends
313
+ });
314
+ let rows;
315
+ try {
316
+ rows = am2.globalState({ kind });
317
+ } catch (err) {
318
+ logErr(err instanceof Error ? err.message : String(err));
319
+ process.exit(1);
320
+ }
321
+ if (opts.json) {
322
+ console.log(JSON.stringify(rows, null, 2));
323
+ return;
324
+ }
325
+ if (rows.length === 0) {
326
+ logInfo("No global dependencies declared in ~/.skaile/skaile.yaml.");
327
+ return;
328
+ }
329
+ const kindW = Math.max(4, ...rows.map((e) => e.kind.length));
330
+ const nameW = Math.max(4, ...rows.map((e) => e.name.length));
331
+ const beW = Math.max(7, ...rows.map((e) => e.backend.length));
332
+ console.log();
333
+ console.log(
334
+ ` ${S.dim("KIND".padEnd(kindW))} ${S.dim("NAME".padEnd(nameW))} ${S.dim("BACKEND".padEnd(beW))} ${S.dim("STATE")}`
335
+ );
336
+ console.log(` ${S.rule(kindW + nameW + beW + 6 + "installed".length)}`);
337
+ for (const e of rows) {
338
+ console.log(
339
+ ` ${kindColorPad(e.kind, kindW)} ${e.name.padEnd(nameW)} ${e.backend.padEnd(beW)} ${stateLabel(e.state)}`
340
+ );
341
+ }
342
+ console.log(`
343
+ ${S.dim(`${rows.length} entries`)}`);
344
+ const drift = rows.filter((e) => e.state === "drift").length;
345
+ if (drift > 0)
346
+ logInfo(`${drift} with drift \u2014 run ${S.cmd("skaile install --global")} to materialize.`);
347
+ console.log();
348
+ }
257
349
  function makeInfoCommand() {
258
350
  return new Command("info").description("Show asset metadata, deps, deploy status").argument("<ref>", "Asset reference (kind:name)").option("--project-dir <path>", "Project directory", process.cwd()).action((ref, opts) => {
259
351
  const am2 = new AssetManager({ projectDir: path14__default.resolve(opts.projectDir) });
@@ -515,9 +607,9 @@ function makeAssetCommand() {
515
607
  });
516
608
  logOk(`${up.added ? "Added" : "Updated"} manifest entry skill:${norm.name}`);
517
609
  if (target.backend === "git" && opts.commit) {
518
- const { spawnSync: spawnSync2 } = await import('child_process');
519
- spawnSync2("git", ["add", "--", destDir], { cwd: target.path, stdio: "inherit" });
520
- const r = spawnSync2("git", ["commit", "-m", `skaile: import ${norm.name}`], {
610
+ const { spawnSync } = await import('child_process');
611
+ spawnSync("git", ["add", "--", destDir], { cwd: target.path, stdio: "inherit" });
612
+ const r = spawnSync("git", ["commit", "-m", `skaile: import ${norm.name}`], {
521
613
  cwd: target.path,
522
614
  stdio: "inherit"
523
615
  });
@@ -585,9 +677,9 @@ function makeAssetCommand() {
585
677
  break;
586
678
  }
587
679
  if (target.backend === "git" && opts.commit) {
588
- const { spawnSync: spawnSync2 } = await import('child_process');
589
- spawnSync2("git", ["add", "--", destPath], { cwd: target.path, stdio: "inherit" });
590
- const r = spawnSync2("git", ["commit", "-m", `skaile: migrate ${basename2}`], {
680
+ const { spawnSync } = await import('child_process');
681
+ spawnSync("git", ["add", "--", destPath], { cwd: target.path, stdio: "inherit" });
682
+ const r = spawnSync("git", ["commit", "-m", `skaile: migrate ${basename2}`], {
591
683
  cwd: target.path,
592
684
  stdio: "inherit"
593
685
  });
@@ -832,7 +924,7 @@ function makeCatalogCommand() {
832
924
  return catalog;
833
925
  }
834
926
  async function runLocalCatalogTest(projectDir) {
835
- const { resolveCatalogSource } = await import('../open-library-TXJMFG34.js');
927
+ const { resolveCatalogSource } = await import('../open-library-ERKRGH4B.js');
836
928
  let resolved;
837
929
  try {
838
930
  resolved = await resolveCatalogSource({ projectDir });
@@ -1083,13 +1175,13 @@ function collectWorkspaceDefaults(projectDir) {
1083
1175
  const wsDefaults = {};
1084
1176
  try {
1085
1177
  const ws = resolveSkWorkspaceConfig(projectDir);
1086
- const p9 = ws.agent_config?.default ?? {};
1087
- if (p9.driver) wsDefaults.driver = p9.driver;
1088
- if (p9.provider) wsDefaults.provider = p9.provider;
1089
- if (p9.model) wsDefaults.model = p9.model;
1090
- if (p9.skills_dir) wsDefaults.skills_dir = p9.skills_dir;
1091
- if (p9.agents_dir) wsDefaults.agents_dir = p9.agents_dir;
1092
- if (p9.prompts_dir) wsDefaults.prompts_dir = p9.prompts_dir;
1178
+ const p8 = ws.agent_config?.default ?? {};
1179
+ if (p8.driver) wsDefaults.driver = p8.driver;
1180
+ if (p8.provider) wsDefaults.provider = p8.provider;
1181
+ if (p8.model) wsDefaults.model = p8.model;
1182
+ if (p8.skills_dir) wsDefaults.skills_dir = p8.skills_dir;
1183
+ if (p8.agents_dir) wsDefaults.agents_dir = p8.agents_dir;
1184
+ if (p8.prompts_dir) wsDefaults.prompts_dir = p8.prompts_dir;
1093
1185
  } catch {
1094
1186
  }
1095
1187
  return wsDefaults;
@@ -1822,7 +1914,7 @@ async function ensureTargetsRegistered(projectDir) {
1822
1914
  const { resolveSkWorkspaceConfig: resolveSkWorkspaceConfig2 } = await import('../core/index.js');
1823
1915
  const config = resolveSkWorkspaceConfig2(projectDir);
1824
1916
  if (config.plugins && config.plugins.length > 0) {
1825
- const { ensurePluginsLoaded } = await import('../plugin-store-JWJHIPUT.js');
1917
+ const { ensurePluginsLoaded } = await import('../plugin-store-SGNJTBYS.js');
1826
1918
  const result = await ensurePluginsLoaded(projectDir, config.plugins, pluginRegistry);
1827
1919
  for (const f of result.failed) logWarn(f.error);
1828
1920
  }
@@ -2031,10 +2123,10 @@ var MCP_SERVER_ENTRY = {
2031
2123
  function pluginDir(projectDir) {
2032
2124
  return path14__default.join(projectDir, ".claude", "plugins", "skaile");
2033
2125
  }
2034
- async function readJsonStrict(p9) {
2126
+ async function readJsonStrict(p8) {
2035
2127
  let raw;
2036
2128
  try {
2037
- raw = await fs6.readFile(p9, "utf-8");
2129
+ raw = await fs6.readFile(p8, "utf-8");
2038
2130
  } catch {
2039
2131
  return null;
2040
2132
  }
@@ -2042,13 +2134,13 @@ async function readJsonStrict(p9) {
2042
2134
  return JSON.parse(raw);
2043
2135
  } catch (e) {
2044
2136
  throw new Error(
2045
- `cannot parse ${p9}: ${e instanceof Error ? e.message : String(e)}; please fix or remove`
2137
+ `cannot parse ${p8}: ${e instanceof Error ? e.message : String(e)}; please fix or remove`
2046
2138
  );
2047
2139
  }
2048
2140
  }
2049
- async function writeJson(p9, value) {
2050
- await fs6.mkdir(path14__default.dirname(p9), { recursive: true });
2051
- await fs6.writeFile(p9, `${JSON.stringify(value, null, 2)}
2141
+ async function writeJson(p8, value) {
2142
+ await fs6.mkdir(path14__default.dirname(p8), { recursive: true });
2143
+ await fs6.writeFile(p8, `${JSON.stringify(value, null, 2)}
2052
2144
  `);
2053
2145
  }
2054
2146
  async function copyDir(src, dest) {
@@ -2103,7 +2195,7 @@ async function uninstall(projectDir) {
2103
2195
  let changed = false;
2104
2196
  const settings = await readJsonStrict(settingsPath);
2105
2197
  if (settings && Array.isArray(settings.plugins)) {
2106
- const next = settings.plugins.filter((p9) => p9 !== dir);
2198
+ const next = settings.plugins.filter((p8) => p8 !== dir);
2107
2199
  if (next.length !== settings.plugins.length) {
2108
2200
  changed = true;
2109
2201
  const updated = { ...settings, plugins: next };
@@ -2150,7 +2242,7 @@ async function enable(projectDir) {
2150
2242
  const settings = await readJsonStrict(settingsPath) ?? {};
2151
2243
  if (detected.method === "plugin") {
2152
2244
  const disabled2 = settings.disabledPlugins ?? [];
2153
- const next2 = disabled2.filter((p9) => p9 !== detected.pluginPath);
2245
+ const next2 = disabled2.filter((p8) => p8 !== detected.pluginPath);
2154
2246
  if (next2.length === disabled2.length) return { changed: false };
2155
2247
  const updated2 = { ...settings, disabledPlugins: next2 };
2156
2248
  if (next2.length === 0) delete updated2.disabledPlugins;
@@ -2231,10 +2323,10 @@ function buildEntry(projectDir) {
2231
2323
  env: { SKAILE_PROJECT_DIR: projectDir }
2232
2324
  };
2233
2325
  }
2234
- async function readConfig(p9) {
2326
+ async function readConfig(p8) {
2235
2327
  let raw;
2236
2328
  try {
2237
- raw = await fs6.readFile(p9, "utf-8");
2329
+ raw = await fs6.readFile(p8, "utf-8");
2238
2330
  } catch {
2239
2331
  return null;
2240
2332
  }
@@ -2242,41 +2334,41 @@ async function readConfig(p9) {
2242
2334
  return parse$1(raw);
2243
2335
  } catch (e) {
2244
2336
  throw new Error(
2245
- `cannot parse ${p9}: ${e instanceof Error ? e.message : String(e)}; please fix or remove`
2337
+ `cannot parse ${p8}: ${e instanceof Error ? e.message : String(e)}; please fix or remove`
2246
2338
  );
2247
2339
  }
2248
2340
  }
2249
- async function writeConfig(p9, value) {
2250
- await fs6.mkdir(path14__default.dirname(p9), { recursive: true });
2341
+ async function writeConfig(p8, value) {
2342
+ await fs6.mkdir(path14__default.dirname(p8), { recursive: true });
2251
2343
  const cleaned = {};
2252
2344
  for (const [k, v] of Object.entries(value)) {
2253
2345
  if (v === void 0) continue;
2254
2346
  cleaned[k] = v;
2255
2347
  }
2256
- await fs6.writeFile(p9, stringify$1(cleaned));
2348
+ await fs6.writeFile(p8, stringify$1(cleaned));
2257
2349
  }
2258
2350
  function configPath(projectDir) {
2259
2351
  return path14__default.join(projectDir, ".codex", "config.toml");
2260
2352
  }
2261
2353
  async function install2(projectDir) {
2262
- const p9 = configPath(projectDir);
2263
- const cfg = await readConfig(p9) ?? {};
2354
+ const p8 = configPath(projectDir);
2355
+ const cfg = await readConfig(p8) ?? {};
2264
2356
  const next = buildEntry(projectDir);
2265
2357
  const servers = { ...cfg.mcp_servers ?? {} };
2266
2358
  const previous = servers[SKAILE_MCP_NAME2];
2267
2359
  const matches = previous && JSON.stringify(previous) === JSON.stringify(next);
2268
2360
  let warning;
2269
2361
  if (previous && !matches) {
2270
- warning = `overwrote existing ${SKAILE_MCP_NAME2} entry in ${path14__default.relative(projectDir, p9)}`;
2362
+ warning = `overwrote existing ${SKAILE_MCP_NAME2} entry in ${path14__default.relative(projectDir, p8)}`;
2271
2363
  }
2272
2364
  if (matches) return { changed: false, method: "toml" };
2273
2365
  servers[SKAILE_MCP_NAME2] = next;
2274
- await writeConfig(p9, { ...cfg, mcp_servers: servers });
2366
+ await writeConfig(p8, { ...cfg, mcp_servers: servers });
2275
2367
  return { changed: true, method: "toml", warning };
2276
2368
  }
2277
2369
  async function uninstall2(projectDir) {
2278
- const p9 = configPath(projectDir);
2279
- const cfg = await readConfig(p9);
2370
+ const p8 = configPath(projectDir);
2371
+ const cfg = await readConfig(p8);
2280
2372
  if (!cfg?.mcp_servers || !(SKAILE_MCP_NAME2 in cfg.mcp_servers)) {
2281
2373
  return { changed: false };
2282
2374
  }
@@ -2284,15 +2376,15 @@ async function uninstall2(projectDir) {
2284
2376
  const updated = { ...cfg, mcp_servers: rest };
2285
2377
  if (Object.keys(rest).length === 0) delete updated.mcp_servers;
2286
2378
  if (Object.keys(updated).length === 0) {
2287
- await fs6.unlink(p9);
2379
+ await fs6.unlink(p8);
2288
2380
  } else {
2289
- await writeConfig(p9, updated);
2381
+ await writeConfig(p8, updated);
2290
2382
  }
2291
2383
  return { changed: true };
2292
2384
  }
2293
2385
  async function status2(projectDir) {
2294
- const p9 = configPath(projectDir);
2295
- const cfg = await readConfig(p9);
2386
+ const p8 = configPath(projectDir);
2387
+ const cfg = await readConfig(p8);
2296
2388
  const installed = !!(cfg?.mcp_servers && SKAILE_MCP_NAME2 in cfg.mcp_servers);
2297
2389
  if (!installed) {
2298
2390
  return {
@@ -2308,7 +2400,7 @@ async function status2(projectDir) {
2308
2400
  installed: "yes",
2309
2401
  enabled: "n/a",
2310
2402
  method: "mcp",
2311
- location: path14__default.relative(projectDir, p9)
2403
+ location: path14__default.relative(projectDir, p8)
2312
2404
  };
2313
2405
  }
2314
2406
  async function install3(projectDir) {
@@ -2525,7 +2617,7 @@ function printStatusTable(rows) {
2525
2617
  const widths = cols.map(
2526
2618
  (c) => Math.max(c.header.length, ...rows.map((r) => String(r[c.key] ?? "").length))
2527
2619
  );
2528
- const fmtRow = (parts) => parts.map((p9, i) => p9.padEnd(widths[i] ?? p9.length)).join(" ");
2620
+ const fmtRow = (parts) => parts.map((p8, i) => p8.padEnd(widths[i] ?? p8.length)).join(" ");
2529
2621
  console.log();
2530
2622
  console.log(` ${S.heading(fmtRow(cols.map((c) => c.header)))}`);
2531
2623
  for (const r of rows) {
@@ -2980,9 +3072,9 @@ function makeLibraryCommand() {
2980
3072
  logErr("`library commit` only applies to git-backed libraries.");
2981
3073
  process.exit(1);
2982
3074
  }
2983
- const { spawnSync: spawnSync2 } = await import('child_process');
2984
- spawnSync2("git", ["add", "-A"], { cwd: lib.path, stdio: "inherit" });
2985
- const r = spawnSync2("git", ["commit", "-m", opts.message], {
3075
+ const { spawnSync } = await import('child_process');
3076
+ spawnSync("git", ["add", "-A"], { cwd: lib.path, stdio: "inherit" });
3077
+ const r = spawnSync("git", ["commit", "-m", opts.message], {
2986
3078
  cwd: lib.path,
2987
3079
  stdio: "inherit"
2988
3080
  });
@@ -3000,8 +3092,8 @@ function makeLibraryCommand() {
3000
3092
  const { manager, close } = await openLibraryManager();
3001
3093
  try {
3002
3094
  const lib = await manager.requireLibrary(name);
3003
- const { spawnSync: spawnSync2 } = await import('child_process');
3004
- const r = spawnSync2("git", args, { cwd: lib.path, stdio: "inherit" });
3095
+ const { spawnSync } = await import('child_process');
3096
+ const r = spawnSync("git", args, { cwd: lib.path, stdio: "inherit" });
3005
3097
  process.exit(r.status ?? 0);
3006
3098
  } finally {
3007
3099
  close();
@@ -3264,6 +3356,8 @@ var ORIGIN_META = {
3264
3356
  var ORIGIN_ORDER = ["library", "source", "factory", "store"];
3265
3357
  var srcKey = (origin, publisher) => `${origin}:${publisher}`;
3266
3358
  var domainKey = (origin, publisher, slug) => `${origin}:${publisher}:dom:${slug}`;
3359
+ var repoKey = (org, repo) => `${org}/${repo}`;
3360
+ var srcDomainKey = (org, repo, slug) => `${org}/${repo}:dom:${slug}`;
3267
3361
  var NO_DOMAIN = "";
3268
3362
  var domainRegistry = /* @__PURE__ */ new Map();
3269
3363
  function relationBadge(relation) {
@@ -3292,12 +3386,20 @@ var state = {
3292
3386
  sourceRows: [],
3293
3387
  libraryRows: [],
3294
3388
  visibleRows: [],
3389
+ sourceTreeRows: [],
3390
+ visibleSourceRows: [],
3295
3391
  pendingAdds: /* @__PURE__ */ new Set(),
3296
3392
  pendingRemoves: /* @__PURE__ */ new Set(),
3297
3393
  collapsedOrigins: /* @__PURE__ */ new Set(),
3298
3394
  collapsedSources: /* @__PURE__ */ new Set(),
3299
3395
  collapsedDomains: /* @__PURE__ */ new Set(),
3300
3396
  expandedDomainInfo: /* @__PURE__ */ new Set(),
3397
+ seenDomains: /* @__PURE__ */ new Set(),
3398
+ collapsedSourceOrgs: /* @__PURE__ */ new Set(),
3399
+ collapsedSourceRepos: /* @__PURE__ */ new Set(),
3400
+ collapsedSourceDomains: /* @__PURE__ */ new Set(),
3401
+ expandedSourceDomainInfo: /* @__PURE__ */ new Set(),
3402
+ seenSourceDomains: /* @__PURE__ */ new Set(),
3301
3403
  seenOrigins: /* @__PURE__ */ new Set(),
3302
3404
  originFilter: null,
3303
3405
  message: "",
@@ -3311,13 +3413,13 @@ function buildVisibleRows(rows, collapsedOrigins, collapsedSources, collapsedDom
3311
3413
  if (row.type === "origin-header") return true;
3312
3414
  if (collapsedOrigins.has(origin)) return false;
3313
3415
  if (row.type === "publisher-header") return true;
3314
- if (collapsedSources.has(srcKey(origin, row.source ?? rowPublisher(row)))) return false;
3416
+ if (collapsedSources.has(srcKey(origin, rowGroup(row)))) return false;
3315
3417
  if (row.type === "domain-header") return true;
3316
3418
  if (row.type === "domain-info") {
3317
3419
  return !collapsedDomains.has(domainKey(origin, row.source, row.domainSlug ?? NO_DOMAIN));
3318
3420
  }
3319
3421
  if (row.underDomain) {
3320
- return !collapsedDomains.has(domainKey(origin, rowPublisher(row), rowDomain(row)));
3422
+ return !collapsedDomains.has(domainKey(origin, rowGroup(row), rowDomain(row)));
3321
3423
  }
3322
3424
  return true;
3323
3425
  });
@@ -3331,6 +3433,80 @@ function domainAssetRefs(rows, origin, publisher, slug) {
3331
3433
  function sourceAssetRefs(rows, origin, source) {
3332
3434
  return refsOf(rows.filter((r) => isAsset(r) && inSource(r, origin, source)));
3333
3435
  }
3436
+ function buildSourceTreeRows(assetRows, registered = []) {
3437
+ const byOrg = /* @__PURE__ */ new Map();
3438
+ const ensure = (org, repo) => {
3439
+ if (!byOrg.has(org)) byOrg.set(org, /* @__PURE__ */ new Map());
3440
+ const byRepo = byOrg.get(org);
3441
+ if (!byRepo.has(repo)) byRepo.set(repo, /* @__PURE__ */ new Map());
3442
+ return byRepo.get(repo);
3443
+ };
3444
+ for (const r of assetRows) {
3445
+ if (!isAsset(r) || rowOrigin(r) !== "source") continue;
3446
+ const org = rowOrg(r);
3447
+ const repo = rowGroup(r);
3448
+ const slug = rowDomain(r);
3449
+ const byDomain = ensure(org, repo);
3450
+ if (!byDomain.has(slug)) byDomain.set(slug, []);
3451
+ byDomain.get(slug).push(r);
3452
+ }
3453
+ for (const { org, repo } of registered) ensure(org, repo);
3454
+ if (byOrg.size === 0) return [];
3455
+ const rows = [{ type: "source-root" }];
3456
+ for (const org of [...byOrg.keys()].sort((a, b) => a.localeCompare(b))) {
3457
+ rows.push({ type: "org-header", origin: "source", org });
3458
+ const byRepo = byOrg.get(org);
3459
+ for (const repo of [...byRepo.keys()].sort((a, b) => a.localeCompare(b))) {
3460
+ rows.push({ type: "repo-header", origin: "source", org, source: repo });
3461
+ const byDomain = byRepo.get(repo);
3462
+ const loose = byDomain.get(NO_DOMAIN);
3463
+ if (loose) for (const a of loose) rows.push({ ...a, org });
3464
+ for (const slug of sortedDomainSlugs([...byDomain.keys()])) {
3465
+ const meta = domainRegistry.get(slug);
3466
+ rows.push({ type: "domain-header", origin: "source", org, source: repo, domainSlug: slug });
3467
+ if (meta?.description) {
3468
+ rows.push({ type: "domain-info", origin: "source", org, source: repo, domainSlug: slug });
3469
+ }
3470
+ for (const a of byDomain.get(slug)) rows.push({ ...a, org });
3471
+ }
3472
+ }
3473
+ }
3474
+ return rows;
3475
+ }
3476
+ function buildVisibleSourceRows(rows, collapsedOrgs, collapsedRepos, collapsedDomains) {
3477
+ return rows.filter((row) => {
3478
+ if (row.type === "source-root" || row.type === "org-header") return true;
3479
+ const org = row.org ?? UNKNOWN_ORG;
3480
+ if (collapsedOrgs.has(org)) return false;
3481
+ if (row.type === "repo-header") return true;
3482
+ if (collapsedRepos.has(repoKey(org, row.source))) return false;
3483
+ if (row.type === "domain-header") return true;
3484
+ if (row.type === "domain-info") {
3485
+ return !collapsedDomains.has(srcDomainKey(org, row.source, row.domainSlug ?? NO_DOMAIN));
3486
+ }
3487
+ if (row.underDomain) {
3488
+ return !collapsedDomains.has(srcDomainKey(org, rowGroup(row), rowDomain(row)));
3489
+ }
3490
+ return true;
3491
+ });
3492
+ }
3493
+ function sourceOrgRefs(rows, org) {
3494
+ return refsOf(rows.filter((r) => isAsset(r) && rowOrigin(r) === "source" && rowOrg(r) === org));
3495
+ }
3496
+ function sourceRepoRefs(rows, org, repo) {
3497
+ return refsOf(
3498
+ rows.filter(
3499
+ (r) => isAsset(r) && rowOrigin(r) === "source" && rowOrg(r) === org && rowGroup(r) === repo
3500
+ )
3501
+ );
3502
+ }
3503
+ function sourceDomainRefs(rows, org, repo, slug) {
3504
+ return refsOf(
3505
+ rows.filter(
3506
+ (r) => isAsset(r) && rowOrigin(r) === "source" && rowOrg(r) === org && rowGroup(r) === repo && rowDomain(r) === slug
3507
+ )
3508
+ );
3509
+ }
3334
3510
  function isAsset(r) {
3335
3511
  return r.type === "asset";
3336
3512
  }
@@ -3340,14 +3516,24 @@ function rowOrigin(r) {
3340
3516
  function rowPublisher(r) {
3341
3517
  return r.entry?.publisher || "other";
3342
3518
  }
3519
+ function rowGroup(r) {
3520
+ return r.source ?? rowPublisher(r);
3521
+ }
3343
3522
  function rowDomain(r) {
3344
3523
  return r.entry?.domainSlug || NO_DOMAIN;
3345
3524
  }
3525
+ var UNKNOWN_ORG = "(unknown)";
3526
+ function rowOrg(r) {
3527
+ if (r.org) return r.org;
3528
+ const repo = r.entry?.repo;
3529
+ if (repo?.includes("/")) return repo.slice(0, repo.indexOf("/"));
3530
+ return UNKNOWN_ORG;
3531
+ }
3346
3532
  function refsOf(rows) {
3347
3533
  return rows.map((r) => assetRefOf(r)).filter((ref) => ref !== null);
3348
3534
  }
3349
3535
  function inSource(r, origin, source) {
3350
- return rowOrigin(r) === origin && rowPublisher(r) === source;
3536
+ return rowOrigin(r) === origin && rowGroup(r) === source;
3351
3537
  }
3352
3538
  function inDomain(r, origin, publisher, slug) {
3353
3539
  if (!inSource(r, origin, publisher)) return false;
@@ -3408,7 +3594,7 @@ function actOnHeader(row, action3) {
3408
3594
  return row.type === "domain-header" || row.type === "domain-info";
3409
3595
  }
3410
3596
  async function loadAssets() {
3411
- const { gatherAssetFeeds } = await import('../asset-feeds-O6R6J4RI.js');
3597
+ const { gatherAssetFeeds } = await import('../asset-feeds-OEGJ6NOF.js');
3412
3598
  const { entries, domains, notes } = await gatherAssetFeeds(am, am.projectDir);
3413
3599
  if (notes.length > 0) {
3414
3600
  state.message = notes.map((n) => `[${n.feed}] ${n.message}`).join(" \u2022 ");
@@ -3420,11 +3606,60 @@ async function loadAssets() {
3420
3606
  state.assetRows = rows;
3421
3607
  reconcileCollapsedSets(rows);
3422
3608
  rebuildVisible();
3609
+ rebuildSourceTree();
3610
+ }
3611
+ function rebuildSourceTree() {
3612
+ const registered = state.sourceRows.map((s) => sourceOrgRepo(s)).filter((x) => x !== null);
3613
+ state.sourceTreeRows = buildSourceTreeRows(state.assetRows, registered);
3614
+ reconcileSourceCollapsedSets(state.sourceTreeRows);
3615
+ rebuildVisibleSources();
3616
+ }
3617
+ function sourceOrgRepo(s) {
3618
+ const parts = s.path.split(path14__default.sep).filter(Boolean);
3619
+ if (parts.length < 2) return null;
3620
+ return { org: parts[parts.length - 2], repo: parts[parts.length - 1] };
3621
+ }
3622
+ function reconcileSourceCollapsedSets(rows) {
3623
+ const orgKeys = new Set(rows.filter((r) => r.type === "org-header").map((r) => r.org));
3624
+ const repoKeys = new Set(
3625
+ rows.filter((r) => r.type === "repo-header").map((r) => repoKey(r.org, r.source))
3626
+ );
3627
+ const domainKeys = new Set(
3628
+ rows.filter((r) => r.type === "domain-header").map((r) => srcDomainKey(r.org, r.source, r.domainSlug ?? NO_DOMAIN))
3629
+ );
3630
+ for (const k of state.collapsedSourceOrgs)
3631
+ if (!orgKeys.has(k)) state.collapsedSourceOrgs.delete(k);
3632
+ for (const k of state.collapsedSourceRepos) {
3633
+ if (!repoKeys.has(k)) state.collapsedSourceRepos.delete(k);
3634
+ }
3635
+ for (const k of domainKeys) {
3636
+ if (!state.seenSourceDomains.has(k)) {
3637
+ state.collapsedSourceDomains.add(k);
3638
+ state.seenSourceDomains.add(k);
3639
+ }
3640
+ }
3641
+ for (const k of state.collapsedSourceDomains) {
3642
+ if (!domainKeys.has(k)) state.collapsedSourceDomains.delete(k);
3643
+ }
3644
+ for (const k of state.expandedSourceDomainInfo) {
3645
+ if (!domainKeys.has(k)) state.expandedSourceDomainInfo.delete(k);
3646
+ }
3647
+ for (const k of state.seenSourceDomains)
3648
+ if (!domainKeys.has(k)) state.seenSourceDomains.delete(k);
3649
+ }
3650
+ function rebuildVisibleSources() {
3651
+ state.visibleSourceRows = buildVisibleSourceRows(
3652
+ state.sourceTreeRows,
3653
+ state.collapsedSourceOrgs,
3654
+ state.collapsedSourceRepos,
3655
+ state.collapsedSourceDomains
3656
+ );
3657
+ clampCursor();
3423
3658
  }
3424
3659
  function groupEntriesByOriginPublisherDomain(entries) {
3425
3660
  const byOrigin = /* @__PURE__ */ new Map();
3426
- for (const { origin, entry } of entries) {
3427
- const publisher = entry.publisher || "other";
3661
+ for (const { origin, entry, group } of entries) {
3662
+ const publisher = group || entry.publisher || "other";
3428
3663
  const slug = entry.domainSlug || NO_DOMAIN;
3429
3664
  if (!byOrigin.has(origin)) byOrigin.set(origin, /* @__PURE__ */ new Map());
3430
3665
  const byPublisher = byOrigin.get(origin);
@@ -3447,29 +3682,40 @@ function buildAssetRowsFromGroups(byOrigin, installed) {
3447
3682
  const sortItems = (items) => items.sort(
3448
3683
  (a, b) => a.kind !== b.kind ? a.kind.localeCompare(b.kind) : a.name.localeCompare(b.name)
3449
3684
  );
3450
- const pushAsset = (origin, entry, underDomain, seq) => {
3685
+ const pushAsset = (origin, group, entry, underDomain, seq) => {
3451
3686
  const ref = `${entry.kind}:${entry.name}`;
3452
- rows.push({ type: "asset", origin, entry, installed: installed.has(ref), underDomain, seq });
3687
+ rows.push({
3688
+ type: "asset",
3689
+ origin,
3690
+ source: group,
3691
+ org: entry.repo?.includes("/") ? entry.repo.slice(0, entry.repo.indexOf("/")) : void 0,
3692
+ entry,
3693
+ installed: installed.has(ref),
3694
+ underDomain,
3695
+ seq
3696
+ });
3453
3697
  };
3454
3698
  for (const origin of ORIGIN_ORDER) {
3455
- const byPublisher = byOrigin.get(origin);
3456
- if (!byPublisher || byPublisher.size === 0) continue;
3699
+ const byGroup = byOrigin.get(origin);
3700
+ if (!byGroup || byGroup.size === 0) continue;
3457
3701
  rows.push({ type: "origin-header", origin });
3458
- for (const [publisher, byDomain] of [...byPublisher.entries()].sort(
3702
+ for (const [group, byDomain] of [...byGroup.entries()].sort(
3459
3703
  (a, b) => a[0].localeCompare(b[0])
3460
3704
  )) {
3461
- rows.push({ type: "publisher-header", origin, source: publisher });
3705
+ rows.push({ type: "publisher-header", origin, source: group });
3462
3706
  const loose = byDomain.get(NO_DOMAIN);
3463
- if (loose) for (const entry of sortItems(loose)) pushAsset(origin, entry, false);
3707
+ if (loose) for (const entry of sortItems(loose)) pushAsset(origin, group, entry, false);
3464
3708
  for (const slug of sortedDomainSlugs([...byDomain.keys()])) {
3465
3709
  const meta = domainRegistry.get(slug);
3466
- rows.push({ type: "domain-header", origin, source: publisher, domainSlug: slug });
3710
+ rows.push({ type: "domain-header", origin, source: group, domainSlug: slug });
3467
3711
  if (meta?.description) {
3468
- rows.push({ type: "domain-info", origin, source: publisher, domainSlug: slug });
3712
+ rows.push({ type: "domain-info", origin, source: group, domainSlug: slug });
3469
3713
  }
3470
3714
  const items = sortItems(byDomain.get(slug));
3471
3715
  const sequenced = meta?.relation === "sequence";
3472
- items.forEach((entry, i) => pushAsset(origin, entry, true, sequenced ? i + 1 : void 0));
3716
+ items.forEach(
3717
+ (entry, i) => pushAsset(origin, group, entry, true, sequenced ? i + 1 : void 0)
3718
+ );
3473
3719
  }
3474
3720
  }
3475
3721
  }
@@ -3496,17 +3742,26 @@ function reconcileCollapsedSets(rows) {
3496
3742
  const newDomainKeys = new Set(
3497
3743
  rows.filter((r) => r.type === "domain-header").map((r) => domainKey(rowOrigin(r), r.source, r.domainSlug ?? NO_DOMAIN))
3498
3744
  );
3745
+ for (const k of newDomainKeys) {
3746
+ if (!state.seenDomains.has(k)) {
3747
+ state.collapsedDomains.add(k);
3748
+ state.seenDomains.add(k);
3749
+ }
3750
+ }
3499
3751
  for (const d of state.collapsedDomains) {
3500
3752
  if (!newDomainKeys.has(d)) state.collapsedDomains.delete(d);
3501
3753
  }
3502
3754
  for (const d of state.expandedDomainInfo) {
3503
3755
  if (!newDomainKeys.has(d)) state.expandedDomainInfo.delete(d);
3504
3756
  }
3757
+ for (const d of state.seenDomains) {
3758
+ if (!newDomainKeys.has(d)) state.seenDomains.delete(d);
3759
+ }
3505
3760
  }
3506
3761
  async function loadSourcesAndLibraries() {
3507
3762
  try {
3508
3763
  const [{ openLibraryManager: openLibraryManager2 }, { skaileHomeDir }, { getGlobalCacheDir: getGlobalCacheDir2 }] = await Promise.all([
3509
- import('../open-library-TXJMFG34.js'),
3764
+ import('../open-library-ERKRGH4B.js'),
3510
3765
  import('../library/index.js'),
3511
3766
  import('../core/index.js')
3512
3767
  ]);
@@ -3542,6 +3797,7 @@ async function loadSourcesAndLibraries() {
3542
3797
  state.sourceRows = [];
3543
3798
  state.libraryRows = [];
3544
3799
  }
3800
+ rebuildSourceTree();
3545
3801
  }
3546
3802
  function rebuildVisible() {
3547
3803
  state.visibleRows = buildVisibleRows(
@@ -3577,11 +3833,11 @@ var TABS = {
3577
3833
  },
3578
3834
  sources: {
3579
3835
  label: "Sources",
3580
- rowCount: () => state.sourceRows.length,
3581
- selectableCount: () => state.sourceRows.length,
3836
+ rowCount: () => state.visibleSourceRows.length,
3837
+ selectableCount: () => state.sourceTreeRows.filter(isAsset).length,
3582
3838
  emptyMsg: " No sources registered. Run `skaile source add <git-url>` from the shell to track a github repo.",
3583
- renderRow: (i, sel) => renderSourceRow(state.sourceRows[i], sel),
3584
- footer: " \u2191\u2193 navigate Tab switch q quit (use `skaile source \u2026` from the shell for CRUD)"
3839
+ renderRow: (i, sel) => renderSourceTreeRow(state.visibleSourceRows[i], sel),
3840
+ footer: " \u2191\u2193/jk nav \u2190/\u2192 collapse/expand [space/+/-] select [a/\u21B5] apply [d] domain info [i] info Tab [q] quit"
3585
3841
  },
3586
3842
  libraries: {
3587
3843
  label: "Libraries",
@@ -3645,8 +3901,9 @@ function groupStatus(refs) {
3645
3901
  }
3646
3902
  function renderHeaderLine(opts) {
3647
3903
  const indicator = opts.collapsed ? "\u25B6" : "\u25BC";
3648
- const total = state.assetRows.filter(opts.match).length;
3649
- const installed = state.assetRows.filter((r) => opts.match(r) && r.installed).length;
3904
+ const countRows = opts.rows ?? state.assetRows;
3905
+ const total = countRows.filter(opts.match).length;
3906
+ const installed = countRows.filter((r) => opts.match(r) && r.installed).length;
3650
3907
  const [add, remove] = pendingCountsFor(opts.match);
3651
3908
  let count = String(installed);
3652
3909
  if (add > 0) count += pc5.green(`+${add}`);
@@ -3685,8 +3942,8 @@ function renderAssetRow(row, selected) {
3685
3942
  const slug = row.domainSlug ?? NO_DOMAIN;
3686
3943
  const meta = domainRegistry.get(slug);
3687
3944
  const title = meta?.title || slug || "(no domain)";
3688
- const badge2 = relationBadge(meta?.relation);
3689
- const label = `${pc5.magenta("\u25C8")} ${pc5.bold(title)}${badge2 ? ` ${badge2}` : ""}`;
3945
+ const badge = relationBadge(meta?.relation);
3946
+ const label = `${pc5.magenta("\u25C8")} ${pc5.bold(title)}${badge ? ` ${badge}` : ""}`;
3690
3947
  return renderHeaderLine({
3691
3948
  indent: " ".repeat(5),
3692
3949
  collapsed: state.collapsedDomains.has(domainKey(origin, row.source, slug)),
@@ -3700,15 +3957,18 @@ function renderAssetRow(row, selected) {
3700
3957
  const origin = rowOrigin(row);
3701
3958
  const slug = row.domainSlug ?? NO_DOMAIN;
3702
3959
  const key = domainKey(origin, row.source, slug);
3703
- const line2 = renderDomainInfoLine(
3960
+ const line = renderDomainInfoLine(
3704
3961
  domainRegistry.get(slug),
3705
3962
  origin,
3706
3963
  row.source,
3707
3964
  slug,
3708
3965
  state.expandedDomainInfo.has(key)
3709
3966
  );
3710
- return selected ? pc5.inverse(line2.padEnd(state.cols)) : line2;
3967
+ return selected ? pc5.inverse(line.padEnd(state.cols)) : line;
3711
3968
  }
3969
+ return renderAssetLeaf(row, selected);
3970
+ }
3971
+ function renderAssetLeaf(row, selected, looseIndent = 5) {
3712
3972
  const e = row.entry;
3713
3973
  const ref = assetRefOf(row) ?? "";
3714
3974
  let status4;
@@ -3730,10 +3990,86 @@ function renderAssetRow(row, selected) {
3730
3990
  const repoBadge = repoBare ? pc5.dim(` \xB7${repoBare}`) : "";
3731
3991
  const repoWidth = repoBare ? repoBare.length + 2 : 0;
3732
3992
  const desc = e.description?.slice(0, Math.max(0, state.cols - 56 - badgeWidth - hintWidth - repoWidth)) ?? "";
3733
- const indent = " ".repeat(row.underDomain ? 7 : 5);
3993
+ const indent = " ".repeat(looseIndent + (row.underDomain ? 2 : 0));
3734
3994
  const line = `${indent}${status4} ${hint}${kind} ${name} ${badge}${pc5.dim(desc)}${repoBadge}`;
3735
3995
  return selected ? pc5.inverse(line.padEnd(state.cols)) : line;
3736
3996
  }
3997
+ function renderSourceTreeRow(row, selected) {
3998
+ if (row.type === "source-root") {
3999
+ const total = state.sourceTreeRows.filter(isAsset).length;
4000
+ const meta = ORIGIN_META.source;
4001
+ const line = ` \u25BC ${meta.icon} ${pc5.bold(pc5.cyan(meta.label))} (${total} assets)`;
4002
+ return selected ? pc5.bgWhite(pc5.black(`${line} `.padEnd(state.cols))) : line;
4003
+ }
4004
+ if (row.type === "org-header") {
4005
+ const org = row.org;
4006
+ return renderHeaderLine({
4007
+ indent: " ".repeat(3),
4008
+ collapsed: state.collapsedSourceOrgs.has(org),
4009
+ match: (r) => isAsset(r) && rowOrigin(r) === "source" && rowOrg(r) === org,
4010
+ refs: sourceOrgRefs(state.sourceTreeRows, org),
4011
+ label: pc5.bold(pc5.cyan(org)),
4012
+ selected,
4013
+ rows: state.sourceTreeRows
4014
+ });
4015
+ }
4016
+ if (row.type === "repo-header") {
4017
+ const org = row.org;
4018
+ const repo = row.source;
4019
+ return renderHeaderLine({
4020
+ indent: " ".repeat(5),
4021
+ collapsed: state.collapsedSourceRepos.has(repoKey(org, repo)),
4022
+ match: (r) => isAsset(r) && rowOrigin(r) === "source" && rowOrg(r) === org && rowGroup(r) === repo,
4023
+ refs: sourceRepoRefs(state.sourceTreeRows, org, repo),
4024
+ label: pc5.bold(repo),
4025
+ selected,
4026
+ rows: state.sourceTreeRows
4027
+ });
4028
+ }
4029
+ if (row.type === "domain-header") {
4030
+ const org = row.org;
4031
+ const repo = row.source;
4032
+ const slug = row.domainSlug ?? NO_DOMAIN;
4033
+ const meta = domainRegistry.get(slug);
4034
+ const title = meta?.title || slug || "(no domain)";
4035
+ const badge = relationBadge(meta?.relation);
4036
+ const label = `${pc5.magenta("\u25C8")} ${pc5.bold(title)}${badge ? ` ${badge}` : ""}`;
4037
+ return renderHeaderLine({
4038
+ indent: " ".repeat(7),
4039
+ collapsed: state.collapsedSourceDomains.has(srcDomainKey(org, repo, slug)),
4040
+ match: (r) => isAsset(r) && rowOrigin(r) === "source" && rowOrg(r) === org && rowGroup(r) === repo && rowDomain(r) === slug,
4041
+ refs: sourceDomainRefs(state.sourceTreeRows, org, repo, slug),
4042
+ label,
4043
+ selected,
4044
+ rows: state.sourceTreeRows
4045
+ });
4046
+ }
4047
+ if (row.type === "domain-info") {
4048
+ const slug = row.domainSlug ?? NO_DOMAIN;
4049
+ const meta = domainRegistry.get(slug);
4050
+ const members = sourceDomainRefs(state.sourceTreeRows, row.org, row.source, slug).length;
4051
+ const key = srcDomainKey(row.org, row.source, slug);
4052
+ const line = renderSourceDomainInfoLine(meta, members, state.expandedSourceDomainInfo.has(key));
4053
+ return selected ? pc5.inverse(line.padEnd(state.cols)) : line;
4054
+ }
4055
+ return renderAssetLeaf(row, selected, 7);
4056
+ }
4057
+ function renderSourceDomainInfoLine(meta, members, expanded) {
4058
+ const indent = " ".repeat(9);
4059
+ const desc = meta?.description ?? "";
4060
+ if (!expanded) {
4061
+ const budget2 = Math.max(0, state.cols - indent.length - 12);
4062
+ const shown2 = desc.length > budget2 ? `${desc.slice(0, Math.max(0, budget2 - 1))}\u2026` : desc;
4063
+ return pc5.dim(`${indent}\u24D8 ${shown2} [d]`);
4064
+ }
4065
+ const parts = [desc, `${members} members`];
4066
+ if (meta?.recommendedRef) parts.push(`\u2605 ${meta.recommendedRef}`);
4067
+ if (meta?.sidecarSource) parts.push(`sidecar ${meta.sidecarSource}`);
4068
+ const full = parts.join(" \xB7 ");
4069
+ const budget = Math.max(0, state.cols - indent.length - 3);
4070
+ const shown = full.length > budget ? `${full.slice(0, Math.max(0, budget - 1))}\u2026` : full;
4071
+ return pc5.dim(`${indent}\u24D8 ${shown}`);
4072
+ }
3737
4073
  function renderDomainInfoLine(meta, origin, publisher, slug, expanded) {
3738
4074
  const indent = " ".repeat(7);
3739
4075
  const desc = meta?.description ?? "";
@@ -3751,10 +4087,6 @@ function renderDomainInfoLine(meta, origin, publisher, slug, expanded) {
3751
4087
  const shown = full.length > budget ? `${full.slice(0, Math.max(0, budget - 1))}\u2026` : full;
3752
4088
  return pc5.dim(`${indent}\u24D8 ${shown}`);
3753
4089
  }
3754
- function renderSourceRow(row, selected) {
3755
- const line = ` ${S.cmd(fitWidth(row.name, 20))} ${pc5.dim(row.ownership.padEnd(12))} ${pc5.dim(`${row.assetCount} assets`)} ${pc5.dim(row.path)}`;
3756
- return selected ? pc5.inverse(line.padEnd(state.cols)) : line;
3757
- }
3758
4090
  function renderLibraryRow(row, selected) {
3759
4091
  const mark = row.isDefault ? pc5.green("\u25B8") : " ";
3760
4092
  const line = `${mark} ${S.cmd(fitWidth(row.name, 16))} ${pc5.dim(row.backend.padEnd(7))} ${pc5.dim(row.ownership.padEnd(12))} ${pc5.dim(`${row.assetCount} assets`)}`;
@@ -3858,19 +4190,15 @@ async function syncAll() {
3858
4190
  "Sync is not yet driven from the manage TUI. Run `skaile source sync` (sources) or `skaile library sync <name>` (git-backed libraries) from the shell."
3859
4191
  );
3860
4192
  }
3861
- function sourceShellHint(action3) {
3862
- state.message = pc5.yellow(
3863
- `${action3} is a shell command. Run \`skaile source \u2026\` from the shell.`
3864
- );
3865
- }
3866
4193
  function libraryShellHint(action3) {
3867
4194
  state.message = pc5.yellow(
3868
4195
  `${action3} is a shell command. Run \`skaile library \u2026\` from the shell.`
3869
4196
  );
3870
4197
  }
3871
4198
  async function showInfo() {
3872
- if (state.tab !== "assets") return;
3873
- const row = state.visibleRows[state.cursor];
4199
+ const rows = state.tab === "sources" ? state.visibleSourceRows : state.visibleRows;
4200
+ if (state.tab !== "assets" && state.tab !== "sources") return;
4201
+ const row = rows[state.cursor];
3874
4202
  if (row?.type !== "asset") return;
3875
4203
  const e = row.entry;
3876
4204
  clearScreen();
@@ -3998,6 +4326,115 @@ function toggleDomainInfoUnderCursor() {
3998
4326
  else state.expandedDomainInfo.add(key);
3999
4327
  rebuildVisible();
4000
4328
  }
4329
+ function actOnSourceHeader(row, action3) {
4330
+ if (row.type === "org-header") {
4331
+ bulkSelection(sourceOrgRefs(state.sourceTreeRows, row.org), action3);
4332
+ return true;
4333
+ }
4334
+ if (row.type === "repo-header") {
4335
+ bulkSelection(sourceRepoRefs(state.sourceTreeRows, row.org, row.source), action3);
4336
+ return true;
4337
+ }
4338
+ return row.type === "source-root" || row.type === "domain-header" || row.type === "domain-info";
4339
+ }
4340
+ function selectSourceUnderCursor(action3) {
4341
+ const row = state.visibleSourceRows[state.cursor];
4342
+ if (!row) return;
4343
+ if (actOnSourceHeader(row, action3 === "toggle" ? "toggle" : action3)) return;
4344
+ const ref = assetRefOf(row);
4345
+ if (!ref) return;
4346
+ if (action3 === "toggle") {
4347
+ setSelection(ref, "toggle");
4348
+ } else if (action3 === "add") {
4349
+ setSelection(ref, state.pendingAdds.has(ref) ? "clear" : "add");
4350
+ } else {
4351
+ setSelection(ref, state.pendingRemoves.has(ref) ? "clear" : "remove");
4352
+ }
4353
+ }
4354
+ function expandSourceUnderCursor() {
4355
+ const row = state.visibleSourceRows[state.cursor];
4356
+ if (!row) return;
4357
+ if (row.type === "org-header" && state.collapsedSourceOrgs.has(row.org)) {
4358
+ state.collapsedSourceOrgs.delete(row.org);
4359
+ rebuildVisibleSources();
4360
+ } else if (row.type === "repo-header") {
4361
+ const k = repoKey(row.org, row.source);
4362
+ if (state.collapsedSourceRepos.has(k)) {
4363
+ state.collapsedSourceRepos.delete(k);
4364
+ rebuildVisibleSources();
4365
+ }
4366
+ } else if (row.type === "domain-header") {
4367
+ const k = srcDomainKey(row.org, row.source, row.domainSlug ?? NO_DOMAIN);
4368
+ if (state.collapsedSourceDomains.has(k)) {
4369
+ state.collapsedSourceDomains.delete(k);
4370
+ rebuildVisibleSources();
4371
+ }
4372
+ }
4373
+ }
4374
+ function collapseOrJumpSourceUnderCursor() {
4375
+ const row = state.visibleSourceRows[state.cursor];
4376
+ if (!row) return;
4377
+ if (row.type === "org-header") {
4378
+ if (!state.collapsedSourceOrgs.has(row.org)) {
4379
+ state.collapsedSourceOrgs.add(row.org);
4380
+ rebuildVisibleSources();
4381
+ }
4382
+ return;
4383
+ }
4384
+ if (row.type === "repo-header") {
4385
+ const k = repoKey(row.org, row.source);
4386
+ if (!state.collapsedSourceRepos.has(k)) {
4387
+ state.collapsedSourceRepos.add(k);
4388
+ rebuildVisibleSources();
4389
+ } else {
4390
+ const idx2 = findLastIndex(
4391
+ state.visibleSourceRows,
4392
+ state.cursor,
4393
+ (r) => r.type === "org-header"
4394
+ );
4395
+ if (idx2 >= 0) state.cursor = idx2;
4396
+ }
4397
+ return;
4398
+ }
4399
+ if (row.type === "domain-header") {
4400
+ const k = srcDomainKey(row.org, row.source, row.domainSlug ?? NO_DOMAIN);
4401
+ if (!state.collapsedSourceDomains.has(k)) {
4402
+ state.collapsedSourceDomains.add(k);
4403
+ rebuildVisibleSources();
4404
+ } else {
4405
+ const idx2 = findLastIndex(
4406
+ state.visibleSourceRows,
4407
+ state.cursor,
4408
+ (r) => r.type === "repo-header"
4409
+ );
4410
+ if (idx2 >= 0) state.cursor = idx2;
4411
+ }
4412
+ return;
4413
+ }
4414
+ const idx = findLastIndex(
4415
+ state.visibleSourceRows,
4416
+ state.cursor,
4417
+ (r) => r.type === "domain-header" || r.type === "repo-header" || r.type === "org-header"
4418
+ );
4419
+ if (idx >= 0) state.cursor = idx;
4420
+ }
4421
+ function toggleSourceDomainInfoUnderCursor() {
4422
+ const row = state.visibleSourceRows[state.cursor];
4423
+ if (!row) return;
4424
+ let key = null;
4425
+ if (row.type === "domain-header" || row.type === "domain-info") {
4426
+ key = srcDomainKey(row.org, row.source, row.domainSlug ?? NO_DOMAIN);
4427
+ } else if (row.type === "asset" && row.underDomain) {
4428
+ key = srcDomainKey(rowOrg(row), rowGroup(row), rowDomain(row));
4429
+ }
4430
+ if (!key) {
4431
+ state.message = pc5.dim("No domain here \u2014 [d] opens a domain's info region.");
4432
+ return;
4433
+ }
4434
+ if (state.expandedSourceDomainInfo.has(key)) state.expandedSourceDomainInfo.delete(key);
4435
+ else state.expandedSourceDomainInfo.add(key);
4436
+ rebuildVisibleSources();
4437
+ }
4001
4438
  function findLastIndex(arr, endExclusive, pred) {
4002
4439
  for (let i = Math.min(endExclusive, arr.length) - 1; i >= 0; i--) {
4003
4440
  if (pred(arr[i])) return i;
@@ -4025,9 +4462,12 @@ var KEYMAP = [
4025
4462
  { keys: [" "], run: () => void nextTab() },
4026
4463
  { keys: ["\x1B[A", "k"], run: () => void moveCursor(-1) },
4027
4464
  { keys: ["\x1B[B", "j"], run: () => void moveCursor(1) },
4028
- // Collapse / expand — assets tab only
4465
+ // Collapse / expand — assets tab
4029
4466
  { keys: ["\x1B[C"], when: TAB_ASSETS, run: expandUnderCursor },
4030
4467
  { keys: ["\x1B[D"], when: TAB_ASSETS, run: collapseOrJumpUnderCursor },
4468
+ // Collapse / expand — sources tab
4469
+ { keys: ["\x1B[C"], when: TAB_SOURCES, run: expandSourceUnderCursor },
4470
+ { keys: ["\x1B[D"], when: TAB_SOURCES, run: collapseOrJumpSourceUnderCursor },
4031
4471
  // + : assets → bulk-add or single-add toggle
4032
4472
  {
4033
4473
  keys: ["+"],
@@ -4041,10 +4481,13 @@ var KEYMAP = [
4041
4481
  }
4042
4482
  }
4043
4483
  },
4044
- // Sources tab: +/-/n/r/x map to shell hints in the minimal scaffold.
4045
- { keys: ["+", "n", "a"], when: TAB_SOURCES, run: () => sourceShellHint("Adding a source") },
4046
- { keys: ["-", "r", "x"], when: TAB_SOURCES, run: () => sourceShellHint("Removing a source") },
4047
- { keys: ["\r", "\n"], when: TAB_SOURCES, run: () => sourceShellHint("Source actions") },
4484
+ // Sources tab: + / - / space select (asset leaf or org/repo bulk header).
4485
+ { keys: ["+"], when: TAB_SOURCES, run: () => selectSourceUnderCursor("add") },
4486
+ { keys: ["-"], when: TAB_SOURCES, run: () => selectSourceUnderCursor("remove") },
4487
+ { keys: [" "], when: TAB_SOURCES, run: () => selectSourceUnderCursor("toggle") },
4488
+ { keys: ["a", "\r", "\n"], when: TAB_SOURCES, run: applyChanges },
4489
+ { keys: ["i"], when: TAB_SOURCES, run: showInfo },
4490
+ { keys: ["d"], when: TAB_SOURCES, run: toggleSourceDomainInfoUnderCursor },
4048
4491
  // Libraries tab: +/-/n/r/x/d/enter map to shell hints in the minimal scaffold.
4049
4492
  {
4050
4493
  keys: ["+", "n", "a"],
@@ -4154,8 +4597,8 @@ function makeManageCommand() {
4154
4597
  }
4155
4598
  function makeMcpServerCommand() {
4156
4599
  return new Command("mcp-server").description("Start skaile workspace MCP server (stdio transport)").option("--project-dir <path>", "Project directory (default: cwd)", process.cwd()).action(async (opts) => {
4157
- const path37 = await import('path');
4158
- const projectDir = path37.default.resolve(opts.projectDir);
4600
+ const path36 = await import('path');
4601
+ const projectDir = path36.default.resolve(opts.projectDir);
4159
4602
  const { LogStore, StdoutSink, registerLogStore } = await import('../core/logging.js');
4160
4603
  registerLogStore(
4161
4604
  new LogStore({
@@ -4371,7 +4814,7 @@ function action2(fn) {
4371
4814
  };
4372
4815
  }
4373
4816
  async function reconcileAndReport(projectDir, plugins) {
4374
- const { reconcilePlugins } = await import('../plugin-store-JWJHIPUT.js');
4817
+ const { reconcilePlugins } = await import('../plugin-store-SGNJTBYS.js');
4375
4818
  if (plugins.length === 0) {
4376
4819
  logInfo("No plugins declared \u2014 nothing to reconcile.");
4377
4820
  return;
@@ -4387,7 +4830,7 @@ async function reconcileAndReport(projectDir, plugins) {
4387
4830
  async function runInstall2(spec, opts) {
4388
4831
  const projectDir = path14__default.resolve(opts.projectDir);
4389
4832
  const { WorkspaceYamlEditor: WorkspaceYamlEditor2 } = await import('../core/index.js');
4390
- const { specName } = await import('../plugin-store-JWJHIPUT.js');
4833
+ const { specName } = await import('../plugin-store-SGNJTBYS.js');
4391
4834
  const yamlPath = path14__default.join(projectDir, "skaile.yaml");
4392
4835
  const editor = WorkspaceYamlEditor2.load(yamlPath);
4393
4836
  const replaced = editor.addPlugin(spec, specName);
@@ -4399,7 +4842,7 @@ async function runInstall2(spec, opts) {
4399
4842
  async function runRemove(name, opts) {
4400
4843
  const projectDir = path14__default.resolve(opts.projectDir);
4401
4844
  const { WorkspaceYamlEditor: WorkspaceYamlEditor2 } = await import('../core/index.js');
4402
- const { specName } = await import('../plugin-store-JWJHIPUT.js');
4845
+ const { specName } = await import('../plugin-store-SGNJTBYS.js');
4403
4846
  const yamlPath = path14__default.join(projectDir, "skaile.yaml");
4404
4847
  const editor = WorkspaceYamlEditor2.load(yamlPath);
4405
4848
  const removed = editor.removePlugin(name, specName);
@@ -4427,7 +4870,7 @@ async function runList(opts) {
4427
4870
  const registry = createPluginRegistry();
4428
4871
  if (declared.length > 0) {
4429
4872
  try {
4430
- const { loadPlugins } = await import('../plugin-store-JWJHIPUT.js');
4873
+ const { loadPlugins } = await import('../plugin-store-SGNJTBYS.js');
4431
4874
  const result = await loadPlugins(projectDir, declared, registry);
4432
4875
  for (const f of result.failed) logWarn(f.error);
4433
4876
  } catch (err) {
@@ -4643,10 +5086,29 @@ async function runPointerOnlyInstall(ref, deps) {
4643
5086
  }
4644
5087
  function makeInstallCommand() {
4645
5088
  return new Command("install").description(`Install dependencies from skaile.yaml, or a single asset by ref (${REF_HINT})`).argument("[ref]", `Asset ref to install from the Catalog (${REF_HINT})`).option("--locked", "Install from lock file (CI mode \u2014 fails on drift)").option("--project-dir <path>", "Project directory", process.cwd()).option("--target <agent>", "Agent framework", "claude-code").option(
5089
+ "--global",
5090
+ "Install the global SSOT (~/.skaile/skaile.yaml), fanning out to every global backend"
5091
+ ).option(
5092
+ "--backend <ids>",
5093
+ "Comma-separated global backends to install into (default: all resolved); implies --global"
5094
+ ).option(
4646
5095
  "--link",
4647
5096
  "symlink local-path assets instead of copying (dev loop; machine-local, not for committing)"
4648
5097
  ).action(runInstallCommand);
4649
5098
  }
5099
+ function parseBackendFilter2(raw) {
5100
+ if (!raw) return void 0;
5101
+ const ids = raw.split(",").map((s) => s.trim()).filter(Boolean);
5102
+ const out = [];
5103
+ for (const id of ids) {
5104
+ if (!SUPPORTED_DRIVER_TARGETS.includes(id)) {
5105
+ logErr(`unknown backend '${id}' \u2014 expected one of: ${SUPPORTED_DRIVER_TARGETS.join(", ")}`);
5106
+ process.exit(1);
5107
+ }
5108
+ out.push(id);
5109
+ }
5110
+ return out.length > 0 ? out : void 0;
5111
+ }
4650
5112
  async function runInstallCommand(ref, opts) {
4651
5113
  if (ref !== void 0) {
4652
5114
  await runRefInstall(ref, opts);
@@ -4662,7 +5124,7 @@ async function runRefInstall(ref, opts) {
4662
5124
  const spinner5 = p6.spinner();
4663
5125
  spinner5.start(`Installing ${ref}`);
4664
5126
  try {
4665
- const { openCatalogSource: openCatalogSource2, openLibrary: openLibrary2 } = await import('../open-library-TXJMFG34.js');
5127
+ const { openCatalogSource: openCatalogSource2, openLibrary: openLibrary2 } = await import('../open-library-ERKRGH4B.js');
4666
5128
  const catalog = await openCatalogSource2({ projectDir: path14__default.resolve(opts.projectDir) });
4667
5129
  if (!supportsInstallManifest(catalog)) {
4668
5130
  throw new Error(
@@ -4693,8 +5155,13 @@ async function runRefInstall(ref, opts) {
4693
5155
  }
4694
5156
  async function runYamlInstall(opts) {
4695
5157
  const projectDir = path14__default.resolve(opts.projectDir);
5158
+ const backends = parseBackendFilter2(opts.backend);
5159
+ if (opts.global || backends) {
5160
+ await runGlobalInstall(opts, backends);
5161
+ return;
5162
+ }
4696
5163
  try {
4697
- const { ensureSourcesCloned } = await import('../ensure-sources-EE7Z2HKS.js');
5164
+ const { ensureSourcesCloned } = await import('../ensure-sources-GJOXWVWO.js');
4698
5165
  const hydrate = ensureSourcesCloned(projectDir, { quiet: true });
4699
5166
  if (hydrate.cloned.length > 0) {
4700
5167
  logOk(`Cloned source(s): ${hydrate.cloned.join(", ")}`);
@@ -4736,6 +5203,37 @@ async function runYamlInstall(opts) {
4736
5203
  process.exit(1);
4737
5204
  }
4738
5205
  }
5206
+ async function runGlobalInstall(opts, backends) {
5207
+ const am2 = new AssetManager({
5208
+ projectDir: path14__default.resolve(opts.projectDir),
5209
+ global: true,
5210
+ backends
5211
+ });
5212
+ const spinner5 = p6.spinner();
5213
+ spinner5.start("Installing global dependencies");
5214
+ try {
5215
+ const result = await am2.install({ locked: opts.locked, link: opts.link });
5216
+ spinner5.stop("Done");
5217
+ if (result.deployed.length > 0) for (const d of result.deployed) logOk(d);
5218
+ if (result.removed.length > 0) for (const r of result.removed) logWarn(`Removed: ${r}`);
5219
+ if (result.deployed.length === 0 && result.removed.length === 0)
5220
+ logInfo("Nothing to install (already up to date).");
5221
+ if (result.missing.length > 0) {
5222
+ for (const m of result.missing) logErr(`Missing: ${m}`);
5223
+ process.exitCode = 1;
5224
+ }
5225
+ if (result.lockWritten) logInfo(`Global lock written: ${S.dim("~/.skaile/skaile.lock.yaml")}`);
5226
+ const state2 = am2.globalState({});
5227
+ const byBackend = /* @__PURE__ */ new Map();
5228
+ for (const s of state2)
5229
+ if (s.state === "installed") byBackend.set(s.backend, (byBackend.get(s.backend) ?? 0) + 1);
5230
+ for (const [backend, n] of byBackend) logInfo(`${backend}: ${n} installed`);
5231
+ } catch (err) {
5232
+ spinner5.stop("Failed");
5233
+ logErr(String(err));
5234
+ process.exit(1);
5235
+ }
5236
+ }
4739
5237
  function makeCheckCommand() {
4740
5238
  return new Command("check").description("Check for unmet requirements").option("--project-dir <path>", "Project directory", process.cwd()).action((opts) => {
4741
5239
  const am2 = new AssetManager({ projectDir: path14__default.resolve(opts.projectDir) });
@@ -4782,8 +5280,8 @@ function makeCleanCommand() {
4782
5280
  return;
4783
5281
  }
4784
5282
  if (!opts.yes) {
4785
- const confirm5 = await p6.confirm({ message: "Proceed?" });
4786
- if (p6.isCancel(confirm5) || !confirm5) {
5283
+ const confirm6 = await p6.confirm({ message: "Proceed?" });
5284
+ if (p6.isCancel(confirm6) || !confirm6) {
4787
5285
  logInfo("Cancelled.");
4788
5286
  return;
4789
5287
  }
@@ -5050,11 +5548,11 @@ function makeClearCommand() {
5050
5548
  }
5051
5549
  function makeReplCommand() {
5052
5550
  return new Command("repl").description("Start an interactive agent session").option("--project-dir <path>", "Project directory").option("--driver <name>", "Driver backend (claude-sdk, omp)").option("--provider <name>", "LLM provider").option("--model <name>", "Model override").option("--resume", "Resume last session").option("--resume-session <id>", "Resume specific session").action(async (opts) => {
5053
- const { findWorkspaceRoot: findWorkspaceRoot2 } = await import('../core/index.js');
5551
+ const { findWorkspaceRoot } = await import('../core/index.js');
5054
5552
  const { startRepl } = await import('../tui/index.js');
5055
5553
  const os2 = await import('os');
5056
5554
  const userCwd = path14__default.resolve(opts.projectDir ?? process.cwd());
5057
- const workspaceRoot = findWorkspaceRoot2(userCwd);
5555
+ const workspaceRoot = findWorkspaceRoot(userCwd);
5058
5556
  let projectDir;
5059
5557
  let agentCwd;
5060
5558
  let driverOverride = opts.driver;
@@ -5132,7 +5630,7 @@ async function ensurePluginsLoadedForServe(projectDir) {
5132
5630
  const { resolveSkWorkspaceConfig: resolveSkWorkspaceConfig2 } = await import('../core/index.js');
5133
5631
  const config = resolveSkWorkspaceConfig2(projectDir);
5134
5632
  if (!config.plugins || config.plugins.length === 0) return;
5135
- const { ensurePluginsLoaded } = await import('../plugin-store-JWJHIPUT.js');
5633
+ const { ensurePluginsLoaded } = await import('../plugin-store-SGNJTBYS.js');
5136
5634
  const { pluginRegistry } = await import('../plugin-registry/index.js');
5137
5635
  const result = await ensurePluginsLoaded(projectDir, config.plugins, pluginRegistry);
5138
5636
  if (result.loaded.length > 0) {
@@ -5148,21 +5646,21 @@ async function ensurePluginsLoadedForServe(projectDir) {
5148
5646
  async function runCompileTest(opts) {
5149
5647
  const { logOk: logOk2, logErr: logErr2, logInfo: logInfo2, S: S2 } = await import('../helpers-F4NZ66VD.js');
5150
5648
  const { tmpdir } = await import('os');
5151
- const { dirname: dirname5, join: join6, resolve: resolve5 } = await import('path');
5649
+ const { dirname: dirname4, join: join5, resolve: resolve5 } = await import('path');
5152
5650
  const { fileURLToPath: fileURLToPath2 } = await import('url');
5153
- const { unlinkSync, existsSync: existsSync14 } = await import('fs');
5651
+ const { unlinkSync, existsSync: existsSync13 } = await import('fs');
5154
5652
  const { spawn } = await import('child_process');
5155
5653
  const { portableSpawnSync: portableSpawnSync2 } = await import('../core/index.js');
5156
- const { MONOREPO_ROOT } = await import('../paths-OMVC4UWY.js');
5654
+ const { MONOREPO_ROOT } = await import('../paths-D6RUQ522.js');
5157
5655
  if (MONOREPO_ROOT === null) {
5158
5656
  logErr2("--compile-test is only available inside the skaile-dev workspace.");
5159
5657
  logInfo2("Run it from a checkout of skaile-ai/skaile-dev (the monorepo shell).");
5160
5658
  process.exitCode = 2;
5161
5659
  return;
5162
5660
  }
5163
- const outfile = join6(tmpdir(), `skaile-compile-test-${Date.now()}`);
5661
+ const outfile = join5(tmpdir(), `skaile-compile-test-${Date.now()}`);
5164
5662
  const __thisFile = fileURLToPath2(import.meta.url);
5165
- const entryPoint = resolve5(dirname5(__thisFile), "..", "index.ts");
5663
+ const entryPoint = resolve5(dirname4(__thisFile), "..", "index.ts");
5166
5664
  logInfo2(`Building compiled binary...`);
5167
5665
  logInfo2(`Entry: ${S2.dim(entryPoint)}`);
5168
5666
  logInfo2(`Output: ${S2.dim(outfile)}`);
@@ -5212,7 +5710,7 @@ async function runCompileTest(opts) {
5212
5710
  } catch {
5213
5711
  }
5214
5712
  try {
5215
- if (existsSync14(outfile)) unlinkSync(outfile);
5713
+ if (existsSync13(outfile)) unlinkSync(outfile);
5216
5714
  } catch {
5217
5715
  }
5218
5716
  }
@@ -5966,324 +6464,6 @@ function makeShowCommand() {
5966
6464
  }
5967
6465
  });
5968
6466
  }
5969
-
5970
- // cli/src/commands/source-manifest.ts
5971
- var NOT_WIRED_MSG = "`skaile source manifest *` is scaffolded but not yet wired. Server-side endpoint (`POST /sources/<id>/manifest`) lands in a follow-up PR.";
5972
- function notWired2(verb) {
5973
- logErr(`${NOT_WIRED_MSG} (verb: source manifest ${verb})`);
5974
- process.exit(2);
5975
- }
5976
- function addSourceManifestCommands(src) {
5977
- const manifest = src.command("manifest").description("Sidecar manifest lifecycle for a source");
5978
- manifest.command("init <src>").description("Start a sidecar manifest for a source").action(() => notWired2("init"));
5979
- manifest.command("edit <src>").description("Open the sidecar manifest in $EDITOR").action(() => notWired2("edit"));
5980
- manifest.command("rebuild <src>").description("Re-derive the manifest after an upstream change").option("--merge", "Merge with existing entries instead of overwriting", false).action(() => notWired2("rebuild"));
5981
- manifest.command("verify <src>").description("Verify hashes, deps, and schema").option("--strict", "Fail on advisory checks", false).action(() => notWired2("verify"));
5982
- manifest.command("publish <src>").description("Register the manifest in the store (provenance=curator)").action(() => notWired2("publish"));
5983
- manifest.command("propose <src>").description("Open a PR to upstream with the sidecar manifest").action(() => notWired2("propose"));
5984
- manifest.command("yank <src>").description("Retract a published manifest from the remote store").option("--version <v>", "Version to yank").action(() => notWired2("yank"));
5985
- }
5986
-
5987
- // cli/src/commands/source.ts
5988
- function sourceClonePath(key) {
5989
- return path14.join(getGlobalCacheDir(), key);
5990
- }
5991
- var SKAILE_MANIFEST_FILENAME2 = "skaile.manifest.yaml";
5992
- var LEGACY_SOURCE_CONFIG_FILENAME = ".skaile-source.yaml";
5993
- function indexingHadFailures(result) {
5994
- return result.errors.length > 0;
5995
- }
5996
- function legacySourceConfigHint(dest) {
5997
- const hasLegacy = existsSync(path14.join(dest, LEGACY_SOURCE_CONFIG_FILENAME));
5998
- const hasManifest = existsSync(path14.join(dest, SKAILE_MANIFEST_FILENAME2));
5999
- if (hasLegacy && !hasManifest) {
6000
- return `This source ships a legacy ${LEGACY_SOURCE_CONFIG_FILENAME} but no ${SKAILE_MANIFEST_FILENAME2}; its declared publisher is ignored and assets are published under their first path segment. Add a ${SKAILE_MANIFEST_FILENAME2} (\`skaile source manifest init\`) to set the publisher and asset inventory.`;
6001
- }
6002
- return void 0;
6003
- }
6004
- function normalizeSourceUrl(input) {
6005
- const s = input.trim();
6006
- if (/^(?:https?|git|ssh|file):\/\//.test(s) || /^git@/.test(s)) return s;
6007
- const repo = s.replace(/\.git$/, "");
6008
- if (/^[\w.-]+\/[\w.-]+$/.test(repo)) return `git@github.com:${repo}.git`;
6009
- if (/^[\w.-]+$/.test(repo)) return `git@github.com:skaile-ai/${repo}.git`;
6010
- return s;
6011
- }
6012
- function sourceMatchesKey(entry, key) {
6013
- if (entry.url === key) return true;
6014
- if (sourceCacheKey(entry.url) === key || sourceShortName(entry.url) === key) return true;
6015
- return entry.url === normalizeSourceUrl(key);
6016
- }
6017
- function findCacheKeyCollision(sources, url) {
6018
- const key = sourceCacheKey(url);
6019
- return sources.find((s) => s.url !== url && sourceCacheKey(s.url) === key);
6020
- }
6021
- function requireProjectYamlPath() {
6022
- const root = findWorkspaceRoot(process.cwd());
6023
- if (!root) {
6024
- logErr(
6025
- "No skaile.yaml found in the current directory or any parent. Run `skaile init` first."
6026
- );
6027
- process.exit(1);
6028
- }
6029
- return path14.join(root, workspaceConfigFilename());
6030
- }
6031
- function readProjectSources(yamlPath) {
6032
- if (!existsSync(yamlPath)) return [];
6033
- try {
6034
- return WorkspaceYamlEditor.load(yamlPath).getSources();
6035
- } catch (err) {
6036
- logErr(`Could not read ${yamlPath}: ${err instanceof Error ? err.message : String(err)}`);
6037
- process.exit(1);
6038
- }
6039
- }
6040
- function ensureClone(entry, opts = {}) {
6041
- const dest = sourceClonePath(sourceCacheKey(entry.url));
6042
- if (existsSync(dest)) {
6043
- if (!opts.force) return false;
6044
- rmSync(dest, { recursive: true, force: true });
6045
- }
6046
- mkdirSync(path14.dirname(dest), { recursive: true });
6047
- const args = ["clone", entry.url, dest];
6048
- if (entry.pin) args.splice(1, 0, "--branch", entry.pin);
6049
- const clone = spawnSync("git", args, { stdio: "inherit" });
6050
- if (clone.status !== 0) {
6051
- logErr(`git clone failed for ${entry.url}`);
6052
- process.exit(1);
6053
- }
6054
- return true;
6055
- }
6056
- async function refreshManifestCache(entry, includeDev = false) {
6057
- const { manager, library, close } = await openLibraryManager();
6058
- try {
6059
- const name = sourceShortName(entry.url);
6060
- const dest = sourceClonePath(sourceCacheKey(entry.url));
6061
- const existing = (await manager.listLibraries()).find((l) => l.name === name);
6062
- const lib = existing ?? await manager.addLibrary({
6063
- name,
6064
- path: dest,
6065
- backend: "git",
6066
- backendConfig: { url: entry.url, branch: entry.pin ?? "main", authHint: "ssh" },
6067
- ownership: "reader"
6068
- });
6069
- const catalog = new LocalCatalogSource(library, lib.id, dest, createFullRegistry());
6070
- const result = await catalog.sync({ includeDev });
6071
- await library.syncSource(lib.id);
6072
- return result;
6073
- } finally {
6074
- close();
6075
- }
6076
- }
6077
- async function dropManifestCache(url, purgeClone) {
6078
- const name = sourceShortName(url);
6079
- const { manager, close } = await openLibraryManager();
6080
- try {
6081
- const existing = (await manager.listLibraries()).find((l) => l.name === name);
6082
- if (existing) {
6083
- await manager.removeLibrary(name, { purge: false });
6084
- }
6085
- } finally {
6086
- close();
6087
- }
6088
- if (purgeClone) {
6089
- const dest = sourceClonePath(sourceCacheKey(url));
6090
- if (existsSync(dest)) rmSync(dest, { recursive: true, force: true });
6091
- }
6092
- }
6093
- function makeSourceCommand() {
6094
- const cmd = new Command("source").description(
6095
- "Manage github sources for the current project (recorded in skaile.yaml)"
6096
- );
6097
- cmd.command("add <source>").description(
6098
- "Clone a github repo and register it in this project's skaile.yaml. <source> may be a full git URL, an owner/repo shorthand, or a bare name (\u2192 git@github.com:skaile-ai/<name>.git)."
6099
- ).option("--name <slug>", "[deprecated] no-op \u2014 the source entry stores only the URL").option("--pin <ref>", "Pin a branch, tag, or 40-char SHA").option("--branch <branch>", "[deprecated] alias for --pin").option("--force", "Remove an existing clone and re-clone", false).option("--dev", "Also index dev-only assets under the source's dev_paths", false).action(
6100
- async (source, opts) => {
6101
- const url = normalizeSourceUrl(source);
6102
- const yamlPath = requireProjectYamlPath();
6103
- const collision = findCacheKeyCollision(readProjectSources(yamlPath), url);
6104
- if (collision) {
6105
- logErr(
6106
- `"${url}" maps to the same cache key (${sourceCacheKey(url)}) as already-registered "${collision.url}". Remove that source first (skaile source remove) or re-use its exact URL to update it.`
6107
- );
6108
- process.exitCode = 1;
6109
- return;
6110
- }
6111
- if (opts.name) {
6112
- logWarn(
6113
- "the --name flag is deprecated; the source entry now stores only the URL (the cache slug is derived from it)."
6114
- );
6115
- }
6116
- const pin = opts.pin ?? opts.branch;
6117
- const slug = sourceShortName(url);
6118
- const entry = { url };
6119
- if (pin) entry.pin = pin;
6120
- const editor = WorkspaceYamlEditor.load(yamlPath);
6121
- editor.setSource(entry);
6122
- editor.save();
6123
- const dest = sourceClonePath(sourceCacheKey(url));
6124
- const preexisting = existsSync(dest);
6125
- const cloned = ensureClone(entry, { force: opts.force });
6126
- if (!cloned) {
6127
- logOk(`Reusing cached clone at ${dest}`);
6128
- } else if (preexisting) {
6129
- logOk(`Re-cloned ${url} \u2192 ${dest}`);
6130
- } else {
6131
- logOk(`Cloned ${url} \u2192 ${dest}`);
6132
- }
6133
- const result = await refreshManifestCache(entry, opts.dev);
6134
- const failed = indexingHadFailures(result);
6135
- const indexedMsg = `Indexed ${result.assetsUpdated} of ${result.assetsFound} asset(s) from "${slug}".`;
6136
- if (failed) {
6137
- logWarn(indexedMsg);
6138
- } else {
6139
- logOk(indexedMsg);
6140
- }
6141
- for (const err of result.errors) logWarn(err);
6142
- const legacyHint = legacySourceConfigHint(dest);
6143
- if (legacyHint) logWarn(legacyHint);
6144
- const where = path14.relative(process.cwd(), yamlPath);
6145
- if (failed) {
6146
- logWarn(
6147
- `Source "${slug}" registered in ${where}, but ${result.errors.length} asset(s) failed to index (see above). Fix the cause, then re-run \`skaile source sync ${slug}\`.`
6148
- );
6149
- process.exitCode = 1;
6150
- } else {
6151
- logOk(`Source "${slug}" registered in ${where}.`);
6152
- }
6153
- }
6154
- );
6155
- cmd.command("list").description("List sources registered in this project's skaile.yaml").option("--json", "Output as JSON").action(async (opts) => {
6156
- const yamlPath = requireProjectYamlPath();
6157
- const sources = readProjectSources(yamlPath);
6158
- if (opts.json) return console.log(JSON.stringify(sources, null, 2));
6159
- if (sources.length === 0) {
6160
- logInfo("No sources in skaile.yaml. Run `skaile source add <git-url>` to register one.");
6161
- return;
6162
- }
6163
- console.log();
6164
- console.log(S.heading(" Sources"));
6165
- for (const s of sources) {
6166
- const slug = sourceShortName(s.url);
6167
- const cached = existsSync(sourceClonePath(sourceCacheKey(s.url))) ? "" : S.warn(" (not cached)");
6168
- console.log(` ${S.cmd(slug.padEnd(24))} ${S.dim(s.url)}${cached}`);
6169
- }
6170
- console.log(`
6171
- ${S.dim(`${sources.length} source(s)`)}
6172
- `);
6173
- });
6174
- cmd.command("show <name-or-url>").description("Show details for a source (matched by URL or derived slug)").action(async (key) => {
6175
- const yamlPath = requireProjectYamlPath();
6176
- const entry = readProjectSources(yamlPath).find((s) => sourceMatchesKey(s, key));
6177
- if (!entry) {
6178
- logErr(`No source matching "${key}" in ${yamlPath}.`);
6179
- process.exit(1);
6180
- }
6181
- const slug = sourceShortName(entry.url);
6182
- const clonePath = sourceClonePath(sourceCacheKey(entry.url));
6183
- console.log(
6184
- JSON.stringify(
6185
- {
6186
- ...entry,
6187
- slug,
6188
- clonePath,
6189
- cached: existsSync(clonePath)
6190
- },
6191
- null,
6192
- 2
6193
- )
6194
- );
6195
- });
6196
- cmd.command("remove <name-or-url>").description("Remove a source from this project's skaile.yaml (matched by URL or slug)").option("--purge", "Also delete the cached clone from ~/.skaile/cache/sources/", false).action(async (key, opts) => {
6197
- const yamlPath = requireProjectYamlPath();
6198
- const editor = WorkspaceYamlEditor.load(yamlPath);
6199
- const match = editor.getSources().find((s) => sourceMatchesKey(s, key));
6200
- if (!match) {
6201
- logErr(`No source matching "${key}" in ${yamlPath}.`);
6202
- process.exit(1);
6203
- }
6204
- const slug = sourceShortName(match.url);
6205
- editor.removeSource(match.url);
6206
- editor.save();
6207
- await dropManifestCache(match.url, opts.purge);
6208
- logOk(
6209
- opts.purge ? `Removed source "${slug}" and purged its clone.` : `Removed source "${slug}" from skaile.yaml (clone left in ~/.skaile/cache/sources/).`
6210
- );
6211
- });
6212
- cmd.command("sync [name]").description("Pull upstream changes and refresh the manifest cache").option("--dev", "Also index dev-only assets under each source's dev_paths", false).option("--all", "Sync every source without prompting", false).action(async (key, opts) => {
6213
- const yamlPath = requireProjectYamlPath();
6214
- const sources = readProjectSources(yamlPath);
6215
- if (sources.length === 0) {
6216
- logInfo("No sources in skaile.yaml.");
6217
- return;
6218
- }
6219
- let targets;
6220
- if (key) {
6221
- targets = sources.filter((s) => sourceMatchesKey(s, key));
6222
- if (targets.length === 0) {
6223
- logInfo(`No source matching "${key}".`);
6224
- return;
6225
- }
6226
- } else if (opts.all || sources.length === 1 || !process.stdin.isTTY) {
6227
- targets = sources;
6228
- } else {
6229
- const selected = await p6.multiselect({
6230
- message: "Select sources to sync",
6231
- options: sources.map((s) => ({
6232
- value: s.url,
6233
- label: sourceShortName(s.url),
6234
- hint: s.url
6235
- })),
6236
- required: true
6237
- });
6238
- if (p6.isCancel(selected)) {
6239
- p6.cancel("Cancelled.");
6240
- return;
6241
- }
6242
- const picked = new Set(selected);
6243
- targets = sources.filter((s) => picked.has(s.url));
6244
- }
6245
- for (const entry of targets) {
6246
- const slug = sourceShortName(entry.url);
6247
- const dest = sourceClonePath(sourceCacheKey(entry.url));
6248
- if (!existsSync(dest)) {
6249
- ensureClone(entry);
6250
- logOk(`${slug}: cloned to ${dest}.`);
6251
- } else {
6252
- const pull = spawnSync("git", ["pull", "--ff-only"], { cwd: dest, stdio: "inherit" });
6253
- if (pull.status !== 0) {
6254
- logWarn(`${slug}: git pull failed, skipping manifest refresh.`);
6255
- continue;
6256
- }
6257
- }
6258
- const result = await refreshManifestCache(entry, opts.dev);
6259
- const failed = indexingHadFailures(result);
6260
- const indexedMsg = `${slug}: indexed ${result.assetsUpdated} of ${result.assetsFound} asset(s).`;
6261
- if (failed) {
6262
- logWarn(indexedMsg);
6263
- process.exitCode = 1;
6264
- } else {
6265
- logOk(indexedMsg);
6266
- }
6267
- for (const err of result.errors) logWarn(`${slug}: ${err}`);
6268
- const legacyHint = legacySourceConfigHint(dest);
6269
- if (legacyHint) logWarn(`${slug}: ${legacyHint}`);
6270
- }
6271
- });
6272
- cmd.command("patch <ref>").description("Extract an asset for editing against the source").action((_ref) => {
6273
- logErr(
6274
- "`skaile source patch` is scaffolded but not yet wired. The patch flow lands in a follow-up PR."
6275
- );
6276
- process.exit(2);
6277
- });
6278
- cmd.command("propose <ref>").description("Open a PR with the patch against the source upstream").action((_ref) => {
6279
- logErr(
6280
- "`skaile source propose` is scaffolded but not yet wired. The propose flow lands in a follow-up PR."
6281
- );
6282
- process.exit(2);
6283
- });
6284
- addSourceManifestCommands(cmd);
6285
- return cmd;
6286
- }
6287
6467
  function makeStoreCommand() {
6288
6468
  const store = new Command("store").description("Manage the AI Asset Store");
6289
6469
  store.command("login").description("Authenticate with the store").option("--email <email>", "Login with email instead of GitHub").action(async (opts) => {
@@ -6461,7 +6641,7 @@ function makeTreeCommand() {
6461
6641
  });
6462
6642
  }
6463
6643
  function makeUpdateCommand() {
6464
- return new Command("update").description("Refresh the Catalog cache and re-deploy outdated assets in the current project").argument("[name]", "Update a specific asset (kind:name)").option("--project-dir <path>", "Project directory", process.cwd()).option("--no-catalog", "Skip refreshing the catalog cache").option("--catalog-only", "Refresh the catalog cache only; do not re-deploy assets").action(
6644
+ return new Command("update").description("Refresh the Catalog cache and re-deploy outdated assets in the current project").argument("[name]", "Update a specific asset (kind:name)").option("--project-dir <path>", "Project directory", process.cwd()).option("--no-catalog", "Skip refreshing the catalog cache").option("--catalog-only", "Refresh the catalog cache only; do not re-deploy assets").option("--no-sync", "Skip syncing sources from upstream before re-deploying").option("-y, --yes", "Sync sources without the confirmation prompt").action(
6465
6645
  async (_name, opts) => {
6466
6646
  if (opts.catalog !== false) {
6467
6647
  try {
@@ -6479,7 +6659,16 @@ function makeUpdateCommand() {
6479
6659
  }
6480
6660
  }
6481
6661
  if (opts.catalogOnly) return;
6482
- const am2 = new AssetManager({ projectDir: path14__default.resolve(opts.projectDir) });
6662
+ const projectDir = path14__default.resolve(opts.projectDir);
6663
+ if (await shouldSyncSources(opts)) {
6664
+ const syncSpinner = p6.spinner();
6665
+ syncSpinner.start("Syncing sources");
6666
+ const { total, synced, failed } = await syncProjectSources(projectDir);
6667
+ syncSpinner.stop(
6668
+ total === 0 ? "No sources to sync." : `Synced ${S.heading(`${synced}/${total}`)} source(s)` + (failed ? ` (${failed} failed)` : "")
6669
+ );
6670
+ }
6671
+ const am2 = new AssetManager({ projectDir });
6483
6672
  const spinner5 = p6.spinner();
6484
6673
  spinner5.start("Re-deploying assets");
6485
6674
  const result = await am2.install();
@@ -6492,6 +6681,19 @@ function makeUpdateCommand() {
6492
6681
  }
6493
6682
  );
6494
6683
  }
6684
+ async function shouldSyncSources(opts) {
6685
+ if (opts.sync === false) return false;
6686
+ if (opts.yes || !process.stdin.isTTY) return true;
6687
+ const answer = await p6.confirm({
6688
+ message: "Sync sources from upstream before re-deploying?",
6689
+ initialValue: true
6690
+ });
6691
+ if (p6.isCancel(answer)) {
6692
+ p6.cancel("Cancelled.");
6693
+ process.exit(130);
6694
+ }
6695
+ return answer;
6696
+ }
6495
6697
  var MANIFEST_GLOBS = [
6496
6698
  { pattern: /\/SKILL\.md$/, kind: "skill" },
6497
6699
  { pattern: /\/agent\.yaml$/, kind: "agent" },
@@ -7221,7 +7423,7 @@ program.command("init [project-dir]").description("Initialize a project director
7221
7423
  (projectDir, opts) => runInit(projectDir, opts)
7222
7424
  );
7223
7425
  async function runInit(projectDir, opts) {
7224
- const { mkdirSync: mkdirSync6, writeFileSync: writeFileSync4 } = await import('fs');
7426
+ const { mkdirSync: mkdirSync5, writeFileSync: writeFileSync4 } = await import('fs');
7225
7427
  const { stringify } = await import('yaml');
7226
7428
  const { DRIVER_TARGETS, SUPPORTED_DRIVER_TARGETS: SUPPORTED_DRIVER_TARGETS2 } = await import('../core/index.js');
7227
7429
  const backend = opts.backend;
@@ -7233,30 +7435,30 @@ async function runInit(projectDir, opts) {
7233
7435
  const resolved = path14__default.resolve(projectDir ?? ".");
7234
7436
  const projectName = path14__default.basename(resolved);
7235
7437
  const created = [];
7236
- const { existsSync: existsSync14 } = await import('fs');
7237
- if (!existsSync14(resolved)) {
7238
- mkdirSync6(resolved, { recursive: true });
7438
+ const { existsSync: existsSync13 } = await import('fs');
7439
+ if (!existsSync13(resolved)) {
7440
+ mkdirSync5(resolved, { recursive: true });
7239
7441
  created.push(".");
7240
7442
  }
7241
7443
  const skaileDir = path14__default.join(resolved, ".skaile");
7242
- if (!existsSync14(skaileDir)) {
7243
- mkdirSync6(path14__default.join(skaileDir, "sessions"), { recursive: true });
7444
+ if (!existsSync13(skaileDir)) {
7445
+ mkdirSync5(path14__default.join(skaileDir, "sessions"), { recursive: true });
7244
7446
  created.push(".skaile/");
7245
7447
  }
7246
7448
  const settingsPath = path14__default.join(skaileDir, "settings.json");
7247
- if (!existsSync14(settingsPath)) {
7449
+ if (!existsSync13(settingsPath)) {
7248
7450
  writeFileSync4(settingsPath, "{}\n");
7249
7451
  created.push(".skaile/settings.json");
7250
7452
  }
7251
7453
  const wsConfigPath = path14__default.join(resolved, "skaile.yaml");
7252
- if (!existsSync14(wsConfigPath)) {
7454
+ if (!existsSync13(wsConfigPath)) {
7253
7455
  writeFileSync4(wsConfigPath, stringify(buildInitWorkspaceConfig(projectName, backend)));
7254
7456
  created.push("skaile.yaml");
7255
7457
  }
7256
7458
  for (const dir of new Set(Object.values(DRIVER_TARGETS[backend].local))) {
7257
7459
  const full = path14__default.join(resolved, dir);
7258
- if (!existsSync14(full)) {
7259
- mkdirSync6(full, { recursive: true });
7460
+ if (!existsSync13(full)) {
7461
+ mkdirSync5(full, { recursive: true });
7260
7462
  created.push(`${dir}/`);
7261
7463
  }
7262
7464
  }
@@ -7286,7 +7488,7 @@ function buildInitWorkspaceConfig(projectName, backend) {
7286
7488
  }
7287
7489
  async function initGitScaffold(resolved, created) {
7288
7490
  const { execSync: execSync3 } = await import('child_process');
7289
- const { existsSync: existsSync14, readFileSync: readFileSync8, writeFileSync: writeFileSync4 } = await import('fs');
7491
+ const { existsSync: existsSync13, readFileSync: readFileSync8, writeFileSync: writeFileSync4 } = await import('fs');
7290
7492
  const gitignorePath = path14__default.join(resolved, ".gitignore");
7291
7493
  const entries = [
7292
7494
  "node_modules/",
@@ -7302,7 +7504,7 @@ async function initGitScaffold(resolved, created) {
7302
7504
  ".env",
7303
7505
  ".env.local"
7304
7506
  ];
7305
- const existing = existsSync14(gitignorePath) ? readFileSync8(gitignorePath, "utf-8") : "";
7507
+ const existing = existsSync13(gitignorePath) ? readFileSync8(gitignorePath, "utf-8") : "";
7306
7508
  const have = new Set(existing.split("\n").map((l) => l.trim()));
7307
7509
  const append = entries.filter((e) => !have.has(e));
7308
7510
  if (append.length > 0) {
@@ -7311,7 +7513,7 @@ async function initGitScaffold(resolved, created) {
7311
7513
  `);
7312
7514
  if (!existing) created.push(".gitignore");
7313
7515
  }
7314
- if (!existsSync14(path14__default.join(resolved, ".git"))) {
7516
+ if (!existsSync13(path14__default.join(resolved, ".git"))) {
7315
7517
  try {
7316
7518
  execSync3("git init", { cwd: resolved, stdio: "pipe" });
7317
7519
  created.push(".git/");
@@ -7341,7 +7543,7 @@ async function bootstrapUserConfig() {
7341
7543
  }
7342
7544
  async function hydrateInitSources(resolved, created) {
7343
7545
  try {
7344
- const { ensureSourcesCloned } = await import('../ensure-sources-EE7Z2HKS.js');
7546
+ const { ensureSourcesCloned } = await import('../ensure-sources-GJOXWVWO.js');
7345
7547
  const hydrate = ensureSourcesCloned(resolved, { quiet: true });
7346
7548
  for (const n of hydrate.cloned) created.push(`~/.skaile/cache/sources/${n}/`);
7347
7549
  if (hydrate.failed.length > 0) {
@@ -7377,7 +7579,7 @@ function printInitSummary(resolved, projectDir, created) {
7377
7579
  console.log();
7378
7580
  }
7379
7581
  program.command("setup").description("Interactive provider setup wizard").action(async () => {
7380
- const { cmdSetup } = await import('../setup-ETNBVZQA.js');
7582
+ const { cmdSetup } = await import('../setup-IV2JFW3T.js');
7381
7583
  await cmdSetup([], { projectDir: process.cwd() });
7382
7584
  });
7383
7585
  program.addCommand(makeInstallCommand());