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 (4119 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   // The token of the hub is never a setting: it comes only from IDEAMINE_COOP_TOKEN.
56   coop_url: {
57     env: 'IDEAMINE_COOP_URL',
58     value: '',
59     about: 'coop hub whose agents build the ideas dropped in Doing (empty: off; needs IDEAMINE_COOP_TOKEN)',
60   },
61   coop_session: { env: 'IDEAMINE_COOP_SESSION', value: 'workers', about: 'coop session of those agents' },
62 };
63 
64 const configPath = () => path.join(home(), 'config.json');
65 
66 function readFile() {
67   try {
68     return JSON.parse(fs.readFileSync(configPath(), 'utf8'));
69   } catch {
70     return {};
71   }
72 }
73 
74 function setting(key) {
75   const s = SETTINGS[key];
76   if (!s) throw new Error(`unknown setting "${key}" (use: ${Object.keys(SETTINGS).join(', ')})`);
77   return s;
78 }
79 
80 function parse(key, raw) {
81   if (typeof setting(key).value !== 'number') return String(raw).trim();
82   const n = Number(raw);
83   if (String(raw).trim() === '' || !Number.isFinite(n) || n < 0 || n > 1) throw new Error(`${key} must be a number from 0 to 1`);
84   return n;
85 }
86 
87 /** The value and where it comes from: env, file, or default. */
88 function lookup(key) {
89   const s = setting(key);
90   const env = process.env[s.env];
91   if (env !== undefined && env !== '') return { value: parse(key, env), from: s.env };
92   const saved = readFile()[key];
93   if (saved !== undefined) return { value: parse(key, saved), from: 'config.json' };
94   return { value: s.value, from: 'default' };
95 }
96 
97 export function get(key) {
98   return lookup(key).value;
99 }
100 
101 /** Save a setting. An empty value removes it from the file, so the default applies again. */
102 export function set(key, value) {
103   const clean = value == null || String(value).trim() === '' ? undefined : parse(key, value);
104   withLock(() => {
105     const all = readFile();
106     if (clean === undefined) delete all[key];
107     else all[key] = clean;
108     writeAtomic(configPath(), JSON.stringify(all, null, 2) + '\n');
109   });
110   return get(key);
111 }
112 
113 /** Every setting, one line each, with its source. */
114 export function describe() {
115   return Object.keys(SETTINGS)
116     .map((key) => {
117       const { value, from } = lookup(key);
118       return `${key.padEnd(17)} ${String(value === '' ? '(off)' : value).padEnd(28)} ${from} ยท ${SETTINGS[key].about}`;
119     })
120     .join('\n');
121 }