Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cc",
"version": "1.0.8",
"version": "1.0.9",
"description": "Claude Code Plugin for Codex. Delegate code reviews, investigations, and tracked tasks to Claude Code from inside Codex.",
"author": {
"name": "Sendbird, Inc.",
Expand Down
110 changes: 110 additions & 0 deletions .github/workflows/update-marketplace.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Update Codex Marketplace

on:
release:
types:
- published
workflow_dispatch:
inputs:
ref:
description: Git ref to publish into the marketplace snapshot
required: false
default: main

jobs:
update-marketplace:
runs-on: ubuntu-latest
permissions:
contents: read
env:
MARKETPLACE_REPO: sendbird/codex-marketplace
MARKETPLACE_BRANCH_PREFIX: auto/update-cc-
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.release.tag_name || inputs.ref || github.ref_name }}

- name: Verify marketplace token is configured
env:
MARKETPLACE_TOKEN: ${{ secrets.SENDBIRD_CODEX_MARKETPLACE_PUSH_TOKEN }}
run: |
if [ -z "$MARKETPLACE_TOKEN" ]; then
echo "SENDBIRD_CODEX_MARKETPLACE_PUSH_TOKEN is required to update $MARKETPLACE_REPO" >&2
exit 1
fi

- name: Build marketplace snapshot
run: |
SNAPSHOT_DIR="$RUNNER_TEMP/marketplace-snapshot/cc"
mkdir -p "$SNAPSHOT_DIR"
for entry in \
.codex-plugin \
CHANGELOG.md \
LICENSE \
NOTICE \
README.md \
agents \
assets \
hooks \
internal-skills \
package.json \
prompts \
schemas \
scripts \
skills
do
if [ -e "$entry" ]; then
rsync -a "$entry" "$SNAPSHOT_DIR/"
fi
done

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
repository: sendbird/codex-marketplace
path: marketplace
token: ${{ secrets.SENDBIRD_CODEX_MARKETPLACE_PUSH_TOKEN }}
fetch-depth: 0

- name: Update marketplace snapshot
run: |
rm -rf marketplace/plugins/cc
mkdir -p marketplace/plugins
rsync -a --delete "$RUNNER_TEMP/marketplace-snapshot/cc/" marketplace/plugins/cc/

- name: Create marketplace pull request
env:
GH_TOKEN: ${{ secrets.SENDBIRD_CODEX_MARKETPLACE_PUSH_TOKEN }}
REF_NAME: ${{ github.event.release.tag_name || inputs.ref || github.ref_name }}
run: |
cd marketplace

if git diff --quiet -- plugins/cc; then
echo "Marketplace snapshot already matches $REF_NAME"
exit 0
fi

BRANCH_NAME="${MARKETPLACE_BRANCH_PREFIX}${REF_NAME//\//-}"
git checkout -B "$BRANCH_NAME"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add plugins/cc
git commit -m "Update cc plugin snapshot to $REF_NAME"
git push --force-with-lease origin "$BRANCH_NAME"

EXISTING_PR_NUMBER=$(gh pr list \
--repo "$MARKETPLACE_REPO" \
--head "$BRANCH_NAME" \
--json number \
--jq '.[0].number // empty')

if [ -n "$EXISTING_PR_NUMBER" ]; then
echo "Marketplace PR already exists: #$EXISTING_PR_NUMBER"
exit 0
fi

gh pr create \
--repo "$MARKETPLACE_REPO" \
--base main \
--head "$BRANCH_NAME" \
--title "Update cc plugin snapshot to $REF_NAME" \
--body "Update \`plugins/cc\` in the Sendbird Codex marketplace to \`$REF_NAME\` from \`sendbird/cc-plugin-codex\`."
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v1.0.9

- Add marketplace-aware install foundation for Codex 0.121+: the installer can now prefer `marketplace/add` + `plugin/install` when an official marketplace source is available, while keeping the existing legacy fallback path for unsupported builds.
- Generalize managed plugin identity handling so setup, hook cleanup, and cache detection work for `cc@<marketplace>` installs instead of assuming `cc@local-plugins`.
- Document the new canonical marketplace location at `sendbird/codex-marketplace` and make Sendbird marketplace install the first documented path, with `$cc:setup` called out as the required post-install hook repair step.

## v1.0.8

