@jacebenson/jsn 3.1.5 → 3.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/bin/jsn.js +14 -2
- package/package.json +6 -4
- package/skills/servicenow/SKILL.md +31 -3
- package/src/app.js +15 -1
- package/src/auth.js +34 -3
- package/src/cli.js +59 -11
- package/src/commands/docs/db.js +140 -0
- package/src/commands/docs/docs.js +166 -0
- package/src/commands/docs/embed.js +46 -0
- package/src/commands/docs/hrr.js +95 -0
- package/src/commands/docs/ingest.js +95 -0
- package/src/commands/docs/public/index.html +178 -0
- package/src/commands/docs/public/vendor/marked.min.js +69 -0
- package/src/commands/docs/public/vendor/purify.min.js +3 -0
- package/src/commands/docs/refresh.js +135 -0
- package/src/commands/docs/search.js +139 -0
- package/src/commands/docs/serve.js +278 -0
- package/src/commands/docs/sync.js +75 -0
- package/src/commands/skill.js +203 -27
- package/src/commands/version.js +2 -2
- package/suppress-warnings.cjs +13 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Clone or update the ServiceNow docs repo locally.
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { getDocsSourceDir, getDocsSourceMarkdownDir, getDocsDbPath, docsDbExists } from './db.js';
|
|
7
|
+
import { ingestDocs } from './ingest.js';
|
|
8
|
+
import { refreshDocs } from './refresh.js';
|
|
9
|
+
|
|
10
|
+
const REPO_URL = 'https://github.com/ServiceNow/ServiceNowDocs';
|
|
11
|
+
const BRANCH = 'australia';
|
|
12
|
+
|
|
13
|
+
function git(args, cwd, stdio = 'inherit') {
|
|
14
|
+
const r = spawnSync('git', args, { cwd, stdio });
|
|
15
|
+
if (r.status !== 0) {
|
|
16
|
+
throw new Error(`git ${args[0]} failed (exit ${r.status}).`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function syncDocs(opts = {}) {
|
|
21
|
+
const repoDir = opts.repoDir || getDocsSourceDir();
|
|
22
|
+
const markdownDir = opts.markdownDir || getDocsSourceMarkdownDir();
|
|
23
|
+
const dbPath = opts.dbPath || getDocsDbPath();
|
|
24
|
+
const embed = opts.embed !== false;
|
|
25
|
+
const noIngest = opts.noIngest === true;
|
|
26
|
+
|
|
27
|
+
const initialStatus = {
|
|
28
|
+
clone: !fs.existsSync(path.join(repoDir, '.git')),
|
|
29
|
+
docsApprox: 45000,
|
|
30
|
+
willEmbed: embed && noIngest === false,
|
|
31
|
+
minutesHint: embed ? 3 : 1,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
process.stderr.write('Note: docs sync downloads ~45k markdown files and builds an FTS5 + HRR index. ');
|
|
35
|
+
process.stderr.write(`This normally takes ${initialStatus.minutesHint}-${initialStatus.minutesHint + 2} minutes.\n`);
|
|
36
|
+
if (initialStatus.willEmbed) {
|
|
37
|
+
process.stderr.write('Pass --no-embed to skip semantic embeddings and finish faster.\n');
|
|
38
|
+
}
|
|
39
|
+
process.stderr.write('\n');
|
|
40
|
+
|
|
41
|
+
if (!initialStatus.clone) {
|
|
42
|
+
process.stderr.write(`Updating existing clone at ${repoDir} ...\n`);
|
|
43
|
+
git(['-C', repoDir, 'pull', '--ff-only'], repoDir, 'inherit');
|
|
44
|
+
} else {
|
|
45
|
+
fs.mkdirSync(path.dirname(repoDir), { recursive: true });
|
|
46
|
+
process.stderr.write(`Cloning ${REPO_URL} (branch ${BRANCH}) into ${repoDir} ...\n`);
|
|
47
|
+
git([
|
|
48
|
+
'clone',
|
|
49
|
+
'--depth', '1',
|
|
50
|
+
'--branch', BRANCH,
|
|
51
|
+
'--config', 'core.longpaths=true',
|
|
52
|
+
REPO_URL,
|
|
53
|
+
repoDir,
|
|
54
|
+
], undefined, 'inherit');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(markdownDir)) {
|
|
58
|
+
throw new Error(`Expected markdown folder not found at ${markdownDir}.`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (noIngest) {
|
|
62
|
+
return { repoDir, markdownDir, dbPath, ingested: false, note: 'Run without --no-ingest to build the index.' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If the DB already exists, use incremental refresh. Otherwise, full ingest.
|
|
66
|
+
if (docsDbExists()) {
|
|
67
|
+
process.stderr.write(`Docs DB found — running incremental refresh...\n`);
|
|
68
|
+
const result = refreshDocs({ docsDir: markdownDir, dbPath });
|
|
69
|
+
return { repoDir, markdownDir, dbPath, ingested: true, incremental: true, ...result };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.stderr.write('No existing DB — running full ingest...\n');
|
|
73
|
+
const result = ingestDocs({ docsDir: markdownDir, dbPath, embed });
|
|
74
|
+
return { repoDir, markdownDir, dbPath, ingested: true, incremental: false, ...result };
|
|
75
|
+
}
|
package/src/commands/skill.js
CHANGED
|
@@ -4,32 +4,86 @@ import os from 'node:os';
|
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
|
|
7
9
|
const SKILL_NAME = 'servicenow';
|
|
8
10
|
const SKILL_REPO_PATH = path.join(
|
|
9
11
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
10
12
|
'..', '..', 'skills', SKILL_NAME, 'SKILL.md'
|
|
11
13
|
);
|
|
12
14
|
|
|
13
|
-
const HERMES_SKILL_PATH = path.join(os.homedir(), '.hermes', 'skills', SKILL_NAME, 'SKILL.md');
|
|
14
|
-
|
|
15
15
|
const SKILL_RAW_URL = 'https://raw.githubusercontent.com/jacebenson/jsn/nodejs/skills/servicenow/SKILL.md';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the user's real home directory, even when Hermes overrides $HOME.
|
|
19
|
+
*/
|
|
20
|
+
function realHomeDir() {
|
|
21
|
+
try {
|
|
22
|
+
const result = execSync('getent passwd ' + process.env.USER + ' 2>/dev/null', { encoding: 'utf-8', timeout: 3000 });
|
|
23
|
+
const home = result.trim().split(':')[5];
|
|
24
|
+
if (home) return home;
|
|
25
|
+
} catch {
|
|
26
|
+
// fall through
|
|
27
|
+
}
|
|
28
|
+
return os.homedir();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the Hermes profile skill directory.
|
|
33
|
+
* Checks HERMES_PROFILE env var, then scans ~/.hermes/profiles/ for the first
|
|
34
|
+
* profile directory, falling back to ~/.hermes/skills/ (legacy symlink path).
|
|
35
|
+
*/
|
|
36
|
+
function hermesSkillDir() {
|
|
37
|
+
const base = path.join(realHomeDir(), '.hermes');
|
|
38
|
+
const profileEnv = process.env.HERMES_PROFILE;
|
|
39
|
+
let profileName = profileEnv;
|
|
40
|
+
|
|
41
|
+
if (!profileName) {
|
|
42
|
+
try {
|
|
43
|
+
const profiles = fs.readdirSync(path.join(base, 'profiles'));
|
|
44
|
+
// Pick the first real profile directory (skip . and ..)
|
|
45
|
+
profileName = profiles.find(d => d !== '.' && d !== '..') || null;
|
|
46
|
+
} catch {
|
|
47
|
+
profileName = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (profileName) {
|
|
52
|
+
return path.join(base, 'profiles', profileName, 'skills', SKILL_NAME);
|
|
53
|
+
}
|
|
54
|
+
return path.join(base, 'skills', SKILL_NAME);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Resolve the installed SKILL.md path for Hermes. */
|
|
58
|
+
function hermesSkillPath() {
|
|
59
|
+
return path.join(hermesSkillDir(), 'SKILL.md');
|
|
60
|
+
}
|
|
61
|
+
|
|
17
62
|
// Known agent skill directories (personal/user-level)
|
|
18
63
|
// Keyed by agent name for the --target flag
|
|
64
|
+
// Each value is the directory for the <name>/SKILL.md file
|
|
19
65
|
const AGENT_SKILL_DIRS = {
|
|
20
|
-
hermes:
|
|
21
|
-
copilot: path.join(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
66
|
+
hermes: hermesSkillDir(),
|
|
67
|
+
copilot: path.join(realHomeDir(), '.copilot', 'skills', SKILL_NAME),
|
|
68
|
+
vscode: path.join(realHomeDir(), '.copilot', 'instructions', SKILL_NAME),
|
|
69
|
+
claude: path.join(realHomeDir(), '.claude', 'skills', SKILL_NAME),
|
|
70
|
+
cursor: path.join(realHomeDir(), '.cursor', 'rules'),
|
|
71
|
+
agents: path.join(realHomeDir(), '.agents', 'skills', SKILL_NAME),
|
|
72
|
+
opencode: path.join(realHomeDir(), '.config', 'opencode', 'skills', SKILL_NAME),
|
|
73
|
+
openclaw: path.join(realHomeDir(), '.openclaw', 'skills', SKILL_NAME),
|
|
74
|
+
codex: path.join(realHomeDir(), '.codex', 'skills', SKILL_NAME),
|
|
25
75
|
};
|
|
26
76
|
|
|
27
77
|
const TARGET_NAMES = {
|
|
28
78
|
hermes: 'Hermes Agent',
|
|
29
79
|
copilot: 'GitHub Copilot',
|
|
80
|
+
vscode: 'VS Code (Copilot Instructions)',
|
|
30
81
|
claude: 'Claude Code',
|
|
31
82
|
cursor: 'Cursor',
|
|
32
83
|
agents: 'Agents (open standard)',
|
|
84
|
+
opencode: 'OpenCode',
|
|
85
|
+
openclaw: 'OpenClaw',
|
|
86
|
+
codex: 'Codex CLI',
|
|
33
87
|
};
|
|
34
88
|
|
|
35
89
|
function readBundledSkill() {
|
|
@@ -40,21 +94,32 @@ function readBundledSkill() {
|
|
|
40
94
|
}
|
|
41
95
|
}
|
|
42
96
|
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
97
|
+
export async function checkSkill() {
|
|
98
|
+
// Compare all installed skill copies against GitHub.
|
|
99
|
+
// Uses a single HTTP fetch and reads all harness paths that exist.
|
|
100
|
+
// Returns the first installed version found for comparison, plus a list
|
|
101
|
+
// of harnesses where the skill is current vs outdated (or missing).
|
|
102
|
+
const SKILL_NAME = 'servicenow';
|
|
103
|
+
|
|
104
|
+
// Collect all installed copies that actually exist on disk
|
|
105
|
+
const installedCopies = [];
|
|
106
|
+
for (const [key, dir] of Object.entries(AGENT_SKILL_DIRS)) {
|
|
107
|
+
const skillPath = key === 'cursor'
|
|
108
|
+
? path.join(dir, `${SKILL_NAME}.mdc`)
|
|
109
|
+
: path.join(dir, 'SKILL.md');
|
|
110
|
+
try {
|
|
111
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
112
|
+
installedCopies.push({ harness: key, label: TARGET_NAMES[key], content, path: skillPath });
|
|
113
|
+
} catch {
|
|
114
|
+
// Not installed to this harness — skip
|
|
115
|
+
}
|
|
48
116
|
}
|
|
49
|
-
}
|
|
50
117
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const installed = readInstalledSkill() || readBundledSkill();
|
|
55
|
-
if (!installed) return { current: false, error: 'Skill file not found' };
|
|
118
|
+
// Fall back to bundled skill if nothing installed yet
|
|
119
|
+
const primary = installedCopies[0] || { content: readBundledSkill() };
|
|
120
|
+
if (!primary.content) return { current: false, error: 'Skill file not found' };
|
|
56
121
|
|
|
57
|
-
const installedVersion = extractVersion(
|
|
122
|
+
const installedVersion = extractVersion(primary.content);
|
|
58
123
|
if (!installedVersion) return { current: false, error: 'No version field in installed skill' };
|
|
59
124
|
|
|
60
125
|
try {
|
|
@@ -68,11 +133,30 @@ export async function checkSkill() {
|
|
|
68
133
|
if (!upstreamVersion) return { current: false, error: 'No version field in upstream skill' };
|
|
69
134
|
|
|
70
135
|
const current = installedVersion === upstreamVersion;
|
|
136
|
+
|
|
137
|
+
// Check each installed copy for version match
|
|
138
|
+
const outdated = [];
|
|
139
|
+
for (const copy of installedCopies) {
|
|
140
|
+
const v = extractVersion(copy.content);
|
|
141
|
+
if (v && v !== upstreamVersion) {
|
|
142
|
+
outdated.push(copy.label);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const outdatedMsg = outdated.length > 0
|
|
147
|
+
? ` Skill outdated for ${outdated.join(', ')} (v${installedVersion} vs GitHub v${upstreamVersion}) — run "jsn skill install" to update`
|
|
148
|
+
: null;
|
|
149
|
+
|
|
71
150
|
return {
|
|
72
151
|
current,
|
|
73
152
|
installed_version: installedVersion,
|
|
74
153
|
upstream_version: upstreamVersion,
|
|
75
|
-
|
|
154
|
+
checked: installedCopies.length,
|
|
155
|
+
harnesses: {
|
|
156
|
+
installed: installedCopies.map(c => c.label),
|
|
157
|
+
outdated,
|
|
158
|
+
},
|
|
159
|
+
error: current ? null : outdatedMsg,
|
|
76
160
|
};
|
|
77
161
|
} catch {
|
|
78
162
|
return { current: false, error: 'Could not check — GitHub unreachable' };
|
|
@@ -85,6 +169,9 @@ function extractVersion(content) {
|
|
|
85
169
|
return m ? m[1] : null;
|
|
86
170
|
}
|
|
87
171
|
|
|
172
|
+
// Exported for testing
|
|
173
|
+
export { realHomeDir, hermesSkillDir, extractVersion, AGENT_SKILL_DIRS, TARGET_NAMES };
|
|
174
|
+
|
|
88
175
|
export function skillCmd(wrap) {
|
|
89
176
|
return {
|
|
90
177
|
command: 'skill',
|
|
@@ -112,13 +199,28 @@ export function skillCmd(wrap) {
|
|
|
112
199
|
describe: 'Check if the bundled skill file is up to date with GitHub',
|
|
113
200
|
handler: wrap(async (_argv, app) => {
|
|
114
201
|
const result = await checkSkill();
|
|
202
|
+
const h = result.harnesses || {};
|
|
203
|
+
|
|
204
|
+
// Build a summary that shows which harnesses are installed and current
|
|
205
|
+
let summary;
|
|
115
206
|
if (result.error && !result.current) {
|
|
116
|
-
|
|
207
|
+
summary = result.error;
|
|
117
208
|
} else if (result.current) {
|
|
118
|
-
|
|
209
|
+
const parts = [`✓ Skill is current (v${result.installed_version})`];
|
|
210
|
+
if (h.installed && h.installed.length > 0) {
|
|
211
|
+
parts.push(` — installed for ${h.installed.join(', ')}`);
|
|
212
|
+
}
|
|
213
|
+
summary = parts.join('');
|
|
119
214
|
} else {
|
|
120
|
-
|
|
215
|
+
const parts = [`⚠ Skill outdated: installed v${result.installed_version} vs GitHub v${result.upstream_version}`];
|
|
216
|
+
if (h.outdated && h.outdated.length > 0) {
|
|
217
|
+
parts.push(` — outdated for ${h.outdated.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
parts.push(` — run "jsn skill install" to update`);
|
|
220
|
+
summary = parts.join('');
|
|
121
221
|
}
|
|
222
|
+
|
|
223
|
+
app.ok(result, { summary });
|
|
122
224
|
}),
|
|
123
225
|
})
|
|
124
226
|
.command({
|
|
@@ -148,7 +250,7 @@ export function skillCmd(wrap) {
|
|
|
148
250
|
}, {
|
|
149
251
|
summary: 'Skill file locations and install targets',
|
|
150
252
|
breadcrumbs: [
|
|
151
|
-
{ action: 'install', cmd: 'jsn skill install', description: '
|
|
253
|
+
{ action: 'install', cmd: 'jsn skill install', description: 'Interactive picker for install targets' },
|
|
152
254
|
{ action: 'install-all', cmd: 'jsn skill install --target all', description: 'Install to all supported agents' },
|
|
153
255
|
{ action: 'install-copilot', cmd: 'jsn skill install --target copilot', description: 'Install for GitHub Copilot' },
|
|
154
256
|
{ action: 'install-claude', cmd: 'jsn skill install --target claude', description: 'Install for Claude Code' },
|
|
@@ -166,8 +268,7 @@ export function skillCmd(wrap) {
|
|
|
166
268
|
})
|
|
167
269
|
.option('target', {
|
|
168
270
|
type: 'string',
|
|
169
|
-
describe: 'Target agent(s): hermes, copilot, claude, cursor, agents, or "all" (comma-separated)',
|
|
170
|
-
default: 'hermes',
|
|
271
|
+
describe: 'Target agent(s): hermes, copilot, vscode, claude, cursor, agents, opencode, openclaw, codex, or "all" (comma-separated). Omit for interactive picker.',
|
|
171
272
|
}),
|
|
172
273
|
handler: wrap(async (argv, app) => {
|
|
173
274
|
const res = await fetch(SKILL_RAW_URL);
|
|
@@ -184,7 +285,8 @@ export function skillCmd(wrap) {
|
|
|
184
285
|
const targetPath = path.join(p, 'SKILL.md');
|
|
185
286
|
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
186
287
|
targets.push({ name: path.basename(argv.dir), path: targetPath });
|
|
187
|
-
} else {
|
|
288
|
+
} else if (argv.target) {
|
|
289
|
+
// Explicit --target: install to specified agents
|
|
188
290
|
const rawTargets = argv.target.split(',').map(t => t.trim().toLowerCase());
|
|
189
291
|
const all = rawTargets.includes('all');
|
|
190
292
|
|
|
@@ -204,6 +306,80 @@ export function skillCmd(wrap) {
|
|
|
204
306
|
}
|
|
205
307
|
}
|
|
206
308
|
}
|
|
309
|
+
} else if (process.stdin.isTTY) {
|
|
310
|
+
// Interactive: show multi-select picker
|
|
311
|
+
const fetchedVersion = extractVersion(content);
|
|
312
|
+
// Check if any existing installed copies are outdated
|
|
313
|
+
const outdated = [];
|
|
314
|
+
if (fetchedVersion) {
|
|
315
|
+
for (const [key, dir] of Object.entries(AGENT_SKILL_DIRS)) {
|
|
316
|
+
const skillPath = key === 'cursor'
|
|
317
|
+
? path.join(dir, `${SKILL_NAME}.mdc`)
|
|
318
|
+
: path.join(dir, 'SKILL.md');
|
|
319
|
+
try {
|
|
320
|
+
const existing = fs.readFileSync(skillPath, 'utf-8');
|
|
321
|
+
const existingVersion = extractVersion(existing);
|
|
322
|
+
if (existingVersion && existingVersion !== fetchedVersion) {
|
|
323
|
+
outdated.push(TARGET_NAMES[key]);
|
|
324
|
+
}
|
|
325
|
+
} catch {
|
|
326
|
+
// Not installed — skip
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const promptMsg = outdated.length > 0
|
|
331
|
+
? `Where should the jsn skill be installed? (${outdated.length} outdated)`
|
|
332
|
+
: 'Where should the jsn skill be installed?';
|
|
333
|
+
|
|
334
|
+
const { default: checkbox } = await import('@inquirer/checkbox');
|
|
335
|
+
const choices = Object.entries(TARGET_NAMES).map(([key, label]) => {
|
|
336
|
+
const dir = AGENT_SKILL_DIRS[key];
|
|
337
|
+
const targetPath = key === 'cursor'
|
|
338
|
+
? path.join(dir, `${SKILL_NAME}.mdc`)
|
|
339
|
+
: path.join(dir, 'SKILL.md');
|
|
340
|
+
// Show tildified path for readability
|
|
341
|
+
const home = realHomeDir();
|
|
342
|
+
const displayPath = targetPath.replace(home, '~');
|
|
343
|
+
return {
|
|
344
|
+
name: label,
|
|
345
|
+
value: key,
|
|
346
|
+
description: displayPath,
|
|
347
|
+
// Pre-check agents whose skill dirs already exist
|
|
348
|
+
checked: fs.existsSync(AGENT_SKILL_DIRS[key]),
|
|
349
|
+
};
|
|
350
|
+
});
|
|
351
|
+
const selected = await checkbox({
|
|
352
|
+
message: promptMsg,
|
|
353
|
+
choices,
|
|
354
|
+
instructions: '(space to toggle, enter to confirm)',
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
if (selected.length === 0) {
|
|
358
|
+
// Fall back to Hermes if nothing selected
|
|
359
|
+
selected.push('hermes');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const key of selected) {
|
|
363
|
+
const dir = AGENT_SKILL_DIRS[key];
|
|
364
|
+
if (key === 'cursor') {
|
|
365
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
366
|
+
const targetPath = path.join(dir, `${SKILL_NAME}.mdc`);
|
|
367
|
+
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
368
|
+
targets.push({ name: TARGET_NAMES[key], path: targetPath });
|
|
369
|
+
} else {
|
|
370
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
371
|
+
const targetPath = path.join(dir, 'SKILL.md');
|
|
372
|
+
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
373
|
+
targets.push({ name: TARGET_NAMES[key], path: targetPath });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
// Non-interactive, no --target: install to Hermes only (backward compat)
|
|
378
|
+
const dir = AGENT_SKILL_DIRS.hermes;
|
|
379
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
380
|
+
const targetPath = path.join(dir, 'SKILL.md');
|
|
381
|
+
fs.writeFileSync(targetPath, content, 'utf-8');
|
|
382
|
+
targets.push({ name: TARGET_NAMES.hermes, path: targetPath });
|
|
207
383
|
}
|
|
208
384
|
|
|
209
385
|
if (targets.length === 0) {
|
package/src/commands/version.js
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
const NPM_PKG = '@jacebenson/jsn';
|
|
6
6
|
const NPM_REGISTRY = `https://registry.npmjs.org/${NPM_PKG}/latest`;
|
|
7
7
|
|
|
8
|
-
function getVersion() {
|
|
8
|
+
export function getVersion() {
|
|
9
9
|
try {
|
|
10
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
const pkgPath = path.join(__dirname, '..', '..', 'package.json');
|
|
@@ -16,7 +16,7 @@ function getVersion() {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async function checkLatest() {
|
|
19
|
+
export async function checkLatest() {
|
|
20
20
|
try {
|
|
21
21
|
const res = await fetch(NPM_REGISTRY);
|
|
22
22
|
if (!res.ok) return null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Suppresses the node:sqlite ExperimentalWarning on Node 22.x.
|
|
2
|
+
// Loaded via NODE_OPTIONS=--require before the main ESM entry point.
|
|
3
|
+
|
|
4
|
+
const original = process.emitWarning;
|
|
5
|
+
process.emitWarning = function emitWarning(warning, ...args) {
|
|
6
|
+
const message = typeof warning === 'string'
|
|
7
|
+
? warning
|
|
8
|
+
: (warning?.message || '');
|
|
9
|
+
if (message.includes('SQLite')) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
return original.apply(this, [warning, ...args]);
|
|
13
|
+
};
|