@hithink-tech/hithink-finance-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +161 -0
  4. package/SECURITY.md +3 -0
  5. package/dist/application/config.d.ts +44 -0
  6. package/dist/application/config.js +111 -0
  7. package/dist/application/ports/auth-provider.d.ts +23 -0
  8. package/dist/application/ports/auth-provider.js +2 -0
  9. package/dist/application/source-policy.d.ts +13 -0
  10. package/dist/application/source-policy.js +31 -0
  11. package/dist/application/use-cases/data-clean.d.ts +2 -0
  12. package/dist/application/use-cases/data-clean.js +18 -0
  13. package/dist/application/use-cases/data-init.d.ts +4 -0
  14. package/dist/application/use-cases/data-init.js +9 -0
  15. package/dist/application/use-cases/data-remove.d.ts +2 -0
  16. package/dist/application/use-cases/data-remove.js +27 -0
  17. package/dist/application/use-cases/data-repair.d.ts +5 -0
  18. package/dist/application/use-cases/data-repair.js +5 -0
  19. package/dist/application/use-cases/data-sync.d.ts +25 -0
  20. package/dist/application/use-cases/data-sync.js +55 -0
  21. package/dist/application/use-cases/local-query.d.ts +4 -0
  22. package/dist/application/use-cases/local-query.js +88 -0
  23. package/dist/application/use-cases/market-history.d.ts +8 -0
  24. package/dist/application/use-cases/market-history.js +16 -0
  25. package/dist/application/use-cases/market-panel.d.ts +3 -0
  26. package/dist/application/use-cases/market-panel.js +5 -0
  27. package/dist/application/use-cases/remote-query.d.ts +4 -0
  28. package/dist/application/use-cases/remote-query.js +66 -0
  29. package/dist/cli/context.d.ts +17 -0
  30. package/dist/cli/context.js +52 -0
  31. package/dist/cli/i18n.d.ts +23 -0
  32. package/dist/cli/i18n.js +150 -0
  33. package/dist/cli/main.d.ts +3 -0
  34. package/dist/cli/main.js +80 -0
  35. package/dist/cli/program.d.ts +20 -0
  36. package/dist/cli/program.js +111 -0
  37. package/dist/commands/auth/index.d.ts +10 -0
  38. package/dist/commands/auth/index.js +131 -0
  39. package/dist/commands/config/index.d.ts +13 -0
  40. package/dist/commands/config/index.js +26 -0
  41. package/dist/commands/data/index.d.ts +10 -0
  42. package/dist/commands/data/index.js +185 -0
  43. package/dist/commands/db/index.d.ts +4 -0
  44. package/dist/commands/db/index.js +72 -0
  45. package/dist/commands/financials/index.d.ts +5 -0
  46. package/dist/commands/financials/index.js +6 -0
  47. package/dist/commands/index/index.d.ts +5 -0
  48. package/dist/commands/index/index.js +6 -0
  49. package/dist/commands/market/index.d.ts +5 -0
  50. package/dist/commands/market/index.js +6 -0
  51. package/dist/commands/market/local.d.ts +4 -0
  52. package/dist/commands/market/local.js +97 -0
  53. package/dist/commands/remote.d.ts +11 -0
  54. package/dist/commands/remote.js +195 -0
  55. package/dist/commands/skills/index.d.ts +4 -0
  56. package/dist/commands/skills/index.js +43 -0
  57. package/dist/commands/special/index.d.ts +5 -0
  58. package/dist/commands/special/index.js +6 -0
  59. package/dist/commands/symbol/index.d.ts +5 -0
  60. package/dist/commands/symbol/index.js +6 -0
  61. package/dist/commands/system/capabilities.d.ts +4 -0
  62. package/dist/commands/system/capabilities.js +24 -0
  63. package/dist/commands/system/doctor.d.ts +4 -0
  64. package/dist/commands/system/doctor.js +15 -0
  65. package/dist/commands/system/schema.d.ts +4 -0
  66. package/dist/commands/system/schema.js +47 -0
  67. package/dist/commands/system/uninstall.d.ts +11 -0
  68. package/dist/commands/system/uninstall.js +68 -0
  69. package/dist/commands/system/update.d.ts +11 -0
  70. package/dist/commands/system/update.js +72 -0
  71. package/dist/contracts/envelope.d.ts +37 -0
  72. package/dist/contracts/envelope.js +36 -0
  73. package/dist/contracts/errors.d.ts +23 -0
  74. package/dist/contracts/errors.js +33 -0
  75. package/dist/contracts/local-capabilities.d.ts +13 -0
  76. package/dist/contracts/local-capabilities.js +37 -0
  77. package/dist/contracts/registry.d.ts +9 -0
  78. package/dist/contracts/registry.js +4 -0
  79. package/dist/contracts/remote-capabilities.d.ts +24 -0
  80. package/dist/contracts/remote-capabilities.js +667 -0
  81. package/dist/domains/financials/types.d.ts +6 -0
  82. package/dist/domains/financials/types.js +2 -0
  83. package/dist/domains/index/types.d.ts +6 -0
  84. package/dist/domains/index/types.js +2 -0
  85. package/dist/domains/market/types.d.ts +6 -0
  86. package/dist/domains/market/types.js +2 -0
  87. package/dist/domains/special/types.d.ts +5 -0
  88. package/dist/domains/special/types.js +2 -0
  89. package/dist/domains/symbol/types.d.ts +6 -0
  90. package/dist/domains/symbol/types.js +2 -0
  91. package/dist/infrastructure/credentials/api-key-provider.d.ts +14 -0
  92. package/dist/infrastructure/credentials/api-key-provider.js +102 -0
  93. package/dist/infrastructure/credentials/keyring.d.ts +15 -0
  94. package/dist/infrastructure/credentials/keyring.js +21 -0
  95. package/dist/infrastructure/credentials/redact.d.ts +3 -0
  96. package/dist/infrastructure/credentials/redact.js +26 -0
  97. package/dist/infrastructure/duckdb/connection.d.ts +8 -0
  98. package/dist/infrastructure/duckdb/connection.js +18 -0
  99. package/dist/infrastructure/duckdb/dump-client.d.ts +20 -0
  100. package/dist/infrastructure/duckdb/dump-client.js +66 -0
  101. package/dist/infrastructure/duckdb/factors.d.ts +3 -0
  102. package/dist/infrastructure/duckdb/factors.js +83 -0
  103. package/dist/infrastructure/duckdb/importer.d.ts +11 -0
  104. package/dist/infrastructure/duckdb/importer.js +105 -0
  105. package/dist/infrastructure/duckdb/migrations.d.ts +17 -0
  106. package/dist/infrastructure/duckdb/migrations.js +92 -0
  107. package/dist/infrastructure/duckdb/quality.d.ts +12 -0
  108. package/dist/infrastructure/duckdb/quality.js +56 -0
  109. package/dist/infrastructure/duckdb/schema.d.ts +4 -0
  110. package/dist/infrastructure/duckdb/schema.js +14 -0
  111. package/dist/infrastructure/filesystem/atomic-file.d.ts +2 -0
  112. package/dist/infrastructure/filesystem/atomic-file.js +38 -0
  113. package/dist/infrastructure/filesystem/platform-paths.d.ts +15 -0
  114. package/dist/infrastructure/filesystem/platform-paths.js +42 -0
  115. package/dist/infrastructure/filesystem/process-lock.d.ts +6 -0
  116. package/dist/infrastructure/filesystem/process-lock.js +56 -0
  117. package/dist/infrastructure/filesystem/stdin.d.ts +5 -0
  118. package/dist/infrastructure/filesystem/stdin.js +9 -0
  119. package/dist/infrastructure/fuyao/client.d.ts +31 -0
  120. package/dist/infrastructure/fuyao/client.js +145 -0
  121. package/dist/infrastructure/fuyao/envelope.d.ts +9 -0
  122. package/dist/infrastructure/fuyao/envelope.js +8 -0
  123. package/dist/infrastructure/fuyao/pagination.d.ts +16 -0
  124. package/dist/infrastructure/fuyao/pagination.js +44 -0
  125. package/dist/infrastructure/fuyao/retry.d.ts +5 -0
  126. package/dist/infrastructure/fuyao/retry.js +18 -0
  127. package/dist/infrastructure/fuyao/windowing.d.ts +8 -0
  128. package/dist/infrastructure/fuyao/windowing.js +25 -0
  129. package/dist/infrastructure/skills/drift.d.ts +3 -0
  130. package/dist/infrastructure/skills/drift.js +8 -0
  131. package/dist/infrastructure/skills/installer.d.ts +13 -0
  132. package/dist/infrastructure/skills/installer.js +48 -0
  133. package/dist/infrastructure/skills/manifest.d.ts +11 -0
  134. package/dist/infrastructure/skills/manifest.js +62 -0
  135. package/dist/infrastructure/updater/cache.d.ts +15 -0
  136. package/dist/infrastructure/updater/cache.js +76 -0
  137. package/dist/infrastructure/updater/check.d.ts +13 -0
  138. package/dist/infrastructure/updater/check.js +51 -0
  139. package/dist/infrastructure/updater/install.d.ts +4 -0
  140. package/dist/infrastructure/updater/install.js +39 -0
  141. package/dist/output/json.d.ts +3 -0
  142. package/dist/output/json.js +16 -0
  143. package/dist/output/renderer.d.ts +4 -0
  144. package/dist/output/renderer.js +47 -0
  145. package/dist/output/table.d.ts +2 -0
  146. package/dist/output/table.js +31 -0
  147. package/migrations/001-initial.sql +95 -0
  148. package/migrations/manifest.json +12 -0
  149. package/package.json +66 -0
  150. package/schemas/capabilities.json +290 -0
  151. package/schemas/command-envelope.schema.json +12 -0
  152. package/scripts/postinstall.mjs +27 -0
  153. package/scripts/update-check.mjs +23 -0
  154. package/skills/hithink-finance-data/SKILL.md +74 -0
  155. package/skills/hithink-finance-data/references/data-clean.md +33 -0
  156. package/skills/hithink-finance-data/references/data-init.md +35 -0
  157. package/skills/hithink-finance-data/references/data-migrate.md +33 -0
  158. package/skills/hithink-finance-data/references/data-remove.md +33 -0
  159. package/skills/hithink-finance-data/references/data-repair.md +33 -0
  160. package/skills/hithink-finance-data/references/data-status.md +33 -0
  161. package/skills/hithink-finance-data/references/data-sync.md +34 -0
  162. package/skills/hithink-finance-data/references/data-validate.md +33 -0
  163. package/skills/hithink-finance-data/references/db-describe.md +33 -0
  164. package/skills/hithink-finance-data/references/db-export.md +33 -0
  165. package/skills/hithink-finance-data/references/db-query.md +33 -0
  166. package/skills/hithink-finance-financials/SKILL.md +59 -0
  167. package/skills/hithink-finance-financials/references/financials-balance-sheet.md +40 -0
  168. package/skills/hithink-finance-financials/references/financials-cash-flow.md +40 -0
  169. package/skills/hithink-finance-financials/references/financials-income.md +40 -0
  170. package/skills/hithink-finance-financials/references/financials-indicators.md +37 -0
  171. package/skills/hithink-finance-index/SKILL.md +59 -0
  172. package/skills/hithink-finance-index/references/index-catalog.md +36 -0
  173. package/skills/hithink-finance-index/references/index-constituents.md +36 -0
  174. package/skills/hithink-finance-index/references/index-history.md +38 -0
  175. package/skills/hithink-finance-index/references/index-snapshot.md +37 -0
  176. package/skills/hithink-finance-market/SKILL.md +63 -0
  177. package/skills/hithink-finance-market/references/market-adjustment-factors.md +33 -0
  178. package/skills/hithink-finance-market/references/market-calendar.md +35 -0
  179. package/skills/hithink-finance-market/references/market-corporate-actions.md +38 -0
  180. package/skills/hithink-finance-market/references/market-history.md +39 -0
  181. package/skills/hithink-finance-market/references/market-panel.md +33 -0
  182. package/skills/hithink-finance-market/references/market-snapshot.md +39 -0
  183. package/skills/hithink-finance-research/SKILL.md +58 -0
  184. package/skills/hithink-finance-research/references/research-workflow.md +34 -0
  185. package/skills/hithink-finance-shared/SKILL.md +55 -0
  186. package/skills/hithink-finance-shared/references/auth-and-config.md +31 -0
  187. package/skills/hithink-finance-shared/references/global-rules.md +21 -0
  188. package/skills/hithink-finance-shared/references/lifecycle.md +23 -0
  189. package/skills/hithink-finance-shared/references/skills-management.md +22 -0
  190. package/skills/hithink-finance-special-data/SKILL.md +68 -0
  191. package/skills/hithink-finance-special-data/references/special-anomaly-list.md +36 -0
  192. package/skills/hithink-finance-special-data/references/special-anomaly-stock.md +37 -0
  193. package/skills/hithink-finance-special-data/references/special-dragon-tiger.md +37 -0
  194. package/skills/hithink-finance-special-data/references/special-hot-stock-history.md +36 -0
  195. package/skills/hithink-finance-special-data/references/special-hot-stock-trend.md +38 -0
  196. package/skills/hithink-finance-special-data/references/special-hot-stock.md +36 -0
  197. package/skills/hithink-finance-special-data/references/special-limit-up-ladder.md +35 -0
  198. package/skills/hithink-finance-special-data/references/special-limit-up-pool.md +40 -0
  199. package/skills/hithink-finance-special-data/references/special-skyrocket.md +36 -0
  200. package/skills/hithink-finance-symbol/SKILL.md +56 -0
  201. package/skills/hithink-finance-symbol/references/symbol-list.md +39 -0
  202. package/skills/hithink-finance-symbol/references/symbol-search.md +39 -0
  203. package/skills/manifest.json +55 -0