- Clarify the routing boundary between `$cc:review`, `$cc:adversarial-review`, and `$cc:rescue`, including the rule that ordinary code-review requests default to `review`, stronger scrutiny plus custom focus text belongs to `adversarial-review`, and rescue is only for Claude-owned follow-through work.
Expand Down
76 changes: 64 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,37 @@ It follows the shape of [openai/codex-plugin-cc](https://github.com/openai/codex

### 1. Install

```bash
npx cc-plugin-codex install
```

That's the entire install. It:
Use one of these install paths, in this order:

1. **Sendbird marketplace (preferred)**
```bash
codex marketplace add sendbird/codex-marketplace
```
Then install `cc` from the Sendbird marketplace inside Codex, and run `$cc:setup` once. Marketplace/plugin install places the plugin, but this plugin still owns global hook setup and repair.

2. **`npx` installer**
```bash
npx cc-plugin-codex install
```
This is the cross-platform path we test on every release.

3. **Local checkout install**
```bash
git clone https://github.com/sendbird/cc-plugin-codex.git ~/.codex/plugins/cc
cd ~/.codex/plugins/cc
node scripts/local-plugin-install.mjs install --plugin-root ~/.codex/plugins/cc
```
After install, run `$cc:setup`.

The `npx` installer:
- Copies the plugin to `~/.codex/plugins/cc`
- Activates the plugin through Codex app-server when available
- Falls back to config-based activation on older Codex builds
- Enables `codex_hooks = true`
- Installs lifecycle, review-gate, and unread-result hooks

On Windows, prefer the `npx` path above. The shell-script installer below is POSIX-only.
Codex CLI's official guidance still treats Windows support as experimental and recommends a WSL workspace for the best Codex experience. Claude Code supports both native Windows and WSL. In hosted CI we currently keep Windows on the native cross-platform core suite, while full integration and E2E coverage run on Linux and macOS.
The `npx` install path is the cross-platform path we test on every release.
On Windows, prefer either the Sendbird marketplace path or the `npx` path. The shell-script installer below is POSIX-only.
Codex CLI's official guidance still treats Windows support as experimental and recommends a WSL workspace for the best Codex experience. Claude Code supports both native Windows and WSL.

> **Prerequisites:** Node.js 18+, Codex with hook support, and `claude` CLI installed and authenticated.
> If you don't have the Claude CLI yet:
Expand Down Expand Up @@ -213,6 +230,7 @@ $cc:setup --disable-review-gate # turn it off
```

Setup checks Claude Code availability, hook installation, and review-gate state. If hooks are missing, it reinstalls them. If Claude Code isn't installed, it offers to install it.
This is also the repair path for marketplace-installed copies of the plugin: marketplace install can place the plugin, but `$cc:setup` is what confirms `codex_hooks = true` and installs the managed global hooks if they are missing.

## Background Jobs

Expand Down Expand Up @@ -279,16 +297,32 @@ The review gate is an **optional** stop-time hook. When enabled, pressing Ctrl+C

## Install Variants

### npx (recommended)
### Sendbird marketplace (preferred)

Add the marketplace:

```bash
npx cc-plugin-codex install
codex marketplace add sendbird/codex-marketplace
```

### Shell script
Then install `cc` from the Sendbird marketplace inside Codex, and run:

```text
$cc:setup
```

Marketplace/plugin install places the plugin, but it does **not** install this plugin's managed global hooks for you. `$cc:setup` is the repair/install step that confirms `codex_hooks = true` and installs hooks when they are missing.

### npx

```bash
curl -fsSL "https://raw.githubusercontent.com/sendbird/cc-plugin-codex/main/scripts/install.sh" | bash
npx cc-plugin-codex install
```

After install, run:

```text
$cc:setup
```

### Local checkout
Expand All @@ -299,8 +333,26 @@ cd ~/.codex/plugins/cc
node scripts/local-plugin-install.mjs install --plugin-root ~/.codex/plugins/cc
```

After install, run:

```text
$cc:setup
```

`local-plugin-install.mjs` expects `--plugin-root` to be the managed install directory itself. If you want to install from an arbitrary checkout path, use `npx cc-plugin-codex install` instead.

### Shell script (POSIX-only)

```bash
curl -fsSL "https://raw.githubusercontent.com/sendbird/cc-plugin-codex/main/scripts/install.sh" | bash
```

After install, run:

```text
$cc:setup
```

### Update

Re-run the install command — it's idempotent.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cc-plugin-codex",
"version": "1.0.8",
"version": "1.0.9",
"description": "Claude Code Plugin for Codex by Sendbird",
"type": "module",
"author": {
Expand Down
52 changes: 50 additions & 2 deletions scripts/claude-companion.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,33 @@ function renderStatusPayload(report, asJson) {
return asJson ? report : renderStatusReport(report);
}

function statusPayloadSurfacesStoredResult(job) {
return (
Boolean(job) &&
(job.status === "completed" ||
job.status === "failed" ||
job.status === "cancelled" ||
job.status === "cancel_failed" ||
job.status === "unknown") &&
Object.prototype.hasOwnProperty.call(job, "result")
);
}

function markViewedViaStatusAccess(workspaceRoot, jobs) {
const viewedAt = nowIso();
let changed = false;

for (const job of jobs) {
if (!job?.id || job.resultViewedAt || !statusPayloadSurfacesStoredResult(job)) {
continue;
}
patchJob(workspaceRoot, job.id, { resultViewedAt: viewedAt });
changed = true;
}

return changed;
}

// ---------------------------------------------------------------------------
// Foreground execution wrapper
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1476,12 +1503,24 @@ async function handleStatus(argv) {
const cwd = resolveCommandCwd(options);
const reference = positionals[0] ?? "";
if (reference) {
const snapshot = options.wait
let snapshot = options.wait
? await waitForSingleJobSnapshot(cwd, reference, {
timeoutMs: options["timeout-ms"],
pollIntervalMs: options["poll-interval-ms"]
})
: buildSingleJobSnapshot(cwd, reference);
if (
options.json &&
markViewedViaStatusAccess(snapshot.workspaceRoot, [snapshot.job])
) {
snapshot = options.wait
? {
...buildSingleJobSnapshot(cwd, reference),
waitTimedOut: snapshot.waitTimedOut,
timeoutMs: snapshot.timeoutMs,
}
: buildSingleJobSnapshot(cwd, reference);
}
outputCommandResult(
snapshot,
renderJobStatusReport(snapshot.job),
Expand All @@ -1494,7 +1533,16 @@ async function handleStatus(argv) {
throw new Error("`status --wait` requires a job id.");
}

const report = buildStatusSnapshot(cwd, { all: options.all });
let report = buildStatusSnapshot(cwd, { all: options.all });
if (
options.json &&
markViewedViaStatusAccess(report.workspaceRoot, [
report.latestFinished,
...report.recent,
])
) {
report = buildStatusSnapshot(cwd, { all: options.all });
}
outputResult(renderStatusPayload(report, options.json), options.json);
}

Expand Down
33 changes: 15 additions & 18 deletions scripts/install-hooks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
* 3. Read existing ~/.codex/hooks.json (or empty {hooks:{}})
* 4. For each event type, append new hooks (don't overwrite existing)
* 5. Write merged result
* 6. Check if ~/.codex/config.toml has codex_hooks = true, print guidance if not
* 6. Ensure ~/.codex/config.toml has codex_hooks = true
*/

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { ensureCodexHooksEnabled } from "./lib/codex-config.mjs";
import { resolveCodexHome } from "./lib/codex-paths.mjs";

const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
Expand Down Expand Up @@ -57,6 +58,15 @@ function writeJsonFile(filePath, data) {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
}

function configureCodexHooks() {
const existing = readTextFile(CODEX_CONFIG_TOML) ?? "";
const { changed, content } = ensureCodexHooksEnabled(existing);
if (changed || !fs.existsSync(CODEX_CONFIG_TOML)) {
writeTextFile(CODEX_CONFIG_TOML, content);
}
return changed;
}

function escapeShellArgument(value) {
const text = String(value);
if (process.platform === "win32") {
Expand Down Expand Up @@ -241,23 +251,10 @@ function main() {
console.log(` Added: ${addedCount} hook entries`);
console.log(` Skipped: ${skippedCount} duplicate entries`);

// Step 6: Check config.toml for codex_hooks setting
let hasCodexHooks = false;
if (fs.existsSync(CODEX_CONFIG_TOML)) {
const configContent = fs.readFileSync(CODEX_CONFIG_TOML, "utf8");
// Simple check — TOML parsing not needed for a boolean flag
hasCodexHooks = /codex_hooks\s*=\s*true/i.test(configContent);
}

if (!hasCodexHooks) {
console.log("\n--- IMPORTANT ---");
console.log("Codex hooks are not enabled in your config.");
console.log("Add the following to ~/.codex/config.toml:");
console.log("");
console.log(" [features]");
console.log(" codex_hooks = true");
console.log("");
console.log("This enables Codex to execute lifecycle hooks from hooks.json.");
// Step 6: Ensure config.toml enables codex_hooks
const codexHooksChanged = configureCodexHooks();
if (codexHooksChanged) {
console.log("\nEnabled codex_hooks in ~/.codex/config.toml.");
} else {
console.log("\nCodex hooks are enabled in config.toml. Ready to go.");
}
Expand Down
Loading