@slats/claude-assets-sync 0.0.4 → 0.0.5

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 (43) hide show
  1. package/dist/commands/add.cjs +2 -7
  2. package/dist/commands/add.mjs +2 -7
  3. package/dist/commands/index.d.ts +22 -0
  4. package/dist/commands/remove.cjs +6 -6
  5. package/dist/commands/remove.mjs +6 -6
  6. package/dist/commands/types.d.ts +2 -4
  7. package/dist/commands/update.cjs +178 -0
  8. package/dist/commands/update.d.ts +13 -0
  9. package/dist/commands/update.mjs +176 -0
  10. package/dist/components/add/AddCommand.cjs +11 -0
  11. package/dist/components/add/AddCommand.mjs +11 -0
  12. package/dist/components/list/ListCommand.cjs +65 -47
  13. package/dist/components/list/ListCommand.mjs +66 -48
  14. package/dist/components/status/StatusDisplay.cjs +4 -3
  15. package/dist/components/status/StatusDisplay.d.ts +2 -4
  16. package/dist/components/status/StatusDisplay.mjs +4 -3
  17. package/dist/components/tree/AssetTreeNode.cjs +21 -4
  18. package/dist/components/tree/AssetTreeNode.d.ts +1 -1
  19. package/dist/components/tree/AssetTreeNode.mjs +21 -4
  20. package/dist/components/tree/TreeSelect.cjs +14 -6
  21. package/dist/components/tree/TreeSelect.mjs +14 -6
  22. package/dist/core/cli.cjs +18 -0
  23. package/dist/core/cli.mjs +18 -0
  24. package/dist/core/constants.cjs +4 -1
  25. package/dist/core/constants.d.ts +8 -1
  26. package/dist/core/constants.mjs +4 -1
  27. package/dist/core/filesystem.cjs +7 -13
  28. package/dist/core/filesystem.mjs +7 -13
  29. package/dist/core/migration.cjs +10 -9
  30. package/dist/core/migration.mjs +10 -9
  31. package/dist/core/packageScanner.cjs +76 -0
  32. package/dist/core/packageScanner.d.ts +6 -1
  33. package/dist/core/packageScanner.mjs +76 -1
  34. package/dist/core/sync.cjs +53 -30
  35. package/dist/core/sync.mjs +53 -30
  36. package/dist/core/syncMeta.cjs +153 -9
  37. package/dist/core/syncMeta.d.ts +22 -18
  38. package/dist/core/syncMeta.mjs +149 -9
  39. package/dist/utils/types.d.ts +24 -7
  40. package/dist/version.cjs +1 -1
  41. package/dist/version.d.ts +1 -1
  42. package/dist/version.mjs +1 -1
  43. package/package.json +1 -1
