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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "switchbot",
"owner": {
"name": "OpenWonderLabs",
"email": "developer@wondertechlabs.com"
},
"plugins": [
{
"name": "switchbot",
"source": "./packages/codex-plugin/plugins/switchbot",
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"source": "./packages/claude-code-plugin/plugins/switchbot",
"description": "Control SwitchBot smart-home devices from Claude Code via MCP.",
"category": "Productivity"
}
]
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [3.7.7]

### Added

- **`switchbot claude-code setup` command** — 6-step pipeline that bootstraps Claude Code integration end-to-end: verify `claude` CLI on PATH, optionally install `@switchbot/openapi-cli`, register the MCP server via `claude mcp add --scope user`, authenticate, and run a health check. Supports `--yes` (non-interactive), `--skip`, `--dry-run`, `--json`. Paste `npx @switchbot/openapi-cli claude-code setup` into Claude Code chat to set up without opening a terminal.
- **Claude Code Plugin Marketplace support** — `.claude-plugin/marketplace.json` now correctly points to `packages/claude-code-plugin/plugins/switchbot` so `/plugin marketplace add OpenWonderLabs/switchbot-openapi-cli` followed by `/plugin install switchbot@switchbot` works end-to-end. `smoke-codex-git-sparse.mjs` extended to validate both the Claude Code and Codex marketplace entry points.

### Fixed

- **`switchbot auth login` clears device/status cache** — switching accounts no longer returns stale data from the previous account's cache. `clearCache()` and `clearStatusCache()` are called immediately after new credentials are saved.
- **`codex setup --help` / `claude-code setup --help`** — both commands now list `--dry-run` and `--json` under a "Global flags that also apply to this command" section so users do not have to discover them from the README.

## [3.7.5]

### Fixed
Expand Down
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,14 @@ Example body:
The CHANGELOG itself follows Keep a Changelog + SemVer, with a
**Changed (BREAKING)** section whenever a release introduces a breaking
change (even in a patch version).

## Plugin hook strategies

The two plugin packages use different `onInstall` hook strategies — this is intentional:

| Package | Hook command | Reason |
|---------|-------------|--------|
| `packages/claude-code-plugin` | `node ../bin/auth.js` (relative path) | Claude Code installs the full npm package; `bin/` is always adjacent. |
| `packages/codex-plugin` | `switchbot-codex-auth` (global binary) | Codex may install only the `plugins/switchbot/` sub-directory, placing it in `~/.codex/plugins/switchbot/`. A relative path to `bin/auth.js` would escape the plugin directory and fail. Using the globally-installed binary works regardless of install layout. |

When changing either hook, preserve this distinction. Do not "unify" them without first verifying that both installation layouts still resolve the hook correctly.
51 changes: 36 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,16 @@ Then restart Codex and confirm it's working.
**Or install directly:**

```bash
npm install -g @switchbot/codex-plugin
switchbot-codex-install # registers the plugin with Codex
switchbot auth login # browser OAuth — saves to OS keychain
npm install -g @switchbot/openapi-cli @switchbot/codex-plugin
switchbot codex setup # one-shot bootstrap: register, auth, verify
```

**Or install via Codex Plugin Marketplace** (requires Codex CLI with marketplace support):

```bash
codex plugin marketplace add OpenWonderLabs/switchbot-openapi-cli
codex plugin add switchbot@switchbot
switchbot auth login
```

