@ncukondo/search-hub 0.21.0 → 0.23.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/README.md +34 -7
- package/dist/cli/commands/config.d.ts +53 -0
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/config.js +62 -0
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/fulltext/index.js +1 -0
- package/dist/cli/commands/fulltext/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +12 -17
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +116 -76
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/entry-bun.d.ts +2 -0
- package/dist/cli/entry-bun.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +194 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/loader.d.ts +4 -2
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +7 -6
- package/dist/config/loader.js.map +1 -1
- package/dist/config/paths.d.ts +21 -0
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +28 -5
- package/dist/config/paths.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/package.json.js +9 -0
- package/dist/package.json.js.map +1 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -3
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,8 +3,27 @@ import { join } from "node:path";
|
|
|
3
3
|
import { stringify } from "@iarna/toml";
|
|
4
4
|
import "../../config/schema.js";
|
|
5
5
|
import { getDefaultConfig } from "../../config/defaults.js";
|
|
6
|
-
import { getConfigDir,
|
|
6
|
+
import { getConfigDir, getProjectDir } from "../../config/paths.js";
|
|
7
7
|
import "node:os";
|
|
8
|
+
const FULL_PROVIDERS = ["pubmed", "eric", "arxiv", "scopus"];
|
|
9
|
+
const MINIMAL_PROVIDERS = ["wos", "embase"];
|
|
10
|
+
function buildProvidersToml(config) {
|
|
11
|
+
const providers = {};
|
|
12
|
+
for (const name of FULL_PROVIDERS) {
|
|
13
|
+
const p = config.providers[name];
|
|
14
|
+
providers[name] = {
|
|
15
|
+
enabled: p.enabled,
|
|
16
|
+
rate_limit: p.rate_limit,
|
|
17
|
+
timeout: p.timeout,
|
|
18
|
+
retries: p.retries,
|
|
19
|
+
max_results: p.max_results
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
for (const name of MINIMAL_PROVIDERS) {
|
|
23
|
+
providers[name] = { enabled: config.providers[name].enabled };
|
|
24
|
+
}
|
|
25
|
+
return providers;
|
|
26
|
+
}
|
|
8
27
|
async function exists(path) {
|
|
9
28
|
try {
|
|
10
29
|
await access(path, constants.F_OK);
|
|
@@ -13,11 +32,20 @@ async function exists(path) {
|
|
|
13
32
|
return false;
|
|
14
33
|
}
|
|
15
34
|
}
|
|
16
|
-
function
|
|
35
|
+
function localConfigToToml(config) {
|
|
17
36
|
return {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
37
|
+
providers: buildProvidersToml(config),
|
|
38
|
+
integration: {
|
|
39
|
+
reference_manager: {
|
|
40
|
+
enabled: config.integration.reference_manager.enabled,
|
|
41
|
+
command: config.integration.reference_manager.command,
|
|
42
|
+
auto_register: config.integration.reference_manager.auto_register
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function generateGlobalConfigContent(config) {
|
|
48
|
+
const tomlObj = {
|
|
21
49
|
log: {
|
|
22
50
|
level: config.log.level
|
|
23
51
|
},
|
|
@@ -25,55 +53,7 @@ function configToToml(config) {
|
|
|
25
53
|
color: config.output.color,
|
|
26
54
|
progress_bar: config.output.progress_bar
|
|
27
55
|
},
|
|
28
|
-
providers:
|
|
29
|
-
pubmed: {
|
|
30
|
-
enabled: config.providers.pubmed.enabled,
|
|
31
|
-
api_key: config.providers.pubmed.api_key ?? "",
|
|
32
|
-
email: config.providers.pubmed.email ?? "",
|
|
33
|
-
rate_limit: config.providers.pubmed.rate_limit,
|
|
34
|
-
timeout: config.providers.pubmed.timeout,
|
|
35
|
-
retries: config.providers.pubmed.retries,
|
|
36
|
-
max_results: config.providers.pubmed.max_results
|
|
37
|
-
},
|
|
38
|
-
eric: {
|
|
39
|
-
enabled: config.providers.eric.enabled,
|
|
40
|
-
rate_limit: config.providers.eric.rate_limit,
|
|
41
|
-
timeout: config.providers.eric.timeout,
|
|
42
|
-
retries: config.providers.eric.retries,
|
|
43
|
-
max_results: config.providers.eric.max_results
|
|
44
|
-
},
|
|
45
|
-
arxiv: {
|
|
46
|
-
enabled: config.providers.arxiv.enabled,
|
|
47
|
-
rate_limit: config.providers.arxiv.rate_limit,
|
|
48
|
-
timeout: config.providers.arxiv.timeout,
|
|
49
|
-
retries: config.providers.arxiv.retries,
|
|
50
|
-
max_results: config.providers.arxiv.max_results
|
|
51
|
-
},
|
|
52
|
-
scopus: {
|
|
53
|
-
enabled: config.providers.scopus.enabled,
|
|
54
|
-
api_key: config.providers.scopus.api_key ?? "",
|
|
55
|
-
inst_token: config.providers.scopus.inst_token ?? "",
|
|
56
|
-
rate_limit: config.providers.scopus.rate_limit,
|
|
57
|
-
timeout: config.providers.scopus.timeout,
|
|
58
|
-
retries: config.providers.scopus.retries,
|
|
59
|
-
max_results: config.providers.scopus.max_results
|
|
60
|
-
},
|
|
61
|
-
wos: {
|
|
62
|
-
enabled: config.providers.wos.enabled,
|
|
63
|
-
api_key: config.providers.wos.api_key ?? "",
|
|
64
|
-
rate_limit: config.providers.wos.rate_limit,
|
|
65
|
-
timeout: config.providers.wos.timeout,
|
|
66
|
-
retries: config.providers.wos.retries,
|
|
67
|
-
max_results: config.providers.wos.max_results
|
|
68
|
-
},
|
|
69
|
-
embase: {
|
|
70
|
-
enabled: config.providers.embase.enabled,
|
|
71
|
-
rate_limit: config.providers.embase.rate_limit,
|
|
72
|
-
timeout: config.providers.embase.timeout,
|
|
73
|
-
retries: config.providers.embase.retries,
|
|
74
|
-
max_results: config.providers.embase.max_results
|
|
75
|
-
}
|
|
76
|
-
},
|
|
56
|
+
providers: buildProvidersToml(config),
|
|
77
57
|
integration: {
|
|
78
58
|
reference_manager: {
|
|
79
59
|
enabled: config.integration.reference_manager.enabled,
|
|
@@ -82,54 +62,114 @@ function configToToml(config) {
|
|
|
82
62
|
}
|
|
83
63
|
}
|
|
84
64
|
};
|
|
85
|
-
|
|
86
|
-
function generateConfigContent(config) {
|
|
87
|
-
const tomlObj = configToToml(config);
|
|
88
|
-
const header = `# search-hub configuration file
|
|
65
|
+
const header = `# search-hub global configuration
|
|
89
66
|
# See: https://github.com/search-hub/search-hub for documentation
|
|
67
|
+
#
|
|
68
|
+
# [session]
|
|
69
|
+
# directory = "" # Override default session directory (empty = platform default)
|
|
70
|
+
|
|
71
|
+
`;
|
|
72
|
+
const tomlContent = stringify(tomlObj);
|
|
73
|
+
const lines = tomlContent.split("\n");
|
|
74
|
+
const result = [];
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
result.push(line);
|
|
77
|
+
if (line.startsWith("[providers.pubmed]")) {
|
|
78
|
+
result.push('# api_key = "" # Optional but recommended (NCBI E-utilities)');
|
|
79
|
+
result.push('# email = "" # Required by NCBI for tracking');
|
|
80
|
+
} else if (line.startsWith("[providers.scopus]")) {
|
|
81
|
+
result.push('# api_key = "" # Required for Scopus access');
|
|
82
|
+
result.push('# inst_token = "" # Optional institutional token');
|
|
83
|
+
} else if (line.startsWith("[providers.wos]")) {
|
|
84
|
+
result.push('# api_key = "" # Required for Web of Science');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return header + result.join("\n");
|
|
88
|
+
}
|
|
89
|
+
function generateLocalConfigContent(config) {
|
|
90
|
+
const tomlObj = localConfigToToml(config);
|
|
91
|
+
const header = `# search-hub project configuration
|
|
92
|
+
# Project-specific overrides (no secrets - use env vars or global config for API keys)
|
|
90
93
|
|
|
91
94
|
`;
|
|
92
95
|
return header + stringify(tomlObj);
|
|
93
96
|
}
|
|
94
|
-
async function
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
} = options;
|
|
100
|
-
const configPath = join(configDir, "config.toml");
|
|
101
|
-
const sessionsDir = join(dataDir, "sessions");
|
|
102
|
-
const queriesDir = join(dataDir, "queries");
|
|
97
|
+
async function initLocal(directory, force) {
|
|
98
|
+
const projectDir = getProjectDir(directory);
|
|
99
|
+
const configPath = join(projectDir, "config.toml");
|
|
100
|
+
const sessionsDir = join(projectDir, "sessions");
|
|
101
|
+
const queriesDir = join(projectDir, "queries");
|
|
103
102
|
const result = {
|
|
104
103
|
success: false,
|
|
105
104
|
configPath,
|
|
106
|
-
|
|
107
|
-
configDir,
|
|
108
|
-
dataDir
|
|
105
|
+
projectDir
|
|
109
106
|
};
|
|
110
|
-
if (await exists(
|
|
107
|
+
if (await exists(projectDir)) {
|
|
111
108
|
if (!force) {
|
|
112
109
|
return {
|
|
113
110
|
...result,
|
|
114
111
|
alreadyExists: true,
|
|
115
|
-
message: `
|
|
112
|
+
message: `Project directory already exists at ${projectDir}. Use --force to overwrite.`
|
|
116
113
|
};
|
|
117
114
|
}
|
|
118
115
|
result.overwritten = true;
|
|
119
116
|
}
|
|
120
|
-
await mkdir(
|
|
117
|
+
await mkdir(projectDir, { recursive: true });
|
|
121
118
|
await mkdir(sessionsDir, { recursive: true });
|
|
122
119
|
await mkdir(queriesDir, { recursive: true });
|
|
123
120
|
const defaultConfig = getDefaultConfig();
|
|
124
|
-
|
|
125
|
-
const configContent = generateConfigContent(defaultConfig);
|
|
121
|
+
const configContent = generateLocalConfigContent(defaultConfig);
|
|
126
122
|
await writeFile(configPath, configContent, "utf-8");
|
|
127
123
|
return {
|
|
128
124
|
...result,
|
|
129
125
|
success: true,
|
|
130
|
-
message: result.overwritten ? `
|
|
126
|
+
message: result.overwritten ? `Project re-initialized at ${projectDir}` : `Project initialized at ${projectDir}`,
|
|
127
|
+
hints: [
|
|
128
|
+
"Set up global credentials: search-hub init --global",
|
|
129
|
+
"Or use environment variables: see search-hub config --env-vars",
|
|
130
|
+
"API keys can also be set via .env file in the project root"
|
|
131
|
+
]
|
|
131
132
|
};
|
|
132
133
|
}
|
|
134
|
+
async function initGlobal(configDir, force) {
|
|
135
|
+
const configPath = join(configDir, "config.toml");
|
|
136
|
+
const result = {
|
|
137
|
+
success: false,
|
|
138
|
+
configPath
|
|
139
|
+
};
|
|
140
|
+
if (await exists(configDir)) {
|
|
141
|
+
if (!force) {
|
|
142
|
+
return {
|
|
143
|
+
...result,
|
|
144
|
+
alreadyExists: true,
|
|
145
|
+
message: `Global configuration already exists at ${configDir}. Use --force to overwrite.`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
result.overwritten = true;
|
|
149
|
+
}
|
|
150
|
+
await mkdir(configDir, { recursive: true });
|
|
151
|
+
const defaultConfig = getDefaultConfig();
|
|
152
|
+
const configContent = generateGlobalConfigContent(defaultConfig);
|
|
153
|
+
await writeFile(configPath, configContent, "utf-8");
|
|
154
|
+
return {
|
|
155
|
+
...result,
|
|
156
|
+
success: true,
|
|
157
|
+
message: result.overwritten ? `Global configuration overwritten at ${configDir}` : `Global configuration created at ${configDir}`,
|
|
158
|
+
hints: [
|
|
159
|
+
`Edit credentials: search-hub config --global set providers.pubmed.api_key <key>`,
|
|
160
|
+
`Or edit directly: ${configPath}`
|
|
161
|
+
]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function init(options = {}) {
|
|
165
|
+
const { force = false } = options;
|
|
166
|
+
if (options.global) {
|
|
167
|
+
const configDir = options.configDir ?? getConfigDir();
|
|
168
|
+
return initGlobal(configDir, force);
|
|
169
|
+
}
|
|
170
|
+
const directory = options.directory ?? process.cwd();
|
|
171
|
+
return initLocal(directory, force);
|
|
172
|
+
}
|
|
133
173
|
export {
|
|
134
174
|
init
|
|
135
175
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sources":["../../../src/cli/commands/init.ts"],"sourcesContent":["import { mkdir, writeFile, access, constants } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { stringify as stringifyToml } from '@iarna/toml';\nimport { getDefaultConfig } from '../../config/index.js';\nimport { getConfigDir, getDataDir } from '../../config/paths.js';\nimport type { Config } from '../../config/index.js';\n\n/**\n * Options for the init command.\n */\nexport interface InitOptions {\n /** Config directory (defaults to platform-specific via getConfigDir()) */\n configDir?: string;\n /** Data directory (defaults to platform-specific via getDataDir()) */\n dataDir?: string;\n /** Force overwrite if directory already exists */\n force?: boolean;\n}\n\n/**\n * Result of the init command.\n */\nexport interface InitResult {\n /** Whether initialization was successful */\n success: boolean;\n /** Path to the created config file */\n configPath: string;\n /** Path to the sessions directory */\n sessionsDir: string;\n /** Path to the config directory */\n configDir: string;\n /** Path to the data directory */\n dataDir: string;\n /** Whether files already existed (only when success=false) */\n alreadyExists?: boolean;\n /** Whether existing files were overwritten (only when force=true) */\n overwritten?: boolean;\n /** Message describing the result */\n message?: string;\n}\n\n/**\n * Check if a file or directory exists.\n */\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Convert Config to TOML-compatible object.\n * Removes undefined values and converts to the expected format.\n */\nfunction configToToml(config: Config): Record<string, unknown> {\n return {\n session: {\n directory: config.session.directory,\n },\n log: {\n level: config.log.level,\n },\n output: {\n color: config.output.color,\n progress_bar: config.output.progress_bar,\n },\n providers: {\n pubmed: {\n enabled: config.providers.pubmed.enabled,\n api_key: config.providers.pubmed.api_key ?? '',\n email: config.providers.pubmed.email ?? '',\n rate_limit: config.providers.pubmed.rate_limit,\n timeout: config.providers.pubmed.timeout,\n retries: config.providers.pubmed.retries,\n max_results: config.providers.pubmed.max_results,\n },\n eric: {\n enabled: config.providers.eric.enabled,\n rate_limit: config.providers.eric.rate_limit,\n timeout: config.providers.eric.timeout,\n retries: config.providers.eric.retries,\n max_results: config.providers.eric.max_results,\n },\n arxiv: {\n enabled: config.providers.arxiv.enabled,\n rate_limit: config.providers.arxiv.rate_limit,\n timeout: config.providers.arxiv.timeout,\n retries: config.providers.arxiv.retries,\n max_results: config.providers.arxiv.max_results,\n },\n scopus: {\n enabled: config.providers.scopus.enabled,\n api_key: config.providers.scopus.api_key ?? '',\n inst_token: config.providers.scopus.inst_token ?? '',\n rate_limit: config.providers.scopus.rate_limit,\n timeout: config.providers.scopus.timeout,\n retries: config.providers.scopus.retries,\n max_results: config.providers.scopus.max_results,\n },\n wos: {\n enabled: config.providers.wos.enabled,\n api_key: config.providers.wos.api_key ?? '',\n rate_limit: config.providers.wos.rate_limit,\n timeout: config.providers.wos.timeout,\n retries: config.providers.wos.retries,\n max_results: config.providers.wos.max_results,\n },\n embase: {\n enabled: config.providers.embase.enabled,\n rate_limit: config.providers.embase.rate_limit,\n timeout: config.providers.embase.timeout,\n retries: config.providers.embase.retries,\n max_results: config.providers.embase.max_results,\n },\n },\n integration: {\n reference_manager: {\n enabled: config.integration.reference_manager.enabled,\n command: config.integration.reference_manager.command,\n auto_register: config.integration.reference_manager.auto_register,\n },\n },\n };\n}\n\n/**\n * Generate TOML config file content with comments.\n */\nfunction generateConfigContent(config: Config): string {\n const tomlObj = configToToml(config);\n const header = `# search-hub configuration file\n# See: https://github.com/search-hub/search-hub for documentation\n\n`;\n return header + stringifyToml(tomlObj as Parameters<typeof stringifyToml>[0]);\n}\n\n/**\n * Initialize the search-hub configuration directory.\n *\n * Creates:\n * - Config directory with config.toml\n * - Data directory with sessions/ subdirectory\n *\n * On Linux (XDG):\n * - ~/.config/search-hub/config.toml\n * - ~/.local/share/search-hub/sessions/\n */\nexport async function init(options: InitOptions = {}): Promise<InitResult> {\n const {\n configDir = getConfigDir(),\n dataDir = getDataDir(),\n force = false,\n } = options;\n\n const configPath = join(configDir, 'config.toml');\n const sessionsDir = join(dataDir, 'sessions');\n const queriesDir = join(dataDir, 'queries');\n\n const result: InitResult = {\n success: false,\n configPath,\n sessionsDir,\n configDir,\n dataDir,\n };\n\n // Check if config directory already exists\n if (await exists(configDir)) {\n if (!force) {\n return {\n ...result,\n alreadyExists: true,\n message: `Configuration directory already exists at ${configDir}. Use --force to overwrite.`,\n };\n }\n result.overwritten = true;\n }\n\n // Create directories\n await mkdir(configDir, { recursive: true });\n await mkdir(sessionsDir, { recursive: true });\n await mkdir(queriesDir, { recursive: true });\n\n // Generate and write config file\n // Use the default sessions directory for the saved config\n const defaultConfig = getDefaultConfig();\n // Set session.directory to the actual sessions path for the config file\n defaultConfig.session.directory = sessionsDir;\n const configContent = generateConfigContent(defaultConfig);\n await writeFile(configPath, configContent, 'utf-8');\n\n return {\n ...result,\n success: true,\n message: result.overwritten\n ? `Configuration overwritten at ${configDir}`\n : `Configuration created at ${configDir}`,\n };\n}\n"],"names":["stringifyToml"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"init.js","sources":["../../../src/cli/commands/init.ts"],"sourcesContent":["import { mkdir, writeFile, access, constants } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { stringify as stringifyToml } from '@iarna/toml';\nimport { getDefaultConfig } from '../../config/index.js';\nimport { getConfigDir, getProjectDir } from '../../config/paths.js';\nimport type { Config, ProviderConfig } from '../../config/index.js';\n\n/** Provider names that carry full settings (rate_limit, timeout, etc.) in generated configs. */\nconst FULL_PROVIDERS = ['pubmed', 'eric', 'arxiv', 'scopus'] as const;\n/** Provider names that only carry the enabled flag in generated configs. */\nconst MINIMAL_PROVIDERS = ['wos', 'embase'] as const;\n\n/**\n * Build the providers TOML object from a Config, excluding secrets.\n */\nfunction buildProvidersToml(config: Config): Record<string, Partial<ProviderConfig>> {\n const providers: Record<string, Partial<ProviderConfig>> = {};\n for (const name of FULL_PROVIDERS) {\n const p = config.providers[name];\n providers[name] = {\n enabled: p.enabled,\n rate_limit: p.rate_limit,\n timeout: p.timeout,\n retries: p.retries,\n max_results: p.max_results,\n };\n }\n for (const name of MINIMAL_PROVIDERS) {\n providers[name] = { enabled: config.providers[name].enabled };\n }\n return providers;\n}\n\n/**\n * Options for the init command.\n */\nexport interface InitOptions {\n /** Directory to create .search-hub/ in (defaults to cwd) */\n directory?: string;\n /** Initialize global config instead of local project */\n global?: boolean;\n /** Config directory for global init (defaults to platform-specific via getConfigDir()) */\n configDir?: string;\n /** Force overwrite if directory already exists */\n force?: boolean;\n}\n\n/**\n * Result of the init command.\n */\nexport interface InitResult {\n /** Whether initialization was successful */\n success: boolean;\n /** Path to the created config file */\n configPath: string;\n /** Path to the .search-hub/ project directory (local init only) */\n projectDir?: string;\n /** Whether files already existed (only when success=false) */\n alreadyExists?: boolean;\n /** Whether existing files were overwritten (only when force=true) */\n overwritten?: boolean;\n /** Message describing the result */\n message?: string;\n /** Actionable hints for the user */\n hints?: string[];\n}\n\n/**\n * Check if a file or directory exists.\n */\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Convert Config to TOML-compatible object for local project config.\n * Excludes secrets (api_key, email, inst_token).\n */\nfunction localConfigToToml(config: Config): Record<string, unknown> {\n return {\n providers: buildProvidersToml(config),\n integration: {\n reference_manager: {\n enabled: config.integration.reference_manager.enabled,\n command: config.integration.reference_manager.command,\n auto_register: config.integration.reference_manager.auto_register,\n },\n },\n };\n}\n\n/**\n * Generate TOML config content for global config with credential hints as comments.\n */\nfunction generateGlobalConfigContent(config: Config): string {\n const tomlObj = {\n log: {\n level: config.log.level,\n },\n output: {\n color: config.output.color,\n progress_bar: config.output.progress_bar,\n },\n providers: buildProvidersToml(config),\n integration: {\n reference_manager: {\n enabled: config.integration.reference_manager.enabled,\n command: config.integration.reference_manager.command,\n auto_register: config.integration.reference_manager.auto_register,\n },\n },\n };\n\n const header = `# search-hub global configuration\n# See: https://github.com/search-hub/search-hub for documentation\n#\n# [session]\n# directory = \"\" # Override default session directory (empty = platform default)\n\n`;\n\n const tomlContent = stringifyToml(tomlObj as Parameters<typeof stringifyToml>[0]);\n\n // Add credential hints as comments after provider sections\n const lines = tomlContent.split('\\n');\n const result: string[] = [];\n for (const line of lines) {\n result.push(line);\n if (line.startsWith('[providers.pubmed]')) {\n result.push('# api_key = \"\" # Optional but recommended (NCBI E-utilities)');\n result.push('# email = \"\" # Required by NCBI for tracking');\n } else if (line.startsWith('[providers.scopus]')) {\n result.push('# api_key = \"\" # Required for Scopus access');\n result.push('# inst_token = \"\" # Optional institutional token');\n } else if (line.startsWith('[providers.wos]')) {\n result.push('# api_key = \"\" # Required for Web of Science');\n }\n }\n\n return header + result.join('\\n');\n}\n\n/**\n * Generate TOML config file content for local project config.\n */\nfunction generateLocalConfigContent(config: Config): string {\n const tomlObj = localConfigToToml(config);\n const header = `# search-hub project configuration\n# Project-specific overrides (no secrets - use env vars or global config for API keys)\n\n`;\n return header + stringifyToml(tomlObj as Parameters<typeof stringifyToml>[0]);\n}\n\n/**\n * Initialize a local .search-hub/ project directory.\n */\nasync function initLocal(directory: string, force: boolean): Promise<InitResult> {\n const projectDir = getProjectDir(directory);\n const configPath = join(projectDir, 'config.toml');\n const sessionsDir = join(projectDir, 'sessions');\n const queriesDir = join(projectDir, 'queries');\n\n const result: InitResult = {\n success: false,\n configPath,\n projectDir,\n };\n\n // Check if .search-hub/ already exists\n if (await exists(projectDir)) {\n if (!force) {\n return {\n ...result,\n alreadyExists: true,\n message: `Project directory already exists at ${projectDir}. Use --force to overwrite.`,\n };\n }\n result.overwritten = true;\n }\n\n // Create directories\n await mkdir(projectDir, { recursive: true });\n await mkdir(sessionsDir, { recursive: true });\n await mkdir(queriesDir, { recursive: true });\n\n // Generate and write local config (no secrets)\n const defaultConfig = getDefaultConfig();\n const configContent = generateLocalConfigContent(defaultConfig);\n await writeFile(configPath, configContent, 'utf-8');\n\n return {\n ...result,\n success: true,\n message: result.overwritten\n ? `Project re-initialized at ${projectDir}`\n : `Project initialized at ${projectDir}`,\n hints: [\n 'Set up global credentials: search-hub init --global',\n 'Or use environment variables: see search-hub config --env-vars',\n 'API keys can also be set via .env file in the project root',\n ],\n };\n}\n\n/**\n * Initialize the global search-hub configuration.\n */\nasync function initGlobal(configDir: string, force: boolean): Promise<InitResult> {\n const configPath = join(configDir, 'config.toml');\n\n const result: InitResult = {\n success: false,\n configPath,\n };\n\n // Check if config directory already exists\n if (await exists(configDir)) {\n if (!force) {\n return {\n ...result,\n alreadyExists: true,\n message: `Global configuration already exists at ${configDir}. Use --force to overwrite.`,\n };\n }\n result.overwritten = true;\n }\n\n // Create directory\n await mkdir(configDir, { recursive: true });\n\n // Generate and write global config with credential hints\n const defaultConfig = getDefaultConfig();\n const configContent = generateGlobalConfigContent(defaultConfig);\n await writeFile(configPath, configContent, 'utf-8');\n\n return {\n ...result,\n success: true,\n message: result.overwritten\n ? `Global configuration overwritten at ${configDir}`\n : `Global configuration created at ${configDir}`,\n hints: [\n `Edit credentials: search-hub config --global set providers.pubmed.api_key <key>`,\n `Or edit directly: ${configPath}`,\n ],\n };\n}\n\n/**\n * Initialize search-hub configuration.\n *\n * By default, creates a `.search-hub/` project directory in the specified directory (or cwd).\n * With `--global`, creates the global config at the XDG-compliant path.\n */\nexport async function init(options: InitOptions = {}): Promise<InitResult> {\n const { force = false } = options;\n\n if (options.global) {\n const configDir = options.configDir ?? getConfigDir();\n return initGlobal(configDir, force);\n }\n\n const directory = options.directory ?? process.cwd();\n return initLocal(directory, force);\n}\n"],"names":["stringifyToml"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-bun.d.ts","sourceRoot":"","sources":["../../src/cli/entry-bun.ts"],"names":[],"mappings":""}
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAoN5C;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAg8FvC;AAED;;GAEG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1C"}
|
package/dist/cli/index.js
CHANGED
|
@@ -4,11 +4,12 @@ import { Command, Option } from "commander";
|
|
|
4
4
|
import { VERSION } from "../version.js";
|
|
5
5
|
import { init } from "./commands/init.js";
|
|
6
6
|
import { EXIT_CODES } from "./exit-codes.js";
|
|
7
|
-
import { loadConfig, saveConfig } from "../config/loader.js";
|
|
7
|
+
import { loadConfig, loadTomlFile, saveConfig } from "../config/loader.js";
|
|
8
8
|
import "../config/schema.js";
|
|
9
9
|
import { getDefaultConfig } from "../config/defaults.js";
|
|
10
|
-
import { getDefaultConfigPath } from "../config/paths.js";
|
|
11
|
-
import { viewConfig, viewConfigKey, setConfigKey } from "./commands/config.js";
|
|
10
|
+
import { isInsideProject, getDefaultConfigPath, getLocalConfigPath } from "../config/paths.js";
|
|
11
|
+
import { formatEnvVars, viewConfigAllOrigins, viewConfigFiltered, viewConfig, formatShowOrigin, getNestedValue, viewConfigKey, resolveWriteScope, checkSecretKeyWarning, setConfigKey, parseValue, setNestedValue } from "./commands/config.js";
|
|
12
|
+
import { ENV_VAR_MAP } from "../config/env.js";
|
|
12
13
|
import { detectSchemaLink, validateQueryCommand, formatValidateResult, formatVocabValidationOutput, hasVocabErrors } from "./commands/query/validate.js";
|
|
13
14
|
import { MeSHLookupClient } from "../query/mesh-lookup.js";
|
|
14
15
|
import { RateLimiter } from "../providers/base/rate-limiter.js";
|
|
@@ -82,16 +83,23 @@ Quick Start:
|
|
|
82
83
|
$ search-hub search my-search --count-only # Check hit counts
|
|
83
84
|
$ search-hub search my-search # Execute search
|
|
84
85
|
$ search-hub results <session> # Review titles`);
|
|
85
|
-
program.command("init").description("Initialize
|
|
86
|
+
program.command("init").description("Initialize search-hub project (local) or global config").option("-f, --force", "overwrite existing configuration", false).option("-g, --global", "initialize global config instead of local project", false).addHelpText("after", `
|
|
86
87
|
Examples:
|
|
87
|
-
$ search-hub init #
|
|
88
|
+
$ search-hub init # Create .search-hub/ in current directory
|
|
89
|
+
$ search-hub init --global # Create global config (~/.config/search-hub/)
|
|
88
90
|
$ search-hub init --force # Overwrite existing configuration`).action(async (options) => {
|
|
89
91
|
const globalOpts = program.opts();
|
|
90
92
|
try {
|
|
91
|
-
const result = await init({ force: options.force });
|
|
93
|
+
const result = await init({ force: options.force, global: options.global });
|
|
92
94
|
if (!globalOpts.quiet) {
|
|
93
95
|
if (result.success) {
|
|
94
96
|
console.log(result.message);
|
|
97
|
+
if (result.hints) {
|
|
98
|
+
console.log("");
|
|
99
|
+
for (const hint of result.hints) {
|
|
100
|
+
console.log(` ${hint}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
95
103
|
} else {
|
|
96
104
|
console.error(result.message);
|
|
97
105
|
}
|
|
@@ -107,13 +115,91 @@ Examples:
|
|
|
107
115
|
process.exitCode = EXIT_CODES.GENERAL_ERROR;
|
|
108
116
|
}
|
|
109
117
|
});
|
|
110
|
-
program.command("config").description("View and edit configuration").argument("[key]", "configuration key to view or set").argument("[value]", "value to set for the key").addHelpText("after", `
|
|
118
|
+
program.command("config").description("View and edit configuration").argument("[key]", "configuration key to view or set").argument("[value]", "value to set for the key").option("--global", "Use global config scope").option("--local", "Use local project config scope").option("--show-origin", "Show where each config value comes from").option("--list", "List all config values (default when no key given)").option("--env-vars", "Show environment variable mappings").option("--force", "Force write even for secret keys in local scope").addHelpText("after", `
|
|
111
119
|
Examples:
|
|
112
|
-
$ search-hub config
|
|
113
|
-
$ search-hub config providers.pubmed
|
|
114
|
-
$ search-hub config providers.pubmed.api_key KEY # Set API key
|
|
120
|
+
$ search-hub config # Show all config
|
|
121
|
+
$ search-hub config providers.pubmed # Show PubMed config
|
|
122
|
+
$ search-hub config --global providers.pubmed.api_key KEY # Set API key globally
|
|
123
|
+
$ search-hub config --local output.color false # Set in project config
|
|
124
|
+
$ search-hub config --show-origin providers.pubmed.api_key # Show value origin
|
|
125
|
+
$ search-hub config --list --global # Show only global values
|
|
126
|
+
$ search-hub config --env-vars # Show env var mappings`).action(async (key, value, cmdOpts) => {
|
|
115
127
|
const globalOpts = program.opts();
|
|
116
128
|
try {
|
|
129
|
+
if (cmdOpts.envVars) {
|
|
130
|
+
if (!globalOpts.quiet) {
|
|
131
|
+
console.log(formatEnvVars());
|
|
132
|
+
}
|
|
133
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const inProject = await isInsideProject();
|
|
137
|
+
if (cmdOpts.list || !key && !value) {
|
|
138
|
+
if (cmdOpts.global && cmdOpts.local) {
|
|
139
|
+
if (!globalOpts.quiet) {
|
|
140
|
+
console.error("Error: --global and --local are mutually exclusive");
|
|
141
|
+
}
|
|
142
|
+
process.exitCode = EXIT_CODES.CONFIG_ERROR;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (cmdOpts.showOrigin && !cmdOpts.global && !cmdOpts.local) {
|
|
146
|
+
let config22;
|
|
147
|
+
try {
|
|
148
|
+
config22 = await loadConfig(
|
|
149
|
+
globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}
|
|
150
|
+
);
|
|
151
|
+
} catch {
|
|
152
|
+
config22 = getDefaultConfig();
|
|
153
|
+
}
|
|
154
|
+
const globalPath = expandPath(getDefaultConfigPath());
|
|
155
|
+
const globalCfg = await loadTomlFile(globalPath);
|
|
156
|
+
const localPath = inProject ? getLocalConfigPath() : "";
|
|
157
|
+
const localCfg = inProject ? await loadTomlFile(localPath) : {};
|
|
158
|
+
if (!globalOpts.quiet) {
|
|
159
|
+
console.log(viewConfigAllOrigins(
|
|
160
|
+
config22,
|
|
161
|
+
ENV_VAR_MAP,
|
|
162
|
+
localCfg,
|
|
163
|
+
localPath,
|
|
164
|
+
globalCfg,
|
|
165
|
+
globalPath
|
|
166
|
+
));
|
|
167
|
+
}
|
|
168
|
+
} else if (cmdOpts.global) {
|
|
169
|
+
const globalConfig = await loadTomlFile(expandPath(getDefaultConfigPath()));
|
|
170
|
+
if (!globalOpts.quiet) {
|
|
171
|
+
const output = viewConfigFiltered(globalConfig);
|
|
172
|
+
console.log(output || "(no global config values set)");
|
|
173
|
+
}
|
|
174
|
+
} else if (cmdOpts.local) {
|
|
175
|
+
if (!inProject) {
|
|
176
|
+
if (!globalOpts.quiet) {
|
|
177
|
+
console.error('Error: --local requires a project directory (.search-hub/). Run "search-hub init" first.');
|
|
178
|
+
}
|
|
179
|
+
process.exitCode = EXIT_CODES.CONFIG_ERROR;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const localConfig = await loadTomlFile(getLocalConfigPath());
|
|
183
|
+
if (!globalOpts.quiet) {
|
|
184
|
+
const output = viewConfigFiltered(localConfig);
|
|
185
|
+
console.log(output || "(no local config values set)");
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
let config22;
|
|
189
|
+
try {
|
|
190
|
+
config22 = await loadConfig(
|
|
191
|
+
globalOpts.config ? { explicitConfigPath: globalOpts.config } : {}
|
|
192
|
+
);
|
|
193
|
+
} catch {
|
|
194
|
+
config22 = getDefaultConfig();
|
|
195
|
+
}
|
|
196
|
+
if (!globalOpts.quiet) {
|
|
197
|
+
console.log(viewConfig(config22));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
117
203
|
let config2;
|
|
118
204
|
try {
|
|
119
205
|
config2 = await loadConfig(
|
|
@@ -122,29 +208,103 @@ Examples:
|
|
|
122
208
|
} catch {
|
|
123
209
|
config2 = getDefaultConfig();
|
|
124
210
|
}
|
|
125
|
-
if (!
|
|
126
|
-
if (
|
|
127
|
-
|
|
211
|
+
if (key && !value) {
|
|
212
|
+
if (cmdOpts.showOrigin) {
|
|
213
|
+
const envEntry = Object.entries(ENV_VAR_MAP).find(([, path]) => path === key);
|
|
214
|
+
if (envEntry && process.env[envEntry[0]] !== void 0) {
|
|
215
|
+
if (!globalOpts.quiet) {
|
|
216
|
+
console.log(formatShowOrigin(key, process.env[envEntry[0]], "env", envEntry[0]));
|
|
217
|
+
}
|
|
218
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (inProject) {
|
|
222
|
+
const localConfig = await loadTomlFile(getLocalConfigPath());
|
|
223
|
+
const localVal = getNestedValue(localConfig, key);
|
|
224
|
+
if (localVal !== void 0) {
|
|
225
|
+
if (!globalOpts.quiet) {
|
|
226
|
+
console.log(formatShowOrigin(key, String(localVal), "local", getLocalConfigPath()));
|
|
227
|
+
}
|
|
228
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const globalConfigPath = expandPath(getDefaultConfigPath());
|
|
233
|
+
const globalConfig = await loadTomlFile(globalConfigPath);
|
|
234
|
+
const globalVal = getNestedValue(globalConfig, key);
|
|
235
|
+
if (globalVal !== void 0) {
|
|
236
|
+
if (!globalOpts.quiet) {
|
|
237
|
+
console.log(formatShowOrigin(key, String(globalVal), "global", globalConfigPath));
|
|
238
|
+
}
|
|
239
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const mergedVal = getNestedValue(config2, key);
|
|
243
|
+
if (mergedVal !== void 0) {
|
|
244
|
+
if (!globalOpts.quiet) {
|
|
245
|
+
console.log(formatShowOrigin(key, String(mergedVal), "default", ""));
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
if (!globalOpts.quiet) {
|
|
249
|
+
console.error(`Error: Key "${key}" not found in configuration`);
|
|
250
|
+
}
|
|
251
|
+
process.exitCode = EXIT_CODES.CONFIG_ERROR;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
const result = viewConfigKey(config2, key);
|
|
256
|
+
if (result.success) {
|
|
257
|
+
if (!globalOpts.quiet) {
|
|
258
|
+
console.log(result.value);
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
if (!globalOpts.quiet) {
|
|
262
|
+
console.error(`Error: ${result.error}`);
|
|
263
|
+
}
|
|
264
|
+
process.exitCode = EXIT_CODES.CONFIG_ERROR;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
128
267
|
}
|
|
129
|
-
} else if (
|
|
130
|
-
const
|
|
131
|
-
|
|
268
|
+
} else if (key && value) {
|
|
269
|
+
const scopeResult = resolveWriteScope({
|
|
270
|
+
global: !!cmdOpts.global,
|
|
271
|
+
local: !!cmdOpts.local,
|
|
272
|
+
insideProject: inProject
|
|
273
|
+
});
|
|
274
|
+
if (scopeResult.scope === "error") {
|
|
132
275
|
if (!globalOpts.quiet) {
|
|
133
|
-
console.
|
|
276
|
+
console.error(`Error: ${scopeResult.error}`);
|
|
134
277
|
}
|
|
135
|
-
|
|
278
|
+
process.exitCode = EXIT_CODES.CONFIG_ERROR;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const warning = checkSecretKeyWarning(key, scopeResult.scope);
|
|
282
|
+
if (warning && !cmdOpts.force) {
|
|
136
283
|
if (!globalOpts.quiet) {
|
|
137
|
-
console.error(`Error: ${
|
|
284
|
+
console.error(`Error: ${warning} Use --force to override.`);
|
|
138
285
|
}
|
|
139
286
|
process.exitCode = EXIT_CODES.CONFIG_ERROR;
|
|
140
287
|
return;
|
|
141
288
|
}
|
|
142
|
-
} else {
|
|
143
289
|
const result = setConfigKey(config2, key, value);
|
|
144
290
|
if (result.success) {
|
|
145
|
-
|
|
291
|
+
let configPath;
|
|
292
|
+
if (globalOpts.config) {
|
|
293
|
+
configPath = expandPath(globalOpts.config);
|
|
294
|
+
} else if (scopeResult.scope === "local") {
|
|
295
|
+
configPath = expandPath(getLocalConfigPath());
|
|
296
|
+
} else {
|
|
297
|
+
configPath = expandPath(getDefaultConfigPath());
|
|
298
|
+
}
|
|
146
299
|
try {
|
|
147
|
-
|
|
300
|
+
const existing = await loadTomlFile(configPath);
|
|
301
|
+
const existingValue = getNestedValue(
|
|
302
|
+
config2,
|
|
303
|
+
key
|
|
304
|
+
);
|
|
305
|
+
const parsedValue = parseValue(value, existingValue);
|
|
306
|
+
setNestedValue(existing, key, parsedValue);
|
|
307
|
+
await saveConfig(existing, { path: configPath });
|
|
148
308
|
if (!globalOpts.quiet) {
|
|
149
309
|
console.log(`Set ${key} = ${result.value}`);
|
|
150
310
|
console.log(`Saved to ${configPath}`);
|
|
@@ -2204,11 +2364,18 @@ async function main() {
|
|
|
2204
2364
|
const currentFile = fileURLToPath(import.meta.url);
|
|
2205
2365
|
const executedFile = process.argv[1];
|
|
2206
2366
|
if (executedFile) {
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2367
|
+
try {
|
|
2368
|
+
if (realpathSync(executedFile) === realpathSync(currentFile)) {
|
|
2369
|
+
main().catch((error) => {
|
|
2370
|
+
console.error("Fatal error:", error);
|
|
2371
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
} catch (e) {
|
|
2375
|
+
const code = e instanceof Error ? e.code : void 0;
|
|
2376
|
+
if (code !== "ENOENT" && code !== "ERR_INVALID_ARG_TYPE") {
|
|
2377
|
+
console.error("[debug] Unexpected error resolving entry path:", e);
|
|
2378
|
+
}
|
|
2212
2379
|
}
|
|
2213
2380
|
}
|
|
2214
2381
|
export {
|