@dealdeploy/skl 1.2.0 → 1.3.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
@@ -6,14 +6,21 @@ import { join } from "path";
6
6
  import { homedir } from "os";
7
7
  import { getCatalogSkills, removeFromLock, catalogDir, buildAddArgs, readLock, detectProjectAgents } from "./lib.ts";
8
8
  import { createTui, type ColId } from "./tui.ts";
9
+ import { checkForUpdate } from "./update-check.ts";
9
10
 
10
11
  // @ts-ignore - bun supports JSON imports
11
12
  const { version: VERSION } = await import("./package.json");
12
13
 
14
+ // Fire update check in background (non-blocking)
15
+ const updateMsg = checkForUpdate(VERSION);
16
+
13
17
  // ── Flags ────────────────────────────────────────────────────────────
18
+ const debug = process.argv.includes("--debug");
14
19
  const arg = process.argv[2];
15
20
  if (arg === "-v" || arg === "--version") {
16
21
  console.log(`skl ${VERSION}`);
22
+ const msg = await updateMsg;
23
+ if (msg) console.log(msg);
17
24
  process.exit(0);
18
25
  }
19
26
  if (arg === "-h" || arg === "--help" || arg === "help") {
@@ -30,21 +37,30 @@ Usage:
30
37
 
31
38
  Options:
32
39
  -h, --help Show this help message
33
- -v, --version Show version`);
40
+ -v, --version Show version
41
+ --debug Show full error output on failures`);
34
42
  process.exit(0);
35
43
  }
36
44
 
37
45
  // ── Subcommand routing ───────────────────────────────────────────────
46
+ async function printUpdateMsg() {
47
+ const msg = await updateMsg;
48
+ if (msg) console.log(`\n${msg}`);
49
+ }
50
+
38
51
  if (arg === "add") {
39
52
  await import("./add.ts");
53
+ await printUpdateMsg();
40
54
  process.exit(0);
41
55
  }
42
56
  if (arg === "update") {
43
57
  await import("./update.ts");
58
+ await printUpdateMsg();
44
59
  process.exit(0);
45
60
  }
46
61
  if (arg === "migrate") {
47
62
  await import("./migrate.ts");
63
+ await printUpdateMsg();
48
64
  process.exit(0);
49
65
  }
50
66
 
@@ -101,8 +117,16 @@ const tui = createTui(renderer, {
101
117
  stdout: "pipe",
102
118
  stderr: "pipe",
103
119
  });
104
- const code = await proc.exited;
105
- return code === 0;
120
+ const [code, stdout, stderr] = await Promise.all([
121
+ proc.exited,
122
+ new Response(proc.stdout).text(),
123
+ new Response(proc.stderr).text(),
124
+ ]);
125
+ if (code === 0) return true;
126
+ if (debug) {
127
+ return (stderr || stdout).trim() || `exit code ${code}`;
128
+ }
129
+ return false;
106
130
  },
107
131
 
108
132
  async onDelete(name: string) {
@@ -141,8 +165,9 @@ const tui = createTui(renderer, {
141
165
  process.exit(0);
142
166
  },
143
167
 
144
- onQuit() {
168
+ async onQuit() {
145
169
  renderer.destroy();
170
+ await printUpdateMsg();
146
171
  process.exit(0);
147
172
  },
148
173
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dealdeploy/skl",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "TUI skill manager for Claude Code agents",
5
5
  "module": "index.ts",
6
6
  "bin": {
package/tui.ts CHANGED
@@ -18,8 +18,8 @@ export type TuiDeps = {
18
18
  globalInstalled: Set<string>;
19
19
  localInstalled: Set<string>;
20
20
  catalogPath: string;
21
- /** Called when a skill toggle is requested. Return true on success. */
22
- onToggle: (col: ColId, name: string, enable: boolean) => Promise<boolean>;
21
+ /** Called when a skill toggle is requested. Return true on success, false or error string on failure. */
22
+ onToggle: (col: ColId, name: string, enable: boolean) => Promise<boolean | string>;
23
23
  /** Called when a skill delete is requested. */
24
24
  onDelete: (name: string) => Promise<void>;
25
25
  /** Called when edit is requested. Return false to stay in TUI. */
@@ -505,8 +505,8 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
505
505
  refreshAll();
506
506
 
507
507
  try {
508
- const ok = await deps.onToggle(col, name, !enabled);
509
- if (ok) {
508
+ const result = await deps.onToggle(col, name, !enabled);
509
+ if (result === true) {
510
510
  const set = col === "global" ? globalInstalled : localInstalled;
511
511
  if (enabled) {
512
512
  set.delete(name);
@@ -516,7 +516,8 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
516
516
  setStatus(`Added ${name}`, C.statusOk);
517
517
  }
518
518
  } else {
519
- setStatus(`Failed to ${enabled ? "remove" : "add"} ${name}`, C.statusErr);
519
+ const detail = typeof result === "string" ? `: ${result}` : "";
520
+ setStatus(`Failed to ${enabled ? "remove" : "add"} ${name}${detail}`, C.statusErr);
520
521
  }
521
522
  } finally {
522
523
  pendingToggles.delete(key);
@@ -0,0 +1,59 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+
5
+ const CACHE_PATH = join(homedir(), ".skl", "update-check.json");
6
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 1 day
7
+ const PKG_NAME = "@dealdeploy/skl";
8
+
9
+ type Cache = { latest: string; checkedAt: number };
10
+
11
+ function readCache(): Cache | null {
12
+ try {
13
+ return JSON.parse(readFileSync(CACHE_PATH, "utf-8"));
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ function writeCache(cache: Cache): void {
20
+ mkdirSync(join(homedir(), ".skl"), { recursive: true });
21
+ writeFileSync(CACHE_PATH, JSON.stringify(cache));
22
+ }
23
+
24
+ /**
25
+ * Check npm for the latest version. Non-blocking — fire and forget.
26
+ * Returns a message string if an update is available, or null.
27
+ */
28
+ export async function checkForUpdate(currentVersion: string): Promise<string | null> {
29
+ const cache = readCache();
30
+ const now = Date.now();
31
+
32
+ // Use cache if fresh
33
+ if (cache && now - cache.checkedAt < CHECK_INTERVAL_MS) {
34
+ return compareVersions(currentVersion, cache.latest);
35
+ }
36
+
37
+ // Fetch from registry
38
+ try {
39
+ const resp = await fetch(`https://registry.npmjs.org/${PKG_NAME}/latest`, {
40
+ signal: AbortSignal.timeout(3000),
41
+ });
42
+ if (!resp.ok) return null;
43
+ const data = (await resp.json()) as { version: string };
44
+ writeCache({ latest: data.version, checkedAt: now });
45
+ return compareVersions(currentVersion, data.version);
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function compareVersions(current: string, latest: string): string | null {
52
+ if (current === latest) return null;
53
+ const [cMaj, cMin, cPat] = current.split(".").map(Number);
54
+ const [lMaj, lMin, lPat] = latest.split(".").map(Number);
55
+ if (lMaj > cMaj || lMin > cMin || lPat > cPat) {
56
+ return `Update available: ${current} → ${latest} (run: bun install -g ${PKG_NAME})`;
57
+ }
58
+ return null;
59
+ }