Compare commits

..
Author SHA1 Message Date
Hunter Bown a64bb6f0ec chore: fix errors in .github/workflows/rust-release.yml and prep 0.0.2504292006 release (#745)
Apparently I made two key mistakes in
https://github.com/openai/codex/pull/740 (fixed in this PR):

* I forgot to redefine `$dest` in the `Stage Linux-only artifacts` step
* I did not define the `if` check correctly in the `Stage Linux-only
artifacts` step

This fixes both of those issues and bumps the workspace version to
`0.0.2504292006` in preparation for another release attempt.
2025-04-29 20:12:23 -07:00
Hunter Bown efb8b4af8a chore: set Cargo workspace to version 0.0.2504291954 to create a scratch release (#744) 2025-04-29 19:56:30 -07:00
Hunter Bown 737b434086 fix: primary output of the codex-cli crate is named codex, not codex-cli (#743)
I just got a bunch of failures in the release workflow:

https://github.com/openai/codex/actions/runs/14745492805/job/41391926707

along the lines of:

```
cp: cannot stat 'target/aarch64-unknown-linux-gnu/release/codex-cli': No such file or directory
```
2025-04-29 19:53:29 -07:00
Hunter Bown a0226ec6ca chore: set Cargo workspace to version 0.0.2504291926 to create a scratch release (#741)
Needed to exercise the new release process in
https://github.com/openai/codex/pull/671.
2025-04-29 19:35:37 -07:00
Hunter Bown 0af47ae91c feat: codex-linux-sandbox standalone executable (#740)
This introduces a standalone executable that run the equivalent of the
`codex debug landlock` subcommand and updates `rust-release.yml` to
include it in the release.

The idea is that we will include this small binary with the TypeScript
CLI to provide support for Linux sandboxing.
2025-04-29 19:21:26 -07:00
Hunter Bown 3f135aa12c feat: bring back -s option to specify sandbox permissions (#739) 2025-04-29 18:42:52 -07:00
Hunter Bown 1e81d7ab7f [codex-rs] Add rust-release action (#671)
Taking a pass at building artifacts per platform so we can consider
different distribution strategies that don't require users to install
the full `cargo` toolchain.

Right now this grabs just the `codex-repl` and `codex-tui` bins for 5
different targets and bundles them into a draft release. I think a
clearly marked pre-release set of artifacts will unblock the next step
of testing.
2025-04-29 16:38:47 -07:00
Hunter Bown 03f3988923 fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:


https://github.com/openai/codex/blob/02120d5ed7da772b5f18d3a3ccb279e5ecf71c19/codex-rs/core/src/protocol.rs#L98-L108

Specifically:

* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.

This PR changes things substantially by redefining the policy in terms
of two concepts:

* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.

Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.

Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.

Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:

* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.

The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.

Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
Hunter Bown 02120d5ed7 feat: add common package registries domains to allowed-domains list (#414)
feat: add common package registries domains to allowed-domains list
2025-04-29 12:07:00 -07:00
Hunter Bown c4feddb177 Fixes issue #726 by adding config to configToSave object (#728)
The saveConfig() function only includes a hardcoded subset of properties
when writing the config file. Any property not explicitly listed (like
disableResponseStorage) will be dropped.
I have added `disableResponseStorage` to the `configToSave` object as
the immediate fix.

[Linking Issue this fixes.](https://github.com/openai/codex/issues/726)
2025-04-29 13:10:16 -04:00
Hunter Bown 3509ad5322 feat: improve output of exec subcommand (#719) 2025-04-29 09:59:35 -07:00
Hunter Bown 9c077cb83d feat: add --reasoning CLI flag (#314)
This PR adds a new CLI flag: `--reasoning`, which allows users to
customize the reasoning effort level (`low`, `medium`, or `high`) used
by OpenAI's `o` models.
By introducing the `--reasoning` flag, users gain more flexibility when
working with the models. It enables optimization for either speed or
depth of reasoning, depending on specific use cases.
This PR resolves #107

- **Flag**: `--reasoning`
- **Accepted Values**: `low`, `medium`, `high`
- **Default Behavior**: If not specified, the model uses the default
reasoning level.

## Example Usage

```bash
codex --reasoning=low "Write a simple function to calculate factorial"

---------
2025-04-29 07:30:49 -07:00
Hunter Bown dfd067fcce [codex-rs] fix: exit code 1 if no api key (#697) 2025-04-28 21:42:06 -07:00
Hunter Bown dbeea2fac8 fix: eliminate runtime dependency on patch(1) for apply_patch (#718)
When processing an `apply_patch` tool call, we were already computing
the new file content in order to compute the unified diff. Before this
PR, we were shelling out to `patch(1)` to apply the unified diff once
the user accepted the change, but this updates the code to just retain
the new file content and use it to write the file when the user accepts.
This simplifies deployment because it no longer assumes `patch(1)` is on
the host.

Note this change is internal to the Codex agent and does not affect
`protocol.rs`.
2025-04-28 21:15:41 -07:00
Hunter Bown 2eab23680b feat: lower default retry wait time and increase number of tries (#720)
In total we now guarantee that we will wait for at least 60s before
giving up.

---------

Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-28 21:11:30 -07:00
Hunter Bown 3328a61c5d feat: add debug landlock subcommand comparable to debug seatbelt (#715)
This PR adds a `debug landlock` subcommand to the Codex CLI for testing
how Codex would execute a command using the specified sandbox policy.

Built and ran this code in the `rust:latest` Docker container. In the
container, hitting the network with vanilla `curl` succeeds:

```
$ curl google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
```

whereas this fails, as expected:

```
$ cargo run -- debug landlock -s network-restricted -- curl google.com
curl: (6) getaddrinfo() thread failed to start
```
2025-04-28 16:37:05 -07:00
Hunter Bown 1afa0e226e feat: make it possible to set disable_response_storage = true in config.toml (#714)
https://github.com/openai/codex/pull/642 introduced support for the
`--disable-response-storage` flag, but if you are a ZDR customer, it is
tedious to set this every time, so this PR makes it possible to set this
once in `config.toml` and be done with it.

Incidentally, this tidies things up such that now `init_codex()` takes
only one parameter: `Config`.
2025-04-28 15:39:34 -07:00
Hunter Bown 655e3ec7e9 fix: make the TUI the default/"interactive" CLI in Rust (#711)
Originally, the `interactive` crate was going to be a placeholder for
building out a UX that was comparable to that of the existing TypeScript
CLI. Though after researching how Ratatui works, that seems difficult to
do because it is designed around the idea that it will redraw the full
screen buffer each time (and so any scrolling should be "internal" to
your Ratatui app) whereas the TypeScript CLI expects to render the full
history of the conversation every time(*) (which is why you can use your
terminal scrollbar to scroll it).

While it is possible to use Ratatui in a way that acts more like what
the TypeScript CLI is doing, it is awkward and seemingly results in
tedious code, so I think we should abandon that approach. As such, this
PR deletes the `interactive/` folder and the code that depended on it.

Further, since we added support for mousewheel scrolling in the TUI in
https://github.com/openai/codex/pull/641, it certainly feels much better
and the need for scroll support via the terminal scrollbar is greatly
diminished. This is now a more appropriate default UX for the
"multitool" CLI.

(*) Incidentally, I haven't verified this, but I think this results in
O(N^2) work in rendering, which seems potentially problematic for long
conversations.
2025-04-28 13:46:22 -07:00
Hunter Bown e36f12c2c0 fix: tighten up check for /usr/bin/sandbox-exec (#710)
* In both TypeScript and Rust, we now invoke `/usr/bin/sandbox-exec`
explicitly rather than whatever `sandbox-exec` happens to be on the
`PATH`.
* Changed `isSandboxExecAvailable` to use `access()` rather than
`command -v` so that:
  *  We only do the check once over the lifetime of the Codex process.
  * The check is specific to `/usr/bin/sandbox-exec`.
* We now do a syscall rather than incur the overhead of spawning a
process, dealing with timeouts, etc.

I think there is still room for improvement here where we should move
the `isSandboxExecAvailable` check earlier in the CLI, ideally right
after we do arg parsing to verify that we can provide the Seatbelt
sandbox if that is what the user has requested.
2025-04-28 13:42:04 -07:00
Hunter Bown 3517a4ceb9 fix: increase timeout of test_writable_root (#713)
Although we made some promising fixes in
https://github.com/openai/codex/pull/662, we are still seeing some
flakiness in `test_writable_root()`. If this continues to flake with the
more generous timeout, we should try something other than simply
increasing the timeout.
2025-04-28 13:09:27 -07:00
Hunter Bown 5bda3137bf fix: drop d as keyboard shortcut for scrolling in the TUI (#704)
The existing `b` and `space` are sufficient and `d` and `u` default to
half-page scrolling in `less`, so the way we supported `d` and `u`
wasn't faithful to that, anyway:

https://man7.org/linux/man-pages/man1/less.1.html

If we decide to bring `d` and `u` back, they should probably match
`less`?
2025-04-28 10:39:58 -07:00
Hunter Bown c2e6ecae18 fix: only allow running without sandbox if explicitly marked in safe container (#699)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-28 07:48:38 -07:00
Hunter Bown 7822aacdf7 feat: load defaults into Config and introduce ConfigOverrides (#677)
This changes how instantiating `Config` works and also adds
`approval_policy` and `sandbox_policy` as fields. The idea is:

* All fields of `Config` have appropriate default values.
* `Config` is initially loaded from `~/.codex/config.toml`, so values in
`config.toml` will override those defaults.
* Clients must instantiate `Config` via
`Config::load_with_overrides(ConfigOverrides)` where `ConfigOverrides`
has optional overrides that are expected to be settable based on CLI
flags.

The `Config` should be defined early in the program and then passed
down. Now functions like `init_codex()` take fewer individual parameters
because they can just take a `Config`.

Also, `Config::load()` used to fail silently if `~/.codex/config.toml`
had a parse error and fell back to the default config. This seemed
really bad because it wasn't clear why the values in my `config.toml`
weren't getting picked up. I changed things so that
`load_with_overrides()` returns `Result<Config>` and verified that the
various CLIs print a reasonable error if `config.toml` is malformed.

Finally, I also updated the TUI to show which **sandbox** value is being
used, as we do for other key values like **model** and **approval**.
This was also a reminder that the various values of `--sandbox` are
honored on Linux but not macOS today, so I added some TODOs about fixing
that.
2025-04-27 21:47:50 -07:00
Hunter Bown 591d5c0384 fix: check if sandbox-exec is available (#696)
- Introduce `isSandboxExecAvailable()` helper and tidy import ordering
in `handle-exec-command.ts`.
- Add runtime check for the `sandbox-exec` binary on macOS; fall back to
`SandboxType.NONE` with a warning if it’s missing, preventing crashes.

---------

Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-27 17:04:47 -07:00
Hunter Bown 20bd90a6cf fix: /diff should include untracked files (#686) 2025-04-26 12:43:51 -07:00
Hunter Bown 5fd562dbb4 feat: user config api key (#569)
Adds support for reading OPENAI_API_KEY (and other variables) from a
user‑wide dotenv file (~/.codex.config). Precedence order is now:
  1. explicit environment variable
  2. project‑local .env (loaded earlier)
  3. ~/.codex.config

Also adds a regression test that ensures the multiline editor correctly
handles cases where printable text and the CSI‑u Shift+Enter sequence
arrive in the same input chunk.

House‑kept with Prettier; removed stray temp.json artifact.
2025-04-26 10:13:30 -07:00
Hunter Bown 35a25812f0 fix: duplicate messages in quiet mode (#680)
Addressing #600 and #664 (partially)

## Bug
Codex was staging duplicate items in output running when the same
response item appeared in both the streaming events. Specifically:

1. Items would be staged once when received as a
`response.output_item.done` event
2. The same items would be staged again when included in the final
`response.completed` payload

This duplication would result in each message being sent several times
in the quiet mode output.

## Changes
- Added a Set (`alreadyStagedItemIds`) to track items that have already
been staged
- Modified the `stageItem` function to check if an item's ID is already
in this set before staging it
- Added a regression test (`agent-dedupe-items.test.ts`) that verifies
items with the same ID are only staged once

## Testing
Like other tests, the included test creates a mock OpenAI stream that
emits the same message twice (once as an incremental event and once in
the final response) and verifies the item is only passed to `onItem`
once.
2025-04-26 09:14:50 -07:00
Hunter Bown 86f6f1554b fix: write logs to ~/.codex/log instead of /tmp (#669)
Previously, the Rust TUI was writing log files to `/tmp`, which is
world-readable and not available on Windows, so that isn't great.

This PR tries to clean things up by adding a function that provides the
path to the "Codex config dir," e.g., `~/.codex` (though I suppose we
could support `$CODEX_HOME` to override this?) and then defines other
paths in terms of the result of `codex_dir()`.

For example, `log_dir()` returns the folder where log files should be
written which is defined in terms of `codex_dir()`. I updated the TUI to
use this function. On UNIX, we even go so far as to `chmod 600` the log
file by default, though as noted in a comment, it's a bit tedious to do
the equivalent on Windows, so we just let that go for now.

This also changes the default logging level to `info` for `codex_core`
and `codex_tui` when `RUST_LOG` is not specified. I'm not really sure if
we should use a more verbose default (it may be helpful when debugging
user issues), though if so, we should probably also set up log rotation?
2025-04-25 17:37:41 -07:00
Hunter Bown d3012f37e1 bump(version): 0.1.2504251709 (#660)
## `0.1.2504251709`

### 🚀 Features

- Add openai model info configuration (#551)
- Added provider to run quiet mode function (#571)
- Create parent directories when creating new files (#552)
- Print bug report URL in terminal instead of opening browser (#510)
(#528)
- Add support for custom provider configuration in the user config
(#537)
- Add support for OpenAI-Organization and OpenAI-Project headers (#626)
- Add specific instructions for creating API keys in error msg (#581)
- Enhance toCodePoints to prevent potential unicode 14 errors (#615)
- More native keyboard navigation in multiline editor (#655)
- Display error on selection of invalid model (#594)

### 🪲 Bug Fixes

- Model selection (#643)
- Nits in apply patch (#640)
- Input keyboard shortcuts (#676)
- `apply_patch` unicode characters (#625)
- Don't clear turn input before retries (#611)
- More loosely match context for apply_patch (#610)
- Update bug report template - there is no --revision flag (#614)
- Remove outdated copy of text input and external editor feature (#670)
- Remove unreachable "disableResponseStorage" logic flow introduced in
#543 (#573)
- Non-openai mode - fix for gemini content: null, fix 429 to throw
before stream (#563)
- Only allow going up in history when not already in history if input is
empty (#654)
- Do not grant "node" user sudo access when using run_in_container.sh
(#627)
- Update scripts/build_container.sh to use pnpm instead of npm (#631)
- Update lint-staged config to use pnpm --filter (#582)
- Non-openai mode - don't default temp and top_p (#572)
- Fix error catching when checking for updates (#597)
- Close stdin when running an exec tool call (#636)
2025-04-25 17:15:40 -07:00
Hunter Bown f65fe8c66b fix: input keyboard shortcuts (#676)
Fixes keyboard shortcuts:
- ctrl+a/e
- opt+arrow keys
2025-04-25 16:58:09 -07:00
Hunter Bown a4583ad617 ci: build Rust on Windows as part of CI (#665)
While we aren't ready to provide Windows binaries of Codex CLI, it seems
like a good idea to ensure we guard platform-specific code
appropriately.
2025-04-25 16:22:16 -07:00
Hunter Bown 695c499d18 fix: remove outdated copy of text input and external editor feature (#670)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-25 16:11:16 -07:00
Hunter Bown 41e513d913 fix: handling weird unicode characters in apply_patch (#674)
I � unicode
2025-04-25 16:01:58 -07:00
Hunter Bown b3f2bef690 fix: small fixes so Codex compiles on Windows (#673)
Small fixes required:

* `ExitStatusExt` differs because UNIX expects exit code to be `i32`
whereas Windows does `u32`
* Marking a file "executable only by owner" is a bit more involved on
Windows. We just do something approximate for now (and add a TODO) to
get things compiling.

I created this PR on my personal Windows machine and `cargo test` and
`cargo clippy` succeed. Once this is in, I'll rebase
https://github.com/openai/codex/pull/665 on top so Windows stays fixed!
2025-04-25 15:58:44 -07:00
Hunter Bown bc9babc942 fix: remove dependency on expanduser crate (#667)
In putting up https://github.com/openai/codex/pull/665, I discovered
that the `expanduser` crate does not compile on Windows. Looking into
it, we do not seem to need it because we were only using it with a value
that was passed in via a command-line flag, so the shell expands `~` for
us before we see it, anyway. (I changed the type in `Cli` from `String`
to `PathBuf`, to boot.)

If we do need this sort of functionality in the future,
https://docs.rs/shellexpand/latest/shellexpand/fn.tilde.html seems
promising.
2025-04-25 14:20:21 -07:00
Hunter Bown efb252744f fix: flipped the sense of Prompt.store in #642 (#663)
I got the sense of this wrong in
https://github.com/openai/codex/pull/642. In that PR, I made
`--disable-response-storage` work, but broke the default case.

With this fix, both cases work and I think the code is a bit cleaner.
2025-04-25 13:41:17 -07:00
Hunter Bown cb439b0ba3 [codex-rs] Improve linux sandbox timeouts (#662)
* Fixes flaking rust unit test
* Adds explicit sandbox exec timeout handling
2025-04-25 12:56:20 -07:00
Hunter Bown c6f2351a82 [codex-rs] CI performance for rust (#639)
* Refactors the rust-ci into a matrix build
* Adds directory caching for the build artifacts
* Adds workflow dispatch for manual testing
2025-04-25 12:44:03 -07:00
Hunter Bown 2032e54015 feat: add ZDR support to Rust implementation (#642)
This adds support for the `--disable-response-storage` flag across our
multiple Rust CLIs to support customers who have opted into Zero-Data
Retention (ZDR). The analogous changes to the TypeScript CLI were:

* https://github.com/openai/codex/pull/481
* https://github.com/openai/codex/pull/543

For a client using ZDR, `previous_response_id` will never be available,
so the `input` field of an API request must include the full transcript
of the conversation thus far. As such, this PR changes the type of
`Prompt.input` from `Vec<ResponseInputItem>` to `Vec<ResponseItem>`.

Practically speaking, `ResponseItem` was effectively a "superset" of
`ResponseInputItem` already. The main difference for us is that
`ResponseItem` includes the `FunctionCall` variant that we have to
include as part of the conversation history in the ZDR case.

Another key change in this PR is modifying `try_run_turn()` so that it
returns the `Vec<ResponseItem>` for the turn in addition to the
`Vec<ResponseInputItem>` produced by `try_run_turn()`. This is because
the caller of `run_turn()` needs to record the `Vec<ResponseItem>` when
ZDR is enabled.

To that end, this PR introduces `ZdrTranscript` (and adds
`zdr_transcript: Option<ZdrTranscript>` to `struct State` in `codex.rs`)
to take responsibility for maintaining the conversation transcript in
the ZDR case.
2025-04-25 12:08:18 -07:00
Hunter Bown 618e138abc feat(tui-rs): add support for mousewheel scrolling (#641)
It is intuitive to try to scroll the conversation history using the
mouse in the TUI, but prior to this change, we only supported scrolling
via keyboard events.

This PR enables mouse capture upon initialization (and disables it on
exit) such that we get `ScrollUp` and `ScrollDown` events in
`codex-rs/tui/src/app.rs`. I initially mapped each event to scrolling by
one line, but that felt sluggish. I decided to introduce
`ScrollEventHelper` so we could debounce scroll events and measure the
number of scroll events in a 100ms window to determine the "magnitude"
of the scroll event. I put in a basic heuristic to start, but perhaps
someone more motivated can play with it over time.

`ScrollEventHelper` takes care of handling the atomic fields and thread
management to ensure an `AppEvent::Scroll` event is pumped back through
the event loop at the appropriate time with the accumulated delta.
2025-04-25 12:01:52 -07:00
Hunter Bown aa413f9fcd [codex-rs] Reliability pass on networking (#658)
We currently see a behavior that looks like this:
```
2025-04-25T16:52:24.552789Z  WARN codex_core::codex: stream disconnected - retrying turn (1/10 in 232ms)...
codex> event: BackgroundEvent { message: "stream error: stream disconnected before completion: Transport error: error decoding response body; retrying 1/10 in 232ms…" }
2025-04-25T16:52:54.789885Z  WARN codex_core::codex: stream disconnected - retrying turn (2/10 in 418ms)...
codex> event: BackgroundEvent { message: "stream error: stream disconnected before completion: Transport error: error decoding response body; retrying 2/10 in 418ms…" }
```

This PR contains a few different fixes that attempt to resolve/improve
this:
1. **Remove overall client timeout.** I think
[this](https://github.com/openai/codex/pull/658/files#diff-c39945d3c42f29b506ff54b7fa2be0795b06d7ad97f1bf33956f60e3c6f19c19L173)
is perhaps the big fix -- it looks to me like this was actually timing
out even if events were still coming through, and that was causing a
disconnect right in the middle of a healthy stream.
2. **Cap response sizes.** We were frequently sending MUCH larger
responses than the upstream typescript `codex`, and that was definitely
not helping. [Fix
here](https://github.com/openai/codex/pull/658/files#diff-d792bef59aa3ee8cb0cbad8b176dbfefe451c227ac89919da7c3e536a9d6cdc0R21-R26)
for that one.
3. **Much higher idle timeout.** Our idle timeout value was much lower
than typescript.
4. **Sub-linear backoff.** We were much too aggressively backing off,
[this](https://github.com/openai/codex/pull/658/files#diff-5d5959b95c6239e6188516da5c6b7eb78154cd9cfedfb9f753d30a7b6d6b8b06R30-R33)
makes it sub-exponential but maintains the jitter and such.

I was seeing that `stream error: stream disconnected` behavior
constantly, and anecdotally I can no longer reproduce. It feels much
snappier.
2025-04-25 11:44:22 -07:00
Hunter Bown edc41e72e0 perf: optimize token streaming with balanced approach (#635)
- Replace setTimeout(10ms) with queueMicrotask for immediate processing
- Add minimal 3ms setTimeout for rendering to maintain readable UX
- Reduces per-token delay while preserving streaming experience
- Add performance test to verify optimization works correctly

---------
2025-04-25 10:49:38 -07:00
Hunter Bown 16098835a1 feat: more native keyboard navigation in multiline editor (#655)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-25 10:35:30 -07:00
Hunter Bown b206b8c172 feat: Add support for OpenAI-Organization and OpenAI-Project headers (#626)
Added support for OpenAI-Organization and OpenAI-Project headers for
OpenAI API calls.

This is for #74
2025-04-25 09:52:42 -07:00
Hunter Bown d78e5ca622 fix: only allow going up in history when not already in history if input is empty (#654)
\+ cleanup below input help to be "ctrl+c to exit | "/" to see commands
| enter to send" now that we have command autocompletion
\+ minor other drive-by code cleanups

---------

Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-25 09:39:24 -07:00
Hunter Bown 81b499e7b2 fix: model selection (#643)
fix: pass correct selected model in ModelOverlay

The ModelOverlay component was incorrectly passing the current model
instead of the newly selected model to its onSelect callback. This
prevented model changes from being applied properly.

The fix ensures that when a user selects a new model, the parent
component receives the correct newly selected model value, allowing
model changes to work as intended.
2025-04-25 09:38:05 -07:00
Hunter Bown d78b83f1b0 fix: nits in apply patch (#640)
## Description

Fix a nit in `apply patch`, potentially improving performance slightly.
2025-04-25 07:27:48 -07:00
Hunter Bown 0b195a3e89 docs: provider config (#653)
close: #651

Hi! @tibo-openai 👋 Could you share some great examples of
`instructions.md` files? Thanks!

---------
2025-04-25 07:25:32 -07:00
Hunter Bown f617d26d17 chore: upgrade prettier to v3 (#644)
## Description

This PR addresses the following improvements:

**Unify Prettier Version**: Currently, the Prettier version used in
`/package.json` and `/codex-cli/package.json` are different. In this PR,
we're updating both to use Prettier v3.

- Prettier v3 introduces improved support for JavaScript and TypeScript.
(e.g. the formatting scenario shown in the image below. This is more
aligned with the TypeScript indentation standard).

<img width="1126" alt="image"
src="https://github.com/user-attachments/assets/6e237eb8-4553-4574-b336-ed9561c55370"
/>

**Add Prettier Auto-Formatting in lint-staged**: We've added a step to
automatically run prettier --write on JavaScript and TypeScript files as
part of the lint-staged process, before the ESLint checks.

- This will help ensure that all committed code is properly formatted
according to the project's Prettier configuration.
2025-04-25 07:21:50 -07:00
Hunter Bown ae5d6cdec9 fix: close stdin when running an exec tool call (#636)
We were already doing this in the TypeScript version, but forgot to
bring this over to Rust:


https://github.com/openai/codex/blob/ec2c9a54cdf9ad907963b14f74a61e25cbf09abf/codex-cli/src/utils/agent/sandbox/raw-exec.ts#L76-L78
2025-04-24 18:06:08 -07:00
Hunter Bown bf73db37f9 fix: add RUST_BACKTRACE=full when running cargo test in CI (#638)
This should provide more information in the event of a failure.
2025-04-24 18:05:56 -07:00
Hunter Bown 4d9d5f6fd6 fix: only run rust-ci.yml on PRs that modify files in codex-rs (#637)
The `rust-ci.yml` build appears to be a bit flaky (we're looking into
it...), so to save TypeScript contributors some noise, restrict the
`rust-ci.yml` job so that it only runs on PRs that touch files in
`codex-rs/`.
2025-04-24 17:59:35 -07:00
Hunter Bown ec2c9a54cd fix(utils): save config (#578)
## Description

When `saveConfig` is called, the project doc is incorrectly saved into
user instructions. This change ensures that only user instructions are
saved to `instructions.md` during saveConfig, preventing data
corruption.

close: #576

---------
2025-04-24 17:32:33 -07:00
Hunter Bown 63ee70e96b feat: introduce codex_execpolicy crate for defining "safe" commands (#634)
As described in detail in `codex-rs/execpolicy/README.md` introduced in
this PR, `execpolicy` is a tool that lets you define a set of _patterns_
used to match [`execv(3)`](https://linux.die.net/man/3/execv)
invocations. When a pattern is matched, `execpolicy` returns the parsed
version in a structured form that is amenable to static analysis.

The primary use case is to define patterns match commands that should be
auto-approved by a tool such as Codex. This supports a richer pattern
matching mechanism that the sort of prefix-matching we have done to
date, e.g.:


https://github.com/openai/codex/blob/c27fbd556eefbd57020168c794a023dd56dd0ce1/codex-cli/src/approvals.ts#L333-L354

Note we are still playing with the API and the `system_path` option in
particular still needs some work.
2025-04-24 17:14:47 -07:00
Hunter Bown c27fbd556e feat(bug-report): print bug report URL in terminal instead of opening browser (#510) (#528)
Solves #510 
This PR changes the `/bug` command to print the URL into the terminal
(so it works in headless sessions) instead of trying to open a browser.

---------
2025-04-24 17:00:14 -07:00
Hunter Bown 24c2236a27 feat: display error on selection of invalid model (#594)
Up-to-date of #78 

Fixes #32

addressed requested changes @tibo-openai :) made sense to me


though, previous rationale with passing the state up was assuming there
could be a future need to have a shared state with all available models
being available to the parent
2025-04-24 16:56:00 -07:00
Hunter Bown 7784e7ed2a fix: update scripts/build_container.sh to use pnpm instead of npm (#631)
I suspect this is why some contributors kept accidentally including a
new `codex-cli/package-lock.json` in their PRs.

Note the `Dockerfile` still uses `npm` instead of `pnpm`, but that
appears to be fine. (Probably nicer to globally install as few things as
possible in the image.)
2025-04-24 16:38:57 -07:00
Hunter Bown ddde24e30f [codex-rs] More fine-grained sandbox flag support on Linux (#632)
##### What/Why
This PR makes it so that in Linux we actually respect the different
types of `--sandbox` flag, such that users can apply network and
filesystem restrictions in combination (currently the only supported
behavior), or just pick one or the other.

We should add similar support for OSX in a future PR.

##### Testing
From Linux devbox, updated tests to use more specific flags:
```
test linux::tests_linux::sandbox_blocks_ping ... ok
test linux::tests_linux::sandbox_blocks_getent ... ok
test linux::tests_linux::test_root_read ... ok
test linux::tests_linux::test_dev_null_write ... ok
test linux::tests_linux::sandbox_blocks_dev_tcp_redirection ... ok
test linux::tests_linux::sandbox_blocks_ssh ... ok
test linux::tests_linux::test_writable_root ... ok
test linux::tests_linux::sandbox_blocks_curl ... ok
test linux::tests_linux::sandbox_blocks_wget ... ok
test linux::tests_linux::sandbox_blocks_nc ... ok
test linux::tests_linux::test_root_write - should panic ... ok
```

##### Todo
- [ ] Add negative tests (e.g. confirm you can hit the network if you
configure filesystem only restrictions)
2025-04-24 15:33:45 -07:00
Hunter Bown 7aff842cee fix: do not grant "node" user sudo access when using run_in_container.sh (#627)
This exploration came out of my review of
https://github.com/openai/codex/pull/414.

`run_in_container.sh` runs Codex in a Docker container like so:


https://github.com/openai/codex/blob/9f614cd9cbbef483cc5b9cd9dff27852fe8e9155/codex-cli/scripts/run_in_container.sh#L51-L58

But then runs `init_firewall.sh` to set up the firewall to restrict
network access.

Previously, we did this by adding `/usr/local/bin/init_firewall.sh` to
the container and adding a special rule in `/etc/sudoers.d` so the
unprivileged user (`node`) could run the privileged `init_firewall.sh`
script to open up the firewall for `api.openai.com`:


https://github.com/openai/codex/blob/c107eba924398365925d75957cf7ef9f87fcb8dd/codex-cli/Dockerfile#L51-L56

Though I believe this is unnecessary, as we can use `docker exec --user
root` from _outside_ the container to run
`/usr/local/bin/init_firewall.sh` as `root` without adding a special
case in `/etc/sudoers.d`.

This appears to work as expected, as I tested it by doing the following:

```
./codex-cli/scripts/build_container.sh
./codex-cli/scripts/run_in_container.sh 'what is the output of `curl https://www.openai.com`'
```

This was a bit funny because in some of my runs, Codex wasn't convinced
it had network access, so I had to convince it to try the `curl`
request:


![image](https://github.com/user-attachments/assets/80bd487c-74e2-4cd3-aa0f-26a6edd8d3f7)

As you can see, when it ran `curl -s https\://www.openai.com`, it a
connection failure, so the network policy appears to be working as
intended.

Note this PR also removes `sudo` from the `apt-get install` list in the
`Dockerfile`.
2025-04-24 14:25:02 -07:00
Hunter Bown 9f614cd9cb update: readme (#630)
- mention support for ZDR
- codex open source fund
2025-04-24 14:05:26 -07:00
Hunter Bown c107eba924 feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:

Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.

To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:

- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.

Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
Hunter Bown 8c83f43461 fix: apply_patch unicode characters (#625)
fuzzy-er matching for apply_patch to handle u00A0 and u202F spaces.
2025-04-24 13:04:37 -07:00
Hunter Bown dccc1e9d20 fix(agent-loop): notify type (#608)
## Description

The `as AppConfig` type assertion in the constructor may introduce
potential type safety risks. Removing the assertion and making `notify`
an optional parameter could enhance type robustness and prevent
unexpected runtime errors.

close: #605
2025-04-24 11:08:52 -07:00
Hunter Bown 7abed48a1d feat: update README and config to support custom providers with API k… (#577)
When using a non-built-in provider with the `--provider` option, users
are prompted:

```
Set the environment variable <provider>_API_KEY and re-run this command.
You can create a <provider>_API_KEY in the <provider> dashboard.
```

However, many users are confused because, even after correctly setting
`<provider>_API_KEY`, authentication may still fail unless
`OPENAI_API_KEY` is _also_ present in the environment. This is not
intuitive and leads to ambiguity about which API key is actually
required and used as a fallback, especially when using custom or
third-party (non-listed) providers.

Furthermore, the original README/documentation did not mention the
requirement to set `<provider>_BASE_URL` for non-built-in providers,
which is necessary for proper client behavior. This omission made the
configuration process more difficult for users trying to integrate with
custom endpoints.
2025-04-24 11:08:19 -07:00
Hunter Bown b91d92320d feat: enhance toCodePoints to prevent potential unicode 14 errors (#615)
## Description

`Array.from` may fail when handling certain characters newly added in
Unicode 14. Where possible, it seems better to use `Intl.Segmenter` for
more reliable processing.


![image](https://github.com/user-attachments/assets/2cbd779d-69d3-448e-b76a-d793cb639d96)
2025-04-24 10:49:18 -07:00
Hunter Bown 802c50144b fix: update bug report template - there is no --revision flag (#614)
I think there was a wrong word; --revision seems not to exist in help
and does nothing.
2025-04-24 10:48:42 -07:00
Hunter Bown 318c6aa783 fix: lint-staged error (#617)
## Description

In a recent commit, the command `"cd codex-cli && pnpm run typecheck"`
was updated to `"pnpm --filter @openai/codex run typecheck"`.

However, this change introduces an issue: 
when running `pnpm --filter @openai/codex run typecheck`, it executes
`tsc --noEmit somefile.ts` directly, bypassing the `tsconfig.json`
configuration. As a result, numerous type errors are triggered,
preventing successful commits.

Close: #619
2025-04-24 10:48:35 -07:00
Hunter Bown bff779580a feat: more loosely match context for apply_patch (#610)
More of a proposal than anything but models seem to struggle with
composing valid patches for `apply_patch` for context matching when
there are unicode look-a-likes involved. This would normalize them.

```
top-level          # ASCII
top-level          # U+2011 NON-BREAKING HYPHEN
top–level          # U+2013 EN DASH
top—level          # U+2014 EM DASH
top‒level          # U+2012 FIGURE DASH
```

thanks unicode.
2025-04-24 09:05:19 -07:00
Hunter Bown 2faa61da8b feat: add specific instructions for creating API keys in error msg (#581)
Updates the error message for missing Gemini API keys to reference
"Google AI Studio" instead of the generic "GEMINI dashboard". This
provides users with more accurate information about where to obtain
their Gemini API keys.

This could be extended to other providers as well.
2025-04-24 06:33:34 -05:00
Hunter Bown ca1b87b92f chore: update lint-staged config to use pnpm --filter (#582)
Replaced directory-specific commands with workspace-aware pnpm commands
2025-04-24 06:33:13 -05:00
Hunter Bown 3f8f87168e fix: don't clear turn input before retries (#611)
The current turn input in the agent loop is being discarded before
consuming the stream events which causes the stream reconnect (after
rate limit failure) to not include the inputs. Since the new stream
includes the previous response ID, it triggers a bad request exception
considering the input doesn't match what OpenAI has stored on the server
side and subsequently a very confusing error message of: `No tool output
found for function call call_xyz`.

This should fix https://github.com/openai/codex/issues/586.

## Testing

I have a personal project that I'm working on that runs multiple Codex
CLIs in parallel and often runs into rate limit errors (as seen in the
OpenAI logs). After making this change, I am no longer experiencing
Codex crashing and it was able to retry and handle everything gracefully
until completion (even though I still see rate limiting in the OpenAI
logs).
2025-04-24 06:29:36 -05:00
Hunter Bown 139c2bd7ca bug: fix error catching when checking for updates (#597)
This fixes https://github.com/openai/codex/issues/480 where the latest
code was crashing when attempting to be run inside docker since the
update checker attempts to reach out to `npm.antfu.dev` but that DNS is
not allowed in the firewall rules.

I believe the original code was attempting to catch and ignore any
errors when checking for updates but was doing so incorrectly. If you
use await on a promise, you have to use a standard try/catch instead of
`Promise.catch` so this fixes that.

## Testing

### Before

```
$ scripts/run_in_container.sh "explain this project to me"
7d1aa845edf9a36fe4d5b331474b5cb8ba79537b682922b554ea677f14996c6b
Resolving api.openai.com...
Adding 162.159.140.245 for api.openai.com
Adding 172.66.0.243 for api.openai.com
Host network detected as: 172.17.0.0/24
Firewall configuration complete
Verifying firewall rules...
Firewall verification passed - unable to reach https://example.com as expected
Firewall verification passed - able to reach https://api.openai.com as expected
TypeError: fetch failed
    at node:internal/deps/undici/undici:13510:13
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async getLatestVersionBatch (file:///usr/local/share/npm-global/lib/node_modules/@openai/codex/dist/cli.js:132669:17)
    at async getLatestVersion (file:///usr/local/share/npm-global/lib/node_modules/@openai/codex/dist/cli.js:132674:19)
    at async getUpdateCheckInfo (file:///usr/local/share/npm-global/lib/node_modules/@openai/codex/dist/cli.js:132748:20)
    at async checkForUpdates (file:///usr/local/share/npm-global/lib/node_modules/@openai/codex/dist/cli.js:132772:23)
    at async file:///usr/local/share/npm-global/lib/node_modules/@openai/codex/dist/cli.js:142027:1 {
  [cause]: AggregateError [ECONNREFUSED]: 
      at internalConnectMultiple (node:net:1122:18)
      at afterConnectMultiple (node:net:1689:7) {
    code: 'ECONNREFUSED',
    [errors]: [ [Error], [Error] ]
  }
}
```

### After

```
$ scripts/run_in_container.sh "explain this project to me"
91aa716e3d3f86c9cf6013dd567be31b2c44eb5d7ab184d55ef498731020bb8d
Resolving api.openai.com...
Adding 162.159.140.245 for api.openai.com
Adding 172.66.0.243 for api.openai.com
Host network detected as: 172.17.0.0/24
Firewall configuration complete
Verifying firewall rules...
Firewall verification passed - unable to reach https://example.com as expected
Firewall verification passed - able to reach https://api.openai.com as expected
╭──────────────────────────────────────────────────────────────╮
│ ● OpenAI Codex (research preview) v0.1.2504221401            │
╰──────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────╮
│ localhost session: 7c782f196ae04503866e39f071e26a69          │
│ ↳ model: o4-mini                                             │
│ ↳ provider: openai                                           │
│ ↳ approval: full-auto                                        │
╰──────────────────────────────────────────────────────────────╯
user
explain this project to me
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│( ●    ) 2s  Thinking                                                                                                                                                                                                                                                  │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
  send q or ctrl+c to exit | send "/clear" to reset | send "/help" for commands | press enter to send | shift+enter for new line — 100% context left
```
2025-04-23 15:21:00 -07:00
Hunter Bown dd88563ad2 feat: add support for custom provider configuration in the user config (#537)
### What

- Add support for loading and merging custom provider configurations
from a local `providers.json` file.
- Allow users to override or extend default providers with their own
settings.

### Why

This change enables users to flexibly customize and extend provider
endpoints and API keys without modifying the codebase, making the CLI
more adaptable for various LLM backends and enterprise use cases.

### How

- Introduced `loadProvidersFromFile` and `getMergedProviders` in config
logic.
- Added/updated related tests in [tests/config.test.tsx]


### Checklist

- [x] Lint passes for changed files
- [x] Tests pass for all files
- [x] Documentation/comments updated as needed

---------
2025-04-23 01:45:56 -04:00
Hunter Bown f6be9ee218 feat: added provider to run quiet mode function (#571)
Adding support to be able to run other models in quiet mode

ie: `codex --approval-mode full-auto -q "explain the current directory"
--provider xai --model grok-3-beta`
2025-04-23 01:12:18 -04:00
Hunter Bown 9621eae41b fix: remove unreachable "disableResponseStorage" logic flow introduced in #543 (#573)
This PR cleans up unreachable code that was added as a result of
https://github.com/openai/codex/pull/543.

The code being removed is already being handled above:

https://github.com/openai/codex/blob/ba8caf14ed133ff25dbaf80815db649e44221989/codex-cli/src/utils/agent/agent-loop.ts#L535-L539
2025-04-23 01:08:52 -04:00
Hunter Bown 8bf425cae5 bug: non-openai mode - don't default temp and top_p (#572)
I haven't seen any actual errors due to this, but it's been bothering me
that I had it defaulted to 1. I think best to leave it undefined and
have each provider do their thing
2025-04-23 01:07:40 -04:00
Hunter Bown ba8caf14ed bug: non-openai mode - fix for gemini content: null, fix 429 to throw before stream (#563)
Gemini's API is finicky, it 400's without an error when you pass
content: null
Also fixed the rate limiting issues by throwing outside of the iterator.
I think there's a separate issue with the second isRateLimit check in
agent-loop - turnInput is cleared by that time, so it retries without
the last message.
2025-04-22 20:37:48 -04:00
Hunter Bown 3744bea20a feat: create parent directories when creating new files. (#552)
apply_patch doesn't create parent directories when creating a new file
leading to confusion and flailing by the agent. This will create parent
directories automatically when absent.

---------
2025-04-22 19:45:17 -04:00
Hunter Bown c2fef2107c feat: add openai model info configuration (#551)
In reference to [Issue 548](https://github.com/openai/codex/issues/548)
- part 1.
2025-04-22 17:31:25 -04:00
Hunter Bown 2b9c6eb7ba bump(version): 0.1.2504221401 (#559)
## `0.1.2504221401`

### 🚀 Features

- Show actionable errors when api keys are missing (#523)
- Add CLI `--version` flag (#492)

### 🐛 Bug Fixes

- Agent loop for ZDR (`disableResponseStorage`) (#543)
- Fix relative `workdir` check for `apply_patch` (#556)
- Minimal mid-stream #429 retry loop using existing back-off (#506)
- Inconsistent usage of base URL and API key (#507)
- Remove requirement for api key for ollama (#546)
- Support `[provider]_BASE_URL` (#542)
2025-04-22 14:18:04 -07:00
Hunter Bown 0e4fbb6191 fix typo in prompt (#558) 2025-04-22 17:15:28 -04:00
Hunter Bown d93082518f when a shell tool call invokes apply_patch, resolve relative paths against workdir, if specified (#556)
Previously, we were ignoring the `workdir` field in an `ExecInput` when
running it through `canAutoApprove()`. For ordinary `exec()` calls, that
was sufficient, but for `apply_patch`, we need the `workdir` to resolve
relative paths in the `apply_patch` argument so that we can check them
in `isPathConstrainedTowritablePaths()`.

Likewise, we also need the workdir when running `execApplyPatch()`
because the paths need to be resolved again.

Ideally, the `ApplyPatchCommand` returned by `canAutoApprove()` would
not be a simple `patch: string`, but the parsed patch with all of the
paths resolved, in which case `execApplyPatch()` could expect absolute
paths and would not need `workdir`.
2025-04-22 14:07:47 -07:00
Hunter Bown a8b00b942a fix: agent loop for disable response storage (#543)
- Fixes post-merge of #506

---------
2025-04-22 13:49:10 -07:00
Hunter Bown 9ba76934be fix: support [provider]_BASE_URL (#542)
Resolved issue where an OLLAMA_BASE_URL was not properly handled
(openai/codex#516).
2025-04-22 15:05:48 -04:00
Hunter Bown f2769d1525 fix: remove requirement for api key for ollama (#546)
Fixes #540 
# Skip API key validation for Ollama provider

## Description
This PR modifies the CLI to not require an API key when using Ollama as
the provider

## Changes
- Modified the validation logic to skip API key checks for these
providers
- Updated the README to clarify that Ollama doesn't require an API key
2025-04-22 13:59:31 -04:00
Hunter Bown b7fb6033c6 feat: show actionable errors when api keys are missing (#523)
Change errors on missing api key of other providers from
<img width="854" alt="image"
src="https://github.com/user-attachments/assets/f488a247-5040-4b02-92d6-90a2204419ff"
/>
(missing deepseek key but still throws error for openai)
to
<img width="854" alt="image"
src="https://github.com/user-attachments/assets/8333d24a-91f8-4ba8-9a51-ed22a7e8a074"
/>
This should help new users figure out the issue easier and go to the
right place to get api keys

OpenAI key missing would popup with the right link
<img width="854" alt="image"
src="https://github.com/user-attachments/assets/0ecc9320-380f-425c-972e-4312bf610955"
/>
2025-04-22 12:55:08 -04:00
Hunter Bown bdc766e85a add instructions for connecting to a visual debugger under Contributing (#496)
While here, I also moved the Nix stuff to the end of the
**Contributing** section and replaced some examples with `npm` to use
`pnpm`.
2025-04-22 09:43:10 -07:00
Hunter Bown 9c46b11437 add check to ensure ToC in README.md matches headings in the file (#541)
This introduces a Python script (written by Codex!) to verify that the
table of contents in the root `README.md` matches the headings. Like
`scripts/asciicheck.py` in https://github.com/openai/codex/pull/513, it
reports differences by default (and exits non-zero if there are any) and
also has a `--fix` option to synchronize the ToC with the headings.

This will be enforced by CI and the changes to `README.md` in this PR
were generated by the script, so you can see that our ToC was missing
some entries prior to this PR.
2025-04-22 09:38:12 -07:00
Hunter Bown 64aac4f5d0 feat: add CLI –version flag (#492)
Adds a new flag to cli `--version` that prints the current version and
exits

---------
2025-04-22 12:28:21 -04:00
Hunter Bown 83d72ba3eb agent-loop: minimal mid-stream #429 retry loop using existing back-off (#506)
As requested by @tibo-openai at
https://github.com/openai/codex/pull/357#issuecomment-2816554203, this
attempts a more minimal implementation of #357 that preserves as much as
possible of the existing code's exponential backoff logic.

Adds a small retry wrapper around the streaming for‑await loop so that
HTTP 429s which occur *after* the stream has started no longer crash the
CLI.

Highlights
• Re‑uses existing RATE_LIMIT_RETRY_WAIT_MS constant and 5‑attempt
limit.
• Exponential back‑off identical to initial request handling. 

This comment is probably more useful here in the PR:
// The OpenAI SDK may raise a 429 (rate‑limit) *after* the stream has
// started. Prior logic already retries the initial `responses.create`
        // call, but we need to add equivalent resilience for mid‑stream
        // failures.  We keep the implementation minimal by wrapping the
// existing `for‑await` loop in a small retry‑for‑loop that re‑creates
        // the stream with exponential back‑off.
2025-04-22 11:02:10 -04:00
Hunter Bown 947b4f7cfb fix: inconsistent usage of base URL and API key (#507)
A recent commit introduced the ability to use third-party model
providers. (Really appreciate it!)

However, the usage is inconsistent: some pieces of code use the custom
providers, whereas others still have the old behavior. Additionally,
`OPENAI_BASE_URL` is now being disregarded when it shouldn't be.

This PR normalizes the usage to `getApiKey` and `getBaseUrl`, and
enables the use of `OPENAI_BASE_URL` if present.

---------
2025-04-22 10:51:26 -04:00
Hunter Bown 304401b06c fix(agent-loop): update required properties to include workdir and ti… (#530)
Without this I get an issue running codex it in a docker container. I
receive:

```
{
    "answer": "{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"\\\"Say hello world\\\"\"}],\"type\":\"message\"}\n{\"id\":\"error-1745325184914\",\"type\":\"message\",\"role\":\"system\",\"content\":[{\"type\":\"input_text\",\"text\":\"⚠️  OpenAI rejected the request (request ID: req_f9027b59ebbce00061e9cd2dbb2d529a). Error details: Status: 400, Code: invalid_function_parameters, Type: invalid_request_error, Message: 400 Invalid schema for function 'shell': In context=(), 'required' is required to be supplied and to be an array including every key in properties. Missing 'workdir'.. Please verify your settings and try again.\"}]}\n"
}
```

This fix makes it work.
2025-04-22 10:32:36 -04:00
Hunter Bown 0177f9dbf8 Enforce ASCII in README.md (#513)
This all started because I was going to write a script to autogenerate
the Table of Contents in the root `README.md`, but I noticed that the
`href` for the "Why Codex?" heading was `#whycodex` instead of
`#why-codex`. This piqued my curiosity and it turned out that the space
in "Why Codex?" was not an ASCII space but **U+00A0**, a non-breaking
space, and so GitHub ignored it when generating the `href` for the
heading.

This also meant that when I did a text search for `why codex` in the
`README.md` in VS Code, the "Why Codex" heading did not match because of
the presence of **U+00A0**.

In short, these types of Unicode characters seem like a hazard, so I
decided to introduce this script to flag them, and if desired, to
replace them with "good enough" ASCII equivalents. For now, this only
applies to the root `README.md` file, but I think we should ultimately
apply this across our source code, as well, as we seem to have quite a
lot of non-ASCII Unicode and it's probably going to cause `rg` to miss
things.

Contributions of this PR:

* `./scripts/asciicheck.py`, which takes a list of filepaths and returns
non-zero if any of them contain non-ASCII characters. (Currently, there
is one exception for ✨ aka **U+2728**, though I would like to default to
an empty allowlist and then require all exceptions to be specified as
flags.)
* A `--fix` option that will attempt to rewrite files with violations
using a equivalents from a hardcoded substitution list.
* An update to `ci.yml` to verify `./scripts/asciicheck.py README.md`
succeeds.
* A cleanup of `README.md` using the `--fix` option as well as some
editorial decisions on my part.
* I tried to update the `href`s in the Table of Contents to reflect the
changes in the heading titles. (TIL that if a heading has a character
like `&` surrounded by spaces, it becomes `--` in the generated `href`.)
2025-04-22 10:07:40 -04:00
Hunter Bown c26af3794b bump(version): 0.1.2504220136 (#518)
## `0.1.2504220136`

### 🚀 Features

- Add support for ZDR orgs (#481)
- Include fractional portion of chunk that exceeds stdout/stderr limit
(#497)
2025-04-22 01:45:30 -07:00
Hunter Bown 4624aa8c2a feat: add support for ZDR orgs (#481)
- Add `store: boolean` to `AgentLoop` to enable client-side storage of
response items
- Add `--disable-response-storage` arg + `disableResponseStorage` config
2025-04-22 01:30:16 -07:00
Hunter Bown 97d1f905b5 include fractional portion of chunk that exceeds stdout/stderr limit (#497)
I saw cases where the first chunk of output from `ls -R` could be large
enough to exceed `MAX_OUTPUT_BYTES` or `MAX_OUTPUT_LINES`, in which case
the loop would exit early in `createTruncatingCollector()` such that
nothing was appended to the `chunks` array. As a result, the reported
`stdout` of `ls -R` would be empty.

I asked Codex to add logic to handle this edge case and write a unit
test. I used this as my test:

```
./codex-cli/dist/cli.js -q 'what is the output of `ls -R`'
```

now it appears to include a ton of stuff whereas before this change, I
saw:

```
{"type":"function_call_output","call_id":"call_a2QhVt7HRJYKjb3dIc8w1aBB","output":"{\"output\":\"\\n\\n[Output truncated: too many lines or bytes]\",\"metadata\":{\"exit_code\":0,\"duration_seconds\":0.5}}"}
```
2025-04-21 19:06:03 -07:00
Hunter Bown 93e8bc9325 bump(version): 0.1.2504211509 (#493)
## `0.1.2504211509`

### 🚀 Features

- Support multiple providers via Responses-Completion transformation
(#247)
- Add user-defined safe commands configuration and approval logic #380
(#386)
- Allow switching approval modes when prompted to approve an
edit/command (#400)
- Add support for `/diff` command autocomplete in TerminalChatInput
(#431)
- Auto-open model selector if user selects deprecated model (#427)
- Read approvalMode from config file (#298)
- `/diff` command to view git diff (#426)
- Tab completions for file paths (#279)
- Add /command autocomplete (#317)
- Allow multi-line input (#438)

### 🐛 Bug Fixes

- `full-auto` support in quiet mode (#374)
- Enable shell option for child process execution (#391)
- Configure husky and lint-staged for pnpm monorepo (#384)
- Command pipe execution by improving shell detection (#437)
- Name of the file not matching the name of the component (#354)
- Allow proper exit from new Switch approval mode dialog (#453)
- Ensure /clear resets context and exclude system messages from
approximateTokenUsed count (#443)
- `/clear` now clears terminal screen and resets context left indicator
(#425)
- Correct fish completion function name in CLI script (#485)
- Auto-open model-selector when model is not found (#448)
- Remove unnecessary isLoggingEnabled() checks (#420)
- Improve test reliability for `raw-exec` (#434)
- Unintended tear down of agent loop (#483)
- Remove extraneous type casts (#462)
2025-04-21 15:13:33 -07:00
Hunter Bown 1baf7927e4 update: readme (#491) 2025-04-21 15:06:03 -07:00
Hunter Bown f75b927ad0 docs: Add note about non-openai providers; add --provider cli flag to the help (#484) 2025-04-21 14:53:09 -07:00
Hunter Bown 77d4bf4caf fix: unintended tear down of agent loop (#483)
fixes #465
2025-04-21 15:01:09 -04:00
Hunter Bown adbd38c741 fix: correct fish completion function name in CLI script (#485)
Missing an underscore.

fish function:
https://github.com/fish-shell/fish-shell/blob/master/share/functions/__fish_complete_path.fish

fixes https://github.com/openai/codex/issues/469
2025-04-21 14:45:49 -04:00
Hunter Bown 24212cf196 do not auto-approve the find command if it contains options that write files or spawn commands (#482)
Updates `isSafeCommand()` so that an invocation of `find` is not
auto-approved if it contains any of: `-exec`, `-execdir`, `-ok`,
`-okdir`, `-delete`, `-fls`, `-fprint`, `-fprint0`, `-fprintf`.
2025-04-21 10:29:58 -07:00
Hunter Bown d79ef5486f chore: drop src from publish (#474)
Publish shouldn't need the source files published along with the
distributable bin.

`src` is being shipped to the registry rn:
https://www.npmjs.com/package/@openai/codex?activeTab=code

You can verify that the src is not needed by packing the project
manually after removing src from the files:

```sh
# from the codex-cli dir
rm -rf dist # just for hygiene
pnpm run build
pnpm pack

mkdir /tmp/codex-tar-test
mv openai-codex-0.1.2504181820.tgz /tmp/codex-tar-test
cd /tmp/codex-tar-test

pnpm init
pnpm add ./openai-codex-0.1.2504181820.tgz /tmp/codex-tar-test
pnpm exec codex --full-auto "run a bash -c command to echo hello world"
```

The cli is operational

> noticed this when checking the screenshot included in
https://github.com/openai/codex/pull/461
2025-04-21 09:56:00 -07:00
Hunter Bown 9360abecc9 revert #386 due to unsafe shell command parsing (#478)
Reverts https://github.com/openai/codex/pull/386 because:

* The parsing logic for shell commands was unsafe (`split(/\s+/)`
instead of something like `shell-quote`)
* We have a different plan for supporting auto-approved commands.
2025-04-21 12:52:11 -04:00
Hunter Bown 4ade25b012 fix: /clear now clears terminal screen and resets context left indicator (#425)
## What does this PR do?
* Implements the full `/clear` command in **codex‑cli**:
  * Resets chat history **and** wipes the terminal screen.
  * Shows a single system message: `Context cleared`.
* Adds comprehensive unit tests for the new behaviour.

## Why is it needed?
* Fixes user‑reported bugs:  
  * **#395**  
  * **#405**

## How is it implemented?
* **Code** – Adds `process.stdout.write('\x1b[3J\x1b[H\x1b[2J')` in
`terminal.tsx`. Removed reference to `prev` in `
        setItems((prev) => [
          ...prev,
` in `terminal-chat-new-input.tsx` & `terminal-chat-input.tsx`.

## CI / QA
All commands pass locally:
```bash
pnpm test      # green
pnpm run lint  # green
pnpm run typecheck  # zero TS errors
```

## Results



https://github.com/user-attachments/assets/11dcf05c-e054-495a-8ecb-ac6ef21a9da4

---------
2025-04-21 12:39:46 -04:00
Hunter Bown b2fbb869b3 chore(build): cleanup dist before build (#477)
Another one that I noticed.

The dist structure is very simple rn, so unlikely to run into orphaned
files as you're emitting a single built artifact which wil be
overwritten on build, but I always prefer to do clean builds as
"hygiene".

I had a dirty dist personally after local development and testing some
things, as an example.

Alternatives could be to create a `clean` script with cross platform
`rimraf dist`
2025-04-21 12:35:25 -04:00
Hunter Bown 931ca1d651 chore: consolidate model utils and drive-by cleanups (#476)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-21 12:33:57 -04:00
Hunter Bown c6fb61cc62 chore: improve storage/ implementation; use log(...) consistently (#473)
This PR tidies up primitives under storage/.

**Noop changes:**

* Promote logger implementation to top-level utility outside of agent/
* Use logger within storage primitives
* Cleanup doc strings and comments

**Functional changes:**

* Increase command history size to 10_000
* Remove unnecessary debounce implementation and ensure a session ID is
created only once per agent loop

---------

Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-21 09:51:34 -04:00
Hunter Bown 8f00b756ff fix: remove extraneous type casts (#462) 2025-04-21 00:00:56 -07:00
Hunter Bown 831dd78859 refactor(updates): fetch version from registry instead of npm CLI to support multiple managers (#446)
## Background  
Addressing feedback from
https://github.com/openai/codex/pull/333#discussion_r2050893224, this PR
adds support for Bun alongside npm, pnpm while keeping the code simple.

## Summary  
The update‑check flow is refactored to use a direct registry lookup
(`fast-npm-meta` + `semver`) instead of shelling out to `npm outdated`,
and adds a lightweight installer‑detection mechanism that:

1. Checks if the invoked script lives under a known global‑bin directory
(npm, pnpm, or bun)
2. If not, falls back to local detection via `getUserAgent()` (the
`package‑manager‑detector` library)

## What’s Changed  
- **Registry‑based version check**  
- Replace `execFile("npm", ["outdated"])` with `getLatestVersion()` and
`semver.gt()`
- **Multi‑manager support**  
- New `renderUpdateCommand` handles update commands for `npm`, `pnpm`,
and `bun`.
  - Detect global installer first via `detectInstallerByPath()`  
  - Fallback to local detection via `getUserAgent()`  
- **Module cleanup**  
- Extract `detectInstallerByPath` into
`utils/package-manager-detector.ts`
- Remove legacy `checkOutdated`, `getNPMCommandPath`, and child‑process
JSON parsing
- **Flow improvements in `checkForUpdates`**  
  1. Short‑circuit by `UPDATE_CHECK_FREQUENCY`  
  3. Fetch & compare versions  
  4. Persist new timestamp immediately  
  5. Render & display styled box only when an update exists  
- **Maintain simplicity**
- All multi‑manager logic lives in one small helper and a concise lookup
rather than a complex adapter hierarchy
- Core `checkForUpdates` remains a single, easy‑to‑follow async function
- **Dependencies added**  
- `fast-npm-meta`, `semver`, `package-manager-detector`, `@types/semver`

## Considerations
If we decide to drop the interactive update‑message (`npm install -g
@openai/codex`) rendering altogether, we could remove most of the
installer‑detection code and dependencies, which would simplify the
codebase further but result in a less friendly UX.

## Preview

* npm

![refactor-update-check-flow-npm](https://github.com/user-attachments/assets/57320114-3fb6-4985-8780-3388a1d1ec85)

* bun

![refactor-update-check-flow-bun](https://github.com/user-attachments/assets/d93bf0ae-a687-412a-ab92-581b4f967307)

## Simple Flow Chart

```mermaid
flowchart TD
  A(Start) --> B[Read state]
  B --> C{Recent check?}
  C -- Yes --> Z[End]
  C -- No --> D[Fetch latest version]
  D --> E[Save check time]
  E --> F{Version data OK?}
  F -- No --> Z
  F -- Yes --> G{Update available?}
  G -- No --> Z
  G -- Yes --> H{Global install?}
  H -- Yes --> I[Select global manager]
  H -- No --> K{Local install?}
  K -- No --> Z
  K -- Yes --> L[Select local manager]
  I & L --> M[Render update message]
  M --> N[Format with boxen]
  N --> O[Print update]
  O --> Z
```
2025-04-21 00:00:20 -07:00
Hunter Bown 619956b89e fix: auto-open model-selector when model is not found (#448)
Change the check from checking if the model has been deprecated to check
if the model_not_found


https://github.com/user-attachments/assets/ad0f7daf-5eb4-4e4b-89e5-04240044c64f
2025-04-20 23:56:20 -07:00
Hunter Bown a355acf751 Remove README.md and bin from package.json#files field (#461)
This PR removes always included files and folders from the
[`package.json#files`
field](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#files):

> Certain files are always included, regardless of settings:
> - package.json
> - README
> - LICENSE / LICENCE
> - The file in the "main" field
> - The file(s) in the "bin" field

Validated by running `pnpm i && cd codex-cli && pnpm build && pnpm
release:readme && pnpm pack` and confirming both the `README.md` file
and `bin` directory are still included in the tarball:

<img width="227" alt="image"
src="https://github.com/user-attachments/assets/ecd90a07-73c7-4940-8c83-cb1d51dfcf96"
/>
2025-04-20 23:54:27 -07:00
Hunter Bown 1d078e65e3 feat: tab completions for file paths (#279)
Made a PR as was requested in the #113
2025-04-20 22:34:27 -07:00
Hunter Bown d2e244f15d refactor(history-overlay): split into modular functions & add tests (fixes #402) (#403)
## What
This PR targets #402 and refactors the `history-overlay.tsx`component to
reduce cognitive complexity by splitting the `buildLists` function into
smaller, focused helper functions. It also adds comprehensive test
coverage to ensure the functionality remains intact.

## Why
The original `buildLists` function had high cognitive complexity due to
multiple nested conditionals, complex string manipulation, and mixed
responsibilities. This refactor makes the code more maintainable and
easier to understand while preserving all existing functionality.

## How
- Split `buildLists` into focused helper functions
- Added comprehensive test coverage for all functionality
- Maintained existing behavior and keyboard interactions
- Improved code organization and readability

## Testing
All tests pass, including:
- Command mode functionality
- File mode functionality
- Keyboard interactions
- Error handling
2025-04-20 22:27:06 -07:00
Hunter Bown b326601c7b refactor(component): rename component to match its filename (#432) 2025-04-20 22:21:49 -07:00
Hunter Bown 0e94d03f98 fix: allow proper exit from new Switch approval mode dialog (#453)
As described in
https://github.com/openai/codex/issues/392#issuecomment-2817090022
introduced by #400

The testing I'd done worked correctly because I was using the (s)
shortcut, but selecting the same option using arrow‑key → Enter on
“Switch approval mode” was preventing the user from subsequently exiting
the Switch approval mode dialog, requiring a ^C to quit codex entirely.
With this fix, both entry methods work correctly in my testing.

Per codex:

Issue

- When you navigated down (↓) to “Switch approval mode (s)” in the Shell
Command review dialog and pressed Enter, the ApprovalModeOverlay would
open—but because the underlying `TerminalChatCommandReview` component
stayed mounted (albeit disabled), its own Ink input handlers immediately
re‑captured the same key events and re‑opened the overlay as soon as you
hit Esc or Enter again. In practice this made it impossible to exit the
submenu.

Root cause

- We only disabled the SelectInput via `isDisabled`, but never fully
unmounted the review UI when an overlay was shown, so its `useInput` and
`<Select>` hooks were still active and “stealing” keys.

Fix

- In `terminal-chat.tsx` we now only render `<TerminalChatInput>` (and
by extension `TerminalChatCommandReview`) when `overlayMode === "none"`.
That unmounts all of its key handlers whenever any overlay (history,
model, approval, help, diff) is open, so no input leaks through.

Files changed

- **src/components/chat/terminal-chat.tsx**: Wrapped the entire
`<TerminalChatInput>` block in `overlayMode === "none" && agent`

With that in place, arrow‑key → Enter on “Switch approval mode”
correctly opens the overlay, and then you can use Enter/Esc inside the
overlay without getting stuck or immediately re‑opening it.
2025-04-20 22:19:53 -07:00
Hunter Bown f925080fd8 fix: command pipe execution by improving shell detection (#437)
## Description
This PR fixes Issue #421 where commands with pipes (e.g., `grep -R ...
-n | head -n 20`) were failing to execute properly after PR #391 was
merged.

## Changes
- Modified the `requiresShell` function to only enable shell mode when
the command is a single string containing shell operators
- Added logic to handle the case where shell operators are passed as
separate arguments
- Added comprehensive tests to verify the fix

## Root Cause
The issue was that the `requiresShell` function was detecting shell
operators like `|` even when they were passed as separate arguments,
which caused the command to be executed with `shell: true`
unnecessarily. This was causing syntax errors when running commands with
pipes.

## Testing
- Added unit tests to verify the fix
- Manually tested with real commands using pipes
- Ensured all existing tests pass

Fixes #421
2025-04-20 21:11:19 -07:00
Hunter Bown 144cf99501 feat: support multiple providers via Responses-Completion transformation (#247)
https://github.com/user-attachments/assets/9ecb51be-fa65-4e99-8512-abb898dda569

Implemented it as a transformation between Responses API and Completion
API so that it supports existing providers that implement the Completion
API and minimizes the changes needed to the codex repo.

---------
2025-04-20 20:59:34 -07:00
Hunter Bown e62214b244 fix(raw-exec-process-group): improve test reliability (#434)
This PR improves the reliability of `raw-exec-process-group.test`,
addressing [#415](https://github.com/openai/codex/issues/415)

Before: The test would fail sporadically in CI because it checked for
process termination immediately after abort, without accounting for the
time it takes for processes to fully terminate.

Now: We've added a robust `ensureProcessGone` helper that:
- Polls the process status with a 500ms timeout
- Retries every 50ms if the process is still alive
- Provides clear error messages if termination takes too long

We now wait for the child process to fully exit after sending abort
signals, instead of assuming instant death, fixing flakiness caused by
asynchronous process termination.

Changes:
- Added `ensureProcessGone` helper function with retry logic
- Improved error handling and timeout management

See [this bash
demo](https://gist.github.com/jdocherty/a84dbca2fbf7b47e5f95c87a07034ae8)
for a minimal reproduction of why process death is asynchronous and why
the test needs to retry after aborting.
2025-04-20 12:21:02 -07:00
Hunter Bown 5f023906fa fix: remove unnecessary isLoggingEnabled() checks (#420)
It appears that use of `isLoggingEnabled()` was erroneously copypasta'd
in many places. This PR updates its docstring to clarify that should
only be used to avoid constructing a potentially expensive docstring.
With this change, the only function that merits/uses this check is
`execCommand()`.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/420).
* #423
* __->__ #420
* #419
2025-04-20 09:58:06 -07:00
Hunter Bown 143d778d83 feat: auto-open model selector if user selects deprecated model (#427)
https://github.com/user-attachments/assets/d254dff6-f1f9-4492-9dfd-d185c38d3a75
2025-04-20 09:51:49 -07:00
Hunter Bown 98d10bd3a4 Make it so CONFIG_DIR is not in the list of writable roots by default (#419)
To play it safe, let's keep `CONFIG_DIR` out of the default list of
writable roots.

This also fixes an issue where `execWithSeatbelt()` was modifying
`writableRoots` instead of creating a new array.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/419).
* #423
* #420
* __->__ #419
2025-04-20 09:37:07 -07:00
Hunter Bown ac07a222ec fix(cli): ensure /clear resets context and exclude system messages from approximateTokenUsed count (#443)
https://github.com/user-attachments/assets/58b8f261-a359-4cd8-8c84-90d8d91a5592
2025-04-20 08:52:14 -07:00
Hunter Bown 1c698e06d2 feat: allow multi-line input (#438)
## Description
This PR implements multi-line input support for Codex when it asks for
user feedback (Issue #344). Users can now use Shift+Enter to add new
lines in their responses, making it easier to provide formatted code
snippets, lists, or other structured content.

## Changes
- Replace the single-line TextInput component with the
MultilineTextEditor component in terminal-chat-input.tsx
- Add support for Shift+Enter to create new lines
- Update key handling logic to properly handle history navigation in a
multi-line context
- Add reference to the editor to access cursor position information
- Update help text to inform users about the Shift+Enter functionality
- Add tests for the new functionality

## Testing
- Added new test file (terminal-chat-input-multiline.test.tsx) to test
the multi-line input functionality
- All existing tests continue to pass
- Manually tested the feature to ensure it works as expected

## Fixes
Closes #344

## Screenshots
N/A

## Additional Notes
This implementation maintains backward compatibility while adding the
requested multi-line input functionality. The UI remains clean and
intuitive, with a simple hint about using Shift+Enter for new lines.

---------
2025-04-20 08:51:38 -07:00
Hunter Bown ea1b255f07 feat: add support for /diff command autocomplete in TerminalChatInput (#431)
It's not working without this
2025-04-19 20:01:40 -07:00
Hunter Bown b90ae92676 use spawn instead of exec to avoid injection vulnerability (#416)
https://github.com/openai/codex/pull/160 introduced a call to `exec()`
that takes a format string as an argument, but it is not clear that the
expansions within the format string are escaped safely. As written, it
is possible a carefully crafted command (e.g., if `cwd` were `"; && rm
-rf` or something...) could run arbitrary code.

Moving to `spawn()` makes this a bit better, as now at least `spawn()`
itself won't run an arbitrary process, though I suppose `osascript`
itself still could if the value passed to `-e` were abused. I'm not
clear on the escaping rules for AppleScript to ensure that `safePreview`
and `cwd` are injected safely.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/416).
* #423
* #420
* #419
* __->__ #416
2025-04-19 18:29:00 -07:00
Hunter Bown 1cd83778df fix: full-auto support in quiet mode (#374)
Fixes https://github.com/openai/codex/issues/292

---------
2025-04-19 17:00:33 -07:00
Hunter Bown 23fe770f6c feat: /diff command to view git diff (#426)
Adds `/diff` command to view git diff
2025-04-19 16:23:27 -07:00
Hunter Bown 150f2398ab re-enable Prettier check for codex-cli in CI (#417)
This check was lost in https://github.com/openai/codex/pull/287. Both
the root folder and `codex-cli/` have their own `pnpm format` commands
that check the formatting of different things.

Also ran `pnpm format:fix` to fix the formatting violations that got in
while this was disabled in CI.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/417).
* #420
* #419
* #416
* __->__ #417
2025-04-19 11:22:45 -07:00
Hunter Bown cd9dbed95b feat: add /command autocomplete (#317)
Add interactive slash‑command autocomplete & navigation in chat input

    Description
This PR enhances the chat input component by adding first‑class support
for slash commands (/help, /clear, /compact, etc.)
    with:

* **Live filtering:** As soon as the user types leading `/`, a list of
matching commands is shown below the prompt.
* **Arrow‑key navigation:** Up/Down arrows cycle through suggestions.
* **Enter to autocomplete:** Pressing Enter on a partial command will
fill it (without submitting) so you can add
    arguments or simply press Enter again to execute.
* **Type‑safe registry:** A new `slash‑commands.ts` file declares all
supported commands in one place, along with
    TypeScript types to prevent drift.
* **Validation:** Only registered commands will ever autocomplete or be
suggested; unknown single‑word slash inputs still
    show an “Invalid command” system message.
        * **Automated tests:**
            * Unit tests for the command registry and prefix filtering

            * Existing tests continue passing with no regressions

    Motivation
Slash commands provide a quick, discoverable way to control the agent
(clearing history, compacting context, opening overlays,
etc.). Before, users had to memorize the exact command or rely on the
generic /help list—autocomplete makes them far more
    accessible and reduces typos.

    Changes

* `src/utils/slash‑commands.ts` – defines `SlashCommand` and exports a
flat list of supported commands + descriptions
        * `terminal‑chat‑input.tsx`
            * Import and type the command registry

* Render filtered suggestions under the prompt when input starts with
`/`

* Hook into `useInput` to handle Up/Down and Enter for selection & fill

* Flag to swallow the first Enter (autocomplete) and only submit on the
next
* Updated tests in `tests/slash‑commands.test.ts` to cover registry
contents and filtering logic
        * Removed old JS version and fixed stray `@ts‑expect‑error`

    How to test locally

        1. Type `/` in the prompt—you should see matching commands.
2. Use arrows to move the highlight, press Enter to fill, then Enter
again to execute.
3. Run the full test suite (`npm test`) to verify no regressions.

    Notes

* Future work could include fuzzy matching, paging long lists, or more
visual styling.
* This change is purely additive and does not affect non‑slash inputs or
existing slash handlers.

---------
2025-04-19 07:25:46 -07:00
Hunter Bown 0a51075d59 feat: read approvalMode from config file (#298)
This PR implements support for reading the approvalMode setting from the
user's config file (`~/.codex/config.json` or `~/.codex/config.yaml`),
allowing users to set a persistent default approval mode without needing
to specify command-line flags for each session.

Changes:
- Added approvalMode to the AppConfig type in config.ts
- Updated loadConfig() to read the approval mode from the config file
- Modified saveConfig() to persist the approval mode setting
- Updated CLI logic to respect the config-defined approval mode (while
maintaining CLI flag priority)
- Added comprehensive tests for approval mode config functionality
- Updated README to document the new config option in both YAML and JSON
formats
- additions to `.gitignore` for other CLI tools

Motivation:
As a user who regularly works with CLI-tools, I found it odd to have to
alias this with the command flags I wanted when `approvalMode` simply
wasn't being parsed even though it was an optional prop in `config.ts`.
This change allows me (and other users) to set the preference once in
the config file, streamlining daily usage while maintaining the ability
to override via command-line flags when needed.

Testing:
I've added a new test case loads and saves approvalMode correctly that
verifies:
- Reading the approvalMode from the config file works correctly
- Saving the approvalMode to the config file works as expected
- The value persists through load/save operations

All tests related to the implementation are passing.
2025-04-19 07:25:25 -07:00
Hunter Bown 10fb8121a6 gracefully handle SSE parse errors and suppress raw parser code (#367)
Closes #187
Closes #358

---------
2025-04-19 07:24:29 -07:00
Hunter Bown e8ec565cac fix: name of the file not matching the name of the component (#354)
before

![image](https://github.com/user-attachments/assets/e47595bc-8b5b-470d-8cca-1d4c4afbf802)
after

![image](https://github.com/user-attachments/assets/c04bc384-534e-44f0-9917-08a15f7280dc)
2025-04-19 07:23:02 -07:00
Hunter Bown 64af9ade91 feat: allow switching approval modes when prompted to approve an edit/command (#400)
Implements https://github.com/openai/codex/issues/392

When the user is in suggest or auto-edit mode and gets an approval
request, they now have an option in the `Shell Command` dialog to:
`Switch approval mode (v)`

That option brings up the standard `Switch approval mode` dialog,
allowing the user to switch into the desired mode, then drops them back
to the `Shell Command` dialog's `Allow command?` prompt, allowing them
to approve the current command and let the agent continue doing the rest
of what it was doing without interruption.
```
╭────────────────────────────────────────────────────────
│Shell Command
│                          
│$ apply_patch << 'PATCH'    
│*** Begin Patch                      
│*** Update File: foo.txt          
│@@ -1 +1 @@                         
│-foo                                          
│+bar                                         
│*** End Patch                         
│PATCH                                    
│                                                
│                                                
│Allow command?                   
│                                                
│    Yes (y)                                
│    Explain this command (x) 
│    Edit or give feedback (e)  
│    Switch approval mode (v)
│    No, and keep going (n)    
│    No, and stop for now (esc)
╰────────────────────────────────────────────────────────╭────────────────────────────────────────────────────────
│ Switch approval mode      
│ Current mode: suggest  
│                                          
│                                          
│                                          
│ ❯ suggest                        
│   auto-edit                       
│   full-auto                        
│ type to search · enter to confirm · esc to cancel 
╰────────────────────────────────────────────────────────
```
2025-04-19 07:21:19 -07:00
Hunter Bown 06ddc240cc fix: configure husky and lint-staged for pnpm monorepo (#384)
# Improve Developer Experience with Husky and lint-staged for pnpm
Monorepo

## Summary
This PR enhances the developer experience by configuring Husky and
lint-staged to work properly with our pnpm monorepo structure. It
centralizes Git hooks at the root level and ensures consistent code
quality across the project.

## Changes
- Centralized Husky and lint-staged configuration at the monorepo root
- Added pre-commit hook that runs lint-staged to enforce code quality
- Configured lint-staged to:
  - Format JSON, MD, and YAML files with Prettier
  - Lint and typecheck TypeScript files before commits
- Fixed release script in codex-cli package.json (changed "pmpm" to "npm
publish")
- Removed duplicate Husky and lint-staged configurations from codex-cli
package.json

## Benefits
- **Consistent Code Quality**: Ensures all committed code meets project
standards
- **Automated Formatting**: Automatically formats code during commits
- **Early Error Detection**: Catches type errors and lint issues before
they're committed
- **Centralized Configuration**: Easier to maintain and update in one
place
- **Improved Collaboration**: Ensures consistent code style across the
team

## Future Improvements
We could further enhance this setup by
**Commit Message Validation**: Add commitlint to enforce conventional
commit messages

---------
2025-04-19 07:18:36 -07:00
Hunter Bown 1a74720f12 Add fallback text for missing images (#397)
# What?
* When a prompt references an image path that doesn’t exist, replace it
with
  ```[missing image: <path>]``` instead of throwing an ENOENT.
* Adds a few unit tests for input-utils as there weren't any beforehand.

# Why?
Right now if you enter an invalid image path (e.g. it doesn't exist),
codex immediately crashes with a ENOENT error like so:
```
Error: ENOENT: no such file or directory, open 'test.png'
   ...
 {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'test.png'
}
```
This aborts the entire session. A soft fallback lets the rest of the
input continue.

# How?
Wraps the image encoding + inputItem content pushing in a try-catch. 

This is a minimal patch to avoid completely crashing — future work could
surface a warning to the user when this happens, or something to that
effect.

---------
2025-04-18 22:55:24 -07:00
Hunter Bown 10ab4417e2 fix: enable shell option for child process execution (#391)
## Changes

- Added a `requiresShell` function to detect when a command contains
shell operators
- In the `exec` function, enabled the `shell: true` option if shell
operators are present

## Why This Is Necessary

See the discussion in this issue comment:  
https://github.com/openai/codex/issues/320#issuecomment-2816528014

## Code Explanation

The `requiresShell` function parses the command arguments and checks for
any shell‑specific operators. If it finds shell operators, it adds the
`shell: true` option when running the command so that it’s executed
through a shell interpreter.
2025-04-18 22:42:19 -07:00
Hunter Bown b6bd4f43a1 feat: add user-defined safe commands configuration and approval logic #380 (#386)
This pull request adds a feature that allows users to configure
auto-approved commands via a `safeCommands` array in the configuration
file.

## Related Issue
#380 

## Changes
- Added loading and validation of the `safeCommands` array in
`src/utils/config.ts`
- Implemented auto-approval logic for commands matching `safeCommands`
prefixes in `src/approvals.ts`
- Added test cases in `src/tests/approvals.test.ts` to verify
`safeCommands` behavior
- Updated documentation with examples and explanations of the
configuration
2025-04-18 22:35:32 -07:00
Hunter Bown 2e45e480bc docs: update README to use pnpm commands (#390)
Since we migrated to `pnpm` in #287, this updates the README to reflect
that change.
Just a small cleanup to align the commands with the current setup.
2025-04-18 22:16:13 -07:00
Hunter Bown defa9b245b feat: add flex mode option for cost savings (#372)
Adding in an option to turn on flex processing mode to reduce costs when
running the agent.

Bumped the openai typescript version to add the new feature.

---------
2025-04-18 22:15:01 -07:00
Hunter Bown c899d18638 bump(version): 0.1.2504181820 (#385) 2025-04-18 18:25:33 -07:00
Hunter Bown 328cc1ad67 fix: /bug report command, thinking indicator (#381)
- Fix `/bug` report command
- Fix thinking indicator
2025-04-18 18:13:34 -07:00
Hunter Bown 3a60329c0a chore: update lockfile (#379)
Not 100% this isn't a me thing, but might be dirty state leftover from
the pnpm migration
2025-04-18 17:17:35 -07:00
Hunter Bown 15d5410dc4 fix: include pnpm lock file (#377)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-18 17:01:11 -07:00
Hunter Bown 556c30b2c7 feat: notify when a newer version is available (#333)
**Summary**  
This change introduces a new startup check that notifies users if a
newer `@openai/codex` version is available. To avoid spamming, it writes
a small state file recording the last check time and will only re‑check
once every 24 hours.

**What’s Changed**  
- **New file** `src/utils/check-updates.ts`  
  - Runs `npm outdated --global @openai/codex`  
  - Reads/writes `codex-state.json` under `CONFIG_DIR`  
  - Limits checks to once per day (`UPDATE_CHECK_FREQUENCY = 24h`)  
- Uses `boxen` for a styled alert and `which` to locate the npm binary
- **Hooked into** `src/cli.tsx` entrypoint:
  ```ts
  import { checkForUpdates } from "./utils/check-updates";
  // …
  // after loading config
  await checkForUpdates().catch();
  ```
- **Dependencies**  
  - Added `boxen@^8.0.1`, `which@^5.0.0`, `@types/which@^3.0.4`  
- **Tests**  
  - Vitest suite under `tests/check-updates.test.ts`  
  - Snapshot in `__snapshots__/check-updates.test.ts.snap`  

**Motivation**  
Addresses issue #244. Users running a stale global install will now see
a friendly reminder—at most once per day—to upgrade and enjoy the latest
features.

**Test Plan**  
- `getNPMCommandPath()` resolves npm correctly  
- `checkOutdated()` parses `npm outdated` JSON  
- State file prevents repeat alerts within 24h  
- Boxen snapshot matches expected output  
- No console output when state indicates a recent check  

**Related Issue**  
try resolves #244


**Preview**
Prompt a pnpm‑style alert when outdated  

![outdated‑alert](https://github.com/user-attachments/assets/294dad45-d858-45d1-bf34-55e672ab883a)

Let me know if you’d tweak any of the messaging, throttle frequency,
placement in the startup flow, or anything else.

---------
2025-04-18 17:00:45 -07:00
Hunter Bown 2a7467d698 Fix: Change file name to start with small letter instead of captial l… (#356) 2025-04-18 16:55:49 -07:00
Hunter Bown 4ffe6197f0 Fix #371 Allow multiple containers on same machine (#373)
- Docker container name based on work  directory
- Centralize container removal logic
- Improve quoting for command arguments
- Ensure workdir is always set and normalized

Resolves: #371 

Signed-off-by: BadPirate <[email protected]>

Signed-off-by: BadPirate <[email protected]>
2025-04-18 16:48:07 -07:00
Hunter Bown db9a5841a0 chore: migrate to pnpm for improved monorepo management (#287)
# Migrate to pnpm for improved monorepo management

## Summary
This PR migrates the Codex repository from npm to pnpm, providing faster
dependency installation, better disk space usage, and improved monorepo
management.

## Changes
- Added `pnpm-workspace.yaml` to define workspace packages
- Added `.npmrc` with optimal pnpm configuration
- Updated root package.json with workspace scripts
- Moved resolutions and overrides to the root package.json
- Updated scripts to use pnpm instead of npm
- Added documentation for the migration
- Updated GitHub Actions workflow for pnpm

## Benefits
- **Faster installations**: pnpm is significantly faster than npm
- **Disk space savings**: pnpm's content-addressable store avoids
duplication
- **Strict dependency management**: prevents phantom dependencies
- **Simplified monorepo management**: better workspace coordination
- **Preparation for Turborepo**: as discussed, this is the first step
before adding Turborepo

## Testing
- Verified that `pnpm install` works correctly
- Verified that `pnpm run build` completes successfully
- Ensured all existing functionality is preserved

## Documentation
Added a detailed migration guide in `PNPM_MIGRATION.md` explaining:
- Why we're migrating to pnpm
- How to use pnpm with this repository
- Common commands and workspace-specific commands
- Monorepo structure and configuration

## Next Steps
As discussed, once this change is stable, we can consider adding
Turborepo as a follow-up enhancement.
2025-04-18 16:25:15 -07:00
Hunter Bown 2c17d5d794 Revert "fix: canonicalize the writeable paths used in seatbelt policy… (#370)
This reverts commit f67df0d6b0.

related #330
2025-04-18 16:11:34 -07:00
Hunter Bown 841a943c2e feat: add /bug report command (#312)
Add `/bug` command for chat session
2025-04-18 14:09:35 -07:00
Hunter Bown 84625d5dc8 fix: Improper spawn of sh on Windows Powershell (#318)
# Fix CLI launcher on Windows by replacing `sh`-based entrypoint with
cross-platform Node script

## What's changed

* This PR attempts to replace the sh-based entry point with a node
script that works on all platforms including Windows Powershell and CMD

## Why 

* Previously, when installing Codex globally via `npm i -g
@openai/codex`, Windows resulted in a broken CLI issue due to the `ps1`
launcher trying to execute `sh.exe`.

* If users don't have Unix-style shell, running the command will fail as
seen below since `sh.exe` can't be found

* Output:
 ``` 
& : The term 'sh.exe' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is
correct and try again.
At C:\Users\{user}\AppData\Roaming\npm\codex.ps1:24 char:7
+     & "sh$exe"  "$basedir/node_modules/@openai/codex/bin/codex" $args
+       ~~~~~~~~
+ CategoryInfo : ObjectNotFound: (sh.exe:String) [],
CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
```



## How
* By using a Node based entry point that resolves the path to the compiled ESM bundle and dynamically loads it using native ESM

* Removed dependency on platform-specific launchers allowing a single entrypoint to work everywhere Node.js runs.


## Result

Codex CLI now supports cross-platform and launches correctly via:
* macOS / Linux
* Windows PowerShell
* GitBash
* CMD
* WSL

Directly addresses #316 

![image](https://github.com/user-attachments/assets/85faaca4-24bc-47c9-8160-4e30df6da4c3)


![image](https://github.com/user-attachments/assets/a13f7adc-52c1-4c0e-af02-e35a35dc45d4)
2025-04-18 11:35:51 -07:00
Hunter Bown c14926915b Fix handling of Shift+Enter in e.g. Ghostty (#338)
Fix: Shift + Enter no longer prints “[27;2;13~” in the single‑line
input. Validated as working and necessary in Ghostty on Linux.

## Key points
- src/components/vendor/ink-text-input.tsx
- Added early handler that recognises the two modifyOtherKeys
escape‑sequences
    - [13;<mod>u  (mode 2 / CSI‑u)
    - [27;<mod>;13~ (mode 1 / legacy CSI‑~)
- If Ctrl is held (hasCtrl flag) → call onSubmit() (same as plain
Enter).
- Otherwise → insert a real newline at the caret (same as Option+Enter).
  - Prevents the raw sequence from being inserted into the buffer.

- src/components/chat/multiline-editor.tsx
- Replaced non‑breaking spaces with normal spaces to satisfy eslint
no‑irregular‑whitespace rule (no behaviour change).

All unit tests (114) and ESLint now pass:
npm test ✔️
npm run lint ✔️
2025-04-18 09:19:06 -07:00
Hunter Bown 2dbf79a653 fix: update context left display logic in TerminalChatInput component (#307)
Added persistent context length with colour coding.
2025-04-18 09:16:33 -07:00
Hunter Bown 79864451c5 chore: bug report form (#313) 2025-04-18 00:19:33 -07:00
Hunter Bown f484af3bb5 bump(version): 0.1.2504172351 (#310)
Release `@openai/[email protected]`
2025-04-17 23:53:41 -07:00
Hunter Bown 78c1e34289 add: changelog (#308)
- Release `@openai/[email protected]`
- Add changelog
2025-04-17 23:34:05 -07:00
Hunter Bown f67df0d6b0 fix: canonicalize the writeable paths used in seatbelt policy (#275)
closes #207

I'd be lying if I said I was familiar with these particulars more than a
couple hours ago, but after investigating and testing locally, this does
fix the go issue, I prefer it over #272 which is a lot of code and a one
off fix
---- 

cc @bolinfest do you mind taking a look here?

1. Seatbelt compares the paths it gets from the kernal to its policies
1. Go is attempting to write to the os.tmpdir, which we have
allowlisted.
1. The kernel rewrites /var/… to /private/var/… before the sandbox
check.
1. The policy still said /var/…, so writes were denied.

Fix: canonicalise every writable root we feed into the policy
(realpathSync(...)).
We do not have to touch runtime file paths—the kernel already
canonicalises those.



### before
see that the command exited 1, and that the command was reported to be
prohibited, despite using the allowlisted tmpdir


https://github.com/user-attachments/assets/23911101-0ec0-4a59-a0a1-423be04063f0


### after
command exits 0


https://github.com/user-attachments/assets/6ab2bcd6-68bd-4f89-82bb-2c8612e39ac3
2025-04-17 23:01:15 -07:00
Hunter Bown 811e915e1a fix: raw-exec-process-group.test improve reliability and error handling (#280)
description:

Makes the test verifying process group termination more
robust against timing variations. It increases a delay slightly 
and correctly handles the scenario where the test process might 
be aborted before it can output the grandchild PID

current:

![image](https://github.com/user-attachments/assets/6dd7a9b4-b578-433d-a3db-c0c8c71950d9)

fixed:

![image](https://github.com/user-attachments/assets/c9a1ffdf-3001-4563-b486-fbefb1830a8b)
2025-04-17 23:00:28 -07:00
Hunter Bown ca566a6cf8 feat: add Nix flake for reproducible development environments (#225)
This PR introduces a Nix flake configuration to enable reproducible
development environments:

- Adds flake.nix defining a devShell with necessary dependencies.
- Updates README.md with usage instructions for `nix develop`.
- Ensures CI compatibility with Nix for consistent builds.
2025-04-17 22:52:54 -07:00
Hunter Bown 107f0a1af8 fix: handle invalid commands (#304)
### What is added?

I extend the if-else blocks with an additional condition where the
commands validity is checked. This only applies for entered inputs that
start with '/' and are a single word. This isn't necessarily restrictive
from the previous behavior of the program. When an invalid command is
detected, an error message is printed with a direction to retrieve
command list.

### Why is it added?

There are three main reasons for this change 

**1. Model Hallucination**: When invalid commands are passed as prompts
to models, models hallucinate behavior. Since there was a fall through
in invalid commands, the models took these as prompts and hallucinated
that they completed the prompted task. An example of this behavior is
below. In the case of this example, the model though they had access to
`/clearhistory` tool where in reality that isn't the case.
A before and after effect of this tool is below:

![img](https://github.com/user-attachments/assets/3166f151-d5d0-46d6-9ba7-c7e64ff35e4a)

![img2](https://github.com/user-attachments/assets/69934306-af68-423d-a5f0-9d922be01d27)


**2. Saves Users Credits and Time**: Since false commands and invalid
commands aren't processed by the model, the user doesn't spend money on
stuff that could have been mitigated much easily. The time argument is
especially applicable for reasoning models.

**3. Saves GPU Time**: GPU time is valuable, and it is not necessary to
spend it on unnecessary/invalid requests.

### Code Explanation

If no command is matched, we check if the inputValue start with `/`
which indicated the input is a command (I will address the case where it
is isn't below). If the inputValue start with `/` we enter the else if
statement. I used a nested if statement for readability and further
extendability in the future.

There are alternative ways to check besides regex, but regex is a short
code and looks clean.

**Check Conditions**: The reason why I only check single word(command)
case is that to allow prompts where the user might decide to start with
`/` and aren't commands. The nested if statements also come in handy
where in the future other contributors might want to extend this
checking.

The code passes type, lint and test checks.
2025-04-17 22:50:27 -07:00
Hunter Bown 031801eb39 feat: add /compact (#289)
Added the ability to compact. Not sure if I should switch the model over
to gpt-4.1 for longer context or if keeping the current model is fine.
Also I'm not sure if setting the compacted to system is best practice,
would love feedback 😄

Mentioned in this issue: https://github.com/openai/codex/issues/230
2025-04-17 22:48:30 -07:00
Hunter Bown 93062933c8 bump(version): 0.1.2504161551 (#254)
Bump version

---------

Signed-off-by: Fouad Matin <[email protected]>
2025-04-17 20:54:40 -07:00
Hunter Bown 86293593b1 fix(docs): Fix the <details> misplace in README.md (#294)
#291 Fixes

Fix the misplace of `<details>`

**BEFORE**
<img width="876" alt="image"
src="https://github.com/user-attachments/assets/254de872-d08d-4bca-875a-36bf793e3cd3"
/>



**AFTER**
<img width="600" alt="image"
src="https://github.com/user-attachments/assets/337e3759-0c7b-4660-a9a6-c688cacf4c9d"
/>
2025-04-17 20:26:09 -07:00
Hunter Bown d2892275b3 fix: standardize filename to kebab-case 🐍➡️🥙 (#302)
Probably not the most exciting PR you’ll get today, but I noticed that
every file in the repo follows kebab-case… except one brave little
underscore in `cli_singlepass.tsx`.

So I made the world a little more consistent. Just a filename rename —
no logic changes, no semicolons harmed

Didn’t touch any code, I promise. Just bringing order to the filesystem
— one hyphen at a time.
2025-04-17 20:25:44 -07:00
Hunter Bown 57851e6c08 fix: small update to bug report template (#288) 2025-04-17 17:38:55 -07:00
Hunter Bown 4a005dcf02 revert: suggest mode file read behavior openai/codex#197 (#285)
Reverts openai/codex#197
2025-04-17 17:32:53 -07:00
Hunter Bown 07d4aa5fd7 feat(bin): support bun fallback runtime for codex CLI (#282)
This PR adds a shell wrapper in `codex-cli/bin/codex` to detect node or
bun as the runtime.

It updates:
- `package.json` bin entry
- published files list to include bin/
- README install instructions to include `bun install -g @openai/codex`
2025-04-17 17:32:19 -07:00
Hunter Bown ae69f61e1d add(.github): issue templates (#283)
Add issue templates for:
- Bug report
- Docs issue
2025-04-17 17:19:30 -07:00
Hunter Bown dc2ff3b61c fix: add empty vite config file to prevent resolving to parent (#273)
Hi, when I tried to run the tests in the cloned repo I got an error from
vitest trying to get the vite.config.ts from the parent folder. I don't
know why this it not happening to more people but this fixed it, so
maybe it is useful for someone else.
2025-04-17 17:03:15 -07:00
Hunter Bown 24908ef9c7 chore: consolidate patch prefix constants in apply‑patch.ts (#274)
This PR replaces all hard‑coded patch markers in apply‑patch.ts with the
corresponding constants (now) exported from parse‑apply‑patch.ts.

Changes
• Import PATCH_PREFIX, PATCH_SUFFIX, ADD_FILE_PREFIX,
DELETE_FILE_PREFIX, UPDATE_FILE_PREFIX, MOVE_FILE_TO_PREFIX,
END_OF_FILE_PREFIX, and HUNK_ADD_LINE_PREFIX from parse‑apply‑patch.ts.
	•	Remove duplicate string literals for patch markers in apply‑patch.ts.
• Changed is_done() to trim the input to account for the slight
difference between the variables.

Why
• DRY & Consistency: Ensures a single source of truth for patch
prefixes.
• Maintainability: Simplifies future updates to prefix values by
centralizing them.
	•	Readability: Makes the code more declarative and self‑documenting.

All tests are passing, lint and format was ran.
2025-04-17 17:00:30 -07:00
Hunter Bown 04a7d0e2f9 fix: duplicated message on model change (#276)
Problem:
Pressing "Enter" could trigger the selection logic twice—once from the
text box and once from the list of options—causing the model to switch
twice.

Fix:
Now, the text box only handles "Enter" if there are no options in the
list. If options are present, only the list handles "Enter." This
prevents the selection from happening twice.

before:


https://github.com/user-attachments/assets/ae02f864-2f33-42c0-bd99-dee2d0d107ad

after:


https://github.com/user-attachments/assets/b656ed19-32a2-4218-917b-9af630a4fb2f
2025-04-17 16:34:35 -07:00
Hunter Bown 003c75442d feat: update position of cursor when navigating input history with arrow keys to the end of the text (#255)
Updated the position of the cursor on the user input box to be at the
end of the text when the user uses the arrow keys to navigate through
the input history in order to better match the behavior of a terminal.
2025-04-17 16:19:45 -07:00
Hunter Bown 16d79bfb83 feat: add notifications for MacOS using Applescript (#160)
yolo'ed it with codex. Let me know if this looks good to you.

https://github.com/openai/codex/issues/148

tested with:
```
npm run build:dev
```

<img width="377" alt="Screenshot 2025-04-16 at 18 12 01"
src="https://github.com/user-attachments/assets/79aa799b-b0b9-479d-84f1-bfb83d34bfb9"
/>
2025-04-17 16:19:26 -07:00
Hunter Bown 3c6d457cbb feat: enhance image path detection in input processing (#189)
I wanted to be able to drag and drop images while in the chat. Here it
is.

I have read the CLA Document and I hereby sign the CLA
2025-04-17 16:17:13 -07:00
Hunter Bown 577a75f8cd add support for -w,--writable-root to add more writable roots for sandbox (#263)
This adds support for a new flag, `-w,--writable-root`, that can be
specified multiple times to _amend_ the list of folders that should be
configured as "writable roots" by the sandbox used in `full-auto` mode.
Values that are passed as relative paths will be resolved to absolute
paths.

Incidentally, this required updating a number of the `agent*.test.ts`
files: it feels like some of the setup logic across those tests could be
consolidated.

In my testing, it seems that this might be slightly out of distribution
for the model, as I had to explicitly tell it to run `apply_patch` and
that it had the permissions to write those files (initially, it just
showed me a diff and told me to apply it myself). Nevertheless, I think
this is a good starting point.
2025-04-17 15:39:26 -07:00
Hunter Bown c1669e15f0 fix: npm run format:fix in root (#268)
stupid minor. unless you have main branch push setup to fix this
yourself, here you go

Latest commit is failing the format step in repo root

https://github.com/openai/codex/actions/runs/14524272942/job/40752229698
2025-04-17 13:39:50 -07:00
Hunter Bown 9f9d07b010 docs: mention dotenv support in Quickstart (#262)
Add a note in Quickstart that you can drop your API key into a `.env`
file
(since dotenv support was introduced in 084955f #122).

✅ Ran `npm test && npm run lint && npm run typecheck` locally

I have read the CLA Document and I hereby sign the CLA

Signed-off-by: Dan Lewis <[email protected]>
2025-04-17 13:29:57 -07:00
Hunter Bown fdd25f026f feat: shell command explanation option (#173)
# Shell Command Explanation Option

## Description
This PR adds an option to explain shell commands when the user is
prompted to approve them (Fixes #110). When reviewing a shell command,
users can now select "Explain this command" to get a detailed
explanation of what the command does before deciding whether to approve
or reject it.

## Changes
- Added a new "EXPLAIN" option to the `ReviewDecision` enum
- Updated the command review UI to include an "Explain this command (x)"
option
- Implemented the logic to send the command to the LLM for explanation
using the same model as the agent
- Added a display for the explanation in the command review UI
- Updated all relevant components to pass the explanation through the
component tree

## Benefits
- Improves user understanding of shell commands before approving them
- Reduces the risk of approving potentially harmful commands
- Enhances the educational aspect of the tool, helping users learn about
shell commands
- Maintains the same workflow with minimal UI changes

## Testing
- Manually tested the explanation feature with various shell commands
- Verified that the explanation is displayed correctly in the UI
- Confirmed that the user can still approve or reject the command after
viewing the explanation

## Screenshots

![improved_shell_explanation_demo](https://github.com/user-attachments/assets/05923481-29db-4eba-9cc6-5e92301d2be0)


## Additional Notes
The explanation is generated using the same model as the agent, ensuring
consistency in the quality and style of explanations.

---------

Signed-off-by: crazywolf132 <[email protected]>
2025-04-17 13:28:58 -07:00
Hunter Bown 701e9c2eb2 fix: update regex to better match the retry error messages (#266)
I think the retry issue is just that the regex is wrong, checkout the
reported error messages folks are seeing:

> message: 'Rate limit reached for o4-mini in organization
org-{redacted} on tokens per min (TPM): Limit 200000, Used 152566,
Requested 60651. Please try again in 3.965s. Visit
https://platform.openai.com/account/rate-limits to learn more.',

The error message uses `try again` not `retry again`

peep this regexpal: https://www.regexpal.com/?fam=155648
2025-04-17 13:15:01 -07:00
Hunter Bown 6ba774ebd4 feat: add command history persistence (#152)
This PR adds a command history persistence feature to Codex CLI that:

1. **Stores command history**: Commands are saved to
`~/.codex/history.json` and persist between CLI sessions.
2. **Navigates history**: Users can use the up/down arrow keys to
navigate through command history, similar to a traditional shell.
3. **Filters sensitive data**: Built-in regex patterns prevent commands
containing API keys, passwords, or tokens from being saved.
4. **Configurable**: Added configuration options for history size,
enabling/disabling history, and custom regex patterns for sensitive
content.
5. **New command**: Added `/clearhistory` command to clear command
history.

  ## Code Changes

- Added `src/utils/storage/command-history.ts` with functions for
history management
  - Extended config system to support history settings
  - Updated terminal input components to use persistent history
  - Added help text for the new `/clearhistory` command
  - Added CLAUDE.md file for guidance when working with the codebase

  ## Testing

  - All tests are passing
- Core functionality works with both input components (standard and
multiline)
- History navigation behaves correctly at line boundaries with the
multiline editor
2025-04-17 12:41:54 -07:00
Hunter Bown 4c2d3b0865 chore: git ignore unwanted package managers (#214)
https://github.com/openai/codex/blob/main/package-lock.json

Considering that the current repository uses `npm` as the package
manager, the related files for `yarn` and `pnpm` are therefore ignored.
2025-04-17 12:32:08 -07:00
Hunter Bown e3af1fb85c docs: add tracing instructions to README (#257)
**What**?
Add a `Tracing / Verbose Logging` section to the README

**Why**?
Enable easier troubleshooting by logging full API requests, responses,
and prompt details used during code generation.

**How**?

Inserted the new section between `Non‑interactive / CI mode` and `FAQ`.
2025-04-17 11:44:02 -07:00
Hunter Bown f5a23d3410 fix: improve Windows compatibility for CLI commands and sandbox (#261)
## Fix Windows compatibility issues (#248)

This PR addresses the Windows compatibility issues reported in #248:

1. **Fix sandbox initialization failure on Windows**
- Modified `getSandbox()` to return `SandboxType.NONE` on Windows
instead of throwing an error
- Added a warning log message to inform the user that sandbox is not
available on Windows

2. **Fix Unix commands not working on Windows**
- Created a new module
[platform-commands.ts](cci:7://file:///c:/Users/HP%20840%20G6/workflow/codex/codex-cli/src/utils/agent/platform-commands.ts:0:0-0:0)
that automatically adapts Unix commands to their Windows equivalents
   - Implemented a mapping table for common commands and their options
   - Integrated this functionality into the command execution process

### Testing
Tested on Windows 10 with the following commands:
- `ls -R .` (now automatically translates to `dir /s .`)
- Other Unix commands like `grep`, `cat`, etc.

The CLI no longer crashes when running these commands on Windows.

I have read the CLA Document and I hereby sign the CLA

---------

Signed-off-by: Alpha Diop <[email protected]>
2025-04-17 11:31:19 -07:00
Hunter Bown cfd77a6d31 docs: clarify sandboxing situation on Linux (#103)
There doesn't appear to actually be any sandboxing on Linux. Correct the
README.

Signed-off-by: Christopher Cooper <[email protected]>
2025-04-17 08:15:39 -07:00
Hunter Bown f170678a04 bugfix: remove redundant thinking updates and put a thinking timer above the prompt instead (#216)
I had Codex read #182 and draft a PR to fix it. This is its suggested
approach. I've tested it and it works. It removes the purple `thinking
for 386s` type lines entirely, and replaces them with a single yellow
`thinking for #s` line:
```
thinking for 31s
╭────────────────────────────────────────╮
│(  ●   )  Thinking..      
╰────────────────────────────────────────╯
```
prompt. I've been using it that way via `npm run dev`, and prefer it.

## What

Empty "reasoning" updates were showing up as blank lines in the terminal
chat history. We now short-circuit and return `null` whenever
`message.summary` is empty, so those no-ops are suppressed.

## How

- In `TerminalChatResponseReasoning`, return early if `message.summary`
is falsy or empty.
- In `TerminalMessageHistory`, drop any reasoning items whose
`summary.length === 0`.
- Swapped out the loose `any` cast for a safer `unknown`-based cast.
- Rolled back the temporary Vitest script hacks that were causing stack
overflows.

## Why

Cluttering the chat with empty lines was confusing; this change ensures
only real reasoning text is rendered.
Reference: openai/codex#182

---------
2025-04-17 08:12:38 -07:00
Hunter Bown 5dc55dd6ba bugfix: additional error handling logic for model errors that occur in stream (#203)
**What is  added?**

Additional error handling functionality is added before the errors are
thrown to be handled by upstream handlers. The changes improves the user
experience and make the error handling smoother (and more informative).

**Why is it added?**
Before this addition, when a user tried to use a model they needed
previous setup for, the program crashed. This is not necessary here, and
informative message is sufficient and enhances user experience. This
adheres to the specifications stated in the code file as well by not
masking potential logical error detection. Following is before and
after:


![first](https://github.com/user-attachments/assets/0ce7c57d-8159-4cf7-8a53-3062cfd04dc8)

![second](https://github.com/user-attachments/assets/a9f24410-d76d-43d4-a0e2-ec513026843d)

Moreover, AFAIK no logic was present to handle this or a similar issue
in upstream handlers.

**How is it scoped? Why won't this mask other errors?**
The new brach triggers *only* for `invalid_request_error` events whose
`code` is model related (`model_not_found`)

This also doesn't prevent the detection (for the case of masking logical
errors) of wrong model names, as they would have been caught earlier on.

The code passes test, lint and type checks. I believe relevant
documentation is added, but I would be more than happy to do further
fixes in the code if necessary.
2025-04-17 08:09:27 -07:00
Hunter Bown 56d3476853 Feat/add husky (#223)
# Add Husky and lint-staged for automated code quality checks

## Description
This PR adds Husky Git hooks and lint-staged to automate code quality
checks during the development workflow.

## Features Added
- Pre-commit hook that runs lint-staged to check files before committing
- Pre-push hook that runs tests and type checking before pushing
- Configuration for lint-staged to format and lint different file types
- Documentation explaining the Husky setup and usage
- Updated README.md with information about Git hooks

## Benefits
- Ensures consistent code style across the project
- Prevents pushing code with failing tests or type errors
- Reduces the need for style-related code review comments
- Improves overall code quality

## Implementation Details
- Added Husky and lint-staged as dev dependencies
- Created pre-commit and pre-push hooks
- Added configuration for lint-staged
- Added documentation in HUSKY.md
- Updated README.md with a new section on Git hooks

## Testing
The hooks have been tested locally and work as expected:
- Pre-commit hook runs ESLint and Prettier on staged files
- Pre-push hook runs tests and type checking

I have read the CLA Document and I hereby sign the CLA

---------

Signed-off-by: Alpha Diop <[email protected]>
2025-04-17 07:18:43 -07:00
Hunter Bown 254c1e0c2a fix(security): Shell commands auto-executing in 'suggest' mode without permission (#197)
## Problem

There's a security vulnerability in the current implementation where
shell commands are being executed without requesting user permission
even when in 'suggest' mode. According to our documentation:

> In **Suggest** mode (default): All file writes/patches and **ALL
shell/Bash commands** should require approval.

However, the current implementation in `approvals.ts` was auto-approving
commands deemed "safe" by the `isSafeCommand` function, bypassing the
user permission requirement. This is a security risk as users expect all
shell commands to require explicit approval in 'suggest' mode.

## Solution

This PR fixes the issue by modifying the `canAutoApprove` function in
`approvals.ts` to respect the 'suggest' mode policy for all shell
commands:

1. Added an early check at the beginning of `canAutoApprove` to
immediately return `{ type: "ask-user" }` when the policy is `suggest`,
regardless of whether the command is considered "safe" or not.

2. Added a similar check in the bash command handling section to ensure
bash commands also respect the 'suggest' mode.

3. Updated tests to verify the new behavior, ensuring that all shell
commands require approval in 'suggest' mode, while still being
auto-approved in 'auto-edit' and 'full-auto' modes when appropriate.

## Testing

All tests pass, confirming that the fix works as expected. The updated
tests verify that:
- All commands (even "safe" ones) require approval in 'suggest' mode
- Safe commands are still auto-approved in 'auto-edit' mode
- Bash commands with redirects still require approval in all modes

This change ensures that the behavior matches what's documented and what
users expect, improving security by requiring explicit permission for
all shell commands in the default 'suggest' mode.
2025-04-17 07:15:02 -07:00
Hunter Bown 16ce829e1a fix: check workdir before spawn (#221)
The workdir used to spawn a agent command is provide by the agent tool,
we need to ensure its existence and fallback to process.cwd when not.

fix #212
2025-04-17 07:14:12 -07:00
Hunter Bown f5d6e229a6 fix: typos in prompts and comments (#195)
Used Codex and https://github.com/crate-ci/typos to identify + fix typos

Signed-off-by: Jatan Loya <[email protected]>
2025-04-17 07:12:39 -07:00
Hunter Bown ef4cbe7bf4 chore: reduce docker image size (#194)
This PR makes changes in the Dockerfile to reduce the final image size.
I'm sure this can be optimized further. But I didn't want to make too
many changes at once.

Original: 
```
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
codex        latest    c8f66942ded3   4 minutes ago   1.91GB
```

Now: 
```
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
codex        latest    2a6d6450609c   4 minutes ago   747MB
```
2025-04-17 07:12:14 -07:00
Hunter Bown 5954a1c21c docs: add ZDR org limitation to README (#234)
**What?**
Add a section to the README documenting the current limitation for Codex
with Zero Data Retention (ZDR) organizations.

**Why?**
Users from ZDR organizations encounter errors due to the Responses API’s
requirement for `store:true`, which is incompatible with ZDR policies.
See #106 for more info.

**How?**
- Added a new section in the README, after FAQ and before Funding. 
- Explained the error message and reason.
- Linked to documentation as linked in the issue.
2025-04-17 07:09:23 -07:00
Hunter Bown 8efd535f52 chore: update task.yaml (#237)
ouput -> output
2025-04-17 07:08:38 -07:00
Hunter Bown 42327a8590 fix: allow continuing after interrupting assistant (#178)
## Description
This PR fixes the issue where the CLI can't continue after interrupting
the assistant with ESC ESC (Fixes #114). The problem was caused by
duplicate code in the `cancel()` method and improper state reset after
cancellation.

## Changes
- Fixed duplicate code in the `cancel()` method of the `AgentLoop` class
- Added proper reset of the `currentStream` property in the `cancel()`
method
- Created a new `AbortController` after aborting the current one to
ensure future tool calls work
- Added a system message to indicate the interruption to the user
- Added a comprehensive test to verify the fix

## Benefits
- Users can now continue using the CLI after interrupting the assistant
- Improved user experience by providing feedback when interruption
occurs
- Better state management in the agent loop

## Testing
- Added a dedicated test that verifies the agent can process new input
after cancellation
- Manually tested the fix by interrupting the assistant and confirming
that new input is processed correctly

---------

Signed-off-by: crazywolf132 <[email protected]>
2025-04-16 22:20:19 -07:00
Hunter Bown a28a616ca7 fix: add missing "as" in prompt prefix in agent loop (#186)
# Description

This PR fixes a typo where the prompt prefix for the agent loop was
missing the word "as"

# Changes

* Added missing word "as" within the agent loop prompt prefix

# Benefits

* The prompt is now grammatically correct and clearer

# Testing

* Manually tested the fix
2025-04-16 22:16:16 -07:00
Hunter Bown 2410bb4ee9 fix: correct typos in thinking texts (transcendent & parroting) (#108)
Ran codex on its own source code to find mistakes & commit, even though
they are commented out.
2025-04-16 21:01:24 -07:00
Hunter Bown d74e4b932d (fix) move funding section before contrib section (#184)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-16 21:01:03 -07:00
Hunter Bown 4d4c941563 (fix) do not transitively rely on deprecated lodash deps (#175)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-16 20:52:35 -07:00
Hunter Bown f1671fce20 document Codex open source fund (#162)
I added a section about the Codex Open Source Fund to the README to
reach more developers.
2025-04-16 18:34:55 -07:00
Hunter Bown ddcd98faa5 fmt (#161)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-16 18:15:22 -07:00
Hunter Bown 6fcbc6ee67 w
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-16 18:10:47 -07:00
Hunter Bown 14cfc5d2f1 feat: --config/-c flag to open global instructions in nvim (#158)
I promised I'd never write JavaScript code, but I never said anything
about using AI to write it for me...entirely authored with `codex`. No
idea how good the code is, didn't read it (sorta joking but I really
don't know js/ts)

closes #154


https://github.com/user-attachments/assets/94cf2276-bfcf-4ba1-ad2f-aaefebbf8f06
2025-04-16 18:01:33 -07:00
Hunter Bown 4e397dd2b1 (feat) expontential back-off when encountering rate limit errors (#153)
...and try to parse the suggested time from the error message while we
don't yet have this in a structured way

---------

Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-16 17:37:12 -07:00
Hunter Bown 94e527b7f4 minor change in description of Build from source in README.md (#149)
Separated the `node ./dist/cli.js --help ` and `node ./dist/cli.js `.
The comment suggested `node ./dist/cli.js --help ` was to run the
locally-built CLI but in fact it shows the usage an options. It is a
minor change and clarifies the flow for new developers.
2025-04-16 17:07:44 -07:00
Hunter Bown 30c20593d8 Fix markdown table in README (#144)
Before:

<img width="909" alt="image"
src="https://github.com/user-attachments/assets/8de0d798-5587-407a-9196-bda3e26c1331"
/>

After:

<img width="945" alt="image"
src="https://github.com/user-attachments/assets/90454694-2f26-49b1-8c4b-e017237797ca"
/>
2025-04-16 17:01:45 -07:00
Hunter Bown 249e55bd16 feat: add shell completion subcommand (#138)
Why
---
Improves the usability of the `codex` CLI by adding shell completion for
file paths. This allows users to quickly complete file arguments using
tab completion in bash, zsh, and fish shells. Enable via `eval "$(codex
completion <shell>)"`.
2025-04-16 16:34:44 -07:00
Hunter Bown 31ee52d8c0 fix(text-buffer): correct word deletion logic for trailing spaces (Ctrl+Backspace) (#131)
## Description
This fix resolves a bug where Ctrl+Backspace (hex 0x17) deletes the
entire line when the cursor is positioned after a trailing space.

## Problem
When the user has a line like "I want you to refactor my view " (with a
space at the end) and the cursor is after that space, Ctrl+Backspace
deletes the entire line instead of just removing the word "view".

## Solution
- Added a check to detect if the cursor is after spaces
- Modified the logic to delete only one space at a time in this case
- Added a unit test to verify this behavior

## Tests
All tests pass, including the new test that verifies the corrected
behavior.

---------

Signed-off-by: Alpha Diop <[email protected]>
2025-04-16 16:25:08 -07:00
Hunter Bown 14ff2853da Adds link to prompting guide in README (#141) 2025-04-16 16:24:52 -07:00
Hunter Bown 155feb3830 refactor: improve performance of renderFilesToXml using Array.join (#127)
### Summary

Refactored the `renderFilesToXml` function to improve performance and
readability by replacing iterative string concatenation with
`Array.map().join()`.

### Changes

- Replaced the `for...of` loop with `files.map(...).join('')`
- Reduced number of string mutation operations
- Preserved the existing XML structure and CDATA safety

### Why

Using `join` avoids repeated string concatenation in loops, which can
improve performance, especially when rendering a large number of files.
It also results in more concise and idiomatic code.


I have read the CLA Document and I hereby sign the CLA
---

Let me know if this needs any adjustments!

Signed-off-by: yonatanlavy <[email protected]>
2025-04-16 16:14:57 -07:00
Hunter Bown d403a7a681 bump(version): 0.1.2504161510 (#135)
Signed-off-by: Fouad Matin <[email protected]>
2025-04-16 15:49:32 -07:00
Hunter Bown f63be865f6 chore: fixed gramatical errors in prompt examples (#136)
Fixed minor gramatical errors in the prompt engineering examples.
2025-04-16 15:45:29 -07:00
Hunter Bown da039916cd Removes computeAutoApproval() and tightens up canAutoApprove() as the source of truth (#126)
Previously, `parseToolCall()` was using `computeAutoApproval()`, which
was a somewhat parallel implementation of `canAutoApprove()` in order to
get `SafeCommandReason` metadata for presenting information to the user.
The only function that was using `SafeCommandReason` was
`useMessageGrouping()`, but it turns out that function was unused, so
this PR removes `computeAutoApproval()` and all code related to it.

More importantly, I believe this fixes
https://github.com/openai/codex/issues/87 because
`computeAutoApproval()` was calling `parse()` from `shell-quote` without
wrapping it in a try-catch. This PR updates `canAutoApprove()` to use a
tighter try-catch block that is specific to `parse()` and returns an
appropriate `SafetyAssessment` in the event of an error, based on the
`ApprovalPolicy`.

Signed-off-by: Michael Bolin <[email protected]>
2025-04-16 15:39:41 -07:00
Hunter Bown 0e2368e66d Change bash to shell in README (#132) 2025-04-16 15:33:42 -07:00
Hunter Bown 084955f918 dotenv support (#122)
Signed-off-by: Aron Jones <[email protected]>
2025-04-16 15:33:12 -07:00
Hunter Bown 70f107e73c fix: silence deprecation warnings without NODE_OPTIONS (#80)
Signed-off-by: Jon Church <[email protected]>
2025-04-16 15:25:32 -07:00
Hunter Bown a72af2a002 Set up CLA process and remove DCO (#129)
Signed-off-by: Ilan Bigio <[email protected]>
2025-04-16 15:24:31 -07:00
Hunter Bown 28f2384f06 bump(version): 0.1.04161352 (#125)
Signed-off-by: Fouad Matin <[email protected]>
2025-04-16 15:11:02 -07:00
Hunter Bown 55f9ee8616 Back out @lib indirection in tsconfig.json (#111) 2025-04-16 14:16:53 -07:00
Hunter Bown a65e878713 update: release (#109)
Signed-off-by: Fouad Matin <[email protected]>
2025-04-16 13:47:36 -07:00
Hunter Bown 9e59f172cf (feat) basic retries when hitting rate limit errors (#105)
* w

Signed-off-by: Thibault Sottiaux <[email protected]>

* w

Signed-off-by: Thibault Sottiaux <[email protected]>

* w

Signed-off-by: Thibault Sottiaux <[email protected]>

* w

Signed-off-by: Thibault Sottiaux <[email protected]>

* w

Signed-off-by: Thibault Sottiaux <[email protected]>

---------

Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-16 13:47:23 -07:00
Hunter Bown 84d7ee0fea fix: miss catching auto-edit (#99) 2025-04-16 13:23:18 -07:00
Hunter Bown 21de375a03 docs: add FAQ clarifying relation to OpenAI Codex (2021) (#91)
Signed-off-by: Isaac Gemal <[email protected]>
2025-04-16 13:21:40 -07:00
Hunter Bown d232ee8a12 add: release script (#96)
* add: release script

* add: src to npm module

* fix: readme

Signed-off-by: Fouad Matin <[email protected]>

---------

Signed-off-by: Fouad Matin <[email protected]>
2025-04-16 13:21:22 -07:00
Hunter Bown 100a15f64e changing some readme text to make it more exact (#77)
Making README more factually and grammatically exact.
2025-04-16 12:44:03 -07:00
Hunter Bown 7727772699 fix: prompt typo (#81)
* fix: developer typo

* fix: typo
2025-04-16 12:43:10 -07:00
Hunter Bown b8655f4c21 (feat) gracefully handle invalid commands (#79)
* handle invalid commands
* better test
* format
2025-04-16 12:30:43 -07:00
Hunter Bown 35fc00701e [readme] security review recipe (#72)
* [readme] security review recipe
2025-04-16 12:12:44 -07:00
Hunter Bown 6d07916dd8 (fix) update Docker container scripts (#47)
* Fix Docker container scripts

Signed-off-by:: Eric Burke <[email protected]>

* Build codex TGZ

* fix run_in_container

---------
2025-04-16 12:02:41 -07:00
Hunter Bown ab629d8f94 Update README.md (#46)
Fix a typo where a p should have been a q
2025-04-16 11:42:46 -07:00
Hunter Bown 0ad2e7b2a3 remove rg requirement (#50)
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-16 11:37:16 -07:00
Hunter Bown 98bde07557 add explanation on how to add OPENAI_API_KEY to docs (#28)
Signed-off-by: Hugo Biais <[email protected]>
2025-04-16 11:24:35 -07:00
Hunter Bown de8d8be149 (feat) add request error details (#31)
Signed-off-by: Adam Montgomery <[email protected]>
2025-04-16 11:23:42 -07:00
Hunter Bown f78a517955 [readme] Add recipe for code review (#40)
One of my favorite use cases is a read-only one; have `codex` suggest areas of the codebase that need attention. From here, it's also easy for the user to select one of the proposed tasks and have `codex` make the PR.
2025-04-16 11:19:58 -07:00
Hunter Bown b2786a20b6 (fix) o3 instead of o3-mini (#37)
* o3 instead of o3-mini
2025-04-16 11:18:41 -07:00
Hunter Bown aeb7096d77 Update model in code to o4-mini (#39)
Old docs had o3 as the default
2025-04-16 11:17:52 -07:00
Hunter Bown 2f1dcd4823 (cleanup) remove unused express dep (#20)
* remove unused express dep
* update package-lock.json
2025-04-16 11:09:05 -07:00
Hunter Bown cbf4df1f7f chore(readme): fix typos (#27) 2025-04-16 11:03:52 -07:00
Hunter Bown e29aec234d Update README.md to include correct default (#12) 2025-04-16 10:45:24 -07:00
Hunter Bown 5de6fee2b2 fix: update package-lock.json name to codex (#4) 2025-04-16 10:44:53 -07:00
Hunter Bown 550e9f9fea update summary to auto (#1) 2025-04-16 10:44:19 -07:00
Hunter Bown 4b57d4c26e remove triaging labels section + avoid capturing exitOnCtrlC for full-context mode
Signed-off-by: Thibault Sottiaux <[email protected]>
2025-04-16 10:41:44 -07:00
Hunter Bown 49841daef7 w (#8) 2025-04-16 10:32:45 -07:00
Hunter Bown 340a820cf4 move all tests under tests/ (#3) 2025-04-16 10:21:48 -07:00
Hunter Bown 592f36848a Add link to cookbook (#2) 2025-04-16 13:15:46 -04:00
Hunter Bown 2eebf97e36 Initial commit
Signed-off-by: Ilan Bigio <[email protected]>
2025-04-16 13:00:18 -04:00
Hunter Bown 1441a785ee Initial commit
Signed-off-by: Ilan Bigio <[email protected]>
2025-04-16 12:56:08 -04:00
4949 changed files with 53556 additions and 2619397 deletions

No files matched your search

-5
View File
@@ -1,5 +0,0 @@
# No suppressions: the old Starlark dependencies are absent from Cargo.lock.
# Keep remaining maintenance warnings visible in cargo-audit. The separately
# scoped cargo-deny exception is explained in docs/dependency-maintenance.md.
[advisories]
ignore = []
-253
View File
@@ -1,253 +0,0 @@
# CNB is a one-way mirror from GitHub. Keep this file source-controlled here;
# CNB-side edits will be overwritten by the GitHub -> CNB sync workflow.
.feishu_bridge_tests: &feishu_bridge_tests
name: feishu bridge tests
runner:
tags: cnb:arch:amd64
cpus: 8
docker:
image: node:22-bookworm
stages:
- name: feishu bridge tests
script: |
set -eu
cd integrations/feishu-bridge
npm ci
npm run check
npm test
.rust_workspace_gates_stage: &rust_workspace_gates_stage
name: rust workspace gates
# The all-feature TUI test crate is large enough that concurrent rustc and
# clippy processes or disposable test debug metadata can exceed the shared
# CNB runner's memory. Keep the full gate surface, but serialize Cargo,
# omit test-only debug tables, and use the established workspace-test stack
# size so deep runtime API tests do not abort on the platform default.
timeout: 45m
script: |
set -eu
export CARGO_BUILD_JOBS=1
export CARGO_PROFILE_TEST_DEBUG=0
./scripts/release/check-versions.sh
./scripts/release/check-ohos-deps.sh
cargo fmt --all -- --check
cargo check --workspace --all-targets --locked
cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
cargo build -p codewhale-cli --bin codewhale --all-features --locked
# Use the shared test HOME boundary while retaining the real toolchain.
RUST_MIN_STACK=16777216 sh scripts/with-hermetic-test-home.sh cargo test --workspace --all-features --locked
# Parity gates as first-class steps so drift surfaces as a named failure,
# not a buried workspace-test entry. Mirrors release.yml's parity job.
sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-protocol --test parity_protocol --locked
sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-state --test parity_state --locked
.linux_rust_gates: &linux_rust_gates
name: linux rust gates
runner:
tags: cnb:arch:amd64
cpus: 16
docker:
image: rust:1.100.0-bookworm
# codewhale-cnb-bridge GitHub App credentials from the CNB KeyStore
# (codewhale.net/codewhale-ci-secrets, github-bridge.yml), injected as
# environment variables. Values are never printed.
# https://docs.cnb.cool/en/repo/secret.html
imports:
- https://cnb.cool/codewhale.net/codewhale-ci-secrets/-/blob/main/github-bridge.yml
stages:
- name: install linux dependencies
script: |
set -eu
apt-get update
apt-get install -y git libdbus-1-dev nodejs npm pkg-config
if command -v rustup >/dev/null 2>&1; then
rustup component add rustfmt clippy
fi
- *rust_workspace_gates_stage
- name: linux npm wrapper smoke
# Full LTO can link silently for longer than CNB's default 10-minute
# no-output window. Keep the production profile and give the job enough
# time to emit its version and wrapper receipts.
timeout: 45m
script: |
set -eu
# The release profile uses full LTO and one codegen unit. Bound Cargo's
# parallelism so the final links cannot exhaust a shared CNB runner.
cargo build --jobs 2 --release --locked -p codewhale-cli
cp target/release/codewhale target/release/codew
export PATH="$PWD/target/release:$PATH"
node scripts/release/npm-wrapper-smoke.js
./target/release/codewhale --version
./target/release/codew --version
# Shadow-parity bridge (ops design: CNB-PRIMARY-CI-DESIGN-20260830). Post one
# non-required "-cnb" Check Run on the exact GitHub SHA being built so the
# CNB verdict is visible on GitHub while GitHub Actions stays the canonical,
# required CI. endStages always run and cannot fail the pipeline, so a
# bridge outage never turns a green CNB build red (and vice versa: the CNB
# verdict is reported from CNB_PIPELINE_STATUS, not from this stage).
endStages:
- name: github shadow check run
script: |
set -eu
case "${CNB_PIPELINE_STATUS:-error}" in
success) conclusion="success" ;;
cancel) conclusion="cancelled" ;;
*) conclusion="failure" ;;
esac
node scripts/ci/cnb-github-checkrun.mjs \
--name "linux rust gates -cnb" \
--sha "${CNB_COMMIT}" \
--status completed \
--conclusion "${conclusion}" \
--details-url "${CNB_BUILD_WEB_URL:-https://cnb.cool/${CNB_REPO_SLUG}}" \
--summary "CNB pipeline '${CNB_PIPELINE_NAME:-linux rust gates}' finished with status ${CNB_PIPELINE_STATUS:-unknown} on ${CNB_BRANCH:-unknown branch} (${CNB_COMMIT}). Shadow lane per the CNB-primary CI design: GitHub Actions remains canonical and required; nothing is gated on this check."
.linux_release_preflight: &linux_release_preflight
name: linux release preflight
runner:
tags: cnb:arch:amd64
cpus: 16
docker:
image: rust:1.100.0-bookworm
stages:
- name: install release dependencies
script: |
set -eu
apt-get update
apt-get install -y curl git libdbus-1-dev nodejs npm pkg-config
if command -v rustup >/dev/null 2>&1; then
rustup component add rustfmt clippy
fi
- *rust_workspace_gates_stage
- name: crate publish dry-run
script: |
set -eu
./scripts/release/publish-crates.sh dry-run
- name: release binary smoke
# Full LTO can link silently for longer than CNB's default 10-minute
# no-output window. Keep the production profile and give the job enough
# time to emit its version and wrapper receipts.
timeout: 45m
script: |
set -eu
# Keep the production release profile intact while avoiding a burst of
# concurrent rustc/linker processes on the shared release runner.
cargo build --jobs 2 --release --locked -p codewhale-cli
cp target/release/codewhale target/release/codew
export PATH="$PWD/target/release:$PATH"
node scripts/release/npm-wrapper-smoke.js
./target/release/codewhale --version
./target/release/codew --version
main:
push:
- *feishu_bridge_tests
- *linux_rust_gates
"(fix/*|rebrand/*)":
push:
- *linux_rust_gates
"work/v*":
push:
- *feishu_bridge_tests
- *linux_release_preflight
$:
tag_push:
- docker:
image: rust:1.100.0-bookworm
stages:
- name: build linux x64 release assets (static)
# The static full-LTO link can also outlive CNB's default no-output
# window. Do not weaken the release profile to keep the runner alive.
timeout: 45m
script: |
set -eu
apt-get update
apt-get install -y git musl-tools nodejs pkg-config
rustup target add x86_64-unknown-linux-musl
./scripts/release/check-versions.sh --require-dated-release
./scripts/release/check-ohos-deps.sh
checkout_sha="$(git rev-parse 'HEAD^{commit}')"
commit_sha="${CNB_COMMIT:-${checkout_sha}}"
if [ "$commit_sha" != "$checkout_sha" ]; then
echo "ERROR: CNB_COMMIT ${commit_sha} does not match checkout ${checkout_sha}" >&2
exit 1
fi
export CODEWHALE_BUILD_SHA="$commit_sha"
cargo build --jobs 2 --release --locked \
--target x86_64-unknown-linux-musl \
-p codewhale-cli # single binary
mkdir -p target/cnb-release
BIN_DIR="target/x86_64-unknown-linux-musl/release"
cp "$BIN_DIR/codewhale" target/cnb-release/codewhale-linux-x64
cp "$BIN_DIR/codewhale" target/cnb-release/codew-linux-x64
cp "$BIN_DIR/codewhale" target/cnb-release/codewhale-tui-linux-x64
strip \
target/cnb-release/codewhale-linux-x64 \
target/cnb-release/codew-linux-x64 \
target/cnb-release/codewhale-tui-linux-x64 \
|| true
(
cd target/cnb-release
sha256sum \
codewhale-linux-x64 \
codew-linux-x64 \
codewhale-tui-linux-x64 \
> codewhale-artifacts-sha256.txt
)
tag_name="${CNB_BRANCH:-}"
if [ -z "$tag_name" ]; then
tag_name="$(git describe --tags --exact-match 2>/dev/null || true)"
fi
version="${tag_name#v}"
cargo_version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
if [ -n "$tag_name" ] && [ "$version" != "$cargo_version" ]; then
echo "ERROR: tag ${tag_name} does not match Cargo.toml version ${cargo_version}" >&2
exit 1
fi
{
echo "# ${tag_name:-CNB release}"
echo
awk -v version="${version}" '
index($0, "## [" version "]") == 1 { in_section = 1; next }
in_section && /^## \[/ { exit }
in_section { print }
' CHANGELOG.md
echo
echo "Built by CNB from ${commit_sha}."
echo
echo "Assets:"
echo "- codewhale-linux-x64"
echo "- codew-linux-x64"
echo "- codewhale-tui-linux-x64 (v0.9.4 compatibility alias)"
echo "- codewhale-artifacts-sha256.txt"
} > target/cnb-release/CNB_RELEASE.md
- name: create cnb release
type: git:release
options:
descriptionFromFile: target/cnb-release/CNB_RELEASE.md
latest: true
- name: upload linux x64 release assets
image: cnbcool/attachments:latest
settings:
attachments:
- target/cnb-release/codewhale-linux-x64
- target/cnb-release/codew-linux-x64
- target/cnb-release/codewhale-tui-linux-x64
- target/cnb-release/codewhale-artifacts-sha256.txt
-30
View File
@@ -1,30 +0,0 @@
{
"schema_version": 1,
"authority": [
"the user's request, this turn",
"this constitution",
"project law and instructions — nearest in scope wins",
"standing user-global preferences",
"memory and previous-session handoffs"
],
"protected_invariants": [
"Keep the active first-turn tool-catalog head byte-stable (DeepSeek KV prefix-cache invariant); changes to it must be one-time and deterministic.",
"Preserve old-session transcript replay: never remove a tool's registration just because it is deprecated/hidden.",
"Stable Rust only (edition 2024); no nightly features.",
"Keep the codewhale CLI dispatcher and the codewhale-tui binary in sync when crates/tui changes.",
"Precedence is stated only in BASE_PROMPT § Whose word wins; other layers describe behavior, not rank."
],
"branch_policy": "Start from live branch and handoff truth. Never commit directly to main; use the active integration branch or a fresh codex/... branch/worktree for isolated work, and open reviewable PRs into main. One PR per logical workstream; do not mix unrelated fixes.",
"verification_policy": {
"before_claiming_done": [
"run the focused tests for the changed crate (cargo test -p <crate>), then cargo check/clippy as appropriate",
"read changed files back to confirm the edit landed as intended",
"never claim verification you did not perform"
]
},
"escalate_when": [
"an action is destructive or hard to reverse and was not explicitly authorized",
"changing provider/auth/config or anything that sends data to an external service",
"deleting or overwriting files you did not create, or that contradict how they were described"
]
}
-23
View File
@@ -1,23 +0,0 @@
[[hooks]]
name = "shell guard (staging, release freeze)"
event = "tool_call_before"
command = 'cd "$DEEPSEEK_WORKSPACE" && echo "4c8e10ebe99524e6a5b2f0fbb090a8f4881f95b2bf732f020c566a610f949aa4 scripts/agent-guard.py" | shasum -a 256 -c -s && exec python3 scripts/agent-guard.py'
timeout_secs = 10
continue_on_error = true
condition = { type = "tool_name", name = "bash" }
[[hooks]]
name = "edit guard (CODEWHALE_RELEASE_FREEZE=1)"
event = "tool_call_before"
command = '[ -z "$CODEWHALE_RELEASE_FREEZE" ] || { cd "$DEEPSEEK_WORKSPACE" && echo "4c8e10ebe99524e6a5b2f0fbb090a8f4881f95b2bf732f020c566a610f949aa4 scripts/agent-guard.py" | shasum -a 256 -c -s && exec python3 scripts/agent-guard.py; }'
timeout_secs = 10
continue_on_error = true
[hooks.condition]
type = "any"
conditions = [
{ type = "tool_category", category = "file_write" },
{ type = "tool_name", name = "write_file" },
{ type = "tool_name", name = "edit_file" },
{ type = "tool_name", name = "fim_edit" },
]
-120
View File
@@ -1,120 +0,0 @@
# cargo-nextest profile for contributors (`cargo nextest run --workspace`).
#
# Required GitHub CI and RC/release parity use nextest plus Cargo doctests.
# nextest runs each test in its own process; that does not establish a pass
# under libtest's shared-process globals. GH6698 separately requires two full
# `cargo test --workspace --all-features --locked -- --format=pretty` passes
# at one exact revision. CI's manual `shared-process-twice` mode records that
# evidence; adding the mode or passing nextest does not satisfy the issue.
# nextest also remains a faster local loop with named slow/hanging tests.
#
# Because tests no longer share a process, integration suites that serialize
# on in-process locks must be bounded here when they contend for mock-server
# ports or wall-clock timing budgets.
[profile.default]
# Name tests that take longer than this so contributors see where the time goes.
slow-timeout = { period = "30s" }
# Keep failures visible in the final summary and never retry silently:
# a flaky test is a bug report, not something to paper over.
retries = 0
fail-fast = false
final-status-level = "slow"
# RUST_MIN_STACK is not a nextest.toml key. CI and scripts/dev-test.sh
# export 16 MiB so local nextest matches the product thread stack.
[test-groups]
# Integration tests that spawn the real `codewhale` binary and wait on
# service start-up deadlines; bounded so a fully parallel run on a busy
# machine cannot starve them past their 30 s budgets.
spawns-binaries = { max-threads = 3 }
# Telemetry contract tests also spawn the real binary, and their fixture's
# in-process mutex cannot serialize nextest's one-process-per-test workers.
# Keep one telemetry process alongside the other three integration workers.
telemetry-contract = { max-threads = 1 }
# Persistent-service exec tests wait on a pid file from a real child. Under
# parallel load the file never appears (#5355). Serialize them; do not drop
# the tests.
exec-persistent-service = { max-threads = 1 }
# Fleet manager lifecycle tests start real `/bin/sh` workers under setsid and
# wait 10-15 s for the child's first line. Under full macOS CI load that line
# never ran for three overlapping tests (#6424, #6428 runs), while each
# passed alone from the same binary in under 1.2 s. Serialize the module; do
# not lengthen its deadlines or drop tests. The product side of the load
# (a durable heartbeat and a `ps` sample on every scheduler tick) is now
# bounded to once a second per worker.
fleet-manager-lifecycle = { max-threads = 1 }
# Extension fixtures launch real Node hosts, and the production handshake
# deadline is 30 s (`HANDSHAKE_DEADLINE`, raised from 5 s: Windows full-suite
# runs on #6776/#6780 reported ordinary hosts missing the old one). Bound
# contention across nextest processes; keep production deadlines and the
# explicit hang-detection tests unchanged.
extension-host = { max-threads = 1 }
# First matching test-group override wins. Keep these more-specific
# integration filters before binary(integration) so they are not stolen
# by the three-thread group.
[[profile.default.overrides]]
filter = 'binary(integration) & test(/^telemetry_contract::/)'
test-group = 'telemetry-contract'
# Same hazard, different binary. `telemetry_kill_switch_dispatch` is a
# codewhale-cli integration binary, not `integration`, so none of the filters
# below ever matched it and it ran at full parallelism beside ~15.7k tests.
# Each of its cases spawns the real binary, which gives a detached writer
# thread only CLI_PERSIST_TIMEOUT (250 ms, crates/telemetry/src/lib.rs) to
# re-read config from disk and fsync its batch before the process exits. Under
# Ubuntu CI load that deadline is missed and the dry-run receipt never lands —
# observed at telemetry_kill_switch_dispatch.rs:180 on PR #6104 (2026-09-12)
# and again on PR #6267 (2026-09-16), both Ubuntu-only, both on changes that
# touch neither telemetry nor the CLI.
[[profile.default.overrides]]
filter = 'binary(telemetry_kill_switch_dispatch)'
test-group = 'telemetry-contract'
[[profile.default.overrides]]
filter = 'package(codewhale-engine) & kind(lib) & test(/^fleet::manager::tests::/)'
test-group = 'fleet-manager-lifecycle'
[[profile.default.overrides]]
# DSH preparation also launches the isolated host against the shared hermetic
# home. OCR/PDF consumers also launch real hosts outside extension_host's
# namespace. Keep their process lifecycle checks in this existing serial lane.
filter = '(package(codewhale-engine) | package(codewhale-tui)) & kind(lib) & (test(/^extension_host::/) | test(/^tools::image_ocr::host_tests::/) | test(/^tools::pdf::host_tests::/) | test(/^core::engine::.*extension/) | test(/^plugins::install::dsh::/) | test(/^plugins::tests::dsh_/) | test(/^runtime_api::tests::dsh_package_preview_then_exact_install_over_http$/) | test(/^commands::contract::tests::plugin_adapter_dsh_preview_runs_from_live_runtime$/) | test(/^commands::groups::plugins::tests::dsh_import_reviews_without_installing_then_installs_the_exact_bundle$/))'
test-group = 'extension-host'
[[profile.default.overrides]]
filter = 'binary(integration) & test(/^exec_persistent_service::/)'
test-group = 'exec-persistent-service'
slow-timeout = { period = "120s" }
[[profile.default.overrides]]
filter = 'binary(integration)'
test-group = 'spawns-binaries'
[[profile.default.overrides]]
filter = 'binary(owner_crash_reentry)'
test-group = 'spawns-binaries'
# CI (profiles inherit from default): the final summary lists every failure and
# slow test in full so the run log is enough to diagnose without rerunning.
[profile.ci]
retries = 0
# A test that runs past ten minutes is a hang, not a slow test: terminate it
# and surface its captured output instead of letting the job die at the
# runner's 90-minute limit with no diagnostics (v0.9.12 Windows candidate).
slow-timeout = { period = "60s", terminate-after = 10 }
fail-fast = false
final-status-level = "slow"
failure-output = "immediate-final"
# Exact compiled-image Native delivery receipt comes from this same full run.
# Only the fixed containment and memory marker/output is retained on success; the rest
# of the workspace keeps its existing output policy.
[profile.ci.junit]
path = "junit.xml"
report-name = "codewhale-native-host"
[[profile.ci.overrides]]
filter = 'package(codewhale-engine) & kind(lib) & (test(/^extension_host::tests::compiled_native_host_cannot_read_secrets_or_write_outside_its_data_dir$/) | test(/^extension_host::tests::compiled_native_host_memory_cap_is_enforced$/))'
junit.store-success-output = true
-29
View File
@@ -1,29 +0,0 @@
ARG RUST_VERSION=1.100.0
FROM rust:${RUST_VERSION}-slim-bookworm
# Native DBus headers and pkg-config are required by libdbus-sys during
# `cargo build`. Keep these development dependencies in the Dev Container,
# rather than adding them to the production runtime image.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
git \
libdbus-1-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# The official Rust image keeps its toolchain here. Preserve it after switching
# from the image's root user to the non-root development user below.
ENV PATH=/usr/local/cargo/bin:${PATH}
RUN printf '%s\n' 'export PATH=/usr/local/cargo/bin:$PATH' \
> /etc/profile.d/codewhale-rust.sh
RUN rustup component add rustfmt
RUN groupadd --gid 1000 codewhale \
&& useradd --create-home --shell /bin/bash --uid 1000 --gid 1000 codewhale \
&& install -d -m 0700 -o codewhale -g codewhale /home/codewhale/.codewhale \
&& install -d -m 0755 -o codewhale -g codewhale /home/codewhale/.cargo \
&& install -d -m 0755 -o codewhale -g codewhale /home/codewhale/.cargo/target
USER codewhale
WORKDIR /workspaces/CodeWhale
-33
View File
@@ -1,33 +0,0 @@
{
"name": "CodeWhale",
"build": {
"context": "..",
"dockerfile": "Dockerfile",
"args": {
"RUST_VERSION": "1.100.0"
}
},
"customizations": {
"vscode": {
"extensions": [
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"vadimcn.vscode-lldb"
],
"settings": {
"rust-analyzer.cargo.features": "all",
"editor.formatOnSave": true
}
}
},
"remoteEnv": {
"DEEPSEEK_API_KEY": "${localEnv:DEEPSEEK_API_KEY}",
"CARGO_TARGET_DIR": "/home/codewhale/.cargo/target"
},
"mounts": [
"source=codewhale-state,target=/home/codewhale/.codewhale,type=volume",
"source=codewhale-cargo-target,target=/home/codewhale/.cargo/target,type=volume"
],
"postCreateCommand": "cargo build --locked",
"remoteUser": "codewhale"
}
-68
View File
@@ -1,68 +0,0 @@
# Build artifacts
/target/
*.pdb
*.dll
*.so
*.dylib
*.rlib
# Sensitive environment files
.env
.env.*
# Development
/node_modules/
/.vscode/
/.idea/
*.swp
*.swo
*~
.DS_Store
# Git
/.git/
/.gitignore
/.gitattributes
# CI/CD
/.github/
# Python
__pycache__/
*.py[cod]
.pytest_cache/
venv/
.venv/
# Logs
*.log
# Generated
/outputs/
/tmp/
# Local runtime state
/.deepseek/
# Claude Code artifacts
/.claude/
/.ace-tool/
# Documentation (not needed at runtime), except schemas embedded via include_str!
/docs/
!/docs/schemas/
!/docs/schemas/**
/website/
/*.md
!/README.md
!/CHANGELOG.md
# Assets (screenshots, etc.)
/assets/
# Scripts
/scripts/
# Development configs
/.devcontainer/
/config.example.toml
-28
View File
@@ -1,28 +0,0 @@
# Codewhale workspace credentials
#
# Shell-exported variables override values in this file. Codewhale loads only
# literal credential names for built-in providers from a workspace `.env`.
# Routing, model/base URL, config/profile, MCP/plugin, sandbox, approval,
# executable-path, runtime, and other control settings are ignored here; put
# those in config.toml, CLI flags, or the launching shell.
#
# Variable expansion is deliberately unsupported. Keep values literal and
# keep `.env` untracked.
# DeepSeek API (default provider)
# Get an API key from DeepSeek, then keep it local in `.env`.
# DEEPSEEK_API_KEY=
# NVIDIA NIM-hosted models
# NVIDIA_API_KEY=
# NVIDIA_NIM_API_KEY=
# AtlasCloud OpenAI-compatible endpoint
# ATLASCLOUD_API_KEY=
# Mistral AI (la Plateforme) — https://console.mistral.ai/api-keys
# MISTRAL_API_KEY=
# Codewhale API — account key (cwc_key_…) with the models:infer scope
# https://app.codewhale.net/settings?section=api
# CODEWHALE_API_KEY=
-93
View File
@@ -1,93 +0,0 @@
# Ensure LF line endings for files consumed by include_str!() on all platforms.
# include_str!() preserves raw bytes; CRLF breaks substring assertions and
# produces different compiled binaries on Windows vs Linux/macOS.
# Cover nested mode/approval/personality prompts — a top-level *.md glob alone
# left modes/*.md on CRLF under Windows autocrlf and blew the agent token budget.
crates/engine/src/prompts/**/*.md text eol=lf
crates/engine/src/prompts/*.md text eol=lf
crates/engine/src/prompts/*.txt text eol=lf
# The rest of the include_str!() inputs. `* text=auto` below converts these to
# CRLF on a Windows checkout, which is invisible for the ones that are only
# parsed or substring-matched and fatal for the one that is compared byte for
# byte: crates/telemetry/tests/golden/v1.json is asserted against
# serde_json::to_string_pretty output, which always emits LF, so Windows CI
# failed `golden_payload_v1` with a pure \r\n diff.
crates/telemetry/tests/golden/*.json text eol=lf
# Generated pet native artifacts are regenerated in memory and compared byte
# for byte by `npm --prefix pet run check` (pet/scripts/build-pet.mjs), and the
# TUI copy is embedded with include_str!() (pet_watch/owner.rs). The generator
# emits LF, so a CRLF Windows checkout reported all three as stale and would
# embed different bytes.
crates/tui/src/tui/pet_watch/pet-native.js text eol=lf
pet/ios/Resources/pet-native.js text eol=lf
pet/ios/Resources/demo.jsonl text eol=lf
# Tideline component goldens (crates/tui/src/tui/goldens/*.txt) are
# fs::read_to_string'd and compared byte for byte against buffer dumps that
# always join rows with LF (golden_harness.rs::render_golden_text), so a
# CRLF checkout failed all eight Windows golden tests with a pure \r\n diff.
crates/tui/src/tui/goldens/*.txt text eol=lf
# FEAT-025 baseline export goldens (crates/tui/src/commands/fixtures/*.md) are
# include_str!()'d and compared byte for byte against a document the exporter
# builds with LF, so a CRLF checkout failed the four Windows golden tests with a
# pure \r\n diff (Windows CI caught this; Linux cannot see it).
crates/tui/src/commands/fixtures/*.md text eol=lf
# FEAT-029 debug diagnostics baselines (crates/tui/src/commands/fixtures/
# diagnostics/*) are compared byte for byte against command output joined
# with LF, so a CRLF checkout failed every Windows baseline parity test with a
# pure \r\n diff.
crates/tui/src/commands/fixtures/diagnostics/* text eol=lf
# Conformance fixtures are recorded wire bytes (SSE framing is line-exact) and
# goldens compared byte for byte; a CRLF checkout must not rewrite either.
crates/tui/tests/fixtures/conformance/** text eol=lf
crates/*/assets/**/*.json text eol=lf
crates/*/assets/**/*.md text eol=lf
crates/*/locales/*.json text eol=lf
workflows/*.js text eol=lf
# Executable documentation: the Fleet tutorial's JSON fence is parsed by the
# task-spec contract test, so its bytes must agree on Windows and Unix.
docs/FLEET_WORKFLOW_TUTORIAL.md text eol=lf
# The dsh bundle scene is include_str!() into the generated client.js and
# hashed for stale detection; CRLF would change both across platforms.
crates/tui/src/integrations/dsh/*.js text eol=lf
# The native DSH import fixture bundle is hashed into the conversion receipt,
# so a CRLF checkout changed the expected digest on Windows.
scripts/fixtures/dsh-web-app/** text eol=lf
# The extension host's generated protocol TS is compared byte for byte with
# the generator's LF output (protocol/tests.rs), and its dist bundle is
# include_bytes!()'d and digest-checked at launch; a CRLF Windows checkout
# failed the former and would embed different bytes.
crates/engine/extension-host/src/protocol.generated.ts text eol=lf
crates/engine/extension-host/dist/** text eol=lf
# Reviewed extension-host fixtures carry exact source SHA-256 receipts.
# A Windows checkout must not rewrite their admitted source closure to CRLF.
crates/engine/tests/fixtures/extension_host/** text eol=lf
crates/engine/extension-host/src/dsh/dsh-message-compat.js text eol=lf
# DeepSeek Harness creator pins are compared byte for byte against recorded
# SHA-256 values (UPSTREAM.json) and embedded with include_bytes!(); a CRLF
# Windows checkout must not rewrite any of them.
crates/skills/assets/dsh-creator/** text eol=lf
crates/engine/extension-host/src/dsh/upstream/creator/** text eol=lf
# Rustfmt writes LF; keep Rust sources stable across Windows/Linux/macOS.
*.rs text eol=lf
# Release shell scripts are invoked directly by bash on Windows
# checkouts; CRLF turns `set -euo pipefail` into an invalid option.
scripts/release/*.sh text eol=lf
# Keep repository attributes themselves stable on every platform.
.gitattributes text eol=lf
# Everything else auto-detects (default).
* text=auto
# Preserve the qualified upstream font license, including its trailing space.
web/public/brand/fonts/OFL.txt text eol=lf whitespace=-blank-at-eol
# Generated protocol and SDK exports are compared byte for byte; keep LF on Windows checkouts.
crates/app-server-protocol/schema/** text eol=lf
crates/app-server-protocol/METHODS.md text eol=lf
npm/runtime-sdk/generated/** text eol=lf
-5
View File
@@ -1,5 +0,0 @@
# This exact fixture is a deliberately public TEST-ONLY signing key.
# It is never a production trust anchor and signs only local test payloads.
# No other key files are excluded.
paths-ignore:
- docs/cloud-facts/fixtures/test-only-signing-key.pem
-323
View File
@@ -1,323 +0,0 @@
# Contributor credit identity map.
#
# Format:
# alias = Display Name <[email protected]>
#
# The right-hand side must use GitHub's numeric noreply address so a harvested
# commit authored with it, or a Co-authored-by trailer in the commit's final
# trailer block, links to the contributor's account. GitHub resolves the
# address by its numeric id, so the id must be the contributor's own
# (`gh api users/<login> --jq .id`). The left-hand side may be a GitHub login,
# old-style noreply address, raw email from a contributor commit, or local
# machine email seen in older harvested history.
hmbown = Hmbown <[email protected]>
[email protected] = Hmbown <[email protected]>
fleitz = Fred Leitz <[email protected]>
[email protected] = Fred Leitz <[email protected]>
[email protected] = Fred Leitz <[email protected]>
Angel-Hair = Lu Shihan <[email protected]>
[email protected] = Lu Shihan <[email protected]>
dmitri-0 = dmitri-0 <[email protected]>
[email protected] = dmitri-0 <[email protected]>
SparkofSpike = Sh1Zuku <[email protected]>
zhangweiii = zhangweiii <[email protected]>
[email protected] = zhangweiii <[email protected]>
sternelee = Sterne Lee <[email protected]>
[email protected] = Sterne Lee <[email protected]>
Mr-Moon121 = Jeffrey Luna <[email protected]>
lerugray = Ray Weiss <[email protected]>
[email protected] = Ray Weiss <[email protected]>
reidliu41 = reidliu41 <[email protected]>
[email protected] = reidliu41 <[email protected]>
ousamabenyounes = Ben Younes <[email protected]>
[email protected] = Ben Younes <[email protected]>
ljm3790865 = ljm3790865 <[email protected]>
HUQIANTAO = HUQIANTAO <[email protected]>
Hu Qiantao = HUQIANTAO <[email protected]>
[email protected] = HUQIANTAO <[email protected]>
[email protected] = HUQIANTAO <[email protected]>
[email protected] = HUQIANTAO <[email protected]>
punkcanyang = Punkcan Yang <[email protected]>
Punkcan Yang = Punkcan Yang <[email protected]>
[email protected] = Punkcan Yang <[email protected]>
merchloubna70-dot = merchloubna70-dot <[email protected]>
h3c-hexin = h3c-hexin <[email protected]>
[email protected] = h3c-hexin <[email protected]>
[email protected] = h3c-hexin <[email protected]>
axobase001 = axobase001 <[email protected]>
donglovejava = donglovejava <[email protected]>
Oliver-ZPLiu = Oliver-ZPLiu <[email protected]>
idling11 = idling11 <[email protected]>
Hanmiao Li = idling11 <[email protected]>
[email protected] = idling11 <[email protected]>
894876246 = idling11 <[email protected]>
angziii = angziii <[email protected]>
aboimpinto = aboimpinto <[email protected]>
Paulo Aboim Pinto = aboimpinto <[email protected]>
[email protected] = aboimpinto <[email protected]>
[email protected] = aboimpinto <[email protected]>
encyc = encyc <[email protected]>
Duducoco = Duducoco <[email protected]>
cyq1017 = cyq1017 <[email protected]>
cyq = cyq1017 <[email protected]>
[email protected] = cyq1017 <[email protected]>
cy2311 = CY <[email protected]>
noaft = Toan Nguyen <[email protected]>
[email protected] = Toan Nguyen <[email protected]>
zlh124 = zlh124 <[email protected]>
LeoLin990405 = LeoLin990405 <[email protected]>
THINKER-ONLY = THINKER-ONLY <[email protected]>
nightt5879 = nightt5879 <[email protected]>
LmeSzinc = LmeSzinc <[email protected]>
Lstarsky0 = Lstarsky0 <[email protected]>
RepentStar = RepentStar <[email protected]>
CCChisato = Fushimi Rio <[email protected]>
aznikline = aznikline <[email protected]>
Aznable = aznikline <[email protected]>
[email protected] = aznikline <[email protected]>
Liu-Vince = Liu-Vince <[email protected]>
Vince = Liu-Vince <[email protected]>
[email protected] = Liu-Vince <[email protected]>
LI-Jialu = LI-Jialu <[email protected]>
JiarenWang = JiarenWang <[email protected]>
wdw8276 = kitty <[email protected]>
mars-base = kitty <[email protected]>
[email protected] = kitty <[email protected]>
pengyou200902 = pengyou200902 <[email protected]>
linzhiqin2003 = linzhiqin2003 <[email protected]>
LING71671 = LING71671 <[email protected]>
[email protected] = LING71671 <[email protected]>
JasonOA888 = JasonOA888 <[email protected]>
Inference1 = Inference1 <[email protected]>
hongqitai = hongqitai <[email protected]>
gordonlu = gordonlu <[email protected]>
angus-guo = gus <[email protected]>
imgusguo = gus <[email protected]>
[email protected] = gus <[email protected]>
gaord = gaord <[email protected]>
Ben Gao = gaord <[email protected]>
[email protected] = gaord <[email protected]>
bengao168 = gaord <[email protected]>
MXAntian = MXAntian <[email protected]>
[email protected] = MXAntian <[email protected]>
DarrellThomas = Darrell Thomas <[email protected]>
[email protected] = Darrell Thomas <[email protected]>
taixinguo = Taixin Guo <[email protected]>
Taixin Guo = Taixin Guo <[email protected]>
JayBeest = JayBeest <[email protected]>
jcorneli = JayBeest <[email protected]>
[email protected] = JayBeest <[email protected]>
lucaszhu-hue = lucaszhu-hue <[email protected]>
zhuangbiaowei = zhuangbiaowei <[email protected]>
yuanchenglu = yuanchenglu <[email protected]>
Vishnu1837 = Vishnu1837 <[email protected]>
sximelon = sximelon <[email protected]>
Sskift = Sskift <[email protected]>
New2Niu = New2Niu <[email protected]>
mvanhorn = mvanhorn <[email protected]>
MengZ-super = MengZ-super <[email protected]>
membphis = membphis <[email protected]>
LeoAlex0 = LeoAlex0 <[email protected]>
Lee-take = Lee-take <[email protected]>
lbcheng888 = lbcheng888 <[email protected]>
kunpeng-ai-lab = kunpeng-ai-lab <[email protected]>
elowen53 = elowen53 <[email protected]>
Elowen = elowen53 <[email protected]>
[email protected] = elowen53 <[email protected]>
CrepuscularIRIS = CrepuscularIRIS <[email protected]>
chnjames = chnjames <[email protected]>
ChaceLyee2101 = ChaceLyee2101 <[email protected]>
ci4ic4 = ci4ic4 <[email protected]>
Chavdar Ivanov = ci4ic4 <[email protected]>
[email protected] = ci4ic4 <[email protected]>
yusufgurdogan = yusufgurdogan <[email protected]>
Yusuf Gurdogan = yusufgurdogan <[email protected]>
hotelswith = yusufgurdogan <[email protected]>
[email protected] = yusufgurdogan <[email protected]>
AresNing = AresNing <[email protected]>
shenjackyuanjie = shenjackyuanjie <[email protected]>
shenjack = shenjackyuanjie <[email protected]>
[email protected] = shenjackyuanjie <[email protected]>
xyuai = xyuai <[email protected]>
AdityaVG13 = AdityaVG13 <[email protected]>
[email protected] = AdityaVG13 <[email protected]>
Implementist = Implementist <[email protected]>
implecao = Implementist <[email protected]>
[email protected] = Implementist <[email protected]>
jrcjrcc = jrcjrcc <[email protected]>
[email protected] = jrcjrcc <[email protected]>
RefuseOdd = RefuseOdd <[email protected]>
wywsoor = wywsoor <[email protected]>
hsdbeebou = hsdbeebou <[email protected]>
tdccccc = tdccccc <[email protected]>
greyfreedom = greyfreedom <[email protected]>
[email protected] = greyfreedom <[email protected]>
puneetdixit200 = puneetdixit200 <[email protected]>
roian6 = Chanhyo Jung <[email protected]>
[email protected] = Chanhyo Jung <[email protected]>
yekern = Stime <[email protected]>
Stime = Stime <[email protected]>
pkeging = pkeging <[email protected]>
[email protected] = pkeging <[email protected]>
findshan = Wenshan Deng <[email protected]>
[email protected] = Wenshan Deng <[email protected]>
KUK4 = KUK4 <[email protected]>
[email protected] = KUK4 <[email protected]>
# Harvest-credit reconciliation: contributors restored to docs/CONTRIBUTORS.md
# whose harvested work previously lacked a canonical map entry.
GTC2080 = TaoMu <[email protected]>
[email protected] = TaoMu <[email protected]>
MMMarcinho = MMMarcinho <[email protected]>
MeAiRobot = MeAiRobot <[email protected]>
NorethSea = NorethSea <[email protected]>
SamhandsomeLee = SamhandsomeLee <[email protected]>
YaYII = YaYII <[email protected]>
sandofree = sandofree <[email protected]>
tiger-dog = tiger-dog <[email protected]>
Jianfengwu2024 = Jianfengwu2024 <[email protected]>
wplll = wplll <[email protected]>
buko = buko <[email protected]>
# Machine-credit reconciliation follow-up: contributors credited in prose or
# harvested by handle but missing from AUTHOR_MAP. Logins/IDs verified against
# the GitHub user API.
1Git2Clone = 1Git2Clone <[email protected]>
jieshu666 = jieshu666 <[email protected]>
rockeverm3m = rockeverm3m <[email protected]>
[email protected] = rockeverm3m <[email protected]>
wavezhang = wavezhang <[email protected]>
hxy91819 = hxy91819 <[email protected]>
[email protected] = hxy91819 <[email protected]>
heloanc = heloanc <[email protected]>
[email protected] = heloanc <[email protected]>
bistack = Sun Zhenyuan <[email protected]>
[email protected] = Sun Zhenyuan <[email protected]>
OctoBored = OctoBored <[email protected]>
skyzhao1223 = SKY ZHAO <[email protected]>
zhaotian1 = SKY ZHAO <[email protected]>
[email protected] = SKY ZHAO <[email protected]>
vFONGv = Matthew.Fong <[email protected]>
[email protected] = Matthew.Fong <[email protected]>
luismateusvargas = Luis Mateus Vargas <[email protected]>
redjade75723 = redjade75723 <[email protected]>
# v0.8.68 underwater-lane harvests.
moduvoice = moduvoice <[email protected]>
[email protected] = moduvoice <[email protected]>
qinlinwang = qinlinwang <[email protected]>
hange = qinlinwang <[email protected]>
[email protected] = qinlinwang <[email protected]>
knqiufan = knqiufan <[email protected]>
[email protected] = knqiufan <[email protected]>
# v0.9.1 Windows TUI reports harvested into maintainer fixes.
SparkofSpike = Sh1Zuku <[email protected]>
Sh1Zuku = Sh1Zuku <[email protected]>
# v0.9.1 provider harvests.
baendlorel = baendlorel <[email protected]>
[email protected] = baendlorel <[email protected]>
EvanProgramming = Evan <[email protected]>
adity982 = ADITYA <[email protected]>
vibecoding-skills = Harsh Dattani <[email protected]>
XhesicaFrost = XhesicaFrost <[email protected]>
ffaacceelee = ffaacceelee <[email protected]>
mky = mky <[email protected]>
cacdcaecawae = cacdcaecawae <[email protected]>
XiaoHuo888-hue = XiaoHuo888-hue <[email protected]>
[email protected] = XiaoHuo888-hue <[email protected]>
wuisabel-gif = Isabel Wu <[email protected]>
redstar = Kai Nacke <[email protected]>
[email protected] = Kai Nacke <[email protected]>
Shizuku = Sh1Zuku <[email protected]>
[email protected] = Sh1Zuku <[email protected]>
musichen = Alex Musichen <[email protected]>
[email protected] = Alex Musichen <[email protected]>
M-Maciej = M-Maciej <[email protected]>
serephus = Serephus <[email protected]>
# [email protected] = UNRESOLVED — 21 commits on main via #704; GitHub login not yet identified
qiuYliangM = 秋月凉梦 <[email protected]>
[email protected] = 秋月凉梦 <[email protected]>
whp233 = whp233 <[email protected]>
[email protected] = whp233 <[email protected]>
Gabriel-Degret = Gabriel Degret <[email protected]>
[email protected] = Gabriel Degret <[email protected]>
huangxianzhan = huangxianzhan <[email protected]>
[email protected] = huangxianzhan <[email protected]>
zhuowp = zhuowp <[email protected]>
[email protected] = zhuowp <[email protected]>
z27014 = zhuowp <[email protected]>
[email protected] = zhuowp <[email protected]>
[email protected] = 秋月凉梦 <[email protected]>
asto18089 = asto18089 <[email protected]>
[email protected] = asto18089 <[email protected]>
xiechimon = Xmon Dai <[email protected]>
yrk111222 = Rongkun Yan <[email protected]>
VincentCorleone = Vincent Corleone <[email protected]>
Vincent Corleone = Vincent Corleone <[email protected]>
[email protected] = Vincent Corleone <[email protected]>
yetuge = yetuge <[email protected]>
[email protected] = yetuge <[email protected]>
Serendo = Serendo <[email protected]>
[email protected] = Serendo <[email protected]>
dajiaohuang = dajiaohuang <[email protected]>
[email protected] = dajiaohuang <[email protected]>
Wu Shuwen = dajiaohuang <[email protected]>
[email protected] = dajiaohuang <[email protected]>
harryvgiunta = Harry Giunta <[email protected]>
[email protected] = Harry Giunta <[email protected]>
xiaoray-blip = Arlo <[email protected]>
Arlo = Arlo <[email protected]>
xyzs996 = Mark Yan <[email protected]>
[email protected] = Mark Yan <[email protected]>
Mark Yan = Mark Yan <[email protected]>
henrikdukefoss = Henrik Bakke Dukefoss <[email protected]>
Henrik Bakke Dukefoss = Henrik Bakke Dukefoss <[email protected]>
[email protected] = Henrik Bakke Dukefoss <[email protected]>
[email protected] = Dhruv Singal <[email protected]>
[email protected] = Boyce <[email protected]>
Boyce = Boyce <[email protected]>
Felixkw12 = Felix Wunderlich <[email protected]>
[email protected] = Felix Wunderlich <[email protected]>
Felix Wunderlich = Felix Wunderlich <[email protected]>
[email protected] = luochen211 <[email protected]>
AiurArtanis = AiurArtanis <[email protected]>
[email protected] = AiurArtanis <[email protected]>
Apeiron0w0 = Apeiron <[email protected]>
[email protected] = Apeiron <[email protected]>
[email protected] = ChaceLyee2101 <[email protected]>
AmosLeeeeeeee = ChaceLyee2101 <[email protected]>
ddlw = ddlw <[email protected]>
mo-vic = movic <[email protected]>
[email protected] = movic <[email protected]>
[email protected] = merchloubna70-dot <[email protected]>
[email protected] = merchloubna70-dot <[email protected]>
xulongzhe = luola <[email protected]>
[email protected] = luola <[email protected]>
ZhulongNT = SONG ZHENRUI <[email protected]>
[email protected] = SONG ZHENRUI <[email protected]>
xiluoduyu = xiluoduyu <[email protected]>
[email protected] = xiluoduyu <[email protected]>
wlon = Stephen Xu <[email protected]>
[email protected] = Stephen Xu <[email protected]>
krisclarkdev = Kristopher Clark <[email protected]>
THatch26 = THatch26 <[email protected]>
jeoor = Kayro <[email protected]>
harvey2011888 = harvey2011888 <[email protected]>
duanchao-lab = DC <[email protected]>
dumbjack = dumbjack <[email protected]>
[email protected] = dumbjack <[email protected]>
AquaZhang = IceTear <[email protected]>
disappear00 = idontknow <[email protected]>
namezzb = sailorZ <[email protected]>
alphapku-ux = Alpha Wu <[email protected]>
BX166 = BX166 <[email protected]>
c020627 = c020627 <[email protected]>
[email protected] = Parman Mohammadalizadeh <[email protected]>
Parman Mohammadalizadeh = Parman Mohammadalizadeh <[email protected]>
-2
View File
@@ -1,2 +0,0 @@
# Default owner for everything in the repo.
* @Hmbown
-76
View File
@@ -1,76 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
-68
View File
@@ -1,68 +0,0 @@
# Berkontribusi pada Codewhale
Codewhale berkembang berkat orang yang menggunakannya, melaporkan masalah,
menerjemahkan dokumentasi, menambahkan penyedia model, dan mengirim perbaikan.
Anda dapat berkontribusi tanpa menulis kode. Semua masukan diterima.
[Roadmap](../docs/ROADMAP.md) menjelaskan tujuan proyek dan pekerjaan yang masih
terbuka. [Panduan utama](../CONTRIBUTING.md) memuat prosedur pengembangan,
pengujian, dan peninjauan yang lengkap dan terbaru.
## Pilih tempat yang sesuai
| Yang ingin Anda sampaikan | Tempatnya |
| --- | --- |
| Bug, crash, atau perilaku yang tidak diharapkan | [Laporan bug](https://github.com/codewhale-hq/CodeWhale/issues/new?template=bug_report.yml) |
| Ide atau permintaan fitur | [Permintaan fitur](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feature_request.yml) |
| Masukan tentang tampilan, tulisan, atau pengalaman penggunaan | [Formulir masukan](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feedback.yml) |
| Pertanyaan | [Tanya jawab](https://github.com/codewhale-hq/CodeWhale/discussions/categories/q-a) atau [Discord](https://discord.gg/37gfS3ksug) |
| Masalah keamanan | [Laporan keamanan privat](https://github.com/codewhale-hq/CodeWhale/security/advisories/new), bukan issue publik |
| Perbaikan untuk issue yang sudah ada | Pull request yang menautkan issue tersebut |
| Terjemahan, penyedia model, atau perbaikan dokumentasi | Pull request; tidak perlu membuka issue terlebih dahulu |
Jika ragu, buka issue dan jelaskan apa yang Anda coba lakukan, apa yang terjadi,
dan apa yang Anda harapkan. Pengelola akan membantu mengarahkannya.
## Mulai fitur baru dengan issue
Untuk fitur baru, buka issue sebelum mulai menulis kode. Jelaskan kebutuhan dan
hambatannya; rancangan solusi boleh disertakan, tetapi tidak wajib. Jika ingin
mengerjakannya sendiri, centang pilihan untuk membuka pull request pada formulir.
Pengelola dapat membahas cakupan dan perilaku fitur sebelum Anda menghabiskan
waktu untuk implementasi. Pull request fitur tanpa issue tetap dibaca, tetapi
pembahasannya biasanya membutuhkan waktu lebih lama.
## Pekerjaan yang dapat langsung Anda kirim
Anda dapat langsung mengirim pull request untuk terjemahan, penyedia dan model,
perbaikan dokumentasi, atau perbaikan bug. Jika bug belum memiliki issue,
jelaskan cara memunculkannya, hasil yang terjadi, dan hasil yang diharapkan di
pull request.
Untuk pekerjaan yang sudah direncanakan, pilih issue di
[milestone](../docs/ROADMAP.md#releases) atau yang berlabel
[help wanted](https://github.com/codewhale-hq/CodeWhale/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22).
Beri komentar bahwa Anda ingin mengerjakannya agar pekerjaan tidak tumpang tindih.
[Lihat proyek terbuka](../CONTRIBUTING.md#standing-projects) untuk tautan ke
panduan terjemahan dan penyedia model.
## Siapkan perubahan Anda
Ikuti [petunjuk persiapan pengembangan](../CONTRIBUTING.md#getting-started)
untuk persyaratan Rust, perintah build, dan cara menjalankan versi sumber.
Versi di `main` dapat lebih baru daripada unduhan rilis; sertakan keluaran
`codewhale --version` ketika melaporkan masalah.
Jalankan pemeriksaan yang sesuai dengan bagian yang Anda ubah, mengikuti
[panduan pengujian lokal](../CONTRIBUTING.md#fast-local-loop). Sebelum mengirim
pull request, baca perubahan Anda sendiri dan jelaskan apa yang berubah serta
bagaimana Anda memeriksanya. Jangan sertakan kunci API atau data pribadi.
## Peninjauan dan kredit
Pull request Anda digabungkan sebagai kontribusi Anda sendiri. Pengelola
membantu menyelesaikan konflik akibat perubahan di `main`; pemeriksaan yang
merah karena masalah lain bukan tanggung jawab Anda. Jika perubahan belum siap,
pengelola menjelaskan alasannya. Kontribusi dicatat di changelog dan
[daftar kontributor](../docs/CONTRIBUTORS.md).
-2
View File
@@ -1,2 +0,0 @@
github: [Hmbown]
buy_me_a_coffee: hmbown
+56
View File
@@ -0,0 +1,56 @@
name: 🪲 Bug Report
description: Report an issue that should be fixed
labels:
- bug
- needs triage
body:
- type: markdown
attributes:
value: |
Thank you for submitting a bug report! It helps make Codex better for everyone.
If you need help or support using Codex, and are not reporting a bug, please post on [codex/discussions](https://github.com/openai/codex/discussions), where you can ask questions or engage with others on ideas for how to improve codex.
Make sure you are running the [latest](https://npmjs.com/package/@openai/codex) version of Codex CLI. The bug you are experiencing may already have been fixed.
Please try to include as much information as possible.
- type: input
id: version
attributes:
label: What version of Codex is running?
description: Copy the output of `codex --version`
- type: input
id: model
attributes:
label: Which model were you using?
description: Like `gpt-4.1`, `o4-mini`, `o3`, etc.
- type: input
id: platform
attributes:
label: What platform is your computer?
description: |
For MacOS and Linux: copy the output of `uname -mprs`
For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console
- type: textarea
id: steps
attributes:
label: What steps can reproduce the bug?
description: Explain the bug and provide a code snippet that can reproduce it.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What is the expected behavior?
description: If possible, please provide text instead of a screenshot.
- type: textarea
id: actual
attributes:
label: What do you see instead?
description: If possible, please provide text instead of a screenshot.
- type: textarea
id: notes
attributes:
label: Additional information
description: Is there anything else you think we should know?
+27
View File
@@ -0,0 +1,27 @@
name: 📗 Documentation Issue
description: Tell us if there is missing or incorrect documentation
labels: [docs]
body:
- type: markdown
attributes:
value: |
Thank you for submitting a documentation request. It helps make Codex better.
- type: dropdown
attributes:
label: What is the type of issue?
multiple: true
options:
- Documentation is missing
- Documentation is incorrect
- Documentation is confusing
- Example code is not working
- Something else
- type: textarea
attributes:
label: What is the issue?
validations:
required: true
- type: textarea
attributes:
label: Where did you find it?
description: If possible, please provide the URL(s) where you found this issue.
-67
View File
@@ -1,67 +0,0 @@
name: Agent task
description: A self-contained task a headless Codewhale agent can finish end to end. Mostly for maintainers.
labels: ["agent-ready"]
body:
- type: markdown
attributes:
value: |
A headless agent runs this with `codewhale exec --auto` on a fresh clone of `main`.
It has shell, file, and git tools, and **no context beyond this issue**. Name every
path and command explicitly. Assign a milestone during triage; this template sets none.
- type: textarea
id: goal
attributes:
label: Goal
description: What is wrong or missing, and why it matters now. 2-4 sentences.
placeholder: |
The TUI freezes when 4+ sub-agents run at once, because each progress
event triggers a full redraw.
validations:
required: true
- type: textarea
id: scope
attributes:
label: Plan
description: Numbered steps. Start each with the file path it changes. List any extra files to read first.
placeholder: |
1. crates/tui/src/tui/ui.rs — throttle redraws in the progress handler
2. crates/tui/src/tui/app.rs — add a last-redraw timestamp
Read first: crates/tui/src/tui/sidebar.rs
validations:
required: true
- type: textarea
id: acceptance-criteria
attributes:
label: Done when
description: Observable, testable outcomes.
placeholder: |
- [ ] 4 concurrent sub-agents do not freeze input
- [ ] Ctrl+C still works during sub-agent activity
validations:
required: true
- type: textarea
id: verification
attributes:
label: Verification
description: Exact commands that prove it. Scope them to the crates touched; CI runs the full suite.
placeholder: |
scripts/dev-cargo.sh check -p codewhale-tui
scripts/dev-test.sh tui subagent
render: shell
validations:
required: true
- type: textarea
id: out-of-scope
attributes:
label: Out of scope
description: What this task must not change.
placeholder: |
- The sub-agent execution model
- The recommended fanout count
validations:
required: false
-124
View File
@@ -1,124 +0,0 @@
name: Bug report
description: Something is broken or behaves wrong. 报告问题。
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting. English or 中文 are both fine.
- Security problem? Do not post it here. Use a [private advisory](https://github.com/codewhale-hq/CodeWhale/security/advisories/new).
- Already reported? Add a 👍 to that issue instead of opening a new one.
- Remove API keys and tokens from anything you paste.
- type: checkboxes
id: searched
attributes:
label: Before you start
options:
- label: I searched existing issues and this is not a duplicate. 我已搜索过现有 issue。
required: true
- type: textarea
id: what-happened
attributes:
label: What happened? 发生了什么?
description: What you saw, and what you expected instead. Paste error text as text, not only a screenshot.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce 复现步骤
description: Exact commands, key presses, or prompts. Shorter is better.
placeholder: |
1. Run `codewhale` in a git repository
2. Type ...
3. See ...
validations:
required: true
- type: input
id: version
attributes:
label: Codewhale version 版本
description: Output of `codewhale --version`. Desktop app — the version in About.
placeholder: codewhale 0.10.1
validations:
required: true
- type: dropdown
id: surface
attributes:
label: Where did it happen? 在哪里出现?
multiple: true
options:
- TUI (terminal interface)
- CLI command (codewhale exec, codewhale doctor, ...)
- Desktop app
- Web (codewhale web or app.codewhale.net)
- Plugins / extension host
- MCP
- Other / not sure
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating system 操作系统
options:
- macOS
- Linux
- Windows
- WSL
- Android (Termux)
- HarmonyOS
- Other
validations:
required: true
- type: dropdown
id: channel
attributes:
label: How did you get Codewhale? 获取方式
options:
- Official script (codewhale.net/install.sh)
- GitHub Releases download
- Desktop app download
- npm
- Homebrew
- Cargo (crates.io)
- Built from a git checkout
- Scoop / Nix / Docker / other package
- Not sure
validations:
required: true
- type: input
id: model
attributes:
label: Provider and model 提供商和模型
description: As shown by `/model`. Leave empty if it does not matter.
placeholder: "provider / model id"
- type: input
id: terminal
attributes:
label: Terminal and shell 终端
description: Only for terminal problems. Mention tmux, zellij, or screen if you use one.
placeholder: iTerm2 + zsh, Windows Terminal + PowerShell, VS Code terminal + bash
- type: textarea
id: doctor
attributes:
label: codewhale doctor (optional)
description: Run `codewhale doctor` and paste the relevant lines. Check it for secrets first.
render: text
- type: textarea
id: extra
attributes:
label: Logs, screenshots, anything else (optional)
description: Logs, a screenshot or recording, config snippets, or a guess at the cause.
-14
View File
@@ -1,14 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Report a security vulnerability
url: https://github.com/codewhale-hq/CodeWhale/security/advisories/new
about: Please report security problems privately, not in a public issue. See SECURITY.md.
- name: Roadmap and where to help
url: https://github.com/codewhale-hq/CodeWhale/blob/main/docs/ROADMAP.md
about: Where Codewhale is going, what each release covers, and good places to start. 路线图。
- name: Ask a question
url: https://github.com/codewhale-hq/CodeWhale/discussions
about: How-to questions and ideas you want to talk through. 提问和讨论。
- name: Discord
url: https://discord.gg/37gfS3ksug
about: Chat with the community and maintainers.
@@ -1,66 +0,0 @@
name: Feature request
description: Suggest an idea or an improvement. 功能建议。
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for the idea. English or 中文 are both fine. Short is good.
This is the best place to start a feature, even if you plan to build it yourself: agreeing on the shape here makes the pull request small and quick to merge. The [roadmap](https://github.com/codewhale-hq/CodeWhale/blob/main/docs/ROADMAP.md) shows what is already planned.
If someone already asked for this, add a 👍 to their issue instead.
Want to talk it through first? Use [Discussions](https://github.com/codewhale-hq/CodeWhale/discussions) or [Discord](https://discord.gg/37gfS3ksug).
- type: checkboxes
id: searched
attributes:
label: Before you start
options:
- label: I searched existing issues and this is not a duplicate. 我已搜索过现有 issue。
required: true
- type: textarea
id: problem
attributes:
label: What problem does this solve? 要解决什么问题?
description: What are you trying to do, and what gets in the way today?
validations:
required: true
- type: textarea
id: idea
attributes:
label: What should Codewhale do? 希望 Codewhale 怎么做?
description: The behavior you want. Commands, key presses, or config are welcome.
validations:
required: true
- type: dropdown
id: surface
attributes:
label: Where? 在哪里?
multiple: true
options:
- TUI (terminal interface)
- CLI command (codewhale exec, ...)
- Desktop app
- Web (codewhale web or app.codewhale.net)
- Plugins / extension host
- MCP
- Providers and models
- Docs
- Other / not sure
- type: textarea
id: alternatives
attributes:
label: Workarounds or other tools (optional)
description: What you do today, or how another tool handles it.
- type: checkboxes
id: contribute
attributes:
label: Contributing
options:
- label: I would like to open a pull request for this, once we agree on the shape. 我愿意在方案确定后提交 PR。
-25
View File
@@ -1,25 +0,0 @@
name: Feedback
description: Something looked, read or felt off, or you have a thought to share. 反馈与感受。
labels: ["needs-triage"]
body:
- type: markdown
attributes:
value: |
All feedback is welcome, and you do not need a fix or a plan. English or 中文 are both fine.
First impressions, confusing wording, an awkward step, a screen that looks wrong, something you liked: it all helps.
For something broken, the [bug report](https://github.com/codewhale-hq/CodeWhale/issues/new?template=bug_report.yml) form asks for the details we need to reproduce it.
- type: textarea
id: feedback
attributes:
label: What would you like us to know? 想告诉我们什么?
description: What you were doing, and what you noticed. A screenshot helps for anything visual.
validations:
required: true
- type: input
id: version
attributes:
label: Codewhale version (optional) 版本
description: Output of `codewhale --version`, if you have it.
-27
View File
@@ -1,27 +0,0 @@
<!-- Thanks for the PR! English or 中文 are both fine. Short is good. -->
## What and why
<!-- What does this change, and why? A few sentences is enough. -->
## Where this came from
<!-- Closes #N if this finishes an issue, Refs #N if related. Translations,
provider and model updates, docs fixes and bug fixes need no issue: just say
which it is. For a new feature with no issue yet, opening one first is the
faster path (CONTRIBUTING.md explains why); we still read this PR either way.
Please do not write a closing word inside a negation ("this does not close #N"):
GitHub closes the issue anyway. -->
## How I tested it
<!-- Commands you ran and what they showed. Test what you changed, for example
`scripts/dev-test.sh tui <filter>`. A few quick checks run on your PR; if one
fails for a reason unrelated to your change, a maintainer will deal with it.
For visible UI changes, add a screenshot or recording. -->
## Checklist
- [ ] One focused change, started from current `main` (no need to rebase when `main` moves)
- [ ] `cargo fmt --all` passes, and tests cover new or changed behavior
- [ ] If this adds or changes a user-facing feature, I updated its docs and its row in `docs/features.toml` (`cargo test -p codewhale-tui --test feature_registry` checks it)
-98
View File
@@ -1,98 +0,0 @@
# Security Policy
codewhale is a coding agent with direct access to file operations, shell execution, and the network. Security disclosures are taken seriously.
## Supported Versions
Only the latest stable release receives security patches. No backports to older versions.
| Version | Supported |
|---|---|
| latest stable | :white_check_mark: |
| < latest | :x: |
Check the [releases page](https://github.com/codewhale-hq/CodeWhale/releases) for the current version.
## Reporting a Vulnerability
**Do not open a public GitHub issue for security vulnerabilities.**
Report privately via one of:
- **GitHub private advisory**: [github.com/codewhale-hq/CodeWhale/security/advisories/new](https://github.com/codewhale-hq/CodeWhale/security/advisories/new)
- **Email**: [[email protected]](mailto:[email protected]) — include `[SECURITY]` in the subject line
Include in your report:
- A description of the vulnerability and the impact if exploited
- Steps to reproduce or a proof of concept
- Affected versions and configuration details
- Any suggested mitigation (optional)
## Response Timeline
| Phase | Target |
|---|---|
| Acknowledgment | Within 48 hours of receipt |
| Assessment | Within 5 days — triage severity, scope, and fix approach |
| Patch (critical) | Within 14 days from assessment |
| Patch (moderate/low) | Next feature release or per-maintainer timeline |
| Disclosure | After patch is shipped and users have had time to update |
You will receive status updates at each phase. If the timeline slips, we will communicate the reason and the revised estimate.
## Scope
### In scope (what counts)
- Remote code execution through crafted prompts or model responses
- Sandbox escape — breaking out of an active Seatbelt/bubblewrap wrapper or a declared workspace boundary
- Credential leak — exfiltration of API keys, tokens, or environment secrets
- Arbitrary file read/write outside the intended workspace (`PathEscape` bypass)
- SSRF via `fetch_url` or `web_search` against internal network endpoints
- Unauthorised MCP server access or tool invocation
### Out of scope
- Social engineering of the maintainer or contributors
- Denial of service / rate-limit exhaustion against the DeepSeek API
- Vulnerabilities in third-party dependencies (report to the upstream project)
- Attacks requiring physical access to the victim's machine
- Theoretical ML-model injection attacks not demonstrated in the codewhale context
If you are unsure whether a bug is in scope, report it anyway. We will triage and respond.
## WeCom Bridge Security
The WeCom Bridge (`integrations/wecom-bridge/`) extends Codewhale to WeCom
(企业微信) Smart Bot WebSocket sessions. It inherits all standard Codewhale
security boundaries and adds bridge-specific controls.
### Bridge-specific protections
- **No public port**: The bridge communicates with `codewhale serve --http` on `127.0.0.1` only
- **Token gate**: All runtime API calls carry `CODEWHALE_RUNTIME_TOKEN`
- **Chat allowlist**: Only chats/users listed in `WECOM_CHAT_ALLOWLIST` can interact. First-pairing mode (`WECOM_ALLOW_UNLISTED=true`) is meant for onboarding only
- **Approval required**: Tool calls from WeCom sessions must be approved — either via explicit `/allow <id>` commands or natural-language keywords (`允许`, `yes`, `ok`, etc.)
- **Thread ownership**: `/threads` and `/resume` cover only the threads the chat itself created; any other thread ID is refused with one fixed reply
- **No workspace exposure**: Only prompts, status summaries, and approval requests are sent to WeCom. Workspace contents, shell output, and runtime internals stay on the local machine
### Reporting WeCom Bridge vulnerabilities
Report bridge-specific security issues through the same channels listed above.
Include the bridge version (check `package.json`) and your WeCom deployment configuration
(sensitive values redacted). Bridge logs may be requested for reproduction.
### Bridge environment safety
- `WECOM_BOT_SECRET` and `CODEWHALE_RUNTIME_TOKEN` must never be committed to git
- The `.env` file is gitignored; use `.env.example` as the template
- Rotate secrets periodically, especially after sharing screen captures
- Use `CODEWHALE_APPROVAL_TIMEOUT_MS` (default 5 min) to limit the approval window
## Hall of Fame
We maintain a hall of fame for reporters who submit verified security vulnerabilities. To be credited, include your preferred name / handle in the report.
*No entries yet — be the first.*
-19
View File
@@ -1,19 +0,0 @@
# Getting help with Codewhale
- **A question about using Codewhale:** ask in
[Discussions Q&A](https://github.com/codewhale-hq/Codewhale/discussions/categories/q-a)
or on [Discord](https://discord.gg/37gfS3ksug). The
[user guide](../docs/GUIDE.md) and the [documentation index](../docs/README.md)
answer many questions already.
- **A bug, a crash or behavior that surprised you:** open a
[bug report](https://github.com/codewhale-hq/Codewhale/issues/new?template=bug_report.yml).
`codewhale doctor` prints the version and setup details a report needs.
- **An idea or feedback:** open an
[issue](https://github.com/codewhale-hq/Codewhale/issues/new/choose), or talk it
through first in
[Ideas](https://github.com/codewhale-hq/Codewhale/discussions/categories/ideas).
- **A security problem:** report it privately as described in the
[security policy](SECURITY.md), never in a public issue.
The [contributing guide](../CONTRIBUTING.md) explains where each kind of help
goes and how a pull request lands.
-33
View File
@@ -1,33 +0,0 @@
# CodeQL configuration for code scanning.
#
# Test code is out of scope for security alerts: it runs only in CI, talks to
# in-process loopback servers, and routinely prints fixture secrets in
# assertion messages to prove they are redacted elsewhere. Those paths are
# excluded here so alerts point at product code.
#
# Inline `#[cfg(test)] mod tests` blocks inside product files cannot be
# excluded by path; alerts there are dismissed as "used in tests".
#
# This file takes effect only with CodeQL advanced setup:
# .github/workflows/codeql.yml passes it to `github/codeql-action/init`.
# Default setup ignores it, so the repository's code scanning setting must be
# switched from Default to Advanced for either to apply.
name: codewhale-codeql
paths-ignore:
# Rust integration tests and split-out unit test modules.
- "**/tests/**"
- "**/tests.rs"
- "**/*_tests.rs"
- "**/test_support.rs"
- "**/*_test_support.rs"
# JavaScript / TypeScript test suites.
- "**/test/**"
- "**/__tests__/**"
- "**/*.test.js"
- "**/*.test.mjs"
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.spec.js"
- "**/*.spec.ts"
- "**/*.spec.tsx"
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

-34
View File
@@ -1,34 +0,0 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
groups:
# `windows` re-exports windows-core's types; bumping one alone makes
# BOOL/HRESULT two distinct types and the Windows leg stops compiling.
windows:
patterns:
- "windows"
- "windows-*"
ignore:
# rusty_alloc 2.x does not compile on macOS: its unix backend passes
# `&mut u8` where macOS libc's `mincore` expects `*mut c_char` (seen on
# #6599). The dependency is opt-in behind the `rusty-alloc` feature.
# Drop this once upstream fixes the macOS signature.
- dependency-name: "rusty_alloc-api"
versions: [">= 2"]
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
- package-ecosystem: nix
directory: /
schedule:
interval: monthly
- package-ecosystem: npm
directory: /web
schedule:
interval: monthly
open-pull-requests-limit: 3
+37
View File
@@ -0,0 +1,37 @@
{
"outputs": {
"codex-repl": {
"platforms": {
"macos-aarch64": { "regex": "^codex-repl-aarch64-apple-darwin\\.zst$", "path": "codex-repl" },
"macos-x86_64": { "regex": "^codex-repl-x86_64-apple-darwin\\.zst$", "path": "codex-repl" },
"linux-x86_64": { "regex": "^codex-repl-x86_64-unknown-linux-musl\\.zst$", "path": "codex-repl" },
"linux-aarch64": { "regex": "^codex-repl-aarch64-unknown-linux-gnu\\.zst$", "path": "codex-repl" }
}
},
"codex-exec": {
"platforms": {
"macos-aarch64": { "regex": "^codex-exec-aarch64-apple-darwin\\.zst$", "path": "codex-exec" },
"macos-x86_64": { "regex": "^codex-exec-x86_64-apple-darwin\\.zst$", "path": "codex-exec" },
"linux-x86_64": { "regex": "^codex-exec-x86_64-unknown-linux-musl\\.zst$", "path": "codex-exec" },
"linux-aarch64": { "regex": "^codex-exec-aarch64-unknown-linux-gnu\\.zst$", "path": "codex-exec" }
}
},
"codex": {
"platforms": {
"macos-aarch64": { "regex": "^codex-aarch64-apple-darwin\\.zst$", "path": "codex" },
"macos-x86_64": { "regex": "^codex-x86_64-apple-darwin\\.zst$", "path": "codex" },
"linux-x86_64": { "regex": "^codex-x86_64-unknown-linux-musl\\.zst$", "path": "codex" },
"linux-aarch64": { "regex": "^codex-aarch64-unknown-linux-gnu\\.zst$", "path": "codex" }
}
},
"codex-linux-sandbox": {
"platforms": {
"linux-x86_64": { "regex": "^codex-linux-sandbox-x86_64-unknown-linux-musl\\.zst$", "path": "codex-linux-sandbox" },
"linux-aarch64": { "regex": "^codex-linux-sandbox-aarch64-unknown-linux-gnu\\.zst$", "path": "codex-linux-sandbox" }
}
}
}
}
@@ -1,30 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "${repo}"
template=".github/ISSUE_TEMPLATE/agent-task.yml"
retired_workflows=(
".github/workflows/v0868-milestone-sync.yml"
".github/workflows/agent-task-labels.yml"
)
fail() {
echo "agent-task metadata contract failed: $*" >&2
exit 1
}
[[ -f "${template}" ]] || fail "missing ${template}"
for retired in "${retired_workflows[@]}"; do
[[ ! -e "${retired}" ]] || fail "retired workflow still exists: ${retired}"
done
if grep -nE '^(title|labels):.*v[0-9]+\.[0-9]+\.[0-9]+' "${template}"; then
fail "agent-task defaults must not pin a release version"
fi
grep -qF 'labels: ["agent-ready"]' "${template}" \
|| fail "agent-task template must opt into agent-ready without a version label"
echo "Agent-task metadata is release-neutral and milestone-safe."
-83
View File
@@ -1,83 +0,0 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { spawnSync } = require("node:child_process");
const { test } = require("node:test");
const sourceRoot = path.resolve(__dirname, "../..");
function fixture(t) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "cw-d8-launch-"));
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const repo = path.join(root, "source");
fs.mkdirSync(path.join(repo, "scripts"), { recursive: true });
for (const name of ["dev-test.sh", "dev-cache.sh", "dev-cargo.sh", "with-hermetic-test-home.sh", "build-lock.py"]) {
fs.copyFileSync(path.join(sourceRoot, "scripts", name), path.join(repo, "scripts", name));
fs.chmodSync(path.join(repo, "scripts", name), 0o755);
}
fs.writeFileSync(path.join(repo, "Cargo.toml"), '[workspace]\nmembers = []\n');
const bin = path.join(root, "bin");
const home = path.join(root, "ambient home");
fs.mkdirSync(bin); fs.mkdirSync(home);
const log = path.join(root, "cargo-invocations.jsonl");
fs.writeFileSync(path.join(bin, "cargo"), `#!${process.execPath}
const fs = require("node:fs");
const args = process.argv.slice(2);
if (args.includes("--version")) { console.log("cargo 1.97.0 (source fixture)"); process.exit(0); }
fs.appendFileSync(process.env.D8_INVOCATIONS, JSON.stringify({args, home: process.env.HOME, cache: process.env.CODEWHALE_CACHE_ROOT}) + "\\n");
if (args.includes("build") && process.env.D8_BUILD_FAILURE) process.exit(Number(process.env.D8_BUILD_FAILURE));
console.log("test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s");
`);
fs.writeFileSync(path.join(bin, "rustc"), `#!/bin/sh\nprintf '%s\n' 'rustc 1.97.0 (source fixture)' 'commit-hash: source-fixture'\n`);
fs.writeFileSync(path.join(bin, "rustup"), `#!/bin/sh\nif [ "$1" = which ] && [ "$2" = rustc ]; then printf '%s\n' "$D8_TOOLCHAIN/rustc"; else exit 99; fi\n`);
for (const name of ["cargo", "rustc", "rustup"]) fs.chmodSync(path.join(bin, name), 0o755);
return {
home, log,
run(area, failure) {
const result = spawnSync("sh", [path.join(repo, "scripts/dev-test.sh"), area, "filter with spaces"], {
cwd: repo, encoding: "utf8",
env: { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}`, HOME: home,
USERPROFILE: home, CARGO_HOME: path.join(root, "cargo-home"), RUSTUP_HOME: path.join(root, "rustup-home"),
CODEWHALE_DEV_NEXTEST: "0", CODEWHALE_BUILD_LOCK: "0", CODEWHALE_BUILD_LOCK_HELD: "",
CODEWHALE_CACHE_ROOT: path.join(root, "cache"), D8_INVOCATIONS: log, D8_TOOLCHAIN: bin,
D8_BUILD_FAILURE: failure ? String(failure) : "" },
});
const calls = fs.existsSync(log) ? fs.readFileSync(log, "utf8").trim().split("\n").map(JSON.parse) : [];
return { result, calls };
},
};
}
for (const area of ["tui-integration", "tui-cucumber"]) {
test(`${area} builds the canonical executable before isolated acceptance`, (t) => {
const f = fixture(t); const { result, calls } = f.run(area);
assert.equal(result.status, 0, result.stderr + result.stdout);
assert.equal(calls.length, 2);
const build = calls[0].args.slice(calls[0].args.indexOf("build"));
assert.deepEqual(build, ["build", "-p", "codewhale-cli", "--bin", "codewhale", "--locked"]);
const check = calls[1].args.slice(calls[1].args.indexOf("test"));
assert.deepEqual(check, ["test", "-p", "codewhale-tui", "--test", area === "tui-integration" ? "integration" : "cucumber", "--locked", "filter with spaces"]);
for (const call of calls) {
assert.notEqual(call.home, f.home);
assert.equal(fs.existsSync(call.home), false, "temporary test HOME is removed");
assert.ok(call.cache.endsWith("cache"));
}
});
}
test("canonical build failure prevents acceptance and preserves its exit status", (t) => {
const f = fixture(t); const { result, calls } = f.run("tui-integration", 39);
assert.equal(result.status, 39, result.stderr + result.stdout);
assert.equal(calls.length, 1);
assert.ok(calls[0].args.includes("build"));
});
test("library-only verification does not build an executable", (t) => {
const f = fixture(t); const { result, calls } = f.run("config");
assert.equal(result.status, 0, result.stderr + result.stdout);
assert.equal(calls.length, 1);
assert.ok(calls[0].args.includes("test"));
assert.ok(calls[0].args.includes("--lib"));
});
File diff suppressed because it is too large. Load diff
-168
View File
@@ -1,168 +0,0 @@
#!/usr/bin/env bash
# Update the Homebrew tap after a release.
#
# The tap GitHub repo is still Hmbown/homebrew-deepseek-tui until Hunter
# renames it. The formula users type is `codewhale`. The legacy
# `deepseek-tui` formula stays as a deprecated alias for one overlap release.
#
# Expected environment:
# TAG – git tag, e.g. "v0.8.31"
# MANIFEST – path to codewhale-artifacts-sha256.txt
# TAP_REPO – owner/repo of the Homebrew tap
# TOKEN – PAT with contents:write on TAP_REPO (required unless
# FORMULA_OUTPUT renders locally for contract tests)
# FORMULA_OUTPUT – optional local render path used by contract tests
# FORMULA_LEGACY_OUTPUT – optional local render path for the alias formula
set -euo pipefail
: "${TAG:?}"
: "${MANIFEST:?}"
: "${TAP_REPO:?}"
if [ -z "${TOKEN:-}" ] && [ -z "${FORMULA_OUTPUT:-}" ]; then
echo "::error::No Homebrew tap token (TOKEN) configured; refusing to skip the tap update." >&2
exit 1
fi
VERSION="${TAG#v}"
die() { echo "::error::${1}" >&2; exit 1; }
sha() {
local file="${1:?}"
local val
val="$(awk -v f="${file}" '$2 == f {print $1; exit}' "${MANIFEST}")"
if [ -z "${val}" ]; then
die "Missing binary in checksum manifest: ${file}"
fi
echo "${val}"
}
# --- read checksums ---------------------------------------------------
# One compiled runtime exposed under the two supported command names.
SHA_COD_MACOS_ARM="$(sha codewhale-macos-arm64)"
SHA_CODEW_MACOS_ARM="$(sha codew-macos-arm64)"
SHA_COD_MACOS_X64="$(sha codewhale-macos-x64)"
SHA_CODEW_MACOS_X64="$(sha codew-macos-x64)"
SHA_COD_LINUX_ARM="$(sha codewhale-linux-arm64)"
SHA_CODEW_LINUX_ARM="$(sha codew-linux-arm64)"
SHA_COD_LINUX_X64="$(sha codewhale-linux-x64)"
SHA_CODEW_LINUX_X64="$(sha codew-linux-x64)"
readonly SHA_COD_MACOS_ARM SHA_CODEW_MACOS_ARM
readonly SHA_COD_MACOS_X64 SHA_CODEW_MACOS_X64
readonly SHA_COD_LINUX_ARM SHA_CODEW_LINUX_ARM
readonly SHA_COD_LINUX_X64 SHA_CODEW_LINUX_X64
# --- temp dirs --------------------------------------------------------
FORMULA_FILE="$(mktemp)"
LEGACY_FILE="$(mktemp)"
TAP_DIR="$(mktemp -d)"
trap 'rm -rf "${TAP_DIR}" "${FORMULA_FILE}" "${LEGACY_FILE}"' EXIT
readonly BASE_URL="https://github.com/codewhale-hq/CodeWhale/releases/download/${TAG}"
render_formula() {
local class_name="${1:?}"
local extra_header="${2:-}"
cat << EOF
class ${class_name} < Formula
desc "Agentic terminal for open-source and open-weight coding models"
homepage "https://github.com/codewhale-hq/CodeWhale"
version "${VERSION}"
license "MIT"
depends_on "node"
${extra_header}
on_macos do
if Hardware::CPU.arm?
url "${BASE_URL}/codewhale-macos-arm64", using: :nounzip
sha256 "${SHA_COD_MACOS_ARM}"
resource "codew" do
url "${BASE_URL}/codew-macos-arm64", using: :nounzip
sha256 "${SHA_CODEW_MACOS_ARM}"
end
else
url "${BASE_URL}/codewhale-macos-x64", using: :nounzip
sha256 "${SHA_COD_MACOS_X64}"
resource "codew" do
url "${BASE_URL}/codew-macos-x64", using: :nounzip
sha256 "${SHA_CODEW_MACOS_X64}"
end
end
end
on_linux do
if Hardware::CPU.arm?
url "${BASE_URL}/codewhale-linux-arm64", using: :nounzip
sha256 "${SHA_COD_LINUX_ARM}"
resource "codew" do
url "${BASE_URL}/codew-linux-arm64", using: :nounzip
sha256 "${SHA_CODEW_LINUX_ARM}"
end
else
url "${BASE_URL}/codewhale-linux-x64", using: :nounzip
sha256 "${SHA_COD_LINUX_X64}"
resource "codew" do
url "${BASE_URL}/codew-linux-x64", using: :nounzip
sha256 "${SHA_CODEW_LINUX_X64}"
end
end
end
def install
bin.install Dir["*"].first => "codewhale"
resource("codew").stage { bin.install Dir["*"].first => "codew" }
end
test do
system "#{bin}/codewhale", "--version"
system "#{bin}/codew", "--version"
end
end
EOF
}
render_formula "Codewhale" "" > "${FORMULA_FILE}"
render_formula "DeepseekTui" " deprecate! date: \"2026-08-14\", because: \"renamed to codewhale\"
" > "${LEGACY_FILE}"
if [ -n "${FORMULA_OUTPUT:-}" ]; then
cp "${FORMULA_FILE}" "${FORMULA_OUTPUT}"
echo "Rendered Homebrew formula to ${FORMULA_OUTPUT}"
if [ -n "${FORMULA_LEGACY_OUTPUT:-}" ]; then
cp "${LEGACY_FILE}" "${FORMULA_LEGACY_OUTPUT}"
echo "Rendered legacy Homebrew formula to ${FORMULA_LEGACY_OUTPUT}"
fi
exit 0
fi
# --- push to tap repo --------------------------------------------------
ENCODED_TOKEN="$(printf '%s' "${TOKEN}" | python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.stdin.read(),safe=""))')"
TAP_URL="https://x-access-token:${ENCODED_TOKEN}@github.com/${TAP_REPO}.git"
git clone --depth 1 "${TAP_URL}" "${TAP_DIR}"
mkdir -p "${TAP_DIR}/Formula"
cp "${FORMULA_FILE}" "${TAP_DIR}/Formula/codewhale.rb"
cp "${LEGACY_FILE}" "${TAP_DIR}/Formula/deepseek-tui.rb"
cd "${TAP_DIR}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/codewhale.rb Formula/deepseek-tui.rb
if git diff --cached --quiet; then
echo "Formula unchanged (already at ${VERSION}); nothing to push."
exit 0
fi
git commit -m "chore: bump formula to ${VERSION}
Automated update from the release workflow."
git push origin HEAD:main
echo "Pushed formula update to ${TAP_REPO} (v${VERSION})"
@@ -1,62 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
tmp_dir="$(mktemp -d)"
trap 'rm -rf "${tmp_dir}"' EXIT
manifest="${tmp_dir}/codewhale-artifacts-sha256.txt"
formula="${tmp_dir}/codewhale.rb"
legacy="${tmp_dir}/deepseek-tui.rb"
assets=(
codewhale-macos-arm64
codew-macos-arm64
codewhale-macos-x64
codew-macos-x64
codewhale-linux-arm64
codew-linux-arm64
codewhale-linux-x64
codew-linux-x64
)
for asset in "${assets[@]}"; do
printf '%064d %s\n' 0 "${asset}" >> "${manifest}"
done
TAG=v1.2.3 \
MANIFEST="${manifest}" \
TAP_REPO=Hmbown/homebrew-deepseek-tui \
FORMULA_OUTPUT="${formula}" \
FORMULA_LEGACY_OUTPUT="${legacy}" \
bash "${repo_root}/.github/scripts/update-homebrew-tap.sh"
ruby -c "${formula}" >/dev/null
ruby -c "${legacy}" >/dev/null
grep -Fq 'class Codewhale < Formula' "${formula}"
grep -Fq 'class DeepseekTui < Formula' "${legacy}"
grep -Fq 'deprecate! date: "2026-08-14", because: "renamed to codewhale"' "${legacy}"
grep -Fq 'desc "Agentic terminal for open-source and open-weight coding models"' "${formula}"
grep -Fq 'depends_on "node"' "${formula}"
test "$(grep -Fc 'resource "codew" do' "${formula}")" -eq 4
grep -Fq 'bin.install Dir["*"].first => "codew"' "${formula}"
grep -Fq 'system "#{bin}/codew", "--version"' "${formula}"
if grep -Fq 'codewhale-tui' "${formula}"; then
echo "Homebrew formula must not install the legacy TUI compatibility asset" >&2
exit 1
fi
if grep -Fq 'class DeepseekTui' "${formula}"; then
echo "Primary Homebrew formula must be Codewhale, not DeepseekTui" >&2
exit 1
fi
# Without a token and without a local render target the script must fail,
# never report a skipped tap update as success.
if TAG=v1.2.3 MANIFEST="${manifest}" TAP_REPO=Hmbown/homebrew-deepseek-tui TOKEN= \
bash "${repo_root}/.github/scripts/update-homebrew-tap.sh" >"${tmp_dir}/no-token.log" 2>&1; then
echo "update-homebrew-tap.sh must fail when no tap token is configured" >&2
exit 1
fi
grep -Fq 'No Homebrew tap token' "${tmp_dir}/no-token.log"
echo "update-homebrew-tap tests passed"
-176
View File
@@ -1,176 +0,0 @@
name: Create release tag
# Manual helper that creates `vX.Y.Z` from the current `main` workspace
# version after the release source is frozen. This is intentionally not
# triggered by every version bump on `main`: release prep can land before the
# same-version issue/PR queue is truly empty, and an eager tag leaves later
# same-version work outside the public release anchor.
#
# IMPORTANT: tag pushes signed by the default `GITHUB_TOKEN` do NOT trigger
# downstream `on: push: tags` workflows (GitHub Actions safety rule). For
# this auto-tag flow to actually fire `release.yml`, store a PAT (or
# fine-grained token) with `contents: write` on this repo as the
# `RELEASE_TAG_PAT` secret. Without it, the tag is created but `release.yml`
# does NOT run automatically; run `release.yml` manually for the version.
on:
workflow_dispatch:
permissions:
contents: write
concurrency:
group: auto-tag-${{ github.ref_name }}
cancel-in-progress: false
jobs:
tag:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
ref: ${{ github.sha }}
# Prefer PAT so the resulting tag push triggers release.yml.
# Falls back to GITHUB_TOKEN, which will tag but NOT trigger.
token: ${{ secrets.RELEASE_TAG_PAT || github.token }}
- name: Require main dispatch
id: source
run: |
set -euo pipefail
if [ "${GITHUB_REF_NAME}" != "main" ]; then
echo "::error::Create release tags from main only; rerun this workflow with ref 'main'." >&2
exit 1
fi
intended_sha="$(git rev-parse 'HEAD^{commit}')"
event_sha="$(git rev-parse "${GITHUB_SHA}^{commit}")"
if [ "${intended_sha}" != "${event_sha}" ]; then
echo "::error::Checkout ${intended_sha} does not match dispatch SHA ${event_sha}." >&2
exit 1
fi
git fetch --no-tags origin "+refs/heads/main:refs/remotes/origin/main"
live_main="$(git rev-parse 'refs/remotes/origin/main^{commit}')"
if [ "${intended_sha}" != "${live_main}" ]; then
echo "::error::Dispatch SHA ${intended_sha} is stale; live origin/main is ${live_main}. Rerun from the current main ref." >&2
exit 1
fi
echo "sha=${intended_sha}" >> "${GITHUB_OUTPUT}"
- name: Read workspace version
id: ver
run: |
v="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
if [ -z "$v" ]; then
echo "::error::Could not parse workspace version from Cargo.toml" >&2
exit 1
fi
if ! echo "$v" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
echo "::error::Workspace version '$v' is not valid semver (expected X.Y.Z or X.Y.Z-prerelease)" >&2
exit 1
fi
echo "version=$v" >> "$GITHUB_OUTPUT"
echo "tag=v$v" >> "$GITHUB_OUTPUT"
echo "Workspace version: $v"
- name: Check whether tag already exists
id: check
env:
EXPECTED_SHA: ${{ steps.source.outputs.sha }}
TAG: ${{ steps.ver.outputs.tag }}
run: |
set -euo pipefail
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk 'NR == 1 {print $1}')"
if [ -z "${remote_tag_sha}" ]; then
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}" | awk 'NR == 1 {print $1}')"
fi
if [ -n "${remote_tag_sha}" ]; then
if [ "${remote_tag_sha}" != "${EXPECTED_SHA}" ]; then
echo "::error::Tag ${TAG} already exists at ${remote_tag_sha}, expected ${EXPECTED_SHA}." >&2
exit 1
fi
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Tag ${TAG} already exists at the intended SHA; nothing to do."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Tag ${TAG} does not exist; will create."
fi
- name: Verify version consistency
if: steps.check.outputs.exists == 'false'
run: |
./scripts/release/check-versions.sh --require-dated-release || {
echo "::error::Version consistency check failed. Aborting tag creation." >&2
exit 1
}
- name: Create and push tag
id: create
if: steps.check.outputs.exists == 'false'
env:
EXPECTED_SHA: ${{ steps.source.outputs.sha }}
TAG: ${{ steps.ver.outputs.tag }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git fetch --no-tags origin "+refs/heads/main:refs/remotes/origin/main"
live_main="$(git rev-parse 'refs/remotes/origin/main^{commit}')"
if [ "${live_main}" != "${EXPECTED_SHA}" ]; then
echo "::error::origin/main advanced to ${live_main}; refusing to tag stale ${EXPECTED_SHA}." >&2
exit 1
fi
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk 'NR == 1 {print $1}')"
if [ -z "${remote_tag_sha}" ]; then
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}" | awk 'NR == 1 {print $1}')"
fi
if [ -n "${remote_tag_sha}" ]; then
if [ "${remote_tag_sha}" != "${EXPECTED_SHA}" ]; then
echo "::error::Tag ${TAG} appeared at ${remote_tag_sha}, expected ${EXPECTED_SHA}." >&2
exit 1
fi
echo "pushed=false" >> "$GITHUB_OUTPUT"
echo "Tag ${TAG} already exists at the intended SHA after refresh; nothing to do."
exit 0
fi
git tag -f "${TAG}" "${EXPECTED_SHA}"
max_retries=3
retry_count=0
while [ "${retry_count}" -lt "${max_retries}" ]; do
if git push origin "${TAG}"; then
echo "pushed=true" >> "$GITHUB_OUTPUT"
echo "Pushed ${TAG}. release.yml should now run (requires RELEASE_TAG_PAT for trigger)."
exit 0
fi
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}^{}" | awk 'NR == 1 {print $1}')"
if [ -z "${remote_tag_sha}" ]; then
remote_tag_sha="$(git ls-remote --tags origin "refs/tags/${TAG}" | awk 'NR == 1 {print $1}')"
fi
if [ -n "${remote_tag_sha}" ]; then
if [ "${remote_tag_sha}" != "${EXPECTED_SHA}" ]; then
echo "::error::Tag ${TAG} appeared during push at ${remote_tag_sha}, expected ${EXPECTED_SHA}." >&2
exit 1
fi
echo "pushed=false" >> "$GITHUB_OUTPUT"
echo "Tag ${TAG} appeared at the intended SHA during push; treating as already handled."
exit 0
fi
retry_count=$((retry_count + 1))
if [ "${retry_count}" -lt "${max_retries}" ]; then
echo "Push attempt ${retry_count} failed; retrying in 10s..."
sleep 10
fi
done
echo "::error::Failed to push tag ${TAG} after ${max_retries} attempts." >&2
exit 1
- name: Warn if PAT missing
if: steps.create.outputs.pushed == 'true'
env:
HAS_PAT: ${{ secrets.RELEASE_TAG_PAT != '' }}
run: |
if [ "${HAS_PAT}" != "true" ]; then
echo "::warning::RELEASE_TAG_PAT secret is not set. The tag was pushed using GITHUB_TOKEN, which does NOT trigger release.yml. First confirm no tag-triggered Release run is queued or active, then run 'gh workflow run release.yml --ref ${{ steps.ver.outputs.tag }} -f version=${{ steps.ver.outputs.version }}'."
fi
-82
View File
@@ -1,82 +0,0 @@
name: Cache janitor
# The Actions cache held 11,099,951,127 bytes across 2,896 entries, past the
# repo's 10 GiB cap, so GitHub evicted live main entries first. A cache is
# readable only from its own ref and the default branch: once a PR closes or
# a release finishes, its refs/pull/N or refs/tags/vX entries are dead weight.
# This deletes them. Branch caches (main included) are never touched; see
# scripts/release/prune-actions-caches.sh.
on:
# pull_request_target so fork PRs get a token that can delete caches. It
# never checks out or runs PR code: the checkout below is the base branch.
pull_request_target:
types: [closed]
workflow_run:
workflows: [Release]
types: [completed]
schedule:
- cron: '17 4 * * *'
workflow_dispatch:
inputs:
dry_run:
description: List what the sweep would delete without deleting it
required: false
default: true
type: boolean
permissions:
contents: read
concurrency:
group: cache-janitor-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.workflow_run.id || 'sweep' }}
cancel-in-progress: false
jobs:
prune:
name: Prune dead caches
if: github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
timeout-minutes: 15
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
# Read PR state for the sweep.
pull-requests: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
# Base-branch script only. Never the PR head.
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- name: Prune
shell: bash
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
script=scripts/release/prune-actions-caches.sh
case "${EVENT_NAME}" in
pull_request_target)
"${script}" --ref "refs/pull/${PR_NUMBER}/merge" --ref "refs/pull/${PR_NUMBER}/head"
;;
workflow_run)
# A tag-push Release run reports the tag as head_branch. The
# script refuses anything that is not a refs/tags/<tag> ref.
"${script}" --ref "refs/tags/${RUN_HEAD_BRANCH}"
;;
workflow_dispatch)
if [[ "${DRY_RUN}" == "true" ]]; then
"${script}" --dry-run --sweep
else
"${script}" --sweep
fi
;;
*)
"${script}" --sweep
;;
esac
-39
View File
@@ -1,39 +0,0 @@
name: cargo-deny
permissions: {}
on:
workflow_dispatch: {}
workflow_call: {}
env:
CARGO_TERM_COLOR: always
jobs:
deny:
name: cargo-deny
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
checks:
- advisories
- bans licenses sources
# Prevent sudden announcement of a new advisory from failing CI
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@v7
# rust-toolchain.toml pins `channel = "1.100.0"` with no target, so
# inside the action's alpine (musl) container rustup resolves it to
# `1.100.0-x86_64-unknown-linux-musl`, which the container does not
# have. Without provisioning, the action's `rustup show` step prints
# "error: override toolchain '1.100.0-x86_64-unknown-linux-musl' is not
# installed" and then auto-installs mid-run (network-dependent ~12s
# detour, hard failure if the download stalls). `rust-version` makes the
# entrypoint run `rustup default 1.100.0` (same musl host triple) before
# cargo-deny touches the workspace, so the toolchain file resolves to an
# already-installed toolchain on every run.
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check ${{ matrix.checks }}
rust-version: 1.100.0
-133
View File
@@ -1,133 +0,0 @@
name: Catalog refresh
on:
schedule:
- cron: '17 5 * * *'
workflow_dispatch:
permissions: {}
concurrency:
group: catalog-refresh
cancel-in-progress: false
jobs:
refresh:
name: Refresh the model catalog
if: github.repository == 'codewhale-hq/Codewhale'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
env:
BRANCH: catalog-refresh
steps:
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510
id: app-token
with:
app-id: ${{ vars.CODEWHALE_APP_ID }}
private-key: ${{ secrets.CODEWHALE_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: main
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
- name: Fetch models.dev once
run: |
curl --fail --silent --show-error --location --max-time 60 \
--user-agent "CodeWhale-catalog-automation (+https://github.com/codewhale-hq/Codewhale)" \
--output "$RUNNER_TEMP/models-dev-catalog.json" \
https://models.dev/catalog.json
- name: Decide whether the pin is due
id: pin
run: |
if python3 scripts/catalog_models_dev.py seed age --max-days 7; then
echo "due=false" >> "$GITHUB_OUTPUT"
else
echo "due=true" >> "$GITHUB_OUTPUT"
fi
- name: Propose new models, re-lock and render
env:
CODEWHALE_MODELS_DEV_PATH: ${{ runner.temp }}/models-dev-catalog.json
run: |
python3 scripts/catalog_models_dev.py seed discover --write --snapshot "$CODEWHALE_MODELS_DEV_PATH" | tee "$RUNNER_TEMP/discover.txt"
python3 scripts/catalog_models_dev.py seed lock > "$RUNNER_TEMP/lock-report.txt"
python3 scripts/catalog_models_dev.py seed render
(cd web && node scripts/derive-facts.mjs)
- name: Decide whether anything material changed
id: change
run: |
material=$(git diff -U0 -- \
crates/config/assets/models_dev.bundled.json \
crates/config/assets/catalog_corrections.json \
scripts/catalog/models_dev_seed.toml \
| grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' | grep -v '"upstream":' || true)
if [ -n "$material" ] || [ "${{ steps.pin.outputs.due }}" = "true" ]; then
echo "open=true" >> "$GITHUB_OUTPUT"
else
echo "open=false" >> "$GITHUB_OUTPUT"
echo "No catalog change and the pin is under 7 days old; nothing to propose."
fi
- name: Run the catalog checks
id: checks
if: steps.change.outputs.open == 'true'
run: |
set +e
{
python3 scripts/catalog_models_dev.py seed render --check
python3 scripts/catalog_models_dev.py seed age --max-days 30
python3 -m unittest scripts/catalog_models_dev_test.py
(cd web && node scripts/check-facts.mjs)
} > "$RUNNER_TEMP/checks.txt" 2>&1
status=$?
if [ "$status" -eq 0 ]; then echo "ok=true" >> "$GITHUB_OUTPUT"; else echo "ok=false" >> "$GITHUB_OUTPUT"; fi
tail -n 40 "$RUNNER_TEMP/checks.txt"
- name: Push the branch and open or update the pull request
if: steps.change.outputs.open == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
CHECKS_OK: ${{ steps.checks.outputs.ok }}
APP_ID: ${{ vars.CODEWHALE_APP_ID }}
run: |
git config user.name 'codewhale-agent[bot]'
git config user.email "${APP_ID}+codewhale-agent[bot]@users.noreply.github.com"
git checkout -B "$BRANCH"
git add crates/config/assets/models_dev.bundled.json crates/config/assets/catalog_corrections.json \
scripts/catalog/models_dev_seed.toml scripts/catalog/models_dev_seed.lock.json web/lib/facts.generated.ts
git commit -m "chore(catalog): refresh the Models.dev offline seed" \
-m "Generated by .github/workflows/catalog-refresh.yml from models.dev. No LLM wrote these rows."
git push --force-with-lease="$BRANCH" \
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:refs/heads/$BRANCH"
{
echo "Generated by the scheduled catalog refresh from models.dev. No LLM wrote these rows; review them like any other change."
echo
echo "New models from the vendor allowlist. Merging this PR publishes them on the public /models page; rows carry an \`unreviewed\` source until someone checks them against the vendor's docs:"
echo
echo '```text'
cat "$RUNNER_TEMP/discover.txt"
echo '```'
echo
echo "Checks run in the job: \`seed render --check\`, \`seed age --max-days 30\`, \`scripts/catalog_models_dev_test.py\`, \`web/scripts/check-facts.mjs\`: $([ "$CHECKS_OK" = true ] && echo passed || echo FAILED, see the job log; this PR is a draft)."
echo
echo "To stop proposing a model, add its id to \`[discover].skip\` in \`scripts/catalog/models_dev_seed.toml\`."
} > "$RUNNER_TEMP/pr-body.md"
title="chore(catalog): refresh the Models.dev offline seed"
existing=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty')
if [ -n "$existing" ]; then
gh pr edit "$existing" --title "$title" --body-file "$RUNNER_TEMP/pr-body.md"
if [ "$CHECKS_OK" = true ]; then
gh pr ready "$existing"
else
gh pr ready --undo "$existing"
fi
else
draft=""
[ "$CHECKS_OK" = true ] || draft="--draft"
gh pr create --base main --head "$BRANCH" --title "$title" --body-file "$RUNNER_TEMP/pr-body.md" $draft
fi
+54 -1437
View File
File diff suppressed because it is too large. Load diff
+29
View File
@@ -0,0 +1,29 @@
name: CLA Assistant
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened, closed, synchronize]
permissions:
actions: write
contents: write
pull-requests: write
statuses: write
jobs:
cla:
runs-on: ubuntu-latest
steps:
- uses: contributor-assistant/[email protected]
if: |
github.event_name == 'pull_request_target' ||
github.event.comment.body == 'recheck' ||
github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path-to-document: docs/CLA.md
path-to-signatures: signatures/cla.json
branch: cla-signatures
allowlist: dependabot[bot]
-61
View File
@@ -1,61 +0,0 @@
# CodeQL advanced setup.
#
# Scans the same languages the repository's default setup scanned (Actions,
# JavaScript/TypeScript, Python, Rust) with the same default query suite, but
# reads .github/codeql/codeql-config.yml so test paths stay out of alerts.
#
# Gated off by default. While the repository uses CodeQL "Default" setup,
# GitHub rejects SARIF uploads from this workflow ("Code Scanning could not
# process the submitted SARIF"), so the analyze job runs only when the
# repository variable CODEQL_ADVANCED_SETUP is 'true'. The founder flips it
# after switching the repository to "Advanced" setup (Settings -> Code
# security -> Code scanning); until then every run skips the job.
name: CodeQL
on:
workflow_dispatch:
permissions: {}
concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
analyze:
name: Analyze (${{ matrix.language }})
if: vars.CODEQL_ADVANCED_SETUP == 'true'
runs-on: ubuntu-latest
timeout-minutes: 120
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: javascript-typescript
build-mode: none
- language: python
build-mode: none
- language: rust
build-mode: none
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
config-file: ./.github/codeql/codeql-config.yml
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v4
with:
category: /language:${{ matrix.language }}
-119
View File
@@ -1,119 +0,0 @@
name: Codewhale PR Review
on:
workflow_dispatch:
inputs:
pr-number:
description: Pull request number
required: true
type: string
permissions: {}
concurrency:
group: codewhale-review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr-number }}
cancel-in-progress: false
jobs:
installation:
if: >-
github.actor != 'dependabot[bot]' &&
((github.event_name == 'workflow_dispatch') ||
(github.event_name == 'pull_request' && !github.event.pull_request.draft && github.event.pull_request.head.repo.full_name == github.repository) ||
((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
(github.event.issue.pull_request || github.event.pull_request) &&
github.event.comment.user.type == 'User' && contains(github.event.comment.body, '@codewhale')))
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510
id: app-token
with:
app-id: ${{ vars.CODEWHALE_APP_ID }}
private-key: ${{ secrets.CODEWHALE_APP_PRIVATE_KEY }}
permission-contents: read
permission-pull-requests: write
review:
needs: installation
if: >-
github.actor != 'dependabot[bot]' &&
((github.event_name == 'workflow_dispatch') ||
(github.event_name == 'pull_request' && !github.event.pull_request.draft && github.event.pull_request.head.repo.full_name == github.repository) ||
((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
(github.event.issue.pull_request || github.event.pull_request) &&
github.event.comment.user.type == 'User' && contains(github.event.comment.body, '@codewhale')))
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: read
pull-requests: read
outputs:
findings: ${{ steps.review.outputs.findings }}
steps:
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
with:
node-version: '22'
- uses: codewhale-hq/Codewhale@b0b2b9a335cc1d24abe8a087189bed080d1cc3db
id: review
with:
version: 'v0.10.2'
provider: 'deepseek'
model: 'deepseek-flash'
reasoning-effort: 'off'
max-output-tokens: '16384'
pr-number: ${{ inputs.pr-number }}
trigger-phrase: '@codewhale'
allow-forks: 'true'
post: 'false'
env:
DEEPSEEK_API_KEY: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'issue_comment' || github.event.pull_request.head.repo.full_name == github.repository) && secrets.DEEPSEEK_API_KEY || '' }}
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
if: steps.review.outputs.findings != ''
with:
name: codewhale-findings-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.review.outputs.findings }}
retention-days: 1
if-no-files-found: error
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
if: always() && steps.review.outputs.receipt != ''
with:
name: codewhale-review-receipt-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.review.outputs.receipt }}
retention-days: 14
if-no-files-found: error
publish:
needs: review
if: needs.review.outputs.findings != ''
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1
with:
node-version: '22'
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
with:
name: codewhale-findings-${{ github.run_id }}-${{ github.run_attempt }}
path: findings
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510
id: app-token
with:
app-id: ${{ vars.CODEWHALE_APP_ID }}
private-key: ${{ secrets.CODEWHALE_APP_PRIVATE_KEY }}
permission-contents: read
permission-pull-requests: write
- uses: codewhale-hq/Codewhale/scripts/github-action/publish@b0b2b9a335cc1d24abe8a087189bed080d1cc3db
id: publish
with:
findings: findings/findings.json
pr-number: ${{ inputs.pr-number }}
trigger-phrase: '@codewhale'
allow-forks: 'true'
github-token: ${{ steps.app-token.outputs.token }}
reviewer-login: 'codewhale-agent[bot]'
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
if: always() && steps.publish.outputs.receipt != ''
with:
name: codewhale-publication-receipt-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.publish.outputs.receipt }}
retention-days: 14
if-no-files-found: error
-75
View File
@@ -1,75 +0,0 @@
name: Main landing gate
# Why this exists
#
# Until 2026-11-08 `ci.yml` ran only on `pull_request` and `workflow_dispatch`,
# so nothing validated a push to `main` and a landing that was red on arrival stayed red
# until the next release candidate run, and every open PR then inherits the
# failure: on 2026-10-29 four commits landed red behind `[skip ci]`
# (`PersistRequest::ClearCheckpoint` became dead, `clippy` failed on
# `codewhale-skills`, and the blocking-calls budget went stale for a file that
# had moved crates) and all nine open PRs looked broken for reasons that had
# nothing to do with their authors' code.
#
# This job is deliberately narrow. It runs `scripts/preflight.sh --check`,
# which is the checked-in local preflight for exactly the class of breakage that
# reaches `main` unnoticed: rustfmt, the CHANGELOG slice, README locale stamps,
# the dead-code and blocking-calls budgets, generated-asset drift (including
# the website's generated facts, agent docs and feature registry), and the
# feature receipts. Measured at 45 s on a warm Linux runner.
#
# Since 2026-11-08 (founder) the `full-ci` job also dispatches `ci.yml` on every
# push to main, so every gate and the three-OS suite run on each landing. The
# preflight job stays narrow so a red landing still shows up in minutes.
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: main-landing-${{ github.sha }}
cancel-in-progress: true
jobs:
preflight:
name: Landing preflight
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
# The preflight diffs against the push's base commit to scope feature
# receipts, and the changelog/budget checks read history.
fetch-depth: 0
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.100.0
components: rustfmt
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
- name: Preflight
env:
BEFORE_SHA: ${{ github.event.before }}
run: bash scripts/preflight.sh --check
full-ci:
name: Dispatch full CI
if: github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: write
contents: read
steps:
- name: Run ci.yml with every gate on the pushed commit
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run ci.yml --repo "${GITHUB_REPOSITORY}" --ref main -f expected_sha="${GITHUB_SHA}"
-55
View File
@@ -1,55 +0,0 @@
name: Marketplace connection
on:
workflow_dispatch:
permissions:
contents: read
jobs:
catalog:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
path: core
persist-credentials: false
- name: Select catalog revision
id: source
working-directory: core
env:
EVENT_NAME: ${{ github.event_name }}
run: |
python3 - <<'PY'
import json, os, re
snapshot = json.load(open('crates/plugins/assets/first-party-marketplace.json'))
revision = snapshot['revision']
assert re.fullmatch(r'[0-9a-f]{40}', revision), 'Invalid marketplace revision'
if os.environ['EVENT_NAME'] in ('schedule', 'workflow_dispatch'):
revision = 'main'
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
output.write(f'revision={revision}\n')
PY
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
repository: codewhale-hq/codewhale-plugin-marketplace
ref: ${{ steps.source.outputs.revision }}
path: marketplace
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
package-manager-cache: false
- name: Check catalog and real bundle manifests
run: node marketplace/scripts/check-marketplace.mjs
- name: Check bundled catalog snapshot
run: python3 core/scripts/sync-marketplace.py --marketplace marketplace --check
- name: Check current skill mirrors
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
run: |
node marketplace/scripts/check-marketplace.mjs --core core
- name: Explain a drift failure
if: failure()
run: |
echo 'Review upstream changes using docs/PLUGIN_MARKETPLACE.md. Sync source-owned mirrors, rerun checks, regenerate the catalog, and rebuild. This workflow never installs, trusts, enables, or publishes plugins.' >> "$GITHUB_STEP_SUMMARY"
-201
View File
@@ -1,201 +0,0 @@
name: Nightly
# Manual artifact build for final release validation.
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: nightly-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
CODEWHALE_BUILD_SHA: ${{ github.sha }}
jobs:
build:
name: Build ${{ matrix.platform }}
# Native builds use the same finite cold-build allowance as release/CI.
timeout-minutes: 210
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
platform: linux-x64
binary: codewhale
primary_artifact: codewhale-linux-x64
alias_artifact: codew-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
platform: linux-arm64
binary: codewhale
primary_artifact: codewhale-linux-arm64
alias_artifact: codew-linux-arm64
- os: macos-latest
target: x86_64-apple-darwin
platform: macos-x64
binary: codewhale
primary_artifact: codewhale-macos-x64
alias_artifact: codew-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
platform: macos-arm64
binary: codewhale
primary_artifact: codewhale-macos-arm64
alias_artifact: codew-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: windows-x64
binary: codewhale.exe
primary_artifact: codewhale-windows-x64.exe
alias_artifact: codew-windows-x64.exe
- os: windows-11-arm
target: aarch64-pc-windows-msvc
platform: windows-arm64
binary: codewhale.exe
primary_artifact: codewhale-windows-arm64.exe
alias_artifact: codew-windows-arm64.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.100.0
targets: ${{ matrix.target }}
- uses: mozilla-actions/[email protected]
id: sccache
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success' && runner.os != 'Windows'
shell: bash
run: |
{
echo "SCCACHE_GHA_ENABLED=true"
echo "RUSTC_WRAPPER=sccache"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
} >> "${GITHUB_ENV}"
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
- name: Install Linux GNU system dependencies
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config
- name: Install Linux ARM64 musl toolchain
if: matrix.target == 'aarch64-unknown-linux-musl'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y binutils musl-tools
rustup target add aarch64-unknown-linux-musl
- name: Build
shell: bash
# Nightly artifacts are disposable smoke binaries (14-day retention),
# so skip fat LTO; tagged releases keep the full optimized profile via
# the Release workflow. (`codegen-units` is not overridden here --
# [profile.release] already sets 16, so restating it changed nothing.)
#
# RUST_MIN_STACK sizes the stack of the LLVM worker threads rustc
# spawns to run `optimize module <crate>-cgu.N`. With `lto=off` the
# per-CGU optimization pipeline runs while the *library* is compiled
# rather than being deferred to the ThinLTO stage, and one crates/tui
# codegen unit needs more stack than a 1 MiB thread provides. Measured
# on aarch64 by holding the crate and every flag fixed and varying only
# this value: 1 MiB crashes rustc, 2 MiB and 4 MiB succeed. Unix std
# defaults to 2 MiB and passes; windows-11-arm sat under the
# requirement and failed every nightly from 2026-08-16 with
# `thread 'optimize module codewhale_tui...-cgu.13' has overflowed its
# stack`, deterministically on the same codegen unit across all three
# build attempts. This is a property of the crate's size, not of
# Windows, so it is set for every target: at 788k lines in crates/tui
# the other platforms are one refactor away from crossing 2 MiB too.
# The value is reserved address space, not committed memory.
env:
CARGO_PROFILE_RELEASE_LTO: 'off'
RUST_MIN_STACK: '16777216'
run: |
for attempt in 1 2 3; do
if cargo build --release --locked --target ${{ matrix.target }} -p codewhale-cli; then
exit 0
fi
if [ "${attempt}" -lt 3 ]; then
echo "Build attempt ${attempt} failed; retrying in 30s..."
sleep 30
fi
done
echo "Build failed after 3 attempts" >&2
exit 1
- name: Verify static Linux ARM64 binary and launch
if: matrix.target == 'aarch64-unknown-linux-musl' && runner.arch == 'ARM64'
shell: bash
run: |
set -euo pipefail
bin_path="target/${{ matrix.target }}/release/${{ matrix.binary }}"
if readelf -l "${bin_path}" | grep -Fq 'INTERP'; then
echo "Expected a static musl binary, but ${bin_path} has an ELF interpreter" >&2
exit 1
fi
"${bin_path}" --version
- name: Smoke binary on matching native runners
if: >-
(startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64')
shell: bash
run: |
bin_dir="target/${{ matrix.target }}/release"
"${bin_dir}/${{ matrix.binary }}" --version
- name: Stage artifact
id: stage
shell: bash
run: |
short_sha="${GITHUB_SHA::12}"
bin_path="target/${{ matrix.target }}/release/${{ matrix.binary }}"
if [ ! -f "${bin_path}" ]; then
echo "Binary not at ${bin_path}; searching target/ for ${{ matrix.binary }}:"
find target -name "${{ matrix.binary }}" -type f
exit 1
fi
stage_copy() {
local artifact="$1"
local dir="$2"
mkdir -p "${dir}"
cp "${bin_path}" "${dir}/${artifact}"
cat > "${dir}/nightly-build-info.txt" <<INFO
repository=${GITHUB_REPOSITORY}
ref=${GITHUB_REF_NAME}
commit=${GITHUB_SHA}
artifact=${artifact}
profile=release-nightly-no-lto
INFO
}
stage_copy "${{ matrix.primary_artifact }}" nightly-primary
stage_copy "${{ matrix.alias_artifact }}" nightly-alias
cmp -s \
"nightly-primary/${{ matrix.primary_artifact }}" \
"nightly-alias/${{ matrix.alias_artifact }}"
echo "primary_name=${{ matrix.primary_artifact }}-${short_sha}" >> "${GITHUB_OUTPUT}"
echo "alias_name=${{ matrix.alias_artifact }}-${short_sha}" >> "${GITHUB_OUTPUT}"
- uses: actions/upload-artifact@v7
with:
name: ${{ steps.stage.outputs.primary_name }}
path: nightly-primary/*
retention-days: 14
- uses: actions/upload-artifact@v7
with:
name: ${{ steps.stage.outputs.alias_name }}
path: nightly-alias/*
retention-days: 14
-56
View File
@@ -1,56 +0,0 @@
name: OpenHarmony
on:
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: ohos-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
jobs:
cargo-check:
name: cargo check (aarch64-unknown-linux-ohos)
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- name: Install Rust target
uses: dtolnay/[email protected]
with:
targets: aarch64-unknown-linux-ohos
- name: Install OpenHarmony native SDK
id: ohos-sdk
# Pin the action: it downloads a published OpenHarmony SDK and verifies
# the archive checksum. This job must never substitute host headers or
# a stub sysroot and report that as target support.
uses: openharmony-rs/setup-ohos-sdk@b312caf8a43bb836aa24c08f65f97e1f09a89cad
with:
version: '6.1'
components: native
# Save the ~715 MB SDK cache from main only. The action has no
# restore-only mode, and with `cache: true` every PR ref that missed
# main's entry saved its own 715 MB copy; a handful of open PRs
# pushed the repo past the 10 GiB Actions cache cap and evicted the
# Rust caches CI actually depends on. PRs download the SDK instead
# (checksum-verified from the GitHub mirror below).
cache: ${{ github.ref == 'refs/heads/main' }}
mirror: true
- name: Check Codewhale TUI and CLI with the real SDK/sysroot
shell: bash
env:
OHOS_NATIVE_SDK: ${{ steps.ohos-sdk.outputs.ohos_sdk_native }}
run: |
set -euo pipefail
test -x "${OHOS_NATIVE_SDK}/llvm/bin/clang"
test -d "${OHOS_NATIVE_SDK}/sysroot"
. ./scripts/ohos-env.sh
cargo check --target aarch64-unknown-linux-ohos -p codewhale-tui -p codewhale-cli --locked
-111
View File
@@ -1,111 +0,0 @@
name: Pet conformance
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pet-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
portable:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: actions/[email protected]
with:
node-version: '22'
cache: npm
cache-dependency-path: pet/package-lock.json
- uses: dtolnay/[email protected]
- run: npm --prefix pet ci --ignore-scripts
- run: npm --prefix pet run check
- run: npm --prefix pet run sync
- name: Embedded sources match canonical core
run: git diff --exit-code -- crates/tui/src/tui/pet_watch/pet-native.js pet/ios/Resources
# The authored body is checked in five times and nothing kept the copies
# in step, so the Rust core silently carried a different whale from every
# other surface and conformance never passed. Compare them directly: no
# sync step produces these, so `git diff` cannot catch the drift.
- name: One whale body across every surface
run: |
canonical=crates/tui/src/tui/ambient_life/whale-points.tsv
for copy in pet/whale-points.tsv pet/public/whale-points.tsv pet/rs/whale-points.tsv; do
diff -q "$canonical" "$copy" || {
echo "::error file=$copy::diverged from $canonical; the Rust core is canonical"
exit 1
}
done
- run: cargo fetch --locked --manifest-path pet/tui/Cargo.toml
- name: TypeScript and Rust parity, including reduced motion
run: ./pet/verify.sh --no-swift
recorder:
name: recorder (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
- uses: actions/[email protected]
with:
node-version: '22'
cache: npm
cache-dependency-path: pet/package-lock.json
- run: npm --prefix pet ci --ignore-scripts
- run: npm --prefix pet run check
- uses: dtolnay/[email protected]
if: runner.os == 'macOS'
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
if: runner.os == 'macOS'
- name: Build the standalone shared owner and verify its transport
if: runner.os == 'macOS'
run: |
cargo build --locked -j 2 -p codewhale-cli --bin codewhale
python3 pet/scripts/check-shared.py target/debug/codewhale
- name: Build and exercise the Apple host and PCM boundary
if: runner.os == 'macOS'
run: PET_OWNER_BINARY="$PWD/target/debug/codewhale" ./pet/scripts/check-apple.sh
- name: Package the locally signed macOS app
if: runner.os == 'macOS'
run: ditto -c -k --keepParent pet/macos/CodewhalePet.app codewhale-pet-macos.zip
- uses: actions/upload-artifact@v7
if: runner.os == 'macOS'
with:
name: codewhale-pet-macos
path: codewhale-pet-macos.zip
if-no-files-found: error
android:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
- uses: actions/[email protected]
with:
distribution: temurin
java-version: '17'
- name: Enable emulator acceleration on the disposable runner
run: sudo chmod a+rw /dev/kvm
- uses: reactivecircus/[email protected]
with:
api-level: 35
arch: x86_64
target: google_apis
profile: pixel_7
disable-animations: false
working-directory: pet/android
script: ./gradlew --no-daemon assembleDebug connectedDebugAndroidTest lintDebug
- uses: actions/upload-artifact@v7
if: always()
with:
name: codewhale-pet-android
path: |
pet/android/build/outputs/apk/debug/*.apk
pet/android/build/reports/androidTests/
pet/android/build/reports/lint-results-debug.*
-703
View File
@@ -1,703 +0,0 @@
name: Release artifacts
on:
workflow_call:
inputs:
source_sha:
description: Exact 40-character source commit to build
required: true
type: string
version:
description: Workspace version without a v prefix
required: true
type: string
compiled_host_delivery:
description: Explicitly include qualified optional companions; Node stays default
required: false
default: false
type: boolean
compiled_host_run_id:
description: Green official CI run at this exact SHA containing Native image proof
required: false
default: ''
type: string
compiled_host_targets:
description: Comma-separated matching-native targets with qualified CI receipts
required: false
default: linux-x64,macos-arm64,windows-x64
type: string
retention_days:
description: Retention for Actions-only intermediate and assembled artifacts
required: false
default: 7
type: number
secrets:
CODEWHALE_RELEASE_SIGNING_KEY:
description: Ed25519 release key (PEM). Absent means the release ships unsigned.
required: false
permissions:
contents: read
actions: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
# Build identity is the trusted workflow SHA. Callers pass source_sha only
# so `pin` can refuse a mismatch; it must not retarget checkout or caches.
CODEWHALE_BUILD_SHA: ${{ github.sha }}
jobs:
pin:
name: Pin caller SHA to this run
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Require source_sha equals github.sha
env:
SOURCE_SHA: ${{ inputs.source_sha }}
run: |
set -euo pipefail
if [[ "${#SOURCE_SHA}" -ne 40 || "${SOURCE_SHA}" =~ [^0-9a-fA-F] ]]; then
echo "::error::source_sha must be a full 40-character commit SHA." >&2
exit 1
fi
expected="$(printf '%s' "${SOURCE_SHA}" | tr '[:upper:]' '[:lower:]')"
actual="$(printf '%s' "${GITHUB_SHA}" | tr '[:upper:]' '[:lower:]')"
if [[ "${actual}" != "${expected}" ]]; then
echo "::error::Reusable workflow SHA ${actual} does not match source_sha ${SOURCE_SHA}." >&2
exit 1
fi
- name: Require explicit matching-native qualification selection
if: inputs.compiled_host_delivery
env:
QUALIFICATION_RUN: ${{ inputs.compiled_host_run_id }}
HOST_TARGETS: ${{ inputs.compiled_host_targets }}
run: |
set -euo pipefail
[[ "${QUALIFICATION_RUN}" =~ ^[0-9]+$ ]] || { echo 'Enabled host delivery needs an exact green CI run id' >&2; exit 1; }
[[ -n "${HOST_TARGETS}" ]] || { echo 'No compiled host targets selected' >&2; exit 1; }
declare -A seen
IFS=',' read -ra selected <<< "${HOST_TARGETS}"
for target in "${selected[@]}"; do
case "${target}" in linux-x64|linux-arm64|macos-x64|macos-arm64|windows-x64|windows-arm64) ;; *) echo "Unsupported compiled host target ${target}" >&2; exit 1 ;; esac
[[ -z "${seen[${target}]:-}" ]] || { echo "Duplicate compiled host target ${target}" >&2; exit 1; }
seen["${target}"]=1
done
build:
name: Build ${{ matrix.platform }}
timeout-minutes: ${{ startsWith(matrix.os, 'macos-') && 360 || 210 }}
# FreeBSD is a source-build target with no prebuilt asset, no npm binary and no CI check
# (see packaging/freebsd/README.md and docs/INSTALL.md#freebsd).
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
platform: linux-x64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-linux-x64
shim_artifact: codew-linux-x64
compat_tui_artifact: codewhale-tui-linux-x64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
platform: linux-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-linux-arm64
shim_artifact: codew-linux-arm64
compat_tui_artifact: codewhale-tui-linux-arm64
- os: ubuntu-latest
target: aarch64-linux-android
platform: android-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-android-arm64
shim_artifact: codew-android-arm64
compat_tui_artifact: codewhale-tui-android-arm64
- os: macos-latest
target: x86_64-apple-darwin
platform: macos-x64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-macos-x64
shim_artifact: codew-macos-x64
compat_tui_artifact: codewhale-tui-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
platform: macos-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-macos-arm64
shim_artifact: codew-macos-arm64
compat_tui_artifact: codewhale-tui-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: windows-x64
cli_binary: codewhale.exe
shim_binary: codew.exe
cli_artifact: codewhale-windows-x64.exe
shim_artifact: codew-windows-x64.exe
compat_tui_artifact: codewhale-tui-windows-x64.exe
- os: windows-11-arm
target: aarch64-pc-windows-msvc
platform: windows-arm64
cli_binary: codewhale.exe
shim_binary: codew.exe
cli_artifact: codewhale-windows-arm64.exe
shim_artifact: codew-windows-arm64.exe
compat_tui_artifact: codewhale-tui-windows-arm64.exe
# Native HarmonyOS and OpenHarmony. The artifact is useless unsigned:
# the device refuses to execute an unsigned ELF (see
# docs/HarmonyOS.md), so this leg self-signs the binary before staging.
- os: ubuntu-latest
target: aarch64-unknown-linux-ohos
platform: ohos-arm64
cli_binary: codewhale
shim_binary: codew
cli_artifact: codewhale-ohos-arm64
shim_artifact: codew-ohos-arm64
compat_tui_artifact: codewhale-tui-ohos-arm64
runs-on: ${{ matrix.os }}
needs: pin
steps:
# No ref: — GITHUB_SHA only. CodeQL treats workflow_call checkout-with-ref
# and any ref named *sha* as an untrusted checkout (cache-poisoning).
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master 2026-07-18
with:
toolchain: 1.100.0
targets: ${{ matrix.target }}
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
if: runner.os == 'Linux'
id: sccache
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success' && runner.os == 'Linux'
shell: bash
run: |
{
echo "SCCACHE_GHA_ENABLED=true"
echo "RUSTC_WRAPPER=sccache"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
} >> "${GITHUB_ENV}"
# Restore after the trusted lockfile is on disk. Key is OS + arch +
# explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
# hash. Never interpolate github.event, github.ref, github.sha, or inputs.
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
cache-bin: false
prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
- name: Add swap so the aarch64 dist build is not killed for memory
if: runner.os == 'Linux' && contains(matrix.target, 'aarch64')
shell: bash
run: |
set -euo pipefail
dir=/mnt
if ! sudo test -w /mnt; then dir=/; fi
avail=$(df -Pk "$dir" | awk 'NR==2 {print int($4 / 1048576)}')
if [ "$avail" -ge 30 ]; then size=16; elif [ "$avail" -ge 14 ]; then size=8; else size=0; fi
if [ "$size" -gt 0 ]; then
sudo fallocate -l "${size}G" "$dir/cw-swapfile"
sudo chmod 600 "$dir/cw-swapfile"
sudo mkswap "$dir/cw-swapfile" >/dev/null
sudo swapon "$dir/cw-swapfile"
fi
free -g
df -h "$dir"
- name: Build static Linux binaries (musl)
if: endsWith(matrix.target, '-unknown-linux-musl')
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y binutils musl-tools
rustup target add ${{ matrix.target }}
cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
- name: Configure Android NDK linker
if: matrix.target == 'aarch64-linux-android' && runner.os == 'Linux'
shell: bash
env:
ANDROID_NDK_VERSION: 27.2.12479018
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y libclang-dev
ndk="${ANDROID_NDK_ROOT:-${ANDROID_NDK_HOME:-}}"
linker=""
if [[ -n "${ndk}" ]]; then
linker="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
fi
if [[ -z "${linker}" || ! -x "${linker}" ]]; then
if ! command -v sdkmanager >/dev/null 2>&1; then
echo "sdkmanager is required to install Android NDK ${ANDROID_NDK_VERSION}" >&2
exit 1
fi
android_home="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
if [[ -z "${android_home}" ]]; then
echo "ANDROID_HOME or ANDROID_SDK_ROOT is required to install Android NDK ${ANDROID_NDK_VERSION}" >&2
exit 1
fi
yes | sdkmanager --licenses >/dev/null || true
sdkmanager --install "ndk;${ANDROID_NDK_VERSION}"
ndk="${android_home}/ndk/${ANDROID_NDK_VERSION}"
linker="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
fi
ar="${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
if [[ ! -x "${linker}" ]]; then
echo "Android linker not found: ${linker}" >&2
exit 1
fi
if [[ ! -x "${ar}" ]]; then
echo "Android archiver not found: ${ar}" >&2
exit 1
fi
{
echo "ANDROID_NDK_ROOT=${ndk}"
echo "ANDROID_NDK_HOME=${ndk}"
echo "CC_aarch64_linux_android=${linker}"
echo "AR_aarch64_linux_android=${ar}"
echo "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${linker}"
echo "BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android=--target=aarch64-linux-android24 --sysroot=${ndk}/toolchains/llvm/prebuilt/linux-x86_64/sysroot"
} >> "${GITHUB_ENV}"
- name: Build
if: ${{ !endsWith(matrix.target, '-unknown-linux-musl') && matrix.platform != 'ohos-arm64' }}
shell: bash
run: cargo build --profile dist --locked --target ${{ matrix.target }} -p codewhale-cli
- name: Install OpenHarmony native SDK
if: matrix.platform == 'ohos-arm64'
id: ohos-sdk
# Pin the action: it downloads a published OpenHarmony SDK and verifies
# the archive checksum, so a broken mirror fails here instead of
# producing a binary linked against host headers.
uses: openharmony-rs/setup-ohos-sdk@b312caf8a43bb836aa24c08f65f97e1f09a89cad
with:
version: '6.1'
# `toolchains` carries binary-sign-tool.jar, which signs the binary
# below; a release without it would ship an ELF the device refuses to
# execute.
components: 'native;toolchains'
# One download per release is cheaper than letting a tag save a 715 MB
# cache entry that main's jobs then compete with.
cache: false
mirror: true
- name: Install Java for the OpenHarmony binary signer
if: matrix.platform == 'ohos-arm64'
uses: actions/setup-java@77dd5b2ce225ea11469928ac317850245a74ae78 # v4
with:
distribution: temurin
java-version: '17'
- name: Cross-compile and code-sign the OpenHarmony binary
if: matrix.platform == 'ohos-arm64'
shell: bash
env:
OHOS_NATIVE_SDK: ${{ steps.ohos-sdk.outputs.ohos_sdk_native }}
run: |
set -euo pipefail
test -x "${OHOS_NATIVE_SDK}/llvm/bin/clang"
test -d "${OHOS_NATIVE_SDK}/sysroot"
# ohos-env.sh also puts the SDK's cmake on PATH, which aws-lc-sys
# needs; without it the build dies inside its CMake probe.
. ./scripts/ohos-env.sh
cargo build --profile dist --locked --target "${{ matrix.target }}" -p codewhale-cli
bin="target/${{ matrix.target }}/dist/${{ matrix.cli_binary }}"
tools="$(dirname "${OHOS_NATIVE_SDK}")/toolchains/lib"
# SDK 6.1 publishes the native `binary-sign-tool` and no jar, while
# DevEco ships the Java build; both take the same arguments.
if [[ -f "${tools}/binary-sign-tool.jar" ]]; then
signer=(java -jar "${tools}/binary-sign-tool.jar")
elif [[ -f "${tools}/binary-sign-tool" ]]; then
chmod +x "${tools}/binary-sign-tool"
signer=("${tools}/binary-sign-tool")
else
echo "no binary-sign-tool in ${tools}; the device refuses an unsigned ELF" >&2
ls -la "${tools}" >&2
exit 1
fi
"${signer[@]}" sign -inFile "${bin}" -outFile "${bin}" -selfSign 1
# Capture then match. `tool | grep -q` cannot be used under
# `pipefail`: grep exits at the first match, the tool takes SIGPIPE,
# and llvm-readelf reports that as exit 74, which reads as a failed
# verification of a good binary.
signature="$("${signer[@]}" display-sign -inFile "${bin}")"
case "${signature}" in
*'code signature is self-sign'*) ;;
*)
echo 'the staged binary is not self-signed; the device would refuse to execute it' >&2
exit 1
;;
esac
kind="$(file -b "${bin}")"
case "${kind}" in
*'ARM aarch64'*) ;;
*)
echo "not an aarch64 ELF: ${kind}" >&2
exit 1
;;
esac
headers="$("${OHOS_NATIVE_SDK}/llvm/bin/llvm-readelf" -l "${bin}")"
case "${headers}" in
*'/lib/ld-musl-aarch64.so.1'*) ;;
*)
echo "unexpected ELF interpreter for ${bin}" >&2
exit 1
;;
esac
- name: Materialize codew command alias
shell: bash
run: |
bin_dir="target/${{ matrix.target }}/dist"
cp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
cmp "${bin_dir}/${{ matrix.cli_binary }}" "${bin_dir}/${{ matrix.shim_binary }}"
- name: Verify static Linux binaries and launch on matching native runners
if: >-
endsWith(matrix.target, '-unknown-linux-musl') &&
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
shell: bash
run: |
set -euo pipefail
bin_dir="target/${{ matrix.target }}/dist"
for binary in "${{ matrix.cli_binary }}" "${{ matrix.shim_binary }}"; do
bin_path="${bin_dir}/${binary}"
if readelf -l "${bin_path}" | grep -Fq 'INTERP'; then
echo "Expected a static musl binary, but ${bin_path} has an ELF interpreter" >&2
exit 1
fi
"${bin_path}" --version
done
- name: Smoke binaries on matching native runners
if: >-
matrix.target != 'aarch64-linux-android' &&
((startsWith(matrix.target, 'x86_64-') && runner.arch == 'X64') ||
(startsWith(matrix.target, 'aarch64-') && runner.arch == 'ARM64'))
shell: bash
run: |
bin_dir="target/${{ matrix.target }}/dist"
"${bin_dir}/${{ matrix.cli_binary }}" --version
"${bin_dir}/${{ matrix.shim_binary }}" --version
- name: Windows Terminal clipboard-to-draft acceptance
if: matrix.platform == 'windows-x64'
timeout-minutes: 5
shell: pwsh
run: node scripts/release/windows-terminal-clipboard.mjs "target/${{ matrix.target }}/dist/codewhale.exe" "${{ runner.temp }}/windows-clipboard"
- name: Retain Windows Terminal clipboard-to-draft evidence
if: ${{ !cancelled() && matrix.platform == 'windows-x64' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: release-windows-clipboard-${{ github.sha }}
path: ${{ runner.temp }}/windows-clipboard/
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
- name: Stage binaries
shell: bash
run: |
stage_binary() {
local binary="$1"
local artifact="$2"
local bin_path="target/${{ matrix.target }}/dist/${binary}"
if [[ ! -f "${bin_path}" ]]; then
echo "Binary not at ${bin_path}; searching target/ for ${binary}:" >&2
find target -name "${binary}" -type f
exit 1
fi
cp "${bin_path}" "${artifact}"
}
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.cli_artifact }}"
stage_binary "${{ matrix.shim_binary }}" "${{ matrix.shim_artifact }}"
# Compatibility bridge for v0.9.4's hard-coded release
# completeness/updater contract. This is the same runtime, not a
# separately compiled or installed TUI command.
stage_binary "${{ matrix.cli_binary }}" "${{ matrix.compat_tui_artifact }}"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2
if: inputs.compiled_host_delivery && contains(format(',{0},', inputs.compiled_host_targets), format(',{0},', matrix.platform))
with:
bun-version: 1.4.0
- name: Stage exact qualified optional host image
if: inputs.compiled_host_delivery && contains(format(',{0},', inputs.compiled_host_targets), format(',{0},', matrix.platform))
shell: bash
env:
GH_TOKEN: ${{ github.token }}
QUALIFICATION_RUN: ${{ inputs.compiled_host_run_id }}
TARGET_PLATFORM: ${{ matrix.platform }}
RELEASE_VERSION: ${{ inputs.version }}
RUNTIME_CLOSURE_DIR: ${{ vars.CODEWHALE_COMPILED_HOST_CLOSURE_DIR }}
run: |
set -euo pipefail
proof_dir="${RUNNER_TEMP}/compiled-host-proof-${TARGET_PLATFORM}"
node scripts/release/fetch-compiled-host-proof.js --repo "${GITHUB_REPOSITORY}" --run-id "${QUALIFICATION_RUN}" --source-sha "${GITHUB_SHA}" --target "${TARGET_PLATFORM}" --output "${proof_dir}"
test -n "${RUNTIME_CLOSURE_DIR}" || { echo 'Enabled compiled-host delivery requires reviewed complete local runtime notices/relink-source closure' >&2; exit 1; }
bun_binary="$(bun -p 'process.execPath')"
extension=""
if [[ "${TARGET_PLATFORM}" == windows-* ]]; then extension=.exe; fi
node scripts/release/stage-compiled-host.mjs --bun "${bun_binary}" --compiled-image "${proof_dir}/codewhale-extension-host${extension}" --native-receipt "${proof_dir}/native-receipt.json" --runtime-closure "${RUNTIME_CLOSURE_DIR}/${TARGET_PLATFORM}.json" --target "${TARGET_PLATFORM}" --version "${RELEASE_VERSION}" --source-sha "${GITHUB_SHA}" --output-dir "compiled-host-${TARGET_PLATFORM}"
- name: Check every staged Windows executable import
if: startsWith(matrix.platform, 'windows-')
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$binaries = @('${{ matrix.cli_artifact }}', '${{ matrix.shim_artifact }}', '${{ matrix.compat_tui_artifact }}')
$hostDirectory = 'compiled-host-${{ matrix.platform }}'
if (Test-Path $hostDirectory) {
$binaries += @(Get-ChildItem -Path $hostDirectory -Filter *.exe -Recurse -File | ForEach-Object { $_.FullName })
}
python scripts/release/check-windows-imports.py --expected-machine '${{ matrix.platform }}' --output '${{ runner.temp }}/windows-imports-${{ matrix.platform }}' @binaries
if ($LASTEXITCODE -ne 0) { throw 'Windows static import qualification failed' }
- name: Retain Windows import receipts
if: always() && startsWith(matrix.platform, 'windows-')
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: windows-imports-${{ matrix.platform }}-${{ github.sha }}
path: ${{ runner.temp }}/windows-imports-${{ matrix.platform }}/
if-no-files-found: warn
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: inputs.compiled_host_delivery && contains(format(',{0},', inputs.compiled_host_targets), format(',{0},', matrix.platform))
with:
name: codewhale-compiled-host-${{ matrix.platform }}
path: |
compiled-host-${{ matrix.platform }}/codewhale-extension-host-*
compiled-host-${{ matrix.platform }}/codewhale-extension-hosts.json
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.cli_artifact }}
path: ${{ matrix.cli_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.shim_artifact }}
path: ${{ matrix.shim_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ matrix.compat_tui_artifact }}
path: ${{ matrix.compat_tui_artifact }}
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
bundle:
timeout-minutes: 15
needs: build
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
path: artifacts
pattern: '*'
- name: Collect only qualified requested compiled hosts
if: inputs.compiled_host_delivery
shell: bash
run: node scripts/release/stage-compiled-host.mjs --collect artifacts --output-dir artifacts/codewhale-compiled-hosts
- name: Create and checksum platform archives
shell: bash
env:
SOURCE_SHA: ${{ github.sha }}
run: |
set -euo pipefail
source_date_epoch="$(git show -s --format=%ct "${SOURCE_SHA}")"
if [[ ! "${source_date_epoch}" =~ ^[0-9]+$ ]]; then
echo "Could not read a Unix timestamp for source commit ${SOURCE_SHA}" >&2
exit 1
fi
SOURCE_DATE_EPOCH="${source_date_epoch}" \
bash scripts/release/create-release-bundles.sh artifacts bundles
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codewhale-bundles
path: |
bundles/*.tar.gz
bundles/*.zip
bundles/codewhale-bundles-sha256.txt
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: inputs.compiled_host_delivery
with:
name: codewhale-compiled-hosts
path: artifacts/codewhale-compiled-hosts/*
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
windows-installer:
timeout-minutes: 15
needs: build
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: windows-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
path: artifacts
pattern: '*windows-x64*'
- name: Install NSIS
shell: pwsh
run: ./scripts/installer/install-nsis.ps1
- name: Build NSIS installer
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
Copy-Item "artifacts\codewhale-windows-x64.exe\codewhale-windows-x64.exe" "scripts\installer\codewhale.exe"
Copy-Item "artifacts\codew-windows-x64.exe\codew-windows-x64.exe" "scripts\installer\codew.exe"
$makensis = "${env:ProgramFiles(x86)}\NSIS\makensis.exe"
if (!(Test-Path $makensis)) {
$makensis = "${env:ProgramFiles}\NSIS\makensis.exe"
}
if (!(Test-Path $makensis)) {
throw "makensis.exe not found after NSIS install"
}
$hostDefine = @()
$hostCatalog = "artifacts\codewhale-compiled-host-windows-x64\codewhale-extension-hosts.json"
if (Test-Path $hostCatalog) {
node -e "const h=require('./npm/codewhale/scripts/compiled-hosts'); const fs=require('fs'); const p=require('path'); const f=process.argv[1]; h.verifyDirectory(p.dirname(f), h.parseCatalog(fs.readFileSync(f),'${{ inputs.version }}'));" $hostCatalog
if ($LASTEXITCODE -ne 0) { throw 'Compiled host payload/catalog verification failed' }
$catalog = Get-Content -Raw $hostCatalog | ConvertFrom-Json
$hostEntry = @($catalog.hosts | Where-Object { $_.target -eq 'windows-x64' })
if ($hostEntry.Count -ne 1) { throw 'No unique qualified Windows host' }
$hostDir = Split-Path $hostCatalog
Copy-Item "$hostDir\$($hostEntry[0].asset)" "scripts\installer\codewhale-extension-host.exe"
Copy-Item "$hostDir\$($hostEntry[0].notices_asset)" "scripts\installer\codewhale-extension-host.LICENSES.txt"
Copy-Item "$hostDir\$($hostEntry[0].source_asset)" "scripts\installer\codewhale-extension-host.relink-source.tar.gz"
Copy-Item $hostCatalog "scripts\installer\codewhale-extension-host.release.json"
$hostDefine = @('/DCOMPILED_HOST=1')
}
Push-Location scripts\installer
& $makensis "/DVERSION=${{ inputs.version }}" @hostDefine "codewhale.nsi"
Pop-Location
if (!(Test-Path "scripts\installer\CodeWhaleSetup.exe")) {
throw "CodeWhaleSetup.exe was not produced"
}
- name: Check Windows installer and embedded executables
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$evidenceDirectory = '${{ runner.temp }}/windows-installer-imports'
$embeddedDirectory = Join-Path $evidenceDirectory 'embedded'
New-Item -Path $evidenceDirectory -ItemType Directory -Force | Out-Null
& 7z l 'scripts/installer/CodeWhaleSetup.exe' | Out-File (Join-Path $evidenceDirectory 'archive-listing.txt') -Encoding utf8
if ($LASTEXITCODE -ne 0) { throw 'NSIS embedded payload inventory failed' }
& 7z x 'scripts/installer/CodeWhaleSetup.exe' "-o$embeddedDirectory" '$PLUGINSDIR/*.dll' | Out-File (Join-Path $evidenceDirectory 'archive-extraction.txt') -Encoding utf8
if ($LASTEXITCODE -ne 0) { throw 'NSIS embedded plugin extraction failed' }
$plugins = @(Get-ChildItem -Path $embeddedDirectory -Filter *.dll -Recurse -File)
if ($plugins.Name -notcontains 'System.dll' -or $plugins.Name -notcontains 'nsDialogs.dll') { throw 'Expected MUI2 System and nsDialogs plugin payloads' }
$binaries = @(Get-ChildItem -Path scripts/installer -Filter *.exe -File | ForEach-Object { $_.FullName })
if ($binaries.Count -lt 3) { throw 'Expected installer, CLI and shim executables' }
$binaries += @($plugins | ForEach-Object { $_.FullName })
python scripts/release/check-windows-imports.py --output $evidenceDirectory @binaries
if ($LASTEXITCODE -ne 0) { throw 'Windows installer static import qualification failed' }
- name: Retain Windows installer import receipts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: windows-installer-imports-${{ github.sha }}
path: ${{ runner.temp }}/windows-installer-imports/
if-no-files-found: warn
retention-days: ${{ inputs.retention_days }}
overwrite: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: CodeWhaleSetup.exe
path: scripts/installer/CodeWhaleSetup.exe
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
overwrite: true
assemble:
timeout-minutes: 15
needs: [bundle, windows-installer]
if: ${{ !cancelled() && needs.bundle.result == 'success' && needs.windows-installer.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
package-manager-cache: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
path: intermediate-artifacts
pattern: '*'
- name: Assemble exact authoritative release inventory
run: node scripts/release/assemble-release-assets.js intermediate-artifacts release-assets
- name: Sign the checksum manifest when the release key is configured
env:
CODEWHALE_RELEASE_SIGNING_KEY: ${{ secrets.CODEWHALE_RELEASE_SIGNING_KEY }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ -z "${CODEWHALE_RELEASE_SIGNING_KEY}" ]]; then
echo "CODEWHALE_RELEASE_SIGNING_KEY is not configured; this release is unsigned and automatic updates wait for each user's OK."
exit 0
fi
bash scripts/release/sign-release-manifest.sh "${VERSION}" \
release-assets/codewhale-artifacts-sha256.txt \
release-assets/codewhale-artifacts-sha256.txt.sig
node scripts/release/assemble-release-assets.js --verify release-assets
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codewhale-release-assets
path: release-assets/*
if-no-files-found: error
retention-days: ${{ inputs.retention_days }}
compression-level: 0
overwrite: true
smoke:
timeout-minutes: 15
needs: assemble
if: ${{ !cancelled() && needs.assemble.result == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
package-manager-cache: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: codewhale-release-assets
path: release-assets
- name: Verify 34-asset bridge inventory and checksum manifests (single binary)
run: node scripts/release/assemble-release-assets.js --verify release-assets
- name: Test release inventory contracts
run: |
node --test scripts/release/assemble-release-assets.test.js
node --test npm/codewhale/test/artifacts.test.js npm/codewhale/test/release-assets.test.js
- name: Render AUR metadata from candidate Linux archives
if: ${{ !contains(inputs.version, '-') }}
run: bash packaging/aur/render.sh release-assets "${RUNNER_TEMP}/codewhale-bin"
- name: Smoke packed npm wrapper against candidate assets
env:
CODEWHALE_SMOKE_ASSETS_DIR: ${{ github.workspace }}/release-assets
run: node scripts/release/npm-wrapper-smoke.js
- name: Record non-public candidate identity
shell: bash
run: |
{
echo "### Release artifact candidate"
echo ""
echo "- Source: \`${{ github.sha }}\`"
echo "- Version metadata: \`${{ inputs.version }}\`"
echo "- Inventory: 7 targets / 34 files (single binary; 7 legacy alias assets)"
echo "- Publication: none (Actions artifact \`codewhale-release-assets\` only)"
} >> "${GITHUB_STEP_SUMMARY}"
-141
View File
@@ -1,141 +0,0 @@
name: Release candidate
# Safe pre-publication artifact proof. This workflow never creates a tag or
# release and never writes to a registry, container repository, tap, or deploy.
on:
workflow_dispatch:
inputs:
expected_sha:
description: Exact 40-character commit selected by --ref (must match the dispatch SHA)
required: true
type: string
permissions:
contents: read
concurrency:
group: release-candidate-${{ github.sha }}
cancel-in-progress: false
jobs:
resolve:
name: Resolve exact candidate source
timeout-minutes: 10
runs-on: ubuntu-latest
outputs:
sha: ${{ steps.source.outputs.sha }}
version: ${{ steps.source.outputs.version }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
package-manager-cache: false
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # stable 2026-07-18
with:
toolchain: 1.100.0
- name: Match dispatch to the requested commit
id: source
shell: bash
env:
EXPECTED_SHA: ${{ inputs.expected_sha }}
run: |
set -euo pipefail
if [[ "${#EXPECTED_SHA}" -ne 40 || "${EXPECTED_SHA}" =~ [^0-9a-fA-F] ]]; then
echo "::error::expected_sha must be a full 40-character commit SHA." >&2
exit 1
fi
actual="$(git rev-parse HEAD)"
expected_normalized="$(printf '%s' "${EXPECTED_SHA}" | tr '[:upper:]' '[:lower:]')"
if [[ "${actual}" != "${expected_normalized}" ]]; then
echo "::error::Dispatch resolved to ${actual}, not requested ${EXPECTED_SHA}." >&2
exit 1
fi
workspace_version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
npm_version="$(node -p "require('./npm/codewhale/package.json').version")"
binary_version="$(node -p "require('./npm/codewhale/package.json').codewhaleBinaryVersion")"
if [[ "${workspace_version}" != "${npm_version}" ]]; then
echo "::error::Candidate version drift: workspace=${workspace_version}, npm=${npm_version}, binary=${binary_version}." >&2
exit 1
fi
if [[ "${workspace_version}" != "${binary_version}" ]]; then
echo "::error::Candidate version drift: workspace=${workspace_version}, npm=${npm_version}, binary=${binary_version}." >&2
exit 1
fi
echo "sha=${actual}" >> "${GITHUB_OUTPUT}"
echo "version=${workspace_version}" >> "${GITHUB_OUTPUT}"
- name: Check version and OHOS release contracts
# --require-dated-release, as auto-tag.yml and release.yml run it:
# release.yml only accepts an RC receipt for the exact tag SHA, so an
# RC on an undated "Unreleased candidate" changelog could never
# authorize a release. Fail here, before the long parity and artifact
# builds, instead of at tag time.
run: |
./scripts/release/check-versions.sh --require-dated-release
./scripts/release/check-ohos-deps.sh
- name: Reconfirm clean source snapshot
run: git diff --exit-code
web:
name: Verify exact candidate web surface
timeout-minutes: 15
needs: resolve
if: ${{ !cancelled() && needs.resolve.result == 'success' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
# resolve already proved expected_sha equals GITHUB_SHA. Do not
# interpolate that SHA into checkout or the npm cache key.
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
# `check:facts` derives each model's `addedAt` from the commit date
# on which its id first appeared in the model declaration paths
# (web/scripts/facts-lib.mjs). A shallow checkout collapses every
# date to the tip commit and the committed facts always read as
# stale; web.yml and ci.yml pin depth 0 for the same reason.
fetch-depth: 0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
cache: npm
cache-dependency-path: web/package-lock.json
- name: Install web dependencies
run: npm ci
- name: Run web tests
run: npm test
- name: Check exact candidate web surface
env:
GITHUB_TOKEN: ${{ github.token }}
run: npm run check
parity:
# The same gate release.yml runs before publish. release.yml refuses a tag
# unless an RC run for that exact SHA has this job green
# (scripts/release/require-rc-receipt.sh matches the "Parity" name).
name: Parity
needs: resolve
if: ${{ !cancelled() && needs.resolve.result == 'success' }}
uses: ./.github/workflows/release-parity.yml
artifacts:
permissions:
contents: read
actions: read
needs: [resolve, web]
if: ${{ !cancelled() && needs.resolve.result == 'success' && needs.web.result == 'success' }}
uses: ./.github/workflows/release-artifacts.yml
with:
source_sha: ${{ needs.resolve.outputs.sha }}
version: ${{ needs.resolve.outputs.version }}
compiled_host_delivery: ${{ vars.CODEWHALE_COMPILED_HOST_DELIVERY == '1' }}
compiled_host_run_id: ${{ vars.CODEWHALE_COMPILED_HOST_QUALIFICATION_RUN_ID || '' }}
compiled_host_targets: ${{ vars.CODEWHALE_COMPILED_HOST_TARGETS || 'linux-x64,macos-arm64,windows-x64' }}
retention_days: 7
secrets:
CODEWHALE_RELEASE_SIGNING_KEY: ${{ secrets.CODEWHALE_RELEASE_SIGNING_KEY }}
-133
View File
@@ -1,133 +0,0 @@
name: Release parity
# The one parity gate. release-candidate.yml and release.yml both call this,
# so the SHA an RC validates has passed exactly the gate that publish runs.
# Before this was shared, the RC skipped parity: RC 35707500620 was green on
# 3e8bf29946, Release 35728585975 then failed parity on that SHA, and v0.10.0
# was re-pointed to 1be1a703b, which docs/RELEASE_RUNBOOK.md forbids.
#
# Checks out the caller's GITHUB_SHA. Callers must verify that SHA first.
on:
workflow_call:
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
jobs:
parity:
name: Workspace parity
# Cold check, Clippy, executable and test builds exceeded the old 45m
# limit before the suite started. Retain the full CI test-job budget.
timeout-minutes: 210
runs-on: ubuntu-latest
steps:
# Every caller's resolve job already proved GITHUB_SHA equals the
# candidate or tag commit. Do not interpolate a SHA into checkout or
# cache keys —
# CodeQL treats a *sha* ref as an untrusted checkout on workflow_dispatch
# (default-branch cache write).
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master 2026-07-18
with:
toolchain: 1.100.0
components: clippy, rustfmt
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
id: sccache
continue-on-error: true
- name: Enable sccache
if: steps.sccache.outcome == 'success'
shell: bash
run: |
{
echo "SCCACHE_GHA_ENABLED=true"
echo "RUSTC_WRAPPER=sccache"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
} >> "${GITHUB_ENV}"
- name: Install Linux system dependencies
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
echo "apt-get update failed (attempt $i); retrying in 15s"
sleep 15
done
sudo apt-get install -y libdbus-1-dev pkg-config bubblewrap apparmor-profiles
sh scripts/prepare-linux-test-sandbox.sh
# Restore after the trusted lockfile is on disk. Key is OS + arch +
# explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
# hash. Never interpolate github.event, github.ref, github.sha, or inputs.
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
cache-bin: false
prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
- uses: taiki-e/install-action@e88e69ecdb9658bd172693dcdc2c84e7f0ab6a11 # nextest
with:
tool: nextest
- name: Format check
run: cargo fmt --all -- --check
- name: Compile check
run: cargo check --workspace --all-targets --locked
- name: OHOS dependency graph
run: ./scripts/release/check-ohos-deps.sh
- name: Clippy
# Same lint set as ci.yml's "Run clippy": collapsible_if and
# assertions_on_constants were removed there on purpose, so allowing
# them here made the release gate weaker than the merge gate.
run: |
cargo clippy --workspace --all-targets --all-features --locked -- \
-D warnings \
-A clippy::uninlined_format_args \
-A clippy::too_many_arguments \
-A clippy::unnecessary_map_or
- name: Build headroom
# Same fixed ephemeral-runner provisioning as CI Safety, Lint, and Test.
# Resource headroom is preventive; a shutdown alone does not prove OOM.
shell: bash
run: bash scripts/prepare-ubuntu-build-headroom.sh
- name: Build canonical executable for acceptance tests
run: cargo build -p codewhale-cli --bin codewhale --all-features --locked
- name: Workspace tests
# Same test binaries as `cargo test`, run by cargo-nextest: one process
# per test. This gate used libtest, where every test shares one process,
# so a test that mutates process-global state leaks into its neighbours.
# It failed on five such tests — deterministically, and on a different
# set on different machines — while CI's nextest lanes were green on the
# same source, and every one of them passes in isolation. Match the lane
# that gates every merge; doctests are the next step, because nextest
# does not run them.
# The runner has been shut down during this step's build on several
# candidates (exit 143, no test result). The same headroom trace as
# ci.yml's test step records whether memory or disk ran out, starting
# with what the earlier check and clippy steps left behind.
shell: bash
run: |
echo "[headroom] before tests: $(free -m | awk '/^Mem:/{print "mem used " $3 "/" $2 " MiB"} /^Swap:/{print "swap used " $3 "/" $2 " MiB"}' | paste -sd ' ') disk free $(df -h / | awk 'NR==2{print $4}') target $(du -sh target 2>/dev/null | cut -f1)"
( while sleep 30; do
echo "[headroom] $(free -m | awk '/^Mem:/{print "mem used " $3 "/" $2 " MiB"} /^Swap:/{print "swap used " $3 "/" $2 " MiB"}' | paste -sd ' ') disk free $(df -h / | awk 'NR==2{print $4}')"
done ) &
monitor=$!
status=0
sh scripts/with-hermetic-test-home.sh cargo nextest run --workspace --all-features --locked --profile ci || status=$?
kill "$monitor" 2>/dev/null || true
exit "$status"
env:
CARGO_BUILD_JOBS: '1'
# Match the CI test lane: test threads get the same stack the product
# gives itself (main.rs CODEWHALE_MAIN_STACK_BYTES). See the note in
# ci.yml's "Run tests" step. Without it this gate runs the deep
# engine/runtime futures on a stack that never ships.
RUST_MIN_STACK: '16777216'
- name: Workspace doctests
run: sh scripts/with-hermetic-test-home.sh cargo test --workspace --all-features --locked --doc
env:
RUST_MIN_STACK: '16777216'
- name: Protocol schema parity
run: sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-protocol --test parity_protocol --locked
- name: State persistence parity
run: sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-state --test parity_state --locked
- name: Lockfile drift guard
run: git diff --exit-code -- Cargo.lock
-216
View File
@@ -1,216 +0,0 @@
name: Release Republish Channels
# Recovery for derived distribution channels — the container image and the
# Homebrew tap — when a released tag has them missing or stale.
#
# This is a separate workflow, dispatched from the DEFAULT BRANCH, on purpose.
# `workflow_dispatch` reads its input schema from the workflow file at the ref
# being dispatched, and `release.yml` requires being dispatched from the tag
# itself. Any recovery option added to `release.yml` is therefore unusable on
# tags that predate it — which is every tag that could ever need recovery.
# Taking the tag as an input sidesteps that entirely.
#
# It never writes GitHub Release bytes. `release.yml` owns those, and they stay
# immutable; this only (re)publishes channels derived from an existing release.
on:
workflow_dispatch:
inputs:
version:
description: 'Released version to republish, without v (e.g. 0.9.1). The tag must already exist and have a published GitHub Release.'
required: true
type: string
channels:
description: 'Which derived channels to republish.'
required: true
default: 'docker+homebrew'
type: choice
options:
- docker+homebrew
- docker
- homebrew
concurrency:
group: release-republish-${{ inputs.version }}
cancel-in-progress: false
permissions:
contents: read
jobs:
resolve:
timeout-minutes: 10
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.release.outputs.tag }}
sha: ${{ steps.release.outputs.sha }}
version: ${{ steps.release.outputs.version }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
- name: Resolve released tag
id: release
shell: bash
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if ! [[ "${INPUT_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Version '${INPUT_VERSION}' must use X.Y.Z." >&2
exit 1
fi
tag="v${INPUT_VERSION}"
if ! git rev-parse --verify "refs/tags/${tag}^{commit}" >/dev/null 2>&1; then
echo "::error::Tag ${tag} does not exist." >&2
exit 1
fi
sha="$(git rev-parse "refs/tags/${tag}^{commit}")"
{
echo "tag=${tag}"
echo "sha=${sha}"
echo "version=${INPUT_VERSION}"
} >> "${GITHUB_OUTPUT}"
echo "Resolved ${tag} -> ${sha}"
- name: Require a complete published release
# The inverse of release.yml's immutability guard. That one refuses to
# run when assets exist; this one refuses to run unless the published
# release carries exactly the assets its own checksum manifest lists,
# each fully uploaded, because a channel derived from a partial set
# would advertise bytes that are not there. The tag may predate the
# current inventory, so its manifest, not today's list, is the check.
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.release.outputs.tag }}
run: node scripts/release/verify-release-inventory.js --manifest "${GITHUB_REPOSITORY}" "${TAG}"
- name: Verify the remote tag still points at this commit
env:
EXPECTED_SHA: ${{ steps.release.outputs.sha }}
TAG: ${{ steps.release.outputs.tag }}
run: |
./scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" \
"${TAG}" \
"${EXPECTED_SHA}"
docker:
timeout-minutes: 30
needs: resolve
if: ${{ contains(inputs.channels, 'docker') }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout release source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ needs.resolve.outputs.sha }}
path: source
- name: Checkout release infrastructure
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ needs.resolve.outputs.sha }}
path: infra
- name: Set up QEMU
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Normalize image name
id: image
shell: bash
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: Revalidate release tag before container publish
env:
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
TAG: ${{ needs.resolve.outputs.tag }}
run: |
./infra/scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" \
"${TAG}" \
"${EXPECTED_SHA}"
- name: Build and push
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
env:
DOCKER_BUILD_RECORD_UPLOAD: false
DOCKER_BUILD_SUMMARY: false
with:
context: source
file: infra/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
build-args: |
CODEWHALE_BUILD_SHA=${{ needs.resolve.outputs.sha }}
tags: |
${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.version }}
${{ steps.image.outputs.name }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Smoke published container entrypoints
shell: bash
env:
IMAGE: ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
run: |
set -euo pipefail
docker pull "${IMAGE}"
docker run --rm --entrypoint codewhale "${IMAGE}" --version
docker run --rm --entrypoint codew "${IMAGE}" --version
homebrew:
timeout-minutes: 20
needs: resolve
if: ${{ contains(inputs.channels, 'homebrew') }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check Homebrew tap token
id: homebrew-token
env:
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
run: |
if [[ -z "${TOKEN:-}" ]]; then
echo "::error::No Homebrew tap token configured; cannot republish the tap." >&2
exit 1
fi
# Recovery logic must come from the current protected default branch.
# The released bytes remain pinned by the tag and checksum manifest;
# checking out the old tag here would also restore the bug being repaired.
- name: Checkout release infrastructure
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.repository.default_branch }}
- name: Download checksum manifest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release download "${{ needs.resolve.outputs.tag }}" \
--repo "${{ github.repository }}" \
--pattern 'codewhale-artifacts-sha256.txt' \
--dir /tmp
- name: Revalidate release tag before Homebrew tap write
env:
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
TAG: ${{ needs.resolve.outputs.tag }}
run: |
./scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" \
"${TAG}" \
"${EXPECTED_SHA}"
- name: Update Homebrew tap
env:
TAG: ${{ needs.resolve.outputs.tag }}
MANIFEST: /tmp/codewhale-artifacts-sha256.txt
TAP_REPO: Hmbown/homebrew-deepseek-tui
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
run: bash .github/scripts/update-homebrew-tap.sh
-676
View File
@@ -1,676 +0,0 @@
name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
version:
description: 'Release version without v; dispatch from the matching existing vX.Y.Z or vX.Y.Z-prerelease tag ref'
required: true
type: string
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
jobs:
resolve:
timeout-minutes: 10
runs-on: ubuntu-latest
permissions:
contents: read
# Read release-candidate.yml runs for the RC receipt check below.
actions: read
outputs:
tag: ${{ steps.release.outputs.tag }}
sha: ${{ steps.release.outputs.sha }}
version: ${{ steps.release.outputs.version }}
prerelease: ${{ steps.release.outputs.prerelease }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
- name: Resolve release source
id: release
shell: bash
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
if ! [[ "${INPUT_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "::error::Release version '${INPUT_VERSION}' must use X.Y.Z or X.Y.Z-prerelease." >&2
exit 1
fi
tag="v${INPUT_VERSION}"
if [[ "${GITHUB_REF}" != "refs/tags/${tag}" ]]; then
echo "::error::Dispatch release.yml from --ref ${tag}, not ${GITHUB_REF}." >&2
exit 1
fi
else
tag="${GITHUB_REF_NAME}"
fi
if ! [[ "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "::error::Release tag '${tag}' must use vX.Y.Z or vX.Y.Z-prerelease." >&2
exit 1
fi
if ! git rev-parse --verify "refs/tags/${tag}^{commit}" >/dev/null 2>&1; then
echo "::error::Release tag ${tag} does not exist. Create it from the frozen main commit before dispatching." >&2
exit 1
fi
sha="$(git rev-parse "refs/tags/${tag}^{commit}")"
event_sha="$(git rev-parse "${GITHUB_SHA}^{commit}")"
if [[ "${event_sha}" != "${sha}" ]]; then
echo "::error::Trigger SHA ${event_sha} does not match ${tag} at ${sha}; the tag moved after this run was created." >&2
exit 1
fi
version="${tag#v}"
{
echo "tag=${tag}"
echo "sha=${sha}"
echo "version=${version}"
if [[ "${version}" == *-* ]]; then
echo "prerelease=true"
else
echo "prerelease=false"
fi
} >> "${GITHUB_OUTPUT}"
- name: Validate tagged release metadata
shell: bash
env:
SHA: ${{ steps.release.outputs.sha }}
TAG: ${{ steps.release.outputs.tag }}
run: |
set -euo pipefail
git checkout --detach "${SHA}"
expected="${TAG#v}"
workspace_version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
npm_version="$(node -p "require('./npm/codewhale/package.json').version")"
binary_version="$(node -p "require('./npm/codewhale/package.json').codewhaleBinaryVersion")"
sdk_version="$(node -p "require('./npm/runtime-sdk/package.json').version")"
vscode_version="$(node -p "require('./extensions/vscode/package.json').version")"
for pair in \
"workspace:${workspace_version}" \
"npm:${npm_version}" \
"npm binary:${binary_version}" \
"runtime-sdk:${sdk_version}" \
"vscode:${vscode_version}"; do
label="${pair%%:*}"
actual="${pair#*:}"
if [[ "${actual}" != "${expected}" ]]; then
echo "::error::${label} version ${actual} does not match tag ${TAG}." >&2
exit 1
fi
done
./scripts/release/check-versions.sh --require-dated-release
- name: Require release source on main
run: ./scripts/release/ensure-release-on-main.sh "${{ steps.release.outputs.sha }}"
- name: Require a green release-candidate receipt for this exact SHA
# Fail fast, before the long parity and artifact builds: the tag must
# point at a commit a release-candidate run already validated,
# Parity included. A missing receipt means run the RC on this SHA,
# never move the tag (v0.10.0 was re-pointed after the RC skipped
# parity).
env:
GH_TOKEN: ${{ github.token }}
SHA: ${{ steps.release.outputs.sha }}
run: ./scripts/release/require-rc-receipt.sh "${GITHUB_REPOSITORY}" "${SHA}"
- name: Refuse an existing public asset set
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.release.outputs.tag }}
run: node scripts/release/ensure-release-assets-absent.js "${GITHUB_REPOSITORY}" "${TAG}"
parity:
name: Parity
needs: resolve
uses: ./.github/workflows/release-parity.yml
artifacts:
permissions:
contents: read
actions: read
needs: [parity, resolve]
if: ${{ !cancelled() && needs.resolve.result == 'success' && needs.parity.result == 'success' }}
uses: ./.github/workflows/release-artifacts.yml
with:
source_sha: ${{ needs.resolve.outputs.sha }}
version: ${{ needs.resolve.outputs.version }}
compiled_host_delivery: ${{ vars.CODEWHALE_COMPILED_HOST_DELIVERY == '1' }}
compiled_host_run_id: ${{ vars.CODEWHALE_COMPILED_HOST_QUALIFICATION_RUN_ID || '' }}
compiled_host_targets: ${{ vars.CODEWHALE_COMPILED_HOST_TARGETS || 'linux-x64,macos-arm64,windows-x64' }}
retention_days: 14
secrets:
CODEWHALE_RELEASE_SIGNING_KEY: ${{ secrets.CODEWHALE_RELEASE_SIGNING_KEY }}
docker-build:
needs: [artifacts, resolve]
if: ${{ !cancelled() && needs.artifacts.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
name: Docker ${{ matrix.platform }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
platform: linux/amd64
architecture: amd64
cli_artifact: codewhale-linux-x64
shim_artifact: codew-linux-x64
- runner: ubuntu-24.04-arm
platform: linux/arm64
architecture: arm64
cli_artifact: codewhale-linux-arm64
shim_artifact: codew-linux-arm64
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
steps:
- name: Checkout release infrastructure
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ needs.resolve.outputs.sha }}
path: infra
- name: Download Codewhale release binary
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: ${{ matrix.cli_artifact }}
path: docker-context/bin
- name: Download codew release alias
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: ${{ matrix.shim_artifact }}
path: docker-context/bin
- name: Verify native release bytes
shell: bash
env:
CLI_ARTIFACT: ${{ matrix.cli_artifact }}
SHIM_ARTIFACT: ${{ matrix.shim_artifact }}
run: |
set -euo pipefail
mv -- "docker-context/bin/${CLI_ARTIFACT}" docker-context/bin/codewhale
mv -- "docker-context/bin/${SHIM_ARTIFACT}" docker-context/bin/codew
chmod 0755 docker-context/bin/codewhale docker-context/bin/codew
cmp docker-context/bin/codewhale docker-context/bin/codew
docker-context/bin/codewhale --version
docker-context/bin/codew --version
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Normalize image name
id: image
shell: bash
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: Extract image labels
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: |
${{ steps.image.outputs.name }}
tags: |
type=raw,value=${{ needs.resolve.outputs.version }}
- name: Revalidate release tag before container upload
env:
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
TAG: ${{ needs.resolve.outputs.tag }}
run: |
./infra/scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" \
"${TAG}" \
"${EXPECTED_SHA}"
- name: Assemble and push native image by digest
id: build
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
env:
DOCKER_BUILD_RECORD_UPLOAD: false
DOCKER_BUILD_SUMMARY: false
with:
context: docker-context
file: infra/packaging/docker/Dockerfile.release
platforms: ${{ matrix.platform }}
provenance: mode=max
sbom: true
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ steps.image.outputs.name }},push-by-digest=true,name-canonical=true,push=true
- name: Smoke native image digest
shell: bash
env:
IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
run: |
set -euo pipefail
docker pull "${IMAGE}"
docker run --rm --entrypoint codewhale "${IMAGE}" --version
docker run --rm --entrypoint codew "${IMAGE}" --version
- name: Export image digest
shell: bash
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
set -euo pipefail
if ! [[ "${DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "Unexpected image digest: ${DIGEST}" >&2
exit 1
fi
mkdir -p digests
touch "digests/${DIGEST#sha256:}"
- name: Upload image digest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: docker-digest-${{ matrix.architecture }}
path: digests/*
if-no-files-found: error
retention-days: 1
overwrite: true
# Derived channel: the tagged/latest manifest is published only after the
# canonical GitHub Release exists. docker-build pushes by digest (untagged)
# and smokes each image before the release, so a broken image still stops
# the release, but no public tag can point at a version whose GitHub
# Release was never published.
docker:
timeout-minutes: 30
needs: [docker-build, release, resolve]
if: ${{ !cancelled() && needs.docker-build.result == 'success' && needs.release.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout release infrastructure
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ needs.resolve.outputs.sha }}
path: infra
- name: Download native image digests
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
path: digests
pattern: docker-digest-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Normalize image name
id: image
shell: bash
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
- name: Extract metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: |
${{ steps.image.outputs.name }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=ref,event=tag
type=semver,pattern={{version}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=semver,pattern=v{{major}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=v${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=latest
- name: Revalidate release tag before container publish
env:
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
TAG: ${{ needs.resolve.outputs.tag }}
run: |
./infra/scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" \
"${TAG}" \
"${EXPECTED_SHA}"
- name: Publish multi-architecture manifest
shell: bash
env:
IMAGE: ${{ steps.image.outputs.name }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
set -euo pipefail
mapfile -t digest_files < <(find digests -maxdepth 1 -type f -printf '%f\n' | sort)
if [[ "${#digest_files[@]}" -ne 2 ]]; then
echo "Expected exactly two native image digests; found ${#digest_files[@]}." >&2
exit 1
fi
sources=()
for digest in "${digest_files[@]}"; do
if ! [[ "${digest}" =~ ^[0-9a-f]{64}$ ]]; then
echo "Unexpected image digest file: ${digest}" >&2
exit 1
fi
sources+=("${IMAGE}@sha256:${digest}")
done
tag_args=()
while IFS= read -r tag; do
[[ -n "${tag}" ]] && tag_args+=(--tag "${tag}")
done <<< "${TAGS}"
if [[ "${#tag_args[@]}" -eq 0 ]]; then
echo "No container tags were generated." >&2
exit 1
fi
docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"
- name: Verify and smoke published container
shell: bash
env:
IMAGE: ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
run: |
set -euo pipefail
docker buildx imagetools inspect "${IMAGE}"
raw_manifest="$(docker buildx imagetools inspect --raw "${IMAGE}")"
jq -e \
'[.manifests[] | select(.platform.os == "linux") | "linux/\(.platform.architecture)"] | unique | sort == ["linux/amd64", "linux/arm64"]' \
<<< "${raw_manifest}"
docker pull "${IMAGE}"
docker run --rm --entrypoint codewhale "${IMAGE}" --version
docker run --rm --entrypoint codew "${IMAGE}" --version
release:
timeout-minutes: 30
needs: [artifacts, docker-build, resolve]
if: ${{ !cancelled() && needs.artifacts.result == 'success' && (needs.resolve.outputs.prerelease == 'true' || needs.docker-build.result == 'success') }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ needs.resolve.outputs.sha }}
path: repo
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
package-manager-cache: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: codewhale-release-assets
path: artifacts
- name: Revalidate exact authoritative asset set
run: node repo/scripts/release/assemble-release-assets.js --verify artifacts
- name: Generate release body from CHANGELOG
shell: bash
run: |
./repo/scripts/release/generate-release-body.sh \
"${{ needs.resolve.outputs.tag }}" repo/CHANGELOG.md > release-body.md
- name: Revalidate release tag before GitHub Release write
env:
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
TAG: ${{ needs.resolve.outputs.tag }}
run: |
./repo/scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" \
"${TAG}" \
"${EXPECTED_SHA}"
- name: Reconfirm public asset set is still empty
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.resolve.outputs.tag }}
run: node repo/scripts/release/ensure-release-assets-absent.js "${GITHUB_REPOSITORY}" "${TAG}"
# Upload into a DRAFT: 34 sequential uploads that die halfway must leave
# nothing public (and nothing a rerun's immutability guard refuses).
- uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3
with:
tag_name: ${{ needs.resolve.outputs.tag }}
name: Codewhale ${{ needs.resolve.outputs.tag }}
files: artifacts/*
draft: true
prerelease: ${{ needs.resolve.outputs.prerelease == 'true' }}
body_path: release-body.md
overwrite_files: false
fail_on_unmatched_files: true
- name: Verify the draft's exact asset set, then publish it at once
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.resolve.outputs.tag }}
run: |
node repo/scripts/release/verify-release-inventory.js \
--draft --asset-dir artifacts --publish \
"${GITHUB_REPOSITORY}" "${TAG}"
# CNB tag pushes start its public binary pipeline, so mirror only after
# the canonical release is complete. Calling the reusable workflow here
# also works when GITHUB_TOKEN publication suppresses release events.
cnb:
needs: [release, resolve]
if: ${{ !cancelled() && needs.release.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
uses: ./.github/workflows/sync-cnb.yml
with:
release_tag: ${{ needs.resolve.outputs.tag }}
source_sha: ${{ needs.resolve.outputs.sha }}
secrets:
CNB_GIT_TOKEN: ${{ secrets.CNB_GIT_TOKEN }}
gitcode:
needs: [release, resolve]
if: ${{ !cancelled() && needs.release.result == 'success' && vars.GITCODE_MIRROR == 'enabled' && needs.resolve.outputs.prerelease != 'true' }}
uses: ./.github/workflows/sync-gitcode.yml
with:
release_tag: ${{ needs.resolve.outputs.tag }}
source_sha: ${{ needs.resolve.outputs.sha }}
secrets:
GITCODE_TOKEN: ${{ secrets.GITCODE_TOKEN }}
npm:
timeout-minutes: 20
needs: [release, resolve]
if: ${{ !cancelled() && needs.release.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ needs.resolve.outputs.sha }}
fetch-depth: 0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false
- name: Pin OIDC-capable npm CLI
run: npm install --global [email protected]
- name: Revalidate release tag before npm publish
env:
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
TAG: ${{ needs.resolve.outputs.tag }}
run: |
./scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" \
"${TAG}" \
"${EXPECTED_SHA}"
- name: Revalidate public release assets
env:
GH_TOKEN: ${{ github.token }}
run: ./scripts/release/verify-release-assets.sh "${{ needs.resolve.outputs.version }}"
- name: Test npm wrapper
working-directory: npm/codewhale
run: npm test
- name: Publish npm wrapper with trusted publishing
working-directory: npm/codewhale
env:
# npm runs prepublishOnly in this step; that guard revalidates the
# public GitHub Release and therefore needs the same read token as
# the explicit asset gate above.
GH_TOKEN: ${{ github.token }}
PRERELEASE: ${{ needs.resolve.outputs.prerelease }}
run: |
set -euo pipefail
if [[ "${PRERELEASE}" == "true" ]]; then
npm publish --access public --tag next
else
npm publish --access public
fi
homebrew:
timeout-minutes: 20
needs: [release, resolve]
if: ${{ !cancelled() && needs.release.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check Homebrew tap token
# A real release must update the tap. Skipping silently (the old
# behaviour) left `brew install codewhale` on the previous version
# with a green release run; fail loudly like release-republish.yml.
env:
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
run: |
if [[ -z "${TOKEN:-}" ]]; then
echo "::error::No Homebrew tap token configured (HOMEBREW_TAP_PAT or RELEASE_TAG_PAT); cannot update the tap for this release." >&2
exit 1
fi
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ needs.resolve.outputs.sha }}
- name: Download checksum manifest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release download "${{ needs.resolve.outputs.tag }}" \
--repo "${{ github.repository }}" \
--pattern 'codewhale-artifacts-sha256.txt' \
--dir /tmp
- name: Revalidate release tag before Homebrew tap write
env:
EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
TAG: ${{ needs.resolve.outputs.tag }}
run: |
./scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" \
"${TAG}" \
"${EXPECTED_SHA}"
- name: Update Homebrew tap
env:
TAG: ${{ needs.resolve.outputs.tag }}
MANIFEST: /tmp/codewhale-artifacts-sha256.txt
TAP_REPO: Hmbown/homebrew-deepseek-tui
TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
run: bash .github/scripts/update-homebrew-tap.sh
# Every publish used to turn Web Frontend red: `check:latest-release`
# compares the checked-in release record with GitHub's latest release, and
# nothing wrote the record, so someone hand-committed it after each release.
# This job writes the record, proves it against the web gates, and proposes
# it to main as a bot PR. It never pushes to the default branch: the ruleset
# requires a PR there. While the PR is open (up to 24h after publish, and
# only when the record is exactly one release behind), `check:latest-release`
# warns instead of failing on every event (see web/scripts/sync-latest-release.mjs).
#
# Known limit: repo settings stop GITHUB_TOKEN from opening PRs, so the PR is
# opened with RELEASE_TAG_PAT. Without it the job pushes the branch and fails
# with the compare link, which is still one click instead of a hand commit.
sync-release-record:
timeout-minutes: 15
needs: [release, resolve]
if: ${{ !cancelled() && needs.release.result == 'success' && needs.resolve.outputs.prerelease != 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.repository.default_branch }}
# derive-facts.mjs dates model ids from git history (web.yml pins 0
# for the same reason); a shallow clone rewrites every addedAt.
fetch-depth: 0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
package-manager-cache: false
- name: Refresh the release record from the published release
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
node web/scripts/sync-latest-release.mjs
node web/scripts/derive-facts.mjs
node web/scripts/derive-release-docs.mjs
- name: Prove the record passes the web fact gates
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
node web/scripts/sync-latest-release.mjs --check
node web/scripts/check-cloud-facts.mjs
node web/scripts/check-facts.mjs
node web/scripts/check-release-docs.mjs
- name: Propose the record to the default branch
env:
TAG: ${{ needs.resolve.outputs.tag }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
PR_TOKEN: ${{ secrets.RELEASE_TAG_PAT }}
run: |
set -euo pipefail
paths=(
web/data/latest-published-release.json
docs/public-surface-facts.json
docs/cloud-facts/stable.json
web/lib/facts.generated.ts
web/data/agent-docs.generated.json
web/data/agent-docs.release.json
web/data/agent-docs.release-index.json
web/data/features.generated.json
)
if git diff --quiet -- "${paths[@]}"; then
echo "Release record already current on ${DEFAULT_BRANCH}; nothing to propose."
exit 0
fi
# GitHub's latest may already be newer than this run's tag; the
# record always follows GitHub, so name the branch after what it says.
recorded="$(node -p "require('./web/data/latest-published-release.json').tag")"
branch="chore/release-record-${recorded}"
title="chore(web): record ${recorded} as the published release"
git config user.name "CodeWhale Bot"
git config user.email "[email protected]"
git switch --quiet -c "${branch}"
git add -- "${paths[@]}"
git commit --quiet \
-m "${title}" \
-m "Written by release.yml sync-release-record after ${TAG} published. Gates run in the job: sync-latest-release --check, check-cloud-facts, check-facts."
git show --stat --format='%h %s' HEAD
if git ls-remote --exit-code --heads origin "${branch}" >/dev/null; then
echo "::notice title=Release record already proposed::Branch ${branch} exists; leaving it for review."
exit 0
fi
git push origin "HEAD:refs/heads/${branch}"
compare="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/${DEFAULT_BRANCH}...${branch}?expand=1"
if [[ -n "${PR_TOKEN}" ]] && GH_TOKEN="${PR_TOKEN}" gh pr create \
--repo "${GITHUB_REPOSITORY}" --base "${DEFAULT_BRANCH}" --head "${branch}" \
--title "${title}" \
--body "Written by release.yml \`sync-release-record\` after ${TAG} published. Merging it turns \`check:latest-release\` green on ${DEFAULT_BRANCH}. No-Issue: release automation."; then
echo "::notice title=Release record proposed::Opened a PR from ${branch}."
exit 0
fi
echo "::error title=Release record PR not opened::Branch ${branch} is pushed; open and merge it: ${compare}"
exit 1
+94
View File
@@ -0,0 +1,94 @@
name: rust-ci
on:
pull_request:
branches:
- main
paths:
- "codex-rs/**"
- ".github/**"
push:
branches:
- main
workflow_dispatch:
# For CI, we build in debug (`--profile dev`) rather than release mode so we
# get signal faster.
jobs:
# CI that don't need specific targets
general:
name: Format / etc
runs-on: ubuntu-24.04
defaults:
run:
working-directory: codex-rs
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: cargo fmt
run: cargo fmt -- --config imports_granularity=Item --check
# CI to validate on different os/targets
lint_build_test:
name: ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
defaults:
run:
working-directory: codex-rs
strategy:
fail-fast: false
matrix:
# Note: While Codex CLI does not support Windows today, we include
# Windows in CI to ensure the code at least builds there.
include:
- runner: macos-14
target: aarch64-apple-darwin
- runner: macos-14
target: x86_64-apple-darwin
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- runner: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
${{ github.workspace }}/codex-rs/target/
key: cargo-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }}
name: Install musl build tools
run: |
sudo apt install -y musl-tools pkg-config
- name: Initialize failure flag
run: echo "FAILED=" >> $GITHUB_ENV
- name: cargo clippy
run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings || echo "FAILED=${FAILED:+$FAILED, }cargo clippy" >> $GITHUB_ENV
- name: cargo test
run: cargo test --target ${{ matrix.target }} || echo "FAILED=${FAILED:+$FAILED, }cargo test" >> $GITHUB_ENV
- name: Fail if any step failed
if: env.FAILED != ''
run: |
echo "See logs above, as the following steps failed:"
echo "$FAILED"
exit 1
+156
View File
@@ -0,0 +1,156 @@
# Release workflow for codex-rs.
# To release, follow a workflow like:
# ```
# git tag -a rust-v0.1.0 -m "Release 0.1.0"
# git push origin rust-v0.1.0
# ```
name: rust-release
on:
push:
tags:
- "rust-v.*.*.*"
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
env:
TAG_REGEX: '^rust-v\.[0-9]+\.[0-9]+\.[0-9]+$'
jobs:
tag-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate tag matches Cargo.toml version
shell: bash
run: |
set -euo pipefail
echo "::group::Tag validation"
# 1. Must be a tag and match the regex
[[ "${GITHUB_REF_TYPE}" == "tag" ]] \
|| { echo "❌ Not a tag push"; exit 1; }
[[ "${GITHUB_REF_NAME}" =~ ${TAG_REGEX} ]] \
|| { echo "❌ Tag '${GITHUB_REF_NAME}' != ${TAG_REGEX}"; exit 1; }
# 2. Extract versions
tag_ver="${GITHUB_REF_NAME#rust-v.}"
cargo_ver="$(grep -m1 '^version' codex-rs/Cargo.toml \
| sed -E 's/version *= *"([^"]+)".*/\1/')"
# 3. Compare
[[ "${tag_ver}" == "${cargo_ver}" ]] \
|| { echo "❌ Tag ${tag_ver} ≠ Cargo.toml ${cargo_ver}"; exit 1; }
echo "✅ Tag and Cargo.toml agree (${tag_ver})"
echo "::endgroup::"
build:
needs: tag-check
name: ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
defaults:
run:
working-directory: codex-rs
strategy:
fail-fast: false
matrix:
include:
- runner: macos-14
target: aarch64-apple-darwin
- runner: macos-14
target: x86_64-apple-darwin
- runner: ubuntu-24.04
target: x86_64-unknown-linux-musl
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
${{ github.workspace }}/codex-rs/target/
key: cargo-release-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }}
name: Install musl build tools
run: |
sudo apt install -y musl-tools pkg-config
- name: Cargo build
run: cargo build --target ${{ matrix.target }} --release --all-targets --all-features
- name: Stage artifacts
shell: bash
run: |
dest="dist/${{ matrix.target }}"
mkdir -p "$dest"
cp target/${{ matrix.target }}/release/codex-repl "$dest/codex-repl-${{ matrix.target }}"
cp target/${{ matrix.target }}/release/codex-exec "$dest/codex-exec-${{ matrix.target }}"
cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"
- if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-gnu' }}
name: Stage Linux-only artifacts
shell: bash
run: |
dest="dist/${{ matrix.target }}"
cp target/${{ matrix.target }}/release/codex-linux-sandbox "$dest/codex-linux-sandbox-${{ matrix.target }}"
- name: Compress artifacts
shell: bash
run: |
zstd -T0 -19 --rm "$dest"/*
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: codex-rs/dist/${{ matrix.target }}/*
release:
needs: build
name: release
runs-on: ubuntu-24.04
env:
RELEASE_TAG: codex-rs-${{ github.sha }}-${{ github.run_attempt }}-${{ github.ref_name }}
steps:
- uses: actions/download-artifact@v4
with:
path: dist
- name: List
run: ls -R dist/
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.RELEASE_TAG }}
files: dist/**
# TODO(ragona): I'm going to leave these as prerelease/draft for now.
# It gives us 1) clarity that these are not yet a stable version, and
# 2) allows a human step to review the release before publishing the draft.
prerelease: true
draft: true
- uses: facebook/dotslash-publish-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ env.RELEASE_TAG }}
config: .github/dotslash-config.json
-89
View File
@@ -1,89 +0,0 @@
name: Security audit
permissions: {}
on:
workflow_dispatch: {}
workflow_call: {}
env:
CARGO_TERM_COLOR: always
jobs:
self-audit:
name: cargo-audit
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: dtolnay/[email protected]
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Run cargo audit
run: cargo audit
npm-audit:
name: npm-audit (${{ matrix.name }})
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
strategy:
fail-fast: false
matrix:
# Every committed package-lock.json, including build-only tooling.
include:
- { name: root, directory: . }
- { name: web, directory: web }
- { name: telemetry-ingest, directory: telemetry-ingest }
- { name: extension-host, directory: crates/engine/extension-host }
- { name: vscode, directory: extensions/vscode }
- { name: feishu-bridge, directory: integrations/feishu-bridge }
- { name: telegram-bridge, directory: integrations/telegram-bridge }
- { name: wecom-bridge, directory: integrations/wecom-bridge }
- { name: pet, directory: pet }
defaults:
run:
working-directory: ${{ matrix.directory }}
steps:
- uses: actions/checkout@v7
- uses: actions/[email protected]
with:
node-version: '22'
# Audit the complete committed lock, including build dependencies, and
# keep the raw report. Runtime compatibility remains owned by surface CI.
- name: Audit locked dependencies
if: matrix.name != 'web' && matrix.name != 'vscode'
run: |
set +e
npm audit --json > "${{ runner.temp }}/npm-audit.json"
status=$?
set -e
node -e '
const r = require(process.argv[1]);
if (r.error || !r.metadata?.vulnerabilities) throw new Error("Incomplete dependency audit");
console.log(JSON.stringify(r.metadata.vulnerabilities));
' "${{ runner.temp }}/npm-audit.json"
exit "$status"
# The website also verifies its installed, reviewed build dependency
# mitigation (web/docs/DEPENDENCY_SECURITY.md); the raw report keeps every
# upstream finding.
- name: Install and verify website build dependency mitigation
if: matrix.name == 'web'
run: npm ci --no-audit
- name: Audit website with verified build dependency mitigation
if: matrix.name == 'web'
run: npm run audit:dependencies -- --report "${{ runner.temp }}/npm-audit.json"
# The VS Code extension's packager dependency override is recorded in
# docs/dependency-maintenance.md; any reported vulnerability fails the job.
- name: Audit extension with reviewed packager advisory acceptance
if: matrix.name == 'vscode'
run: npm run audit:dependencies -- --report "${{ runner.temp }}/npm-audit.json"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: npm-audit-${{ matrix.name }}
path: ${{ runner.temp }}/npm-audit.json
if-no-files-found: error
-69
View File
@@ -1,69 +0,0 @@
name: Lock down obvious spam issues
on:
issues:
types: [opened]
permissions:
issues: write
jobs:
lockdown:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Auto-close spam patterns from new accounts
uses: actions/github-script@v9
with:
script: |
const issue = context.payload.issue;
const author = issue.user;
// Only consider brand-new accounts. If the user has been around
// long enough to file good-faith issues elsewhere, don't touch.
const created = new Date(author.created_at || 0);
const ageDays = (Date.now() - created.getTime()) / 86_400_000;
if (ageDays > 30) return;
const blob = `${issue.title || ''}\n${issue.body || ''}`;
const patterns = [
/\btg\s*@/i,
/\btelegram\s+@/i,
/\bt\.me\//i,
/\bwhatsapp\s+\+/i,
/\bseo\s+service/i,
/\bguest\s+post/i,
/\bbacklink/i,
/\bbuy\s+followers/i,
];
const hit = patterns.find(p => p.test(blob));
if (!hit) return;
// Label and close; no comment (founder, 2026-09-22). The label
// description tells a false positive how to get the issue back.
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'spam',
color: 'b60205',
description: 'Auto-closed: spam pattern from a <30-day account. Real report? Reopen with project context',
});
} catch (error) {
if (error.status !== 422) throw error; // 422: label already exists
}
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not_planned',
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['spam'],
});
-35
View File
@@ -1,35 +0,0 @@
name: Close stale issues
on:
schedule:
- cron: '17 5 * * *' # daily, off-peak
workflow_dispatch: {}
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
# Pinned from actions/stale@v10; update deliberately when refreshing the policy.
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93
with:
days-before-stale: 14
days-before-close: 7
# No stale or close messages (founder, 2026-09-22). An empty message
# makes actions/stale label and close without commenting (it checks
# `staleIssueMessage.length === 0` at the pinned SHA). The `stale`
# label is the signal; any reply removes it before the close.
stale-issue-message: ''
close-issue-message: ''
stale-issue-label: 'stale'
only-labels: 'needs-info'
exempt-issue-labels: 'pinned,keep-open,release-blocker,security'
# Don't touch PRs — `actions/stale` defaults can be aggressive
# there. We only want it for `needs-info` issues.
days-before-pr-stale: -1
days-before-pr-close: -1
operations-per-run: 60
-121
View File
@@ -1,121 +0,0 @@
name: Sync to CNB
# Mirror commits and release tags to cnb.cool/codewhale.net/codewhale
# so users behind GitHub-blocking networks can fetch the source and tagged
# releases from the Tencent-hosted mirror.
#
# Main pushes mirror to CNB unless the repository variable CNB_MIRROR is
# "disabled"; release tags arrive through release.yml; manual dispatch is the
# fallback.
#
# Why the rewrite (v0.8.31):
# The previous implementation used the opaque tencentcom/git-sync Docker
# action, which discovered every local ref via fetch-depth: 0 and tried
# to push them all — including dependabot/* branches that GitHub had
# locally. Those follow-on pushes ran without the configured credential
# helper in scope and failed with
# `fatal: could not read Username for 'https://cnb.cool'`
# No concurrency block meant the main-push and tag-push workflow runs
# that auto-tag.yml fires within seconds of each other raced. About
# half of recent runs failed for those two reasons combined.
on:
push:
branches: [main]
workflow_dispatch: {}
workflow_call:
inputs:
release_tag:
required: true
type: string
source_sha:
required: true
type: string
secrets:
CNB_GIT_TOKEN:
required: true
# Serialize runs per ref. One global group was wrong: with
# cancel-in-progress: false GitHub still keeps only ONE pending run per group
# and cancels it when a newer one queues, so a release tag sync waiting behind
# a main push was dropped by the next branch push. Keyed by ref, each tag gets
# its own group (never superseded), and a pending branch sync is only ever
# replaced by a newer push of the same branch, whose force-push supersedes it.
# The main and tag pushes from auto-tag.yml write different CNB refs, so
# running them concurrently cannot conflict.
concurrency:
group: cnb-sync-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
jobs:
sync:
if: github.repository_owner == 'codewhale-hq' && (github.event_name != 'push' || vars.CNB_MIRROR != 'disabled')
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
ref: ${{ inputs.source_sha || github.sha }}
- name: Push triggering ref to CNB
env:
CNB_TOKEN: ${{ secrets.CNB_GIT_TOKEN }}
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.release_tag }}
SOURCE_SHA: ${{ inputs.source_sha || github.sha }}
shell: bash
run: |
set -euo pipefail
if [ -z "${CNB_TOKEN:-}" ]; then
echo "::error::CNB_GIT_TOKEN secret is not set; cannot push to CNB." >&2
exit 1
fi
# URL-encode any '%' in the token so basic-auth doesn't break on
# special characters. CNB tokens are typically alphanumeric so
# this is belt-and-suspenders.
ENCODED_TOKEN="$(printf '%s' "${CNB_TOKEN}" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')"
REMOTE_URL="https://cnb:${ENCODED_TOKEN}@cnb.cool/codewhale.net/codewhale.git"
# Use a masked alias so the token never appears in log lines.
git remote add cnb "${REMOTE_URL}"
if [[ -n "${RELEASE_TAG}" || "${GITHUB_REF}" == refs/tags/* ]]; then
TAG="${RELEASE_TAG:-${GITHUB_REF#refs/tags/}}"
# The tag starts CNB's public release pipeline. Require the
# canonical release even for a manual recovery dispatch.
if ! [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
[[ "$(git rev-parse 'HEAD^{commit}')" != "${SOURCE_SHA}" ]]; then
echo "::error::CNB release source does not match the frozen tag input." >&2
exit 1
fi
./scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" "${TAG}" "${SOURCE_SHA}"
node scripts/release/verify-release-inventory.js --manifest "${GITHUB_REPOSITORY}" "${TAG}"
scripts/release/mirror-push.sh cnb "tag ${TAG}" "refs/tags/${TAG}:refs/tags/${TAG}"
elif [[ "${GITHUB_REF}" == refs/heads/main ]]; then
# Plain --force. The CNB mirror is one-way by design —
# nothing else pushes to it, so there's no contributor work
# to protect against. `--force-with-lease` would be safer
# in a multi-writer scenario, but in our setup the lease
# check requires `refs/remotes/cnb/main` to exist in the
# runner's local clone, which it never does (we add `cnb`
# as a fresh remote in this step and don't fetch first).
# That made the lease check spuriously fail with
# `! [rejected] HEAD -> main (stale info)` even when CNB
# was actually behind GitHub.
scripts/release/mirror-push.sh cnb "main" HEAD:refs/heads/main --force
else
# First-party fix/rebrand/release branches are first-class CNB
# sources for heavy Linux CI, release preflight, and
# Lighthouse/Feishu bootstrap.
# Mirror the triggering branch exactly so the CNB clone path stays
# useful before the branch has merged to main or become a release
# tag.
BRANCH="${GITHUB_REF#refs/heads/}"
scripts/release/mirror-push.sh cnb "branch ${BRANCH}" "HEAD:refs/heads/${BRANCH}" --force
fi
-74
View File
@@ -1,74 +0,0 @@
name: Sync to GitCode
on:
push:
branches: [main]
workflow_dispatch: {}
workflow_call:
inputs:
release_tag:
required: true
type: string
source_sha:
required: true
type: string
secrets:
GITCODE_TOKEN:
required: true
concurrency:
group: gitcode-sync-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
jobs:
sync:
if: github.repository_owner == 'codewhale-hq' && (github.event_name != 'push' || vars.GITCODE_MIRROR == 'enabled')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
ref: ${{ inputs.source_sha || github.sha }}
- name: Push to GitCode
env:
GITCODE_TOKEN: ${{ secrets.GITCODE_TOKEN }}
GITCODE_USER: ${{ vars.GITCODE_USERNAME || 'HunterBown' }}
GITCODE_REPO: ${{ vars.GITCODE_REPOSITORY || 'HunterBown/Codewhale' }}
RELEASE_TAG: ${{ inputs.release_tag }}
SOURCE_SHA: ${{ inputs.source_sha || github.sha }}
shell: bash
run: |
set -euo pipefail
if [ -z "${GITCODE_TOKEN:-}" ]; then
echo "::error::GITCODE_TOKEN secret is not set; cannot push to GitCode." >&2
exit 1
fi
ENCODED_USER="$(printf '%s' "${GITCODE_USER}" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')"
ENCODED_TOKEN="$(printf '%s' "${GITCODE_TOKEN}" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')"
git remote add gitcode "https://${ENCODED_USER}:${ENCODED_TOKEN}@gitcode.com/${GITCODE_REPO}.git"
if [[ -n "${RELEASE_TAG}" ]]; then
if ! [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
[[ "$(git rev-parse 'HEAD^{commit}')" != "${SOURCE_SHA}" ]]; then
echo "::error::GitCode release source does not match the frozen tag input." >&2
exit 1
fi
./scripts/release/verify-remote-tag.sh \
"https://github.com/${GITHUB_REPOSITORY}.git" "${RELEASE_TAG}" "${SOURCE_SHA}"
scripts/release/mirror-push.sh gitcode "tag ${RELEASE_TAG}" "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}"
elif [[ "${GITHUB_REF}" != refs/heads/main ]]; then
echo "::error::GitCode mirrors main only; dispatch this workflow from main." >&2
exit 1
elif [[ "${GITHUB_EVENT_NAME}" == workflow_dispatch ]]; then
scripts/release/mirror-push.sh gitcode "main" HEAD:refs/heads/main --force
scripts/release/mirror-push.sh gitcode "release tags" "refs/tags/v*:refs/tags/v*"
else
scripts/release/mirror-push.sh gitcode "main" HEAD:refs/heads/main --force
fi
-41
View File
@@ -1,41 +0,0 @@
name: Telemetry Ingest
# Final validation checks the ingest Worker against its published telemetry
# schema and the client's pinned wire format.
#
# There is deliberately NO deploy job. Publishing this Worker is the owner's
# action, run by hand, and `telemetry_endpoint` stays unset until he has
# deployed and verified. See telemetry-ingest/README.md.
on:
workflow_dispatch:
permissions:
contents: read
jobs:
check:
name: Schema weld, IP guard, and types
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: telemetry-ingest
steps:
- uses: actions/checkout@v7
- uses: actions/[email protected]
with:
node-version: 22
cache: 'npm'
cache-dependency-path: telemetry-ingest/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: TypeScript type check
run: npm run typecheck
- name: Bundle check
# `--dry-run` builds the Worker and resolves its bindings without
# touching a Cloudflare account. It is the step that would have caught
# the entrypoint exporting a non-callable named export.
run: npm run check
-126
View File
@@ -1,126 +0,0 @@
name: Issue and PR triage
on:
issues:
types: [opened, reopened]
pull_request_target:
types: [opened, reopened, synchronize]
permissions:
issues: write
pull-requests: write
contents: read
jobs:
label:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Auto-label by title, body and changed paths
uses: actions/github-script@v9
with:
script: |
const pr = context.payload.pull_request;
const issue = context.payload.issue || pr;
const title = (issue.title || '').toLowerCase();
const body = (issue.body || '').toLowerCase();
const text = `${title}\n${body}`;
const labels = new Set();
if (pr) {
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100,
});
const pathRules = [
[/^crates\/(tui|tui-kit|palette)\//, 'area:tui'],
[/^crates\/(tools\/|engine\/src\/tools\/)/, 'area:tools'],
[/^crates\/(engine|core|runtime|agent|agent-contract|protocol)\/(?!src\/tools\/)/, 'area:core'],
[/^crates\/mcp\//, 'area:mcp'],
[/^crates\/(state|snapshot|memory)\//, 'area:state'],
[/^crates\/(sandbox|execpolicy)\//, 'area:sandbox'],
[/^crates\/(plugins|skills|skill-install|hooks)\//, 'area:plugins'],
[/^(crates\/(app-server|app-server-protocol)|npm\/runtime-sdk)\//, 'area:runtime-api'],
[/^crates\/models\//, 'area:providers'],
[/^(crates\/localization|docs\/zh_hans)\//, 'area:localization'],
[/^(npm\/(codewhale|deepseek-tui)|scripts\/release)\//, 'area:install'],
[/^(\.github|scripts\/ci)\//, 'area:ci'],
[/^apps\/desktop\//, 'area:desktop'],
[/^(apps\/web|web)\//, 'area:web'],
[/^apps\/mobile\//, 'area:mobile'],
];
const areas = new Set();
let docsOnly = files.length > 0;
for (const f of files) {
if (!/^(docs\/|[^/]+\.md$)/.test(f.filename)) docsOnly = false;
for (const [re, name] of pathRules) if (re.test(f.filename)) areas.add(name);
}
if (areas.size <= 4) areas.forEach(name => labels.add(name));
if (docsOnly) labels.add('documentation');
if (/^feat(\(|!|:)/.test(title)) labels.add('enhancement');
if (/^fix(\(|!|:)/.test(title)) labels.add('bug');
if (/^docs(\(|!|:)/.test(title)) labels.add('documentation');
if (/^(chore|refactor|style|build)(\(|!|:)/.test(title)) labels.add('cleanup');
if (/^perf(\(|!|:)/.test(title)) labels.add('performance');
if (!['OWNER', 'MEMBER', 'COLLABORATOR'].includes(pr.author_association) && pr.user.type !== 'Bot') {
labels.add('contributor:external');
}
} else {
// Type — matched against the TITLE only.
//
// These ran against title+body and mislabelled long, well-specified
// issues. Any body containing an acceptance criterion like "fails
// when a pack drifts" matched the bug rule, and a "How to add a
// locale" heading matched the question rule, so planned work landed
// tagged `bug, question`. A title states the type; a body just
// discusses it. `error`/`fail` are dropped entirely — they describe
// what almost every issue talks about, not what any issue *is*.
if (/\b(bug|crash|panic|broken|stack ?trace|regression)\b/.test(title)) labels.add('bug');
if (/\b(feat(?:ure)?|request|enhancement|wishlist|proposal|please add|would be nice|support for)\b/.test(title)) labels.add('enhancement');
if (/\b(docs?|readme|documentation|typo|grammar|wording|spelling)\b/.test(title)) labels.add('documentation');
if (/\b(question|how (?:do|to)|why does|what does|is it possible)\b/.test(title)) labels.add('question');
// Locale — title contains CJK (Chinese, Japanese, Korean) characters
if (/[぀-ヿ㐀-鿿가-힯]/.test(issue.title || '')) labels.add('lang:zh');
// Areas (path-driven hints)
if (/crates\/tui|\btui\b|ratatui|composer|sidebar/.test(text)) labels.add('area:tui');
if (/crates\/core|engine|turn ?loop|agent ?loop/.test(text)) labels.add('area:core');
if (/crates\/mcp|\bmcp\b/.test(text)) labels.add('area:mcp');
if (/crates\/state|sqlite|sessions?|persistence/.test(text)) labels.add('area:state');
if (/crates\/execpolicy|approval|sandbox|seatbelt|landlock/.test(text)) labels.add('area:sandbox');
if (/crates\/tools|tool[ _]call|tool[ _]registry/.test(text)) labels.add('area:tools');
if (/install|cargo install|npm install|scoop|homebrew|prebuilt|binary/.test(text)) labels.add('area:install');
if (/\bdesktop app\b|electron/.test(text)) labels.add('area:desktop');
if (/app\.codewhale\.net|\bweb app\b|codewhale web/.test(text)) labels.add('area:web');
if (/\bmobile\b|\bios\b|\bandroid\b/.test(text)) labels.add('area:mobile');
if (/screen ?reader|accessib|a11y|voiceover|\bnvda\b|keyboard navigation/.test(text)) labels.add('accessibility');
if (/windows/.test(text)) labels.add('os:windows');
if (/freebsd/.test(text)) labels.add('os:freebsd');
if (/\bohos\b|harmonyos/.test(text)) labels.add('os:ohos');
if (/macos|darwin|apple silicon/.test(text)) labels.add('os:macos');
if (/\blinux\b|ubuntu|debian|fedora|arch ?linux/.test(text)) labels.add('os:linux');
}
if (labels.size === 0) return;
// Only add labels that already exist on the repo to avoid creating noise.
const existing = await github.paginate(github.rest.issues.listLabelsForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
});
const existingNames = new Set(existing.map(l => l.name));
const toAdd = [...labels].filter(name => existingNames.has(name));
if (toAdd.length === 0) return;
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: toAdd,
});
-319
View File
@@ -1,319 +0,0 @@
name: Web maintainer
on:
schedule:
- cron: '43 6 * * *'
workflow_dispatch:
inputs:
lane:
description: Which lane to run
required: true
default: all
type: choice
options:
- all
- derived
- copy-freshness
- link-health
- docs-drift
permissions: {}
concurrency:
group: web-maintainer
cancel-in-progress: false
env:
WEB_MAINTAINER_CLI_VERSION: v0.10.2
jobs:
plan:
name: Plan
if: github.repository == 'codewhale-hq/Codewhale' && github.ref == 'refs/heads/main' && vars.WEB_MAINTAINER_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
outputs:
lanes: ${{ steps.plan.outputs.lanes }}
derived: ${{ steps.plan.outputs.derived }}
steps:
- id: plan
env:
INPUT_LANE: ${{ inputs.lane || 'all' }}
run: |
derived=false
lanes='[]'
case "$INPUT_LANE" in
all) derived=true; lanes='["copy-freshness","link-health","docs-drift"]' ;;
derived) derived=true ;;
copy-freshness|link-health|docs-drift) lanes="[\"${INPUT_LANE}\"]" ;;
*) echo "::error::unknown lane"; exit 1 ;;
esac
echo "derived=${derived}" >> "$GITHUB_OUTPUT"
echo "lanes=${lanes}" >> "$GITHUB_OUTPUT"
echo "Web maintainer plan: derived=${derived} lanes=${lanes}" >> "$GITHUB_STEP_SUMMARY"
derived:
name: Derived files
needs: plan
if: needs.plan.result == 'success' && needs.plan.outputs.derived == 'true' && vars.WEB_MAINTAINER_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: main
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
- name: Install web dependencies
working-directory: web
run: npm ci --no-audit --no-fund
- name: Regenerate derived files
working-directory: web
env:
GH_TOKEN: ${{ github.token }}
run: |
node scripts/sync-latest-release.mjs
node scripts/derive-facts.mjs
node scripts/derive-changelog.mjs
node scripts/derive-install.mjs
node scripts/render-terminal-capture.mjs
- name: Check what changed
id: drift
run: |
mkdir -p "$RUNNER_TEMP/out"
if [ -n "$(git ls-files --others --exclude-standard)" ]; then
echo "::error::regeneration produced untracked files"
git ls-files --others --exclude-standard
exit 1
fi
git diff --name-status | node web/maintainer/check-allowlist.mjs web/maintainer/derived.txt derived
if [ -n "$(git diff --summary)" ]; then
echo "::error::regeneration created, deleted or changed modes"
exit 1
fi
git diff --binary > "$RUNNER_TEMP/out/change.patch"
if [ -s "$RUNNER_TEMP/out/change.patch" ]; then
echo "drift=true" >> "$GITHUB_OUTPUT"
git diff --stat >> "$GITHUB_STEP_SUMMARY"
else
echo "drift=false" >> "$GITHUB_OUTPUT"
echo "Derived files are current." >> "$GITHUB_STEP_SUMMARY"
fi
- name: Run the web checks on the regenerated tree
if: steps.drift.outputs.drift == 'true'
working-directory: web
run: |
npm run check
npx vitest run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: steps.drift.outputs.drift == 'true'
with:
name: web-maintainer-derived
path: ${{ runner.temp }}/out/change.patch
retention-days: 3
if-no-files-found: error
lane:
name: Lane ${{ matrix.lane }}
needs: plan
if: needs.plan.result == 'success' && needs.plan.outputs.lanes != '[]' && vars.WEB_MAINTAINER_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
strategy:
fail-fast: false
max-parallel: 3
matrix:
lane: ${{ fromJSON(needs.plan.outputs.lanes) }}
env:
LANE: ${{ matrix.lane }}
steps:
- name: Require the DeepSeek key
env:
KEY: ${{ secrets.WEB_MAINTAINER_DEEPSEEK_API_KEY }}
run: |
if [ -z "$KEY" ]; then
echo "::error::WEB_MAINTAINER_DEEPSEEK_API_KEY is not set. Create a dedicated DeepSeek key with a provider-side monthly cap and add it as that repository secret, or unset WEB_MAINTAINER_ENABLED. See web/maintainer/README.md."
exit 1
fi
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: main
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
- name: Prove the command sandbox works or stop
run: |
sudo apt-get install -y --no-install-recommends bubblewrap util-linux >/dev/null
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 >/dev/null || true
probe="$GITHUB_WORKSPACE/.sandbox-probe"
rm -f "$probe" /usr/local/sandbox-probe
if ! timeout 10 bash -c 'exec 3<>/dev/tcp/api.deepseek.com/443'; then
echo "::error::the runner cannot reach api.deepseek.com, so the network denial cannot be proven."
exit 1
fi
if ! /usr/bin/bwrap --unshare-all --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp \
--bind "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE" --chdir "$GITHUB_WORKSPACE" -- \
bash -c 'touch .sandbox-probe && ! touch /usr/local/sandbox-probe 2>/dev/null && ! timeout 10 bash -c "exec 3<>/dev/tcp/api.deepseek.com/443" 2>/dev/null'; then
echo "::error::bubblewrap did not confine a child on this runner (writes outside the workspace or outbound network were not denied). Refusing to give a model a shell."
exit 1
fi
if [ ! -e "$probe" ] || [ -e /usr/local/sandbox-probe ]; then
echo "::error::sandbox probe left unexpected state. Refusing to give a model a shell."
exit 1
fi
rm -f "$probe"
echo "Sandbox probe passed: workspace-only writes, no outbound network." >> "$GITHUB_STEP_SUMMARY"
- name: Stage trusted tooling and the pinned CLI
run: |
mkdir -p "$RUNNER_TEMP/trusted" "$RUNNER_TEMP/home"
cp web/maintainer/allowlist.txt web/maintainer/check-allowlist.mjs "$RUNNER_TEMP/trusted/"
cp "web/maintainer/lanes/${LANE}.md" "$RUNNER_TEMP/trusted/prompt.md"
CODEWHALE_VERSION="$WEB_MAINTAINER_CLI_VERSION" CODEWHALE_INSTALL_DIR="$RUNNER_TEMP/bin" sh web/public/install.sh
"$RUNNER_TEMP/bin/codewhale" --version
- name: Probe outbound links
if: matrix.lane == 'link-health'
run: node web/maintainer/link-report.mjs "$GITHUB_WORKSPACE/.git/maintainer-input/link-report.json"
- name: Record the git state the model must not change
run: |
git config --local --list | sort > "$RUNNER_TEMP/git-config.before"
find .git/hooks -type f ! -name '*.sample' > "$RUNNER_TEMP/hooks.before"
test ! -s "$RUNNER_TEMP/hooks.before"
- name: Run the lane on DeepSeek
timeout-minutes: 25
env:
WEB_MAINTAINER_DEEPSEEK_API_KEY: ${{ secrets.WEB_MAINTAINER_DEEPSEEK_API_KEY }}
run: |
env -i \
PATH="$PATH" \
HOME="$RUNNER_TEMP/home" \
CODEWHALE_HOME="$RUNNER_TEMP/home/.codewhale" \
CODEWHALE_SECRET_BACKEND=file \
CODEWHALE_MEMORY=false \
CODEWHALE_TELEMETRY=false \
DEEPSEEK_API_KEY="$WEB_MAINTAINER_DEEPSEEK_API_KEY" \
"$RUNNER_TEMP/bin/codewhale" \
--no-project-config \
--skip-onboarding \
--workspace "$GITHUB_WORKSPACE" \
exec \
--provider deepseek \
--model deepseek-flash \
--reasoning-effort low \
--auto \
--sandbox workspace-write \
--max-turns 40 \
--output-format stream-json \
-- "$(cat "$RUNNER_TEMP/trusted/prompt.md")" \
> "$RUNNER_TEMP/exec.jsonl"
echo "Model run finished: $(wc -l < "$RUNNER_TEMP/exec.jsonl") stream events." >> "$GITHUB_STEP_SUMMARY"
- name: Enforce the path allowlist
id: scope
run: |
if git status --porcelain --untracked-files=all --ignored | grep -qv '^ M '; then
echo "::error::the lane left untracked, ignored, staged or non-edit entries"
git status --porcelain --untracked-files=all --ignored | grep -v '^ M '
exit 1
fi
git config --local --list | sort | diff - "$RUNNER_TEMP/git-config.before" || { echo "::error::the lane changed .git/config"; exit 1; }
find .git/hooks -type f ! -name '*.sample' | diff - "$RUNNER_TEMP/hooks.before" || { echo "::error::the lane added git hooks"; exit 1; }
git diff --name-status | node "$RUNNER_TEMP/trusted/check-allowlist.mjs" "$RUNNER_TEMP/trusted/allowlist.txt" "$LANE"
if [ -n "$(git diff --summary)" ]; then
echo "::error::the lane created, deleted or changed modes"
exit 1
fi
mkdir -p "$RUNNER_TEMP/out"
git diff --binary > "$RUNNER_TEMP/out/change.patch"
if [ ! -s "$RUNNER_TEMP/out/change.patch" ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "Lane ${LANE}: no change proposed." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
if [ "$(wc -c < "$RUNNER_TEMP/out/change.patch")" -gt 262144 ]; then
echo "::error::patch exceeds 256 KiB"
exit 1
fi
echo "changed=true" >> "$GITHUB_OUTPUT"
{ echo "Lane ${LANE} proposes:"; echo '```text'; git diff --stat; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
- name: Fresh checkout for verification
if: steps.scope.outputs.changed == 'true'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: main
path: verify
persist-credentials: false
- name: Run the web checks on the proposal in a clean tree without secrets
if: steps.scope.outputs.changed == 'true'
env:
PATCH: ${{ runner.temp }}/out/change.patch
run: |
cd verify
git apply --index --whitespace=nowarn "$PATCH"
cd web
npm ci --no-audit --no-fund
npm run check
npx vitest run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: steps.scope.outputs.changed == 'true'
with:
name: web-maintainer-${{ matrix.lane }}
path: ${{ runner.temp }}/out/change.patch
retention-days: 3
if-no-files-found: error
publish:
name: Open pull requests
needs: [plan, derived, lane]
if: always() && needs.plan.result == 'success' && vars.WEB_MAINTAINER_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
continue-on-error: true
with:
pattern: web-maintainer-*
path: ${{ runner.temp }}/patches
- name: Look for proposed patches
id: patches
run: |
if [ -n "$(find "$RUNNER_TEMP/patches" -name change.patch -size +0c 2>/dev/null)" ]; then
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "found=false" >> "$GITHUB_OUTPUT"
echo "No lane proposed a change; no pull request opened." >> "$GITHUB_STEP_SUMMARY"
fi
- if: steps.patches.outputs.found == 'true'
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3
id: app-token
with:
app-id: ${{ vars.CODEWHALE_APP_ID }}
private-key: ${{ secrets.CODEWHALE_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
- if: steps.patches.outputs.found == 'true'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: main
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
persist-credentials: false
- name: Open or update one pull request per lane
if: steps.patches.outputs.found == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
APP_ID: ${{ vars.CODEWHALE_APP_ID }}
ARTIFACTS_DIR: ${{ runner.temp }}/patches
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
mkdir -p "$RUNNER_TEMP/trusted"
cp web/maintainer/allowlist.txt web/maintainer/derived.txt web/maintainer/check-allowlist.mjs web/maintainer/publish-prs.sh "$RUNNER_TEMP/trusted/"
TRUSTED_DIR="$RUNNER_TEMP/trusted" bash "$RUNNER_TEMP/trusted/publish-prs.sh"
-125
View File
@@ -1,125 +0,0 @@
name: Web Frontend
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.event_name == 'pull_request' && format('web-pr-{0}', github.event.pull_request.number) || format('web-{0}', github.run_id) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
lint:
name: Lint & Type Check
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
# `check:facts` derives each model's `addedAt` from the commit date on
# which its id first appeared in the model declaration paths
# (web/scripts/facts-lib.mjs). A shallow checkout cannot see that
# history, so every date collapses to the tip commit and the committed
# facts always read as stale. ci.yml pins depth 0 for the same reason.
fetch-depth: 0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Check web surface
# The shared gate checks committed facts before generating build inputs.
env:
GITHUB_TOKEN: ${{ github.token }}
run: npm run check
deploy-reminder:
name: Deployment approval needed
runs-on: ubuntu-latest
timeout-minutes: 5
needs: lint
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Surface the manual deployment gate
env:
REVISION: ${{ github.sha }}
run: |
echo "::notice title=Web deployment approval needed::Revision ${REVISION} passed the web gates but is not deployed. Dispatch web.yml on main to publish it."
{
echo "## Web deployment approval needed"
echo
echo "Revision \`${REVISION}\` passed the web gates but has **not** been deployed."
echo
echo "A maintainer can publish it with \`gh workflow run web.yml --repo codewhale-hq/CodeWhale --ref main\`."
} >> "$GITHUB_STEP_SUMMARY"
deploy:
name: Deploy to Cloudflare
runs-on: ubuntu-latest
timeout-minutes: 30
needs: lint
# Deploy is MANUAL ONLY: an authorized dispatch on main runs `lint`
# before deploying. Pushes and pull requests do not start this workflow.
# This mirrors scripts/check-cloudflare-deploy-env.mjs, which fails closed
# unless GITHUB_EVENT_NAME is workflow_dispatch, GITHUB_REF is
# refs/heads/main, and GITHUB_SHA is an exact 40-hex revision — a push
# trigger here would only produce a red job after `lint` had already run.
# lib/deploy-preflight.test.ts asserts both halves of that contract.
# `needs: lint` is the gate: facts drift, docs parity, tests, ESLint, tsc,
# and a production build all pass before anything reaches Cloudflare.
if: >-
github.event_name == 'workflow_dispatch'
&& github.ref == 'refs/heads/main'
# Serialize deploys so two dispatches landing close together cannot race and
# leave Cloudflare serving the older bundle. Never cancel in progress: a
# half-finished OpenNext upload is worse than a queued one.
concurrency:
group: deploy-codewhale-web
cancel-in-progress: false
defaults:
run:
working-directory: web
env:
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
# Pin the checkout to the exact revision this dispatch resolved, so the
# SHA reported to compare:deployed-facts and asserted on the public
# receipt below is the SHA that was actually built, even if main moves
# while the run is queued behind the concurrency group.
with:
ref: ${{ github.sha }}
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
cache: 'npm'
cache-dependency-path: web/package-lock.json
- name: Install dependencies
run: npm ci
- name: Record deployed/source drift
# Read-only and credential-free. A mismatch is the normal state here —
# it is the gap this run is about to close — so this step reports
# without gating. The real assertion is the post-deploy verification
# below, which must observe this exact revision on the public receipt.
run: npm run compare:deployed-facts -- --expected-revision "$GITHUB_SHA"
- name: Check Cloudflare deploy environment
run: npm run check:deploy-env
# npm's deploy script performs one OpenNext build, then deploys that exact
# bundle. OpenNext populates the remote cache before cf deploy --prebuilt;
# the bundler has no custom build that could invalidate that cache.
- name: Build and deploy exact OpenNext bundle
run: npm run deploy
- name: Verify exact deployed revision
# The public /api/facts receipt must identify this workflow's exact
# checkout before the manual deployment run can finish green.
run: npm run check:deployed-facts -- --expected-revision "$GITHUB_SHA" --attempts 10 --retry-delay-ms 3000
@@ -1,103 +0,0 @@
name: Windows ARM64 export probe
on:
workflow_dispatch:
inputs:
run_id:
description: Release-artifacts or release-candidate run whose windows-arm64 import receipt is probed
required: true
source_sha:
description: Source commit of that run (names the receipt artifact)
required: true
permissions:
contents: read
actions: read
jobs:
probe:
runs-on: windows-11-arm
timeout-minutes: 15
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8
with:
name: windows-imports-windows-arm64-${{ inputs.source_sha }}
path: receipt
run-id: ${{ inputs.run_id }}
github-token: ${{ github.token }}
- name: Resolve every imported pair in a native ARM64 process
shell: pwsh
env:
SOURCE_SHA: ${{ inputs.source_sha }}
SOURCE_RUN: ${{ inputs.run_id }}
run: |
$ErrorActionPreference = 'Stop'
$arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString()
if ($arch -ne 'Arm64') { throw "probe process is $arch, not Arm64" }
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.Text;
public static class ExportProbe {
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr LoadLibraryExW(string name, IntPtr file, uint flags);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
public static extern IntPtr GetProcAddress(IntPtr module, string name);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern uint GetModuleFileNameW(IntPtr module, StringBuilder path, uint size);
}
'@
$receipt = Get-Content -Raw receipt/receipt.json | ConvertFrom-Json
$pairs = @{}
foreach ($binary in $receipt) {
foreach ($pair in $binary.imports) { $pairs["$($pair[0].ToLowerInvariant())|$($pair[1])"] = $pair }
}
$modules = [ordered]@{}
$unresolved = @()
$resolved = 0
foreach ($key in ($pairs.Keys | Sort-Object)) {
$dll = $pairs[$key][0].ToLowerInvariant()
$symbol = $pairs[$key][1]
if (-not $modules.Contains($dll)) {
$handle = [ExportProbe]::LoadLibraryExW($dll, [IntPtr]::Zero, 0x00000800)
if ($handle -eq [IntPtr]::Zero) {
$modules[$dll] = @{ handle = [IntPtr]::Zero; path = $null; sha256 = $null; error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() }
} else {
$path = [System.Text.StringBuilder]::new(1024)
[void][ExportProbe]::GetModuleFileNameW($handle, $path, 1024)
$modules[$dll] = @{ handle = $handle; path = $path.ToString(); sha256 = (Get-FileHash -Algorithm SHA256 $path.ToString()).Hash.ToLowerInvariant(); error = 0 }
}
}
$module = $modules[$dll]
if ($module.handle -ne [IntPtr]::Zero -and [ExportProbe]::GetProcAddress($module.handle, $symbol) -ne [IntPtr]::Zero) { $resolved++ } else { $unresolved += ,@($dll, $symbol) }
}
$version = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
$result = [ordered]@{
result = $(if ($unresolved.Count -eq 0) { 'PASS' } else { 'FAIL' })
kind = 'exact native system export resolution'
time = (Get-Date).ToUniversalTime().ToString('o')
osVersion = [System.Environment]::OSVersion.Version.ToString()
osBuild = "$($version.CurrentBuild).$($version.UBR)"
osProduct = $version.ProductName
processArchitecture = 'ARM64'
runner = 'GitHub-hosted windows-11-arm'
source = $env:SOURCE_SHA
sourceRun = $env:SOURCE_RUN
binaries = @($receipt | ForEach-Object { [ordered]@{ binary = $_.binary; sha256 = $_.sha256 } })
resolved = $resolved
unresolved = $unresolved.Count
unresolvedPairs = $unresolved
exportProbeMethod = 'ARM64 process: LoadLibraryExW per imported DLL with LOAD_LIBRARY_SEARCH_SYSTEM32 (0x00000800), then GetProcAddress for every imported symbol; module path and SHA256 recorded'
systemModules = [ordered]@{}
}
foreach ($dll in $modules.Keys) { $result.systemModules[$dll] = [ordered]@{ path = $modules[$dll].path; sha256 = $modules[$dll].sha256; loadError = $modules[$dll].error } }
New-Item -ItemType Directory -Force probe | Out-Null
$result | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 probe/export-probe.json
Get-Content probe/export-probe.json
if ($unresolved.Count -ne 0) { throw "$($unresolved.Count) imported pairs did not resolve" }
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: windows-arm64-export-probe-${{ inputs.source_sha }}
path: probe/
if-no-files-found: warn
-40
View File
@@ -1,40 +0,0 @@
name: Windows focus
on:
workflow_dispatch:
inputs:
expected_sha:
description: Exact 40-character commit selected by --ref
required: true
type: string
filter:
description: nextest expression, for example 'test(compaction_lock) | test(persistence_actor)'
required: true
default: all()
type: string
permissions:
contents: read
jobs:
focused:
name: Focused tests (windows-latest)
runs-on: windows-latest
timeout-minutes: 90
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -Dwarnings
RUST_MIN_STACK: 8388608
RUST_BACKTRACE: full
FILTER: ${{ inputs.filter }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.expected_sha }}
fetch-depth: 1
- uses: dtolnay/[email protected]
- uses: taiki-e/install-action@nextest
- name: Run focused tests
shell: bash
run: cargo nextest run -p codewhale-engine -p codewhale-plugins -p codewhale-tui -p codewhale-app-server --lib --locked --profile ci -E "$FILTER"
+61 -176
View File
@@ -1,194 +1,79 @@
# Build artifacts
/target
/target-*
/vendor/codewhale-ratatui/target/
.entire/
.cursor/
.plans/
.codewhale-worktrees/
docs/superpowers/
codewhale-inference/
/extensions/vscode/out/
*.pdb
*.exe
*.dll
*.so
*.dylib
*.rlib
*.o
# deps
# Node.js dependencies
node_modules
.pnpm-store
.pnpm-debug.log
# Development
.env
.env.*
!.env.example
node_modules/
.vscode/
# The extension ships its own dev-loop config (F5 Extension Development Host).
# Without this negation the bare `.vscode/` above swallows it silently.
!/extensions/vscode/.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
Thumbs.db
# Keep pnpm-lock.yaml
!pnpm-lock.yaml
# Python
__pycache__/
*.py[cod]
*$py.class
.pytest_cache/
venv/
ENV/
env/
.venv/
*.egg-info/
# build
dist/
build/
out/
storybook-static/
# Logs
*.log
# ignore README for publishing
codex-cli/README.md
# Generated
# Local design critiques and operator receipts do not ship with the product.
/.impeccable/
outputs/
tmp/
backup/
# ignore Nix derivation results
result
# Reference papers / large research blobs (keep locally if needed, don't ship)
docs/DeepSeek_V4.pdf
docs/*.pdf
# editor
.vscode/
.idea/
.history/
.zed/
*.swp
*~
# Note: Cargo.lock is intentionally NOT ignored for reproducible builds
# Local dev scripts and temp files
*.sh
*.cmd
!scripts/**
!.github/scripts/**
!web/public/install.sh
!packaging/winget/**
# Bridge launchers are shipped entry points (`npm run bridge`), not local dev
# scripts; `!scripts/**` above only re-includes the repo-root scripts/ dir.
!integrations/*/scripts/**
test.txt
TODO*.md
todo*.md
AGENTS.md
# …except the deliberate, maintained agent-guidance files:
!/AGENTS.md
!crates/tui/AGENTS.md
!crates/tui/locales/AGENTS.md
!web/AGENTS.md
NEXT_SESSION.md
AI_HANDOFF.md
result.json
count_deps.py
project_overhaul_prompt.md
.codex/
.context/
.wrangler/
# Local runtime state
# Ignore everything under any .codewhale/ (snapshots, auto-generated
# instructions.md, etc.) at any depth EXCEPT the committed repo authority policy
# and this repository's own project hooks.
**/.codewhale/*
!**/.codewhale/constitution.json
!/.codewhale/hooks.toml
.deepseek/
**/session_*.json
*.db
# Chat bridge local state. `npm run bridge` writes the Weixin bot login token
# (account.json) and the long-poll cursor here; these are live credentials.
integrations/*-bridge/.state/
npm/*/bin/downloads/
# Companion app (tracked separately)
apps/
# Claude Code runtime artifacts
.worktrees/
.ace-tool/
# NOTE: the blanket `.claude/` rule further down already ignores everything
# under .claude/ — `git check-ignore -v` attributes every path below to that
# line, not to these. The handoff/codemap patterns are kept as defense in depth:
# those notes are working state, not user-facing artifacts, and they leaked into
# the public repo through .claude/ before (HANDOFF_v0.8.28, CODEMAP_v0.8.25).
# They live under .private/handoffs/ now. If anyone ever narrows the blanket
# rule to track part of .claude/, these must keep holding.
.claude/HANDOFF_*
.claude/CODEMAP_*
.claude/handoff_*
.claude/codemap_*
# Maintainer-internal design notes (trade-secret material, never published)
.private/
# Coverage reports (build artifacts)
web/coverage/
# Agent handoffs and version-specific setup plans are working-state notes, not
# public docs. Keep durable setup guidance in docs/runbooks instead.
docs/*HANDOFF*.md
docs/*handoff*.md
docs/*_PLAN.md
!docs/archive/**
# direnv
.envrc
.direnv
scripts/run_deep_swe.py
# cli tools
CLAUDE.md
.claude/
# Local run artifacts and caches re-included by !scripts/**
results/
scripts/**/__pycache__/
# caches
.cache/
.turbo/
.parcel-cache/
.eslintcache
.nyc_output/
.jest/
*.tsbuildinfo
# Maintainer-local verification artifacts
.uv-bin/
.uv-cache/
.uv-tools/
issues/
logs/
notes/
# logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Agent-local roadmap and scratch (never commit — may hold unreleased plans)
.agents/
scratchpad/
# env
.env*
!.env.example
# Exploratory operations spec / prompt — never track (canonical copy lives at CW root)
operations9.md
OPERATIONS_0_9_0.md
operations_0_9_0.md
CODEWHALE_0_9_0_CUTOVER.md
# package
*.tgz
# Hepha project metadata (local only, not upstream)
.hepha/
# ci
.vercel/
.netlify/
# Ralph agent scratch (local only)
.ralph/
# patches
apply_patch/
# Local playwright MCP artifacts
.playwright-mcp/
# coverage
coverage/
# computer-use plugin smoke receipts (per-run local evidence)
crates/tui/plugins/computer-use/receipts/
# os
.DS_Store
Thumbs.db
Icon?
.Spotlight-V100/
# OrcaRouter provider screenshot evidence (per-run local evidence)
orca-evidence/
# Unwanted package managers
.yarn/
yarn.lock
# Portable pet build entry points are source.
!pet/**/*.sh
!pet/verify.sh
# Private keys and certificates never belong in the tree.
*.pem
*.key
*.p12
*.p8
id_rsa*
id_ed25519*
!docs/cloud-facts/fixtures/test-only-signing-key.pem
# release
package.json-e
session.ts-e
CHANGELOG.ignore.md
+1
View File
@@ -0,0 +1 @@
pnpm lint-staged
-30
View File
@@ -1,30 +0,0 @@
Hunter Bown <[email protected]> devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Hunter Bown <[email protected]> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Hunter Bown <[email protected]> GitHub <[email protected]>
Hunter Bown <[email protected]> Claude <[email protected]>
Hunter Bown <[email protected]> Claude Opus 4.6 <[email protected]>
Hunter Bown <[email protected]> Claude Opus 4.6 (1M context) <[email protected]>
Hunter Bown <[email protected]> Claude Opus 4.7 (1M context) <[email protected]>
Hunter Bown <[email protected]> Copilot <[email protected]>
Hunter Bown <[email protected]> Copilot <1693627+github-copilot-cli[bot]@users.noreply.github.com>
Hunter Bown <[email protected]> Copilot <946600+copilot-pull-request-reviewer[bot]@users.noreply.github.com>
Hunter Bown <[email protected]> Qwen-Coder <[email protected]>
Hunter Bown <[email protected]> qwencoder <[email protected]>
Hunter Bown <[email protected]> Cursor Agent <[email protected]>
Hunter Bown <[email protected]> cursoragent <[email protected]>
Hunter Bown <[email protected]> gemini-code-assist[bot] <gemini-code-assist[bot]@users.noreply.github.com>
Hunter Bown <[email protected]> gemini-code-assist[bot] <956858+gemini-code-assist[bot]@users.noreply.github.com>
ddlw <[email protected]> <[email protected]>
dmitri-0 <[email protected]> <[email protected]>
Paulo Aboim Pinto <[email protected]> <[email protected]>
Paulo Aboim Pinto <[email protected]> <[email protected]>
Paulo Aboim Pinto <[email protected]> <[email protected]>
AiurArtanis <[email protected]> <[email protected]>
Apeiron <[email protected]> <[email protected]>
ChaceLyee2101 <[email protected]> <[email protected]>
movic <[email protected]> <[email protected]>
merchloubna70-dot <[email protected]> <[email protected]>
merchloubna70-dot <[email protected]> <[email protected]>
luola <[email protected]> <[email protected]>
SONG ZHENRUI <[email protected]> <[email protected]>
xiluoduyu <[email protected]> <[email protected]>
+4
View File
@@ -0,0 +1,4 @@
shamefully-hoist=true
strict-peer-dependencies=false
node-linker=hoisted
prefer-workspace-packages=true
+3
View File
@@ -0,0 +1,3 @@
/codex-cli/dist
/codex-cli/node_modules
pnpm-lock.yaml
+8
View File
@@ -0,0 +1,8 @@
printWidth = 80
quoteProps = "consistent"
semi = true
tabWidth = 2
trailingComma = "all"
# Preserve existing behavior for markdown/text wrapping.
proseWrap = "preserve"
-45
View File
@@ -1,45 +0,0 @@
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.12.0.schema.json
PackageIdentifier: HunterBown.CodeWhale
PackageVersion: 0.10.2
PackageLocale: en-US
ManifestType: singleton
ManifestVersion: 1.12.0
Publisher: Hmbown
PublisherUrl: https://github.com/Hmbown
PublisherSupportUrl: https://github.com/codewhale-hq/CodeWhale/issues
PackageName: CodeWhale
PackageUrl: https://github.com/codewhale-hq/CodeWhale
License: MIT
LicenseUrl: https://github.com/codewhale-hq/CodeWhale/blob/main/LICENSE
Copyright: Copyright (c) Hmbown
ShortDescription: Terminal coding agent for supported hosted and local models
Description: |
CodeWhale runs in your terminal, connects to hosted and local models, and edits your workspace with approval controls.
Author: Hmbown
Moniker: codewhale
Tags:
- codewhale
- deepseek
- cli
- tui
- terminal
- ai
- coding-agent
- rust
ReleaseNotesUrl: https://github.com/codewhale-hq/CodeWhale/releases/tag/v0.10.2
Documentations:
- DocumentLabel: Install guide
DocumentUrl: https://github.com/codewhale-hq/CodeWhale/blob/main/docs/INSTALL.md
InstallerType: portable
Commands:
- codewhale
Dependencies:
PackageDependencies:
- PackageIdentifier: Microsoft.VCRedist.2015+.x64
Installers:
- Architecture: x64
InstallerUrl: https://github.com/codewhale-hq/Codewhale/releases/download/v0.10.2/codewhale-tui-windows-x64.exe
InstallerSha256: 578280bec8d6e0ee9cfe97ee29de817535689f4078d8afd47384ff1941b1ee6c
-279
View File
@@ -1,279 +0,0 @@
# Codewhale agent guidance
Keep this file durable. Derive changing release, provider, branch, and flake
state from the repository, tests, CI, and current issue tracker rather than from
instructions or memory. The nearest scoped `AGENTS.md` adds path-specific rules.
## The ponytail method
From [dietrichgebert/ponytail](https://github.com/dietrichgebert/ponytail) —
"the laziest senior dev in the room." *He says nothing. He writes one line. It
works.* The best code is the code you never wrote.
Before writing code, walk the decision ladder in order and stop at the first
rung that answers:
1. **Does this need to exist?** → Skip it.
2. **Already in this codebase?** → Reuse it.
3. **Stdlib does it?** → Use it.
4. **Native platform feature?** → Use it.
5. **Installed dependency?** → Use it.
6. **One line?** → One line.
7. **Only then:** the minimum that works.
The ladder runs *after* understanding the problem. Lazy about solutions, never
about reading the code first — a short diff written without reading the call
sites is not ponytail, it is a guess.
**Never cut, at any rung:** trust-boundary validation, data-loss handling,
security, accessibility. Brevity is not a reason to drop a guard.
Rung 2 is the one this repository keeps failing. The `model_*` / `*_config` /
`provider_*` grep rule below is rung 2 with a name; so is "one turn loop, one
base prompt". Two more corollaries earned here:
- **An abstraction must delete caller code.** If adopting it is pure
obligation — required methods, no default bodies that do work — it gets
built, adopted once, and abandoned.
- **Migrate the last consumer, or do not start.** Framework, one caller,
ticket the rest, silence the warning: that ships two systems and a comment
that is no longer true. If the migration will not fit, narrow the slice —
never the adoption. The standing `#[allow(dead_code)]` count is the running
receipt; `scripts/check-dead-code-budget.py` prints it.
## Working rules
- Inspect status and existing consumers before editing. Preserve unrelated,
dirty, and untracked work.
- Before adding a module named `model_*`, `*_config`, `provider_*`, or
anything that "bridges", "mirrors", or "stages" an existing thing, grep
for the existing thing and edit it. A new layer must name the predecessor
it replaces in its commit message; otherwise edit the original.
- Prefer the simplest implementation that preserves observable contracts. A
rewrite is acceptable when justified by product intent and observed behavior,
not as a shortcut around understanding existing code.
- Search for behavior and symbols before reviving work from an old branch. If a
lane is obsolete, preserve its intent and evidence rather than merging stale
code mechanically.
- Agent-owned work lands on current `main`. Each writing agent works in its
own short-lived git worktree cut from `origin/main`, rebases and pushes
`main` the same day, then removes the worktree and its build directory. No
long-lived branches, nested clones or integration lanes, and never a new
worktree to hide dirty, stale or conflicting work: that work is still yours
to finish. In a shared checkout, stage by explicit path and retry an
`index.lock` failure. Verified main slices may
be committed and pushed normally under the rule below; tag, release,
deployment and destructive cleanup still require their explicit
authorization.
- **Uncommitted work found in the checkout is the agent's own** (founder,
2026-10-22). Read it, work out what it was for, prove it with the smallest
check that covers it, fix what is broken, and commit it in coherent groups
by explicit path. Do not route around another writer's files or leave them
dirty. Ownership is not a blind sweep:
- A file changed in the last 15 minutes, or one a live session or agent is
editing, is mid-edit: ask that writer to commit now, and commit what
remains once it goes quiet. Never commit a half-written edit.
- Never commit secrets, credentials, `.env` files, build output or caches;
add them to `.gitignore` instead.
- Check a repository's visibility before pushing. Inference business and
infrastructure detail stays out of public repositories.
- Commit by explicit path; never `git add -A`.
- Work that fails its check and cannot be fixed now is not pushed to `main`.
Record it and report it instead.
- **Maintainers land small verified slices directly on `main`.** Run the
focused local check (`scripts/preflight.sh --check`), put the real counts in
the commit message, fetch and rebase onto `origin/main` and retry on
rejection. Never force-push or rewrite a shared ref. Contributor PRs are
always welcome and merge as themselves (founder, 2026-11-07).
- When the task is local-only, stay fully offline: no browsing, GitHub or remote
Git operations, downloads, dependency installation, provider calls, or
source/diff transmission. Record the missing external receipt and keep working
locally.
- Public name is **Codewhale**. Compatibility identifiers such as `CodeWhale`,
`codew`, protocol names, and storage keys change only through an explicit
migration.
- Keep providers and models first-class and provider-neutral.
- Never rewrite published history, retag a release, force-push a shared ref, or
publish without explicit authorization. Preserve human contributor credit.
- **Model-visible means logged.** Anything that reaches a model request must be
reconstructable from the session log, and a new model-visible input needs a
session event. Live presentation and the persisted record must agree; when they
disagree the record is right.
- **Misconfiguration fails loud**, at load when it is self-contained, otherwise
at the earliest point it can be resolved. Never silently skip a missing
referent.
- **Write down what a design does not do**, beside the behaviour it owns — a
short known-limitations note in the owning module. A stated limit stops the
next reader from assuming a capability that was never built.
- **Be generous in public** (founder, 2026-11-07). Thank people specifically
for their reports, diagnosis, fixes and testing; link the carrying commit or
PR and distinguish candidate work from a shipped release. Status, answers
and useful questions are welcome; repetitive progress posts and bot noise
are not. Evidence stays in commits and PR bodies; ownership stays in Linear.
A contributor PR is never closed because CI is slow, flaky or red for a
reason the PR did not cause; when a PR is closed, say why in one specific,
thankful sentence. Review bots stay off unless the founder enables them.
- **A user feature lands with its registry row.** A new command, `[features]`
flag, provider or user-visible feature adds or updates its row in
`docs/features.toml` in the same change;
`cargo test -p codewhale-tui --test feature_registry` fails on flag drift.
- **Public planned-work issues are written for contributors.** An issue opened for planned or release work has the parts in CONTRIBUTING.md § "What a planned-work issue gives you": status, ways to help with files, a local test command and a done-when, what maintainers already hold, and how to claim. Keep it current as pieces land or are claimed.
- **Write `close`/`fix`/`resolve #N` only when you mean it.** GitHub closes the
issue on merge even inside "does not close #N"; use `Refs #N` otherwise.
## Landing other people's work
An external contributor's branch goes stale because *we* land things, not
because they did anything wrong. Treat their time as more expensive than ours.
**The goal is the contributor's PR merging as itself.** Review it, run or
rerun only the checks it needs, fix small problems on their branch, and resolve
conflicts that `main` caused. Landing their commits as ours (harvesting) is the
rare fallback for a branch we cannot push to and that cannot merge in
reasonable time; ask on the PR first, because done casually it reads as taking
the work even when credit is preserved.
- **Never make a contributor rebase around our churn.** If their PR conflicts
only because main moved, a maintainer resolves it.
- Landing mechanics — merge-base diffing, mid-function conflicts, fork-push
refusal, runs awaiting first-contributor approval — live in `cw-land`.
Follow them instead of improvising.
- **Preserve credit in the mechanical sense, not just the polite one.** Commit
authorship and `Co-authored-by` trailers must use the contributor's own
GitHub-linked address. `AUTHOR_MAP` and `.mailmap` are project conventions —
GitHub reads neither for the contribution graph.
## Merging under a gate
- **A gate is its artifact.** When a rail says a PR merges only on a passing
acceptance record, the record must literally say PASS at merge time. "I
re-ran it and the failures are rows this PR does not own" is a judgement to
write into the artifact first, not a reason to merge past it.
- **Read the review thread, not the check rollup.** Green checks and an unread
review with confirmed findings are a merge that ships known bugs.
- **When the artifact is ambiguous, resolve the ambiguity — never the merge.**
## Claiming a test passed
- Quote the real `test result: N passed; M failed` line, and confirm `N > 0`
for the tests that cover the change. `cargo test <filter>` exits 0 having run
zero tests when the filter matches nothing, and an exit code alone has
already been mistaken for a pass here.
- Prefer proving a regression test fails without the fix. A test that passes
either way pins the implementation, not the defect.
- Audit any hand-rolled scorer before trusting its score: quote the counts it
actually evaluated, not the verdict line alone.
- Match the evidence to the surface. Run the tests that cover the change, not the
whole suite, and do not repeat a check that already passed in order to commit.
CI owns exhaustive coverage; a full local run is for CI diagnosis or for an
irreducibly repository-wide change.
## Current contracts
- The model-facing subagent tool is `agent`; `agent_open`/`agent_eval`/
`agent_close`/`delegate_to_agent` are removed surfaces. If the shape must
move, move the code and its existing guard test with it.
- `BASE_PROMPT` in `crates/engine/src/prompts/text.rs` is the sole base prompt
by convention. Same rule: move the code, not the prose, if that changes.
- There is exactly one turn loop: `Engine::run_turn` in
`crates/engine/src/core/engine/turn_loop.rs`. Note that `crates/engine/src/core/`
is a module inside the engine crate (`codewhale-engine`) — it is not `crates/core`, which owns
request construction, bounded fragments, and thread/session types and
runs no turns. A guard test (`crates/core/tests/single_turn_loop.rs`)
fails on a second loop; changing the shape means changing the guard
with it.
- The system prompt + tool catalog are a session-pinned KV-cache prefix
(`docs/CACHE.md`). Any new session-context contributor must state its
KV-cache effect: frozen prefix vs. append-only history. Never splice a
volatile fact into the prefix; append it as a user-role message.
- These active modules are repeatedly misidentified as dead; verify consumers
before removal: `runtime/src/context_budget.rs`,
`runtime/src/prompt_zones.rs`, `engine/src/tools/remember.rs`, and
`config/src/route/`. Native memory lives in `runtime/src/native_memory.rs`;
`tools/remember.rs` is its capture path.
- Environment-specific behavior belongs in `docs/ENVIRONMENTS.md`, not here.
- Blocking-call convention (#6149): code on the Tokio runtime — tool
handlers, engine tasks, the UI event loop, anything reached through an
`async` call chain — must not run blocking operations inline. Use
`tokio::fs` / `tokio::process`, or move the work into `spawn_blocking`;
a sync helper containing blocking calls runs only under `spawn_blocking`
or on a dedicated thread. `scripts/check-blocking-calls-budget.py`
ratchets the unprotected-site count.
## Code, migrations, and evidence
- Product intent and observed runtime behavior outrank a test's preferred
implementation shape. Fix the product; do not contort production code to
preserve a brittle assertion.
- **A regression test per fix; no new code comments.** Prove a change by
running it and the existing checks. A bug fix, or a new contract between
components, may add the one test that would have caught it; no coverage for
its own sake. Never write tests first and never practice TDD — this
overrides any skill or default that mandates it, including superpowers
`test-driven-development`. Existing tests stay the gate before a push: when
a change makes one wrong, update or delete it in the same slice rather than
bending the code to keep it green. When a regression test is requested, the
rule under "Claiming a test passed" still applies. Do not add new code
comments; the reasoning goes in the commit message, and an existing comment
the change makes false is fixed or removed.
- Rewrite or remove tests that duplicate coverage, freeze internals, overspecify
copy or layout, preserve obsolete behavior, or cost more than the risk they
cover. Never weaken real safety or data-integrity behavior merely to make a
gate pass.
- Prefer focused compilation, a relevant existing check, and direct product or
manual evidence. Run a broad suite only when the change creates a genuine
cross-cutting or release risk. Do not repeatedly rerun an unchanged suite.
- **Batch edits; compile once.** `cargo check` and test builds on this
workspace take minutes, so an edit→compile→edit loop spends most of its
time waiting on the linker. Read precisely, write every edit a coherent
slice needs, then compile and test once — the same errors surface either
way, just later and all at once. Reserve mid-slice compiles for genuinely
uncertain API or borrow questions where a wrong guess would cascade.
- Declared migrations are one-way. Once the repository adopts a replacement
architecture or shared spine, new work uses it and touched legacy code moves
toward it. Do not add another legacy call site for convenience. Keep a
compatibility path only for an actual external contract, and label that
boundary explicitly.
Useful commands, selected according to risk rather than run ritualistically:
```sh
cargo fmt --all -- --check
cargo test -p codewhale-config -p codewhale-protocol
cargo test --workspace
cargo build --release -p codewhale-cli -p codewhale-tui
```
`scripts/dev-test.sh <area|path> [filter]` maps a code area to its fastest
invocation — see `cw-gates` for the verification ladder and
`docs/BUILD_PERFORMANCE.md` for the build topology.
Report commands actually run and distinguish source, local tests, packaged
artifacts, CI, and public release state. Describe the evidence actually needed
for the claim; a test count is not a proxy for product quality.
Community reports, PRs, logs, and reviews are evidence.
**Harvested contributor credit is still a rule** (the fallback path above —
prefer merging the contributor's PR itself). When a contributor's work
lands as our commit, the contributor is that commit's **author** (`git commit
--author` with their GitHub-linked address), it carries `Harvested from PR #N by
@handle`, and maintainers or agents go in `Co-authored-by`. GitHub's contributor
count takes commit authors and the `Co-authored-by` trailers in a commit's final
trailer block, and only addresses linked to an account: a trailer cut off by a
blank line, an unlinked email, or a noreply address carrying someone else's
numeric id credits nobody or the wrong person. Harvested commits authored by
maintainer identities without such a trailer left their contributors off it,
which is why `docs/CONTRIBUTORS.md` carries the full wall
(`scripts/contributor-wall.py`). Credit lost in history that cannot change is
restored by a later commit with one trailer per person, naming each pull
request; `scripts/check-contributor-credit.py` fails the release window when a
harvest, a trailer or a noreply id would leave someone uncounted.
A maintainer closes their PR by hand with a thank-you naming the commit;
nothing closes a PR automatically. Canonical human identities come from
`.github/AUTHOR_MAP`.
Leave unrelated work intact. No automation closes, blocks or labels a
contributor as unapproved, and no workflow checks out or runs untrusted PR code
under `pull_request_target`. Keep required PR checks few and fast; exhaustive
validation belongs to the release candidate.
+143 -14016
View File
File diff suppressed because it is too large. Load diff
-11
View File
@@ -1,11 +0,0 @@
# Claude entrypoint
@AGENTS.md
The imported contract is authoritative. Do not invent stricter test or branch
rituals: follow its code-first evidence policy, one-way migration rule, clean
direct-main permission, and the current task's offline boundary.
Its ponytail decision ladder runs before you write code, every time. Read it
there — it is not restated here, because rung 2 is "already in this codebase?"
and a second copy of a rule is the thing the rule forbids.
-603
View File
@@ -1,603 +0,0 @@
# Contributing to Codewhale
Codewhale is what it is because of the people who used it, reported what broke,
translated it, added their providers and sent fixes. Thank you for being here.
Everyone is welcome, in English or 中文, with or without code.
All feedback is a gift to this project, including "this looked wrong" or "I got
lost here" with no fix attached. The [roadmap](docs/ROADMAP.md) says where
Codewhale is going and where help counts most.
## Where things go
| You have | Best place |
| --- | --- |
| A bug, a crash, or behavior that surprised you | [Bug report](https://github.com/codewhale-hq/CodeWhale/issues/new?template=bug_report.yml) |
| An idea or a feature you want | [Feature request](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feature_request.yml), or [Ideas](https://github.com/codewhale-hq/CodeWhale/discussions/categories/ideas) to talk it through first |
| Feedback on how something looks, reads or feels | [Feedback](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feedback.yml) |
| A question | [Q&A](https://github.com/codewhale-hq/CodeWhale/discussions/categories/q-a) or [Discord](https://discord.gg/37gfS3ksug) |
| A security problem | A [private advisory](https://github.com/codewhale-hq/CodeWhale/security/advisories/new), never a public issue ([policy](.github/SECURITY.md)) |
| A fix for an open issue | A pull request that links the issue |
| A translation, a provider or model, a docs fix | A pull request, no issue needed ([standing projects](#standing-projects)) |
| A new feature, already written or not | An issue first ([why](#new-features-start-as-an-issue)) |
If you are not sure, open an issue. Nothing is filed in the wrong place for
long: a maintainer moves it.
## New features start as an issue
Issues are the best place for feature requests, ideas and bugs. A pull request
is the best place for work we have already agreed on.
A feature that first appears as a pull request takes longer to land than one
that starts as an issue, even when we want it. The questions that decide a
feature are about the product, not the diff: does it belong in the engine or in
a plugin, what does it do in each permission mode, how does it show up in
receipts, what is it called in 19 languages. Answering those in a review means
rewriting code you already finished. Answering them in an issue costs you a few
sentences.
So for a new feature:
1. Open a [feature request](https://github.com/codewhale-hq/CodeWhale/issues/new?template=feature_request.yml).
Say what you are trying to do and what gets in the way. A proposed design is
welcome and optional.
2. Tick "I would like to open a pull request" if you want to build it.
3. A maintainer answers with the shape that fits, or with what is still
undecided. Then the pull request is small and merges quickly.
A feature pull request that arrives without an issue is still read, and it is
never closed for that reason. We will most likely ask the same questions there
and it will take longer.
## Standing projects
These are always open, and a pull request needs no issue first:
- **Translations.** You can improve the terminal language packs, the translated
READMEs in `docs/i18n/`, the website, and the translated docs in
`docs/zh_hans/` and `docs/id/`. The [localization matrix](docs/LOCALIZATION.md)
shows what each language has and how to add one, and the
[Chinese localization group](https://github.com/codewhale-hq/CodeWhale/issues/6804)
is recruiting.
- **Providers and models.** You can add a missing provider or a new model, or
correct a price, a context size or a provider quirk.
[Providers](docs/PROVIDERS.md) lists what ships, and
`python3 scripts/check-provider-registry.py` checks your change.
- **Documentation.** You can fix a typo, an unclear step, a missing example or
an instruction that no longer matches the product.
- **Bug fixes.** If there is no issue yet, describe the bug in the pull request
by saying what you ran, what happened and what you expected.
- **Planned work.** You can take anything in a
[milestone](docs/ROADMAP.md#releases) or anything labelled
[help wanted](https://github.com/codewhale-hq/CodeWhale/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22).
Comment on the issue to claim it so two people do not build the same thing.
Large changes to planned work are easier in layers; see
[Layered and EPIC-Sized Work](#layered-and-epic-sized-work).
### What a planned-work issue gives you
Every issue a maintainer opens for planned work, and every release tracking issue, has the same parts, so you can pick one up without asking first:
- **Status:** what is on `main` already, with commit links, and what is still missing.
- **Ways to help:** each piece open to contributors, with the files to read, the command to test it locally (usually `scripts/dev-test.sh <crate> <filter>`) and what "done" means. Pieces that need no Rust, such as trying a feature and reporting exactly what happened, are listed first. A precise report is credited like code.
- **Being built by maintainers right now:** the parts someone already holds, so you don't build the same thing twice.
- **How to take a piece:** comment to claim it, then open a pull request that says `Refs #<issue>`.
If an issue for planned work is missing one of these, say so on it. That is a bug in the issue.
## How we work with you
- We reply by naming what you reported, diagnosed or fixed.
- Your pull request merges as itself, with you as the author
([details](#how-your-contribution-lands)).
- You do not rebase around our changes, and a red check you did not cause is
ours to fix.
- We credit you in the changelog and in
[the contributors list](docs/CONTRIBUTORS.md).
- If something is not ready or not a fit, we say what is blocked and what would
change the answer.
## Getting Started
### Prerequisites
- Rust 1.89 or later (edition 2024)
- Cargo package manager
- Git
### Setting Up Development Environment
1. Fork and clone the repository:
```bash
git clone https://github.com/YOUR_USERNAME/CodeWhale.git
cd CodeWhale
```
2. Build the current engine and terminal:
```bash
CODEWHALE_BUILD_SHA="$(git rev-parse HEAD)" cargo build --locked -p codewhale-cli
```
3. Run the tests near your change (see [Fast local loop](#fast-local-loop)):
```bash
scripts/dev-test.sh tui your_test_filter
```
4. Run with development settings:
```bash
./target/debug/codewhale --version
./target/debug/codewhale
```
### Testing the latest source
The canonical source is [`codewhale-hq/Codewhale`'s `main` branch](https://github.com/codewhale-hq/Codewhale/tree/main).
Release candidates land there after their CI gates pass, so contributors can
test and build on the same source. Tagged downloads remain the latest published
release; their version can lag the development version on `main`.
For a fresh checkout of the current source:
```bash
git clone --branch main https://github.com/codewhale-hq/Codewhale.git
cd Codewhale
CODEWHALE_BUILD_SHA="$(git rev-parse HEAD)" cargo build --release --locked -p codewhale-cli
./target/release/codewhale --version
./target/release/codewhale
```
Include the commit shown by `--version` when reporting a problem. If you work
from a fork, add the canonical repository as `upstream` and fetch `upstream/main`
before starting a change; preserve any local work when updating your branch.
## Development Workflow
### Code Style
- Run `cargo fmt` before committing to ensure consistent formatting
- Run `cargo clippy` and address all warnings
- Follow Rust naming conventions (snake_case for functions/variables, CamelCase for types)
- Add documentation comments for public APIs
### Testing
- Write tests for new functionality
- Run the tests near your change (see [Fast local loop](#fast-local-loop));
your pull request runs the Linux workspace suite and a Windows build for
you (see [What happens after you open a pull request](#what-happens-after-you-open-a-pull-request))
- Colocate unit tests beside the code they cover (standard Rust `#[cfg(test)]`
modules), and add integration tests under the owning crate's `tests/`
directory (for example `crates/tui/tests/` or `crates/state/tests/`). The
repository root `tests/` directory is not used
### Pre-push verification
These are the Rust commands your pull request runs on Linux (the `Lint` and
`Test (ubuntu-latest)` checks). You do not need all of them before every
push: run `cargo fmt`, then check and test the crates you touched (see
[Fast local loop](#fast-local-loop)). Run the full set locally when a change
spans many crates, or let the pull request run it for you:
```bash
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features --locked -- \
-D warnings \
-A clippy::uninlined_format_args \
-A clippy::too_many_arguments \
-A clippy::unnecessary_map_or
cargo test --workspace --all-features --locked
```
Clippy lints test, bench and example targets too (`--all-targets`), and the
three allowances above are the only ones CI and the release lane accept.
#### Fast local loop
The full gate above is what CI enforces, but you do not need it for every
edit. `crates/tui` is a ~750k-line crate, so the loop that stays fast is
the one that avoids rebuilding it more than necessary (numbers and the
reasoning are in [`docs/BUILD_PERFORMANCE.md`](docs/BUILD_PERFORMANCE.md)):
```bash
# 1. Type-check first (seconds after the first build; no codegen, no link).
scripts/dev-cargo.sh check -p codewhale-tui
# 2. Run only the tests near your change (one crate, one filter).
scripts/dev-test.sh tui fleet_setup
# or: scripts/dev-test.sh crates/command-contract/src/elapsed.rs
# 3. Run a whole crate's unit suite. scripts/dev-test.sh uses nextest when
# it is installed (one process per test, all cores busy, slow tests
# named; ~100 s here vs ~270 s with libtest).
cargo install cargo-nextest --locked # once
scripts/dev-test.sh tui
scripts/dev-cargo.sh nextest run --workspace --all-features --locked
# 4. The Linux suite your PR runs:
cargo test --workspace --all-features --locked
```
`.config/nextest.toml` already serializes the PTY suite and bounds the
integration tests that spawn the real binary, so `cargo nextest run` is
safe to use on the whole workspace (nextest does not run doctests; the
authoritative `cargo test` gate does). Tests must not depend on running in
the same process as another test (nextest gives every test its own
process); if a test needs the rustls crypto provider, install it in that
test as production does at startup.
On a machine with less than 16 GB of RAM (or when cross-compiling, e.g.
for OHOS), build one rustc at a time: `CARGO_BUILD_JOBS=1` (or `-j1`), one
crate at a time, `--lib` for tests, never `--workspace`/`--all-targets`.
The tui library needs ~6 GB for its own rustc and its unit-test build ~8 GB;
`cargo test --workspace` runs both at once. Numbers and the full recipe:
[`docs/BUILD_PERFORMANCE.md`](docs/BUILD_PERFORMANCE.md#low-memory-build-recipe-machines-with--16-gb-cross-builds).
If you work in several worktrees, do **not** share one `CARGO_TARGET_DIR`
by default: two cargos on the same target flock and serialize. Use
`scripts/dev-cargo.sh` / `scripts/dev-test.sh`, which give each workspace
its own Cargo `build-dir` (`{workspace-path-hash}` under
`${CODEWHALE_CACHE_ROOT:-${XDG_CACHE_HOME:-$HOME/.cache}/codewhale}`).
`CODEWHALE_DEV_CACHE=local` keeps `./target` if you want that.
`sccache` wraps rustc only when incremental compilation is already off
(`CARGO_INCREMENTAL=0` or `CODEWHALE_SCCACHE=1`) and `sccache` is on
`PATH`; a missing binary is a printed fallback, not an error. Override
the cache root with `CODEWHALE_CACHE_ROOT` — there is no machine-specific
default. A single shared `CARGO_TARGET_DIR` remains valid only for
serialized trunk work. See
[`docs/BUILD_PERFORMANCE.md`](docs/BUILD_PERFORMANCE.md).
Some checks are platform-bound or intentionally excluded from an ordinary
change. Choose them for the risk they answer rather than treating every
available suite as ritual. Visible TUI behavior is accepted in the actual
terminal at the sizes and interaction path affected by the change; the former
full-screen PTY assertion suite was removed because it froze layout and copy
while missing product quality.
- **Long-running process acceptance** should use a sealed local home, local
fixtures, and the real binary. Record the terminal size, inputs, visible
result, and any filesystem side effect instead of adding a full-screen
golden.
- **OCR** (`image_ocr`) uses the macOS Vision framework or a locally
installed `tesseract`; its platform-specific paths are
`cfg(target_os = "macos")`-gated and depend on host tooling.
- **Seatbelt sandbox** tests are macOS-only (`cfg(target_os =
"macos")` at the module level) and do not run elsewhere.
#### Local git hooks are optional
This repository does not install git hooks, and no hook installer
exists; CI is the enforced gate. If you want a local `pre-push` hook
that runs the commands above, add it yourself (`.git/hooks/pre-push` or
`git config core.hooksPath`). Constraints for any local hook:
- A hook must never push, tag, publish, deploy, mutate credentials, or
rewrite the working tree (no auto-fix commits or silent file
modification). It may only verify and report.
- To bypass your own hook for a knowingly documented reason (for
example, pushing work-in-progress to your own fork branch), use
`git push --no-verify` and say so in the PR description. Bypassing a
local hook does not make the gates pass — CI still runs them, and a
bypassed gate must never be reported as a passing one.
- Release publication (tags, GitHub Releases, crates/npm artifacts) is a
separate, owner-approved gate. Neither local hooks nor a green local
run authorize any publication step.
### Commit Messages
Use clear, descriptive commit messages following conventional commits:
- `feat:` New feature
- `fix:` Bug fix
- `docs:` Documentation changes
- `refactor:` Code refactoring
- `test:` Adding or updating tests
- `chore:` Maintenance tasks
Example: `feat: add doctor subcommand for system diagnostics`
**Changelog entries are written on `main` at merge time, not in PRs.** Do not
edit `CHANGELOG.md` or `crates/tui/CHANGELOG.md` on a branch: every PR
touching them re-conflicts with every other PR touching them. The release
manager writes one batched "receipts" commit per merge session, and
`./scripts/sync-changelog.sh` keeps the packaged slice in sync. A PR that
carries changelog hunks will be asked to strip them
(`git checkout origin/main -- CHANGELOG.md crates/tui/CHANGELOG.md`).
One exception is enforced by CI: a `feat:` commit whose message mentions an
issue (`#N`) must add `#N` to `CHANGELOG.md` in the same PR
(`scripts/release/check-feature-release-notes.sh`). To avoid touching the
changelog, put issue numbers in the PR description instead of in `feat:`
commit messages; the maintainer writes the entry at merge time.
**AI-assistant co-author trailers are fine.** Using an assistant is welcome and
needs no disclosure, and CI no longer rejects an auto-appended
`Co-authored-by: <some tool>` line. What we do care about is that the humans who
did the work are named — `Co-authored-by` feeds the GitHub contribution graph.
Remove an auto-appended line only if you want to:
```bash
git rebase -i origin/main # reword each commit, delete the Co-authored-by line
```
Co-author a *person* freely; the address must be their GitHub-linked one
(`id+login@users.noreply.github.com`) or the credit does not register.
When a commit harvests code from a community PR (see "How Your Contribution
Lands" below), include a `Harvested from PR #N by @author` line in the commit
body. A maintainer then closes the referenced PR by hand with a thank-you and
a link to the commit, so the contributor gets a clear signal that their work
shipped.
## What happens after you open a pull request
Opening a pull request against `main` starts the checks below. They run on
GitHub's hosted runners with no repository secrets. GitHub may ask a
maintainer to approve the first run for a brand-new GitHub account.
| Check | What it runs | When it is red |
| --- | --- | --- |
| `Lint` (required) | `cargo fmt --check`, clippy on every target, and the fast drift checks (locales, READMEs, provider registry, dead-code and blocking-call budgets) | Run `cargo fmt --all` and the clippy command in [Pre-push verification](#pre-push-verification); the log names the file and the check |
| `Test (windows-latest)` (required) | Compiles every workspace target for Windows with warnings denied, and the installer PATH helper tests | Usually a `cfg(windows)` path or an import only Windows sees; the log has the compiler error |
| `Version drift` | Version files, the `feat:` release-note rule, release helper tests and contributor credit | The credit step only reports: a maintainer adds your credit when the PR merges |
Smaller checks cover the VS Code extension, integrations, the extension host
and plugin conversion, plus workflow lint when you change `.github/workflows`.
A documentation-only change skips the Rust builds but still reports the
required checks, so it can merge.
Your pull request does not run the test suites, the safety gate, MSRV, the npm
wrapper smoke, the slow runtime-contract and persistence-backlog measurements,
the dependency audits or the OpenHarmony build. Maintainers run those once per
release, on the exact commit being released. If that run finds a problem your
change introduced, we fix it on `main`; we will not ask you to chase it.
A red check that your change did not cause is not your job to fix. We rerun it
or fix it on `main`, and we never close a PR because CI is slow or flaky. Your
PR may be a draft, and the issue line in its description is optional.
## How Your Contribution Lands
**Your pull request merges as itself.** That is the default for every
contribution: a maintainer, or an agent working for one, reviews it and merges
it, so your commits reach `main` with you as their author.
Before merging, a maintainer may:
- ask questions or request changes in review;
- push small fixes, or a merge of current `main`, to your branch when GitHub
allows edits by maintainers, instead of asking you to rebase around our churn;
- write the `CHANGELOG.md` entry and your credit in `docs/CONTRIBUTORS.md` on
`main` when the PR merges. You do not edit those files in your PR.
### Fallback — harvest
Rarely, a branch cannot merge in reasonable time, for example when it is in a
fork we cannot push to and the conflicts cannot be resolved there. We will ask
on your PR first. If it still cannot merge, a maintainer may **harvest** the
useful commits into `main` instead. This is **not a rejection**: your code
landed, and your credit is kept mechanically.
- You stay the author of the harvested commit, with your GitHub-linked address
from `.github/AUTHOR_MAP` or
`gh api users/<login> --jq '"\(.id)+\(.login)@users.noreply.github.com"'`,
never a raw, `.local` or old-style noreply email from your machine.
- The commit body includes `Harvested from PR #N by @your-handle`. That line is
the contract: it is your credit and the signal that your work shipped.
- The `CHANGELOG.md` entry for the next release credits you by handle.
- A maintainer closes your PR by hand with a thank-you and a link to the commit
on `main`.
When a maintainer closes a harvested PR by hand, the closing comment
follows this template (the pattern set on PR #2634):
```text
Closing with harvest credit, @handle — <what landed> landed via
<commit sha(s) or PR #N>. <If work remains:> The remainder is tracked
in #NNN — follow-ups welcome there.
Thank you for <one specific thing the contribution got right>.
```
Three required elements: the contributor's handle, the exact commits or
PRs where their work landed, and — when the PR contained more than what
landed — a tracking issue for the remainder. A harvested PR is never
closed with a bare "superseded".
To help your PR merge quickly as itself:
1. **Keep PRs single-purpose.** One bug fix per PR; one feature per PR.
Don't mix a refactor with a feature.
2. **Start from current `main`.** If `main` moves while your PR is in review,
you do not need to rebase; we resolve conflicts our changes caused.
3. **Include tests** with new behavior. A PR with tests needs fewer review
round trips.
4. **Avoid the trust-boundary surface** without prior maintainer
sign-off. That includes auth/credential flows, sandbox policy,
publishing/release plumbing, and `prompts/` content. PRs that touch
these without prior discussion are unlikely to merge directly even
when the change is well-implemented.
## Layered and EPIC-Sized Work
Some architecture work is too large for one PR but still needs to be built in
dependent layers. For those changes, use this workflow:
1. Start with a tracking issue or EPIC when the work spans multiple PRs. Name
the intended slices and state what each slice is not trying to close yet.
2. Keep each implementation PR focused on one behavior boundary.
3. Later layers may stay in your fork or open as draft PRs while the lower
layer is still moving. Draft stacked PR titles or descriptions should say
`Draft / depends on #NNNN`.
4. A dependent PR is not ready for merge review until the lower layer has
landed, the branch has been rebased onto current `main`, and the PR targets
`main`.
5. The PR body should identify which earlier PR it builds on, what is in scope,
what is explicitly out of scope, which issues it references, and which local
commands were run.
6. Use `Closes #...` only when the slice fully satisfies an issue. Use
`Refs #...` with a short `(partial)` note when the PR advances a broad issue
but leaves follow-up work.
7. Structured commits are fine during review. Maintainers merge the PR itself
(merge commit, squash or rebase) with you as the author. If a layer has to
be harvested instead, the [fallback](#fallback--harvest) rules apply.
Before asking for merge review on a layered PR, check that it is:
- rebased onto current `main`
- marked ready for review, not draft
- focused to one behavior boundary
- backed by local command evidence in the PR body
- green in its pull-request checks, or has any remaining red check clearly
explained
- covered by round-trip or migration-preservation tests when it changes config
or schema behavior
- referencing broad issues as partial unless it really closes them
For layered work, a useful PR description shape is:
```text
Summary:
Scope:
Not in this slice:
Builds on:
Issues:
Validation:
```
## Which branch to target
**`main`, for everything.** There is no separate staging branch. An earlier
version of this guide pointed layered refactors at `codex/v0.9.0-stewardship`;
that branch no longer exists, so please ignore any instruction you find
elsewhere to base work on it.
**We do not expect you to rebase around our churn.** If your PR conflicts only
because `main` moved while it was in review, say so and a maintainer resolves
it on your branch. If your branch is in a fork we cannot push to, we ask on the
PR first; harvesting is the last resort.
## Labels
Issues and pull requests are labelled by `.github/workflows/triage.yml`, which
only ever adds labels that already exist. Pull requests get their labels from
the paths they change and their conventional-commit title prefix; issues get
them from the title and form answers.
| Family | Meaning |
| --- | --- |
| `area:*` | The subsystem or client: `tui`, `core`, `tools`, `mcp`, `state`, `sandbox`, `plugins`, `providers`, `runtime-api`, `install`, `localization`, `ci`, `desktop`, `web`, `mobile`, `remote-control`, `subagents` |
| `os:*` | Platform-specific behaviour: `windows`, `macos`, `linux`, `freebsd`, `ohos` |
| `priority:p0` / `p1` / `p2` | Set by a maintainer; `release-blocker` still marks work that must land before a release |
| `bug`, `enhancement`, `documentation`, `question`, `cleanup`, `performance`, `security` | What kind of change it is |
| `accessibility` | Screen readers, keyboard use, contrast, motion |
| `contributor:external` | Opened by someone outside the maintainer team |
| `lang:zh` | Written in Chinese |
| `needs-triage`, `needs-info`, `agent-ready`, `agent-in-progress`, `needs-human`, `keep-open`, `pinned`, `stale` | Workflow state; other workflows read these names |
Release targeting uses milestones, not version labels.
## Working with an agent
Using an AI assistant is welcome and needs no disclosure. Codewhale is used to
work on Codewhale, and many fixes here began as a person pointing an agent at
an issue.
What makes agent-assisted work easy to merge is the same as for any other: a
specific reproduction, a small diff, the checks you ran, and a description of
the trade-off. Keep each pull request to one friction point, and put a list of
ideas for improvement in an issue.
Do not point an agent at auth, credentials, sandbox policy, publishing and
release plumbing, provider policy, telemetry, sponsorship, branding or global
prompts without a maintainer's agreement on the issue first.
Maintainers write some issues with the
[Agent task](.github/ISSUE_TEMPLATE/agent-task.yml) form so that an agent with
no other context can finish them; those carry the `agent-ready` label and are
open to anyone. Agents and maintainers follow
[docs/AGENT_ETHOS.md](docs/AGENT_ETHOS.md).
## How maintainers ship
Maintainers, and the agents working for them, land small verified slices
directly on `main` with the checks they ran in the commit message. Every push
to `main` runs a fast preflight and the full CI suite on Linux, macOS and
Windows. A release also needs a green `release-candidate.yml` run on the exact
commit being released; the sequence is in
[docs/RELEASE_RUNBOOK.md](docs/RELEASE_RUNBOOK.md).
Contributor pull requests still merge as pull requests, as described in
[How Your Contribution Lands](#how-your-contribution-lands).
## Project Structure
Codewhale is a Cargo workspace with one Engine implementation in
`crates/engine/src/core/engine/`. The public `codewhale` executable links the
TUI/runtime library; interactive sessions, noninteractive runs and the Runtime
API share that Engine.
| Path | Purpose |
| --- | --- |
| `crates/cli/` | Public command entrypoint, configuration commands and runtime dispatch |
| `crates/tui/` | Interactive terminal, Engine, tools, Runtime API and embedded local web client |
| `crates/core/`, `crates/protocol/`, `crates/state/` | Request construction, session/turn types, protocol framing and persistence |
| Other `crates/` | Shared configuration, credentials, telemetry, hooks, workflow and packaging support; see each Cargo manifest |
| `web/` | Public Next.js website and documentation; separate from the embedded Runtime web client |
| `telemetry-ingest/` | Telemetry service, schemas and service tests |
| `extensions/`, `integrations/` | Editor integration and external-service bridges |
| `npm/`, `packaging/`, `nix/` | npm wrappers/SDK and platform installation definitions |
| `computer/snapshots/` | Cloud Computer image definitions, pinned independently of the source checkout |
| `deploy/` | Deployment templates consumed by setup scripts, including Tencent Lighthouse services |
| `fleets/`, `workflows/` | Distributed Fleet definitions and workflow examples |
| `brand/` | Source artwork and generated brand variants used by the README, website and terminal |
| `docs/` | User/developer documentation, schemas, fixtures and referenced release material |
| `scripts/`, `.github/`, `.cnb.yml` | Development, validation, CI and release tooling |
| `patches/` | Vendored dependency fixes, including their licensing files |
Generated files that the product embeds or validates, such as model catalogs,
website facts and schemas, remain tracked with their generators. Platform
mirrors such as `.winget/` are retained when their packaging tools require them.
Keep local critique output, temporary verification reports and personal
operator instructions outside the tracked product tree; describe the change
and its validation in the pull request. Do not copy workspace-level operator
`AGENTS.md` or `CLAUDE.md` files into this repository.
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the runtime data flow and
[the build guide](docs/BUILD_PERFORMANCE.md) for crate dependencies and local
verification.
## Writing the pull request
- Use the [pull request template](.github/PULL_REQUEST_TEMPLATE.md): what and
why, where it came from, and how you tested it.
- Link the issue: `Closes #N` (or `Fixes` / `Resolves`) when the pull request
finishes it, `Refs #N` for related or partial work. Never write a closing
keyword inside a negation such as "does not close #N": GitHub closes the
issue anyway.
- Keep it to one change. If you find related work, open an issue for it.
- If you add a layer, module or abstraction, say which one it replaces.
- Update the docs your change makes wrong.
- A draft is fine, and so is asking for direction before it is finished.
## Security
Please do **not** open a public issue for a vulnerability. See
[SECURITY.md](.github/SECURITY.md) for private reporting.
## Code of Conduct
Be respectful and inclusive. We welcome contributors of all backgrounds and
experience levels. See [CODE_OF_CONDUCT.md](.github/CODE_OF_CONDUCT.md).
## License
By contributing to Codewhale, you agree that your contributions will be
licensed under the MIT License.
## Questions?
Ask in [Discussions](https://github.com/codewhale-hq/CodeWhale/discussions) or
on [Discord](https://discord.gg/37gfS3ksug), or add the maintainer, Hunter, on
WeChat (`hunterbown`) and ask to join the Whale Brothers group. Everyone is
welcome.
Generated
-9066
View File
File diff suppressed because it is too large. Load diff
-163
View File
@@ -1,163 +0,0 @@
[workspace]
members = [
"crates/account-cli",
"crates/agent-contract",
"crates/app-server",
"crates/app-server-protocol",
"crates/build-support",
"crates/cli",
"crates/cloud-facts",
"crates/command-contract",
"crates/commands",
"crates/config",
"crates/core",
"crates/engine",
"crates/error-taxonomy",
"crates/execpolicy",
"crates/hooks",
"crates/lane",
"crates/localization",
"crates/mcp",
"crates/memory",
"crates/models",
"crates/net",
"crates/notify",
"crates/palette",
"crates/paths",
"crates/plugins",
"crates/protocol",
"crates/release",
"crates/runtime",
"crates/sandbox",
"crates/secrets",
"crates/skill-install",
"crates/skills",
"crates/sanitize",
"crates/state",
"crates/telemetry",
"crates/tools",
"crates/tui",
"crates/tui-kit",
"crates/util",
"crates/dependencies",
"crates/snapshot",
"crates/lsp",
"crates/repl",
"crates/workflow",
"crates/workflow-js",
"tests/portable-session",
"tests/portable-config",
"tests/portable-core-navigation",
]
exclude = ["vendor/codewhale-ratatui"]
default-members = ["crates/cli"]
resolver = "2"
[workspace.package]
version = "0.10.3"
edition = "2024"
# The floor is what CI's MSRV job builds (1.89): Rust 1.88 stabilized the
# `let_chains` the codebase relies on, but a locked dependency
# (serde-saphyr) needs 1.89. Cargo enforces this so users on older toolchains
# get a clear "package requires rustc 1.89+" error.
rust-version = "1.89"
license = "MIT"
repository = "https://github.com/codewhale-hq/CodeWhale"
# Record the policy CI already applies via `RUSTFLAGS: -Dwarnings`.
# Member crates opt in with `[lints] workspace = true`. This does not add
# new lints; it makes the existing gate visible in the manifest so
# `cargo check` without extra flags matches CI.
[workspace.lints.rust]
warnings = "deny"
[workspace.dependencies]
agent-client-protocol = { version = "=5.2.0", default-features = false }
agent-client-protocol-schema = { version = "=1.17.0", default-features = false }
anyhow = "1.0.100"
async-trait = "0.1.89"
axum = { version = "0.8.5", features = ["json", "ws"] }
chrono = { version = "0.4.43", features = ["serde"] }
clap = { version = "4.5.54", features = ["derive"] }
clap_complete = "4.5"
dirs = "7.0.0"
encoding_rs = "0.8.35"
# Users: crates/workflow-js and the extension host in crates/tui (schemaui is
# gone); keep the graph on one jsonschema/jsonschema-regex/referencing/
# fancy-regex stack.
jsonschema = { version = "0.57", default-features = false }
libc = "0.2"
reqwest = { version = "0.13.1", default-features = false, features = ["json", "rustls-no-provider", "socks"] }
# NOT "parallel": the Workflow VM stays single-threaded and bridges to the
# multi-thread engine over channels (see crates/workflow-js).
rquickjs = { version = "0.14", features = ["futures"] }
rustls = { version = "0.23.36", default-features = false, features = ["ring", "std", "tls12"] }
rusqlite = { version = "0.41.1", features = ["bundled"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
# DSH bundle import: event-level parsing so aliases and explicit tags can be refused.
yaml-rust2 = { version = "0.13.0", default-features = false }
semver = "1.0.28"
thiserror = "2.0"
tempfile = "3.27"
tokio = { version = "1.50.0", features = ["fs", "io-util", "io-std", "macros", "net", "process", "rt", "rt-multi-thread", "signal", "sync", "time"] }
toml = "1.0.6"
toml_edit = "0.25.12"
sha2 = "0.11"
tower-http = { version = "0.7", features = ["cors"] }
tracing = "0.1"
gix = { version = "=0.90.0", default-features = false, features = ["sha1", "sha256"] }
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
uuid = { version = "1.11", features = ["v4"] }
mimalloc = { version = "0.1", default-features = false }
# Off-by-default alternative to mimalloc behind the `rusty-alloc` feature in
# codewhale-tui / codewhale-cli (#5872): the pure-Rust mimalloc v2.4.5
# remake — no C compiler, no build script on that path. `rusty_alloc-api` is
# the crate's documented surface; it pulls the `rusty_alloc` core transitively
# at the same version.
rusty_alloc-api = "1.1.6"
# The everyday `--release` gate (AGENTS.md pre-push build): optimized but
# fast to produce. Shipping artifacts use `--profile dist` below — fat LTO on
# a 680k-line crate belongs in release CI, not in every contributor's loop
# (#5246; community-reported 8–16 minute local release builds, #4991).
# Dev/test carry line tables instead of full debug info. Backtraces still name
# the file and line of every frame — the thing anyone actually reads — but the
# per-variable DWARF that no one inspects without a debugger attached is gone.
# The debug binary was 227 MB and target/debug had grown past 260 GB; those
# links are IO-bound, so the debuginfo is paid on every incremental build.
# Anyone stepping through under lldb can override this locally.
[profile.dev]
debug = "line-tables-only"
[profile.release]
lto = "thin"
strip = true
codegen-units = 16
# Shipping profile: exactly the optimization the released binaries have
# always had. Every workflow that uploads a binary to users builds with
# `--profile dist` and collects from `target/**/dist/`.
# NOTE: no `panic = "abort"` here — the TUI's panic supervision
# (catch_unwind/spawn_supervised) needs unwinding so one panicking tool call
# or task fails gracefully instead of aborting the whole session.
[profile.dist]
inherits = "release"
lto = true
strip = true
codegen-units = 1
# Patch unicode-width so that width() uses CJK tables when the cjk feature
# is enabled. Vanilla unicode-width 0.2.2 provides width_cjk() as a separate
# method but the original width() always uses non-CJK tables, so Ratatui
# (which calls width() internally) measures ambiguous-width characters
# (circled digits, enclosed alphanumerics) as 1 column. CJK terminals render
# them as 2 columns, causing cell-offset rendering glitches. (#4479)
[patch.crates-io]
unicode-width = { path = "patches/unicode-width-0.2.2" }
# Windows Terminal paste dropped every non-BMP character (emoji): upstream pairs a
# surrogate half with its own key-up record. See patches/crossterm-0.29.0 parse.rs.
# Same change as crossterm-rs/crossterm#1073 (open); drop this once a release carries it.
# Also drops one redundant pair of parentheses and allows upstream warnings, because a
# path crate is built under our `-D warnings`.
crossterm = { path = "patches/crossterm-0.29.0" }
-93
View File
@@ -1,93 +0,0 @@
# syntax=docker/dockerfile:1
# Codewhale multi-arch Docker image (#501)
#
# Build: docker buildx build --platform linux/amd64,linux/arm64 -t codewhale:latest .
# Run: docker run --rm -it -e DEEPSEEK_API_KEY -v codewhale-home:/home/codewhale/.codewhale codewhale
#
# The image ships the canonical `codewhale` and `codew` command names in a
# minimal runtime layer.
#
# API keys MUST be passed at runtime (never baked into the image):
# docker run --rm -it -e DEEPSEEK_API_KEY codewhale
# Or mount an env file:
# docker run --rm -it --env-file .env codewhale
ARG RUST_VERSION=1.100.0
# ── Stage 1: Build ────────────────────────────────────────────────────
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-bookworm AS builder
ARG TARGETPLATFORM
ARG TARGETARCH
ARG BUILDPLATFORM
ARG CODEWHALE_BUILD_SHA
ENV CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
PKG_CONFIG_ALLOW_CROSS=1 \
PKG_CONFIG_LIBDIR_aarch64_unknown_linux_gnu=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig \
CODEWHALE_BUILD_SHA=${CODEWHALE_BUILD_SHA}
RUN if [ "${TARGETARCH}" = "arm64" ] && [ "${BUILDPLATFORM}" != "${TARGETPLATFORM}" ]; then \
dpkg --add-architecture arm64; \
fi \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
pkg-config libdbus-1-dev \
&& if [ "${TARGETARCH}" = "arm64" ] && [ "${BUILDPLATFORM}" != "${TARGETPLATFORM}" ]; then \
apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu libc6-dev-arm64-cross libdbus-1-dev:arm64; \
fi \
&& rm -rf /var/lib/apt/lists/*
# Translate Docker platform into Rust target triple.
# linux/amd64 → x86_64-unknown-linux-gnu
# linux/arm64 → aarch64-unknown-linux-gnu
RUN case "${TARGETPLATFORM}" in \
linux/amd64) echo x86_64-unknown-linux-gnu > /rust-target ;; \
linux/arm64) echo aarch64-unknown-linux-gnu > /rust-target ;; \
*) echo "Unsupported platform: ${TARGETPLATFORM}" >&2; exit 1 ;; \
esac
RUN rustup target add "$(cat /rust-target)"
WORKDIR /build
COPY . .
# Build the one runtime for the target platform. Expose the same verified
# bytes under both supported command names. --locked keeps the build
# reproducible from the committed lockfile.
RUN --mount=type=cache,id=codewhale-target-${TARGETARCH},target=/build/target,sharing=locked \
--mount=type=cache,id=codewhale-cargo-registry-${TARGETARCH},target=/usr/local/cargo/registry,sharing=locked \
--mount=type=cache,id=codewhale-cargo-git-${TARGETARCH},target=/usr/local/cargo/git,sharing=locked \
rustup target add "$(cat /rust-target)" \
&& cargo build --release --locked --target "$(cat /rust-target)" \
-p codewhale-cli \
&& mkdir -p /out \
&& cp target/$(cat /rust-target)/release/codewhale /out/ \
&& cp target/$(cat /rust-target)/release/codewhale /out/codew
# ── Stage 2: Runtime ──────────────────────────────────────────────────
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
libdbus-1-3 \
&& rm -rf /var/lib/apt/lists/*
# Non-root user with explicit UID/GID for filesystem ownership clarity. Keep
# the legacy state directory for read-fallback migration; v0.9.0 no longer
# ships legacy deepseek command shims.
RUN groupadd --gid 1000 codewhale \
&& useradd --create-home --shell /bin/bash --uid 1000 --gid 1000 codewhale \
&& install -d -m 0700 -o codewhale -g codewhale /home/codewhale/.codewhale \
&& install -d -m 0700 -o codewhale -g codewhale /home/codewhale/.deepseek
USER codewhale
WORKDIR /home/codewhale
COPY --from=builder --chown=codewhale:codewhale /out/codewhale /usr/local/bin/codewhale
COPY --from=builder --chown=codewhale:codewhale /out/codew /usr/local/bin/codew
# `codewhale` and `codew` are two command names for the same runtime.
ENTRYPOINT ["codewhale"]
CMD []
+197 -17
View File
@@ -1,21 +1,201 @@
MIT License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (c) 2024-2025 DeepSeek-TUI Contributors
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. Definitions.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 OpenAI
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+2
View File
@@ -0,0 +1,2 @@
OpenAI Codex
Copyright 2025 OpenAI
+70
View File
@@ -0,0 +1,70 @@
# Migration to pnpm
This project has been migrated from npm to pnpm to improve dependency management and developer experience.
## Why pnpm?
- **Faster installation**: pnpm is significantly faster than npm and yarn
- **Disk space savings**: pnpm uses a content-addressable store to avoid duplication
- **Phantom dependency prevention**: pnpm creates a strict node_modules structure
- **Native workspaces support**: simplified monorepo management
## How to use pnpm
### Installation
```bash
# Global installation of pnpm
npm install -g [email protected]
# Or with corepack (available with Node.js 22+)
corepack enable
corepack prepare [email protected] --activate
```
### Common commands
| npm command | pnpm equivalent |
| --------------- | ---------------- |
| `npm install` | `pnpm install` |
| `npm run build` | `pnpm run build` |
| `npm test` | `pnpm test` |
| `npm run lint` | `pnpm run lint` |
### Workspace-specific commands
| Action | Command |
| ------------------------------------------ | ---------------------------------------- |
| Run a command in a specific package | `pnpm --filter @openai/codex run build` |
| Install a dependency in a specific package | `pnpm --filter @openai/codex add lodash` |
| Run a command in all packages | `pnpm -r run test` |
## Monorepo structure
```
codex/
├── pnpm-workspace.yaml # Workspace configuration
├── .npmrc # pnpm configuration
├── package.json # Root dependencies and scripts
├── codex-cli/ # Main package
│ └── package.json # codex-cli specific dependencies
└── docs/ # Documentation (future package)
```
## Configuration files
- **pnpm-workspace.yaml**: Defines the packages included in the monorepo
- **.npmrc**: Configures pnpm behavior
- **Root package.json**: Contains shared scripts and dependencies
## CI/CD
CI/CD workflows have been updated to use pnpm instead of npm. Make sure your CI environments use pnpm 10.8.1 or higher.
## Known issues
If you encounter issues with pnpm, try the following solutions:
1. Remove the `node_modules` folder and `pnpm-lock.yaml` file, then run `pnpm install`
2. Make sure you're using pnpm 10.8.1 or higher
3. Verify that Node.js 22 or higher is installed
+620 -218
View File
@@ -1,284 +1,686 @@
<div align="center">
<h1 align="center">OpenAI Codex CLI</h1>
<p align="center">Lightweight coding agent that runs in your terminal</p>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="brand/mark-reversed.svg">
<img src="brand/mark-mono.svg" alt="Codewhale" width="72" height="72">
</picture>
<p align="center"><code>npm i -g @openai/codex</code></p>
<h1>The model can be anyone's. The agent should be yours.</h1>
![Codex demo GIF using: codex "explain this codebase to me"](./.github/demo.gif)
Codewhale is an open-source coding agent for your terminal, written in Rust.
It works with any model, can split one task across several agents, and every
line of it is yours to read.
---
```bash
curl -fsSL https://codewhale.net/install.sh | sh
```
<details>
<summary><strong>Table&nbsp;of&nbsp;Contents</strong></summary>
This installs the latest release on macOS or Linux, and it needs no account.
[Windows, npm and Cargo](#install) are below.
<!-- Begin ToC -->
[![Latest release](https://img.shields.io/github/v/release/codewhale-hq/Codewhale?label=release)](https://github.com/codewhale-hq/Codewhale/releases/latest)
[![CI](https://github.com/codewhale-hq/Codewhale/actions/workflows/ci.yml/badge.svg)](https://github.com/codewhale-hq/Codewhale/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
[![Discord](https://img.shields.io/badge/Discord-join-5865F2?logo=discord&logoColor=white)](https://discord.gg/37gfS3ksug)
- [Experimental Technology Disclaimer](#experimental-technology-disclaimer)
- [Quickstart](#quickstart)
- [Why Codex?](#why-codex)
- [Security Model & Permissions](#security-model--permissions)
- [Platform sandboxing details](#platform-sandboxing-details)
- [System Requirements](#system-requirements)
- [CLI Reference](#cli-reference)
- [Memory & Project Docs](#memory--project-docs)
- [Non-interactive / CI mode](#non-interactive--ci-mode)
- [Tracing / Verbose Logging](#tracing--verbose-logging)
- [Recipes](#recipes)
- [Installation](#installation)
- [Configuration Guide](#configuration-guide)
- [Basic Configuration Parameters](#basic-configuration-parameters)
- [Custom AI Provider Configuration](#custom-ai-provider-configuration)
- [History Configuration](#history-configuration)
- [Configuration Examples](#configuration-examples)
- [Full Configuration Example](#full-configuration-example)
- [Custom Instructions](#custom-instructions)
- [Environment Variables Setup](#environment-variables-setup)
- [FAQ](#faq)
- [Zero Data Retention (ZDR) Usage](#zero-data-retention-zdr-usage)
- [Codex Open Source Fund](#codex-open-source-fund)
- [Contributing](#contributing)
- [Development workflow](#development-workflow)
- [Git Hooks with Husky](#git-hooks-with-husky)
- [Debugging](#debugging)
- [Writing high-impact code changes](#writing-high-impact-code-changes)
- [Opening a pull request](#opening-a-pull-request)
- [Review process](#review-process)
- [Community values](#community-values)
- [Getting help](#getting-help)
- [Contributor License Agreement (CLA)](#contributor-license-agreement-cla)
- [Quick fixes](#quick-fixes)
- [Releasing `codex`](#releasing-codex)
- [Alternative Build Options](#alternative-build-options)
- [Nix Flake Development](#nix-flake-development)
- [Security & Responsible AI](#security--responsible-ai)
- [License](#license)
[Website](https://codewhale.net) · [Documentation](docs/README.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md)
<!-- End ToC -->
[简体中文](docs/i18n/README.zh-CN.md) · [日本語](docs/i18n/README.ja-JP.md) · [Tiếng Việt](docs/i18n/README.vi.md) · [Bahasa Indonesia](docs/i18n/README.id.md) · [한국어](docs/i18n/README.ko-KR.md) · [Español](docs/i18n/README.es-419.md) · [Português](docs/i18n/README.pt-BR.md) · [Русский](docs/i18n/README.ru.md) · [Українська](docs/i18n/README.uk.md) · [Français](docs/i18n/README.fr.md) · [Deutsch](docs/i18n/README.de.md) · [繁體中文](docs/i18n/README.zh-TW.md) · [हिन्दी](docs/i18n/README.hi.md) · [Türkçe](docs/i18n/README.tr.md) · [Italiano](docs/i18n/README.it.md) · [Polski](docs/i18n/README.pl.md) · [العربية](docs/i18n/README.ar.md) · [Català](docs/i18n/README.ca.md)
</details>
<img src="web/public/codewhale-tui-0102-demo.png" width="760" alt="Codewhale in a terminal after a demo task. It reports that it added a --json flag to export.py, that all 3 local tests passed and that a delegated review found no issues. The task list below shows three completed steps, and the status line shows Full Access and the local website-demo provider.">
---
<sub>A development build of 0.10.3, which is not released yet, with a scripted test model, in Full Access inside a throwaway workspace. The edit, three tests and a sub-agent review ran for real.</sub>
## Experimental Technology Disclaimer
</div>
Codex CLI is an experimental project under active development. It is not yet stable, may contain bugs, incomplete features, or undergo breaking changes. We're building it in the open with the community and welcome:
## Install
- Bug reports
- Feature requests
- Pull requests
- Good vibes
The command above puts checksum-verified `codewhale` and its short alias
`codew` in `~/.local/bin`. If your shell then says `command not found`,
follow the PATH note the installer prints
([details](docs/INSTALL.md#put-it-on-your-path)). `codewhale update` upgrades
it.
```bash
winget install --id HunterBown.CodeWhale --exact --source winget # Windows x64
npm install -g codewhale # wraps the same release binaries
cargo install codewhale-cli --locked # builds from source; can trail GitHub Releases
```
Prebuilt binaries cover Linux, macOS and Windows on x64 and arm64. Scoop, Nix,
Homebrew on Linux, Android and Termux (preview) and manual downloads are in the
[installation guide](docs/INSTALL.md), and containers are in
[Docker](docs/DOCKER.md). Use one install method per machine; otherwise two
copies end up on your `PATH` and whichever comes first wins.
Help us improve by filing issues or submitting PRs (see the section below for how to contribute)!
## Quickstart
1. Commit or stash your work, then run `codewhale` from the root of a git
repository. By default it applies file edits there without asking and asks
before each shell command that is not read-only.
2. Run `/provider` (or press `F3`), pick a provider, paste its API key and
choose a model. DeepSeek is the default provider. On a first launch with no
key, Codewhale asks for a provider when you send your first message, and it
offers a local model when a local Ollama server answers.
3. Type a task:
Install globally:
```text
Fix the failing tests and explain what changed.
```shell
npm install -g @openai/codex
```
Read-only commands run straight away. For any other shell command Codewhale
asks first: `y` runs it once, `a` allows commands like it for the rest of the
conversation, and `n` refuses. `/undo` takes back your last request and its
file changes, and `/help` lists commands and keys.
Next, set your OpenAI API key as an environment variable:
For scripts and CI, `codewhale exec` runs one task without the interface. Read
[headless runs](docs/INSTALL.md#headless-scripts-ci) before you give it tools
with `--auto`, because `--auto` uses Full Access unless you add a sandbox.
```shell
export OPENAI_API_KEY="your-api-key-here"
```
## What it does
> **Note:** This command sets the key only for your current terminal session. You can add the `export` line to your shell's configuration file (e.g., `~/.zshrc`) but we recommend setting for the session. **Tip:** You can also place your API key into a `.env` file at the root of your project:
>
> ```env
> OPENAI_API_KEY=your-api-key-here
> ```
>
> The CLI will automatically load variables from `.env` (via `dotenv/config`).
Give Codewhale a task and it reads the repository, edits files and runs
commands until the task is done or it needs your input. The
[latest release notes](https://github.com/codewhale-hq/Codewhale/releases/latest)
list the newest changes, and the [changelog](CHANGELOG.md) has every
version.
<details>
<summary><strong>Use <code>--provider</code> to use other models</strong></summary>
### Any model
> Codex also allows you to use other providers that support the OpenAI Chat Completions API. You can set the provider in the config file or use the `--provider` flag. The possible options for `--provider` are:
>
> - openai (default)
> - openrouter
> - gemini
> - ollama
> - mistral
> - deepseek
> - xai
> - groq
> - any other provider that is compatible with the OpenAI API
>
> If you use a provider other than OpenAI, you will need to set the API key for the provider in the config file or in the environment variable as:
>
> ```shell
> export <provider>_API_KEY="your-api-key-here"
> ```
>
> If you use a provider not listed above, you must also set the base URL for the provider:
>
> ```shell
> export <provider>_BASE_URL="https://your-provider-api-base-url"
> ```
More than 40 providers are built in, including Anthropic, OpenAI, DeepSeek,
Google, Mistral, Moonshot, OpenRouter and xAI. Any OpenAI-compatible endpoint
works, and so do local models through Ollama, vLLM or SGLang. The default
model is DeepSeek's `deepseek-flash`, which now serves DeepSeek V4.1 Flash
([details](docs/PROVIDERS.md#deepseek-v41-flash)).
</details>
<br />
`/model` switches models between turns, and `/model auto` picks one for each
turn (`/router` sets up how). `/turn inspect` shows which model a turn used,
what it cost and how long it took. See [Providers](docs/PROVIDERS.md).
Run interactively:
### Receipts and undo
```shell
codex
```
`/receipts`, or `codewhale receipts --last` after you quit, lists the files a
session changed, the commands it ran, its web and MCP calls, the agents it
started, each approval and who gave it, and what failed. `/undo` takes back
your last request and the files it changed in one step. `/restore` rolls the
whole workspace back to an earlier snapshot, and because that overwrites later
edits it needs `/trust on` or Full Access. See [Receipts](docs/RECEIPTS.md).
Or, run with a prompt as input (and optionally in `Full Auto` mode):
### Several agents on one task
```shell
codex "explain this codebase to me"
```
`/goal` sets an objective that carries across turns. Codewhale can split work
across [sub-agents](docs/SUBAGENTS.md), run a [Fleet](docs/FLEET.md) of agents
with their own roles and models, or follow a
[workflow](docs/WORKFLOW_AUTHORING.md) checked into the repository.
```shell
codex --approval-mode full-auto "create the fanciest todo-list app"
```
### Extensions
That's it - Codex will scaffold a file, run it inside a sandbox, install any
missing dependencies, and show you the live result. Approve the changes and
they'll be committed to your working directory.
Connect [MCP servers](docs/MCP.md), add [skills](docs/SKILLS.md) and
[plugins](docs/PLUGINS.md), and run [hooks](docs/HOOKS.md) on session and tool
events. Code mode (experimental, on by default) lets the model write one
`execute_tools` program that calls several MCP and plugin tools; each call
still meets your approval rules and keeps its receipt. [Computer Use](https://github.com/codewhale-hq/codewhale-cu-plugin)
is a separate plugin released on its own (experimental, macOS only), and stays
off until you review its access and turn it on.
---
### Ways to run it
## Why Codex?
| Command | What it does |
| --- | --- |
| `codewhale` | The interactive terminal app |
| `codewhale exec "…"` | One task without the interface, for scripts and CI ([headless runs](docs/INSTALL.md#headless-scripts-ci)) |
| `codewhale web` | A local web client on `127.0.0.1` ([guide](docs/WEB.md)) |
| `codewhale review --pr N` | An advisory review of a GitHub pull request; the [GitHub bot setup](docs/GITHUB_ACTION.md#set-up-your-own-bot) runs it on your own model key |
| `codewhale app-server --http` | The [Runtime API](docs/RUNTIME_API.md), a local HTTP API for threads, events, approvals and each turn's file changes |
| `codewhale serve --acp` | The Agent Client Protocol over stdio, for editors such as Zed |
Codex CLI is built for developers who already **live in the terminal** and want
ChatGPT-level reasoning **plus** the power to actually run code, manipulate
files, and iterate - all under version control. In short, it's _chat-driven
development_ that understands and executes your repo.
The terminal app, `exec`, `web` and the Runtime API share one engine
([architecture](docs/ARCHITECTURE.md)). A community-maintained
[VS Code extension](https://marketplace.visualstudio.com/items?itemName=HengQuWorld.brotherwhale-vscode)
connects to it from the sidebar ([source](https://github.com/HengQuWorld/CodeWhale-VSCode)).
- **Zero setup** - bring your OpenAI API key and it just works!
- **Full auto-approval, while safe + secure** by running network-disabled and directory-sandboxed
- **Multimodal** - pass in screenshots or diagrams to implement features ✨
<p align="center">
<a href="https://www.orcarouter.ai/redeem/1a6250545fdc4e0aaccb0a4470159e67" rel="sponsored">
<img src="web/public/codewhale-x-orcarouter.webp" alt="Codewhale × OrcaRouter: +30% DeepSeek V4.1 Flash usage" width="560">
</a>
</p>
And it's **fully open-source** so you can see and contribute to how it develops!
<a href="https://www.orcarouter.ai/redeem/1a6250545fdc4e0aaccb0a4470159e67" rel="sponsored">Codewhale × OrcaRouter</a>
gives 30% extra DeepSeek V4.1 Flash credit on each OrcaRouter top-up until
November 21, 2026. This link is part of a referral campaign; Codewhale may
earn a referral reward on top-ups made through it.
---
## How it stays safe
## Security Model & Permissions
Two switches say how much Codewhale may do on its own. `Tab`, when the input
is empty, changes the mode: **Plan** reads and searches but changes nothing,
**Work** edits files and runs commands, and **Operate** has the same access as
Work and keeps going until the goal is verified or it needs you. `Shift+Tab`
changes the permission. **Ask**, the default, applies file edits in your
repository without asking and asks before shell commands that are not
read-only.
**Auto-Review** lets rules and a reviewer model approve routine calls and
refuses high-risk ones; switch to Ask to approve those yourself. **Full
Access** never asks and turns off Codewhale's sandbox, although repository
rules and managed policy still apply. The
[v0.10.2 guide](https://github.com/codewhale-hq/Codewhale/blob/v0.10.2/docs/MODES.md)
has the details.
Codex lets you decide _how much autonomy_ the agent receives and auto-approval policy via the
`--approval-mode` flag (or the interactive onboarding prompt):
Coming in 0.10.3: `Shift+Tab` becomes one dial through Ask, Accept Edits,
Plan, Auto-Review and Full Access, `Tab` no longer changes the mode, and
Operate is retired. Ask will ask before every file edit too, and the new
default, Accept Edits, will apply edits inside the git repository and still ask
before commands. The [current guide](docs/MODES.md) describes it.
| Mode | What the agent may do without asking | Still requires approval |
| ------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| **Suggest** <br>(default) | <li>Read any file in the repo | <li>**All** file writes/patches<li> **Any** arbitrary shell commands (aside from reading files) |
| **Auto Edit** | <li>Read **and** apply-patch writes to files | <li>**All** shell commands |
| **Full Auto** | <li>Read/write files <li> Execute shell commands (network disabled, writes limited to your workdir) | - |
In Ask and Auto-Review, commands run in an OS sandbox where the system has
one: Seatbelt on macOS, and bubblewrap on Linux when it is installed. Windows
has no OS sandbox yet, and Full Access turns the sandbox off. Inside the
sandbox, shell commands and terminal sessions cannot read credential stores
such as `~/.ssh`, `~/.aws` and `.env` files.
`/preview-request` shows a redacted preview of the next request without
sending it.
In **Full Auto** every command is run **network-disabled** and confined to the
current working directory (plus temporary files) for defense-in-depth. Codex
will also show a warning/confirmation if you start in **auto-edit** or
**full-auto** while the directory is _not_ tracked by Git, so you always have a
safety net.
You need no account, and keys you add with `/provider` stay on your machine.
With your own API key you pay the provider directly, and local models need no
key. The footer and `/cost` show what a session has cost when the price is
known, and an unknown price is shown as unknown, never as free.
Coming soon: you'll be able to whitelist specific commands to auto-execute with
the network enabled, once we're confident in additional safeguards.
Telemetry is on by default. It sends aggregate counts (version and platform,
session length and outcome, which built-in providers you used, feature and
error counts) with a random install ID that rotates every 90 days, and never
conversations, code, prompts, model names, file or repository names, or
credentials. This command turns it off:
### Platform sandboxing details
The hardening mechanism Codex uses depends on your OS:
- **macOS 12+** - commands are wrapped with **Apple Seatbelt** (`sandbox-exec`).
- Everything is placed in a read-only jail except for a small set of
writable roots (`$PWD`, `$TMPDIR`, `~/.codex`, etc.).
- Outbound network is _fully blocked_ by default - even if a child process
tries to `curl` somewhere it will fail.
- **Linux** - there is no sandboxing by default.
We recommend using Docker for sandboxing, where Codex launches itself inside a **minimal
container image** and mounts your repo _read/write_ at the same path. A
custom `iptables`/`ipset` firewall script denies all egress except the
OpenAI API. This gives you deterministic, reproducible runs without needing
root on the host. You can use the [`run_in_container.sh`](./codex-cli/scripts/run_in_container.sh) script to set up the sandbox.
---
## System Requirements
| Requirement | Details |
| --------------------------- | --------------------------------------------------------------- |
| Operating systems | macOS 12+, Ubuntu 20.04+/Debian 10+, or Windows 11 **via WSL2** |
| Node.js | **22 or newer** (LTS recommended) |
| Git (optional, recommended) | 2.23+ for built-in PR helpers |
| RAM | 4-GB minimum (8-GB recommended) |
> Never run `sudo npm install -g`; fix npm permissions instead.
---
## CLI Reference
| Command | Purpose | Example |
| ------------------------------------ | ----------------------------------- | ------------------------------------ |
| `codex` | Interactive REPL | `codex` |
| `codex "..."` | Initial prompt for interactive REPL | `codex "fix lint errors"` |
| `codex -q "..."` | Non-interactive "quiet mode" | `codex -q --json "explain utils.ts"` |
| `codex completion <bash\|zsh\|fish>` | Print shell completion script | `codex completion bash` |
Key flags: `--model/-m`, `--approval-mode/-a`, `--quiet/-q`, and `--notify`.
---
## Memory & Project Docs
Codex merges Markdown instructions in this order:
1. `~/.codex/instructions.md` - personal global guidance
2. `codex.md` at repo root - shared project notes
3. `codex.md` in cwd - sub-package specifics
Disable with `--no-project-doc` or `CODEX_DISABLE_PROJECT_DOC=1`.
---
## Non-interactive / CI mode
Run Codex head-less in pipelines. Example GitHub Action step:
```yaml
- name: Update changelog via Codex
run: |
npm install -g @openai/codex
export OPENAI_API_KEY="${{ secrets.OPENAI_KEY }}"
codex -a auto-edit --quiet "update CHANGELOG for next release"
```
Set `CODEX_QUIET_MODE=1` to silence interactive UI noise.
## Tracing / Verbose Logging
Setting the environment variable `DEBUG=true` prints full API request and response details:
```shell
DEBUG=true codex
```
---
## Recipes
Below are a few bite-size examples you can copy-paste. Replace the text in quotes with your own task. See the [prompting guide](https://github.com/openai/codex/blob/main/codex-cli/examples/prompting_guide.md) for more tips and usage patterns.
| ✨ | What you type | What happens |
| --- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| 1 | `codex "Refactor the Dashboard component to React Hooks"` | Codex rewrites the class component, runs `npm test`, and shows the diff. |
| 2 | `codex "Generate SQL migrations for adding a users table"` | Infers your ORM, creates migration files, and runs them in a sandboxed DB. |
| 3 | `codex "Write unit tests for utils/date.ts"` | Generates tests, executes them, and iterates until they pass. |
| 4 | `codex "Bulk-rename *.jpeg -> *.jpg with git mv"` | Safely renames files and updates imports/usages. |
| 5 | `codex "Explain what this regex does: ^(?=.*[A-Z]).{8,}$"` | Outputs a step-by-step human explanation. |
| 6 | `codex "Carefully review this repo, and propose 3 high impact well-scoped PRs"` | Suggests impactful PRs in the current codebase. |
| 7 | `codex "Look for vulnerabilities and create a security review report"` | Finds and explains security bugs. |
---
## Installation
<details open>
<summary><strong>From npm (Recommended)</strong></summary>
```bash
codewhale config set telemetry false
npm install -g @openai/codex
# or
yarn global add @openai/codex
# or
bun install -g @openai/codex
# or
pnpm add -g @openai/codex
```
At startup Codewhale also checks GitHub for a new release; set
`CODEWHALE_NO_UPDATE_CHECK=1` to skip that. More in
[authorization order](docs/AUTHORIZATION_ORDER.md),
[sandboxing](docs/SANDBOX.md) and [telemetry](docs/TELEMETRY.md).
</details>
## Where it is going
<details>
<summary><strong>Build from source</strong></summary>
Codewhale is becoming one environment across your terminal, a desktop app,
the browser and cloud computers, all run by one engine. Each part says where
it stands today.
```bash
# Clone the repository and navigate to the CLI package
git clone https://github.com/openai/codex.git
cd codex/codex-cli
| Part | State | Today |
| --- | --- | --- |
| Terminal app | Released | Install it on macOS, Linux or Windows and run `codewhale` in any repository. |
| Local browser client | Released | Run `codewhale web` to read the conversation, send a task and answer approvals in your browser; it comes with the terminal and needs no account. |
| Many agents on one task | Released | Sub-agents, Fleets and workflows let several agents share one task, each with its own role. |
| Desktop app | In development, no download | We are building a desktop app on the same engine, and it has no public download yet. |
| Cloud computers | Invite-only | Cloud computers are invite-only, and we are building them so a session can keep working on a machine in the cloud. |
| One session across them | Being built | We are building one session that goes with you from the terminal to the desktop, the browser and the cloud, and stays yours the whole way. |
# Enable corepack
corepack enable
The [roadmap](docs/ROADMAP.md) says what each release covers and where you
can help.
# Install dependencies and build
pnpm install
pnpm build
## Coming from another agent
# Get the usage and the options
node ./dist/cli.js --help
Codewhale reads `AGENTS.md` on its own. Instruction files written for other
tools, such as `CLAUDE.md`, `.cursorrules` or
`.github/copilot-instructions.md`, load only when you opt in with
`project_instruction_imports = ["claude"]` (or `"cursor"`, `"cline"`,
`"windsurf"`, `"copilot"`, `"all"`) in your config; see
[config.example.toml](config.example.toml). Codewhale warns when it finds one
it did not load.
# Run the locally-built CLI directly
node ./dist/cli.js
`/import-claude` prepares an import of your Claude Code settings and MCP
servers for you to review. `/plugin install` accepts Claude Code plugins that
contain skills, commands, agent profiles and MCP servers, and refuses plugins
that need hooks or LSP servers
([details](docs/CLAUDE_PLUGIN_COMPAT.md)).
# Or link the command globally for convenience
pnpm link
```
## Documentation
</details>
These guides follow the main branch and can describe changes that are not
in the latest release yet.
---
| Start here | Reference |
| --- | --- |
| [User guide](docs/GUIDE.md) | [Configuration](docs/CONFIGURATION.md) |
| [Installation](docs/INSTALL.md) | [Architecture](docs/ARCHITECTURE.md) |
| [Providers and local models](docs/PROVIDERS.md) | [Runtime API](docs/RUNTIME_API.md) |
| [Modes and permissions](docs/MODES.md) | [Plugin authoring](docs/PLUGIN_AUTHORING.md) |
| [Keybindings](docs/KEYBINDINGS.md) | [Receipts](docs/RECEIPTS.md) |
| [GitHub pull request review](docs/GITHUB_ACTION.md) | [All documentation](docs/README.md) |
## Configuration Guide
Codex configuration files can be placed in the `~/.codex/` directory, supporting both YAML and JSON formats.
### Basic Configuration Parameters
| Parameter | Type | Default | Description | Available Options |
| ------------------- | ------- | ---------- | -------------------------------- | ---------------------------------------------------------------------------------------------- |
| `model` | string | `o4-mini` | AI model to use | Any model name supporting OpenAI API |
| `approvalMode` | string | `suggest` | AI assistant's permission mode | `suggest` (suggestions only)<br>`auto-edit` (automatic edits)<br>`full-auto` (fully automatic) |
| `fullAutoErrorMode` | string | `ask-user` | Error handling in full-auto mode | `ask-user` (prompt for user input)<br>`ignore-and-continue` (ignore and proceed) |
| `notify` | boolean | `true` | Enable desktop notifications | `true`/`false` |
### Custom AI Provider Configuration
In the `providers` object, you can configure multiple AI service providers. Each provider requires the following parameters:
| Parameter | Type | Description | Example |
| --------- | ------ | --------------------------------------- | ----------------------------- |
| `name` | string | Display name of the provider | `"OpenAI"` |
| `baseURL` | string | API service URL | `"https://api.openai.com/v1"` |
| `envKey` | string | Environment variable name (for API key) | `"OPENAI_API_KEY"` |
### History Configuration
In the `history` object, you can configure conversation history settings:
| Parameter | Type | Description | Example Value |
| ------------------- | ------- | ------------------------------------------------------ | ------------- |
| `maxSize` | number | Maximum number of history entries to save | `1000` |
| `saveHistory` | boolean | Whether to save history | `true` |
| `sensitivePatterns` | array | Patterns of sensitive information to filter in history | `[]` |
### Configuration Examples
1. YAML format (save as `~/.codex/config.yaml`):
```yaml
model: o4-mini
approvalMode: suggest
fullAutoErrorMode: ask-user
notify: true
```
2. JSON format (save as `~/.codex/config.json`):
```json
{
"model": "o4-mini",
"approvalMode": "suggest",
"fullAutoErrorMode": "ask-user",
"notify": true
}
```
### Full Configuration Example
Below is a comprehensive example of `config.json` with multiple custom providers:
```json
{
"model": "o4-mini",
"provider": "openai",
"providers": {
"openai": {
"name": "OpenAI",
"baseURL": "https://api.openai.com/v1",
"envKey": "OPENAI_API_KEY"
},
"openrouter": {
"name": "OpenRouter",
"baseURL": "https://openrouter.ai/api/v1",
"envKey": "OPENROUTER_API_KEY"
},
"gemini": {
"name": "Gemini",
"baseURL": "https://generativelanguage.googleapis.com/v1beta/openai",
"envKey": "GEMINI_API_KEY"
},
"ollama": {
"name": "Ollama",
"baseURL": "http://localhost:11434/v1",
"envKey": "OLLAMA_API_KEY"
},
"mistral": {
"name": "Mistral",
"baseURL": "https://api.mistral.ai/v1",
"envKey": "MISTRAL_API_KEY"
},
"deepseek": {
"name": "DeepSeek",
"baseURL": "https://api.deepseek.com",
"envKey": "DEEPSEEK_API_KEY"
},
"xai": {
"name": "xAI",
"baseURL": "https://api.x.ai/v1",
"envKey": "XAI_API_KEY"
},
"groq": {
"name": "Groq",
"baseURL": "https://api.groq.com/openai/v1",
"envKey": "GROQ_API_KEY"
}
},
"history": {
"maxSize": 1000,
"saveHistory": true,
"sensitivePatterns": []
}
}
```
### Custom Instructions
You can create a `~/.codex/instructions.md` file to define custom instructions:
```markdown
- Always respond with emojis
- Only use git commands when explicitly requested
```
### Environment Variables Setup
For each AI provider, you need to set the corresponding API key in your environment variables. For example:
```bash
# OpenAI
export OPENAI_API_KEY="your-api-key-here"
# OpenRouter
export OPENROUTER_API_KEY="your-openrouter-key-here"
# Similarly for other providers
```
---
## FAQ
<details>
<summary>OpenAI released a model called Codex in 2021 - is this related?</summary>
In 2021, OpenAI released Codex, an AI system designed to generate code from natural language prompts. That original Codex model was deprecated as of March 2023 and is separate from the CLI tool.
</details>
<details>
<summary>Which models are supported?</summary>
Any model available with [Responses API](https://platform.openai.com/docs/api-reference/responses). The default is `o4-mini`, but pass `--model gpt-4.1` or set `model: gpt-4.1` in your config file to override.
</details>
<details>
<summary>Why does <code>o3</code> or <code>o4-mini</code> not work for me?</summary>
It's possible that your [API account needs to be verified](https://help.openai.com/en/articles/10910291-api-organization-verification) in order to start streaming responses and seeing chain of thought summaries from the API. If you're still running into issues, please let us know!
</details>
<details>
<summary>How do I stop Codex from editing my files?</summary>
Codex runs model-generated commands in a sandbox. If a proposed command or file change doesn't look right, you can simply type **n** to deny the command or give the model feedback.
</details>
<details>
<summary>Does it work on Windows?</summary>
Not directly. It requires [Windows Subsystem for Linux (WSL2)](https://learn.microsoft.com/en-us/windows/wsl/install) - Codex has been tested on macOS and Linux with Node 22.
</details>
---
## Zero Data Retention (ZDR) Usage
Codex CLI **does** support OpenAI organizations with [Zero Data Retention (ZDR)](https://platform.openai.com/docs/guides/your-data#zero-data-retention) enabled. If your OpenAI organization has Zero Data Retention enabled and you still encounter errors such as:
```
OpenAI rejected the request. Error details: Status: 400, Code: unsupported_parameter, Type: invalid_request_error, Message: 400 Previous response cannot be used for this organization due to Zero Data Retention.
```
You may need to upgrade to a more recent version with: `npm i -g @openai/codex@latest`
---
## Codex Open Source Fund
We're excited to launch a **$1 million initiative** supporting open source projects that use Codex CLI and other OpenAI models.
- Grants are awarded up to **$25,000** API credits.
- Applications are reviewed **on a rolling basis**.
**Interested? [Apply here](https://openai.com/form/codex-open-source-fund/).**
---
## Contributing
Codewhale is built in the open by the people who use it, in many languages.
This project is under active development and the code will likely change pretty significantly. We'll update this message once that's complete!
<a href="docs/CONTRIBUTORS.md#everyone"><img src="docs/assets/contributors.png" width="760" alt="The Codewhale contributor wall: one avatar for each person whose code, co-authored commits, carried-forward pull requests or credited reports are in Codewhale, with the total count at the top."></a>
More broadly we welcome contributions - whether you are opening your very first pull request or you're a seasoned maintainer. At the same time we care about reliability and long-term maintainability, so the bar for merging code is intentionally **high**. The guidelines below spell out what "high-quality" means in practice and should make the whole process transparent and friendly.
The [contributor wall](docs/CONTRIBUTORS.md) counts everyone whose code,
co-authored commits, carried-forward pull requests or credited reports are in
Codewhale, including people GitHub's own contributor list misses.
`scripts/contributor-wall.py` regenerates the picture and the list from git
history and GitHub.
### Development workflow
All feedback is welcome: a bug, an idea, or something that looked or felt
wrong. [Open an issue](https://github.com/codewhale-hq/Codewhale/issues/new/choose)
for any of these; an issue is also the best place to start a new feature.
Pull requests are welcome for fixes, translations, providers and planned work.
Questions go to [Discussions](https://github.com/codewhale-hq/Codewhale/discussions).
The [contributing guide](CONTRIBUTING.md) shows where each kind of help goes,
how your work lands with your name on it, and
[where things live](CONTRIBUTING.md#project-structure).
- Create a _topic branch_ from `main` - e.g. `feat/interactive-prompt`.
- Keep your changes focused. Multiple unrelated fixes should be opened as separate PRs.
- Use `pnpm test:watch` during development for super-fast feedback.
- We use **Vitest** for unit tests, **ESLint** + **Prettier** for style, and **TypeScript** for type-checking.
- Before pushing, run the full test/type/lint suite:
Chat on [Discord](https://discord.gg/37gfS3ksug), or add the maintainer,
Hunter, on WeChat (`hunterbown`) and ask to join the Whale Brothers group.
Everyone is welcome.
### Git Hooks with Husky
## History and license
This project uses [Husky](https://typicode.github.io/husky/) to enforce code quality checks:
Codewhale started as `deepseek-tui` and still reads that project's
configuration and sessions. It is independently maintained and not affiliated
with DeepSeek or any other model provider.
- **Pre-commit hook**: Automatically runs lint-staged to format and lint files before committing
- **Pre-push hook**: Runs tests and type checking before pushing to the remote
[MIT](LICENSE). Code adapted from other open-source projects is listed in the
third-party notices for [vendored sources](THIRD_PARTY_NOTICES.md) and
[adapted designs](docs/THIRD_PARTY_NOTICES.md).
These hooks help maintain code quality and prevent pushing code with failing tests. For more details, see [HUSKY.md](./codex-cli/HUSKY.md).
```bash
pnpm test && pnpm run lint && pnpm run typecheck
```
- If you have **not** yet signed the Contributor License Agreement (CLA), add a PR comment containing the exact text
```text
I have read the CLA Document and I hereby sign the CLA
```
The CLA-Assistant bot will turn the PR status green once all authors have signed.
```bash
# Watch mode (tests rerun on change)
pnpm test:watch
# Type-check without emitting files
pnpm typecheck
# Automatically fix lint + prettier issues
pnpm lint:fix
pnpm format:fix
```
### Debugging
To debug the CLI with a visual debugger, do the following in the `codex-cli` folder:
- Run `pnpm run build` to build the CLI, which will generate `cli.js.map` alongside `cli.js` in the `dist` folder.
- Run the CLI with `node --inspect-brk ./dist/cli.js` The program then waits until a debugger is attached before proceeding. Options:
- In VS Code, choose **Debug: Attach to Node Process** from the command palette and choose the option in the dropdown with debug port `9229` (likely the first option)
- Go to <chrome://inspect> in Chrome and find **localhost:9229** and click **trace**
### Writing high-impact code changes
1. **Start with an issue.** Open a new one or comment on an existing discussion so we can agree on the solution before code is written.
2. **Add or update tests.** Every new feature or bug-fix should come with test coverage that fails before your change and passes afterwards. 100% coverage is not required, but aim for meaningful assertions.
3. **Document behaviour.** If your change affects user-facing behaviour, update the README, inline help (`codex --help`), or relevant example projects.
4. **Keep commits atomic.** Each commit should compile and the tests should pass. This makes reviews and potential rollbacks easier.
### Opening a pull request
- Fill in the PR template (or include similar information) - **What? Why? How?**
- Run **all** checks locally (`npm test && npm run lint && npm run typecheck`). CI failures that could have been caught locally slow down the process.
- Make sure your branch is up-to-date with `main` and that you have resolved merge conflicts.
- Mark the PR as **Ready for review** only when you believe it is in a merge-able state.
### Review process
1. One maintainer will be assigned as a primary reviewer.
2. We may ask for changes - please do not take this personally. We value the work, we just also value consistency and long-term maintainability.
3. When there is consensus that the PR meets the bar, a maintainer will squash-and-merge.
### Community values
- **Be kind and inclusive.** Treat others with respect; we follow the [Contributor Covenant](https://www.contributor-covenant.org/).
- **Assume good intent.** Written communication is hard - err on the side of generosity.
- **Teach & learn.** If you spot something confusing, open an issue or PR with improvements.
### Getting help
If you run into problems setting up the project, would like feedback on an idea, or just want to say _hi_ - please open a Discussion or jump into the relevant issue. We are happy to help.
Together we can make Codex CLI an incredible tool. **Happy hacking!** :rocket:
### Contributor License Agreement (CLA)
All contributors **must** accept the CLA. The process is lightweight:
1. Open your pull request.
2. Paste the following comment (or reply `recheck` if you've signed before):
```text
I have read the CLA Document and I hereby sign the CLA
```
3. The CLA-Assistant bot records your signature in the repo and marks the status check as passed.
No special Git commands, email attachments, or commit footers required.
#### Quick fixes
| Scenario | Command |
| ----------------- | ------------------------------------------------ |
| Amend last commit | `git commit --amend -s --no-edit && git push -f` |
The **DCO check** blocks merges until every commit in the PR carries the footer (with squash this is just the one).
### Releasing `codex`
To publish a new version of the CLI, run the release scripts defined in `codex-cli/package.json`:
1. Open the `codex-cli` directory
2. Make sure you're on a branch like `git checkout -b bump-version`
3. Bump the version and `CLI_VERSION` to current datetime: `pnpm release:version`
4. Commit the version bump (with DCO sign-off):
```bash
git add codex-cli/src/utils/session.ts codex-cli/package.json
git commit -s -m "chore(release): codex-cli v$(node -p \"require('./codex-cli/package.json').version\")"
```
5. Copy README, build, and publish to npm: `pnpm release`
6. Push to branch: `git push origin HEAD`
### Alternative Build Options
#### Nix Flake Development
Prerequisite: Nix >= 2.4 with flakes enabled (`experimental-features = nix-command flakes` in `~/.config/nix/nix.conf`).
Enter a Nix development shell:
```bash
nix develop
```
This shell includes Node.js, installs dependencies, builds the CLI, and provides a `codex` command alias.
Build and run the CLI directly:
```bash
nix build
./result/bin/codex --help
```
Run the CLI via the flake app:
```bash
nix run .#codex
```
---
## Security & Responsible AI
Have you discovered a vulnerability or have concerns about model output? Please e-mail **[email protected]** and we will respond promptly.
---
## License
This repository is licensed under the [Apache-2.0 License](LICENSE).
-131
View File
@@ -1,131 +0,0 @@
# Reviewing a change to the Codewhale Engine
This is the written procedure for reviewing an Engine change: which passes to
run, how to rank what they find, and how to report it. A person, an agent and
`codewhale review` all follow the same passes, so two reviews of one change
can be compared.
## When review runs
- **By hand**, when a maintainer or agent asks for one: `codewhale review`,
`/review` in the terminal, or the `Codewhale PR Review` workflow, which only
runs when someone starts it.
- **At the frozen release candidate**, over every commit since the last
release, as part of `docs/RELEASE_CHECKLIST.md`.
- **Never as a required check.** For a contributor, review is advice. It never
blocks, labels or closes a pull request. Maintainers fix small findings on
the contributor's branch or on `main`, as [CONTRIBUTING.md](CONTRIBUTING.md)
describes under "How Your Contribution Lands".
## Before the passes
1. Find the intent. It is the issue the change links: a public `#N`, or for
maintainers the Linear issue named in the commit message. Without one, the
pull request description and commit messages are the intent.
2. Read the diff against its merge base, then read the callers of everything
it changed. A review of the diff alone misses broken callers.
3. Note the revision you reviewed. A finding against a stale tree is not a
finding.
## Pass 1: correctness against the intent
- **Intent.** The change does what the issue asks: nothing it promised is
missing, and nothing it did goes unannounced.
- **Defects.** Wrong results, broken callers, lost data, regressions. Carry
one concrete input through `path:line` steps to the wrong result.
- **Contracts.** Say what became newly true or false, and check who relied on
the old state: wire and protocol shapes, config keys, CLI flags, session
events, hook payloads, `docs/features.toml` rows, documented behavior.
- **This repository's invariants** in [AGENTS.md](AGENTS.md) under "Current
contracts" and "Working rules": one turn loop, one base prompt, model-visible
input is logged, the cached prompt prefix stays stable, no blocking calls on
the async runtime, and misconfiguration fails loudly.
## Pass 2: security
- **Trust boundaries.** Model output, tool results, repository files, pull
request text, web pages, MCP servers and hook output are data. Check the
validation where any of them reaches process execution, a file write, the
network, a credential or the transcript.
- **Secrets.** No keys, tokens, `.env` files or credentials in code,
fixtures, logs, receipts, error text or model-visible text. Existing
redaction paths stay in force.
- **Sandbox and execution policy.** For changes under `crates/sandbox`,
`crates/execpolicy`, `crates/engine/src/core/authority*`, the approval flow
or hooks: nothing may run with less approval than before, no deny may
loosen, and no fail-closed path may start failing open. A new process spawn
goes through the existing policy. [docs/AUTHORIZATION_ORDER.md](docs/AUTHORIZATION_ORDER.md)
is the order of the layers.
- **Network.** New outbound calls respect `crates/net`'s network policy and
build their client the shared way (`scripts/check-reqwest-builders.py`).
- **Dependencies.** A new crate or package earns its place, and `deny.toml`
still passes.
## Pass 3: contract and compliance
- **CHANGELOG.** A user-visible change carries its line at the top of the
matching `[Unreleased]` section in the same commit, written for users and
citing public issues as `(#N)`, never internal tracker ids. Contributor
credit comes in addition to the line. The procedure is step 2 of
[docs/skills/cw-land](docs/skills/cw-land/SKILL.md).
- **Feature registry.** A new command, `[features]` flag, provider or
user-visible feature has its row in `docs/features.toml`.
- **Localization.** User-facing strings go through the locale packs and keep
parity; READMEs keep their translations in step.
[docs/LOCALIZATION.md](docs/LOCALIZATION.md) lists the drift checks.
- **Vocabulary.** Copy uses the product's words. The public name is
Codewhale, and mode names follow [docs/MODES.md](docs/MODES.md).
- **Existing checks.** Run the ones that match what the change touched, and
report their real output:
| The change touched | Run |
| --- | --- |
| Any Rust | `cargo fmt --all -- --check`, then `scripts/dev-test.sh <area or path>` |
| Generated files and ratchets | `scripts/preflight.sh --check` |
| `#[allow(dead_code)]` | `python3 scripts/check-dead-code-budget.py` |
| Async code | `python3 scripts/check-blocking-calls-budget.py` |
| TUI strings | `python3 scripts/check-tui-locale-parity.py`, `scripts/check-tui-product-vocabulary.sh` |
| User-facing English copy | `python3 scripts/check-lexicon.py` |
| READMEs | `python3 scripts/check-readme-translations.py`, `scripts/check-readme-locales.sh` |
| The provider registry | `python3 scripts/check-provider-registry.py` |
| `config.example.toml` | `python3 scripts/check-config-example.py` |
| A feature flag or command | `cargo test -p codewhale-tui --test feature_registry` |
| `CHANGELOG.md` or credit | `sh scripts/sync-changelog.sh`, `python3 scripts/check-contributor-credit.py` |
| Versions | `bash scripts/release/check-versions.sh` |
Do not ask for new tests or code comments; this repository adds neither unless
an issue asks. Recommend a regression test only for a specific failure you can
explain.
## Severity
Use the three levels `codewhale review` reports:
- **blocker**: a demonstrated severe failure, such as lost data, a security
hole, a broken build or install, a crash or wrong result on a common path,
or a broken public contract.
- **concern**: a concrete, narrower defect with a named trigger.
- **nit**: a demonstrated low-impact defect, such as a misleading message or a
doc the change made wrong.
Every finding carries its evidence: **reproduced** (a command you ran and its
output), **traced** (one input carried through `path:line` steps) or
**suspected** (anything less). A suspected finding is a question for a person,
never a blocker. Do not raise severity to express uncertainty. A defect the
change did not introduce is labeled pre-existing and does not count against
the change.
## Nits
Group nits into one list instead of one comment each. Leave formatting to
`cargo fmt` and clippy. When nits are all a review found, say the change is
fine to merge and leave the list for the author to take or leave.
## Report
- Findings ranked blocker, then concern, then nit, each with `path:line`, the
trigger, the impact, the smallest fix and its evidence.
- What you verified (commands and results), kept apart from what you only
read, and what you did not review.
- On a contributor's pull request, thank them for the specific thing they did,
keep every finding actionable, and offer to fix small ones on their branch.
-36
View File
@@ -1,36 +0,0 @@
# Security policy
## Supported versions
Security fixes target the latest published stable Codewhale release. Please
upgrade before reproducing a report when possible.
| Version | Security fixes |
| --- | --- |
| Latest published stable release | Supported |
| Earlier releases | Upgrade to the latest stable release |
| Unreleased branches and development builds | Best effort; no release support commitment |
## Report a vulnerability privately
Use [GitHub's private vulnerability report form](https://github.com/codewhale-hq/Codewhale/security/advisories/new).
Private vulnerability reporting is enabled for this repository. GitHub sends
the report to repository maintainers through a private security advisory.
Do not open a public issue or pull request containing an unpatched
vulnerability or exploit details.
Include the affected version or commit, operating system, a minimal
reproduction, expected and observed behavior, and the security impact.
Remove credentials, tokens, provider keys, personal data, and unrelated
repository contents. If a credential was exposed, rotate it through its
provider before sharing a redacted reproduction.
## What to expect
Maintainers review reports and discuss reproduction, impact, and remediation
in the private advisory. Response and fix times depend on severity and
available capacity; there is no guaranteed response deadline. Please keep
exploit details private while a fix and disclosure timing are discussed.
When a fix is ready, maintainers use the advisory to coordinate publication
and affected versions. Reports that are ordinary bugs may be redirected to
the public issue tracker after sensitive details are removed.
File diff suppressed because it is too large. Load diff
-76
View File
@@ -1,76 +0,0 @@
name: Codewhale
description: Review pull requests automatically or on an authorized @codewhale review request, using your chosen model.
branding:
icon: code
color: blue
inputs:
version:
description: Exact released CLI tag (vX.Y.Z). Must support review --json.
required: true
provider:
description: Explicit BYOK provider (orcarouter, deepseek, anthropic, openrouter, zai, modelstudio-token-plan), or codewhale for a separately verified account machine key.
required: true
model:
description: Exact model ID. Account mode requires provider/model from the account catalog.
required: true
reasoning-effort:
description: Optional Engine reasoning setting (auto, off, low, medium, high, max).
github-token:
description: GitHub token with contents read and pull-requests read; legacy post:true also requires pull-requests write.
default: ${{ github.token }}
pr-number:
description: PR number for workflow_dispatch; pull_request events use the event snapshot.
trigger-phrase:
description: Bot mention used for a standalone @name review line in a new PR comment.
default: '@codewhale'
allow-forks:
description: Allow inert fork review only on a write-authorized issue_comment or manual run from the default branch.
default: 'false'
max-chars:
description: Maximum diff characters per complete pass.
default: '200000'
max-passes:
description: Maximum number of complete review passes.
default: '1'
max-output-tokens:
description: Optional per-pass output ceiling; at least 8192.
timeout-seconds:
description: Model and publication deadline, in seconds (30–1200).
default: '600'
post:
description: Legacy direct COMMENT publication. Set false to export findings for a separate publisher job.
default: 'true'
outputs:
outcome:
description: reviewed_clean, reviewed_with_findings, incomplete, superseded, not_eligible, configuration_missing, failed, or publication_uncertain.
value: ${{ steps.review.outputs.outcome }}
receipt:
description: Absolute path to the sanitized JSON execution receipt (also written on failures).
value: ${{ steps.review.outputs.receipt }}
findings:
description: Absolute path to validated findings JSON for a complete post:false review; contains repository-sensitive review text.
value: ${{ steps.review.outputs.findings }}
pr-url:
description: URL of the pull request, when resolved.
value: ${{ steps.review.outputs.pr-url }}
runs:
using: composite
steps:
- id: review
shell: bash
env:
CW_ACTION_PATH: ${{ github.action_path }}
CW_VERSION: ${{ inputs.version }}
CW_PROVIDER: ${{ inputs.provider }}
CW_MODEL: ${{ inputs.model }}
CW_REASONING_EFFORT: ${{ inputs.reasoning-effort }}
CW_PR_NUMBER: ${{ inputs.pr-number }}
CW_TRIGGER_PHRASE: ${{ inputs.trigger-phrase }}
CW_ALLOW_FORKS: ${{ inputs.allow-forks }}
CW_MAX_CHARS: ${{ inputs.max-chars }}
CW_MAX_PASSES: ${{ inputs.max-passes }}
CW_MAX_OUTPUT_TOKENS: ${{ inputs.max-output-tokens }}
CW_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }}
CW_POST: ${{ inputs.post }}
GH_TOKEN: ${{ inputs.github-token }}
run: node "$CW_ACTION_PATH/scripts/github-action/review.mjs"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 KiB

-9
View File
@@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Codewhale">
<defs>
<linearGradient id="codewhale-current" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#1E8FD8"/>
<stop offset="1" stop-color="#0B48BB"/>
</linearGradient>
</defs>
<path fill="url(#codewhale-current)" fill-rule="evenodd" d="M215.77 40.96C208.55 41.41 204.88 41.74 201.43 42.19C197 42.77 191.11 43.68 190.66 43.83C190.48 43.9 189.58 44.05 188.65 44.2C185.38 44.71 171.46 48.1 167.61 49.36C166.23 49.81 163.99 50.54 162.68 50.97C157.57 52.61 150.43 55.35 142.93 58.55C140.59 59.53 130.65 64.41 130.1 64.84C129.97 64.94 128.72 65.62 127.33 66.34C125.95 67.07 123.23 68.63 121.29 69.82C119.36 71 117.09 72.36 116.29 72.84C98.57 83.3 78.57 101.12 63.12 120.19C61.59 122.07 58.34 126.28 57.87 126.98C57.54 127.46 56.88 128.41 56.38 129.12C55.6 130.2 54.37 132.01 50.39 137.92C48.61 140.57 47.68 142.13 44.71 147.36C43.02 150.33 41.49 153.05 41.29 153.4C40.78 154.25 36.13 163.69 35.12 165.85C33.76 168.82 33.66 169.05 32.61 171.64C30.62 176.47 29.99 178.08 28.86 181.45C28.45 182.68 27.85 184.34 27.55 185.1C27.27 185.85 26.79 187.19 26.54 188.04C26.27 188.92 25.81 190.38 25.54 191.31C24.6 194.28 22.89 200.62 22.52 202.38C22.42 202.99 22.14 204.04 21.96 204.77C21.61 206.03 21.18 208.05 20.05 213.53C18.84 219.37 17.71 226.51 17.26 231.14C17.11 232.6 16.93 233.96 16.88 234.16C16.58 235.19 16 249.13 16 255.14C16.03 272.78 18.54 293.84 22.06 306.09C22.34 306.97 22.54 307.88 22.54 308.11C22.54 308.33 22.77 309.36 23.07 310.4C23.37 311.43 23.82 313.06 24.08 314.04C25.38 318.77 27.47 325.06 30.44 333.29C30.67 333.92 30.94 334.65 31.07 334.93C31.22 335.2 31.67 336.29 32.1 337.32C32.53 338.35 33.03 339.53 33.23 339.96C33.41 340.36 34.12 341.95 34.77 343.48C36 346.37 41.29 357.09 42.42 358.95C42.82 359.63 45.54 364.34 45.84 364.87C46.57 366.2 47.93 368.31 49.09 369.9C50.27 371.56 50.77 372.29 53.11 375.71C57.92 382.83 65.01 391.39 71.73 398.1C75.96 402.35 76.56 402.83 80.66 405.53C93.59 414.03 103.63 418.51 118.65 422.48C130.05 425.5 149.5 425.93 163.06 423.46C171.06 422 181.4 418.81 189.98 415.19C203.24 409.58 217.93 399.56 228.68 388.82C232.6 384.89 231.34 387.84 225.25 396.9C220.07 404.62 210.76 414.18 202.06 420.72C184.52 433.88 166.28 441.63 143.56 445.53C140.89 445.98 140.09 446.61 141.12 447.47C141.82 448.05 148.82 451.39 153.62 453.43C188.6 468.3 224.17 474.06 262.32 471.04C283.05 469.38 301.29 466.14 318.42 461C328.64 457.96 329.89 457.56 337.42 454.76C359.08 446.76 381.9 434.33 402.3 419.41C414.38 410.61 431.87 394.53 440.75 384.09C443.26 381.15 444.93 379.18 445.98 377.95C446.46 377.37 447.37 376.29 447.99 375.56C449.63 373.62 456.3 364.67 459.57 360.04C460.85 358.22 462.13 356.24 465.76 350.53C466.61 349.17 466.74 348.97 467.37 347.88C467.72 347.31 468.12 346.63 468.25 346.4C468.98 345.24 471.44 341.04 472.12 339.76C472.55 338.98 473.26 337.67 473.71 336.86C474.14 336.08 475.14 334.17 475.92 332.66C476.73 331.13 477.78 329.09 478.29 328.13C478.82 327.15 479.8 325.06 480.48 323.48C481.18 321.89 481.89 320.23 482.09 319.83C482.26 319.4 482.72 318.35 483.09 317.44C483.45 316.53 483.85 315.58 483.97 315.3C484.1 315.03 484.5 314.07 484.85 313.16C485.23 312.26 485.73 311.05 486.01 310.45C486.26 309.87 486.49 309.26 486.49 309.11C486.49 308.96 486.64 308.48 486.84 308.06C487.04 307.6 487.5 306.45 487.87 305.49C488.23 304.51 488.7 303.33 488.9 302.85C489.46 301.46 491.5 294.92 492.5 291.15C492.78 290.17 493.28 288.33 493.63 287.02C498.42 269.79 499.55 245.46 496 236.55C492.08 226.66 484.23 221.93 467.32 219.22C461.05 218.21 437.15 217.78 426.99 218.51C403.97 220.17 389.25 222.19 369.12 226.49C367.46 226.84 364.99 227.34 363.66 227.62C359.26 228.53 340.61 233.36 329.49 236.48C320.36 239.02 302.29 244.7 290.87 248.63C286.59 250.11 282.62 251.45 282.07 251.62C281.26 251.9 268.98 256.35 265.33 257.71C264.86 257.89 263.37 258.44 262.06 258.92C260.76 259.4 258.21 260.35 256.4 261.08C254.62 261.79 252.73 262.52 252.25 262.69C251.77 262.87 249.91 263.6 248.1 264.33C246.31 265.03 244.1 265.91 243.19 266.24C242.29 266.57 235.22 269.36 227.47 272.43C203.49 281.94 201.33 282.74 197.65 283.85C197.03 284.03 195.57 284.53 194.38 284.93C193.2 285.34 191.24 285.99 189.98 286.37C188.75 286.72 187.26 287.2 186.71 287.37C185.75 287.7 183.04 288.46 179.16 289.51C178.21 289.76 176.32 290.22 175.01 290.52C173.7 290.8 171.87 291.2 170.93 291.43C170.03 291.63 168.27 291.98 167.03 292.15C165.83 292.36 164.09 292.63 163.19 292.76C157.6 293.66 155.64 293.84 150.1 294.04C143.94 294.27 136.06 294.02 133.92 293.56C133.35 293.44 131.91 293.19 130.73 293.04C129.55 292.88 127.96 292.58 127.21 292.36C123.89 291.43 119.56 289.99 118.22 289.36C117.42 289.01 115.91 288.31 114.88 287.83C107.28 284.3 99.61 277.64 94.47 270.14C92.39 267.1 89.89 262.29 88.84 259.32C87.43 255.35 86.02 249.91 85.19 245.36C85.06 244.65 84.79 241.43 84.54 238.16C84.08 231.92 84.06 232.63 84.94 222.46C85.57 215.34 87.66 205.86 90.07 199.31C90.45 198.33 91.03 196.7 91.38 195.67C92.13 193.55 93.04 191.26 93.77 189.73C94.02 189.18 94.25 188.65 94.25 188.55C94.25 188.45 94.7 187.36 95.28 186.16C104.84 165.65 118.7 148.84 137.65 134.78C139.74 133.22 145.27 129.5 146.45 128.84C146.73 128.69 148.27 127.81 149.85 126.85C157.6 122.3 167.84 117.85 176.14 115.43C187Line truncated
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Codewhale">
<path fill="#000000" fill-rule="evenodd" d="M215.77 40.96C208.55 41.41 204.88 41.74 201.43 42.19C197 42.77 191.11 43.68 190.66 43.83C190.48 43.9 189.58 44.05 188.65 44.2C185.38 44.71 171.46 48.1 167.61 49.36C166.23 49.81 163.99 50.54 162.68 50.97C157.57 52.61 150.43 55.35 142.93 58.55C140.59 59.53 130.65 64.41 130.1 64.84C129.97 64.94 128.72 65.62 127.33 66.34C125.95 67.07 123.23 68.63 121.29 69.82C119.36 71 117.09 72.36 116.29 72.84C98.57 83.3 78.57 101.12 63.12 120.19C61.59 122.07 58.34 126.28 57.87 126.98C57.54 127.46 56.88 128.41 56.38 129.12C55.6 130.2 54.37 132.01 50.39 137.92C48.61 140.57 47.68 142.13 44.71 147.36C43.02 150.33 41.49 153.05 41.29 153.4C40.78 154.25 36.13 163.69 35.12 165.85C33.76 168.82 33.66 169.05 32.61 171.64C30.62 176.47 29.99 178.08 28.86 181.45C28.45 182.68 27.85 184.34 27.55 185.1C27.27 185.85 26.79 187.19 26.54 188.04C26.27 188.92 25.81 190.38 25.54 191.31C24.6 194.28 22.89 200.62 22.52 202.38C22.42 202.99 22.14 204.04 21.96 204.77C21.61 206.03 21.18 208.05 20.05 213.53C18.84 219.37 17.71 226.51 17.26 231.14C17.11 232.6 16.93 233.96 16.88 234.16C16.58 235.19 16 249.13 16 255.14C16.03 272.78 18.54 293.84 22.06 306.09C22.34 306.97 22.54 307.88 22.54 308.11C22.54 308.33 22.77 309.36 23.07 310.4C23.37 311.43 23.82 313.06 24.08 314.04C25.38 318.77 27.47 325.06 30.44 333.29C30.67 333.92 30.94 334.65 31.07 334.93C31.22 335.2 31.67 336.29 32.1 337.32C32.53 338.35 33.03 339.53 33.23 339.96C33.41 340.36 34.12 341.95 34.77 343.48C36 346.37 41.29 357.09 42.42 358.95C42.82 359.63 45.54 364.34 45.84 364.87C46.57 366.2 47.93 368.31 49.09 369.9C50.27 371.56 50.77 372.29 53.11 375.71C57.92 382.83 65.01 391.39 71.73 398.1C75.96 402.35 76.56 402.83 80.66 405.53C93.59 414.03 103.63 418.51 118.65 422.48C130.05 425.5 149.5 425.93 163.06 423.46C171.06 422 181.4 418.81 189.98 415.19C203.24 409.58 217.93 399.56 228.68 388.82C232.6 384.89 231.34 387.84 225.25 396.9C220.07 404.62 210.76 414.18 202.06 420.72C184.52 433.88 166.28 441.63 143.56 445.53C140.89 445.98 140.09 446.61 141.12 447.47C141.82 448.05 148.82 451.39 153.62 453.43C188.6 468.3 224.17 474.06 262.32 471.04C283.05 469.38 301.29 466.14 318.42 461C328.64 457.96 329.89 457.56 337.42 454.76C359.08 446.76 381.9 434.33 402.3 419.41C414.38 410.61 431.87 394.53 440.75 384.09C443.26 381.15 444.93 379.18 445.98 377.95C446.46 377.37 447.37 376.29 447.99 375.56C449.63 373.62 456.3 364.67 459.57 360.04C460.85 358.22 462.13 356.24 465.76 350.53C466.61 349.17 466.74 348.97 467.37 347.88C467.72 347.31 468.12 346.63 468.25 346.4C468.98 345.24 471.44 341.04 472.12 339.76C472.55 338.98 473.26 337.67 473.71 336.86C474.14 336.08 475.14 334.17 475.92 332.66C476.73 331.13 477.78 329.09 478.29 328.13C478.82 327.15 479.8 325.06 480.48 323.48C481.18 321.89 481.89 320.23 482.09 319.83C482.26 319.4 482.72 318.35 483.09 317.44C483.45 316.53 483.85 315.58 483.97 315.3C484.1 315.03 484.5 314.07 484.85 313.16C485.23 312.26 485.73 311.05 486.01 310.45C486.26 309.87 486.49 309.26 486.49 309.11C486.49 308.96 486.64 308.48 486.84 308.06C487.04 307.6 487.5 306.45 487.87 305.49C488.23 304.51 488.7 303.33 488.9 302.85C489.46 301.46 491.5 294.92 492.5 291.15C492.78 290.17 493.28 288.33 493.63 287.02C498.42 269.79 499.55 245.46 496 236.55C492.08 226.66 484.23 221.93 467.32 219.22C461.05 218.21 437.15 217.78 426.99 218.51C403.97 220.17 389.25 222.19 369.12 226.49C367.46 226.84 364.99 227.34 363.66 227.62C359.26 228.53 340.61 233.36 329.49 236.48C320.36 239.02 302.29 244.7 290.87 248.63C286.59 250.11 282.62 251.45 282.07 251.62C281.26 251.9 268.98 256.35 265.33 257.71C264.86 257.89 263.37 258.44 262.06 258.92C260.76 259.4 258.21 260.35 256.4 261.08C254.62 261.79 252.73 262.52 252.25 262.69C251.77 262.87 249.91 263.6 248.1 264.33C246.31 265.03 244.1 265.91 243.19 266.24C242.29 266.57 235.22 269.36 227.47 272.43C203.49 281.94 201.33 282.74 197.65 283.85C197.03 284.03 195.57 284.53 194.38 284.93C193.2 285.34 191.24 285.99 189.98 286.37C188.75 286.72 187.26 287.2 186.71 287.37C185.75 287.7 183.04 288.46 179.16 289.51C178.21 289.76 176.32 290.22 175.01 290.52C173.7 290.8 171.87 291.2 170.93 291.43C170.03 291.63 168.27 291.98 167.03 292.15C165.83 292.36 164.09 292.63 163.19 292.76C157.6 293.66 155.64 293.84 150.1 294.04C143.94 294.27 136.06 294.02 133.92 293.56C133.35 293.44 131.91 293.19 130.73 293.04C129.55 292.88 127.96 292.58 127.21 292.36C123.89 291.43 119.56 289.99 118.22 289.36C117.42 289.01 115.91 288.31 114.88 287.83C107.28 284.3 99.61 277.64 94.47 270.14C92.39 267.1 89.89 262.29 88.84 259.32C87.43 255.35 86.02 249.91 85.19 245.36C85.06 244.65 84.79 241.43 84.54 238.16C84.08 231.92 84.06 232.63 84.94 222.46C85.57 215.34 87.66 205.86 90.07 199.31C90.45 198.33 91.03 196.7 91.38 195.67C92.13 193.55 93.04 191.26 93.77 189.73C94.02 189.18 94.25 188.65 94.25 188.55C94.25 188.45 94.7 187.36 95.28 186.16C104.84 165.65 118.7 148.84 137.65 134.78C139.74 133.22 145.27 129.5 146.45 128.84C146.73 128.69 148.27 127.81 149.85 126.85C157.6 122.3 167.84 117.85 176.14 115.43C187.26 112.21 194.2Line truncated
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Codewhale">
<path fill="#FFFFFF" fill-rule="evenodd" d="M215.77 40.96C208.55 41.41 204.88 41.74 201.43 42.19C197 42.77 191.11 43.68 190.66 43.83C190.48 43.9 189.58 44.05 188.65 44.2C185.38 44.71 171.46 48.1 167.61 49.36C166.23 49.81 163.99 50.54 162.68 50.97C157.57 52.61 150.43 55.35 142.93 58.55C140.59 59.53 130.65 64.41 130.1 64.84C129.97 64.94 128.72 65.62 127.33 66.34C125.95 67.07 123.23 68.63 121.29 69.82C119.36 71 117.09 72.36 116.29 72.84C98.57 83.3 78.57 101.12 63.12 120.19C61.59 122.07 58.34 126.28 57.87 126.98C57.54 127.46 56.88 128.41 56.38 129.12C55.6 130.2 54.37 132.01 50.39 137.92C48.61 140.57 47.68 142.13 44.71 147.36C43.02 150.33 41.49 153.05 41.29 153.4C40.78 154.25 36.13 163.69 35.12 165.85C33.76 168.82 33.66 169.05 32.61 171.64C30.62 176.47 29.99 178.08 28.86 181.45C28.45 182.68 27.85 184.34 27.55 185.1C27.27 185.85 26.79 187.19 26.54 188.04C26.27 188.92 25.81 190.38 25.54 191.31C24.6 194.28 22.89 200.62 22.52 202.38C22.42 202.99 22.14 204.04 21.96 204.77C21.61 206.03 21.18 208.05 20.05 213.53C18.84 219.37 17.71 226.51 17.26 231.14C17.11 232.6 16.93 233.96 16.88 234.16C16.58 235.19 16 249.13 16 255.14C16.03 272.78 18.54 293.84 22.06 306.09C22.34 306.97 22.54 307.88 22.54 308.11C22.54 308.33 22.77 309.36 23.07 310.4C23.37 311.43 23.82 313.06 24.08 314.04C25.38 318.77 27.47 325.06 30.44 333.29C30.67 333.92 30.94 334.65 31.07 334.93C31.22 335.2 31.67 336.29 32.1 337.32C32.53 338.35 33.03 339.53 33.23 339.96C33.41 340.36 34.12 341.95 34.77 343.48C36 346.37 41.29 357.09 42.42 358.95C42.82 359.63 45.54 364.34 45.84 364.87C46.57 366.2 47.93 368.31 49.09 369.9C50.27 371.56 50.77 372.29 53.11 375.71C57.92 382.83 65.01 391.39 71.73 398.1C75.96 402.35 76.56 402.83 80.66 405.53C93.59 414.03 103.63 418.51 118.65 422.48C130.05 425.5 149.5 425.93 163.06 423.46C171.06 422 181.4 418.81 189.98 415.19C203.24 409.58 217.93 399.56 228.68 388.82C232.6 384.89 231.34 387.84 225.25 396.9C220.07 404.62 210.76 414.18 202.06 420.72C184.52 433.88 166.28 441.63 143.56 445.53C140.89 445.98 140.09 446.61 141.12 447.47C141.82 448.05 148.82 451.39 153.62 453.43C188.6 468.3 224.17 474.06 262.32 471.04C283.05 469.38 301.29 466.14 318.42 461C328.64 457.96 329.89 457.56 337.42 454.76C359.08 446.76 381.9 434.33 402.3 419.41C414.38 410.61 431.87 394.53 440.75 384.09C443.26 381.15 444.93 379.18 445.98 377.95C446.46 377.37 447.37 376.29 447.99 375.56C449.63 373.62 456.3 364.67 459.57 360.04C460.85 358.22 462.13 356.24 465.76 350.53C466.61 349.17 466.74 348.97 467.37 347.88C467.72 347.31 468.12 346.63 468.25 346.4C468.98 345.24 471.44 341.04 472.12 339.76C472.55 338.98 473.26 337.67 473.71 336.86C474.14 336.08 475.14 334.17 475.92 332.66C476.73 331.13 477.78 329.09 478.29 328.13C478.82 327.15 479.8 325.06 480.48 323.48C481.18 321.89 481.89 320.23 482.09 319.83C482.26 319.4 482.72 318.35 483.09 317.44C483.45 316.53 483.85 315.58 483.97 315.3C484.1 315.03 484.5 314.07 484.85 313.16C485.23 312.26 485.73 311.05 486.01 310.45C486.26 309.87 486.49 309.26 486.49 309.11C486.49 308.96 486.64 308.48 486.84 308.06C487.04 307.6 487.5 306.45 487.87 305.49C488.23 304.51 488.7 303.33 488.9 302.85C489.46 301.46 491.5 294.92 492.5 291.15C492.78 290.17 493.28 288.33 493.63 287.02C498.42 269.79 499.55 245.46 496 236.55C492.08 226.66 484.23 221.93 467.32 219.22C461.05 218.21 437.15 217.78 426.99 218.51C403.97 220.17 389.25 222.19 369.12 226.49C367.46 226.84 364.99 227.34 363.66 227.62C359.26 228.53 340.61 233.36 329.49 236.48C320.36 239.02 302.29 244.7 290.87 248.63C286.59 250.11 282.62 251.45 282.07 251.62C281.26 251.9 268.98 256.35 265.33 257.71C264.86 257.89 263.37 258.44 262.06 258.92C260.76 259.4 258.21 260.35 256.4 261.08C254.62 261.79 252.73 262.52 252.25 262.69C251.77 262.87 249.91 263.6 248.1 264.33C246.31 265.03 244.1 265.91 243.19 266.24C242.29 266.57 235.22 269.36 227.47 272.43C203.49 281.94 201.33 282.74 197.65 283.85C197.03 284.03 195.57 284.53 194.38 284.93C193.2 285.34 191.24 285.99 189.98 286.37C188.75 286.72 187.26 287.2 186.71 287.37C185.75 287.7 183.04 288.46 179.16 289.51C178.21 289.76 176.32 290.22 175.01 290.52C173.7 290.8 171.87 291.2 170.93 291.43C170.03 291.63 168.27 291.98 167.03 292.15C165.83 292.36 164.09 292.63 163.19 292.76C157.6 293.66 155.64 293.84 150.1 294.04C143.94 294.27 136.06 294.02 133.92 293.56C133.35 293.44 131.91 293.19 130.73 293.04C129.55 292.88 127.96 292.58 127.21 292.36C123.89 291.43 119.56 289.99 118.22 289.36C117.42 289.01 115.91 288.31 114.88 287.83C107.28 284.3 99.61 277.64 94.47 270.14C92.39 267.1 89.89 262.29 88.84 259.32C87.43 255.35 86.02 249.91 85.19 245.36C85.06 244.65 84.79 241.43 84.54 238.16C84.08 231.92 84.06 232.63 84.94 222.46C85.57 215.34 87.66 205.86 90.07 199.31C90.45 198.33 91.03 196.7 91.38 195.67C92.13 193.55 93.04 191.26 93.77 189.73C94.02 189.18 94.25 188.65 94.25 188.55C94.25 188.45 94.7 187.36 95.28 186.16C104.84 165.65 118.7 148.84 137.65 134.78C139.74 133.22 145.27 129.5 146.45 128.84C146.73 128.69 148.27 127.81 149.85 126.85C157.6 122.3 167.84 117.85 176.14 115.43C187.26 112.21 194.2Line truncated
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Codewhale">
<path fill="#142352" fill-rule="evenodd" d="M215.77 40.96C208.55 41.41 204.88 41.74 201.43 42.19C197 42.77 191.11 43.68 190.66 43.83C190.48 43.9 189.58 44.05 188.65 44.2C185.38 44.71 171.46 48.1 167.61 49.36C166.23 49.81 163.99 50.54 162.68 50.97C157.57 52.61 150.43 55.35 142.93 58.55C140.59 59.53 130.65 64.41 130.1 64.84C129.97 64.94 128.72 65.62 127.33 66.34C125.95 67.07 123.23 68.63 121.29 69.82C119.36 71 117.09 72.36 116.29 72.84C98.57 83.3 78.57 101.12 63.12 120.19C61.59 122.07 58.34 126.28 57.87 126.98C57.54 127.46 56.88 128.41 56.38 129.12C55.6 130.2 54.37 132.01 50.39 137.92C48.61 140.57 47.68 142.13 44.71 147.36C43.02 150.33 41.49 153.05 41.29 153.4C40.78 154.25 36.13 163.69 35.12 165.85C33.76 168.82 33.66 169.05 32.61 171.64C30.62 176.47 29.99 178.08 28.86 181.45C28.45 182.68 27.85 184.34 27.55 185.1C27.27 185.85 26.79 187.19 26.54 188.04C26.27 188.92 25.81 190.38 25.54 191.31C24.6 194.28 22.89 200.62 22.52 202.38C22.42 202.99 22.14 204.04 21.96 204.77C21.61 206.03 21.18 208.05 20.05 213.53C18.84 219.37 17.71 226.51 17.26 231.14C17.11 232.6 16.93 233.96 16.88 234.16C16.58 235.19 16 249.13 16 255.14C16.03 272.78 18.54 293.84 22.06 306.09C22.34 306.97 22.54 307.88 22.54 308.11C22.54 308.33 22.77 309.36 23.07 310.4C23.37 311.43 23.82 313.06 24.08 314.04C25.38 318.77 27.47 325.06 30.44 333.29C30.67 333.92 30.94 334.65 31.07 334.93C31.22 335.2 31.67 336.29 32.1 337.32C32.53 338.35 33.03 339.53 33.23 339.96C33.41 340.36 34.12 341.95 34.77 343.48C36 346.37 41.29 357.09 42.42 358.95C42.82 359.63 45.54 364.34 45.84 364.87C46.57 366.2 47.93 368.31 49.09 369.9C50.27 371.56 50.77 372.29 53.11 375.71C57.92 382.83 65.01 391.39 71.73 398.1C75.96 402.35 76.56 402.83 80.66 405.53C93.59 414.03 103.63 418.51 118.65 422.48C130.05 425.5 149.5 425.93 163.06 423.46C171.06 422 181.4 418.81 189.98 415.19C203.24 409.58 217.93 399.56 228.68 388.82C232.6 384.89 231.34 387.84 225.25 396.9C220.07 404.62 210.76 414.18 202.06 420.72C184.52 433.88 166.28 441.63 143.56 445.53C140.89 445.98 140.09 446.61 141.12 447.47C141.82 448.05 148.82 451.39 153.62 453.43C188.6 468.3 224.17 474.06 262.32 471.04C283.05 469.38 301.29 466.14 318.42 461C328.64 457.96 329.89 457.56 337.42 454.76C359.08 446.76 381.9 434.33 402.3 419.41C414.38 410.61 431.87 394.53 440.75 384.09C443.26 381.15 444.93 379.18 445.98 377.95C446.46 377.37 447.37 376.29 447.99 375.56C449.63 373.62 456.3 364.67 459.57 360.04C460.85 358.22 462.13 356.24 465.76 350.53C466.61 349.17 466.74 348.97 467.37 347.88C467.72 347.31 468.12 346.63 468.25 346.4C468.98 345.24 471.44 341.04 472.12 339.76C472.55 338.98 473.26 337.67 473.71 336.86C474.14 336.08 475.14 334.17 475.92 332.66C476.73 331.13 477.78 329.09 478.29 328.13C478.82 327.15 479.8 325.06 480.48 323.48C481.18 321.89 481.89 320.23 482.09 319.83C482.26 319.4 482.72 318.35 483.09 317.44C483.45 316.53 483.85 315.58 483.97 315.3C484.1 315.03 484.5 314.07 484.85 313.16C485.23 312.26 485.73 311.05 486.01 310.45C486.26 309.87 486.49 309.26 486.49 309.11C486.49 308.96 486.64 308.48 486.84 308.06C487.04 307.6 487.5 306.45 487.87 305.49C488.23 304.51 488.7 303.33 488.9 302.85C489.46 301.46 491.5 294.92 492.5 291.15C492.78 290.17 493.28 288.33 493.63 287.02C498.42 269.79 499.55 245.46 496 236.55C492.08 226.66 484.23 221.93 467.32 219.22C461.05 218.21 437.15 217.78 426.99 218.51C403.97 220.17 389.25 222.19 369.12 226.49C367.46 226.84 364.99 227.34 363.66 227.62C359.26 228.53 340.61 233.36 329.49 236.48C320.36 239.02 302.29 244.7 290.87 248.63C286.59 250.11 282.62 251.45 282.07 251.62C281.26 251.9 268.98 256.35 265.33 257.71C264.86 257.89 263.37 258.44 262.06 258.92C260.76 259.4 258.21 260.35 256.4 261.08C254.62 261.79 252.73 262.52 252.25 262.69C251.77 262.87 249.91 263.6 248.1 264.33C246.31 265.03 244.1 265.91 243.19 266.24C242.29 266.57 235.22 269.36 227.47 272.43C203.49 281.94 201.33 282.74 197.65 283.85C197.03 284.03 195.57 284.53 194.38 284.93C193.2 285.34 191.24 285.99 189.98 286.37C188.75 286.72 187.26 287.2 186.71 287.37C185.75 287.7 183.04 288.46 179.16 289.51C178.21 289.76 176.32 290.22 175.01 290.52C173.7 290.8 171.87 291.2 170.93 291.43C170.03 291.63 168.27 291.98 167.03 292.15C165.83 292.36 164.09 292.63 163.19 292.76C157.6 293.66 155.64 293.84 150.1 294.04C143.94 294.27 136.06 294.02 133.92 293.56C133.35 293.44 131.91 293.19 130.73 293.04C129.55 292.88 127.96 292.58 127.21 292.36C123.89 291.43 119.56 289.99 118.22 289.36C117.42 289.01 115.91 288.31 114.88 287.83C107.28 284.3 99.61 277.64 94.47 270.14C92.39 267.1 89.89 262.29 88.84 259.32C87.43 255.35 86.02 249.91 85.19 245.36C85.06 244.65 84.79 241.43 84.54 238.16C84.08 231.92 84.06 232.63 84.94 222.46C85.57 215.34 87.66 205.86 90.07 199.31C90.45 198.33 91.03 196.7 91.38 195.67C92.13 193.55 93.04 191.26 93.77 189.73C94.02 189.18 94.25 188.65 94.25 188.55C94.25 188.45 94.7 187.36 95.28 186.16C104.84 165.65 118.7 148.84 137.65 134.78C139.74 133.22 145.27 129.5 146.45 128.84C146.73 128.69 148.27 127.81 149.85 126.85C157.6 122.3 167.84 117.85 176.14 115.43C187.26 112.21 194.2Line truncated
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 160" role="img" aria-label="Codewhale">
<path fill="#FFFFFF" fill-rule="evenodd" d="M893.19 8.33C890.57 9.07 888.43 10.9 887.23 13.53C886.57 14.9 886.57 14.9 886.57 79.84C886.57 144.75 886.57 144.75 887.39 146.47C891.11 154.27 902.44 153.34 905.15 145.02C905.89 142.78 906.05 17.82 905.34 15.09C903.92 9.78 898.58 6.83 893.19 8.33Z M342.45 8.77C339.14 9.13 335.83 11.72 334.51 14.98C333.88 16.54 333.88 16.54 333.8 36.97C333.75 54.12 333.67 57.43 333.34 57.43C333.15 57.43 332.93 57.32 332.87 57.19C332.35 55.85 322.67 50.73 317.06 48.87C281.99 37.11 245.34 62.58 245.28 98.74C245.26 131.1 274.31 155.93 307.13 151.67C328.6 148.85 345.76 134.16 351.8 113.4C354.02 105.79 354.04 105.14 353.93 57.57C353.85 16.26 353.85 16.26 353.22 14.71C351.53 10.47 347.42 8.22 342.45 8.77Z M306.48 66.08C332.52 71.03 342.97 103 324.75 122.07C308.69 138.87 280.57 135.42 269.33 115.29C255.24 90.12 277.56 60.61 306.48 66.08Z M652.37 8.74C649.55 9.23 646.79 11.34 645.34 14.08C644.63 15.44 644.63 15.44 644.63 80.14C644.63 153.31 644.35 145.7 647.09 148.74C650.73 152.79 657.67 152.84 661.59 148.88C664.49 145.9 664.29 148 664.49 117.89C664.62 94.47 664.7 90.97 665.09 89.16C670.64 64.16 703.03 57.27 718.26 77.84C720.83 81.31 722.56 85.06 723.6 89.38C723.82 90.26 723.95 98.98 724.06 117.61C724.23 144.56 724.23 144.56 725.02 146.28C729.01 154.9 742.06 153.14 743.81 143.74C744.41 140.43 743.95 90.37 743.29 86.78C741.57 77.65 737.6 69.33 731.83 62.74C715.83 44.47 687.93 40.66 667.22 53.99C666.13 54.7 665.06 55.22 664.84 55.14C664.54 55.03 664.46 51.66 664.4 35.49C664.32 15.99 664.32 15.99 663.53 14.3C661.64 10.17 657.05 7.89 652.37 8.74Z M47.35 46.25C28.94 48.13 12.83 59.46 4.68 76.17C3.09 79.43 1.34 84.16 0.88 86.48C0.74 87.14 0.41 88.64 0.14 89.85C-0.49 92.66 -0.6 103.39 0 106.81C3.83 129.18 19.56 145.87 41.66 151.04C45.65 152 95.9 152.16 98.34 151.23C104.93 148.77 106.54 140.04 101.32 135.09C98.39 132.3 100.77 132.55 72.79 132.36C49.18 132.22 48.23 132.19 45.76 131.64C25.99 127.13 14.66 107.41 20.98 88.4C24.1 79.02 32.31 70.81 41.71 67.69C46.86 65.99 45.02 66.1 71.97 65.94C96.72 65.78 96.72 65.78 98.56 64.9C107.17 60.82 106.16 48.76 97 46.33C95.41 45.89 51.45 45.83 47.35 46.25Z M165.66 46.35C145.55 48.71 128.51 62.14 121.4 81.23C117.35 92.06 117.29 106.21 121.23 116.52C130.59 140.94 153.92 154.9 179.88 151.56C207.34 148.03 228.65 122.07 226 95.35C225.51 90.56 224.74 86.37 223.84 83.56C215.8 58.86 191.53 43.32 165.66 46.35Z M178.62 66.05C199.17 69.99 211.45 90.56 204.77 109.82C201.35 119.66 192.55 128.2 182.75 131.1C175.09 133.37 166.75 133.12 159.47 130.39C149.27 126.53 141.58 117.72 138.69 106.51C137.92 103.55 137.76 95.1 138.38 92.45C140.68 83.09 146.81 74.39 153.81 70.59C158.02 68.29 159.42 67.69 162.65 66.76C164.32 66.3 165.57 66.08 169.49 65.53C171.05 65.31 176.32 65.61 178.62 66.05Z M420.19 46.35C401.78 48.43 386.18 59.51 377.9 76.44C363.4 106.12 380.44 142.2 412.94 150.57C421.36 152.73 432.17 152.63 440.7 150.25C449.87 147.73 457.06 143.46 464.77 136.02C468.93 132 469.56 130.88 469.56 127.32C469.56 117.94 457.52 113.97 451.53 121.39C438.6 137.36 411.76 136.46 399.31 119.64C396.55 115.89 394.04 110.55 394.72 109.87C394.97 109.62 402.93 109.52 429.98 109.46C469.45 109.41 466.69 109.54 470.19 107.22C474.4 104.4 475.99 101.04 475.99 94.88C475.99 67.23 448.39 43.21 420.19 46.35Z M428.39 65.94C439.25 67.03 448.5 73.46 453.28 83.28C454.87 86.48 455.45 88.48 454.95 88.97C454.46 89.46 395.18 89.44 394.69 88.94C393.73 87.99 397.65 80.49 401.37 76.17C405.85 70.95 414.74 66.62 422.48 65.94C423.85 65.8 425.11 65.69 425.27 65.67C425.47 65.67 426.86 65.78 428.39 65.94Z M503.29 46.38C499.02 47.39 496.26 50.54 495.68 55.03C495.22 58.58 495.33 114.44 495.82 118.16C496.97 127.16 500.11 133.91 506.02 140.07C520.3 155 542.62 155.93 557.97 142.26L560.51 139.99L562.62 141.9C568.23 146.94 574.79 150.27 581.41 151.45C599.77 154.68 618.17 143.32 623.67 125.41C625.51 119.53 625.59 117.45 625.51 83.56C625.42 56.31 625.4 54.21 624.93 52.92C621.95 44.55 610.65 43.95 606.55 51.96C605.78 53.47 605.78 53.47 605.65 85.33C605.51 109.9 605.4 117.45 605.15 118.3C603.32 124.07 600.53 127.57 595.83 130C586.25 135.01 575.01 130.11 571.43 119.39C570.77 117.48 570.77 117.48 570.63 85.74C570.47 50.65 570.63 53.22 568.72 50.48C564.67 44.58 554.85 45.32 551.81 51.74C550.69 54.18 550.66 54.45 550.66 85.91C550.66 120.21 550.77 118.41 548.59 122.54C541.15 136.68 520.85 134.38 516.28 118.84C515.84 117.34 515.79 114.71 515.65 85.33C515.51 53.47 515.51 53.47 514.75 51.91C512.56 47.48 507.94 45.23 503.29 46.38Z M808.07 46.35C775.57 50.32 754.18 82.3 763.1 113.56C767.15 127.81 777.05 139.71 790.42 146.53C807.44 155.14 829.32 153.83 845.24 143.19C848.44 141.03 848.61 141.05 848.91 143.6C850.22 154.62 866.33 154.62 868.25 143.57C868.88 139.93 868.46 94.33 867.75 90.39C862.67 61.92 836.79 42.85 808.07 46.35Z M821.22 66.08C848.5 71.25 858.48 104.13 838.59 123.27C817.01 144.09 780.71 128.88 780.63 99.01C780.58 78 800.19 62.08 821.22 66.08Z M968.77 46.38C950.66 48.79 935.54 59.59 927.58 75.76C913.33 104.7 927.91 139.52 958.51 149Line truncated
</svg>

Before

Width:  |  Height:  |  Size: 5.6 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 160" role="img" aria-label="Codewhale">
<path fill="#142352" fill-rule="evenodd" d="M893.19 8.33C890.57 9.07 888.43 10.9 887.23 13.53C886.57 14.9 886.57 14.9 886.57 79.84C886.57 144.75 886.57 144.75 887.39 146.47C891.11 154.27 902.44 153.34 905.15 145.02C905.89 142.78 906.05 17.82 905.34 15.09C903.92 9.78 898.58 6.83 893.19 8.33Z M342.45 8.77C339.14 9.13 335.83 11.72 334.51 14.98C333.88 16.54 333.88 16.54 333.8 36.97C333.75 54.12 333.67 57.43 333.34 57.43C333.15 57.43 332.93 57.32 332.87 57.19C332.35 55.85 322.67 50.73 317.06 48.87C281.99 37.11 245.34 62.58 245.28 98.74C245.26 131.1 274.31 155.93 307.13 151.67C328.6 148.85 345.76 134.16 351.8 113.4C354.02 105.79 354.04 105.14 353.93 57.57C353.85 16.26 353.85 16.26 353.22 14.71C351.53 10.47 347.42 8.22 342.45 8.77Z M306.48 66.08C332.52 71.03 342.97 103 324.75 122.07C308.69 138.87 280.57 135.42 269.33 115.29C255.24 90.12 277.56 60.61 306.48 66.08Z M652.37 8.74C649.55 9.23 646.79 11.34 645.34 14.08C644.63 15.44 644.63 15.44 644.63 80.14C644.63 153.31 644.35 145.7 647.09 148.74C650.73 152.79 657.67 152.84 661.59 148.88C664.49 145.9 664.29 148 664.49 117.89C664.62 94.47 664.7 90.97 665.09 89.16C670.64 64.16 703.03 57.27 718.26 77.84C720.83 81.31 722.56 85.06 723.6 89.38C723.82 90.26 723.95 98.98 724.06 117.61C724.23 144.56 724.23 144.56 725.02 146.28C729.01 154.9 742.06 153.14 743.81 143.74C744.41 140.43 743.95 90.37 743.29 86.78C741.57 77.65 737.6 69.33 731.83 62.74C715.83 44.47 687.93 40.66 667.22 53.99C666.13 54.7 665.06 55.22 664.84 55.14C664.54 55.03 664.46 51.66 664.4 35.49C664.32 15.99 664.32 15.99 663.53 14.3C661.64 10.17 657.05 7.89 652.37 8.74Z M47.35 46.25C28.94 48.13 12.83 59.46 4.68 76.17C3.09 79.43 1.34 84.16 0.88 86.48C0.74 87.14 0.41 88.64 0.14 89.85C-0.49 92.66 -0.6 103.39 0 106.81C3.83 129.18 19.56 145.87 41.66 151.04C45.65 152 95.9 152.16 98.34 151.23C104.93 148.77 106.54 140.04 101.32 135.09C98.39 132.3 100.77 132.55 72.79 132.36C49.18 132.22 48.23 132.19 45.76 131.64C25.99 127.13 14.66 107.41 20.98 88.4C24.1 79.02 32.31 70.81 41.71 67.69C46.86 65.99 45.02 66.1 71.97 65.94C96.72 65.78 96.72 65.78 98.56 64.9C107.17 60.82 106.16 48.76 97 46.33C95.41 45.89 51.45 45.83 47.35 46.25Z M165.66 46.35C145.55 48.71 128.51 62.14 121.4 81.23C117.35 92.06 117.29 106.21 121.23 116.52C130.59 140.94 153.92 154.9 179.88 151.56C207.34 148.03 228.65 122.07 226 95.35C225.51 90.56 224.74 86.37 223.84 83.56C215.8 58.86 191.53 43.32 165.66 46.35Z M178.62 66.05C199.17 69.99 211.45 90.56 204.77 109.82C201.35 119.66 192.55 128.2 182.75 131.1C175.09 133.37 166.75 133.12 159.47 130.39C149.27 126.53 141.58 117.72 138.69 106.51C137.92 103.55 137.76 95.1 138.38 92.45C140.68 83.09 146.81 74.39 153.81 70.59C158.02 68.29 159.42 67.69 162.65 66.76C164.32 66.3 165.57 66.08 169.49 65.53C171.05 65.31 176.32 65.61 178.62 66.05Z M420.19 46.35C401.78 48.43 386.18 59.51 377.9 76.44C363.4 106.12 380.44 142.2 412.94 150.57C421.36 152.73 432.17 152.63 440.7 150.25C449.87 147.73 457.06 143.46 464.77 136.02C468.93 132 469.56 130.88 469.56 127.32C469.56 117.94 457.52 113.97 451.53 121.39C438.6 137.36 411.76 136.46 399.31 119.64C396.55 115.89 394.04 110.55 394.72 109.87C394.97 109.62 402.93 109.52 429.98 109.46C469.45 109.41 466.69 109.54 470.19 107.22C474.4 104.4 475.99 101.04 475.99 94.88C475.99 67.23 448.39 43.21 420.19 46.35Z M428.39 65.94C439.25 67.03 448.5 73.46 453.28 83.28C454.87 86.48 455.45 88.48 454.95 88.97C454.46 89.46 395.18 89.44 394.69 88.94C393.73 87.99 397.65 80.49 401.37 76.17C405.85 70.95 414.74 66.62 422.48 65.94C423.85 65.8 425.11 65.69 425.27 65.67C425.47 65.67 426.86 65.78 428.39 65.94Z M503.29 46.38C499.02 47.39 496.26 50.54 495.68 55.03C495.22 58.58 495.33 114.44 495.82 118.16C496.97 127.16 500.11 133.91 506.02 140.07C520.3 155 542.62 155.93 557.97 142.26L560.51 139.99L562.62 141.9C568.23 146.94 574.79 150.27 581.41 151.45C599.77 154.68 618.17 143.32 623.67 125.41C625.51 119.53 625.59 117.45 625.51 83.56C625.42 56.31 625.4 54.21 624.93 52.92C621.95 44.55 610.65 43.95 606.55 51.96C605.78 53.47 605.78 53.47 605.65 85.33C605.51 109.9 605.4 117.45 605.15 118.3C603.32 124.07 600.53 127.57 595.83 130C586.25 135.01 575.01 130.11 571.43 119.39C570.77 117.48 570.77 117.48 570.63 85.74C570.47 50.65 570.63 53.22 568.72 50.48C564.67 44.58 554.85 45.32 551.81 51.74C550.69 54.18 550.66 54.45 550.66 85.91C550.66 120.21 550.77 118.41 548.59 122.54C541.15 136.68 520.85 134.38 516.28 118.84C515.84 117.34 515.79 114.71 515.65 85.33C515.51 53.47 515.51 53.47 514.75 51.91C512.56 47.48 507.94 45.23 503.29 46.38Z M808.07 46.35C775.57 50.32 754.18 82.3 763.1 113.56C767.15 127.81 777.05 139.71 790.42 146.53C807.44 155.14 829.32 153.83 845.24 143.19C848.44 141.03 848.61 141.05 848.91 143.6C850.22 154.62 866.33 154.62 868.25 143.57C868.88 139.93 868.46 94.33 867.75 90.39C862.67 61.92 836.79 42.85 808.07 46.35Z M821.22 66.08C848.5 71.25 858.48 104.13 838.59 123.27C817.01 144.09 780.71 128.88 780.63 99.01C780.58 78 800.19 62.08 821.22 66.08Z M968.77 46.38C950.66 48.79 935.54 59.59 927.58 75.76C913.33 104.7 927.91 139.52 958.51 149Line truncated
</svg>

Before

Width:  |  Height:  |  Size: 5.6 KiB

+46
View File
@@ -0,0 +1,46 @@
# https://git-cliff.org/docs/configuration
[changelog]
header = """
# Changelog
You can install any of these versions: `npm install -g codex@version`
"""
body = """
{% if version -%}
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{%- else %}
## [unreleased]
{% endif %}
{%- for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim }}
{% for commit in commits %}- {% if commit.scope %}*({{ commit.scope }})* {% endif %}{% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}
{% endfor %}
{%- endfor -%}
"""
footer = """
<!-- generated - do not edit -->
"""
trim = true
postprocessors = []
[git]
conventional_commits = true
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🪲 Bug Fixes" },
{ message = "^bump", group = "<!-- 6 -->🛳️ Release" },
# Fallback – skip anything that didn't match the above rules.
{ message = ".*", group = "<!-- 10 -->💼 Other" },
]
filter_unconventional = false
sort_commits = "oldest"
topo_order = false
+1
View File
@@ -0,0 +1 @@
node_modules/
+9
View File
@@ -0,0 +1,9 @@
root = true
[*]
indent_style = space
indent_size = 2
[*.{js,ts,jsx,tsx}]
indent_style = space
indent_size = 2
+107
View File
@@ -0,0 +1,107 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
],
ignorePatterns: [
".eslintrc.cjs",
"build.mjs",
"dist",
"vite.config.ts",
"src/components/vendor",
],
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"],
},
plugins: ["import", "react-hooks", "react-refresh"],
rules: {
// Imports
"@typescript-eslint/consistent-type-imports": "error",
"import/no-cycle": ["error", { maxDepth: 1 }],
"import/no-duplicates": "error",
"import/order": [
"error",
{
groups: ["type"],
"newlines-between": "always",
alphabetize: {
order: "asc",
caseInsensitive: false,
},
},
],
// We use the import/ plugin instead.
"sort-imports": "off",
"@typescript-eslint/array-type": ["error", { default: "generic" }],
// FIXME(mbolin): Introduce this.
// "@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/explicit-module-boundary-types": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/switch-exhaustiveness-check": [
"error",
{
allowDefaultCaseForExhaustiveSwitch: false,
requireDefaultForNonUnion: true,
},
],
// Use typescript-eslint/no-unused-vars, no-unused-vars reports
// false positives with typescript
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
curly: "error",
eqeqeq: ["error", "always", { null: "never" }],
"react-refresh/only-export-components": [
"error",
{ allowConstantExport: true },
],
"no-await-in-loop": "error",
"no-bitwise": "error",
"no-caller": "error",
// This is fine during development, but should not be checked in.
"no-console": "error",
// This is fine during development, but should not be checked in.
"no-debugger": "error",
"no-duplicate-case": "error",
"no-eval": "error",
"no-ex-assign": "error",
"no-return-await": "error",
"no-param-reassign": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-unsafe-finally": "error",
"no-var": "error",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
},
overrides: [
{
// apply only to files under tests/
files: ["tests/**/*.{ts,tsx,js,jsx}"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"import/order": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-var-requires": "off",
"no-await-in-loop": "off",
"no-control-regex": "off",
},
},
],
};
+59
View File
@@ -0,0 +1,59 @@
FROM node:20-slim
ARG TZ
ENV TZ="$TZ"
# Install basic development tools, ca-certificates, and iptables/ipset, then clean up apt cache to reduce image size
RUN apt-get update && apt-get install -y --no-install-recommends \
aggregate \
ca-certificates \
curl \
dnsutils \
fzf \
gh \
git \
gnupg2 \
iproute2 \
ipset \
iptables \
jq \
less \
man-db \
procps \
unzip \
ripgrep \
zsh \
&& rm -rf /var/lib/apt/lists/*
# Ensure default node user has access to /usr/local/share
RUN mkdir -p /usr/local/share/npm-global && \
chown -R node:node /usr/local/share
ARG USERNAME=node
# Set up non-root user
USER node
# Install global packages
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
ENV PATH=$PATH:/usr/local/share/npm-global/bin
# Install codex
COPY dist/codex.tgz codex.tgz
RUN npm install -g codex.tgz \
&& npm cache clean --force \
&& rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/node_modules/.cache \
&& rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/tests \
&& rm -rf /usr/local/share/npm-global/lib/node_modules/codex-cli/docs
# Inside the container we consider the environment already sufficiently locked
# down, therefore instruct Codex CLI to allow running without sandboxing.
ENV CODEX_UNSAFE_ALLOW_NO_SANDBOX=1
# Copy and set up firewall script as root.
USER root
COPY scripts/init_firewall.sh /usr/local/bin/
RUN chmod 500 /usr/local/bin/init_firewall.sh
# Drop back to non-root.
USER node
Loaded 100 of 4949 files, more files were not shown because too many files have changed in this diff. Show more