@solarains/va-cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/cli/command-types.js +2 -0
  4. package/dist/cli/help-copy.js +97 -0
  5. package/dist/cli/help.js +29 -0
  6. package/dist/cli/root-command.js +24 -0
  7. package/dist/cli/run-cli.js +107 -0
  8. package/dist/commands/assets/assets-command.js +10 -0
  9. package/dist/commands/assets/list-command.js +52 -0
  10. package/dist/commands/auth/auth-command.js +11 -0
  11. package/dist/commands/auth/login-command.js +127 -0
  12. package/dist/commands/auth/logout-command.js +57 -0
  13. package/dist/commands/doctor/doctor-command.js +33 -0
  14. package/dist/commands/init/init-command.js +19 -0
  15. package/dist/commands/intelligence/intelligence-command.js +10 -0
  16. package/dist/commands/intelligence/query-command.js +109 -0
  17. package/dist/commands/reports/get-command.js +69 -0
  18. package/dist/commands/reports/list-command.js +33 -0
  19. package/dist/commands/reports/reports-command.js +11 -0
  20. package/dist/commands/usage/usage-command.js +27 -0
  21. package/dist/config/locale.js +30 -0
  22. package/dist/config/runtime-config.js +64 -0
  23. package/dist/features/assets/assets-api-client.js +18 -0
  24. package/dist/features/assets/assets-output.js +28 -0
  25. package/dist/features/auth/auth-api-client.js +60 -0
  26. package/dist/features/auth/authenticated-api-client.js +142 -0
  27. package/dist/features/auth/browser-login.js +191 -0
  28. package/dist/features/auth/installation-state.js +65 -0
  29. package/dist/features/auth/loopback-callback-page.js +231 -0
  30. package/dist/features/daily-reports/daily-reports-api-client.js +22 -0
  31. package/dist/features/daily-reports/daily-reports-output.js +54 -0
  32. package/dist/features/init/detect-targets.js +57 -0
  33. package/dist/features/init/init-copy.js +348 -0
  34. package/dist/features/init/init-flow.js +661 -0
  35. package/dist/features/init/installer.js +122 -0
  36. package/dist/features/init/manifest.js +29 -0
  37. package/dist/features/init/presentation.js +147 -0
  38. package/dist/features/init/prompt.js +135 -0
  39. package/dist/features/init/target-registry.js +45 -0
  40. package/dist/features/init/templates.js +84 -0
  41. package/dist/features/init/types.js +2 -0
  42. package/dist/features/intelligence/intelligence-api-client.js +18 -0
  43. package/dist/features/intelligence/intelligence-output.js +36 -0
  44. package/dist/features/intelligence/intelligence-query-state.js +20 -0
  45. package/dist/features/usage/usage-api-client.js +7 -0
  46. package/dist/features/usage/usage-output.js +52 -0
  47. package/dist/main.js +7 -0
  48. package/dist/shared/argv.js +65 -0
  49. package/dist/shared/logger.js +32 -0
  50. package/dist/shared/output.js +22 -0
  51. package/dist/state/paths.js +32 -0
  52. package/dist/state/stores/file-json-store.js +29 -0
  53. package/dist/state/stores/installation-store.js +20 -0
  54. package/dist/state/stores/token-store.js +20 -0
  55. package/package.json +19 -0
  56. package/skills/visionalpha-operator/SKILL.md +38 -0
  57. package/skills/visionalpha-operator/agents/openai.yaml +4 -0
  58. package/skills/visionalpha-operator/references/asset-intelligence.md +35 -0
  59. package/skills/visionalpha-operator/references/assets.md +29 -0
  60. package/skills/visionalpha-operator/references/auth-recovery.md +26 -0
  61. package/skills/visionalpha-operator/references/daily-reports.md +30 -0
  62. package/skills/visionalpha-operator/references/usage-summary.md +24 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VAOne contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # va-cli