@@ -50,10 +50,10 @@ const ListCommand = ({ cwd }) => {
50
50
  const clonedTree = { ...assetTypeTree };
51
51
  if (clonedTree.children) {
52
52
  clonedTree.children = clonedTree.children.map((fileNode) => {
53
- const filePath = `${assetTypeTree.label}/${fileNode.label}`;
53
+ const selected = packageData.selected.has(fileNode.path);
54
54
  return {
55
55
  ...fileNode,
56
- selected: packageData.selected.has(filePath),
56
+ selected,
57
57
  };
58
58
  });
59
59
  const anySelected = clonedTree.children.some((f) => f.selected);
@@ -112,15 +112,35 @@ const ListCommand = ({ cwd }) => {
112
112
  catch {
113
113
  const assetTypeTrees = [];
114
114
  for (const [assetType, files] of Object.entries(packageInfo.files)) {
115
- const fileArray = Array.isArray(files) ? files : [];
116
- if (fileArray.length === 0)
115
+ const units = Array.isArray(files) ? files : [];
116
+ if (units.length === 0)
117
117
  continue;
118
- const fileNodes = fileArray.map((file) => {
119
- const fileName = typeof file === 'string' ? file : file.transformed;
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
+ }
120
140
  return {
121
- id: `${prefix}/${assetType}/${fileName}`,
122
- label: fileName,
123
- path: `${assetType}/${fileName}`,
141
+ id: `${prefix}/${assetType}/${displayName}`,
142
+ label: displayName,
143
+ path: `${assetType}/${unit.name}`,
124
144
  type: 'file',
125
145
  selected: true,
126
146
  expanded: false,
@@ -131,7 +151,7 @@ const ListCommand = ({ cwd }) => {
131
151
  label: assetType,
132
152
  path: assetType,
133
153
  type: 'directory',
134
- children: fileNodes,
154
+ children: skillNodes,
135
155
  selected: true,
136
156
  expanded: true,
137
157
  });
@@ -144,11 +164,10 @@ const ListCommand = ({ cwd }) => {
144
164
  for (const { prefix, available, packageInfo } of results) {
145
165
  const excludedFiles = new Set(packageInfo.exclusions?.files || []);
146
166
  const metaSelectedFiles = new Set(Object.entries(packageInfo.files).flatMap(([assetType, files]) => (Array.isArray(files) ? files : [])
147
- .map((f) => {
148
- const fileName = typeof f === 'string' ? f : f.original;
149
- const filePath = `${assetType}/${fileName}`;
167
+ .map((unit) => {
168
+ const filePath = `${assetType}/${unit.name}`;
150
169
  if (excludedFiles.has(filePath) ||
151
- excludedFiles.has(fileName)) {
170
+ excludedFiles.has(unit.name)) {
152
171
  return null;
153
172
  }
154
173
  return filePath;
@@ -216,8 +235,10 @@ const ListCommand = ({ cwd }) => {
216
235
  for (const assetTypeNode of packageTree.children) {
217
236
  if (assetTypeNode.children) {
218
237
  for (const fileNode of assetTypeNode.children) {
238
+ if (fileNode.disabled)
239
+ continue;
219
240
  if (fileNode.selected) {
220
- selectedPaths.add(`${assetTypeNode.label}/${fileNode.label}`);
241
+ selectedPaths.add(fileNode.path);
221
242
  }
222
243
  }
223
244
  }
@@ -240,21 +261,20 @@ const ListCommand = ({ cwd }) => {
240
261
  for (const [assetType, files] of Object.entries(packageInfo.files)) {
241
262
  if (!Array.isArray(files))
242
263
  continue;
243
- const firstFile = files[0];
244
- const isFlat = typeof firstFile === 'object' && 'transformed' in firstFile;
245
- for (const file of files) {
246
- const fileName = typeof file === 'string' ? file : file.original;
264
+ const units = files;
265
+ for (const unit of units) {
247
266
  fileOperations.push({
248
267
  type: 'remove',
249
268
  prefix,
250
269
  assetType,
251
- fileName,
270
+ skillName: unit.name,
252
271
  });
253
272
  }
254
- if (isFlat) {
255
- for (const file of files) {
256
- const filePath = path__namespace.join(cwd, '.claude', assetType, file.transformed);
257
- filesToDelete.push(filePath);
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);
258
278
  }
259
279
  }
260
280
  else {
@@ -275,24 +295,23 @@ const ListCommand = ({ cwd }) => {
275
295
  }
276
296
  for (const [assetType, originalFiles] of Object.entries(packageInfo.files)) {
277
297
  const selectedFiles = selectedByAssetType[assetType];
278
- const fileArray = Array.isArray(originalFiles) ? originalFiles : [];
279
- const firstFile = fileArray[0];
280
- const isFlat = typeof firstFile === 'object' && 'transformed' in firstFile;
298
+ const units = Array.isArray(originalFiles) ? originalFiles : [];
299
+ const firstUnit = units[0];
300
+ const isFlat = !!firstUnit?.transformed;
281
301
  if (!selectedFiles || selectedFiles.size === 0) {
282
302
  hasChanges = true;
283
- for (const file of fileArray) {
284
- const fileName = typeof file === 'string' ? file : file.original;
303
+ for (const unit of units) {
285
304
  fileOperations.push({
286
305
  type: 'remove',
287
306
  prefix,
288
307
  assetType,
289
- fileName,
308
+ skillName: unit.name,
290
309
  });
291
310
  }
292
311
  if (isFlat) {
293
- for (const file of fileArray) {
294
- const filePath = path__namespace.join(cwd, '.claude', assetType, file.transformed);
295
- filesToDelete.push(filePath);
312
+ for (const unit of units) {
313
+ const targetPath = path__namespace.join(cwd, '.claude', assetType, unit.transformed);
314
+ filesToDelete.push(targetPath);
296
315
  }
297
316
  }
298
317
  else {
@@ -301,35 +320,34 @@ const ListCommand = ({ cwd }) => {
301
320
  }
302
321
  continue;
303
322
  }
304
- const originalFileNames = new Set(fileArray.map((f) => (typeof f === 'string' ? f : f.transformed)));
305
- for (const file of fileArray) {
306
- const fileName = typeof file === 'string' ? file : file.original;
307
- if (!selectedFiles.has(fileName)) {
323
+ const originalNames = new Set(units.map((u) => u.name));
324
+ for (const unit of units) {
325
+ if (!selectedFiles.has(unit.name)) {
308
326
  hasChanges = true;
309
327
  fileOperations.push({
310
328
  type: 'remove',
311
329
  prefix,
312
330
  assetType,
313
- fileName,
331
+ skillName: unit.name,
314
332
  });
315
- if (isFlat) {
316
- const filePath = path__namespace.join(cwd, '.claude', assetType, file.transformed);
317
- filesToDelete.push(filePath);
333
+ if (unit.transformed) {
334
+ const targetPath = path__namespace.join(cwd, '.claude', assetType, unit.transformed);
335
+ filesToDelete.push(targetPath);
318
336
  }
319
337
  else {
320
- const filePath = path__namespace.join(cwd, '.claude', assetType, packageInfo.originalName, fileName);
338
+ const filePath = path__namespace.join(cwd, '.claude', assetType, packageInfo.originalName, unit.name);
321
339
  filesToDelete.push(filePath);
322
340
  }
323
341
  }
324
342
  }
325
- for (const fileName of selectedFiles) {
326
- if (!originalFileNames.has(fileName)) {
343
+ for (const skillName of selectedFiles) {
344
+ if (!originalNames.has(skillName)) {
327
345
  hasChanges = true;
328
346
  fileOperations.push({
329
347
  type: 'add',
330
348
  prefix,
331
349
  assetType,
332
- fileName,
350
+ skillName,
333
351
  });
334
352
  }
335
353
  }
@@ -369,7 +387,7 @@ const ListCommand = ({ cwd }) => {
369
387
  const hasAddOperations = changesSummary.fileOperations.some((op) => op.type === 'add');
370
388
  for (const op of changesSummary.fileOperations) {
371
389
  if (op.type === 'remove') {
372
- updatedMeta = syncMeta.removeFileFromPackage(updatedMeta, op.prefix, op.assetType, op.fileName);
390
+ updatedMeta = syncMeta.removeSkillUnitFromPackage(updatedMeta, op.prefix, op.assetType, op.skillName);
373
391
  }
374
392
  }
375
393
  for (const prefix of Object.keys(updatedMeta.packages)) {
@@ -385,7 +403,7 @@ const ListCommand = ({ cwd }) => {
385
403
  try {
386
404
  const removedFiles = changesSummary.fileOperations
387
405
  .filter((op) => op.type === 'remove' && op.prefix === prefix)
388
- .map((op) => `${op.assetType}/${op.fileName}`);
406
+ .map((op) => `${op.assetType}/${op.skillName}`);
389
407
  const exclusions = removedFiles.length > 0
390
408
  ? { directories: [], files: removedFiles }
391
409
  : undefined;
@@ -6,7 +6,7 @@ import Spinner from 'ink-spinner';
6
6
  import { useState, useRef, useCallback, useEffect } from 'react';
7
7
  import { scanPackageAssets } from '../../core/packageScanner.mjs';
8
8
  import { syncPackage } from '../../core/sync.mjs';
9
- import { readUnifiedSyncMeta, removeFileFromPackage, removePackageFromMeta, writeUnifiedSyncMeta } from '../../core/syncMeta.mjs';
9
+ import { readUnifiedSyncMeta, removeSkillUnitFromPackage, removePackageFromMeta, writeUnifiedSyncMeta } from '../../core/syncMeta.mjs';
10
10
  import { TreeSelect } from '../tree/TreeSelect.mjs';
11
11
 
12
12
  const ListCommand = ({ cwd }) => {
@@ -28,10 +28,10 @@ const ListCommand = ({ cwd }) => {
28
28
  const clonedTree = { ...assetTypeTree };
29
29
  if (clonedTree.children) {
30
30
  clonedTree.children = clonedTree.children.map((fileNode) => {
31
- const filePath = `${assetTypeTree.label}/${fileNode.label}`;
31
+ const selected = packageData.selected.has(fileNode.path);
32
32
  return {
33
33
  ...fileNode,
34
- selected: packageData.selected.has(filePath),
34
+ selected,
35
35
  };
36
36
  });
37
37
  const anySelected = clonedTree.children.some((f) => f.selected);
@@ -90,15 +90,35 @@ const ListCommand = ({ cwd }) => {
90
90
  catch {
91
91
  const assetTypeTrees = [];
92
92
  for (const [assetType, files] of Object.entries(packageInfo.files)) {
93
- const fileArray = Array.isArray(files) ? files : [];
94
- if (fileArray.length === 0)
93
+ const units = Array.isArray(files) ? files : [];
94
+ if (units.length === 0)
95
95
  continue;
96
- const fileNodes = fileArray.map((file) => {
97
- const fileName = typeof file === 'string' ? file : file.transformed;
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
+ }
98
118
  return {
99
- id: `${prefix}/${assetType}/${fileName}`,
100
- label: fileName,
101
- path: `${assetType}/${fileName}`,
119
+ id: `${prefix}/${assetType}/${displayName}`,
120
+ label: displayName,
121
+ path: `${assetType}/${unit.name}`,
102
122
  type: 'file',
103
123
  selected: true,
104
124
  expanded: false,
@@ -109,7 +129,7 @@ const ListCommand = ({ cwd }) => {
109
129
  label: assetType,
110
130
  path: assetType,
111
131
  type: 'directory',
112
- children: fileNodes,
132
+ children: skillNodes,
113
133
  selected: true,
114
134
  expanded: true,
115
135
  });
@@ -122,11 +142,10 @@ const ListCommand = ({ cwd }) => {
122
142
  for (const { prefix, available, packageInfo } of results) {
123
143
  const excludedFiles = new Set(packageInfo.exclusions?.files || []);
124
144
  const metaSelectedFiles = new Set(Object.entries(packageInfo.files).flatMap(([assetType, files]) => (Array.isArray(files) ? files : [])
125
- .map((f) => {
126
- const fileName = typeof f === 'string' ? f : f.original;
127
- const filePath = `${assetType}/${fileName}`;
145
+ .map((unit) => {
146
+ const filePath = `${assetType}/${unit.name}`;
128
147
  if (excludedFiles.has(filePath) ||
129
- excludedFiles.has(fileName)) {
148
+ excludedFiles.has(unit.name)) {
130
149
  return null;
131
150
  }
132
151
  return filePath;
@@ -194,8 +213,10 @@ const ListCommand = ({ cwd }) => {
194
213
  for (const assetTypeNode of packageTree.children) {
195
214
  if (assetTypeNode.children) {
196
215
  for (const fileNode of assetTypeNode.children) {
216
+ if (fileNode.disabled)
217
+ continue;
197
218
  if (fileNode.selected) {
198
- selectedPaths.add(`${assetTypeNode.label}/${fileNode.label}`);
219
+ selectedPaths.add(fileNode.path);
199
220
  }
200
221
  }
201
222
  }
@@ -218,21 +239,20 @@ const ListCommand = ({ cwd }) => {
218
239
  for (const [assetType, files] of Object.entries(packageInfo.files)) {
219
240
  if (!Array.isArray(files))
220
241
  continue;
221
- const firstFile = files[0];
222
- const isFlat = typeof firstFile === 'object' && 'transformed' in firstFile;
223
- for (const file of files) {
224
- const fileName = typeof file === 'string' ? file : file.original;
242
+ const units = files;
243
+ for (const unit of units) {
225
244
  fileOperations.push({
226
245
  type: 'remove',
227
246
  prefix,
228
247
  assetType,
229
- fileName,
248
+ skillName: unit.name,
230
249
  });
231
250
  }
232
- if (isFlat) {
233
- for (const file of files) {
234
- const filePath = path.join(cwd, '.claude', assetType, file.transformed);
235
- filesToDelete.push(filePath);
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);
236
256
  }
237
257
  }
238
258
  else {
@@ -253,24 +273,23 @@ const ListCommand = ({ cwd }) => {
253
273
  }
254
274
  for (const [assetType, originalFiles] of Object.entries(packageInfo.files)) {
255
275
  const selectedFiles = selectedByAssetType[assetType];
256
- const fileArray = Array.isArray(originalFiles) ? originalFiles : [];
257
- const firstFile = fileArray[0];
258
- const isFlat = typeof firstFile === 'object' && 'transformed' in firstFile;
276
+ const units = Array.isArray(originalFiles) ? originalFiles : [];
277
+ const firstUnit = units[0];
278
+ const isFlat = !!firstUnit?.transformed;
259
279
  if (!selectedFiles || selectedFiles.size === 0) {
260
280
  hasChanges = true;
261
- for (const file of fileArray) {
262
- const fileName = typeof file === 'string' ? file : file.original;
281
+ for (const unit of units) {
263
282
  fileOperations.push({
264
283
  type: 'remove',
265
284
  prefix,
266
285
  assetType,
267
- fileName,
286
+ skillName: unit.name,
268
287
  });
269
288
  }
270
289
  if (isFlat) {
271
- for (const file of fileArray) {
272
- const filePath = path.join(cwd, '.claude', assetType, file.transformed);
273
- filesToDelete.push(filePath);
290
+ for (const unit of units) {
291
+ const targetPath = path.join(cwd, '.claude', assetType, unit.transformed);
292
+ filesToDelete.push(targetPath);
274
293
  }
275
294
  }
276
295
  else {
@@ -279,35 +298,34 @@ const ListCommand = ({ cwd }) => {
279
298
  }
280
299
  continue;
281
300
  }
282
- const originalFileNames = new Set(fileArray.map((f) => (typeof f === 'string' ? f : f.transformed)));
283
- for (const file of fileArray) {
284
- const fileName = typeof file === 'string' ? file : file.original;
285
- if (!selectedFiles.has(fileName)) {
301
+ const originalNames = new Set(units.map((u) => u.name));
302
+ for (const unit of units) {
303
+ if (!selectedFiles.has(unit.name)) {
286
304
  hasChanges = true;
287
305
  fileOperations.push({
288
306
  type: 'remove',
289
307
  prefix,
290
308
  assetType,
291
- fileName,
309
+ skillName: unit.name,
292
310
  });
293
- if (isFlat) {
294
- const filePath = path.join(cwd, '.claude', assetType, file.transformed);
295
- filesToDelete.push(filePath);
311
+ if (unit.transformed) {
312
+ const targetPath = path.join(cwd, '.claude', assetType, unit.transformed);
313
+ filesToDelete.push(targetPath);
296
314
  }
297
315
  else {
298
- const filePath = path.join(cwd, '.claude', assetType, packageInfo.originalName, fileName);
316
+ const filePath = path.join(cwd, '.claude', assetType, packageInfo.originalName, unit.name);
299
317
  filesToDelete.push(filePath);
300
318
  }
301
319
  }
302
320
  }
303
- for (const fileName of selectedFiles) {
304
- if (!originalFileNames.has(fileName)) {
321
+ for (const skillName of selectedFiles) {
322
+ if (!originalNames.has(skillName)) {
305
323
  hasChanges = true;
306
324
  fileOperations.push({
307
325
  type: 'add',
308
326
  prefix,
309
327
  assetType,
310
- fileName,
328
+ skillName,
311
329
  });
312
330
  }
313
331
  }
@@ -347,7 +365,7 @@ const ListCommand = ({ cwd }) => {
347
365
  const hasAddOperations = changesSummary.fileOperations.some((op) => op.type === 'add');
348
366
  for (const op of changesSummary.fileOperations) {
349
367
  if (op.type === 'remove') {
350
- updatedMeta = removeFileFromPackage(updatedMeta, op.prefix, op.assetType, op.fileName);
368
+ updatedMeta = removeSkillUnitFromPackage(updatedMeta, op.prefix, op.assetType, op.skillName);
351
369
  }
352
370
  }
353
371
  for (const prefix of Object.keys(updatedMeta.packages)) {
@@ -363,7 +381,7 @@ const ListCommand = ({ cwd }) => {
363
381
  try {
364
382
  const removedFiles = changesSummary.fileOperations
365
383
  .filter((op) => op.type === 'remove' && op.prefix === prefix)
366
- .map((op) => `${op.assetType}/${op.fileName}`);
384
+ .map((op) => `${op.assetType}/${op.skillName}`);
367
385
  const exclusions = removedFiles.length > 0
368
386
  ? { directories: [], files: removedFiles }
369
387
  : undefined;
@@ -14,9 +14,10 @@ const StatusDisplay = ({ packages, loading, summary, }) => {
14
14
  return (jsxRuntime.jsxs(ink.Box, { flexDirection: "column", marginBottom: 1, children: [jsxRuntime.jsx(StatusTreeNode.StatusTreeNode, { label: pkg.name, depth: 0, type: "package", expanded: true, status: pkg.status, version: pkg.localVersion, remoteVersion: pkg.remoteVersion, fileCount: pkg.fileCount }), assetTypes.map((assetType) => {
15
15
  const files = pkg.files[assetType];
16
16
  const fileArray = Array.isArray(files) ? files : [];
17
- return (jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [jsxRuntime.jsx(StatusTreeNode.StatusTreeNode, { label: assetType, depth: 1, type: "assetType", expanded: true, fileCount: fileArray.length }), fileArray.map((file, index) => {
18
- const fileName = typeof file === 'string' ? file : file.transformed;
19
- return (jsxRuntime.jsx(StatusTreeNode.StatusTreeNode, { label: fileName, depth: 2, type: "file" }, `${assetType}-${index}`));
17
+ return (jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [jsxRuntime.jsx(StatusTreeNode.StatusTreeNode, { label: assetType, depth: 1, type: "assetType", expanded: true, fileCount: fileArray.length }), fileArray.map((unit, index) => {
18
+ const displayName = unit.transformed ?? unit.name;
19
+ const label = unit.isDirectory ? `${displayName}/` : displayName;
20
+ return (jsxRuntime.jsx(StatusTreeNode.StatusTreeNode, { label: label, depth: 2, type: "file" }, `${assetType}-${index}`));
20
21
  })] }, assetType));
21
22
  }), jsxRuntime.jsx(ink.Box, { marginLeft: 2, children: jsxRuntime.jsxs(ink.Text, { dimColor: true, children: ["Last synced: ", new Date(pkg.syncedAt).toLocaleString()] }) }), pkg.error && (jsxRuntime.jsx(ink.Box, { marginLeft: 2, children: jsxRuntime.jsxs(ink.Text, { color: "red", children: ["Error: ", pkg.error] }) }))] }, pkg.name));
22
23
  }), jsxRuntime.jsx(ink.Box, { marginTop: 1, paddingTop: 1, borderStyle: "single", borderTop: true, borderColor: "gray", children: jsxRuntime.jsxs(ink.Text, { dimColor: true, children: ["Summary: ", summary.upToDate, " up to date", summary.outdated > 0 && `, ${summary.outdated} updates available`, summary.error > 0 && `, ${summary.error} errors`, summary.unknown > 0 && `, ${summary.unknown} unknown`] }) })] }));
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import type { SkillUnit } from '../../utils/types.js';
2
3
  export interface PackageStatusItem {
3
4
  name: string;
4
5
  localVersion: string;
@@ -6,10 +7,7 @@ export interface PackageStatusItem {
6
7
  status: 'up-to-date' | 'outdated' | 'error' | 'unknown';
7
8
  syncedAt: string;
8
9
  error?: string;
9
- files: Record<string, Array<string | {
10
- original: string;
11
- transformed: string;
12
- }>>;
10
+ files: Record<string, SkillUnit[]>;
13
11
  fileCount: number;
14
12
  }
15
13
  export interface StatusDisplayProps {
@@ -12,9 +12,10 @@ const StatusDisplay = ({ packages, loading, summary, }) => {
12
12
  return (jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [jsx(StatusTreeNode, { label: pkg.name, depth: 0, type: "package", expanded: true, status: pkg.status, version: pkg.localVersion, remoteVersion: pkg.remoteVersion, fileCount: pkg.fileCount }), assetTypes.map((assetType) => {
13
13
  const files = pkg.files[assetType];
14
14
  const fileArray = Array.isArray(files) ? files : [];
15
- return (jsxs(Box, { flexDirection: "column", children: [jsx(StatusTreeNode, { label: assetType, depth: 1, type: "assetType", expanded: true, fileCount: fileArray.length }), fileArray.map((file, index) => {
16
- const fileName = typeof file === 'string' ? file : file.transformed;
17
- return (jsx(StatusTreeNode, { label: fileName, depth: 2, type: "file" }, `${assetType}-${index}`));
15
+ return (jsxs(Box, { flexDirection: "column", children: [jsx(StatusTreeNode, { label: assetType, depth: 1, type: "assetType", expanded: true, fileCount: fileArray.length }), fileArray.map((unit, index) => {
16
+ const displayName = unit.transformed ?? unit.name;
17
+ const label = unit.isDirectory ? `${displayName}/` : displayName;
18
+ return (jsx(StatusTreeNode, { label: label, depth: 2, type: "file" }, `${assetType}-${index}`));
18
19
  })] }, assetType));
19
20
  }), jsx(Box, { marginLeft: 2, children: jsxs(Text, { dimColor: true, children: ["Last synced: ", new Date(pkg.syncedAt).toLocaleString()] }) }), pkg.error && (jsx(Box, { marginLeft: 2, children: jsxs(Text, { color: "red", children: ["Error: ", pkg.error] }) }))] }, pkg.name));
20
21
  }), jsx(Box, { marginTop: 1, paddingTop: 1, borderStyle: "single", borderTop: true, borderColor: "gray", children: jsxs(Text, { dimColor: true, children: ["Summary: ", summary.upToDate, " up to date", summary.outdated > 0 && `, ${summary.outdated} updates available`, summary.error > 0 && `, ${summary.error} errors`, summary.unknown > 0 && `, ${summary.unknown} unknown`] }) })] }));
@@ -10,8 +10,24 @@ function hasPartialSelection(node) {
10
10
  const selectedCount = node.children.filter((child) => child.selected).length;
11
11
  return selectedCount > 0 && selectedCount < node.children.length;
12
12
  }
13
- const AssetTreeNode = ({ node, depth, isSelected, }) => {
14
- const indent = ' '.repeat(depth);
13
+ function getTreePrefix(isLastAtDepth) {
14
+ let prefix = '';
15
+ const depth = isLastAtDepth.length;
16
+ for (let i = 0; i < depth; i++) {
17
+ if (i === depth - 1) {
18
+ prefix += isLastAtDepth[i] ? '└─ ' : '├─ ';
19
+ }
20
+ else {
21
+ prefix += isLastAtDepth[i] ? ' ' : '│ ';
22
+ }
23
+ }
24
+ return prefix;
25
+ }
26
+ const AssetTreeNode = ({ node, isLastAtDepth, isSelected, }) => {
27
+ const treePrefix = getTreePrefix(isLastAtDepth);
28
+ if (node.disabled) {
29
+ return (jsxRuntime.jsx(ink.Box, { children: jsxRuntime.jsxs(ink.Text, { color: isSelected ? 'cyan' : undefined, dimColor: !isSelected, children: [treePrefix, node.label] }) }));
30
+ }
15
31
  let selectionIcon;
16
32
  let iconColor;
17
33
  if (hasPartialSelection(node)) {
@@ -26,12 +42,13 @@ const AssetTreeNode = ({ node, depth, isSelected, }) => {
26
42
  selectionIcon = '○';
27
43
  iconColor = 'red';
28
44
  }
29
- const expandIcon = node.type === 'directory' && node.children
45
+ const expandIcon = (node.type === 'directory' || node.type === 'skill-directory') &&
46
+ node.children
30
47
  ? node.expanded
31
48
  ? '▼'
32
49
  : '▶'
33
50
  : ' ';
34
- return (jsxRuntime.jsx(ink.Box, { children: jsxRuntime.jsxs(ink.Text, { color: isSelected ? 'cyan' : undefined, children: [indent, expandIcon, " ", jsxRuntime.jsx(ink.Text, { color: iconColor, children: selectionIcon }), " ", node.label] }) }));
51
+ return (jsxRuntime.jsx(ink.Box, { children: jsxRuntime.jsxs(ink.Text, { color: isSelected ? 'cyan' : undefined, children: [treePrefix, expandIcon, " ", jsxRuntime.jsx(ink.Text, { color: iconColor, children: selectionIcon }), " ", node.label] }) }));
35
52
  };
36
53
 
37
54
  exports.AssetTreeNode = AssetTreeNode;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import type { TreeNode } from '../../utils/types.js';
3
3
  export interface AssetTreeNodeProps {
4
4
  node: TreeNode;
5
- depth: number;
5
+ isLastAtDepth: boolean[];
6
6
  isSelected: boolean;
7
7
  onToggle: () => void;
8
8
  }
@@ -8,8 +8,24 @@ function hasPartialSelection(node) {
8
8
  const selectedCount = node.children.filter((child) => child.selected).length;
9
9
  return selectedCount > 0 && selectedCount < node.children.length;
10
10
  }
11
- const AssetTreeNode = ({ node, depth, isSelected, }) => {
12
- const indent = ' '.repeat(depth);
11
+ function getTreePrefix(isLastAtDepth) {
12
+ let prefix = '';
13
+ const depth = isLastAtDepth.length;
14
+ for (let i = 0; i < depth; i++) {
15
+ if (i === depth - 1) {
16
+ prefix += isLastAtDepth[i] ? '└─ ' : '├─ ';
17
+ }
18
+ else {
19
+ prefix += isLastAtDepth[i] ? ' ' : '│ ';
20
+ }
21
+ }
22
+ return prefix;
23
+ }
24
+ const AssetTreeNode = ({ node, isLastAtDepth, isSelected, }) => {
25
+ const treePrefix = getTreePrefix(isLastAtDepth);
26
+ if (node.disabled) {
27
+ return (jsx(Box, { children: jsxs(Text, { color: isSelected ? 'cyan' : undefined, dimColor: !isSelected, children: [treePrefix, node.label] }) }));
28
+ }
13
29
  let selectionIcon;
14
30
  let iconColor;
15
31
  if (hasPartialSelection(node)) {
@@ -24,12 +40,13 @@ const AssetTreeNode = ({ node, depth, isSelected, }) => {
24
40
  selectionIcon = '○';
25
41
  iconColor = 'red';
26
42
  }
27
- const expandIcon = node.type === 'directory' && node.children
43
+ const expandIcon = (node.type === 'directory' || node.type === 'skill-directory') &&
44
+ node.children
28
45
  ? node.expanded
29
46
  ? '▼'
30
47
  : '▶'
31
48
  : ' ';
32
- return (jsx(Box, { children: jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [indent, expandIcon, " ", jsx(Text, { color: iconColor, children: selectionIcon }), " ", node.label] }) }));
49
+ return (jsx(Box, { children: jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [treePrefix, expandIcon, " ", jsx(Text, { color: iconColor, children: selectionIcon }), " ", node.label] }) }));
33
50
  };
34
51
 
35
52
  export { AssetTreeNode };