rippletide-package 0.2.0 → 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.
@@ -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
@@ -10,6 +10,7 @@ npx rippletide-package <feature-name> [flags]
10
10
  > Published on npm as **`rippletide-package`**, and the installed command is also
11
11
  > **`rippletide-package`**. Via `npx`: `npx rippletide-package …`; after
12
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,7 +18,8 @@ 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 and the installed command are both `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
25
  - **flags** choose behavior *inside* a feature, e.g. `rippletide-package <feature-name> --<flag>`;
@@ -57,11 +59,19 @@ single `rippletide-package` CLI worse.
57
59
  npm registry : https://registry.npmjs.org/
58
60
  repo folder : ./
59
61
  package name : rippletide-package
60
- command : rippletide-package
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
- npx ./rippletide-package-0.1.0.tgz --help # or: ... <feature-name> --help
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
package/README.md CHANGED
@@ -6,6 +6,8 @@ while each feature lives in its own self-contained top-level folder.
6
6
  > Published on npm as **`rippletide-package`**, and the installed command is also
7
7
  > **`rippletide-package`**. So: `npx rippletide-package <feature-name>`, or
8
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
@@ -22,6 +24,18 @@ Each feature is a **subcommand** of `rippletide-package`, implemented in its own
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rippletide-package",
3
- "version": "0.2.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-package": "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/tide/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # tide — plain-English runtime policy for coding agents
2
2
 
3
- > Feature flag: `--tide` on `rippletide-package`. **Status: draft / v1 scoped to Codex.**
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`. `rippletide-package --tide` compiles
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.4, once)──► rules.policy.yaml + ontology + golden tests
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.4`
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.4`) to turn prose into rules. This
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** `gpt-5.4` key and makes the call **locally**.
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 a `gpt-5.4` key; their `POLICY.md` and tool manifest are sent
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-package` npm CLI). Locally: `cd tide && npm install`,
75
- then use `node bin/tide.js <cmd>` (or the `tide` bin). Wired into the parent it's
76
- `rippletide-package tide <cmd>`.
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,23 +99,26 @@ 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
- **Identity is simple — no export needed.** `tide compile` auto-detects the OpenAI
96
- credential you already use — your `OPENAI_API_KEY` env var, or **your Codex login**
97
- (`~/.codex/auth.json`) and asks before reusing it:
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 Codex login (~/.codex/auth.json), model gpt-5.5 (key sk-pro…6H0A).
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. Enforcement is
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, model auto-matched) → deterministic
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
124
  golden + e2e tests pass. Implemented in Node/ESM (`src/`) to unify with the `rippletide-package`
@@ -118,8 +128,8 @@ local and needs no credentials.
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) and `tide/tests/e2e_codex.mjs` (spawns real `codex`; needs
122
- an OpenAI credential).
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/src/cli.js CHANGED
@@ -1,16 +1,21 @@
1
1
  // The `tide` CLI — and the tide feature entry point for the parent rippletide-package CLI.
2
- // tide compile | test | check | install | uninstall | status
2
+ // tide compile | test | check | install | uninstall | status | codex install
3
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); }
@@ -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.4";
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
  }
@@ -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
- const CODEX_AUTH = path.join(os.homedir(), ".codex", "auth.json");
11
- const CODEX_CONFIG = path.join(os.homedir(), ".codex", "config.toml");
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(CODEX_CONFIG, "utf8"); } catch { return null; }
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(CODEX_AUTH);
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 login (~/.codex/auth.json)", model, baseUrl };
41
+ return { apiKey: auth.OPENAI_API_KEY, source: "your Codex auth file", model, baseUrl };
39
42
  }
40
43
  return null;
41
44
  }