sleev 0.0.0 → 0.0.11

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/README.md CHANGED
@@ -1,3 +1,49 @@
1
1
  # sleev
2
2
 
3
- Reserved package placeholder.
3
+ The Sleev command-line tool.
4
+
5
+ Use it to sign in to Sleev from your terminal and connect supported local apps.
6
+
7
+ ## Login
8
+
9
+ Run:
10
+
11
+ ```bash
12
+ npx sleev auth login
13
+ ```
14
+
15
+ Sleev will:
16
+
17
+ 1. Open your browser.
18
+ 2. Ask you to sign in or create an account.
19
+ 3. Return you to the terminal after login.
20
+ 4. Ask which supported local apps you want to use with Sleev.
21
+
22
+ After that, your local apps can use Sleev without logging in again.
23
+
24
+ ## Logout
25
+
26
+ Run:
27
+
28
+ ```bash
29
+ npx sleev auth logout
30
+ ```
31
+
32
+ This removes your local Sleev login from this machine.
33
+
34
+ ## Already Logged In
35
+
36
+ If you need to replace your current local login, run:
37
+
38
+ ```bash
39
+ npx sleev auth login --force
40
+ ```
41
+
42
+ ## Requirements
43
+
44
+ - Node.js 20 or newer
45
+ - A browser you can sign in with
46
+
47
+ ## Notes
48
+
49
+ Your login is stored locally on your machine. Do not share your Sleev auth file.
@@ -0,0 +1,151 @@
1
+ // Claude Code setup owns global settings patching for the Anthropic-compatible proxy.
2
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { field, success } from '../terminal.js';
6
+ const BASE = 'ANTHROPIC_BASE_URL';
7
+ const CUSTOM = 'ANTHROPIC_CUSTOM_HEADERS';
8
+ const HARNESS = 'claude code';
9
+ const OLD_HARNESS = 'claude-code';
10
+ export async function setupClaude(auth, path = claudePath()) {
11
+ const result = await patchClaude(auth, path);
12
+ success(process.stdout, 'Configured Claude Code');
13
+ field(process.stdout, 'config', result.path);
14
+ return result;
15
+ }
16
+ export async function patchClaude(auth, path = claudePath()) {
17
+ const existing = await readJson(path);
18
+ const config = patchConfig(existing, auth);
19
+ await writeJson(path, config);
20
+ return { path };
21
+ }
22
+ export async function unpatchClaude(path = claudePath()) {
23
+ const existing = await readExistingJson(path);
24
+ if (!existing)
25
+ return;
26
+ const env = object(existing.env);
27
+ if (typeof env.ANTHROPIC_CUSTOM_HEADERS !== 'string')
28
+ return;
29
+ if (!patched(env))
30
+ return;
31
+ await writeJson(path, removeBackup({ ...existing, env: restoreEnv(env, backup(existing)) }));
32
+ }
33
+ export function claudePath(env = process.env) {
34
+ return join(env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'settings.json');
35
+ }
36
+ function patchConfig(input, auth) {
37
+ const env = object(input.env);
38
+ const previous = backup(input) ?? backupEnv(env);
39
+ return {
40
+ ...withBackup(input, previous),
41
+ env: {
42
+ ...env,
43
+ [BASE]: trim(auth.proxyUrl),
44
+ [CUSTOM]: headers(auth),
45
+ },
46
+ };
47
+ }
48
+ function headers(auth) {
49
+ return [
50
+ `sleeve-token: ${auth.token}`,
51
+ 'sleeve-provider: anthropic',
52
+ `sleeve-harness: ${HARNESS}`,
53
+ ].join('\n');
54
+ }
55
+ function backup(input) {
56
+ const sleev = object(input.sleev);
57
+ const claude = object(sleev.claudeCode);
58
+ if (!('previousEnv' in claude))
59
+ return null;
60
+ return object(claude.previousEnv);
61
+ }
62
+ function backupEnv(env) {
63
+ if (patched(env))
64
+ return {};
65
+ const previous = {};
66
+ if (BASE in env)
67
+ previous[BASE] = env[BASE];
68
+ if (CUSTOM in env)
69
+ previous[CUSTOM] = env[CUSTOM];
70
+ return previous;
71
+ }
72
+ function withBackup(input, previous) {
73
+ const sleev = object(input.sleev);
74
+ return {
75
+ ...input,
76
+ sleev: {
77
+ ...sleev,
78
+ claudeCode: {
79
+ previousEnv: previous,
80
+ },
81
+ },
82
+ };
83
+ }
84
+ function removeBackup(input) {
85
+ const sleev = object(input.sleev);
86
+ const { claudeCode: _claude, ...rest } = sleev;
87
+ if (Object.keys(rest).length > 0)
88
+ return { ...input, sleev: rest };
89
+ const { sleev: _sleev, ...config } = input;
90
+ return config;
91
+ }
92
+ function restoreEnv(env, previous) {
93
+ const { [BASE]: _base, [CUSTOM]: _headers, ...rest } = env;
94
+ const restored = { ...rest };
95
+ if (!previous)
96
+ return restored;
97
+ if (BASE in previous)
98
+ restored[BASE] = previous[BASE];
99
+ if (CUSTOM in previous)
100
+ restored[CUSTOM] = previous[CUSTOM];
101
+ return restored;
102
+ }
103
+ function patched(env) {
104
+ const value = env[CUSTOM];
105
+ if (typeof value !== 'string')
106
+ return false;
107
+ return value.includes(`sleeve-harness: ${HARNESS}`) || value.includes(`sleeve-harness: ${OLD_HARNESS}`);
108
+ }
109
+ async function writeJson(path, config) {
110
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
111
+ await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
112
+ await chmod(path, 0o600);
113
+ }
114
+ async function readJson(path) {
115
+ try {
116
+ const raw = await readFile(path, 'utf8');
117
+ const parsed = JSON.parse(raw);
118
+ return object(parsed);
119
+ }
120
+ catch (err) {
121
+ if (code(err) === 'ENOENT')
122
+ return {};
123
+ throw err;
124
+ }
125
+ }
126
+ async function readExistingJson(path) {
127
+ try {
128
+ const raw = await readFile(path, 'utf8');
129
+ const parsed = JSON.parse(raw);
130
+ return object(parsed);
131
+ }
132
+ catch (err) {
133
+ if (code(err) === 'ENOENT')
134
+ return null;
135
+ throw err;
136
+ }
137
+ }
138
+ function object(value) {
139
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
140
+ return {};
141
+ return value;
142
+ }
143
+ function trim(url) {
144
+ return url.replace(/\/+$/, '');
145
+ }
146
+ function code(err) {
147
+ if (typeof err !== 'object' || err === null || !('code' in err))
148
+ return undefined;
149
+ const value = err.code;
150
+ return typeof value === 'string' ? value : undefined;
151
+ }
@@ -0,0 +1,184 @@
1
+ // Codex setup owns global TOML patching for the OpenAI-compatible proxy provider.
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { field, success } from '../terminal.js';
6
+ export async function setupCodex(auth, path = codexPath()) {
7
+ const result = await patchCodex(auth, path);
8
+ success(process.stdout, 'Configured Codex');
9
+ field(process.stdout, 'config', result.path);
10
+ return result;
11
+ }
12
+ export async function patchCodex(auth, path = codexPath()) {
13
+ const existing = await readText(path);
14
+ const config = patchCodexConfig(existing, auth);
15
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
16
+ await writeFile(path, config, { mode: 0o600 });
17
+ return { path };
18
+ }
19
+ export async function unpatchCodex(path = codexPath()) {
20
+ const existing = await readText(path);
21
+ if (!existing)
22
+ return;
23
+ const config = unpatchCodexConfig(existing);
24
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
25
+ await writeFile(path, config, { mode: 0o600 });
26
+ }
27
+ export function codexPath(env = process.env) {
28
+ return join(env.CODEX_HOME || join(homedir(), '.codex'), 'config.toml');
29
+ }
30
+ export function patchCodexConfig(input, auth) {
31
+ const provider = rootString(input, 'model_provider') ?? 'openai';
32
+ const target = reserved(provider) ? 'sleev' : provider;
33
+ const stale = removeTable(input.trimEnd(), 'model_providers.sleev');
34
+ const clean = target === provider ? stale : removeRootKey(stale, 'model_provider');
35
+ const root = target === provider ? clean : insertRootKey(clean.trimEnd(), 'model_provider', target);
36
+ const base = trim(auth.proxyUrl);
37
+ return `${patchProvider(root, target, auth, base).trimEnd()}\n`;
38
+ }
39
+ export function unpatchCodexConfig(input) {
40
+ const provider = rootString(input, 'model_provider') ?? 'openai';
41
+ const clean = removeTable(removeRootKey(input.trimEnd(), 'model_provider'), 'model_providers.sleev');
42
+ const config = provider === 'sleev' ? clean : removeSleevKeys(input, provider);
43
+ return `${config.trimEnd()}\n`;
44
+ }
45
+ function insertRootKey(input, key, value) {
46
+ const line = `${key} = "${toml(value)}"`;
47
+ if (input.length === 0)
48
+ return line;
49
+ const lines = input.split('\n');
50
+ const index = lines.findIndex((item) => /^\s*\[/.test(item));
51
+ if (index === -1)
52
+ return `${input}\n${line}`;
53
+ return [...lines.slice(0, index), line, '', ...lines.slice(index)].join('\n');
54
+ }
55
+ function removeRootKey(input, key) {
56
+ const lines = input.split('\n');
57
+ const next = [];
58
+ let root = true;
59
+ let skipped = false;
60
+ for (const line of lines) {
61
+ if (/^\s*\[/.test(line))
62
+ root = false;
63
+ if (root && new RegExp(`^\\s*${escape(key)}\\s*=`).test(line)) {
64
+ skipped = true;
65
+ continue;
66
+ }
67
+ if (skipped && root && /^\s*$/.test(line)) {
68
+ skipped = false;
69
+ continue;
70
+ }
71
+ skipped = false;
72
+ next.push(line);
73
+ }
74
+ return next.join('\n');
75
+ }
76
+ function removeTable(input, table) {
77
+ const lines = input.split('\n');
78
+ const next = [];
79
+ let skip = false;
80
+ const header = new RegExp(`^\\s*\\[\\s*${escape(table)}(?:\\.|\\s*\\])`);
81
+ for (const line of lines) {
82
+ if (header.test(line)) {
83
+ skip = true;
84
+ continue;
85
+ }
86
+ if (skip && /^\s*\[/.test(line))
87
+ skip = header.test(line);
88
+ if (!skip)
89
+ next.push(line);
90
+ }
91
+ return next.join('\n');
92
+ }
93
+ function patchProvider(input, provider, auth, url) {
94
+ const table = `model_providers.${provider}`;
95
+ const clean = removeTable(input, `${table}.http_headers`);
96
+ const lines = clean.split('\n');
97
+ const header = new RegExp(`^\\s*\\[\\s*${escape(table)}\\s*\\]\\s*(#.*)?$`);
98
+ const index = lines.findIndex((line) => header.test(line));
99
+ const keys = providerKeys(auth, url);
100
+ if (index === -1)
101
+ return `${clean.trimEnd()}\n\n[${table}]\nname = "Sleev"\n${keys.join('\n')}`;
102
+ const next = [];
103
+ for (let i = 0; i < lines.length; i += 1) {
104
+ const line = lines[i] ?? '';
105
+ const inside = i > index && !/^\s*\[/.test(line);
106
+ if (inside && /^\s*(base_url|wire_api|http_headers)\s*=/.test(line))
107
+ continue;
108
+ next.push(line);
109
+ if (i === index)
110
+ next.push(...keys);
111
+ }
112
+ return next.join('\n');
113
+ }
114
+ function providerKeys(auth, url) {
115
+ return [
116
+ `base_url = "${toml(url)}"`,
117
+ 'wire_api = "responses"',
118
+ `http_headers = { "sleeve-token" = "${toml(auth.token)}", "sleeve-provider" = "openai", "sleeve-harness" = "codex" }`,
119
+ ];
120
+ }
121
+ function removeSleevKeys(input, provider) {
122
+ const table = `model_providers.${provider}`;
123
+ const lines = input.split('\n');
124
+ const header = new RegExp(`^\\s*\\[\\s*${escape(table)}\\s*\\]\\s*(#.*)?$`);
125
+ const index = lines.findIndex((line) => header.test(line));
126
+ if (index === -1)
127
+ return input;
128
+ const current = [];
129
+ for (let i = index + 1; i < lines.length; i += 1) {
130
+ const line = lines[i] ?? '';
131
+ if (/^\s*\[/.test(line))
132
+ break;
133
+ current.push(line);
134
+ }
135
+ if (!current.some((line) => line.includes('"sleeve-harness" = "codex"')))
136
+ return input;
137
+ return lines
138
+ .filter((line, i) => {
139
+ const inside = i > index && !/^\s*\[/.test(line);
140
+ if (!inside)
141
+ return true;
142
+ return !/^\s*(base_url|wire_api|http_headers)\s*=/.test(line);
143
+ })
144
+ .join('\n');
145
+ }
146
+ function rootString(input, key) {
147
+ const lines = input.split('\n');
148
+ for (const line of lines) {
149
+ if (/^\s*\[/.test(line))
150
+ return null;
151
+ const match = new RegExp(`^\\s*${escape(key)}\\s*=\\s*([\"'])([^\"']+)\\1`).exec(line);
152
+ if (match?.[2])
153
+ return match[2];
154
+ }
155
+ return null;
156
+ }
157
+ function reserved(provider) {
158
+ return provider === 'openai' || provider === 'ollama' || provider === 'lmstudio';
159
+ }
160
+ async function readText(path) {
161
+ try {
162
+ return await readFile(path, 'utf8');
163
+ }
164
+ catch (err) {
165
+ if (code(err) === 'ENOENT')
166
+ return '';
167
+ throw err;
168
+ }
169
+ }
170
+ function toml(value) {
171
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
172
+ }
173
+ function escape(value) {
174
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
175
+ }
176
+ function trim(url) {
177
+ return url.replace(/\/+$/, '');
178
+ }
179
+ function code(err) {
180
+ if (typeof err !== 'object' || err === null || !('code' in err))
181
+ return undefined;
182
+ const value = err.code;
183
+ return typeof value === 'string' ? value : undefined;
184
+ }
@@ -0,0 +1,268 @@
1
+ // OpenCode config ownership picks the active config file and edits its provider state.
2
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { applyEdits, modify, parse } from 'jsonc-parser';
6
+ import { provider as registryProvider } from '../providers.js';
7
+ export async function patchOpenCodeConfig(auth, path, provider = 'openai') {
8
+ const target = path ?? (await resolveOpenCodePath());
9
+ const existing = await readJson(target);
10
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
11
+ if (jsoncPath(target)) {
12
+ const raw = await readRaw(target);
13
+ await writeFile(target, patchJsonc(raw, existing, auth, provider), { mode: 0o600 });
14
+ return target;
15
+ }
16
+ const config = patchConfig(existing, auth, provider);
17
+ await writeFile(target, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
18
+ return target;
19
+ }
20
+ export async function unpatchOpenCodeConfig(path) {
21
+ const target = path ?? (await resolveOpenCodePath());
22
+ const existing = await readExistingJson(target);
23
+ if (!existing)
24
+ return null;
25
+ const provider = object(existing.provider);
26
+ const next = unpatchProvider(unpatchOpenCodeZen(provider), 'openai');
27
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
28
+ if (jsoncPath(target)) {
29
+ const raw = await readRaw(target);
30
+ await writeFile(target, unpatchJsonc(raw, provider, next), { mode: 0o600 });
31
+ return target;
32
+ }
33
+ await writeFile(target, `${JSON.stringify({ ...existing, provider: next }, null, 2)}\n`, { mode: 0o600 });
34
+ return target;
35
+ }
36
+ export function openCodePath(env = process.env, platform = process.platform) {
37
+ return join(openCodeRoot(env, platform), 'opencode.json');
38
+ }
39
+ export function openCodeJsoncPath(env = process.env, platform = process.platform) {
40
+ return join(openCodeRoot(env, platform), 'opencode.jsonc');
41
+ }
42
+ export function openCodeAuthPath(env = process.env, platform = process.platform) {
43
+ const root = platform === 'win32'
44
+ ? join(homedir(), '.local', 'share')
45
+ : env.XDG_DATA_HOME || join(homedir(), '.local', 'share');
46
+ return join(root, 'opencode', 'auth.json');
47
+ }
48
+ export async function resolveOpenCodePath(env = process.env, platform = process.platform) {
49
+ const jsonc = openCodeJsoncPath(env, platform);
50
+ const json = openCodePath(env, platform);
51
+ const [jsoncConfig, jsonConfig] = await Promise.all([readExistingJson(jsonc), readExistingJson(json)]);
52
+ if (hasProvider(jsoncConfig))
53
+ return jsonc;
54
+ if (hasProvider(jsonConfig))
55
+ return json;
56
+ if (await exists(jsonc))
57
+ return jsonc;
58
+ return json;
59
+ }
60
+ function openCodeRoot(env = process.env, platform = process.platform) {
61
+ const root = platform === 'win32'
62
+ ? join(homedir(), '.config')
63
+ : env.XDG_CONFIG_HOME || join(homedir(), '.config');
64
+ return join(root, 'opencode');
65
+ }
66
+ function patchConfig(input, auth, id) {
67
+ const provider = object(input.provider);
68
+ const base = trim(auth.proxyUrl);
69
+ const next = id === 'opencode' ? patchOpenCodeZen(provider, auth, base) : patchProvider(provider, auth, base, id);
70
+ return {
71
+ ...input,
72
+ provider: next,
73
+ };
74
+ }
75
+ function patchProvider(provider, auth, base, id) {
76
+ const existing = object(provider[id]);
77
+ const options = object(existing.options);
78
+ const headers = object(options.headers);
79
+ return {
80
+ ...provider,
81
+ [id]: {
82
+ ...existing,
83
+ options: {
84
+ ...options,
85
+ baseURL: base,
86
+ headers: {
87
+ ...headers,
88
+ ...sleeveHeaders(auth, id === 'openai' ? 'codex' : id),
89
+ },
90
+ },
91
+ },
92
+ };
93
+ }
94
+ function unpatchProvider(provider, id) {
95
+ const existing = object(provider[id]);
96
+ const options = object(existing.options);
97
+ const headers = object(options.headers);
98
+ if (headers['sleeve-harness'] !== 'opencode')
99
+ return provider;
100
+ const { ['sleeve-token']: _token, ['sleeve-provider']: _provider, ['sleeve-harness']: _harness, ...rest } = headers;
101
+ const { baseURL: _base, ...other } = options;
102
+ return {
103
+ ...provider,
104
+ [id]: {
105
+ ...existing,
106
+ options: {
107
+ ...other,
108
+ headers: rest,
109
+ },
110
+ },
111
+ };
112
+ }
113
+ function patchOpenCodeZen(provider, auth, base) {
114
+ const existing = object(provider.opencode);
115
+ const models = object(existing.models);
116
+ const registry = registryProvider('opencode');
117
+ const patched = Object.fromEntries((registry.models ?? []).map((id) => {
118
+ const model = object(models[id]);
119
+ const source = object(model.provider);
120
+ const headers = object(model.headers);
121
+ return [
122
+ id,
123
+ {
124
+ ...model,
125
+ provider: {
126
+ ...source,
127
+ api: base,
128
+ },
129
+ headers: {
130
+ ...headers,
131
+ ...sleeveHeaders(auth, 'opencode'),
132
+ },
133
+ },
134
+ ];
135
+ }));
136
+ return {
137
+ ...provider,
138
+ opencode: {
139
+ ...existing,
140
+ models: {
141
+ ...models,
142
+ ...patched,
143
+ },
144
+ },
145
+ };
146
+ }
147
+ function unpatchOpenCodeZen(provider) {
148
+ const existing = object(provider.opencode);
149
+ const models = object(existing.models);
150
+ const patched = Object.fromEntries(Object.entries(models).map(([id, value]) => {
151
+ const model = object(value);
152
+ const headers = object(model.headers);
153
+ if (headers['sleeve-harness'] !== 'opencode')
154
+ return [id, model];
155
+ const { ['sleeve-token']: _token, ['sleeve-provider']: _provider, ['sleeve-harness']: _harness, ...rest } = headers;
156
+ const source = object(model.provider);
157
+ const { api: _api, ...provider } = source;
158
+ return [
159
+ id,
160
+ {
161
+ ...model,
162
+ provider,
163
+ headers: rest,
164
+ },
165
+ ];
166
+ }));
167
+ return {
168
+ ...provider,
169
+ opencode: {
170
+ ...existing,
171
+ models: patched,
172
+ },
173
+ };
174
+ }
175
+ function sleeveHeaders(auth, provider) {
176
+ return {
177
+ 'sleeve-token': auth.token,
178
+ 'sleeve-provider': provider,
179
+ 'sleeve-harness': 'opencode',
180
+ };
181
+ }
182
+ async function readJson(path) {
183
+ try {
184
+ const raw = await readFile(path, 'utf8');
185
+ const parsed = jsoncPath(path) ? parse(raw, [], { allowTrailingComma: true }) : JSON.parse(raw);
186
+ return object(parsed);
187
+ }
188
+ catch (err) {
189
+ if (code(err) === 'ENOENT')
190
+ return {};
191
+ throw err;
192
+ }
193
+ }
194
+ async function readExistingJson(path) {
195
+ try {
196
+ const raw = await readFile(path, 'utf8');
197
+ const parsed = jsoncPath(path) ? parse(raw, [], { allowTrailingComma: true }) : JSON.parse(raw);
198
+ return object(parsed);
199
+ }
200
+ catch (err) {
201
+ if (code(err) === 'ENOENT')
202
+ return null;
203
+ throw err;
204
+ }
205
+ }
206
+ async function readRaw(path) {
207
+ try {
208
+ return await readFile(path, 'utf8');
209
+ }
210
+ catch (err) {
211
+ if (code(err) === 'ENOENT')
212
+ return '{}\n';
213
+ throw err;
214
+ }
215
+ }
216
+ async function exists(path) {
217
+ try {
218
+ await access(path);
219
+ return true;
220
+ }
221
+ catch (err) {
222
+ if (code(err) === 'ENOENT')
223
+ return false;
224
+ throw err;
225
+ }
226
+ }
227
+ function hasProvider(config) {
228
+ if (!config)
229
+ return false;
230
+ return 'provider' in config && typeof config.provider === 'object' && config.provider !== null && !Array.isArray(config.provider);
231
+ }
232
+ function patchJsonc(raw, existing, auth, id) {
233
+ const provider = object(existing.provider);
234
+ const base = trim(auth.proxyUrl);
235
+ const next = id === 'opencode' ? patchOpenCodeZen(provider, auth, base) : patchProvider(provider, auth, base, id);
236
+ const key = id === 'opencode' ? 'opencode' : id;
237
+ const value = next[key];
238
+ return editJsonc(raw, ['provider', key], value);
239
+ }
240
+ function unpatchJsonc(raw, provider, next) {
241
+ const openai = object(next.openai);
242
+ const opencode = object(next.opencode);
243
+ const target = 'openai' in provider ? editJsonc(raw, ['provider', 'openai'], 'openai' in next ? openai : undefined) : raw;
244
+ return 'opencode' in provider ? editJsonc(target, ['provider', 'opencode'], 'opencode' in next ? opencode : undefined) : target;
245
+ }
246
+ function editJsonc(raw, path, value) {
247
+ const edits = modify(raw, path, value, {
248
+ formattingOptions: { insertSpaces: true, tabSize: 2, keepLines: true, insertFinalNewline: true },
249
+ });
250
+ return applyEdits(raw, edits);
251
+ }
252
+ function jsoncPath(path) {
253
+ return path.endsWith('.jsonc');
254
+ }
255
+ function object(value) {
256
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
257
+ return {};
258
+ return value;
259
+ }
260
+ function trim(url) {
261
+ return url.replace(/\/+$/, '');
262
+ }
263
+ function code(err) {
264
+ if (typeof err !== 'object' || err === null || !('code' in err))
265
+ return undefined;
266
+ const value = err.code;
267
+ return typeof value === 'string' ? value : undefined;
268
+ }