@slats/claude-assets-sync 0.1.0 → 0.1.1

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 (61) hide show
  1. package/dist/commands/add.cjs +24 -0
  2. package/dist/commands/add.d.ts +2 -6
  3. package/dist/commands/add.mjs +24 -0
  4. package/dist/commands/index.d.ts +1 -2
  5. package/dist/commands/list.cjs +12 -1
  6. package/dist/commands/list.d.ts +10 -1
  7. package/dist/commands/list.mjs +12 -2
  8. package/dist/commands/remove.cjs +49 -31
  9. package/dist/commands/remove.mjs +49 -30
  10. package/dist/commands/types.d.ts +9 -0
  11. package/dist/commands/update.cjs +27 -0
  12. package/dist/commands/update.d.ts +16 -0
  13. package/dist/commands/update.mjs +27 -1
  14. package/dist/components/add/BulkAddView.cjs +169 -0
  15. package/dist/components/add/BulkAddView.d.ts +11 -0
  16. package/dist/components/add/BulkAddView.mjs +167 -0
  17. package/dist/components/list/ListCommand.cjs +585 -392
  18. package/dist/components/list/ListCommand.d.ts +0 -3
  19. package/dist/components/list/ListCommand.mjs +590 -377
  20. package/dist/components/list/index.d.ts +1 -0
  21. package/dist/components/list/types.d.ts +14 -0
  22. package/dist/components/remove/RemoveConfirm.cjs +18 -0
  23. package/dist/components/remove/RemoveConfirm.d.ts +11 -0
  24. package/dist/components/remove/RemoveConfirm.mjs +16 -0
  25. package/dist/components/shared/Confirm.cjs +30 -0
  26. package/dist/components/shared/Confirm.d.ts +8 -0
  27. package/dist/components/shared/Confirm.mjs +28 -0
  28. package/dist/components/shared/MenuItem.cjs +18 -0
  29. package/dist/components/shared/MenuItem.d.ts +7 -0
  30. package/dist/components/shared/MenuItem.mjs +16 -0
  31. package/dist/components/shared/ProgressBar.d.ts +7 -0
  32. package/dist/components/shared/StepRunner.cjs +58 -0
  33. package/dist/components/shared/StepRunner.d.ts +15 -0
  34. package/dist/components/shared/StepRunner.mjs +56 -0
  35. package/dist/components/shared/Table.cjs +19 -0
  36. package/dist/components/shared/Table.d.ts +8 -0
  37. package/dist/components/shared/Table.mjs +17 -0
  38. package/dist/components/shared/index.d.ts +6 -0
  39. package/dist/core/cli.cjs +4 -8
  40. package/dist/core/cli.mjs +5 -9
  41. package/dist/core/constants.cjs +2 -0
  42. package/dist/core/constants.d.ts +4 -0
  43. package/dist/core/constants.mjs +2 -1
  44. package/dist/core/io.mjs +1 -1
  45. package/dist/core/listOperations.cjs +228 -0
  46. package/dist/core/listOperations.d.ts +43 -0
  47. package/dist/core/listOperations.mjs +205 -0
  48. package/dist/core/packageScanner.cjs +8 -6
  49. package/dist/core/packageScanner.mjs +9 -7
  50. package/dist/core/sync.cjs +9 -15
  51. package/dist/core/sync.mjs +10 -16
  52. package/dist/utils/asyncPool.cjs +26 -0
  53. package/dist/utils/asyncPool.d.ts +5 -0
  54. package/dist/utils/asyncPool.mjs +24 -0
  55. package/dist/utils/dependencies.cjs +57 -0
  56. package/dist/utils/dependencies.d.ts +10 -0
  57. package/dist/utils/dependencies.mjs +34 -0
  58. package/dist/utils/package.cjs +5 -0
  59. package/dist/utils/package.d.ts +6 -1
  60. package/dist/utils/package.mjs +6 -2
  61. package/package.json +2 -1
@@ -1,147 +1,380 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import * as fs from 'node:fs';
3
- import * as path from 'node:path';
4
- import { useInput, Box, Text } from 'ink';
2
+ import { useApp, useInput, Box, Text } from 'ink';
3
+ import SelectInput from 'ink-select-input';
5
4
  import Spinner from 'ink-spinner';
6
- import { useState, useRef, useCallback, useEffect } from 'react';
5
+ import { useState, useRef, useEffect, useCallback } from 'react';
6
+ import { applyChangesAndSync, computeFileOperations } from '../../core/listOperations.mjs';
7
7
  import { scanPackageAssets } from '../../core/packageScanner.mjs';
8
8
  import { syncPackage } from '../../core/sync.mjs';
9
- import { readUnifiedSyncMeta, removeSkillUnitFromPackage, removePackageFromMeta, writeUnifiedSyncMeta } from '../../core/syncMeta.mjs';
9
+ import { readUnifiedSyncMeta } from '../../core/syncMeta.mjs';
10
+ import { updatePackageVersionAndSync } from '../../commands/update.mjs';
11
+ import { readLocalPackageJson, readPackageJson } from '../../utils/package.mjs';
12
+ import { Confirm } from '../shared/Confirm.mjs';
13
+ import { MenuItem } from '../shared/MenuItem.mjs';
14
+ import { StepRunner } from '../shared/StepRunner.mjs';
15
+ import { Table } from '../shared/Table.mjs';
10
16
  import { TreeSelect } from '../tree/TreeSelect.mjs';
11
17
 
