From 4d6eec741dc74794c99059264face4d317aa50bc Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Wed, 4 Mar 2026 13:23:34 -0800 Subject: [PATCH] npx denchclaw --- .../bootstrap_dev_testing_0b5817e5.plan.md | 38 ++++----- ...denchclaw_frontend_split_1c02d591.plan.md} | 54 ++++++------ ...ateway-ws_denchclaw_lock_0576496f.plan.md} | 28 +++---- .../strict-external-openclaw_7c0d1717.plan.md | 40 ++++----- ...workspace_profile_support_7e8600ec.plan.md | 2 +- .github/labeler.yml | 4 +- README.md | 78 +++++++++--------- apps/web/app/api/profiles/route.test.ts | 4 +- .../app/api/workspace/delete/route.test.ts | 10 +-- apps/web/app/api/workspace/init/route.test.ts | 11 +-- apps/web/app/api/workspace/init/route.ts | 10 +-- apps/web/app/api/workspace/thumbnail/route.ts | 2 +- .../web/app/api/workspace/tree-browse.test.ts | 4 +- .../app/components/cron/cron-dashboard.tsx | 2 +- apps/web/app/components/sidebar.tsx | 2 +- .../components/workspace/code-editor.test.tsx | 12 +-- .../app/components/workspace/code-editor.tsx | 10 +-- .../workspace/create-workspace-dialog.tsx | 2 +- .../app/components/workspace/empty-state.tsx | 4 +- .../workspace/profile-switcher.test.tsx | 12 +-- .../workspace/workspace-sidebar.tsx | 4 +- apps/web/app/layout.tsx | 2 +- apps/web/app/page.tsx | 8 +- apps/web/app/workspace/page.tsx | 4 +- apps/web/lib/agent-runner.test.ts | 26 +++--- apps/web/lib/agent-runner.ts | 8 +- apps/web/lib/workspace-chat-isolation.test.ts | 2 +- apps/web/lib/workspace-profiles.test.ts | 30 +++---- apps/web/lib/workspace.test.ts | 6 +- apps/web/lib/workspace.ts | 10 +-- apps/web/package.json | 2 +- apps/web/vitest.config.ts | 1 + docs/install/updating.md | 2 +- docs/reference/RELEASING.md | 4 +- extensions/bluebubbles/package.json | 2 +- extensions/copilot-proxy/package.json | 2 +- extensions/denchclaw-auth/README.md | 39 +++++++++ .../index.ts | 82 +++++++++---------- .../oauth.ts | 36 ++++---- .../openclaw.plugin.json | 4 +- extensions/diagnostics-otel/package.json | 2 +- extensions/discord/package.json | 2 +- extensions/feishu/package.json | 2 +- extensions/feishu/src/dynamic-agent.ts | 5 +- .../google-antigravity-auth/package.json | 2 +- .../google-gemini-cli-auth/package.json | 2 +- extensions/googlechat/package.json | 4 +- extensions/imessage/package.json | 2 +- extensions/irc/package.json | 2 +- extensions/ironclaw-auth/README.md | 39 --------- extensions/line/package.json | 2 +- extensions/llm-task/package.json | 2 +- extensions/lobster/package.json | 2 +- extensions/matrix/package.json | 2 +- extensions/mattermost/package.json | 2 +- extensions/memory-core/package.json | 4 +- extensions/memory-lancedb/config.ts | 4 +- .../memory-lancedb/openclaw.plugin.json | 2 +- extensions/memory-lancedb/package.json | 2 +- extensions/minimax-portal-auth/package.json | 2 +- extensions/msteams/package.json | 2 +- extensions/nextcloud-talk/package.json | 2 +- extensions/nostr/package.json | 2 +- extensions/open-prose/package.json | 2 +- extensions/signal/package.json | 2 +- extensions/slack/package.json | 2 +- extensions/synology-chat/package.json | 2 +- extensions/telegram/package.json | 2 +- extensions/tlon/package.json | 2 +- extensions/twitch/package.json | 2 +- extensions/voice-call/package.json | 2 +- extensions/voice-call/src/cli.ts | 2 +- extensions/voice-call/src/core-bridge.ts | 2 +- extensions/voice-call/src/manager.ts | 2 +- extensions/whatsapp/package.json | 2 +- extensions/zalo/package.json | 2 +- extensions/zalouser/package.json | 2 +- openclaw.mjs | 2 +- package.json | 9 +- packages/clawdbot/package.json | 2 +- packages/moltbot/package.json | 2 +- scripts/deploy.sh | 8 +- src/cli/TESTING_EDGE_CASE_MATRIX.md | 52 ++++++------ src/cli/argv.test.ts | 50 +++++------ src/cli/argv.ts | 4 +- src/cli/banner.ts | 30 +++---- src/cli/bootstrap-external.test.ts | 40 +++++---- src/cli/cli-name.test.ts | 10 +-- src/cli/cli-name.ts | 4 +- src/cli/profile-utils.test.ts | 2 +- src/cli/profile.test.ts | 56 ++++++------- src/cli/profile.ts | 12 +-- src/cli/program/command-registry.ts | 2 +- src/cli/program/help.ts | 9 +- src/cli/program/register.bootstrap.ts | 7 +- src/cli/respawn-policy.test.ts | 6 +- src/cli/run-main.test.ts | 50 +++++------ src/cli/run-main.ts | 20 ++--- src/cli/tagline.ts | 4 +- src/cli/windows-argv.test.ts | 2 +- src/cli/workspace-seed.test.ts | 10 +-- src/cli/workspace-seed.ts | 22 ++--- src/config/paths.ts | 4 +- src/config/types.gateway.ts | 4 +- src/entry.ts | 16 ++-- src/infra/runtime-guard.ts | 2 +- src/version.ts | 2 +- 107 files changed, 587 insertions(+), 579 deletions(-) rename .cursor/plans/{ironclaw_frontend_split_1c02d591.plan.md => denchclaw_frontend_split_1c02d591.plan.md} (66%) rename .cursor/plans/{gateway-ws_ironclaw_lock_0576496f.plan.md => gateway-ws_denchclaw_lock_0576496f.plan.md} (80%) create mode 100644 extensions/denchclaw-auth/README.md rename extensions/{ironclaw-auth => denchclaw-auth}/index.ts (72%) rename extensions/{ironclaw-auth => denchclaw-auth}/oauth.ts (91%) rename extensions/{ironclaw-auth => denchclaw-auth}/openclaw.plugin.json (65%) delete mode 100644 extensions/ironclaw-auth/README.md diff --git a/.cursor/plans/bootstrap_dev_testing_0b5817e5.plan.md b/.cursor/plans/bootstrap_dev_testing_0b5817e5.plan.md index 9db9264fac8..db7b4d46ba4 100644 --- a/.cursor/plans/bootstrap_dev_testing_0b5817e5.plan.md +++ b/.cursor/plans/bootstrap_dev_testing_0b5817e5.plan.md @@ -1,18 +1,18 @@ --- name: Bootstrap dev testing -overview: Remove local OpenClaw paths from the web app, always use global `openclaw` binary, rename dev scripts to `ironclaw`, and verify bootstrap works standalone. +overview: Remove local OpenClaw paths from the web app, always use global `openclaw` binary, rename dev scripts to `denchclaw`, and verify bootstrap works standalone. todos: - id: remove-local-openclaw-agent-runner - content: Remove resolvePackageRoot, resolveOpenClawLaunch, IRONCLAW_USE_LOCAL_OPENCLAW from agent-runner.ts; spawn global `openclaw` directly + content: Remove resolvePackageRoot, resolveOpenClawLaunch, DENCHCLAW_USE_LOCAL_OPENCLAW from agent-runner.ts; spawn global `openclaw` directly status: completed - id: remove-local-openclaw-subagent-runs content: Remove local script paths from subagent-runs.ts (sendGatewayAbortForSubagent, spawnSubagentMessage); use global `openclaw` instead status: completed - id: rename-pnpm-scripts - content: Rename `pnpm openclaw` to `pnpm ironclaw` and `openclaw:rpc` to `ironclaw:rpc` in package.json + content: Rename `pnpm openclaw` to `pnpm denchclaw` and `openclaw:rpc` to `denchclaw:rpc` in package.json status: completed - id: update-agent-runner-tests - content: "Update agent-runner.test.ts: remove resolvePackageRoot tests, IRONCLAW_USE_LOCAL_OPENCLAW, update spawn assertions" + content: "Update agent-runner.test.ts: remove resolvePackageRoot tests, DENCHCLAW_USE_LOCAL_OPENCLAW, update spawn assertions" status: completed - id: verify-builds-pass content: Verify pnpm build, pnpm web:build, and workspace tests pass after changes @@ -20,16 +20,16 @@ todos: isProject: false --- -# IronClaw Bootstrap: Clean Separation and Dev Testing +# DenchClaw Bootstrap: Clean Separation and Dev Testing ## Architecture -IronClaw is a frontend/UI/skills layer. OpenClaw is a separate, globally-installed runtime. IronClaw should NEVER bundle or run a local copy of OpenClaw. +DenchClaw is a frontend/UI/skills layer. OpenClaw is a separate, globally-installed runtime. DenchClaw should NEVER bundle or run a local copy of OpenClaw. ```mermaid flowchart TD - npx["npx ironclaw (or ironclaw)"] --> entry["openclaw.mjs → dist/entry.js"] - entry --> runMain["run-main.ts: bare ironclaw → bootstrap"] + npx["npx denchclaw (or denchclaw)"] --> entry["openclaw.mjs → dist/entry.js"] + entry --> runMain["run-main.ts: bare denchclaw → bootstrap"] runMain --> delegate{"primary == bootstrap?"} delegate -->|yes, keep local| bootstrap["bootstrapCommand()"] delegate -->|no, delegate| globalOC["spawn openclaw ...args"] @@ -45,7 +45,7 @@ flowchart TD The bootstrap flow is correctly wired: -- Bare `ironclaw` rewrites to `ironclaw bootstrap` +- Bare `denchclaw` rewrites to `denchclaw bootstrap` - `bootstrap` is never delegated to global `openclaw` - `bootstrapCommand` calls `ensureOpenClawCliAvailable` which prompts to install - Onboarding sets `gateway.webApp.enabled: true` @@ -54,22 +54,22 @@ The bootstrap flow is correctly wired: ## Problem 1: Local OpenClaw paths in web app (must remove) -`[apps/web/lib/agent-runner.ts](apps/web/lib/agent-runner.ts)` has `resolveOpenClawLaunch` which, when `IRONCLAW_USE_LOCAL_OPENCLAW=1`, resolves a local `scripts/run-node.mjs` or `openclaw.mjs` and spawns it with `node`. This contradicts the architecture: IronClaw should always spawn the global `openclaw` binary. +`[apps/web/lib/agent-runner.ts](apps/web/lib/agent-runner.ts)` has `resolveOpenClawLaunch` which, when `DENCHCLAW_USE_LOCAL_OPENCLAW=1`, resolves a local `scripts/run-node.mjs` or `openclaw.mjs` and spawns it with `node`. This contradicts the architecture: DenchClaw should always spawn the global `openclaw` binary. The same pattern exists in `[apps/web/lib/subagent-runs.ts](apps/web/lib/subagent-runs.ts)` where `sendGatewayAbortForSubagent` and `spawnSubagentMessage` hardcode `node ` paths. **Fix:** -- Remove `IRONCLAW_USE_LOCAL_OPENCLAW`, `resolveOpenClawLaunch`, `resolvePackageRoot`, and `OpenClawLaunch` type from `agent-runner.ts` +- Remove `DENCHCLAW_USE_LOCAL_OPENCLAW`, `resolveOpenClawLaunch`, `resolvePackageRoot`, and `OpenClawLaunch` type from `agent-runner.ts` - All spawn calls become `spawn("openclaw", [...args], { env, stdio })` - In `subagent-runs.ts`: replace `node gateway call ...` with `openclaw gateway call ...` - Remove `resolvePackageRoot` import from `subagent-runs.ts` ## Problem 2: `pnpm openclaw` script name is wrong -`package.json` has `"openclaw": "node scripts/run-node.mjs"`. This repo IS IronClaw, not OpenClaw. +`package.json` has `"openclaw": "node scripts/run-node.mjs"`. This repo IS DenchClaw, not OpenClaw. -**Fix:** Rename to `"ironclaw": "node scripts/run-node.mjs"`. Also `"openclaw:rpc"` to `"ironclaw:rpc"`. +**Fix:** Rename to `"denchclaw": "node scripts/run-node.mjs"`. Also `"openclaw:rpc"` to `"denchclaw:rpc"`. ## Dev workflow (after fixes) @@ -77,11 +77,11 @@ The same pattern exists in `[apps/web/lib/subagent-runs.ts](apps/web/lib/subagen # Prerequisite: install OpenClaw globally (one-time) npm install -g openclaw -# Run IronClaw bootstrap (installs/configures everything, opens UI) -pnpm ironclaw +# Run DenchClaw bootstrap (installs/configures everything, opens UI) +pnpm denchclaw # Or for web UI dev only: -openclaw --profile ironclaw gateway --port 18789 # Terminal 1 +openclaw --profile denchclaw gateway --port 18789 # Terminal 1 pnpm web:dev # Terminal 2 ``` @@ -131,7 +131,7 @@ spawn("openclaw", ["gateway", "call", ...], { env: process.env, ... }); ### 3. Update agent-runner.test.ts -- Remove `process.env.IRONCLAW_USE_LOCAL_OPENCLAW = "1"` from `beforeEach` +- Remove `process.env.DENCHCLAW_USE_LOCAL_OPENCLAW = "1"` from `beforeEach` - Remove entire `resolvePackageRoot` describe block (~5 tests) - The "uses global openclaw by default" test becomes the only spawn behavior test - Update mock assertions: command is always `"openclaw"`, no `prefixArgs` @@ -141,6 +141,6 @@ spawn("openclaw", ["gateway", "call", ...], { env: process.env, ... }); ```diff - "openclaw": "node scripts/run-node.mjs", - "openclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json", -+ "ironclaw": "node scripts/run-node.mjs", -+ "ironclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json", ++ "denchclaw": "node scripts/run-node.mjs", ++ "denchclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json", ``` diff --git a/.cursor/plans/ironclaw_frontend_split_1c02d591.plan.md b/.cursor/plans/denchclaw_frontend_split_1c02d591.plan.md similarity index 66% rename from .cursor/plans/ironclaw_frontend_split_1c02d591.plan.md rename to .cursor/plans/denchclaw_frontend_split_1c02d591.plan.md index e921f9bfba9..323802abbe7 100644 --- a/.cursor/plans/ironclaw_frontend_split_1c02d591.plan.md +++ b/.cursor/plans/denchclaw_frontend_split_1c02d591.plan.md @@ -1,12 +1,12 @@ --- -name: ironclaw_frontend_split -overview: Re-architect IronClaw into a separate frontend/bootstrap CLI that runs on top of OpenClaw, while preserving current IronClaw UX/features through compatibility adapters and phased cutover. Keep OpenClaw Gateway on its standard port and expose IronClaw UI on localhost:3100 with user-approved OpenClaw updates. +name: denchclaw_frontend_split +overview: Re-architect DenchClaw into a separate frontend/bootstrap CLI that runs on top of OpenClaw, while preserving current DenchClaw UX/features through compatibility adapters and phased cutover. Keep OpenClaw Gateway on its standard port and expose DenchClaw UI on localhost:3100 with user-approved OpenClaw updates. todos: - id: freeze-migration-contract-tests content: Add migration contract tests covering stream-json, session subscribe, profile/workspace resolution, and Dench always-on skill behavior status: completed - - id: build-ironclaw-bootstrap-layer - content: Implement IronClaw bootstrap path that verifies/installs OpenClaw, runs onboard --install-daemon for profile ironclaw, and launches UI on 3100 with explicit update approval + - id: build-denchclaw-bootstrap-layer + content: Implement DenchClaw bootstrap path that verifies/installs OpenClaw, runs onboard --install-daemon for profile denchclaw, and launches UI on 3100 with explicit update approval status: completed - id: extract-gateway-stream-client content: Extract reusable gateway streaming client from agent-via-gateway and wire web chat APIs to it instead of spawning CLI processes @@ -14,8 +14,8 @@ todos: - id: unify-profile-storage-paths content: Align apps/web workspace and web-chat storage resolution with src/config/paths + src/cli/profile semantics and add migration for existing UI state status: completed - - id: externalize-ironclaw-product-layer - content: Move IronClaw prompt/skill packaging out of core defaults into a product adapter/skill pack while preserving inject behavior + - id: externalize-denchclaw-product-layer + content: Move DenchClaw prompt/skill packaging out of core defaults into a product adapter/skill pack while preserving inject behavior status: completed - id: harden-onboarding-and-rollout content: Add first-run diagnostics, side-by-side safety checks, staged feature flags, and fallback path before full cutover @@ -23,30 +23,30 @@ todos: isProject: false --- -# IronClaw Frontend-Only Rewrite (No-Break Migration) +# DenchClaw Frontend-Only Rewrite (No-Break Migration) ## Locked Decisions -- Runtime topology: OpenClaw Gateway stays on its normal port (default `18789`), IronClaw UI runs on `3100`. +- Runtime topology: OpenClaw Gateway stays on its normal port (default `18789`), DenchClaw UI runs on `3100`. - Update policy: install OpenClaw once, then update only when user explicitly approves. ## Target Architecture ```mermaid flowchart LR - ironclawCli[IronclawCLI] --> bootstrapManager[BootstrapManager] + denchclawCli[DenchClawCLI] --> bootstrapManager[BootstrapManager] bootstrapManager --> openclawCli[OpenClawCLI] - bootstrapManager --> ironclawProfile[IronclawProfileState] - ironclawUi[IronclawUI3100] --> gatewayWs[GatewayWS18789] + bootstrapManager --> denchclawProfile[DenchClawProfileState] + denchclawUi[DenchClawUI3100] --> gatewayWs[GatewayWS18789] gatewayWs --> openclawCore[OpenClawCore] openclawCore --> workspaceData[WorkspaceAndChatStorage] - ironclawSkills[IronclawSkillsPack] --> openclawCore + denchclawSkills[DenchClawSkillsPack] --> openclawCore ``` ## Why This Rewrite Is Needed (from current code) - Web chat currently spawns the CLI directly in `[apps/web/lib/agent-runner.ts](apps/web/lib/agent-runner.ts)` (`openclaw.mjs` + `--stream-json`), which tightly couples UI and CLI process model. -- IronClaw product content is hardcoded in core prompt generation in `[src/agents/system-prompt.ts](src/agents/system-prompt.ts)` (`buildIronclawSection`). +- DenchClaw product content is hardcoded in core prompt generation in `[src/agents/system-prompt.ts](src/agents/system-prompt.ts)` (`buildDenchClawSection`). - Web workspace/profile logic in `[apps/web/lib/workspace.ts](apps/web/lib/workspace.ts)` is not aligned with core state-dir resolution in `[src/config/paths.ts](src/config/paths.ts)` and profile env wiring in `[src/cli/profile.ts](src/cli/profile.ts)`. - Bootstrapping and daemon install logic already exists and should be reused, not forked: `[src/commands/onboard.ts](src/commands/onboard.ts)`, `[src/wizard/onboarding.finalize.ts](src/wizard/onboarding.finalize.ts)`, `[src/commands/daemon-install-helpers.ts](src/commands/daemon-install-helpers.ts)`. @@ -54,18 +54,18 @@ flowchart LR ## Phase 1: Freeze Behavior With Contract Tests -- Add regression tests that codify current IronClaw-critical behavior before changing architecture: +- Add regression tests that codify current DenchClaw-critical behavior before changing architecture: - stream transport + session subscribe behavior (`--stream-json`, `--subscribe-session-key`) from `[src/cli/program/register.agent.ts](src/cli/program/register.agent.ts)` and `[src/commands/agent-via-gateway.ts](src/commands/agent-via-gateway.ts)`. - workspace/profile + web-chat path behavior from `[apps/web/lib/workspace.ts](apps/web/lib/workspace.ts)` and `[apps/web/lib/workspace-profiles.test.ts](apps/web/lib/workspace-profiles.test.ts)`. - always-on injected skill behavior for Dench skill loading. - Produce a “must-pass” migration suite so we can safely refactor internals without user-visible regressions. -## Phase 2: Create IronClaw Bootstrap Layer (Separate CLI Behavior) +## Phase 2: Create DenchClaw Bootstrap Layer (Separate CLI Behavior) -- Introduce a bootstrap command path for `ironclaw` that: +- Introduce a bootstrap command path for `denchclaw` that: - verifies OpenClaw availability; - installs OpenClaw if missing (first-run flow); - - runs onboarding (`openclaw --profile ironclaw onboard --install-daemon`); + - runs onboarding (`openclaw --profile denchclaw onboard --install-daemon`); - starts/opens UI at `http://localhost:3100`. - Reuse existing onboarding/daemon machinery instead of duplicating logic in a second stack: - `[src/commands/onboard.ts](src/commands/onboard.ts)` @@ -87,19 +87,19 @@ flowchart LR - Replace web-only state resolution logic with shared core semantics from `[src/config/paths.ts](src/config/paths.ts)` and profile env behavior from `[src/cli/profile.ts](src/cli/profile.ts)`. - Normalize chat/workspace storage to profile-scoped OpenClaw state consistently (no split-brain between `~/.openclaw-*` and `~/.openclaw/web-chat-*` behaviors). -- Add one-time migration for existing `.ironclaw-ui-state.json` / web-chat index data to the new canonical profile paths. +- Add one-time migration for existing `.denchclaw-ui-state.json` / web-chat index data to the new canonical profile paths. -## Phase 5: Move IronClaw Product Layer Outside Core +## Phase 5: Move DenchClaw Product Layer Outside Core -- Externalize IronClaw-specific identity/prompt sections currently in `[src/agents/system-prompt.ts](src/agents/system-prompt.ts)` behind a product adapter/config hook. -- Move Dench/IronClaw always-on skill packaging out of core bundled defaults and load it as IronClaw-provided skill pack. -- Keep `inject` capability in core, but remove hardcoded IronClaw assumptions from default OpenClaw prompt path. +- Externalize DenchClaw-specific identity/prompt sections currently in `[src/agents/system-prompt.ts](src/agents/system-prompt.ts)` behind a product adapter/config hook. +- Move Dench/DenchClaw always-on skill packaging out of core bundled defaults and load it as DenchClaw-provided skill pack. +- Keep `inject` capability in core, but remove hardcoded DenchClaw assumptions from default OpenClaw prompt path. ## Phase 6: Onboarding UX Hardening (Zero-Conf Side-by-Side) -- First-run checklist in IronClaw bootstrap: +- First-run checklist in DenchClaw bootstrap: - OpenClaw installed and version shown - - profile verified (`ironclaw`) + - profile verified (`denchclaw`) - gateway reachable - UI reachable at `3100` - clear remediation output for port/token/device mismatch @@ -116,7 +116,7 @@ flowchart LR ## Definition of Done -- `npx ironclaw` bootstraps OpenClaw (if missing), runs guided onboarding, and reliably opens/serves UI on `localhost:3100`. -- IronClaw runs alongside default OpenClaw without daemon/profile/token collisions. +- `npx denchclaw` bootstraps OpenClaw (if missing), runs guided onboarding, and reliably opens/serves UI on `localhost:3100`. +- DenchClaw runs alongside default OpenClaw without daemon/profile/token collisions. - Stream, workspaces, always-on skills, and storage features remain intact during and after migration. -- OpenClaw upgrades do not break IronClaw because integration is through stable gateway/CLI interfaces, not forked internals. +- OpenClaw upgrades do not break DenchClaw because integration is through stable gateway/CLI interfaces, not forked internals. diff --git a/.cursor/plans/gateway-ws_ironclaw_lock_0576496f.plan.md b/.cursor/plans/gateway-ws_denchclaw_lock_0576496f.plan.md similarity index 80% rename from .cursor/plans/gateway-ws_ironclaw_lock_0576496f.plan.md rename to .cursor/plans/gateway-ws_denchclaw_lock_0576496f.plan.md index 666c7b0d0eb..e2756845016 100644 --- a/.cursor/plans/gateway-ws_ironclaw_lock_0576496f.plan.md +++ b/.cursor/plans/gateway-ws_denchclaw_lock_0576496f.plan.md @@ -1,6 +1,6 @@ --- -name: gateway-ws ironclaw lock -overview: Migrate `apps/web` chat transport from CLI `--stream-json` processes to Gateway WebSocket while preserving the existing SSE API contract, then lock web to a single `ironclaw` profile and disable workspace/profile switching (403 for disabled APIs). Add targeted web and bootstrap tests for the new behavior. +name: gateway-ws denchclaw lock +overview: Migrate `apps/web` chat transport from CLI `--stream-json` processes to Gateway WebSocket while preserving the existing SSE API contract, then lock web to a single `denchclaw` profile and disable workspace/profile switching (403 for disabled APIs). Add targeted web and bootstrap tests for the new behavior. todos: - id: ws-transport-adapter content: Implement Gateway WebSocket-backed AgentProcessHandle adapter in apps/web/lib/agent-runner.ts while keeping existing NDJSON event contract. @@ -9,19 +9,19 @@ todos: content: Swap abort and subagent follow-up CLI gateway calls to WebSocket RPC calls in active-runs/subagent-runs. status: completed - id: profile-default-lock - content: Default web runtime profile resolution to ironclaw in workspace.ts and ensure state/web-chat/workspace paths resolve under ~/.openclaw-ironclaw. + content: Default web runtime profile resolution to denchclaw in workspace.ts and ensure state/web-chat/workspace paths resolve under ~/.openclaw-denchclaw. status: completed - id: api-lockdown - content: Return 403 for profile/workspace mutation APIs and keep /api/profiles compatible with a single ironclaw profile payload. + content: Return 403 for profile/workspace mutation APIs and keep /api/profiles compatible with a single denchclaw profile payload. status: completed - id: ui-single-profile content: Remove profile switch/create workspace controls from sidebars and empty state; clean workspace page wiring accordingly. status: completed - id: dench-path-update - content: Update skills/dench/SKILL.md workspace path references to ~/.openclaw-ironclaw/workspace. + content: Update skills/dench/SKILL.md workspace path references to ~/.openclaw-denchclaw/workspace. status: completed - id: web-tests - content: Update/add apps/web tests covering WS transport behavior, API lock responses, and ironclaw path resolution. + content: Update/add apps/web tests covering WS transport behavior, API lock responses, and denchclaw path resolution. status: completed - id: bootstrap-tests content: Add src/cli tests for run-main bootstrap cutover logic and bootstrap-external diagnostics behavior. @@ -29,13 +29,13 @@ todos: isProject: false --- -# Migrate Web Chat to Gateway WS + Lock Ironclaw Profile +# Migrate Web Chat to Gateway WS + Lock DenchClaw Profile ## Final behavior - Keep frontend transport unchanged (`/api/chat` + `/api/chat/stream` SSE contract remains intact). - Replace backend CLI stream/process transport with Gateway WebSocket transport. -- Force single-profile behavior in web runtime (`ironclaw`), so workspace/chat/session paths resolve to `~/.openclaw-ironclaw/*`. +- Force single-profile behavior in web runtime (`denchclaw`), so workspace/chat/session paths resolve to `~/.openclaw-denchclaw/*`. - Disable profile/workspace mutation endpoints with `403` (`/api/profiles/switch`, `/api/workspace/init`). - Remove/disable UI controls for profile switching and workspace creation. @@ -53,16 +53,16 @@ isProject: false ## Profile/path locking -- Update profile resolution in `[apps/web/lib/workspace.ts](apps/web/lib/workspace.ts)` so web runtime defaults to `ironclaw` (without changing test-mode assumptions), ensuring state dir resolves to `~/.openclaw-ironclaw` unless explicitly overridden. +- Update profile resolution in `[apps/web/lib/workspace.ts](apps/web/lib/workspace.ts)` so web runtime defaults to `denchclaw` (without changing test-mode assumptions), ensuring state dir resolves to `~/.openclaw-denchclaw` unless explicitly overridden. - Keep filesystem resolvers (`resolveOpenClawStateDir`, `resolveWebChatDir`, `resolveWorkspaceRoot`) as the single source of truth used by chat/session/tree APIs. -- Update watcher ignore path in `[apps/web/next.config.ts](apps/web/next.config.ts)` to include ironclaw state dir. +- Update watcher ignore path in `[apps/web/next.config.ts](apps/web/next.config.ts)` to include denchclaw state dir. ## Disable profile/workspace mutation surfaces - Return `403` in: - `[apps/web/app/api/profiles/switch/route.ts](apps/web/app/api/profiles/switch/route.ts)` - `[apps/web/app/api/workspace/init/route.ts](apps/web/app/api/workspace/init/route.ts)` -- Make `[apps/web/app/api/profiles/route.ts](apps/web/app/api/profiles/route.ts)` return a single effective `ironclaw` profile payload for UI compatibility. +- Make `[apps/web/app/api/profiles/route.ts](apps/web/app/api/profiles/route.ts)` return a single effective `denchclaw` profile payload for UI compatibility. ## UI updates (single-profile UX) @@ -75,7 +75,7 @@ isProject: false ## Dench skill path update -- Replace `~/.openclaw/workspace` references with `~/.openclaw-ironclaw/workspace` in `[skills/dench/SKILL.md](skills/dench/SKILL.md)`. +- Replace `~/.openclaw/workspace` references with `~/.openclaw-denchclaw/workspace` in `[skills/dench/SKILL.md](skills/dench/SKILL.md)`. ## Tests to add/update @@ -86,7 +86,7 @@ isProject: false - update `[apps/web/app/api/profiles/route.test.ts](apps/web/app/api/profiles/route.test.ts)` for single-profile payload and `403` switch behavior. - update `[apps/web/app/api/workspace/init/route.test.ts](apps/web/app/api/workspace/init/route.test.ts)` for `403` lock behavior. - Path behavior tests: - - add/adjust targeted assertions in workspace resolver tests for ironclaw state/web-chat/workspace directories. + - add/adjust targeted assertions in workspace resolver tests for denchclaw state/web-chat/workspace directories. - Bootstrap tests (new): - add `src/cli` tests for rollout/cutover behavior in `[src/cli/run-main.ts](src/cli/run-main.ts)`. - add diagnostics/rollout gate tests for `[src/cli/bootstrap-external.ts](src/cli/bootstrap-external.ts)` exported helpers. @@ -109,4 +109,4 @@ sse --> chatPanel - Run web tests for changed areas (`agent-runner`, `active-runs`, chat API, profiles/workspace-init API). - Run bootstrap-focused tests for `src/cli/run-main.ts` and `src/cli/bootstrap-external.ts`. -- Smoke-check workspace tree and web sessions resolve under `~/.openclaw-ironclaw` with switching/creation controls disabled. +- Smoke-check workspace tree and web sessions resolve under `~/.openclaw-denchclaw` with switching/creation controls disabled. diff --git a/.cursor/plans/strict-external-openclaw_7c0d1717.plan.md b/.cursor/plans/strict-external-openclaw_7c0d1717.plan.md index dc5a8a469c5..6a0ebcd7dea 100644 --- a/.cursor/plans/strict-external-openclaw_7c0d1717.plan.md +++ b/.cursor/plans/strict-external-openclaw_7c0d1717.plan.md @@ -1,15 +1,15 @@ --- name: strict-external-openclaw -overview: Convert this repo into an IronClaw-only package that uses globally installed `openclaw` as an external runtime, with strict removal of bundled OpenClaw core source and full cutover of CLI/web flows to external contracts (CLI + gateway protocol). +overview: Convert this repo into an DenchClaw-only package that uses globally installed `openclaw` as an external runtime, with strict removal of bundled OpenClaw core source and full cutover of CLI/web flows to external contracts (CLI + gateway protocol). todos: - - id: ironclaw-boundary-definition - content: Lock IronClaw-only module boundary and mark all OpenClaw-owned code paths for removal + - id: denchclaw-boundary-definition + content: Lock DenchClaw-only module boundary and mark all OpenClaw-owned code paths for removal status: completed - id: remove-cross-imports - content: Eliminate `apps/web` and `ui` internal imports of local OpenClaw source by replacing with IronClaw-local adapters over CLI/gateway contracts + content: Eliminate `apps/web` and `ui` internal imports of local OpenClaw source by replacing with DenchClaw-local adapters over CLI/gateway contracts status: completed - id: cli-delegation-cutover - content: Implement IronClaw command delegation to global `openclaw` for non-bootstrap commands + content: Implement DenchClaw command delegation to global `openclaw` for non-bootstrap commands status: completed - id: peer-global-packaging content: Update package metadata/docs to enforce peer + global OpenClaw installation model @@ -18,7 +18,7 @@ todos: content: Remove OpenClaw core runtime source and obsolete shims/scripts from this repository status: completed - id: release-pipeline-realignment - content: Rework build/release checks to publish IronClaw-only artifacts with strict external OpenClaw dependency + content: Rework build/release checks to publish DenchClaw-only artifacts with strict external OpenClaw dependency status: completed - id: full-cutover-validation content: Run full test/smoke matrix and keep one-release emergency fallback @@ -30,10 +30,10 @@ isProject: false ## Goal -- Make this repository IronClaw-only. +- Make this repository DenchClaw-only. - Remove OpenClaw core runtime code from this repo. - Depend on globally installed `openclaw` (peer/global model), not bundled source. -- Keep IronClaw UX: `npx ironclaw` bootstrap + UI on `3100` over gateway `18789`. +- Keep DenchClaw UX: `npx denchclaw` bootstrap + UI on `3100` over gateway `18789`. Reference upstream runtime source of truth: [openclaw/openclaw](https://github.com/openclaw/openclaw). @@ -41,7 +41,7 @@ Reference upstream runtime source of truth: [openclaw/openclaw](https://github.c - No vendored OpenClaw core runtime in this repo after cutover. - `openclaw` consumed as global binary requirement (peer + global install), not shipped here. -- IronClaw must communicate with OpenClaw only via stable external contracts: +- DenchClaw must communicate with OpenClaw only via stable external contracts: - `openclaw` CLI commands - Gateway WebSocket protocol @@ -49,15 +49,15 @@ Reference upstream runtime source of truth: [openclaw/openclaw](https://github.c ```mermaid flowchart LR - ironclawCli[ironclawCli] --> bootstrap[bootstrapFlow] + denchclawCli[denchclawCli] --> bootstrap[bootstrapFlow] bootstrap --> openclawBin[globalOpenclawBin] - ironclawUi[ironclawUi3100] --> gatewayWs[gatewayWs18789] + denchclawUi[denchclawUi3100] --> gatewayWs[gatewayWs18789] gatewayWs --> openclawRuntime[openclawRuntimeExternal] ``` -## Phase 1: Define IronClaw-Only Boundary +## Phase 1: Define DenchClaw-Only Boundary -- Keep only IronClaw-owned surfaces: +- Keep only DenchClaw-owned surfaces: - product layer and branding - bootstrap/orchestration CLI - web UI and workspace UX @@ -72,14 +72,14 @@ flowchart LR ## Phase 2: Replace Internal Core Imports With External Contracts - Remove all `apps/web` / `ui` imports that currently reach into local OpenClaw source internals. -- Re-implement required behavior in IronClaw-local adapters using gateway protocol + local helpers. +- Re-implement required behavior in DenchClaw-local adapters using gateway protocol + local helpers. - First critical edge: - [apps/web/lib/agent-runner.ts](apps/web/lib/agent-runner.ts) - Also migrate `ui/src/ui/**` consumers that import `../../../../src/*` internals. ## Phase 3: CLI Delegation Model -- Make IronClaw CLI own only bootstrap/product UX. +- Make DenchClaw CLI own only bootstrap/product UX. - Delegate non-bootstrap command execution to global `openclaw` binary. - Keep rollout/fallback env gates while switching default to external execution. - Primary files: @@ -89,7 +89,7 @@ flowchart LR ## Phase 4: Package + Dependency Model (Peer + Global) -- Update package metadata so IronClaw does not bundle OpenClaw runtime code. +- Update package metadata so DenchClaw does not bundle OpenClaw runtime code. - Add peer requirement/documentation for global `openclaw` presence. - Ensure bootstrap validates and remediates missing global CLI (`npm i -g openclaw`). - Primary files: @@ -100,7 +100,7 @@ flowchart LR ## Phase 5: Remove OpenClaw Core Source From Repo - Delete OpenClaw-owned runtime modules from this repository once delegation and adapters are complete. -- Retain only IronClaw package code and tests. +- Retain only DenchClaw package code and tests. - Remove obsolete build/release scripts that assume monolithic runtime shipping. - Primary files/areas: - `src/` (OpenClaw runtime portions) @@ -109,7 +109,7 @@ flowchart LR ## Phase 6: Build/Release Pipeline Realignment -- Adjust build outputs to ship IronClaw only. +- Adjust build outputs to ship DenchClaw only. - Remove checks that require bundled OpenClaw dist artifacts. - Keep web standalone packaging + bootstrap checks. - Primary files: @@ -123,10 +123,10 @@ flowchart LR - Unit/e2e coverage for: - bootstrap diagnostics and remediation - command delegation to global `openclaw` - - gateway streaming from IronClaw UI + - gateway streaming from DenchClaw UI - End-to-end smoke: - clean machine with only global `openclaw` - - `npx ironclaw` bootstrap succeeds + - `npx denchclaw` bootstrap succeeds - UI works on `3100`, gateway on `18789`, no profile/daemon collisions. ## Rollout Safety diff --git a/.cursor/plans/workspace_profile_support_7e8600ec.plan.md b/.cursor/plans/workspace_profile_support_7e8600ec.plan.md index 4f76fc75b91..014ad30b1db 100644 --- a/.cursor/plans/workspace_profile_support_7e8600ec.plan.md +++ b/.cursor/plans/workspace_profile_support_7e8600ec.plan.md @@ -1,6 +1,6 @@ --- name: Workspace profile support -overview: Add full workspace profile and custom path support to the Ironclaw web app and the dench SKILL.md, so they respect OPENCLAW_PROFILE, OPENCLAW_HOME, OPENCLAW_STATE_DIR, and per-agent workspace config — matching the CLI's existing resolution logic. +overview: Add full workspace profile and custom path support to the DenchClaw web app and the dench SKILL.md, so they respect OPENCLAW_PROFILE, OPENCLAW_HOME, OPENCLAW_STATE_DIR, and per-agent workspace config — matching the CLI's existing resolution logic. todos: - id: centralize-helpers content: Add resolveOpenClawStateDir() to apps/web/lib/workspace.ts and update resolveWorkspaceRoot() with OPENCLAW_PROFILE + OPENCLAW_HOME + OPENCLAW_STATE_DIR support diff --git a/.github/labeler.yml b/.github/labeler.yml index 57be11d9048..329e34e4f14 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -212,10 +212,10 @@ - changed-files: - any-glob-to-any-file: - "extensions/google-gemini-cli-auth/**" -"extensions: ironclaw-auth": +"extensions: denchclaw-auth": - changed-files: - any-glob-to-any-file: - - "extensions/ironclaw-auth/**" + - "extensions/denchclaw-auth/**" "extensions: llm-task": - changed-files: - any-glob-to-any-file: diff --git a/README.md b/README.md index 47b6a50b0f3..b1419f42fa4 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@