2
+
3
+ Bootstrap CLI for the new VAOne product.
4
+
5
+ This project intentionally starts as a runnable but business-light CLI shell. It
6
+ defines the command tree, local config and state layout, token-storage
7
+ abstraction, diagnostics path, and placeholder commands that later child
8
+ changes will extend for browser auth, installation binding, and usage queries.
9
+
10
+ Parent architecture references:
11
+
12
+ - `openspec/changes/define-skills-mvp-architecture/proposal.md`
13
+ - `openspec/changes/define-skills-mvp-architecture/design.md`
14
+ - `openspec/changes/define-skills-mvp-architecture/specs/skills-system-architecture/spec.md`
15
+ - `openspec/changes/define-skills-mvp-architecture/master-task-list.md`
16
+ - `openspec/changes/bootstrap-va-cli/design.md`
17
+ - `openspec/changes/add-cli-browser-auth-flow/proposal.md`
18
+ - `openspec/changes/add-cli-browser-auth-flow/design.md`
19
+
20
+ Current command surface:
21
+
22
+ - `vaone init`
23
+ - `vaone auth login`
24
+ - `vaone auth logout`
25
+ - `vaone usage`
26
+ - `vaone doctor`
27
+
28
+ Current auth foundation:
29
+
30
+ - `vaone auth login` now generates PKCE verifier/challenge pairs for a public CLI client
31
+ - the CLI creates or resumes a durable installation identity with ed25519 key material
32
+ - primary completion uses a localhost loopback callback
33
+ - fallback completion prints the browser URL and terminal QR code, then polls the same auth session
34
+ - token persistence stores account id, installation id, session id, access token, and refresh token metadata
35
+ - refresh continuation signs `refreshToken + installationId + signedAt` with the local installation private key
36
+ - revoked installations and invalid installation-proof refresh attempts now resolve to explicit re-login guidance instead of generic refresh failure
37
+
38
+ Current source layout:
39
+
40
+ - `src/cli/` startup, command tree, help rendering
41
+ - `src/commands/` grouped command handlers
42
+ - `src/config/` runtime configuration and endpoint loading
43
+ - `src/features/init/` agent target detection, managed pack generation, init orchestration
44
+ - `src/state/` local path layout and file-backed storage adapters
45
+ - `src/shared/` logging and output helpers
46
+
47
+ ## `vaone init`
48
+
49
+ `vaone init` is the supported onboarding path for agent-ready setup. It now:
50
+
51
+ - detects Codex, Claude Code, and OpenClaw install surfaces
52
+ - supports `project` and `user` scope installation
53
+ - installs thin VAOne-managed agent packs that call back into `vaone`
54
+ - chains into `vaone auth login` by default unless `--skip-login` is set
55
+ - runs CLI-native readiness checks before exit
56
+
57
+ Supported flags:
58
+
59
+ - `--agents codex,claude,openclaw`
60
+ - `--scope project|user`
61
+ - `--skip-login`
62
+ - `--force`
63
+ - `--yes`
64
+
65
+ Target-specific install behavior:
66
+
67
+ - Codex: installs `vaone/SKILL.md` under `.codex/skills` or `~/.codex/skills`
68
+ - Claude Code: installs managed files under `.claude/commands`, `.claude/agents`, and `.claude/VAONE.md`
69
+ - OpenClaw: installs `vaone/SKILL.md` under `skills` or `~/.openclaw/skills`
70
+
71
+ Current MVP limits:
72
+
73
+ - Claude Code support is limited to managed command and agent guide files; deeper adapter behavior can be expanded later
74
+ - OpenClaw marketplace plugin packaging is still deferred; this change only installs local skill assets
75
+ - Agents may need a full restart or a new session before they discover newly installed files
76
+
77
+ ## Local development
78
+
79
+ ```bash
80
+ pnpm install
81
+ pnpm cli -- --help
82
+ ```
83
+
84
+ ## Validation
85
+
86
+ ```bash
87
+ pnpm typecheck
88
+ pnpm lint
89
+ pnpm test
90
+ pnpm build
91
+ node dist/main.js --help
92
+ ```
93
+
94
+ ## npm release packaging
95
+
96
+ `va-cli` now assembles a dedicated publish root under `dist/publish/` and
97
+ publishes from that directory instead of from the repository root.
98
+
99
+ Useful commands:
100
+
101
+ ```bash
102
+ pnpm build:publish
103
+ pnpm pack:publish
104
+ pnpm verify:publish
105
+ ```
106
+
107
+ Release flow:
108
+
109
+ ```bash
110
+ pnpm release:prepare
111
+ pnpm verify:publish
112
+ npm publish dist/publish --access public
113
+ ```
114
+
115
+ `pnpm release:prepare` increments the patch version in the root
116
+ `package.json`, then rebuilds `dist/publish/` with the updated version.
117
+
118
+ Before the first real npm publish, add the intended license text at
119
+ `va-cli/LICENSE`. The packaging flow copies that file into `dist/publish/` when
120
+ present.
121
+
122
+ ## Git Workflow
123
+
124
+ - 提交信息使用 Conventional Commits,建议以中文描述主要变更,例如 `feat(cli): 增加日报查询命令`
125
+ - 本仓库启用 Husky:
126
+ - `pre-commit` 会执行 `pnpm lint-staged` 和 `pnpm typecheck`
127
+ - `commit-msg` 会执行 `pnpm commitlint --edit`
128
+ - PR 需要填写摘要、背景、验证方式,以及配置 / 认证 / 本地状态影响
129
+ - CLI 相关 PR 请附关键命令示例输出
130
+ - 提交前最少通过:
131
+ - `pnpm lint`
132
+ - `pnpm typecheck`
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHelpCopy = getHelpCopy;
4
+ exports.getLocalizedCommandHelp = getLocalizedCommandHelp;
5
+ exports.getLocalizedChildSummary = getLocalizedChildSummary;
6
+ const HELP_COPY = {
7
+ en: {
8
+ commandsHeading: 'Commands:',
9
+ globalFlagsHeading: 'Global flags:',
10
+ helpFlag: 'Show help',
11
+ debugFlag: 'Enable debug logging',
12
+ commandHelp: {
13
+ '': {
14
+ summary: 'VAOne CLI with guided init, auth, assets, usage, daily reports, intelligence queries, and diagnostics.',
15
+ usage: 'vaone <command> [subcommand] [options]',
16
+ },
17
+ init: {
18
+ summary: 'Install VisionAlpha operator skills with branded onboarding and optional browser login.',
19
+ usage: 'vaone init [.] [--agents codex,claude,openclaw] [--scope project|user] [--lang en|zh-CN] [--skip-login] [--force] [--yes]',
20
+ },
21
+ auth: {
22
+ summary: 'Authentication placeholder commands for future browser login.',
23
+ },
24
+ assets: {
25
+ summary: 'Browse asset catalog commands.',
26
+ },
27
+ usage: {
28
+ summary: 'Show current account usage summary.',
29
+ },
30
+ reports: {
31
+ summary: 'List or read normalized daily reports.',
32
+ },
33
+ intelligence: {
34
+ summary: 'Query paid asset intelligence data.',
35
+ },
36
+ doctor: {
37
+ summary: 'Run local diagnostics for auth, install, and configuration state.',
38
+ },
39
+ },
40
+ unknownCommandSegment(path, token) {
41
+ return `Unknown command segment "${token}" under "${path}".`;
42
+ },
43
+ },
44
+ 'zh-CN': {
45
+ commandsHeading: '命令:',
46
+ globalFlagsHeading: '全局参数:',
47
+ helpFlag: '显示帮助',
48
+ debugFlag: '启用调试日志',
49
+ commandHelp: {
50
+ '': {
51
+ summary: 'VAOne CLI,提供引导式 init、认证、资产、usage、日报、intelligence 查询与诊断能力。',
52
+ usage: 'vaone <命令> [子命令] [参数]',
53
+ },
54
+ init: {
55
+ summary: '通过带品牌感的引导流程安装 VisionAlpha operator skills,并可选串联浏览器登录。',
56
+ usage: 'vaone init [.] [--agents codex,claude,openclaw] [--scope project|user] [--lang en|zh-CN] [--skip-login] [--force] [--yes]',
57
+ },
58
+ auth: {
59
+ summary: '认证相关命令,包括浏览器登录与退出登录。',
60
+ },
61
+ assets: {
62
+ summary: '浏览资产目录相关命令。',
63
+ },
64
+ usage: {
65
+ summary: '显示当前账户 usage 摘要。',
66
+ },
67
+ reports: {
68
+ summary: '列出或读取标准化日报。',
69
+ },
70
+ intelligence: {
71
+ summary: '查询付费资产 intelligence 数据。',
72
+ },
73
+ doctor: {
74
+ summary: '运行本地认证、安装和配置状态诊断。',
75
+ },
76
+ },
77
+ unknownCommandSegment(path, token) {
78
+ return `命令 "${path}" 下不存在 "${token}" 这一段。`;
79
+ },
80
+ },
81
+ };
82
+ function pathKey(pathSegments) {
83
+ return pathSegments.join(' ');
84
+ }
85
+ function getHelpCopy(locale) {
86
+ return HELP_COPY[locale];
87
+ }
88
+ function getLocalizedCommandHelp(locale, command, pathSegments) {
89
+ return (HELP_COPY[locale].commandHelp[pathKey(pathSegments)] ?? {
90
+ summary: command.summary,
91
+ usage: command.usage,
92
+ });
93
+ }
94
+ function getLocalizedChildSummary(locale, pathSegments, child) {
95
+ return (HELP_COPY[locale].commandHelp[pathKey([...pathSegments, child.name])]
96
+ ?.summary ?? child.summary);
97
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderHelp = renderHelp;
4
+ const help_copy_1 = require("./help-copy");
5
+ function formatPath(segments) {
6
+ return ['vaone', ...segments].join(' ');
7
+ }
8
+ function renderHelp(command, pathSegments = [], locale = 'en') {
9
+ const copy = (0, help_copy_1.getHelpCopy)(locale);
10
+ const localized = (0, help_copy_1.getLocalizedCommandHelp)(locale, command, pathSegments);
11
+ const lines = [];
12
+ const usage = localized.usage ?? command.usage ?? formatPath(pathSegments);
13
+ const summary = localized.summary ?? command.summary;
14
+ lines.push(`Usage: ${usage}`);
15
+ lines.push('');
16
+ lines.push(summary);
17
+ if (command.children?.length) {
18
+ lines.push('');
19
+ lines.push(copy.commandsHeading);
20
+ for (const child of command.children) {
21
+ lines.push(` ${child.name.padEnd(10, ' ')} ${(0, help_copy_1.getLocalizedChildSummary)(locale, pathSegments, child)}`);
22
+ }
23
+ }
24
+ lines.push('');
25
+ lines.push(copy.globalFlagsHeading);
26
+ lines.push(` --help, -h ${copy.helpFlag}`);
27
+ lines.push(` --debug ${copy.debugFlag}`);
28
+ return lines.join('\n');
29
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rootCommand = void 0;
4
+ const assets_command_1 = require("../commands/assets/assets-command");
5
+ const auth_command_1 = require("../commands/auth/auth-command");
6
+ const doctor_command_1 = require("../commands/doctor/doctor-command");
7
+ const init_command_1 = require("../commands/init/init-command");
8
+ const intelligence_command_1 = require("../commands/intelligence/intelligence-command");
9
+ const reports_command_1 = require("../commands/reports/reports-command");
10
+ const usage_command_1 = require("../commands/usage/usage-command");
11
+ exports.rootCommand = {
12
+ name: 'vaone',
13
+ summary: 'VAOne CLI with guided init, auth, assets, usage, daily reports, intelligence queries, and diagnostics.',
14
+ usage: 'vaone <command> [subcommand] [options]',
15
+ children: [
16
+ init_command_1.initCommand,
17
+ auth_command_1.authCommand,
18
+ assets_command_1.assetsCommand,
19
+ usage_command_1.usageCommand,
20
+ reports_command_1.reportsCommand,
21
+ intelligence_command_1.intelligenceCommand,
22
+ doctor_command_1.doctorCommand,
23
+ ],
24
+ };
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCli = runCli;
4
+ const runtime_config_1 = require("../config/runtime-config");
5
+ const paths_1 = require("../state/paths");
6
+ const installation_store_1 = require("../state/stores/installation-store");
7
+ const token_store_1 = require("../state/stores/token-store");
8
+ const logger_1 = require("../shared/logger");
9
+ const help_1 = require("./help");
10
+ const help_copy_1 = require("./help-copy");
11
+ const root_command_1 = require("./root-command");
12
+ function parseArgv(argv) {
13
+ const tokens = [];
14
+ let debug = false;
15
+ let help = false;
16
+ for (const token of argv) {
17
+ if (token === '--') {
18
+ continue;
19
+ }
20
+ if (token === '--debug') {
21
+ debug = true;
22
+ continue;
23
+ }
24
+ if (token === '--help' || token === '-h') {
25
+ help = true;
26
+ continue;
27
+ }
28
+ tokens.push(token);
29
+ }
30
+ if (tokens[0] === 'help') {
31
+ help = true;
32
+ tokens.shift();
33
+ }
34
+ return {
35
+ debug,
36
+ help,
37
+ tokens,
38
+ };
39
+ }
40
+ function resolveCommand(tokens) {
41
+ let command = root_command_1.rootCommand;
42
+ const path = [];
43
+ let index = 0;
44
+ while (index < tokens.length && command.children?.length) {
45
+ const nextToken = tokens[index];
46
+ const nextCommand = command.children.find((child) => child.name === nextToken);
47
+ if (!nextCommand) {
48
+ break;
49
+ }
50
+ command = nextCommand;
51
+ path.push(nextToken);
52
+ index += 1;
53
+ }
54
+ return {
55
+ command,
56
+ path,
57
+ remainingArgs: tokens.slice(index),
58
+ };
59
+ }
60
+ function printUnknownCommand(logger, command, path, token, locale) {
61
+ logger.error((0, help_copy_1.getHelpCopy)(locale).unknownCommandSegment(['vaone', ...path].join(' '), token));
62
+ logger.plain('');
63
+ logger.plain((0, help_1.renderHelp)(command, path, locale));
64
+ }
65
+ async function runCli(argv, env) {
66
+ const parsedArgv = parseArgv(argv);
67
+ const logger = new logger_1.Logger(parsedArgv.debug || env.VAONE_DEBUG === 'true');
68
+ const paths = (0, paths_1.createCliPaths)(env);
69
+ const runtimeConfig = await (0, runtime_config_1.loadRuntimeConfig)(env, paths);
70
+ logger.setDebugMode(parsedArgv.debug || runtimeConfig.debug);
71
+ const tokenStore = new token_store_1.TokenStore(paths.authStateFile);
72
+ const installationStore = new installation_store_1.InstallationStore(paths.installationStateFile);
73
+ const resolvedCommand = resolveCommand(parsedArgv.tokens);
74
+ const unknownToken = resolvedCommand.remainingArgs[0];
75
+ const canTraverseFurther = Boolean(resolvedCommand.command.children?.length && unknownToken);
76
+ const locale = runtimeConfig.locale;
77
+ const context = {
78
+ argv,
79
+ logger,
80
+ paths,
81
+ runtimeConfig,
82
+ tokenStore,
83
+ installationStore,
84
+ };
85
+ if (parsedArgv.help || !resolvedCommand.path.length) {
86
+ logger.plain((0, help_1.renderHelp)(resolvedCommand.command, resolvedCommand.path, locale));
87
+ return 0;
88
+ }
89
+ await (0, paths_1.ensureCliDirectories)(paths);
90
+ if (canTraverseFurther && !resolvedCommand.command.run) {
91
+ printUnknownCommand(logger, resolvedCommand.command, resolvedCommand.path, unknownToken, locale);
92
+ return 1;
93
+ }
94
+ if (!resolvedCommand.command.run) {
95
+ logger.plain((0, help_1.renderHelp)(resolvedCommand.command, resolvedCommand.path, locale));
96
+ return 0;
97
+ }
98
+ try {
99
+ return await resolvedCommand.command.run(context, resolvedCommand.remainingArgs);
100
+ }
101
+ catch (error) {
102
+ const message = error instanceof Error ? error.message : 'Unknown CLI failure.';
103
+ logger.error(message);
104
+ logger.debug('Unhandled CLI error payload:', error);
105
+ return 1;
106
+ }
107
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assetsCommand = void 0;
4
+ const list_command_1 = require("./list-command");
5
+ exports.assetsCommand = {
6
+ name: 'assets',
7
+ summary: 'Browse the filterable asset catalog.',
8
+ usage: 'vaone assets <list> [options]',
9
+ children: [list_command_1.assetsListCommand],
10
+ };
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assetsListCommand = void 0;
4
+ const authenticated_api_client_1 = require("../../features/auth/authenticated-api-client");
5
+ const assets_api_client_1 = require("../../features/assets/assets-api-client");
6
+ const assets_output_1 = require("../../features/assets/assets-output");
7
+ const argv_1 = require("../../shared/argv");
8
+ function parseEnabledFilter(parsed) {
9
+ const raw = parsed.options.enabled;
10
+ if (raw === undefined) {
11
+ return undefined;
12
+ }
13
+ if (typeof raw === 'boolean') {
14
+ return raw;
15
+ }
16
+ const normalized = raw.trim().toLowerCase();
17
+ if (normalized === 'true' || normalized === '1') {
18
+ return true;
19
+ }
20
+ if (normalized === 'false' || normalized === '0') {
21
+ return false;
22
+ }
23
+ throw new Error('Option "--enabled" must be true or false.');
24
+ }
25
+ exports.assetsListCommand = {
26
+ name: 'list',
27
+ summary: 'List asset catalog entries using supported API filters.',
28
+ usage: 'vaone assets list [--category <category>] [--enabled true|false]',
29
+ async run(context, args) {
30
+ const parsed = (0, argv_1.parseCommandArgs)(args);
31
+ if (parsed.positionals.length) {
32
+ throw new Error('`vaone assets list` does not accept positional arguments.');
33
+ }
34
+ try {
35
+ const assets = await (0, assets_api_client_1.listAssets)(context, {
36
+ category: (0, argv_1.getStringOption)(parsed, 'category'),
37
+ enabled: parseEnabledFilter(parsed),
38
+ });
39
+ (0, assets_output_1.renderAssetList)(context.logger, assets);
40
+ return 0;
41
+ }
42
+ catch (error) {
43
+ if (error instanceof authenticated_api_client_1.AuthRequiredError) {
44
+ throw new Error('Asset catalog access requires login. Run `vaone auth login` first.');
45
+ }
46
+ if (error instanceof authenticated_api_client_1.CliApiError && error.status === 401) {
47
+ throw new Error('Asset catalog access requires a valid CLI login session.');
48
+ }
49
+ throw error;
50
+ }
51
+ },
52
+ };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authCommand = void 0;
4
+ const login_command_1 = require("./login-command");
5
+ const logout_command_1 = require("./logout-command");
6
+ exports.authCommand = {
7
+ name: 'auth',
8
+ summary: 'Authentication placeholder commands for future browser login.',
9
+ usage: 'vaone auth <login|logout> [options]',
10
+ children: [login_command_1.loginCommand, logout_command_1.logoutCommand],
11
+ };
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loginCommand = void 0;
4
+ const output_1 = require("../../shared/output");
5
+ const auth_api_client_1 = require("../../features/auth/auth-api-client");
6
+ const browser_login_1 = require("../../features/auth/browser-login");
7
+ const installation_state_1 = require("../../features/auth/installation-state");
8
+ const AUTH_TIMEOUT_MS = 10 * 60 * 1000;
9
+ function printFallbackInstructions(logger, continuationUri, callbackMode) {
10
+ (0, output_1.printSection)(logger, 'Browser continuation');
11
+ (0, output_1.printKeyValue)(logger, 'URL', continuationUri);
12
+ const qr = (0, browser_login_1.renderQrCode)(continuationUri);
13
+ if (qr) {
14
+ logger.plain('');
15
+ logger.plain(qr);
16
+ }
17
+ logger.plain('');
18
+ (0, output_1.printList)(logger, [
19
+ callbackMode === 'loopback'
20
+ ? 'If the browser cannot return to localhost automatically, keep the terminal open and finish the same URL manually.'
21
+ : 'Finish login in any browser, then return to the terminal while the CLI keeps polling the same auth session.',
22
+ 'Do not enter credentials inside the terminal. The terminal only waits for server-side session completion.',
23
+ ]);
24
+ }
25
+ async function waitForCompletion(apiBaseUrl, sessionId, loopbackListener) {
26
+ const pollPromise = (0, browser_login_1.pollForCliRedeemableSession)(() => (0, auth_api_client_1.getAuthSession)(apiBaseUrl, sessionId), AUTH_TIMEOUT_MS).then((session) => session.sessionId);
27
+ if (!loopbackListener) {
28
+ return pollPromise;
29
+ }
30
+ const callbackPromise = loopbackListener
31
+ .waitForCallback(AUTH_TIMEOUT_MS)
32
+ .then((payload) => payload.sessionId);
33
+ return Promise.race([callbackPromise, pollPromise]);
34
+ }
35
+ exports.loginCommand = {
36
+ name: 'login',
37
+ summary: 'Start browser-based login with PKCE and installation binding.',
38
+ usage: 'vaone auth login',
39
+ async run(context) {
40
+ const installation = await (0, installation_state_1.ensureInstallationState)(context.installationStore);
41
+ const pkce = (0, browser_login_1.createPkcePair)();
42
+ const cliState = (0, browser_login_1.createCliState)();
43
+ const cliSessionId = (0, browser_login_1.createCliSessionId)();
44
+ let loopbackListener = null;
45
+ let callbackMode = 'manual';
46
+ let callbackPort;
47
+ if (context.runtimeConfig.authCallbackMode !== 'manual') {
48
+ try {
49
+ loopbackListener = await (0, browser_login_1.startLoopbackListener)(cliState, context.runtimeConfig.locale);
50
+ callbackMode = 'loopback';
51
+ callbackPort = loopbackListener.port;
52
+ }
53
+ catch (error) {
54
+ context.logger.debug('Loopback listener unavailable, falling back to manual mode.', error);
55
+ }
56
+ }
57
+ else {
58
+ context.logger.debug('Manual auth callback mode is enabled by runtime configuration.');
59
+ }
60
+ try {
61
+ const session = await (0, auth_api_client_1.startCliAuthSession)(context.runtimeConfig.apiBaseUrl, {
62
+ cliSessionId,
63
+ pkceCodeChallenge: pkce.codeChallenge,
64
+ pkceMethod: pkce.method,
65
+ callbackMode,
66
+ callbackPort,
67
+ cliState,
68
+ });
69
+ if (!session.continuationUri) {
70
+ throw new Error('Auth session did not return a browser continuation URL.');
71
+ }
72
+ (0, output_1.printSection)(context.logger, 'Login context');
73
+ (0, output_1.printList)(context.logger, [
74
+ `Account login is handled in the browser against ${context.runtimeConfig.apiBaseUrl}.`,
75
+ `Installation binding uses ${installation.installationId} on ${installation.clientPlatform}/${installation.clientArch}.`,
76
+ `Callback mode: ${callbackMode}.`,
77
+ ]);
78
+ const browserOpened = await (0, browser_login_1.openSystemBrowser)(session.continuationUri);
79
+ if (!browserOpened) {
80
+ context.logger.info('Automatic browser launch failed. Use the fallback URL or QR code below.');
81
+ printFallbackInstructions(context.logger, session.continuationUri, callbackMode);
82
+ }
83
+ else if (callbackMode === 'manual') {
84
+ context.logger.info('Browser opened. Keep the terminal open while the CLI polls the auth session.');
85
+ printFallbackInstructions(context.logger, session.continuationUri, callbackMode);
86
+ }
87
+ else {
88
+ context.logger.info('Browser opened. Finish sign-in there; the CLI is waiting for the localhost callback.');
89
+ }
90
+ const completedSessionId = await waitForCompletion(context.runtimeConfig.apiBaseUrl, session.sessionId, loopbackListener);
91
+ const tokenBundle = await (0, auth_api_client_1.redeemCliAuthSession)(context.runtimeConfig.apiBaseUrl, completedSessionId, {
92
+ codeVerifier: pkce.codeVerifier,
93
+ installationId: installation.installationId,
94
+ publicKey: installation.publicKey,
95
+ keyFingerprint: installation.keyFingerprint,
96
+ clientPlatform: installation.clientPlatform,
97
+ clientArch: installation.clientArch,
98
+ cliVersion: installation.cliVersion,
99
+ deviceName: installation.deviceName,
100
+ });
101
+ await context.tokenStore.save({
102
+ accountId: tokenBundle.accountId,
103
+ installationId: tokenBundle.installationId,
104
+ sessionId: completedSessionId,
105
+ accessToken: tokenBundle.accessToken,
106
+ refreshToken: tokenBundle.refreshToken,
107
+ expiresAt: tokenBundle.expiresAt,
108
+ tokenType: tokenBundle.tokenType,
109
+ refreshBoundAt: new Date().toISOString(),
110
+ });
111
+ (0, output_1.printSection)(context.logger, 'Login complete');
112
+ (0, output_1.printList)(context.logger, [
113
+ `Account: ${tokenBundle.accountId}`,
114
+ `Installation: ${tokenBundle.installationId}`,
115
+ `Access token expires at: ${tokenBundle.expiresAt}`,
116
+ ]);
117
+ return 0;
118
+ }
119
+ finally {
120
+ if (loopbackListener) {
121
+ await loopbackListener.close().catch((error) => {
122
+ context.logger.debug('Failed to close loopback listener cleanly.', error);
123
+ });
124
+ }
125
+ }
126
+ },
127
+ };
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logoutCommand = void 0;
4
+ const output_1 = require("../../shared/output");
5
+ const auth_api_client_1 = require("../../features/auth/auth-api-client");
6
+ exports.logoutCommand = {
7
+ name: 'logout',
8
+ summary: 'Attempt remote logout, then clear local auth state.',
9
+ usage: 'vaone auth logout',
10
+ async run(context) {
11
+ const tokens = await context.tokenStore.read();
12
+ let remoteLogoutAttempted = false;
13
+ let remoteLogoutSucceeded = false;
14
+ let remoteLogoutFailure;
15
+ if (tokens?.sessionId) {
16
+ remoteLogoutAttempted = true;
17
+ try {
18
+ await (0, auth_api_client_1.logoutCliSession)(context.runtimeConfig.apiBaseUrl, {
19
+ sessionId: tokens.sessionId,
20
+ });
21
+ remoteLogoutSucceeded = true;
22
+ }
23
+ catch (error) {
24
+ remoteLogoutFailure =
25
+ error instanceof Error ? error.message : 'Unknown remote logout failure.';
26
+ context.logger.debug('Remote logout request failed.', error);
27
+ }
28
+ }
29
+ await context.tokenStore.clear();
30
+ (0, output_1.printSection)(context.logger, 'Logout status');
31
+ if (!tokens) {
32
+ (0, output_1.printList)(context.logger, [
33
+ 'No active local auth state remained on this machine.',
34
+ ]);
35
+ return 0;
36
+ }
37
+ if (remoteLogoutSucceeded) {
38
+ (0, output_1.printList)(context.logger, [
39
+ 'Remote session logout completed successfully.',
40
+ 'Local auth state cleared.',
41
+ ]);
42
+ return 0;
43
+ }
44
+ if (remoteLogoutAttempted) {
45
+ (0, output_1.printList)(context.logger, [
46
+ 'Local auth state cleared.',
47
+ `Remote session logout could not be confirmed: ${remoteLogoutFailure}`,
48
+ ]);
49
+ return 0;
50
+ }
51
+ (0, output_1.printList)(context.logger, [
52
+ 'Local auth state cleared.',
53
+ 'Remote session logout was skipped because no stored session id was available.',
54
+ ]);
55
+ return 0;
56
+ },
57
+ };