@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
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.doctorCommand = void 0;
4
+ const output_1 = require("../../shared/output");
5
+ exports.doctorCommand = {
6
+ name: 'doctor',
7
+ summary: 'Inspect config, paths, and local bootstrap state.',
8
+ usage: 'vaone doctor',
9
+ async run(context) {
10
+ const tokens = await context.tokenStore.read();
11
+ const installation = await context.installationStore.read();
12
+ (0, output_1.printSection)(context.logger, 'Runtime configuration');
13
+ (0, output_1.printKeyValue)(context.logger, 'Environment', context.runtimeConfig.appEnv);
14
+ (0, output_1.printKeyValue)(context.logger, 'API base URL', context.runtimeConfig.apiBaseUrl);
15
+ (0, output_1.printKeyValue)(context.logger, 'Debug', String(context.runtimeConfig.debug));
16
+ (0, output_1.printSection)(context.logger, 'Local paths');
17
+ (0, output_1.printKeyValue)(context.logger, 'Root', context.paths.rootDir);
18
+ (0, output_1.printKeyValue)(context.logger, 'Config', context.paths.configFile);
19
+ (0, output_1.printKeyValue)(context.logger, 'State directory', context.paths.stateDir);
20
+ (0, output_1.printKeyValue)(context.logger, 'Auth state', context.paths.authStateFile);
21
+ (0, output_1.printKeyValue)(context.logger, 'Installation state', context.paths.installationStateFile);
22
+ (0, output_1.printSection)(context.logger, 'Stored bootstrap state');
23
+ (0, output_1.printList)(context.logger, [
24
+ tokens
25
+ ? 'Auth token state file exists.'
26
+ : 'Auth token state file is empty.',
27
+ installation
28
+ ? 'Installation state file exists.'
29
+ : 'Installation state file is empty.',
30
+ ]);
31
+ return 0;
32
+ },
33
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initCommand = void 0;
4
+ const doctor_command_1 = require("../doctor/doctor-command");
5
+ const usage_command_1 = require("../usage/usage-command");
6
+ const login_command_1 = require("../auth/login-command");
7
+ const init_flow_1 = require("../../features/init/init-flow");
8
+ exports.initCommand = {
9
+ name: 'init',
10
+ summary: 'Install VisionAlpha operator skills and optionally chain browser login.',
11
+ usage: 'vaone init [.] [--agents codex,claude,openclaw] [--scope project|user] [--lang en|zh-CN] [--skip-login] [--force] [--yes]',
12
+ async run(context, args) {
13
+ return (0, init_flow_1.runInitFlow)(context, args, {
14
+ login: async (logger) => login_command_1.loginCommand.run?.({ ...context, logger }, []) ?? 0,
15
+ verifyDoctor: async (logger) => doctor_command_1.doctorCommand.run?.({ ...context, logger }, []) ?? 0,
16
+ verifyUsage: async (logger) => usage_command_1.usageCommand.run?.({ ...context, logger }, []) ?? 0,
17
+ });
18
+ },
19
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.intelligenceCommand = void 0;
4
+ const query_command_1 = require("./query-command");
5
+ exports.intelligenceCommand = {
6
+ name: 'intelligence',
7
+ summary: 'Query paid asset intelligence data.',
8
+ usage: 'vaone intelligence <query> [options]',
9
+ children: [query_command_1.intelligenceQueryCommand],
10
+ };
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.intelligenceQueryCommand = void 0;
4
+ const authenticated_api_client_1 = require("../../features/auth/authenticated-api-client");
5
+ const intelligence_query_state_1 = require("../../features/intelligence/intelligence-query-state");
6
+ const intelligence_api_client_1 = require("../../features/intelligence/intelligence-api-client");
7
+ const intelligence_output_1 = require("../../features/intelligence/intelligence-output");
8
+ const argv_1 = require("../../shared/argv");
9
+ function ensureManualCursorCompatible(useNext, cursor) {
10
+ if (useNext && cursor) {
11
+ throw new Error('Use either `--next` or `--cursor`, not both.');
12
+ }
13
+ }
14
+ function buildFreshQuery(args) {
15
+ return {
16
+ assetId: (0, argv_1.getStringOption)(args, 'asset-id'),
17
+ category: (0, argv_1.getStringOption)(args, 'category'),
18
+ issuer: (0, argv_1.getStringOption)(args, 'issuer'),
19
+ publishDateFrom: (0, argv_1.getStringOption)(args, 'publish-date-from'),
20
+ publishDateTo: (0, argv_1.getStringOption)(args, 'publish-date-to'),
21
+ minRelevanceScore: (0, argv_1.getNumberOption)(args, 'min-relevance-score'),
22
+ cursor: (0, argv_1.getStringOption)(args, 'cursor'),
23
+ limit: (0, argv_1.getNumberOption)(args, 'limit'),
24
+ };
25
+ }
26
+ function hasFreshQueryFilters(query) {
27
+ return Boolean(query.assetId ||
28
+ query.category ||
29
+ query.issuer ||
30
+ query.publishDateFrom ||
31
+ query.publishDateTo ||
32
+ query.minRelevanceScore !== undefined ||
33
+ query.limit !== undefined);
34
+ }
35
+ function hydrateNextQuery(saved) {
36
+ return {
37
+ ...saved.query,
38
+ cursor: saved.nextCursor,
39
+ limit: saved.limit,
40
+ };
41
+ }
42
+ exports.intelligenceQueryCommand = {
43
+ name: 'query',
44
+ summary: 'Query paid asset intelligence data with stable filters.',
45
+ usage: 'vaone intelligence query [--asset-id <id>] [--category <category>] [--issuer <issuer>] [--publish-date-from <yyyy-mm-dd>] [--publish-date-to <yyyy-mm-dd>] [--min-relevance-score <n>] [--limit <n>] [--next]',
46
+ async run(context, args) {
47
+ const parsed = (0, argv_1.parseCommandArgs)(args);
48
+ if (parsed.positionals.length) {
49
+ throw new Error('`vaone intelligence query` does not accept positional arguments.');
50
+ }
51
+ const useNext = (0, argv_1.getBooleanOption)(parsed, 'next');
52
+ const manualCursor = (0, argv_1.getStringOption)(parsed, 'cursor');
53
+ ensureManualCursorCompatible(useNext, manualCursor);
54
+ const stateStore = new intelligence_query_state_1.IntelligenceQueryStateStore(context.paths.intelligenceQueryStateFile);
55
+ const freshQuery = buildFreshQuery(parsed);
56
+ let query;
57
+ if (useNext) {
58
+ if (hasFreshQueryFilters(freshQuery)) {
59
+ throw new Error('`--next` reuses the previous query. Do not combine it with new filters or limits.');
60
+ }
61
+ const saved = await stateStore.read();
62
+ if (!saved) {
63
+ throw new Error('No saved intelligence query continuation exists. Run `vaone intelligence query` without `--next` first.');
64
+ }
65
+ query = hydrateNextQuery(saved);
66
+ }
67
+ else {
68
+ query = freshQuery;
69
+ }
70
+ try {
71
+ const page = await (0, intelligence_api_client_1.queryAssetIntelligence)(context, query);
72
+ if (page.nextCursor) {
73
+ await stateStore.save({
74
+ query: {
75
+ assetId: query.assetId,
76
+ category: query.category,
77
+ issuer: query.issuer,
78
+ publishDateFrom: query.publishDateFrom,
79
+ publishDateTo: query.publishDateTo,
80
+ minRelevanceScore: query.minRelevanceScore,
81
+ },
82
+ limit: page.limit,
83
+ nextCursor: page.nextCursor,
84
+ savedAt: new Date().toISOString(),
85
+ });
86
+ }
87
+ else {
88
+ await stateStore.clear();
89
+ }
90
+ (0, intelligence_output_1.renderIntelligencePage)(context.logger, page);
91
+ return 0;
92
+ }
93
+ catch (error) {
94
+ if (error instanceof authenticated_api_client_1.CliApiError &&
95
+ error.code === 'ASSET_INTELLIGENCE_ACCESS_DENIED') {
96
+ throw new Error('This account does not currently have paid asset-intelligence access.');
97
+ }
98
+ if (error instanceof authenticated_api_client_1.CliApiError &&
99
+ error.code === 'ASSET_INTELLIGENCE_QUOTA_EXCEEDED') {
100
+ throw new Error('The current intelligence query would exceed the daily paid usage quota.');
101
+ }
102
+ if (error instanceof authenticated_api_client_1.CliApiError && error.code === 'INVALID_CURSOR') {
103
+ await stateStore.clear();
104
+ throw new Error('The saved or provided query cursor is no longer valid. Restart the query without `--next`.');
105
+ }
106
+ throw error;
107
+ }
108
+ },
109
+ };
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportsGetCommand = void 0;
4
+ const authenticated_api_client_1 = require("../../features/auth/authenticated-api-client");
5
+ const daily_reports_api_client_1 = require("../../features/daily-reports/daily-reports-api-client");
6
+ const daily_reports_output_1 = require("../../features/daily-reports/daily-reports-output");
7
+ const usage_api_client_1 = require("../../features/usage/usage-api-client");
8
+ const argv_1 = require("../../shared/argv");
9
+ function resolveAssetId(positionals, optionAssetId) {
10
+ if (positionals.length > 1) {
11
+ throw new Error('Only one asset id may be provided to `vaone reports get`.');
12
+ }
13
+ return optionAssetId ?? positionals[0] ?? '';
14
+ }
15
+ function getSelectionMessage(input) {
16
+ if (input.previousSelectedReportKey === input.requestedReportKey) {
17
+ return 'This report is already the locked daily selection for the current product day.';
18
+ }
19
+ if (!input.previousSelectedReportKey && input.detailSelectedToday) {
20
+ return 'This read locked the account daily-report selection for the current product day.';
21
+ }
22
+ if (input.detailSelectedToday) {
23
+ return 'This report is currently the selected daily report for the current product day.';
24
+ }
25
+ return 'Selection state is available but no new lock was created.';
26
+ }
27
+ exports.reportsGetCommand = {
28
+ name: 'get',
29
+ summary: 'Get a normalized daily report by asset id and optional date.',
30
+ usage: 'vaone reports get <asset-id> [--date <yyyy-mm-dd>] [--asset-id <id>]',
31
+ async run(context, args) {
32
+ const parsed = (0, argv_1.parseCommandArgs)(args);
33
+ const assetId = resolveAssetId(parsed.positionals, (0, argv_1.getStringOption)(parsed, 'asset-id'));
34
+ const date = (0, argv_1.getStringOption)(parsed, 'date');
35
+ if (!assetId) {
36
+ throw new Error('`vaone reports get` requires an asset id either as a positional argument or `--asset-id`.');
37
+ }
38
+ const usageSummary = await (0, usage_api_client_1.getUsageSummary)(context);
39
+ try {
40
+ const report = await (0, daily_reports_api_client_1.getDailyReportDetail)(context, assetId, { date });
41
+ const requestedReportKey = `${report.assetId}:${report.date}`;
42
+ (0, daily_reports_output_1.renderDailyReportDetail)(context.logger, report, getSelectionMessage({
43
+ previousSelectedReportKey: usageSummary.dailyReport.selectedReportKey,
44
+ requestedReportKey,
45
+ detailSelectedToday: report.selectedToday,
46
+ }));
47
+ return 0;
48
+ }
49
+ catch (error) {
50
+ if (error instanceof authenticated_api_client_1.CliApiError &&
51
+ error.code === 'DAILY_REPORT_SELECTION_LIMIT_REACHED') {
52
+ const selectedReportKey = usageSummary.dailyReport.selectedReportKey;
53
+ throw new Error(selectedReportKey
54
+ ? `Daily report selection is already locked for ${selectedReportKey}. Free accounts can only keep one report selection per product day.`
55
+ : 'Daily report selection is already locked for the current product day.');
56
+ }
57
+ if (error instanceof authenticated_api_client_1.CliApiError &&
58
+ error.code === 'DAILY_REPORT_NOT_FOUND') {
59
+ throw new Error(date
60
+ ? `No daily report was found for asset ${assetId} on ${date}.`
61
+ : `No daily report was found for asset ${assetId}.`);
62
+ }
63
+ if (error instanceof authenticated_api_client_1.CliApiError && error.code === 'ASSET_NOT_FOUND') {
64
+ throw new Error(`Asset ${assetId} was not found.`);
65
+ }
66
+ throw error;
67
+ }
68
+ },
69
+ };
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportsListCommand = void 0;
4
+ const argv_1 = require("../../shared/argv");
5
+ const daily_reports_api_client_1 = require("../../features/daily-reports/daily-reports-api-client");
6
+ const daily_reports_output_1 = require("../../features/daily-reports/daily-reports-output");
7
+ const usage_api_client_1 = require("../../features/usage/usage-api-client");
8
+ exports.reportsListCommand = {
9
+ name: 'list',
10
+ summary: 'List normalized daily reports with current selection state.',
11
+ usage: 'vaone reports list [--asset-id <id>] [--category <category>] [--date-from <yyyy-mm-dd>] [--date-to <yyyy-mm-dd>] [--limit <n>]',
12
+ async run(context, args) {
13
+ const parsed = (0, argv_1.parseCommandArgs)(args);
14
+ if (parsed.positionals.length) {
15
+ throw new Error('`vaone reports list` does not accept positional arguments.');
16
+ }
17
+ const [usageSummary, reports] = await Promise.all([
18
+ (0, usage_api_client_1.getUsageSummary)(context),
19
+ (0, daily_reports_api_client_1.listDailyReports)(context, {
20
+ assetId: (0, argv_1.getStringOption)(parsed, 'asset-id'),
21
+ category: (0, argv_1.getStringOption)(parsed, 'category'),
22
+ dateFrom: (0, argv_1.getStringOption)(parsed, 'date-from'),
23
+ dateTo: (0, argv_1.getStringOption)(parsed, 'date-to'),
24
+ limit: (0, argv_1.getNumberOption)(parsed, 'limit'),
25
+ }),
26
+ ]);
27
+ (0, daily_reports_output_1.renderDailyReportList)(context.logger, reports, {
28
+ statusLabel: usageSummary.dailyReport.statusLabel,
29
+ selectedReportKey: usageSummary.dailyReport.selectedReportKey,
30
+ });
31
+ return 0;
32
+ },
33
+ };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reportsCommand = void 0;
4
+ const get_command_1 = require("./get-command");
5
+ const list_command_1 = require("./list-command");
6
+ exports.reportsCommand = {
7
+ name: 'reports',
8
+ summary: 'Browse and retrieve daily reports.',
9
+ usage: 'vaone reports <list|get> [options]',
10
+ children: [list_command_1.reportsListCommand, get_command_1.reportsGetCommand],
11
+ };
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usageCommand = void 0;
4
+ const authenticated_api_client_1 = require("../../features/auth/authenticated-api-client");
5
+ const usage_api_client_1 = require("../../features/usage/usage-api-client");
6
+ const usage_output_1 = require("../../features/usage/usage-output");
7
+ exports.usageCommand = {
8
+ name: 'usage',
9
+ summary: 'Show plan, quota, and commercial summary.',
10
+ usage: 'vaone usage',
11
+ async run(context) {
12
+ try {
13
+ const summary = await (0, usage_api_client_1.getUsageSummary)(context);
14
+ (0, usage_output_1.renderUsageSummary)(context.logger, summary);
15
+ return 0;
16
+ }
17
+ catch (error) {
18
+ if (error instanceof authenticated_api_client_1.AuthRequiredError) {
19
+ throw new Error('Usage summary requires login. Run `vaone auth login` first.');
20
+ }
21
+ if (error instanceof authenticated_api_client_1.CliApiError && error.status === 401) {
22
+ throw new Error('Usage summary requires a valid CLI login session.');
23
+ }
24
+ throw error;
25
+ }
26
+ },
27
+ };
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_LOCALE = exports.SUPPORTED_LOCALES = void 0;
4
+ exports.isSupportedLocale = isSupportedLocale;
5
+ exports.parseSupportedLocale = parseSupportedLocale;
6
+ exports.getLocaleDisplayName = getLocaleDisplayName;
7
+ exports.SUPPORTED_LOCALES = ['en', 'zh-CN'];
8
+ exports.DEFAULT_LOCALE = 'en';
9
+ function isSupportedLocale(value) {
10
+ return exports.SUPPORTED_LOCALES.includes(value);
11
+ }
12
+ function parseSupportedLocale(value, sourceLabel = 'locale') {
13
+ if (value === undefined || value === null) {
14
+ return undefined;
15
+ }
16
+ if (typeof value !== 'string') {
17
+ throw new Error(`Invalid ${sourceLabel} value. Expected a string.`);
18
+ }
19
+ const trimmed = value.trim();
20
+ if (!trimmed) {
21
+ return undefined;
22
+ }
23
+ if (isSupportedLocale(trimmed)) {
24
+ return trimmed;
25
+ }
26
+ throw new Error(`Unsupported ${sourceLabel} "${trimmed}". Supported values: ${exports.SUPPORTED_LOCALES.join(', ')}.`);
27
+ }
28
+ function getLocaleDisplayName(locale) {
29
+ return locale === 'zh-CN' ? '简体中文' : 'English';
30
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadRuntimeConfig = loadRuntimeConfig;
4
+ exports.saveRuntimeConfig = saveRuntimeConfig;
5
+ const node_os_1 = require("node:os");
6
+ const promises_1 = require("node:fs/promises");
7
+ const node_path_1 = require("node:path");
8
+ const locale_1 = require("./locale");
9
+ function parseBoolean(value) {
10
+ if (value === undefined) {
11
+ return undefined;
12
+ }
13
+ return value === '1' || value.toLowerCase() === 'true';
14
+ }
15
+ function normalizeHomePath(value) {
16
+ if (value === '~') {
17
+ return (0, node_os_1.homedir)();
18
+ }
19
+ if (value.startsWith('~/')) {
20
+ return value.replace('~', (0, node_os_1.homedir)());
21
+ }
22
+ return value;
23
+ }
24
+ async function readConfigFile(paths) {
25
+ try {
26
+ const raw = await (0, promises_1.readFile)(paths.configFile, 'utf8');
27
+ return JSON.parse(raw);
28
+ }
29
+ catch (error) {
30
+ if (error.code === 'ENOENT') {
31
+ return {};
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+ async function loadRuntimeConfig(env, paths) {
37
+ const fileConfig = await readConfigFile(paths);
38
+ const authCallbackMode = env.VAONE_AUTH_CALLBACK_MODE ?? fileConfig.authCallbackMode ?? 'auto';
39
+ if (authCallbackMode !== 'auto' && authCallbackMode !== 'manual') {
40
+ throw new Error(`Invalid VAONE_AUTH_CALLBACK_MODE value: ${authCallbackMode}`);
41
+ }
42
+ const envLocale = (0, locale_1.parseSupportedLocale)(env.VAONE_LANG, 'VAONE_LANG');
43
+ const fileLocale = (0, locale_1.parseSupportedLocale)(fileConfig.locale, 'config locale');
44
+ const locale = envLocale ?? fileLocale ?? locale_1.DEFAULT_LOCALE;
45
+ return {
46
+ appEnv: env.VAONE_ENV ?? fileConfig.appEnv ?? 'local',
47
+ apiBaseUrl: normalizeHomePath(env.VAONE_API_BASE_URL ??
48
+ fileConfig.apiBaseUrl ??
49
+ 'http://localhost:3000').replace(/\/$/, ''),
50
+ debug: parseBoolean(env.VAONE_DEBUG) ?? fileConfig.debug ?? false,
51
+ authCallbackMode,
52
+ locale,
53
+ localeConfigured: envLocale !== undefined || fileLocale !== undefined,
54
+ };
55
+ }
56
+ async function saveRuntimeConfig(paths, patch) {
57
+ const current = await readConfigFile(paths);
58
+ const next = {
59
+ ...current,
60
+ ...patch,
61
+ };
62
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(paths.configFile), { recursive: true });
63
+ await (0, promises_1.writeFile)(paths.configFile, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
64
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listAssets = listAssets;
4
+ const authenticated_api_client_1 = require("../auth/authenticated-api-client");
5
+ function createQueryString(query) {
6
+ const searchParams = new URLSearchParams();
7
+ for (const [key, value] of Object.entries(query)) {
8
+ if (value === undefined) {
9
+ continue;
10
+ }
11
+ searchParams.set(key, String(value));
12
+ }
13
+ const encoded = searchParams.toString();
14
+ return encoded ? `?${encoded}` : '';
15
+ }
16
+ async function listAssets(context, query) {
17
+ return (0, authenticated_api_client_1.requestAuthenticatedJson)(context, `/v1/asset-intelligence/assets${createQueryString(query)}`);
18
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderAssetList = renderAssetList;
4
+ const output_1 = require("../../shared/output");
5
+ function formatAssetName(asset) {
6
+ return asset.nameZh || asset.nameEn || asset.assetId;
7
+ }
8
+ function formatEnabledState(enabled) {
9
+ return enabled ? 'enabled' : 'disabled';
10
+ }
11
+ function renderAssetList(logger, assets) {
12
+ (0, output_1.printSection)(logger, 'Asset catalog');
13
+ if (!assets.length) {
14
+ logger.info('No assets matched the current filters.');
15
+ return;
16
+ }
17
+ assets.forEach((asset, index) => {
18
+ if (index > 0) {
19
+ logger.plain('');
20
+ }
21
+ (0, output_1.printKeyValue)(logger, 'Asset', `${formatAssetName(asset)} (${asset.assetId})`);
22
+ (0, output_1.printKeyValue)(logger, 'Category', asset.category || 'unknown');
23
+ (0, output_1.printKeyValue)(logger, 'Status', formatEnabledState(asset.enabled));
24
+ if (asset.lastUpdated) {
25
+ (0, output_1.printKeyValue)(logger, 'Last updated', asset.lastUpdated);
26
+ }
27
+ });
28
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startCliAuthSession = startCliAuthSession;
4
+ exports.getAuthSession = getAuthSession;
5
+ exports.redeemCliAuthSession = redeemCliAuthSession;
6
+ exports.refreshCliToken = refreshCliToken;
7
+ exports.logoutCliSession = logoutCliSession;
8
+ async function requestJson(apiBaseUrl, path, init) {
9
+ const response = await fetch(`${apiBaseUrl}${path}`, {
10
+ ...init,
11
+ headers: {
12
+ 'content-type': 'application/json',
13
+ ...(init?.headers ?? {}),
14
+ },
15
+ });
16
+ if (!response.ok) {
17
+ let message = `${response.status} ${response.statusText}`;
18
+ try {
19
+ const payload = (await response.json());
20
+ if (payload.message) {
21
+ message = payload.message;
22
+ }
23
+ }
24
+ catch {
25
+ // Ignore invalid JSON and keep the HTTP status line.
26
+ }
27
+ throw new Error(message);
28
+ }
29
+ return (await response.json());
30
+ }
31
+ async function startCliAuthSession(apiBaseUrl, payload) {
32
+ return requestJson(apiBaseUrl, '/v1/auth/sessions/start', {
33
+ method: 'POST',
34
+ body: JSON.stringify({
35
+ flowSource: 'cli',
36
+ ...payload,
37
+ }),
38
+ });
39
+ }
40
+ async function getAuthSession(apiBaseUrl, sessionId) {
41
+ return requestJson(apiBaseUrl, `/v1/auth/sessions/${encodeURIComponent(sessionId)}`);
42
+ }
43
+ async function redeemCliAuthSession(apiBaseUrl, sessionId, payload) {
44
+ return requestJson(apiBaseUrl, `/v1/auth/sessions/${encodeURIComponent(sessionId)}/redeem`, {
45
+ method: 'POST',
46
+ body: JSON.stringify(payload),
47
+ });
48
+ }
49
+ async function refreshCliToken(apiBaseUrl, payload) {
50
+ return requestJson(apiBaseUrl, '/v1/auth/tokens/refresh', {
51
+ method: 'POST',
52
+ body: JSON.stringify(payload),
53
+ });
54
+ }
55
+ async function logoutCliSession(apiBaseUrl, payload) {
56
+ return requestJson(apiBaseUrl, '/v1/auth/logout', {
57
+ method: 'POST',
58
+ body: JSON.stringify(payload),
59
+ });
60
+ }
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CliApiError = exports.AuthRequiredError = void 0;
4
+ exports.requestAuthenticatedJson = requestAuthenticatedJson;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const installation_state_1 = require("./installation-state");
7
+ const auth_api_client_1 = require("./auth-api-client");
8
+ class AuthRequiredError extends Error {
9
+ }
10
+ exports.AuthRequiredError = AuthRequiredError;
11
+ class CliApiError extends Error {
12
+ status;
13
+ code;
14
+ details;
15
+ constructor(message, status, code, details) {
16
+ super(message);
17
+ this.status = status;
18
+ this.code = code;
19
+ this.details = details;
20
+ }
21
+ }
22
+ exports.CliApiError = CliApiError;
23
+ const ACCESS_TOKEN_REFRESH_SKEW_MS = 30 * 1000;
24
+ function toRefreshRecoveryError(error) {
25
+ if (!(error instanceof Error)) {
26
+ return new AuthRequiredError('The local session could not be refreshed. Run `vaone auth login` again.');
27
+ }
28
+ const message = error.message.toLowerCase();
29
+ if (message.includes('revoked')) {
30
+ return new AuthRequiredError('This installation has been revoked. Run `vaone auth login` on an allowed device.');
31
+ }
32
+ if (message.includes('not bound to this installation') ||
33
+ message.includes('proof verification failed') ||
34
+ message.includes('proof signature is malformed') ||
35
+ message.includes('proof timestamp is invalid') ||
36
+ message.includes('proof is stale')) {
37
+ return new AuthRequiredError('The local installation proof is no longer valid. Run `vaone auth login` again.');
38
+ }
39
+ return error;
40
+ }
41
+ function isAccessTokenExpired(tokens) {
42
+ if (!tokens.expiresAt) {
43
+ return false;
44
+ }
45
+ return (new Date(tokens.expiresAt).getTime() <=
46
+ Date.now() + ACCESS_TOKEN_REFRESH_SKEW_MS);
47
+ }
48
+ async function readTokensOrThrow(tokenStore) {
49
+ const tokens = await tokenStore.read();
50
+ if (!tokens?.accessToken || !tokens.installationId) {
51
+ throw new AuthRequiredError('Authentication is required. Run `vaone auth login` first.');
52
+ }
53
+ if (!tokens.refreshToken) {
54
+ return {
55
+ ...tokens,
56
+ refreshToken: '',
57
+ accessToken: tokens.accessToken,
58
+ installationId: tokens.installationId,
59
+ };
60
+ }
61
+ return {
62
+ ...tokens,
63
+ accessToken: tokens.accessToken,
64
+ refreshToken: tokens.refreshToken,
65
+ installationId: tokens.installationId,
66
+ };
67
+ }
68
+ async function refreshAccessToken(context, tokens) {
69
+ if (!tokens.refreshToken) {
70
+ throw new AuthRequiredError('The local access token has expired and no refresh token is available. Run `vaone auth login` again.');
71
+ }
72
+ const installation = await (0, installation_state_1.ensureInstallationState)(context.installationStore);
73
+ const signedAt = new Date().toISOString();
74
+ const payload = Buffer.from(`${tokens.refreshToken}.${tokens.installationId}.${signedAt}`, 'utf8');
75
+ const signature = (0, node_crypto_1.sign)(null, payload, (0, node_crypto_1.createPrivateKey)(installation.privateKey)).toString('base64url');
76
+ let refreshed;
77
+ try {
78
+ refreshed = await (0, auth_api_client_1.refreshCliToken)(context.runtimeConfig.apiBaseUrl, {
79
+ refreshToken: tokens.refreshToken,
80
+ installationId: tokens.installationId,
81
+ signedAt,
82
+ signature,
83
+ });
84
+ }
85
+ catch (error) {
86
+ throw toRefreshRecoveryError(error);
87
+ }
88
+ await context.tokenStore.save({
89
+ ...tokens,
90
+ accountId: refreshed.accountId,
91
+ installationId: refreshed.installationId,
92
+ accessToken: refreshed.accessToken,
93
+ refreshToken: refreshed.refreshToken,
94
+ expiresAt: refreshed.expiresAt,
95
+ tokenType: refreshed.tokenType,
96
+ refreshBoundAt: new Date().toISOString(),
97
+ });
98
+ context.logger.debug('Refreshed CLI access token.');
99
+ return {
100
+ accessToken: refreshed.accessToken,
101
+ };
102
+ }
103
+ async function resolveSession(context) {
104
+ const tokens = await readTokensOrThrow(context.tokenStore);
105
+ if (!isAccessTokenExpired(tokens)) {
106
+ return {
107
+ accessToken: tokens.accessToken,
108
+ };
109
+ }
110
+ return refreshAccessToken(context, tokens);
111
+ }
112
+ async function parseApiError(response) {
113
+ let payload;
114
+ try {
115
+ payload = (await response.json());
116
+ }
117
+ catch {
118
+ payload = undefined;
119
+ }
120
+ return new CliApiError(payload?.message ?? `${response.status} ${response.statusText}`, response.status, payload?.code, payload?.details);
121
+ }
122
+ async function requestAuthenticatedJson(context, path, init) {
123
+ const send = async (accessToken) => fetch(`${context.runtimeConfig.apiBaseUrl}${path}`, {
124
+ ...init,
125
+ headers: {
126
+ ...(init?.body ? { 'content-type': 'application/json' } : {}),
127
+ authorization: `Bearer ${accessToken}`,
128
+ ...(init?.headers ?? {}),
129
+ },
130
+ });
131
+ const session = await resolveSession(context);
132
+ let response = await send(session.accessToken);
133
+ if (response.status === 401) {
134
+ const tokens = await readTokensOrThrow(context.tokenStore);
135
+ const refreshed = await refreshAccessToken(context, tokens);
136
+ response = await send(refreshed.accessToken);
137
+ }
138
+ if (!response.ok) {
139
+ throw await parseApiError(response);
140
+ }
141
+ return (await response.json());
142
+ }