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/CHANGELOG.md +89 -0
- package/SKILL.md +47 -35
- package/bin/heart.js +30 -8
- package/bin/heartcode.js +75 -103
- package/package.json +1 -1
- package/src/acp.js +7 -1
- package/src/agent.js +23 -54
- package/src/client.js +11 -43
- package/src/config.js +65 -6
- package/src/guided.js +2 -8
- package/src/headless.js +3 -0
- package/src/settings.html +149 -111
- package/src/settings.js +36 -1
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
|