---
Expand All @@ -107,9 +114,10 @@ switchbot auth login # browser OAuth — saves to OS keychain
The OpenClaw skill is published to npm as [`@switchbot/openclaw-skill`](https://www.npmjs.com/package/@switchbot/openclaw-skill).

```bash
openclaw plugins install @switchbot/openclaw-skill # via OpenClaw plugin manager (recommended)
npm install -g @switchbot/openapi-cli # required CLI
openclaw plugins install @switchbot/openclaw-skill # via OpenClaw plugin manager (recommended)
# or
npm install -g @switchbot/openclaw-skill # via npm
npm install -g @switchbot/openclaw-skill # via npm

switchbot-openclaw setup # verify CLI install and credentials
switchbot auth login # browser OAuth — saves to OS keychain
Expand All @@ -121,27 +129,40 @@ switchbot auth login # browser OAuth — saves to OS keychain

## Claude Code integration

**Step 0 — verify first.** If the MCP server is already configured (e.g. via a managed environment or prior setup), no additional steps are needed:
**Already configured? Paste into Claude Code chat:**

```bash
switchbot doctor --json
```
Is my SwitchBot integration working? Please check and list my devices.
```

Claude will call `account_overview` or `switchbot doctor` to verify — no terminal needed.

**Fresh setup — paste into Claude Code chat:**

```
Please set up my SwitchBot integration by running:
npx @switchbot/openapi-cli claude-code setup
```

If 24 tools are visible and the integration is healthy, you're done.
Claude will run the setup command via the Bash tool. It installs the CLI if missing, registers the MCP server via `claude mcp add --scope user`, and opens a browser login if credentials are not yet configured. Restart Claude Code afterwards to load the MCP tools.

**Fresh setup:**
**Or run manually in your terminal:**

```bash
npm install -g @switchbot/openapi-cli
claude mcp add switchbot -- switchbot mcp serve --tools all
switchbot auth login # browser OAuth — saves to OS keychain
switchbot claude-code setup
```

Run `switchbot doctor --json` afterwards to confirm everything is working.
The optional skill package [`@switchbot/claude-code-plugin`](https://www.npmjs.com/package/@switchbot/claude-code-plugin) bundles the SKILL.md context document. Install it only if your environment does not already load the skill automatically.

The optional skill package [`@switchbot/claude-code-plugin`](https://www.npmjs.com/package/@switchbot/claude-code-plugin) bundles the SKILL.md context document and install hook. Install it only if your environment does not already load the skill automatically.
**Or install via Claude Code Plugin Marketplace** (requires Claude Code with plugin marketplace support enabled):

```
/plugin marketplace add OpenWonderLabs/switchbot-openapi-cli
/plugin install switchbot@switchbot
```

**Note:** The root `marketplace.json` file in this repo is for Codex CLI Route B (git sparse clone) and points to the Codex plugin at `packages/codex-plugin/plugins/switchbot`. Claude Code users register via `claude mcp add` and do not use this file.
**Note:** The root `marketplace.json` in this repo is for Codex CLI Route B (git sparse clone) and points to `packages/codex-plugin/plugins/switchbot`. The `.claude-plugin/marketplace.json` is for Claude Code Plugin Marketplace and points to `packages/claude-code-plugin/plugins/switchbot`.

---

Expand Down
10 changes: 9 additions & 1 deletion marketplace.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
{
"name": "switchbot",
"interface": {
"displayName": "SwitchBot"
},
"plugins": [
{
"name": "switchbot",
"source": "./packages/codex-plugin/plugins/switchbot"
"source": "./packages/codex-plugin/plugins/switchbot",
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@switchbot/openapi-cli",
"version": "3.7.6",
"version": "3.7.7",
"description": "SwitchBot smart home CLI — control devices, run scenes, stream real-time events, and integrate AI agents via MCP. Full API v1.1 coverage.",
"keywords": [
"switchbot",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"name": "OpenWonderLabs"
},
"source": "./plugins/switchbot",
"category": "productivity",
"category": "Productivity",
"homepage": "https://github.com/OpenWonderLabs/switchbot-openapi-cli"
}
]
Expand Down
2 changes: 1 addition & 1 deletion packages/claude-code-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@switchbot/claude-code-plugin",
"version": "0.1.1",
"version": "0.1.2",
"type": "module",
"description": "SwitchBot Claude Code plugin — wires Claude Code to the SwitchBot CLI MCP server (24 tools, zero Node.js dependencies)",
"homepage": "https://github.com/OpenWonderLabs/switchbot-openapi-cli/tree/main/packages/claude-code-plugin",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
{
"name": "switchbot",
"displayName": "SwitchBot",
"version": "0.1.0",
"description": "Control SwitchBot smart-home devices from Claude Code via MCP.",
"author": {
"name": "OpenWonderLabs"
},
"homepage": "https://github.com/OpenWonderLabs/switchbot-openapi-cli"
"repository": "https://github.com/OpenWonderLabs/switchbot-openapi-cli",
"homepage": "https://github.com/OpenWonderLabs/switchbot-openapi-cli",
"license": "MIT",
"keywords": ["switchbot", "smart-home", "iot", "mcp"]
}
8 changes: 5 additions & 3 deletions packages/claude-code-plugin/plugins/switchbot/.mcp.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"switchbot": {
"command": "switchbot",
"args": ["mcp", "serve", "--tools", "all"]
"mcpServers": {
"switchbot": {
"command": "switchbot",
"args": ["mcp", "serve", "--tools", "all"]
}
}
}
10 changes: 9 additions & 1 deletion packages/codex-plugin/.agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "switchbot",
"interface": {
"displayName": "SwitchBot"
},
"plugins": [
{
"name": "switchbot",
"source": "./plugins/switchbot"
"source": "./plugins/switchbot",
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
11 changes: 10 additions & 1 deletion packages/codex-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "switchbot",
"interface": {
"displayName": "SwitchBot"
},
"plugins": [
{
"name": "switchbot",
"source": "./plugins/switchbot"
"source": "./plugins/switchbot",
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
2 changes: 1 addition & 1 deletion packages/codex-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@switchbot/codex-plugin",
"version": "0.1.4",
"version": "0.1.5",
"type": "module",
"description": "SwitchBot Codex plugin — wires Codex to the SwitchBot CLI MCP server (24 tools, zero Node.js dependencies)",
"homepage": "https://github.com/OpenWonderLabs/switchbot-openapi-cli/tree/main/packages/codex-plugin",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"onInstall": {
"command": "node",
"args": ["../../../bin/auth.js", "--hook"]
"command": "switchbot-codex-auth",
"args": ["--hook"]
}
}
32 changes: 32 additions & 0 deletions packages/codex-plugin/tests/hooks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = fileURLToPath(new URL('.', import.meta.url));
const pluginRoot = resolve(__dirname, '../plugins/switchbot');
const pkgJson = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf8'));

describe('hooks.json', () => {
const hooksPath = resolve(pluginRoot, '.codex-plugin/hooks.json');

it('exists on disk', () => {
assert.ok(existsSync(hooksPath), `hooks.json missing at ${hooksPath}`);
});

it('onInstall.command is switchbot-codex-auth (not a relative path)', () => {
const hooks = JSON.parse(readFileSync(hooksPath, 'utf8'));
const cmd = hooks?.onInstall?.command;
assert.equal(cmd, 'switchbot-codex-auth',
`onInstall.command must be the global binary "switchbot-codex-auth", got "${cmd}"`);
});

it('switchbot-codex-auth is declared in package.json#bin', () => {
const bin = pkgJson?.bin ?? {};
assert.ok(
Object.prototype.hasOwnProperty.call(bin, 'switchbot-codex-auth'),
'switchbot-codex-auth must be declared in package.json#bin so the hook command is on PATH after npm install -g'
);
});
});
15 changes: 10 additions & 5 deletions scripts/smoke-codex-git-sparse.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,27 +41,32 @@ try {
runGit(['clone', '--no-checkout', '--branch', ref, repoRoot, stagingDir], { cwd: workDir });
}
runGit(['-C', stagingDir, 'sparse-checkout', 'init', '--cone'], { cwd: workDir });
runGit(['-C', stagingDir, 'sparse-checkout', 'set', '.claude-plugin', 'packages/codex-plugin'], { cwd: workDir });
runGit(['-C', stagingDir, 'sparse-checkout', 'set', '.claude-plugin', 'packages/codex-plugin', 'packages/claude-code-plugin'], { cwd: workDir });
runGit(['-C', stagingDir, 'checkout', ref], { cwd: workDir });

// .claude-plugin/marketplace.json — Claude Code plugin marketplace entry point
const rootMarketplacePath = path.join(stagingDir, '.claude-plugin', 'marketplace.json');
// packages/codex-plugin/.agents/plugins/marketplace.json — Codex Route B entry point
const packageMarketplacePath = path.join(stagingDir, 'packages', 'codex-plugin', '.agents', 'plugins', 'marketplace.json');
const pluginMcpPath = path.join(stagingDir, 'packages', 'codex-plugin', 'plugins', 'switchbot', '.mcp.json');
// packages/claude-code-plugin/plugins/switchbot — Claude Code plugin source
const claudeCodePluginJsonPath = path.join(stagingDir, 'packages', 'claude-code-plugin', 'plugins', 'switchbot', '.claude-plugin', 'plugin.json');

for (const requiredPath of [rootMarketplacePath, packageMarketplacePath, pluginMcpPath]) {
for (const requiredPath of [rootMarketplacePath, packageMarketplacePath, pluginMcpPath, claudeCodePluginJsonPath]) {
if (!existsSync(requiredPath)) {
throw new Error(`sparse checkout missing ${path.relative(stagingDir, requiredPath)}`);
}
}

// Root marketplace must point to the Claude Code plugin directory
const rootMarketplace = readJson(rootMarketplacePath);
if (rootMarketplace?.name !== 'switchbot') {
throw new Error(`root marketplace name must be switchbot, got ${rootMarketplace?.name ?? '<missing>'}`);
}
const rootPlugin = rootMarketplace?.plugins?.find((plugin) => plugin?.name === 'switchbot');
if (rootPlugin?.source !== './packages/codex-plugin/plugins/switchbot') {
if (rootPlugin?.source !== './packages/claude-code-plugin/plugins/switchbot') {
throw new Error(
`root marketplace switchbot source must be ./packages/codex-plugin/plugins/switchbot, got ${rootPlugin?.source ?? '<missing>'}`,
`root marketplace switchbot source must be ./packages/claude-code-plugin/plugins/switchbot, got ${rootPlugin?.source ?? '<missing>'}`,
);
}

Expand All @@ -74,7 +79,7 @@ try {
throw new Error(`package marketplace switchbot source must be ./plugins/switchbot, got ${packagePlugin?.source ?? '<missing>'}`);
}

console.log(`codex git sparse smoke ok: ref ${ref} exposes root and package marketplace manifests with switchbot sources`);
console.log(`codex git sparse smoke ok: ref ${ref} exposes Claude Code and Codex marketplace manifests with correct sources`);
} finally {
try {
rmSync(workDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
Expand Down
1 change: 1 addition & 0 deletions src/commands/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export const COMMAND_META: Record<string, CommandMeta> = {
'codex doctor': READ_LOCAL,
'codex repair': ACTION_LOCAL,
'codex setup': ACTION_LOCAL,
'claude-code setup': ACTION_LOCAL,
'uninstall': ACTION_LOCAL,
'upgrade-check': READ_REMOTE,
'webhook setup': ACTION_REMOTE,
Expand Down
Loading
Loading