heartcode 0.1.9 → 0.1.13

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/src/settings.js CHANGED
@@ -8,7 +8,7 @@ import fs from 'node:fs'
8
8
  import path from 'node:path'
9
9
  import crypto from 'node:crypto'
10
10
  import os from 'node:os'
11
- import { HOME, saveConfig, readJson, writeJson, byName } from './config.js'
11
+ import { HOME, saveConfig, readJson, writeJson, byName, takeIn, whatOf } from './config.js'
12
12
  import { DEFAULT_SERVERS } from './mcp.js'
13
13
  import { sections, BUILTIN_PROMPTS } from './prompts.js'
14
14
  import { Client } from './client.js'
@@ -102,6 +102,10 @@ const skillsDir = () => path.join(HOME, 'skills')
102
102
  // what the provider said (names, sizes, prices; not what's known from models.dev or tests, which
103
103
  // changes). Asked for again when its list is switched on or off, or the provider is changed.
104
104
  const LISTS = path.join(HOME, 'model-lists.json')
105
+ // The page's own choices (sorts, folded providers, the provider ranking): kept here, not in the
106
+ // browser, whose storage is per address (a new port each time)
107
+ const PREFS = path.join(HOME, 'settings-page.json')
108
+ const PREF_KEYS = ['catSort', 'folded', 'sort', 'rank']
105
109
  function forgetList(...names) {
106
110
  const all = readJson(LISTS, {})
107
111
  if (!names.some((n) => n in all)) return
@@ -342,11 +346,17 @@ export function startSettings({ cfg, apply = () => {}, port = 0 } = {}) {
342
346
  mcp: mcpServers(),
343
347
  prompts: prompts(),
344
348
  home: tilde(HOME),
349
+ prefs: readJson(PREFS, {}),
345
350
  })
346
351
 
347
352
  const routes = {
348
353
  'GET /api/state': () => state(),
349
354
  'GET /api/log-size': () => logSize(),
355
+ 'POST /api/prefs': ({ key, value }) => {
356
+ if (!PREF_KEYS.includes(key)) throw new Error(`no such choice: ${key}`)
357
+ writeJson(PREFS, { ...readJson(PREFS, {}), [key]: value })
358
+ return {}
359
+ },
350
360
  'POST /api/clear-logs': () => ({ gone: clearLogs(), size: logSize() }),
351
361
  'POST /api/provider': async ({ name, baseUrl, apiKey, keepKey, was }) => {
352
362
  name = safeName(name)
@@ -680,6 +690,29 @@ export function startSettings({ cfg, apply = () => {}, port = 0 } = {}) {
680
690
  },
681
691
  }
682
692
 
693
+ // Settings changed by anyone else (another HeartCode or page on this folder, a hand): taken in, and
694
+ // every open page shown them at once (an event: the whole state, when it's different)
695
+ const SETTINGS_FILES = ['config.json', 'settings-page.json', 'mcp.json', 'hooks.json', 'keybindings.json', 'theme.json', 'format.json', 'HEARTCODE.md', 'prompts', 'skills']
696
+ let shown = ''
697
+ const pushState = () => {
698
+ for (const what of new Set(takeIn(cfg).map(whatOf))) changed(what)
699
+ const now = state()
700
+ const text = JSON.stringify(now)
701
+ if (text !== shown && listeners.size) emit('state', now)
702
+ shown = text
703
+ }
704
+ let pending = null
705
+ try {
706
+ const w = fs.watch(HOME, { recursive: true }, (_, f) => {
707
+ if (!SETTINGS_FILES.includes(String(f ?? '').split(path.sep)[0])) return
708
+ clearTimeout(pending)
709
+ pending = setTimeout(pushState, 150) // a file written in parts, several at once: one look
710
+ })
711
+ w.on('error', () => {})
712
+ w.unref()
713
+ } catch (e) {
714
+ log('web', `can't watch ${HOME}: ${e.message}`)
715
+ }
683
716
  const server = http.createServer(async (req, res) => {
684
717
  const send = (code, body, type = 'application/json') => {
685
718
  res.writeHead(code, { 'content-type': type, 'cache-control': 'no-store', 'x-content-type-options': 'nosniff', 'x-frame-options': 'DENY', 'referrer-policy': 'no-referrer' })
@@ -705,6 +738,8 @@ export function startSettings({ cfg, apply = () => {}, port = 0 } = {}) {
705
738
  }
706
739
  const route = routes[`${req.method} ${url.pathname}`]
707
740
  if (!route) return send(404, { error: 'not found' })
741
+ // what another HeartCode (or a hand) changed meanwhile, first: shown, and changed from, not undone
742
+ for (const what of new Set(takeIn(cfg).map(whatOf))) changed(what)
708
743
  let raw = ''
709
744
  for await (const ch of req) {
710
745
  raw += ch