@solarains/va-cli 0.1.2 → 0.1.4

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.
@@ -4,7 +4,7 @@ exports.getHelpCopy = getHelpCopy;
4
4
  exports.getLocalizedCommandHelp = getLocalizedCommandHelp;
5
5
  exports.getLocalizedChildSummary = getLocalizedChildSummary;
6
6
  const HELP_COPY = {
7
- en: {
7
+ 'en': {
8
8
  commandsHeading: 'Commands:',
9
9
  globalFlagsHeading: 'Global flags:',
10
10
  helpFlag: 'Show help',
@@ -14,26 +14,30 @@ const HELP_COPY = {
14
14
  summary: 'VAOne CLI with guided init, auth, assets, usage, daily reports, intelligence queries, and diagnostics.',
15
15
  usage: 'vaone <command> [subcommand] [options]',
16
16
  },
17
- init: {
17
+ 'init': {
18
18
  summary: 'Install VisionAlpha operator skills with branded onboarding and optional browser login.',
19
19
  usage: 'vaone init [.] [--agents codex,claude,openclaw] [--scope project|user] [--lang en|zh-CN] [--skip-login] [--force] [--yes]',
20
20
  },
21
- auth: {
21
+ 'uninstall': {
22
+ summary: 'Remove VA-managed skill packs from project or global agent roots.',
23
+ usage: 'vaone uninstall [--scope project|user|all] [--agents codex,claude,openclaw]',
24
+ },
25
+ 'auth': {
22
26
  summary: 'Authentication placeholder commands for future browser login.',
23
27
  },
24
- assets: {
28
+ 'assets': {
25
29
  summary: 'Browse asset catalog commands.',
26
30
  },
27
- usage: {
31
+ 'usage': {
28
32
  summary: 'Show current account usage summary.',
29
33
  },
30
- reports: {
34
+ 'reports': {
31
35
  summary: 'List or read normalized daily reports.',
32
36
  },
33
- intelligence: {
37
+ 'intelligence': {
34
38
  summary: 'Query paid asset intelligence data.',
35
39
  },
36
- doctor: {
40
+ 'doctor': {
37
41
  summary: 'Run local diagnostics for auth, install, and configuration state.',
38
42
  },
39
43
  },
@@ -51,26 +55,30 @@ const HELP_COPY = {
51
55
  summary: 'VAOne CLI,提供引导式 init、认证、资产、usage、日报、intelligence 查询与诊断能力。',
52
56
  usage: 'vaone <命令> [子命令] [参数]',
53
57
  },
54
- init: {
58
+ 'init': {
55
59
  summary: '通过带品牌感的引导流程安装 VisionAlpha operator skills,并可选串联浏览器登录。',
56
60
  usage: 'vaone init [.] [--agents codex,claude,openclaw] [--scope project|user] [--lang en|zh-CN] [--skip-login] [--force] [--yes]',
57
61
  },
58
- auth: {
62
+ 'uninstall': {
63
+ summary: '从当前项目或全局 agent 根目录移除 VA 受管 skill pack。',
64
+ usage: 'vaone uninstall [--scope project|user|all] [--agents codex,claude,openclaw]',
65
+ },
66
+ 'auth': {
59
67
  summary: '认证相关命令,包括浏览器登录与退出登录。',
60
68
  },
61
- assets: {
69
+ 'assets': {
62
70
  summary: '浏览资产目录相关命令。',
63
71
  },
64
- usage: {
72
+ 'usage': {
65
73
  summary: '显示当前账户 usage 摘要。',
66
74
  },
67
- reports: {
75
+ 'reports': {
68
76
  summary: '列出或读取标准化日报。',
69
77
  },
70
- intelligence: {
78
+ 'intelligence': {
71
79
  summary: '查询付费资产 intelligence 数据。',
72
80
  },
73
- doctor: {
81
+ 'doctor': {
74
82
  summary: '运行本地认证、安装和配置状态诊断。',
75
83
  },
76
84
  },
@@ -7,6 +7,7 @@ const doctor_command_1 = require("../commands/doctor/doctor-command");
7
7
  const init_command_1 = require("../commands/init/init-command");
8
8
  const intelligence_command_1 = require("../commands/intelligence/intelligence-command");
9
9
  const reports_command_1 = require("../commands/reports/reports-command");
10
+ const uninstall_command_1 = require("../commands/uninstall/uninstall-command");
10
11
  const usage_command_1 = require("../commands/usage/usage-command");
11
12
  exports.rootCommand = {
12
13
  name: 'vaone',
@@ -14,6 +15,7 @@ exports.rootCommand = {
14
15
  usage: 'vaone <command> [subcommand] [options]',
15
16
  children: [
16
17
  init_command_1.initCommand,
18
+ uninstall_command_1.uninstallCommand,
17
19
  auth_command_1.authCommand,
18
20
  assets_command_1.assetsCommand,
19
21
  usage_command_1.usageCommand,
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runCli = runCli;
4
+ const node_os_1 = require("node:os");
4
5
  const runtime_config_1 = require("../config/runtime-config");
6
+ const preflight_1 = require("../features/update/preflight");
5
7
  const paths_1 = require("../state/paths");
6
8
  const installation_store_1 = require("../state/stores/installation-store");
7
9
  const token_store_1 = require("../state/stores/token-store");
@@ -62,7 +64,7 @@ function printUnknownCommand(logger, command, path, token, locale) {
62
64
  logger.plain('');
63
65
  logger.plain((0, help_1.renderHelp)(command, path, locale));
64
66
  }
65
- async function runCli(argv, env) {
67
+ async function runCli(argv, env, deps = {}) {
66
68
  const parsedArgv = parseArgv(argv);
67
69
  const logger = new logger_1.Logger(parsedArgv.debug || env.VAONE_DEBUG === 'true');
68
70
  const paths = (0, paths_1.createCliPaths)(env);
@@ -86,7 +88,6 @@ async function runCli(argv, env) {
86
88
  logger.plain((0, help_1.renderHelp)(resolvedCommand.command, resolvedCommand.path, locale));
87
89
  return 0;
88
90
  }
89
- await (0, paths_1.ensureCliDirectories)(paths);
90
91
  if (canTraverseFurther && !resolvedCommand.command.run) {
91
92
  printUnknownCommand(logger, resolvedCommand.command, resolvedCommand.path, unknownToken, locale);
92
93
  return 1;
@@ -95,6 +96,20 @@ async function runCli(argv, env) {
95
96
  logger.plain((0, help_1.renderHelp)(resolvedCommand.command, resolvedCommand.path, locale));
96
97
  return 0;
97
98
  }
99
+ await (0, paths_1.ensureCliDirectories)(paths);
100
+ const preflightExitCode = await (deps.runStartupPreflight ?? preflight_1.runStartupPreflight)({
101
+ argv,
102
+ env,
103
+ logger,
104
+ paths,
105
+ runtimeConfig,
106
+ commandPath: resolvedCommand.path,
107
+ cwd: process.cwd(),
108
+ homeDir: (0, node_os_1.homedir)(),
109
+ });
110
+ if (typeof preflightExitCode === 'number') {
111
+ return preflightExitCode;
112
+ }
98
113
  try {
99
114
  return await resolvedCommand.command.run(context, resolvedCommand.remainingArgs);
100
115
  }
@@ -6,6 +6,14 @@ const auth_api_client_1 = require("../../features/auth/auth-api-client");
6
6
  const browser_login_1 = require("../../features/auth/browser-login");
7
7
  const installation_state_1 = require("../../features/auth/installation-state");
8
8
  const AUTH_TIMEOUT_MS = 10 * 60 * 1000;
9
+ function formatBrowserOrigin(continuationUri) {
10
+ try {
11
+ return new URL(continuationUri).origin;
12
+ }
13
+ catch {
14
+ return continuationUri;
15
+ }
16
+ }
9
17
  function printFallbackInstructions(logger, continuationUri, callbackMode) {
10
18
  (0, output_1.printSection)(logger, 'Browser continuation');
11
19
  (0, output_1.printKeyValue)(logger, 'URL', continuationUri);
@@ -71,7 +79,8 @@ exports.loginCommand = {
71
79
  }
72
80
  (0, output_1.printSection)(context.logger, 'Login context');
73
81
  (0, output_1.printList)(context.logger, [
74
- `Account login is handled in the browser against ${context.runtimeConfig.apiBaseUrl}.`,
82
+ `Account login is handled in the browser against ${formatBrowserOrigin(session.continuationUri)}.`,
83
+ `API requests use ${context.runtimeConfig.apiBaseUrl}.`,
75
84
  `Installation binding uses ${installation.installationId} on ${installation.clientPlatform}/${installation.clientArch}.`,
76
85
  `Callback mode: ${callbackMode}.`,
77
86
  ]);
@@ -22,7 +22,9 @@ exports.logoutCommand = {
22
22
  }
23
23
  catch (error) {
24
24
  remoteLogoutFailure =
25
- error instanceof Error ? error.message : 'Unknown remote logout failure.';
25
+ error instanceof Error
26
+ ? error.message
27
+ : 'Unknown remote logout failure.';
26
28
  context.logger.debug('Remote logout request failed.', error);
27
29
  }
28
30
  }
@@ -1,7 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.doctorCommand = void 0;
4
+ const node_os_1 = require("node:os");
4
5
  const output_1 = require("../../shared/output");
6
+ const package_metadata_1 = require("../../features/runtime/package-metadata");
7
+ const managed_pack_1 = require("../../features/init/managed-pack");
8
+ const paths_1 = require("../../state/paths");
9
+ const update_state_store_1 = require("../../state/stores/update-state-store");
10
+ function formatManagedStatus(input) {
11
+ if (input.kind === 'current') {
12
+ return `current (${input.installedVersion})`;
13
+ }
14
+ if (input.kind === 'stale') {
15
+ return `stale (${input.installedVersion} -> ${input.currentVersion})`;
16
+ }
17
+ if (input.kind === 'legacy') {
18
+ return `legacy (missing pack version; current ${input.currentVersion})`;
19
+ }
20
+ if (input.kind === 'ahead') {
21
+ return `ahead (${input.installedVersion})`;
22
+ }
23
+ if (input.kind === 'untracked') {
24
+ return 'managed files detected without a valid manifest';
25
+ }
26
+ return 'not installed';
27
+ }
5
28
  exports.doctorCommand = {
6
29
  name: 'doctor',
7
30
  summary: 'Inspect config, paths, and local bootstrap state.',
@@ -9,6 +32,14 @@ exports.doctorCommand = {
9
32
  async run(context) {
10
33
  const tokens = await context.tokenStore.read();
11
34
  const installation = await context.installationStore.read();
35
+ const runtimePackage = await (0, package_metadata_1.resolveRuntimePackageInfo)();
36
+ const updateStateStore = new update_state_store_1.UpdateStateStore((0, paths_1.resolveUpdateStateFile)(context.paths));
37
+ const updateState = await updateStateStore.read();
38
+ const managedStatuses = await (0, managed_pack_1.collectManagedPackStatuses)({
39
+ cwd: process.cwd(),
40
+ homeDir: (0, node_os_1.homedir)(),
41
+ currentVersion: runtimePackage.version,
42
+ });
12
43
  (0, output_1.printSection)(context.logger, 'Runtime configuration');
13
44
  (0, output_1.printKeyValue)(context.logger, 'Environment', context.runtimeConfig.appEnv);
14
45
  (0, output_1.printKeyValue)(context.logger, 'API base URL', context.runtimeConfig.apiBaseUrl);
@@ -19,6 +50,13 @@ exports.doctorCommand = {
19
50
  (0, output_1.printKeyValue)(context.logger, 'State directory', context.paths.stateDir);
20
51
  (0, output_1.printKeyValue)(context.logger, 'Auth state', context.paths.authStateFile);
21
52
  (0, output_1.printKeyValue)(context.logger, 'Installation state', context.paths.installationStateFile);
53
+ (0, output_1.printKeyValue)(context.logger, 'Update state', (0, paths_1.resolveUpdateStateFile)(context.paths));
54
+ (0, output_1.printSection)(context.logger, 'Runtime version state');
55
+ (0, output_1.printKeyValue)(context.logger, 'CLI version', runtimePackage.version);
56
+ (0, output_1.printKeyValue)(context.logger, 'Packaged install', String(runtimePackage.isPackagedInstall));
57
+ (0, output_1.printKeyValue)(context.logger, 'Deferred update boundary', context.runtimeConfig.updateDeferredUntilVersion ?? 'none');
58
+ (0, output_1.printKeyValue)(context.logger, 'Last checked', updateState?.lastCheckedAt ?? 'never');
59
+ (0, output_1.printKeyValue)(context.logger, 'Last known latest version', updateState?.lastKnownLatestVersion ?? 'unknown');
22
60
  (0, output_1.printSection)(context.logger, 'Stored bootstrap state');
23
61
  (0, output_1.printList)(context.logger, [
24
62
  tokens
@@ -28,6 +66,12 @@ exports.doctorCommand = {
28
66
  ? 'Installation state file exists.'
29
67
  : 'Installation state file is empty.',
30
68
  ]);
69
+ (0, output_1.printSection)(context.logger, 'Managed skill packs');
70
+ (0, output_1.printList)(context.logger, managedStatuses.map((status) => `${status.target.displayName} (${status.scope}): ${formatManagedStatus({
71
+ kind: status.kind,
72
+ installedVersion: status.installedVersion,
73
+ currentVersion: status.currentVersion,
74
+ })} at ${status.rootDir}`));
31
75
  return 0;
32
76
  },
33
77
  };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uninstallCommand = void 0;
4
+ const node_os_1 = require("node:os");
5
+ const argv_1 = require("../../shared/argv");
6
+ const target_registry_1 = require("../../features/init/target-registry");
7
+ const managed_pack_1 = require("../../features/init/managed-pack");
8
+ function parseScope(value) {
9
+ if (!value) {
10
+ return 'project';
11
+ }
12
+ if (value === 'project' || value === 'user' || value === 'all') {
13
+ return value;
14
+ }
15
+ throw new Error('Option "--scope" must be project, user, or all.');
16
+ }
17
+ function parseAgentIds(value) {
18
+ if (!value) {
19
+ return (0, target_registry_1.getAgentTargetRegistry)().map((target) => target.id);
20
+ }
21
+ const supported = new Set((0, target_registry_1.getAgentTargetRegistry)().map((target) => target.id));
22
+ return value
23
+ .split(',')
24
+ .map((entry) => entry.trim().toLowerCase())
25
+ .filter(Boolean)
26
+ .map((entry) => {
27
+ if (!supported.has(entry)) {
28
+ throw new Error(`Unsupported agent "${entry}". Use one or more of: ${Array.from(supported).join(', ')}.`);
29
+ }
30
+ return entry;
31
+ })
32
+ .filter((entry, index, items) => items.indexOf(entry) === index);
33
+ }
34
+ exports.uninstallCommand = {
35
+ name: 'uninstall',
36
+ summary: 'Remove VA-managed skill packs from project or global agent roots.',
37
+ usage: 'vaone uninstall [--scope project|user|all] [--agents codex,claude,openclaw]',
38
+ async run(context, args) {
39
+ const parsed = (0, argv_1.parseCommandArgs)(args);
40
+ if (parsed.positionals.length) {
41
+ throw new Error('`vaone uninstall` does not accept positional arguments.');
42
+ }
43
+ const scope = parseScope((0, argv_1.getStringOption)(parsed, 'scope'));
44
+ const scopes = scope === 'all' ? ['project', 'user'] : [scope];
45
+ const agentIds = parseAgentIds((0, argv_1.getStringOption)(parsed, 'agents'));
46
+ const removed = await (0, managed_pack_1.removeManagedPacks)({
47
+ cwd: process.cwd(),
48
+ homeDir: (0, node_os_1.homedir)(),
49
+ agentIds,
50
+ scopes,
51
+ });
52
+ if (removed.length === 0) {
53
+ context.logger.info('No VA-managed skill packs were found for removal.');
54
+ return 0;
55
+ }
56
+ context.logger.plain('Managed skill packs removed:');
57
+ for (const entry of removed) {
58
+ context.logger.plain(`- ${entry.target.displayName} (${entry.scope}): ${entry.rootDir}`);
59
+ }
60
+ return 0;
61
+ },
62
+ };
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_API_BASE_URL = void 0;
3
4
  exports.loadRuntimeConfig = loadRuntimeConfig;
4
5
  exports.saveRuntimeConfig = saveRuntimeConfig;
5
6
  const node_os_1 = require("node:os");
6
7
  const promises_1 = require("node:fs/promises");
7
8
  const node_path_1 = require("node:path");
8
9
  const locale_1 = require("./locale");
10
+ exports.DEFAULT_API_BASE_URL = 'https://skills.api.dev.vizionalpha.com';
9
11
  function parseBoolean(value) {
10
12
  if (value === undefined) {
11
13
  return undefined;
@@ -44,13 +46,12 @@ async function loadRuntimeConfig(env, paths) {
44
46
  const locale = envLocale ?? fileLocale ?? locale_1.DEFAULT_LOCALE;
45
47
  return {
46
48
  appEnv: env.VAONE_ENV ?? fileConfig.appEnv ?? 'local',
47
- apiBaseUrl: normalizeHomePath(env.VAONE_API_BASE_URL ??
48
- fileConfig.apiBaseUrl ??
49
- 'http://localhost:3000').replace(/\/$/, ''),
49
+ apiBaseUrl: normalizeHomePath(env.VAONE_API_BASE_URL ?? fileConfig.apiBaseUrl ?? exports.DEFAULT_API_BASE_URL).replace(/\/$/, ''),
50
50
  debug: parseBoolean(env.VAONE_DEBUG) ?? fileConfig.debug ?? false,
51
51
  authCallbackMode,
52
52
  locale,
53
53
  localeConfigured: envLocale !== undefined || fileLocale !== undefined,
54
+ updateDeferredUntilVersion: fileConfig.updateDeferredUntilVersion?.trim() || undefined,
54
55
  };
55
56
  }
56
57
  async function saveRuntimeConfig(paths, patch) {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureInstallationState = ensureInstallationState;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const node_os_1 = require("node:os");
6
- const CLI_VERSION = '0.1.0';
6
+ const package_metadata_1 = require("../runtime/package-metadata");
7
7
  function exportPublicKeyPem(publicKey) {
8
8
  return publicKey.export({
9
9
  type: 'spki',
@@ -28,6 +28,7 @@ function hasUsableKeys(installation) {
28
28
  installation.updatedAt);
29
29
  }
30
30
  async function ensureInstallationState(installationStore) {
31
+ const runtimePackage = await (0, package_metadata_1.resolveRuntimePackageInfo)();
31
32
  const existing = await installationStore.read();
32
33
  const now = new Date().toISOString();
33
34
  if (hasUsableKeys(existing)) {
@@ -35,7 +36,7 @@ async function ensureInstallationState(installationStore) {
35
36
  ...existing,
36
37
  clientPlatform: (0, node_os_1.platform)(),
37
38
  clientArch: (0, node_os_1.arch)(),
38
- cliVersion: existing.cliVersion ?? CLI_VERSION,
39
+ cliVersion: runtimePackage.version,
39
40
  deviceName: existing.deviceName ?? (0, node_os_1.hostname)(),
40
41
  updatedAt: now,
41
42
  };
@@ -55,7 +56,7 @@ async function ensureInstallationState(installationStore) {
55
56
  keyFingerprint,
56
57
  clientPlatform: (0, node_os_1.platform)(),
57
58
  clientArch: (0, node_os_1.arch)(),
58
- cliVersion: CLI_VERSION,
59
+ cliVersion: runtimePackage.version,
59
60
  deviceName: (0, node_os_1.hostname)(),
60
61
  createdAt: now,
61
62
  updatedAt: now,
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderLoopbackCallbackPage = renderLoopbackCallbackPage;
4
4
  const CALLBACK_PAGE_COPY = {
5
- en: {
5
+ 'en': {
6
6
  brand: 'VAOne',
7
7
  closeAction: 'Close page',
8
8
  success: {
@@ -121,6 +121,9 @@ function getInitCopy(locale) {
121
121
  summaryBrowser(value) {
122
122
  return `浏览器状态: ${value}`;
123
123
  },
124
+ summaryBrowserOrigin(value) {
125
+ return `浏览器地址: ${value}`;
126
+ },
124
127
  summaryCallbackMode(value) {
125
128
  return `回调模式: ${value}`;
126
129
  },
@@ -279,6 +282,9 @@ function getInitCopy(locale) {
279
282
  summaryBrowser(value) {
280
283
  return `Browser: ${value}`;
281
284
  },
285
+ summaryBrowserOrigin(value) {
286
+ return `Browser origin: ${value}`;
287
+ },
282
288
  summaryCallbackMode(value) {
283
289
  return `Callback mode: ${value}`;
284
290
  },
@@ -69,6 +69,11 @@ function extractValue(lines, key) {
69
69
  function extractLoginSummary(lines, copy) {
70
70
  const summary = [];
71
71
  const details = [];
72
+ const browserOrigin = lines
73
+ .find((line) => line.startsWith('Account login is handled in the browser against '))
74
+ ?.replace('Account login is handled in the browser against ', '')
75
+ .replace(/\.$/, '')
76
+ .trim();
72
77
  const browserLine = lines.find((line) => line.includes('Automatic browser launch failed') ||
73
78
  line.includes('Browser opened'));
74
79
  const callbackMode = extractValue(lines, 'Callback mode');
@@ -79,6 +84,9 @@ function extractLoginSummary(lines, copy) {
79
84
  if (browserLine) {
80
85
  summary.push(copy.messages.summaryBrowser(browserLine));
81
86
  }
87
+ if (browserOrigin) {
88
+ summary.push(copy.messages.summaryBrowserOrigin(browserOrigin));
89
+ }
82
90
  if (callbackMode) {
83
91
  summary.push(copy.messages.summaryCallbackMode(callbackMode));
84
92
  }
@@ -100,7 +108,8 @@ function extractLoginSummary(lines, copy) {
100
108
  /^-+$/.test(line.trim())) {
101
109
  continue;
102
110
  }
103
- if (line.includes('Automatic browser launch failed') || line.startsWith('URL:')) {
111
+ if (line.includes('Automatic browser launch failed') ||
112
+ line.startsWith('URL:')) {
104
113
  details.push(line);
105
114
  continue;
106
115
  }
@@ -397,14 +406,14 @@ function resolveInitOptions(input) {
397
406
  return resolve();
398
407
  }
399
408
  function positionalscopeAndScopeConflict(positionalScope, parsedScope) {
400
- return Boolean(positionalScope &&
401
- parsedScope &&
402
- positionalScope !== parsedScope);
409
+ return Boolean(positionalScope && parsedScope && positionalScope !== parsedScope);
403
410
  }
404
411
  function describeDetection(presenter, copy, detectedTargets) {
405
412
  presenter.section(copy.sections.detection);
406
413
  for (const target of detectedTargets) {
407
- const summary = target.detected ? copy.labels.detected : copy.labels.notDetected;
414
+ const summary = target.detected
415
+ ? copy.labels.detected
416
+ : copy.labels.notDetected;
408
417
  presenter.list([
409
418
  `${target.displayName}: ${summary}`,
410
419
  ...(target.detectedIn.length > 0
@@ -469,9 +478,7 @@ async function runVerification(context, presenter, copy, options, dependencies)
469
478
  });
470
479
  if (options.skipLogin) {
471
480
  presenter.step('skipped', copy.statuses.skipped, copy.steps.usageSkipped);
472
- presenter.list([
473
- copy.messages.usageVerificationSkippedDeferred,
474
- ]);
481
+ presenter.list([copy.messages.usageVerificationSkippedDeferred]);
475
482
  return;
476
483
  }
477
484
  await runCommandInBox({
@@ -562,7 +569,9 @@ async function runInitFlow(context, args, partialDependencies = {}) {
562
569
  cwd: dependencies.cwd,
563
570
  homeDir: dependencies.homeDir,
564
571
  });
565
- const prompt = dependencies.isInteractive ? dependencies.promptFactory() : null;
572
+ const prompt = dependencies.isInteractive
573
+ ? dependencies.promptFactory()
574
+ : null;
566
575
  let shouldForceExit = false;
567
576
  let exitCode = 1;
568
577
  try {
@@ -4,6 +4,7 @@ exports.prepareInstallations = prepareInstallations;
4
4
  exports.applyInstallations = applyInstallations;
5
5
  const promises_1 = require("node:fs/promises");
6
6
  const node_path_1 = require("node:path");
7
+ const package_metadata_1 = require("../runtime/package-metadata");
7
8
  const manifest_1 = require("./manifest");
8
9
  const target_registry_1 = require("./target-registry");
9
10
  const templates_1 = require("./templates");
@@ -85,8 +86,10 @@ async function prepareInstallations(input) {
85
86
  async function applyInstallations(prepared, options) {
86
87
  const applied = [];
87
88
  const timestamp = options.now ?? (() => new Date().toISOString());
89
+ const runtimePackage = await (0, package_metadata_1.resolveRuntimePackageInfo)();
88
90
  for (const entry of prepared) {
89
- if (entry.analysis.conflicts.length > 0 && !options.allowUnmanagedOverwrite) {
91
+ if (entry.analysis.conflicts.length > 0 &&
92
+ !options.allowUnmanagedOverwrite) {
90
93
  throw new Error(`Unmanaged files block ${entry.target.displayName} installation: ${entry.analysis.conflicts.join(', ')}. Re-run with --force or confirm overwrite interactively.`);
91
94
  }
92
95
  const overwritten = [...entry.analysis.conflicts];
@@ -106,6 +109,7 @@ async function applyInstallations(prepared, options) {
106
109
  packId: 'vaone-agent-pack',
107
110
  targetId: entry.target.id,
108
111
  scope: entry.scope,
112
+ packVersion: runtimePackage.version,
109
113
  updatedAt: timestamp(),
110
114
  files: entry.files.map((file) => file.relativePath),
111
115
  });
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inspectManagedPack = inspectManagedPack;
4
+ exports.collectManagedPackStatuses = collectManagedPackStatuses;
5
+ exports.syncManagedPacks = syncManagedPacks;
6
+ exports.removeManagedPacks = removeManagedPacks;
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const version_1 = require("../update/version");
11
+ const package_metadata_1 = require("../runtime/package-metadata");
12
+ const manifest_1 = require("./manifest");
13
+ const installer_1 = require("./installer");
14
+ const target_registry_1 = require("./target-registry");
15
+ const templates_1 = require("./templates");
16
+ async function readText(path) {
17
+ try {
18
+ return await (0, promises_1.readFile)(path, 'utf8');
19
+ }
20
+ catch (error) {
21
+ if (error.code === 'ENOENT') {
22
+ return null;
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+ async function pruneEmptyDirectories(startDir, stopDir) {
28
+ let current = startDir;
29
+ while (current.startsWith(stopDir) && current !== stopDir) {
30
+ try {
31
+ await (0, promises_1.rmdir)(current);
32
+ }
33
+ catch (error) {
34
+ const code = error.code;
35
+ if (code === 'ENOTEMPTY' || code === 'ENOENT') {
36
+ return;
37
+ }
38
+ throw error;
39
+ }
40
+ current = (0, node_path_1.dirname)(current);
41
+ }
42
+ }
43
+ function resolveRoot(input) {
44
+ const target = (0, target_registry_1.getAgentTarget)(input.targetId);
45
+ const cwd = input.cwd ?? process.cwd();
46
+ const homeDir = input.homeDir ?? (0, node_os_1.homedir)();
47
+ const rootDir = target.resolveRoot({
48
+ cwd,
49
+ homeDir,
50
+ scope: input.scope,
51
+ });
52
+ return {
53
+ target,
54
+ rootDir,
55
+ manifestPath: (0, node_path_1.join)(rootDir, manifest_1.MANIFEST_FILE_NAME),
56
+ };
57
+ }
58
+ async function inspectManagedPack(input) {
59
+ const runtimePackage = input.currentVersion === undefined
60
+ ? await (0, package_metadata_1.resolveRuntimePackageInfo)()
61
+ : null;
62
+ const currentVersion = input.currentVersion ?? runtimePackage?.version ?? '';
63
+ const { target, rootDir, manifestPath } = resolveRoot(input);
64
+ const manifest = await (0, manifest_1.readManagedManifest)(manifestPath);
65
+ const managedSkillPath = (0, node_path_1.join)(rootDir, templates_1.SKILL_NAME, 'SKILL.md');
66
+ const existingSkill = await readText(managedSkillPath);
67
+ const hasManagedSkillFile = existingSkill?.includes(templates_1.MANAGED_MARKER) ?? false;
68
+ if (!manifest) {
69
+ return {
70
+ target,
71
+ scope: input.scope,
72
+ rootDir,
73
+ manifestPath,
74
+ kind: hasManagedSkillFile ? 'untracked' : 'missing',
75
+ currentVersion,
76
+ };
77
+ }
78
+ const installedVersion = manifest.packVersion;
79
+ if (!installedVersion) {
80
+ return {
81
+ target,
82
+ scope: input.scope,
83
+ rootDir,
84
+ manifestPath,
85
+ kind: 'legacy',
86
+ currentVersion,
87
+ };
88
+ }
89
+ const comparison = (0, version_1.compareVersions)(installedVersion, currentVersion);
90
+ return {
91
+ target,
92
+ scope: input.scope,
93
+ rootDir,
94
+ manifestPath,
95
+ kind: comparison === 0 ? 'current' : comparison < 0 ? 'stale' : 'ahead',
96
+ currentVersion,
97
+ installedVersion,
98
+ };
99
+ }
100
+ async function collectManagedPackStatuses(input) {
101
+ const runtimePackage = input?.currentVersion === undefined
102
+ ? await (0, package_metadata_1.resolveRuntimePackageInfo)()
103
+ : null;
104
+ const currentVersion = input?.currentVersion ?? runtimePackage?.version ?? '';
105
+ const agentIds = input?.agentIds ?? (0, target_registry_1.getAgentTargetRegistry)().map((target) => target.id);
106
+ const scopes = input?.scopes ?? ['project', 'user'];
107
+ const statuses = [];
108
+ for (const scope of scopes) {
109
+ for (const agentId of agentIds) {
110
+ statuses.push(await inspectManagedPack({
111
+ targetId: agentId,
112
+ scope,
113
+ cwd: input?.cwd,
114
+ homeDir: input?.homeDir,
115
+ currentVersion,
116
+ }));
117
+ }
118
+ }
119
+ return statuses;
120
+ }
121
+ async function syncManagedPacks(input) {
122
+ const runtimePackage = input?.currentVersion === undefined
123
+ ? await (0, package_metadata_1.resolveRuntimePackageInfo)()
124
+ : null;
125
+ const currentVersion = input?.currentVersion ?? runtimePackage?.version ?? '';
126
+ const statuses = await collectManagedPackStatuses({
127
+ ...input,
128
+ currentVersion,
129
+ });
130
+ const refreshed = [];
131
+ for (const status of statuses) {
132
+ if (status.kind !== 'legacy' && status.kind !== 'stale') {
133
+ continue;
134
+ }
135
+ const prepared = await (0, installer_1.prepareInstallations)({
136
+ agentIds: [status.target.id],
137
+ scope: status.scope,
138
+ cwd: input?.cwd ?? process.cwd(),
139
+ homeDir: input?.homeDir ?? (0, node_os_1.homedir)(),
140
+ });
141
+ await (0, installer_1.applyInstallations)(prepared, {
142
+ allowUnmanagedOverwrite: false,
143
+ });
144
+ refreshed.push({
145
+ target: status.target,
146
+ scope: status.scope,
147
+ rootDir: status.rootDir,
148
+ previousStatus: status.kind,
149
+ });
150
+ }
151
+ return refreshed;
152
+ }
153
+ async function removeManagedPacks(input) {
154
+ const agentIds = input?.agentIds ?? (0, target_registry_1.getAgentTargetRegistry)().map((target) => target.id);
155
+ const scopes = input?.scopes ?? ['project', 'user'];
156
+ const removed = [];
157
+ for (const scope of scopes) {
158
+ for (const agentId of agentIds) {
159
+ const { target, rootDir, manifestPath } = resolveRoot({
160
+ targetId: agentId,
161
+ scope,
162
+ cwd: input?.cwd,
163
+ homeDir: input?.homeDir,
164
+ });
165
+ const manifest = await (0, manifest_1.readManagedManifest)(manifestPath);
166
+ if (!manifest) {
167
+ continue;
168
+ }
169
+ const removedFiles = [];
170
+ for (const relativePath of manifest.files) {
171
+ await (0, promises_1.rm)((0, node_path_1.join)(rootDir, relativePath), { force: true });
172
+ removedFiles.push(relativePath);
173
+ }
174
+ await (0, promises_1.rm)(manifestPath, { force: true });
175
+ await pruneEmptyDirectories((0, node_path_1.join)(rootDir, templates_1.SKILL_NAME), rootDir);
176
+ removed.push({
177
+ target,
178
+ scope,
179
+ rootDir,
180
+ removedFiles,
181
+ });
182
+ }
183
+ }
184
+ return removed;
185
+ }
@@ -29,7 +29,9 @@ const TARGET_REGISTRY = [
29
29
  displayName: 'OpenClaw',
30
30
  supportedScopes: ['project', 'user'],
31
31
  resolveRoot({ cwd, homeDir, scope }) {
32
- return scope === 'project' ? (0, node_path_1.join)(cwd, 'skills') : (0, node_path_1.join)(homeDir, '.openclaw', 'skills');
32
+ return scope === 'project'
33
+ ? (0, node_path_1.join)(cwd, 'skills')
34
+ : (0, node_path_1.join)(homeDir, '.openclaw', 'skills');
33
35
  },
34
36
  },
35
37
  ];
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MANAGED_MARKER = void 0;
3
+ exports.SKILL_NAME = exports.MANAGED_MARKER = void 0;
4
4
  exports.resolveSkillSourceRoot = resolveSkillSourceRoot;
5
5
  exports.createAgentPackFiles = createAgentPackFiles;
6
6
  const promises_1 = require("node:fs/promises");
7
7
  const node_path_1 = require("node:path");
8
+ const package_metadata_1 = require("../runtime/package-metadata");
8
9
  exports.MANAGED_MARKER = 'VAONE-MANAGED FILE';
9
- const SKILL_NAME = 'visionalpha-operator';
10
+ exports.SKILL_NAME = 'visionalpha-operator';
10
11
  async function pathExists(path) {
11
12
  try {
12
13
  await (0, promises_1.access)((0, node_path_1.join)(path, 'SKILL.md'));
@@ -23,12 +24,12 @@ async function resolveSkillSourceRoot(input) {
23
24
  const env = input?.env ?? process.env;
24
25
  const candidates = [
25
26
  env.VAONE_SKILL_SOURCE_DIR,
26
- (0, node_path_1.join)(moduleDir, '..', '..', '..', 'skills', SKILL_NAME),
27
- (0, node_path_1.join)(cwd, 'skills', SKILL_NAME),
28
- (0, node_path_1.join)(cwd, 'va-cli', 'skills', SKILL_NAME),
29
- (0, node_path_1.join)((0, node_path_1.dirname)(execPath), 'skills', SKILL_NAME),
30
- (0, node_path_1.join)((0, node_path_1.dirname)(execPath), '..', 'skills', SKILL_NAME),
31
- (0, node_path_1.join)((0, node_path_1.dirname)(execPath), '..', 'va-cli', 'skills', SKILL_NAME),
27
+ (0, node_path_1.join)(moduleDir, '..', '..', '..', 'skills', exports.SKILL_NAME),
28
+ (0, node_path_1.join)(cwd, 'skills', exports.SKILL_NAME),
29
+ (0, node_path_1.join)(cwd, 'va-cli', 'skills', exports.SKILL_NAME),
30
+ (0, node_path_1.join)((0, node_path_1.dirname)(execPath), 'skills', exports.SKILL_NAME),
31
+ (0, node_path_1.join)((0, node_path_1.dirname)(execPath), '..', 'skills', exports.SKILL_NAME),
32
+ (0, node_path_1.join)((0, node_path_1.dirname)(execPath), '..', 'va-cli', 'skills', exports.SKILL_NAME),
32
33
  ]
33
34
  .filter((candidate) => Boolean(candidate))
34
35
  .map((candidate) => (0, node_path_1.resolve)(candidate));
@@ -38,7 +39,7 @@ async function resolveSkillSourceRoot(input) {
38
39
  return candidate;
39
40
  }
40
41
  }
41
- throw new Error(`Unable to locate the managed skill source for "${SKILL_NAME}". Checked: ${deduped.join(', ')}. Set VAONE_SKILL_SOURCE_DIR to override the lookup path.`);
42
+ throw new Error(`Unable to locate the managed skill source for "${exports.SKILL_NAME}". Checked: ${deduped.join(', ')}. Set VAONE_SKILL_SOURCE_DIR to override the lookup path.`);
42
43
  }
43
44
  function buildManagedHeader(target, relativePath) {
44
45
  if (relativePath.endsWith('.yaml') || relativePath.endsWith('.yml')) {
@@ -58,6 +59,27 @@ function injectManagedHeader(target, relativePath, contents) {
58
59
  const insertAt = closingIndex + '\n---\n'.length;
59
60
  return `${contents.slice(0, insertAt)}${header}\n\n${contents.slice(insertAt)}`;
60
61
  }
62
+ function upsertFrontMatterValue(contents, key, value) {
63
+ if (!contents.startsWith('---\n')) {
64
+ return contents;
65
+ }
66
+ const closingIndex = contents.indexOf('\n---\n', 4);
67
+ if (closingIndex === -1) {
68
+ return contents;
69
+ }
70
+ const frontMatter = contents.slice(4, closingIndex);
71
+ const body = contents.slice(closingIndex);
72
+ const lines = frontMatter.split('\n');
73
+ const nextLine = `${key}: "${value}"`;
74
+ const existingIndex = lines.findIndex((line) => line.startsWith(`${key}:`));
75
+ if (existingIndex >= 0) {
76
+ lines[existingIndex] = nextLine;
77
+ }
78
+ else {
79
+ lines.push(nextLine);
80
+ }
81
+ return `---\n${lines.join('\n')}${body}`;
82
+ }
61
83
  async function listRelativeFiles(rootDir, currentDir = rootDir) {
62
84
  const entries = await (0, promises_1.readdir)(currentDir, { withFileTypes: true });
63
85
  const files = await Promise.all(entries.map(async (entry) => {
@@ -71,14 +93,18 @@ async function listRelativeFiles(rootDir, currentDir = rootDir) {
71
93
  }
72
94
  async function createAgentPackFiles(target, scope) {
73
95
  void scope;
96
+ const runtimePackage = await (0, package_metadata_1.resolveRuntimePackageInfo)();
74
97
  const skillSourceRoot = await resolveSkillSourceRoot();
75
98
  const relativeFiles = await listRelativeFiles(skillSourceRoot);
76
99
  return Promise.all(relativeFiles.map(async (relativePath) => {
77
- const contents = await (0, promises_1.readFile)((0, node_path_1.join)(skillSourceRoot, relativePath), 'utf8');
100
+ const rawContents = await (0, promises_1.readFile)((0, node_path_1.join)(skillSourceRoot, relativePath), 'utf8');
101
+ const contents = relativePath.endsWith('SKILL.md')
102
+ ? upsertFrontMatterValue(rawContents, 'version', runtimePackage.version)
103
+ : rawContents;
78
104
  return {
79
- relativePath: (0, node_path_1.join)(SKILL_NAME, relativePath),
105
+ relativePath: (0, node_path_1.join)(exports.SKILL_NAME, relativePath),
80
106
  description: `${target.displayName} VisionAlpha operator skill asset`,
81
- contents: injectManagedHeader(target, (0, node_path_1.join)(SKILL_NAME, relativePath), contents),
107
+ contents: injectManagedHeader(target, (0, node_path_1.join)(exports.SKILL_NAME, relativePath), contents),
82
108
  };
83
109
  }));
84
110
  }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRuntimePackageInfo = resolveRuntimePackageInfo;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ async function pathExists(path) {
7
+ try {
8
+ await (0, promises_1.access)(path);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ async function findNearestPackageJson(startDir) {
16
+ let current = (0, node_path_1.resolve)(startDir);
17
+ for (;;) {
18
+ const packageJsonPath = (0, node_path_1.resolve)(current, 'package.json');
19
+ if (await pathExists(packageJsonPath)) {
20
+ return packageJsonPath;
21
+ }
22
+ const parent = (0, node_path_1.dirname)(current);
23
+ if (parent === current) {
24
+ return null;
25
+ }
26
+ current = parent;
27
+ }
28
+ }
29
+ async function resolveRuntimePackageInfo(input) {
30
+ const entryPath = input?.entryPath ?? process.argv[1];
31
+ const moduleDir = input?.moduleDir ?? __dirname;
32
+ const candidateDirs = [
33
+ entryPath ? (0, node_path_1.dirname)((0, node_path_1.resolve)(entryPath)) : null,
34
+ (0, node_path_1.resolve)(moduleDir),
35
+ ].filter((value) => Boolean(value));
36
+ const seen = new Set();
37
+ for (const candidateDir of candidateDirs) {
38
+ const packageJsonPath = await findNearestPackageJson(candidateDir);
39
+ if (!packageJsonPath || seen.has(packageJsonPath)) {
40
+ continue;
41
+ }
42
+ seen.add(packageJsonPath);
43
+ const raw = await (0, promises_1.readFile)(packageJsonPath, 'utf8');
44
+ const parsed = JSON.parse(raw);
45
+ if (!parsed.name || !parsed.version) {
46
+ continue;
47
+ }
48
+ return {
49
+ rootDir: (0, node_path_1.dirname)(packageJsonPath),
50
+ packageJsonPath,
51
+ name: parsed.name,
52
+ version: parsed.version,
53
+ private: parsed.private === true,
54
+ isPackagedInstall: parsed.private !== true,
55
+ };
56
+ }
57
+ throw new Error('Unable to resolve runtime package metadata for `vaone`.');
58
+ }
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveUpdateAction = resolveUpdateAction;
4
+ exports.runStartupPreflight = runStartupPreflight;
5
+ const node_child_process_1 = require("node:child_process");
6
+ const paths_1 = require("../../state/paths");
7
+ const runtime_config_1 = require("../../config/runtime-config");
8
+ const update_state_store_1 = require("../../state/stores/update-state-store");
9
+ const prompt_1 = require("../init/prompt");
10
+ const managed_pack_1 = require("../init/managed-pack");
11
+ const package_metadata_1 = require("../runtime/package-metadata");
12
+ const version_1 = require("./version");
13
+ const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
14
+ const PREFLIGHT_GUARD_ENV = 'VAONE_SKIP_PREFLIGHT';
15
+ const UPDATE_COPY = {
16
+ en: {
17
+ promptTitle(currentVersion, latestVersion) {
18
+ return `A newer VAOne CLI is available (${currentVersion} -> ${latestVersion}).`;
19
+ },
20
+ updateNow: 'Update now',
21
+ updateNowDescription: 'Run npm global install immediately',
22
+ skip: 'Skip this run',
23
+ skipDescription: 'Continue without updating',
24
+ defer: 'Wait for next release',
25
+ deferDescription: 'Hide this prompt until a newer version appears',
26
+ timeoutNotice(latestVersion) {
27
+ return `No response after 5 seconds. Updating VAOne CLI to ${latestVersion}.`;
28
+ },
29
+ updatingNotice(latestVersion) {
30
+ return `Updating VAOne CLI to ${latestVersion}...`;
31
+ },
32
+ restartingNotice: 'Restarting the requested command with the updated CLI.',
33
+ },
34
+ 'zh-CN': {
35
+ promptTitle(currentVersion, latestVersion) {
36
+ return `检测到更高版本的 VAOne CLI(${currentVersion} -> ${latestVersion})。`;
37
+ },
38
+ updateNow: '立即更新',
39
+ updateNowDescription: '马上执行 npm 全局安装',
40
+ skip: '本次跳过',
41
+ skipDescription: '继续执行当前命令,不更新',
42
+ defer: '下个版本再提醒',
43
+ deferDescription: '在出现更新版本前不再提示',
44
+ timeoutNotice(latestVersion) {
45
+ return `5 秒内未收到响应,开始将 VAOne CLI 更新到 ${latestVersion}。`;
46
+ },
47
+ updatingNotice(latestVersion) {
48
+ return `正在将 VAOne CLI 更新到 ${latestVersion}...`;
49
+ },
50
+ restartingNotice: '正在使用更新后的 CLI 重新执行原命令。',
51
+ },
52
+ };
53
+ function isAutomationContext(env) {
54
+ return env.CI === '1' || env.CI === 'true';
55
+ }
56
+ function shouldSkipManagedSync(commandPath) {
57
+ return (commandPath.length === 0 ||
58
+ commandPath[0] === 'init' ||
59
+ commandPath[0] === 'doctor' ||
60
+ commandPath[0] === 'uninstall');
61
+ }
62
+ function shouldSkipCliUpdate(commandPath) {
63
+ return commandPath[0] === 'doctor';
64
+ }
65
+ function canPromptForUpdate(env) {
66
+ return (process.stdin.isTTY === true &&
67
+ process.stdout.isTTY === true &&
68
+ !isAutomationContext(env));
69
+ }
70
+ async function fetchLatestPublishedVersion(packageName) {
71
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`, {
72
+ headers: {
73
+ accept: 'application/json',
74
+ },
75
+ });
76
+ if (!response.ok) {
77
+ throw new Error(`npm registry returned ${response.status} while checking ${packageName}.`);
78
+ }
79
+ const payload = (await response.json());
80
+ if (!payload.version) {
81
+ throw new Error(`npm registry did not return a version for ${packageName}.`);
82
+ }
83
+ return payload.version;
84
+ }
85
+ function runUpdateCommand(packageName, version) {
86
+ const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
87
+ return (0, node_child_process_1.spawnSync)(command, ['install', '--global', `${packageName}@${version}`], {
88
+ encoding: 'utf8',
89
+ stdio: 'pipe',
90
+ env: process.env,
91
+ });
92
+ }
93
+ async function performSelfUpdate(packageName, version) {
94
+ const result = runUpdateCommand(packageName, version);
95
+ if (result.status === 0) {
96
+ return { success: true };
97
+ }
98
+ return {
99
+ success: false,
100
+ failureCode: result.status === null ? 'npm_update_signal' : 'npm_update_failed',
101
+ failureMessage: result.stderr?.trim() || result.stdout?.trim() || 'npm update failed.',
102
+ };
103
+ }
104
+ async function reexecCommand(argv) {
105
+ if (!process.argv[1]) {
106
+ return null;
107
+ }
108
+ const result = (0, node_child_process_1.spawnSync)(process.execPath, [process.argv[1], ...argv], {
109
+ stdio: 'inherit',
110
+ env: {
111
+ ...process.env,
112
+ [PREFLIGHT_GUARD_ENV]: '1',
113
+ },
114
+ });
115
+ if (typeof result.status === 'number') {
116
+ return result.status;
117
+ }
118
+ return null;
119
+ }
120
+ async function resolveUpdateAction(input) {
121
+ const copy = UPDATE_COPY[input.locale];
122
+ const prompt = (input.promptFactory ?? prompt_1.createTerminalPrompt)();
123
+ const selectionPromise = prompt
124
+ .select({
125
+ message: copy.promptTitle(input.currentVersion, input.latestVersion),
126
+ defaultValue: 'update',
127
+ locale: input.locale,
128
+ choices: [
129
+ {
130
+ value: 'update',
131
+ label: copy.updateNow,
132
+ description: copy.updateNowDescription,
133
+ },
134
+ {
135
+ value: 'skip',
136
+ label: copy.skip,
137
+ description: copy.skipDescription,
138
+ },
139
+ {
140
+ value: 'defer',
141
+ label: copy.defer,
142
+ description: copy.deferDescription,
143
+ },
144
+ ],
145
+ })
146
+ .then((choice) => ({ kind: 'choice', choice }), (error) => ({ kind: 'error', error }));
147
+ const timeoutMs = input.timeoutMs ?? 5_000;
148
+ const timeoutPromise = new Promise((resolve) => {
149
+ setTimeout(() => {
150
+ void prompt.close().catch(() => undefined);
151
+ resolve({ kind: 'timeout' });
152
+ }, timeoutMs);
153
+ });
154
+ const result = await Promise.race([selectionPromise, timeoutPromise]);
155
+ if (result.kind === 'timeout') {
156
+ input.logger.info(copy.timeoutNotice(input.latestVersion));
157
+ return 'update';
158
+ }
159
+ if (result.kind === 'error') {
160
+ throw result.error;
161
+ }
162
+ return result.choice;
163
+ }
164
+ async function runStartupPreflight(input, deps = {}) {
165
+ if (input.env[PREFLIGHT_GUARD_ENV] === '1') {
166
+ return null;
167
+ }
168
+ const now = deps.now ?? Date.now;
169
+ const runtimePackage = (await deps.resolvePackageInfo?.()) ?? (await (0, package_metadata_1.resolveRuntimePackageInfo)());
170
+ if (!shouldSkipManagedSync(input.commandPath)) {
171
+ try {
172
+ const refreshed = await (deps.syncManagedPacks ?? managed_pack_1.syncManagedPacks)({
173
+ cwd: input.cwd,
174
+ homeDir: input.homeDir,
175
+ currentVersion: runtimePackage.version,
176
+ });
177
+ if (refreshed.length > 0) {
178
+ const summary = refreshed
179
+ .map((entry) => `${entry.target.displayName} (${entry.scope})`)
180
+ .join(', ');
181
+ input.logger.info(`Refreshed stale managed skill packs: ${summary}.`);
182
+ }
183
+ }
184
+ catch (error) {
185
+ input.logger.warn(`Managed skill-pack sync failed: ${error instanceof Error ? error.message : 'unknown error'}`);
186
+ }
187
+ }
188
+ if (shouldSkipCliUpdate(input.commandPath)) {
189
+ return null;
190
+ }
191
+ if (!runtimePackage.isPackagedInstall || !canPromptForUpdate(input.env)) {
192
+ return null;
193
+ }
194
+ const updateStateStore = new update_state_store_1.UpdateStateStore((0, paths_1.resolveUpdateStateFile)(input.paths));
195
+ const updateState = (await updateStateStore.read()) ?? {};
196
+ const lastCheckedAt = updateState.lastCheckedAt
197
+ ? Date.parse(updateState.lastCheckedAt)
198
+ : NaN;
199
+ if (Number.isFinite(lastCheckedAt) &&
200
+ now() - lastCheckedAt < UPDATE_CHECK_INTERVAL_MS) {
201
+ return null;
202
+ }
203
+ let latestVersion;
204
+ try {
205
+ latestVersion = await (deps.fetchLatestVersion ?? fetchLatestPublishedVersion)(runtimePackage.name);
206
+ }
207
+ catch (error) {
208
+ await updateStateStore.save({
209
+ ...updateState,
210
+ lastCheckedAt: new Date(now()).toISOString(),
211
+ lastFailureCode: 'version_check_failed',
212
+ lastFailureMessage: error instanceof Error ? error.message : 'Version check failed.',
213
+ });
214
+ input.logger.warn(`CLI update check failed: ${error instanceof Error ? error.message : 'unknown error'}`);
215
+ return null;
216
+ }
217
+ const nextState = {
218
+ ...updateState,
219
+ lastCheckedAt: new Date(now()).toISOString(),
220
+ lastKnownLatestVersion: latestVersion,
221
+ };
222
+ if (!(0, version_1.isVersionNewer)(latestVersion, runtimePackage.version)) {
223
+ await updateStateStore.save({
224
+ ...nextState,
225
+ lastFailureCode: undefined,
226
+ lastFailureMessage: undefined,
227
+ });
228
+ return null;
229
+ }
230
+ const deferredBoundary = input.runtimeConfig.updateDeferredUntilVersion;
231
+ if (deferredBoundary &&
232
+ !(0, version_1.isVersionNewer)(latestVersion, deferredBoundary)) {
233
+ await updateStateStore.save(nextState);
234
+ return null;
235
+ }
236
+ const action = await resolveUpdateAction({
237
+ currentVersion: runtimePackage.version,
238
+ latestVersion,
239
+ locale: input.runtimeConfig.locale,
240
+ logger: input.logger,
241
+ promptFactory: deps.promptFactory,
242
+ timeoutMs: deps.promptTimeoutMs,
243
+ });
244
+ if (action === 'skip') {
245
+ await updateStateStore.save(nextState);
246
+ return null;
247
+ }
248
+ if (action === 'defer') {
249
+ await (0, runtime_config_1.saveRuntimeConfig)(input.paths, {
250
+ updateDeferredUntilVersion: latestVersion,
251
+ });
252
+ input.runtimeConfig.updateDeferredUntilVersion = latestVersion;
253
+ await updateStateStore.save(nextState);
254
+ return null;
255
+ }
256
+ const copy = UPDATE_COPY[input.runtimeConfig.locale];
257
+ input.logger.info(copy.updatingNotice(latestVersion));
258
+ const result = await (deps.performSelfUpdate ?? performSelfUpdate)(runtimePackage.name, latestVersion);
259
+ if (!result.success) {
260
+ await updateStateStore.save({
261
+ ...nextState,
262
+ lastAttemptedUpdateAt: new Date(now()).toISOString(),
263
+ lastAttemptedTargetVersion: latestVersion,
264
+ lastFailureCode: result.failureCode,
265
+ lastFailureMessage: result.failureMessage,
266
+ });
267
+ input.logger.warn(result.failureMessage ?? 'CLI update failed.');
268
+ return null;
269
+ }
270
+ await (0, runtime_config_1.saveRuntimeConfig)(input.paths, {
271
+ updateDeferredUntilVersion: undefined,
272
+ });
273
+ input.runtimeConfig.updateDeferredUntilVersion = undefined;
274
+ await updateStateStore.save({
275
+ ...nextState,
276
+ lastAttemptedUpdateAt: new Date(now()).toISOString(),
277
+ lastAttemptedTargetVersion: latestVersion,
278
+ lastFailureCode: undefined,
279
+ lastFailureMessage: undefined,
280
+ });
281
+ input.logger.info(copy.restartingNotice);
282
+ const exitCode = await (deps.reexecCommand ?? reexecCommand)(input.argv);
283
+ return exitCode;
284
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compareVersions = compareVersions;
4
+ exports.isVersionNewer = isVersionNewer;
5
+ function parseVersion(raw) {
6
+ const trimmed = raw.trim();
7
+ const [core, prerelease] = trimmed.split('-', 2);
8
+ const segments = core.split('.').map((value) => {
9
+ if (!/^\d+$/.test(value)) {
10
+ throw new Error(`Invalid version segment "${value}" in "${raw}".`);
11
+ }
12
+ return Number(value);
13
+ });
14
+ return {
15
+ segments,
16
+ prerelease,
17
+ };
18
+ }
19
+ function compareVersions(left, right) {
20
+ const parsedLeft = parseVersion(left);
21
+ const parsedRight = parseVersion(right);
22
+ const maxLength = Math.max(parsedLeft.segments.length, parsedRight.segments.length);
23
+ for (let index = 0; index < maxLength; index += 1) {
24
+ const leftValue = parsedLeft.segments[index] ?? 0;
25
+ const rightValue = parsedRight.segments[index] ?? 0;
26
+ if (leftValue !== rightValue) {
27
+ return leftValue > rightValue ? 1 : -1;
28
+ }
29
+ }
30
+ if (parsedLeft.prerelease && !parsedRight.prerelease) {
31
+ return -1;
32
+ }
33
+ if (!parsedLeft.prerelease && parsedRight.prerelease) {
34
+ return 1;
35
+ }
36
+ if (parsedLeft.prerelease === parsedRight.prerelease) {
37
+ return 0;
38
+ }
39
+ return (parsedLeft.prerelease ?? '').localeCompare(parsedRight.prerelease ?? '');
40
+ }
41
+ function isVersionNewer(candidate, baseline) {
42
+ return compareVersions(candidate, baseline) > 0;
43
+ }
@@ -15,6 +15,9 @@ class Logger {
15
15
  info(message) {
16
16
  this.plain(message);
17
17
  }
18
+ warn(message) {
19
+ this.plain(`Warning: ${message}`);
20
+ }
18
21
  error(message) {
19
22
  process.stderr.write(`Error: ${message}\n`);
20
23
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createCliPaths = createCliPaths;
4
4
  exports.ensureCliDirectories = ensureCliDirectories;
5
+ exports.resolveUpdateStateFile = resolveUpdateStateFile;
5
6
  const promises_1 = require("node:fs/promises");
6
7
  const node_os_1 = require("node:os");
7
8
  const node_path_1 = require("node:path");
@@ -30,3 +31,6 @@ async function ensureCliDirectories(paths) {
30
31
  await (0, promises_1.mkdir)(paths.rootDir, { recursive: true });
31
32
  await (0, promises_1.mkdir)(paths.stateDir, { recursive: true });
32
33
  }
34
+ function resolveUpdateStateFile(paths) {
35
+ return (0, node_path_1.join)(paths.stateDir, 'update.json');
36
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UpdateStateStore = void 0;
4
+ const file_json_store_1 = require("./file-json-store");
5
+ class UpdateStateStore {
6
+ store;
7
+ constructor(filePath) {
8
+ this.store = new file_json_store_1.FileJsonStore(filePath);
9
+ }
10
+ async read() {
11
+ return this.store.read();
12
+ }
13
+ async save(state) {
14
+ await this.store.write(state);
15
+ }
16
+ async clear() {
17
+ await this.store.clear();
18
+ }
19
+ }
20
+ exports.UpdateStateStore = UpdateStateStore;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solarains/va-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Bootstrap CLI for the VAOne product.",
5
5
  "bin": {
6
6
  "vaone": "dist/main.js"