subconscious-cli 6.3.1 → 7.0.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/README.md +5 -2
- package/bin/agent-launch.js +1 -25
- package/bin/agents.js +2 -19
- package/bin/auth.js +31 -7
- package/bin/claude-config.js +13 -629
- package/bin/cli.js +42 -0
- package/bin/feedback.js +2 -1
- package/bin/models.js +4 -2
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/network.js +710 -0
- package/bin/profiles.js +7 -1
- package/bin/runbook/claude-code/agent.json +6 -27
- package/bin/runbook/claude-code/install.sh +3 -2
- package/bin/runbook/shared.json +10 -0
- package/bin/update-check.js +2 -1
- package/bin/upgrade.js +2 -1
- package/bin/usage.js +19 -3
- package/bin/windows/launch.js +5 -17
- package/package.json +1 -1
- package/bin/claude-config-drift.js +0 -58
- package/bin/claude-config-plugins.js +0 -103
package/bin/profiles.js
CHANGED
|
@@ -12,9 +12,11 @@ import os from 'node:os';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { c } from './colors.js';
|
|
14
14
|
import { PUBLIC_CATALOG_FALLBACK_MESSAGE } from './models.js';
|
|
15
|
+
import { describeFailure } from './network.js';
|
|
15
16
|
import {
|
|
16
17
|
profileTemplate,
|
|
17
18
|
RUNBOOK_DEFAULTS,
|
|
19
|
+
resolvedProfileValues,
|
|
18
20
|
SUPPORTED_MODELS,
|
|
19
21
|
} from './profile-settings.js';
|
|
20
22
|
|
|
@@ -26,6 +28,10 @@ export {
|
|
|
26
28
|
SUPPORTED_MODELS,
|
|
27
29
|
} from './profile-settings.js';
|
|
28
30
|
|
|
31
|
+
export function systemCaEnabledFor(profile) {
|
|
32
|
+
return String(resolvedProfileValues(profile).SYSTEM_CA ?? '').trim() === 'on';
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
import { runWindows, windowsEditor } from './windows/process.js';
|
|
30
36
|
|
|
31
37
|
export const DEFAULT_PROFILE = 'default';
|
|
@@ -572,7 +578,7 @@ export function modelsCommand(models = SUPPORTED_MODELS, options = {}) {
|
|
|
572
578
|
console.error(
|
|
573
579
|
`\n ${c.yellow}Could not fetch the live model catalog; showing packaged defaults.${c.reset}`,
|
|
574
580
|
);
|
|
575
|
-
console.error(` ${c.dim}${options.error
|
|
581
|
+
console.error(` ${c.dim}${describeFailure(options.error)}${c.reset}`);
|
|
576
582
|
}
|
|
577
583
|
console.log();
|
|
578
584
|
}
|
|
@@ -28,11 +28,14 @@
|
|
|
28
28
|
},
|
|
29
29
|
"help": {
|
|
30
30
|
"usage": "subc [-p NAME] claude [help|status|uninstall] [Claude arguments...]",
|
|
31
|
-
"behavior": "Launches Claude Code with the active Subconscious profile and model picker
|
|
31
|
+
"behavior": "Launches Claude Code with the active Subconscious profile and model picker, using your own Claude config (~/.claude, or your CLAUDE_CONFIG_DIR) unchanged - plans, sessions, memory, skills and settings stay where plain claude keeps them. Subconscious routing, credentials and model pinning ride the launch env and --settings, which persist nothing. A /model pick inside a subc session changes your Claude default, as it does in plain claude. Persistent files are leftover-only: subc claude uninstall.",
|
|
32
32
|
"options": [
|
|
33
33
|
["help", "Show this help"],
|
|
34
|
-
["status", "Inspect
|
|
35
|
-
[
|
|
34
|
+
["status", "Inspect leftovers: ~/.claude/subconscious-gateway.env"],
|
|
35
|
+
[
|
|
36
|
+
"uninstall",
|
|
37
|
+
"Remove leftover ~/.claude/subconscious-gateway.env from older persistent setup"
|
|
38
|
+
],
|
|
36
39
|
["--model MODEL", "Override the profile model for this launch"]
|
|
37
40
|
],
|
|
38
41
|
"args": ["-- ARGS...", "Pass remaining arguments to Claude Code"]
|
|
@@ -113,18 +116,6 @@
|
|
|
113
116
|
"type": "integer",
|
|
114
117
|
"min": 1
|
|
115
118
|
},
|
|
116
|
-
{
|
|
117
|
-
"name": "CLAUDE_CODE_ISOLATE",
|
|
118
|
-
"flag": "--no-isolate",
|
|
119
|
-
"flag_value": "off",
|
|
120
|
-
"default": "on",
|
|
121
|
-
"strict": true,
|
|
122
|
-
"choices": ["on", "off"],
|
|
123
|
-
"help": "Use ~/.claude directly, so a /model pick in subc also changes plain Claude Code",
|
|
124
|
-
"label": "Isolated Claude config",
|
|
125
|
-
"description": "When on, subc claude sessions use ~/.subconscious/claude-code as the Claude config dir. It links back to ~/.claude for CLAUDE.md, skills, hooks and session history, copies the plugin index, rebuilds settings.json from ~/.claude/settings.json on each launch, and keeps only the subc model pick apart.",
|
|
126
|
-
"comment": "CLAUDE_CODE_ISOLATE=off shares ~/.claude with plain Claude Code."
|
|
127
|
-
},
|
|
128
119
|
{ "name": "MAX_CONCURRENT_SUBAGENTS" },
|
|
129
120
|
{
|
|
130
121
|
"name": "MAX_SUBAGENT_SPAWN_DEPTH",
|
|
@@ -159,12 +150,6 @@
|
|
|
159
150
|
"override": ["ANTHROPIC_SMALL_FAST_MODEL"],
|
|
160
151
|
"description": "Small/fast background model."
|
|
161
152
|
},
|
|
162
|
-
{
|
|
163
|
-
"name": "CLAUDE_CONFIG_DIR",
|
|
164
|
-
"value": "{claudeConfigDir}",
|
|
165
|
-
"optional": true,
|
|
166
|
-
"description": "Claude Code config dir for subc sessions, prepared before each launch to share the user's own Claude config (~/.claude, or a CLAUDE_CONFIG_DIR they set, which becomes the source rather than the launch dir). When CLAUDE_CODE_ISOLATE=off, the user's own CLAUDE_CONFIG_DIR is kept, or none is set, so sessions use it directly."
|
|
167
|
-
},
|
|
168
153
|
{
|
|
169
154
|
"name": "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
|
|
170
155
|
"value": "0",
|
|
@@ -251,12 +236,6 @@
|
|
|
251
236
|
},
|
|
252
237
|
"description": "Replaces the built-in model picker with the live catalog and allowlists only those models."
|
|
253
238
|
},
|
|
254
|
-
{
|
|
255
|
-
"kind": "file",
|
|
256
|
-
"path": "~/.subconscious/claude-code",
|
|
257
|
-
"lifetime": "persistent",
|
|
258
|
-
"description": "Claude Code config dir for subc sessions (~/.subconscious/claude-code, honoring $SUBC_CONFIG_DIR), set via CLAUDE_CONFIG_DIR. Each launch links CLAUDE.md, history.jsonl and keybindings.json (when present) and the rules, agents, commands, skills, output-styles, hooks, projects, file-history, todos, plans and ide folders (created in ~/.claude when missing) back to ~/.claude, copies the plugin index files (plugin changes made inside subc stay in subc and reset on the next launch), rebuilds settings.json from ~/.claude/settings.json (keeping the subc model pick, dropping apiKeyHelper and env names subc sets), and copies MCP servers, trust and onboarding state from ~/.claude.json (never login state). Settings or MCP servers changed inside a subc session are replaced on the next launch; that launch names them and saves them in settings.discarded.json or mcp.discarded.json in this dir. subc never deletes it and never writes ~/.claude settings; it refuses to launch when this dir resolves to your own config. To remove it, delete the links first: rm -r with a trailing slash, or PowerShell Remove-Item -Recurse, follows them into ~/.claude. Opt out with CLAUDE_CODE_ISOLATE=off or subc claude --no-isolate."
|
|
259
|
-
},
|
|
260
239
|
{
|
|
261
240
|
"kind": "file",
|
|
262
241
|
"path": "~/.claude/subconscious-gateway.env",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ── Subconscious API Gateway — Claude Code leftovers ──────────────────────────
|
|
3
3
|
# Claude Code is launch-only: `subc claude` passes everything as env and flags
|
|
4
|
-
# and writes nothing.
|
|
5
|
-
# ~/.claude/subconscious-gateway.env
|
|
4
|
+
# and writes nothing persistent. One leftover remains from older releases:
|
|
5
|
+
# the ~/.claude/subconscious-gateway.env file from the first persistent setup.
|
|
6
|
+
# This script reports and removes it.
|
|
6
7
|
#
|
|
7
8
|
# subc claude status
|
|
8
9
|
# subc claude uninstall
|
package/bin/runbook/shared.json
CHANGED
|
@@ -51,6 +51,16 @@
|
|
|
51
51
|
"description": "Maximum concurrent Claude/Codex subagent threads.",
|
|
52
52
|
"type": "integer",
|
|
53
53
|
"min": 1
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "SYSTEM_CA",
|
|
57
|
+
"label": "Trust the OS certificate store",
|
|
58
|
+
"description": "Trust certificates from the operating system store, for a TLS-inspecting corporate proxy. Off by default.",
|
|
59
|
+
"type": "choice",
|
|
60
|
+
"strict": true,
|
|
61
|
+
"choices": ["off", "on"],
|
|
62
|
+
"default": "off",
|
|
63
|
+
"template": false
|
|
54
64
|
}
|
|
55
65
|
]
|
|
56
66
|
}
|
package/bin/update-check.js
CHANGED
|
@@ -6,6 +6,7 @@ import readline from 'node:readline';
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { LOGO_ART_SMALL_LINES } from './branding.js';
|
|
8
8
|
import { c, colorEnabled } from './colors.js';
|
|
9
|
+
import { proxyAwareFetch } from './network.js';
|
|
9
10
|
import { spawnWindows } from './windows/process.js';
|
|
10
11
|
|
|
11
12
|
export const PACKAGE_NAME = 'subconscious-cli';
|
|
@@ -162,7 +163,7 @@ export async function currentVersion() {
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
export async function fetchLatestVersion(options = {}) {
|
|
165
|
-
const fetchImpl = options.fetchImpl ||
|
|
166
|
+
const fetchImpl = options.fetchImpl || proxyAwareFetch;
|
|
166
167
|
const timeoutMs = options.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS;
|
|
167
168
|
const signal = options.signal;
|
|
168
169
|
if (typeof fetchImpl !== 'function') throw new Error('fetch is unavailable');
|
package/bin/upgrade.js
CHANGED
|
@@ -10,6 +10,7 @@ import fs from 'node:fs/promises';
|
|
|
10
10
|
import readline from 'node:readline';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { c } from './colors.js';
|
|
13
|
+
import { proxyAwareFetch } from './network.js';
|
|
13
14
|
import {
|
|
14
15
|
compareVersions,
|
|
15
16
|
detectInstallTarget,
|
|
@@ -42,7 +43,7 @@ export async function currentCliVersion() {
|
|
|
42
43
|
return pkg.version;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
export async function fetchLatestVersion(fetchImpl =
|
|
46
|
+
export async function fetchLatestVersion(fetchImpl = proxyAwareFetch) {
|
|
46
47
|
const res = await fetchImpl(
|
|
47
48
|
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
48
49
|
{
|
package/bin/usage.js
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import { getApiKey, getPlatformUrl } from './auth.js';
|
|
6
6
|
import { c } from './colors.js';
|
|
7
|
+
import {
|
|
8
|
+
describeFetchError,
|
|
9
|
+
proxyAwareFetch,
|
|
10
|
+
proxyHint,
|
|
11
|
+
tlsTrustHint,
|
|
12
|
+
} from './network.js';
|
|
7
13
|
import { DEFAULT_PROFILE } from './profiles.js';
|
|
8
14
|
import { printLoginUpgradeWarning } from './upgrade.js';
|
|
9
15
|
|
|
@@ -181,7 +187,11 @@ export function formatUsageDisplay(data) {
|
|
|
181
187
|
return lines.join('\n');
|
|
182
188
|
}
|
|
183
189
|
|
|
184
|
-
export async function fetchUsage(
|
|
190
|
+
export async function fetchUsage(
|
|
191
|
+
apiKey,
|
|
192
|
+
platformUrl,
|
|
193
|
+
fetchImpl = proxyAwareFetch,
|
|
194
|
+
) {
|
|
185
195
|
const res = await fetchImpl(
|
|
186
196
|
`${platformUrl.replace(/\/$/, '')}${USAGE_API_PATH}`,
|
|
187
197
|
{
|
|
@@ -260,10 +270,16 @@ export async function usageCommand(argv = [], options = {}) {
|
|
|
260
270
|
console.log(
|
|
261
271
|
`\n ${c.yellow}Could not reach the platform (timed out).${c.reset}`,
|
|
262
272
|
);
|
|
263
|
-
console.log(` ${c.dim}Host: ${platformUrl}${c.reset}
|
|
273
|
+
console.log(` ${c.dim}Host: ${platformUrl}${c.reset}`);
|
|
274
|
+
const proxy = proxyHint(error, platformUrl);
|
|
275
|
+
if (proxy) console.log(` ${c.dim}${proxy}${c.reset}`);
|
|
276
|
+
console.log();
|
|
264
277
|
return;
|
|
265
278
|
}
|
|
266
|
-
console.error(`\n ${c.red}${error
|
|
279
|
+
console.error(`\n ${c.red}${describeFetchError(error)}${c.reset}`);
|
|
280
|
+
const hint = tlsTrustHint(error) || proxyHint(error, platformUrl);
|
|
281
|
+
if (hint) console.error(` ${c.yellow}${hint}${c.reset}`);
|
|
282
|
+
console.error();
|
|
267
283
|
process.exitCode = 1;
|
|
268
284
|
}
|
|
269
285
|
}
|
package/bin/windows/launch.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
resolveInputs,
|
|
10
10
|
takeFlags,
|
|
11
11
|
} from '../agent-launch.js';
|
|
12
|
-
import { prepareClaudeLaunch } from '../claude-config.js';
|
|
13
12
|
import { claudePickerSettings } from '../claude-picker.js';
|
|
14
13
|
import { modelIds, positiveInteger } from '../launch-values.js';
|
|
15
14
|
import { modelSupportsVision } from '../model-capabilities.js';
|
|
@@ -62,13 +61,7 @@ function plannedLaunch(agent, argv, env) {
|
|
|
62
61
|
return { command: plan.argv[0], args: plan.argv.slice(1), env: plan.env };
|
|
63
62
|
}
|
|
64
63
|
const plan = claudeLaunch(agent, argv, env);
|
|
65
|
-
return {
|
|
66
|
-
command: plan.argv[0],
|
|
67
|
-
args: plan.argv.slice(1),
|
|
68
|
-
env: plan.env,
|
|
69
|
-
// Runs only when the launch executes, so planning never touches disk.
|
|
70
|
-
prepare: (options) => prepareClaudeLaunch(agent, plan.env, env, options),
|
|
71
|
-
};
|
|
64
|
+
return { command: plan.argv[0], args: plan.argv.slice(1), env: plan.env };
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
function codexCatalog(env, context, maxContext, compact) {
|
|
@@ -274,17 +267,12 @@ export async function windowsLaunch(
|
|
|
274
267
|
throw new Error(`No native Windows launcher is available for ${id}`);
|
|
275
268
|
}
|
|
276
269
|
|
|
277
|
-
export async function executeWindowsLaunch(spec, run
|
|
270
|
+
export async function executeWindowsLaunch(spec, run) {
|
|
278
271
|
try {
|
|
279
|
-
const prepared = spec.prepare?.(prepareOptions);
|
|
280
|
-
for (const warning of prepared?.warnings ?? []) {
|
|
281
|
-
console.error(` ${warning}`);
|
|
282
|
-
}
|
|
283
|
-
// Claude Code would write into the user's own config there.
|
|
284
|
-
if (prepared?.refused) return 1;
|
|
285
|
-
const env = { ...spec.env, ...prepared?.env };
|
|
286
272
|
const options =
|
|
287
|
-
spec.input === undefined
|
|
273
|
+
spec.input === undefined
|
|
274
|
+
? { env: spec.env }
|
|
275
|
+
: { env: spec.env, input: spec.input };
|
|
288
276
|
return await run(spec.command, spec.args, options);
|
|
289
277
|
} finally {
|
|
290
278
|
await spec.cleanup?.();
|
package/package.json
CHANGED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
-
|
|
3
|
-
// Each launch rebuilds the subc copies of settings.json and the MCP servers
|
|
4
|
-
// in .claude.json from the user's own files, so anything a subc session
|
|
5
|
-
// changed in those copies is replaced. These helpers find what a session
|
|
6
|
-
// changed, by comparing the copies with what the last launch wrote, so the
|
|
7
|
-
// launch can save it aside and name it instead of losing it silently.
|
|
8
|
-
|
|
9
|
-
const isObject = (value) =>
|
|
10
|
-
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Keys of `current` that a session added or changed since `written`.
|
|
14
|
-
* Removed keys are ignored: Claude Code drops keys on its own.
|
|
15
|
-
*
|
|
16
|
-
* @param {Record<string, unknown>} written what the last launch wrote
|
|
17
|
-
* @param {Record<string, unknown>} current what the copy holds now
|
|
18
|
-
* @param {string[]} [ignored] keys subc keeps across launches
|
|
19
|
-
* @returns {string[]}
|
|
20
|
-
*/
|
|
21
|
-
export function changedKeys(written, current, ignored = []) {
|
|
22
|
-
if (!isObject(written) || !isObject(current)) return [];
|
|
23
|
-
return Object.keys(current).filter(
|
|
24
|
-
(key) =>
|
|
25
|
-
!ignored.includes(key) && !isDeepStrictEqual(current[key], written[key]),
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The MCP servers of a .claude.json: user scope and each project. */
|
|
30
|
-
export function mcpServersOf(state) {
|
|
31
|
-
const projects = isObject(state?.projects) ? state.projects : {};
|
|
32
|
-
return {
|
|
33
|
-
mcpServers: isObject(state?.mcpServers) ? state.mcpServers : {},
|
|
34
|
-
projects: Object.fromEntries(
|
|
35
|
-
Object.entries(projects)
|
|
36
|
-
.filter(([, project]) => isObject(project?.mcpServers))
|
|
37
|
-
.map(([name, project]) => [name, project.mcpServers]),
|
|
38
|
-
),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* MCP server names a session added or changed since `written`, as
|
|
44
|
-
* `name` for user scope and `name (project)` for a project.
|
|
45
|
-
*
|
|
46
|
-
* @param {ReturnType<typeof mcpServersOf>} written
|
|
47
|
-
* @param {ReturnType<typeof mcpServersOf>} current
|
|
48
|
-
* @returns {string[]}
|
|
49
|
-
*/
|
|
50
|
-
export function changedMcpServers(written, current) {
|
|
51
|
-
const changed = changedKeys(written.mcpServers, current.mcpServers);
|
|
52
|
-
for (const [project, servers] of Object.entries(current.projects)) {
|
|
53
|
-
for (const name of changedKeys(written.projects[project] ?? {}, servers)) {
|
|
54
|
-
changed.push(`${name} (${project})`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return changed;
|
|
58
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
// Claude Code finds an enabled plugin that installed_plugins.json does not
|
|
6
|
-
// list (a marketplace plugin pinned to a git SHA, for example) by its place
|
|
7
|
-
// in its own cache: <config dir>/plugins/cache/<marketplace>/<plugin>/<version>.
|
|
8
|
-
// The subc config dir never links that cache, so such a plugin, and the MCP
|
|
9
|
-
// servers it ships, would not load. Each missing version is copied once. A
|
|
10
|
-
// copy, unlike a link, cannot carry a cache sweep or orphan marker made in a
|
|
11
|
-
// subc session back into ~/.claude.
|
|
12
|
-
|
|
13
|
-
// A marketplace or plugin name used as one path segment.
|
|
14
|
-
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
15
|
-
|
|
16
|
-
/** `[plugin, marketplace]` of an enabledPlugins key, or `undefined`. */
|
|
17
|
-
function parsePluginId(id) {
|
|
18
|
-
const at = id.lastIndexOf('@');
|
|
19
|
-
if (at <= 0) return undefined;
|
|
20
|
-
const plugin = id.slice(0, at);
|
|
21
|
-
const marketplace = id.slice(at + 1);
|
|
22
|
-
return SAFE_NAME.test(plugin) && SAFE_NAME.test(marketplace)
|
|
23
|
-
? [plugin, marketplace]
|
|
24
|
-
: undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function copyVersion(from, to) {
|
|
28
|
-
// A copy is renamed into place only when complete, so a launch stopped
|
|
29
|
-
// half way never leaves a partial plugin that later launches skip.
|
|
30
|
-
const temp = `${to}.${crypto.randomBytes(6).toString('hex')}.tmp`;
|
|
31
|
-
try {
|
|
32
|
-
fs.cpSync(from, temp, { recursive: true, verbatimSymlinks: true });
|
|
33
|
-
fs.renameSync(temp, to);
|
|
34
|
-
} finally {
|
|
35
|
-
fs.rmSync(temp, { recursive: true, force: true });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Whether `base` or any folder from it down through `parts` is a link. */
|
|
40
|
-
function linkOnPath(base, parts) {
|
|
41
|
-
let current = base;
|
|
42
|
-
for (const part of ['', ...parts]) {
|
|
43
|
-
current = part ? path.join(current, part) : current;
|
|
44
|
-
try {
|
|
45
|
-
if (fs.lstatSync(current).isSymbolicLink()) return true;
|
|
46
|
-
} catch {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function directories(dir) {
|
|
54
|
-
try {
|
|
55
|
-
return fs
|
|
56
|
-
.readdirSync(dir, { withFileTypes: true })
|
|
57
|
-
.filter((entry) => entry.isDirectory() && SAFE_NAME.test(entry.name))
|
|
58
|
-
.map((entry) => entry.name);
|
|
59
|
-
} catch {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Copy into `dir`'s plugin cache every cached version of each enabled plugin
|
|
66
|
-
* that the plugin index does not list, unless a copy is already there.
|
|
67
|
-
*
|
|
68
|
-
* @param {{ dir: string, source: string, enabledPlugins: unknown,
|
|
69
|
-
* indexed: unknown, warnings: string[] }} options
|
|
70
|
-
*/
|
|
71
|
-
export function copyUnindexedPlugins({
|
|
72
|
-
dir,
|
|
73
|
-
source,
|
|
74
|
-
enabledPlugins,
|
|
75
|
-
indexed,
|
|
76
|
-
warnings,
|
|
77
|
-
}) {
|
|
78
|
-
if (!enabledPlugins || typeof enabledPlugins !== 'object') return;
|
|
79
|
-
const listed =
|
|
80
|
-
indexed && typeof indexed === 'object' ? Object.keys(indexed) : [];
|
|
81
|
-
for (const [id, enabled] of Object.entries(enabledPlugins)) {
|
|
82
|
-
if (enabled !== true || listed.includes(id)) continue;
|
|
83
|
-
const parsed = parsePluginId(id);
|
|
84
|
-
if (!parsed) continue;
|
|
85
|
-
const [plugin, marketplace] = parsed;
|
|
86
|
-
const from = path.join(source, 'plugins', 'cache', marketplace, plugin);
|
|
87
|
-
const to = path.join(dir, 'plugins', 'cache', marketplace, plugin);
|
|
88
|
-
// A link here could carry the copy into the user's own cache.
|
|
89
|
-
if (linkOnPath(path.join(dir, 'plugins'), ['cache', marketplace, plugin])) {
|
|
90
|
-
warnings.push(`Skipped plugin ${id} for subc: its cache path is a link.`);
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
for (const version of directories(from)) {
|
|
94
|
-
if (fs.existsSync(path.join(to, version))) continue;
|
|
95
|
-
try {
|
|
96
|
-
fs.mkdirSync(to, { recursive: true });
|
|
97
|
-
copyVersion(path.join(from, version), path.join(to, version));
|
|
98
|
-
} catch (error) {
|
|
99
|
-
warnings.push(`Could not copy plugin ${id} for subc: ${error.message}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|