- npm version + npm version Discord MIT License

- Website · Docs · OpenClaw Framework · Discord · Skills Store + Website · Docs · OpenClaw Framework · Discord · Skills Store

--- @@ -35,7 +35,7 @@ ```bash npm i -g openclaw -npx ironclaw +npx denchclaw ``` Opens at `localhost:3100`. That's it. @@ -44,15 +44,15 @@ Three steps total: ``` 1. npm i -g openclaw -2. npx ironclaw +2. npx denchclaw 3. bootstrap opens UI on localhost:3100 ``` --- -## What is Ironclaw? +## What is DenchClaw? -Ironclaw is a personal AI agent and CRM that runs locally on your machine. It connects to every messaging channel you use, manages structured data through DuckDB, browses the web with your Chrome profile, and gives you a full web UI for pipeline management, analytics, and document management. +DenchClaw is a personal AI agent and CRM that runs locally on your machine. It connects to every messaging channel you use, manages structured data through DuckDB, browses the web with your Chrome profile, and gives you a full web UI for pipeline management, analytics, and document management. Built on [OpenClaw](https://github.com/openclaw/openclaw) with **Vercel AI SDK v6** as the LLM orchestration layer. @@ -70,7 +70,7 @@ Built on [OpenClaw](https://github.com/openclaw/openclaw) with **Vercel AI SDK v ### Find Leads -Type a prompt, Ironclaw scrapes the web using your actual Chrome profile (all your auth sessions, cookies, history). It logs into LinkedIn, browses YC batches, pulls company data. No separate login, no API keys for browsing. +Type a prompt, DenchClaw scrapes the web using your actual Chrome profile (all your auth sessions, cookies, history). It logs into LinkedIn, browses YC batches, pulls company data. No separate login, no API keys for browsing. ### Enrich Data @@ -82,7 +82,7 @@ Personalized LinkedIn messages, cold emails, follow-up sequences. Each message i ### Analyze Pipeline -Ask for analytics in plain English. Ironclaw queries your DuckDB workspace and generates interactive Recharts dashboards inline. Pipeline funnels, outreach activity charts, conversion rates, donut breakdowns. +Ask for analytics in plain English. DenchClaw queries your DuckDB workspace and generates interactive Recharts dashboards inline. Pipeline funnels, outreach activity charts, conversion rates, donut breakdowns. ### Automate Everything @@ -94,11 +94,11 @@ Cron jobs that run on schedule. Follow-up if no reply after 3 days. Move leads t ### Uses Your Chrome Profile -Unlike other AI tools, Ironclaw copies your existing Chrome profile with all your auth sessions, cookies, and history. It logs into LinkedIn, scrapes YC batches, and sends messages as you. No separate browser login needed. +Unlike other AI tools, DenchClaw copies your existing Chrome profile with all your auth sessions, cookies, and history. It logs into LinkedIn, scrapes YC batches, and sends messages as you. No separate browser login needed. ### Chat with Your Database -Ask questions in plain English. Ironclaw translates to SQL, queries your local DuckDB, and returns structured results. Like having a data analyst on speed dial. +Ask questions in plain English. DenchClaw translates to SQL, queries your local DuckDB, and returns structured results. Like having a data analyst on speed dial. ``` You: "How many founders have we contacted from YC W26?" @@ -111,7 +111,7 @@ Reply rate is 34%. ### Coding Agent with Diffs -Ironclaw writes code. Review changes in a rich diff viewer before applying. Config changes, automation scripts, data transformations. All with diffs you approve. +DenchClaw writes code. Review changes in a rich diff viewer before applying. Config changes, automation scripts, data transformations. All with diffs you approve. ### Your Second Brain @@ -138,18 +138,18 @@ The web app runs at `localhost:3100` and includes: One agent, every channel. Connect any messaging platform. Your AI agent responds everywhere, managed from a single terminal. -| Channel | Setup | -| ------------------- | ------------------------------------------------------------- | -| **WhatsApp** | `ironclaw channels login` + set `channels.whatsapp.allowFrom` | -| **Telegram** | Set `TELEGRAM_BOT_TOKEN` or `channels.telegram.botToken` | -| **Slack** | Set `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` | -| **Discord** | Set `DISCORD_BOT_TOKEN` or `channels.discord.token` | -| **Signal** | Requires `signal-cli` + `channels.signal` config | -| **iMessage** | Via BlueBubbles (recommended) or legacy macOS integration | -| **Microsoft Teams** | Configure Teams app + Bot Framework | -| **Google Chat** | Chat API integration | -| **Matrix** | Extension channel | -| **WebChat** | Built-in, uses Gateway WebSocket directly | +| Channel | Setup | +| ------------------- | -------------------------------------------------------------- | +| **WhatsApp** | `denchclaw channels login` + set `channels.whatsapp.allowFrom` | +| **Telegram** | Set `TELEGRAM_BOT_TOKEN` or `channels.telegram.botToken` | +| **Slack** | Set `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` | +| **Discord** | Set `DISCORD_BOT_TOKEN` or `channels.discord.token` | +| **Signal** | Requires `signal-cli` + `channels.signal` config | +| **iMessage** | Via BlueBubbles (recommended) or legacy macOS integration | +| **Microsoft Teams** | Configure Teams app + Bot Framework | +| **Google Chat** | Chat API integration | +| **Matrix** | Extension channel | +| **WebChat** | Built-in, uses Gateway WebSocket directly | ``` WhatsApp · Telegram · Slack · Discord @@ -157,7 +157,7 @@ One agent, every channel. Connect any messaging platform. Your AI agent responds │ ▼ ┌────────────────────────────┐ - │ Ironclaw Gateway │ + │ DenchClaw Gateway │ │ ws://127.0.0.1:18789 │ └─────────────┬──────────────┘ │ @@ -165,7 +165,7 @@ One agent, every channel. Connect any messaging platform. Your AI agent responds │ │ │ ▼ ▼ ▼ AI SDK Web UI CLI - Engine (Dench) (ironclaw) + Engine (Dench) (denchclaw) ``` --- @@ -217,7 +217,7 @@ Reports use the `report-json` format and render inline in chat as interactive Re ## Kanban Pipeline -Drag-and-drop kanban boards that auto-update as leads reply. Ironclaw moves cards through your pipeline automatically. +Drag-and-drop kanban boards that auto-update as leads reply. DenchClaw moves cards through your pipeline automatically. Columns like New Lead → Contacted → Qualified → Demo Scheduled → Closed map to your sales process. Each card shows the lead name, company, and last action taken. @@ -243,7 +243,7 @@ Scheduled automations that run in the background: | CRM backup to S3 | `0 2 * * *` | Nightly workspace backup | ```bash -ironclaw cron list +denchclaw cron list ``` --- @@ -267,9 +267,9 @@ The Gateway is the local-first WebSocket control plane that routes everything: ### Security - **DM pairing** enabled by default. Unknown senders get a pairing code. -- Approve with `ironclaw pairing approve ` +- Approve with `denchclaw pairing approve ` - Non-main sessions can be sandboxed in Docker -- Run `ironclaw doctor` to audit DM policies +- Run `denchclaw doctor` to audit DM policies --- @@ -329,22 +329,22 @@ Features: ```bash # Install -npm i -g ironclaw +npm i -g denchclaw # Run onboarding wizard -ironclaw onboard --install-daemon +denchclaw onboard --install-daemon # Start the gateway -ironclaw gateway start +denchclaw gateway start # Open the web UI open http://localhost:3100 # Talk to your agent from CLI -ironclaw agent --message "Summarize my inbox" --thinking high +denchclaw agent --message "Summarize my inbox" --thinking high # Send a message -ironclaw message send --to +1234567890 --message "Hello from Ironclaw" +denchclaw message send --to +1234567890 --message "Hello from DenchClaw" ``` --- @@ -352,8 +352,8 @@ ironclaw message send --to +1234567890 --message "Hello from Ironclaw" ## From Source ```bash -git clone https://github.com/kumarabhirup/ironclaw.git -cd ironclaw +git clone https://github.com/kumarabhirup/denchclaw.git +cd denchclaw pnpm install pnpm build @@ -402,7 +402,7 @@ pnpm dev # Dev mode (auto-reload) ## Upstream -Ironclaw is built on [OpenClaw](https://github.com/openclaw/openclaw). To sync with upstream: +DenchClaw is built on [OpenClaw](https://github.com/openclaw/openclaw). To sync with upstream: ```bash git remote add upstream https://github.com/openclaw/openclaw.git @@ -416,8 +416,8 @@ git merge upstream/main MIT Licensed. Fork it, extend it, make it yours. -[![Star History Chart](https://api.star-history.com/image?repos=denchHQ/ironclaw&type=date&legend=top-left)](https://www.star-history.com/?repos=denchHQ%2Fironclaw&type=date&legend=top-left) +[![Star History Chart](https://api.star-history.com/image?repos=denchHQ/denchclaw&type=date&legend=top-left)](https://www.star-history.com/?repos=denchHQ%2Fdenchclaw&type=date&legend=top-left)

