@ncukondo/search-hub 0.22.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +14 -7
  2. package/dist/cli/commands/config.d.ts +53 -0
  3. package/dist/cli/commands/config.d.ts.map +1 -1
  4. package/dist/cli/commands/config.js +62 -0
  5. package/dist/cli/commands/config.js.map +1 -1
  6. package/dist/cli/commands/fulltext/index.js +1 -0
  7. package/dist/cli/commands/fulltext/index.js.map +1 -1
  8. package/dist/cli/commands/init.d.ts +12 -17
  9. package/dist/cli/commands/init.d.ts.map +1 -1
  10. package/dist/cli/commands/init.js +116 -76
  11. package/dist/cli/commands/init.js.map +1 -1
  12. package/dist/cli/commands/query/init.d.ts +0 -1
  13. package/dist/cli/commands/query/init.d.ts.map +1 -1
  14. package/dist/cli/commands/query/init.js +2 -3
  15. package/dist/cli/commands/query/init.js.map +1 -1
  16. package/dist/cli/commands/query/resolve.d.ts.map +1 -1
  17. package/dist/cli/commands/query/resolve.js +5 -2
  18. package/dist/cli/commands/query/resolve.js.map +1 -1
  19. package/dist/cli/index.d.ts.map +1 -1
  20. package/dist/cli/index.js +183 -23
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/suggestions/rules.js +1 -1
  23. package/dist/cli/suggestions/rules.js.map +1 -1
  24. package/dist/config/index.d.ts +1 -0
  25. package/dist/config/index.d.ts.map +1 -1
  26. package/dist/config/loader.d.ts +4 -2
  27. package/dist/config/loader.d.ts.map +1 -1
  28. package/dist/config/loader.js +7 -6
  29. package/dist/config/loader.js.map +1 -1
  30. package/dist/config/paths.d.ts +21 -0
  31. package/dist/config/paths.d.ts.map +1 -1
  32. package/dist/config/paths.js +28 -5
  33. package/dist/config/paths.js.map +1 -1
  34. package/dist/index.js +6 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/package.json.js +1 -1
  37. package/package.json +1 -1
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 configuration directory").option("-f, --force", "overwrite existing configuration", false).addHelpText("after", `
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 # Initialize with default settings
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 # Show all config
113
- $ search-hub config providers.pubmed # Show PubMed config
114
- $ search-hub config providers.pubmed.api_key KEY # Set API key`).action(async (key, value) => {
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 (!key) {
126
- if (!globalOpts.quiet) {
127
- console.log(viewConfig(config2));
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 (!value) {
130
- const result = viewConfigKey(config2, key);
131
- if (result.success) {
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.log(result.value);
276
+ console.error(`Error: ${scopeResult.error}`);
134
277
  }
135
- } else {
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: ${result.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
- const configPath = globalOpts.config ? expandPath(globalOpts.config) : getDefaultConfigPath();
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
- await saveConfig(config2, { path: configPath });
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}`);
@@ -334,7 +494,7 @@ Examples:
334
494
  });
335
495
  queryCommand.command("init").description("Generate a template query YAML file").argument("<title>", "query title (used for name field and filename)").option("-o, --output <path>", "write to specific file path").option("--stdout", "output to stdout instead of file").option("--force", "overwrite existing file", false).addHelpText("after", `
336
496
  Examples:
337
- $ search-hub query init "WBA pain mechanisms" # → queries/wba-pain-mechanisms.yaml
497
+ $ search-hub query init "WBA pain mechanisms" # → .search-hub/queries/wba-pain-mechanisms.yaml
338
498
  $ search-hub query init "WBA pain" -o ./custom-path.yaml # Custom output path
339
499
  $ search-hub query init "WBA pain" --stdout # Print to stdout`).action(async (title, options) => {
340
500
  const globalOpts = program.opts();