skillwatch 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.
package/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2026 Matt Blode
3
+ Copyright (c) 2026 Matthew Blode
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,16 +1,6 @@
1
1
  # skillwatch
2
2
 
3
- Daily macOS notifications when installed GitHub-backed skills have updates.
4
-
5
- - Compares each installed skill's `skillFolderHash` with the current GitHub tree hash for that folder
6
- - Installs a per-user LaunchAgent that runs once per day
7
- - Deduplicates notifications until the update set changes
8
-
9
- ## Requirements
10
-
11
- - macOS
12
- - Node.js 22 or newer
13
- - An existing skills lock file at `~/.agents/.skill-lock.json` or `$XDG_STATE_HOME/skills/.skill-lock.json`
3
+ Daily macOS notifications when your installed agent skills have updates on GitHub.
14
4
 
15
5
  ## Install
16
6
 
@@ -18,14 +8,9 @@ Daily macOS notifications when installed GitHub-backed skills have updates.
18
8
  npx skillwatch install
19
9
  ```
20
10
 
21
- Default schedule is daily at `09:00` local time.
11
+ Runs daily at 09:00. Customise with `--hour 14 --minute 30`.
22
12
 
23
- If you want the command installed permanently:
24
-
25
- ```bash
26
- npm install -g skillwatch
27
- skillwatch install
28
- ```
13
+ For frequent use, install globally with `npm install -g skillwatch`.
29
14
 
30
15
  ## Verify
31
16
 
@@ -39,68 +24,10 @@ npx skillwatch check-now
39
24
  npx skillwatch uninstall
40
25
  ```
41
26
 
42
- If you installed the CLI globally and want to remove that too:
43
-
44
- ```bash
45
- npm uninstall -g skillwatch
46
- ```
47
-
48
- This removes the LaunchAgent, installed support files, state, and logs. The global uninstall only removes the CLI package.
49
-
50
- ## Custom Time
51
-
52
- ```bash
53
- npx skillwatch install --hour 14 --minute 30
54
- ```
55
-
56
- ## What Gets Installed
57
-
58
- - LaunchAgent: `~/Library/LaunchAgents/com.mblode.skillwatch.plist`
59
- - Checker: `~/Library/Application Support/skillwatch/checker.js`
60
- - State: `~/Library/Application Support/skillwatch/state.json`
61
- - Logs: `~/Library/Logs/skillwatch/`
62
-
63
27
  ## Troubleshooting
64
28
 
65
- - If you see `No skill lock file found`, run `skills` or `npx skills` first so the lock file exists.
66
- - If your Node path changes later, rerun `npx skillwatch install` so the LaunchAgent points at the new `node` binary.
67
- - This project is macOS-only and requires `launchctl`, `plutil`, and `osascript`.
68
-
69
- ## Local Development
70
-
71
- Use the repo wrapper if you are working from source:
72
-
73
- ```bash
74
- git clone https://github.com/mblode/update-skills.git
75
- cd update-skills
76
- npm install
77
- ./install.sh
78
- ```
79
-
80
- `./install.sh` auto-builds `dist/` if needed and forwards to the built CLI.
81
-
82
- Use the built CLI directly if you want to test the package output:
83
-
84
- ```bash
85
- npm run build
86
- node dist/cli.js install
87
- node dist/cli.js check-now
88
- node dist/cli.js uninstall
89
- ```
90
-
91
- Install command options from `--help`:
92
-
93
- ```text
94
- --hour <0-23> Daily check hour, default 9
95
- --minute <0-59> Daily check minute, default 0
96
- ```
97
-
98
- ## Development
99
-
100
- ```bash
101
- npm run validate
102
- npm run pack:dry-run
103
- ```
29
+ - **"No skill lock file found"** run `npx skills` first so the lock file exists.
30
+ - **Node path changed** rerun `npx skillwatch install` to update the LaunchAgent.
104
31
 
105
32
  ## License
106
33
 
package/dist/checker.js CHANGED
@@ -221,5 +221,3 @@ if (isExecutedDirectly()) try {
221
221
  }
222
222
  //#endregion
223
223
  export { buildNotificationBody, buildSignature, findFolderHashInTree, getRepoId, groupUpdatesByRepo };
