@baanish/hydra-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -0
  3. package/dist/config.d.ts +29 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +338 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/db/client.d.ts +10 -0
  8. package/dist/db/client.d.ts.map +1 -0
  9. package/dist/db/client.js +93 -0
  10. package/dist/db/client.js.map +1 -0
  11. package/dist/db/queries.d.ts +67 -0
  12. package/dist/db/queries.d.ts.map +1 -0
  13. package/dist/db/queries.js +336 -0
  14. package/dist/db/queries.js.map +1 -0
  15. package/dist/engine/concurrency.d.ts +3 -0
  16. package/dist/engine/concurrency.d.ts.map +1 -0
  17. package/dist/engine/concurrency.js +42 -0
  18. package/dist/engine/concurrency.js.map +1 -0
  19. package/dist/engine/eta.d.ts +16 -0
  20. package/dist/engine/eta.d.ts.map +1 -0
  21. package/dist/engine/eta.js +54 -0
  22. package/dist/engine/eta.js.map +1 -0
  23. package/dist/engine/model.d.ts +57 -0
  24. package/dist/engine/model.d.ts.map +1 -0
  25. package/dist/engine/model.js +445 -0
  26. package/dist/engine/model.js.map +1 -0
  27. package/dist/engine/personas.d.ts +30 -0
  28. package/dist/engine/personas.d.ts.map +1 -0
  29. package/dist/engine/personas.js +336 -0
  30. package/dist/engine/personas.js.map +1 -0
  31. package/dist/engine/pipeline.d.ts +61 -0
  32. package/dist/engine/pipeline.d.ts.map +1 -0
  33. package/dist/engine/pipeline.js +638 -0
  34. package/dist/engine/pipeline.js.map +1 -0
  35. package/dist/engine/prompts.d.ts +10 -0
  36. package/dist/engine/prompts.d.ts.map +1 -0
  37. package/dist/engine/prompts.js +49 -0
  38. package/dist/engine/prompts.js.map +1 -0
  39. package/dist/engine/search.d.ts +46 -0
  40. package/dist/engine/search.d.ts.map +1 -0
  41. package/dist/engine/search.js +159 -0
  42. package/dist/engine/search.js.map +1 -0
  43. package/dist/index.d.ts +5 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +648 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/security.d.ts +18 -0
  48. package/dist/security.d.ts.map +1 -0
  49. package/dist/security.js +168 -0
  50. package/dist/security.js.map +1 -0
  51. package/dist/types.d.ts +143 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +2 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/ui/agent-mode.d.ts +8 -0
  56. package/dist/ui/agent-mode.d.ts.map +1 -0
  57. package/dist/ui/agent-mode.js +138 -0
  58. package/dist/ui/agent-mode.js.map +1 -0
  59. package/dist/ui/animations.d.ts +8 -0
  60. package/dist/ui/animations.d.ts.map +1 -0
  61. package/dist/ui/animations.js +19 -0
  62. package/dist/ui/animations.js.map +1 -0
  63. package/dist/ui/components/agent-list.d.ts +2 -0
  64. package/dist/ui/components/agent-list.d.ts.map +1 -0
  65. package/dist/ui/components/agent-list.js +2 -0
  66. package/dist/ui/components/agent-list.js.map +1 -0
  67. package/dist/ui/components/header.d.ts +2 -0
  68. package/dist/ui/components/header.d.ts.map +1 -0
  69. package/dist/ui/components/header.js +2 -0
  70. package/dist/ui/components/header.js.map +1 -0
  71. package/dist/ui/components/phase-bar.d.ts +2 -0
  72. package/dist/ui/components/phase-bar.d.ts.map +1 -0
  73. package/dist/ui/components/phase-bar.js +2 -0
  74. package/dist/ui/components/phase-bar.js.map +1 -0
  75. package/dist/ui/components/stats-bar.d.ts +2 -0
  76. package/dist/ui/components/stats-bar.d.ts.map +1 -0
  77. package/dist/ui/components/stats-bar.js +2 -0
  78. package/dist/ui/components/stats-bar.js.map +1 -0
  79. package/dist/ui/tui.d.ts +18 -0
  80. package/dist/ui/tui.d.ts.map +1 -0
  81. package/dist/ui/tui.js +464 -0
  82. package/dist/ui/tui.js.map +1 -0
  83. package/dist/web/app.html +1352 -0
  84. package/dist/web/index.d.ts +2 -0
  85. package/dist/web/index.d.ts.map +1 -0
  86. package/dist/web/index.js +2 -0
  87. package/dist/web/index.js.map +1 -0
  88. package/dist/web/server.d.ts +2 -0
  89. package/dist/web/server.d.ts.map +1 -0
  90. package/dist/web/server.js +864 -0
  91. package/dist/web/server.js.map +1 -0
  92. package/package.json +59 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aanish Bhirud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # hydra-cli
