claude-settings-to-codex 0.1.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,8 @@
1
+ Copyright 2026 newnakashima2014@gmail.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # claude-settings-to-codex
2
+
3
+ `Claude Code` の `settings.json` を、`Codex` 向けの `config.toml` と変換レポートへ落とす CLI です。
4
+
5
+ 特に `permissions.allow / ask / deny` を対象にしています。ただし Codex は Claude のようなコマンド単位 allowlist/denylist をそのまま表現できないため、このツールは安全側の近似変換を行い、非互換部分はレポートに残します。
6
+
7
+ ## 使い方
8
+
9
+ ```bash
10
+ npx claude-settings-to-codex
11
+ ```
12
+
13
+ 任意のプロジェクトディレクトリで実行すると、そのディレクトリを基準に以下の既定パスを使います。
14
+
15
+ - 入力: `.claude/settings.json`
16
+ - 出力: `.codex/config.toml`
17
+ - レポート: `conversion-report.md`
18
+
19
+ 明示指定もできます。
20
+
21
+ ```bash
22
+ npx claude-settings-to-codex \
23
+ --input ./path/to/settings.json \
24
+ --output ./.codex/config.toml \
25
+ --report ./conversion-report.md
26
+ ```
27
+
28
+ ネットワーク設定の扱い:
29
+
30
+ ```bash
31
+ npx claude-settings-to-codex --network off
32
+ npx claude-settings-to-codex --network auto
33
+ npx claude-settings-to-codex --network on
34
+ ```
35
+
36
+ - `off`: 常に `network_access = false`
37
+ - `auto`: `npm install` や `curl` などのパターンがある場合のみ `true`
38
+ - `on`: 常に `network_access = true`
39
+
40
+ ## ローカル開発時の確認
41
+
42
+ 公開前に手元で CLI として確認したい場合:
43
+
44
+ ```bash
45
+ npm link
46
+ cd /path/to/target-project
47
+ claude-settings-to-codex
48
+ ```
49
+
50
+ あるいは、このリポジトリ内で直接実行しても構いません。
51
+
52
+ ```bash
53
+ node src/cli.js --help
54
+ ```
55
+
56
+ ## 変換方針
57
+
58
+ - `Bash(...)` ルールを抽出して集計
59
+ - Codex 側は原則 `approval_policy = "on-request"` を生成
60
+ - Bash 権限が1つでもあれば `sandbox_mode = "workspace-write"`、なければ `read-only`
61
+ - Claude の `allow` / `deny` の厳密再現はできないので、レポートに unsupported / lossy として残す
62
+ - `ask` は `on-request` に近似
63
+
64
+ ## テスト
65
+
66
+ ```bash
67
+ npm test
68
+ ```
69
+
70
+ ## npm 公開手順
71
+
72
+ 1. npm アカウントでログインする: `npm login`
73
+ 2. 公開名が空いているか確認する: `npm view claude-settings-to-codex`
74
+ 3. テストを実行する: `npm test`
75
+ 4. 公開内容を確認する: `npm pack --dry-run`
76
+ 5. 公開する: `npm publish --access public`
77
+
78
+ 公開後は任意のプロジェクトで次のように使えます。
79
+
80
+ ```bash
81
+ cd /path/to/project
82
+ npx claude-settings-to-codex
83
+ ```
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "claude-settings-to-codex",
3
+ "version": "0.1.0",
4
+ "description": "Convert Claude Code settings.json permission settings into a Codex-oriented config.toml and conversion report.",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-settings-to-codex": "./src/cli.js"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "test": "node --test"
16
+ },
17
+ "keywords": [
18
+ "claude-code",
19
+ "codex",
20
+ "converter",
21
+ "permissions",
22
+ "cli"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/newnakashima/claude-settings-to-codex.git"
27
+ },
28
+ "homepage": "https://github.com/newnakashima/claude-settings-to-codex#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/newnakashima/claude-settings-to-codex/issues"
31
+ },
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=20"
35
+ }
36
+ }
package/src/cli.js ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { convertClaudeSettings, defaultPaths } from "./converter.js";
7
+ import { parseJsonc } from "./jsonc.js";
8
+
9
+ async function main() {
10
+ const cwd = process.cwd();
11
+ const defaults = defaultPaths(cwd);
12
+ const args = parseArgs(process.argv.slice(2), defaults);
13
+
14
+ if (args.help) {
15
+ printHelp(defaults);
16
+ return;
17
+ }
18
+
19
+ const sourceText = await fs.readFile(args.input, "utf8");
20
+ const settings = parseJsonc(sourceText);
21
+ const result = convertClaudeSettings(settings, {
22
+ sourcePath: relativeOrAbsolute(cwd, args.input),
23
+ outputPath: relativeOrAbsolute(cwd, args.output),
24
+ approvalPolicy: args.approvalPolicy,
25
+ network: args.network
26
+ });
27
+
28
+ await fs.mkdir(path.dirname(args.output), { recursive: true });
29
+ await fs.mkdir(path.dirname(args.report), { recursive: true });
30
+ await fs.writeFile(args.output, result.configToml, "utf8");
31
+ await fs.writeFile(args.report, result.reportMarkdown, "utf8");
32
+
33
+ process.stdout.write(
34
+ [
35
+ `Wrote ${relativeOrAbsolute(cwd, args.output)}`,
36
+ `Wrote ${relativeOrAbsolute(cwd, args.report)}`,
37
+ `approval_policy=${result.analysis.approvalPolicy}`,
38
+ `sandbox_mode=${result.analysis.sandboxMode}`,
39
+ `network_access=${result.analysis.network.enabled}`
40
+ ].join("\n") + "\n"
41
+ );
42
+ }
43
+
44
+ function parseArgs(argv, defaults) {
45
+ const args = {
46
+ input: defaults.input,
47
+ output: defaults.output,
48
+ report: defaults.report,
49
+ approvalPolicy: "on-request",
50
+ network: "off",
51
+ help: false
52
+ };
53
+
54
+ for (let index = 0; index < argv.length; index += 1) {
55
+ const arg = argv[index];
56
+
57
+ if (arg === "--help" || arg === "-h") {
58
+ args.help = true;
59
+ continue;
60
+ }
61
+
62
+ if (arg === "--input") {
63
+ args.input = requireValue(argv, index, "--input");
64
+ index += 1;
65
+ continue;
66
+ }
67
+
68
+ if (arg === "--output") {
69
+ args.output = requireValue(argv, index, "--output");
70
+ index += 1;
71
+ continue;
72
+ }
73
+
74
+ if (arg === "--report") {
75
+ args.report = requireValue(argv, index, "--report");
76
+ index += 1;
77
+ continue;
78
+ }
79
+
80
+ if (arg === "--approval-policy") {
81
+ const value = requireValue(argv, index, "--approval-policy");
82
+ if (!["untrusted", "on-failure", "on-request", "never"].includes(value)) {
83
+ throw new Error(`Unsupported --approval-policy value: ${value}`);
84
+ }
85
+ args.approvalPolicy = value;
86
+ index += 1;
87
+ continue;
88
+ }
89
+
90
+ if (arg === "--network") {
91
+ const value = requireValue(argv, index, "--network");
92
+ if (!["off", "auto", "on"].includes(value)) {
93
+ throw new Error(`Unsupported --network value: ${value}`);
94
+ }
95
+ args.network = value;
96
+ index += 1;
97
+ continue;
98
+ }
99
+
100
+ throw new Error(`Unknown argument: ${arg}`);
101
+ }
102
+
103
+ return args;
104
+ }
105
+
106
+ function requireValue(argv, index, flag) {
107
+ const value = argv[index + 1];
108
+ if (!value) {
109
+ throw new Error(`Missing value for ${flag}`);
110
+ }
111
+ return value;
112
+ }
113
+
114
+ function printHelp(defaults) {
115
+ process.stdout.write(
116
+ [
117
+ "Usage: claude-settings-to-codex [options]",
118
+ "",
119
+ "Options:",
120
+ ` --input <path> Input Claude settings file (default: ${defaults.input})`,
121
+ ` --output <path> Output Codex config.toml (default: ${defaults.output})`,
122
+ ` --report <path> Output conversion report (default: ${defaults.report})`,
123
+ " --approval-policy <value> Codex approval policy: untrusted | on-failure | on-request | never",
124
+ " --network <mode> Network mapping: off | auto | on",
125
+ " -h, --help Show this help",
126
+ ""
127
+ ].join("\n")
128
+ );
129
+ }
130
+
131
+ function relativeOrAbsolute(cwd, target) {
132
+ const relative = path.relative(cwd, target);
133
+ return relative && !relative.startsWith("..") ? relative : target;
134
+ }
135
+
136
+ main().catch((error) => {
137
+ process.stderr.write(`${error.message}\n`);
138
+ process.exitCode = 1;
139
+ });
@@ -0,0 +1,333 @@
1
+ import path from "node:path";
2
+
3
+ const NETWORK_PATTERNS = [
4
+ /\bnpm\s+(install|i)\b/i,
5
+ /\bpnpm\s+(add|install|up|update|dlx)\b/i,
6
+ /\byarn\s+(add|install|up|upgrade|dlx)\b/i,
7
+ /\bbun\s+(add|install|updatex?)\b/i,
8
+ /\bpip(?:3)?\s+install\b/i,
9
+ /\buv\s+pip\s+install\b/i,
10
+ /\bpoetry\s+add\b/i,
11
+ /\bcargo\s+(add|install)\b/i,
12
+ /\bgo\s+(get|install)\b/i,
13
+ /\bgem\s+install\b/i,
14
+ /\bbrew\s+(install|upgrade|tap)\b/i,
15
+ /\bapt(?:-get)?\s+(install|update)\b/i,
16
+ /\bapk\s+add\b/i,
17
+ /\bdnf\s+install\b/i,
18
+ /\byum\s+install\b/i,
19
+ /\bpod\s+install\b/i,
20
+ /\bcomposer\s+(require|install|update)\b/i,
21
+ /\bnuget\s+install\b/i,
22
+ /\bdotnet\s+add\s+package\b/i,
23
+ /\bgit\s+clone\b/i,
24
+ /\bcurl\b/i,
25
+ /\bwget\b/i,
26
+ /\bnpx\b/i,
27
+ /\bdocker\s+pull\b/i
28
+ ];
29
+
30
+ export function convertClaudeSettings(settings, options = {}) {
31
+ const normalized = normalizeClaudeSettings(settings);
32
+ const entries = extractPermissionEntries(normalized.permissions);
33
+ const bashEntries = entries.filter((entry) => entry.kind === "bash");
34
+ const nonBashEntries = entries.filter((entry) => entry.kind !== "bash");
35
+
36
+ const sandboxMode = deriveSandboxMode(bashEntries);
37
+ const network = deriveNetworkAccess(bashEntries, options.network ?? "off");
38
+ const approvalPolicy = options.approvalPolicy ?? "on-request";
39
+
40
+ const configToml = renderCodexConfig({
41
+ approvalPolicy,
42
+ sandboxMode,
43
+ networkAccess: network.enabled,
44
+ notes: buildConfigNotes({ entries, bashEntries, nonBashEntries, network })
45
+ });
46
+
47
+ const reportMarkdown = renderReport({
48
+ sourcePath: options.sourcePath ?? ".claude/settings.json",
49
+ outputPath: options.outputPath ?? ".codex/config.toml",
50
+ normalized,
51
+ entries,
52
+ bashEntries,
53
+ nonBashEntries,
54
+ approvalPolicy,
55
+ sandboxMode,
56
+ network
57
+ });
58
+
59
+ return {
60
+ configToml,
61
+ reportMarkdown,
62
+ analysis: {
63
+ entries,
64
+ bashEntries,
65
+ nonBashEntries,
66
+ approvalPolicy,
67
+ sandboxMode,
68
+ network
69
+ }
70
+ };
71
+ }
72
+
73
+ export function normalizeClaudeSettings(settings) {
74
+ const permissions = isPlainObject(settings?.permissions) ? settings.permissions : {};
75
+
76
+ return {
77
+ permissions: {
78
+ allow: normalizeStringArray(permissions.allow),
79
+ ask: normalizeStringArray(permissions.ask),
80
+ deny: normalizeStringArray(permissions.deny)
81
+ }
82
+ };
83
+ }
84
+
85
+ function normalizeStringArray(value) {
86
+ if (!Array.isArray(value)) {
87
+ return [];
88
+ }
89
+
90
+ return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
91
+ }
92
+
93
+ export function extractPermissionEntries(permissions) {
94
+ const groups = ["allow", "ask", "deny"];
95
+ const entries = [];
96
+
97
+ for (const group of groups) {
98
+ for (const raw of permissions[group] ?? []) {
99
+ entries.push(parsePermissionEntry(raw, group));
100
+ }
101
+ }
102
+
103
+ return entries;
104
+ }
105
+
106
+ export function parsePermissionEntry(raw, group) {
107
+ const bashMatch = /^Bash\(([\s\S]*)\)$/u.exec(raw);
108
+ if (bashMatch) {
109
+ const command = bashMatch[1].trim();
110
+ return {
111
+ group,
112
+ raw,
113
+ kind: "bash",
114
+ tool: "Bash",
115
+ command,
116
+ requiresNetwork: NETWORK_PATTERNS.some((pattern) => pattern.test(command))
117
+ };
118
+ }
119
+
120
+ const toolCallMatch = /^([A-Za-z][A-Za-z0-9_-]*)\(([\s\S]*)\)$/u.exec(raw);
121
+ if (toolCallMatch) {
122
+ return {
123
+ group,
124
+ raw,
125
+ kind: "tool-call",
126
+ tool: toolCallMatch[1],
127
+ argument: toolCallMatch[2].trim()
128
+ };
129
+ }
130
+
131
+ const bareToolMatch = /^([A-Za-z][A-Za-z0-9_-]*)$/u.exec(raw);
132
+ if (bareToolMatch) {
133
+ return {
134
+ group,
135
+ raw,
136
+ kind: "tool",
137
+ tool: bareToolMatch[1]
138
+ };
139
+ }
140
+
141
+ return {
142
+ group,
143
+ raw,
144
+ kind: "unknown",
145
+ tool: null
146
+ };
147
+ }
148
+
149
+ function deriveSandboxMode(bashEntries) {
150
+ return bashEntries.length > 0 ? "workspace-write" : "read-only";
151
+ }
152
+
153
+ function deriveNetworkAccess(bashEntries, mode) {
154
+ if (mode === "on") {
155
+ return { enabled: true, reason: "Forced by --network on", commands: [] };
156
+ }
157
+
158
+ if (mode === "off") {
159
+ return { enabled: false, reason: "Forced by --network off", commands: [] };
160
+ }
161
+
162
+ const commands = bashEntries.filter((entry) => entry.requiresNetwork).map((entry) => entry.command);
163
+ return {
164
+ enabled: commands.length > 0,
165
+ reason: commands.length > 0 ? "Enabled by heuristic network command detection" : "No network-oriented command patterns detected",
166
+ commands
167
+ };
168
+ }
169
+
170
+ function buildConfigNotes({ entries, bashEntries, nonBashEntries, network }) {
171
+ const notes = [];
172
+
173
+ if (entries.length === 0) {
174
+ notes.push("No Claude permissions entries were found.");
175
+ }
176
+
177
+ if (bashEntries.some((entry) => entry.group === "allow")) {
178
+ notes.push("Claude Bash allowlist rules are not representable in Codex config; approval is approximated globally.");
179
+ }
180
+
181
+ if (bashEntries.some((entry) => entry.group === "deny")) {
182
+ notes.push("Claude Bash denylist rules are not representable in Codex config; see the conversion report for manual follow-up.");
183
+ }
184
+
185
+ if (nonBashEntries.length > 0) {
186
+ notes.push("Non-Bash Claude permission entries were preserved only in the conversion report.");
187
+ }
188
+
189
+ if (network.reason) {
190
+ notes.push(`Network access decision: ${network.reason}.`);
191
+ }
192
+
193
+ return notes;
194
+ }
195
+
196
+ export function renderCodexConfig({ approvalPolicy, sandboxMode, networkAccess, notes }) {
197
+ const lines = [
198
+ "# Generated by claude-settings-to-codex",
199
+ "# This config is a best-effort approximation of Claude Code permissions.",
200
+ ...notes.map((note) => `# ${note}`),
201
+ `approval_policy = ${tomlString(approvalPolicy)}`,
202
+ `sandbox_mode = ${tomlString(sandboxMode)}`
203
+ ];
204
+
205
+ if (sandboxMode === "workspace-write") {
206
+ lines.push("");
207
+ lines.push("[sandbox_workspace_write]");
208
+ lines.push(`network_access = ${networkAccess ? "true" : "false"}`);
209
+ lines.push("writable_roots = []");
210
+ }
211
+
212
+ lines.push("");
213
+ return lines.join("\n");
214
+ }
215
+
216
+ export function renderReport({
217
+ sourcePath,
218
+ outputPath,
219
+ normalized,
220
+ entries,
221
+ bashEntries,
222
+ nonBashEntries,
223
+ approvalPolicy,
224
+ sandboxMode,
225
+ network
226
+ }) {
227
+ const counts = countByGroup(entries);
228
+ const lines = [
229
+ "# Conversion Report",
230
+ "",
231
+ `- Source: \`${sourcePath}\``,
232
+ `- Generated config: \`${outputPath}\``,
233
+ `- Bash permission entries: ${bashEntries.length}`,
234
+ `- Non-Bash permission entries: ${nonBashEntries.length}`,
235
+ "",
236
+ "## Generated Codex Defaults",
237
+ "",
238
+ `- approval_policy: \`${approvalPolicy}\``,
239
+ `- sandbox_mode: \`${sandboxMode}\``,
240
+ `- network_access: \`${network.enabled}\``,
241
+ `- network decision: ${network.reason}`,
242
+ "",
243
+ "## Claude Permission Summary",
244
+ "",
245
+ `- allow: ${counts.allow}`,
246
+ `- ask: ${counts.ask}`,
247
+ `- deny: ${counts.deny}`,
248
+ "",
249
+ "## Compatibility Notes",
250
+ "",
251
+ "- Claude `ask` entries are approximated by Codex's global `approval_policy = \"on-request\"`.",
252
+ "- Claude command-level `allow` rules cannot be represented exactly in Codex config today.",
253
+ "- Claude command-level `deny` rules cannot be represented exactly in Codex config today.",
254
+ "- Non-Bash Claude permissions are not translated into Codex config keys by this tool.",
255
+ ""
256
+ ];
257
+
258
+ if (network.commands.length > 0) {
259
+ lines.push("## Network Heuristic Matches", "");
260
+ for (const command of network.commands) {
261
+ lines.push(`- \`${command}\``);
262
+ }
263
+ lines.push("");
264
+ }
265
+
266
+ if (entries.length > 0) {
267
+ lines.push("## Entry-by-Entry Analysis", "");
268
+ for (const entry of entries) {
269
+ lines.push(`- ${formatEntryLine(entry)}`);
270
+ }
271
+ lines.push("");
272
+ }
273
+
274
+ if (normalized.permissions.allow.length === 0 && normalized.permissions.ask.length === 0 && normalized.permissions.deny.length === 0) {
275
+ lines.push("## Notes", "", "- No Claude `permissions` entries were found, so the generated Codex config is only a baseline shell-execution profile.", "");
276
+ }
277
+
278
+ lines.push("## Manual Follow-up", "");
279
+ lines.push("- Review `allow` and `deny` entries manually; Codex cannot enforce them as exact per-command rules.");
280
+ lines.push("- If your workflows need package installation or HTTP access, re-run with `--network auto` or `--network on`.");
281
+ lines.push("- If you need extra writable directories, edit `writable_roots` in the generated `config.toml`.");
282
+ lines.push("");
283
+
284
+ return lines.join("\n");
285
+ }
286
+
287
+ function formatEntryLine(entry) {
288
+ const base = `\`${entry.raw}\` (${entry.group})`;
289
+
290
+ if (entry.kind === "bash") {
291
+ if (entry.group === "ask") {
292
+ return `${base}: approximated by global on-request approval.`;
293
+ }
294
+
295
+ if (entry.group === "allow") {
296
+ return `${base}: unsupported exact mapping; treated as a manual review item.`;
297
+ }
298
+
299
+ if (entry.group === "deny") {
300
+ return `${base}: unsupported exact mapping; treated as a manual review item.`;
301
+ }
302
+ }
303
+
304
+ if (entry.kind === "tool" || entry.kind === "tool-call") {
305
+ return `${base}: non-Bash Claude permission, preserved in report only.`;
306
+ }
307
+
308
+ return `${base}: unrecognized permission syntax, preserved in report only.`;
309
+ }
310
+
311
+ function countByGroup(entries) {
312
+ return {
313
+ allow: entries.filter((entry) => entry.group === "allow").length,
314
+ ask: entries.filter((entry) => entry.group === "ask").length,
315
+ deny: entries.filter((entry) => entry.group === "deny").length
316
+ };
317
+ }
318
+
319
+ function tomlString(value) {
320
+ return JSON.stringify(value);
321
+ }
322
+
323
+ function isPlainObject(value) {
324
+ return value !== null && typeof value === "object" && !Array.isArray(value);
325
+ }
326
+
327
+ export function defaultPaths(cwd) {
328
+ return {
329
+ input: path.join(cwd, ".claude", "settings.json"),
330
+ output: path.join(cwd, ".codex", "config.toml"),
331
+ report: path.join(cwd, "conversion-report.md")
332
+ };
333
+ }
package/src/jsonc.js ADDED
@@ -0,0 +1,57 @@
1
+ export function parseJsonc(input) {
2
+ const stripped = stripJsonComments(input);
3
+ return JSON.parse(stripped);
4
+ }
5
+
6
+ function stripJsonComments(input) {
7
+ let output = "";
8
+ let inString = false;
9
+ let quote = "";
10
+ let escaped = false;
11
+
12
+ for (let i = 0; i < input.length; i += 1) {
13
+ const char = input[i];
14
+ const next = input[i + 1];
15
+
16
+ if (inString) {
17
+ output += char;
18
+ if (escaped) {
19
+ escaped = false;
20
+ } else if (char === "\\") {
21
+ escaped = true;
22
+ } else if (char === quote) {
23
+ inString = false;
24
+ quote = "";
25
+ }
26
+ continue;
27
+ }
28
+
29
+ if (char === "\"" || char === "'") {
30
+ inString = true;
31
+ quote = char;
32
+ output += char;
33
+ continue;
34
+ }
35
+
36
+ if (char === "/" && next === "/") {
37
+ while (i < input.length && input[i] !== "\n") {
38
+ i += 1;
39
+ }
40
+ output += "\n";
41
+ continue;
42
+ }
43
+
44
+ if (char === "/" && next === "*") {
45
+ i += 2;
46
+ while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) {
47
+ i += 1;
48
+ }
49
+ i += 1;
50
+ continue;
51
+ }
52
+
53
+ output += char;
54
+ }
55
+
56
+ return output;
57
+ }