224
-
225
- //# sourceMappingURL=checker.js.map
package/dist/cli.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
- import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { copyFile, mkdir, rm, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  //#region src/cli.ts
8
- const { version, name } = await import("../package.json", { with: { type: "json" } }).then((m) => m.default);
8
+ const PACKAGE_NAME = "skillwatch";
9
+ const PACKAGE_VERSION = "0.1.1";
9
10
  const ENTRYPOINT_PATH = fileURLToPath(import.meta.url);
10
- const PACKAGE_DIR = dirname(ENTRYPOINT_PATH);
11
- const CHECKER_SOURCE_PATH = join(PACKAGE_DIR, "checker.js");
12
- const COMMAND_NAME = name.split("/").at(-1) ?? "skillwatch";
11
+ const CHECKER_SOURCE_PATH = join(dirname(ENTRYPOINT_PATH), "checker.js");
12
+ const COMMAND_NAME = PACKAGE_NAME.split("/").at(-1) ?? "skillwatch";
13
13
  const APP_DIR_NAME = "skillwatch";
14
14
  const LEGACY_APP_DIR_NAME = "skills-update-notifier";
15
15
  const fail = (message) => {
@@ -44,9 +44,38 @@ const getUid = () => {
44
44
  if (typeof process.getuid !== "function") fail("Could not determine the current user id.");
45
45
  return process.getuid();
46
46
  };
47
- const writePlist = async (templatePath, options, paths) => {
47
+ const renderPlist = (options, paths, nodePath = process.execPath) => `<?xml version="1.0" encoding="UTF-8"?>
48
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
49
+ <plist version="1.0">
50
+ <dict>
51
+ <key>Label</key>
52
+ <string>${LABEL}</string>
53
+
54
+ <key>ProgramArguments</key>
55
+ <array>
56
+ <string>${nodePath}</string>
57
+ <string>${paths.checkerTargetPath}</string>
58
+ </array>
59
+
60
+ <key>StartCalendarInterval</key>
61
+ <dict>
62
+ <key>Hour</key>
63
+ <integer>${options.hour}</integer>
64
+ <key>Minute</key>
65
+ <integer>${options.minute}</integer>
66
+ </dict>
67
+
68
+ <key>StandardOutPath</key>
69
+ <string>${paths.stdoutPath}</string>
70
+
71
+ <key>StandardErrorPath</key>
72
+ <string>${paths.stderrPath}</string>
73
+ </dict>
74
+ </plist>
75
+ `;
76
+ const writePlist = async (options, paths) => {
48
77
  const plistTargetPath = getPlistTargetPath();
49
- const content = (await readFile(templatePath, "utf8")).replaceAll("__NODE_BIN__", process.execPath).replaceAll("__SCRIPT_PATH__", paths.checkerTargetPath).replaceAll("__HOUR__", String(options.hour)).replaceAll("__MINUTE__", String(options.minute)).replaceAll("__STDOUT_PATH__", paths.stdoutPath).replaceAll("__STDERR_PATH__", paths.stderrPath);
78
+ const content = renderPlist(options, paths);
50
79
  await mkdir(dirname(plistTargetPath), { recursive: true });
51
80
  await writeFile(plistTargetPath, content, "utf8");
52
81
  };
@@ -67,7 +96,7 @@ const bootstrapAgent = () => {
67
96
  };
68
97
  const printUsage = () => {
69
98
  console.log(`
70
- ${name} ${version}
99
+ ${PACKAGE_NAME} ${PACKAGE_VERSION}
71
100
 
72
101
  Usage:
73
102
  ${COMMAND_NAME} install [--hour 9] [--minute 0]
@@ -119,11 +148,10 @@ const installCommand = async (args) => {
119
148
  const appDir = getAppDir();
120
149
  const logDir = getLogDir();
121
150
  const checkerTargetPath = join(appDir, "checker.js");
122
- const plistTemplatePath = join(PACKAGE_DIR, "com.mblode.skillwatch.plist.template");
123
151
  await mkdir(appDir, { recursive: true });
124
152
  await mkdir(logDir, { recursive: true });
125
153
  await copyFile(CHECKER_SOURCE_PATH, checkerTargetPath);
126
- await writePlist(plistTemplatePath, options, {
154
+ await writePlist(options, {
127
155
  checkerTargetPath,
128
156
  stderrPath: join(logDir, "stderr.log"),
129
157
  stdoutPath: join(logDir, "stdout.log")
@@ -186,7 +214,7 @@ const main = async () => {
186
214
  return;
187
215
  }
188
216
  if (command === "--version" || command === "-v" || command === "version") {
189
- console.log(version);
217
+ console.log(PACKAGE_VERSION);
190
218
  return;
191
219
  }
192
220
  if (command === "install") {
@@ -213,6 +241,4 @@ if (isExecutedDirectly()) try {
213
241
  fail(error instanceof Error ? error.stack || error.message : String(error));
214
242
  }
215
243
  //#endregion
216
- export { parseInteger };
217
-
218
- //# sourceMappingURL=cli.js.map
244
+ export { parseInteger, renderPlist };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwatch",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Daily macOS notifications when installed GitHub-backed skills have updates.",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -9,17 +9,19 @@
9
9
  "notification",
10
10
  "skills"
11
11
  ],
12
- "homepage": "https://github.com/mblode/update-skills#readme",
12
+ "homepage": "https://github.com/mblode/skillwatch#readme",
13
13
  "bugs": {
14
- "url": "https://github.com/mblode/update-skills/issues"
14
+ "url": "https://github.com/mblode/skillwatch/issues"
15
15
  },
16
16
  "license": "MIT",
17
17
  "author": "Matt Blode",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/mblode/update-skills.git"
20
+ "url": "git+https://github.com/mblode/skillwatch.git"
21
+ },
22
+ "bin": {
23
+ "skillwatch": "./dist/cli.js"
21
24
  },
22
- "bin": "./dist/cli.js",
23
25
  "files": [
24
26
  "dist",
25
27
  "README.md",
@@ -30,7 +32,7 @@
30
32
  "access": "public"
31
33
  },
32
34
  "scripts": {
33
- "build": "tsdown && cp com.mblode.skillwatch.plist.template dist/",
35
+ "build": "tsdown",
34
36
  "dev": "tsdown --watch",
35
37
  "typecheck": "tsc --noEmit",
36
38
  "check": "ultracite check",
@@ -1 +0,0 @@
1
- {"version":3,"file":"checker.js","names":[],"sources":["../src/checker.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n// --- Types ---\n\ninterface LockFileEntry {\n sourceType?: string;\n source?: string;\n sourceUrl?: string;\n skillPath?: string;\n skillFolderHash?: string;\n}\n\ninterface LockFile {\n skills?: Record<string, LockFileEntry>;\n}\n\ninterface TrackedSkill {\n skillName: string;\n repoId: string;\n skillPath: string;\n localHash: string;\n}\n\ninterface UpdateResult {\n repoId: string;\n skillName: string;\n localHash: string;\n remoteHash: string;\n}\n\ninterface CheckError {\n repoId: string;\n skillName: string;\n reason: string;\n}\n\ninterface CheckResult {\n trackedSkills: TrackedSkill[];\n updates: UpdateResult[];\n errors: CheckError[];\n}\n\ninterface GroupedUpdate {\n repoId: string;\n skillNames: string[];\n}\n\ninterface GitHubTreeEntry {\n path: string;\n type: string;\n sha: string;\n}\n\ninterface GitHubTreeResponse {\n sha?: string;\n tree?: GitHubTreeEntry[];\n}\n\ninterface GitHubRepoResponse {\n default_branch?: string;\n}\n\n// --- Utilities ---\n\nconst log = (message: string): void => {\n const timestamp = new Date().toISOString();\n console.log(`[${timestamp}] ${message}`);\n};\n\nconst readJson = async <T>(path: string, fallback: T): Promise<T> => {\n try {\n const content = await readFile(path, \"utf8\");\n return JSON.parse(content) as T;\n } catch {\n return fallback;\n }\n};\n\nconst writeJson = async (path: string, value: unknown): Promise<void> => {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n};\n\nconst normalizeSkillFolderPath = (skillPath: string): string =>\n skillPath\n .replaceAll(\"\\\\\", \"/\")\n .replace(/\\/?SKILL\\.md$/, \"\")\n .replace(/\\/$/, \"\");\n\n// --- Config ---\n\nconst DEFAULT_STATE_DIR = join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"skillwatch\"\n);\nconst ENTRYPOINT_PATH = fileURLToPath(import.meta.url);\nconst DEFAULT_STATE_PATH = join(DEFAULT_STATE_DIR, \"state.json\");\nconst DEFAULT_LOCK_PATH = process.env.XDG_STATE_HOME\n ? join(process.env.XDG_STATE_HOME, \"skills\", \".skill-lock.json\")\n : join(homedir(), \".agents\", \".skill-lock.json\");\n\nconst STATE_PATH = process.env.SKILLS_CHECK_STATE_PATH || DEFAULT_STATE_PATH;\nconst LOCK_PATH = process.env.SKILLS_CHECK_LOCK_PATH || DEFAULT_LOCK_PATH;\n\n// --- GitHub API helpers ---\n\nconst getRepoId = (entry: LockFileEntry): string | null => {\n if (typeof entry?.source === \"string\" && entry.source.includes(\"/\")) {\n return entry.source.replace(/\\.git$/, \"\");\n }\n\n if (typeof entry?.sourceUrl === \"string\") {\n const sshMatch = entry.sourceUrl.match(/^git@[^:]+:(.+?)(?:\\.git)?$/);\n if (sshMatch?.[1]) {\n return sshMatch[1];\n }\n\n try {\n const pathname = new URL(entry.sourceUrl).pathname\n .replace(/^\\/+/, \"\")\n .replace(/\\.git$/, \"\");\n if (pathname.includes(\"/\")) {\n return pathname;\n }\n } catch {\n // Ignore parse failures and fall through.\n }\n }\n\n return null;\n};\n\nconst getGitHubToken = (): string | null => {\n const envToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;\n if (envToken) {\n return envToken;\n }\n\n const ghCandidates = [\n process.env.GH_PATH,\n \"/opt/homebrew/bin/gh\",\n \"/usr/local/bin/gh\",\n \"gh\",\n ].filter(Boolean) as string[];\n\n for (const candidate of ghCandidates) {\n try {\n const token = execFileSync(candidate, [\"auth\", \"token\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n\n if (token) {\n return token;\n }\n } catch {\n // Try the next candidate.\n }\n }\n\n return null;\n};\n\nconst fetchGitHubJson = async (\n url: string,\n token: string | null\n): Promise<unknown> => {\n try {\n const response = await fetch(url, {\n headers: {\n Accept: \"application/vnd.github.v3+json\",\n \"User-Agent\": \"skillwatch\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n },\n });\n\n if (!response.ok) {\n return null;\n }\n\n return await response.json();\n } catch {\n return null;\n }\n};\n\nconst fetchRepoTree = async (\n ownerRepo: string,\n token: string | null\n): Promise<GitHubTreeResponse | null> => {\n const repoData = (await fetchGitHubJson(\n `https://api.github.com/repos/${ownerRepo}`,\n token\n )) as GitHubRepoResponse | null;\n\n const branches = [repoData?.default_branch, \"main\", \"master\"].filter(\n Boolean\n ) as string[];\n const uniqueBranches = [...new Set(branches)];\n\n for (const branch of uniqueBranches) {\n const treeData = (await fetchGitHubJson(\n `https://api.github.com/repos/${ownerRepo}/git/trees/${branch}?recursive=1`,\n token\n )) as GitHubTreeResponse | null;\n\n if (treeData) {\n return treeData;\n }\n }\n\n return null;\n};\n\nconst findFolderHashInTree = (\n treeData: GitHubTreeResponse | null,\n skillPath: string\n): string | null => {\n const folderPath = normalizeSkillFolderPath(skillPath);\n\n if (!folderPath) {\n return treeData?.sha ?? null;\n }\n\n if (!Array.isArray(treeData?.tree)) {\n return null;\n }\n\n const folderEntry = treeData.tree.find(\n (entry: GitHubTreeEntry) =>\n entry.type === \"tree\" && entry.path === folderPath\n );\n return folderEntry?.sha ?? null;\n};\n\n// --- Notification helpers ---\n\nconst groupUpdatesByRepo = (updates: UpdateResult[]): GroupedUpdate[] => {\n const grouped = new Map<string, string[]>();\n\n for (const update of updates) {\n const existing = grouped.get(update.repoId) ?? [];\n existing.push(update.skillName);\n grouped.set(update.repoId, existing);\n }\n\n return [...grouped.entries()]\n .map(([repoId, skillNames]) => ({\n repoId,\n skillNames: skillNames.toSorted(),\n }))\n .toSorted((a, b) => a.repoId.localeCompare(b.repoId));\n};\n\nconst buildNotificationBody = (grouped: GroupedUpdate[]): string => {\n const preview = grouped\n .slice(0, 3)\n .map(({ repoId, skillNames }) => `${repoId} (${skillNames.length})`);\n\n if (grouped.length > 3) {\n preview.push(`+${grouped.length - 3} more`);\n }\n\n return preview.join(\", \");\n};\n\nconst buildSignature = (grouped: GroupedUpdate[]): string =>\n createHash(\"sha256\").update(JSON.stringify(grouped)).digest(\"hex\");\n\nconst escapeAppleScript = (value: string): string =>\n value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll('\"', '\\\\\"');\n\nconst sendNotification = (title: string, body: string): void => {\n execFileSync(\n \"/usr/bin/osascript\",\n [\n \"-e\",\n `display notification \"${escapeAppleScript(body)}\" with title \"${escapeAppleScript(title)}\"`,\n ],\n {\n stdio: \"ignore\",\n }\n );\n};\n\nconst writeState = async (\n signature: string | null,\n grouped?: GroupedUpdate[]\n): Promise<void> => {\n await writeJson(STATE_PATH, {\n lastCheckedAt: new Date().toISOString(),\n lastNotifiedSignature: signature,\n ...(grouped ? { updates: grouped } : {}),\n });\n};\n\n// --- Core checker logic ---\n\n// Exported for tests\nexport {\n buildNotificationBody,\n buildSignature,\n findFolderHashInTree,\n getRepoId,\n groupUpdatesByRepo,\n};\n\nconst getTrackedSkills = async (): Promise<TrackedSkill[]> => {\n const lockFile = await readJson<LockFile>(LOCK_PATH, { skills: {} });\n const tracked: TrackedSkill[] = [];\n\n for (const [skillName, entry] of Object.entries(lockFile.skills ?? {})) {\n if (entry?.sourceType !== \"github\") {\n continue;\n }\n\n if (!entry.skillPath || !entry.skillFolderHash) {\n continue;\n }\n\n const repoId = getRepoId(entry);\n if (!repoId) {\n continue;\n }\n\n tracked.push({\n localHash: entry.skillFolderHash,\n repoId,\n skillName,\n skillPath: entry.skillPath,\n });\n }\n\n return tracked;\n};\n\nconst findUpdates = async (): Promise<CheckResult> => {\n const trackedSkills = await getTrackedSkills();\n const token = getGitHubToken();\n const updates: UpdateResult[] = [];\n const errors: CheckError[] = [];\n const treeCache = new Map<\n string,\n Awaited<ReturnType<typeof fetchRepoTree>>\n >();\n\n for (const tracked of trackedSkills) {\n let treeData = treeCache.get(tracked.repoId);\n\n if (treeData === undefined) {\n treeData = await fetchRepoTree(tracked.repoId, token);\n treeCache.set(tracked.repoId, treeData);\n }\n\n const remoteHash = treeData\n ? findFolderHashInTree(treeData, tracked.skillPath)\n : null;\n\n if (!remoteHash) {\n errors.push({\n reason: \"Could not fetch remote folder hash\",\n repoId: tracked.repoId,\n skillName: tracked.skillName,\n });\n continue;\n }\n\n if (remoteHash !== tracked.localHash) {\n updates.push({\n localHash: tracked.localHash,\n remoteHash,\n repoId: tracked.repoId,\n skillName: tracked.skillName,\n });\n }\n }\n\n return { errors, trackedSkills, updates };\n};\n\nconst main = async (): Promise<void> => {\n log(`Using lock file: ${LOCK_PATH}`);\n log(`Using state file: ${STATE_PATH}`);\n\n if (!existsSync(LOCK_PATH)) {\n log(\"No skill lock file found. Nothing to check.\");\n process.exitCode = 0;\n return;\n }\n\n const { trackedSkills, updates, errors } = await findUpdates();\n const grouped: GroupedUpdate[] = groupUpdatesByRepo(updates);\n\n log(`Tracked ${trackedSkills.length} GitHub-backed skill(s).`);\n\n if (errors.length > 0) {\n for (const error of errors) {\n log(`Warning: ${error.repoId}/${error.skillName} - ${error.reason}`);\n }\n }\n\n if (grouped.length === 0) {\n log(\"No updates available.\");\n await writeState(null);\n process.exitCode = 0;\n return;\n }\n\n for (const repo of grouped) {\n log(`Update available: ${repo.repoId} -> ${repo.skillNames.join(\", \")}`);\n }\n\n const state = await readJson<{ lastNotifiedSignature?: string }>(\n STATE_PATH,\n {}\n );\n const signature = buildSignature(grouped);\n\n if (state.lastNotifiedSignature === signature) {\n log(\"Updates already notified. Skipping duplicate notification.\");\n await writeState(signature, grouped);\n process.exitCode = 0;\n return;\n }\n\n const title = \"Skill updates available\";\n const body = buildNotificationBody(grouped);\n\n sendNotification(title, body);\n log(`Notification sent: ${body}`);\n log(\"To update installed skills, run: npx skills update\");\n\n await writeState(signature, grouped);\n};\n\nconst isExecutedDirectly = (): boolean => {\n const [, entryArg] = process.argv;\n\n return entryArg !== undefined && resolve(entryArg) === ENTRYPOINT_PATH;\n};\n\nif (isExecutedDirectly()) {\n try {\n await main();\n } catch (error: unknown) {\n log(\n `Fatal error: ${error instanceof Error ? error.stack || error.message : String(error)}`\n );\n process.exitCode = 1;\n }\n}\n"],"mappings":";;;;;;;;AAsEA,MAAM,OAAO,YAA0B;CACrC,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,SAAQ,IAAI,IAAI,UAAU,IAAI,UAAU;;AAG1C,MAAM,WAAW,OAAU,MAAc,aAA4B;AACnE,KAAI;EACF,MAAM,UAAU,MAAM,SAAS,MAAM,OAAO;AAC5C,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO;;;AAIX,MAAM,YAAY,OAAO,MAAc,UAAkC;AACvE,OAAM,MAAM,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;AAC/C,OAAM,UAAU,MAAM,GAAG,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC,KAAK,OAAO;;AAGtE,MAAM,4BAA4B,cAChC,UACG,WAAW,MAAM,IAAI,CACrB,QAAQ,iBAAiB,GAAG,CAC5B,QAAQ,OAAO,GAAG;AAIvB,MAAM,oBAAoB,KACxB,SAAS,EACT,WACA,uBACA,aACD;AACD,MAAM,kBAAkB,cAAc,OAAO,KAAK,IAAI;AACtD,MAAM,qBAAqB,KAAK,mBAAmB,aAAa;AAChE,MAAM,oBAAoB,QAAQ,IAAI,iBAClC,KAAK,QAAQ,IAAI,gBAAgB,UAAU,mBAAmB,GAC9D,KAAK,SAAS,EAAE,WAAW,mBAAmB;AAElD,MAAM,aAAa,QAAQ,IAAI,2BAA2B;AAC1D,MAAM,YAAY,QAAQ,IAAI,0BAA0B;AAIxD,MAAM,aAAa,UAAwC;AACzD,KAAI,OAAO,OAAO,WAAW,YAAY,MAAM,OAAO,SAAS,IAAI,CACjE,QAAO,MAAM,OAAO,QAAQ,UAAU,GAAG;AAG3C,KAAI,OAAO,OAAO,cAAc,UAAU;EACxC,MAAM,WAAW,MAAM,UAAU,MAAM,8BAA8B;AACrE,MAAI,WAAW,GACb,QAAO,SAAS;AAGlB,MAAI;GACF,MAAM,WAAW,IAAI,IAAI,MAAM,UAAU,CAAC,SACvC,QAAQ,QAAQ,GAAG,CACnB,QAAQ,UAAU,GAAG;AACxB,OAAI,SAAS,SAAS,IAAI,CACxB,QAAO;UAEH;;AAKV,QAAO;;AAGT,MAAM,uBAAsC;CAC1C,MAAM,WAAW,QAAQ,IAAI,gBAAgB,QAAQ,IAAI;AACzD,KAAI,SACF,QAAO;CAGT,MAAM,eAAe;EACnB,QAAQ,IAAI;EACZ;EACA;EACA;EACD,CAAC,OAAO,QAAQ;AAEjB,MAAK,MAAM,aAAa,aACtB,KAAI;EACF,MAAM,QAAQ,aAAa,WAAW,CAAC,QAAQ,QAAQ,EAAE;GACvD,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACpC,CAAC,CAAC,MAAM;AAET,MAAI,MACF,QAAO;SAEH;AAKV,QAAO;;AAGT,MAAM,kBAAkB,OACtB,KACA,UACqB;AACrB,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,KAAK,EAChC,SAAS;GACP,QAAQ;GACR,cAAc;GACd,GAAI,QAAQ,EAAE,eAAe,UAAU,SAAS,GAAG,EAAE;GACtD,EACF,CAAC;AAEF,MAAI,CAAC,SAAS,GACZ,QAAO;AAGT,SAAO,MAAM,SAAS,MAAM;SACtB;AACN,SAAO;;;AAIX,MAAM,gBAAgB,OACpB,WACA,UACuC;CAMvC,MAAM,WAAW;GALC,MAAM,gBACtB,gCAAgC,aAChC,MACD,GAE2B;EAAgB;EAAQ;EAAS,CAAC,OAC5D,QACD;CACD,MAAM,iBAAiB,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AAE7C,MAAK,MAAM,UAAU,gBAAgB;EACnC,MAAM,WAAY,MAAM,gBACtB,gCAAgC,UAAU,aAAa,OAAO,eAC9D,MACD;AAED,MAAI,SACF,QAAO;;AAIX,QAAO;;AAGT,MAAM,wBACJ,UACA,cACkB;CAClB,MAAM,aAAa,yBAAyB,UAAU;AAEtD,KAAI,CAAC,WACH,QAAO,UAAU,OAAO;AAG1B,KAAI,CAAC,MAAM,QAAQ,UAAU,KAAK,CAChC,QAAO;AAOT,QAJoB,SAAS,KAAK,MAC/B,UACC,MAAM,SAAS,UAAU,MAAM,SAAS,WAC3C,EACmB,OAAO;;AAK7B,MAAM,sBAAsB,YAA6C;CACvE,MAAM,0BAAU,IAAI,KAAuB;AAE3C,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,WAAW,QAAQ,IAAI,OAAO,OAAO,IAAI,EAAE;AACjD,WAAS,KAAK,OAAO,UAAU;AAC/B,UAAQ,IAAI,OAAO,QAAQ,SAAS;;AAGtC,QAAO,CAAC,GAAG,QAAQ,SAAS,CAAC,CAC1B,KAAK,CAAC,QAAQ,iBAAiB;EAC9B;EACA,YAAY,WAAW,UAAU;EAClC,EAAE,CACF,UAAU,GAAG,MAAM,EAAE,OAAO,cAAc,EAAE,OAAO,CAAC;;AAGzD,MAAM,yBAAyB,YAAqC;CAClE,MAAM,UAAU,QACb,MAAM,GAAG,EAAE,CACX,KAAK,EAAE,QAAQ,iBAAiB,GAAG,OAAO,IAAI,WAAW,OAAO,GAAG;AAEtE,KAAI,QAAQ,SAAS,EACnB,SAAQ,KAAK,IAAI,QAAQ,SAAS,EAAE,OAAO;AAG7C,QAAO,QAAQ,KAAK,KAAK;;AAG3B,MAAM,kBAAkB,YACtB,WAAW,SAAS,CAAC,OAAO,KAAK,UAAU,QAAQ,CAAC,CAAC,OAAO,MAAM;AAEpE,MAAM,qBAAqB,UACzB,MAAM,WAAW,MAAM,OAAO,CAAC,WAAW,MAAK,OAAM;AAEvD,MAAM,oBAAoB,OAAe,SAAuB;AAC9D,cACE,sBACA,CACE,MACA,yBAAyB,kBAAkB,KAAK,CAAC,gBAAgB,kBAAkB,MAAM,CAAC,GAC3F,EACD,EACE,OAAO,UACR,CACF;;AAGH,MAAM,aAAa,OACjB,WACA,YACkB;AAClB,OAAM,UAAU,YAAY;EAC1B,gCAAe,IAAI,MAAM,EAAC,aAAa;EACvC,uBAAuB;EACvB,GAAI,UAAU,EAAE,SAAS,SAAS,GAAG,EAAE;EACxC,CAAC;;AAcJ,MAAM,mBAAmB,YAAqC;CAC5D,MAAM,WAAW,MAAM,SAAmB,WAAW,EAAE,QAAQ,EAAE,EAAE,CAAC;CACpE,MAAM,UAA0B,EAAE;AAElC,MAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,SAAS,UAAU,EAAE,CAAC,EAAE;AACtE,MAAI,OAAO,eAAe,SACxB;AAGF,MAAI,CAAC,MAAM,aAAa,CAAC,MAAM,gBAC7B;EAGF,MAAM,SAAS,UAAU,MAAM;AAC/B,MAAI,CAAC,OACH;AAGF,UAAQ,KAAK;GACX,WAAW,MAAM;GACjB;GACA;GACA,WAAW,MAAM;GAClB,CAAC;;AAGJ,QAAO;;AAGT,MAAM,cAAc,YAAkC;CACpD,MAAM,gBAAgB,MAAM,kBAAkB;CAC9C,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,UAA0B,EAAE;CAClC,MAAM,SAAuB,EAAE;CAC/B,MAAM,4BAAY,IAAI,KAGnB;AAEH,MAAK,MAAM,WAAW,eAAe;EACnC,IAAI,WAAW,UAAU,IAAI,QAAQ,OAAO;AAE5C,MAAI,aAAa,KAAA,GAAW;AAC1B,cAAW,MAAM,cAAc,QAAQ,QAAQ,MAAM;AACrD,aAAU,IAAI,QAAQ,QAAQ,SAAS;;EAGzC,MAAM,aAAa,WACf,qBAAqB,UAAU,QAAQ,UAAU,GACjD;AAEJ,MAAI,CAAC,YAAY;AACf,UAAO,KAAK;IACV,QAAQ;IACR,QAAQ,QAAQ;IAChB,WAAW,QAAQ;IACpB,CAAC;AACF;;AAGF,MAAI,eAAe,QAAQ,UACzB,SAAQ,KAAK;GACX,WAAW,QAAQ;GACnB;GACA,QAAQ,QAAQ;GAChB,WAAW,QAAQ;GACpB,CAAC;;AAIN,QAAO;EAAE;EAAQ;EAAe;EAAS;;AAG3C,MAAM,OAAO,YAA2B;AACtC,KAAI,oBAAoB,YAAY;AACpC,KAAI,qBAAqB,aAAa;AAEtC,KAAI,CAAC,WAAW,UAAU,EAAE;AAC1B,MAAI,8CAA8C;AAClD,UAAQ,WAAW;AACnB;;CAGF,MAAM,EAAE,eAAe,SAAS,WAAW,MAAM,aAAa;CAC9D,MAAM,UAA2B,mBAAmB,QAAQ;AAE5D,KAAI,WAAW,cAAc,OAAO,0BAA0B;AAE9D,KAAI,OAAO,SAAS,EAClB,MAAK,MAAM,SAAS,OAClB,KAAI,YAAY,MAAM,OAAO,GAAG,MAAM,UAAU,KAAK,MAAM,SAAS;AAIxE,KAAI,QAAQ,WAAW,GAAG;AACxB,MAAI,wBAAwB;AAC5B,QAAM,WAAW,KAAK;AACtB,UAAQ,WAAW;AACnB;;AAGF,MAAK,MAAM,QAAQ,QACjB,KAAI,qBAAqB,KAAK,OAAO,MAAM,KAAK,WAAW,KAAK,KAAK,GAAG;CAG1E,MAAM,QAAQ,MAAM,SAClB,YACA,EAAE,CACH;CACD,MAAM,YAAY,eAAe,QAAQ;AAEzC,KAAI,MAAM,0BAA0B,WAAW;AAC7C,MAAI,6DAA6D;AACjE,QAAM,WAAW,WAAW,QAAQ;AACpC,UAAQ,WAAW;AACnB;;CAGF,MAAM,QAAQ;CACd,MAAM,OAAO,sBAAsB,QAAQ;AAE3C,kBAAiB,OAAO,KAAK;AAC7B,KAAI,sBAAsB,OAAO;AACjC,KAAI,qDAAqD;AAEzD,OAAM,WAAW,WAAW,QAAQ;;AAGtC,MAAM,2BAAoC;CACxC,MAAM,GAAG,YAAY,QAAQ;AAE7B,QAAO,aAAa,KAAA,KAAa,QAAQ,SAAS,KAAK;;AAGzD,IAAI,oBAAoB,CACtB,KAAI;AACF,OAAM,MAAM;SACL,OAAgB;AACvB,KACE,gBAAgB,iBAAiB,QAAQ,MAAM,SAAS,MAAM,UAAU,OAAO,MAAM,GACtF;AACD,SAAQ,WAAW"}
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { copyFile, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst { version, name } = await import(\"../package.json\", {\n with: { type: \"json\" },\n}).then((m) => m.default);\n\nconst ENTRYPOINT_PATH = fileURLToPath(import.meta.url);\nconst PACKAGE_DIR = dirname(ENTRYPOINT_PATH);\nconst CHECKER_SOURCE_PATH = join(PACKAGE_DIR, \"checker.js\");\nconst COMMAND_NAME = name.split(\"/\").at(-1) ?? \"skillwatch\";\nconst APP_DIR_NAME = \"skillwatch\";\nconst LEGACY_APP_DIR_NAME = \"skills-update-notifier\";\n\n// --- Utilities ---\n\nconst fail = (message: string): never => {\n console.error(message);\n process.exit(1);\n};\n\nexport const parseInteger = (\n value: string,\n flagName: string,\n min: number,\n max: number\n): number => {\n const parsed = Number.parseInt(value, 10);\n\n if (!Number.isInteger(parsed) || parsed < min || parsed > max) {\n fail(`${flagName} must be an integer between ${min} and ${max}.`);\n }\n\n return parsed;\n};\n\n// --- LaunchAgent management ---\n\ninterface InstallOptions {\n hour: number;\n minute: number;\n}\n\nconst LABEL = \"com.mblode.skillwatch\";\nconst LEGACY_LABEL = \"com.mblode.skills-check\";\n\nconst getSupportDir = (dirName: string): string =>\n join(homedir(), \"Library\", \"Application Support\", dirName);\n\nconst getAppDir = (): string => getSupportDir(APP_DIR_NAME);\n\nconst getLegacyAppDir = (): string => getSupportDir(LEGACY_APP_DIR_NAME);\n\nconst getLogDirForName = (dirName: string): string =>\n join(homedir(), \"Library\", \"Logs\", dirName);\n\nconst getLogDir = (): string => getLogDirForName(APP_DIR_NAME);\n\nconst getLegacyLogDir = (): string => getLogDirForName(LEGACY_APP_DIR_NAME);\n\nconst getPlistTargetPath = (label = LABEL): string =>\n join(homedir(), \"Library\", \"LaunchAgents\", `${label}.plist`);\n\nconst assertMacOS = (): void => {\n if (process.platform !== \"darwin\") {\n fail(\n `${COMMAND_NAME} only supports macOS because it uses launchd and osascript.`\n );\n }\n};\n\nconst requireCommand = (command: string): void => {\n try {\n execFileSync(\"which\", [command], {\n stdio: \"ignore\",\n });\n } catch {\n fail(`Missing required command: ${command}`);\n }\n};\n\nconst getUid = (): number => {\n if (typeof process.getuid !== \"function\") {\n fail(\"Could not determine the current user id.\");\n }\n\n // Safe: guarded above, fail() returns never\n return (process.getuid as () => number)();\n};\n\nconst writePlist = async (\n templatePath: string,\n options: InstallOptions,\n paths: {\n checkerTargetPath: string;\n stdoutPath: string;\n stderrPath: string;\n }\n): Promise<void> => {\n const plistTargetPath = getPlistTargetPath();\n const template = await readFile(templatePath, \"utf8\");\n const content = template\n .replaceAll(\"__NODE_BIN__\", process.execPath)\n .replaceAll(\"__SCRIPT_PATH__\", paths.checkerTargetPath)\n .replaceAll(\"__HOUR__\", String(options.hour))\n .replaceAll(\"__MINUTE__\", String(options.minute))\n .replaceAll(\"__STDOUT_PATH__\", paths.stdoutPath)\n .replaceAll(\"__STDERR_PATH__\", paths.stderrPath);\n\n await mkdir(dirname(plistTargetPath), { recursive: true });\n await writeFile(plistTargetPath, content, \"utf8\");\n};\n\nconst lintPlist = (): void => {\n execFileSync(\"plutil\", [\"-lint\", getPlistTargetPath()], {\n stdio: \"ignore\",\n });\n};\n\nconst bootoutIfLoaded = (label = LABEL): void => {\n try {\n execFileSync(\"launchctl\", [\"bootout\", `gui/${getUid()}/${label}`], {\n stdio: \"ignore\",\n });\n } catch {\n // Ignore when the agent is not loaded.\n }\n};\n\nconst bootstrapAgent = (): void => {\n execFileSync(\n \"launchctl\",\n [\"bootstrap\", `gui/${getUid()}`, getPlistTargetPath()],\n {\n stdio: \"inherit\",\n }\n );\n};\n\n// --- CLI commands ---\n\nconst printUsage = (): void => {\n console.log(\n `\n${name} ${version}\n\nUsage:\n ${COMMAND_NAME} install [--hour 9] [--minute 0]\n ${COMMAND_NAME} uninstall\n ${COMMAND_NAME} check-now\n ${COMMAND_NAME} --help\n ${COMMAND_NAME} --version\n\nCommands:\n install Install the daily LaunchAgent and checker script\n uninstall Remove the LaunchAgent, checker script, and logs\n check-now Run the checker immediately\n\nOptions for install:\n --hour <0-23> Daily check hour, default 9\n --minute <0-59> Daily check minute, default 0\n`.trim()\n );\n};\n\nconst parseInstallOptions = (args: string[]): InstallOptions => {\n const options: InstallOptions = {\n hour: parseInteger(process.env.CHECK_HOUR ?? \"9\", \"CHECK_HOUR\", 0, 23),\n minute: parseInteger(\n process.env.CHECK_MINUTE ?? \"0\",\n \"CHECK_MINUTE\",\n 0,\n 59\n ),\n };\n\n for (let index = 0; index < args.length; index += 1) {\n const current = args[index];\n\n if (current === \"--help\" || current === \"-h\") {\n printUsage();\n process.exit(0);\n }\n\n if (current === \"--hour\") {\n index += 1;\n options.hour = parseInteger(args[index] ?? \"\", \"--hour\", 0, 23);\n continue;\n }\n\n if (current === \"--minute\") {\n index += 1;\n options.minute = parseInteger(args[index] ?? \"\", \"--minute\", 0, 59);\n continue;\n }\n\n fail(`Unknown install option: ${current}`);\n }\n\n return options;\n};\n\nconst installCommand = async (args: string[]): Promise<void> => {\n assertMacOS();\n requireCommand(\"launchctl\");\n requireCommand(\"plutil\");\n\n const options = parseInstallOptions(args);\n const appDir = getAppDir();\n const logDir = getLogDir();\n const checkerTargetPath = join(appDir, \"checker.js\");\n const plistTemplatePath = join(\n PACKAGE_DIR,\n \"com.mblode.skillwatch.plist.template\"\n );\n\n await mkdir(appDir, { recursive: true });\n await mkdir(logDir, { recursive: true });\n await copyFile(CHECKER_SOURCE_PATH, checkerTargetPath);\n await writePlist(plistTemplatePath, options, {\n checkerTargetPath,\n stderrPath: join(logDir, \"stderr.log\"),\n stdoutPath: join(logDir, \"stdout.log\"),\n });\n lintPlist();\n\n bootoutIfLoaded();\n bootstrapAgent();\n\n console.log(\n `\nInstalled ${COMMAND_NAME}.\n\nFiles:\n agent: ${getPlistTargetPath()}\n script: ${checkerTargetPath}\n logs: ${logDir}\n\nCheck now:\n npx ${COMMAND_NAME} check-now\n launchctl kickstart -k gui/${getUid()}/${LABEL}\n`.trim()\n );\n};\n\nconst uninstallCommand = async (): Promise<void> => {\n assertMacOS();\n requireCommand(\"launchctl\");\n\n bootoutIfLoaded();\n bootoutIfLoaded(LEGACY_LABEL);\n await rm(getPlistTargetPath(), { force: true });\n await rm(getPlistTargetPath(LEGACY_LABEL), { force: true });\n await rm(getAppDir(), { force: true, recursive: true });\n await rm(getLegacyAppDir(), { force: true, recursive: true });\n await rm(getLogDir(), { force: true, recursive: true });\n await rm(getLegacyLogDir(), { force: true, recursive: true });\n\n console.log(`Uninstalled ${COMMAND_NAME}.`);\n};\n\nconst checkNowCommand = (): void => {\n assertMacOS();\n const appDir = getAppDir();\n const installedChecker = join(appDir, \"checker.js\");\n const checkerPath = existsSync(installedChecker)\n ? installedChecker\n : CHECKER_SOURCE_PATH;\n\n execFileSync(process.execPath, [checkerPath], {\n env: process.env,\n stdio: \"inherit\",\n });\n};\n\nconst main = async (): Promise<void> => {\n const [rawCommand, ...rest] = process.argv.slice(2);\n const command = rawCommand === \"--uninstall\" ? \"uninstall\" : rawCommand;\n\n if (\n !command ||\n command === \"--help\" ||\n command === \"-h\" ||\n command === \"help\"\n ) {\n printUsage();\n return;\n }\n\n if (command === \"--version\" || command === \"-v\" || command === \"version\") {\n console.log(version);\n return;\n }\n\n if (command === \"install\") {\n await installCommand(rest);\n return;\n }\n\n if (command === \"uninstall\") {\n await uninstallCommand();\n return;\n }\n\n if (command === \"check-now\") {\n checkNowCommand();\n return;\n }\n\n fail(`Unknown command: ${command}`);\n};\n\nconst isExecutedDirectly = (): boolean => {\n const [, entryArg] = process.argv;\n\n return entryArg !== undefined && resolve(entryArg) === ENTRYPOINT_PATH;\n};\n\nif (isExecutedDirectly()) {\n try {\n await main();\n } catch (error: unknown) {\n fail(error instanceof Error ? error.stack || error.message : String(error));\n }\n}\n"],"mappings":"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"}
@@ -1,28 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>Label</key>
6
- <string>com.mblode.skillwatch</string>
7
-
8
- <key>ProgramArguments</key>
9
- <array>
10
- <string>__NODE_BIN__</string>
11
- <string>__SCRIPT_PATH__</string>
12
- </array>
13
-
14
- <key>StartCalendarInterval</key>
15
- <dict>
16
- <key>Hour</key>
17
- <integer>__HOUR__</integer>
18
- <key>Minute</key>
19
- <integer>__MINUTE__</integer>
20
- </dict>
21
-
22
- <key>StandardOutPath</key>
23
- <string>__STDOUT_PATH__</string>
24
-
25
- <key>StandardErrorPath</key>
26
- <string>__STDERR_PATH__</string>
27
- </dict>
28
- </plist>
@@ -1,59 +0,0 @@
1
- //#region package.json
2
- var package_default = {
3
- name: "skillwatch",
4
- version: "0.1.0",
5
- description: "Daily macOS notifications when installed GitHub-backed skills have updates.",
6
- keywords: [
7
- "agent-skills",
8
- "launchd",
9
- "macos",
10
- "notification",
11
- "skills"
12
- ],
13
- homepage: "https://github.com/mblode/update-skills#readme",
14
- bugs: { "url": "https://github.com/mblode/update-skills/issues" },
15
- license: "MIT",
16
- author: "Matt Blode",
17
- repository: {
18
- "type": "git",
19
- "url": "git+https://github.com/mblode/update-skills.git"
20
- },
21
- bin: "./dist/cli.js",
22
- files: [
23
- "dist",
24
- "README.md",
25
- "LICENSE.md"
26
- ],
27
- type: "module",
28
- publishConfig: { "access": "public" },
29
- scripts: {
30
- "build": "tsdown && cp com.mblode.skillwatch.plist.template dist/",
31
- "dev": "tsdown --watch",
32
- "typecheck": "tsc --noEmit",
33
- "check": "ultracite check",
34
- "fix": "ultracite fix",
35
- "test": "vitest run",
36
- "smoke": "SKILLS_CHECK_LOCK_PATH=/tmp/skillwatch-missing-lock.json SKILLS_CHECK_STATE_PATH=/tmp/skillwatch-state.json node dist/checker.js && node dist/cli.js --help && node dist/cli.js --version",
37
- "validate": "npm run build && npm run typecheck && npm run check && npm run test && npm run smoke",
38
- "pack:dry-run": "npm pack --dry-run",
39
- "changeset": "changeset",
40
- "release": "npm run build && changeset publish",
41
- "prepare": "git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true"
42
- },
43
- devDependencies: {
44
- "@changesets/cli": "^2.29.0",
45
- "@types/node": "^22.15.0",
46
- "lefthook": "^2.1.4",
47
- "oxfmt": "^0.41.0",
48
- "oxlint": "^1.56.0",
49
- "tsdown": "^0.12.0",
50
- "typescript": "^5.8.0",
51
- "ultracite": "^7.3.2",
52
- "vitest": "^3.1.0"
53
- },
54
- engines: { "node": ">=22" }
55
- };
56
- //#endregion
57
- export { package_default as default };
58
-
59
- //# sourceMappingURL=package-D8pw1JVM.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"package-D8pw1JVM.js","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}