@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 +29 -4
- package/package.json +1 -1
- package/tui.ts +6 -5
- package/update-check.ts +59 -0
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
|
|
105
|
-
|
|
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
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
|
|
509
|
-
if (
|
|
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
|
-
|
|
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);
|
package/update-check.ts
ADDED
|
@@ -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
|
+
}
|