2
+
3
+ hydra is a multi-agent research and synthesis cli that breaks a question into specialist workstreams, runs parallel research plus debate rounds, and produces one consolidated synthesis. it is built for long-form, evidence-oriented questions where a single-model answer is too shallow.
4
+
5
+ ## requirements
6
+
7
+ - node `>=24`
8
+ - npm `>=11`
9
+
10
+ the `engines` field in `package.json` enforces `"node": ">=24"`. this migration is intentionally node-24-first: the cli now relies on modern node esm behavior, built-in web platform primitives like `fetch`/`Request`/`Response`, and the runtime shape we verified for the node http-to-fetch web adapter. older node versions are not tested in this phase, so treat them as unsupported until compatibility is widened deliberately.
11
+
12
+ ## install and run
13
+
14
+ ### one-off with npx
15
+
16
+ ```bash
17
+ npx @baanish/hydra-cli --help
18
+ npx @baanish/hydra-cli run "market entry strategy for industrial batteries"
19
+ ```
20
+
21
+ ### project-local install
22
+
23
+ ```bash
24
+ npm install @baanish/hydra-cli
25
+ npx hydra --help
26
+ npx hydra run "market entry strategy for industrial batteries"
27
+ ```
28
+
29
+ ### global install
30
+
31
+ ```bash
32
+ npm install -g @baanish/hydra-cli
33
+ hydra --help
34
+ hydra run "market entry strategy for industrial batteries"
35
+ ```
36
+
37
+ ## quick start
38
+
39
+ ```bash
40
+ hydra config set synthetic-api-key <key>
41
+ # optional llm override key:
42
+ # hydra config set api-key <key>
43
+ hydra run "your query"
44
+ ```
45
+
46
+ to quickly see a full real run output (the bundled ai transition 2036 retrospective) without running anything:
47
+
48
+ ```bash
49
+ cat examples/ai-transition-2036.json | jq -r '.brief'
50
+ # no jq:
51
+ node --input-type=module -e "import { readFile } from 'node:fs/promises'; console.log(JSON.parse(await readFile('./examples/ai-transition-2036.json', 'utf8')).brief)"
52
+ ```
53
+
54
+ ## storage
55
+
56
+ hydra keeps the same on-disk locations across install modes:
57
+
58
+ - config: `~/.config/hydra-cli/config.json`
59
+ - database: `~/.config/hydra-cli/hydra.db`
60
+ - personas: `~/.config/hydra-cli/personas.json`
61
+
62
+ ## key commands
63
+
64
+ | command | purpose | example |
65
+ | --- | --- | --- |
66
+ | `hydra run <query>` | start a run (also supports `hydra "<query>"`) | `hydra run "market entry strategy"` |
67
+ | `hydra run --custom-personas-only <query>` | use custom personas only and fill any shortfall with ephemeral generated personas | `hydra run --custom-personas-only --agents 5 "supply chain strategy"` |
68
+ | `hydra view <run-id>` | inspect a run summary | `hydra view Rw9k...` |
69
+ | `hydra history` | list recent runs | `hydra history --limit 20` |
70
+ | `hydra config show` | print effective config with masked keys | `hydra config show` |
71
+ | `hydra config set <key> <value>` | update a config value | `hydra config set max-concurrency 5` |
72
+ | `hydra web` | launch the local web ui with an authenticated local api session | `hydra web --port 3737` |
73
+
74
+ ## personas
75
+
76
+ personas are specialist analytical lenses assigned to agents (for example `skeptic`, `futurist`, `economist`) to bias how perspectives are generated and debated. hydra ships with 20 built-in personas.
77
+
78
+ | command | purpose | example |
79
+ | --- | --- | --- |
80
+ | `hydra persona list` | list all personas (built-in + custom) | `hydra persona list` |
81
+ | `hydra persona list --json` | output personas as json | `hydra persona list --json` |
82
+ | `hydra persona add` | add a custom persona | `hydra persona add --name "the lawyer" --description "applies legal reasoning and precedent." --methodology "case law analysis"` |
83
+ | `hydra persona remove <id>` | remove a custom persona | `hydra persona remove the-lawyer` |
84
+
85
+ custom personas are stored in `~/.config/hydra-cli/personas.json`. built-in personas cannot be removed. if `--id` is not provided when adding a persona, it is auto-derived from the name using slugification.
86
+
87
+ ## config options
88
+
89
+ | key | type | default |
90
+ | --- | --- | --- |
91
+ | `apiKey` | `string \| undefined` | unset |
92
+ | `syntheticApiKey` | `string \| undefined` | unset |
93
+ | `searchProvider` | `"synthetic" \| "exa" \| "brave"` | `synthetic` |
94
+ | `exaApiKey` | `string \| undefined` | unset |
95
+ | `braveApiKey` | `string \| undefined` | unset |
96
+ | `baseUrl` | `string` | `https://api.synthetic.new/openai/v1` |
97
+ | `model` | `string` | `hf:MiniMaxAI/MiniMax-M2.5` |
98
+ | `defaultAgentCount` | `number` | `5` |
99
+ | `maxConcurrency` | `number` | `5` |
100
+ | `debateRounds` | `number` | `2` |
101
+ | `searchEnabled` | `boolean` | `true` |
102
+ | `customPersonasOnly` | `boolean` | `false` |
103
+
104
+ ## development
105
+
106
+ ```bash
107
+ npm install
108
+ npm run lint
109
+ npm run typecheck
110
+ npm test
111
+ npm run build
112
+ ```
113
+
114
+ manual first publish:
115
+
116
+ ```bash
117
+ npm publish --access public
118
+ ```
119
+
120
+ ## note on backend
121
+
122
+ hydra uses synthetic.new as the default openai-compatible llm backend (`baseUrl` + model defaults target synthetic.new). `baseUrl` must use `https://`, or `http://` only for localhost / loopback development endpoints, and embedded url credentials are rejected.
@@ -0,0 +1,29 @@
1
+ import type { HydraConfig, HydraConfigFile } from "./types.js";
2
+ /** default api base url for llm requests. */
3
+ export declare const DEFAULT_BASE_URL = "https://api.synthetic.new/openai/v1";
4
+ /** default llm model identifier used by the project. */
5
+ export declare const DEFAULT_MODEL = "hf:MiniMaxAI/MiniMax-M2.5";
6
+ /** minimum supported debate rounds value. */
7
+ export declare const MIN_DEBATE_ROUNDS = 1;
8
+ /** maximum supported debate rounds value. */
9
+ export declare const MAX_DEBATE_ROUNDS = 8;
10
+ /** config directory under user home. */
11
+ export declare const CONFIG_DIR: string;
12
+ /** default config file path. */
13
+ export declare const CONFIG_FILE: string;
14
+ /** convert raw numeric values into clamped integers with safe fallback. */
15
+ export declare function clampInt(value: unknown, min: number, max: number, fallback: number): number;
16
+ /** return resolved config file path for diagnostics and tests. */
17
+ export declare function getConfigPath(): string;
18
+ /** load config from defaults, env, and overrides and return validated values. */
19
+ export declare function loadConfig(overrides?: HydraConfigFile): HydraConfig;
20
+ /** persist config values and return normalized merged config. */
21
+ export declare function writeConfig(updates: HydraConfigFile): HydraConfig;
22
+ /** mask api-like values for display to avoid accidental leakage in logs. */
23
+ export declare function maskConfigValue(value: string | undefined): string;
24
+ /** sanitize raw config-string input and return typed value or validation error. */
25
+ export declare function sanitizeConfigValueForSet(key: keyof HydraConfig, value: string): {
26
+ error?: string;
27
+ value: unknown;
28
+ };
29
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAkB,MAAM,YAAY,CAAC;AAE/E,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,wCAAwC,CAAC;AACtE,wDAAwD;AACxD,eAAO,MAAM,aAAa,8BAA8B,CAAC;AACzD,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,wCAAwC;AACxC,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,gCAAgC;AAChC,eAAO,MAAM,WAAW,QAAqC,CAAC;AAiB9D,2EAA2E;AAC3E,wBAAgB,QAAQ,CACvB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACd,MAAM,CAMR;AAoMD,kEAAkE;AAClE,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,SAAS,GAAE,eAAoB,GAAG,WAAW,CAQvE;AAED,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,WAAW,CAgBjE;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAajE;AAED,mFAAmF;AACnF,wBAAgB,yBAAyB,CACxC,GAAG,EAAE,MAAM,WAAW,EACtB,KAAK,EAAE,MAAM,GACX;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAkHpC"}
package/dist/config.js ADDED
@@ -0,0 +1,338 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { resolve } from "node:path";
4
+ import { validateBaseUrl } from "./security.js";
5
+ /** default api base url for llm requests. */
6
+ export const DEFAULT_BASE_URL = "https://api.synthetic.new/openai/v1";
7
+ /** default llm model identifier used by the project. */
8
+ export const DEFAULT_MODEL = "hf:MiniMaxAI/MiniMax-M2.5";
9
+ /** minimum supported debate rounds value. */
10
+ export const MIN_DEBATE_ROUNDS = 1;
11
+ /** maximum supported debate rounds value. */
12
+ export const MAX_DEBATE_ROUNDS = 8;
13
+ /** config directory under user home. */
14
+ export const CONFIG_DIR = resolve(homedir(), ".config", "hydra-cli");
15
+ /** default config file path. */
16
+ export const CONFIG_FILE = resolve(CONFIG_DIR, "config.json");
17
+ const DEFAULTS = {
18
+ apiKey: undefined,
19
+ syntheticApiKey: undefined,
20
+ searchProvider: "synthetic",
21
+ exaApiKey: undefined,
22
+ braveApiKey: undefined,
23
+ baseUrl: DEFAULT_BASE_URL,
24
+ model: DEFAULT_MODEL,
25
+ defaultAgentCount: 5,
26
+ maxConcurrency: 1,
27
+ debateRounds: 2,
28
+ searchEnabled: true,
29
+ customPersonasOnly: false,
30
+ };
31
+ /** convert raw numeric values into clamped integers with safe fallback. */
32
+ export function clampInt(value, min, max, fallback) {
33
+ const parsed = Number.parseInt(String(value), 10);
34
+ if (!Number.isFinite(parsed)) {
35
+ return fallback;
36
+ }
37
+ return Math.min(Math.max(parsed, min), max);
38
+ }
39
+ /** normalize truthy / falsy string values from env and config files. */
40
+ function normalizeBoolean(value) {
41
+ if (typeof value === "boolean") {
42
+ return value;
43
+ }
44
+ if (typeof value === "string") {
45
+ const normalized = value.trim().toLowerCase();
46
+ if (["1", "true", "yes", "y", "on"].includes(normalized)) {
47
+ return true;
48
+ }
49
+ if (["0", "false", "no", "n", "off"].includes(normalized)) {
50
+ return false;
51
+ }
52
+ }
53
+ return undefined;
54
+ }
55
+ /** normalize and fallback invalid search provider values. */
56
+ function normalizeSearchProvider(value) {
57
+ if (typeof value === "string") {
58
+ const normalized = value.trim().toLowerCase();
59
+ if (normalized === "synthetic" ||
60
+ normalized === "exa" ||
61
+ normalized === "brave") {
62
+ return normalized;
63
+ }
64
+ }
65
+ return DEFAULTS.searchProvider;
66
+ }
67
+ /** trim optional string values and convert blank values to undefined. */
68
+ function trimOptionalString(value) {
69
+ if (typeof value !== "string") {
70
+ return undefined;
71
+ }
72
+ const trimmed = value.trim();
73
+ return trimmed.length > 0 ? trimmed : undefined;
74
+ }
75
+ /** trim optional api key strings and convert blank values to undefined. */
76
+ function trimOptionalApiKey(value) {
77
+ return trimOptionalString(value);
78
+ }
79
+ /** normalize base-url values and surface invalid explicit settings immediately. */
80
+ function normalizeBaseUrl(value) {
81
+ if (typeof value !== "string") {
82
+ return DEFAULTS.baseUrl;
83
+ }
84
+ const trimmed = value.trim();
85
+ if (!trimmed) {
86
+ return DEFAULTS.baseUrl;
87
+ }
88
+ const parsed = validateBaseUrl(trimmed);
89
+ if (!parsed.value) {
90
+ throw new Error(`invalid base-url "${trimmed}": ${parsed.error ?? "must be a valid absolute URL"}`);
91
+ }
92
+ return parsed.value;
93
+ }
94
+ /** ensure the config directory exists before read/write operations. */
95
+ function ensureConfigDir() {
96
+ if (!existsSync(CONFIG_DIR)) {
97
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
98
+ }
99
+ }
100
+ /** load raw config from disk and return parsed config json if valid. */
101
+ function readConfigFile() {
102
+ if (!existsSync(CONFIG_FILE)) {
103
+ return {};
104
+ }
105
+ const raw = readFileSync(CONFIG_FILE, "utf8").trim();
106
+ if (!raw) {
107
+ return {};
108
+ }
109
+ try {
110
+ const parsed = JSON.parse(raw);
111
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
112
+ return {};
113
+ }
114
+ return parsed;
115
+ }
116
+ catch {
117
+ return {};
118
+ }
119
+ }
120
+ /** overlay environment variables on top of persisted config values. */
121
+ function applyEnvironmentOverrides(config) {
122
+ const merged = { ...config };
123
+ if (process.env.HYDRA_API_KEY) {
124
+ merged.apiKey = process.env.HYDRA_API_KEY;
125
+ }
126
+ if (process.env.HYDRA_SYNTHETIC_API_KEY) {
127
+ merged.syntheticApiKey = process.env.HYDRA_SYNTHETIC_API_KEY;
128
+ }
129
+ else if (process.env.SYNTHETIC_API_KEY) {
130
+ merged.syntheticApiKey = process.env.SYNTHETIC_API_KEY;
131
+ }
132
+ if (process.env.HYDRA_SEARCH_PROVIDER) {
133
+ merged.searchProvider = normalizeSearchProvider(process.env.HYDRA_SEARCH_PROVIDER);
134
+ }
135
+ if (process.env.HYDRA_EXA_API_KEY) {
136
+ merged.exaApiKey = process.env.HYDRA_EXA_API_KEY;
137
+ }
138
+ else if (process.env.EXA_API_KEY) {
139
+ merged.exaApiKey = process.env.EXA_API_KEY;
140
+ }
141
+ if (process.env.HYDRA_BRAVE_API_KEY) {
142
+ merged.braveApiKey = process.env.HYDRA_BRAVE_API_KEY;
143
+ }
144
+ else if (process.env.BRAVE_API_KEY) {
145
+ merged.braveApiKey = process.env.BRAVE_API_KEY;
146
+ }
147
+ if (process.env.HYDRA_MODEL) {
148
+ merged.model = process.env.HYDRA_MODEL;
149
+ }
150
+ if (process.env.HYDRA_ORCHESTRATOR_MODEL !== undefined) {
151
+ merged.orchestratorModel = process.env.HYDRA_ORCHESTRATOR_MODEL;
152
+ }
153
+ if (process.env.HYDRA_RESEARCH_MODEL !== undefined) {
154
+ merged.researchModel = process.env.HYDRA_RESEARCH_MODEL;
155
+ }
156
+ if (process.env.HYDRA_BASE_URL) {
157
+ merged.baseUrl = process.env.HYDRA_BASE_URL;
158
+ }
159
+ if (process.env.HYDRA_CUSTOM_PERSONAS_ONLY) {
160
+ const parsed = normalizeBoolean(process.env.HYDRA_CUSTOM_PERSONAS_ONLY);
161
+ if (parsed !== undefined) {
162
+ merged.customPersonasOnly = parsed;
163
+ }
164
+ }
165
+ if (process.env.HYDRA_CONCURRENCY) {
166
+ const parsed = Number.parseInt(process.env.HYDRA_CONCURRENCY, 10);
167
+ if (Number.isFinite(parsed)) {
168
+ merged.maxConcurrency = parsed;
169
+ }
170
+ }
171
+ return merged;
172
+ }
173
+ /** normalize config values and apply hard constraints to numeric fields. */
174
+ function normalizeConfig(config) {
175
+ const syntheticApiKey = trimOptionalApiKey(config.syntheticApiKey);
176
+ const explicitApiKey = trimOptionalApiKey(config.apiKey);
177
+ const apiKey = explicitApiKey ?? syntheticApiKey;
178
+ return {
179
+ apiKey: apiKey ?? undefined,
180
+ syntheticApiKey,
181
+ searchProvider: normalizeSearchProvider(config.searchProvider),
182
+ exaApiKey: trimOptionalApiKey(config.exaApiKey),
183
+ braveApiKey: trimOptionalApiKey(config.braveApiKey),
184
+ baseUrl: normalizeBaseUrl(config.baseUrl),
185
+ model: config.model || DEFAULTS.model,
186
+ orchestratorModel: trimOptionalString(config.orchestratorModel),
187
+ researchModel: trimOptionalString(config.researchModel),
188
+ defaultAgentCount: clampInt(config.defaultAgentCount, 1, 20, DEFAULTS.defaultAgentCount),
189
+ maxConcurrency: clampInt(config.maxConcurrency, 1, 1, DEFAULTS.maxConcurrency),
190
+ debateRounds: clampInt(config.debateRounds, MIN_DEBATE_ROUNDS, MAX_DEBATE_ROUNDS, DEFAULTS.debateRounds),
191
+ searchEnabled: typeof config.searchEnabled === "boolean"
192
+ ? config.searchEnabled
193
+ : DEFAULTS.searchEnabled,
194
+ customPersonasOnly: typeof config.customPersonasOnly === "boolean"
195
+ ? config.customPersonasOnly
196
+ : DEFAULTS.customPersonasOnly,
197
+ };
198
+ }
199
+ /** return resolved config file path for diagnostics and tests. */
200
+ export function getConfigPath() {
201
+ return CONFIG_FILE;
202
+ }
203
+ /** load config from defaults, env, and overrides and return validated values. */
204
+ export function loadConfig(overrides = {}) {
205
+ const merged = {
206
+ ...DEFAULTS,
207
+ ...applyEnvironmentOverrides(readConfigFile()),
208
+ ...overrides,
209
+ };
210
+ return normalizeConfig(merged);
211
+ }
212
+ /** persist config values and return normalized merged config. */
213
+ export function writeConfig(updates) {
214
+ ensureConfigDir();
215
+ const normalized = normalizeConfig({
216
+ ...DEFAULTS,
217
+ ...readConfigFile(),
218
+ ...updates,
219
+ });
220
+ writeFileSync(CONFIG_FILE, JSON.stringify(normalized, null, 2), {
221
+ encoding: "utf8",
222
+ mode: 0o600,
223
+ });
224
+ // Explicit chmod ensures 0o600 regardless of umask.
225
+ chmodSync(CONFIG_FILE, 0o600);
226
+ return normalized;
227
+ }
228
+ /** mask api-like values for display to avoid accidental leakage in logs. */
229
+ export function maskConfigValue(value) {
230
+ if (!value) {
231
+ return "not set";
232
+ }
233
+ const trimmed = value.trim();
234
+ if (!trimmed) {
235
+ return "not set";
236
+ }
237
+ if (trimmed.length <= 8) {
238
+ return "[redacted]";
239
+ }
240
+ const suffix = trimmed.slice(-4);
241
+ return `sk-...${suffix}`;
242
+ }
243
+ /** sanitize raw config-string input and return typed value or validation error. */
244
+ export function sanitizeConfigValueForSet(key, value) {
245
+ if (key === "defaultAgentCount") {
246
+ const parsed = Number.parseInt(value, 10);
247
+ if (!Number.isFinite(parsed)) {
248
+ return {
249
+ error: "defaultAgentCount must be an integer",
250
+ value: undefined,
251
+ };
252
+ }
253
+ return { value: clampInt(parsed, 1, 20, DEFAULTS.defaultAgentCount) };
254
+ }
255
+ if (key === "maxConcurrency") {
256
+ const parsed = Number.parseInt(value, 10);
257
+ if (!Number.isFinite(parsed)) {
258
+ return {
259
+ error: "maxConcurrency must be 1",
260
+ value: undefined,
261
+ };
262
+ }
263
+ if (parsed !== 1) {
264
+ return {
265
+ error: "maxConcurrency must be 1",
266
+ value: undefined,
267
+ };
268
+ }
269
+ return { value: parsed };
270
+ }
271
+ if (key === "debateRounds") {
272
+ const parsed = Number.parseInt(value, 10);
273
+ if (!Number.isFinite(parsed)) {
274
+ return { error: "debateRounds must be an integer", value: undefined };
275
+ }
276
+ return {
277
+ value: clampInt(parsed, MIN_DEBATE_ROUNDS, MAX_DEBATE_ROUNDS, DEFAULTS.debateRounds),
278
+ };
279
+ }
280
+ if (key === "searchEnabled") {
281
+ const parsed = normalizeBoolean(value);
282
+ if (parsed === undefined) {
283
+ return {
284
+ error: "searchEnabled must be one of: true, false, 1, 0, yes, no",
285
+ value: undefined,
286
+ };
287
+ }
288
+ return { value: parsed };
289
+ }
290
+ if (key === "customPersonasOnly") {
291
+ const parsed = normalizeBoolean(value);
292
+ if (parsed === undefined) {
293
+ return {
294
+ error: "custom-personas-only must be one of: true, false, 1, 0, yes, no",
295
+ value: undefined,
296
+ };
297
+ }
298
+ return { value: parsed };
299
+ }
300
+ if (key === "baseUrl") {
301
+ const parsed = validateBaseUrl(value);
302
+ if (!parsed.value) {
303
+ return {
304
+ error: parsed.error ?? "base-url must be a valid absolute URL",
305
+ value: undefined,
306
+ };
307
+ }
308
+ return { value: parsed.value };
309
+ }
310
+ if (key === "model") {
311
+ return { value: value.trim() };
312
+ }
313
+ if (key === "syntheticApiKey" ||
314
+ key === "orchestratorModel" ||
315
+ key === "researchModel") {
316
+ return { value: trimOptionalString(value) };
317
+ }
318
+ if (key === "apiKey") {
319
+ return { value: trimOptionalString(value) };
320
+ }
321
+ if (key === "exaApiKey" || key === "braveApiKey") {
322
+ return { value: trimOptionalString(value) };
323
+ }
324
+ if (key === "searchProvider") {
325
+ const normalized = value.trim().toLowerCase();
326
+ if (normalized !== "synthetic" &&
327
+ normalized !== "exa" &&
328
+ normalized !== "brave") {
329
+ return {
330
+ error: "search-provider must be one of: synthetic, exa, brave",
331
+ value: undefined,
332
+ };
333
+ }
334
+ return { value: normalized };
335
+ }
336
+ return { value };
337
+ }
338
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import Database from "better-sqlite3";
2
+ type SqliteDatabase = InstanceType<typeof Database>;
3
+ /** return fully qualified path to the sqlite database file. */
4
+ export declare function getDatabasePath(): string;
5
+ /** get initialized singleton sqlite connection with required pragmas applied. */
6
+ export declare function getDatabase(): SqliteDatabase;
7
+ /** close singleton database connection and clear in-memory handle. */
8
+ export declare function closeDatabase(): void;
9
+ export {};
10
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAyCtC,KAAK,cAAc,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAqCpD,+DAA+D;AAC/D,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,iFAAiF;AACjF,wBAAgB,WAAW,IAAI,cAAc,CAW5C;AAED,sEAAsE;AACtE,wBAAgB,aAAa,SAK5B"}
@@ -0,0 +1,93 @@
1
+ import { chmodSync, existsSync, mkdirSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import Database from "better-sqlite3";
4
+ import { CONFIG_DIR } from "../config.js";
5
+ const DB_DIR = CONFIG_DIR;
6
+ const DB_PATH = resolve(DB_DIR, "hydra.db");
7
+ const SCHEMA_SQL = `
8
+ CREATE TABLE IF NOT EXISTS runs (
9
+ id TEXT PRIMARY KEY,
10
+ query TEXT NOT NULL,
11
+ agent_count INTEGER NOT NULL,
12
+ status TEXT NOT NULL DEFAULT 'decomposing',
13
+ brief TEXT,
14
+ error TEXT,
15
+ pipeline_state TEXT,
16
+ total_prompt_tokens INTEGER DEFAULT 0,
17
+ total_completion_tokens INTEGER DEFAULT 0,
18
+ created_at INTEGER NOT NULL,
19
+ completed_at INTEGER,
20
+ elapsed_ms INTEGER
21
+ );
22
+
23
+ CREATE TABLE IF NOT EXISTS agent_runs (
24
+ id TEXT PRIMARY KEY,
25
+ run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
26
+ phase TEXT NOT NULL,
27
+ persona TEXT NOT NULL,
28
+ system_prompt TEXT NOT NULL,
29
+ messages TEXT,
30
+ output TEXT DEFAULT '',
31
+ search_queries TEXT,
32
+ status TEXT NOT NULL DEFAULT 'running',
33
+ prompt_tokens INTEGER DEFAULT 0,
34
+ completion_tokens INTEGER DEFAULT 0,
35
+ started_at INTEGER NOT NULL,
36
+ completed_at INTEGER
37
+ );
38
+
39
+ CREATE INDEX IF NOT EXISTS idx_agent_runs_run_id ON agent_runs(run_id);
40
+ `;
41
+ let db = null;
42
+ function ensureDbDir() {
43
+ if (!existsSync(DB_DIR)) {
44
+ mkdirSync(DB_DIR, { recursive: true, mode: 0o700 });
45
+ }
46
+ }
47
+ function enforceDatabaseFilePermissions() {
48
+ const filePaths = [DB_PATH, `${DB_PATH}-wal`, `${DB_PATH}-shm`];
49
+ for (const path of filePaths) {
50
+ if (!existsSync(path)) {
51
+ continue;
52
+ }
53
+ try {
54
+ chmodSync(path, 0o600);
55
+ }
56
+ catch (error) {
57
+ if (path === DB_PATH) {
58
+ const message = error instanceof Error ? error.message : "unknown permission error";
59
+ console.warn(`[hydra] warning: could not set permissions on ${DB_PATH}: ${message}`);
60
+ }
61
+ // Ignore chmod failures for transient sqlite sidecar files.
62
+ }
63
+ }
64
+ }
65
+ function initializeSchema(database) {
66
+ database.exec("PRAGMA foreign_keys = ON;");
67
+ database.exec("PRAGMA journal_mode = WAL;");
68
+ database.exec(SCHEMA_SQL);
69
+ }
70
+ /** return fully qualified path to the sqlite database file. */
71
+ export function getDatabasePath() {
72
+ return DB_PATH;
73
+ }
74
+ /** get initialized singleton sqlite connection with required pragmas applied. */
75
+ export function getDatabase() {
76
+ if (db) {
77
+ return db;
78
+ }
79
+ ensureDbDir();
80
+ const database = new Database(DB_PATH);
81
+ initializeSchema(database);
82
+ enforceDatabaseFilePermissions();
83
+ db = database;
84
+ return db;
85
+ }
86
+ /** close singleton database connection and clear in-memory handle. */
87
+ export function closeDatabase() {
88
+ if (db) {
89
+ db.close();
90
+ db = null;
91
+ }
92
+ }
93
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,MAAM,GAAG,UAAU,CAAC;AAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAE5C,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiClB,CAAC;AAIF,IAAI,EAAE,GAA0B,IAAI,CAAC;AAErC,SAAS,WAAW;IACnB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrD,CAAC;AACF,CAAC;AAED,SAAS,8BAA8B;IACtC,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,GAAG,OAAO,MAAM,EAAE,GAAG,OAAO,MAAM,CAAC,CAAC;IAChE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,SAAS;QACV,CAAC;QACD,IAAI,CAAC;YACJ,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACtB,MAAM,OAAO,GACZ,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC;gBACrE,OAAO,CAAC,IAAI,CACX,iDAAiD,OAAO,KAAK,OAAO,EAAE,CACtE,CAAC;YACH,CAAC;YACD,4DAA4D;QAC7D,CAAC;IACF,CAAC;AACF,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAwB;IACjD,QAAQ,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IAC3C,QAAQ,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAC3B,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,eAAe;IAC9B,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW;IAC1B,IAAI,EAAE,EAAE,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;IAED,WAAW,EAAE,CAAC;IACd,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAC;IACvC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC3B,8BAA8B,EAAE,CAAC;IACjC,EAAE,GAAG,QAAQ,CAAC;IACd,OAAO,EAAE,CAAC;AACX,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,aAAa;IAC5B,IAAI,EAAE,EAAE,CAAC;QACR,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,EAAE,GAAG,IAAI,CAAC;IACX,CAAC;AACF,CAAC"}