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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changed in each version of HeartCode, newest first.
|
|
4
|
+
|
|
5
|
+
## 0.1.13 (2026-10-30)
|
|
6
|
+
|
|
7
|
+
- Settings no longer go back by themselves: a save writes only what changed, so another HeartCode
|
|
8
|
+
or settings page with an older copy doesn't undo your change (and keys HeartCode doesn't know stay).
|
|
9
|
+
- A settings change reaches every HeartCode running on the same settings at once, mid-task too: the
|
|
10
|
+
model order, prompts and HEARTCODE.md, hooks, skills, MCP servers. Open settings pages update by themselves.
|
|
11
|
+
- `--temp` runs going on at once share one throwaway folder: a change in one reaches the others,
|
|
12
|
+
never your real settings. Ctrl+C now cleans it up too.
|
|
13
|
+
- Settings page: nothing is kept in the browser (the provider ranking, sorts and folds are in
|
|
14
|
+
~/.heart); Save order is always there; Invert selection on Your models.
|
|
15
|
+
|
|
16
|
+
## 0.1.12 (2026-10-30)
|
|
17
|
+
|
|
18
|
+
- `auto` is the name for your model list (#1, then the next when one fails); `default` still works.
|
|
19
|
+
- Models are written `provider/model` (`-m groq/openai/gpt-oss-120b`); the old `"provider model"` still works.
|
|
20
|
+
- `heart models` prints what `-m` takes, one per line (`auto` first); `--json` gives an `id` for each.
|
|
21
|
+
- `--fallback-model <provider/model>`, as in `claude -p`: tried after the `-m` model when it fails.
|
|
22
|
+
- Settings changed while HeartCode runs (also `-p`, stream-json and editors over ACP) are taken in
|
|
23
|
+
without a restart: a new model order from the running task's next request.
|
|
24
|
+
- Settings page: adding a provider, an MCP server or a skill opens a dialog; every dialog is 70% of
|
|
25
|
+
the screen wide. Your models can be sorted by a provider ranking you drag into order.
|
|
26
|
+
|
|
27
|
+
## 0.1.11 (2026-10-30)
|
|
28
|
+
|
|
29
|
+
- A changelog (this file) in place of the old TODO list.
|
|
30
|
+
|
|
31
|
+
## 0.1.10 (2026-10-30)
|
|
32
|
+
|
|
33
|
+
- Any model error (a rate limit, an outage, "request too large", a bad model name, a wrong key…)
|
|
34
|
+
moves on to the next model in your list, another provider's first, without the app using
|
|
35
|
+
HeartCode seeing it. A run stops with an error only when every model has failed.
|
|
36
|
+
- No more token estimates deciding when to shrink a request or switch models. A long
|
|
37
|
+
conversation is summarized every 100 messages instead.
|
|
38
|
+
- Settings: the rotation choice "at any error" is gone; it now works the same as "at its first
|
|
39
|
+
failure".
|
|
40
|
+
|
|
41
|
+
## 0.1.9 (2026-10-29)
|
|
42
|
+
|
|
43
|
+
- After an error, the next model tried is from another provider; the failing provider's other
|
|
44
|
+
models come only after every provider has had a go.
|
|
45
|
+
- Model lists in Cloudflare's format (`{ "result": [...] }`) are read.
|
|
46
|
+
- Settings: model lists no longer stay stuck on "loading…", and an empty saved list is fetched again.
|
|
47
|
+
- A shorter system prompt, with the duplicates taken out.
|
|
48
|
+
|
|
49
|
+
## 0.1.8 (2026-10-29)
|
|
50
|
+
|
|
51
|
+
- Settings page: model tests run on the server, long model lists scroll smoothly, prices are
|
|
52
|
+
shown, and "Test all" shows its progress.
|
|
53
|
+
|
|
54
|
+
## 0.1.7 (2026-10-29)
|
|
55
|
+
|
|
56
|
+
- Each request is fitted to the model it goes to (images, tool calls, thinking).
|
|
57
|
+
- Safer file changes.
|
|
58
|
+
- Better handing of work to the other models.
|
|
59
|
+
|
|
60
|
+
## 0.1.6 (2026-10-29)
|
|
61
|
+
|
|
62
|
+
- Better handing of work to the other models.
|
|
63
|
+
- A helper's steps are sent to apps using HeartCode, like Claude Code's subagents.
|
|
64
|
+
- Fixed switching models.
|
|
65
|
+
- npm page: GitHub links and keywords.
|
|
66
|
+
|
|
67
|
+
## 0.1.5 (2026-10-28)
|
|
68
|
+
|
|
69
|
+
- Several helpers can run at once; thinking is shown with its name; the model lists show what
|
|
70
|
+
each model can do.
|
|
71
|
+
- Tab only completes, it doesn't send.
|
|
72
|
+
|
|
73
|
+
## 0.1.3 (2026-10-28)
|
|
74
|
+
|
|
75
|
+
- Another of your models does what the one in use can't (for example, looking at an image).
|
|
76
|
+
- A path typed at the prompt is no longer taken for a command.
|
|
77
|
+
|
|
78
|
+
## 0.1.2 (2026-10-28)
|
|
79
|
+
|
|
80
|
+
- Logging: the size on disk, and a Delete all button.
|
|
81
|
+
- Refresh model data from models.dev.
|
|
82
|
+
- A Test button on the Your models page, a simpler list, and `-latest` model names matched in models.dev.
|
|
83
|
+
- Screenshots in the README.
|
|
84
|
+
|
|
85
|
+
## 0.1.1 (2026-10-28)
|
|
86
|
+
|
|
87
|
+
- First release: MCP tools as real tools, fallback models that keep going, a model Test button.
|
|
88
|
+
- The npm package holds only what HeartCode needs to run.
|
|
89
|
+
- README, MIT license, and failures described in SKILL.md.
|
package/SKILL.md
CHANGED
|
@@ -14,63 +14,74 @@ answer (or JSON) and exits. Its flags and output match Claude Code's `claude -p`
|
|
|
14
14
|
```sh
|
|
15
15
|
heart --version # installed? (else: node /path/to/heart_cli/bin/heart.js --version)
|
|
16
16
|
heart providers # the providers set up, and their URLs (keys are masked)
|
|
17
|
-
heart models
|
|
18
|
-
heart models --
|
|
17
|
+
heart models # what -m takes, one per line: "auto" first, then the user's models (provider/model), #1 first
|
|
18
|
+
heart models --json # the same, as JSON
|
|
19
|
+
heart models --all # every provider's models too (slower: asks each provider)
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
`heart models` prints one id per line, nothing else (like `opencode models`):
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
auto
|
|
26
|
+
groq/openai/gpt-oss-120b
|
|
27
|
+
openrouter/qwen/qwen3-coder
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
With `--json`:
|
|
22
31
|
|
|
23
32
|
```json
|
|
24
33
|
{ "models": [
|
|
25
|
-
{ "
|
|
26
|
-
{ "
|
|
27
|
-
{ "
|
|
34
|
+
{ "id": "auto", "fallbacks": true },
|
|
35
|
+
{ "id": "groq/openai/gpt-oss-120b", "provider": "groq", "model": "openai/gpt-oss-120b", "place": 1 },
|
|
36
|
+
{ "id": "openrouter/qwen/qwen3-coder", "provider": "openrouter", "model": "qwen/qwen3-coder", "place": 2 } ],
|
|
28
37
|
"providers": [
|
|
29
|
-
{ "name": "groq", "baseUrl": "https://api.groq.com/openai/v1", "list": true, "models": ["
|
|
38
|
+
{ "name": "groq", "baseUrl": "https://api.groq.com/openai/v1", "list": true, "models": ["groq/…"] },
|
|
30
39
|
{ "name": "local", "baseUrl": "http://127.0.0.1:9/v1", "list": true, "models": [], "error": "cannot reach …" } ] }
|
|
31
40
|
```
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
`id` is exactly what to pass to `-m`:
|
|
34
43
|
|
|
35
|
-
- **`
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
- **`auto`** (always first): the user's list. `fallbacks: true` when it has more than one model:
|
|
45
|
+
#1 is used, and the next one in order when it fails. No `-m` means `auto` too. (`default` is
|
|
46
|
+
its old name and still works.)
|
|
47
|
+
- **the user's models** (`place` 1, 2, …), as `provider/model`: #1 is the one `auto` starts with.
|
|
39
48
|
|
|
40
49
|
No model set up yet: `models` is empty (and `heart -p` fails with "no model set up").
|
|
41
|
-
`heart models [--all] <filter>` keeps only the
|
|
50
|
+
`heart models [--all] <filter>` keeps only the ids that contain `<filter>`.
|
|
42
51
|
|
|
43
52
|
`providers` (only with `--all`): every provider's models. `list: false`: the user switched that
|
|
44
53
|
provider's list off, and `models` are the ones they added by name. A provider that can't be
|
|
45
|
-
reached has an `error` and is skipped; the command still exits `0`.
|
|
54
|
+
reached has an `error` (in text: a line on stderr) and is skipped; the command still exits `0`.
|
|
46
55
|
|
|
47
56
|
**Which model to pass:**
|
|
48
57
|
|
|
49
58
|
| `-m` | What runs |
|
|
50
59
|
|---|---|
|
|
51
|
-
| `
|
|
52
|
-
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
any error"). A provider that refuses a part
|
|
67
|
-
of the request HeartCode can do without (the model's thinking sent back, keys in the tools'
|
|
68
|
-
schemas, images) gets it again without that part, and that is remembered for the model for a day.
|
|
60
|
+
| `auto`, or no `-m` | the user's list: #1, and the next one in order when it fails. Use this unless you need one particular model. |
|
|
61
|
+
| `provider/model` (`-m groq/openai/gpt-oss-120b`), or a model of #1's provider alone | that model alone: never another one, also when it fails (a rate limit or an outage is tried again, up to `--max-retries`) |
|
|
62
|
+
| that, plus `--fallback-model provider/model` (repeatable) | that model, then the fallbacks in order when it fails (as `claude -p --fallback-model`) |
|
|
63
|
+
|
|
64
|
+
**Failures:** with `auto` (the list), any failure (an error status of any kind, a timeout, a reply
|
|
65
|
+
that stops coming, a lost connection, an error page from a proxy, a model that keeps replying with
|
|
66
|
+
nothing) makes way for the next model, from another provider first; that provider's other models
|
|
67
|
+
are tried only after every provider has had a go. None of it shows: the run goes on with the model
|
|
68
|
+
that answers. It stops with exit code `1` only when every model in the list fails: at once if they
|
|
69
|
+
all refused the request outright, else after `--max-retries` (10) whole rounds of them. A single
|
|
70
|
+
model (`-m <model>`) waits out rate limits and outages, up to `--max-retries` tries, and stops at
|
|
71
|
+
any other error. A provider that refuses a part of the request HeartCode can do without (the
|
|
72
|
+
model's thinking sent back, keys in the tools' schemas, images) gets it again without that part,
|
|
73
|
+
and that is remembered for the model for a day. A long conversation is summarized every 100
|
|
74
|
+
messages.
|
|
69
75
|
Each request is fitted to the model it goes to: one that can't see gets a note in place of each
|
|
70
76
|
image, one that can't call tools gets the tool calls as text.
|
|
71
77
|
|
|
72
78
|
`--provider <name>` and `-u <url>` (a server for this run) also mean one model, alone.
|
|
73
79
|
|
|
80
|
+
A run picks up settings the user changes meanwhile (`heart settings`, another HeartCode, or
|
|
81
|
+
`~/.heart` by hand) without being restarted, from its next request: the model order, prompts,
|
|
82
|
+
hooks, skills, MCP servers. `--temp` runs share one throwaway folder, so a change in one reaches the
|
|
83
|
+
other `--temp` runs going on, never the real ones.
|
|
84
|
+
|
|
74
85
|
`heart` is installed by `npm start` in the HeartCode folder. `heartcode` is the same command.
|
|
75
86
|
Needs Node.js 20 or newer. No providers? See "Setting up a model" below.
|
|
76
87
|
|
|
@@ -123,7 +134,7 @@ can't switch the model you chose.
|
|
|
123
134
|
| Everything (edits and commands) | `--permission-mode all` (the default unless the user set another) |
|
|
124
135
|
| Changes kept apart from the user's files | `--worktree=<name>` (in a git repo): it works in `.heartcode/worktrees/<name>` on the branch `heartcode/<name>`; kept if anything changed (merge it from there), removed if nothing did |
|
|
125
136
|
| A cap on how long it works | `--max-turns 30` (model steps), `--max-retries 3` (a failing provider) |
|
|
126
|
-
| Nothing written to the user's `~/.heart` (sessions, logs, settings) | `--temp` |
|
|
137
|
+
| Nothing written to the user's `~/.heart` (sessions, logs, settings) | `--temp` (a throwaway copy, shared by the `--temp` runs going on at once) |
|
|
127
138
|
|
|
128
139
|
Tool names: HeartCode's own (`read_file`, `edit_file`, `write_file`, `run_command`, `search`,
|
|
129
140
|
`list_files`, `web_fetch`, `web_search`…) or Claude Code's (`Read`, `Edit`, `Write`, `Bash`,
|
|
@@ -134,8 +145,9 @@ server's tools, `mcp` all of them.
|
|
|
134
145
|
## Choose the model
|
|
135
146
|
|
|
136
147
|
```sh
|
|
137
|
-
heart -p -m
|
|
138
|
-
heart -p -m
|
|
148
|
+
heart -p -m auto "…" # the user's list, with its fallbacks (the same as no -m)
|
|
149
|
+
heart -p -m groq/openai/gpt-oss-120b "…" # that provider and model alone, for this run
|
|
150
|
+
heart -p -m groq/openai/gpt-oss-120b --fallback-model openrouter/qwen/qwen3-coder "…" # and that one when it fails
|
|
139
151
|
heart -p --provider ollama -m qwen3-coder "…" # the same, written apart
|
|
140
152
|
heart -p --effort high "…" # how hard a thinking model thinks
|
|
141
153
|
```
|
package/bin/heart.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
// heart . this folder
|
|
5
5
|
// heart ~/code/my-app that folder (a folder: a file is an error)
|
|
6
6
|
// heart ~/code/my-app "fix the failing test" -m … then anything heartcode takes (heart --help)
|
|
7
|
-
// heart --temp … a throwaway ~/.heart
|
|
8
|
-
// models' test results and
|
|
9
|
-
// quits; your own is
|
|
7
|
+
// heart --temp … a throwaway ~/.heart, shared by the --temp runs going on at once:
|
|
8
|
+
// your config.json (providers, keys), the models' test results and
|
|
9
|
+
// prices copied in; deleted when the last of them quits; your own is
|
|
10
|
+
// never written to
|
|
10
11
|
// Logs, when they're switched on (the settings page: Logging): each run's log in
|
|
11
12
|
// ~/.heart/logs/runs/<day-month-year-hour-min-sec>/, a file per tab ("log": true in config.json);
|
|
12
13
|
// each request to a model in ~/.heart/logs/requests/<same>/, a file per request ("logRequests").
|
|
@@ -30,15 +31,36 @@ const stamp = (() => {
|
|
|
30
31
|
return `${d.getDate()}-${d.getMonth() + 1}-${d.getFullYear()}-${d.getHours()}-${d.getMinutes()}-${d.getSeconds()}`.replace(/\b(\d)\b/g, '0$1')
|
|
31
32
|
})()
|
|
32
33
|
|
|
33
|
-
// --temp: HeartCode's folder is
|
|
34
|
+
// --temp: HeartCode's folder is one in the system's temp folder, shared by every --temp run going
|
|
35
|
+
// on at the same time (a setting changed in one reaches the others, as ~/.heart's do for the real
|
|
36
|
+
// ones). The first one makes it, the last one to quit deletes it. Each run's pid is a file in
|
|
37
|
+
// .runs/ (a run that crashed leaves one: its process is gone, so it doesn't count).
|
|
34
38
|
const temp = args.includes('--temp')
|
|
35
39
|
if (temp) {
|
|
36
40
|
args.splice(args.indexOf('--temp'), 1)
|
|
37
|
-
const dir =
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
const dir = path.join(os.tmpdir(), `heart-temp-${os.userInfo().username}`)
|
|
42
|
+
const runs = path.join(dir, '.runs')
|
|
43
|
+
const alive = (pid) => {
|
|
44
|
+
try {
|
|
45
|
+
return process.kill(Number(pid), 0), true
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return e.code === 'EPERM'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const others = () => (fs.existsSync(runs) ? fs.readdirSync(runs).filter((pid) => Number(pid) !== process.pid && (alive(pid) || (fs.rmSync(path.join(runs, pid), { force: true }), false))) : [])
|
|
51
|
+
if (!others().length) {
|
|
52
|
+
fs.rmSync(dir, { recursive: true, force: true }) // left by a crashed run: from your settings again
|
|
53
|
+
fs.mkdirSync(runs, { recursive: true })
|
|
54
|
+
// your providers and keys; what HeartCode learned about each model and prices (or it tests them again)
|
|
55
|
+
for (const f of ['config.json', 'models', 'prices.json']) if (fs.existsSync(path.join(realHome, f))) fs.cpSync(path.join(realHome, f), path.join(dir, f), { recursive: true })
|
|
56
|
+
}
|
|
57
|
+
fs.writeFileSync(path.join(runs, String(process.pid)), '')
|
|
40
58
|
process.env.HEART_HOME = dir
|
|
41
|
-
process.on('exit', () =>
|
|
59
|
+
process.on('exit', () => {
|
|
60
|
+
fs.rmSync(path.join(runs, String(process.pid)), { force: true })
|
|
61
|
+
if (!others().length) fs.rmSync(dir, { recursive: true, force: true })
|
|
62
|
+
})
|
|
63
|
+
process.once('SIGINT', () => process.exit(130)) // Ctrl+C (heart settings, -p): through 'exit', so the folder goes too
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
// a folder first: HeartCode opens it (subcommands and options go straight through)
|