@chocodrop/setup 0.1.0-alpha.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ChocoDrop Project
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @chocodrop/setup
2
+
3
+ Codex、Claude Code、Antigravityを検出し、ChocoDrop MCPを登録するセットアップコマンドです。
4
+
5
+ ```bash
6
+ pnpm dlx @chocodrop/setup@0.1.0-alpha.0
7
+ ```
8
+
9
+ 既定では、見つかったツールを表示して確認した後、`~/ChocoDropAssets`を作成し、ユーザー設定へChocoDropを登録します。Antigravityでは既存の`~/.gemini/config/mcp_config.json`を保持し、`mcpServers.chocodrop`だけを追加・更新します。
10
+
11
+ ```bash
12
+ # Codexだけに登録
13
+ pnpm dlx @chocodrop/setup@0.1.0-alpha.0 --client codex --yes
14
+
15
+ # 素材フォルダを指定
16
+ pnpm dlx @chocodrop/setup@0.1.0-alpha.0 --assets-dir /path/to/assets
17
+
18
+ # 変更内容だけ確認
19
+ pnpm dlx @chocodrop/setup@0.1.0-alpha.0 --dry-run
20
+ ```
21
+
22
+ pnpmがない環境では`npx --yes @chocodrop/setup@0.1.0-alpha.0`も使用できます。
23
+
24
+ 登録後、CLIを再起動して「ChocoDropの`get_status`でURLを教えて」と依頼してください。
25
+
26
+ このセットアップは生成サービスを追加しません。普段使っている生成MCPやCLIでPNG、動画、GLBを素材フォルダへ保存し、ChocoDropの`import_asset`で配置します。
27
+
28
+ 案内するパッケージは検証済みバージョンへ固定しています。このパッケージに`install`・`postinstall`スクリプトや外部依存はありません。
29
+
30
+ [Website](https://nyukicorn.github.io/chocodrop/) · [Source](https://github.com/nyukicorn/chocodrop)
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { runSetup } from '../src/index.js';
3
+
4
+ runSetup().catch((error) => {
5
+ process.stderr.write(`\n設定できませんでした: ${error.message}\n`);
6
+ process.exitCode = 1;
7
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@chocodrop/setup",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "One-command setup for connecting ChocoDrop to Codex, Claude Code, and Antigravity.",
5
+ "type": "module",
6
+ "bin": {
7
+ "chocodrop-setup": "./bin/chocodrop-setup.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "keywords": [
16
+ "chocodrop",
17
+ "mcp",
18
+ "codex",
19
+ "claude-code",
20
+ "antigravity"
21
+ ],
22
+ "author": {
23
+ "name": "ChocoDrop Project",
24
+ "email": "chocodrop.dev@gmail.com"
25
+ },
26
+ "license": "MIT",
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "tag": "alpha"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }
package/src/index.js ADDED
@@ -0,0 +1,265 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { createInterface } from 'node:readline/promises';
7
+
8
+ const DEFAULT_MCP_PACKAGE = '@chocodrop/mcp@0.1.0-alpha.0';
9
+ const CLIENT_ORDER = ['codex', 'claude', 'antigravity'];
10
+ const CLIENTS = {
11
+ codex: { command: 'codex', label: 'Codex' },
12
+ claude: { command: 'claude', label: 'Claude Code' },
13
+ antigravity: { command: 'agy', label: 'Antigravity' },
14
+ };
15
+
16
+ function execute(command, args, options = {}) {
17
+ const result = spawnSync(command, args, {
18
+ encoding: 'utf8',
19
+ stdio: options.stdio || 'pipe',
20
+ env: options.env || process.env,
21
+ });
22
+ return {
23
+ status: result.status ?? 1,
24
+ stdout: result.stdout || '',
25
+ stderr: result.stderr || '',
26
+ error: result.error,
27
+ };
28
+ }
29
+
30
+ function expandHome(value) {
31
+ if (value === '~') return homedir();
32
+ if (value.startsWith(`~${path.sep}`)) return path.join(homedir(), value.slice(2));
33
+ return value;
34
+ }
35
+
36
+ export function parseArgs(argv = process.argv.slice(2)) {
37
+ const result = { clients: [], yes: false, dryRun: false, help: false };
38
+ for (let index = 0; index < argv.length; index += 1) {
39
+ const value = argv[index];
40
+ if (value === '--client') result.clients.push(...String(argv[++index] || '').split(','));
41
+ else if (value === '--assets-dir') result.assetsDir = argv[++index];
42
+ else if (value === '--yes' || value === '-y') result.yes = true;
43
+ else if (value === '--dry-run') result.dryRun = true;
44
+ else if (value === '--help' || value === '-h') result.help = true;
45
+ else throw new Error(`不明なオプションです: ${value}`);
46
+ }
47
+ result.clients = [...new Set(result.clients.map((value) => value.trim()).filter(Boolean))];
48
+ return result;
49
+ }
50
+
51
+ function antigravityLocations(home = homedir(), platform = process.platform) {
52
+ if (platform === 'darwin')
53
+ return ['/Applications/Antigravity.app', path.join(home, 'Applications/Antigravity.app')];
54
+ if (platform === 'win32') {
55
+ const localAppData = process.env.LOCALAPPDATA;
56
+ return localAppData ? [path.join(localAppData, 'Programs', 'Antigravity', 'Antigravity.exe')] : [];
57
+ }
58
+ return ['/usr/bin/antigravity', '/usr/local/bin/antigravity'];
59
+ }
60
+
61
+ export function detectClients(run = execute, fileExists = existsSync) {
62
+ return CLIENT_ORDER.filter((name) => {
63
+ if (run(CLIENTS[name].command, ['--version']).status === 0) return true;
64
+ return name === 'antigravity' && antigravityLocations().some((candidate) => fileExists(candidate));
65
+ });
66
+ }
67
+
68
+ function packageCommand(packageSpec, assetsDir, runner = 'pnpm') {
69
+ if (runner === 'pnpm') return ['pnpm', 'dlx', packageSpec, '--assets-dir', assetsDir];
70
+ return ['npx', '-y', packageSpec, '--assets-dir', assetsDir];
71
+ }
72
+
73
+ export function registration(
74
+ name,
75
+ assetsDir,
76
+ packageSpec = DEFAULT_MCP_PACKAGE,
77
+ runner = 'pnpm',
78
+ home = homedir()
79
+ ) {
80
+ const mcpCommand = packageCommand(packageSpec, assetsDir, runner);
81
+ if (name === 'codex') {
82
+ return {
83
+ name,
84
+ ...CLIENTS[name],
85
+ add: ['mcp', 'add', 'chocodrop', '--', ...mcpCommand],
86
+ get: ['mcp', 'get', 'chocodrop'],
87
+ remove: ['mcp', 'remove', 'chocodrop'],
88
+ };
89
+ }
90
+ if (name === 'claude') {
91
+ return {
92
+ name,
93
+ ...CLIENTS[name],
94
+ add: ['mcp', 'add', '--transport', 'stdio', '--scope', 'user', 'chocodrop', '--', ...mcpCommand],
95
+ get: ['mcp', 'get', 'chocodrop'],
96
+ remove: ['mcp', 'remove', '--scope', 'user', 'chocodrop'],
97
+ };
98
+ }
99
+ if (name === 'antigravity') {
100
+ return {
101
+ name,
102
+ ...CLIENTS[name],
103
+ kind: 'json',
104
+ configPath: path.join(home, '.gemini', 'config', 'mcp_config.json'),
105
+ server: { command: mcpCommand[0], args: mcpCommand.slice(1) },
106
+ };
107
+ }
108
+ throw new Error(`対応していないツールです: ${name}`);
109
+ }
110
+
111
+ export function shellCommand(command, args) {
112
+ const quote = (value) => (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`);
113
+ return [command, ...args].map(quote).join(' ');
114
+ }
115
+
116
+ function readJsonConfig(configPath) {
117
+ try {
118
+ const source = readFileSync(configPath, 'utf8').trim();
119
+ if (!source) return {};
120
+ const value = JSON.parse(source);
121
+ if (!value || typeof value !== 'object' || Array.isArray(value))
122
+ throw new Error('設定のルートがJSONオブジェクトではありません');
123
+ return value;
124
+ } catch (error) {
125
+ if (error.code === 'ENOENT') return {};
126
+ throw new Error(`Antigravity設定を読み込めません: ${error.message}`);
127
+ }
128
+ }
129
+
130
+ function configured(item, run, loadConfig = readJsonConfig) {
131
+ if (item.kind === 'json') return Boolean(loadConfig(item.configPath).mcpServers?.chocodrop);
132
+ const result = run(item.command, item.get);
133
+ return result.status === 0;
134
+ }
135
+
136
+ async function updateAntigravityConfig(item) {
137
+ await mkdir(path.dirname(item.configPath), { recursive: true });
138
+ let config = {};
139
+ let mode = 0o600;
140
+ try {
141
+ const source = (await readFile(item.configPath, 'utf8')).trim();
142
+ config = source ? JSON.parse(source) : {};
143
+ mode = (await stat(item.configPath)).mode & 0o777;
144
+ } catch (error) {
145
+ if (error.code !== 'ENOENT') throw new Error(`Antigravity設定を更新できません: ${error.message}`);
146
+ }
147
+ if (!config || typeof config !== 'object' || Array.isArray(config))
148
+ throw new Error('Antigravity設定のルートがJSONオブジェクトではありません');
149
+ if (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers))
150
+ config.mcpServers = {};
151
+ config.mcpServers.chocodrop = item.server;
152
+ const temporary = `${item.configPath}.${process.pid}.tmp`;
153
+ await writeFile(temporary, `${JSON.stringify(config, null, 2)}\n`, { mode });
154
+ await rename(temporary, item.configPath);
155
+ }
156
+
157
+ export async function applySetup(
158
+ plan,
159
+ { run = execute, makeDirectory = mkdir, updateConfig = updateAntigravityConfig } = {}
160
+ ) {
161
+ await makeDirectory(plan.assetsDir, { recursive: true });
162
+ const completed = [];
163
+ for (const item of plan.items) {
164
+ if (item.kind === 'json') {
165
+ await updateConfig(item);
166
+ completed.push(item.name);
167
+ continue;
168
+ }
169
+ if (configured(item, run)) {
170
+ const removed = run(item.command, item.remove);
171
+ if (removed.status !== 0)
172
+ throw new Error(`${item.label}の既存ChocoDrop設定を置き換えられませんでした`);
173
+ }
174
+ const added = run(item.command, item.add);
175
+ if (added.status !== 0) {
176
+ const details = (added.stderr || added.stdout).trim();
177
+ throw new Error(`${item.label}へ登録できませんでした${details ? `: ${details}` : ''}`);
178
+ }
179
+ completed.push(item.name);
180
+ }
181
+ return completed;
182
+ }
183
+
184
+ function usage() {
185
+ return `🍫 ChocoDrop setup\n\n使い方:\n pnpm dlx @chocodrop/setup@0.1.0-alpha.0\n pnpm dlx @chocodrop/setup@0.1.0-alpha.0 --client codex,antigravity --yes\n\nオプション:\n --client <name> codex / claude / antigravity / all(カンマ区切り可)\n --assets-dir <path> 素材フォルダ(既定: ~/ChocoDropAssets)\n --yes, -y 確認を省略\n --dry-run 変更せず、実行内容だけ表示\n`;
186
+ }
187
+
188
+ async function confirm(message, input, output) {
189
+ const prompt = createInterface({ input, output });
190
+ try {
191
+ const answer = (await prompt.question(`${message} [Y/n] `)).trim().toLowerCase();
192
+ return answer === '' || answer === 'y' || answer === 'yes';
193
+ } finally {
194
+ prompt.close();
195
+ }
196
+ }
197
+
198
+ export async function runSetup(
199
+ argv = process.argv.slice(2),
200
+ {
201
+ run = execute,
202
+ makeDirectory = mkdir,
203
+ fileExists = existsSync,
204
+ loadConfig = readJsonConfig,
205
+ updateConfig = updateAntigravityConfig,
206
+ input = process.stdin,
207
+ output = process.stdout,
208
+ errorOutput = process.stderr,
209
+ } = {}
210
+ ) {
211
+ const args = parseArgs(argv);
212
+ if (args.help) {
213
+ output.write(usage());
214
+ return { changed: false, clients: [] };
215
+ }
216
+
217
+ const detected = detectClients(run, fileExists);
218
+ let selected = args.clients.length ? args.clients : detected;
219
+ if (selected.includes('all')) selected = detected;
220
+ const invalid = selected.filter((name) => !CLIENTS[name]);
221
+ if (invalid.length) throw new Error(`対応していないツールです: ${invalid.join(', ')}`);
222
+ const missing = selected.filter((name) => !detected.includes(name));
223
+ if (missing.length)
224
+ throw new Error(`ツールが見つかりません: ${missing.map((name) => CLIENTS[name].label).join(', ')}`);
225
+ if (!selected.length)
226
+ throw new Error('Codex、Claude Code、Antigravityが見つかりません。先に利用するツールをインストールしてください');
227
+
228
+ const assetsDir = path.resolve(expandHome(args.assetsDir || path.join(homedir(), 'ChocoDropAssets')));
229
+ const runner = run('pnpm', ['--version']).status === 0 ? 'pnpm' : 'npx';
230
+ const plan = { assetsDir, items: selected.map((name) => registration(name, assetsDir, DEFAULT_MCP_PACKAGE, runner)) };
231
+ output.write('\n🍫 ChocoDropを設定します\n');
232
+ output.write(`素材フォルダ: ${assetsDir}\n`);
233
+ output.write(`接続先: ${plan.items.map((item) => item.label).join('・')}\n\n`);
234
+ for (const item of plan.items) {
235
+ if (item.kind === 'json')
236
+ output.write(` ${item.configPath} の mcpServers.chocodrop を設定\n`);
237
+ else output.write(` ${shellCommand(item.command, item.add)}\n`);
238
+ }
239
+ const replacements = plan.items.filter((item) => configured(item, run, loadConfig));
240
+ if (replacements.length)
241
+ output.write(`\n既存のChocoDrop設定を置き換えます: ${replacements.map((item) => item.label).join('・')}\n`);
242
+
243
+ if (args.dryRun) {
244
+ output.write('\nドライランのため変更していません。\n');
245
+ return { changed: false, clients: selected, assetsDir };
246
+ }
247
+ if (!args.yes) {
248
+ if (!input.isTTY)
249
+ throw new Error('非対話環境では--yesを付けて実行してください');
250
+ if (!(await confirm('\n素材フォルダを作成し、上記の設定を登録しますか?', input, output))) {
251
+ output.write('変更しませんでした。\n');
252
+ return { changed: false, clients: selected, assetsDir };
253
+ }
254
+ }
255
+
256
+ const completed = await applySetup(plan, { run, makeDirectory, updateConfig });
257
+ output.write('\n✅ ChocoDrop MCPを登録しました。\n');
258
+ output.write('ツールを再起動し、「ChocoDropのget_statusでURLを教えて」と依頼してください。\n');
259
+ output.write('生成した素材は上記フォルダへ保存し、import_assetで配置できます。\n');
260
+ if (completed.length !== selected.length)
261
+ errorOutput.write('一部のCLIを登録できませんでした。\n');
262
+ return { changed: true, clients: completed, assetsDir };
263
+ }
264
+
265
+ export { CLIENTS, DEFAULT_MCP_PACKAGE, usage };