18
+ function buildPackageDetail(prefix, info, syncedAt) {
19
+ let totalAssets = 0;
20
+ for (const files of Object.values(info.files)) {
21
+ totalAssets += Array.isArray(files) ? files.length : 0;
22
+ }
23
+ return {
24
+ prefix,
25
+ originalName: info.originalName,
26
+ version: info.version,
27
+ local: info.local ?? false,
28
+ syncedAt,
29
+ files: info.files,
30
+ totalAssets,
31
+ };
32
+ }
33
+ function formatSyncDate(dateStr) {
34
+ if (!dateStr)
35
+ return 'unknown';
36
+ try {
37
+ return new Date(dateStr).toLocaleString();
38
+ }
39
+ catch {
40
+ return dateStr;
41
+ }
42
+ }
12
43
  const ListCommand = ({ cwd }) => {
13
- const [state, setState] = useState('loading');
14
- const [packageFiles, setPackageFiles] = useState({});
44
+ const { exit } = useApp();
45
+ const [phase, setPhase] = useState('loading');
15
46
  const [meta, setMeta] = useState(null);
16
- const [lastRefresh, setLastRefresh] = useState(Date.now());
17
- const [changesSummary, setChangesSummary] = useState(null);
47
+ const [errorMessage, setErrorMessage] = useState(null);
48
+ const [selectedPackage, setSelectedPackage] = useState(null);
49
+ const [syncSteps, setSyncSteps] = useState([]);
50
+ const [packageFiles, setPackageFiles] = useState({});
51
+ const [editTrees, setEditTrees] = useState([]);
52
+ const [updateChecking, setUpdateChecking] = useState(false);
53
+ const [updateResult, setUpdateResult] = useState(null);
54
+ const [updateConfirmPending, setUpdateConfirmPending] = useState(false);
55
+ const [updateSyncSteps, setUpdateSyncSteps] = useState([]);
56
+ const [updateSyncing, setUpdateSyncing] = useState(false);
57
+ const [checkingRemote, setCheckingRemote] = useState(false);
58
+ const [remoteVersions, setRemoteVersions] = useState(null);
18
59
  const hasLoadedRef = useRef(false);
19
- const buildTrees = () => {
60
+ useInput((_input, key) => {
61
+ if (!key.escape)
62
+ return;
63
+ switch (phase) {
64
+ case 'main-menu':
65
+ exit();
66
+ break;
67
+ case 'package-list':
68
+ setPhase('main-menu');
69
+ break;
70
+ case 'package-detail':
71
+ setSelectedPackage(null);
72
+ setPhase('package-list');
73
+ break;
74
+ case 'edit-assets':
75
+ setPhase('package-detail');
76
+ break;
77
+ case 'status-view':
78
+ setRemoteVersions(null);
79
+ setCheckingRemote(false);
80
+ setPhase('main-menu');
81
+ break;
82
+ case 'error':
83
+ exit();
84
+ break;
85
+ }
86
+ });
87
+ useEffect(() => {
88
+ if (phase !== 'done')
89
+ return;
90
+ const timer = setTimeout(() => exit(), 1500);
91
+ return () => clearTimeout(timer);
92
+ }, [phase, exit]);
93
+ const loadMeta = useCallback(() => {
94
+ try {
95
+ const loaded = readUnifiedSyncMeta(cwd);
96
+ setMeta(loaded);
97
+ return loaded;
98
+ }
99
+ catch (err) {
100
+ setErrorMessage(err instanceof Error ? err.message : 'Failed to load sync metadata');
101
+ setPhase('error');
102
+ return null;
103
+ }
104
+ }, [cwd]);
105
+ useEffect(() => {
106
+ if (!hasLoadedRef.current) {
107
+ hasLoadedRef.current = true;
108
+ const loaded = loadMeta();
109
+ if (loaded && Object.keys(loaded.packages).length > 0) {
110
+ setPhase('main-menu');
111
+ }
112
+ else if (loaded) {
113
+ setPhase('done');
114
+ }
115
+ }
116
+ }, [loadMeta]);
117
+ const reloadMeta = useCallback(() => {
118
+ const loaded = loadMeta();
119
+ if (loaded && selectedPackage) {
120
+ const pkgInfo = loaded.packages[selectedPackage.prefix];
121
+ if (pkgInfo) {
122
+ setSelectedPackage(buildPackageDetail(selectedPackage.prefix, pkgInfo, loaded.syncedAt));
123
+ }
124
+ else {
125
+ setSelectedPackage(null);
126
+ }
127
+ }
128
+ return loaded;
129
+ }, [loadMeta, selectedPackage]);
130
+ const handleMainMenuSelect = (item) => {
131
+ switch (item.value) {
132
+ case 'packages':
133
+ setPhase('package-list');
134
+ break;
135
+ case 'status':
136
+ setRemoteVersions(null);
137
+ setCheckingRemote(false);
138
+ setPhase('status-view');
139
+ break;
140
+ case 'exit':
141
+ exit();
142
+ break;
143
+ }
144
+ };
145
+ const handlePackageSelect = (item) => {
146
+ if (item.value === 'back') {
147
+ setPhase('main-menu');
148
+ return;
149
+ }
20
150
  if (!meta)
21
- return [];
22
- const trees = [];
23
- for (const [prefix, packageInfo] of Object.entries(meta.packages)) {
24
- const packageData = packageFiles[prefix];
25
- if (!packageData)
26
- continue;
27
- const assetTypeTrees = packageData.available.map((assetTypeTree) => {
28
- const clonedTree = { ...assetTypeTree };
29
- if (clonedTree.children) {
30
- clonedTree.children = clonedTree.children.map((fileNode) => {
31
- const selected = packageData.selected.has(fileNode.path);
32
- return {
33
- ...fileNode,
34
- selected,
35
- };
36
- });
37
- const anySelected = clonedTree.children.some((f) => f.selected);
38
- clonedTree.selected = anySelected;
39
- }
40
- return clonedTree;
41
- });
42
- trees.push({
43
- id: prefix,
44
- label: `${packageInfo.originalName}@${packageInfo.version}`,
45
- path: packageInfo.originalName,
46
- type: 'directory',
47
- children: assetTypeTrees,
48
- selected: true,
49
- expanded: true,
50
- });
151
+ return;
152
+ const prefix = item.value;
153
+ const pkgInfo = meta.packages[prefix];
154
+ if (pkgInfo) {
155
+ setSelectedPackage(buildPackageDetail(prefix, pkgInfo, meta.syncedAt));
156
+ setPhase('package-detail');
157
+ }
158
+ };
159
+ const handleDetailActionSelect = (item) => {
160
+ switch (item.value) {
161
+ case 'sync':
162
+ setPhase('sync-action');
163
+ handleSyncAction();
164
+ break;
165
+ case 'update':
166
+ setUpdateResult(null);
167
+ setUpdateConfirmPending(false);
168
+ setUpdateSyncing(false);
169
+ setUpdateSyncSteps([]);
170
+ setPhase('update-action');
171
+ handleUpdateActionCheck();
172
+ break;
173
+ case 'edit-assets':
174
+ handleEditAssetsInit();
175
+ break;
176
+ case 'remove':
177
+ setPhase('remove-action');
178
+ break;
179
+ case 'back':
180
+ setSelectedPackage(null);
181
+ setPhase('package-list');
182
+ break;
183
+ }
184
+ };
185
+ const handleSyncAction = async () => {
186
+ if (!selectedPackage || !meta)
187
+ return;
188
+ const pkgInfo = meta.packages[selectedPackage.prefix];
189
+ if (!pkgInfo)
190
+ return;
191
+ setSyncSteps([
192
+ { name: `Syncing ${selectedPackage.originalName}`, status: 'running' },
193
+ ]);
194
+ try {
195
+ await syncPackage(selectedPackage.originalName, {
196
+ force: true,
197
+ dryRun: false,
198
+ local: pkgInfo.local ?? false,
199
+ ref: undefined,
200
+ flat: undefined,
201
+ }, cwd, pkgInfo.exclusions);
202
+ setSyncSteps([
203
+ { name: `Syncing ${selectedPackage.originalName}`, status: 'success' },
204
+ ]);
205
+ }
206
+ catch (err) {
207
+ setSyncSteps([
208
+ {
209
+ name: `Syncing ${selectedPackage.originalName}`,
210
+ status: 'failed',
211
+ error: err instanceof Error ? err.message : String(err),
212
+ },
213
+ ]);
214
+ }
215
+ setTimeout(() => {
216
+ reloadMeta();
217
+ setPhase('package-detail');
218
+ }, 1000);
219
+ };
220
+ const handleUpdateActionCheck = async () => {
221
+ if (!selectedPackage || !meta)
222
+ return;
223
+ setUpdateChecking(true);
224
+ const pkgInfo = meta.packages[selectedPackage.prefix];
225
+ if (!pkgInfo)
226
+ return;
227
+ const isLocal = pkgInfo.local ?? false;
228
+ try {
229
+ const currentPkgInfo = isLocal
230
+ ? readLocalPackageJson(pkgInfo.originalName, cwd)
231
+ : readPackageJson(pkgInfo.originalName, cwd);
232
+ if (!currentPkgInfo) {
233
+ setUpdateResult({ versionChanged: false, notFound: true });
234
+ setUpdateChecking(false);
235
+ setTimeout(() => {
236
+ reloadMeta();
237
+ setPhase('package-detail');
238
+ }, 2000);
239
+ return;
240
+ }
241
+ const installedVersion = currentPkgInfo.version;
242
+ const syncedVersion = pkgInfo.version;
243
+ if (installedVersion === syncedVersion) {
244
+ setUpdateResult({
245
+ versionChanged: false,
246
+ oldVersion: syncedVersion,
247
+ newVersion: installedVersion,
248
+ });
249
+ setUpdateChecking(false);
250
+ setTimeout(() => {
251
+ reloadMeta();
252
+ setPhase('package-detail');
253
+ }, 1500);
254
+ }
255
+ else {
256
+ setUpdateResult({
257
+ versionChanged: true,
258
+ oldVersion: syncedVersion,
259
+ newVersion: installedVersion,
260
+ });
261
+ setUpdateChecking(false);
262
+ setUpdateConfirmPending(true);
263
+ }
264
+ }
265
+ catch {
266
+ setUpdateResult({ versionChanged: false, notFound: true });
267
+ setUpdateChecking(false);
268
+ setTimeout(() => {
269
+ reloadMeta();
270
+ setPhase('package-detail');
271
+ }, 2000);
51
272
  }
52
- return trees;
53
273
  };
54
- const loadSyncedFiles = useCallback(async (isRefresh = false) => {
55
- const loadedMeta = readUnifiedSyncMeta(cwd);
56
- if (!loadedMeta || Object.keys(loadedMeta.packages).length === 0) {
57
- setState('done');
274
+ const handleUpdateConfirm = async (yes) => {
275
+ setUpdateConfirmPending(false);
276
+ if (!yes) {
277
+ reloadMeta();
278
+ setPhase('package-detail');
279
+ return;
280
+ }
281
+ if (!selectedPackage || !meta)
58
282
  return;
283
+ setUpdateSyncing(true);
284
+ setUpdateSyncSteps([
285
+ { name: 'Updating version and syncing files...', status: 'running' },
286
+ ]);
287
+ try {
288
+ await updatePackageVersionAndSync(selectedPackage.prefix, meta, { local: meta.packages[selectedPackage.prefix]?.local, sync: true }, cwd);
289
+ setUpdateSyncSteps([
290
+ { name: 'Version updated and files synced', status: 'success' },
291
+ ]);
59
292
  }
60
- if (!isRefresh) {
61
- setMeta(loadedMeta);
293
+ catch (err) {
294
+ setUpdateSyncSteps([
295
+ {
296
+ name: 'Update version',
297
+ status: 'failed',
298
+ error: err instanceof Error ? err.message : String(err),
299
+ },
300
+ ]);
301
+ }
302
+ setTimeout(() => {
303
+ reloadMeta();
304
+ setPhase('package-detail');
305
+ }, 1000);
306
+ };
307
+ const handleRemoveConfirm = async (yes) => {
308
+ if (!yes) {
309
+ setPhase('package-detail');
310
+ return;
62
311
  }
63
- const currentSelected = {};
64
- setPackageFiles((prev) => {
65
- for (const [prefix, data] of Object.entries(prev)) {
66
- currentSelected[prefix] = data.selected;
312
+ if (!selectedPackage || !meta)
313
+ return;
314
+ const pkgInfo = meta.packages[selectedPackage.prefix];
315
+ if (!pkgInfo)
316
+ return;
317
+ try {
318
+ const filesToDelete = [];
319
+ const fileOperations = [];
320
+ for (const [assetType, files] of Object.entries(pkgInfo.files)) {
321
+ if (!Array.isArray(files))
322
+ continue;
323
+ const units = files;
324
+ for (const unit of units) {
325
+ fileOperations.push({
326
+ type: 'remove',
327
+ prefix: selectedPackage.prefix,
328
+ assetType,
329
+ skillName: unit.name,
330
+ });
331
+ }
67
332
  }
68
- return prev;
69
- });
70
- const fetchPromises = Object.entries(loadedMeta.packages).map(async ([prefix, packageInfo]) => {
71
- try {
72
- const isLocal = packageInfo.local ?? true;
73
- let scannedTrees = await scanPackageAssets(packageInfo.originalName, {
74
- local: isLocal,
333
+ await applyChangesAndSync({ filesToDelete, fileOperations, packagesToSync: [] }, meta, cwd);
334
+ const reloaded = reloadMeta();
335
+ if (reloaded &&
336
+ Object.keys(reloaded.packages).length > 0) {
337
+ setSelectedPackage(null);
338
+ setPhase('package-list');
339
+ }
340
+ else {
341
+ setPhase('done');
342
+ }
343
+ }
344
+ catch (err) {
345
+ setErrorMessage(err instanceof Error ? err.message : 'Failed to remove package');
346
+ setPhase('error');
347
+ }
348
+ };
349
+ const handleEditAssetsInit = async () => {
350
+ if (!selectedPackage || !meta)
351
+ return;
352
+ setPhase('edit-assets');
353
+ const pkgInfo = meta.packages[selectedPackage.prefix];
354
+ if (!pkgInfo)
355
+ return;
356
+ const prefix = selectedPackage.prefix;
357
+ try {
358
+ const isLocal = pkgInfo.local ?? true;
359
+ let scannedTrees = await scanPackageAssets(pkgInfo.originalName, {
360
+ local: isLocal,
361
+ ref: undefined,
362
+ }).catch(() => null);
363
+ if (!scannedTrees && pkgInfo.local === undefined) {
364
+ scannedTrees = await scanPackageAssets(pkgInfo.originalName, {
365
+ local: !isLocal,
75
366
  ref: undefined,
76
367
  }).catch(() => null);
77
- if (!scannedTrees && packageInfo.local === undefined) {
78
- scannedTrees = await scanPackageAssets(packageInfo.originalName, {
79
- local: !isLocal,
80
- ref: undefined,
81
- }).catch(() => null);
82
- }
83
- if (scannedTrees) {
84
- return { prefix, available: scannedTrees, packageInfo };
85
- }
86
- else {
87
- throw new Error('Scan failed, using synced files only');
88
- }
89
368
  }
90
- catch {
91
- const assetTypeTrees = [];
92
- for (const [assetType, files] of Object.entries(packageInfo.files)) {
93
- const units = Array.isArray(files) ? files : [];
94
- if (units.length === 0)
95
- continue;
96
- const skillNodes = units.map((unit) => {
97
- const displayName = unit.transformed ?? unit.name;
98
- if (unit.isDirectory) {
99
- return {
100
- id: `${prefix}/${assetType}/${displayName}`,
101
- label: displayName,
102
- path: `${assetType}/${unit.name}`,
103
- type: 'skill-directory',
104
- viewOnly: true,
105
- selected: true,
106
- expanded: false,
107
- children: (unit.internalFiles || []).map((f) => ({
108
- id: `${prefix}/${assetType}/${displayName}/${f}`,
109
- label: f,
110
- path: `${assetType}/${unit.name}/${f}`,
111
- type: 'file',
112
- selected: true,
113
- expanded: false,
114
- disabled: true,
115
- })),
116
- };
117
- }
118
- return {
119
- id: `${prefix}/${assetType}/${displayName}`,
120
- label: displayName,
121
- path: `${assetType}/${unit.name}`,
122
- type: 'file',
123
- selected: true,
124
- expanded: false,
125
- };
126
- });
127
- assetTypeTrees.push({
128
- id: `${prefix}/${assetType}`,
129
- label: assetType,
130
- path: assetType,
131
- type: 'directory',
132
- children: skillNodes,
133
- selected: true,
134
- expanded: true,
135
- });
136
- }
137
- return { prefix, available: assetTypeTrees, packageInfo };
369
+ let available;
370
+ if (scannedTrees) {
371
+ available = scannedTrees;
138
372
  }
139
- });
140
- const results = await Promise.all(fetchPromises);
141
- const newPackageFiles = {};
142
- for (const { prefix, available, packageInfo } of results) {
143
- const excludedFiles = new Set(packageInfo.exclusions?.files || []);
144
- const metaSelectedFiles = new Set(Object.entries(packageInfo.files).flatMap(([assetType, files]) => (Array.isArray(files) ? files : [])
373
+ else {
374
+ available = buildFallbackTrees(prefix, pkgInfo);
375
+ }
376
+ const excludedFiles = new Set(pkgInfo.exclusions?.files || []);
377
+ const metaSelectedFiles = new Set(Object.entries(pkgInfo.files).flatMap(([assetType, files]) => (Array.isArray(files) ? files : [])
145
378
  .map((unit) => {
146
379
  const filePath = `${assetType}/${unit.name}`;
147
380
  if (excludedFiles.has(filePath) ||
@@ -151,286 +384,266 @@ const ListCommand = ({ cwd }) => {
151
384
  return filePath;
152
385
  })
153
386
  .filter((f) => f !== null)));
154
- const selectedFiles = isRefresh && currentSelected[prefix]
155
- ? currentSelected[prefix]
156
- : metaSelectedFiles;
157
- newPackageFiles[prefix] = {
158
- available,
159
- selected: selectedFiles,
387
+ const newPackageFiles = {
388
+ [prefix]: { available, selected: metaSelectedFiles },
160
389
  };
390
+ setPackageFiles(newPackageFiles);
391
+ const trees = buildEditTrees(prefix, pkgInfo, available, metaSelectedFiles);
392
+ setEditTrees(trees);
161
393
  }
162
- setPackageFiles(newPackageFiles);
163
- setState('selecting');
164
- }, [cwd]);
165
- useEffect(() => {
166
- if (!hasLoadedRef.current) {
167
- hasLoadedRef.current = true;
168
- loadSyncedFiles().catch((error) => {
169
- console.error('Failed to load synced files:', error);
170
- setState('done');
171
- });
394
+ catch {
395
+ setPhase('package-detail');
172
396
  }
173
- }, [loadSyncedFiles]);
174
- const handleRefresh = () => {
175
- if (state !== 'selecting')
397
+ };
398
+ const handleEditAssetsSubmit = async (selectedTrees) => {
399
+ if (!meta || !selectedPackage)
176
400
  return;
177
- const now = Date.now();
178
- if (now - lastRefresh < 30000) {
401
+ const summary = computeFileOperations(meta, packageFiles, selectedTrees, cwd);
402
+ if (summary.filesToDelete.length === 0 &&
403
+ summary.packagesToSync.length === 0 &&
404
+ summary.fileOperations.length === 0) {
405
+ setPhase('package-detail');
179
406
  return;
180
407
  }
181
- setLastRefresh(now);
182
- setState('loading');
183
- setTimeout(() => {
184
- loadSyncedFiles(true).catch((error) => {
185
- console.error('Failed to refresh:', error);
186
- setState('selecting');
408
+ setPhase('sync-action');
409
+ setSyncSteps([{ name: 'Applying changes...', status: 'running' }]);
410
+ try {
411
+ await applyChangesAndSync(summary, meta, cwd, {
412
+ onSyncStart: () => {
413
+ setSyncSteps([{ name: 'Syncing files...', status: 'running' }]);
414
+ },
187
415
  });
188
- }, 100);
189
- };
190
- useInput((input) => {
191
- if (state === 'confirming') {
192
- if (input === 'y' || input === 'Y') {
193
- handleConfirm();
194
- }
195
- else if (input === 'n' || input === 'N') {
196
- handleCancelConfirm();
197
- }
416
+ setSyncSteps([{ name: 'Changes applied', status: 'success' }]);
198
417
  }
199
- });
200
- const handleSubmit = async (selectedTrees) => {
201
- if (!meta) {
202
- setState('done');
203
- process.exit(0);
204
- return;
418
+ catch (err) {
419
+ setSyncSteps([
420
+ {
421
+ name: 'Apply changes',
422
+ status: 'failed',
423
+ error: err instanceof Error ? err.message : String(err),
424
+ },
425
+ ]);
205
426
  }
206
- const newPackageFiles = { ...packageFiles };
207
- for (const packageTree of selectedTrees) {
208
- const prefix = packageTree.id;
209
- if (!newPackageFiles[prefix])
210
- continue;
211
- const selectedPaths = new Set();
212
- if (packageTree.children) {
213
- for (const assetTypeNode of packageTree.children) {
214
- if (assetTypeNode.children) {
215
- for (const fileNode of assetTypeNode.children) {
216
- if (fileNode.disabled)
217
- continue;
218
- if (fileNode.selected) {
219
- selectedPaths.add(fileNode.path);
220
- }
221
- }
222
- }
223
- }
224
- }
225
- newPackageFiles[prefix] = {
226
- ...newPackageFiles[prefix],
227
- selected: selectedPaths,
228
- };
427
+ setTimeout(() => {
428
+ reloadMeta();
429
+ setPhase('package-detail');
430
+ }, 1000);
431
+ };
432
+ const handleEditAssetsCancel = () => {
433
+ setPhase('package-detail');
434
+ };
435
+ const handleStatusMenuSelect = (item) => {
436
+ if (item.value === 'check-remote') {
437
+ handleCheckRemoteVersions();
229
438
  }
230
- setPackageFiles(newPackageFiles);
231
- const fileOperations = [];
232
- const filesToDelete = [];
233
- const packagesToSync = [];
234
- for (const [prefix] of Object.entries(meta.packages)) {
235
- const packageTree = selectedTrees.find((t) => t.id === prefix);
236
- const packageInfo = meta.packages[prefix];
237
- const packageData = newPackageFiles[prefix];
238
- if (!packageTree || !packageTree.selected || !packageData) {
239
- for (const [assetType, files] of Object.entries(packageInfo.files)) {
240
- if (!Array.isArray(files))
241
- continue;
242
- const units = files;
243
- for (const unit of units) {
244
- fileOperations.push({
245
- type: 'remove',
246
- prefix,
247
- assetType,
248
- skillName: unit.name,
249
- });
250
- }
251
- const firstUnit = units[0];
252
- if (firstUnit?.transformed) {
253
- for (const unit of units) {
254
- const targetPath = path.join(cwd, '.claude', assetType, unit.transformed);
255
- filesToDelete.push(targetPath);
256
- }
257
- }
258
- else {
259
- const dirPath = path.join(cwd, '.claude', assetType, packageInfo.originalName);
260
- filesToDelete.push(dirPath);
261
- }
262
- }
263
- continue;
264
- }
265
- let hasChanges = false;
266
- const selectedByAssetType = {};
267
- for (const filePath of packageData.selected) {
268
- const [assetType, fileName] = filePath.split('/');
269
- if (!selectedByAssetType[assetType]) {
270
- selectedByAssetType[assetType] = new Set();
271
- }
272
- selectedByAssetType[assetType].add(fileName);
273
- }
274
- for (const [assetType, originalFiles] of Object.entries(packageInfo.files)) {
275
- const selectedFiles = selectedByAssetType[assetType];
276
- const units = Array.isArray(originalFiles) ? originalFiles : [];
277
- const firstUnit = units[0];
278
- const isFlat = !!firstUnit?.transformed;
279
- if (!selectedFiles || selectedFiles.size === 0) {
280
- hasChanges = true;
281
- for (const unit of units) {
282
- fileOperations.push({
283
- type: 'remove',
284
- prefix,
285
- assetType,
286
- skillName: unit.name,
287
- });
288
- }
289
- if (isFlat) {
290
- for (const unit of units) {
291
- const targetPath = path.join(cwd, '.claude', assetType, unit.transformed);
292
- filesToDelete.push(targetPath);
293
- }
294
- }
295
- else {
296
- const dirPath = path.join(cwd, '.claude', assetType, packageInfo.originalName);
297
- filesToDelete.push(dirPath);
298
- }
299
- continue;
300
- }
301
- const originalNames = new Set(units.map((u) => u.name));
302
- for (const unit of units) {
303
- if (!selectedFiles.has(unit.name)) {
304
- hasChanges = true;
305
- fileOperations.push({
306
- type: 'remove',
307
- prefix,
308
- assetType,
309
- skillName: unit.name,
310
- });
311
- if (unit.transformed) {
312
- const targetPath = path.join(cwd, '.claude', assetType, unit.transformed);
313
- filesToDelete.push(targetPath);
314
- }
315
- else {
316
- const filePath = path.join(cwd, '.claude', assetType, packageInfo.originalName, unit.name);
317
- filesToDelete.push(filePath);
318
- }
319
- }
320
- }
321
- for (const skillName of selectedFiles) {
322
- if (!originalNames.has(skillName)) {
323
- hasChanges = true;
324
- fileOperations.push({
325
- type: 'add',
326
- prefix,
327
- assetType,
328
- skillName,
329
- });
330
- }
331
- }
332
- }
333
- if (hasChanges) {
334
- packagesToSync.push({
335
- prefix,
336
- name: packageInfo.originalName,
337
- local: packageInfo.local,
338
- });
339
- }
439
+ else if (item.value === 'back') {
440
+ setRemoteVersions(null);
441
+ setCheckingRemote(false);
442
+ setPhase('main-menu');
340
443
  }
341
- setChangesSummary({ filesToDelete, fileOperations, packagesToSync });
342
- setState('confirming');
343
444
  };
344
- const handleConfirm = async () => {
345
- if (!changesSummary || !meta)
445
+ const handleCheckRemoteVersions = async () => {
446
+ if (!meta)
346
447
  return;
347
- setState('saving');
348
- for (const filePath of changesSummary.filesToDelete) {
349
- try {
350
- const stat = fs.statSync(filePath);
351
- if (stat.isDirectory()) {
352
- fs.rmSync(filePath, { recursive: true, force: true });
448
+ setCheckingRemote(true);
449
+ try {
450
+ const versions = {};
451
+ for (const [, pkgInfo] of Object.entries(meta.packages)) {
452
+ try {
453
+ const res = await fetch(`https://registry.npmjs.org/${pkgInfo.originalName}/latest`);
454
+ if (res.ok) {
455
+ const data = (await res.json());
456
+ versions[pkgInfo.originalName] = data.version;
457
+ }
353
458
  }
354
- else {
355
- fs.unlinkSync(filePath);
459
+ catch {
356
460
  }
357
461
  }
358
- catch (error) {
359
- if (error.code !== 'ENOENT') {
360
- console.error(`Failed to remove ${filePath}: ${error}`);
361
- }
362
- }
363
- }
364
- let updatedMeta = { ...meta };
365
- const hasAddOperations = changesSummary.fileOperations.some((op) => op.type === 'add');
366
- for (const op of changesSummary.fileOperations) {
367
- if (op.type === 'remove') {
368
- updatedMeta = removeSkillUnitFromPackage(updatedMeta, op.prefix, op.assetType, op.skillName);
369
- }
462
+ setRemoteVersions(versions);
370
463
  }
371
- for (const prefix of Object.keys(updatedMeta.packages)) {
372
- const pkg = updatedMeta.packages[prefix];
373
- if (!pkg.files || Object.keys(pkg.files).length === 0) {
374
- updatedMeta = removePackageFromMeta(updatedMeta, prefix);
375
- }
464
+ catch {
376
465
  }
377
- writeUnifiedSyncMeta(cwd, updatedMeta);
378
- if (hasAddOperations && changesSummary.packagesToSync.length > 0) {
379
- setState('syncing');
380
- for (const { prefix, name, local } of changesSummary.packagesToSync) {
381
- try {
382
- const removedFiles = changesSummary.fileOperations
383
- .filter((op) => op.type === 'remove' && op.prefix === prefix)
384
- .map((op) => `${op.assetType}/${op.skillName}`);
385
- const exclusions = removedFiles.length > 0
386
- ? { directories: [], files: removedFiles }
387
- : undefined;
388
- await syncPackage(name, {
389
- force: true,
390
- dryRun: false,
391
- local: local ?? false,
392
- ref: undefined,
393
- flat: undefined,
394
- }, cwd, exclusions);
395
- }
396
- catch (error) {
397
- console.error(`Failed to sync ${name}: ${error}`);
398
- }
399
- }
466
+ finally {
467
+ setCheckingRemote(false);
400
468
  }
401
- setState('done');
402
- setTimeout(() => process.exit(0), 500);
403
- };
404
- const handleCancelConfirm = () => {
405
- setChangesSummary(null);
406
- setState('selecting');
407
- };
408
- const handleCancel = () => {
409
- setState('done');
410
- process.exit(0);
411
469
  };
412
- if (state === 'loading') {
470
+ if (phase === 'loading') {
413
471
  return (jsx(Box, { children: jsxs(Text, { color: "cyan", children: [jsx(Spinner, { type: "dots" }), " Loading synced packages..."] }) }));
414
472
  }
415
- if (state === 'confirming' && changesSummary) {
416
- return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsx(Text, { bold: true, children: "Changes to be applied:" }) }), changesSummary.filesToDelete.length > 0 && (jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [jsxs(Text, { color: "red", bold: true, children: ["Files to delete (", changesSummary.filesToDelete.length, "):"] }), changesSummary.filesToDelete.slice(0, 10).map((filePath) => (jsx(Box, { marginLeft: 2, children: jsxs(Text, { color: "red", children: ["- ", filePath.replace(cwd, '.')] }) }, filePath))), changesSummary.filesToDelete.length > 10 && (jsx(Box, { marginLeft: 2, children: jsxs(Text, { dimColor: true, children: ["... and ", changesSummary.filesToDelete.length - 10, " more"] }) }))] })), changesSummary.packagesToSync.length > 0 && (jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [jsxs(Text, { color: "green", bold: true, children: ["Packages to sync (", changesSummary.packagesToSync.length, "):"] }), changesSummary.packagesToSync.map(({ name }) => (jsx(Box, { marginLeft: 2, children: jsxs(Text, { color: "green", children: ["+ ", name] }) }, name)))] })), changesSummary.filesToDelete.length === 0 &&
417
- changesSummary.packagesToSync.length === 0 && (jsx(Box, { marginBottom: 1, children: jsx(Text, { dimColor: true, children: "No changes to apply" }) })), jsx(Box, { marginTop: 1, children: jsx(Text, { bold: true, children: "Apply changes and sync? (y/n): " }) })] }));
473
+ if (phase === 'error') {
474
+ return (jsxs(Box, { flexDirection: "column", children: [jsxs(Text, { color: "red", children: ["Error: ", errorMessage ?? 'Unknown error'] }), jsx(Box, { marginTop: 1, children: jsxs(Text, { dimColor: true, children: ["Press ", jsx(Text, { bold: true, children: "ESC" }), " to exit"] }) })] }));
475
+ }
476
+ if (phase === 'done') {
477
+ if (!meta || Object.keys(meta.packages).length === 0) {
478
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(Text, { color: "yellow", children: "No packages synced yet." }), jsx(Text, { dimColor: true, children: "Exiting..." })] }));
479
+ }
480
+ return null;
481
+ }
482
+ if (phase === 'main-menu' && meta) {
483
+ const packageCount = Object.keys(meta.packages).length;
484
+ const menuItems = [
485
+ { label: `Packages (${packageCount})`, value: 'packages' },
486
+ { label: 'Status', value: 'status' },
487
+ { label: 'Exit', value: 'exit' },
488
+ ];
489
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(SelectInput, { items: menuItems, onSelect: handleMainMenuSelect, itemComponent: MenuItem }), jsx(Box, { marginTop: 1, children: jsxs(Text, { dimColor: true, children: ["Press ", jsx(Text, { bold: true, children: "ESC" }), " to exit"] }) })] }));
490
+ }
491
+ if (phase === 'package-list' && meta) {
492
+ const items = Object.entries(meta.packages).map(([prefix, pkgInfo]) => {
493
+ let totalAssets = 0;
494
+ for (const files of Object.values(pkgInfo.files)) {
495
+ totalAssets += Array.isArray(files) ? files.length : 0;
496
+ }
497
+ const syncDate = formatSyncDate(meta.syncedAt);
498
+ return {
499
+ label: `${pkgInfo.originalName}@${pkgInfo.version} \u2022 ${totalAssets} assets \u2022 ${syncDate}`,
500
+ value: prefix,
501
+ };
502
+ });
503
+ items.push({ label: '\u2190 Back', value: 'back' });
504
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsx(Text, { bold: true, children: "Synced Packages" }) }), jsx(SelectInput, { items: items, onSelect: handlePackageSelect, itemComponent: MenuItem }), jsx(Box, { marginTop: 1, children: jsxs(Text, { dimColor: true, children: ["Press ", jsx(Text, { bold: true, children: "ESC" }), " to go back"] }) })] }));
505
+ }
506
+ if (phase === 'package-detail' && selectedPackage && meta) {
507
+ const assetEntries = Object.entries(selectedPackage.files);
508
+ const actionItems = [
509
+ { label: 'Sync', value: 'sync' },
510
+ { label: 'Update Version', value: 'update' },
511
+ { label: 'Edit Assets', value: 'edit-assets' },
512
+ { label: 'Remove', value: 'remove' },
513
+ { label: '\u2190 Back', value: 'back' },
514
+ ];
515
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsxs(Text, { bold: true, children: [selectedPackage.originalName, "@", selectedPackage.version] }) }), jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [jsxs(Text, { children: [jsx(Text, { dimColor: true, children: "Source: " }), jsx(Text, { children: selectedPackage.local ? 'local' : 'remote' })] }), jsxs(Text, { children: [jsx(Text, { dimColor: true, children: "Synced: " }), jsx(Text, { children: formatSyncDate(selectedPackage.syncedAt) })] }), jsxs(Text, { children: [jsx(Text, { dimColor: true, children: "Total: " }), jsxs(Text, { children: [selectedPackage.totalAssets, " assets"] })] }), assetEntries.map(([assetType, files]) => {
516
+ const count = Array.isArray(files) ? files.length : 0;
517
+ return (jsxs(Text, { children: [jsxs(Text, { dimColor: true, children: [" ", assetType, ": "] }), jsxs(Text, { children: [count, " files"] })] }, assetType));
518
+ })] }), jsx(SelectInput, { items: actionItems, onSelect: handleDetailActionSelect, itemComponent: MenuItem }), jsx(Box, { marginTop: 1, children: jsxs(Text, { dimColor: true, children: ["Press ", jsx(Text, { bold: true, children: "ESC" }), " to go back"] }) })] }));
519
+ }
520
+ if (phase === 'sync-action') {
521
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsx(Text, { bold: true, children: "Syncing Package" }) }), jsx(StepRunner, { steps: syncSteps, currentStep: 0, total: syncSteps.length })] }));
418
522
  }
