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 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 --json # the models to pass to -m: "default" first (the user's list, with fallbacks), then each model
18
- heart models --all --json # every provider's models too (slower: asks each provider)
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
- Read `--json`, not the text: the text layout is for people and may change.
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
- { "place": null, "provider": null, "model": "default", "role": "default", "rotates": true, "about": "your models in order: #1, then the next when one fails (-m default, or no -m)" },
26
- { "place": 1, "provider": "groq", "model": "openai/gpt-oss-120b", "role": "in use", "rotates": false },
27
- { "place": 2, "provider": "openrouter", "model": "qwen/qwen3-coder", "role": "fallback", "rotates": false } ],
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
- Each entry in `models` is something to pass to `-m`, in order:
42
+ `id` is exactly what to pass to `-m`:
34
43
 
35
- - **`default`** (always first; `place` and `provider` are `null`): the user's list. `rotates: true`
36
- when they have more than one model: #1 is used, and the next one in order when it fails.
37
- - **the user's models** (`place` 1, 2, …): `role` is `in use` for #1 and `fallback` for the rest;
38
- `rotates: false`, since a model named on its own is used alone.
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 entries whose `model` contains `<filter>`.
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
- | `default`, or no `-m` | the user's list: #1, and the next one in order when it fails (rate limits, outages…). Use this unless you need one particular model. |
52
- | a model from the list: `-m "<provider> <model>"` (`-m "groq openai/gpt-oss-120b"`), or `-m <model>` alone | that model alone: never another one, also when it fails (a rate limit or an outage is tried again, up to `--max-retries`) |
53
-
54
- **Failures:** a rate limit, a server error, a timeout, a reply that stops coming (nothing for 150s),
55
- a lost connection, or an error page from a proxy in front of the provider is waited out and tried
56
- again (or handed to the next model in the list), up to `--max-retries` (10). With the default
57
- list, a model that can't be used (a wrong key, no access to it, a name the server doesn't know:
58
- 401, 403, 404) also makes way for the next one, and so does one that keeps replying with nothing. When a
59
- provider answers with an error (status 400 or above), the next model tried is from another
60
- provider; that provider's other models are tried only after every provider has had a go.
61
- A model that takes over with a smaller context window gets the conversation summarized first, if it
62
- wouldn't fit.
63
- The run stops with that error and exit code `1` when every model in the list fails that way, or
64
- when a single model (`-m <model>`) does. A request a server refuses (400) is sent as it is to two
65
- more models of the list at most, then the run stops (unless the user set the fallback rule to "at
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 default "…" # the user's list, with its fallbacks (the same as no -m)
138
- heart -p -m "groq openai/gpt-oss-120b" "…" # that provider and model alone, for this run
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: only your config.json (providers, keys), the
8
- // models' test results and prices copied in; deleted when HeartCode
9
- // quits; your own is never written to
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 a new one in the system's temp folder
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 = fs.mkdtempSync(path.join(os.tmpdir(), 'heart-temp-'))
38
- // your providers and keys; what HeartCode learned about each model and prices (or it tests them again)
39
- 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 })
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', () => fs.rmSync(dir, { recursive: true, force: true }))
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)