@getlore/cli 0.3.0 → 0.5.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/dist/cli/commands/ingest.d.ts +8 -0
- package/dist/cli/commands/ingest.js +104 -0
- package/dist/cli/commands/sync.d.ts +12 -0
- package/dist/cli/commands/sync.js +35 -19
- package/dist/cli/commands/update.d.ts +8 -0
- package/dist/cli/commands/update.js +71 -0
- package/dist/cli/update-notifier.d.ts +12 -0
- package/dist/cli/update-notifier.js +95 -0
- package/dist/index.js +12 -2
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ingest Command
|
|
3
|
+
*
|
|
4
|
+
* Push content directly into Lore from the CLI.
|
|
5
|
+
* Accepts inline text, a file path, or piped stdin.
|
|
6
|
+
*/
|
|
7
|
+
import type { Command } from 'commander';
|
|
8
|
+
export declare function registerIngestCommand(program: Command, defaultDataDir: string): void;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ingest Command
|
|
3
|
+
*
|
|
4
|
+
* Push content directly into Lore from the CLI.
|
|
5
|
+
* Accepts inline text, a file path, or piped stdin.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, existsSync } from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
export function registerIngestCommand(program, defaultDataDir) {
|
|
10
|
+
program
|
|
11
|
+
.command('ingest')
|
|
12
|
+
.description('Ingest content into the knowledge base')
|
|
13
|
+
.argument('[content]', 'Content to ingest (or use --file / stdin)')
|
|
14
|
+
.option('-f, --file <path>', 'Read content from a file')
|
|
15
|
+
.option('-t, --title <title>', 'Document title')
|
|
16
|
+
.option('-p, --project <project>', 'Project name', 'default')
|
|
17
|
+
.option('--type <type>', 'Source type (e.g. meeting, notes, article)')
|
|
18
|
+
.option('--url <url>', 'Source URL for citation linking')
|
|
19
|
+
.option('--name <name>', 'Human-readable source name')
|
|
20
|
+
.option('--tags <tags>', 'Comma-separated tags')
|
|
21
|
+
.option('-d, --data-dir <dir>', 'Data directory', defaultDataDir)
|
|
22
|
+
.action(async (contentArg, options) => {
|
|
23
|
+
const { handleIngest } = await import('../../mcp/handlers/ingest.js');
|
|
24
|
+
const dataDir = options.dataDir;
|
|
25
|
+
const dbPath = path.join(dataDir, 'lore.lance');
|
|
26
|
+
// Resolve content from argument, file, or stdin
|
|
27
|
+
let content;
|
|
28
|
+
if (options.file) {
|
|
29
|
+
const filePath = options.file.replace(/^~/, process.env.HOME || '~');
|
|
30
|
+
if (!existsSync(filePath)) {
|
|
31
|
+
console.error(`File not found: ${filePath}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
content = readFileSync(filePath, 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
else if (contentArg) {
|
|
37
|
+
content = contentArg;
|
|
38
|
+
}
|
|
39
|
+
else if (!process.stdin.isTTY) {
|
|
40
|
+
// Reading from pipe/stdin
|
|
41
|
+
content = readFileSync(0, 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.error('No content provided. Use one of:');
|
|
45
|
+
console.error(' lore ingest "Your content here"');
|
|
46
|
+
console.error(' lore ingest --file ./notes.md');
|
|
47
|
+
console.error(' echo "content" | lore ingest');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
content = content.trim();
|
|
51
|
+
if (!content) {
|
|
52
|
+
console.error('Content is empty.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
// Derive title from file name or content
|
|
56
|
+
let title = options.title;
|
|
57
|
+
if (!title && options.file) {
|
|
58
|
+
title = path.basename(options.file, path.extname(options.file));
|
|
59
|
+
}
|
|
60
|
+
if (!title) {
|
|
61
|
+
// Use first line or first 60 chars
|
|
62
|
+
const firstLine = content.split('\n')[0].replace(/^#+\s*/, '');
|
|
63
|
+
title = firstLine.length > 60 ? firstLine.slice(0, 57) + '...' : firstLine;
|
|
64
|
+
}
|
|
65
|
+
const tags = options.tags ? options.tags.split(',').map((t) => t.trim()) : [];
|
|
66
|
+
console.log(`\nIngesting: ${title}`);
|
|
67
|
+
console.log(`Project: ${options.project}`);
|
|
68
|
+
if (options.type)
|
|
69
|
+
console.log(`Type: ${options.type}`);
|
|
70
|
+
console.log(`Content: ${content.length} chars`);
|
|
71
|
+
console.log('');
|
|
72
|
+
const result = await handleIngest(dbPath, dataDir, {
|
|
73
|
+
content,
|
|
74
|
+
title,
|
|
75
|
+
project: options.project,
|
|
76
|
+
source_type: options.type,
|
|
77
|
+
tags,
|
|
78
|
+
source_url: options.url,
|
|
79
|
+
source_name: options.name,
|
|
80
|
+
}, {
|
|
81
|
+
hookContext: { mode: 'cli' },
|
|
82
|
+
});
|
|
83
|
+
if (result.deduplicated) {
|
|
84
|
+
console.log('Already exists (identical content). Skipped.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (result.success) {
|
|
88
|
+
console.log(`Ingested (ID: ${result.id})`);
|
|
89
|
+
if (result.indexed) {
|
|
90
|
+
console.log('Indexed and searchable.');
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.log('Saved to disk. Run "lore sync" to index.');
|
|
94
|
+
}
|
|
95
|
+
if (result.synced) {
|
|
96
|
+
console.log('Pushed to git.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.error('Ingestion failed.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -19,6 +19,14 @@ export interface DaemonStatus {
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
export declare function registerSyncCommand(program: Command, defaultDataDir: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Restart the background sync daemon.
|
|
24
|
+
* Stops the existing daemon (if running), then starts a fresh one.
|
|
25
|
+
* Returns { pid } on success, null on failure.
|
|
26
|
+
*/
|
|
27
|
+
export declare function restartDaemon(dataDir: string): Promise<{
|
|
28
|
+
pid: number;
|
|
29
|
+
} | null>;
|
|
22
30
|
/**
|
|
23
31
|
* Start the background sync daemon process.
|
|
24
32
|
* Returns { pid } on success, null on failure.
|
|
@@ -28,4 +36,8 @@ export declare function startDaemonProcess(dataDir: string): Promise<{
|
|
|
28
36
|
pid: number;
|
|
29
37
|
alreadyRunning: boolean;
|
|
30
38
|
} | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Check if the sync daemon is currently running.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isDaemonRunning(): boolean;
|
|
31
43
|
export { CONFIG_DIR, PID_FILE, STATUS_FILE, LOG_FILE };
|
|
@@ -270,25 +270,7 @@ export function registerSyncCommand(program, defaultDataDir) {
|
|
|
270
270
|
.description('Restart background sync daemon')
|
|
271
271
|
.option('-d, --data-dir <dir>', 'Data directory', defaultDataDir)
|
|
272
272
|
.action(async (options) => {
|
|
273
|
-
|
|
274
|
-
if (isLaunchdInstalled()) {
|
|
275
|
-
uninstallLaunchdAgent();
|
|
276
|
-
}
|
|
277
|
-
const pid = getPid();
|
|
278
|
-
if (pid) {
|
|
279
|
-
try {
|
|
280
|
-
process.kill(pid, 'SIGTERM');
|
|
281
|
-
console.log(`Stopped existing daemon (PID: ${pid})`);
|
|
282
|
-
if (existsSync(PID_FILE))
|
|
283
|
-
unlinkSync(PID_FILE);
|
|
284
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
// Process might already be dead
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
// startDaemonProcess will reinstall launchd with fresh config on macOS
|
|
291
|
-
const result = await startDaemonProcess(options.dataDir);
|
|
273
|
+
const result = await restartDaemon(options.dataDir);
|
|
292
274
|
if (!result) {
|
|
293
275
|
console.error('Failed to restart daemon - check logs with: lore sync logs');
|
|
294
276
|
return;
|
|
@@ -723,6 +705,34 @@ export function registerSyncCommand(program, defaultDataDir) {
|
|
|
723
705
|
// ============================================================================
|
|
724
706
|
// Exported helpers
|
|
725
707
|
// ============================================================================
|
|
708
|
+
/**
|
|
709
|
+
* Restart the background sync daemon.
|
|
710
|
+
* Stops the existing daemon (if running), then starts a fresh one.
|
|
711
|
+
* Returns { pid } on success, null on failure.
|
|
712
|
+
*/
|
|
713
|
+
export async function restartDaemon(dataDir) {
|
|
714
|
+
// Uninstall launchd agent so it doesn't auto-restart during our restart
|
|
715
|
+
if (isLaunchdInstalled()) {
|
|
716
|
+
uninstallLaunchdAgent();
|
|
717
|
+
}
|
|
718
|
+
const pid = getPid();
|
|
719
|
+
if (pid) {
|
|
720
|
+
try {
|
|
721
|
+
process.kill(pid, 'SIGTERM');
|
|
722
|
+
if (existsSync(PID_FILE))
|
|
723
|
+
unlinkSync(PID_FILE);
|
|
724
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
// Process might already be dead
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
// startDaemonProcess will reinstall launchd with fresh config on macOS
|
|
731
|
+
const result = await startDaemonProcess(dataDir);
|
|
732
|
+
if (!result)
|
|
733
|
+
return null;
|
|
734
|
+
return { pid: result.pid };
|
|
735
|
+
}
|
|
726
736
|
/**
|
|
727
737
|
* Start the background sync daemon process.
|
|
728
738
|
* Returns { pid } on success, null on failure.
|
|
@@ -764,5 +774,11 @@ export async function startDaemonProcess(dataDir) {
|
|
|
764
774
|
return null;
|
|
765
775
|
}
|
|
766
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* Check if the sync daemon is currently running.
|
|
779
|
+
*/
|
|
780
|
+
export function isDaemonRunning() {
|
|
781
|
+
return getPid() !== null;
|
|
782
|
+
}
|
|
767
783
|
// Export for daemon-runner and browse
|
|
768
784
|
export { CONFIG_DIR, PID_FILE, STATUS_FILE, LOG_FILE };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Command
|
|
3
|
+
*
|
|
4
|
+
* Check for and install updates to @getlore/cli.
|
|
5
|
+
* Restarts the background daemon after upgrading so it picks up new code.
|
|
6
|
+
*/
|
|
7
|
+
import type { Command } from 'commander';
|
|
8
|
+
export declare function registerUpdateCommand(program: Command, defaultDataDir: string): void;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Command
|
|
3
|
+
*
|
|
4
|
+
* Check for and install updates to @getlore/cli.
|
|
5
|
+
* Restarts the background daemon after upgrading so it picks up new code.
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from 'child_process';
|
|
8
|
+
import { c } from '../colors.js';
|
|
9
|
+
import { getLoreVersionString } from '../../extensions/registry.js';
|
|
10
|
+
import { restartDaemon, isDaemonRunning } from './sync.js';
|
|
11
|
+
const NPM_PACKAGE = '@getlore/cli';
|
|
12
|
+
function getLatestVersion() {
|
|
13
|
+
const result = spawnSync('npm', ['view', NPM_PACKAGE, 'version'], {
|
|
14
|
+
encoding: 'utf-8',
|
|
15
|
+
timeout: 10_000,
|
|
16
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
17
|
+
});
|
|
18
|
+
if (result.status !== 0 || !result.stdout)
|
|
19
|
+
return null;
|
|
20
|
+
return result.stdout.trim();
|
|
21
|
+
}
|
|
22
|
+
export function registerUpdateCommand(program, defaultDataDir) {
|
|
23
|
+
program
|
|
24
|
+
.command('update')
|
|
25
|
+
.description('Check for and install updates')
|
|
26
|
+
.option('--check', 'Check for updates without installing')
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
const currentVersion = (await getLoreVersionString()) || 'unknown';
|
|
29
|
+
console.log(`\nCurrent version: ${c.bold(currentVersion)}`);
|
|
30
|
+
console.log('Checking npm for latest version...');
|
|
31
|
+
const latestVersion = getLatestVersion();
|
|
32
|
+
if (!latestVersion) {
|
|
33
|
+
console.error('Could not check npm registry. Are you online?');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
console.log(`Latest version: ${c.bold(latestVersion)}`);
|
|
37
|
+
if (currentVersion === latestVersion) {
|
|
38
|
+
console.log(c.success('\nAlready up to date!'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log(`\nUpdate available: ${c.dim(currentVersion)} → ${c.success(latestVersion)}`);
|
|
42
|
+
if (options.check) {
|
|
43
|
+
console.log(`\nRun ${c.bold('lore update')} to install.`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Install
|
|
47
|
+
console.log(`\nInstalling ${NPM_PACKAGE}@${latestVersion}...`);
|
|
48
|
+
const installResult = spawnSync('npm', ['install', '-g', `${NPM_PACKAGE}@latest`], {
|
|
49
|
+
stdio: 'inherit',
|
|
50
|
+
timeout: 120_000,
|
|
51
|
+
});
|
|
52
|
+
if (installResult.status !== 0) {
|
|
53
|
+
console.error('\nInstallation failed. You may need to run with sudo:');
|
|
54
|
+
console.error(` sudo npm install -g ${NPM_PACKAGE}@latest`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
console.log(c.success(`\nUpdated to ${latestVersion}!`));
|
|
58
|
+
// Restart daemon if running
|
|
59
|
+
if (isDaemonRunning()) {
|
|
60
|
+
console.log('\nRestarting background daemon...');
|
|
61
|
+
const result = await restartDaemon(defaultDataDir);
|
|
62
|
+
if (result) {
|
|
63
|
+
console.log(c.success(`Daemon restarted (PID: ${result.pid})`));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log(c.warning('Could not restart daemon. Run: lore sync restart'));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
console.log('');
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passive Update Notifier
|
|
3
|
+
*
|
|
4
|
+
* Checks npm for a newer version of @getlore/cli once every 24 hours.
|
|
5
|
+
* Prints a subtle notification after command output if an update is available.
|
|
6
|
+
* Never blocks or throws — all errors are silently swallowed.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Check for updates and print a notification if one is available.
|
|
10
|
+
* Safe to call fire-and-forget — never throws, never blocks meaningfully.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkForUpdates(): Promise<void>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passive Update Notifier
|
|
3
|
+
*
|
|
4
|
+
* Checks npm for a newer version of @getlore/cli once every 24 hours.
|
|
5
|
+
* Prints a subtle notification after command output if an update is available.
|
|
6
|
+
* Never blocks or throws — all errors are silently swallowed.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
9
|
+
import { spawnSync } from 'child_process';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import { colors } from './colors.js';
|
|
13
|
+
import { getLoreVersionString } from '../extensions/registry.js';
|
|
14
|
+
const NPM_PACKAGE = '@getlore/cli';
|
|
15
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'lore');
|
|
16
|
+
const CACHE_FILE = path.join(CONFIG_DIR, 'update-check.json');
|
|
17
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
18
|
+
function readCache() {
|
|
19
|
+
try {
|
|
20
|
+
if (!existsSync(CACHE_FILE))
|
|
21
|
+
return null;
|
|
22
|
+
return JSON.parse(readFileSync(CACHE_FILE, 'utf-8'));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function writeCache(cache) {
|
|
29
|
+
try {
|
|
30
|
+
if (!existsSync(CONFIG_DIR))
|
|
31
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
32
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Silently ignore
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check for updates and print a notification if one is available.
|
|
40
|
+
* Safe to call fire-and-forget — never throws, never blocks meaningfully.
|
|
41
|
+
*/
|
|
42
|
+
export async function checkForUpdates() {
|
|
43
|
+
// Don't notify in non-interactive contexts
|
|
44
|
+
if (!process.stdout.isTTY)
|
|
45
|
+
return;
|
|
46
|
+
const cache = readCache();
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
let latestVersion;
|
|
49
|
+
if (cache && (now - cache.last_check) < CHECK_INTERVAL_MS) {
|
|
50
|
+
// Use cached value
|
|
51
|
+
latestVersion = cache.latest_version;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Fetch from npm with short timeout
|
|
55
|
+
const result = spawnSync('npm', ['view', NPM_PACKAGE, 'version'], {
|
|
56
|
+
encoding: 'utf-8',
|
|
57
|
+
timeout: 5_000,
|
|
58
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
59
|
+
});
|
|
60
|
+
if (result.status !== 0 || !result.stdout)
|
|
61
|
+
return;
|
|
62
|
+
latestVersion = result.stdout.trim();
|
|
63
|
+
writeCache({
|
|
64
|
+
last_check: now,
|
|
65
|
+
latest_version: latestVersion,
|
|
66
|
+
last_notified_version: cache?.last_notified_version,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const currentVersion = await getLoreVersionString();
|
|
70
|
+
if (!currentVersion || currentVersion === latestVersion)
|
|
71
|
+
return;
|
|
72
|
+
// Don't re-notify for the same version
|
|
73
|
+
if (cache?.last_notified_version === latestVersion)
|
|
74
|
+
return;
|
|
75
|
+
// Print notification
|
|
76
|
+
const border = `${colors.dim}╭────────────────────────────────────────╮${colors.reset}`;
|
|
77
|
+
const bottom = `${colors.dim}╰────────────────────────────────────────╯${colors.reset}`;
|
|
78
|
+
const pad = (s, width) => {
|
|
79
|
+
// Strip ANSI for length calculation
|
|
80
|
+
const stripped = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
81
|
+
const padding = Math.max(0, width - stripped.length);
|
|
82
|
+
return s + ' '.repeat(padding);
|
|
83
|
+
};
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(border);
|
|
86
|
+
console.log(`${colors.dim}│${colors.reset} ${pad(`Update available: ${colors.dim}${currentVersion}${colors.reset} → ${colors.green}${latestVersion}${colors.reset}`, 39)}${colors.dim}│${colors.reset}`);
|
|
87
|
+
console.log(`${colors.dim}│${colors.reset} ${pad(`Run ${colors.bold}lore update${colors.reset} to upgrade`, 39)}${colors.dim}│${colors.reset}`);
|
|
88
|
+
console.log(bottom);
|
|
89
|
+
// Mark as notified
|
|
90
|
+
writeCache({
|
|
91
|
+
last_check: cache?.last_check || now,
|
|
92
|
+
latest_version: latestVersion,
|
|
93
|
+
last_notified_version: latestVersion,
|
|
94
|
+
});
|
|
95
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,8 @@ import { registerPendingCommand } from './cli/commands/pending.js';
|
|
|
29
29
|
import { registerAskCommand } from './cli/commands/ask.js';
|
|
30
30
|
import { registerAuthCommands } from './cli/commands/auth.js';
|
|
31
31
|
import { registerSkillsCommand } from './cli/commands/skills.js';
|
|
32
|
+
import { registerUpdateCommand } from './cli/commands/update.js';
|
|
33
|
+
import { registerIngestCommand } from './cli/commands/ingest.js';
|
|
32
34
|
import { getExtensionRegistry, getLoreVersionString } from './extensions/registry.js';
|
|
33
35
|
import { bridgeConfigToEnv } from './core/config.js';
|
|
34
36
|
import { expandPath } from './sync/config.js';
|
|
@@ -76,6 +78,8 @@ registerMiscCommands(program, DEFAULT_DATA_DIR);
|
|
|
76
78
|
registerAskCommand(program, DEFAULT_DATA_DIR);
|
|
77
79
|
registerAuthCommands(program);
|
|
78
80
|
registerSkillsCommand(program);
|
|
81
|
+
registerUpdateCommand(program, DEFAULT_DATA_DIR);
|
|
82
|
+
registerIngestCommand(program, DEFAULT_DATA_DIR);
|
|
79
83
|
// Extension system — hidden from top-level help for now
|
|
80
84
|
const extensionCmd = registerExtensionCommands(program);
|
|
81
85
|
extensionCmd._hidden = true;
|
|
@@ -101,5 +105,11 @@ process.on('unhandledRejection', (reason) => {
|
|
|
101
105
|
console.error(`\nError: ${message}`);
|
|
102
106
|
process.exit(1);
|
|
103
107
|
});
|
|
104
|
-
// Parse and run
|
|
105
|
-
program.
|
|
108
|
+
// Parse and run, then show update notification after command output
|
|
109
|
+
await program.parseAsync();
|
|
110
|
+
// Passive update notification (non-blocking, silent on errors)
|
|
111
|
+
try {
|
|
112
|
+
const { checkForUpdates } = await import('./cli/update-notifier.js');
|
|
113
|
+
await checkForUpdates();
|
|
114
|
+
}
|
|
115
|
+
catch { }
|