Recently Written · git

ideamine

An idea inbox for Claude Code: /idea saves ideas at zero tokens; Claude triages them and routes each to the cheapest model that can build it.

git clone https://github.com/equwal/ideamine

Log | Files | Refs


src/config.js (3753 bytes)

1 // Settings. `ideamine config` writes them to ~/.ideamine/config.json. An environment variable
2 // overrides the file, so a test or a single command can use other values.
3 
4 import fs from 'node:fs';
5 import path from 'node:path';
6 import { home, withLock, writeAtomic } from './store.js';
7 
8 export const SETTINGS = {
9   embed_url: {
10     env: 'IDEAMINE_EMBED_URL',
11     value: 'http://127.0.0.1:11434/v1',
12     about: 'OpenAI-compatible embeddings API (Ollama, llama.cpp server)',
13   },
14   embed_model: { env: 'IDEAMINE_EMBED_MODEL', value: 'nomic-embed-text', about: 'embedding model' },
15   search_threshold: {
16     env: 'IDEAMINE_SEARCH_THRESHOLD',
17     value: 0.5,
18     about: 'lowest similarity (0 to 1) of a search result to the query',
19   },
20   group_threshold: {
21     env: 'IDEAMINE_GROUP_THRESHOLD',
22     value: 0.65,
23     about: 'lowest mean similarity (0 to 1) of the ideas in a group; also the floor for related ideas',
24   },
25   publish_url: {
26     env: 'IDEAMINE_PUBLISH_URL',
27     value: '',
28     about: 'dashboard server that gets index.html and data.json by HTTP PUT (empty: off)',
29   },
30   serve_port: {
31     env: 'IDEAMINE_SERVE_PORT',
32     value: '4332',
33     about: 'port of `ideamine serve`, the dashboard with buttons on 127.0.0.1',
34   },
35   serve_hosts: {
36     env: 'IDEAMINE_SERVE_HOSTS',
37     value: '',
38     about: 'more Host names that `ideamine serve` answers, comma-separated (for a server behind nginx)',
39   },
40   sync_url: {
41     env: 'IDEAMINE_SYNC_URL',
42     value: '',
43     about: 'ideamine server that holds the archive for every machine (empty: the archive is here)',
44   },
45   prompt_log: {
46     env: 'IDEAMINE_PROMPT_LOG',
47     value: 'off',
48     about: 'on: send each prompt to the ideamine server, for the Prompts tab of the dashboard',
49   },
50   memstate_url: {
51     env: 'IDEAMINE_MEMSTATE_URL',
52     value: '',
53     about: 'memstated daemon that the Memory tab of `ideamine serve` reads (empty: no Memory tab)',
54   },
55 };
56 
57 const configPath = () => path.join(home(), 'config.json');
58 
59 function readFile() {
60   try {
61     return JSON.parse(fs.readFileSync(configPath(), 'utf8'));
62   } catch {
63     return {};
64   }
65 }
66 
67 function setting(key) {
68   const s = SETTINGS[key];
69   if (!s) throw new Error(`unknown setting "${key}" (use: ${Object.keys(SETTINGS).join(', ')})`);
70   return s;
71 }
72 
73 function parse(key, raw) {
74   if (typeof setting(key).value !== 'number') return String(raw).trim();
75   const n = Number(raw);
76   if (String(raw).trim() === '' || !Number.isFinite(n) || n < 0 || n > 1) throw new Error(`${key} must be a number from 0 to 1`);
77   return n;
78 }
79 
80 /** The value and where it comes from: env, file, or default. */
81 function lookup(key) {
82   const s = setting(key);
83   const env = process.env[s.env];
84   if (env !== undefined && env !== '') return { value: parse(key, env), from: s.env };
85   const saved = readFile()[key];
86   if (saved !== undefined) return { value: parse(key, saved), from: 'config.json' };
87   return { value: s.value, from: 'default' };
88 }
89 
90 export function get(key) {
91   return lookup(key).value;
92 }
93 
94 /** Save a setting. An empty value removes it from the file, so the default applies again. */
95 export function set(key, value) {
96   const clean = value == null || String(value).trim() === '' ? undefined : parse(key, value);
97   withLock(() => {
98     const all = readFile();
99     if (clean === undefined) delete all[key];
100     else all[key] = clean;
101     writeAtomic(configPath(), JSON.stringify(all, null, 2) + '\n');
102   });
103   return get(key);
104 }
105 
106 /** Every setting, one line each, with its source. */
107 export function describe() {
108   return Object.keys(SETTINGS)
109     .map((key) => {
110       const { value, from } = lookup(key);
111       return `${key.padEnd(17)} ${String(value === '' ? '(off)' : value).padEnd(28)} ${from} ยท ${SETTINGS[key].about}`;
112     })
113     .join('\n');
114 }