@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.
- package/README.md +14 -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/commands/query/init.d.ts +0 -1
- package/dist/cli/commands/query/init.d.ts.map +1 -1
- package/dist/cli/commands/query/init.js +2 -3
- package/dist/cli/commands/query/init.js.map +1 -1
- package/dist/cli/commands/query/resolve.d.ts.map +1 -1
- package/dist/cli/commands/query/resolve.js +5 -2
- package/dist/cli/commands/query/resolve.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +183 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/suggestions/rules.js +1 -1
- package/dist/cli/suggestions/rules.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 +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,18 +49,25 @@ Requires Node.js 22+.
|
|
|
49
49
|
|
|
50
50
|
## Quick Start
|
|
51
51
|
|
|
52
|
-
1. Initialize
|
|
52
|
+
1. Initialize a project:
|
|
53
53
|
```bash
|
|
54
54
|
search-hub init
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
This creates
|
|
57
|
+
This creates a `.search-hub/` directory in the current folder with project config, sessions, and queries.
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
For global setup (API keys, user preferences):
|
|
60
|
+
```bash
|
|
61
|
+
search-hub init --global
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This creates a config file in your platform-specific config directory:
|
|
65
|
+
|
|
66
|
+
| Platform | Global Config |
|
|
67
|
+
|----------|---------------|
|
|
68
|
+
| Linux | `~/.config/search-hub/config.toml` |
|
|
69
|
+
| macOS | `~/Library/Preferences/search-hub/config.toml` |
|
|
70
|
+
| Windows | `%APPDATA%/search-hub/Config/config.toml` |
|
|
64
71
|
|
|
65
72
|
2. Create a query file:
|
|
66
73
|
```bash
|
|
@@ -21,6 +21,13 @@ export declare function getNestedValue(obj: Record<string, unknown>, path: strin
|
|
|
21
21
|
* setNestedValue({ a: { b: 1 } }, 'a.b', 2) // modifies obj to { a: { b: 2 } }
|
|
22
22
|
*/
|
|
23
23
|
export declare function setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void;
|
|
24
|
+
/**
|
|
25
|
+
* Flatten a nested object into dot-notation keys.
|
|
26
|
+
*/
|
|
27
|
+
export declare function flattenObject(obj: Record<string, unknown>, prefix?: string): Array<{
|
|
28
|
+
key: string;
|
|
29
|
+
value: unknown;
|
|
30
|
+
}>;
|
|
24
31
|
/**
|
|
25
32
|
* View all configuration values.
|
|
26
33
|
*/
|
|
@@ -29,9 +36,55 @@ export declare function viewConfig(config: Config): string;
|
|
|
29
36
|
* View a specific configuration key.
|
|
30
37
|
*/
|
|
31
38
|
export declare function viewConfigKey(config: Config, key: string): ConfigResult;
|
|
39
|
+
/**
|
|
40
|
+
* Parse a string value to its appropriate type.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseValue(value: string, existingValue: unknown): unknown;
|
|
32
43
|
/**
|
|
33
44
|
* Set a configuration key to a new value.
|
|
34
45
|
* Only allows setting keys that already exist in the configuration.
|
|
35
46
|
*/
|
|
36
47
|
export declare function setConfigKey(config: Config, key: string, value: string): ConfigResult;
|
|
48
|
+
/**
|
|
49
|
+
* Write scope resolution result.
|
|
50
|
+
*/
|
|
51
|
+
export type WriteScope = {
|
|
52
|
+
scope: 'global';
|
|
53
|
+
} | {
|
|
54
|
+
scope: 'local';
|
|
55
|
+
} | {
|
|
56
|
+
scope: 'error';
|
|
57
|
+
error: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the write scope based on flags and project context.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveWriteScope(opts: {
|
|
63
|
+
global: boolean;
|
|
64
|
+
local: boolean;
|
|
65
|
+
insideProject: boolean;
|
|
66
|
+
}): WriteScope;
|
|
67
|
+
/**
|
|
68
|
+
* Check if writing a key to local config should trigger a warning.
|
|
69
|
+
* Returns warning message or null.
|
|
70
|
+
*/
|
|
71
|
+
export declare function checkSecretKeyWarning(key: string, scope: 'global' | 'local'): string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Format a config value with its origin information.
|
|
74
|
+
* Format: <origin>\t<path>\t<key> = <value>
|
|
75
|
+
*/
|
|
76
|
+
export declare function formatShowOrigin(key: string, value: string, origin: string, path: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Build show-origin output for all keys in a merged config.
|
|
79
|
+
* Checks each key against env, local, global sources in priority order.
|
|
80
|
+
*/
|
|
81
|
+
export declare function viewConfigAllOrigins(merged: Config, envVarMap: Record<string, string>, localConfig: Record<string, unknown>, localPath: string, globalConfig: Record<string, unknown>, globalPath: string): string;
|
|
82
|
+
/**
|
|
83
|
+
* View config values from a partial (filtered) config object.
|
|
84
|
+
*/
|
|
85
|
+
export declare function viewConfigFiltered(partial: Record<string, unknown>): string;
|
|
86
|
+
/**
|
|
87
|
+
* Format the ENV_VAR_MAP as a human-readable table.
|
|
88
|
+
*/
|
|
89
|
+
export declare function formatEnvVars(): string;
|
|
37
90
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,IAAI,EAAE,MAAM,GACX,OAAO,CAeT;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,IAAI,CAcN;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,MAAM,SAAK,GACV,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAgBxC;AAcD;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAiBvE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAazE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GACZ,YAAY,CAiCd;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAClB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,UAAU,CAcb;AAKD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,QAAQ,GAAG,OAAO,GACxB,MAAM,GAAG,IAAI,CAOf;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,UAAU,EAAE,MAAM,GACjB,MAAM,CAuBR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAKR;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAItC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ENV_VAR_MAP } from "../../config/env.js";
|
|
1
2
|
function getNestedValue(obj, path) {
|
|
2
3
|
const keys = path.split(".");
|
|
3
4
|
let current = obj;
|
|
@@ -108,11 +109,72 @@ function setConfigKey(config, key, value) {
|
|
|
108
109
|
value: formatValue(parsedValue)
|
|
109
110
|
};
|
|
110
111
|
}
|
|
112
|
+
function resolveWriteScope(opts) {
|
|
113
|
+
if (opts.global && opts.local) {
|
|
114
|
+
return { scope: "error", error: "--global and --local are mutually exclusive" };
|
|
115
|
+
}
|
|
116
|
+
if (opts.local && !opts.insideProject) {
|
|
117
|
+
return {
|
|
118
|
+
scope: "error",
|
|
119
|
+
error: '--local requires a project directory (.search-hub/). Run "search-hub init" first.'
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (opts.global) return { scope: "global" };
|
|
123
|
+
if (opts.local) return { scope: "local" };
|
|
124
|
+
return opts.insideProject ? { scope: "local" } : { scope: "global" };
|
|
125
|
+
}
|
|
126
|
+
const SECRET_KEY_SUFFIXES = ["api_key", "inst_token", "email"];
|
|
127
|
+
function checkSecretKeyWarning(key, scope) {
|
|
128
|
+
if (scope !== "local") return null;
|
|
129
|
+
const lastPart = key.split(".").pop() ?? "";
|
|
130
|
+
if (SECRET_KEY_SUFFIXES.includes(lastPart)) {
|
|
131
|
+
return `Warning: "${key}" contains sensitive data. Consider using --global to store it in the global config instead.`;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function formatShowOrigin(key, value, origin, path) {
|
|
136
|
+
return `${origin} ${path} ${key} = ${value}`;
|
|
137
|
+
}
|
|
138
|
+
function viewConfigAllOrigins(merged, envVarMap, localConfig, localPath, globalConfig, globalPath) {
|
|
139
|
+
const flattened = flattenObject(merged);
|
|
140
|
+
const lines = flattened.map(({ key, value }) => {
|
|
141
|
+
const formattedValue = formatValue(value);
|
|
142
|
+
const envEntry = Object.entries(envVarMap).find(([, path]) => path === key);
|
|
143
|
+
if (envEntry && process.env[envEntry[0]] !== void 0) {
|
|
144
|
+
return formatShowOrigin(key, formattedValue, "env", envEntry[0]);
|
|
145
|
+
}
|
|
146
|
+
const localVal = getNestedValue(localConfig, key);
|
|
147
|
+
if (localVal !== void 0) {
|
|
148
|
+
return formatShowOrigin(key, formattedValue, "local", localPath);
|
|
149
|
+
}
|
|
150
|
+
const globalVal = getNestedValue(globalConfig, key);
|
|
151
|
+
if (globalVal !== void 0) {
|
|
152
|
+
return formatShowOrigin(key, formattedValue, "global", globalPath);
|
|
153
|
+
}
|
|
154
|
+
return formatShowOrigin(key, formattedValue, "default", "");
|
|
155
|
+
});
|
|
156
|
+
return lines.join("\n");
|
|
157
|
+
}
|
|
158
|
+
function viewConfigFiltered(partial) {
|
|
159
|
+
const flattened = flattenObject(partial);
|
|
160
|
+
return flattened.map(({ key, value }) => `${key} = ${formatValue(value)}`).join("\n");
|
|
161
|
+
}
|
|
162
|
+
function formatEnvVars() {
|
|
163
|
+
return Object.entries(ENV_VAR_MAP).map(([envVar, configPath]) => `${envVar} → ${configPath}`).join("\n");
|
|
164
|
+
}
|
|
111
165
|
export {
|
|
166
|
+
checkSecretKeyWarning,
|
|
167
|
+
flattenObject,
|
|
168
|
+
formatEnvVars,
|
|
169
|
+
formatShowOrigin,
|
|
112
170
|
getNestedValue,
|
|
171
|
+
parseValue,
|
|
172
|
+
resolveWriteScope,
|
|
113
173
|
setConfigKey,
|
|
114
174
|
setNestedValue,
|
|
115
175
|
viewConfig,
|
|
176
|
+
viewConfigAllOrigins,
|
|
177
|
+
viewConfigFiltered,
|
|
116
178
|
viewConfigKey
|
|
117
179
|
};
|
|
118
180
|
//# sourceMappingURL=config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sources":["../../../src/cli/commands/config.ts"],"sourcesContent":["/**\n * Config command implementation.\n *\n * Provides functionality to view and edit configuration values.\n */\nimport type { Config } from '../../config/index.js';\n\n/**\n * Result of a config operation.\n */\nexport interface ConfigResult {\n success: boolean;\n value?: string;\n error?: string;\n}\n\n/**\n * Get a nested value from an object using dot notation.\n *\n * @example\n * getNestedValue({ a: { b: 1 } }, 'a.b') // returns 1\n */\nexport function getNestedValue(\n obj: Record<string, unknown>,\n path: string\n): unknown {\n const keys = path.split('.');\n let current: unknown = obj;\n\n for (const key of keys) {\n if (current === null || current === undefined) {\n return undefined;\n }\n if (typeof current !== 'object') {\n return undefined;\n }\n current = (current as Record<string, unknown>)[key];\n }\n\n return current;\n}\n\n/**\n * Set a nested value in an object using dot notation.\n *\n * @example\n * setNestedValue({ a: { b: 1 } }, 'a.b', 2) // modifies obj to { a: { b: 2 } }\n */\nexport function setNestedValue(\n obj: Record<string, unknown>,\n path: string,\n value: unknown\n): void {\n const keys = path.split('.');\n let current = obj;\n\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]!;\n if (!(key in current) || typeof current[key] !== 'object') {\n current[key] = {};\n }\n current = current[key] as Record<string, unknown>;\n }\n\n const lastKey = keys[keys.length - 1]!;\n current[lastKey] = value;\n}\n\n/**\n * Flatten a nested object into dot-notation keys.\n */\nfunction flattenObject(\n obj: Record<string, unknown>,\n prefix = ''\n): Array<{ key: string; value: unknown }> {\n const result: Array<{ key: string; value: unknown }> = [];\n\n for (const [key, value] of Object.entries(obj)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n result.push(\n ...flattenObject(value as Record<string, unknown>, fullKey)\n );\n } else {\n result.push({ key: fullKey, value });\n }\n }\n\n return result;\n}\n\n/**\n * Format a value for display.\n */\nfunction formatValue(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value === 'string') return value;\n if (Array.isArray(value)) return JSON.stringify(value);\n if (typeof value === 'object') return JSON.stringify(value, null, 2);\n return String(value);\n}\n\n/**\n * View all configuration values.\n */\nexport function viewConfig(config: Config): string {\n const flattened = flattenObject(config as unknown as Record<string, unknown>);\n const lines = flattened.map(({ key, value }) => {\n const formattedValue = formatValue(value);\n return `${key} = ${formattedValue}`;\n });\n return lines.join('\\n');\n}\n\n/**\n * View a specific configuration key.\n */\nexport function viewConfigKey(config: Config, key: string): ConfigResult {\n const value = getNestedValue(\n config as unknown as Record<string, unknown>,\n key\n );\n\n if (value === undefined) {\n return {\n success: false,\n error: `Key \"${key}\" not found in configuration`,\n };\n }\n\n return {\n success: true,\n value: formatValue(value),\n };\n}\n\n/**\n * Parse a string value to its appropriate type.\n */\nfunction parseValue(value: string, existingValue: unknown): unknown {\n // Boolean\n if (value === 'true') return true;\n if (value === 'false') return false;\n\n // Number (if existing value is a number)\n if (typeof existingValue === 'number') {\n const num = Number(value);\n if (!isNaN(num)) return num;\n }\n\n // Default to string\n return value;\n}\n\n/**\n * Set a configuration key to a new value.\n * Only allows setting keys that already exist in the configuration.\n */\nexport function setConfigKey(\n config: Config,\n key: string,\n value: string\n): ConfigResult {\n if (!key) {\n return {\n success: false,\n error: 'Key cannot be empty',\n };\n }\n\n const existingValue = getNestedValue(\n config as unknown as Record<string, unknown>,\n key\n );\n\n // Reject unknown keys\n if (existingValue === undefined) {\n return {\n success: false,\n error: `Unknown configuration key: \"${key}\". Use \"search-hub config\" to see available keys.`,\n };\n }\n\n const parsedValue = parseValue(value, existingValue);\n\n setNestedValue(\n config as unknown as Record<string, unknown>,\n key,\n parsedValue\n );\n\n return {\n success: true,\n value: formatValue(parsedValue),\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.js","sources":["../../../src/cli/commands/config.ts"],"sourcesContent":["/**\n * Config command implementation.\n *\n * Provides functionality to view and edit configuration values.\n */\nimport type { Config } from '../../config/index.js';\nimport { ENV_VAR_MAP } from '../../config/env.js';\n\n/**\n * Result of a config operation.\n */\nexport interface ConfigResult {\n success: boolean;\n value?: string;\n error?: string;\n}\n\n/**\n * Get a nested value from an object using dot notation.\n *\n * @example\n * getNestedValue({ a: { b: 1 } }, 'a.b') // returns 1\n */\nexport function getNestedValue(\n obj: Record<string, unknown>,\n path: string\n): unknown {\n const keys = path.split('.');\n let current: unknown = obj;\n\n for (const key of keys) {\n if (current === null || current === undefined) {\n return undefined;\n }\n if (typeof current !== 'object') {\n return undefined;\n }\n current = (current as Record<string, unknown>)[key];\n }\n\n return current;\n}\n\n/**\n * Set a nested value in an object using dot notation.\n *\n * @example\n * setNestedValue({ a: { b: 1 } }, 'a.b', 2) // modifies obj to { a: { b: 2 } }\n */\nexport function setNestedValue(\n obj: Record<string, unknown>,\n path: string,\n value: unknown\n): void {\n const keys = path.split('.');\n let current = obj;\n\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]!;\n if (!(key in current) || typeof current[key] !== 'object') {\n current[key] = {};\n }\n current = current[key] as Record<string, unknown>;\n }\n\n const lastKey = keys[keys.length - 1]!;\n current[lastKey] = value;\n}\n\n/**\n * Flatten a nested object into dot-notation keys.\n */\nexport function flattenObject(\n obj: Record<string, unknown>,\n prefix = ''\n): Array<{ key: string; value: unknown }> {\n const result: Array<{ key: string; value: unknown }> = [];\n\n for (const [key, value] of Object.entries(obj)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n result.push(\n ...flattenObject(value as Record<string, unknown>, fullKey)\n );\n } else {\n result.push({ key: fullKey, value });\n }\n }\n\n return result;\n}\n\n/**\n * Format a value for display.\n */\nfunction formatValue(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value === 'string') return value;\n if (Array.isArray(value)) return JSON.stringify(value);\n if (typeof value === 'object') return JSON.stringify(value, null, 2);\n return String(value);\n}\n\n/**\n * View all configuration values.\n */\nexport function viewConfig(config: Config): string {\n const flattened = flattenObject(config as unknown as Record<string, unknown>);\n const lines = flattened.map(({ key, value }) => {\n const formattedValue = formatValue(value);\n return `${key} = ${formattedValue}`;\n });\n return lines.join('\\n');\n}\n\n/**\n * View a specific configuration key.\n */\nexport function viewConfigKey(config: Config, key: string): ConfigResult {\n const value = getNestedValue(\n config as unknown as Record<string, unknown>,\n key\n );\n\n if (value === undefined) {\n return {\n success: false,\n error: `Key \"${key}\" not found in configuration`,\n };\n }\n\n return {\n success: true,\n value: formatValue(value),\n };\n}\n\n/**\n * Parse a string value to its appropriate type.\n */\nexport function parseValue(value: string, existingValue: unknown): unknown {\n // Boolean\n if (value === 'true') return true;\n if (value === 'false') return false;\n\n // Number (if existing value is a number)\n if (typeof existingValue === 'number') {\n const num = Number(value);\n if (!isNaN(num)) return num;\n }\n\n // Default to string\n return value;\n}\n\n/**\n * Set a configuration key to a new value.\n * Only allows setting keys that already exist in the configuration.\n */\nexport function setConfigKey(\n config: Config,\n key: string,\n value: string\n): ConfigResult {\n if (!key) {\n return {\n success: false,\n error: 'Key cannot be empty',\n };\n }\n\n const existingValue = getNestedValue(\n config as unknown as Record<string, unknown>,\n key\n );\n\n // Reject unknown keys\n if (existingValue === undefined) {\n return {\n success: false,\n error: `Unknown configuration key: \"${key}\". Use \"search-hub config\" to see available keys.`,\n };\n }\n\n const parsedValue = parseValue(value, existingValue);\n\n setNestedValue(\n config as unknown as Record<string, unknown>,\n key,\n parsedValue\n );\n\n return {\n success: true,\n value: formatValue(parsedValue),\n };\n}\n\n/**\n * Write scope resolution result.\n */\nexport type WriteScope =\n | { scope: 'global' }\n | { scope: 'local' }\n | { scope: 'error'; error: string };\n\n/**\n * Resolve the write scope based on flags and project context.\n */\nexport function resolveWriteScope(opts: {\n global: boolean;\n local: boolean;\n insideProject: boolean;\n}): WriteScope {\n if (opts.global && opts.local) {\n return { scope: 'error', error: '--global and --local are mutually exclusive' };\n }\n if (opts.local && !opts.insideProject) {\n return {\n scope: 'error',\n error: '--local requires a project directory (.search-hub/). Run \"search-hub init\" first.',\n };\n }\n if (opts.global) return { scope: 'global' };\n if (opts.local) return { scope: 'local' };\n // Default: local if inside project, global otherwise\n return opts.insideProject ? { scope: 'local' } : { scope: 'global' };\n}\n\n/** Keys considered secrets that should not be stored in local config. */\nconst SECRET_KEY_SUFFIXES = ['api_key', 'inst_token', 'email'];\n\n/**\n * Check if writing a key to local config should trigger a warning.\n * Returns warning message or null.\n */\nexport function checkSecretKeyWarning(\n key: string,\n scope: 'global' | 'local'\n): string | null {\n if (scope !== 'local') return null;\n const lastPart = key.split('.').pop() ?? '';\n if (SECRET_KEY_SUFFIXES.includes(lastPart)) {\n return `Warning: \"${key}\" contains sensitive data. Consider using --global to store it in the global config instead.`;\n }\n return null;\n}\n\n/**\n * Format a config value with its origin information.\n * Format: <origin>\\t<path>\\t<key> = <value>\n */\nexport function formatShowOrigin(\n key: string,\n value: string,\n origin: string,\n path: string\n): string {\n return `${origin}\\t${path}\\t${key} = ${value}`;\n}\n\n/**\n * Build show-origin output for all keys in a merged config.\n * Checks each key against env, local, global sources in priority order.\n */\nexport function viewConfigAllOrigins(\n merged: Config,\n envVarMap: Record<string, string>,\n localConfig: Record<string, unknown>,\n localPath: string,\n globalConfig: Record<string, unknown>,\n globalPath: string\n): string {\n const flattened = flattenObject(merged as unknown as Record<string, unknown>);\n const lines = flattened.map(({ key, value }) => {\n const formattedValue = formatValue(value);\n // Check env\n const envEntry = Object.entries(envVarMap).find(([, path]) => path === key);\n if (envEntry && process.env[envEntry[0]] !== undefined) {\n return formatShowOrigin(key, formattedValue, 'env', envEntry[0]);\n }\n // Check local\n const localVal = getNestedValue(localConfig, key);\n if (localVal !== undefined) {\n return formatShowOrigin(key, formattedValue, 'local', localPath);\n }\n // Check global\n const globalVal = getNestedValue(globalConfig, key);\n if (globalVal !== undefined) {\n return formatShowOrigin(key, formattedValue, 'global', globalPath);\n }\n // Default\n return formatShowOrigin(key, formattedValue, 'default', '');\n });\n return lines.join('\\n');\n}\n\n/**\n * View config values from a partial (filtered) config object.\n */\nexport function viewConfigFiltered(\n partial: Record<string, unknown>\n): string {\n const flattened = flattenObject(partial);\n return flattened\n .map(({ key, value }) => `${key} = ${formatValue(value)}`)\n .join('\\n');\n}\n\n/**\n * Format the ENV_VAR_MAP as a human-readable table.\n */\nexport function formatEnvVars(): string {\n return Object.entries(ENV_VAR_MAP)\n .map(([envVar, configPath]) => `${envVar} → ${configPath}`)\n .join('\\n');\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ import { sessionExists } from "../../../session/manager.js";
|
|
|
12
12
|
import { loadConfig } from "../../../config/loader.js";
|
|
13
13
|
import "../../../config/schema.js";
|
|
14
14
|
import "../../../config/defaults.js";
|
|
15
|
+
import "../../../config/paths.js";
|
|
15
16
|
import { join } from "node:path";
|
|
16
17
|
function registerFulltextCommands(program, getSessionsDir) {
|
|
17
18
|
const fulltextCommand = program.command("fulltext").description("Fulltext management: retrieval, conversion, attachment").addHelpText("after", `
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/cli/commands/fulltext/index.ts"],"sourcesContent":["/**\n * Fulltext command group registration.\n */\n\nimport type { Command } from 'commander';\nimport { executeFulltextInit } from './init.js';\nimport { executeFulltextSync } from './sync.js';\nimport { executeFulltextConvert } from './convert.js';\nimport { executeFulltextCheck } from './check.js';\nimport { executeFulltextFetch } from './fetch.js';\nimport { executeFulltextAttach } from './attach.js';\nimport { executeFulltextStatus, type FulltextStatusResult } from './status.js';\nimport { executeFulltextPending, type PendingArticle } from './pending.js';\nimport { formatInitOutput, formatSyncOutput } from './format.js';\nimport type { GlobalOptions } from '../../index.js';\nimport { EXIT_CODES } from '../../exit-codes.js';\nimport { sessionExists } from '../../../session/manager.js';\nimport { loadConfig } from '../../../config/index.js';\nimport { join } from 'node:path';\n\nexport function registerFulltextCommands(\n program: Command,\n getSessionsDir: (opts: GlobalOptions) => Promise<string>,\n): void {\n const fulltextCommand = program\n .command('fulltext')\n .description('Fulltext management: retrieval, conversion, attachment')\n .addHelpText('after', `\nExamples:\n $ search-hub fulltext init SESSION_ID # Create directories for included articles\n $ search-hub fulltext init SESSION_ID --dry-run # Preview what would be created\n $ search-hub fulltext sync SESSION_ID # Detect and register added files\n $ search-hub fulltext sync SESSION_ID --dry-run # Preview what would be synced\n $ search-hub fulltext convert SESSION_ID # Convert PMC XML to Markdown\n $ search-hub fulltext check --session SESSION_ID # Check OA availability\n $ search-hub fulltext fetch SESSION_ID # Download available OA articles\n $ search-hub fulltext fetch SESSION_ID --dry-run # Preview what would be downloaded\n $ search-hub fulltext fetch SESSION_ID --source pmc # Download from specific sources\n $ search-hub fulltext attach SESSION_ID # Attach fulltexts to ref entries\n $ search-hub fulltext attach SESSION_ID --dry-run # Preview what would be attached\n $ search-hub fulltext status SESSION_ID # Show fulltext retrieval status\n $ search-hub fulltext pending SESSION_ID # List articles needing download`);\n\n fulltextCommand\n .command('init')\n .description('Create directories for included articles with meta.json and README')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be created without creating', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const result = await executeFulltextInit({\n sessionId,\n sessionsDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n console.log(formatInitOutput(result));\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('sync')\n .description('Detect and register manually added fulltext files')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be synced without modifying', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const result = await executeFulltextSync({\n sessionId,\n sessionsDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n console.log(formatSyncOutput(result));\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('convert')\n .description('Convert PMC XML files to Markdown')\n .argument('<session-id>', 'session ID')\n .option('--article <dir>', 'convert specific article directory')\n .action(async (sessionId: string, options?: { article?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const convertOpts: Parameters<typeof executeFulltextConvert>[0] = { sessionId };\n if (options?.article) convertOpts.article = options.article;\n const result = await executeFulltextConvert(convertOpts, sessionsDir);\n\n if (!globalOpts.quiet) {\n console.log(`Converted: ${result.converted} Skipped: ${result.skipped} Failed: ${result.failed}`);\n for (const article of result.articles) {\n const icon = article.status === 'converted' ? '+' : article.status === 'skipped' ? '-' : '!';\n console.log(` [${icon}] ${article.dirName}: ${article.title}`);\n }\n }\n\n process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.SESSION_ERROR;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('attach')\n .description('Attach fulltext files to reference-manager entries')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be attached without attaching', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionDir = join(sessionsDir, sessionId);\n\n const result = await executeFulltextAttach({\n sessionDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n const prefix = options.dryRun ? 'Would attach' : 'Attached';\n console.log(`\\nFulltext ${prefix.toLowerCase()}:`);\n if (result.summary.attached > 0) {\n const totalFiles = result.attached.reduce((sum, a) => sum + a.files.length, 0);\n console.log(` ✓ ${result.summary.attached} articles (${totalFiles} files)`);\n for (const item of result.attached) {\n console.log(` ${item.refId}: ${item.files.join(', ')}`);\n }\n }\n if (result.summary.skipped > 0) {\n console.log(` ⚠ ${result.summary.skipped} skipped`);\n }\n if (result.summary.failed > 0) {\n console.log(` ✗ ${result.summary.failed} failed`);\n for (const item of result.failed) {\n console.log(` ${item.dirName}: ${item.reason}`);\n }\n }\n if (result.summary.total === 0) {\n console.log(' No fulltext directories found.');\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('check')\n .description('Check Open Access availability for included articles')\n .requiredOption('--session <id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .action(async (options: { session: string; format: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, options.session);\n const config = await loadConfig(\n globalOpts.config ? { globalConfigPath: globalOpts.config } : {}\n );\n const result = await executeFulltextCheck({\n sessionDir,\n config: {\n unpaywallEmail: config.fulltext?.sources?.unpaywall_email ?? '',\n coreApiKey: config.fulltext?.sources?.core_api_key ?? '',\n ncbiEmail: config.fulltext?.sources?.ncbi_email ?? '',\n ncbiTool: config.fulltext?.sources?.ncbi_tool ?? 'search-hub',\n preferSources: config.fulltext?.sources?.prefer_sources ?? ['pmc', 'arxiv', 'unpaywall', 'core'],\n },\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(`\\nOA Status Summary:`);\n console.log(` Open Access: ${result.summary.open}`);\n console.log(` Closed Access: ${result.summary.closed}`);\n console.log(` Unknown: ${result.summary.unknown}`);\n console.log(` Total: ${result.summary.total}`);\n if (result.summary.open > 0) {\n console.log(`\\nRun \\`fulltext fetch\\` to download available OA articles.`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('fetch')\n .description('Download available OA fulltexts')\n .argument('<session-id>', 'session ID')\n .option('--source <sources>', 'filter by source (comma-separated: pmc,arxiv,unpaywall,core)')\n .option('--no-convert-markdown', 'skip auto-conversion of PMC XML to Markdown')\n .option('--dry-run', 'show what would be downloaded without downloading', false)\n .action(async (sessionId: string, options: { source?: string; convertMarkdown: boolean; dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const fetchOpts: Parameters<typeof executeFulltextFetch>[0] = {\n sessionId,\n sessionsDir,\n convertMarkdown: options.convertMarkdown,\n dryRun: options.dryRun,\n };\n if (options.source) fetchOpts.source = options.source.split(',');\n const result = await executeFulltextFetch(fetchOpts);\n\n if (!globalOpts.quiet) {\n if (result.dryRun) {\n console.log(`\\nDry run: would fetch ${result.articles.length} articles`);\n for (const article of result.articles) {\n console.log(` ${article.dirName}: ${article.title} (${article.locationCount} sources)`);\n }\n } else {\n console.log(`\\nFulltext Fetch Summary:`);\n console.log(` Downloaded: ${result.summary.downloaded}`);\n console.log(` Failed: ${result.summary.failed}`);\n console.log(` Skipped: ${result.summary.skipped}`);\n console.log(` Total: ${result.summary.total}`);\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('status')\n .description('Show overall fulltext retrieval status')\n .argument('<session-id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .action(async (sessionId: string, options: { format: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n const result = await executeFulltextStatus({\n sessionDir,\n format: options.format as 'table' | 'json',\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(formatStatusOutput(result, sessionId));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('pending')\n .description('List articles needing manual fulltext download')\n .argument('<session-id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .option('--export <file>', 'export URLs to file for batch download')\n .action(async (sessionId: string, options: { format: string; export?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n const result = await executeFulltextPending({\n sessionDir,\n format: options.format as 'table' | 'json',\n exportPath: options.export,\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(formatPendingOutput(result.articles, result.totalPending));\n if (options.export) {\n console.log(`\\nExported URLs to ${options.export}`);\n } else if (result.totalPending > 0) {\n console.log(`\\nExport URLs: fulltext pending ${sessionId} --export urls.txt`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n}\n\nfunction formatStatusOutput(result: FulltextStatusResult, sessionId: string): string {\n const lines = [\n `Fulltext Status: ${sessionId}`,\n '',\n ` Included articles: ${result.totalIncluded}`,\n ` With fulltext: ${result.withFulltext}`,\n ` - PDF only: ${result.pdfOnly}`,\n ` - Markdown only: ${result.markdownOnly}`,\n ` - Both: ${result.both}`,\n ` Pending: ${result.pending} (directories created, no files)`,\n ` Not initialized: ${result.notInitialized} (no directory)`,\n ];\n return lines.join('\\n');\n}\n\nfunction formatPendingOutput(articles: PendingArticle[], total: number): string {\n if (total === 0) {\n return 'All included articles have fulltext.';\n }\n\n const lines: string[] = [`${total} articles need fulltext:`, ''];\n\n for (const [i, article] of articles.entries()) {\n const num = i + 1;\n const identifier = article.dirName ?? '(not initialized)';\n lines.push(`${num}. ${identifier} - \"${article.title}\"`);\n if (article.doi) {\n lines.push(` DOI: ${article.doi}`);\n }\n if (article.publisherUrl) {\n lines.push(` Publisher: ${article.publisherUrl}`);\n }\n if (article.oaLocations) {\n for (const loc of article.oaLocations) {\n lines.push(` ${loc.source}: ${loc.url}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/cli/commands/fulltext/index.ts"],"sourcesContent":["/**\n * Fulltext command group registration.\n */\n\nimport type { Command } from 'commander';\nimport { executeFulltextInit } from './init.js';\nimport { executeFulltextSync } from './sync.js';\nimport { executeFulltextConvert } from './convert.js';\nimport { executeFulltextCheck } from './check.js';\nimport { executeFulltextFetch } from './fetch.js';\nimport { executeFulltextAttach } from './attach.js';\nimport { executeFulltextStatus, type FulltextStatusResult } from './status.js';\nimport { executeFulltextPending, type PendingArticle } from './pending.js';\nimport { formatInitOutput, formatSyncOutput } from './format.js';\nimport type { GlobalOptions } from '../../index.js';\nimport { EXIT_CODES } from '../../exit-codes.js';\nimport { sessionExists } from '../../../session/manager.js';\nimport { loadConfig } from '../../../config/index.js';\nimport { join } from 'node:path';\n\nexport function registerFulltextCommands(\n program: Command,\n getSessionsDir: (opts: GlobalOptions) => Promise<string>,\n): void {\n const fulltextCommand = program\n .command('fulltext')\n .description('Fulltext management: retrieval, conversion, attachment')\n .addHelpText('after', `\nExamples:\n $ search-hub fulltext init SESSION_ID # Create directories for included articles\n $ search-hub fulltext init SESSION_ID --dry-run # Preview what would be created\n $ search-hub fulltext sync SESSION_ID # Detect and register added files\n $ search-hub fulltext sync SESSION_ID --dry-run # Preview what would be synced\n $ search-hub fulltext convert SESSION_ID # Convert PMC XML to Markdown\n $ search-hub fulltext check --session SESSION_ID # Check OA availability\n $ search-hub fulltext fetch SESSION_ID # Download available OA articles\n $ search-hub fulltext fetch SESSION_ID --dry-run # Preview what would be downloaded\n $ search-hub fulltext fetch SESSION_ID --source pmc # Download from specific sources\n $ search-hub fulltext attach SESSION_ID # Attach fulltexts to ref entries\n $ search-hub fulltext attach SESSION_ID --dry-run # Preview what would be attached\n $ search-hub fulltext status SESSION_ID # Show fulltext retrieval status\n $ search-hub fulltext pending SESSION_ID # List articles needing download`);\n\n fulltextCommand\n .command('init')\n .description('Create directories for included articles with meta.json and README')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be created without creating', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const result = await executeFulltextInit({\n sessionId,\n sessionsDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n console.log(formatInitOutput(result));\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('sync')\n .description('Detect and register manually added fulltext files')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be synced without modifying', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const result = await executeFulltextSync({\n sessionId,\n sessionsDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n console.log(formatSyncOutput(result));\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('convert')\n .description('Convert PMC XML files to Markdown')\n .argument('<session-id>', 'session ID')\n .option('--article <dir>', 'convert specific article directory')\n .action(async (sessionId: string, options?: { article?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const convertOpts: Parameters<typeof executeFulltextConvert>[0] = { sessionId };\n if (options?.article) convertOpts.article = options.article;\n const result = await executeFulltextConvert(convertOpts, sessionsDir);\n\n if (!globalOpts.quiet) {\n console.log(`Converted: ${result.converted} Skipped: ${result.skipped} Failed: ${result.failed}`);\n for (const article of result.articles) {\n const icon = article.status === 'converted' ? '+' : article.status === 'skipped' ? '-' : '!';\n console.log(` [${icon}] ${article.dirName}: ${article.title}`);\n }\n }\n\n process.exitCode = result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.SESSION_ERROR;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('attach')\n .description('Attach fulltext files to reference-manager entries')\n .argument('<session-id>', 'session ID')\n .option('--dry-run', 'show what would be attached without attaching', false)\n .action(async (sessionId: string, options: { dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n if (!(await sessionExists(sessionId, sessionsDir))) {\n if (!globalOpts.quiet) {\n console.error(`Error: session '${sessionId}' not found`);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n return;\n }\n\n const sessionDir = join(sessionsDir, sessionId);\n\n const result = await executeFulltextAttach({\n sessionDir,\n dryRun: options.dryRun,\n });\n\n if (!globalOpts.quiet) {\n const prefix = options.dryRun ? 'Would attach' : 'Attached';\n console.log(`\\nFulltext ${prefix.toLowerCase()}:`);\n if (result.summary.attached > 0) {\n const totalFiles = result.attached.reduce((sum, a) => sum + a.files.length, 0);\n console.log(` ✓ ${result.summary.attached} articles (${totalFiles} files)`);\n for (const item of result.attached) {\n console.log(` ${item.refId}: ${item.files.join(', ')}`);\n }\n }\n if (result.summary.skipped > 0) {\n console.log(` ⚠ ${result.summary.skipped} skipped`);\n }\n if (result.summary.failed > 0) {\n console.log(` ✗ ${result.summary.failed} failed`);\n for (const item of result.failed) {\n console.log(` ${item.dirName}: ${item.reason}`);\n }\n }\n if (result.summary.total === 0) {\n console.log(' No fulltext directories found.');\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error(\n 'Error:',\n error instanceof Error ? error.message : error,\n );\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('check')\n .description('Check Open Access availability for included articles')\n .requiredOption('--session <id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .action(async (options: { session: string; format: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, options.session);\n const config = await loadConfig(\n globalOpts.config ? { globalConfigPath: globalOpts.config } : {}\n );\n const result = await executeFulltextCheck({\n sessionDir,\n config: {\n unpaywallEmail: config.fulltext?.sources?.unpaywall_email ?? '',\n coreApiKey: config.fulltext?.sources?.core_api_key ?? '',\n ncbiEmail: config.fulltext?.sources?.ncbi_email ?? '',\n ncbiTool: config.fulltext?.sources?.ncbi_tool ?? 'search-hub',\n preferSources: config.fulltext?.sources?.prefer_sources ?? ['pmc', 'arxiv', 'unpaywall', 'core'],\n },\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(`\\nOA Status Summary:`);\n console.log(` Open Access: ${result.summary.open}`);\n console.log(` Closed Access: ${result.summary.closed}`);\n console.log(` Unknown: ${result.summary.unknown}`);\n console.log(` Total: ${result.summary.total}`);\n if (result.summary.open > 0) {\n console.log(`\\nRun \\`fulltext fetch\\` to download available OA articles.`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('fetch')\n .description('Download available OA fulltexts')\n .argument('<session-id>', 'session ID')\n .option('--source <sources>', 'filter by source (comma-separated: pmc,arxiv,unpaywall,core)')\n .option('--no-convert-markdown', 'skip auto-conversion of PMC XML to Markdown')\n .option('--dry-run', 'show what would be downloaded without downloading', false)\n .action(async (sessionId: string, options: { source?: string; convertMarkdown: boolean; dryRun: boolean }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n\n const fetchOpts: Parameters<typeof executeFulltextFetch>[0] = {\n sessionId,\n sessionsDir,\n convertMarkdown: options.convertMarkdown,\n dryRun: options.dryRun,\n };\n if (options.source) fetchOpts.source = options.source.split(',');\n const result = await executeFulltextFetch(fetchOpts);\n\n if (!globalOpts.quiet) {\n if (result.dryRun) {\n console.log(`\\nDry run: would fetch ${result.articles.length} articles`);\n for (const article of result.articles) {\n console.log(` ${article.dirName}: ${article.title} (${article.locationCount} sources)`);\n }\n } else {\n console.log(`\\nFulltext Fetch Summary:`);\n console.log(` Downloaded: ${result.summary.downloaded}`);\n console.log(` Failed: ${result.summary.failed}`);\n console.log(` Skipped: ${result.summary.skipped}`);\n console.log(` Total: ${result.summary.total}`);\n }\n }\n\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('status')\n .description('Show overall fulltext retrieval status')\n .argument('<session-id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .action(async (sessionId: string, options: { format: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n const result = await executeFulltextStatus({\n sessionDir,\n format: options.format as 'table' | 'json',\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(formatStatusOutput(result, sessionId));\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n\n fulltextCommand\n .command('pending')\n .description('List articles needing manual fulltext download')\n .argument('<session-id>', 'session ID')\n .option('--format <format>', 'output format (table or json)', 'table')\n .option('--export <file>', 'export URLs to file for batch download')\n .action(async (sessionId: string, options: { format: string; export?: string }) => {\n const globalOpts = program.opts() as GlobalOptions;\n try {\n const sessionsDir = await getSessionsDir(globalOpts);\n const sessionDir = join(sessionsDir, sessionId);\n const result = await executeFulltextPending({\n sessionDir,\n format: options.format as 'table' | 'json',\n exportPath: options.export,\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (!globalOpts.quiet) {\n console.log(formatPendingOutput(result.articles, result.totalPending));\n if (options.export) {\n console.log(`\\nExported URLs to ${options.export}`);\n } else if (result.totalPending > 0) {\n console.log(`\\nExport URLs: fulltext pending ${sessionId} --export urls.txt`);\n }\n }\n process.exitCode = EXIT_CODES.SUCCESS;\n } catch (error) {\n if (!globalOpts.quiet) {\n console.error('Error:', error instanceof Error ? error.message : error);\n }\n process.exitCode = EXIT_CODES.SESSION_ERROR;\n }\n });\n}\n\nfunction formatStatusOutput(result: FulltextStatusResult, sessionId: string): string {\n const lines = [\n `Fulltext Status: ${sessionId}`,\n '',\n ` Included articles: ${result.totalIncluded}`,\n ` With fulltext: ${result.withFulltext}`,\n ` - PDF only: ${result.pdfOnly}`,\n ` - Markdown only: ${result.markdownOnly}`,\n ` - Both: ${result.both}`,\n ` Pending: ${result.pending} (directories created, no files)`,\n ` Not initialized: ${result.notInitialized} (no directory)`,\n ];\n return lines.join('\\n');\n}\n\nfunction formatPendingOutput(articles: PendingArticle[], total: number): string {\n if (total === 0) {\n return 'All included articles have fulltext.';\n }\n\n const lines: string[] = [`${total} articles need fulltext:`, ''];\n\n for (const [i, article] of articles.entries()) {\n const num = i + 1;\n const identifier = article.dirName ?? '(not initialized)';\n lines.push(`${num}. ${identifier} - \"${article.title}\"`);\n if (article.doi) {\n lines.push(` DOI: ${article.doi}`);\n }\n if (article.publisherUrl) {\n lines.push(` Publisher: ${article.publisherUrl}`);\n }\n if (article.oaLocations) {\n for (const loc of article.oaLocations) {\n lines.push(` ${loc.source}: ${loc.url}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Options for the init command.
|
|
3
3
|
*/
|
|
4
4
|
export interface InitOptions {
|
|
5
|
-
/**
|
|
5
|
+
/** Directory to create .search-hub/ in (defaults to cwd) */
|
|
6
|
+
directory?: string;
|
|
7
|
+
/** Initialize global config instead of local project */
|
|
8
|
+
global?: boolean;
|
|
9
|
+
/** Config directory for global init (defaults to platform-specific via getConfigDir()) */
|
|
6
10
|
configDir?: string;
|
|
7
|
-
/** Data directory (defaults to platform-specific via getDataDir()) */
|
|
8
|
-
dataDir?: string;
|
|
9
11
|
/** Force overwrite if directory already exists */
|
|
10
12
|
force?: boolean;
|
|
11
13
|
}
|
|
@@ -17,29 +19,22 @@ export interface InitResult {
|
|
|
17
19
|
success: boolean;
|
|
18
20
|
/** Path to the created config file */
|
|
19
21
|
configPath: string;
|
|
20
|
-
/** Path to the
|
|
21
|
-
|
|
22
|
-
/** Path to the config directory */
|
|
23
|
-
configDir: string;
|
|
24
|
-
/** Path to the data directory */
|
|
25
|
-
dataDir: string;
|
|
22
|
+
/** Path to the .search-hub/ project directory (local init only) */
|
|
23
|
+
projectDir?: string;
|
|
26
24
|
/** Whether files already existed (only when success=false) */
|
|
27
25
|
alreadyExists?: boolean;
|
|
28
26
|
/** Whether existing files were overwritten (only when force=true) */
|
|
29
27
|
overwritten?: boolean;
|
|
30
28
|
/** Message describing the result */
|
|
31
29
|
message?: string;
|
|
30
|
+
/** Actionable hints for the user */
|
|
31
|
+
hints?: string[];
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Initialize
|
|
34
|
+
* Initialize search-hub configuration.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* - Data directory with sessions/ subdirectory
|
|
39
|
-
*
|
|
40
|
-
* On Linux (XDG):
|
|
41
|
-
* - ~/.config/search-hub/config.toml
|
|
42
|
-
* - ~/.local/share/search-hub/sessions/
|
|
36
|
+
* By default, creates a `.search-hub/` project directory in the specified directory (or cwd).
|
|
37
|
+
* With `--global`, creates the global config at the XDG-compliant path.
|
|
43
38
|
*/
|
|
44
39
|
export declare function init(options?: InitOptions): Promise<InitResult>;
|
|
45
40
|
//# sourceMappingURL=init.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAiCA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qEAAqE;IACrE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AA6LD;;;;;GAKG;AACH,wBAAsB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAUzE"}
|