@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.
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/batch.d.ts +20 -0
- package/dist/batch.js +69 -0
- package/dist/build-manifest.d.ts +38 -0
- package/dist/build-manifest.js +186 -0
- package/dist/cache.d.ts +28 -0
- package/dist/cache.js +126 -0
- package/dist/cli-manifest.json +1500 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +336 -0
- package/dist/clis/_shared/common.d.ts +8 -0
- package/dist/clis/_shared/common.js +13 -0
- package/dist/clis/_shared/eutils.d.ts +9 -0
- package/dist/clis/_shared/eutils.js +9 -0
- package/dist/clis/_shared/organism-db.d.ts +23 -0
- package/dist/clis/_shared/organism-db.js +58 -0
- package/dist/clis/_shared/xml-helpers.d.ts +58 -0
- package/dist/clis/_shared/xml-helpers.js +266 -0
- package/dist/clis/aggregate/enrichment.d.ts +7 -0
- package/dist/clis/aggregate/enrichment.js +105 -0
- package/dist/clis/aggregate/gene-dossier.d.ts +13 -0
- package/dist/clis/aggregate/gene-dossier.js +248 -0
- package/dist/clis/aggregate/gene-profile.d.ts +16 -0
- package/dist/clis/aggregate/gene-profile.js +305 -0
- package/dist/clis/aggregate/literature-brief.d.ts +7 -0
- package/dist/clis/aggregate/literature-brief.js +79 -0
- package/dist/clis/aggregate/variant-dossier.d.ts +11 -0
- package/dist/clis/aggregate/variant-dossier.js +161 -0
- package/dist/clis/aggregate/variant-interpret.d.ts +10 -0
- package/dist/clis/aggregate/variant-interpret.js +210 -0
- package/dist/clis/aggregate/workflow-prepare.d.ts +12 -0
- package/dist/clis/aggregate/workflow-prepare.js +228 -0
- package/dist/clis/aggregate/workflow-scout.d.ts +13 -0
- package/dist/clis/aggregate/workflow-scout.js +175 -0
- package/dist/clis/clinvar/search.d.ts +8 -0
- package/dist/clis/clinvar/search.js +61 -0
- package/dist/clis/clinvar/variant.d.ts +7 -0
- package/dist/clis/clinvar/variant.js +53 -0
- package/dist/clis/enrichr/analyze.d.ts +7 -0
- package/dist/clis/enrichr/analyze.js +48 -0
- package/dist/clis/ensembl/lookup.d.ts +6 -0
- package/dist/clis/ensembl/lookup.js +38 -0
- package/dist/clis/ensembl/vep.d.ts +7 -0
- package/dist/clis/ensembl/vep.js +86 -0
- package/dist/clis/ensembl/xrefs.d.ts +6 -0
- package/dist/clis/ensembl/xrefs.js +36 -0
- package/dist/clis/gene/fetch.d.ts +10 -0
- package/dist/clis/gene/fetch.js +96 -0
- package/dist/clis/gene/info.d.ts +7 -0
- package/dist/clis/gene/info.js +37 -0
- package/dist/clis/gene/search.d.ts +7 -0
- package/dist/clis/gene/search.js +71 -0
- package/dist/clis/geo/dataset.d.ts +7 -0
- package/dist/clis/geo/dataset.js +55 -0
- package/dist/clis/geo/download.d.ts +17 -0
- package/dist/clis/geo/download.js +115 -0
- package/dist/clis/geo/samples.d.ts +7 -0
- package/dist/clis/geo/samples.js +57 -0
- package/dist/clis/geo/search.d.ts +8 -0
- package/dist/clis/geo/search.js +66 -0
- package/dist/clis/kegg/convert.d.ts +7 -0
- package/dist/clis/kegg/convert.js +37 -0
- package/dist/clis/kegg/disease.d.ts +6 -0
- package/dist/clis/kegg/disease.js +57 -0
- package/dist/clis/kegg/link.d.ts +7 -0
- package/dist/clis/kegg/link.js +36 -0
- package/dist/clis/kegg/pathway.d.ts +6 -0
- package/dist/clis/kegg/pathway.js +37 -0
- package/dist/clis/pubmed/abstract.d.ts +7 -0
- package/dist/clis/pubmed/abstract.js +42 -0
- package/dist/clis/pubmed/cited-by.d.ts +7 -0
- package/dist/clis/pubmed/cited-by.js +77 -0
- package/dist/clis/pubmed/fetch.d.ts +6 -0
- package/dist/clis/pubmed/fetch.js +36 -0
- package/dist/clis/pubmed/info.yaml +22 -0
- package/dist/clis/pubmed/related.d.ts +7 -0
- package/dist/clis/pubmed/related.js +81 -0
- package/dist/clis/pubmed/search.d.ts +8 -0
- package/dist/clis/pubmed/search.js +63 -0
- package/dist/clis/snp/lookup.d.ts +7 -0
- package/dist/clis/snp/lookup.js +57 -0
- package/dist/clis/sra/download.d.ts +18 -0
- package/dist/clis/sra/download.js +217 -0
- package/dist/clis/sra/run.d.ts +8 -0
- package/dist/clis/sra/run.js +77 -0
- package/dist/clis/sra/search.d.ts +8 -0
- package/dist/clis/sra/search.js +83 -0
- package/dist/clis/string/enrichment.d.ts +7 -0
- package/dist/clis/string/enrichment.js +50 -0
- package/dist/clis/string/network.d.ts +7 -0
- package/dist/clis/string/network.js +47 -0
- package/dist/clis/string/partners.d.ts +4 -0
- package/dist/clis/string/partners.js +44 -0
- package/dist/clis/taxonomy/lookup.d.ts +8 -0
- package/dist/clis/taxonomy/lookup.js +54 -0
- package/dist/clis/uniprot/fetch.d.ts +7 -0
- package/dist/clis/uniprot/fetch.js +82 -0
- package/dist/clis/uniprot/search.d.ts +6 -0
- package/dist/clis/uniprot/search.js +65 -0
- package/dist/clis/uniprot/sequence.d.ts +7 -0
- package/dist/clis/uniprot/sequence.js +51 -0
- package/dist/commander-adapter.d.ts +27 -0
- package/dist/commander-adapter.js +286 -0
- package/dist/completion.d.ts +19 -0
- package/dist/completion.js +117 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.js +94 -0
- package/dist/databases/enrichr.d.ts +28 -0
- package/dist/databases/enrichr.js +131 -0
- package/dist/databases/ensembl.d.ts +14 -0
- package/dist/databases/ensembl.js +106 -0
- package/dist/databases/index.d.ts +45 -0
- package/dist/databases/index.js +49 -0
- package/dist/databases/kegg.d.ts +26 -0
- package/dist/databases/kegg.js +136 -0
- package/dist/databases/ncbi.d.ts +28 -0
- package/dist/databases/ncbi.js +144 -0
- package/dist/databases/string-db.d.ts +19 -0
- package/dist/databases/string-db.js +105 -0
- package/dist/databases/uniprot.d.ts +13 -0
- package/dist/databases/uniprot.js +110 -0
- package/dist/discovery.d.ts +32 -0
- package/dist/discovery.js +235 -0
- package/dist/doctor.d.ts +19 -0
- package/dist/doctor.js +151 -0
- package/dist/errors.d.ts +68 -0
- package/dist/errors.js +105 -0
- package/dist/execution.d.ts +15 -0
- package/dist/execution.js +178 -0
- package/dist/hooks.d.ts +48 -0
- package/dist/hooks.js +58 -0
- package/dist/main.d.ts +13 -0
- package/dist/main.js +31 -0
- package/dist/ncbi-fetch.d.ts +10 -0
- package/dist/ncbi-fetch.js +10 -0
- package/dist/output.d.ts +18 -0
- package/dist/output.js +394 -0
- package/dist/pipeline/executor.d.ts +22 -0
- package/dist/pipeline/executor.js +40 -0
- package/dist/pipeline/index.d.ts +6 -0
- package/dist/pipeline/index.js +6 -0
- package/dist/pipeline/registry.d.ts +16 -0
- package/dist/pipeline/registry.js +31 -0
- package/dist/pipeline/steps/fetch.d.ts +21 -0
- package/dist/pipeline/steps/fetch.js +160 -0
- package/dist/pipeline/steps/transform.d.ts +26 -0
- package/dist/pipeline/steps/transform.js +92 -0
- package/dist/pipeline/steps/xml-parse.d.ts +12 -0
- package/dist/pipeline/steps/xml-parse.js +27 -0
- package/dist/pipeline/template.d.ts +35 -0
- package/dist/pipeline/template.js +312 -0
- package/dist/rate-limiter.d.ts +56 -0
- package/dist/rate-limiter.js +120 -0
- package/dist/registry-api.d.ts +15 -0
- package/dist/registry-api.js +13 -0
- package/dist/registry.d.ts +90 -0
- package/dist/registry.js +100 -0
- package/dist/schema.d.ts +80 -0
- package/dist/schema.js +72 -0
- package/dist/spinner.d.ts +19 -0
- package/dist/spinner.js +37 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +27 -0
- package/dist/utils.d.ts +16 -0
- package/dist/utils.js +40 -0
- package/dist/validate.d.ts +29 -0
- package/dist/validate.js +136 -0
- package/dist/verify.d.ts +20 -0
- package/dist/verify.js +131 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +36 -0
- package/dist/xml-parser.d.ts +19 -0
- package/dist/xml-parser.js +119 -0
- package/dist/yaml-schema.d.ts +40 -0
- package/dist/yaml-schema.js +62 -0
- package/package.json +68 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding-window rate limiter for biocli database backends.
|
|
3
|
+
*
|
|
4
|
+
* Each database has its own rate limit:
|
|
5
|
+
* - NCBI: 3/s (anonymous), 10/s (with API key)
|
|
6
|
+
* - UniProt: 50/s
|
|
7
|
+
* - KEGG: 10/s
|
|
8
|
+
* - STRING: 1/s
|
|
9
|
+
* - Ensembl: 15/s
|
|
10
|
+
* - Enrichr: 5/s
|
|
11
|
+
*
|
|
12
|
+
* Singletons are maintained via globalThis so that all code paths
|
|
13
|
+
* share the same limiter instances (including across npm-linked plugins).
|
|
14
|
+
*/
|
|
15
|
+
export declare class RateLimiter {
|
|
16
|
+
private maxPerSecond;
|
|
17
|
+
/** Timestamps (ms) of requests within the current 1-second window. */
|
|
18
|
+
private timestamps;
|
|
19
|
+
/** Queued resolve callbacks waiting for a slot. */
|
|
20
|
+
private queue;
|
|
21
|
+
/** Whether a drain loop is already scheduled. */
|
|
22
|
+
private draining;
|
|
23
|
+
constructor(maxPerSecond: number);
|
|
24
|
+
/**
|
|
25
|
+
* Wait until a request slot is available, then record the timestamp.
|
|
26
|
+
* Callers should `await limiter.acquire()` before each HTTP request.
|
|
27
|
+
*/
|
|
28
|
+
acquire(): Promise<void>;
|
|
29
|
+
/** Update the rate limit (e.g. when an API key is added mid-session). */
|
|
30
|
+
setRate(maxPerSecond: number): void;
|
|
31
|
+
/** Remove timestamps older than 1 second from the window. */
|
|
32
|
+
private pruneOldTimestamps;
|
|
33
|
+
/** Start a drain loop that services the queue as slots open up. */
|
|
34
|
+
private scheduleDrain;
|
|
35
|
+
}
|
|
36
|
+
declare global {
|
|
37
|
+
var __biocli_rate_limiters__: Map<string, RateLimiter> | undefined;
|
|
38
|
+
var __ncbicli_rate_limiter__: RateLimiter | undefined;
|
|
39
|
+
var __ncbicli_rate_limiter_has_key__: boolean | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get (or create) a rate limiter for a specific database.
|
|
43
|
+
*
|
|
44
|
+
* Each database gets its own independent limiter instance keyed by ID.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getRateLimiterForDatabase(databaseId: string, maxPerSecond: number): RateLimiter;
|
|
47
|
+
/**
|
|
48
|
+
* Get (or create) the NCBI rate limiter.
|
|
49
|
+
*
|
|
50
|
+
* @deprecated Use getRateLimiterForDatabase('ncbi', rate) instead.
|
|
51
|
+
* Kept for backward compatibility with existing NCBI adapters.
|
|
52
|
+
*
|
|
53
|
+
* @param hasApiKey Whether the user has an NCBI API key configured.
|
|
54
|
+
* This determines the rate: 10/s with key, 3/s without.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getRateLimiter(hasApiKey: boolean): RateLimiter;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding-window rate limiter for biocli database backends.
|
|
3
|
+
*
|
|
4
|
+
* Each database has its own rate limit:
|
|
5
|
+
* - NCBI: 3/s (anonymous), 10/s (with API key)
|
|
6
|
+
* - UniProt: 50/s
|
|
7
|
+
* - KEGG: 10/s
|
|
8
|
+
* - STRING: 1/s
|
|
9
|
+
* - Ensembl: 15/s
|
|
10
|
+
* - Enrichr: 5/s
|
|
11
|
+
*
|
|
12
|
+
* Singletons are maintained via globalThis so that all code paths
|
|
13
|
+
* share the same limiter instances (including across npm-linked plugins).
|
|
14
|
+
*/
|
|
15
|
+
import { sleep } from './utils.js';
|
|
16
|
+
// ── RateLimiter class ────────────────────────────────────────────────────────
|
|
17
|
+
export class RateLimiter {
|
|
18
|
+
maxPerSecond;
|
|
19
|
+
/** Timestamps (ms) of requests within the current 1-second window. */
|
|
20
|
+
timestamps = [];
|
|
21
|
+
/** Queued resolve callbacks waiting for a slot. */
|
|
22
|
+
queue = [];
|
|
23
|
+
/** Whether a drain loop is already scheduled. */
|
|
24
|
+
draining = false;
|
|
25
|
+
constructor(maxPerSecond) {
|
|
26
|
+
this.maxPerSecond = maxPerSecond;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Wait until a request slot is available, then record the timestamp.
|
|
30
|
+
* Callers should `await limiter.acquire()` before each HTTP request.
|
|
31
|
+
*/
|
|
32
|
+
async acquire() {
|
|
33
|
+
this.pruneOldTimestamps();
|
|
34
|
+
if (this.timestamps.length < this.maxPerSecond) {
|
|
35
|
+
// Slot available immediately
|
|
36
|
+
this.timestamps.push(Date.now());
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// No slot available — enqueue and wait
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
this.queue.push(resolve);
|
|
42
|
+
this.scheduleDrain();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Update the rate limit (e.g. when an API key is added mid-session). */
|
|
46
|
+
setRate(maxPerSecond) {
|
|
47
|
+
this.maxPerSecond = maxPerSecond;
|
|
48
|
+
}
|
|
49
|
+
// ── Internal helpers ────────────────────────────────────────────────────
|
|
50
|
+
/** Remove timestamps older than 1 second from the window. */
|
|
51
|
+
pruneOldTimestamps() {
|
|
52
|
+
const cutoff = Date.now() - 1000;
|
|
53
|
+
while (this.timestamps.length > 0 && this.timestamps[0] <= cutoff) {
|
|
54
|
+
this.timestamps.shift();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Start a drain loop that services the queue as slots open up. */
|
|
58
|
+
scheduleDrain() {
|
|
59
|
+
if (this.draining)
|
|
60
|
+
return;
|
|
61
|
+
this.draining = true;
|
|
62
|
+
const drain = async () => {
|
|
63
|
+
while (this.queue.length > 0) {
|
|
64
|
+
this.pruneOldTimestamps();
|
|
65
|
+
if (this.timestamps.length < this.maxPerSecond) {
|
|
66
|
+
// A slot opened up — release the next waiter
|
|
67
|
+
this.timestamps.push(Date.now());
|
|
68
|
+
const next = this.queue.shift();
|
|
69
|
+
if (next)
|
|
70
|
+
next();
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Calculate how long until the oldest timestamp expires
|
|
74
|
+
const oldest = this.timestamps[0];
|
|
75
|
+
const waitMs = Math.max(1, (oldest + 1000) - Date.now() + 1);
|
|
76
|
+
await sleep(waitMs);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
this.draining = false;
|
|
80
|
+
};
|
|
81
|
+
// Fire-and-forget — errors here are programming bugs, not user-facing
|
|
82
|
+
drain().catch(() => { this.draining = false; });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const _limiters = globalThis.__biocli_rate_limiters__ ??= new Map();
|
|
86
|
+
/**
|
|
87
|
+
* Get (or create) a rate limiter for a specific database.
|
|
88
|
+
*
|
|
89
|
+
* Each database gets its own independent limiter instance keyed by ID.
|
|
90
|
+
*/
|
|
91
|
+
export function getRateLimiterForDatabase(databaseId, maxPerSecond) {
|
|
92
|
+
let limiter = _limiters.get(databaseId);
|
|
93
|
+
if (!limiter) {
|
|
94
|
+
limiter = new RateLimiter(maxPerSecond);
|
|
95
|
+
_limiters.set(databaseId, limiter);
|
|
96
|
+
}
|
|
97
|
+
return limiter;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get (or create) the NCBI rate limiter.
|
|
101
|
+
*
|
|
102
|
+
* @deprecated Use getRateLimiterForDatabase('ncbi', rate) instead.
|
|
103
|
+
* Kept for backward compatibility with existing NCBI adapters.
|
|
104
|
+
*
|
|
105
|
+
* @param hasApiKey Whether the user has an NCBI API key configured.
|
|
106
|
+
* This determines the rate: 10/s with key, 3/s without.
|
|
107
|
+
*/
|
|
108
|
+
export function getRateLimiter(hasApiKey) {
|
|
109
|
+
const rate = hasApiKey ? 10 : 3;
|
|
110
|
+
// Delegate to the per-database registry
|
|
111
|
+
const limiter = getRateLimiterForDatabase('ncbi', rate);
|
|
112
|
+
// If the API key status changed, update the rate
|
|
113
|
+
if (globalThis.__ncbicli_rate_limiter_has_key__ !== hasApiKey) {
|
|
114
|
+
limiter.setRate(rate);
|
|
115
|
+
globalThis.__ncbicli_rate_limiter_has_key__ = hasApiKey;
|
|
116
|
+
}
|
|
117
|
+
// Keep legacy global reference in sync
|
|
118
|
+
globalThis.__ncbicli_rate_limiter__ = limiter;
|
|
119
|
+
return limiter;
|
|
120
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @experimental This API is pre-v1.0 and may change without notice.
|
|
3
|
+
* See PLUGIN_DEV.md and docs/decisions/001-plugin-ecosystem.md.
|
|
4
|
+
*
|
|
5
|
+
* Public API re-exports for plugin authors and external consumers.
|
|
6
|
+
*
|
|
7
|
+
* Import from '@biocli/cli/registry' to register commands:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { cli, Strategy } from '@biocli/cli/registry';
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
|
|
14
|
+
export type { CliCommand, Arg, CliOptions, CommandArgs } from './registry.js';
|
|
15
|
+
export type { HttpContext, NcbiFetchOptions } from './types.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @experimental This API is pre-v1.0 and may change without notice.
|
|
3
|
+
* See PLUGIN_DEV.md and docs/decisions/001-plugin-ecosystem.md.
|
|
4
|
+
*
|
|
5
|
+
* Public API re-exports for plugin authors and external consumers.
|
|
6
|
+
*
|
|
7
|
+
* Import from '@biocli/cli/registry' to register commands:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { cli, Strategy } from '@biocli/cli/registry';
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core registry: Strategy enum, Arg/CliCommand interfaces, cli() registration.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from opencli's registry.ts for NCBI public API access only.
|
|
5
|
+
* No browser, no cookie/header/intercept/UI strategies — just PUBLIC and API_KEY.
|
|
6
|
+
*/
|
|
7
|
+
import type { HttpContext } from './types.js';
|
|
8
|
+
export declare enum Strategy {
|
|
9
|
+
/** No authentication needed — anonymous NCBI E-utilities access. */
|
|
10
|
+
PUBLIC = "public",
|
|
11
|
+
/** Requires an NCBI API key for higher rate limits or restricted endpoints. */
|
|
12
|
+
API_KEY = "api_key"
|
|
13
|
+
}
|
|
14
|
+
export interface Arg {
|
|
15
|
+
name: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
default?: unknown;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
positional?: boolean;
|
|
20
|
+
help?: string;
|
|
21
|
+
choices?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface RequiredEnv {
|
|
24
|
+
name: string;
|
|
25
|
+
help?: string;
|
|
26
|
+
}
|
|
27
|
+
export type CommandArgs = Record<string, any>;
|
|
28
|
+
export interface CliCommand {
|
|
29
|
+
site: string;
|
|
30
|
+
name: string;
|
|
31
|
+
aliases?: string[];
|
|
32
|
+
description: string;
|
|
33
|
+
/** NCBI database this command targets (e.g. 'pubmed', 'gene', 'geo'). */
|
|
34
|
+
database?: string;
|
|
35
|
+
strategy?: Strategy;
|
|
36
|
+
args: Arg[];
|
|
37
|
+
columns?: string[];
|
|
38
|
+
func?: (ctx: HttpContext, kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
|
|
39
|
+
pipeline?: Record<string, unknown>[];
|
|
40
|
+
timeoutSeconds?: number;
|
|
41
|
+
/** Origin of this command: 'yaml', 'ts', or plugin name. */
|
|
42
|
+
source?: string;
|
|
43
|
+
requiredEnv?: RequiredEnv[];
|
|
44
|
+
/** Deprecation note shown in help / execution warnings. */
|
|
45
|
+
deprecated?: boolean | string;
|
|
46
|
+
/** Preferred replacement command, if any. */
|
|
47
|
+
replacedBy?: string;
|
|
48
|
+
/** Override the default CLI output format when the user does not pass -f/--format. */
|
|
49
|
+
defaultFormat?: 'table' | 'plain' | 'json' | 'yaml' | 'yml' | 'md' | 'markdown' | 'csv';
|
|
50
|
+
}
|
|
51
|
+
/** Internal extension for lazy-loaded TS modules (not exposed in public API). */
|
|
52
|
+
export interface InternalCliCommand extends CliCommand {
|
|
53
|
+
_lazy?: boolean;
|
|
54
|
+
_modulePath?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface CliOptions extends Partial<Omit<CliCommand, 'args' | 'description'>> {
|
|
57
|
+
site: string;
|
|
58
|
+
name: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
args?: Arg[];
|
|
61
|
+
}
|
|
62
|
+
declare global {
|
|
63
|
+
var __biocli_registry__: Map<string, CliCommand> | undefined;
|
|
64
|
+
/** @deprecated Alias for __biocli_registry__ — kept for plugin backward compat. */
|
|
65
|
+
var __ncbicli_registry__: Map<string, CliCommand> | undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Register a new CLI command. Returns the created CliCommand object.
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* export const search = cli({
|
|
72
|
+
* site: 'pubmed',
|
|
73
|
+
* name: 'search',
|
|
74
|
+
* description: 'Search PubMed articles',
|
|
75
|
+
* database: 'pubmed',
|
|
76
|
+
* args: [{ name: 'query', positional: true, required: true, help: 'Search query' }],
|
|
77
|
+
* func: async (ctx, kwargs) => { ... },
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function cli(opts: CliOptions): CliCommand;
|
|
82
|
+
/** Return the global command registry map. */
|
|
83
|
+
export declare function getRegistry(): Map<string, CliCommand>;
|
|
84
|
+
/** Return the canonical key for a command: "site/name". */
|
|
85
|
+
export declare function fullName(cmd: CliCommand): string;
|
|
86
|
+
/** Return a human-readable label for the command's access strategy. */
|
|
87
|
+
export declare function strategyLabel(cmd: CliCommand): string;
|
|
88
|
+
/** Add a command (and its aliases) to the global registry. */
|
|
89
|
+
export declare function registerCommand(cmd: CliCommand): void;
|
|
90
|
+
export declare function normalizeAliases(aliases: string[] | undefined, commandName: string): string[];
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core registry: Strategy enum, Arg/CliCommand interfaces, cli() registration.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from opencli's registry.ts for NCBI public API access only.
|
|
5
|
+
* No browser, no cookie/header/intercept/UI strategies — just PUBLIC and API_KEY.
|
|
6
|
+
*/
|
|
7
|
+
// ── Strategy ─────────────────────────────────────────────────────────────────
|
|
8
|
+
export var Strategy;
|
|
9
|
+
(function (Strategy) {
|
|
10
|
+
/** No authentication needed — anonymous NCBI E-utilities access. */
|
|
11
|
+
Strategy["PUBLIC"] = "public";
|
|
12
|
+
/** Requires an NCBI API key for higher rate limits or restricted endpoints. */
|
|
13
|
+
Strategy["API_KEY"] = "api_key";
|
|
14
|
+
})(Strategy || (Strategy = {}));
|
|
15
|
+
const _registry = globalThis.__biocli_registry__ ??= globalThis.__ncbicli_registry__ ?? new Map();
|
|
16
|
+
// Keep legacy alias in sync
|
|
17
|
+
globalThis.__ncbicli_registry__ = _registry;
|
|
18
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Register a new CLI command. Returns the created CliCommand object.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* export const search = cli({
|
|
24
|
+
* site: 'pubmed',
|
|
25
|
+
* name: 'search',
|
|
26
|
+
* description: 'Search PubMed articles',
|
|
27
|
+
* database: 'pubmed',
|
|
28
|
+
* args: [{ name: 'query', positional: true, required: true, help: 'Search query' }],
|
|
29
|
+
* func: async (ctx, kwargs) => { ... },
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function cli(opts) {
|
|
34
|
+
const strategy = opts.strategy ?? Strategy.PUBLIC;
|
|
35
|
+
const aliases = normalizeAliases(opts.aliases, opts.name);
|
|
36
|
+
const cmd = {
|
|
37
|
+
site: opts.site,
|
|
38
|
+
name: opts.name,
|
|
39
|
+
aliases,
|
|
40
|
+
description: opts.description ?? '',
|
|
41
|
+
database: opts.database,
|
|
42
|
+
strategy,
|
|
43
|
+
args: opts.args ?? [],
|
|
44
|
+
columns: opts.columns,
|
|
45
|
+
func: opts.func,
|
|
46
|
+
pipeline: opts.pipeline,
|
|
47
|
+
timeoutSeconds: opts.timeoutSeconds,
|
|
48
|
+
requiredEnv: opts.requiredEnv,
|
|
49
|
+
deprecated: opts.deprecated,
|
|
50
|
+
replacedBy: opts.replacedBy,
|
|
51
|
+
defaultFormat: opts.defaultFormat,
|
|
52
|
+
};
|
|
53
|
+
registerCommand(cmd);
|
|
54
|
+
return cmd;
|
|
55
|
+
}
|
|
56
|
+
/** Return the global command registry map. */
|
|
57
|
+
export function getRegistry() {
|
|
58
|
+
return _registry;
|
|
59
|
+
}
|
|
60
|
+
/** Return the canonical key for a command: "site/name". */
|
|
61
|
+
export function fullName(cmd) {
|
|
62
|
+
return `${cmd.site}/${cmd.name}`;
|
|
63
|
+
}
|
|
64
|
+
/** Return a human-readable label for the command's access strategy. */
|
|
65
|
+
export function strategyLabel(cmd) {
|
|
66
|
+
return cmd.strategy ?? Strategy.PUBLIC;
|
|
67
|
+
}
|
|
68
|
+
/** Add a command (and its aliases) to the global registry. */
|
|
69
|
+
export function registerCommand(cmd) {
|
|
70
|
+
const canonicalKey = fullName(cmd);
|
|
71
|
+
const existing = _registry.get(canonicalKey);
|
|
72
|
+
if (existing) {
|
|
73
|
+
// Remove stale alias entries that pointed to the old command object
|
|
74
|
+
for (const [key, value] of _registry.entries()) {
|
|
75
|
+
if (value === existing && key !== canonicalKey)
|
|
76
|
+
_registry.delete(key);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const aliases = normalizeAliases(cmd.aliases, cmd.name);
|
|
80
|
+
cmd.aliases = aliases.length > 0 ? aliases : undefined;
|
|
81
|
+
_registry.set(canonicalKey, cmd);
|
|
82
|
+
for (const alias of aliases) {
|
|
83
|
+
_registry.set(`${cmd.site}/${alias}`, cmd);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
87
|
+
export function normalizeAliases(aliases, commandName) {
|
|
88
|
+
if (!Array.isArray(aliases) || aliases.length === 0)
|
|
89
|
+
return [];
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
const normalized = [];
|
|
92
|
+
for (const alias of aliases) {
|
|
93
|
+
const value = typeof alias === 'string' ? alias.trim() : '';
|
|
94
|
+
if (!value || value === commandName || seen.has(value))
|
|
95
|
+
continue;
|
|
96
|
+
seen.add(value);
|
|
97
|
+
normalized.push(value);
|
|
98
|
+
}
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema definitions for biocli result types.
|
|
3
|
+
*
|
|
4
|
+
* Used by the `biocli schema` command to help AI agents validate outputs.
|
|
5
|
+
*/
|
|
6
|
+
export declare const biocliResultSchema: {
|
|
7
|
+
$schema: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
type: string;
|
|
11
|
+
properties: {
|
|
12
|
+
data: {
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
ids: {
|
|
16
|
+
type: string;
|
|
17
|
+
description: string;
|
|
18
|
+
additionalProperties: {
|
|
19
|
+
type: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
sources: {
|
|
23
|
+
type: string;
|
|
24
|
+
description: string;
|
|
25
|
+
items: {
|
|
26
|
+
type: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
warnings: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
items: {
|
|
33
|
+
type: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
queriedAt: {
|
|
37
|
+
type: string;
|
|
38
|
+
format: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
organism: {
|
|
42
|
+
type: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
query: {
|
|
46
|
+
type: string;
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
required: string[];
|
|
51
|
+
};
|
|
52
|
+
export declare const resultWithMetaSchema: {
|
|
53
|
+
$schema: string;
|
|
54
|
+
title: string;
|
|
55
|
+
description: string;
|
|
56
|
+
type: string;
|
|
57
|
+
properties: {
|
|
58
|
+
rows: {
|
|
59
|
+
type: string;
|
|
60
|
+
description: string;
|
|
61
|
+
items: {
|
|
62
|
+
type: string;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
meta: {
|
|
66
|
+
type: string;
|
|
67
|
+
properties: {
|
|
68
|
+
totalCount: {
|
|
69
|
+
type: string;
|
|
70
|
+
description: string;
|
|
71
|
+
};
|
|
72
|
+
query: {
|
|
73
|
+
type: string;
|
|
74
|
+
description: string;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
required: string[];
|
|
80
|
+
};
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema definitions for biocli result types.
|
|
3
|
+
*
|
|
4
|
+
* Used by the `biocli schema` command to help AI agents validate outputs.
|
|
5
|
+
*/
|
|
6
|
+
export const biocliResultSchema = {
|
|
7
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
8
|
+
title: 'BiocliResult',
|
|
9
|
+
description: 'Structured result envelope returned by biocli aggregation commands (gene-profile, gene-dossier, etc.)',
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
data: {
|
|
13
|
+
description: 'Primary result payload (structure varies by command)',
|
|
14
|
+
},
|
|
15
|
+
ids: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
description: 'Cross-database identifiers (e.g. geneId, uniprotAccession, ensemblId)',
|
|
18
|
+
additionalProperties: { type: 'string' },
|
|
19
|
+
},
|
|
20
|
+
sources: {
|
|
21
|
+
type: 'array',
|
|
22
|
+
description: 'Database backends that contributed to this result',
|
|
23
|
+
items: { type: 'string' },
|
|
24
|
+
},
|
|
25
|
+
warnings: {
|
|
26
|
+
type: 'array',
|
|
27
|
+
description: 'Non-fatal issues encountered during data retrieval',
|
|
28
|
+
items: { type: 'string' },
|
|
29
|
+
},
|
|
30
|
+
queriedAt: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
format: 'date-time',
|
|
33
|
+
description: 'ISO 8601 timestamp of when the query was executed',
|
|
34
|
+
},
|
|
35
|
+
organism: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Scientific name of the organism (if applicable)',
|
|
38
|
+
},
|
|
39
|
+
query: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Original query string',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ['data', 'ids', 'sources', 'warnings', 'queriedAt', 'query'],
|
|
45
|
+
};
|
|
46
|
+
export const resultWithMetaSchema = {
|
|
47
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
48
|
+
title: 'ResultWithMeta',
|
|
49
|
+
description: 'Result envelope returned by atomic biocli commands (search, fetch, etc.)',
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
rows: {
|
|
53
|
+
type: 'array',
|
|
54
|
+
description: 'Array of result records (columns vary by command)',
|
|
55
|
+
items: { type: 'object' },
|
|
56
|
+
},
|
|
57
|
+
meta: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
totalCount: {
|
|
61
|
+
type: 'number',
|
|
62
|
+
description: 'Total number of results available (may exceed returned rows)',
|
|
63
|
+
},
|
|
64
|
+
query: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: 'Original query string',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ['rows'],
|
|
72
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight terminal spinner — zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Shows a cycling animation (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) with an optional message
|
|
5
|
+
* while async work is in progress. Clears itself when stopped.
|
|
6
|
+
*/
|
|
7
|
+
export interface Spinner {
|
|
8
|
+
/** Update the spinner message while running. */
|
|
9
|
+
update(message: string): void;
|
|
10
|
+
/** Stop and clear the spinner line. */
|
|
11
|
+
stop(): void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Start a spinner. Returns a handle to stop it.
|
|
15
|
+
*
|
|
16
|
+
* Only activates when stderr is a TTY — in pipes / CI it becomes a no-op
|
|
17
|
+
* so output stays clean.
|
|
18
|
+
*/
|
|
19
|
+
export declare function startSpinner(message: string): Spinner;
|
package/dist/spinner.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight terminal spinner — zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Shows a cycling animation (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) with an optional message
|
|
5
|
+
* while async work is in progress. Clears itself when stopped.
|
|
6
|
+
*/
|
|
7
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
8
|
+
const INTERVAL_MS = 80;
|
|
9
|
+
/**
|
|
10
|
+
* Start a spinner. Returns a handle to stop it.
|
|
11
|
+
*
|
|
12
|
+
* Only activates when stderr is a TTY — in pipes / CI it becomes a no-op
|
|
13
|
+
* so output stays clean.
|
|
14
|
+
*/
|
|
15
|
+
export function startSpinner(message) {
|
|
16
|
+
// No-op in non-TTY environments (piped output, CI, etc.)
|
|
17
|
+
if (!process.stderr.isTTY) {
|
|
18
|
+
return { update() { }, stop() { } };
|
|
19
|
+
}
|
|
20
|
+
let frame = 0;
|
|
21
|
+
let text = message;
|
|
22
|
+
const timer = setInterval(() => {
|
|
23
|
+
const line = `\r ${FRAMES[frame % FRAMES.length]} ${text}`;
|
|
24
|
+
process.stderr.write(line);
|
|
25
|
+
frame++;
|
|
26
|
+
}, INTERVAL_MS);
|
|
27
|
+
return {
|
|
28
|
+
update(msg) {
|
|
29
|
+
text = msg;
|
|
30
|
+
},
|
|
31
|
+
stop() {
|
|
32
|
+
clearInterval(timer);
|
|
33
|
+
// Clear the spinner line
|
|
34
|
+
process.stderr.write('\r' + ' '.repeat((text.length || 0) + 6) + '\r');
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|