@dealdeploy/skl 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -91,18 +91,27 @@ const projectAgents = [...new Set(["universal", ...detectProjectAgents(process.c
91
91
 
92
92
  // ── Create TUI ──────────────────────────────────────────────────────
93
93
 
94
- const renderer = await createCliRenderer({ exitOnCtrlC: true });
94
+ let tui: ReturnType<typeof createTui> | null = null;
95
+
96
+ const renderer = await createCliRenderer({
97
+ exitOnCtrlC: true,
98
+ onDestroy: () => tui?.destroy(),
99
+ });
95
100
 
96
101
  const allSkills = getCatalogSkills();
97
102
  const lock = readLock();
98
103
  const skillRepos = new Map<string, string | null>();
104
+ const skillDates = new Map<string, string | null>();
99
105
  for (const name of allSkills) {
100
- skillRepos.set(name, lock.skills[name]?.source || null);
106
+ const entry = lock.skills[name];
107
+ skillRepos.set(name, entry?.source || null);
108
+ skillDates.set(name, entry?.updatedAt || entry?.addedAt || null);
101
109
  }
102
110
 
103
- const tui = createTui(renderer, {
111
+ tui = createTui(renderer, {
104
112
  allSkills,
105
113
  skillRepos,
114
+ skillDates,
106
115
  globalInstalled,
107
116
  localInstalled,
108
117
  catalogPath: CATALOG,
package/lib.ts CHANGED
@@ -20,6 +20,7 @@ export type LockEntry = {
20
20
  skillPath: string;
21
21
  treeSHA: string;
22
22
  addedAt: string;
23
+ updatedAt?: string;
23
24
  };
24
25
 
25
26
  export type LockFile = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dealdeploy/skl",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "TUI skill manager for Claude Code agents",
5
5
  "module": "index.ts",
6
6
  "bin": {
package/tui.test.ts CHANGED
@@ -16,6 +16,7 @@ function setup(opts?: {
16
16
  }) {
17
17
  const skills = opts?.skills ?? ["alpha", "beta", "gamma"];
18
18
  const skillRepos = opts?.skillRepos ?? new Map(skills.map((s) => [s, null]));
19
+ const skillDates = new Map<string, string | null>(skills.map((s) => [s, null]));
19
20
  const globalInstalled = new Set(opts?.globalInstalled ?? []);
20
21
  const localInstalled = new Set(opts?.localInstalled ?? []);
21
22
 
@@ -43,6 +44,7 @@ function setup(opts?: {
43
44
  return {
44
45
  allSkills: skills,
45
46
  skillRepos,
47
+ skillDates,
46
48
  globalInstalled,
47
49
  localInstalled,
48
50
  catalogPath: "/tmp/test-catalog",
@@ -68,6 +70,7 @@ function setup(opts?: {
68
70
  return {
69
71
  allSkills: skills,
70
72
  skillRepos,
73
+ skillDates,
71
74
  globalInstalled,
72
75
  localInstalled,
73
76
  catalogPath: "/tmp/test-catalog",
package/tui.ts CHANGED
@@ -15,6 +15,7 @@ export type ColId = "global" | "local";
15
15
  export type TuiDeps = {
16
16
  allSkills: string[];
17
17
  skillRepos: Map<string, string | null>;
18
+ skillDates: Map<string, string | null>;
18
19
  globalInstalled: Set<string>;
19
20
  localInstalled: Set<string>;
20
21
  catalogPath: string;
@@ -49,6 +50,21 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
49
50
  return `${text.slice(0, max - 1)}\u2026`;
50
51
  }
51
52
 
53
+ function relativeDate(iso: string): string {
54
+ const ms = Date.now() - new Date(iso).getTime();
55
+ if (ms < 0) return "just now";
56
+ const mins = Math.floor(ms / 60000);
57
+ if (mins < 1) return "just now";
58
+ if (mins < 60) return `${mins}m ago`;
59
+ const hrs = Math.floor(mins / 60);
60
+ if (hrs < 24) return `${hrs}h ago`;
61
+ const days = Math.floor(hrs / 24);
62
+ if (days < 30) return `${days}d ago`;
63
+ const months = Math.floor(days / 30);
64
+ if (months < 12) return `${months}mo ago`;
65
+ return `${Math.floor(months / 12)}y ago`;
66
+ }
67
+
52
68
  // ── Build display list ─────────────────────────────────────────────
53
69
 
54
70
  const displayItems: DisplayItem[] = [];
@@ -178,12 +194,14 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
178
194
  });
179
195
 
180
196
  let COL_W = 14;
197
+ let DATE_W = 10;
181
198
  let NAME_W = 34;
182
199
 
183
200
  function calcWidths() {
184
201
  const w = (renderer as any).width ?? process.stdout.columns ?? 80;
185
202
  COL_W = Math.max(5, Math.min(14, Math.floor((w - 20) / 2)));
186
- NAME_W = Math.min(40, Math.max(15, w - COL_W * 2 - 1));
203
+ DATE_W = 10;
204
+ NAME_W = Math.min(40, Math.max(15, w - COL_W * 2 - DATE_W - 1));
187
205
  }
188
206
  calcWidths();
189
207
 
@@ -208,10 +226,18 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
208
226
  attributes: TextAttributes.BOLD,
209
227
  width: COL_W,
210
228
  });
229
+ const colUpdated = new TextRenderable(renderer, {
230
+ id: "col-updated",
231
+ content: "Updated",
232
+ fg: C.fgDim,
233
+ attributes: TextAttributes.BOLD,
234
+ width: DATE_W,
235
+ });
211
236
 
212
237
  colHeaderRow.add(colName);
213
238
  colHeaderRow.add(colGlobal);
214
239
  colHeaderRow.add(colLocal);
240
+ colHeaderRow.add(colUpdated);
215
241
 
216
242
  const sep = new TextRenderable(renderer, {
217
243
  id: "sep",
@@ -235,6 +261,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
235
261
  nameText: TextRenderable;
236
262
  globalText: TextRenderable;
237
263
  localText: TextRenderable;
264
+ dateText: TextRenderable;
238
265
  };
239
266
  const rows: RowRefs[] = new Array(allSkills.length);
240
267
 
@@ -262,7 +289,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
262
289
  id: `header-text-${di}`,
263
290
  content: ` ${item.repo}`,
264
291
  fg: C.fgDim,
265
- width: NAME_W + COL_W * 2,
292
+ width: NAME_W + COL_W * 2 + DATE_W,
266
293
  });
267
294
  hRow.add(hText);
268
295
  scrollBox.add(hRow);
@@ -301,11 +328,20 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
301
328
  width: COL_W,
302
329
  });
303
330
 
331
+ const dateStr = deps.skillDates.get(skill);
332
+ const dateText = new TextRenderable(renderer, {
333
+ id: `date-${i}`,
334
+ content: dateStr ? relativeDate(dateStr) : "",
335
+ fg: C.fgDim,
336
+ width: DATE_W,
337
+ });
338
+
304
339
  row.add(nameText);
305
340
  row.add(globalText);
306
341
  row.add(localText);
342
+ row.add(dateText);
307
343
  scrollBox.add(row);
308
- rows[i] = { row, nameText, globalText, localText };
344
+ rows[i] = { row, nameText, globalText, localText, dateText };
309
345
  }
310
346
  }
311
347
 
@@ -436,7 +472,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
436
472
  statusLine.fg = color;
437
473
  if (statusTimeout) clearTimeout(statusTimeout);
438
474
  statusTimeout = setTimeout(() => {
439
- try { statusLine.content = ""; } catch {}
475
+ statusLine.content = "";
440
476
  }, 3000);
441
477
  }
442
478
 
@@ -445,14 +481,16 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
445
481
  colName.width = NAME_W;
446
482
  colGlobal.width = COL_W;
447
483
  colLocal.width = COL_W;
484
+ colUpdated.width = DATE_W;
448
485
  for (let i = 0; i < allSkills.length; i++) {
449
486
  const r = rows[i]!;
450
487
  r.nameText.width = NAME_W;
451
488
  r.globalText.width = COL_W;
452
489
  r.localText.width = COL_W;
490
+ r.dateText.width = DATE_W;
453
491
  }
454
492
  for (const [, ref] of headerRefs) {
455
- ref.text.width = NAME_W + COL_W * 2;
493
+ ref.text.width = NAME_W + COL_W * 2 + DATE_W;
456
494
  }
457
495
  }
458
496
 
@@ -723,9 +761,14 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
723
761
  ensureVisible();
724
762
  });
725
763
 
764
+ function destroy() {
765
+ if (statusTimeout) clearTimeout(statusTimeout);
766
+ }
767
+
726
768
  return {
727
769
  refreshAll,
728
770
  relayout,
771
+ destroy,
729
772
  get state() {
730
773
  return {
731
774
  cursor,
package/update.ts CHANGED
@@ -89,6 +89,7 @@ for (const [repo, skills] of byRepo) {
89
89
  const fileCount = await downloadSkillFiles(repo, branch, skill.skillPath, skillDir, tree);
90
90
 
91
91
  lock.skills[skill.name]!.treeSHA = skill.newSHA;
92
+ lock.skills[skill.name]!.updatedAt = new Date().toISOString();
92
93
  console.log(` updated (${fileCount} files)`);
93
94
  totalUpdated++;
94
95
  }