419
- if (state === 'saving') {
420
- return (jsx(Box, { children: jsxs(Text, { color: "green", children: [jsx(Spinner, { type: "dots" }), " Saving changes..."] }) }));
523
+ if (phase === 'remove-action' && selectedPackage) {
524
+ return (jsx(Box, { flexDirection: "column", children: jsx(Confirm, { message: `Remove ${selectedPackage.originalName}? This will delete all synced files.`, onConfirm: handleRemoveConfirm, defaultYes: false }) }));
421
525
  }
422
- if (state === 'syncing') {
423
- return (jsx(Box, { children: jsxs(Text, { color: "cyan", children: [jsx(Spinner, { type: "dots" }), " Syncing packages..."] }) }));
526
+ if (phase === 'update-action') {
527
+ if (updateChecking) {
528
+ return (jsx(Box, { children: jsxs(Text, { color: "cyan", children: [jsx(Spinner, { type: "dots" }), " Checking installed version..."] }) }));
529
+ }
530
+ if (updateSyncing) {
531
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsx(Text, { bold: true, children: "Updating Package" }) }), jsx(StepRunner, { steps: updateSyncSteps, currentStep: 0, total: updateSyncSteps.length })] }));
532
+ }
533
+ if (updateConfirmPending && updateResult) {
534
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsxs(Text, { children: [jsx(Text, { dimColor: true, children: "Current sync: " }), jsx(Text, { color: "yellow", children: updateResult.oldVersion }), jsx(Text, { dimColor: true, children: " \u2192 Installed: " }), jsx(Text, { color: "green", children: updateResult.newVersion })] }) }), jsx(Confirm, { message: "Update version and re-sync?", onConfirm: handleUpdateConfirm, defaultYes: true })] }));
535
+ }
536
+ if (updateResult && !updateResult.versionChanged) {
537
+ if (updateResult.notFound) {
538
+ return (jsx(Box, { children: jsx(Text, { color: "yellow", children: "Package not found in node_modules or workspace" }) }));
539
+ }
540
+ return (jsx(Box, { children: jsxs(Text, { color: "green", children: ["Already up to date (version ", updateResult.oldVersion, ")"] }) }));
541
+ }
542
+ return null;
424
543
  }
