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