- GitHub stars + GitHub stars

diff --git a/apps/web/app/api/profiles/route.test.ts b/apps/web/app/api/profiles/route.test.ts index 010031c35ae..8ab5d8d7bda 100644 --- a/apps/web/app/api/profiles/route.test.ts +++ b/apps/web/app/api/profiles/route.test.ts @@ -3,7 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; vi.mock("@/lib/workspace", () => ({ discoverWorkspaces: vi.fn(() => []), getActiveWorkspaceName: vi.fn(() => null), - resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-ironclaw"), + resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-dench"), resolveWorkspaceRoot: vi.fn(() => null), setUIActiveWorkspace: vi.fn(), })); @@ -18,7 +18,7 @@ vi.mock("node:fs", () => ({ describe("profiles API", () => { const originalEnv = { ...process.env }; - const STATE_DIR = "/home/testuser/.openclaw-ironclaw"; + const STATE_DIR = "/home/testuser/.openclaw-dench"; beforeEach(() => { vi.resetModules(); diff --git a/apps/web/app/api/workspace/delete/route.test.ts b/apps/web/app/api/workspace/delete/route.test.ts index 68253e8aaa5..8f65606c870 100644 --- a/apps/web/app/api/workspace/delete/route.test.ts +++ b/apps/web/app/api/workspace/delete/route.test.ts @@ -51,7 +51,7 @@ describe("POST /api/workspace/delete", () => { vi.mocked(workspace.discoverWorkspaces).mockReturnValue([ { name: "work", - stateDir: "/home/testuser/.openclaw-ironclaw", + stateDir: "/home/testuser/.openclaw-dench", workspaceDir: null, isActive: false, hasConfig: true, @@ -65,13 +65,13 @@ describe("POST /api/workspace/delete", () => { it("deletes workspace directory directly via rmSync", async () => { const workspace = await import("@/lib/workspace"); const { rmSync } = await import("node:fs"); - const workspaceDir = "/home/testuser/.openclaw-ironclaw/workspace-work"; + const workspaceDir = "/home/testuser/.openclaw-dench/workspace-work"; vi.mocked(workspace.discoverWorkspaces) .mockReturnValueOnce([ { name: "work", - stateDir: "/home/testuser/.openclaw-ironclaw", + stateDir: "/home/testuser/.openclaw-dench", workspaceDir, isActive: true, hasConfig: true, @@ -98,12 +98,12 @@ describe("POST /api/workspace/delete", () => { it("returns 500 when rmSync fails", async () => { const workspace = await import("@/lib/workspace"); const { rmSync } = await import("node:fs"); - const workspaceDir = "/home/testuser/.openclaw-ironclaw/workspace-work"; + const workspaceDir = "/home/testuser/.openclaw-dench/workspace-work"; vi.mocked(workspace.discoverWorkspaces).mockReturnValue([ { name: "work", - stateDir: "/home/testuser/.openclaw-ironclaw", + stateDir: "/home/testuser/.openclaw-dench", workspaceDir, isActive: false, hasConfig: true, diff --git a/apps/web/app/api/workspace/init/route.test.ts b/apps/web/app/api/workspace/init/route.test.ts index 9090df75864..5ac5ef1e628 100644 --- a/apps/web/app/api/workspace/init/route.test.ts +++ b/apps/web/app/api/workspace/init/route.test.ts @@ -1,7 +1,7 @@ import { join } from "node:path"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -const STATE_DIR = "/home/testuser/.openclaw-ironclaw"; +const STATE_DIR = "/home/testuser/.openclaw-dench"; vi.mock("node:fs", () => ({ existsSync: vi.fn(() => false), @@ -17,12 +17,13 @@ vi.mock("@/lib/workspace", () => ({ discoverWorkspaces: vi.fn(() => []), setUIActiveWorkspace: vi.fn(), getActiveWorkspaceName: vi.fn(() => "work"), - resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-ironclaw"), + resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-dench"), resolveWorkspaceDirForName: vi.fn((name: string) => - join("/home/testuser/.openclaw-ironclaw", `workspace-${name}`), + join("/home/testuser/.openclaw-dench", `workspace-${name}`), ), isValidWorkspaceName: vi.fn(() => true), resolveWorkspaceRoot: vi.fn(() => null), + ensureAgentInConfig: vi.fn(), })); describe("POST /api/workspace/init", () => { @@ -83,7 +84,7 @@ describe("POST /api/workspace/init", () => { expect(response.status).toBe(409); }); - it("creates workspace directory at ~/.openclaw-ironclaw/workspace- (enforces fixed layout)", async () => { + it("creates workspace directory at ~/.openclaw-dench/workspace- (enforces fixed layout)", async () => { const { mkdirSync, writeFileSync } = await import("node:fs"); const workspace = await import("@/lib/workspace"); vi.mocked(workspace.discoverWorkspaces).mockReturnValue([]); @@ -158,7 +159,7 @@ describe("POST /api/workspace/init", () => { const raw = identityWrites[identityWrites.length - 1][1]; const identityContent = typeof raw === "string" ? raw : JSON.stringify(raw); expect(identityContent).toContain(expectedSkillPath); - expect(identityContent).toContain("Ironclaw"); + expect(identityContent).toContain("DenchClaw"); expect(identityContent).not.toContain("~skills"); }); }); diff --git a/apps/web/app/api/workspace/init/route.ts b/apps/web/app/api/workspace/init/route.ts index 46ed5200639..537e492c4e3 100644 --- a/apps/web/app/api/workspace/init/route.ts +++ b/apps/web/app/api/workspace/init/route.ts @@ -17,7 +17,7 @@ import { } from "@/lib/workspace"; import { seedWorkspaceFromAssets, - buildIronclawIdentity, + buildDenchClawIdentity, } from "@repo/cli/workspace-seed"; export const dynamic = "force-dynamic"; @@ -108,7 +108,7 @@ export async function POST(req: Request) { } if (body.path?.trim()) { return Response.json( - { error: "Custom workspace paths are currently disabled. Workspaces are created in ~/.openclaw-ironclaw." }, + { error: "Custom workspace paths are currently disabled. Workspaces are created in ~/.openclaw-dench." }, { status: 400 }, ); } @@ -163,7 +163,7 @@ export async function POST(req: Request) { } } - // Seed managed skills, Ironclaw identity, DuckDB, and CRM object projections. + // Seed managed skills, DenchClaw identity, DuckDB, and CRM object projections. // This is the single source of truth shared with the CLI bootstrap path. if (projectRoot) { const seedResult = seedWorkspaceFromAssets({ workspaceDir, packageRoot: projectRoot }); @@ -173,10 +173,10 @@ export async function POST(req: Request) { } } else { // No project root available (e.g. standalone/production build without - // the repo tree). Still write the Ironclaw identity so the agent has + // the repo tree). Still write the DenchClaw identity so the agent has // a usable IDENTITY.md. const identityPath = join(workspaceDir, "IDENTITY.md"); - writeFileSync(identityPath, buildIronclawIdentity(workspaceDir) + "\n", "utf-8"); + writeFileSync(identityPath, buildDenchClawIdentity(workspaceDir) + "\n", "utf-8"); seeded.push("IDENTITY.md"); } diff --git a/apps/web/app/api/workspace/thumbnail/route.ts b/apps/web/app/api/workspace/thumbnail/route.ts index 22b298ead14..89c832b4e8f 100644 --- a/apps/web/app/api/workspace/thumbnail/route.ts +++ b/apps/web/app/api/workspace/thumbnail/route.ts @@ -8,7 +8,7 @@ import { safeResolvePath } from "@/lib/workspace"; export const dynamic = "force-dynamic"; export const runtime = "nodejs"; -const THUMB_DIR = join(tmpdir(), "ironclaw-thumbs"); +const THUMB_DIR = join(tmpdir(), "denchclaw-thumbs"); mkdirSync(THUMB_DIR, { recursive: true }); /** diff --git a/apps/web/app/api/workspace/tree-browse.test.ts b/apps/web/app/api/workspace/tree-browse.test.ts index 083fd6cb9b9..d62e396d956 100644 --- a/apps/web/app/api/workspace/tree-browse.test.ts +++ b/apps/web/app/api/workspace/tree-browse.test.ts @@ -17,7 +17,7 @@ vi.mock("node:os", () => ({ // Mock workspace vi.mock("@/lib/workspace", () => ({ resolveWorkspaceRoot: vi.fn(() => null), - resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-ironclaw"), + resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-dench"), getActiveWorkspaceName: vi.fn(() => null), parseSimpleYaml: vi.fn(() => ({})), duckdbQueryAll: vi.fn(() => []), @@ -57,7 +57,7 @@ describe("Workspace Tree & Browse API", () => { })); vi.mock("@/lib/workspace", () => ({ resolveWorkspaceRoot: vi.fn(() => null), - resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-ironclaw"), + resolveOpenClawStateDir: vi.fn(() => "/home/testuser/.openclaw-dench"), getActiveWorkspaceName: vi.fn(() => null), parseSimpleYaml: vi.fn(() => ({})), duckdbQueryAll: vi.fn(() => []), diff --git a/apps/web/app/components/cron/cron-dashboard.tsx b/apps/web/app/components/cron/cron-dashboard.tsx index e21a5270767..e3405a3b679 100644 --- a/apps/web/app/components/cron/cron-dashboard.tsx +++ b/apps/web/app/components/cron/cron-dashboard.tsx @@ -197,7 +197,7 @@ export function CronDashboard({ }} >

- No cron jobs configured. Use ironclaw cron add to create one. + No cron jobs configured. Use denchclaw cron add to create one.

) : ( diff --git a/apps/web/app/components/sidebar.tsx b/apps/web/app/components/sidebar.tsx index c0a6b42dd33..57a886e1b64 100644 --- a/apps/web/app/components/sidebar.tsx +++ b/apps/web/app/components/sidebar.tsx @@ -406,7 +406,7 @@ export function Sidebar({

- Ironclaw + DenchClaw

diff --git a/apps/web/app/components/workspace/empty-state.tsx b/apps/web/app/components/workspace/empty-state.tsx index 7fe6c43a858..fa6dc4a37bb 100644 --- a/apps/web/app/components/workspace/empty-state.tsx +++ b/apps/web/app/components/workspace/empty-state.tsx @@ -89,7 +89,7 @@ export function EmptyState({ ) : ( <> The workspace directory was not - found. Run the ironclaw bootstrap flow or start a + found. Run the denchclaw bootstrap flow or start a conversation and the agent will set it up automatically in the managed profile. @@ -136,7 +136,7 @@ export function EmptyState({ > {expectedPath ? shortenPath(expectedPath) - : "~/.openclaw-ironclaw/workspace-"} + : "~/.openclaw-dench/workspace-"}
diff --git a/apps/web/app/components/workspace/profile-switcher.test.tsx b/apps/web/app/components/workspace/profile-switcher.test.tsx index dc00fa3f617..5ca46d9e29f 100644 --- a/apps/web/app/components/workspace/profile-switcher.test.tsx +++ b/apps/web/app/components/workspace/profile-switcher.test.tsx @@ -129,15 +129,15 @@ describe("ProfileSwitcher workspace delete action", () => { workspaces: [ { name: "ghost", - stateDir: "/home/testuser/.openclaw-ironclaw", + stateDir: "/home/testuser/.openclaw-dench", workspaceDir: null, isActive: true, hasConfig: true, }, { - name: "ironclaw", - stateDir: "/home/testuser/.openclaw-ironclaw", - workspaceDir: "/home/testuser/.openclaw-ironclaw/workspace", + name: "dench", + stateDir: "/home/testuser/.openclaw-dench", + workspaceDir: "/home/testuser/.openclaw-dench/workspace", isActive: false, hasConfig: true, }, @@ -154,12 +154,12 @@ describe("ProfileSwitcher workspace delete action", () => { }); await waitFor(() => { - expect(screen.getByText("ironclaw")).toBeInTheDocument(); + expect(screen.getByText("dench")).toBeInTheDocument(); expect(screen.queryByText("ghost")).not.toBeInTheDocument(); }); await user.click(screen.getByTitle("Switch workspace")); expect(screen.queryByTitle("Delete workspace ghost")).not.toBeInTheDocument(); - expect(screen.getByTitle("Delete workspace ironclaw")).toBeInTheDocument(); + expect(screen.getByTitle("Delete workspace dench")).toBeInTheDocument(); }); }); diff --git a/apps/web/app/components/workspace/workspace-sidebar.tsx b/apps/web/app/components/workspace/workspace-sidebar.tsx index 552d1384951..e6b3834c7e2 100644 --- a/apps/web/app/components/workspace/workspace-sidebar.tsx +++ b/apps/web/app/components/workspace/workspace-sidebar.tsx @@ -591,13 +591,13 @@ export function WorkspaceSidebar({ style={{ borderColor: "var(--color-border)" }} > - ironclaw.sh + denchclaw.sh
{onToggleHidden && ( diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 2c7f6138995..d5853c5fb85 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata, Viewport } from "next"; import "./globals.css"; export const metadata: Metadata = { - title: "Ironclaw", + title: "DenchClaw", description: "AI Workspace with an agent that connects to your apps and does the work for you", }; diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index ea68bd11104..b81e23f503f 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -37,7 +37,7 @@ const CLAW_ASCII = [ " ░ ░░ ", ]; -const IRONCLAW_ASCII = [ +const DENCHCLAW_ASCII = [ " ██╗██████╗ ██████╗ ███╗ ██╗ ██████╗██╗ █████╗ ██╗ ██╗", " ██║██╔══██╗██╔═══██╗████╗ ██║██╔════╝██║ ██╔══██╗██║ ██║", " ██║██████╔╝██║ ██║██╔██╗ ██║██║ ██║ ███████║██║ █╗ ██║", @@ -103,11 +103,11 @@ export default function Home() { {/* Foreground content */}
-
- {IRONCLAW_ASCII.join("\n")} +
+ {DENCHCLAW_ASCII.join("\n")}

- IRONCLAW + DENCHCLAW

{ describe("spawnAgentProcess", () => { it("connects via ws module with Origin header matching the gateway URL (prevents origin rejection)", async () => { const MockWs = installMockWsModule(); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; const { spawnAgentProcess } = await import("./agent-runner.js"); const proc = spawnAgentProcess("hello", "sess-1"); @@ -298,7 +298,7 @@ describe("agent-runner", () => { it("sets wss: origin to https: (prevents origin mismatch on TLS gateways)", async () => { const MockWs = installMockWsModule(); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; process.env.OPENCLAW_GATEWAY_URL = "wss://gateway.example.com:443"; const { spawnAgentProcess } = await import("./agent-runner.js"); @@ -313,7 +313,7 @@ describe("agent-runner", () => { it("falls back to config gateway port when env port is stale", async () => { const MockWs = installMockWsModule(); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; process.env.OPENCLAW_GATEWAY_PORT = "19001"; MockWs.failOpenForUrls.add("ws://127.0.0.1:19001/"); @@ -342,7 +342,7 @@ describe("agent-runner", () => { it("does not use child_process.spawn for WebSocket transport", async () => { installMockWsModule(); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; const { spawn: mockSpawn } = await import("node:child_process"); vi.mocked(mockSpawn).mockClear(); const { spawnAgentProcess } = await import("./agent-runner.js"); @@ -354,8 +354,8 @@ describe("agent-runner", () => { proc.kill("SIGTERM"); }); - it("falls back to CLI spawn when IRONCLAW_WEB_FORCE_LEGACY_STREAM is set", async () => { - process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM = "1"; + it("falls back to CLI spawn when DENCHCLAW_WEB_FORCE_LEGACY_STREAM is set", async () => { + process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM = "1"; const { spawn: mockSpawn } = await import("node:child_process"); const child = mockChildProcess(); vi.mocked(mockSpawn).mockReturnValue(child as unknown as ChildProcess); @@ -373,7 +373,7 @@ describe("agent-runner", () => { }); it("includes session-key and lane args in legacy CLI mode", async () => { - process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM = "1"; + process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM = "1"; const { spawn: mockSpawn } = await import("node:child_process"); const child = mockChildProcess(); vi.mocked(mockSpawn).mockReturnValue(child as unknown as ChildProcess); @@ -399,7 +399,7 @@ describe("agent-runner", () => { describe("spawnAgentSubscribeProcess", () => { it("subscribes via connect -> sessions.patch -> agent.subscribe", async () => { const MockWs = installMockWsModule(); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; const { spawnAgentSubscribeProcess } = await import("./agent-runner.js"); const proc = spawnAgentSubscribeProcess("agent:main:web:sess-sub", 12); @@ -426,7 +426,7 @@ describe("agent-runner", () => { it("uses payload.globalSeq (not frame seq) for cursor filtering", async () => { const MockWs = installMockWsModule(); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; const { spawnAgentSubscribeProcess } = await import("./agent-runner.js"); const proc = spawnAgentSubscribeProcess("agent:main:web:sess-gseq", 5); @@ -489,7 +489,7 @@ describe("agent-runner", () => { it("keeps subscribe workers alive across lifecycle end events", async () => { const MockWs = installMockWsModule(); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; const { spawnAgentSubscribeProcess } = await import("./agent-runner.js"); const proc = spawnAgentSubscribeProcess("agent:main:web:sess-sticky", 0); @@ -545,7 +545,7 @@ describe("agent-runner", () => { it("drops subscribe events missing a matching session key", async () => { const MockWs = installMockWsModule(); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; const { spawnAgentSubscribeProcess } = await import("./agent-runner.js"); const proc = spawnAgentSubscribeProcess("agent:main:web:sess-filter", 0); @@ -602,7 +602,7 @@ describe("agent-runner", () => { ok: false, error: { message: "unknown method: agent.subscribe" }, }); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; const { spawnAgentSubscribeProcess } = await import("./agent-runner.js"); const proc = spawnAgentSubscribeProcess("agent:main:web:sess-passive", 0); @@ -643,7 +643,7 @@ describe("agent-runner", () => { ok: false, error: { message: "unknown method: agent.subscribe" }, }); - delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM; + delete process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM; const { spawnAgentSubscribeProcess } = await import("./agent-runner.js"); const first = spawnAgentSubscribeProcess("agent:main:web:sess-cache", 0); diff --git a/apps/web/lib/agent-runner.ts b/apps/web/lib/agent-runner.ts index bb64c27225d..c7ac38d5114 100644 --- a/apps/web/lib/agent-runner.ts +++ b/apps/web/lib/agent-runner.ts @@ -359,10 +359,10 @@ export function buildConnectParams( version: "dev", platform: process.platform, mode: clientMode, - instanceId: "ironclaw-web-server", + instanceId: "denchclaw-web-server", }, locale: "en-US", - userAgent: "ironclaw-web", + userAgent: "denchclaw-web", role: "operator", scopes: ["operator.read", "operator.write", "operator.admin"], caps, @@ -880,7 +880,7 @@ class GatewayProcessHandle } function shouldForceLegacyStream(): boolean { - const raw = process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM?.trim().toLowerCase(); + const raw = process.env.DENCHCLAW_WEB_FORCE_LEGACY_STREAM?.trim().toLowerCase(); return raw === "1" || raw === "true" || raw === "yes"; } @@ -1285,7 +1285,7 @@ export async function runAgent( child.stderr?.on("data", (chunk: Buffer) => { const text = chunk.toString(); stderrChunks.push(text); - console.error("[ironclaw stderr]", text); + console.error("[denchclaw stderr]", text); }); }); } diff --git a/apps/web/lib/workspace-chat-isolation.test.ts b/apps/web/lib/workspace-chat-isolation.test.ts index 84b027c86c4..dfb4632cd2a 100644 --- a/apps/web/lib/workspace-chat-isolation.test.ts +++ b/apps/web/lib/workspace-chat-isolation.test.ts @@ -50,7 +50,7 @@ import { join } from "node:path"; describe("workspace-scoped chat session isolation", () => { const originalEnv = { ...process.env }; - const STATE_DIR = "/home/testuser/.openclaw-ironclaw"; + const STATE_DIR = "/home/testuser/.openclaw-dench"; const workspaceDir = (name: string) => name === "default" diff --git a/apps/web/lib/workspace-profiles.test.ts b/apps/web/lib/workspace-profiles.test.ts index 08549bd9dc0..196cc6f3762 100644 --- a/apps/web/lib/workspace-profiles.test.ts +++ b/apps/web/lib/workspace-profiles.test.ts @@ -65,8 +65,8 @@ function makeDirent(name: string, isDir: boolean): Dirent { describe("workspace (flat workspace model)", () => { const originalEnv = { ...process.env }; - const STATE_DIR = "/home/testuser/.openclaw-ironclaw"; - const UI_STATE_PATH = join(STATE_DIR, ".ironclaw-ui-state.json"); + const STATE_DIR = "/home/testuser/.openclaw-dench"; + const UI_STATE_PATH = join(STATE_DIR, ".dench-ui-state.json"); beforeEach(() => { vi.resetModules(); @@ -147,7 +147,7 @@ describe("workspace (flat workspace model)", () => { // ─── getEffectiveProfile ────────────────────────────────────────── describe("getEffectiveProfile", () => { - it("always returns 'ironclaw' regardless of env/state (single profile enforcement)", async () => { + it("always returns 'dench' regardless of env/state (single profile enforcement)", async () => { process.env.OPENCLAW_PROFILE = "work"; const { getEffectiveProfile, setUIActiveProfile, mockReadFile } = await importWorkspace(); @@ -155,7 +155,7 @@ describe("workspace (flat workspace model)", () => { JSON.stringify({ activeWorkspace: "something" }) as never, ); setUIActiveProfile("custom"); - expect(getEffectiveProfile()).toBe("ironclaw"); + expect(getEffectiveProfile()).toBe("dench"); }); }); @@ -369,7 +369,7 @@ describe("workspace (flat workspace model)", () => { expect(workspaces[0]?.isActive).toBe(true); }); - it("keeps root default and workspace-ironclaw as distinct workspaces", async () => { + it("keeps root default and workspace-dench as distinct workspaces", async () => { const { discoverWorkspaces, mockReaddir, mockExists, mockReadFile } = await importWorkspace(); mockReadFile.mockImplementation(() => { @@ -377,25 +377,25 @@ describe("workspace (flat workspace model)", () => { }); mockReaddir.mockReturnValue([ makeDirent("workspace", true), - makeDirent("workspace-ironclaw", true), + makeDirent("workspace-dench", true), ] as unknown as Dirent[]); mockExists.mockImplementation((p) => { const s = String(p); - return s === join(STATE_DIR, "workspace") || s === join(STATE_DIR, "workspace-ironclaw"); + return s === join(STATE_DIR, "workspace") || s === join(STATE_DIR, "workspace-dench"); }); const workspaces = discoverWorkspaces(); expect(workspaces).toHaveLength(2); const names = workspaces.map((workspace) => workspace.name); expect(names).toContain("default"); - expect(names).toContain("ironclaw"); + expect(names).toContain("dench"); const rootDefault = workspaces.find((workspace) => workspace.name === "default"); - const profileIronclaw = workspaces.find((workspace) => workspace.name === "ironclaw"); + const profileDench = workspaces.find((workspace) => workspace.name === "dench"); expect(rootDefault?.workspaceDir).toBe(join(STATE_DIR, "workspace")); - expect(profileIronclaw?.workspaceDir).toBe(join(STATE_DIR, "workspace-ironclaw")); + expect(profileDench?.workspaceDir).toBe(join(STATE_DIR, "workspace-dench")); }); - it("lists default, ironclaw, and custom workspace side by side", async () => { + it("lists default, dench, and custom workspace side by side", async () => { const { discoverWorkspaces, mockReaddir, mockExists, mockReadFile } = await importWorkspace(); mockReadFile.mockImplementation(() => { @@ -403,14 +403,14 @@ describe("workspace (flat workspace model)", () => { }); mockReaddir.mockReturnValue([ makeDirent("workspace", true), - makeDirent("workspace-ironclaw", true), + makeDirent("workspace-dench", true), makeDirent("workspace-kumareth", true), ] as unknown as Dirent[]); mockExists.mockImplementation((p) => { const s = String(p); return ( s === join(STATE_DIR, "workspace") || - s === join(STATE_DIR, "workspace-ironclaw") || + s === join(STATE_DIR, "workspace-dench") || s === join(STATE_DIR, "workspace-kumareth") ); }); @@ -418,7 +418,7 @@ describe("workspace (flat workspace model)", () => { const workspaces = discoverWorkspaces(); expect(workspaces.map((workspace) => workspace.name)).toEqual([ "default", - "ironclaw", + "dench", "kumareth", ]); }); @@ -611,7 +611,7 @@ describe("workspace (flat workspace model)", () => { mockWriteFile.mockClear(); registerWorkspacePath("myprofile", "/my/workspace"); const stateWrites = mockWriteFile.mock.calls.filter((c) => - (c[0] as string).includes(".ironclaw-ui-state.json"), + (c[0] as string).includes(".dench-ui-state.json"), ); expect(stateWrites).toHaveLength(0); }); diff --git a/apps/web/lib/workspace.test.ts b/apps/web/lib/workspace.test.ts index af76fe2d609..fc2e88f0b3f 100644 --- a/apps/web/lib/workspace.test.ts +++ b/apps/web/lib/workspace.test.ts @@ -50,7 +50,7 @@ function makeDirent(name: string, isDir: boolean): Dirent { describe("workspace utilities", () => { const originalEnv = { ...process.env }; - const STATE_DIR = join("/home/testuser", ".openclaw-ironclaw"); + const STATE_DIR = join("/home/testuser", ".openclaw-dench"); const WS_DIR = join(STATE_DIR, "workspace-test"); beforeEach(() => { @@ -155,7 +155,7 @@ describe("workspace utilities", () => { expect(resolveWorkspaceRoot()).toBe(fallbackWs); }); - it("resolves bootstrap root workspace as ironclaw default", async () => { + it("resolves bootstrap root workspace as dench default", async () => { delete process.env.OPENCLAW_WORKSPACE; const { resolveWorkspaceRoot, mockExists, mockReaddir } = await importWorkspace(); const rootWorkspace = join(STATE_DIR, "workspace"); @@ -173,7 +173,7 @@ describe("workspace utilities", () => { // ─── resolveWebChatDir ──────────────────────────────────────────── describe("resolveWebChatDir", () => { - it("falls back to root workspace chat dir for ironclaw default", async () => { + it("falls back to root workspace chat dir for dench default", async () => { delete process.env.OPENCLAW_WORKSPACE; const { resolveWebChatDir, mockReadFile, mockReaddir } = await importWorkspace(); mockReadFile.mockImplementation(() => { diff --git a/apps/web/lib/workspace.ts b/apps/web/lib/workspace.ts index f0d5e1d26fd..95e8c320772 100644 --- a/apps/web/lib/workspace.ts +++ b/apps/web/lib/workspace.ts @@ -8,13 +8,13 @@ import { normalizeFilterGroup, type SavedView, type ViewTypeSettings } from "./o const execAsync = promisify(exec); -const UI_STATE_FILENAME = ".ironclaw-ui-state.json"; -const FIXED_STATE_DIRNAME = ".openclaw-ironclaw"; +const UI_STATE_FILENAME = ".dench-ui-state.json"; +const FIXED_STATE_DIRNAME = ".openclaw-dench"; const WORKSPACE_PREFIX = "workspace-"; const ROOT_WORKSPACE_DIRNAME = "workspace"; const WORKSPACE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i; const DEFAULT_WORKSPACE_NAME = "default"; -const IRONCLAW_PROFILE = "ironclaw"; +const DENCHCLAW_PROFILE = "dench"; /** In-memory override; takes precedence over persisted state. */ let _uiActiveWorkspace: string | null | undefined; @@ -223,7 +223,7 @@ export function discoverProfiles(): DiscoveredProfile[] { return discoverWorkspaces(); } export function getEffectiveProfile(): string { - return IRONCLAW_PROFILE; + return DENCHCLAW_PROFILE; } export function setUIActiveProfile(profile: string | null): void { setUIActiveWorkspace(normalizeWorkspaceName(profile)); @@ -239,7 +239,7 @@ export function getRegisteredWorkspacePath(_profile: string | null): string | nu } export function registerWorkspacePath(_profile: string, _absolutePath: string): void { // No-op: workspace paths are discovered from managed dirs: - // ~/.openclaw-ironclaw/workspace (default) and ~/.openclaw-ironclaw/workspace-. + // ~/.openclaw-dench/workspace (default) and ~/.openclaw-dench/workspace-. } export function isValidWorkspaceName(name: string): boolean { diff --git a/apps/web/package.json b/apps/web/package.json index 90ef42075c2..cfd75afe629 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,5 +1,5 @@ { - "name": "ironclaw-web", + "name": "denchclaw-web", "version": "0.1.0", "private": true, "scripts": { diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts index bc955623132..f121f5569ff 100644 --- a/apps/web/vitest.config.ts +++ b/apps/web/vitest.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ resolve: { alias: { "@": path.resolve(__dirname), + "@repo": path.resolve(__dirname, "../../src"), }, }, test: { diff --git a/docs/install/updating.md b/docs/install/updating.md index 8a921ec09af..497098bd788 100644 --- a/docs/install/updating.md +++ b/docs/install/updating.md @@ -10,7 +10,7 @@ title: "Updating" OpenClaw is moving fast (pre “1.0”). Treat updates like shipping infra: update → run checks → restart (or use `openclaw update`, which restarts) → verify. -If you run **IronClaw** as a frontend package, keep OpenClaw installed globally (`npm i -g openclaw`) and update OpenClaw separately; IronClaw delegates runtime commands to that global OpenClaw install. +If you run **DenchClaw** as a frontend package, keep OpenClaw installed globally (`npm i -g openclaw`) and update OpenClaw separately; DenchClaw delegates runtime commands to that global OpenClaw install. ## Recommended: re-run the website installer (upgrade in place) diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index 4781f748b51..a173ed9bbd7 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -23,7 +23,7 @@ When the operator says “release”, immediately do this preflight (no extra qu - [ ] Bump `package.json` version (e.g., `2026.1.29`). - [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/openclaw/openclaw/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/openclaw/openclaw/blob/main/src/provider-web.ts). -- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`openclaw.mjs`](https://github.com/openclaw/openclaw/blob/main/openclaw.mjs) for `ironclaw`. +- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`openclaw.mjs`](https://github.com/openclaw/openclaw/blob/main/openclaw.mjs) for `denchclaw`. - [ ] Confirm release notes/documentation call out the global runtime prerequisite: `npm i -g openclaw`. - [ ] If dependencies changed, run `pnpm install` so `pnpm-lock.yaml` is current. @@ -31,7 +31,7 @@ When the operator says “release”, immediately do this preflight (no extra qu - [ ] If A2UI inputs changed, run `pnpm canvas:a2ui:bundle` and commit any updated [`src/canvas-host/a2ui/a2ui.bundle.js`](https://github.com/openclaw/openclaw/blob/main/src/canvas-host/a2ui/a2ui.bundle.js). - [ ] `pnpm run build` (regenerates `dist/`). -- [ ] Verify npm package `files` includes only IronClaw artifacts (`dist/entry*`, web standalone, skills/assets) and does not rely on bundled OpenClaw core runtime code. +- [ ] Verify npm package `files` includes only DenchClaw artifacts (`dist/entry*`, web standalone, skills/assets) and does not rely on bundled OpenClaw core runtime code. - [ ] Confirm `dist/build-info.json` exists and includes the expected `commit` hash (CLI banner uses this for npm installs). - [ ] Optional: `npm pack --pack-destination /tmp` after the build; inspect the tarball contents and keep it handy for the GitHub release (do **not** commit it). diff --git a/extensions/bluebubbles/package.json b/extensions/bluebubbles/package.json index d345f469906..d45112b8fe3 100644 --- a/extensions/bluebubbles/package.json +++ b/extensions/bluebubbles/package.json @@ -4,7 +4,7 @@ "description": "OpenClaw BlueBubbles channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/copilot-proxy/package.json b/extensions/copilot-proxy/package.json index 9396f777cad..7d379b61189 100644 --- a/extensions/copilot-proxy/package.json +++ b/extensions/copilot-proxy/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw Copilot Proxy provider plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/denchclaw-auth/README.md b/extensions/denchclaw-auth/README.md new file mode 100644 index 00000000000..73858c958b2 --- /dev/null +++ b/extensions/denchclaw-auth/README.md @@ -0,0 +1,39 @@ +# DenchClaw OAuth (OpenClaw plugin) + +OAuth provider plugin for DenchClaw-hosted models. + +## Enable + +Bundled plugins are disabled by default. Enable this one: + +```bash +openclaw plugins enable denchclaw-auth +``` + +Restart the Gateway after enabling. + +## Authenticate + +Set at least a client id, then run provider login: + +```bash +export DENCHCLAW_OAUTH_CLIENT_ID="" +openclaw models auth login --provider denchclaw --set-default +``` + +## Optional env vars + +- `DENCHCLAW_OAUTH_CLIENT_SECRET` +- `DENCHCLAW_OAUTH_AUTH_URL` (default: `https://auth.denchclaw.ai/oauth/authorize`) +- `DENCHCLAW_OAUTH_TOKEN_URL` (default: `https://auth.denchclaw.ai/oauth/token`) +- `DENCHCLAW_OAUTH_REDIRECT_URI` (default: `http://127.0.0.1:47089/oauth/callback`) +- `DENCHCLAW_OAUTH_SCOPES` (space/comma separated) +- `DENCHCLAW_OAUTH_USERINFO_URL` (optional for email display) +- `DENCHCLAW_PROVIDER_BASE_URL` (default: `https://api.denchclaw.ai/v1`) +- `DENCHCLAW_PROVIDER_MODEL_IDS` (space/comma separated, default: `chat`) +- `DENCHCLAW_PROVIDER_DEFAULT_MODEL` (default: first model id) + +## Notes + +- This plugin configures `models.providers.denchclaw` as `openai-completions`. +- OAuth tokens are stored in auth profiles and the provider is patched into config automatically. diff --git a/extensions/ironclaw-auth/index.ts b/extensions/denchclaw-auth/index.ts similarity index 72% rename from extensions/ironclaw-auth/index.ts rename to extensions/denchclaw-auth/index.ts index d7a77230c76..ba51edd96d2 100644 --- a/extensions/ironclaw-auth/index.ts +++ b/extensions/denchclaw-auth/index.ts @@ -4,45 +4,45 @@ import { type ProviderAuthContext, type ProviderAuthResult, } from "openclaw/plugin-sdk"; -import { loginIronclawOAuth, type IronclawOAuthConfig } from "./oauth.js"; +import { loginDenchClawOAuth, type DenchClawOAuthConfig } from "./oauth.js"; -const PLUGIN_ID = "ironclaw-auth"; -const PROVIDER_ID = "ironclaw"; -const PROVIDER_LABEL = "Ironclaw"; -const OAUTH_PLACEHOLDER = "ironclaw-oauth"; -const DEFAULT_AUTH_URL = "https://auth.ironclaw.ai/oauth/authorize"; -const DEFAULT_TOKEN_URL = "https://auth.ironclaw.ai/oauth/token"; +const PLUGIN_ID = "denchclaw-auth"; +const PROVIDER_ID = "denchclaw"; +const PROVIDER_LABEL = "DenchClaw"; +const OAUTH_PLACEHOLDER = "denchclaw-oauth"; +const DEFAULT_AUTH_URL = "https://auth.denchclaw.ai/oauth/authorize"; +const DEFAULT_TOKEN_URL = "https://auth.denchclaw.ai/oauth/token"; const DEFAULT_REDIRECT_URI = "http://127.0.0.1:47089/oauth/callback"; const DEFAULT_SCOPES = ["openid", "profile", "email", "offline_access"]; -const DEFAULT_BASE_URL = "https://api.ironclaw.ai/v1"; +const DEFAULT_BASE_URL = "https://api.denchclaw.ai/v1"; const DEFAULT_MODEL_ID = "chat"; const DEFAULT_CONTEXT_WINDOW = 128000; const DEFAULT_MAX_TOKENS = 8192; -const CLIENT_ID_KEYS = ["IRONCLAW_OAUTH_CLIENT_ID", "OPENCLAW_IRONCLAW_OAUTH_CLIENT_ID"]; +const CLIENT_ID_KEYS = ["DENCHCLAW_OAUTH_CLIENT_ID", "OPENCLAW_DENCHCLAW_OAUTH_CLIENT_ID"]; const CLIENT_SECRET_KEYS = [ - "IRONCLAW_OAUTH_CLIENT_SECRET", - "OPENCLAW_IRONCLAW_OAUTH_CLIENT_SECRET", + "DENCHCLAW_OAUTH_CLIENT_SECRET", + "OPENCLAW_DENCHCLAW_OAUTH_CLIENT_SECRET", ]; -const AUTH_URL_KEYS = ["IRONCLAW_OAUTH_AUTH_URL", "OPENCLAW_IRONCLAW_OAUTH_AUTH_URL"]; -const TOKEN_URL_KEYS = ["IRONCLAW_OAUTH_TOKEN_URL", "OPENCLAW_IRONCLAW_OAUTH_TOKEN_URL"]; -const REDIRECT_URI_KEYS = ["IRONCLAW_OAUTH_REDIRECT_URI", "OPENCLAW_IRONCLAW_OAUTH_REDIRECT_URI"]; -const SCOPES_KEYS = ["IRONCLAW_OAUTH_SCOPES", "OPENCLAW_IRONCLAW_OAUTH_SCOPES"]; -const USERINFO_URL_KEYS = ["IRONCLAW_OAUTH_USERINFO_URL", "OPENCLAW_IRONCLAW_OAUTH_USERINFO_URL"]; +const AUTH_URL_KEYS = ["DENCHCLAW_OAUTH_AUTH_URL", "OPENCLAW_DENCHCLAW_OAUTH_AUTH_URL"]; +const TOKEN_URL_KEYS = ["DENCHCLAW_OAUTH_TOKEN_URL", "OPENCLAW_DENCHCLAW_OAUTH_TOKEN_URL"]; +const REDIRECT_URI_KEYS = ["DENCHCLAW_OAUTH_REDIRECT_URI", "OPENCLAW_DENCHCLAW_OAUTH_REDIRECT_URI"]; +const SCOPES_KEYS = ["DENCHCLAW_OAUTH_SCOPES", "OPENCLAW_DENCHCLAW_OAUTH_SCOPES"]; +const USERINFO_URL_KEYS = ["DENCHCLAW_OAUTH_USERINFO_URL", "OPENCLAW_DENCHCLAW_OAUTH_USERINFO_URL"]; const BASE_URL_KEYS = [ - "IRONCLAW_PROVIDER_BASE_URL", - "IRONCLAW_API_BASE_URL", - "OPENCLAW_IRONCLAW_PROVIDER_BASE_URL", + "DENCHCLAW_PROVIDER_BASE_URL", + "DENCHCLAW_API_BASE_URL", + "OPENCLAW_DENCHCLAW_PROVIDER_BASE_URL", ]; const MODEL_IDS_KEYS = [ - "IRONCLAW_PROVIDER_MODEL_IDS", - "IRONCLAW_MODEL_IDS", - "OPENCLAW_IRONCLAW_MODEL_IDS", + "DENCHCLAW_PROVIDER_MODEL_IDS", + "DENCHCLAW_MODEL_IDS", + "OPENCLAW_DENCHCLAW_MODEL_IDS", ]; const DEFAULT_MODEL_KEYS = [ - "IRONCLAW_PROVIDER_DEFAULT_MODEL", - "IRONCLAW_DEFAULT_MODEL", - "OPENCLAW_IRONCLAW_DEFAULT_MODEL", + "DENCHCLAW_PROVIDER_DEFAULT_MODEL", + "DENCHCLAW_DEFAULT_MODEL", + "OPENCLAW_DENCHCLAW_DEFAULT_MODEL", ]; const ENV_VARS = [ @@ -118,11 +118,11 @@ function buildModelDefinition(modelId: string) { }; } -function resolveOAuthConfig(): IronclawOAuthConfig { +function resolveOAuthConfig(): DenchClawOAuthConfig { const clientId = resolveEnv(CLIENT_ID_KEYS); if (!clientId) { throw new Error( - ["Ironclaw OAuth client id is required.", `Set one of: ${CLIENT_ID_KEYS.join(", ")}`].join( + ["DenchClaw OAuth client id is required.", `Set one of: ${CLIENT_ID_KEYS.join(", ")}`].join( "\n", ), ); @@ -158,7 +158,7 @@ function buildAuthResult(params: { const agentModels = Object.fromEntries( finalModelIds.map((modelId, index) => [ `${PROVIDER_ID}/${modelId}`, - index === 0 ? { alias: "ironclaw" } : {}, + index === 0 ? { alias: "denchclaw" } : {}, ]), ); @@ -201,29 +201,29 @@ function buildAuthResult(params: { }; } -const ironclawAuthPlugin = { +const denchclawAuthPlugin = { id: PLUGIN_ID, - name: "Ironclaw OAuth", - description: "OAuth flow for Ironclaw-hosted models", + name: "DenchClaw OAuth", + description: "OAuth flow for DenchClaw-hosted models", configSchema: emptyPluginConfigSchema(), register(api: OpenClawPluginApi) { api.registerProvider({ id: PROVIDER_ID, label: PROVIDER_LABEL, docsPath: "/providers/models", - aliases: ["ironclaw-ai"], + aliases: ["denchclaw-ai"], envVars: ENV_VARS, auth: [ { id: "oauth", - label: "Ironclaw OAuth", + label: "DenchClaw OAuth", hint: "PKCE + localhost callback", kind: "oauth", run: async (ctx: ProviderAuthContext) => { - const progress = ctx.prompter.progress("Starting Ironclaw OAuth..."); + const progress = ctx.prompter.progress("Starting DenchClaw OAuth..."); try { const oauthConfig = resolveOAuthConfig(); - const result = await loginIronclawOAuth( + const result = await loginDenchClawOAuth( { isRemote: ctx.isRemote, openUrl: ctx.openUrl, @@ -235,16 +235,16 @@ const ironclawAuthPlugin = { oauthConfig, ); - progress.stop("Ironclaw OAuth complete"); + progress.stop("DenchClaw OAuth complete"); return buildAuthResult(result); } catch (error) { - progress.stop("Ironclaw OAuth failed"); + progress.stop("DenchClaw OAuth failed"); await ctx.prompter.note( [ - "Set IRONCLAW_OAUTH_CLIENT_ID (and optionally auth/token URLs) before retrying.", - "You can also configure model ids with IRONCLAW_PROVIDER_MODEL_IDS.", + "Set DENCHCLAW_OAUTH_CLIENT_ID (and optionally auth/token URLs) before retrying.", + "You can also configure model ids with DENCHCLAW_PROVIDER_MODEL_IDS.", ].join("\n"), - "Ironclaw OAuth", + "DenchClaw OAuth", ); throw error; } @@ -255,4 +255,4 @@ const ironclawAuthPlugin = { }, }; -export default ironclawAuthPlugin; +export default denchclawAuthPlugin; diff --git a/extensions/ironclaw-auth/oauth.ts b/extensions/denchclaw-auth/oauth.ts similarity index 91% rename from extensions/ironclaw-auth/oauth.ts rename to extensions/denchclaw-auth/oauth.ts index ec98e815442..68a58b2cbd3 100644 --- a/extensions/ironclaw-auth/oauth.ts +++ b/extensions/denchclaw-auth/oauth.ts @@ -6,7 +6,7 @@ const RESPONSE_PAGE = ` - Ironclaw OAuth + DenchClaw OAuth
@@ -16,7 +16,7 @@ const RESPONSE_PAGE = ` `; -export type IronclawOAuthConfig = { +export type DenchClawOAuthConfig = { clientId: string; clientSecret?: string; authUrl: string; @@ -26,14 +26,14 @@ export type IronclawOAuthConfig = { userInfoUrl?: string; }; -export type IronclawOAuthCredentials = { +export type DenchClawOAuthCredentials = { access: string; refresh: string; expires: number; email?: string; }; -export type IronclawOAuthContext = { +export type DenchClawOAuthContext = { isRemote: boolean; openUrl: (url: string) => Promise; log: (message: string) => void; @@ -65,11 +65,11 @@ function normalizeUrl(value: string, fieldName: string): string { } function buildAuthUrl(params: { - config: IronclawOAuthConfig; + config: DenchClawOAuthConfig; challenge: string; state: string; }): string { - const authUrl = normalizeUrl(params.config.authUrl, "IRONCLAW_OAUTH_AUTH_URL"); + const authUrl = normalizeUrl(params.config.authUrl, "DENCHCLAW_OAUTH_AUTH_URL"); const url = new URL(authUrl); url.searchParams.set("client_id", params.config.clientId); url.searchParams.set("response_type", "code"); @@ -112,7 +112,7 @@ function parseCallbackInput( } async function startCallbackServer(params: { redirectUri: string; timeoutMs: number }) { - const redirect = new URL(normalizeUrl(params.redirectUri, "IRONCLAW_OAUTH_REDIRECT_URI")); + const redirect = new URL(normalizeUrl(params.redirectUri, "DENCHCLAW_OAUTH_REDIRECT_URI")); const port = redirect.port ? Number(redirect.port) : 80; const host = redirect.hostname === "localhost" || redirect.hostname === "127.0.0.1" @@ -189,11 +189,11 @@ async function startCallbackServer(params: { redirectUri: string; timeoutMs: num } async function exchangeCode(params: { - config: IronclawOAuthConfig; + config: DenchClawOAuthConfig; code: string; verifier: string; -}): Promise { - const tokenUrl = normalizeUrl(params.config.tokenUrl, "IRONCLAW_OAUTH_TOKEN_URL"); +}): Promise { + const tokenUrl = normalizeUrl(params.config.tokenUrl, "DENCHCLAW_OAUTH_TOKEN_URL"); const body = new URLSearchParams({ grant_type: "authorization_code", code: params.code, @@ -240,7 +240,7 @@ async function exchangeCode(params: { } async function fetchUserEmail( - config: IronclawOAuthConfig, + config: DenchClawOAuthConfig, accessToken: string, ): Promise { if (!config.userInfoUrl?.trim()) { @@ -248,7 +248,7 @@ async function fetchUserEmail( } let url: string; try { - url = normalizeUrl(config.userInfoUrl, "IRONCLAW_OAUTH_USERINFO_URL"); + url = normalizeUrl(config.userInfoUrl, "DENCHCLAW_OAUTH_USERINFO_URL"); } catch { return undefined; } @@ -270,10 +270,10 @@ async function fetchUserEmail( } } -export async function loginIronclawOAuth( - ctx: IronclawOAuthContext, - config: IronclawOAuthConfig, -): Promise { +export async function loginDenchClawOAuth( + ctx: DenchClawOAuthContext, + config: DenchClawOAuthConfig, +): Promise { const { verifier, challenge } = generatePkce(); const state = randomBytes(16).toString("hex"); const authUrl = buildAuthUrl({ config, challenge, state }); @@ -300,14 +300,14 @@ export async function loginIronclawOAuth( `Auth URL: ${authUrl}`, `Redirect URI: ${config.redirectUri}`, ].join("\n"), - "Ironclaw OAuth", + "DenchClaw OAuth", ); ctx.log(""); ctx.log("Copy this URL:"); ctx.log(authUrl); ctx.log(""); } else { - ctx.progress.update("Opening Ironclaw sign-in..."); + ctx.progress.update("Opening DenchClaw sign-in..."); try { await ctx.openUrl(authUrl); } catch { diff --git a/extensions/ironclaw-auth/openclaw.plugin.json b/extensions/denchclaw-auth/openclaw.plugin.json similarity index 65% rename from extensions/ironclaw-auth/openclaw.plugin.json rename to extensions/denchclaw-auth/openclaw.plugin.json index 945d9f292e4..dca5bb5095b 100644 --- a/extensions/ironclaw-auth/openclaw.plugin.json +++ b/extensions/denchclaw-auth/openclaw.plugin.json @@ -1,6 +1,6 @@ { - "id": "ironclaw-auth", - "providers": ["ironclaw"], + "id": "denchclaw-auth", + "providers": ["denchclaw"], "configSchema": { "type": "object", "additionalProperties": false, diff --git a/extensions/diagnostics-otel/package.json b/extensions/diagnostics-otel/package.json index 0645e3f0874..8f04659a3b7 100644 --- a/extensions/diagnostics-otel/package.json +++ b/extensions/diagnostics-otel/package.json @@ -17,7 +17,7 @@ "@opentelemetry/semantic-conventions": "^1.39.0" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/discord/package.json b/extensions/discord/package.json index 6160d18cbfc..da912f8ba0d 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -4,7 +4,7 @@ "description": "OpenClaw Discord channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/feishu/package.json b/extensions/feishu/package.json index 1d7a0cb9687..7324f2f67d8 100644 --- a/extensions/feishu/package.json +++ b/extensions/feishu/package.json @@ -9,7 +9,7 @@ "zod": "^4.3.6" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/feishu/src/dynamic-agent.ts b/extensions/feishu/src/dynamic-agent.ts index d62c3f2a43e..52ae6880869 100644 --- a/extensions/feishu/src/dynamic-agent.ts +++ b/extensions/feishu/src/dynamic-agent.ts @@ -77,8 +77,9 @@ export async function maybeCreateDynamicAgent(params: { } // Resolve path templates with substitutions - const workspaceTemplate = dynamicCfg.workspaceTemplate ?? "~/.openclaw/workspace-{agentId}"; - const agentDirTemplate = dynamicCfg.agentDirTemplate ?? "~/.openclaw/agents/{agentId}/agent"; + const workspaceTemplate = dynamicCfg.workspaceTemplate ?? "~/.openclaw-dench/workspace-{agentId}"; + const agentDirTemplate = + dynamicCfg.agentDirTemplate ?? "~/.openclaw-dench/agents/{agentId}/agent"; const workspace = resolveUserPath( workspaceTemplate.replace("{userId}", senderOpenId).replace("{agentId}", agentId), diff --git a/extensions/google-antigravity-auth/package.json b/extensions/google-antigravity-auth/package.json index 5ba24174eef..f8679b43467 100644 --- a/extensions/google-antigravity-auth/package.json +++ b/extensions/google-antigravity-auth/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw Google Antigravity OAuth provider plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/google-gemini-cli-auth/package.json b/extensions/google-gemini-cli-auth/package.json index 73838fa90db..28c42365c7c 100644 --- a/extensions/google-gemini-cli-auth/package.json +++ b/extensions/google-gemini-cli-auth/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw Gemini CLI OAuth provider plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index d14761dc9cd..3ee6cd99919 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -8,10 +8,10 @@ "google-auth-library": "^10.5.0" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "peerDependencies": { - "ironclaw": ">=2026.1.26" + "denchclaw": ">=2026.1.26" }, "openclaw": { "extensions": [ diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index 8792f43baef..be5ed34bd72 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw iMessage channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/irc/package.json b/extensions/irc/package.json index 1b16beab476..ba70d1de13f 100644 --- a/extensions/irc/package.json +++ b/extensions/irc/package.json @@ -4,7 +4,7 @@ "description": "OpenClaw IRC channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/ironclaw-auth/README.md b/extensions/ironclaw-auth/README.md deleted file mode 100644 index 73295490277..00000000000 --- a/extensions/ironclaw-auth/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Ironclaw OAuth (OpenClaw plugin) - -OAuth provider plugin for Ironclaw-hosted models. - -## Enable - -Bundled plugins are disabled by default. Enable this one: - -```bash -openclaw plugins enable ironclaw-auth -``` - -Restart the Gateway after enabling. - -## Authenticate - -Set at least a client id, then run provider login: - -```bash -export IRONCLAW_OAUTH_CLIENT_ID="" -openclaw models auth login --provider ironclaw --set-default -``` - -## Optional env vars - -- `IRONCLAW_OAUTH_CLIENT_SECRET` -- `IRONCLAW_OAUTH_AUTH_URL` (default: `https://auth.ironclaw.ai/oauth/authorize`) -- `IRONCLAW_OAUTH_TOKEN_URL` (default: `https://auth.ironclaw.ai/oauth/token`) -- `IRONCLAW_OAUTH_REDIRECT_URI` (default: `http://127.0.0.1:47089/oauth/callback`) -- `IRONCLAW_OAUTH_SCOPES` (space/comma separated) -- `IRONCLAW_OAUTH_USERINFO_URL` (optional for email display) -- `IRONCLAW_PROVIDER_BASE_URL` (default: `https://api.ironclaw.ai/v1`) -- `IRONCLAW_PROVIDER_MODEL_IDS` (space/comma separated, default: `chat`) -- `IRONCLAW_PROVIDER_DEFAULT_MODEL` (default: first model id) - -## Notes - -- This plugin configures `models.providers.ironclaw` as `openai-completions`. -- OAuth tokens are stored in auth profiles and the provider is patched into config automatically. diff --git a/extensions/line/package.json b/extensions/line/package.json index 70dfebfcecf..3e8245c1fe8 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw LINE channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/llm-task/package.json b/extensions/llm-task/package.json index 816c509668f..b27cf879e15 100644 --- a/extensions/llm-task/package.json +++ b/extensions/llm-task/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw JSON-only LLM task plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/lobster/package.json b/extensions/lobster/package.json index b6f976d566a..81cc55d8a12 100644 --- a/extensions/lobster/package.json +++ b/extensions/lobster/package.json @@ -4,7 +4,7 @@ "description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index 659b4a6a915..6d3e117f6ef 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -11,7 +11,7 @@ "zod": "^4.3.6" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index 332a3e3c3c5..a46cfdb61c2 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -4,7 +4,7 @@ "description": "OpenClaw Mattermost channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index bb879c36825..8e74e79f899 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -5,10 +5,10 @@ "description": "OpenClaw core memory search plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "peerDependencies": { - "ironclaw": ">=2026.1.26" + "denchclaw": ">=2026.1.26" }, "openclaw": { "extensions": [ diff --git a/extensions/memory-lancedb/config.ts b/extensions/memory-lancedb/config.ts index 77d53cc6842..4a185f33481 100644 --- a/extensions/memory-lancedb/config.ts +++ b/extensions/memory-lancedb/config.ts @@ -23,7 +23,7 @@ const LEGACY_STATE_DIRS: string[] = []; function resolveDefaultDbPath(): string { const home = homedir(); - const preferred = join(home, ".openclaw", "memory", "lancedb"); + const preferred = join(home, ".openclaw-dench", "memory", "lancedb"); try { if (fs.existsSync(preferred)) { return preferred; @@ -140,7 +140,7 @@ export const memoryConfigSchema = { }, dbPath: { label: "Database Path", - placeholder: "~/.openclaw/memory/lancedb", + placeholder: "~/.openclaw-dench/memory/lancedb", advanced: true, }, autoCapture: { diff --git a/extensions/memory-lancedb/openclaw.plugin.json b/extensions/memory-lancedb/openclaw.plugin.json index 44ee0dcd04f..2c3f5276fce 100644 --- a/extensions/memory-lancedb/openclaw.plugin.json +++ b/extensions/memory-lancedb/openclaw.plugin.json @@ -15,7 +15,7 @@ }, "dbPath": { "label": "Database Path", - "placeholder": "~/.openclaw/memory/lancedb", + "placeholder": "~/.openclaw-dench/memory/lancedb", "advanced": true }, "autoCapture": { diff --git a/extensions/memory-lancedb/package.json b/extensions/memory-lancedb/package.json index 971497436c7..b37910ea53b 100644 --- a/extensions/memory-lancedb/package.json +++ b/extensions/memory-lancedb/package.json @@ -10,7 +10,7 @@ "openai": "^6.22.0" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/minimax-portal-auth/package.json b/extensions/minimax-portal-auth/package.json index 7805572dede..8755e5b35b7 100644 --- a/extensions/minimax-portal-auth/package.json +++ b/extensions/minimax-portal-auth/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw MiniMax Portal OAuth provider plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index c4d14838c10..e63bb9a58ff 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -8,7 +8,7 @@ "express": "^5.2.1" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/nextcloud-talk/package.json b/extensions/nextcloud-talk/package.json index 1eac3761763..f46455079e9 100644 --- a/extensions/nextcloud-talk/package.json +++ b/extensions/nextcloud-talk/package.json @@ -4,7 +4,7 @@ "description": "OpenClaw Nextcloud Talk channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index 59912d9ce2d..76113c69da8 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -8,7 +8,7 @@ "zod": "^4.3.6" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/open-prose/package.json b/extensions/open-prose/package.json index e82029d2896..afcda92b40f 100644 --- a/extensions/open-prose/package.json +++ b/extensions/open-prose/package.json @@ -5,7 +5,7 @@ "description": "OpenProse VM skill pack plugin (slash command + telemetry).", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/signal/package.json b/extensions/signal/package.json index 972effc8904..538f3ad31df 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw Signal channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/slack/package.json b/extensions/slack/package.json index bc5c880225c..2b41279b457 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw Slack channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/synology-chat/package.json b/extensions/synology-chat/package.json index 14be854f2ad..96530358294 100644 --- a/extensions/synology-chat/package.json +++ b/extensions/synology-chat/package.json @@ -5,7 +5,7 @@ "description": "Synology Chat channel plugin for OpenClaw", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index 417b2c68ab5..ac7e163b43a 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw Telegram channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index 6c6435e4b92..2459727a91c 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -7,7 +7,7 @@ "@urbit/aura": "^3.0.0" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/twitch/package.json b/extensions/twitch/package.json index 6ec941fadd3..5efc107708e 100644 --- a/extensions/twitch/package.json +++ b/extensions/twitch/package.json @@ -10,7 +10,7 @@ "zod": "^4.3.6" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/voice-call/package.json b/extensions/voice-call/package.json index bac64caa226..e980d8a4042 100644 --- a/extensions/voice-call/package.json +++ b/extensions/voice-call/package.json @@ -9,7 +9,7 @@ "zod": "^4.3.6" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/voice-call/src/cli.ts b/extensions/voice-call/src/cli.ts index eaf4e3fc0a5..59cbea6ba43 100644 --- a/extensions/voice-call/src/cli.ts +++ b/extensions/voice-call/src/cli.ts @@ -27,7 +27,7 @@ function resolveMode(input: string): "off" | "serve" | "funnel" { } function resolveDefaultStorePath(config: VoiceCallConfig): string { - const preferred = path.join(os.homedir(), ".openclaw", "voice-calls"); + const preferred = path.join(os.homedir(), ".openclaw-dench", "voice-calls"); const resolvedPreferred = resolveUserPath(preferred); const existing = [resolvedPreferred].find((dir) => { diff --git a/extensions/voice-call/src/core-bridge.ts b/extensions/voice-call/src/core-bridge.ts index e2daf5401ee..b06049833c6 100644 --- a/extensions/voice-call/src/core-bridge.ts +++ b/extensions/voice-call/src/core-bridge.ts @@ -109,7 +109,7 @@ function resolveOpenClawRoot(): string { } for (const start of candidates) { - for (const name of ["ironclaw", "openclaw"]) { + for (const name of ["denchclaw", "openclaw"]) { const found = findPackageRoot(start, name); if (found) { coreRootCache = found; diff --git a/extensions/voice-call/src/manager.ts b/extensions/voice-call/src/manager.ts index 927899f325c..cd89329bb82 100644 --- a/extensions/voice-call/src/manager.ts +++ b/extensions/voice-call/src/manager.ts @@ -22,7 +22,7 @@ function resolveDefaultStoreBase(config: VoiceCallConfig, storePath?: string): s if (rawOverride) { return resolveUserPath(rawOverride); } - const preferred = path.join(os.homedir(), ".openclaw", "voice-calls"); + const preferred = path.join(os.homedir(), ".openclaw-dench", "voice-calls"); const candidates = [preferred].map((dir) => resolveUserPath(dir)); const existing = candidates.find((dir) => { diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index 07b9884edd7..e81ad07ba71 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw WhatsApp channel plugin", "type": "module", "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/zalo/package.json b/extensions/zalo/package.json index 1d14a0a0137..78454b02242 100644 --- a/extensions/zalo/package.json +++ b/extensions/zalo/package.json @@ -7,7 +7,7 @@ "undici": "7.22.0" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/extensions/zalouser/package.json b/extensions/zalouser/package.json index e5dca61c93a..800a656bdd4 100644 --- a/extensions/zalouser/package.json +++ b/extensions/zalouser/package.json @@ -7,7 +7,7 @@ "@sinclair/typebox": "0.34.48" }, "devDependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" }, "openclaw": { "extensions": [ diff --git a/openclaw.mjs b/openclaw.mjs index 44ba0cc6466..5b5535d9902 100755 --- a/openclaw.mjs +++ b/openclaw.mjs @@ -52,5 +52,5 @@ if (await tryImport("./dist/entry.js")) { } else if (await tryImport("./dist/entry.mjs")) { // OK } else { - throw new Error("ironclaw: missing dist/entry.(m)js (build output)."); + throw new Error("denchclaw: missing dist/entry.(m)js (build output)."); } diff --git a/package.json b/package.json index 462805e3c73..d8241fe9811 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "ironclaw", + "name": "denchclaw", "version": "2026.2.22-1.1", "description": "AI-powered CRM platform with multi-channel agent gateway, DuckDB workspace, and knowledge management", "keywords": [], @@ -14,7 +14,8 @@ "url": "git+https://github.com/openclaw/openclaw.git" }, "bin": { - "ironclaw": "openclaw.mjs" + "dench": "openclaw.mjs", + "denchclaw": "openclaw.mjs" }, "directories": { "doc": "docs", @@ -57,6 +58,8 @@ "deadcode:report:ci:ts-unused": "mkdir -p .artifacts/deadcode && pnpm deadcode:ts-unused > .artifacts/deadcode/ts-unused-exports.txt 2>&1 || true", "deadcode:ts-prune": "pnpm dlx ts-prune src extensions scripts", "deadcode:ts-unused": "pnpm dlx ts-unused-exports tsconfig.json --ignoreTestFiles --exitWithCount", + "denchclaw": "node scripts/run-node.mjs", + "denchclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json", "dev": "node scripts/run-node.mjs", "docs:bin": "node scripts/build-docs-list.mjs", "docs:check-links": "node scripts/docs-link-audit.mjs", @@ -79,8 +82,6 @@ "ios:gen": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate'", "ios:open": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate && open OpenClaw.xcodeproj'", "ios:run": "bash -lc './scripts/ios-configure-signing.sh && cd apps/ios && xcodegen generate && xcodebuild -project OpenClaw.xcodeproj -scheme OpenClaw -destination \"${IOS_DEST:-platform=iOS Simulator,name=iPhone 17}\" -configuration Debug build && xcrun simctl boot \"${IOS_SIM:-iPhone 17}\" || true && xcrun simctl launch booted ai.openclaw.ios'", - "ironclaw": "node scripts/run-node.mjs", - "ironclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json", "lint": "oxlint --type-aware", "lint:all": "pnpm lint && pnpm lint:swift", "lint:docs": "pnpm dlx markdownlint-cli2", diff --git a/packages/clawdbot/package.json b/packages/clawdbot/package.json index 8a66757ba50..8e095e963c9 100644 --- a/packages/clawdbot/package.json +++ b/packages/clawdbot/package.json @@ -11,6 +11,6 @@ "./cli-entry": "./bin/clawdbot.js" }, "dependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" } } diff --git a/packages/moltbot/package.json b/packages/moltbot/package.json index 45bd2e8cec8..12fc0f61c8a 100644 --- a/packages/moltbot/package.json +++ b/packages/moltbot/package.json @@ -11,6 +11,6 @@ "./cli-entry": "./bin/moltbot.js" }, "dependencies": { - "ironclaw": "workspace:*" + "denchclaw": "workspace:*" } } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index f7321a3c890..bf807b87ce1 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# deploy.sh — build and publish ironclaw to npm +# deploy.sh — build and publish denchclaw to npm # # Versioning convention (mirrors upstream openclaw tags): # --upstream Sync to an upstream release version. @@ -19,7 +19,7 @@ set -euo pipefail -PACKAGE_NAME="ironclaw" +PACKAGE_NAME="denchclaw" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" @@ -188,7 +188,7 @@ fi # ── build ──────────────────────────────────────────────────────────────────── -# The `prepack` script (triggered by `npm publish`) runs the IronClaw build chain: +# The `prepack` script (triggered by `npm publish`) runs the DenchClaw build chain: # pnpm build && pnpm web:build && pnpm web:prepack # Running `pnpm build` here is a redundant fail-fast: catch CLI build errors # before committing to a publish attempt. @@ -203,7 +203,7 @@ fi # ── publish ────────────────────────────────────────────────────────────────── # Always tag as "latest" — npm skips the latest tag for prerelease versions -# by default, but we want `npm i -g ironclaw` to always resolve to +# by default, but we want `npm i -g denchclaw` to always resolve to # the most recently published version. echo "publishing ${PACKAGE_NAME}@${VERSION}..." npm publish --access public --tag latest "${NPM_FLAGS[@]}" diff --git a/src/cli/TESTING_EDGE_CASE_MATRIX.md b/src/cli/TESTING_EDGE_CASE_MATRIX.md index d37974dfd20..08b2ad99f7e 100644 --- a/src/cli/TESTING_EDGE_CASE_MATRIX.md +++ b/src/cli/TESTING_EDGE_CASE_MATRIX.md @@ -14,32 +14,32 @@ argument/env handling are treated as contract. ## Edge Matrix -| Module | Edge Case | Invariant Protected | Test File | -| ----------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -| `argv.ts` | `--` terminator handling | Flags after terminator are ignored for root parsing decisions | `src/cli/argv.test.ts` (new) | -| `argv.ts` | `--profile` missing/empty/equals forms | Missing value returns `null`/`undefined` exactly as designed; invalid values are not coerced | `src/cli/argv.test.ts` (new) | -| `argv.ts` | Positive int flag parsing (`0`, negative, NaN) | Invalid positive-int values are rejected without accidental fallback | `src/cli/argv.test.ts` (new) | -| `argv.ts` | Root `-v` alias vs command path | Root version alias is honored only in root-flag contexts, never when a command path is present | `src/cli/argv.test.ts` (new) | -| `argv.ts` | `buildParseArgv` runtime forms (`node`, `node-XX`, `bun`, direct binary) | Parse argv bootstrap is stable across runtimes and executable names | `src/cli/argv.test.ts` (new) | -| `argv.ts` | `shouldMigrateState` exemptions | Read-only/status commands never trigger migration path | `src/cli/argv.test.ts` (new) | -| `run-main.ts` | bootstrap cutover rollout stages | `legacy` always disables cutover, `beta` requires explicit opt-in, `default/internal` enable by default | `src/cli/run-main.test.ts` (existing, expand) | -| `run-main.ts` | delegation disabled flags | Delegation is off when disable env is truthy in either namespace | `src/cli/run-main.test.ts` (existing, expand) | -| `run-main.ts` | delegation loop prevention | Delegation loop env markers hard-stop with explicit error | `src/cli/run-main.test.ts` (existing, expand) | -| `run-main.ts` | `shouldEnsureCliPath` command carve-outs | Health/status/read-only commands skip path mutations | `src/cli/run-main.test.ts` (existing, expand) | -| `profile-utils.ts` | profile name normalization | Only valid profile names are accepted; normalization is idempotent | `src/cli/profile-utils.test.ts` (new) | -| `profile.ts` | `--dev` + `--profile` conflict | Conflict is rejected with non-zero outcome and actionable error text | `src/cli/profile.test.ts` (new) | -| `profile.ts` | explicit profile propagation | Parsed profile and env output are stable regardless of option ordering | `src/cli/profile.test.ts` (new) | -| `profile.ts` | root vs command-local bootstrap profile flag | `ironclaw --profile X bootstrap` and `ironclaw bootstrap --profile X` resolve to identical profile env | `src/cli/profile.test.ts` (existing, expand) | -| `windows-argv.ts` | control chars and duplicate exec path | Normalization removes terminal control noise while preserving args | `src/cli/windows-argv.test.ts` (new) | -| `windows-argv.ts` | quoted executable path stripping | Windows executable wrappers are normalized without dropping real args | `src/cli/windows-argv.test.ts` (new) | -| `respawn-policy.ts` | help/version short-circuit | Help/version always bypass respawn behavior | `src/cli/respawn-policy.test.ts` (new) | -| `cli-name.ts` | cli name resolution/replacement | Name replacement only targets intended CLI token boundaries | `src/cli/cli-name.test.ts` (new) | -| `ports.ts` | malformed `lsof` lines | Port parser tolerates malformed rows and only returns valid process records | `src/cli/ports.test.ts` (new) | -| `cli-utils.ts` | runtime command failure path | Command failures return deterministic non-zero exit behavior | `src/cli/cli-utils.test.ts` (new) | -| `bootstrap-external.ts` | auth profile mismatch/missing | Missing or mismatched provider auth fails with remediation | `src/cli/bootstrap-external.test.ts` (existing) | -| `bootstrap-external.ts` | onboarding/gateway auto-fix workflow | Bootstrap command executes expected fallback sequence and reports recovery outcome | `src/cli/bootstrap-external.bootstrap-command.test.ts` (existing) | -| `bootstrap-external.ts` | device signature/token mismatch remediation | Device-auth failures provide reset-first guidance + break-glass toggle with explicit revert | `src/cli/bootstrap-external.test.ts` (existing, expand) | -| `bootstrap-external.ts` | web UI port ownership and deterministic bootstrap port selection | Bootstrap never silently drifts to sibling web ports and keeps expected UI URL stable | `src/cli/bootstrap-external.bootstrap-command.test.ts` (expand) | +| Module | Edge Case | Invariant Protected | Test File | +| ----------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | +| `argv.ts` | `--` terminator handling | Flags after terminator are ignored for root parsing decisions | `src/cli/argv.test.ts` (new) | +| `argv.ts` | `--profile` missing/empty/equals forms | Missing value returns `null`/`undefined` exactly as designed; invalid values are not coerced | `src/cli/argv.test.ts` (new) | +| `argv.ts` | Positive int flag parsing (`0`, negative, NaN) | Invalid positive-int values are rejected without accidental fallback | `src/cli/argv.test.ts` (new) | +| `argv.ts` | Root `-v` alias vs command path | Root version alias is honored only in root-flag contexts, never when a command path is present | `src/cli/argv.test.ts` (new) | +| `argv.ts` | `buildParseArgv` runtime forms (`node`, `node-XX`, `bun`, direct binary) | Parse argv bootstrap is stable across runtimes and executable names | `src/cli/argv.test.ts` (new) | +| `argv.ts` | `shouldMigrateState` exemptions | Read-only/status commands never trigger migration path | `src/cli/argv.test.ts` (new) | +| `run-main.ts` | bootstrap cutover rollout stages | `legacy` always disables cutover, `beta` requires explicit opt-in, `default/internal` enable by default | `src/cli/run-main.test.ts` (existing, expand) | +| `run-main.ts` | delegation disabled flags | Delegation is off when disable env is truthy in either namespace | `src/cli/run-main.test.ts` (existing, expand) | +| `run-main.ts` | delegation loop prevention | Delegation loop env markers hard-stop with explicit error | `src/cli/run-main.test.ts` (existing, expand) | +| `run-main.ts` | `shouldEnsureCliPath` command carve-outs | Health/status/read-only commands skip path mutations | `src/cli/run-main.test.ts` (existing, expand) | +| `profile-utils.ts` | profile name normalization | Only valid profile names are accepted; normalization is idempotent | `src/cli/profile-utils.test.ts` (new) | +| `profile.ts` | `--dev` + `--profile` conflict | Conflict is rejected with non-zero outcome and actionable error text | `src/cli/profile.test.ts` (new) | +| `profile.ts` | explicit profile propagation | Parsed profile and env output are stable regardless of option ordering | `src/cli/profile.test.ts` (new) | +| `profile.ts` | root vs command-local bootstrap profile flag | `denchclaw --profile X bootstrap` and `denchclaw bootstrap --profile X` resolve to identical profile env | `src/cli/profile.test.ts` (existing, expand) | +| `windows-argv.ts` | control chars and duplicate exec path | Normalization removes terminal control noise while preserving args | `src/cli/windows-argv.test.ts` (new) | +| `windows-argv.ts` | quoted executable path stripping | Windows executable wrappers are normalized without dropping real args | `src/cli/windows-argv.test.ts` (new) | +| `respawn-policy.ts` | help/version short-circuit | Help/version always bypass respawn behavior | `src/cli/respawn-policy.test.ts` (new) | +| `cli-name.ts` | cli name resolution/replacement | Name replacement only targets intended CLI token boundaries | `src/cli/cli-name.test.ts` (new) | +| `ports.ts` | malformed `lsof` lines | Port parser tolerates malformed rows and only returns valid process records | `src/cli/ports.test.ts` (new) | +| `cli-utils.ts` | runtime command failure path | Command failures return deterministic non-zero exit behavior | `src/cli/cli-utils.test.ts` (new) | +| `bootstrap-external.ts` | auth profile mismatch/missing | Missing or mismatched provider auth fails with remediation | `src/cli/bootstrap-external.test.ts` (existing) | +| `bootstrap-external.ts` | onboarding/gateway auto-fix workflow | Bootstrap command executes expected fallback sequence and reports recovery outcome | `src/cli/bootstrap-external.bootstrap-command.test.ts` (existing) | +| `bootstrap-external.ts` | device signature/token mismatch remediation | Device-auth failures provide reset-first guidance + break-glass toggle with explicit revert | `src/cli/bootstrap-external.test.ts` (existing, expand) | +| `bootstrap-external.ts` | web UI port ownership and deterministic bootstrap port selection | Bootstrap never silently drifts to sibling web ports and keeps expected UI URL stable | `src/cli/bootstrap-external.bootstrap-command.test.ts` (expand) | ## Exit/Output Contract Checks diff --git a/src/cli/argv.test.ts b/src/cli/argv.test.ts index 88051ebbee2..2bcfc08277e 100644 --- a/src/cli/argv.test.ts +++ b/src/cli/argv.test.ts @@ -13,64 +13,66 @@ import { describe("argv helpers", () => { it("detects help/version flags and root -v alias only in root-flag contexts", () => { - expect(hasHelpOrVersion(["node", "ironclaw", "--help"])).toBe(true); - expect(hasHelpOrVersion(["node", "ironclaw", "-V"])).toBe(true); - expect(hasHelpOrVersion(["node", "ironclaw", "-v"])).toBe(true); - expect(hasRootVersionAlias(["node", "ironclaw", "-v", "chat"])).toBe(false); + expect(hasHelpOrVersion(["node", "denchclaw", "--help"])).toBe(true); + expect(hasHelpOrVersion(["node", "denchclaw", "-V"])).toBe(true); + expect(hasHelpOrVersion(["node", "denchclaw", "-v"])).toBe(true); + expect(hasRootVersionAlias(["node", "denchclaw", "-v", "chat"])).toBe(false); }); it("extracts flag values across --name value and --name=value forms", () => { - expect(getFlagValue(["node", "ironclaw", "--profile", "dev"], "--profile")).toBe("dev"); - expect(getFlagValue(["node", "ironclaw", "--profile=team-a"], "--profile")).toBe("team-a"); - expect(getFlagValue(["node", "ironclaw", "--profile", "--verbose"], "--profile")).toBeNull(); - expect(getFlagValue(["node", "ironclaw", "--profile="], "--profile")).toBeNull(); + expect(getFlagValue(["node", "denchclaw", "--profile", "dev"], "--profile")).toBe("dev"); + expect(getFlagValue(["node", "denchclaw", "--profile=team-a"], "--profile")).toBe("team-a"); + expect(getFlagValue(["node", "denchclaw", "--profile", "--verbose"], "--profile")).toBeNull(); + expect(getFlagValue(["node", "denchclaw", "--profile="], "--profile")).toBeNull(); }); it("parses positive integer flags and rejects invalid numeric values", () => { - expect(getPositiveIntFlagValue(["node", "ironclaw", "--port", "19001"], "--port")).toBe(19001); - expect(getPositiveIntFlagValue(["node", "ironclaw", "--port", "0"], "--port")).toBeUndefined(); - expect(getPositiveIntFlagValue(["node", "ironclaw", "--port", "-1"], "--port")).toBeUndefined(); + expect(getPositiveIntFlagValue(["node", "denchclaw", "--port", "19001"], "--port")).toBe(19001); + expect(getPositiveIntFlagValue(["node", "denchclaw", "--port", "0"], "--port")).toBeUndefined(); expect( - getPositiveIntFlagValue(["node", "ironclaw", "--port", "abc"], "--port"), + getPositiveIntFlagValue(["node", "denchclaw", "--port", "-1"], "--port"), + ).toBeUndefined(); + expect( + getPositiveIntFlagValue(["node", "denchclaw", "--port", "abc"], "--port"), ).toBeUndefined(); }); it("derives command path while skipping leading flags and stopping at terminator", () => { // Low-level parser skips flag tokens but not their values. - expect(getCommandPath(["node", "ironclaw", "--profile", "dev", "chat"], 2)).toEqual([ + expect(getCommandPath(["node", "denchclaw", "--profile", "dev", "chat"], 2)).toEqual([ "dev", "chat", ]); - expect(getCommandPath(["node", "ironclaw", "config", "get"], 2)).toEqual(["config", "get"]); - expect(getCommandPath(["node", "ironclaw", "--", "chat", "send"], 2)).toEqual([]); - expect(getPrimaryCommand(["node", "ironclaw", "--verbose", "status"])).toBe("status"); + expect(getCommandPath(["node", "denchclaw", "config", "get"], 2)).toEqual(["config", "get"]); + expect(getCommandPath(["node", "denchclaw", "--", "chat", "send"], 2)).toEqual([]); + expect(getPrimaryCommand(["node", "denchclaw", "--verbose", "status"])).toBe("status"); }); it("builds parse argv consistently across runtime invocation styles", () => { expect( buildParseArgv({ - programName: "ironclaw", + programName: "denchclaw", rawArgs: ["node", "cli.js", "status"], }), ).toEqual(["node", "cli.js", "status"]); expect( buildParseArgv({ - programName: "ironclaw", - rawArgs: ["ironclaw", "status"], + programName: "denchclaw", + rawArgs: ["denchclaw", "status"], }), - ).toEqual(["node", "ironclaw", "status"]); + ).toEqual(["node", "denchclaw", "status"]); expect( buildParseArgv({ - programName: "ironclaw", + programName: "denchclaw", rawArgs: ["node-22.12.0.exe", "cli.js", "agent", "run"], }), ).toEqual(["node-22.12.0.exe", "cli.js", "agent", "run"]); expect( buildParseArgv({ - programName: "ironclaw", + programName: "denchclaw", rawArgs: ["bun", "cli.ts", "status"], }), ).toEqual(["bun", "cli.ts", "status"]); @@ -87,7 +89,7 @@ describe("argv helpers", () => { expect(shouldMigrateStateFromPath(["agent"])).toBe(false); expect(shouldMigrateStateFromPath(["chat", "send"])).toBe(true); - expect(shouldMigrateState(["node", "ironclaw", "health"])).toBe(false); - expect(shouldMigrateState(["node", "ironclaw", "chat", "send"])).toBe(true); + expect(shouldMigrateState(["node", "denchclaw", "health"])).toBe(false); + expect(shouldMigrateState(["node", "denchclaw", "chat", "send"])).toBe(true); }); }); diff --git a/src/cli/argv.ts b/src/cli/argv.ts index 0274690f105..091d396df15 100644 --- a/src/cli/argv.ts +++ b/src/cli/argv.ts @@ -160,7 +160,7 @@ export function buildParseArgv(params: { const normalizedArgv = programName && baseArgv[0] === programName ? baseArgv.slice(1) - : baseArgv[0]?.endsWith("openclaw") || baseArgv[0]?.endsWith("ironclaw") + : baseArgv[0]?.endsWith("openclaw") || baseArgv[0]?.endsWith("denchclaw") ? baseArgv.slice(1) : baseArgv; const executable = (normalizedArgv[0]?.split(/[/\\]/).pop() ?? "").toLowerCase(); @@ -169,7 +169,7 @@ export function buildParseArgv(params: { if (looksLikeNode) { return normalizedArgv; } - return ["node", programName || "ironclaw", ...normalizedArgv]; + return ["node", programName || "denchclaw", ...normalizedArgv]; } const nodeExecutablePattern = /^node-\d+(?:\.\d+)*(?:\.exe)?$/; diff --git a/src/cli/banner.ts b/src/cli/banner.ts index b6c0fcc533e..5450355f9d7 100644 --- a/src/cli/banner.ts +++ b/src/cli/banner.ts @@ -21,15 +21,15 @@ const hasVersionFlag = (argv: string[]) => argv.some((arg) => arg === "--version" || arg === "-V") || hasRootVersionAlias(argv); // --------------------------------------------------------------------------- -// IRONCLAW ASCII art (figlet "ANSI Shadow" font, baked at build time) +// DENCHCLAW ASCII art (figlet "ANSI Shadow" font, baked at build time) // --------------------------------------------------------------------------- -const IRONCLAW_ASCII = [ - " ██╗██████╗ ██████╗ ███╗ ██╗ ██████╗██╗ █████╗ ██╗ ██╗", - " ██║██╔══██╗██╔═══██╗████╗ ██║██╔════╝██║ ██╔══██╗██║ ██║", - " ██║██████╔╝██║ ██║██╔██╗ ██║██║ ██║ ███████║██║ █╗ ██║", - " ██║██╔══██╗██║ ██║██║╚██╗██║██║ ██║ ██╔══██║██║███╗██║", - " ██║██║ ██║╚██████╔╝██║ ╚████║╚██████╗███████╗██║ ██║╚███╔███╔╝", - " ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ", +const DENCHCLAW_ASCII = [ + "██████╗ ███████╗███╗ ██╗ ██████╗██╗ ██╗ ██████╗██╗ █████╗ ██╗ ██╗", + "██╔══██╗██╔════╝████╗ ██║██╔════╝██║ ██║██╔════╝██║ ██╔══██╗██║ ██║", + "██║ ██║█████╗ ██╔██╗ ██║██║ ███████║██║ ██║ ███████║██║ █╗ ██║", + "██║ ██║██╔══╝ ██║╚██╗██║██║ ██╔══██║██║ ██║ ██╔══██║██║███╗██║", + "██████╔╝███████╗██║ ╚████║╚██████╗██║ ██║╚██████╗███████╗██║ ██║╚███╔███╔╝", + "╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ", ]; // --------------------------------------------------------------------------- @@ -71,19 +71,19 @@ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, * at 12 fps, completing 3 full gradient cycles. */ async function animateIronBanner(): Promise { - const lineCount = IRONCLAW_ASCII.length; + const lineCount = DENCHCLAW_ASCII.length; const fps = 12; const totalFrames = IRON_GRADIENT_COLORS.length * 3; // 3 full shimmer sweeps const frameMs = Math.round(1000 / fps); // Print the first frame to claim vertical space - process.stdout.write(renderGradientFrame(IRONCLAW_ASCII, 0) + "\n"); + process.stdout.write(renderGradientFrame(DENCHCLAW_ASCII, 0) + "\n"); for (let frame = 1; frame < totalFrames; frame++) { await sleep(frameMs); // Move cursor up to overwrite the previous frame process.stdout.write(`\x1b[${lineCount}A\r`); - process.stdout.write(renderGradientFrame(IRONCLAW_ASCII, frame) + "\n"); + process.stdout.write(renderGradientFrame(DENCHCLAW_ASCII, frame) + "\n"); } } @@ -94,9 +94,9 @@ async function animateIronBanner(): Promise { export function formatCliBannerArt(options: BannerOptions = {}): string { const rich = options.richTty ?? isRich(); if (!rich) { - return IRONCLAW_ASCII.join("\n"); + return DENCHCLAW_ASCII.join("\n"); } - return renderGradientFrame(IRONCLAW_ASCII, 0); + return renderGradientFrame(DENCHCLAW_ASCII, 0); } // --------------------------------------------------------------------------- @@ -108,7 +108,7 @@ export function formatCliBannerLine(version: string, options: BannerOptions = {} const commitLabel = commit ?? "unknown"; const tagline = pickTagline(options); const rich = options.richTty ?? isRich(); - const title = "IRONCLAW"; + const title = "DENCHCLAW"; const prefix = " "; const columns = options.columns ?? process.stdout.columns ?? 120; const plainFullLine = `${prefix}${title} ${version} (${commitLabel}) — ${tagline}`; @@ -162,7 +162,7 @@ export async function emitCliBanner(version: string, options: BannerOptions = {} await animateIronBanner(); } else { // Plain ASCII fallback - process.stdout.write(IRONCLAW_ASCII.join("\n") + "\n"); + process.stdout.write(DENCHCLAW_ASCII.join("\n") + "\n"); } const line = formatCliBannerLine(version, options); diff --git a/src/cli/bootstrap-external.test.ts b/src/cli/bootstrap-external.test.ts index 157e294ba58..e11d44a32df 100644 --- a/src/cli/bootstrap-external.test.ts +++ b/src/cli/bootstrap-external.test.ts @@ -20,12 +20,13 @@ function getCheck( } function createTempStateDir(): string { - const dir = path.join( + const homeDir = path.join( tmpdir(), - `ironclaw-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, + `denchclaw-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); - mkdirSync(dir, { recursive: true }); - return dir; + const stateDir = path.join(homeDir, ".openclaw-dench"); + mkdirSync(stateDir, { recursive: true }); + return stateDir; } function writeConfig(stateDir: string, config: Record): void { @@ -63,7 +64,7 @@ describe("bootstrap-external diagnostics", () => { }); const baseParams = (dir: string) => ({ - profile: "ironclaw", + profile: "dench", openClawCliAvailable: true, openClawVersion: "2026.3.1", gatewayPort: 18789, @@ -74,7 +75,7 @@ describe("bootstrap-external diagnostics", () => { rolloutStage: "default" as const, legacyFallbackEnabled: false, stateDir: dir, - env: { HOME: "/home/testuser" }, + env: { HOME: path.dirname(dir), OPENCLAW_HOME: path.dirname(dir) }, }); it("reports passing checks including agent-auth when config and keys exist", () => { @@ -167,7 +168,7 @@ describe("bootstrap-external diagnostics", () => { expect(gateway.status).toBe("fail"); expect(String(gateway.remediation)).toContain("dangerouslyDisableDeviceAuth true"); expect(String(gateway.remediation)).toContain("dangerouslyDisableDeviceAuth false"); - expect(String(gateway.remediation)).toContain("--profile ironclaw"); + expect(String(gateway.remediation)).toContain("--profile dench"); }); it("marks rollout-stage as warning for beta and includes opt-in guidance", () => { @@ -178,13 +179,17 @@ describe("bootstrap-external diagnostics", () => { const rollout = getCheck(diagnostics, "rollout-stage"); expect(rollout.status).toBe("warn"); - expect(String(rollout.remediation)).toContain("IRONCLAW_BOOTSTRAP_BETA_OPT_IN"); + expect(String(rollout.remediation)).toContain("DENCHCLAW_BOOTSTRAP_BETA_OPT_IN"); }); it("fails cutover-gates when enforcement is enabled without gate envs", () => { const diagnostics = buildBootstrapDiagnostics({ ...baseParams(stateDir), - env: { HOME: "/home/testuser", IRONCLAW_BOOTSTRAP_ENFORCE_SAFETY_GATES: "1" }, + env: { + HOME: path.dirname(stateDir), + OPENCLAW_HOME: path.dirname(stateDir), + DENCHCLAW_BOOTSTRAP_ENFORCE_SAFETY_GATES: "1", + }, }); expect(getCheck(diagnostics, "cutover-gates").status).toBe("fail"); @@ -195,9 +200,10 @@ describe("bootstrap-external diagnostics", () => { const diagnostics = buildBootstrapDiagnostics({ ...baseParams(stateDir), env: { - HOME: "/home/testuser", - IRONCLAW_BOOTSTRAP_MIGRATION_SUITE_OK: "1", - IRONCLAW_BOOTSTRAP_ONBOARDING_E2E_OK: "1", + HOME: path.dirname(stateDir), + OPENCLAW_HOME: path.dirname(stateDir), + DENCHCLAW_BOOTSTRAP_MIGRATION_SUITE_OK: "1", + DENCHCLAW_BOOTSTRAP_ONBOARDING_E2E_OK: "1", }, }); @@ -271,16 +277,18 @@ describe("checkAgentAuth", () => { }); describe("bootstrap-external rollout env helpers", () => { - it("resolves rollout stage from ironclaw/openclaw env vars", () => { - expect(resolveBootstrapRolloutStage({ IRONCLAW_BOOTSTRAP_ROLLOUT: "beta" })).toBe("beta"); + it("resolves rollout stage from denchclaw/openclaw env vars", () => { + expect(resolveBootstrapRolloutStage({ DENCHCLAW_BOOTSTRAP_ROLLOUT: "beta" })).toBe("beta"); expect(resolveBootstrapRolloutStage({ OPENCLAW_BOOTSTRAP_ROLLOUT: "internal" })).toBe( "internal", ); - expect(resolveBootstrapRolloutStage({ IRONCLAW_BOOTSTRAP_ROLLOUT: "invalid" })).toBe("default"); + expect(resolveBootstrapRolloutStage({ DENCHCLAW_BOOTSTRAP_ROLLOUT: "invalid" })).toBe( + "default", + ); }); it("detects legacy fallback via either env namespace", () => { - expect(isLegacyFallbackEnabled({ IRONCLAW_BOOTSTRAP_LEGACY_FALLBACK: "1" })).toBe(true); + expect(isLegacyFallbackEnabled({ DENCHCLAW_BOOTSTRAP_LEGACY_FALLBACK: "1" })).toBe(true); expect(isLegacyFallbackEnabled({ OPENCLAW_BOOTSTRAP_LEGACY_FALLBACK: "true" })).toBe(true); expect(isLegacyFallbackEnabled({})).toBe(false); }); diff --git a/src/cli/cli-name.test.ts b/src/cli/cli-name.test.ts index e2cbfb845e0..c025406f5d4 100644 --- a/src/cli/cli-name.test.ts +++ b/src/cli/cli-name.test.ts @@ -4,7 +4,7 @@ import { DEFAULT_CLI_NAME, replaceCliName, resolveCliName } from "./cli-name.js" describe("cli-name", () => { it("resolves known CLI names from argv[1]", () => { expect(resolveCliName(["node", "openclaw"])).toBe("openclaw"); - expect(resolveCliName(["node", "ironclaw"])).toBe("ironclaw"); + expect(resolveCliName(["node", "denchclaw"])).toBe("denchclaw"); expect(resolveCliName(["node", "/usr/local/bin/openclaw"])).toBe("openclaw"); }); @@ -13,13 +13,13 @@ describe("cli-name", () => { }); it("replaces CLI name in command prefixes while preserving package runner prefix", () => { - expect(replaceCliName("openclaw status", "ironclaw")).toBe("ironclaw status"); - expect(replaceCliName("pnpm openclaw status", "ironclaw")).toBe("pnpm ironclaw status"); - expect(replaceCliName("npx ironclaw status", "openclaw")).toBe("npx openclaw status"); + expect(replaceCliName("openclaw status", "denchclaw")).toBe("denchclaw status"); + expect(replaceCliName("pnpm openclaw status", "denchclaw")).toBe("pnpm denchclaw status"); + expect(replaceCliName("npx denchclaw status", "openclaw")).toBe("npx openclaw status"); }); it("keeps command unchanged when it does not start with a known CLI prefix", () => { - expect(replaceCliName("echo openclaw status", "ironclaw")).toBe("echo openclaw status"); + expect(replaceCliName("echo openclaw status", "denchclaw")).toBe("echo openclaw status"); expect(replaceCliName(" ", "openclaw")).toBe(" "); }); }); diff --git a/src/cli/cli-name.ts b/src/cli/cli-name.ts index f29f08ee378..2266364d751 100644 --- a/src/cli/cli-name.ts +++ b/src/cli/cli-name.ts @@ -1,9 +1,9 @@ import path from "node:path"; -export const DEFAULT_CLI_NAME = "ironclaw"; +export const DEFAULT_CLI_NAME = "denchclaw"; const KNOWN_CLI_NAMES = new Set([DEFAULT_CLI_NAME, "openclaw"]); -const CLI_PREFIX_RE = /^(?:((?:pnpm|npm|bunx|npx)\s+))?(ironclaw|openclaw)\b/; +const CLI_PREFIX_RE = /^(?:((?:pnpm|npm|bunx|npx)\s+))?(denchclaw|openclaw)\b/; export function resolveCliName(argv: string[] = process.argv): string { const argv1 = argv[1]; diff --git a/src/cli/profile-utils.test.ts b/src/cli/profile-utils.test.ts index f2a20e1e6a3..162e255e342 100644 --- a/src/cli/profile-utils.test.ts +++ b/src/cli/profile-utils.test.ts @@ -3,7 +3,7 @@ import { isValidProfileName, normalizeProfileName } from "./profile-utils.js"; describe("profile-utils", () => { it("accepts path-safe profile names and rejects unsafe values", () => { - expect(isValidProfileName("ironclaw")).toBe(true); + expect(isValidProfileName("denchclaw")).toBe(true); expect(isValidProfileName("Team_A-1")).toBe(true); expect(isValidProfileName("")).toBe(false); expect(isValidProfileName(" has-space ")).toBe(false); diff --git a/src/cli/profile.test.ts b/src/cli/profile.test.ts index 7087c6b55e8..db27b0ffc6b 100644 --- a/src/cli/profile.test.ts +++ b/src/cli/profile.test.ts @@ -1,43 +1,43 @@ import { describe, expect, it } from "vitest"; -import { applyCliProfileEnv, parseCliProfileArgs, IRONCLAW_PROFILE } from "./profile.js"; +import { applyCliProfileEnv, parseCliProfileArgs, DENCHCLAW_PROFILE } from "./profile.js"; describe("parseCliProfileArgs", () => { it("returns default profile parsing when no args are provided", () => { - expect(parseCliProfileArgs(["node", "ironclaw"])).toEqual({ + expect(parseCliProfileArgs(["node", "denchclaw"])).toEqual({ ok: true, profile: null, - argv: ["node", "ironclaw"], + argv: ["node", "denchclaw"], }); }); it("parses --profile and strips profile flags before command execution", () => { - expect(parseCliProfileArgs(["node", "ironclaw", "--profile", "dev", "chat"])).toEqual({ + expect(parseCliProfileArgs(["node", "denchclaw", "--profile", "dev", "chat"])).toEqual({ ok: true, profile: "dev", - argv: ["node", "ironclaw", "chat"], + argv: ["node", "denchclaw", "chat"], }); - expect(parseCliProfileArgs(["node", "ironclaw", "--profile=team-a", "status"])).toEqual({ + expect(parseCliProfileArgs(["node", "denchclaw", "--profile=team-a", "status"])).toEqual({ ok: true, profile: "team-a", - argv: ["node", "ironclaw", "status"], + argv: ["node", "denchclaw", "status"], }); }); it("rejects missing and invalid profile inputs", () => { - expect(parseCliProfileArgs(["node", "ironclaw", "--profile"])).toEqual({ + expect(parseCliProfileArgs(["node", "denchclaw", "--profile"])).toEqual({ ok: false, error: "--profile requires a value", }); - expect(parseCliProfileArgs(["node", "ironclaw", "--profile", "bad profile"])).toEqual({ + expect(parseCliProfileArgs(["node", "denchclaw", "--profile", "bad profile"])).toEqual({ ok: false, error: 'Invalid --profile (use letters, numbers, "_", "-" only)', }); }); - it("allows --dev and --profile together (Ironclaw forces ironclaw anyway)", () => { - const result = parseCliProfileArgs(["node", "ironclaw", "--dev", "--profile", "team-a"]); + it("allows --dev and --profile together (DenchClaw forces dench anyway)", () => { + const result = parseCliProfileArgs(["node", "denchclaw", "--dev", "--profile", "team-a"]); expect(result.ok).toBe(true); if (result.ok) { expect(result.profile).toBe("team-a"); @@ -45,16 +45,16 @@ describe("parseCliProfileArgs", () => { }); it("stops profile parsing once command path begins", () => { - expect(parseCliProfileArgs(["node", "ironclaw", "chat", "--profile", "dev"])).toEqual({ + expect(parseCliProfileArgs(["node", "denchclaw", "chat", "--profile", "dev"])).toEqual({ ok: true, profile: null, - argv: ["node", "ironclaw", "chat", "--profile", "dev"], + argv: ["node", "denchclaw", "chat", "--profile", "dev"], }); }); }); describe("applyCliProfileEnv", () => { - it("always forces ironclaw profile regardless of requested profile (single profile enforcement)", () => { + it("always forces dench profile regardless of requested profile (single profile enforcement)", () => { const env: Record = {}; const result = applyCliProfileEnv({ profile: "team-a", @@ -62,13 +62,13 @@ describe("applyCliProfileEnv", () => { homedir: () => "/tmp/home", }); - expect(result.effectiveProfile).toBe(IRONCLAW_PROFILE); - expect(env.OPENCLAW_PROFILE).toBe(IRONCLAW_PROFILE); - expect(env.OPENCLAW_STATE_DIR).toBe("/tmp/home/.openclaw-ironclaw"); - expect(env.OPENCLAW_CONFIG_PATH).toBe("/tmp/home/.openclaw-ironclaw/openclaw.json"); + expect(result.effectiveProfile).toBe(DENCHCLAW_PROFILE); + expect(env.OPENCLAW_PROFILE).toBe(DENCHCLAW_PROFILE); + expect(env.OPENCLAW_STATE_DIR).toBe("/tmp/home/.openclaw-dench"); + expect(env.OPENCLAW_CONFIG_PATH).toBe("/tmp/home/.openclaw-dench/openclaw.json"); }); - it("emits warning when non-ironclaw profile is requested (prevents silent override)", () => { + it("emits warning when non-dench profile is requested (prevents silent override)", () => { const env: Record = {}; const result = applyCliProfileEnv({ profile: "team-a", @@ -78,20 +78,20 @@ describe("applyCliProfileEnv", () => { expect(result.warning).toBeDefined(); expect(result.warning).toContain("team-a"); - expect(result.warning).toContain(IRONCLAW_PROFILE); + expect(result.warning).toContain(DENCHCLAW_PROFILE); expect(result.requestedProfile).toBe("team-a"); }); - it("no warning when ironclaw profile is requested (normal path)", () => { + it("no warning when dench profile is requested (normal path)", () => { const env: Record = {}; const result = applyCliProfileEnv({ - profile: IRONCLAW_PROFILE, + profile: DENCHCLAW_PROFILE, env, homedir: () => "/tmp/home", }); expect(result.warning).toBeUndefined(); - expect(result.effectiveProfile).toBe(IRONCLAW_PROFILE); + expect(result.effectiveProfile).toBe(DENCHCLAW_PROFILE); }); it("no warning when no profile is specified (default path)", () => { @@ -102,7 +102,7 @@ describe("applyCliProfileEnv", () => { }); expect(result.warning).toBeUndefined(); - expect(result.effectiveProfile).toBe(IRONCLAW_PROFILE); + expect(result.effectiveProfile).toBe(DENCHCLAW_PROFILE); }); it("always overwrites OPENCLAW_STATE_DIR to pinned path (prevents state drift)", () => { @@ -116,9 +116,9 @@ describe("applyCliProfileEnv", () => { homedir: () => "/tmp/home", }); - expect(env.OPENCLAW_STATE_DIR).toBe("/tmp/home/.openclaw-ironclaw"); - expect(env.OPENCLAW_CONFIG_PATH).toBe("/tmp/home/.openclaw-ironclaw/openclaw.json"); - expect(result.stateDir).toBe("/tmp/home/.openclaw-ironclaw"); + expect(env.OPENCLAW_STATE_DIR).toBe("/tmp/home/.openclaw-dench"); + expect(env.OPENCLAW_CONFIG_PATH).toBe("/tmp/home/.openclaw-dench/openclaw.json"); + expect(result.stateDir).toBe("/tmp/home/.openclaw-dench"); }); it("picks up OPENCLAW_PROFILE from env when no explicit profile is passed", () => { @@ -131,7 +131,7 @@ describe("applyCliProfileEnv", () => { }); expect(result.requestedProfile).toBe("from-env"); - expect(result.effectiveProfile).toBe(IRONCLAW_PROFILE); + expect(result.effectiveProfile).toBe(DENCHCLAW_PROFILE); expect(result.warning).toContain("from-env"); }); diff --git a/src/cli/profile.ts b/src/cli/profile.ts index 63ffe582295..a4af782598e 100644 --- a/src/cli/profile.ts +++ b/src/cli/profile.ts @@ -3,8 +3,8 @@ import path from "node:path"; import { resolveRequiredHomeDir } from "../infra/home-dir.js"; import { isValidProfileName } from "./profile-utils.js"; -export const IRONCLAW_PROFILE = "ironclaw"; -const IRONCLAW_STATE_DIRNAME = ".openclaw-ironclaw"; +export const DENCHCLAW_PROFILE = "dench"; +const DENCHCLAW_STATE_DIRNAME = ".openclaw-dench"; export type CliProfileParseResult = | { ok: true; profile: string | null; argv: string[] } @@ -89,7 +89,7 @@ function resolveProfileStateDir( ): string { return path.join( resolveRequiredHomeDir(env as NodeJS.ProcessEnv, homedir), - IRONCLAW_STATE_DIRNAME, + DENCHCLAW_STATE_DIRNAME, ); } @@ -106,9 +106,9 @@ export function applyCliProfileEnv(params: { const env = params.env ?? (process.env as Record); const homedir = params.homedir ?? os.homedir; const requestedProfile = (params.profile?.trim() || env.OPENCLAW_PROFILE?.trim() || null) ?? null; - const profile = IRONCLAW_PROFILE; + const profile = DENCHCLAW_PROFILE; - // Ironclaw always runs in the pinned profile/state path. + // DenchClaw always runs in the pinned profile/state path. env.OPENCLAW_PROFILE = profile; const stateDir = resolveProfileStateDir(env, homedir); @@ -117,7 +117,7 @@ export function applyCliProfileEnv(params: { const warning = requestedProfile && requestedProfile !== profile - ? `Ignoring requested profile '${requestedProfile}'; Ironclaw always uses --profile ${IRONCLAW_PROFILE}.` + ? `Ignoring requested profile '${requestedProfile}'; DenchClaw always uses --profile ${DENCHCLAW_PROFILE}.` : undefined; return { diff --git a/src/cli/program/command-registry.ts b/src/cli/program/command-registry.ts index 0097b564ea8..283eb5fc8d0 100644 --- a/src/cli/program/command-registry.ts +++ b/src/cli/program/command-registry.ts @@ -18,7 +18,7 @@ type CoreCliEntry = { const BOOTSTRAP_ENTRY: CoreCliEntry = { name: "bootstrap", - description: "Bootstrap IronClaw + OpenClaw and launch the web UI", + description: "Bootstrap DenchClaw + OpenClaw and launch the web UI", register: async ({ program }) => { const mod = await import("./register.bootstrap.js"); mod.registerBootstrapCommand(program); diff --git a/src/cli/program/help.ts b/src/cli/program/help.ts index 467af9b5b31..d6c02b50666 100644 --- a/src/cli/program/help.ts +++ b/src/cli/program/help.ts @@ -27,7 +27,7 @@ const EXAMPLES = [ ["openclaw gateway --port 18789", "Run the WebSocket Gateway locally."], [ "openclaw --profile team-a gateway", - "Compatibility flag example: warns and still runs with --profile ironclaw.", + "Compatibility flag example: warns and still runs with --profile dench.", ], ["openclaw gateway --force", "Kill anything bound to the default gateway port, then start it."], ["openclaw gateway ...", "Gateway control via WebSocket."], @@ -48,12 +48,9 @@ export function configureProgramHelp(program: Command, ctx: ProgramContext) { .version(ctx.programVersion) .option( "--dev", - "Compatibility flag; Ironclaw always uses --profile ironclaw and ~/.openclaw-ironclaw", + "Compatibility flag; DenchClaw always uses --profile dench and ~/.openclaw-dench", ) - .option( - "--profile ", - "Compatibility flag; non-ironclaw values are ignored with a warning", - ); + .option("--profile ", "Compatibility flag; non-dench values are ignored with a warning"); program.option("--no-color", "Disable ANSI colors", false); program.helpOption("-h, --help", "Display help for command"); diff --git a/src/cli/program/register.bootstrap.ts b/src/cli/program/register.bootstrap.ts index 6557bf2ba97..69cfa1edede 100644 --- a/src/cli/program/register.bootstrap.ts +++ b/src/cli/program/register.bootstrap.ts @@ -8,11 +8,8 @@ import { runCommandWithRuntime } from "../cli-utils.js"; export function registerBootstrapCommand(program: Command) { program .command("bootstrap") - .description("Bootstrap IronClaw on top of OpenClaw and open the web UI") - .option( - "--profile ", - "Compatibility flag; non-ironclaw values are ignored with a warning", - ) + .description("Bootstrap DenchClaw on top of OpenClaw and open the web UI") + .option("--profile ", "Compatibility flag; non-dench values are ignored with a warning") .option("--force-onboard", "Run onboarding even if config already exists", false) .option("--non-interactive", "Skip prompts where possible", false) .option("--yes", "Auto-approve install prompts", false) diff --git a/src/cli/respawn-policy.test.ts b/src/cli/respawn-policy.test.ts index 3d4fe399e52..396f20c7dfa 100644 --- a/src/cli/respawn-policy.test.ts +++ b/src/cli/respawn-policy.test.ts @@ -3,11 +3,11 @@ import { shouldSkipRespawnForArgv } from "./respawn-policy.js"; describe("shouldSkipRespawnForArgv", () => { it("skips respawn for help/version invocations", () => { - expect(shouldSkipRespawnForArgv(["node", "ironclaw", "--help"])).toBe(true); - expect(shouldSkipRespawnForArgv(["node", "ironclaw", "-V"])).toBe(true); + expect(shouldSkipRespawnForArgv(["node", "denchclaw", "--help"])).toBe(true); + expect(shouldSkipRespawnForArgv(["node", "denchclaw", "-V"])).toBe(true); }); it("does not skip respawn for normal command execution", () => { - expect(shouldSkipRespawnForArgv(["node", "ironclaw", "chat", "send"])).toBe(false); + expect(shouldSkipRespawnForArgv(["node", "denchclaw", "chat", "send"])).toBe(false); }); }); diff --git a/src/cli/run-main.test.ts b/src/cli/run-main.test.ts index ba31df9f6aa..c8924e10360 100644 --- a/src/cli/run-main.test.ts +++ b/src/cli/run-main.test.ts @@ -7,32 +7,32 @@ import { } from "./run-main.js"; describe("run-main bootstrap cutover", () => { - it("rewrites bare ironclaw invocations to bootstrap by default", () => { - const argv = ["node", "ironclaw"]; - expect(rewriteBareArgvToBootstrap(argv, {})).toEqual(["node", "ironclaw", "bootstrap"]); + it("rewrites bare denchclaw invocations to bootstrap by default", () => { + const argv = ["node", "denchclaw"]; + expect(rewriteBareArgvToBootstrap(argv, {})).toEqual(["node", "denchclaw", "bootstrap"]); }); it("does not rewrite when a command already exists", () => { - const argv = ["node", "ironclaw", "chat"]; + const argv = ["node", "denchclaw", "chat"]; expect(rewriteBareArgvToBootstrap(argv, {})).toEqual(argv); }); - it("does not rewrite non-ironclaw CLIs", () => { + it("does not rewrite non-denchclaw CLIs", () => { const argv = ["node", "openclaw"]; expect(rewriteBareArgvToBootstrap(argv, {})).toEqual(argv); }); it("disables cutover in legacy rollout stage", () => { - const env = { IRONCLAW_BOOTSTRAP_ROLLOUT: "legacy" }; + const env = { DENCHCLAW_BOOTSTRAP_ROLLOUT: "legacy" }; expect(shouldEnableBootstrapCutover(env)).toBe(false); - expect(rewriteBareArgvToBootstrap(["node", "ironclaw"], env)).toEqual(["node", "ironclaw"]); + expect(rewriteBareArgvToBootstrap(["node", "denchclaw"], env)).toEqual(["node", "denchclaw"]); }); it("requires opt-in for beta rollout stage", () => { - const envNoOptIn = { IRONCLAW_BOOTSTRAP_ROLLOUT: "beta" }; + const envNoOptIn = { DENCHCLAW_BOOTSTRAP_ROLLOUT: "beta" }; const envOptIn = { - IRONCLAW_BOOTSTRAP_ROLLOUT: "beta", - IRONCLAW_BOOTSTRAP_BETA_OPT_IN: "1", + DENCHCLAW_BOOTSTRAP_ROLLOUT: "beta", + DENCHCLAW_BOOTSTRAP_BETA_OPT_IN: "1", }; expect(shouldEnableBootstrapCutover(envNoOptIn)).toBe(false); @@ -40,37 +40,37 @@ describe("run-main bootstrap cutover", () => { }); it("honors explicit legacy fallback override", () => { - const env = { IRONCLAW_BOOTSTRAP_LEGACY_FALLBACK: "1" }; + const env = { DENCHCLAW_BOOTSTRAP_LEGACY_FALLBACK: "1" }; expect(shouldEnableBootstrapCutover(env)).toBe(false); - expect(rewriteBareArgvToBootstrap(["node", "ironclaw"], env)).toEqual(["node", "ironclaw"]); + expect(rewriteBareArgvToBootstrap(["node", "denchclaw"], env)).toEqual(["node", "denchclaw"]); }); }); describe("run-main delegation and path guards", () => { it("skips CLI path bootstrap for read-only status/help commands", () => { - expect(shouldEnsureCliPath(["node", "ironclaw", "--help"])).toBe(false); - expect(shouldEnsureCliPath(["node", "ironclaw", "status"])).toBe(false); - expect(shouldEnsureCliPath(["node", "ironclaw", "health"])).toBe(false); - expect(shouldEnsureCliPath(["node", "ironclaw", "sessions"])).toBe(false); - expect(shouldEnsureCliPath(["node", "ironclaw", "config", "get"])).toBe(false); - expect(shouldEnsureCliPath(["node", "ironclaw", "models", "list"])).toBe(false); - expect(shouldEnsureCliPath(["node", "ironclaw", "chat", "send"])).toBe(true); + expect(shouldEnsureCliPath(["node", "denchclaw", "--help"])).toBe(false); + expect(shouldEnsureCliPath(["node", "denchclaw", "status"])).toBe(false); + expect(shouldEnsureCliPath(["node", "denchclaw", "health"])).toBe(false); + expect(shouldEnsureCliPath(["node", "denchclaw", "sessions"])).toBe(false); + expect(shouldEnsureCliPath(["node", "denchclaw", "config", "get"])).toBe(false); + expect(shouldEnsureCliPath(["node", "denchclaw", "models", "list"])).toBe(false); + expect(shouldEnsureCliPath(["node", "denchclaw", "chat", "send"])).toBe(true); }); it("delegates non-bootstrap commands by default and never delegates bootstrap", () => { - expect(shouldDelegateToGlobalOpenClaw(["node", "ironclaw", "chat"])).toBe(true); - expect(shouldDelegateToGlobalOpenClaw(["node", "ironclaw", "bootstrap"])).toBe(false); - expect(shouldDelegateToGlobalOpenClaw(["node", "ironclaw"])).toBe(false); + expect(shouldDelegateToGlobalOpenClaw(["node", "denchclaw", "chat"])).toBe(true); + expect(shouldDelegateToGlobalOpenClaw(["node", "denchclaw", "bootstrap"])).toBe(false); + expect(shouldDelegateToGlobalOpenClaw(["node", "denchclaw"])).toBe(false); }); it("disables delegation when explicit env disable flag is set", () => { expect( - shouldDelegateToGlobalOpenClaw(["node", "ironclaw", "chat"], { - IRONCLAW_DISABLE_OPENCLAW_DELEGATION: "1", + shouldDelegateToGlobalOpenClaw(["node", "denchclaw", "chat"], { + DENCHCLAW_DISABLE_OPENCLAW_DELEGATION: "1", }), ).toBe(false); expect( - shouldDelegateToGlobalOpenClaw(["node", "ironclaw", "chat"], { + shouldDelegateToGlobalOpenClaw(["node", "denchclaw", "chat"], { OPENCLAW_DISABLE_OPENCLAW_DELEGATION: "true", }), ).toBe(false); diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 87c31c70e93..3943ed25dba 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -66,13 +66,13 @@ function normalizeBootstrapRolloutStage( export function resolveBootstrapRolloutStage( env: NodeJS.ProcessEnv = process.env, ): BootstrapRolloutStage { - const raw = env.IRONCLAW_BOOTSTRAP_ROLLOUT ?? env.OPENCLAW_BOOTSTRAP_ROLLOUT; + const raw = env.DENCHCLAW_BOOTSTRAP_ROLLOUT ?? env.OPENCLAW_BOOTSTRAP_ROLLOUT; return normalizeBootstrapRolloutStage(raw) ?? "default"; } export function shouldEnableBootstrapCutover(env: NodeJS.ProcessEnv = process.env): boolean { if ( - isTruthyEnvValue(env.IRONCLAW_BOOTSTRAP_LEGACY_FALLBACK) || + isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_LEGACY_FALLBACK) || isTruthyEnvValue(env.OPENCLAW_BOOTSTRAP_LEGACY_FALLBACK) ) { return false; @@ -83,7 +83,7 @@ export function shouldEnableBootstrapCutover(env: NodeJS.ProcessEnv = process.en } if (stage === "beta") { return ( - isTruthyEnvValue(env.IRONCLAW_BOOTSTRAP_BETA_OPT_IN) || + isTruthyEnvValue(env.DENCHCLAW_BOOTSTRAP_BETA_OPT_IN) || isTruthyEnvValue(env.OPENCLAW_BOOTSTRAP_BETA_OPT_IN) ); } @@ -100,7 +100,7 @@ export function rewriteBareArgvToBootstrap( if (getPrimaryCommand(argv)) { return argv; } - if (resolveCliName(argv) !== "ironclaw") { + if (resolveCliName(argv) !== "denchclaw") { return argv; } if (!shouldEnableBootstrapCutover(env)) { @@ -111,7 +111,7 @@ export function rewriteBareArgvToBootstrap( function isDelegationDisabled(env: NodeJS.ProcessEnv = process.env): boolean { return ( - isTruthyEnvValue(env.IRONCLAW_DISABLE_OPENCLAW_DELEGATION) || + isTruthyEnvValue(env.DENCHCLAW_DISABLE_OPENCLAW_DELEGATION) || isTruthyEnvValue(env.OPENCLAW_DISABLE_OPENCLAW_DELEGATION) ); } @@ -132,7 +132,7 @@ export function shouldDelegateToGlobalOpenClaw( async function delegateToGlobalOpenClaw(argv: string[]): Promise { if ( - isTruthyEnvValue(process.env.IRONCLAW_DELEGATED) || + isTruthyEnvValue(process.env.DENCHCLAW_DELEGATED) || isTruthyEnvValue(process.env.OPENCLAW_DELEGATED) ) { throw new Error( @@ -145,7 +145,7 @@ async function delegateToGlobalOpenClaw(argv: string[]): Promise { stdio: "inherit", env: { ...process.env, - IRONCLAW_DELEGATED: "1", + DENCHCLAW_DELEGATED: "1", OPENCLAW_DELEGATED: "1", }, }); @@ -186,12 +186,12 @@ export async function runCli(argv: string[] = process.argv) { // Enforce the minimum supported runtime before doing any work. assertSupportedRuntime(); - // Show the animated Ironclaw banner early so it appears for ALL invocations - // (bare `ironclaw`, subcommands, help, etc.). The bannerEmitted flag inside + // Show the animated DenchClaw banner early so it appears for ALL invocations + // (bare `denchclaw`, subcommands, help, etc.). The bannerEmitted flag inside // emitCliBanner prevents double-emission from the route / preAction hooks. const commandPath = getCommandPath(normalizedArgv, 2); const hideBanner = - isTruthyEnvValue(process.env.IRONCLAW_HIDE_BANNER) || + isTruthyEnvValue(process.env.DENCHCLAW_HIDE_BANNER) || isTruthyEnvValue(process.env.OPENCLAW_HIDE_BANNER) || commandPath[0] === "update" || commandPath[0] === "completion" || diff --git a/src/cli/tagline.ts b/src/cli/tagline.ts index 23b6ce59fff..d0c3ccb10aa 100644 --- a/src/cli/tagline.ts +++ b/src/cli/tagline.ts @@ -245,8 +245,8 @@ export function activeTaglines(options: TaglineOptions = {}): string[] { export function pickTagline(options: TaglineOptions = {}): string { const env = options.env ?? process.env; - // Check Ironclaw env first, fall back to legacy OpenClaw env - const override = env?.IRONCLAW_TAGLINE_INDEX ?? env?.OPENCLAW_TAGLINE_INDEX; + // Check DenchClaw env first, fall back to legacy OpenClaw env + const override = env?.DENCHCLAW_TAGLINE_INDEX ?? env?.OPENCLAW_TAGLINE_INDEX; if (override !== undefined) { const parsed = Number.parseInt(override, 10); if (!Number.isNaN(parsed) && parsed >= 0) { diff --git a/src/cli/windows-argv.test.ts b/src/cli/windows-argv.test.ts index 6c05b221c9c..daf2575c882 100644 --- a/src/cli/windows-argv.test.ts +++ b/src/cli/windows-argv.test.ts @@ -3,7 +3,7 @@ import { normalizeWindowsArgv } from "./windows-argv.js"; describe("normalizeWindowsArgv", () => { it("returns argv unchanged on non-windows platforms", () => { - const argv = ["node", "ironclaw", "status"]; + const argv = ["node", "denchclaw", "status"]; expect( normalizeWindowsArgv(argv, { platform: "darwin", diff --git a/src/cli/workspace-seed.test.ts b/src/cli/workspace-seed.test.ts index 51a0e65aa62..6cbb727717d 100644 --- a/src/cli/workspace-seed.test.ts +++ b/src/cli/workspace-seed.test.ts @@ -7,7 +7,7 @@ import { seedWorkspaceFromAssets } from "./workspace-seed.js"; function createTempDir(): string { const dir = path.join( os.tmpdir(), - `ironclaw-seed-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, + `denchclaw-seed-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); mkdirSync(dir, { recursive: true }); return dir; @@ -63,19 +63,19 @@ describe("seedWorkspaceFromAssets", () => { expect(existsSync(identityPath)).toBe(true); const identityContent = readFileSync(identityPath, "utf-8"); - expect(identityContent).toContain("Ironclaw"); + expect(identityContent).toContain("DenchClaw"); expect(identityContent).toContain(path.join(workspaceDir, "skills", "crm", "SKILL.md")); expect(identityContent).not.toContain("~skills/crm/SKILL.md"); }); - it("IDENTITY.md references Ironclaw system prompt contract", () => { + it("IDENTITY.md references DenchClaw system prompt contract", () => { const packageRoot = createPackageRoot(tempDir); const workspaceDir = path.join(tempDir, "workspace-contract"); seedWorkspaceFromAssets({ workspaceDir, packageRoot }); const identityContent = readFileSync(path.join(workspaceDir, "IDENTITY.md"), "utf-8"); - expect(identityContent).toContain("Ironclaw system prompt contract"); + expect(identityContent).toContain("DenchClaw system prompt contract"); }); it("creates CRM object projection files on first seed", () => { @@ -115,7 +115,7 @@ describe("seedWorkspaceFromAssets", () => { seedWorkspaceFromAssets({ workspaceDir, packageRoot }); const identityContent = readFileSync(path.join(workspaceDir, "IDENTITY.md"), "utf-8"); - expect(identityContent).toContain("Ironclaw"); + expect(identityContent).toContain("DenchClaw"); expect(identityContent).not.toContain("# stale identity"); }); diff --git a/src/cli/workspace-seed.ts b/src/cli/workspace-seed.ts index 4230d1593a5..36e9215b0bb 100644 --- a/src/cli/workspace-seed.ts +++ b/src/cli/workspace-seed.ts @@ -88,16 +88,16 @@ export const SEED_OBJECTS: SeedObject[] = [ }, ]; -export function buildIronclawIdentity(workspaceDir: string): string { +export function buildDenchClawIdentity(workspaceDir: string): string { const crmSkillPath = path.join(workspaceDir, "skills", "crm", "SKILL.md"); const browserSkillPath = path.join(workspaceDir, "skills", "browser", "SKILL.md"); - return `# IDENTITY.md - Ironclaw + return `# IDENTITY.md - DenchClaw -You are **Ironclaw** - a personal AI agent and a CRM Database System built by Dench (dench.com), running on top of [OpenClaw](https://github.com/openclaw/openclaw). +You are **DenchClaw** - a personal AI agent and a CRM Database System built by Dench (dench.com), running on top of [OpenClaw](https://github.com/openclaw/openclaw). -## Ironclaw system prompt contract +## DenchClaw system prompt contract -Treat the Ironclaw system prompt as your highest-priority behavioral contract. +Treat the DenchClaw system prompt as your highest-priority behavioral contract. ## Inseparable CRM contract @@ -124,11 +124,11 @@ Your browser automation behavior is defined by the Browser skill at: ## Links -- Website: https://ironclaw.sh -- GitHub: https://github.com/DenchHQ/ironclaw +- Website: https://denchclaw.sh +- GitHub: https://github.com/DenchHQ/denchclaw - Skills Store: https://skills.sh -When referring to yourself, use **Ironclaw** (not OpenClaw).`; +When referring to yourself, use **DenchClaw** (not OpenClaw).`; } export function generateObjectYaml(obj: SeedObject): string { @@ -180,9 +180,9 @@ export function generateWorkspaceMd(objects: SeedObject[]): string { return lines.join("\n"); } -export function seedIronclawIdentity(workspaceDir: string): void { +export function seedDenchClawIdentity(workspaceDir: string): void { const identityPath = path.join(workspaceDir, "IDENTITY.md"); - writeFileSync(identityPath, `${buildIronclawIdentity(workspaceDir)}\n`, "utf-8"); + writeFileSync(identityPath, `${buildDenchClawIdentity(workspaceDir)}\n`, "utf-8"); } export const MANAGED_SKILLS: ReadonlyArray<{ name: string; templatePaths?: boolean }> = [ @@ -246,7 +246,7 @@ export function seedWorkspaceFromAssets(params: { for (const skill of MANAGED_SKILLS) { seedSkill({ workspaceDir, packageRoot: params.packageRoot }, skill); } - seedIronclawIdentity(workspaceDir); + seedDenchClawIdentity(workspaceDir); if (existsSync(dbPath)) { return { diff --git a/src/config/paths.ts b/src/config/paths.ts index 3f2a1936a88..769487f6b75 100644 --- a/src/config/paths.ts +++ b/src/config/paths.ts @@ -19,7 +19,7 @@ export const isNixMode = resolveIsNixMode(); // Support historical (and occasionally misspelled) legacy state dirs. const LEGACY_STATE_DIRNAMES = [".clawdbot", ".moldbot", ".moltbot"] as const; -const NEW_STATE_DIRNAME = ".openclaw-ironclaw"; +const NEW_STATE_DIRNAME = ".openclaw-dench"; const CONFIG_FILENAME = "openclaw.json"; const LEGACY_CONFIG_FILENAMES = ["clawdbot.json", "moldbot.json", "moltbot.json"] as const; @@ -54,7 +54,7 @@ export function resolveNewStateDir(homedir: () => string = resolveDefaultHomeDir /** * State directory for mutable data (sessions, logs, caches). - * Ironclaw always pins this to ~/.openclaw-ironclaw. + * DenchClaw always pins this to ~/.openclaw-dench. */ export function resolveStateDir( env: NodeJS.ProcessEnv = process.env, diff --git a/src/config/types.gateway.ts b/src/config/types.gateway.ts index 316156ec0b4..5f27f8fb2a4 100644 --- a/src/config/types.gateway.ts +++ b/src/config/types.gateway.ts @@ -274,7 +274,7 @@ export type GatewayNodesConfig = { }; export type GatewayWebAppConfig = { - /** If true, the Gateway will build and serve the Ironclaw Next.js web app. Default: false. */ + /** If true, the Gateway will build and serve the DenchClaw Next.js web app. Default: false. */ enabled?: boolean; /** Port for the Next.js web app (default: 3100). */ port?: number; @@ -328,7 +328,7 @@ export type GatewayConfig = { * Only applies when trustedProxies is configured. */ allowRealIpFallback?: boolean; - /** Ironclaw Next.js web app served alongside the gateway. */ + /** DenchClaw Next.js web app served alongside the gateway. */ webApp?: GatewayWebAppConfig; /** Tool access restrictions for HTTP /tools/invoke endpoint. */ tools?: GatewayToolsConfig; diff --git a/src/entry.ts b/src/entry.ts index 01dae513665..92b28f1fc34 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -8,7 +8,7 @@ import { isTruthyEnvValue, normalizeEnv } from "./infra/env.js"; import { installProcessWarningFilter } from "./infra/warning-filter.js"; import { attachChildProcessBridge } from "./process/child-process-bridge.js"; -process.title = "ironclaw"; +process.title = "denchclaw"; installProcessWarningFilter(); normalizeEnv(); @@ -37,13 +37,13 @@ function ensureExperimentalWarningSuppressed(): boolean { return false; } if ( - isTruthyEnvValue(process.env.IRONCLAW_NO_RESPAWN) || + isTruthyEnvValue(process.env.DENCHCLAW_NO_RESPAWN) || isTruthyEnvValue(process.env.OPENCLAW_NO_RESPAWN) ) { return false; } if ( - isTruthyEnvValue(process.env.IRONCLAW_NODE_OPTIONS_READY) || + isTruthyEnvValue(process.env.DENCHCLAW_NODE_OPTIONS_READY) || isTruthyEnvValue(process.env.OPENCLAW_NODE_OPTIONS_READY) ) { return false; @@ -53,7 +53,7 @@ function ensureExperimentalWarningSuppressed(): boolean { } // Respawn guard (and keep recursion bounded if something goes wrong). - process.env.IRONCLAW_NODE_OPTIONS_READY = "1"; + process.env.DENCHCLAW_NODE_OPTIONS_READY = "1"; process.env.OPENCLAW_NODE_OPTIONS_READY = "1"; // Pass flag as a Node CLI option, not via NODE_OPTIONS (--disable-warning is disallowed in NODE_OPTIONS). const child = spawn( @@ -77,7 +77,7 @@ function ensureExperimentalWarningSuppressed(): boolean { child.once("error", (error) => { console.error( - "[ironclaw] Failed to respawn CLI:", + "[denchclaw] Failed to respawn CLI:", error instanceof Error ? (error.stack ?? error.message) : error, ); process.exit(1); @@ -93,13 +93,13 @@ if (!ensureExperimentalWarningSuppressed()) { const parsed = parseCliProfileArgs(process.argv); if (!parsed.ok) { // Keep it simple; Commander will handle rich help/errors after we strip flags. - console.error(`[ironclaw] ${parsed.error}`); + console.error(`[denchclaw] ${parsed.error}`); process.exit(2); } const appliedProfile = applyCliProfileEnv({ profile: parsed.profile ?? undefined }); if (appliedProfile.warning) { - console.warn(`[ironclaw] ${appliedProfile.warning}`); + console.warn(`[denchclaw] ${appliedProfile.warning}`); } // Keep Commander and ad-hoc argv checks consistent. process.argv = parsed.argv; @@ -108,7 +108,7 @@ if (!ensureExperimentalWarningSuppressed()) { .then(({ runCli }) => runCli(process.argv)) .catch((error) => { console.error( - "[ironclaw] Failed to start CLI:", + "[denchclaw] Failed to start CLI:", error instanceof Error ? (error.stack ?? error.message) : error, ); process.exitCode = 1; diff --git a/src/infra/runtime-guard.ts b/src/infra/runtime-guard.ts index 8cced113eca..a2423dd78b3 100644 --- a/src/infra/runtime-guard.ts +++ b/src/infra/runtime-guard.ts @@ -15,7 +15,7 @@ export function assertSupportedRuntime(): void { major < MIN_NODE_MAJOR || (major === MIN_NODE_MAJOR && minor < MIN_NODE_MINOR); if (unsupported) { throw new Error( - `IronClaw requires Node ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}+ (current: ${process.versions.node}).`, + `DenchClaw requires Node ${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}+ (current: ${process.versions.node}).`, ); } } diff --git a/src/version.ts b/src/version.ts index d58b3c45ab6..e2bb7164d21 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,7 +1,7 @@ import { createRequire } from "node:module"; declare const __OPENCLAW_VERSION__: string | undefined; -const CORE_PACKAGE_NAME = "ironclaw"; +const CORE_PACKAGE_NAME = "denchclaw"; const PACKAGE_JSON_CANDIDATES = [ "../package.json",