425
- if (state === 'done' &&
426
- (!meta || Object.keys(meta?.packages || {}).length === 0)) {
427
- return (jsx(Box, { children: jsx(Text, { color: "yellow", children: "No packages synced yet." }) }));
544
+ if (phase === 'edit-assets') {
545
+ if (editTrees.length === 0) {
546
+ return (jsx(Box, { children: jsxs(Text, { color: "cyan", children: [jsx(Spinner, { type: "dots" }), " Loading available assets..."] }) }));
547
+ }
548
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsx(Text, { bold: true, children: "Edit Assets" }) }), jsx(Box, { marginBottom: 1, children: jsx(Text, { dimColor: true, children: "Space: toggle | Enter: save | Esc: cancel" }) }), jsx(TreeSelect, { trees: editTrees, onSubmit: handleEditAssetsSubmit, onCancel: handleEditAssetsCancel })] }));
428
549
  }
429
- if (state === 'selecting') {
430
- const trees = buildTrees();
431
- return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsx(Text, { bold: true, children: "Edit synced packages" }) }), jsx(Box, { marginBottom: 1, children: jsx(Text, { dimColor: true, children: "\u2191/\u2193: navigate \u2022 Space: toggle \u2022 \u2192/\u2190: expand/collapse \u2022 r: refresh (30s cooldown) \u2022 Enter: save \u2022 Esc: cancel" }) }), jsx(TreeSelect, { trees: trees, onSubmit: handleSubmit, onCancel: handleCancel, onRefresh: handleRefresh })] }));
550
+ if (phase === 'status-view' && meta) {
551
+ const packageEntries = Object.entries(meta.packages);
552
+ const statusRows = packageEntries.map(([, pkgInfo]) => {
553
+ let totalAssets = 0;
554
+ for (const files of Object.values(pkgInfo.files)) {
555
+ totalAssets += Array.isArray(files) ? files.length : 0;
556
+ }
557
+ const remoteVer = remoteVersions?.[pkgInfo.originalName] ?? '-';
558
+ return [
559
+ pkgInfo.originalName,
560
+ pkgInfo.version,
561
+ remoteVer,
562
+ String(totalAssets),
563
+ formatSyncDate(meta.syncedAt),
564
+ ];
565
+ });
566
+ const statusMenuItems = [
567
+ { label: 'Check Remote Versions', value: 'check-remote' },
568
+ { label: '\u2190 Back', value: 'back' },
569
+ ];
570
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(Box, { marginBottom: 1, children: jsx(Text, { bold: true, children: "Sync Status" }) }), jsx(Table, { headers: ['Package', 'Local', 'Remote', 'Assets', 'Synced'], rows: statusRows }), checkingRemote && (jsx(Box, { marginTop: 1, children: jsxs(Text, { color: "cyan", children: [jsx(Spinner, { type: "dots" }), " Checking remote versions..."] }) })), jsx(Box, { marginTop: 1, children: jsx(SelectInput, { items: statusMenuItems, onSelect: handleStatusMenuSelect, itemComponent: MenuItem }) }), jsx(Box, { marginTop: 1, children: jsxs(Text, { dimColor: true, children: ["Press ", jsx(Text, { bold: true, children: "ESC" }), " to go back"] }) })] }));
432
571
  }
