@geekbeer/minion 2.59.0 → 2.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,279 @@
1
+ /**
2
+ * CLI Permission Abstraction Layer
3
+ *
4
+ * Provides a unified interface for reading/writing permission configurations
5
+ * across multiple AI coding CLI tools:
6
+ * - Claude Code: ~/.claude/settings.local.json (JSON)
7
+ * - Gemini CLI: ~/.gemini/settings.json (JSON)
8
+ * - Codex CLI: ~/.codex/config.toml (TOML)
9
+ */
10
+
11
+ const fs = require('fs')
12
+ const path = require('path')
13
+
14
+ // ── CLI Definitions ──────────────────────────────────────────────────────────
15
+
16
+ const CLI_DEFS = {
17
+ 'claude-code': {
18
+ label: 'Claude Code',
19
+ configDir: '.claude',
20
+ // Read from settings.json (bundled sync target) + merge settings.local.json (user overrides)
21
+ readFiles: ['settings.json', 'settings.local.json'],
22
+ // Write to settings.local.json to survive server restarts (settings.json is overwritten by syncPermissions)
23
+ writeFile: 'settings.local.json',
24
+ format: 'json',
25
+ },
26
+ 'gemini': {
27
+ label: 'Gemini CLI',
28
+ configDir: '.gemini',
29
+ readFiles: ['settings.json'],
30
+ writeFile: 'settings.json',
31
+ format: 'json',
32
+ },
33
+ 'codex': {
34
+ label: 'Codex CLI',
35
+ configDir: '.codex',
36
+ readFiles: ['config.toml'],
37
+ writeFile: 'config.toml',
38
+ format: 'toml',
39
+ },
40
+ }
41
+
42
+ // ── TOML Helpers (minimal, for Codex config.toml) ────────────────────────────
43
+
44
+ /**
45
+ * Parse a minimal TOML file to extract permissions.allow and permissions.deny arrays.
46
+ * Only supports the subset needed for Codex CLI config.
47
+ */
48
+ function parseTomlPermissions(content) {
49
+ const allow = []
50
+ const deny = []
51
+
52
+ let currentSection = ''
53
+ for (const rawLine of content.split('\n')) {
54
+ const line = rawLine.trim()
55
+ if (!line || line.startsWith('#')) continue
56
+
57
+ // Section header: [permissions] or [permissions.default] etc.
58
+ const sectionMatch = line.match(/^\[([^\]]+)\]$/)
59
+ if (sectionMatch) {
60
+ currentSection = sectionMatch[1]
61
+ continue
62
+ }
63
+
64
+ if (currentSection !== 'permissions') continue
65
+
66
+ // Key = value
67
+ const kvMatch = line.match(/^(\w+)\s*=\s*(.+)$/)
68
+ if (!kvMatch) continue
69
+ const [, key, rawValue] = kvMatch
70
+
71
+ if (key === 'allow' || key === 'deny') {
72
+ const arr = parseTomlArray(rawValue)
73
+ if (key === 'allow') allow.push(...arr)
74
+ else deny.push(...arr)
75
+ }
76
+ }
77
+
78
+ return { allow, deny }
79
+ }
80
+
81
+ /**
82
+ * Parse a TOML inline array: ["a", "b", "c"]
83
+ */
84
+ function parseTomlArray(raw) {
85
+ const trimmed = raw.trim()
86
+ if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return []
87
+ const inner = trimmed.slice(1, -1)
88
+ const items = []
89
+ // Match quoted strings
90
+ const re = /"([^"]*?)"|'([^']*?)'/g
91
+ let m
92
+ while ((m = re.exec(inner)) !== null) {
93
+ items.push(m[1] ?? m[2])
94
+ }
95
+ return items
96
+ }
97
+
98
+ /**
99
+ * Write permissions into a TOML file, preserving non-permissions content.
100
+ */
101
+ function writeTomlPermissions(existingContent, { allow, deny }) {
102
+ const lines = existingContent ? existingContent.split('\n') : []
103
+ const outputLines = []
104
+ let inPermissionsSection = false
105
+ let permissionsWritten = false
106
+
107
+ for (const line of lines) {
108
+ const trimmed = line.trim()
109
+ const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/)
110
+
111
+ if (sectionMatch) {
112
+ if (sectionMatch[1] === 'permissions') {
113
+ inPermissionsSection = true
114
+ // Write our new permissions section
115
+ outputLines.push('[permissions]')
116
+ outputLines.push(`allow = [${allow.map(s => `"${s}"`).join(', ')}]`)
117
+ outputLines.push(`deny = [${deny.map(s => `"${s}"`).join(', ')}]`)
118
+ permissionsWritten = true
119
+ continue
120
+ } else {
121
+ inPermissionsSection = false
122
+ }
123
+ }
124
+
125
+ if (inPermissionsSection) continue // skip old permission lines
126
+ outputLines.push(line)
127
+ }
128
+
129
+ // If no existing [permissions] section, append one
130
+ if (!permissionsWritten) {
131
+ if (outputLines.length > 0 && outputLines[outputLines.length - 1] !== '') {
132
+ outputLines.push('')
133
+ }
134
+ outputLines.push('[permissions]')
135
+ outputLines.push(`allow = [${allow.map(s => `"${s}"`).join(', ')}]`)
136
+ outputLines.push(`deny = [${deny.map(s => `"${s}"`).join(', ')}]`)
137
+ }
138
+
139
+ return outputLines.join('\n')
140
+ }
141
+
142
+ // ── JSON Helpers ─────────────────────────────────────────────────────────────
143
+
144
+ function readJsonPermissions(filePath) {
145
+ try {
146
+ const content = fs.readFileSync(filePath, 'utf-8')
147
+ const data = JSON.parse(content)
148
+ const perms = data.permissions || {}
149
+ return {
150
+ allow: Array.isArray(perms.allow) ? perms.allow : [],
151
+ deny: Array.isArray(perms.deny) ? perms.deny : [],
152
+ }
153
+ } catch {
154
+ return { allow: [], deny: [] }
155
+ }
156
+ }
157
+
158
+ function writeJsonPermissions(filePath, { allow, deny }) {
159
+ let data = {}
160
+ try {
161
+ data = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
162
+ } catch {
163
+ // File doesn't exist or is invalid — start fresh
164
+ }
165
+ data.permissions = { allow, deny }
166
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
167
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8')
168
+ }
169
+
170
+ // ── Public API ───────────────────────────────────────────────────────────────
171
+
172
+ /**
173
+ * Detect which CLIs have config directories present.
174
+ * @param {string} homeDir
175
+ * @returns {string[]} Array of cli_type strings
176
+ */
177
+ function detectInstalledClis(homeDir) {
178
+ return Object.entries(CLI_DEFS)
179
+ .filter(([, def]) => fs.existsSync(path.join(homeDir, def.configDir)))
180
+ .map(([type]) => type)
181
+ }
182
+
183
+ /**
184
+ * Read effective permissions for a specific CLI.
185
+ * For Claude Code, merges settings.json + settings.local.json.
186
+ * @param {string} homeDir
187
+ * @param {string} cliType
188
+ * @returns {{ cli_type: string, label: string, allow: string[], deny: string[], config_path: string } | null}
189
+ */
190
+ function getPermissions(homeDir, cliType) {
191
+ const def = CLI_DEFS[cliType]
192
+ if (!def) return null
193
+
194
+ const configDir = path.join(homeDir, def.configDir)
195
+ if (!fs.existsSync(configDir)) return null
196
+
197
+ let mergedAllow = []
198
+ let mergedDeny = []
199
+
200
+ for (const file of def.readFiles) {
201
+ const filePath = path.join(configDir, file)
202
+ if (!fs.existsSync(filePath)) continue
203
+
204
+ if (def.format === 'json') {
205
+ const perms = readJsonPermissions(filePath)
206
+ // Later files override earlier ones (settings.local.json overrides settings.json)
207
+ if (perms.allow.length > 0 || perms.deny.length > 0) {
208
+ mergedAllow = perms.allow
209
+ mergedDeny = perms.deny
210
+ }
211
+ } else if (def.format === 'toml') {
212
+ try {
213
+ const content = fs.readFileSync(filePath, 'utf-8')
214
+ const perms = parseTomlPermissions(content)
215
+ if (perms.allow.length > 0 || perms.deny.length > 0) {
216
+ mergedAllow = perms.allow
217
+ mergedDeny = perms.deny
218
+ }
219
+ } catch {
220
+ // Skip unreadable files
221
+ }
222
+ }
223
+ }
224
+
225
+ return {
226
+ cli_type: cliType,
227
+ label: def.label,
228
+ allow: mergedAllow,
229
+ deny: mergedDeny,
230
+ config_path: path.join(configDir, def.writeFile),
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Read permissions for all detected CLIs.
236
+ * @param {string} homeDir
237
+ * @returns {Array<{ cli_type: string, label: string, allow: string[], deny: string[], config_path: string }>}
238
+ */
239
+ function getAllPermissions(homeDir) {
240
+ const results = []
241
+ for (const cliType of Object.keys(CLI_DEFS)) {
242
+ const perms = getPermissions(homeDir, cliType)
243
+ if (perms) results.push(perms)
244
+ }
245
+ return results
246
+ }
247
+
248
+ /**
249
+ * Update permissions for a specific CLI.
250
+ * @param {string} homeDir
251
+ * @param {string} cliType
252
+ * @param {{ allow: string[], deny: string[] }} permissions
253
+ */
254
+ function setPermissions(homeDir, cliType, { allow, deny }) {
255
+ const def = CLI_DEFS[cliType]
256
+ if (!def) throw new Error(`Unsupported CLI type: ${cliType}`)
257
+
258
+ const configDir = path.join(homeDir, def.configDir)
259
+ const filePath = path.join(configDir, def.writeFile)
260
+
261
+ fs.mkdirSync(configDir, { recursive: true })
262
+
263
+ if (def.format === 'json') {
264
+ writeJsonPermissions(filePath, { allow, deny })
265
+ } else if (def.format === 'toml') {
266
+ let existing = ''
267
+ try { existing = fs.readFileSync(filePath, 'utf-8') } catch {}
268
+ const newContent = writeTomlPermissions(existing, { allow, deny })
269
+ fs.writeFileSync(filePath, newContent, 'utf-8')
270
+ }
271
+ }
272
+
273
+ module.exports = {
274
+ detectInstalledClis,
275
+ getPermissions,
276
+ getAllPermissions,
277
+ setPermissions,
278
+ CLI_DEFS,
279
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Permission management endpoints
3
+ *
4
+ * Provides read/write access to CLI tool permission configurations.
5
+ * Supports Claude Code, Gemini CLI, and Codex CLI.
6
+ *
7
+ * Endpoints:
8
+ * GET /api/config/permissions - Get permissions for all detected CLIs
9
+ * GET /api/config/permissions/:cli_type - Get permissions for a specific CLI
10
+ * POST /api/config/permissions - Update permissions for a specific CLI
11
+ */
12
+
13
+ const { verifyToken } = require('../lib/auth')
14
+ const { config } = require('../config')
15
+ const { getAllPermissions, getPermissions, setPermissions, CLI_DEFS } = require('../lib/permissions')
16
+
17
+ /**
18
+ * Register permission routes as Fastify plugin
19
+ * @param {import('fastify').FastifyInstance} fastify
20
+ */
21
+ async function permissionRoutes(fastify) {
22
+
23
+ // GET /api/config/permissions - Get permissions for all detected CLIs
24
+ fastify.get('/api/config/permissions', async (request, reply) => {
25
+ if (!verifyToken(request)) {
26
+ reply.code(401)
27
+ return { success: false, error: 'Unauthorized' }
28
+ }
29
+
30
+ const permissions = getAllPermissions(config.HOME_DIR)
31
+ return { success: true, permissions }
32
+ })
33
+
34
+ // GET /api/config/permissions/:cli_type - Get permissions for a specific CLI
35
+ fastify.get('/api/config/permissions/:cli_type', async (request, reply) => {
36
+ if (!verifyToken(request)) {
37
+ reply.code(401)
38
+ return { success: false, error: 'Unauthorized' }
39
+ }
40
+
41
+ const { cli_type } = request.params
42
+ if (!CLI_DEFS[cli_type]) {
43
+ reply.code(400)
44
+ return { success: false, error: `Unsupported CLI type: ${cli_type}. Supported: ${Object.keys(CLI_DEFS).join(', ')}` }
45
+ }
46
+
47
+ const perms = getPermissions(config.HOME_DIR, cli_type)
48
+ if (!perms) {
49
+ reply.code(404)
50
+ return { success: false, error: `CLI ${cli_type} is not installed on this minion` }
51
+ }
52
+
53
+ return { success: true, ...perms }
54
+ })
55
+
56
+ // POST /api/config/permissions - Update permissions for a specific CLI
57
+ fastify.post('/api/config/permissions', async (request, reply) => {
58
+ if (!verifyToken(request)) {
59
+ reply.code(401)
60
+ return { success: false, error: 'Unauthorized' }
61
+ }
62
+
63
+ const { cli_type, allow, deny } = request.body || {}
64
+
65
+ if (!cli_type) {
66
+ reply.code(400)
67
+ return { success: false, error: 'cli_type is required' }
68
+ }
69
+ if (!CLI_DEFS[cli_type]) {
70
+ reply.code(400)
71
+ return { success: false, error: `Unsupported CLI type: ${cli_type}. Supported: ${Object.keys(CLI_DEFS).join(', ')}` }
72
+ }
73
+ if (!Array.isArray(allow) || !Array.isArray(deny)) {
74
+ reply.code(400)
75
+ return { success: false, error: 'allow and deny must be arrays' }
76
+ }
77
+
78
+ try {
79
+ setPermissions(config.HOME_DIR, cli_type, { allow, deny })
80
+
81
+ // Read back effective permissions to confirm
82
+ const updated = getPermissions(config.HOME_DIR, cli_type)
83
+ console.log(`[Permissions] Updated ${cli_type}: allow=${allow.length}, deny=${deny.length}`)
84
+
85
+ return { success: true, ...updated }
86
+ } catch (err) {
87
+ reply.code(500)
88
+ return { success: false, error: err.message }
89
+ }
90
+ })
91
+ }
92
+
93
+ module.exports = { permissionRoutes }
@@ -204,6 +204,46 @@ Changes via the config API take effect immediately (no restart required).
204
204
 
205
205
  Allowed keys: `LLM_COMMAND`, `REFLECTION_TIME`
206
206
 
207
+ ### Permissions
208
+
209
+ CLIツール(Claude Code, Gemini CLI, Codex CLI)のパーミッション管理。
210
+ CLIツールは自身の設定ファイルを直接編集できないため、このAPIを経由して更新する。
211
+
212
+ | Method | Endpoint | Description |
213
+ |--------|----------|-------------|
214
+ | GET | `/api/config/permissions` | Get permissions for all detected CLIs |
215
+ | GET | `/api/config/permissions/:cli_type` | Get permissions for a specific CLI |
216
+ | POST | `/api/config/permissions` | Update permissions. Body: `{cli_type, allow, deny}` |
217
+
218
+ Supported `cli_type`: `claude-code`, `gemini`, `codex`
219
+
220
+ **GET response**:
221
+ ```json
222
+ {
223
+ "success": true,
224
+ "permissions": [
225
+ {
226
+ "cli_type": "claude-code",
227
+ "label": "Claude Code",
228
+ "allow": ["Bash", "Read", "Write", "Edit"],
229
+ "deny": ["Bash(sudo *)"],
230
+ "config_path": "/home/minion/.claude/settings.local.json"
231
+ }
232
+ ]
233
+ }
234
+ ```
235
+
236
+ **POST body**:
237
+ ```json
238
+ {
239
+ "cli_type": "claude-code",
240
+ "allow": ["Bash", "Read", "Write", "Edit", "WebSearch"],
241
+ "deny": ["Bash(sudo *)", "Bash(rm -rf *)"]
242
+ }
243
+ ```
244
+
245
+ Note: Claude Code の場合、書き込み先は `settings.local.json`(サーバー再起動時に上書きされない)。
246
+
207
247
  ### Commands
208
248
 
209
249
  | Method | Endpoint | Description |
@@ -28,9 +28,20 @@ requires:
28
28
 
29
29
  ## MCP サーバーの設定
30
30
 
31
- MCP サーバーは `~/.mcp.json` に JSON 形式で設定する。このファイルが Claude Code セッション起動時に読み込まれる。
31
+ MCPサーバーの設定ファイルは使用するCLIツールによって異なる。
32
+ いずれも LLM から直接編集可能(パーミッション設定とは異なり、API経由は不要)。
32
33
 
33
- ### ファイル形式
34
+ ### CLI別の設定ファイル
35
+
36
+ | CLI | 設定ファイル | フォーマット | 編集方法 |
37
+ |-----|------------|------------|---------|
38
+ | Claude Code | `.mcp.json`(プロジェクトルート) | JSON | Edit/Write ツールで直接編集 |
39
+ | Gemini CLI | `.gemini/settings.json` | JSON | Edit/Write ツールで直接編集 |
40
+ | Codex CLI | `~/.codex/config.toml` | TOML | `codex mcp add/remove` コマンド推奨 |
41
+
42
+ ### Claude Code
43
+
44
+ 設定ファイル: `.mcp.json`(プロジェクトルート)または `~/.mcp.json`(ユーザーレベル)
34
45
 
35
46
  ```json
36
47
  {
@@ -43,9 +54,8 @@ MCP サーバーは `~/.mcp.json` に JSON 形式で設定する。このファ
43
54
  }
44
55
  ```
45
56
 
46
- ### 設定の追加・変更手順
47
-
48
- 1. `~/.mcp.json` が存在しない場合は新規作成する
57
+ **設定の追加・変更手順:**
58
+ 1. `.mcp.json` が存在しない場合は新規作成する
49
59
  2. 既存の場合は内容を読み取り、`mcpServers` オブジェクトにエントリを追加する
50
60
  3. 既存エントリを壊さないよう注意する
51
61
 
@@ -54,10 +64,54 @@ MCP サーバーは `~/.mcp.json` に JSON 形式で設定する。このファ
54
64
  cat ~/.mcp.json 2>/dev/null || echo '(not found)'
55
65
  ```
56
66
 
67
+ **スコープの優先順:**
68
+ 1. ローカル (`~/.claude.json` 内のプロジェクト固有設定) — 最優先
69
+ 2. プロジェクト (`.mcp.json`) — チーム共有
70
+ 3. ユーザー (`~/.claude.json`) — 全プロジェクト共通
71
+
72
+ ### Gemini CLI
73
+
74
+ 設定ファイル: `.gemini/settings.json`(プロジェクト)または `~/.gemini/settings.json`(ユーザーレベル)
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "<server-name>": {
80
+ "command": "<起動コマンド>",
81
+ "args": ["<引数1>", "<引数2>"],
82
+ "timeout": 30000
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ 追加フィールド: `cwd`(作業ディレクトリ)、`timeout`(ミリ秒)、`trust`(確認プロンプト省略)、`includeTools`/`excludeTools`(ツールフィルタ)
89
+
90
+ ### Codex CLI
91
+
92
+ 設定ファイル: `.codex/config.toml`(プロジェクト)または `~/.codex/config.toml`(ユーザーレベル)
93
+
94
+ ```toml
95
+ [mcp_servers.<server-name>]
96
+ command = "<起動コマンド>"
97
+ args = ["<引数1>", "<引数2>"]
98
+ startup_timeout_sec = 10.0
99
+ tool_timeout_sec = 60.0
100
+ ```
101
+
102
+ **注意:** `.codex/` ディレクトリは LLM からの直接編集が制限される場合がある。以下のコマンドを使用すること:
103
+
104
+ ```bash
105
+ codex mcp add <name> --command "<command>" --args "<arg1>,<arg2>"
106
+ codex mcp remove <name>
107
+ codex mcp list
108
+ ```
109
+
57
110
  ### よく使う MCP サーバーの設定例
58
111
 
59
112
  #### Playwright(ブラウザ自動化)
60
113
 
114
+ **Claude Code / Gemini CLI:**
61
115
  ```json
62
116
  {
63
117
  "mcpServers": {
@@ -69,10 +123,18 @@ cat ~/.mcp.json 2>/dev/null || echo '(not found)'
69
123
  }
70
124
  ```
71
125
 
126
+ **Codex CLI:**
127
+ ```toml
128
+ [mcp_servers.playwright]
129
+ command = "npx"
130
+ args = ["-y", "@playwright/mcp@latest"]
131
+ ```
132
+
72
133
  `npx -y` により、未インストールでも自動ダウンロード・実行される。事前の `npm install` は不要。
73
134
 
74
135
  #### Supabase(データベース)
75
136
 
137
+ **Claude Code / Gemini CLI:**
76
138
  ```json
77
139
  {
78
140
  "mcpServers": {
@@ -83,14 +145,20 @@ cat ~/.mcp.json 2>/dev/null || echo '(not found)'
83
145
  }
84
146
  ```
85
147
 
148
+ **Codex CLI:**
149
+ ```toml
150
+ [mcp_servers.supabase]
151
+ url = "http://<supabase-host>:54321/mcp?read_only=true&features=database,docs"
152
+ ```
153
+
86
154
  URL ベースの MCP サーバーは `url` フィールドで指定する(`command`/`args` は不要)。
87
155
 
88
156
  ### 注意事項
89
157
 
90
- - `~/.mcp.json` は手動で編集する。Claude Code の設定 UI からは変更できない
158
+ - MCP設定ファイルはLLMから直接編集できる(パーミッション設定ファイルとは異なり書き込み保護がない)
91
159
  - `npx -y <package>` 形式を使えば、グローバルインストールなしで MCP サーバーを起動できる
92
160
  - サーバー名はスキルの `requires.mcp_servers` と一致させる必要がある(例: `playwright`)
93
- - `claude-settings.json`(`~/.claude/settings.json`)の `mcpServers` に設定しても同様に動作するが、`~/.mcp.json` が推奨
161
+ - Claude Code では `.mcp.json`(プロジェクトルート)が推奨。`~/.claude/settings.json` `mcpServers` には設定しない
94
162
 
95
163
  ---
96
164
 
package/linux/server.js CHANGED
@@ -67,6 +67,7 @@ const { variableRoutes } = require('../core/routes/variables')
67
67
  const { memoryRoutes } = require('../core/routes/memory')
68
68
  const { dailyLogRoutes } = require('../core/routes/daily-logs')
69
69
  const { sudoersRoutes } = require('../core/routes/sudoers')
70
+ const { permissionRoutes } = require('../core/routes/permissions')
70
71
 
71
72
  // Linux-specific routes
72
73
  const { commandRoutes, getProcessManager, getAllowedCommands } = require('./routes/commands')
@@ -268,6 +269,7 @@ async function registerAllRoutes(app) {
268
269
  await app.register(memoryRoutes)
269
270
  await app.register(dailyLogRoutes)
270
271
  await app.register(sudoersRoutes)
272
+ await app.register(permissionRoutes)
271
273
 
272
274
  // Linux-specific routes
273
275
  await app.register(commandRoutes)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekbeer/minion",
3
- "version": "2.59.0",
3
+ "version": "2.62.0",
4
4
  "description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
5
5
  "main": "linux/server.js",
6
6
  "bin": {
package/rules/core.md CHANGED
@@ -45,7 +45,72 @@ minion-cli --version # バージョン確認
45
45
 
46
46
  `http://localhost:8080` — 認証: `Authorization: Bearer $API_TOKEN`
47
47
 
48
- 主なカテゴリ: Health, Skills, Workflows, Executions, Terminal, Files, Commands
48
+ 主なカテゴリ: Health, Skills, Workflows, Executions, Terminal, Files, Commands, Permissions
49
+
50
+ #### Permission Management
51
+
52
+ CLIツール(Claude Code, Gemini CLI, Codex CLI)のパーミッション(allow/denyリスト)をAPI経由で読み書きできる。
53
+ CLIツールは自身の設定ファイルを直接編集できないため、このAPIを使用すること。
54
+
55
+ ```bash
56
+ # 全CLIのパーミッション取得
57
+ curl -H "Authorization: Bearer $API_TOKEN" http://localhost:8080/api/config/permissions
58
+
59
+ # 特定CLIのパーミッション取得
60
+ curl -H "Authorization: Bearer $API_TOKEN" http://localhost:8080/api/config/permissions/claude-code
61
+
62
+ # パーミッション更新
63
+ curl -X POST -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
64
+ http://localhost:8080/api/config/permissions \
65
+ -d '{"cli_type": "claude-code", "allow": ["Bash", "Read", "Write", "Edit"], "deny": ["Bash(sudo *)"]}'
66
+ ```
67
+
68
+ 対応CLI: `claude-code` (.claude/settings.local.json), `gemini` (.gemini/settings.json), `codex` (.codex/config.toml)
69
+
70
+ #### MCP Server Configuration
71
+
72
+ MCPサーバーの設定は各CLIの設定ファイルを直接編集して行う(APIは不要)。
73
+ パーミッション(allow/deny)とは異なり、MCP設定ファイルはLLMから直接編集可能。
74
+
75
+ | CLI | 設定ファイル | 編集方法 |
76
+ |-----|------------|---------|
77
+ | Claude Code | `.mcp.json`(プロジェクトルート) | 直接編集(Write/Edit ツール) |
78
+ | Gemini CLI | `.gemini/settings.json` | 直接編集 |
79
+ | Codex CLI | `.codex/config.toml` | `codex mcp add/remove` コマンドを使用 |
80
+
81
+ **Claude Code** — `.mcp.json` を編集:
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "playwright": {
86
+ "command": "npx",
87
+ "args": ["-y", "@playwright/mcp@latest"]
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ **Gemini CLI** — `.gemini/settings.json` の `mcpServers` を編集:
94
+ ```json
95
+ {
96
+ "mcpServers": {
97
+ "playwright": {
98
+ "command": "npx",
99
+ "args": ["-y", "@playwright/mcp@latest"],
100
+ "timeout": 30000
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ **Codex CLI** — `codex mcp add` コマンド、または `~/.codex/config.toml` を編集:
107
+ ```toml
108
+ [mcp_servers.playwright]
109
+ command = "npx"
110
+ args = ["-y", "@playwright/mcp@latest"]
111
+ ```
112
+
113
+ Note: Codex CLI の `.codex/` ディレクトリはLLMからの直接編集が制限される場合がある。その場合は `codex mcp add` コマンドを使用すること。
49
114
 
50
115
  ### HQ API
51
116
 
package/win/server.js CHANGED
@@ -50,6 +50,7 @@ const { authRoutes } = require('../core/routes/auth')
50
50
  const { variableRoutes } = require('../core/routes/variables')
51
51
  const { memoryRoutes } = require('../core/routes/memory')
52
52
  const { dailyLogRoutes } = require('../core/routes/daily-logs')
53
+ const { permissionRoutes } = require('../core/routes/permissions')
53
54
 
54
55
  // Validate configuration
55
56
  validate()
@@ -201,6 +202,7 @@ async function registerRoutes(app) {
201
202
  await app.register(variableRoutes)
202
203
  await app.register(memoryRoutes)
203
204
  await app.register(dailyLogRoutes)
205
+ await app.register(permissionRoutes)
204
206
 
205
207
  // Windows-specific routes
206
208
  await app.register(commandRoutes)