synabun 2.0.5 → 2.0.7
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/CHANGELOG.md +40 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +13 -0
- package/docs/whatsapp.md +151 -16
- package/hooks/claude-code/shared.mjs +6 -3
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/packaged-runtime.js +138 -11
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -7
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +34 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- package/mcp-server/dist/tui.js +2 -1
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +33 -14
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tui.ts +2 -1
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +627 -57
- package/neural-interface/i18n/pt-BR.json +626 -56
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/assistant-api.js +255 -52
- package/neural-interface/lib/assistant-brains/claude.js +15 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-config.js +62 -14
- package/neural-interface/lib/assistant-dispatch.js +432 -125
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-persona.js +68 -11
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +307 -47
- package/neural-interface/lib/assistant-user-writer.js +4 -2
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +90 -3
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +3 -3
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -7
- package/neural-interface/lib/codex-account-auth.js +178 -0
- package/neural-interface/lib/codex-accounts.js +489 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +120 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/git-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-read.js +34 -2
- package/neural-interface/lib/install-details.js +145 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/native-loop-providers.js +16 -3
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/session-title-generator.js +4 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +2 -2
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/offline.html +16 -1
- package/neural-interface/public/onboarding.html +96 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +539 -0
- package/neural-interface/public/shared/account-usage.js +19 -0
- package/neural-interface/public/shared/account-view.js +335 -0
- package/neural-interface/public/shared/agent-runtime-options.js +28 -8
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +32 -2
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +124 -25
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
- package/neural-interface/public/shared/assistant/asst-menu.js +15 -3
- package/neural-interface/public/shared/assistant/asst-panel.js +48 -14
- package/neural-interface/public/shared/assistant/asst-render.js +38 -7
- package/neural-interface/public/shared/assistant/asst-route.js +72 -14
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +1 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +99 -17
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +48 -107
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +128 -107
- package/neural-interface/public/shared/cdx/cdx-tabs.js +261 -664
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +46 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -0
- package/neural-interface/public/shared/editor/editor-panel.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +1 -1
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/html-shell.js +1 -0
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +32 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +118 -0
- package/neural-interface/public/shared/pwa-install-page.js +3 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -0
- package/neural-interface/public/shared/settings/settings-ia.js +15 -0
- package/neural-interface/public/shared/showcase-privacy.js +489 -0
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +278 -31
- package/neural-interface/public/shared/synabun-mascot.js +48 -2
- package/neural-interface/public/shared/ui-claude-panel.js +212 -213
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-loading.js +30 -6
- package/neural-interface/public/shared/ui-menubar.js +13 -3
- package/neural-interface/public/shared/ui-navbar.js +3 -1
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +55 -3
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +6 -0
- package/neural-interface/public/shared/ui-terminal.js +17 -0
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-update.js +594 -5
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +2 -0
- package/neural-interface/public/sw.js +1 -1
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/server.js +1219 -608
- package/package.json +4 -1
- package/setup.js +9 -8
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const cache = new Map();
|
|
7
|
+
const featureCache = new Map();
|
|
8
|
+
export const CODEX_SETTINGS_KEYS = Object.freeze([
|
|
9
|
+
'model', 'review_model', 'model_provider', 'approval_policy', 'sandbox_mode', 'personality',
|
|
10
|
+
'service_tier', 'model_reasoning_effort', 'model_reasoning_summary', 'model_verbosity',
|
|
11
|
+
'model_context_window', 'model_auto_compact_token_limit', 'web_search',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export function schemaEnum(schema, definitions) {
|
|
15
|
+
if (!schema) return [];
|
|
16
|
+
if (schema.$ref) return schemaEnum(definitions[schema.$ref.split('/').pop()], definitions);
|
|
17
|
+
return [...new Set([...(schema.enum || []).filter(v => typeof v === 'string'),
|
|
18
|
+
...['anyOf', 'oneOf', 'allOf'].flatMap(k => (schema[k] || []).flatMap(s => schemaEnum(s, definitions)))])];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function settingsSchemaFromProtocol(configSchema, threadSchema) {
|
|
22
|
+
const properties = configSchema.definitions?.Config?.properties || {};
|
|
23
|
+
const fields = {};
|
|
24
|
+
for (const key of CODEX_SETTINGS_KEYS) {
|
|
25
|
+
const field = properties[key];
|
|
26
|
+
if (!field || /@deprecated/.test(field.description || '')) continue;
|
|
27
|
+
const values = schemaEnum(field, configSchema.definitions || {});
|
|
28
|
+
fields[key] = { values, enumerated: values.length > 0,
|
|
29
|
+
type: key.includes('token_limit') || key === 'model_context_window' ? 'number' : 'select' };
|
|
30
|
+
}
|
|
31
|
+
// Some older runtimes expose personality only on thread/turn params. Newer
|
|
32
|
+
// ones explicitly retain it as a no-op: never present a no-op preference.
|
|
33
|
+
const personality = threadSchema.properties?.personality;
|
|
34
|
+
if (personality && !/@deprecated/.test(personality.description || '')) {
|
|
35
|
+
fields.personality = { type: 'select', values: schemaEnum(personality, threadSchema.definitions || {}) };
|
|
36
|
+
}
|
|
37
|
+
return { fields };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Generate from the executable we actually launch; never use the global home. */
|
|
41
|
+
export function readCodexSettingsSchema(codexBin) {
|
|
42
|
+
const stamp = statSync(codexBin);
|
|
43
|
+
const key = `${codexBin}:${stamp.mtimeMs}:${stamp.size}`;
|
|
44
|
+
if (cache.has(key)) return cache.get(key);
|
|
45
|
+
const home = mkdtempSync(join(tmpdir(), 'synabun-codex-schema-'));
|
|
46
|
+
try {
|
|
47
|
+
const out = join(home, 'schema');
|
|
48
|
+
const run = spawnSync(codexBin, ['app-server', 'generate-json-schema', '--experimental', '--out', out], {
|
|
49
|
+
env: { ...process.env, CODEX_HOME: home }, encoding: 'utf8', timeout: 15000, windowsHide: true,
|
|
50
|
+
});
|
|
51
|
+
if (run.error || run.status !== 0) throw new Error('Codex could not report its settings schema');
|
|
52
|
+
const read = name => JSON.parse(readFileSync(join(out, 'v2', `${name}.json`), 'utf8'));
|
|
53
|
+
const schema = settingsSchemaFromProtocol(read('ConfigReadResponse'), read('ThreadStartParams'));
|
|
54
|
+
// Protocol enums can retain deprecated variants that the config loader
|
|
55
|
+
// already rejects (0.162 still exports `untrusted`). Ask the loader too.
|
|
56
|
+
// `features list` validates config locally and never runs a model or tool.
|
|
57
|
+
for (const [fieldKey, field] of Object.entries(schema.fields)) {
|
|
58
|
+
field.values = field.values.filter(value => {
|
|
59
|
+
const check = spawnSync(codexBin, ['-c', `${fieldKey}=${JSON.stringify(value)}`, 'features', 'list'], {
|
|
60
|
+
env: { ...process.env, CODEX_HOME: home }, encoding: 'utf8', timeout: 5000, windowsHide: true,
|
|
61
|
+
});
|
|
62
|
+
return !check.error && check.status === 0;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
cache.set(key, schema);
|
|
66
|
+
return schema;
|
|
67
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function validateCodexSettings(config, schema, features = []) {
|
|
71
|
+
const result = {};
|
|
72
|
+
for (const [key, value] of Object.entries(config || {})) {
|
|
73
|
+
if (key === 'features') {
|
|
74
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid feature settings');
|
|
75
|
+
Object.assign(result, validateCodexSettings(Object.fromEntries(Object.entries(value).map(([k, v]) => [`features.${k}`, v])), schema, features));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (key.startsWith('features.')) {
|
|
79
|
+
const feature = features.find(f => `features.${f.name}` === key && ['stable', 'beta'].includes(f.stage) && f.configurable !== false);
|
|
80
|
+
if (!feature || typeof value !== 'boolean') throw new Error(`Unsupported Codex feature: ${key}`);
|
|
81
|
+
} else {
|
|
82
|
+
const field = schema.fields?.[key];
|
|
83
|
+
if (!field) throw new Error(`Unsupported Codex setting: ${key}`);
|
|
84
|
+
if (value !== null) {
|
|
85
|
+
if (field.type === 'number') {
|
|
86
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`Invalid token count: ${key}`);
|
|
87
|
+
} else if (typeof value !== 'string' || !value || ((field.enumerated || field.values.length) && !field.values.includes(value))) {
|
|
88
|
+
// Preserve granular approval policies; only the UI's string choices are editable.
|
|
89
|
+
throw new Error(`Invalid Codex setting value: ${key}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
result[key] = value;
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Some stable flags are now mandatory and ignore false (e.g. unified_exec).
|
|
99
|
+
* Check both states with the local loader instead of offering a no-op switch. */
|
|
100
|
+
export function readCodexFeatureSupport(codexBin, features) {
|
|
101
|
+
const names = features.filter(f => ['stable', 'beta'].includes(f.stage) && /^[A-Za-z0-9_-]+$/.test(f.name)).map(f => f.name).sort();
|
|
102
|
+
const stat = statSync(codexBin);
|
|
103
|
+
const key = `${codexBin}:${stat.mtimeMs}:${stat.size}:${names.join(',')}`;
|
|
104
|
+
if (featureCache.has(key)) return featureCache.get(key);
|
|
105
|
+
const home = mkdtempSync(join(tmpdir(), 'synabun-feature-support-'));
|
|
106
|
+
try {
|
|
107
|
+
const states = [false, true].map(value => {
|
|
108
|
+
const check = spawnSync(codexBin, ['-c', `features={${names.map(name => `${name}=${value}`).join(',')}}`, 'features', 'list'], {
|
|
109
|
+
env: { ...process.env, CODEX_HOME: home }, encoding: 'utf8', timeout: 10000, windowsHide: true,
|
|
110
|
+
});
|
|
111
|
+
if (check.error || check.status !== 0) throw new Error('Codex could not validate feature switches');
|
|
112
|
+
return new Map(check.stdout.split('\n').map(line => {
|
|
113
|
+
const parts = line.trim().split(/\s+/); return [parts[0], parts.at(-1)];
|
|
114
|
+
}));
|
|
115
|
+
});
|
|
116
|
+
const supported = Object.fromEntries(names.map(name => [name, states[0].get(name) === 'false' && states[1].get(name) === 'true']));
|
|
117
|
+
featureCache.set(key, supported);
|
|
118
|
+
return supported;
|
|
119
|
+
} finally { rmSync(home, { recursive: true, force: true }); }
|
|
120
|
+
}
|
|
@@ -446,6 +446,9 @@ export const DESKTOP_DEFAULTS = Object.freeze({
|
|
|
446
446
|
bundleIds: ['com.apple.Terminal', 'com.googlecode.iterm2', 'dev.warp.Warp-Stable', 'com.mitchellh.ghostty', 'net.kovidgoyal.kitty', 'com.github.wez.wezterm', 'org.alacritty', 'co.zeit.hyper'],
|
|
447
447
|
reason: 'Terminal emulators are off-limits (your other agents run there); use your own shell tool instead',
|
|
448
448
|
},
|
|
449
|
+
// The SynaBun desktop app (packaging/desktop): every window it has is SynaBun's own interface, as the
|
|
450
|
+
// synabun-ui rule below says of SynaBun in a browser or a web app.
|
|
451
|
+
{ id: 'synabun-app', bundleIds: ['ai.synabun.app'], reason: "SynaBun's own interface" },
|
|
449
452
|
],
|
|
450
453
|
// Arrays replace wholesale (mergeConfig): a user's own protectedWindows (or
|
|
451
454
|
// blockedApps) list drops every default rule below, including the localized
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — bzip2, read
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// Git for Windows publishes its full installation as a .tar.bz2, and Node has
|
|
6
|
+
// no bzip2. A Windows 10 tar.exe may not have one either (older builds stop at
|
|
7
|
+
// "unable to run program bzip2 -d"), so SynaBun reads the file itself, the same
|
|
8
|
+
// way on the build machine and on the user's: this decoder, then the tar
|
|
9
|
+
// reader in install.js.
|
|
10
|
+
//
|
|
11
|
+
// The whole format of bzip2 1.0 (Julian Seward's): one stream or several
|
|
12
|
+
// concatenated, each "BZh1".."BZh9", blocks of Huffman-coded move-to-front
|
|
13
|
+
// values with their runs, the inverse Burrows-Wheeler transform, the final run
|
|
14
|
+
// decoding. Every block's CRC is compared with the one it carries, and every
|
|
15
|
+
// stream's combined CRC with its trailer: a block that decodes to anything else
|
|
16
|
+
// than what was compressed stops the read. A "randomised" block (written only
|
|
17
|
+
// by bzip2 0.9.0, before 1998) is refused rather than guessed at.
|
|
18
|
+
//
|
|
19
|
+
// What it hands out is the decompressed bytes, block by block
|
|
20
|
+
// (bunzip2Blocks: a generator, so a caller can stop at any block, and see how
|
|
21
|
+
// far into the file it got). Every problem is a Bzip2Error.
|
|
22
|
+
//
|
|
23
|
+
// No imports: install.js and the extraction worker load it, and so does a
|
|
24
|
+
// build, from the application it stages.
|
|
25
|
+
|
|
26
|
+
export class Bzip2Error extends Error {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = 'Bzip2Error';
|
|
30
|
+
this.code = 'unpack';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const BLOCK_MAGIC_HI = 0x314159; // π
|
|
35
|
+
const BLOCK_MAGIC_LO = 0x265359;
|
|
36
|
+
const END_MAGIC_HI = 0x177245; // √π
|
|
37
|
+
const END_MAGIC_LO = 0x385090;
|
|
38
|
+
const MAX_GROUPS = 6;
|
|
39
|
+
const MAX_ALPHA = 258;
|
|
40
|
+
const MAX_CODE_LEN = 20;
|
|
41
|
+
const MAX_SELECTORS = 18002;
|
|
42
|
+
const RUNA = 0;
|
|
43
|
+
const RUNB = 1;
|
|
44
|
+
const GROUP_SIZE = 50;
|
|
45
|
+
|
|
46
|
+
/** CRC-32 as bzip2 has it: polynomial 0x04c11db7, most significant bit first. */
|
|
47
|
+
const CRC_TABLE = (() => {
|
|
48
|
+
const table = new Uint32Array(256);
|
|
49
|
+
for (let i = 0; i < 256; i++) {
|
|
50
|
+
let c = i << 24;
|
|
51
|
+
for (let k = 0; k < 8; k++) c = (c & 0x80000000) ? (c << 1) ^ 0x04c11db7 : c << 1;
|
|
52
|
+
table[i] = c >>> 0;
|
|
53
|
+
}
|
|
54
|
+
return table;
|
|
55
|
+
})();
|
|
56
|
+
|
|
57
|
+
/** Reads a Buffer bit by bit, most significant bit first. */
|
|
58
|
+
class BitReader {
|
|
59
|
+
constructor(buffer) {
|
|
60
|
+
this.buffer = buffer;
|
|
61
|
+
this.pos = 0; // the next byte to take
|
|
62
|
+
this.bits = 0; // what is held, in the low `count` bits
|
|
63
|
+
this.count = 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** `n` bits (1 to 24) as a number. */
|
|
67
|
+
read(n) {
|
|
68
|
+
while (this.count < n) {
|
|
69
|
+
if (this.pos >= this.buffer.length) throw new Bzip2Error('The archive ends inside a compressed block.');
|
|
70
|
+
this.bits = ((this.bits << 8) | this.buffer[this.pos++]) >>> 0;
|
|
71
|
+
this.count += 8;
|
|
72
|
+
}
|
|
73
|
+
this.count -= n;
|
|
74
|
+
const value = (this.bits >>> this.count) & ((1 << n) - 1);
|
|
75
|
+
this.bits &= (1 << this.count) - 1;
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
bit() {
|
|
80
|
+
if (this.count === 0) {
|
|
81
|
+
if (this.pos >= this.buffer.length) throw new Bzip2Error('The archive ends inside a compressed block.');
|
|
82
|
+
this.bits = this.buffer[this.pos++];
|
|
83
|
+
this.count = 8;
|
|
84
|
+
}
|
|
85
|
+
this.count--;
|
|
86
|
+
const value = (this.bits >>> this.count) & 1;
|
|
87
|
+
this.bits &= (1 << this.count) - 1;
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
read32() {
|
|
92
|
+
return ((this.read(16) << 16) | this.read(16)) >>> 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Drop what is left of the current byte (a stream ends on a byte boundary). */
|
|
96
|
+
align() {
|
|
97
|
+
this.bits = 0;
|
|
98
|
+
this.count = 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get done() {
|
|
102
|
+
return this.count === 0 && this.pos >= this.buffer.length;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Canonical Huffman decoding tables of one group, as bzip2's hbCreateDecodeTables makes them. */
|
|
107
|
+
function decodeTables(lengths, alphaSize) {
|
|
108
|
+
let minLen = 32;
|
|
109
|
+
let maxLen = 0;
|
|
110
|
+
for (let i = 0; i < alphaSize; i++) {
|
|
111
|
+
if (lengths[i] > maxLen) maxLen = lengths[i];
|
|
112
|
+
if (lengths[i] < minLen) minLen = lengths[i];
|
|
113
|
+
}
|
|
114
|
+
const perm = new Int32Array(MAX_ALPHA);
|
|
115
|
+
const base = new Int32Array(MAX_CODE_LEN + 2);
|
|
116
|
+
const limit = new Int32Array(MAX_CODE_LEN + 1).fill(-1);
|
|
117
|
+
let pp = 0;
|
|
118
|
+
for (let len = minLen; len <= maxLen; len++) for (let j = 0; j < alphaSize; j++) if (lengths[j] === len) perm[pp++] = j;
|
|
119
|
+
for (let i = 0; i < alphaSize; i++) base[lengths[i] + 1]++;
|
|
120
|
+
for (let i = 1; i < base.length; i++) base[i] += base[i - 1];
|
|
121
|
+
let vec = 0;
|
|
122
|
+
for (let len = minLen; len <= maxLen; len++) {
|
|
123
|
+
vec += base[len + 1] - base[len];
|
|
124
|
+
limit[len] = vec - 1;
|
|
125
|
+
vec <<= 1;
|
|
126
|
+
}
|
|
127
|
+
for (let len = minLen + 1; len <= maxLen; len++) base[len] = ((limit[len - 1] + 1) << 1) - base[len];
|
|
128
|
+
return { minLen, maxLen, perm, base, limit };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The next symbol of a group's code. */
|
|
132
|
+
function nextSymbol(reader, table) {
|
|
133
|
+
let len = table.minLen;
|
|
134
|
+
let code = reader.read(len);
|
|
135
|
+
while (len <= table.maxLen && code > table.limit[len]) {
|
|
136
|
+
len++;
|
|
137
|
+
code = (code << 1) | reader.bit();
|
|
138
|
+
}
|
|
139
|
+
if (len > table.maxLen) throw new Bzip2Error('The archive holds a code its tables do not know.');
|
|
140
|
+
const index = code - table.base[len];
|
|
141
|
+
if (index < 0 || index >= MAX_ALPHA) throw new Bzip2Error('The archive holds a code its tables do not know.');
|
|
142
|
+
return table.perm[index];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* One block, from just after its magic: its bytes (the final run decoding done) and the CRC it carries.
|
|
147
|
+
* `tt` is the block's workspace, as large as the stream's block size.
|
|
148
|
+
*/
|
|
149
|
+
function decodeBlock(reader, tt, blockSize) {
|
|
150
|
+
const storedCrc = reader.read32();
|
|
151
|
+
if (reader.bit()) throw new Bzip2Error('The archive has a randomised block (bzip2 0.9.0), which this reader does not read.');
|
|
152
|
+
const origPtr = reader.read(24);
|
|
153
|
+
|
|
154
|
+
// Which byte values the block uses.
|
|
155
|
+
const used = [];
|
|
156
|
+
const inUse16 = reader.read(16);
|
|
157
|
+
for (let i = 0; i < 16; i++) {
|
|
158
|
+
if (!(inUse16 & (0x8000 >>> i))) continue;
|
|
159
|
+
const bits = reader.read(16);
|
|
160
|
+
for (let j = 0; j < 16; j++) if (bits & (0x8000 >>> j)) used.push(i * 16 + j);
|
|
161
|
+
}
|
|
162
|
+
if (!used.length) throw new Bzip2Error('The archive has a block that uses no byte value.');
|
|
163
|
+
const alphaSize = used.length + 2;
|
|
164
|
+
const eob = used.length + 1;
|
|
165
|
+
|
|
166
|
+
// The Huffman groups and which one codes each run of fifty symbols.
|
|
167
|
+
const groups = reader.read(3);
|
|
168
|
+
if (groups < 2 || groups > MAX_GROUPS) throw new Bzip2Error('The archive has a block with an impossible number of code tables.');
|
|
169
|
+
const selectorCount = reader.read(15);
|
|
170
|
+
if (selectorCount < 1) throw new Bzip2Error('The archive has a block without code selectors.');
|
|
171
|
+
const mtfGroups = [0, 1, 2, 3, 4, 5].slice(0, groups);
|
|
172
|
+
const selectors = new Uint8Array(Math.min(selectorCount, MAX_SELECTORS));
|
|
173
|
+
for (let i = 0; i < selectorCount; i++) {
|
|
174
|
+
let j = 0;
|
|
175
|
+
while (reader.bit()) {
|
|
176
|
+
j++;
|
|
177
|
+
if (j >= groups) throw new Bzip2Error('The archive has a block with an impossible code selector.');
|
|
178
|
+
}
|
|
179
|
+
// Selectors past the most a block can use are read and dropped, as bzip2 1.0.8 does.
|
|
180
|
+
if (i >= MAX_SELECTORS) continue;
|
|
181
|
+
const value = mtfGroups[j];
|
|
182
|
+
for (let k = j; k > 0; k--) mtfGroups[k] = mtfGroups[k - 1];
|
|
183
|
+
mtfGroups[0] = value;
|
|
184
|
+
selectors[i] = value;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const tables = [];
|
|
188
|
+
for (let t = 0; t < groups; t++) {
|
|
189
|
+
const lengths = new Uint8Array(alphaSize);
|
|
190
|
+
let current = reader.read(5);
|
|
191
|
+
for (let i = 0; i < alphaSize; i++) {
|
|
192
|
+
for (;;) {
|
|
193
|
+
if (current < 1 || current > MAX_CODE_LEN) throw new Bzip2Error('The archive has a block with an impossible code length.');
|
|
194
|
+
if (!reader.bit()) break;
|
|
195
|
+
current += reader.bit() ? -1 : 1;
|
|
196
|
+
}
|
|
197
|
+
lengths[i] = current;
|
|
198
|
+
}
|
|
199
|
+
tables.push(decodeTables(lengths, alphaSize));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// The move-to-front values and their runs: the block before the inverse transform.
|
|
203
|
+
const counts = new Int32Array(256);
|
|
204
|
+
const order = new Uint8Array(256);
|
|
205
|
+
for (let i = 0; i < 256; i++) order[i] = i;
|
|
206
|
+
let length = 0;
|
|
207
|
+
let selector = 0;
|
|
208
|
+
let left = 0;
|
|
209
|
+
let table = null;
|
|
210
|
+
const next = () => {
|
|
211
|
+
if (left === 0) {
|
|
212
|
+
if (selector >= selectors.length) throw new Bzip2Error('The archive has a block that runs past its code selectors.');
|
|
213
|
+
table = tables[selectors[selector++]];
|
|
214
|
+
left = GROUP_SIZE;
|
|
215
|
+
}
|
|
216
|
+
left--;
|
|
217
|
+
return nextSymbol(reader, table);
|
|
218
|
+
};
|
|
219
|
+
let symbol = next();
|
|
220
|
+
for (;;) {
|
|
221
|
+
if (symbol === eob) break;
|
|
222
|
+
if (symbol === RUNA || symbol === RUNB) {
|
|
223
|
+
let run = 0;
|
|
224
|
+
let weight = 1;
|
|
225
|
+
do {
|
|
226
|
+
run += (symbol === RUNA ? 1 : 2) * weight;
|
|
227
|
+
weight <<= 1;
|
|
228
|
+
if (run > blockSize) throw new Bzip2Error('The archive has a block larger than its stream says blocks are.');
|
|
229
|
+
symbol = next();
|
|
230
|
+
} while (symbol === RUNA || symbol === RUNB);
|
|
231
|
+
const value = used[order[0]];
|
|
232
|
+
if (length + run > blockSize) throw new Bzip2Error('The archive has a block larger than its stream says blocks are.');
|
|
233
|
+
counts[value] += run;
|
|
234
|
+
tt.fill(value, length, length + run);
|
|
235
|
+
length += run;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (length >= blockSize) throw new Bzip2Error('The archive has a block larger than its stream says blocks are.');
|
|
239
|
+
const index = symbol - 1;
|
|
240
|
+
const front = order[index];
|
|
241
|
+
order.copyWithin(1, 0, index);
|
|
242
|
+
order[0] = front;
|
|
243
|
+
const value = used[front];
|
|
244
|
+
counts[value]++;
|
|
245
|
+
tt[length++] = value;
|
|
246
|
+
symbol = next();
|
|
247
|
+
}
|
|
248
|
+
if (origPtr >= length) throw new Bzip2Error('The archive has a block whose start lies outside it.');
|
|
249
|
+
|
|
250
|
+
// The inverse Burrows-Wheeler transform: each byte's successor in the upper 24 bits.
|
|
251
|
+
const start = new Int32Array(256);
|
|
252
|
+
for (let i = 0, sum = 0; i < 256; i++) { start[i] = sum; sum += counts[i]; }
|
|
253
|
+
for (let i = 0; i < length; i++) {
|
|
254
|
+
const value = tt[i] & 0xff;
|
|
255
|
+
tt[start[value]++] |= i << 8;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// The final run decoding (four equal bytes and a count), with the CRC over what comes out.
|
|
259
|
+
let out = Buffer.allocUnsafe(Math.max(1024, length + (length >>> 2)));
|
|
260
|
+
let at = 0;
|
|
261
|
+
let crc = 0xffffffff;
|
|
262
|
+
const room = (need) => {
|
|
263
|
+
const bigger = Buffer.allocUnsafe(Math.max(need, out.length * 2));
|
|
264
|
+
out.copy(bigger, 0, 0, at);
|
|
265
|
+
out = bigger;
|
|
266
|
+
};
|
|
267
|
+
let pos = tt[origPtr] >>> 8;
|
|
268
|
+
let last = -1;
|
|
269
|
+
let same = 0;
|
|
270
|
+
for (let i = 0; i < length; i++) {
|
|
271
|
+
const entry = tt[pos];
|
|
272
|
+
const byte = entry & 0xff;
|
|
273
|
+
pos = entry >>> 8;
|
|
274
|
+
if (same === 4) {
|
|
275
|
+
if (at + byte > out.length) room(at + byte);
|
|
276
|
+
for (let k = 0; k < byte; k++) {
|
|
277
|
+
out[at++] = last;
|
|
278
|
+
crc = (crc << 8) ^ CRC_TABLE[((crc >>> 24) ^ last) & 0xff];
|
|
279
|
+
}
|
|
280
|
+
same = 0;
|
|
281
|
+
last = -1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (byte === last) same++;
|
|
285
|
+
else { last = byte; same = 1; }
|
|
286
|
+
if (at === out.length) room(at + 1);
|
|
287
|
+
out[at++] = byte;
|
|
288
|
+
crc = (crc << 8) ^ CRC_TABLE[((crc >>> 24) ^ byte) & 0xff];
|
|
289
|
+
}
|
|
290
|
+
crc = (~crc) >>> 0;
|
|
291
|
+
if (crc !== storedCrc) throw new Bzip2Error('A block of the archive does not decode to what was compressed (CRC mismatch).');
|
|
292
|
+
return { data: out.subarray(0, at), crc };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The decompressed bytes of `buffer` (one or more concatenated bzip2 streams), one block at a time. `progress`,
|
|
297
|
+
* when given, is told after each block how far into `buffer` the reader is.
|
|
298
|
+
* @param {Buffer} buffer
|
|
299
|
+
* @param {{ progress?: (consumed: number, total: number) => void }} [o]
|
|
300
|
+
*/
|
|
301
|
+
export function* bunzip2Blocks(buffer, { progress } = {}) {
|
|
302
|
+
const reader = new BitReader(buffer);
|
|
303
|
+
let streams = 0;
|
|
304
|
+
while (!reader.done) {
|
|
305
|
+
// Zero bytes after the last stream are padding some writers leave; anything else has to be a stream.
|
|
306
|
+
if (streams > 0 && buffer.subarray(reader.pos).every((byte) => byte === 0)) break;
|
|
307
|
+
if (reader.read(8) !== 0x42 || reader.read(8) !== 0x5a || reader.read(8) !== 0x68) throw new Bzip2Error(streams ? 'The archive has something after its last stream.' : 'The archive is not a bzip2 file.');
|
|
308
|
+
const level = reader.read(8) - 0x30;
|
|
309
|
+
if (level < 1 || level > 9) throw new Bzip2Error('The archive names an impossible block size.');
|
|
310
|
+
const blockSize = level * 100000;
|
|
311
|
+
const tt = new Uint32Array(blockSize);
|
|
312
|
+
let combined = 0;
|
|
313
|
+
for (;;) {
|
|
314
|
+
const hi = reader.read(24);
|
|
315
|
+
const lo = reader.read(24);
|
|
316
|
+
if (hi === BLOCK_MAGIC_HI && lo === BLOCK_MAGIC_LO) {
|
|
317
|
+
const block = decodeBlock(reader, tt, blockSize);
|
|
318
|
+
combined = (((combined << 1) | (combined >>> 31)) ^ block.crc) >>> 0;
|
|
319
|
+
progress?.(reader.pos, buffer.length);
|
|
320
|
+
if (block.data.length) yield block.data;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (hi === END_MAGIC_HI && lo === END_MAGIC_LO) {
|
|
324
|
+
if (reader.read32() !== combined) throw new Bzip2Error('The archive does not decode to what was compressed (stream CRC mismatch).');
|
|
325
|
+
reader.align();
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
throw new Bzip2Error('The archive has something that is not a bzip2 block.');
|
|
329
|
+
}
|
|
330
|
+
streams++;
|
|
331
|
+
}
|
|
332
|
+
if (!streams) throw new Bzip2Error('The archive is empty.');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** All of `buffer`, decompressed (tests and small files). */
|
|
336
|
+
export function bunzip2(buffer) {
|
|
337
|
+
const parts = [...bunzip2Blocks(buffer)];
|
|
338
|
+
return Buffer.concat(parts);
|
|
339
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — Git Bash for the user's own `claude`
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// Every Claude Code that SynaBun starts gets its Git Bash in its environment
|
|
6
|
+
// (resolver.js gitBashChildEnv). A `claude` the user starts in another
|
|
7
|
+
// terminal does not: on a PC whose only Git Bash is SynaBun's copy, that one
|
|
8
|
+
// is named in the `env` block of ~/.claude/settings.json, which Claude Code
|
|
9
|
+
// applies before it looks for Git Bash (checked in its 2.1.302 build:
|
|
10
|
+
// applySafeConfigEnvironmentVariables applies the user settings' env in full,
|
|
11
|
+
// in init(), before the Git Bash lookup).
|
|
12
|
+
//
|
|
13
|
+
// SynaBun's copy is in use env.CLAUDE_CODE_GIT_BASH_PATH = its bash.exe,
|
|
14
|
+
// unless the user put a value of their own there
|
|
15
|
+
// it is not a value that is SynaBun's (a path inside one of
|
|
16
|
+
// its copies) is removed; any other is left
|
|
17
|
+
//
|
|
18
|
+
// Windows only. The file is read and written by the caller's functions
|
|
19
|
+
// (server.js readClaudeSettingsForWrite / writeClaudeSettings: a file that is
|
|
20
|
+
// not JSON is never written over, and the write is atomic). Never throws.
|
|
21
|
+
|
|
22
|
+
import { GIT_BASH_ENV, insideAny } from './resolver.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bring the user's settings in line with the Git Bash in use.
|
|
26
|
+
* @param {object} o
|
|
27
|
+
* @param {{ source: string|null, path: string|null }} o.resolution resolver.js resolveGitBash()
|
|
28
|
+
* @param {string} o.settingsPath ~/.claude/settings.json
|
|
29
|
+
* @param {(path: string) => object} o.read {} when the file does not exist; throws when it is not JSON
|
|
30
|
+
* @param {(path: string, settings: object) => void} o.write
|
|
31
|
+
* @param {string[]} o.ownRoots SynaBun's own Git folders
|
|
32
|
+
* @returns {{ state: 'skipped' | 'unchanged' | 'written' | 'removed' | 'kept-user-value' | 'error', value?: string|null, message?: string }}
|
|
33
|
+
*/
|
|
34
|
+
export function syncClaudeSettingsGitBash({ resolution, settingsPath, read, write, ownRoots = [], platform = process.platform }) {
|
|
35
|
+
if (platform !== 'win32' || !settingsPath) return { state: 'skipped' };
|
|
36
|
+
const ours = resolution?.source === 'bundled' || resolution?.source === 'downloaded' ? resolution.path : null;
|
|
37
|
+
let settings;
|
|
38
|
+
try { settings = read(settingsPath); } catch (error) {
|
|
39
|
+
return { state: 'error', message: String(error?.message || error) };
|
|
40
|
+
}
|
|
41
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) return { state: 'error', message: `${settingsPath} does not hold a settings object.` };
|
|
42
|
+
const env = settings.env && typeof settings.env === 'object' && !Array.isArray(settings.env) ? settings.env : null;
|
|
43
|
+
const current = typeof env?.[GIT_BASH_ENV] === 'string' ? env[GIT_BASH_ENV] : null;
|
|
44
|
+
const isOurs = current !== null && insideAny(current, ownRoots);
|
|
45
|
+
|
|
46
|
+
if (ours) {
|
|
47
|
+
if (current === ours) return { state: 'unchanged', value: ours };
|
|
48
|
+
// A value of the user's own (one that does not work, or the resolver would have taken it) is theirs to change.
|
|
49
|
+
if (current !== null && !isOurs) return { state: 'kept-user-value', value: current };
|
|
50
|
+
settings.env = { ...(env || {}), [GIT_BASH_ENV]: ours };
|
|
51
|
+
} else {
|
|
52
|
+
if (!isOurs) return { state: 'unchanged', value: current };
|
|
53
|
+
const rest = { ...env };
|
|
54
|
+
delete rest[GIT_BASH_ENV];
|
|
55
|
+
if (Object.keys(rest).length) settings.env = rest;
|
|
56
|
+
else delete settings.env;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
write(settingsPath, settings);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return { state: 'error', message: String(error?.message || error) };
|
|
62
|
+
}
|
|
63
|
+
return ours ? { state: 'written', value: ours } : { state: 'removed', value: null };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The user's own value of CLAUDE_CODE_GIT_BASH_PATH in their settings, or null (what the resolver weighs). */
|
|
67
|
+
export function settingsGitBashValue({ settingsPath, read }) {
|
|
68
|
+
try {
|
|
69
|
+
const settings = read(settingsPath);
|
|
70
|
+
const value = settings?.env?.[GIT_BASH_ENV];
|
|
71
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
72
|
+
} catch { return null; }
|
|
73
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — Git for Windows: the unpacking, in a thread of its own
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// install.js unpackInWorker() starts this with what unpackAndCheck() takes and
|
|
6
|
+
// a shared flag the server raises to stop it. The reading of the .tar.bz2, the
|
|
7
|
+
// writing of some ten thousand files and the digest of what was written take
|
|
8
|
+
// about a minute of CPU: here, they never hold the server's event loop. The
|
|
9
|
+
// thread answers with messages: `step`, `progress` (a few times a second at
|
|
10
|
+
// most), then `done` with the result or `error` with a GitBashError's code.
|
|
11
|
+
|
|
12
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
13
|
+
import { asGitBashError, unpackAndCheck } from './install.js';
|
|
14
|
+
|
|
15
|
+
const { flag, ...options } = workerData;
|
|
16
|
+
let last = 0;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = unpackAndCheck({
|
|
20
|
+
...options,
|
|
21
|
+
stopped: () => Atomics.load(flag, 0) === 1,
|
|
22
|
+
onStep: (step) => parentPort.postMessage({ type: 'step', step }),
|
|
23
|
+
onProgress: (progress) => {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
if (progress.consumed !== progress.total && now - last < 250) return;
|
|
26
|
+
last = now;
|
|
27
|
+
parentPort.postMessage({ type: 'progress', progress });
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
parentPort.postMessage({ type: 'done', result });
|
|
31
|
+
} catch (failure) {
|
|
32
|
+
const error = asGitBashError(failure, 'unpack', 'The archive could not be unpacked.');
|
|
33
|
+
parentPort.postMessage({ type: 'error', code: error.code, message: error.message, detail: error.detail || null });
|
|
34
|
+
}
|