@yangfei_93sky/biocli 0.2.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 (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +197 -0
  3. package/dist/batch.d.ts +20 -0
  4. package/dist/batch.js +69 -0
  5. package/dist/build-manifest.d.ts +38 -0
  6. package/dist/build-manifest.js +186 -0
  7. package/dist/cache.d.ts +28 -0
  8. package/dist/cache.js +126 -0
  9. package/dist/cli-manifest.json +1500 -0
  10. package/dist/cli.d.ts +7 -0
  11. package/dist/cli.js +336 -0
  12. package/dist/clis/_shared/common.d.ts +8 -0
  13. package/dist/clis/_shared/common.js +13 -0
  14. package/dist/clis/_shared/eutils.d.ts +9 -0
  15. package/dist/clis/_shared/eutils.js +9 -0
  16. package/dist/clis/_shared/organism-db.d.ts +23 -0
  17. package/dist/clis/_shared/organism-db.js +58 -0
  18. package/dist/clis/_shared/xml-helpers.d.ts +58 -0
  19. package/dist/clis/_shared/xml-helpers.js +266 -0
  20. package/dist/clis/aggregate/enrichment.d.ts +7 -0
  21. package/dist/clis/aggregate/enrichment.js +105 -0
  22. package/dist/clis/aggregate/gene-dossier.d.ts +13 -0
  23. package/dist/clis/aggregate/gene-dossier.js +248 -0
  24. package/dist/clis/aggregate/gene-profile.d.ts +16 -0
  25. package/dist/clis/aggregate/gene-profile.js +305 -0
  26. package/dist/clis/aggregate/literature-brief.d.ts +7 -0
  27. package/dist/clis/aggregate/literature-brief.js +79 -0
  28. package/dist/clis/aggregate/variant-dossier.d.ts +11 -0
  29. package/dist/clis/aggregate/variant-dossier.js +161 -0
  30. package/dist/clis/aggregate/variant-interpret.d.ts +10 -0
  31. package/dist/clis/aggregate/variant-interpret.js +210 -0
  32. package/dist/clis/aggregate/workflow-prepare.d.ts +12 -0
  33. package/dist/clis/aggregate/workflow-prepare.js +228 -0
  34. package/dist/clis/aggregate/workflow-scout.d.ts +13 -0
  35. package/dist/clis/aggregate/workflow-scout.js +175 -0
  36. package/dist/clis/clinvar/search.d.ts +8 -0
  37. package/dist/clis/clinvar/search.js +61 -0
  38. package/dist/clis/clinvar/variant.d.ts +7 -0
  39. package/dist/clis/clinvar/variant.js +53 -0
  40. package/dist/clis/enrichr/analyze.d.ts +7 -0
  41. package/dist/clis/enrichr/analyze.js +48 -0
  42. package/dist/clis/ensembl/lookup.d.ts +6 -0
  43. package/dist/clis/ensembl/lookup.js +38 -0
  44. package/dist/clis/ensembl/vep.d.ts +7 -0
  45. package/dist/clis/ensembl/vep.js +86 -0
  46. package/dist/clis/ensembl/xrefs.d.ts +6 -0
  47. package/dist/clis/ensembl/xrefs.js +36 -0
  48. package/dist/clis/gene/fetch.d.ts +10 -0
  49. package/dist/clis/gene/fetch.js +96 -0
  50. package/dist/clis/gene/info.d.ts +7 -0
  51. package/dist/clis/gene/info.js +37 -0
  52. package/dist/clis/gene/search.d.ts +7 -0
  53. package/dist/clis/gene/search.js +71 -0
  54. package/dist/clis/geo/dataset.d.ts +7 -0
  55. package/dist/clis/geo/dataset.js +55 -0
  56. package/dist/clis/geo/download.d.ts +17 -0
  57. package/dist/clis/geo/download.js +115 -0
  58. package/dist/clis/geo/samples.d.ts +7 -0
  59. package/dist/clis/geo/samples.js +57 -0
  60. package/dist/clis/geo/search.d.ts +8 -0
  61. package/dist/clis/geo/search.js +66 -0
  62. package/dist/clis/kegg/convert.d.ts +7 -0
  63. package/dist/clis/kegg/convert.js +37 -0
  64. package/dist/clis/kegg/disease.d.ts +6 -0
  65. package/dist/clis/kegg/disease.js +57 -0
  66. package/dist/clis/kegg/link.d.ts +7 -0
  67. package/dist/clis/kegg/link.js +36 -0
  68. package/dist/clis/kegg/pathway.d.ts +6 -0
  69. package/dist/clis/kegg/pathway.js +37 -0
  70. package/dist/clis/pubmed/abstract.d.ts +7 -0
  71. package/dist/clis/pubmed/abstract.js +42 -0
  72. package/dist/clis/pubmed/cited-by.d.ts +7 -0
  73. package/dist/clis/pubmed/cited-by.js +77 -0
  74. package/dist/clis/pubmed/fetch.d.ts +6 -0
  75. package/dist/clis/pubmed/fetch.js +36 -0
  76. package/dist/clis/pubmed/info.yaml +22 -0
  77. package/dist/clis/pubmed/related.d.ts +7 -0
  78. package/dist/clis/pubmed/related.js +81 -0
  79. package/dist/clis/pubmed/search.d.ts +8 -0
  80. package/dist/clis/pubmed/search.js +63 -0
  81. package/dist/clis/snp/lookup.d.ts +7 -0
  82. package/dist/clis/snp/lookup.js +57 -0
  83. package/dist/clis/sra/download.d.ts +18 -0
  84. package/dist/clis/sra/download.js +217 -0
  85. package/dist/clis/sra/run.d.ts +8 -0
  86. package/dist/clis/sra/run.js +77 -0
  87. package/dist/clis/sra/search.d.ts +8 -0
  88. package/dist/clis/sra/search.js +83 -0
  89. package/dist/clis/string/enrichment.d.ts +7 -0
  90. package/dist/clis/string/enrichment.js +50 -0
  91. package/dist/clis/string/network.d.ts +7 -0
  92. package/dist/clis/string/network.js +47 -0
  93. package/dist/clis/string/partners.d.ts +4 -0
  94. package/dist/clis/string/partners.js +44 -0
  95. package/dist/clis/taxonomy/lookup.d.ts +8 -0
  96. package/dist/clis/taxonomy/lookup.js +54 -0
  97. package/dist/clis/uniprot/fetch.d.ts +7 -0
  98. package/dist/clis/uniprot/fetch.js +82 -0
  99. package/dist/clis/uniprot/search.d.ts +6 -0
  100. package/dist/clis/uniprot/search.js +65 -0
  101. package/dist/clis/uniprot/sequence.d.ts +7 -0
  102. package/dist/clis/uniprot/sequence.js +51 -0
  103. package/dist/commander-adapter.d.ts +27 -0
  104. package/dist/commander-adapter.js +286 -0
  105. package/dist/completion.d.ts +19 -0
  106. package/dist/completion.js +117 -0
  107. package/dist/config.d.ts +57 -0
  108. package/dist/config.js +94 -0
  109. package/dist/databases/enrichr.d.ts +28 -0
  110. package/dist/databases/enrichr.js +131 -0
  111. package/dist/databases/ensembl.d.ts +14 -0
  112. package/dist/databases/ensembl.js +106 -0
  113. package/dist/databases/index.d.ts +45 -0
  114. package/dist/databases/index.js +49 -0
  115. package/dist/databases/kegg.d.ts +26 -0
  116. package/dist/databases/kegg.js +136 -0
  117. package/dist/databases/ncbi.d.ts +28 -0
  118. package/dist/databases/ncbi.js +144 -0
  119. package/dist/databases/string-db.d.ts +19 -0
  120. package/dist/databases/string-db.js +105 -0
  121. package/dist/databases/uniprot.d.ts +13 -0
  122. package/dist/databases/uniprot.js +110 -0
  123. package/dist/discovery.d.ts +32 -0
  124. package/dist/discovery.js +235 -0
  125. package/dist/doctor.d.ts +19 -0
  126. package/dist/doctor.js +151 -0
  127. package/dist/errors.d.ts +68 -0
  128. package/dist/errors.js +105 -0
  129. package/dist/execution.d.ts +15 -0
  130. package/dist/execution.js +178 -0
  131. package/dist/hooks.d.ts +48 -0
  132. package/dist/hooks.js +58 -0
  133. package/dist/main.d.ts +13 -0
  134. package/dist/main.js +31 -0
  135. package/dist/ncbi-fetch.d.ts +10 -0
  136. package/dist/ncbi-fetch.js +10 -0
  137. package/dist/output.d.ts +18 -0
  138. package/dist/output.js +394 -0
  139. package/dist/pipeline/executor.d.ts +22 -0
  140. package/dist/pipeline/executor.js +40 -0
  141. package/dist/pipeline/index.d.ts +6 -0
  142. package/dist/pipeline/index.js +6 -0
  143. package/dist/pipeline/registry.d.ts +16 -0
  144. package/dist/pipeline/registry.js +31 -0
  145. package/dist/pipeline/steps/fetch.d.ts +21 -0
  146. package/dist/pipeline/steps/fetch.js +160 -0
  147. package/dist/pipeline/steps/transform.d.ts +26 -0
  148. package/dist/pipeline/steps/transform.js +92 -0
  149. package/dist/pipeline/steps/xml-parse.d.ts +12 -0
  150. package/dist/pipeline/steps/xml-parse.js +27 -0
  151. package/dist/pipeline/template.d.ts +35 -0
  152. package/dist/pipeline/template.js +312 -0
  153. package/dist/rate-limiter.d.ts +56 -0
  154. package/dist/rate-limiter.js +120 -0
  155. package/dist/registry-api.d.ts +15 -0
  156. package/dist/registry-api.js +13 -0
  157. package/dist/registry.d.ts +90 -0
  158. package/dist/registry.js +100 -0
  159. package/dist/schema.d.ts +80 -0
  160. package/dist/schema.js +72 -0
  161. package/dist/spinner.d.ts +19 -0
  162. package/dist/spinner.js +37 -0
  163. package/dist/types.d.ts +101 -0
  164. package/dist/types.js +27 -0
  165. package/dist/utils.d.ts +16 -0
  166. package/dist/utils.js +40 -0
  167. package/dist/validate.d.ts +29 -0
  168. package/dist/validate.js +136 -0
  169. package/dist/verify.d.ts +20 -0
  170. package/dist/verify.js +131 -0
  171. package/dist/version.d.ts +13 -0
  172. package/dist/version.js +36 -0
  173. package/dist/xml-parser.d.ts +19 -0
  174. package/dist/xml-parser.js +119 -0
  175. package/dist/yaml-schema.d.ts +40 -0
  176. package/dist/yaml-schema.js +62 -0
  177. package/package.json +68 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * CLI entry point: registers built-in commands and wires up Commander.
3
+ *
4
+ * Built-in commands are registered inline here (list, validate, config, etc.).
5
+ * Dynamic adapter commands are registered via commander-adapter.ts.
6
+ */
7
+ export declare function runCli(): void;
package/dist/cli.js ADDED
@@ -0,0 +1,336 @@
1
+ /**
2
+ * CLI entry point: registers built-in commands and wires up Commander.
3
+ *
4
+ * Built-in commands are registered inline here (list, validate, config, etc.).
5
+ * Dynamic adapter commands are registered via commander-adapter.ts.
6
+ */
7
+ import { Command } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { fullName, getRegistry, strategyLabel } from './registry.js';
10
+ import { render as renderOutput } from './output.js';
11
+ import { getVersion } from './version.js';
12
+ import { printCompletionScript, getCompletions } from './completion.js';
13
+ import { registerAllCommands } from './commander-adapter.js';
14
+ import { validateAll } from './validate.js';
15
+ import { runDoctor, formatDoctorText, formatDoctorJson } from './doctor.js';
16
+ import { biocliResultSchema, resultWithMetaSchema } from './schema.js';
17
+ import { runVerify, formatVerifyText, formatVerifyJson } from './verify.js';
18
+ import { loadConfig, saveConfig, getConfigPath } from './config.js';
19
+ import { getStats as getCacheStats, clearCache } from './cache.js';
20
+ import { BUILTIN_CLIS_DIR, USER_CLIS_DIR } from './discovery.js';
21
+ export function runCli() {
22
+ const program = new Command();
23
+ const version = getVersion();
24
+ program
25
+ .name('biocli')
26
+ .description('Query biological databases from the terminal')
27
+ .version(version)
28
+ .enablePositionalOptions()
29
+ .addHelpText('before', `
30
+ ${chalk.bold.cyan('biocli')} ${chalk.dim(`v${version}`)} ${chalk.dim('─')} Query biological databases from the terminal
31
+ ${chalk.dim('NCBI · UniProt · KEGG · STRING · Ensembl · Enrichr')}
32
+ `)
33
+ .addHelpText('after', `
34
+ ${chalk.bold('Examples:')}
35
+ ${chalk.cyan('biocli pubmed search "CRISPR cancer"')} Search PubMed articles
36
+ ${chalk.cyan('biocli pubmed fetch 30684591')} Get article details by PMID
37
+ ${chalk.cyan('biocli gene search TP53')} Search genes
38
+ ${chalk.cyan('biocli gene info 7157')} Gene details by ID
39
+ ${chalk.cyan('biocli geo search "breast cancer RNA-seq"')} Search GEO datasets
40
+ ${chalk.cyan('biocli sra search "CRISPR human"')} Search SRA runs
41
+ ${chalk.cyan('biocli clinvar search BRCA1')} Search clinical variants
42
+ ${chalk.cyan('biocli snp lookup rs334')} Look up SNP by rsID
43
+ ${chalk.cyan('biocli taxonomy lookup "Homo sapiens"')} Taxonomy lookup
44
+
45
+ ${chalk.bold('Output formats:')}
46
+ -f table ${chalk.dim('(default)')} -f json -f csv
47
+ -f yaml -f md -f plain
48
+
49
+ ${chalk.bold('Configuration:')}
50
+ ${chalk.cyan('biocli config set api_key YOUR_KEY')} Set NCBI API key (increases rate limit to 10 req/s)
51
+ ${chalk.cyan('biocli config set email you@example.com')}
52
+ ${chalk.cyan('biocli config show')} Show current config
53
+ `);
54
+ // ── Hidden: shell completion data endpoint ──────────────────────────────
55
+ program
56
+ .option('--get-completions', 'Internal: return completion candidates')
57
+ .option('--cursor <n>', 'Internal: cursor position for completions');
58
+ // ── Built-in: list ────────────────────────────────────────────────────────
59
+ program
60
+ .command('list')
61
+ .description('List all available CLI commands')
62
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table')
63
+ .option('--json', 'JSON output (shorthand)')
64
+ .action((opts) => {
65
+ const registry = getRegistry();
66
+ const commands = [...new Set(registry.values())].sort((a, b) => fullName(a).localeCompare(fullName(b)));
67
+ const fmt = opts.json && opts.format === 'table' ? 'json' : opts.format;
68
+ if (fmt !== 'table') {
69
+ const downloadNames = new Set(['download', 'fetch', 'sequence']);
70
+ const rows = commands.map(c => {
71
+ const tags = [];
72
+ if (c.database === 'aggregate')
73
+ tags.push('workflow');
74
+ else if (downloadNames.has(c.name))
75
+ tags.push('download');
76
+ else
77
+ tags.push('query');
78
+ return {
79
+ command: fullName(c),
80
+ site: c.site,
81
+ name: c.name,
82
+ aliases: c.aliases?.join(', ') ?? '',
83
+ description: c.description,
84
+ strategy: strategyLabel(c),
85
+ database: c.database ?? '',
86
+ args: c.args.map(a => ({
87
+ name: a.name,
88
+ ...(a.type ? { type: a.type } : {}),
89
+ ...(a.required ? { required: true } : {}),
90
+ ...(a.positional ? { positional: true } : {}),
91
+ ...(a.default !== undefined ? { default: a.default } : {}),
92
+ ...(a.choices ? { choices: a.choices } : {}),
93
+ ...(a.help ? { help: a.help } : {}),
94
+ })),
95
+ defaultFormat: c.defaultFormat ?? 'table',
96
+ columns: c.columns ?? [],
97
+ tags,
98
+ };
99
+ });
100
+ renderOutput(rows, {
101
+ fmt,
102
+ columns: ['command', 'site', 'name', 'aliases', 'description', 'strategy', 'database'],
103
+ title: 'biocli/list',
104
+ source: 'biocli list',
105
+ });
106
+ return;
107
+ }
108
+ // Table (default) — grouped by site
109
+ const sites = new Map();
110
+ for (const cmd of commands) {
111
+ const g = sites.get(cmd.site) ?? [];
112
+ g.push(cmd);
113
+ sites.set(cmd.site, g);
114
+ }
115
+ console.log();
116
+ console.log(chalk.bold(' biocli') + chalk.dim(' — available commands'));
117
+ console.log();
118
+ for (const [site, cmds] of sites) {
119
+ console.log(chalk.bold.cyan(` ${site}`));
120
+ for (const cmd of cmds) {
121
+ const label = strategyLabel(cmd);
122
+ const tag = label === 'public'
123
+ ? chalk.green('[public]')
124
+ : chalk.yellow(`[${label}]`);
125
+ const aliases = cmd.aliases?.length ? chalk.dim(` (aliases: ${cmd.aliases.join(', ')})`) : '';
126
+ const db = cmd.database ? chalk.dim(` [${cmd.database}]`) : '';
127
+ console.log(` ${cmd.name} ${tag}${db}${aliases}${cmd.description ? chalk.dim(` — ${cmd.description}`) : ''}`);
128
+ }
129
+ console.log();
130
+ }
131
+ console.log(chalk.dim(` ${commands.length} commands total`));
132
+ });
133
+ // ── Built-in: validate ────────────────────────────────────────────────────
134
+ program
135
+ .command('validate')
136
+ .description('Validate YAML adapter definitions')
137
+ .option('-d, --dir <path>', 'Directory to validate', BUILTIN_CLIS_DIR)
138
+ .action((opts) => {
139
+ const dirs = [opts.dir];
140
+ // Also check user CLIs if they exist
141
+ try {
142
+ const { existsSync } = require('node:fs');
143
+ if (existsSync(USER_CLIS_DIR))
144
+ dirs.push(USER_CLIS_DIR);
145
+ }
146
+ catch { /* ignore */ }
147
+ let totalErrors = 0;
148
+ for (const dir of dirs) {
149
+ const results = validateAll(dir);
150
+ if (results.length === 0) {
151
+ console.log(chalk.green(`All YAML definitions in ${dir} are valid.`));
152
+ continue;
153
+ }
154
+ for (const result of results) {
155
+ console.error(chalk.red(`\n${result.file}:`));
156
+ for (const error of result.errors) {
157
+ console.error(chalk.yellow(` - ${error}`));
158
+ }
159
+ totalErrors += result.errors.length;
160
+ }
161
+ }
162
+ if (totalErrors > 0) {
163
+ console.error(chalk.red(`\n${totalErrors} validation error(s) found.`));
164
+ process.exitCode = 1;
165
+ }
166
+ });
167
+ // ── Built-in: config ──────────────────────────────────────────────────────
168
+ const configCmd = program
169
+ .command('config')
170
+ .description('Manage biocli configuration');
171
+ configCmd
172
+ .command('show')
173
+ .description('Show current configuration')
174
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml', 'yaml')
175
+ .action((opts) => {
176
+ const config = loadConfig();
177
+ // Mask API key for display
178
+ const display = { ...config };
179
+ if (display.api_key) {
180
+ display.api_key = display.api_key.slice(0, 4) + '****' + display.api_key.slice(-4);
181
+ }
182
+ renderOutput([display], { fmt: opts.format, title: 'biocli/config' });
183
+ });
184
+ configCmd
185
+ .command('set <key> <value>')
186
+ .description('Set a configuration value (api_key, email, defaults.format, defaults.limit)')
187
+ .action((key, value) => {
188
+ const config = loadConfig();
189
+ if (key === 'api_key' || key === 'email') {
190
+ config[key] = value;
191
+ }
192
+ else if (key === 'defaults.format') {
193
+ config.defaults = config.defaults ?? {};
194
+ config.defaults.format = value;
195
+ }
196
+ else if (key === 'defaults.limit') {
197
+ config.defaults = config.defaults ?? {};
198
+ config.defaults.limit = parseInt(value, 10);
199
+ if (isNaN(config.defaults.limit)) {
200
+ console.error(chalk.red(`Invalid limit value: "${value}". Must be a number.`));
201
+ process.exitCode = 1;
202
+ return;
203
+ }
204
+ }
205
+ else if (key === 'cache.enabled') {
206
+ config.cache = config.cache ?? {};
207
+ config.cache.enabled = value.toLowerCase() === 'true' || value === '1';
208
+ }
209
+ else if (key === 'cache.ttl') {
210
+ config.cache = config.cache ?? {};
211
+ config.cache.ttl = parseInt(value, 10);
212
+ if (isNaN(config.cache.ttl) || config.cache.ttl <= 0) {
213
+ console.error(chalk.red(`Invalid cache TTL: "${value}". Must be a positive number (hours).`));
214
+ process.exitCode = 1;
215
+ return;
216
+ }
217
+ }
218
+ else {
219
+ console.error(chalk.red(`Unknown config key: "${key}".`));
220
+ console.error(chalk.dim('Valid keys: api_key, email, defaults.format, defaults.limit, cache.enabled, cache.ttl'));
221
+ process.exitCode = 1;
222
+ return;
223
+ }
224
+ saveConfig(config);
225
+ console.log(chalk.green(`Set ${key} = ${key === 'api_key' ? '****' : value}`));
226
+ });
227
+ configCmd
228
+ .command('path')
229
+ .description('Show configuration file path')
230
+ .action(() => {
231
+ console.log(getConfigPath());
232
+ });
233
+ // ── Built-in: completion ──────────────────────────────────────────────────
234
+ program
235
+ .command('completion [shell]')
236
+ .description('Output shell completion script (bash, zsh, fish)')
237
+ .action((shell) => {
238
+ printCompletionScript(shell ?? 'bash');
239
+ });
240
+ // ── Built-in: schema ───────────────────────────────────────────────────────
241
+ program
242
+ .command('schema [type]')
243
+ .description('Output JSON Schema for biocli result types (default: result, or: meta)')
244
+ .action((type) => {
245
+ const schema = type === 'meta' ? resultWithMetaSchema : biocliResultSchema;
246
+ console.log(JSON.stringify(schema, null, 2));
247
+ });
248
+ // ── Built-in: doctor ───────────────────────────────────────────────────────
249
+ program
250
+ .command('doctor')
251
+ .description('Diagnose biocli configuration and backend connectivity')
252
+ .option('-f, --format <fmt>', 'Output format: text, json', 'text')
253
+ .action(async (opts) => {
254
+ const { checks, allPassed } = await runDoctor();
255
+ if (opts.format === 'json') {
256
+ console.log(formatDoctorJson(checks, allPassed));
257
+ }
258
+ else {
259
+ console.log(formatDoctorText(checks, allPassed));
260
+ }
261
+ if (!allPassed)
262
+ process.exitCode = 1;
263
+ });
264
+ // ── Built-in: verify ───────────────────────────────────────────────────────
265
+ program
266
+ .command('verify')
267
+ .description('Run all checks: validate + doctor (+ optional smoke)')
268
+ .option('--smoke', 'Also run core smoke tests')
269
+ .option('-f, --format <fmt>', 'Output format: text, json', 'text')
270
+ .action(async (opts) => {
271
+ const result = await runVerify({ smoke: opts.smoke });
272
+ if (opts.format === 'json') {
273
+ console.log(formatVerifyJson(result));
274
+ }
275
+ else {
276
+ console.log(formatVerifyText(result));
277
+ }
278
+ if (!result.allPassed)
279
+ process.exitCode = 1;
280
+ });
281
+ // ── Built-in: cache ────────────────────────────────────────────────────────
282
+ const cacheCmd = program
283
+ .command('cache')
284
+ .description('Manage local response cache');
285
+ cacheCmd
286
+ .command('stats')
287
+ .description('Show cache statistics')
288
+ .option('-f, --format <fmt>', 'Output format: text, json', 'text')
289
+ .action((opts) => {
290
+ const stats = getCacheStats();
291
+ if (opts.format === 'json') {
292
+ console.log(JSON.stringify(stats, null, 2));
293
+ }
294
+ else {
295
+ console.log();
296
+ console.log(chalk.bold(' biocli cache stats'));
297
+ console.log();
298
+ console.log(` Entries ${stats.totalEntries}`);
299
+ console.log(` Size ${(stats.totalSizeBytes / 1024).toFixed(1)} KB`);
300
+ if (stats.oldestEntry)
301
+ console.log(` Oldest ${stats.oldestEntry}`);
302
+ if (stats.newestEntry)
303
+ console.log(` Newest ${stats.newestEntry}`);
304
+ if (Object.keys(stats.databases).length > 0) {
305
+ console.log(` Databases ${Object.entries(stats.databases).map(([k, v]) => `${k}(${v})`).join(', ')}`);
306
+ }
307
+ console.log();
308
+ }
309
+ });
310
+ cacheCmd
311
+ .command('clear')
312
+ .description('Clear all cached responses')
313
+ .action(() => {
314
+ const count = clearCache();
315
+ console.log(chalk.green(`Cleared ${count} cache entries.`));
316
+ });
317
+ // ── Register dynamic adapter commands ─────────────────────────────────────
318
+ registerAllCommands(program);
319
+ // ── Handle hidden --get-completions flag ─────────────────────────────────
320
+ const rawArgs = process.argv.slice(2);
321
+ if (rawArgs.includes('--get-completions')) {
322
+ const cursorIdx = rawArgs.indexOf('--cursor');
323
+ const cursor = cursorIdx >= 0 ? parseInt(rawArgs[cursorIdx + 1], 10) : 1;
324
+ const words = rawArgs.filter((a, i) => a !== '--get-completions' && i !== cursorIdx && i !== cursorIdx + 1);
325
+ const candidates = getCompletions(words, cursor);
326
+ if (candidates.length > 0) {
327
+ process.stdout.write(candidates.join('\n') + '\n');
328
+ }
329
+ return;
330
+ }
331
+ // ── Parse and execute ────────────────────────────────────────────────────���
332
+ program.parseAsync(process.argv).catch((err) => {
333
+ console.error(chalk.red(err instanceof Error ? err.message : String(err)));
334
+ process.exitCode = 1;
335
+ });
336
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Common utility functions shared across NCBI adapter commands.
3
+ */
4
+ export { clamp } from '../../utils.js';
5
+ /**
6
+ * Truncate a string to `maxLen` characters, appending '...' if trimmed.
7
+ */
8
+ export declare function truncate(text: string, maxLen: number): string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Common utility functions shared across NCBI adapter commands.
3
+ */
4
+ // Re-export clamp from the core utils module.
5
+ export { clamp } from '../../utils.js';
6
+ /**
7
+ * Truncate a string to `maxLen` characters, appending '...' if trimmed.
8
+ */
9
+ export function truncate(text, maxLen) {
10
+ if (text.length <= maxLen)
11
+ return text;
12
+ return text.slice(0, maxLen - 3) + '...';
13
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared E-utilities helpers for NCBI adapter commands.
3
+ *
4
+ * Re-exports the core buildEutilsUrl from ncbi-fetch so that adapter
5
+ * files can import from a short, consistent path:
6
+ *
7
+ * import { buildEutilsUrl, EUTILS_BASE } from '../_shared/eutils.js';
8
+ */
9
+ export { EUTILS_BASE, buildEutilsUrl } from '../../ncbi-fetch.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared E-utilities helpers for NCBI adapter commands.
3
+ *
4
+ * Re-exports the core buildEutilsUrl from ncbi-fetch so that adapter
5
+ * files can import from a short, consistent path:
6
+ *
7
+ * import { buildEutilsUrl, EUTILS_BASE } from '../_shared/eutils.js';
8
+ */
9
+ export { EUTILS_BASE, buildEutilsUrl } from '../../ncbi-fetch.js';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Unified organism database for cross-database ID resolution.
3
+ *
4
+ * Maps common organism names to identifiers used by each database:
5
+ * - NCBI taxonomy name and ID
6
+ * - KEGG organism code
7
+ * - Ensembl species name
8
+ */
9
+ export interface OrganismEntry {
10
+ name: string;
11
+ taxId: number;
12
+ keggOrg: string;
13
+ ensemblName: string;
14
+ }
15
+ export declare const ORGANISM_DB: Record<string, OrganismEntry>;
16
+ /**
17
+ * Resolve an organism identifier (common name, scientific name, taxId, or KEGG code)
18
+ * to a full OrganismEntry.
19
+ *
20
+ * Throws if the organism is not recognized — never silently defaults to human,
21
+ * because that would produce scientifically wrong cross-database queries.
22
+ */
23
+ export declare function resolveOrganism(input: string): OrganismEntry;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Unified organism database for cross-database ID resolution.
3
+ *
4
+ * Maps common organism names to identifiers used by each database:
5
+ * - NCBI taxonomy name and ID
6
+ * - KEGG organism code
7
+ * - Ensembl species name
8
+ */
9
+ import { ArgumentError } from '../../errors.js';
10
+ export const ORGANISM_DB = {
11
+ human: { name: 'Homo sapiens', taxId: 9606, keggOrg: 'hsa', ensemblName: 'homo_sapiens' },
12
+ mouse: { name: 'Mus musculus', taxId: 10090, keggOrg: 'mmu', ensemblName: 'mus_musculus' },
13
+ rat: { name: 'Rattus norvegicus', taxId: 10116, keggOrg: 'rno', ensemblName: 'rattus_norvegicus' },
14
+ zebrafish: { name: 'Danio rerio', taxId: 7955, keggOrg: 'dre', ensemblName: 'danio_rerio' },
15
+ fly: { name: 'Drosophila melanogaster', taxId: 7227, keggOrg: 'dme', ensemblName: 'drosophila_melanogaster' },
16
+ worm: { name: 'Caenorhabditis elegans', taxId: 6239, keggOrg: 'cel', ensemblName: 'caenorhabditis_elegans' },
17
+ yeast: { name: 'Saccharomyces cerevisiae', taxId: 4932, keggOrg: 'sce', ensemblName: 'saccharomyces_cerevisiae' },
18
+ chicken: { name: 'Gallus gallus', taxId: 9031, keggOrg: 'gga', ensemblName: 'gallus_gallus' },
19
+ dog: { name: 'Canis lupus familiaris', taxId: 9615, keggOrg: 'cfa', ensemblName: 'canis_lupus_familiaris' },
20
+ pig: { name: 'Sus scrofa', taxId: 9823, keggOrg: 'ssc', ensemblName: 'sus_scrofa' },
21
+ cow: { name: 'Bos taurus', taxId: 9913, keggOrg: 'bta', ensemblName: 'bos_taurus' },
22
+ rabbit: { name: 'Oryctolagus cuniculus', taxId: 9986, keggOrg: 'ocu', ensemblName: 'oryctolagus_cuniculus' },
23
+ frog: { name: 'Xenopus tropicalis', taxId: 8364, keggOrg: 'xtr', ensemblName: 'xenopus_tropicalis' },
24
+ };
25
+ /**
26
+ * Resolve an organism identifier (common name, scientific name, taxId, or KEGG code)
27
+ * to a full OrganismEntry.
28
+ *
29
+ * Throws if the organism is not recognized — never silently defaults to human,
30
+ * because that would produce scientifically wrong cross-database queries.
31
+ */
32
+ export function resolveOrganism(input) {
33
+ const lower = input.toLowerCase().trim();
34
+ // Direct match by common name
35
+ if (ORGANISM_DB[lower])
36
+ return ORGANISM_DB[lower];
37
+ // Match by scientific name
38
+ for (const entry of Object.values(ORGANISM_DB)) {
39
+ if (entry.name.toLowerCase() === lower)
40
+ return entry;
41
+ }
42
+ // Match by taxId
43
+ const taxId = parseInt(lower, 10);
44
+ if (!isNaN(taxId)) {
45
+ for (const entry of Object.values(ORGANISM_DB)) {
46
+ if (entry.taxId === taxId)
47
+ return entry;
48
+ }
49
+ }
50
+ // Match by KEGG org code
51
+ for (const entry of Object.values(ORGANISM_DB)) {
52
+ if (entry.keggOrg === lower)
53
+ return entry;
54
+ }
55
+ // Unknown organism — fail as argument error (exit code 2, not generic error)
56
+ const supported = Object.keys(ORGANISM_DB).join(', ');
57
+ throw new ArgumentError(`Unknown organism: "${input}"`, `Supported: ${supported}. Also accepts scientific names (e.g. "Homo sapiens"), taxonomy IDs (e.g. 9606), or KEGG codes (e.g. hsa).`);
58
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * PubMed & Gene XML parsing helpers.
3
+ *
4
+ * After fast-xml-parser processes NCBI XML (see xml-parser.ts), the
5
+ * result is a deeply nested JS object. These helpers navigate that
6
+ * structure and return flat, typed records suitable for CLI table output.
7
+ *
8
+ * NOTE: The xml-parser config uses:
9
+ * - '@_' prefix for attributes (e.g. @_EIdType)
10
+ * - '#text' for text nodes
11
+ * - Tags listed in ARRAY_TAGS are always arrays (Author, PubmedArticle, etc.)
12
+ */
13
+ export interface PubmedArticle {
14
+ pmid: string;
15
+ title: string;
16
+ authors: string;
17
+ journal: string;
18
+ year: string;
19
+ doi: string;
20
+ abstract: string;
21
+ }
22
+ /**
23
+ * Parse a PubMed efetch XML response (after fast-xml-parser processing)
24
+ * into an array of PubmedArticle records.
25
+ */
26
+ export declare function parsePubmedArticles(parsed: unknown): PubmedArticle[];
27
+ export interface GeneInfo {
28
+ geneId: string;
29
+ symbol: string;
30
+ name: string;
31
+ organism: string;
32
+ summary: string;
33
+ chromosome: string;
34
+ location: string;
35
+ }
36
+ /**
37
+ * Parse Gene esummary JSON response into GeneInfo records.
38
+ *
39
+ * The esummary JSON for the gene database has the structure:
40
+ * {
41
+ * result: {
42
+ * uids: ["7157", ...],
43
+ * "7157": { uid: "7157", name: "TP53", description: "...", ... }
44
+ * }
45
+ * }
46
+ */
47
+ export declare function parseGeneSummaries(parsed: unknown): GeneInfo[];
48
+ /**
49
+ * Parse Gene efetch XML response (Entrezgene-Set) into GeneInfo records.
50
+ *
51
+ * Gene efetch XML has the structure:
52
+ * Entrezgene-Set > Entrezgene[] > Entrezgene_track-info > Gene-track > Gene-track_geneid
53
+ * etc.
54
+ *
55
+ * This is considerably more complex than esummary, so we prefer esummary
56
+ * for most gene commands. This parser is provided for completeness.
57
+ */
58
+ export declare function parseGeneEntries(parsed: unknown): GeneInfo[];