433
- return (jsx(Box, { children: jsx(Text, { color: "green", children: "\u2713 Changes saved" }) }));
572
+ return null;
434
573
  };
574
+ function buildFallbackTrees(prefix, packageInfo) {
575
+ const assetTypeTrees = [];
576
+ for (const [assetType, files] of Object.entries(packageInfo.files)) {
577
+ const units = Array.isArray(files) ? files : [];
578
+ if (units.length === 0)
579
+ continue;
580
+ const skillNodes = units.map((unit) => {
581
+ const displayName = unit.transformed ?? unit.name;
582
+ if (unit.isDirectory) {
583
+ return {
584
+ id: `${prefix}/${assetType}/${displayName}`,
585
+ label: displayName,
586
+ path: `${assetType}/${unit.name}`,
587
+ type: 'skill-directory',
588
+ viewOnly: true,
589
+ selected: true,
590
+ expanded: false,
591
+ children: (unit.internalFiles || []).map((f) => ({
592
+ id: `${prefix}/${assetType}/${displayName}/${f}`,
593
+ label: f,
594
+ path: `${assetType}/${unit.name}/${f}`,
595
+ type: 'file',
596
+ selected: true,
597
+ expanded: false,
598
+ disabled: true,
599
+ })),
600
+ };
601
+ }
602
+ return {
603
+ id: `${prefix}/${assetType}/${displayName}`,
604
+ label: displayName,
605
+ path: `${assetType}/${unit.name}`,
606
+ type: 'file',
607
+ selected: true,
608
+ expanded: false,
609
+ };
610
+ });
611
+ assetTypeTrees.push({
612
+ id: `${prefix}/${assetType}`,
613
+ label: assetType,
614
+ path: assetType,
615
+ type: 'directory',
616
+ children: skillNodes,
617
+ selected: true,
618
+ expanded: true,
619
+ });
620
+ }
621
+ return assetTypeTrees;
622
+ }
623
+ function buildEditTrees(prefix, packageInfo, available, selectedFiles) {
624
+ const assetTypeTrees = available.map((assetTypeTree) => {
625
+ const clonedTree = { ...assetTypeTree };
626
+ if (clonedTree.children) {
627
+ clonedTree.children = clonedTree.children.map((fileNode) => {
628
+ const selected = selectedFiles.has(fileNode.path);
629
+ return { ...fileNode, selected };
630
+ });
631
+ const anySelected = clonedTree.children.some((f) => f.selected);
632
+ clonedTree.selected = anySelected;
633
+ }
634
+ return clonedTree;
635
+ });
636
+ return [
637
+ {
638
+ id: prefix,
639
+ label: `${packageInfo.originalName}@${packageInfo.version}`,
640
+ path: packageInfo.originalName,
641
+ type: 'directory',
642
+ children: assetTypeTrees,
643
+ selected: true,
644
+ expanded: true,
645
+ },
646
+ ];
647
+ }
435
648
 
436
649
  export { ListCommand };