@@ -0,0 +1,111 @@
1
+ import { Command, CommanderError, Option } from 'commander';
2
+ import { localizeText, translate } from './i18n.js';
3
+ import { successEnvelope } from '../contracts/envelope.js';
4
+ import { CliError } from '../contracts/errors.js';
5
+ import { renderResult } from '../output/renderer.js';
6
+ import { registerAuthCommands } from '../commands/auth/index.js';
7
+ import { registerSymbolCommands } from '../commands/symbol/index.js';
8
+ import { registerMarketCommands } from '../commands/market/index.js';
9
+ import { registerSpecialCommands } from '../commands/special/index.js';
10
+ import { registerFinancialCommands } from '../commands/financials/index.js';
11
+ import { registerIndexCommands } from '../commands/index/index.js';
12
+ import { registerCapabilitiesCommand } from '../commands/system/capabilities.js';
13
+ import { registerSchemaCommand } from '../commands/system/schema.js';
14
+ import { registerSkillsCommands } from '../commands/skills/index.js';
15
+ import { registerUpdateCommand } from '../commands/system/update.js';
16
+ import { registerUninstallCommand } from '../commands/system/uninstall.js';
17
+ import { registerDoctorCommand } from '../commands/system/doctor.js';
18
+ import { registerDataCommands } from '../commands/data/index.js';
19
+ import { registerDbCommands } from '../commands/db/index.js';
20
+ import { registerLocalMarketCommands } from '../commands/market/local.js';
21
+ import { registerConfigCommands } from '../commands/config/index.js';
22
+ export function createProgram(metadata, context, dependencies) {
23
+ const program = new Command();
24
+ program
25
+ .name('hithink-finance')
26
+ .description(translate(context.language, 'rootDescription'))
27
+ .addOption(new Option('--format <format>', localizeText(context.language, 'output format'))
28
+ .choices(['auto', 'json', 'ndjson', 'csv', 'table'])
29
+ .default(dependencies.resolvedConfig.format))
30
+ .addOption(new Option('--lang <lang>', localizeText(context.language, 'human interface language')).choices(['zh-CN', 'en']))
31
+ .option('--profile <name>', localizeText(context.language, 'configuration and credential profile'), dependencies.resolvedConfig.profile)
32
+ .option('--config <path>', localizeText(context.language, 'explicit JSON configuration file'))
33
+ .option('--api-key <value>', localizeText(context.language, 'API key for this process'))
34
+ .option('--api-key-stdin', localizeText(context.language, 'read an API key from stdin'))
35
+ .option('--no-input', localizeText(context.language, 'disable interactive input'))
36
+ .option('--yes', localizeText(context.language, 'confirm non-interactive operations'))
37
+ .option('--source <source>', localizeText(context.language, 'data source: auto, local, or remote'), 'auto')
38
+ .option('--db <path>', localizeText(context.language, 'local DuckDB path'))
39
+ .option('--request-id <id>', localizeText(context.language, 'caller-supplied correlation ID'))
40
+ .option('--debug', localizeText(context.language, 'enable diagnostic details on stderr'))
41
+ .option('--no-color', localizeText(context.language, 'disable terminal colors'))
42
+ .showHelpAfterError(false)
43
+ .exitOverride()
44
+ .configureOutput({
45
+ writeOut: (value) => context.stdout.write(value),
46
+ writeErr: () => undefined,
47
+ });
48
+ program.hook('preAction', (_root, action) => {
49
+ const options = action.optsWithGlobals();
50
+ if (options.apiKey !== undefined && options.apiKeyStdin === true)
51
+ throw new CliError({
52
+ code: 'CLI_CONFLICTING_ARGUMENTS',
53
+ category: 'validation',
54
+ message: '--api-key and --api-key-stdin cannot be used together.',
55
+ hint: 'Choose exactly one API key input method.',
56
+ retryable: false,
57
+ exitCode: 2,
58
+ });
59
+ });
60
+ const versionDescriptor = {
61
+ id: 'version',
62
+ path: ['version'],
63
+ describe: (language) => translate(language, 'versionDescription'),
64
+ register(root) {
65
+ root
66
+ .command('version')
67
+ .description(this.describe(context.language))
68
+ .action(async () => {
69
+ await renderResult(successEnvelope('version', { package: metadata.name, version: metadata.version, node: process.version }, { requestId: context.requestId }), context);
70
+ });
71
+ },
72
+ };
73
+ versionDescriptor.register(program);
74
+ registerAuthCommands(program, context, metadata, dependencies.authProvider);
75
+ registerConfigCommands(program, context, dependencies.resolvedConfig);
76
+ const remoteDependencies = {
77
+ authProvider: dependencies.authProvider,
78
+ baseUrl: dependencies.fuyaoBaseUrl,
79
+ defaultDbPath: dependencies.platformPaths.defaultDbPath,
80
+ };
81
+ registerSymbolCommands(program, context, remoteDependencies);
82
+ registerMarketCommands(program, context, remoteDependencies);
83
+ registerSpecialCommands(program, context, remoteDependencies);
84
+ registerFinancialCommands(program, context, remoteDependencies);
85
+ registerIndexCommands(program, context, remoteDependencies);
86
+ registerCapabilitiesCommand(program, context);
87
+ registerSchemaCommand(program, context);
88
+ registerSkillsCommands(program, context, dependencies.packageRoot);
89
+ registerUpdateCommand(program, context, metadata, dependencies);
90
+ registerUninstallCommand(program, context, metadata, dependencies);
91
+ registerDoctorCommand(program, context);
92
+ registerDataCommands(program, context, dependencies.platformPaths, {
93
+ ...remoteDependencies,
94
+ cliVersion: metadata.version,
95
+ });
96
+ registerDbCommands(program, context, dependencies.platformPaths.defaultDbPath);
97
+ registerLocalMarketCommands(program, context, dependencies.platformPaths.defaultDbPath);
98
+ return program;
99
+ }
100
+ export function commanderError(error, language) {
101
+ const isUnknownCommand = error.code === 'commander.unknownCommand';
102
+ return new CliError({
103
+ code: isUnknownCommand ? 'CLI_UNKNOWN_COMMAND' : 'CLI_BAD_ARGUMENT',
104
+ category: 'validation',
105
+ message: isUnknownCommand ? translate(language, 'unknownCommand') : error.message,
106
+ hint: translate(language, 'unknownCommandHint'),
107
+ retryable: false,
108
+ exitCode: 2,
109
+ });
110
+ }
111
+ //# sourceMappingURL=program.js.map
@@ -0,0 +1,10 @@
1
+ import type { Readable } from 'node:stream';
2
+ import type { Command } from 'commander';
3
+ import type { CliContext } from '../../cli/context.js';
4
+ import type { PackageMetadata } from '../../cli/program.js';
5
+ import type { ApiKeyAuthProvider } from '../../infrastructure/credentials/api-key-provider.js';
6
+ export declare function readHiddenApiKey(context: CliContext, input?: Readable & {
7
+ isTTY?: boolean;
8
+ }): Promise<string>;
9
+ export declare function registerAuthCommands(program: Command, context: CliContext, metadata: PackageMetadata, provider: ApiKeyAuthProvider): void;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,131 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { Writable } from 'node:stream';
3
+ import { localizeText, translate } from '../../cli/i18n.js';
4
+ import { successEnvelope } from '../../contracts/envelope.js';
5
+ import { CliError } from '../../contracts/errors.js';
6
+ import { renderResult } from '../../output/renderer.js';
7
+ import { readStdin } from '../../infrastructure/filesystem/stdin.js';
8
+ const apiKeyHint = 'Get an API key at https://fuyao.aicubes.cn/admin, then run `hithink-finance auth login` and paste it into the hidden prompt. In non-interactive shells, use `hithink-finance auth login --api-key-stdin`.';
9
+ export async function readHiddenApiKey(context, input = process.stdin) {
10
+ if (input.isTTY !== true) {
11
+ throw new CliError({
12
+ code: 'CLI_MISSING_ARGUMENT',
13
+ category: 'validation',
14
+ message: 'An API key is required in non-interactive mode.',
15
+ hint: apiKeyHint,
16
+ retryable: false,
17
+ exitCode: 2,
18
+ });
19
+ }
20
+ const muted = new Writable({
21
+ write(_chunk, _encoding, callback) {
22
+ callback();
23
+ },
24
+ });
25
+ const readline = createInterface({ input, output: muted, terminal: true });
26
+ context.stderr.write(context.language === 'zh-CN'
27
+ ? [
28
+ '欢迎使用同花顺金融数据 CLI',
29
+ '',
30
+ '请前往同花顺金融数据服务官网,登录同花顺账号,创建您的 API Key,复制到当前终端后按 Enter 完成登录。',
31
+ '',
32
+ '官网 API Key 获取地址:https://fuyao.aicubes.cn/admin',
33
+ '',
34
+ '下方为隐藏输入模式,粘贴或输入时不会显示在终端中;输入完成后按 Enter。',
35
+ '',
36
+ '在此处填写您的 API Key:',
37
+ ].join('\n')
38
+ : [
39
+ 'Welcome to HiThink Finance CLI',
40
+ '',
41
+ 'Visit the HiThink Finance Data Service website, sign in with your HiThink account, create an API key, paste it into this terminal, then press Enter to finish login.',
42
+ '',
43
+ 'API key page: https://fuyao.aicubes.cn/admin',
44
+ '',
45
+ 'The prompt below uses hidden input mode. Typed or pasted characters will not be shown; press Enter when done.',
46
+ '',
47
+ 'Paste your API key here:',
48
+ ].join('\n'));
49
+ try {
50
+ return await readline.question('');
51
+ }
52
+ finally {
53
+ readline.close();
54
+ context.stderr.write('\n');
55
+ }
56
+ }
57
+ export function registerAuthCommands(program, context, metadata, provider) {
58
+ const auth = program
59
+ .command('auth')
60
+ .description(localizeText(context.language, 'Manage API key authentication'));
61
+ const login = auth
62
+ .command('login')
63
+ .description(localizeText(context.language, 'Store an API key in the system credential store'));
64
+ login.action(async () => {
65
+ const options = login.optsWithGlobals();
66
+ if (options.apiKey !== undefined && options.apiKeyStdin === true) {
67
+ throw new CliError({
68
+ code: 'CLI_CONFLICTING_ARGUMENTS',
69
+ category: 'validation',
70
+ message: '--api-key and --api-key-stdin cannot be used together.',
71
+ hint: 'Choose exactly one API key input method.',
72
+ retryable: false,
73
+ exitCode: 2,
74
+ });
75
+ }
76
+ if (options.apiKey === undefined && options.apiKeyStdin !== true && options.input === false) {
77
+ throw new CliError({
78
+ code: 'CLI_MISSING_ARGUMENT',
79
+ category: 'validation',
80
+ message: 'An API key is required when --no-input is set.',
81
+ hint: apiKeyHint,
82
+ retryable: false,
83
+ exitCode: 2,
84
+ });
85
+ }
86
+ const profile = options.profile ?? 'default';
87
+ const existing = await provider.status(profile);
88
+ if (existing.configured) {
89
+ await renderResult(successEnvelope('auth.login', {
90
+ ...existing,
91
+ alreadyLoggedIn: true,
92
+ nextStep: translate(context.language, 'authLoginAlreadyConfigured'),
93
+ }, { requestId: context.requestId }), context);
94
+ return;
95
+ }
96
+ let apiKey;
97
+ if (options.apiKey !== undefined) {
98
+ context.stderr.write('Warning: --api-key may be visible in shell history and process listings. Prefer --api-key-stdin.\n');
99
+ apiKey = options.apiKey;
100
+ }
101
+ else if (options.apiKeyStdin === true) {
102
+ apiKey = await readStdin(process.stdin, { stripFinalNewlines: true });
103
+ }
104
+ else {
105
+ apiKey = await readHiddenApiKey(context);
106
+ }
107
+ const status = await provider.login({ profile, apiKey });
108
+ await renderResult(successEnvelope('auth.login', status, { requestId: context.requestId }), context);
109
+ });
110
+ const status = auth
111
+ .command('status')
112
+ .description(localizeText(context.language, 'Show whether an API key is configured'));
113
+ status.action(async () => {
114
+ const options = status.optsWithGlobals();
115
+ const result = await provider.status(options.profile ?? 'default');
116
+ await renderResult(successEnvelope('auth.status', result, { requestId: context.requestId }), context);
117
+ });
118
+ const logout = auth
119
+ .command('logout')
120
+ .description(localizeText(context.language, 'Delete API key credentials'))
121
+ .option('--all', localizeText(context.language, 'delete every hithink-finance profile'));
122
+ logout.action(async () => {
123
+ const options = logout.optsWithGlobals();
124
+ if (options.all === true)
125
+ await provider.logoutAll();
126
+ else
127
+ await provider.logout(options.profile ?? 'default');
128
+ await renderResult(successEnvelope('auth.logout', { removed: true, all: options.all === true, cliVersion: metadata.version }, { requestId: context.requestId }), context);
129
+ });
130
+ }
131
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ import type { ResolvedConfig } from '../../application/config.js';
2
+ import type { Command } from 'commander';
3
+ import type { CliContext } from '../../cli/context.js';
4
+ export interface PublicConfig {
5
+ dbPath: string;
6
+ profile: string;
7
+ format: ResolvedConfig['format'];
8
+ language?: ResolvedConfig['language'];
9
+ updateCheck: boolean;
10
+ }
11
+ export declare function publicConfig(config: ResolvedConfig): PublicConfig;
12
+ export declare function registerConfigCommands(program: Command, context: CliContext, config: ResolvedConfig): void;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { localizeText } from '../../cli/i18n.js';
2
+ import { successEnvelope } from '../../contracts/envelope.js';
3
+ import { renderResult } from '../../output/renderer.js';
4
+ export function publicConfig(config) {
5
+ const result = {
6
+ dbPath: config.dbPath,
7
+ profile: config.profile,
8
+ format: config.format,
9
+ updateCheck: config.updateCheck,
10
+ };
11
+ if (config.language !== undefined)
12
+ result.language = config.language;
13
+ return result;
14
+ }
15
+ export function registerConfigCommands(program, context, config) {
16
+ const command = program
17
+ .command('config')
18
+ .description(localizeText(context.language, 'Inspect resolved non-secret configuration'));
19
+ command
20
+ .command('show')
21
+ .description(localizeText(context.language, 'Show resolved non-secret configuration'))
22
+ .action(async () => {
23
+ await renderResult(successEnvelope('config.show', publicConfig(config), { requestId: context.requestId }), context);
24
+ });
25
+ }
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ import type { Command } from 'commander';
2
+ import type { CliContext } from '../../cli/context.js';
3
+ import type { PlatformPaths } from '../../infrastructure/filesystem/platform-paths.js';
4
+ import type { ApiKeyAuthProvider } from '../../infrastructure/credentials/api-key-provider.js';
5
+ export declare function registerDataCommands(program: Command, context: CliContext, paths: PlatformPaths, remote: {
6
+ authProvider: ApiKeyAuthProvider;
7
+ baseUrl: string;
8
+ cliVersion: string;
9
+ }): void;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,185 @@
1
+ import { localizeText } from '../../cli/i18n.js';
2
+ import { successEnvelope } from '../../contracts/envelope.js';
3
+ import { CliError } from '../../contracts/errors.js';
4
+ import { openDatabase } from '../../infrastructure/duckdb/connection.js';
5
+ import { applyMigrations, assertSchemaCompatibility, planMigrations, } from '../../infrastructure/duckdb/migrations.js';
6
+ import { rebuildAdjustmentFactors } from '../../infrastructure/duckdb/factors.js';
7
+ import { validateDatabase } from '../../infrastructure/duckdb/quality.js';
8
+ import { renderResult } from '../../output/renderer.js';
9
+ import { initializeData } from '../../application/use-cases/data-init.js';
10
+ import { syncDataFromFuyao } from '../../application/use-cases/data-sync.js';
11
+ import { cleanManagedCache } from '../../application/use-cases/data-clean.js';
12
+ import { removeDatabase } from '../../application/use-cases/data-remove.js';
13
+ import { mkdir, stat } from 'node:fs/promises';
14
+ import path from 'node:path';
15
+ import { withExclusiveDataLock } from '../../infrastructure/filesystem/process-lock.js';
16
+ function dbPath(command, fallback) {
17
+ return (command.optsWithGlobals().db ?? process.env.HITHINK_FINANCE_DB_PATH ?? fallback);
18
+ }
19
+ async function withDataLock(paths, command, cliVersion, action) {
20
+ await mkdir(paths.stateDir, { recursive: true });
21
+ return withExclusiveDataLock(path.join(paths.stateDir, 'data.lock'), { command, cliVersion }, action);
22
+ }
23
+ async function stdinText() {
24
+ const chunks = [];
25
+ for await (const chunk of process.stdin)
26
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
27
+ return Buffer.concat(chunks).toString('utf8').trim();
28
+ }
29
+ export function registerDataCommands(program, context, paths, remote) {
30
+ const data = program
31
+ .command('data')
32
+ .description(localizeText(context.language, 'Manage local DuckDB data'));
33
+ const init = data
34
+ .command('init')
35
+ .description(localizeText(context.language, 'Initialize local data from a verified dump'))
36
+ .option('--kline <path>')
37
+ .option('--events <path>')
38
+ .option('--symbols <path>');
39
+ init.action(async () => withDataLock(paths, 'data.init', remote.cliVersion, async () => {
40
+ await mkdir(paths.dataDir, { recursive: true });
41
+ const db = await openDatabase(dbPath(init, paths.defaultDbPath));
42
+ try {
43
+ const options = init.opts();
44
+ const hasKline = options.kline !== undefined;
45
+ const hasEvents = options.events !== undefined;
46
+ if (hasKline !== hasEvents)
47
+ throw new CliError({
48
+ code: 'CLI_BAD_ARGUMENT',
49
+ category: 'validation',
50
+ message: '--kline and --events must be provided together.',
51
+ hint: 'Provide both local dump files, or omit both to initialize from Fuyao.',
52
+ retryable: false,
53
+ exitCode: 2,
54
+ });
55
+ const result = options.kline !== undefined && options.events !== undefined
56
+ ? {
57
+ decision: 'FULL',
58
+ factorRows: await initializeData(db.connection, {
59
+ klinePath: options.kline,
60
+ eventsPath: options.events,
61
+ ...(options.symbols === undefined ? {} : { symbolsPath: options.symbols }),
62
+ batchId: `local-${Date.now()}`,
63
+ source: 'local-files',
64
+ }),
65
+ }
66
+ : await syncDataFromFuyao(db.connection, {
67
+ baseUrl: remote.baseUrl,
68
+ apiKey: (await remote.authProvider.resolve(init.optsWithGlobals().profile ?? 'default', init.optsWithGlobals()
69
+ .apiKeyStdin === true
70
+ ? await stdinText()
71
+ : init.optsWithGlobals().apiKey)).apiKey,
72
+ cacheDir: paths.cacheDir,
73
+ });
74
+ await renderResult(successEnvelope('data.init', { initialized: true, path: db.path, ...result }, { requestId: context.requestId }), context);
75
+ }
76
+ finally {
77
+ db.close();
78
+ }
79
+ }));
80
+ const sync = data
81
+ .command('sync')
82
+ .description(localizeText(context.language, 'Synchronize local data'));
83
+ sync.action(async () => withDataLock(paths, 'data.sync', remote.cliVersion, async () => {
84
+ await mkdir(paths.dataDir, { recursive: true });
85
+ const db = await openDatabase(dbPath(sync, paths.defaultDbPath));
86
+ try {
87
+ const globals = sync.optsWithGlobals();
88
+ const auth = await remote.authProvider.resolve(globals.profile ?? 'default', globals.apiKeyStdin === true ? await stdinText() : globals.apiKey);
89
+ const result = await syncDataFromFuyao(db.connection, {
90
+ baseUrl: remote.baseUrl,
91
+ apiKey: auth.apiKey,
92
+ cacheDir: paths.cacheDir,
93
+ });
94
+ await renderResult(successEnvelope('data.sync', result, { requestId: context.requestId }), context);
95
+ }
96
+ finally {
97
+ db.close();
98
+ }
99
+ }));
100
+ data
101
+ .command('status')
102
+ .description(localizeText(context.language, 'Show local database status'))
103
+ .action(async () => {
104
+ const db = await openDatabase(dbPath(data, paths.defaultDbPath));
105
+ try {
106
+ const schema = await assertSchemaCompatibility(db.connection);
107
+ await renderResult(successEnvelope('data.status', { path: db.path, ...schema }, { requestId: context.requestId }), context);
108
+ }
109
+ finally {
110
+ db.close();
111
+ }
112
+ });
113
+ data
114
+ .command('validate')
115
+ .description(localizeText(context.language, 'Validate local data quality'))
116
+ .action(async () => {
117
+ const db = await openDatabase(dbPath(data, paths.defaultDbPath));
118
+ try {
119
+ await applyMigrations(db.connection);
120
+ await renderResult(successEnvelope('data.validate', await validateDatabase(db.connection), {
121
+ requestId: context.requestId,
122
+ }), context);
123
+ }
124
+ finally {
125
+ db.close();
126
+ }
127
+ });
128
+ data
129
+ .command('repair')
130
+ .description(localizeText(context.language, 'Rebuild derived factors'))
131
+ .action(async () => withDataLock(paths, 'data.repair', remote.cliVersion, async () => {
132
+ const db = await openDatabase(dbPath(data, paths.defaultDbPath));
133
+ try {
134
+ await applyMigrations(db.connection);
135
+ const factorRows = await rebuildAdjustmentFactors(db.connection);
136
+ await renderResult(successEnvelope('data.repair', { factorRows }, { requestId: context.requestId }), context);
137
+ }
138
+ finally {
139
+ db.close();
140
+ }
141
+ }));
142
+ const migrate = data
143
+ .command('migrate')
144
+ .description(localizeText(context.language, 'Plan or apply schema migrations'))
145
+ .option('--apply')
146
+ .option('--allow-heavy');
147
+ migrate.action(async () => withDataLock(paths, 'data.migrate', remote.cliVersion, async () => {
148
+ const db = await openDatabase(dbPath(migrate, paths.defaultDbPath));
149
+ try {
150
+ const options = migrate.opts();
151
+ const plan = await planMigrations(db.connection);
152
+ if (options.apply === true)
153
+ await applyMigrations(db.connection, undefined, {
154
+ allowHeavy: options.allowHeavy === true,
155
+ });
156
+ await renderResult(successEnvelope('data.migrate', { applied: options.apply === true, versions: plan.map((item) => item.version) }, { requestId: context.requestId }), context);
157
+ }
158
+ finally {
159
+ db.close();
160
+ }
161
+ }));
162
+ const clean = data
163
+ .command('clean')
164
+ .description(localizeText(context.language, 'Clean managed download caches'))
165
+ .option('--cache');
166
+ clean.action(async () => {
167
+ await cleanManagedCache(paths.cacheDir, paths.cacheDir);
168
+ await renderResult(successEnvelope('data.clean', { cacheRemoved: true, path: paths.cacheDir }, { requestId: context.requestId }), context);
169
+ });
170
+ const remove = data
171
+ .command('remove')
172
+ .description(localizeText(context.language, 'Remove the explicitly confirmed database'))
173
+ .option('--plan');
174
+ remove.action(async () => withDataLock(paths, 'data.remove', remote.cliVersion, async () => {
175
+ const target = dbPath(remove, paths.defaultDbPath);
176
+ const globals = remove.optsWithGlobals();
177
+ const size = await stat(target)
178
+ .then((value) => value.size)
179
+ .catch(() => 0);
180
+ if (remove.opts().plan !== true)
181
+ await removeDatabase(target, target, globals.yes === true);
182
+ await renderResult(successEnvelope('data.remove', { path: target, size, removed: remove.opts().plan !== true }, { requestId: context.requestId }), context);
183
+ }));
184
+ }
185
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { CliContext } from '../../cli/context.js';
3
+ export declare function registerDbCommands(program: Command, context: CliContext, fallback: string): void;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,72 @@
1
+ import { localizeText } from '../../cli/i18n.js';
2
+ import { exportQuery, queryReadOnly } from '../../application/use-cases/local-query.js';
3
+ import { successEnvelope } from '../../contracts/envelope.js';
4
+ import { CliError } from '../../contracts/errors.js';
5
+ import { openDatabase } from '../../infrastructure/duckdb/connection.js';
6
+ import { applyMigrations } from '../../infrastructure/duckdb/migrations.js';
7
+ import { renderResult } from '../../output/renderer.js';
8
+ function databasePath(command, fallback) {
9
+ return (command.optsWithGlobals().db ?? process.env.HITHINK_FINANCE_DB_PATH ?? fallback);
10
+ }
11
+ export function registerDbCommands(program, context, fallback) {
12
+ const db = program
13
+ .command('db')
14
+ .description(localizeText(context.language, 'Query the local DuckDB'));
15
+ db.command('describe')
16
+ .description(localizeText(context.language, 'Describe local database objects'))
17
+ .action(async () => {
18
+ const opened = await openDatabase(databasePath(db, fallback));
19
+ try {
20
+ await applyMigrations(opened.connection);
21
+ const rows = await queryReadOnly(opened.connection, "SELECT table_name,table_type FROM information_schema.tables WHERE table_schema='main' ORDER BY table_name");
22
+ await renderResult(successEnvelope('db.describe', rows, {
23
+ requestId: context.requestId,
24
+ count: rows.length,
25
+ }), context);
26
+ }
27
+ finally {
28
+ opened.close();
29
+ }
30
+ });
31
+ const query = db
32
+ .command('query')
33
+ .description(localizeText(context.language, 'Run a read-only SQL query'))
34
+ .requiredOption('--sql <sql>');
35
+ query.action(async () => {
36
+ const opened = await openDatabase(databasePath(query, fallback));
37
+ try {
38
+ const rows = await queryReadOnly(opened.connection, query.opts().sql);
39
+ await renderResult(successEnvelope('db.query', rows, { requestId: context.requestId, count: rows.length }), context);
40
+ }
41
+ finally {
42
+ opened.close();
43
+ }
44
+ });
45
+ const exportCommand = db
46
+ .command('export')
47
+ .description(localizeText(context.language, 'Export a read-only SQL query'))
48
+ .requiredOption('--sql <sql>')
49
+ .requiredOption('--output <path>')
50
+ .option('--file-format <format>', localizeText(context.language, 'ndjson, csv, or parquet'), 'ndjson');
51
+ exportCommand.action(async () => {
52
+ const options = exportCommand.opts();
53
+ if (!['ndjson', 'csv', 'parquet'].includes(options.fileFormat))
54
+ throw new CliError({
55
+ code: 'CLI_BAD_ARGUMENT',
56
+ category: 'validation',
57
+ message: '--file-format must be ndjson, csv, or parquet',
58
+ hint: 'Use --file-format ndjson, --file-format csv, or --file-format parquet.',
59
+ retryable: false,
60
+ exitCode: 2,
61
+ });
62
+ const opened = await openDatabase(databasePath(exportCommand, fallback));
63
+ try {
64
+ const count = await exportQuery(opened.connection, options.sql, options.output, options.fileFormat);
65
+ await renderResult(successEnvelope('db.export', { path: options.output, format: options.fileFormat, count }, { requestId: context.requestId }), context);
66
+ }
67
+ finally {
68
+ opened.close();
69
+ }
70
+ });
71
+ }
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,5 @@
1
+ import type { Command } from 'commander';
2
+ import type { CliContext } from '../../cli/context.js';
3
+ import { type RemoteCommandDependencies } from '../remote.js';
4
+ export declare function registerFinancialCommands(program: Command, context: CliContext, dependencies: RemoteCommandDependencies): void;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { remoteCapabilities } from '../../contracts/remote-capabilities.js';
2
+ import { registerRemoteCapabilityGroup } from '../remote.js';
3
+ export function registerFinancialCommands(program, context, dependencies) {
4
+ registerRemoteCapabilityGroup(program, 'financials', remoteCapabilities.filter((item) => item.command[0] === 'financials'), context, dependencies);
5
+ }
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,5 @@
1
+ import type { Command } from 'commander';
2
+ import type { CliContext } from '../../cli/context.js';
3
+ import { type RemoteCommandDependencies } from '../remote.js';
4
+ export declare function registerIndexCommands(program: Command, context: CliContext, dependencies: RemoteCommandDependencies): void;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { remoteCapabilities } from '../../contracts/remote-capabilities.js';
2
+ import { registerRemoteCapabilityGroup } from '../remote.js';
3
+ export function registerIndexCommands(program, context, dependencies) {
4
+ registerRemoteCapabilityGroup(program, 'index', remoteCapabilities.filter((item) => item.command[0] === 'index'), context, dependencies);
5
+ }
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,5 @@
1
+ import type { Command } from 'commander';
2
+ import type { CliContext } from '../../cli/context.js';
3
+ import { type RemoteCommandDependencies } from '../remote.js';
4
+ export declare function registerMarketCommands(program: Command, context: CliContext, dependencies: RemoteCommandDependencies): void;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { remoteCapabilities } from '../../contracts/remote-capabilities.js';
2
+ import { registerRemoteCapabilityGroup } from '../remote.js';
3
+ export function registerMarketCommands(program, context, dependencies) {
4
+ registerRemoteCapabilityGroup(program, 'market', remoteCapabilities.filter((item) => item.command[0] === 'market'), context, dependencies);
5
+ }
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { CliContext } from '../../cli/context.js';
3
+ export declare function registerLocalMarketCommands(program: Command, context: CliContext, fallbackDbPath: string): void;
4
+ //# sourceMappingURL=local.d.ts.map