@solarains/va-cli 0.1.3 → 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.
- package/dist/cli/help-copy.js +23 -15
- package/dist/cli/root-command.js +2 -0
- package/dist/cli/run-cli.js +17 -2
- package/dist/commands/auth/logout-command.js +3 -1
- package/dist/commands/doctor/doctor-command.js +44 -0
- package/dist/commands/uninstall/uninstall-command.js +62 -0
- package/dist/config/runtime-config.js +2 -3
- package/dist/features/auth/installation-state.js +4 -3
- package/dist/features/auth/loopback-callback-page.js +1 -1
- package/dist/features/init/init-flow.js +10 -9
- package/dist/features/init/installer.js +5 -1
- package/dist/features/init/managed-pack.js +185 -0
- package/dist/features/init/target-registry.js +3 -1
- package/dist/features/init/templates.js +38 -12
- package/dist/features/runtime/package-metadata.js +58 -0
- package/dist/features/update/preflight.js +284 -0
- package/dist/features/update/version.js +43 -0
- package/dist/shared/logger.js +3 -0
- package/dist/state/paths.js +4 -0
- package/dist/state/stores/update-state-store.js +20 -0
- package/package.json +1 -1
package/dist/cli/help-copy.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
},
|
package/dist/cli/root-command.js
CHANGED
|
@@ -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,
|
package/dist/cli/run-cli.js
CHANGED
|
@@ -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
|
}
|
|
@@ -22,7 +22,9 @@ exports.logoutCommand = {
|
|
|
22
22
|
}
|
|
23
23
|
catch (error) {
|
|
24
24
|
remoteLogoutFailure =
|
|
25
|
-
error instanceof Error
|
|
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
|
+
};
|
|
@@ -46,13 +46,12 @@ async function loadRuntimeConfig(env, paths) {
|
|
|
46
46
|
const locale = envLocale ?? fileLocale ?? locale_1.DEFAULT_LOCALE;
|
|
47
47
|
return {
|
|
48
48
|
appEnv: env.VAONE_ENV ?? fileConfig.appEnv ?? 'local',
|
|
49
|
-
apiBaseUrl: normalizeHomePath(env.VAONE_API_BASE_URL ??
|
|
50
|
-
fileConfig.apiBaseUrl ??
|
|
51
|
-
exports.DEFAULT_API_BASE_URL).replace(/\/$/, ''),
|
|
49
|
+
apiBaseUrl: normalizeHomePath(env.VAONE_API_BASE_URL ?? fileConfig.apiBaseUrl ?? exports.DEFAULT_API_BASE_URL).replace(/\/$/, ''),
|
|
52
50
|
debug: parseBoolean(env.VAONE_DEBUG) ?? fileConfig.debug ?? false,
|
|
53
51
|
authCallbackMode,
|
|
54
52
|
locale,
|
|
55
53
|
localeConfigured: envLocale !== undefined || fileLocale !== undefined,
|
|
54
|
+
updateDeferredUntilVersion: fileConfig.updateDeferredUntilVersion?.trim() || undefined,
|
|
56
55
|
};
|
|
57
56
|
}
|
|
58
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
|
|
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:
|
|
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:
|
|
59
|
+
cliVersion: runtimePackage.version,
|
|
59
60
|
deviceName: (0, node_os_1.hostname)(),
|
|
60
61
|
createdAt: now,
|
|
61
62
|
updatedAt: now,
|
|
@@ -108,7 +108,8 @@ function extractLoginSummary(lines, copy) {
|
|
|
108
108
|
/^-+$/.test(line.trim())) {
|
|
109
109
|
continue;
|
|
110
110
|
}
|
|
111
|
-
if (line.includes('Automatic browser launch failed') ||
|
|
111
|
+
if (line.includes('Automatic browser launch failed') ||
|
|
112
|
+
line.startsWith('URL:')) {
|
|
112
113
|
details.push(line);
|
|
113
114
|
continue;
|
|
114
115
|
}
|
|
@@ -405,14 +406,14 @@ function resolveInitOptions(input) {
|
|
|
405
406
|
return resolve();
|
|
406
407
|
}
|
|
407
408
|
function positionalscopeAndScopeConflict(positionalScope, parsedScope) {
|
|
408
|
-
return Boolean(positionalScope &&
|
|
409
|
-
parsedScope &&
|
|
410
|
-
positionalScope !== parsedScope);
|
|
409
|
+
return Boolean(positionalScope && parsedScope && positionalScope !== parsedScope);
|
|
411
410
|
}
|
|
412
411
|
function describeDetection(presenter, copy, detectedTargets) {
|
|
413
412
|
presenter.section(copy.sections.detection);
|
|
414
413
|
for (const target of detectedTargets) {
|
|
415
|
-
const summary = target.detected
|
|
414
|
+
const summary = target.detected
|
|
415
|
+
? copy.labels.detected
|
|
416
|
+
: copy.labels.notDetected;
|
|
416
417
|
presenter.list([
|
|
417
418
|
`${target.displayName}: ${summary}`,
|
|
418
419
|
...(target.detectedIn.length > 0
|
|
@@ -477,9 +478,7 @@ async function runVerification(context, presenter, copy, options, dependencies)
|
|
|
477
478
|
});
|
|
478
479
|
if (options.skipLogin) {
|
|
479
480
|
presenter.step('skipped', copy.statuses.skipped, copy.steps.usageSkipped);
|
|
480
|
-
presenter.list([
|
|
481
|
-
copy.messages.usageVerificationSkippedDeferred,
|
|
482
|
-
]);
|
|
481
|
+
presenter.list([copy.messages.usageVerificationSkippedDeferred]);
|
|
483
482
|
return;
|
|
484
483
|
}
|
|
485
484
|
await runCommandInBox({
|
|
@@ -570,7 +569,9 @@ async function runInitFlow(context, args, partialDependencies = {}) {
|
|
|
570
569
|
cwd: dependencies.cwd,
|
|
571
570
|
homeDir: dependencies.homeDir,
|
|
572
571
|
});
|
|
573
|
-
const prompt = dependencies.isInteractive
|
|
572
|
+
const prompt = dependencies.isInteractive
|
|
573
|
+
? dependencies.promptFactory()
|
|
574
|
+
: null;
|
|
574
575
|
let shouldForceExit = false;
|
|
575
576
|
let exitCode = 1;
|
|
576
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 &&
|
|
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'
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/shared/logger.js
CHANGED
package/dist/state/paths.js
CHANGED
|
@@ -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;
|