rippletide-package 0.1.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/PUBLISHING.md +55 -26
- package/README.md +19 -5
- package/bin/rippletide.js +14 -7
- package/counter/README.md +1 -1
- package/counter/src/cli.js +2 -2
- package/package.json +5 -2
- package/plugins/tide/.codex-plugin/plugin.json +28 -0
- package/plugins/tide/skills/tide/SKILL.md +92 -0
- package/reviewer/README.md +3 -3
- package/reviewer/src/integrations/cli/rippletide.js +7 -7
- package/tide/README.md +29 -19
- package/tide/bin/tide.js +1 -1
- package/tide/src/cli.js +56 -8
- package/tide/src/compile.js +4 -2
- package/tide/src/credentials.js +8 -5
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rippletide-package",
|
|
3
|
+
"interface": {
|
|
4
|
+
"displayName": "Rippletide Package"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "tide",
|
|
9
|
+
"source": {
|
|
10
|
+
"source": "local",
|
|
11
|
+
"path": "./plugins/tide"
|
|
12
|
+
},
|
|
13
|
+
"policy": {
|
|
14
|
+
"installation": "AVAILABLE",
|
|
15
|
+
"authentication": "ON_INSTALL"
|
|
16
|
+
},
|
|
17
|
+
"category": "Developer Tools"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/PUBLISHING.md
CHANGED
|
@@ -7,9 +7,10 @@ subcommand of the same command:
|
|
|
7
7
|
npx rippletide-package <feature-name> [flags]
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
> Published on npm as **`rippletide-package
|
|
11
|
-
>
|
|
12
|
-
>
|
|
10
|
+
> Published on npm as **`rippletide-package`**, and the installed command is also
|
|
11
|
+
> **`rippletide-package`**. Via `npx`: `npx rippletide-package …`; after
|
|
12
|
+
> `npm i -g rippletide-package` you run `rippletide-package …`.
|
|
13
|
+
> Tide additionally exposes a direct `tide` bin alias for `rippletide-package tide`.
|
|
13
14
|
|
|
14
15
|
## One product, many features
|
|
15
16
|
|
|
@@ -17,10 +18,11 @@ npx rippletide-package <feature-name> [flags]
|
|
|
17
18
|
before product-market fit — but it should feel like **one product** to users:
|
|
18
19
|
|
|
19
20
|
- the publishing unit is the repo root;
|
|
20
|
-
- the npm package name is `rippletide-package
|
|
21
|
+
- the npm package name is `rippletide-package`, with `rippletide-package` as the primary command;
|
|
22
|
+
- Tide may also be run through the direct `tide` bin alias;
|
|
21
23
|
- each user-facing feature is a **subcommand** (`<feature-name>`), implemented in its own
|
|
22
24
|
self-contained top-level folder;
|
|
23
|
-
- **flags** choose behavior *inside* a feature, e.g. `rippletide <feature-name> --<flag>`;
|
|
25
|
+
- **flags** choose behavior *inside* a feature, e.g. `rippletide-package <feature-name> --<flag>`;
|
|
24
26
|
- a new feature becomes a subcommand or flag before we ever create another package name.
|
|
25
27
|
|
|
26
28
|
This keeps experiments cheap without fragmenting the user experience: users learn one
|
|
@@ -49,7 +51,7 @@ When it graduates from experiment to a user-facing surface:
|
|
|
49
51
|
|
|
50
52
|
Do not create a new public npm package for every experiment. Create another package only
|
|
51
53
|
when a feature needs a separate install surface, audience, or lifecycle that would make the
|
|
52
|
-
single `rippletide` CLI worse.
|
|
54
|
+
single `rippletide-package` CLI worse.
|
|
53
55
|
|
|
54
56
|
## Package shape
|
|
55
57
|
|
|
@@ -57,11 +59,19 @@ single `rippletide` CLI worse.
|
|
|
57
59
|
npm registry : https://registry.npmjs.org/
|
|
58
60
|
repo folder : ./
|
|
59
61
|
package name : rippletide-package
|
|
60
|
-
|
|
62
|
+
commands : rippletide-package, tide
|
|
61
63
|
```
|
|
62
64
|
|
|
63
65
|
The published tarball ships the root dispatcher plus each user-facing feature's runtime and
|
|
64
66
|
docs (see `files` in `package.json`). It excludes curated test agents and generated artifacts.
|
|
67
|
+
It also ships the Tide Codex plugin (`plugins/tide`) and repo-local marketplace
|
|
68
|
+
(`.agents/plugins/marketplace.json`) so the plugin can be installed from this package/repo.
|
|
69
|
+
The public Codex marketplace lives in `rippletideco/tide-codex-plugin`, and the npm CLI
|
|
70
|
+
bootstraps it with:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx -y rippletide-package@latest tide codex install
|
|
74
|
+
```
|
|
65
75
|
|
|
66
76
|
## Local verification
|
|
67
77
|
|
|
@@ -70,7 +80,12 @@ From the repo root:
|
|
|
70
80
|
```bash
|
|
71
81
|
npm run release:check # syntax-checks every feature + a packaging dry-run
|
|
72
82
|
npm pack # build the tarball
|
|
73
|
-
|
|
83
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
84
|
+
npm exec --package "./rippletide-package-${VERSION}.tgz" -- rippletide-package tide --help
|
|
85
|
+
npm exec --package "./rippletide-package-${VERSION}.tgz" -- tide --help
|
|
86
|
+
codex plugin marketplace add .
|
|
87
|
+
codex plugin add tide@rippletide-package
|
|
88
|
+
npm exec --package "./rippletide-package-${VERSION}.tgz" -- tide codex install
|
|
74
89
|
```
|
|
75
90
|
|
|
76
91
|
## Publish
|
|
@@ -91,28 +106,40 @@ npm run release:dry # dry-run
|
|
|
91
106
|
npm run release:publish # publish (prompts for a 2FA OTP if your account requires one)
|
|
92
107
|
```
|
|
93
108
|
|
|
94
|
-
### Publishing rights:
|
|
109
|
+
### Publishing rights: CI (OIDC) and teammates
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
Releases go through CI using npm **Trusted Publishing (OIDC)** — no token and no 2FA. This is
|
|
112
|
+
the recommended path and the reason it's used here: on this account 2FA is required to publish,
|
|
113
|
+
and **npm access tokens do not bypass 2FA** (only classic Automation tokens do, and the npm UI
|
|
114
|
+
now only offers granular tokens, which get `EOTP` in CI). OIDC sidesteps all of that: GitHub's
|
|
115
|
+
workflow identity is the credential, verified cryptographically per run.
|
|
99
116
|
|
|
100
117
|
**CI release (recommended).** [`.github/workflows/release.yml`](.github/workflows/release.yml)
|
|
101
118
|
publishes automatically when a `rippletide-package-v*` tag is pushed (or via Actions → *Run
|
|
102
|
-
workflow*).
|
|
119
|
+
workflow*). The job has `permissions: id-token: write`, upgrades npm to a version that supports
|
|
120
|
+
OIDC, and runs `npm publish` — no secret is involved. One-time setup (already done for this
|
|
121
|
+
package):
|
|
103
122
|
|
|
104
|
-
1. On npmjs.com → **
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
2.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
3. Release by pushing a version tag — **any teammate who can push** can do this; no npm login
|
|
112
|
-
or OTP needed (see the checklist below).
|
|
123
|
+
1. On npmjs.com → the **rippletide-package** package → **Settings** → **Trusted Publisher** →
|
|
124
|
+
*GitHub Actions*, with organization `rippletideco`, repository `rippletide-package`, and
|
|
125
|
+
workflow file `release.yml` (leave *Environment* blank). Allow the `npm publish` action.
|
|
126
|
+
2. That's it. Release by pushing a version tag — **any teammate who can push** can cut a
|
|
127
|
+
release; no npm login, token, or OTP (see the checklist below). (Signed build provenance
|
|
128
|
+
is not enabled: npm provenance requires a public source repo, and this repo is internal.)
|
|
113
129
|
|
|
114
|
-
**
|
|
115
|
-
|
|
130
|
+
**First publish caveat.** Trusted publishing (and provenance) can only be configured on a
|
|
131
|
+
package that already exists, so the *very first* publish of a brand-new package name must be
|
|
132
|
+
done manually (below), after which OIDC handles every subsequent release.
|
|
133
|
+
|
|
134
|
+
**Manual publish (first publish, or a one-off).** With a passkey/WebAuthn 2FA method, npm can
|
|
135
|
+
only complete the 2FA challenge in a browser, and it only offers that when it has a real
|
|
136
|
+
terminal. So run it in an interactive terminal (not a script/CI context):
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm publish # prints "Authenticate your account at: <url>" — approve with Touch ID
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
To let a teammate publish manually, a current owner grants them rights:
|
|
116
143
|
|
|
117
144
|
```bash
|
|
118
145
|
npm owner add <their-npm-username> rippletide-package # run by a current owner
|
|
@@ -131,10 +158,12 @@ VERSION=$(node -p "require('./package.json').version")
|
|
|
131
158
|
git add package.json
|
|
132
159
|
git commit -m "Release rippletide-package v${VERSION}"
|
|
133
160
|
git tag "rippletide-package-v${VERSION}"
|
|
134
|
-
git push origin main --tags
|
|
135
|
-
npm run release:publish
|
|
161
|
+
git push origin main --tags # pushing the tag triggers CI, which publishes via OIDC
|
|
136
162
|
```
|
|
137
163
|
|
|
164
|
+
Pushing the `rippletide-package-v*` tag is the whole release — the
|
|
165
|
+
[release workflow](.github/workflows/release.yml) publishes it. No local `npm publish` needed.
|
|
166
|
+
|
|
138
167
|
## Hosted direction (optional, per feature)
|
|
139
168
|
|
|
140
169
|
The CLI stays the user entry point. A feature may later move its heavy logic behind a hosted
|
package/README.md
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
Rippletide's product package. It ships as **one npm CLI** — `npx rippletide-package <feature-name>` —
|
|
4
4
|
while each feature lives in its own self-contained top-level folder.
|
|
5
5
|
|
|
6
|
-
> Published on npm as **`rippletide-package
|
|
7
|
-
>
|
|
8
|
-
> `npm i -g rippletide-package` then `rippletide <feature-name>`.
|
|
6
|
+
> Published on npm as **`rippletide-package`**, and the installed command is also
|
|
7
|
+
> **`rippletide-package`**. So: `npx rippletide-package <feature-name>`, or
|
|
8
|
+
> `npm i -g rippletide-package` then `rippletide-package <feature-name>`.
|
|
9
|
+
> Tide also installs a direct **`tide`** command for the policy workflow; it is the same
|
|
10
|
+
> implementation as `rippletide-package tide`.
|
|
9
11
|
|
|
10
12
|
**Zero sharing between folders.** Each feature folder is fully self-contained — its own
|
|
11
13
|
code, dependencies, config, and docs. Nothing imports from or reaches into another
|
|
@@ -14,7 +16,7 @@ to the feature that owns it.
|
|
|
14
16
|
|
|
15
17
|
## Features
|
|
16
18
|
|
|
17
|
-
Each feature is a **subcommand** of `rippletide`, implemented in its own top-level folder.
|
|
19
|
+
Each feature is a **subcommand** of `rippletide-package`, implemented in its own top-level folder.
|
|
18
20
|
**A feature is explained in its own folder** — see that folder's `README.md` (and any
|
|
19
21
|
`PRD.md`/`docs`) for what it does and how to use it.
|
|
20
22
|
|
|
@@ -22,12 +24,24 @@ Each feature is a **subcommand** of `rippletide`, implemented in its own top-lev
|
|
|
22
24
|
npx rippletide-package <feature-name> [flags]
|
|
23
25
|
```
|
|
24
26
|
|
|
27
|
+
## Codex Plugin
|
|
28
|
+
|
|
29
|
+
The package also ships a repo-local Codex plugin marketplace at `.agents/plugins/marketplace.json`.
|
|
30
|
+
It exposes the `tide` skill from `plugins/tide`, so Codex can guide Tide install, compile,
|
|
31
|
+
hook setup, and release-readiness checks from inside a thread.
|
|
32
|
+
|
|
33
|
+
For public Codex installation from any terminal, use:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx -y rippletide-package@latest tide codex install
|
|
37
|
+
```
|
|
38
|
+
|
|
25
39
|
## How it fits together
|
|
26
40
|
|
|
27
41
|
One product, one command, many features. The root [`bin/rippletide.js`](bin/rippletide.js)
|
|
28
42
|
is a small, **feature-agnostic dispatcher**: it reads the first argument and hands off to
|
|
29
43
|
the feature folder that owns that subcommand. Behavior *within* a feature is chosen with
|
|
30
|
-
flags (e.g. `rippletide <feature-name> --<flag>`). Runtime code is not shared across feature
|
|
44
|
+
flags (e.g. `rippletide-package <feature-name> --<flag>`). Runtime code is not shared across feature
|
|
31
45
|
folders until a shared contract is deliberately designed.
|
|
32
46
|
|
|
33
47
|
## Adding a feature
|
package/bin/rippletide.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Rippletide product CLI — the single public entry point (`npx rippletide <feature> …`).
|
|
2
|
+
// Rippletide product CLI — the single public entry point (`npx rippletide-package <feature> …`).
|
|
3
3
|
//
|
|
4
4
|
// The repo is one product with several self-contained feature folders. This dispatcher
|
|
5
5
|
// routes a subcommand to the feature that owns it; it is feature-agnostic — add one route
|
|
6
6
|
// per user-facing feature. See PUBLISHING.md.
|
|
7
7
|
//
|
|
8
|
-
// rippletide review
|
|
9
|
-
// rippletide tide [...] -> tide/ (compile, install, check, …)
|
|
10
|
-
// rippletide counter [...] -> counter/ (live Codex calls/tokens/cost window)
|
|
8
|
+
// rippletide-package review [...] -> reviewer/ (review, inspect, …)
|
|
9
|
+
// rippletide-package tide [...] -> tide/ (compile, install, check, …)
|
|
10
|
+
// rippletide-package counter [...] -> counter/ (live Codex calls/tokens/cost window)
|
|
11
11
|
|
|
12
12
|
const argv = process.argv.slice(2);
|
|
13
13
|
const sub = argv[0];
|
|
@@ -22,6 +22,13 @@ if (sub === "counter") {
|
|
|
22
22
|
process.exit(await main(argv.slice(1)));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// Every other subcommand (review, inspect, --help, …) is owned by the reviewer feature
|
|
26
|
-
//
|
|
27
|
-
|
|
25
|
+
// Every other subcommand (review, inspect, --help, …) is owned by the reviewer feature.
|
|
26
|
+
// Its main() reads process.argv itself (command = argv[2]); call it explicitly here — the
|
|
27
|
+
// module's own run-as-main guard doesn't fire when imported through this dispatcher.
|
|
28
|
+
const { main } = await import("../reviewer/src/integrations/cli/rippletide.js");
|
|
29
|
+
try {
|
|
30
|
+
await main();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
package/counter/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Ported from the [x-ray](https://github.com/tototozip/x-ray) counter — the coun
|
|
|
6
6
|
token/cost half only (no risk scanning, no enforcement).
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
rippletide counter
|
|
9
|
+
rippletide-package counter
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Then use Codex — the desktop app or the CLI. The window updates on every model call.
|
package/counter/src/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// The `counter` feature entry point — `rippletide counter`.
|
|
1
|
+
// The `counter` feature entry point — `rippletide-package counter`.
|
|
2
2
|
// Starts the Codex live counter: a floating sticky window that tallies LLM calls, tokens,
|
|
3
3
|
// and estimated cost in real time. macOS for the window; counting works anywhere.
|
|
4
4
|
import { run } from "./counter.js";
|
|
5
5
|
|
|
6
6
|
const HELP = `counter — a live Codex sticky window: LLM calls, tokens, and estimated cost.
|
|
7
7
|
|
|
8
|
-
Usage: rippletide counter
|
|
8
|
+
Usage: rippletide-package counter
|
|
9
9
|
|
|
10
10
|
Run it, then use Codex (the desktop app or the CLI). A floating window shows the running
|
|
11
11
|
call count, total tokens, and estimated USD cost. It points Codex's openai_base_url at a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rippletide-package",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rippletide's CLI for AI coding agents — one command, each feature a subcommand.",
|
|
6
6
|
"keywords": [
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"registry": "https://registry.npmjs.org/"
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
|
-
"rippletide": "bin/rippletide.js"
|
|
22
|
+
"rippletide-package": "bin/rippletide.js",
|
|
23
|
+
"tide": "tide/bin/tide.js"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"bin/",
|
|
@@ -32,6 +33,8 @@
|
|
|
32
33
|
"tide/src/",
|
|
33
34
|
"tide/examples/",
|
|
34
35
|
"tide/README.md",
|
|
36
|
+
"plugins/tide/",
|
|
37
|
+
".agents/plugins/marketplace.json",
|
|
35
38
|
"counter/src/",
|
|
36
39
|
"counter/frontend/",
|
|
37
40
|
"counter/README.md",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tide",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Run Tide policy guardrails from Codex.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Rippletide"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://www.rippletide.com/",
|
|
9
|
+
"repository": "https://github.com/rippletideco/rippletide-package",
|
|
10
|
+
"license": "UNLICENSED",
|
|
11
|
+
"keywords": ["tide", "policy", "codex", "guardrails", "agents"],
|
|
12
|
+
"skills": "./skills/",
|
|
13
|
+
"interface": {
|
|
14
|
+
"displayName": "Tide",
|
|
15
|
+
"shortDescription": "Compile and enforce policy guardrails for Codex.",
|
|
16
|
+
"longDescription": "Tide turns a plain-English POLICY.md into deterministic rules, installs the Codex PreToolUse hook, and verifies that unsafe tool calls are blocked before execution.",
|
|
17
|
+
"developerName": "Rippletide",
|
|
18
|
+
"category": "Developer Tools",
|
|
19
|
+
"capabilities": ["Skills", "Workflow"],
|
|
20
|
+
"websiteURL": "https://www.rippletide.com/",
|
|
21
|
+
"defaultPrompt": [
|
|
22
|
+
"Install Tide in this repo.",
|
|
23
|
+
"Compile and test my Tide policy.",
|
|
24
|
+
"Verify Tide blocks a risky command."
|
|
25
|
+
],
|
|
26
|
+
"brandColor": "#0F766E"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tide
|
|
3
|
+
description: Use when the user asks to install, configure, test, or verify Tide policy guardrails for Codex, including npm package checks, POLICY.md compilation, hook installation, or release-readiness proof.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tide
|
|
7
|
+
|
|
8
|
+
Use Tide as a Codex-scoped policy guardrail. Tide is distributed by the `rippletide-package`
|
|
9
|
+
npm package and exposes both `tide` and `rippletide-package tide`.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
- v1 supports Codex enforcement through a PreToolUse hook.
|
|
14
|
+
- Claude Code, OpenCode, and Pi are placeholders until their adapters are implemented.
|
|
15
|
+
- Do not claim release readiness unless the OpenAI compile path and a real Codex block/allow path
|
|
16
|
+
have both been verified.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
Prefer the smallest install path for the target project:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install --save-dev rippletide-package
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
or run without adding a dependency:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm exec --package rippletide-package -- tide --help
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Compile
|
|
33
|
+
|
|
34
|
+
For release or publication checks, require the LLM path so missing or broken credentials cannot
|
|
35
|
+
fall back to local heuristics:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
tide compile --policy POLICY.md --project .tide --require-openai
|
|
39
|
+
tide test --project .tide
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If the environment does not expose `OPENAI_API_KEY`, ask for a real OpenAI API key or pass
|
|
43
|
+
`--api-key`. Azure/ChatGPT Codex auth is not the same as an OpenAI API key for Tide compile.
|
|
44
|
+
|
|
45
|
+
## Install Enforcement
|
|
46
|
+
|
|
47
|
+
After reviewing `.tide/findings.md`:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
tide install --project .tide
|
|
51
|
+
tide status --project .tide
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For Codex CLI automation, run with hooks enabled and hook trust bypassed only in a controlled test
|
|
55
|
+
home:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
codex exec --skip-git-repo-check --ephemeral --dangerously-bypass-hook-trust --enable hooks \
|
|
59
|
+
"Run exactly this shell command and then stop: rm -rf build"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Release Gate
|
|
63
|
+
|
|
64
|
+
Before saying Tide is good to publish, run the strongest practical checks:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm run check
|
|
68
|
+
npm audit --omit=dev
|
|
69
|
+
npm pack
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then install the tarball in a clean temp project and verify:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install /tmp/rippletide-package-<version>.tgz
|
|
76
|
+
tide --help
|
|
77
|
+
rippletide-package tide --help
|
|
78
|
+
tide compile --policy POLICY.md --project .tide --require-openai
|
|
79
|
+
tide test --project .tide
|
|
80
|
+
tide check --project .tide --command "echo ok"
|
|
81
|
+
tide check --project .tide --command "rm -rf build"
|
|
82
|
+
tide install --project .tide
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The final agent proof is:
|
|
86
|
+
|
|
87
|
+
- a risky command is blocked by the Tide PreToolUse hook before execution;
|
|
88
|
+
- a harmless command runs;
|
|
89
|
+
- `.tide/.runtime/events.jsonl` records at least one `deny` and one `allow`;
|
|
90
|
+
- any sentinel file or directory targeted by the risky command is still present.
|
|
91
|
+
|
|
92
|
+
Skipped: broad multi-agent claims. Add them only after non-Codex adapters have executable tests.
|
package/reviewer/README.md
CHANGED
|
@@ -24,7 +24,7 @@ RIPPLETIDE_MODEL=gpt-5.5
|
|
|
24
24
|
Run a review with npx:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npx rippletide review
|
|
27
|
+
npx rippletide-package review
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
The command reviews the current directory by default.
|
|
@@ -32,10 +32,10 @@ The command reviews the current directory by default.
|
|
|
32
32
|
Explicit path:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
npx rippletide review /path/to/agent
|
|
35
|
+
npx rippletide-package review /path/to/agent
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Current local reviewer mode requires `OPENAI_API_KEY`. The product direction is hosted-first, so the final `npx rippletide review` flow will use the Rippletide hosted reviewer instead of asking users to configure model keys.
|
|
38
|
+
Current local reviewer mode requires `OPENAI_API_KEY`. The product direction is hosted-first, so the final `npx rippletide-package review` flow will use the Rippletide hosted reviewer instead of asking users to configure model keys.
|
|
39
39
|
|
|
40
40
|
Run a review from a local checkout:
|
|
41
41
|
|
|
@@ -290,17 +290,17 @@ function usage() {
|
|
|
290
290
|
return `Rippletide Reviewer
|
|
291
291
|
|
|
292
292
|
Usage:
|
|
293
|
-
rippletide review [path] [--evidence-budget 140000] [--fix]
|
|
294
|
-
rippletide inspect [path] [--evidence-budget 140000]
|
|
293
|
+
rippletide-package review [path] [--evidence-budget 140000] [--fix]
|
|
294
|
+
rippletide-package inspect [path] [--evidence-budget 140000]
|
|
295
295
|
npm run inspect -- [path]
|
|
296
296
|
npm run review -- [path]
|
|
297
297
|
|
|
298
298
|
Example:
|
|
299
|
-
npx rippletide review
|
|
300
|
-
npx rippletide review ../my-agent
|
|
301
|
-
rippletide review ../my-agent
|
|
302
|
-
rippletide review ../my-agent --fix
|
|
303
|
-
rippletide inspect ../my-agent
|
|
299
|
+
npx rippletide-package review
|
|
300
|
+
npx rippletide-package review ../my-agent
|
|
301
|
+
rippletide-package review ../my-agent
|
|
302
|
+
rippletide-package review ../my-agent --fix
|
|
303
|
+
rippletide-package inspect ../my-agent
|
|
304
304
|
|
|
305
305
|
Interactive fix mode:
|
|
306
306
|
--fix shows a short findings menu first, then proposes an action for the finding you choose.
|
package/tide/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# tide — plain-English runtime policy for coding agents
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> npm command: `tide` or `rippletide-package tide`. **Status: v1 scoped to Codex.**
|
|
4
4
|
> See [`PRD.md`](PRD.md) for the full design.
|
|
5
5
|
|
|
6
6
|
## What it is
|
|
7
7
|
|
|
8
|
-
Write your guardrails in plain English in a `POLICY.md`. `
|
|
8
|
+
Write your guardrails in plain English in a `POLICY.md`. `tide compile` compiles
|
|
9
9
|
that policy — **once**, with an LLM — into deterministic, reviewable rules, and then
|
|
10
10
|
**enforces them at the agent's tool-call boundary at runtime** (no LLM in the hot path).
|
|
11
11
|
|
|
@@ -18,7 +18,7 @@ covers the actions that actually matter, and doesn't depend on the agent using M
|
|
|
18
18
|
placeholders (same interface, stubbed).
|
|
19
19
|
|
|
20
20
|
```
|
|
21
|
-
POLICY.md ──compile (gpt-5.
|
|
21
|
+
POLICY.md ──compile (gpt-5.5, once)──► rules.policy.yaml + ontology + golden tests
|
|
22
22
|
│
|
|
23
23
|
enforce (deterministic)
|
|
24
24
|
│
|
|
@@ -27,7 +27,7 @@ POLICY.md ──compile (gpt-5.4, once)──► rules.policy.yaml + ontology
|
|
|
27
27
|
|
|
28
28
|
## Two phases
|
|
29
29
|
|
|
30
|
-
1. **Compile (authoring, LLM once).** `POLICY.md` + the agent's tool manifest → `gpt-5.
|
|
30
|
+
1. **Compile (authoring, LLM once).** `POLICY.md` + the agent's tool manifest → `gpt-5.5`
|
|
31
31
|
→ `ontology/actions.yaml`, `policies/rules.policy.yaml`, `tests/golden.scenarios.yaml`,
|
|
32
32
|
`findings.md` (the `tide-test` schema). Validated with `compile` + `test`; you review.
|
|
33
33
|
2. **Enforce (runtime, deterministic).** A ported `tide-test` engine evaluates the rules on
|
|
@@ -35,16 +35,16 @@ POLICY.md ──compile (gpt-5.4, once)──► rules.policy.yaml + ontology
|
|
|
35
35
|
|
|
36
36
|
## ⚠️ The LLM step: whose key & compute?
|
|
37
37
|
|
|
38
|
-
The **compile** step (phase 1 only) calls an LLM (`gpt-5.
|
|
38
|
+
The **compile** step (phase 1 only) calls an LLM (`gpt-5.5`) to turn prose into rules. This
|
|
39
39
|
is a **one-time authoring** call — **enforcement never calls an LLM**. Still, it raises a
|
|
40
40
|
real question: whose API key runs it, and where does the policy + tool data go? Three
|
|
41
41
|
options, each with a different trust/cost profile:
|
|
42
42
|
|
|
43
43
|
### Option 1 — user's key, on the user's machine ✅ **chosen for v1**
|
|
44
|
-
The package uses the **user's own**
|
|
44
|
+
The package uses the **user's own** OpenAI API key and makes the call **locally**.
|
|
45
45
|
- **Pros:** nothing leaves the user's environment except to *their own* OpenAI account; no
|
|
46
46
|
Rippletide compute cost; simplest to ship.
|
|
47
|
-
- **Cons:** the user must have
|
|
47
|
+
- **Cons:** the user must have an OpenAI API key with access to the selected model; their `POLICY.md` and tool manifest are sent
|
|
48
48
|
to OpenAI under their account (acceptable for most, but it *is* egress).
|
|
49
49
|
|
|
50
50
|
### Option 2 — send to Rippletide, generate server-side
|
|
@@ -71,12 +71,19 @@ one interface, so switching later is a contained change.
|
|
|
71
71
|
|
|
72
72
|
## Usage (v1 — Codex)
|
|
73
73
|
|
|
74
|
-
Node package (unified with the `rippletide` npm CLI).
|
|
75
|
-
|
|
76
|
-
`
|
|
74
|
+
Node package (unified with the `rippletide-package` npm CLI). From npm, use either
|
|
75
|
+
`tide <cmd>` or `rippletide-package tide <cmd>`. Locally: `cd tide && npm install`,
|
|
76
|
+
then use `node bin/tide.js <cmd>`.
|
|
77
|
+
|
|
78
|
+
Install the Tide Codex plugin from the public marketplace repo:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx -y rippletide-package@latest tide codex install
|
|
82
|
+
```
|
|
77
83
|
|
|
78
84
|
```bash
|
|
79
85
|
tide compile --policy POLICY.md --project .tide # POLICY.md -> rules (LLM) + golden tests
|
|
86
|
+
tide compile --policy POLICY.md --project .tide --require-openai # release gate: no heuristic fallback
|
|
80
87
|
tide test --project .tide # run the golden scenarios (deterministic)
|
|
81
88
|
tide check --project .tide --command "rm -rf x" # try a single command -> ALLOW/DENY
|
|
82
89
|
tide install --project .tide # install the Codex PreToolUse hook
|
|
@@ -92,34 +99,37 @@ the model, which adapts); everything else passes. The compiled policy lives in `
|
|
|
92
99
|
(`rules.policy.yaml`, `ontology/actions.yaml`, `tests/golden.scenarios.yaml`, `findings.md`)
|
|
93
100
|
— all human-readable and reviewable. `.tide/.runtime/events.jsonl` records every firing.
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
`tide compile` auto-detects an OpenAI API credential when one is available: your
|
|
103
|
+
`OPENAI_API_KEY` env var, or a Codex `auth.json` that contains an actual `OPENAI_API_KEY`.
|
|
104
|
+
Codex configs that use Azure OpenAI or ChatGPT auth are not treated as OpenAI API keys;
|
|
105
|
+
export `OPENAI_API_KEY` or pass `--api-key` for those setups.
|
|
98
106
|
|
|
99
107
|
```
|
|
100
|
-
tide: detected you're already using OpenAI via your
|
|
108
|
+
tide: detected you're already using OpenAI via your OPENAI_API_KEY environment variable, model gpt-5.5 (key sk-pro…6H0A).
|
|
101
109
|
Use the same to build your rules? [Y/n]
|
|
102
110
|
```
|
|
103
111
|
|
|
104
112
|
It also matches the model your Codex uses. The key is read locally and used only for the
|
|
105
113
|
one-time compile call; it never leaves your machine except to OpenAI under your own account.
|
|
106
|
-
Override with `--api-key` / `--model`, or decline the prompt to skip the LLM.
|
|
114
|
+
Override with `--api-key` / `--model`, or decline the prompt to skip the LLM. For release
|
|
115
|
+
verification, use `--require-openai` so a missing or failing key stops the command instead
|
|
116
|
+
of falling back to heuristics. Enforcement is
|
|
107
117
|
local and needs no credentials.
|
|
108
118
|
|
|
109
119
|
## Status
|
|
110
120
|
|
|
111
|
-
- **v1 shipped — Codex (Node):** compile (your key,
|
|
121
|
+
- **v1 shipped — Codex (Node):** compile (your key, `gpt-5.5` default) → deterministic
|
|
112
122
|
engine → PreToolUse hook. Verified end-to-end against real `codex` instances:
|
|
113
123
|
file-modifying commands **and** `apply_patch` edits are blocked; reads run; unit +
|
|
114
|
-
golden + e2e tests pass. Implemented in Node/ESM (`src/`) to unify with the `rippletide`
|
|
124
|
+
golden + e2e tests pass. Implemented in Node/ESM (`src/`) to unify with the `rippletide-package`
|
|
115
125
|
npm package — one dependency (`js-yaml`).
|
|
116
126
|
- **Placeholders:** Claude Code / OpenCode / Pi adapters are stubbed with their target seam
|
|
117
127
|
(see [`PRD.md`](PRD.md) §8). **Escalate** currently holds by blocking-with-reason (a live
|
|
118
128
|
approval queue is future work).
|
|
119
129
|
|
|
120
130
|
**Requirements:** Node ≥ 18, Codex installed. Tests: `tide/tests/engine.test.mjs`
|
|
121
|
-
(`node --test`, deterministic
|
|
122
|
-
|
|
131
|
+
(`node --test`, deterministic, includes an OpenAI-compatible mock Responses endpoint) and
|
|
132
|
+
`tide/tests/e2e_codex.mjs` (spawns real `codex`; needs a working OpenAI credential).
|
|
123
133
|
|
|
124
134
|
## Related
|
|
125
135
|
|
package/tide/bin/tide.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Tide CLI entry. Also the
|
|
2
|
+
// Tide CLI entry. Also the tide feature entry point for the parent rippletide-package CLI.
|
|
3
3
|
import { main } from "../src/cli.js";
|
|
4
4
|
process.exit(await main(process.argv.slice(2)));
|
package/tide/src/cli.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
// The `tide` CLI — and the
|
|
2
|
-
// tide compile | test | check | install | uninstall | status
|
|
3
|
-
// The parent wires `rippletide
|
|
1
|
+
// The `tide` CLI — and the tide feature entry point for the parent rippletide-package CLI.
|
|
2
|
+
// tide compile | test | check | install | uninstall | status | codex install
|
|
3
|
+
// The parent wires `rippletide-package tide <args>` to `main(args)`.
|
|
4
4
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import readline from "node:readline";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
8
9
|
import * as codex from "./codex.js";
|
|
9
10
|
import { compilePolicy, DEFAULT_MODEL } from "./compile.js";
|
|
10
11
|
import { Engine } from "./engine.js";
|
|
11
12
|
import { detectOpenAI, maskKey } from "./credentials.js";
|
|
12
13
|
import { DEFAULT_DIR, loadBundle, loadScenarios, writeBundle } from "./schema.js";
|
|
13
14
|
|
|
15
|
+
const TIDE_CODEX_MARKETPLACE = "rippletideco/tide-codex-plugin";
|
|
16
|
+
const TIDE_CODEX_MARKETPLACE_REF = "main";
|
|
17
|
+
const TIDE_CODEX_HANDLE = "tide@tide-codex";
|
|
18
|
+
|
|
14
19
|
const PLACEHOLDER_SEAMS = {
|
|
15
20
|
claude_code: "PreToolUse hook in settings.json -> permissionDecision allow/deny/ask",
|
|
16
21
|
opencode: "permission rules (its hooks can't block) -> DeniedError",
|
|
@@ -26,7 +31,7 @@ function adapter(agent) {
|
|
|
26
31
|
|
|
27
32
|
function parseArgs(argv) {
|
|
28
33
|
const cmd = argv[0];
|
|
29
|
-
const opts = { agent: "codex", project: DEFAULT_DIR, policy: "POLICY.md", model: null, apiKey: null, command: null };
|
|
34
|
+
const opts = { agent: "codex", project: DEFAULT_DIR, policy: "POLICY.md", model: null, apiKey: null, command: null, requireOpenAI: false, args: argv.slice(1) };
|
|
30
35
|
for (let i = 1; i < argv.length; i++) {
|
|
31
36
|
const a = argv[i];
|
|
32
37
|
const next = () => argv[++i];
|
|
@@ -36,6 +41,7 @@ function parseArgs(argv) {
|
|
|
36
41
|
else if (a === "--model") opts.model = next();
|
|
37
42
|
else if (a === "--api-key") opts.apiKey = next();
|
|
38
43
|
else if (a === "--command") opts.command = next();
|
|
44
|
+
else if (a === "--require-openai") opts.requireOpenAI = true;
|
|
39
45
|
}
|
|
40
46
|
return { cmd, opts };
|
|
41
47
|
}
|
|
@@ -81,9 +87,9 @@ function runScenarios(bundle, scenarios) {
|
|
|
81
87
|
|
|
82
88
|
async function cmdCompile(opts) {
|
|
83
89
|
const { apiKey, model } = await resolveCredential(opts);
|
|
84
|
-
if (!apiKey) console.log("tide: no OpenAI credential — using local heuristics for now.");
|
|
90
|
+
if (!apiKey && !opts.requireOpenAI) console.log("tide: no OpenAI credential — using local heuristics for now.");
|
|
85
91
|
const { bundle, scenarios, diagnostics, source, findings } =
|
|
86
|
-
await compilePolicy({ policyPath: opts.policy, agent: opts.agent, projectDir: opts.project, model, apiKey });
|
|
92
|
+
await compilePolicy({ policyPath: opts.policy, agent: opts.agent, projectDir: opts.project, model, apiKey, requireOpenAI: opts.requireOpenAI });
|
|
87
93
|
writeBundle(opts.project, bundle, scenarios, findings);
|
|
88
94
|
console.log(`tide: compiled ${bundle.rules.length} rule(s) from ${opts.policy} via ${source} -> ${opts.project}/`);
|
|
89
95
|
for (const r of bundle.rules) console.log(` - ${r.id} [${r.mode}/${r.verdict}] ${r.when || "requires " + JSON.stringify(r.requires)}`);
|
|
@@ -153,19 +159,61 @@ function cmdStatus(opts) {
|
|
|
153
159
|
return 0;
|
|
154
160
|
}
|
|
155
161
|
|
|
162
|
+
function codexBins() {
|
|
163
|
+
return [
|
|
164
|
+
process.env.CODEX_BIN,
|
|
165
|
+
"codex",
|
|
166
|
+
process.platform === "darwin" ? "/Applications/Codex.app/Contents/Resources/codex" : null,
|
|
167
|
+
].filter(Boolean);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function findCodexBin() {
|
|
171
|
+
const failures = [];
|
|
172
|
+
for (const bin of codexBins()) {
|
|
173
|
+
const out = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 5000 });
|
|
174
|
+
if (!out.error && out.status === 0) return bin;
|
|
175
|
+
failures.push(`${bin}: ${out.error?.message || out.stderr?.trim() || `exit ${out.status}`}`);
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`Codex CLI not found. Install Codex or set CODEX_BIN. Tried: ${failures.join("; ")}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function runCodex(bin, args) {
|
|
181
|
+
const out = spawnSync(bin, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
|
|
182
|
+
if (out.stdout) process.stdout.write(out.stdout);
|
|
183
|
+
if (out.stderr) process.stderr.write(out.stderr);
|
|
184
|
+
if (out.error) throw out.error;
|
|
185
|
+
if (out.status !== 0) throw new Error(`codex ${args.join(" ")} failed with exit ${out.status}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function cmdCodex(opts) {
|
|
189
|
+
const sub = opts.args[0];
|
|
190
|
+
if (sub !== "install") {
|
|
191
|
+
console.error("tide: expected `tide codex install`");
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
const bin = findCodexBin();
|
|
195
|
+
console.log(`tide: using Codex CLI at ${bin}`);
|
|
196
|
+
runCodex(bin, ["plugin", "marketplace", "add", TIDE_CODEX_MARKETPLACE, "--ref", TIDE_CODEX_MARKETPLACE_REF]);
|
|
197
|
+
runCodex(bin, ["plugin", "add", TIDE_CODEX_HANDLE]);
|
|
198
|
+
console.log(`tide: Codex plugin installed: ${TIDE_CODEX_HANDLE}`);
|
|
199
|
+
console.log("tide: start a new Codex thread and ask Tide to install, compile, or verify your policy.");
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
156
203
|
const HELP = `tide — compile a plain-English POLICY.md into deterministic guardrails on a coding agent's tool calls.
|
|
157
204
|
|
|
158
|
-
Usage: tide <command> [--policy POLICY.md] [--project .tide] [--agent codex] [--model M] [--api-key K]
|
|
205
|
+
Usage: tide <command> [--policy POLICY.md] [--project .tide] [--agent codex] [--model M] [--api-key K] [--require-openai]
|
|
159
206
|
compile POLICY.md + tools -> rules (uses your OpenAI credential; auto-detected)
|
|
160
207
|
test run the golden scenarios through the deterministic engine
|
|
161
208
|
check --command "..." evaluate a single command -> ALLOW/DENY
|
|
162
209
|
install install the enforcement hook into the agent (codex)
|
|
163
210
|
uninstall remove the hook
|
|
211
|
+
codex install install the Tide Codex plugin from the public marketplace repo
|
|
164
212
|
status show enforcement state + pass/block tally`;
|
|
165
213
|
|
|
166
214
|
export async function main(argv) {
|
|
167
215
|
const { cmd, opts } = parseArgs(argv);
|
|
168
|
-
const cmds = { compile: cmdCompile, test: cmdTest, check: cmdCheck, install: cmdInstall, uninstall: cmdUninstall, status: cmdStatus };
|
|
216
|
+
const cmds = { compile: cmdCompile, test: cmdTest, check: cmdCheck, install: cmdInstall, uninstall: cmdUninstall, status: cmdStatus, codex: cmdCodex };
|
|
169
217
|
if (!cmd || cmd === "-h" || cmd === "--help" || cmd === "help") { console.log(HELP); return cmd ? 0 : 1; }
|
|
170
218
|
if (!cmds[cmd]) { console.error(`tide: unknown command '${cmd}'\n\n${HELP}`); return 1; }
|
|
171
219
|
try { return await cmds[cmd](opts); }
|
package/tide/src/compile.js
CHANGED
|
@@ -8,7 +8,7 @@ import { validateBundle } from "./engine.js";
|
|
|
8
8
|
import { evaluate, parse } from "./expr.js";
|
|
9
9
|
import { actionsFor } from "./ontology.js";
|
|
10
10
|
|
|
11
|
-
export const DEFAULT_MODEL = "gpt-5.
|
|
11
|
+
export const DEFAULT_MODEL = "gpt-5.5";
|
|
12
12
|
|
|
13
13
|
const SYSTEM =
|
|
14
14
|
"You generate deterministic Tide policy rules that gate a coding agent's tool calls. " +
|
|
@@ -31,7 +31,7 @@ export function readPolicy(p) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export async function compilePolicy({ policyPath, agent = "codex", projectDir = ".tide",
|
|
34
|
-
model = null, apiKey = null } = {}) {
|
|
34
|
+
model = null, apiKey = null, requireOpenAI = false } = {}) {
|
|
35
35
|
const actions = actionsFor(agent);
|
|
36
36
|
const policyText = readPolicy(policyPath);
|
|
37
37
|
apiKey = apiKey || process.env.OPENAI_API_KEY;
|
|
@@ -39,11 +39,13 @@ export async function compilePolicy({ policyPath, agent = "codex", projectDir =
|
|
|
39
39
|
|
|
40
40
|
let source = "heuristic";
|
|
41
41
|
let rawRules = [];
|
|
42
|
+
if (requireOpenAI && !apiKey) throw new Error("OPENAI_API_KEY or --api-key is required when --require-openai is set");
|
|
42
43
|
if (apiKey) {
|
|
43
44
|
try {
|
|
44
45
|
rawRules = await openaiRules(policyText, actions, model, apiKey);
|
|
45
46
|
source = "llm";
|
|
46
47
|
} catch (e) {
|
|
48
|
+
if (requireOpenAI) throw e;
|
|
47
49
|
console.error(`tide: LLM rule generation failed (${e.message}); using local heuristics`);
|
|
48
50
|
rawRules = [];
|
|
49
51
|
}
|
package/tide/src/credentials.js
CHANGED
|
@@ -7,8 +7,9 @@ import fs from "node:fs";
|
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
function codexHome() {
|
|
11
|
+
return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
12
|
+
}
|
|
12
13
|
|
|
13
14
|
function readJson(p) {
|
|
14
15
|
try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
|
|
@@ -16,7 +17,9 @@ function readJson(p) {
|
|
|
16
17
|
|
|
17
18
|
function codexModel() {
|
|
18
19
|
let text;
|
|
19
|
-
try { text = fs.readFileSync(
|
|
20
|
+
try { text = fs.readFileSync(path.join(codexHome(), "config.toml"), "utf8"); } catch { return null; }
|
|
21
|
+
const provider = text.match(/^\s*model_provider\s*=\s*"([^"]+)"/m)?.[1];
|
|
22
|
+
if (provider && provider !== "openai") return null;
|
|
20
23
|
const head = text.split(/^\s*\[/m)[0]; // before any [table]/[profile]
|
|
21
24
|
const m = head.match(/^\s*model\s*=\s*"([^"]+)"/m) || text.match(/^\s*model\s*=\s*"([^"]+)"/m);
|
|
22
25
|
return m ? m[1] : null;
|
|
@@ -33,9 +36,9 @@ export function detectOpenAI() {
|
|
|
33
36
|
if (process.env.OPENAI_API_KEY) {
|
|
34
37
|
return { apiKey: process.env.OPENAI_API_KEY, source: "your OPENAI_API_KEY environment variable", model, baseUrl };
|
|
35
38
|
}
|
|
36
|
-
const auth = readJson(
|
|
39
|
+
const auth = readJson(path.join(codexHome(), "auth.json"));
|
|
37
40
|
if (auth && auth.OPENAI_API_KEY) {
|
|
38
|
-
return { apiKey: auth.OPENAI_API_KEY, source: "your Codex
|
|
41
|
+
return { apiKey: auth.OPENAI_API_KEY, source: "your Codex auth file", model, baseUrl };
|
|
39
42
|
}
|
|
40
43
|
return null;
|
|
41
44
|
}
|