uneven-ai 1.0.4 → 1.0.5

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/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to Uneven AI will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.5] - 2026-04-16
9
+
10
+ ### Fixed
11
+
12
+ - **config**: `uneven.config.ts` changes now applied automatically on every command — no longer requires manual edit of `.uneven/config.json` or re-running `uneven init`
13
+ - **init**: default Gemini model changed from `gemini-2.0-flash` to `gemini-2.5-flash`
14
+ - **index-planner**: added `gemini-2.5-flash` pricing entry to cost estimation table
15
+
8
16
  ## [1.0.4] - 2026-04-16
9
17
 
10
18
  ### Fixed
@@ -48,9 +48,9 @@ function defaultModelFor(provider) {
48
48
  ollama: 'llama3.2',
49
49
  claude: 'claude-sonnet-4-6',
50
50
  openai: 'gpt-4o-mini',
51
- gemini: 'gemini-2.0-flash',
51
+ gemini: 'gemini-2.5-flash',
52
52
  };
53
- return MAP[provider] ?? 'gemini-2.0-flash';
53
+ return MAP[provider] ?? 'gemini-2.5-flash';
54
54
  }
55
55
  function makeConfig(provider, threads, gpuLayers) {
56
56
  const model = defaultModelFor(provider);
@@ -11,7 +11,7 @@ import * as path from 'path';
11
11
  import * as crypto from 'crypto';
12
12
  // ─── Constants ────────────────────────────────────────────────────────────────
13
13
  const SESSION_FILE = '.uneven/session.json';
14
- const UNEVEN_VERSION = '1.0.4';
14
+ const UNEVEN_VERSION = '1.0.5';
15
15
  const STALE_THRESHOLD_MS = 60 * 60 * 1000; // 1 hour
16
16
  const LOCK_TIMEOUT_MS = 30_000; // 30 seconds
17
17
  const LOCK_DEBOUNCE_MS = 1_500; // 1.5 seconds
@@ -1 +1 @@
1
- {"version":3,"file":"index-planner.d.ts","sourceRoot":"","sources":["../../../src/domain/services/index-planner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AA8C1D,eAAO,MAAM,sBAAsB,QAAkB,CAAA;AACrD,eAAO,MAAM,eAAe,QAA0B,CAAA;AAItD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAO,MAAM,CAAA;IACrB,SAAS,EAAM,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,uDAAuD;IACvD,OAAO,EAAQ,OAAO,CAAA;IACtB,mEAAmE;IACnE,UAAU,EAAK,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAW,QAAQ,EAAE,CAAA;IAC1B,UAAU,EAAM,MAAM,CAAA;IACtB,UAAU,EAAM,QAAQ,EAAE,CAAA;IAC1B,aAAa,EAAG,QAAQ,EAAE,CAAA;IAC1B,WAAW,EAAK,MAAM,CAAA;IACtB,WAAW,EAAK,MAAM,CAAA;IACtB,2CAA2C;IAC3C,aAAa,EAAG,MAAM,CAAA;IACtB,qDAAqD;IACrD,YAAY,EAAI,MAAM,CAAA;IACtB,QAAQ,EAAQ,MAAM,CAAA;IACtB,KAAK,EAAW,MAAM,CAAA;IACtB,MAAM,EAAU,OAAO,CAAA;CACxB;AAID,qBAAa,YAAY;IACvB,OAAO,CAAC,YAAY,CAAQ;gBAEhB,YAAY,SAAyB;IAIjD;;;OAGG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;YA4BzD,QAAQ;IAqBtB,OAAO,CAAC,WAAW;CAepB;AAID,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM7C"}
1
+ {"version":3,"file":"index-planner.d.ts","sourceRoot":"","sources":["../../../src/domain/services/index-planner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AA+C1D,eAAO,MAAM,sBAAsB,QAAkB,CAAA;AACrD,eAAO,MAAM,eAAe,QAA0B,CAAA;AAItD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAO,MAAM,CAAA;IACrB,SAAS,EAAM,MAAM,CAAA;IACrB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAA;IACvB,uDAAuD;IACvD,OAAO,EAAQ,OAAO,CAAA;IACtB,mEAAmE;IACnE,UAAU,EAAK,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAW,QAAQ,EAAE,CAAA;IAC1B,UAAU,EAAM,MAAM,CAAA;IACtB,UAAU,EAAM,QAAQ,EAAE,CAAA;IAC1B,aAAa,EAAG,QAAQ,EAAE,CAAA;IAC1B,WAAW,EAAK,MAAM,CAAA;IACtB,WAAW,EAAK,MAAM,CAAA;IACtB,2CAA2C;IAC3C,aAAa,EAAG,MAAM,CAAA;IACtB,qDAAqD;IACrD,YAAY,EAAI,MAAM,CAAA;IACtB,QAAQ,EAAQ,MAAM,CAAA;IACtB,KAAK,EAAW,MAAM,CAAA;IACtB,MAAM,EAAU,OAAO,CAAA;CACxB;AAID,qBAAa,YAAY;IACvB,OAAO,CAAC,YAAY,CAAQ;gBAEhB,YAAY,SAAyB;IAIjD;;;OAGG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;YA4BzD,QAAQ;IAqBtB,OAAO,CAAC,WAAW;CAepB;AAID,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM7C"}
@@ -31,6 +31,7 @@ const COST_TABLE = [
31
31
  { provider: 'claude', modelPrefix: '*', usdPer1k: 0.003 },
32
32
  // Gemini (Google)
33
33
  { provider: 'gemini', modelPrefix: 'text-embedding-004', usdPer1k: 0.000025 },
34
+ { provider: 'gemini', modelPrefix: 'gemini-2.5-flash', usdPer1k: 0.000075 },
34
35
  { provider: 'gemini', modelPrefix: 'gemini-2.0-flash', usdPer1k: 0.000075 },
35
36
  { provider: 'gemini', modelPrefix: 'gemini-1.5-flash', usdPer1k: 0.000075 },
36
37
  { provider: 'gemini', modelPrefix: 'gemini-1.5-pro', usdPer1k: 0.00125 },
@@ -4,14 +4,17 @@
4
4
  * Reads .uneven/config.json, written by `uneven init`.
5
5
  * Falls back to safe defaults when the file is missing.
6
6
  *
7
- * Note: uneven.config.ts is the human-readable source of truth, but it
8
- * cannot be imported at runtime without a TypeScript compiler. The JSON
9
- * copy is generated at init time and kept in sync.
7
+ * Note: uneven.config.ts is the human-readable source of truth. On every
8
+ * loadConfig() call the loader parses uneven.config.ts (if present) and
9
+ * syncs any changes into config.json so edits to the TS file are always
10
+ * picked up without having to re-run `uneven init`.
10
11
  */
11
12
  import type { UnevenConfig } from '../../../types/index.js';
12
13
  /**
13
14
  * Load Uneven config from .uneven/config.json.
14
- * Falls back to defaults if the file does not exist (e.g. before `uneven init`).
15
+ * Automatically syncs uneven.config.ts config.json first, so any edits
16
+ * to the TS file are reflected without re-running `uneven init`.
17
+ * Falls back to defaults if neither file exists (e.g. before `uneven init`).
15
18
  */
16
19
  export declare function loadConfig(overrides?: Partial<UnevenConfig>): Promise<UnevenConfig>;
17
20
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/utils/config-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAqB3D;;;GAGG;AACH,wBAAsB,UAAU,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CA4BzF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlE"}
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/utils/config-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAkO3D;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CA8BzF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlE"}
@@ -4,12 +4,14 @@
4
4
  * Reads .uneven/config.json, written by `uneven init`.
5
5
  * Falls back to safe defaults when the file is missing.
6
6
  *
7
- * Note: uneven.config.ts is the human-readable source of truth, but it
8
- * cannot be imported at runtime without a TypeScript compiler. The JSON
9
- * copy is generated at init time and kept in sync.
7
+ * Note: uneven.config.ts is the human-readable source of truth. On every
8
+ * loadConfig() call the loader parses uneven.config.ts (if present) and
9
+ * syncs any changes into config.json so edits to the TS file are always
10
+ * picked up without having to re-run `uneven init`.
10
11
  */
11
12
  import * as fs from 'fs/promises';
12
13
  const CONFIG_FILE = '.uneven/config.json';
14
+ const TS_CONFIG_FILE = 'uneven.config.ts';
13
15
  const DEFAULTS = {
14
16
  brain: {
15
17
  provider: 'local',
@@ -26,11 +28,232 @@ const DEFAULTS = {
26
28
  watch: { terminal: 'npm run dev', autoFix: true, confirmBeforeFix: false, dirs: ['./src'] },
27
29
  log: { path: './.uneven/log.md' },
28
30
  };
31
+ // ─── uneven.config.ts parser ─────────────────────────────────────────────────
32
+ /**
33
+ * Extract the body of a named object section from TypeScript source.
34
+ * Handles nested braces and quoted strings correctly.
35
+ * Returns null if the key is not found.
36
+ */
37
+ function extractSection(src, key) {
38
+ const re = new RegExp(`\\b${key}\\s*:\\s*\\{`);
39
+ const m = re.exec(src);
40
+ if (!m)
41
+ return null;
42
+ let depth = 0;
43
+ let start = -1;
44
+ let inStr = false;
45
+ let strChar = '';
46
+ for (let i = m.index + m[0].length - 1; i < src.length; i++) {
47
+ const ch = src[i];
48
+ if (inStr) {
49
+ if (ch === strChar && src[i - 1] !== '\\')
50
+ inStr = false;
51
+ continue;
52
+ }
53
+ if (ch === '"' || ch === "'" || ch === '`') {
54
+ inStr = true;
55
+ strChar = ch;
56
+ continue;
57
+ }
58
+ if (ch === '{') {
59
+ if (depth === 0)
60
+ start = i + 1;
61
+ depth++;
62
+ }
63
+ else if (ch === '}') {
64
+ depth--;
65
+ if (depth === 0)
66
+ return src.slice(start, i);
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ function strVal(section, key) {
72
+ const m = section.match(new RegExp(`\\b${key}\\s*:\\s*['"\`]([^'"\`]+)['"\`]`));
73
+ return m?.[1];
74
+ }
75
+ function numVal(section, key) {
76
+ const m = section.match(new RegExp(`\\b${key}\\s*:\\s*([\\d.]+)`));
77
+ return m ? parseFloat(m[1]) : undefined;
78
+ }
79
+ function boolVal(section, key) {
80
+ const m = section.match(new RegExp(`\\b${key}\\s*:\\s*(true|false)`));
81
+ return m ? m[1] === 'true' : undefined;
82
+ }
83
+ function arrVal(section, key) {
84
+ const m = section.match(new RegExp(`\\b${key}\\s*:\\s*\\[([^\\]]*)\\]`));
85
+ if (!m)
86
+ return undefined;
87
+ return m[1].split(',')
88
+ .map(s => s.trim().replace(/^['"`]|['"`]$/g, ''))
89
+ .filter(Boolean);
90
+ }
91
+ /**
92
+ * Parse uneven.config.ts as text and return a partial config overlay.
93
+ * Only fields that are explicitly set in the TS file are included.
94
+ * Returns null if the file is absent or yields nothing parseable.
95
+ */
96
+ async function parseConfigTs() {
97
+ let src;
98
+ try {
99
+ src = await fs.readFile(TS_CONFIG_FILE, 'utf-8');
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ const result = {};
105
+ // brain
106
+ const brainSrc = extractSection(src, 'brain');
107
+ if (brainSrc) {
108
+ const provider = strVal(brainSrc, 'provider');
109
+ const model = strVal(brainSrc, 'model');
110
+ const temp = numVal(brainSrc, 'temperature');
111
+ const maxTok = numVal(brainSrc, 'maxTokens');
112
+ if (provider ?? model ?? temp ?? maxTok) {
113
+ result.brain = { provider: provider, model: model };
114
+ if (temp !== undefined)
115
+ result.brain.temperature = temp;
116
+ if (maxTok !== undefined)
117
+ result.brain.maxTokens = maxTok;
118
+ const localSrc = extractSection(brainSrc, 'local');
119
+ if (localSrc) {
120
+ result.brain.local = {};
121
+ const threads = numVal(localSrc, 'threads');
122
+ const gpuLayers = numVal(localSrc, 'gpuLayers');
123
+ const unloadTimeout = numVal(localSrc, 'unloadTimeout');
124
+ if (threads !== undefined)
125
+ result.brain.local.threads = threads;
126
+ if (gpuLayers !== undefined)
127
+ result.brain.local.gpuLayers = gpuLayers;
128
+ if (unloadTimeout !== undefined)
129
+ result.brain.local.unloadTimeout = unloadTimeout;
130
+ }
131
+ }
132
+ }
133
+ // knowledge
134
+ const knowledgeSrc = extractSection(src, 'knowledge');
135
+ if (knowledgeSrc) {
136
+ const dirs = arrVal(knowledgeSrc, 'dirs');
137
+ const files = arrVal(knowledgeSrc, 'files');
138
+ const urls = arrVal(knowledgeSrc, 'urls');
139
+ if (dirs ?? files ?? urls) {
140
+ result.knowledge = {};
141
+ if (dirs)
142
+ result.knowledge.dirs = dirs;
143
+ if (files)
144
+ result.knowledge.files = files;
145
+ if (urls)
146
+ result.knowledge.urls = urls;
147
+ }
148
+ }
149
+ // watch
150
+ const watchSrc = extractSection(src, 'watch');
151
+ if (watchSrc) {
152
+ const terminal = strVal(watchSrc, 'terminal');
153
+ const autoFix = boolVal(watchSrc, 'autoFix');
154
+ const confirmBeforeFix = boolVal(watchSrc, 'confirmBeforeFix');
155
+ const alertOnly = boolVal(watchSrc, 'alertOnly');
156
+ const dirs = arrVal(watchSrc, 'dirs');
157
+ if (terminal ?? autoFix ?? confirmBeforeFix ?? alertOnly ?? dirs) {
158
+ result.watch = {};
159
+ if (terminal !== undefined)
160
+ result.watch.terminal = terminal;
161
+ if (autoFix !== undefined)
162
+ result.watch.autoFix = autoFix;
163
+ if (confirmBeforeFix !== undefined)
164
+ result.watch.confirmBeforeFix = confirmBeforeFix;
165
+ if (alertOnly !== undefined)
166
+ result.watch.alertOnly = alertOnly;
167
+ if (dirs)
168
+ result.watch.dirs = dirs;
169
+ }
170
+ }
171
+ // log
172
+ const logSrc = extractSection(src, 'log');
173
+ if (logSrc) {
174
+ const logPath = strVal(logSrc, 'path');
175
+ const append = boolVal(logSrc, 'append');
176
+ const includeTimestamp = boolVal(logSrc, 'includeTimestamp');
177
+ const includeDiff = boolVal(logSrc, 'includeDiff');
178
+ if (logPath ?? append ?? includeTimestamp ?? includeDiff) {
179
+ result.log = {};
180
+ if (logPath !== undefined)
181
+ result.log.path = logPath;
182
+ if (append !== undefined)
183
+ result.log.append = append;
184
+ if (includeTimestamp !== undefined)
185
+ result.log.includeTimestamp = includeTimestamp;
186
+ if (includeDiff !== undefined)
187
+ result.log.includeDiff = includeDiff;
188
+ }
189
+ }
190
+ // pentester (top-level scalar fields only; nested objects are rare to change)
191
+ const pentesterSrc = extractSection(src, 'pentester');
192
+ if (pentesterSrc) {
193
+ const enabled = boolVal(pentesterSrc, 'enabled');
194
+ const mode = strVal(pentesterSrc, 'mode');
195
+ const severity = strVal(pentesterSrc, 'severity');
196
+ if (enabled ?? mode ?? severity) {
197
+ result.pentester = {};
198
+ if (enabled !== undefined)
199
+ result.pentester.enabled = enabled;
200
+ if (mode)
201
+ result.pentester.mode = mode;
202
+ if (severity)
203
+ result.pentester.severity = severity;
204
+ }
205
+ }
206
+ return Object.keys(result).length > 0 ? result : null;
207
+ }
208
+ /**
209
+ * Parse uneven.config.ts and sync any changes into .uneven/config.json.
210
+ * Called automatically at the top of loadConfig() — the user never has to
211
+ * re-run `uneven init` just because they edited the TS config file.
212
+ */
213
+ async function syncConfigFromTs() {
214
+ const overlay = await parseConfigTs();
215
+ if (!overlay)
216
+ return;
217
+ let current = {};
218
+ let currentJson = '';
219
+ try {
220
+ currentJson = await fs.readFile(CONFIG_FILE, 'utf-8');
221
+ current = JSON.parse(currentJson);
222
+ }
223
+ catch { /* config.json absent — will be created */ }
224
+ const merged = {
225
+ brain: {
226
+ ...DEFAULTS.brain,
227
+ ...current.brain,
228
+ ...overlay.brain,
229
+ local: {
230
+ ...DEFAULTS.brain.local,
231
+ ...current.brain?.local,
232
+ ...overlay.brain?.local,
233
+ },
234
+ },
235
+ knowledge: { ...DEFAULTS.knowledge, ...current.knowledge, ...overlay.knowledge },
236
+ watch: { ...DEFAULTS.watch, ...current.watch, ...overlay.watch },
237
+ log: { ...DEFAULTS.log, ...current.log, ...overlay.log },
238
+ ...((overlay.pentester ?? current.pentester)
239
+ ? { pentester: { ...current.pentester, ...overlay.pentester } }
240
+ : {}),
241
+ };
242
+ const mergedJson = JSON.stringify(merged, null, 2);
243
+ if (mergedJson !== currentJson) {
244
+ await fs.mkdir('.uneven', { recursive: true });
245
+ await fs.writeFile(CONFIG_FILE, mergedJson);
246
+ }
247
+ }
248
+ // ─── Public API ───────────────────────────────────────────────────────────────
29
249
  /**
30
250
  * Load Uneven config from .uneven/config.json.
31
- * Falls back to defaults if the file does not exist (e.g. before `uneven init`).
251
+ * Automatically syncs uneven.config.ts config.json first, so any edits
252
+ * to the TS file are reflected without re-running `uneven init`.
253
+ * Falls back to defaults if neither file exists (e.g. before `uneven init`).
32
254
  */
33
255
  export async function loadConfig(overrides) {
256
+ await syncConfigFromTs();
34
257
  let config;
35
258
  try {
36
259
  const raw = await fs.readFile(CONFIG_FILE, 'utf-8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uneven-ai",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -27,7 +27,7 @@ const prebuildPath = path.join(root, `prebuilds/${platformKey}/uneven_core.node`
27
27
  const binaryExists = fs.existsSync(prebuildPath)
28
28
 
29
29
  if (!binaryExists) {
30
- const supported = ['linux-x64', 'linux-arm64', 'darwin-x64', 'darwin-arm64', 'win32-x64']
30
+ const supported = ['linux-x64', 'linux-arm64', 'darwin-arm64', 'win32-x64']
31
31
 
32
32
  if (!supported.includes(platformKey)) {
33
33
  console.warn(`\n[uneven-ai] ⚠ Platform '${platformKey}' is not officially supported.`)