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,355 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — the CLI banner of the side panels (page side)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// One banner for the Claude Code, Codex and OpenCode panels, above their
|
|
6
|
+
// transcript, whenever their CLI is not usable: not installed (with Install,
|
|
7
|
+
// which runs the vendor's official installer on the server, the command to run
|
|
8
|
+
// by hand and the install guide), being installed (its steps and the
|
|
9
|
+
// installer's last lines), failed to install, or found but failed to start
|
|
10
|
+
// (with the error it gave). Each panel keeps its own look through a class
|
|
11
|
+
// prefix: cp-cli-banner* (Claude), cxp-cli-banner* (Codex), ocp-cli-banner*
|
|
12
|
+
// (OpenCode); the panel element gets `<prefix>-cli-blocked` while sending is
|
|
13
|
+
// off.
|
|
14
|
+
//
|
|
15
|
+
// What it says is cli-install-view.js (DOM-free, tested under node); the data
|
|
16
|
+
// and the install stream are cli-status.js. A panel mounts one controller:
|
|
17
|
+
//
|
|
18
|
+
// const cli = mountCliBanner({ toolKey: 'codex', prefix: 'cxp',
|
|
19
|
+
// panel: () => panelEl, anchor: () => messagesContainer,
|
|
20
|
+
// onBlockedChange: (blocked) => syncSendButton() });
|
|
21
|
+
// cli.refresh(); when the panel is shown
|
|
22
|
+
// cli.blocked(); before a send
|
|
23
|
+
// cli.nudge(); a send that was held back
|
|
24
|
+
// cli.flagFailure(text); a session could not start the CLI
|
|
25
|
+
|
|
26
|
+
import { subscribeCliStatus, recheckCliStatus, getCliDocUrl, getCliInstallCommand, getCliLabel, installCli, followCliInstall, getCliInstallJobs, addCliToPath } from './cli-status.js';
|
|
27
|
+
import { cliBannerView, cliInstallText, cliStateOf, reduceInstallJob, setCliInstallTranslator, INSTALLABLE_CLIS } from './cli-install-view.js';
|
|
28
|
+
import { t } from './i18n.js';
|
|
29
|
+
|
|
30
|
+
setCliInstallTranslator((key, params) => t(`cliInstall.${key}`, params));
|
|
31
|
+
|
|
32
|
+
/** How long the note about where an install put the CLI stays, when it has one. */
|
|
33
|
+
const DONE_NOTICE_MS = 12000;
|
|
34
|
+
const NUDGE_MS = 700;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {object} o
|
|
38
|
+
* @param {'claude-code' | 'codex' | 'opencode'} o.toolKey
|
|
39
|
+
* @param {'cp' | 'cxp' | 'ocp'} o.prefix the panel's class prefix
|
|
40
|
+
* @param {() => HTMLElement | null} o.panel the panel element (gets `<prefix>-cli-blocked`)
|
|
41
|
+
* @param {() => HTMLElement | null} o.anchor the banner goes right before this element (the transcript's container)
|
|
42
|
+
* @param {(blocked: boolean, view: object) => void} [o.onBlockedChange] sending was held back / let go
|
|
43
|
+
* @param {number} [o.doneNoticeMs]
|
|
44
|
+
*/
|
|
45
|
+
export function mountCliBanner({ toolKey, prefix, panel, anchor, onBlockedChange = () => {}, doneNoticeMs = DONE_NOTICE_MS } = {}) {
|
|
46
|
+
const p = String(prefix || 'cp');
|
|
47
|
+
let info = null; // this CLI in the latest answer of /api/system/tool-versions (null: none yet)
|
|
48
|
+
let forced = null; // { reason }: a session could not start it, and the server was not asked again yet
|
|
49
|
+
let job = null; // the install under way or just ended (cli-install-view.js reduceInstallJob)
|
|
50
|
+
let installing = false;
|
|
51
|
+
let addingPath = false;
|
|
52
|
+
let pathError = '';
|
|
53
|
+
let looked = false; // asked whether the server is already installing it since it went missing
|
|
54
|
+
let el = null;
|
|
55
|
+
let doneTimer = null;
|
|
56
|
+
let nudgeTimer = null;
|
|
57
|
+
let lastBlocked = null;
|
|
58
|
+
let destroyed = false;
|
|
59
|
+
|
|
60
|
+
const view = () => cliBannerView({
|
|
61
|
+
toolKey,
|
|
62
|
+
label: getCliLabel(toolKey),
|
|
63
|
+
info,
|
|
64
|
+
forced,
|
|
65
|
+
job,
|
|
66
|
+
addingPath, pathError,
|
|
67
|
+
guideUrl: getCliDocUrl(toolKey),
|
|
68
|
+
installCommand: getCliInstallCommand(toolKey),
|
|
69
|
+
}, { t: cliInstallText });
|
|
70
|
+
|
|
71
|
+
const cls = (name) => `${p}-cli-banner-${name}`;
|
|
72
|
+
|
|
73
|
+
function build() {
|
|
74
|
+
const root = document.createElement('div');
|
|
75
|
+
root.className = `${p}-cli-banner`;
|
|
76
|
+
root.dataset.cliTool = toolKey;
|
|
77
|
+
root.innerHTML = `
|
|
78
|
+
<div class="${cls('icon')}" aria-hidden="true"></div>
|
|
79
|
+
<div class="${cls('text')}">
|
|
80
|
+
<div class="${cls('title')}"></div>
|
|
81
|
+
<div class="${cls('body')}"></div>
|
|
82
|
+
<div class="${cls('command')}" hidden><code></code><button class="${cls('copy')}" type="button"></button></div>
|
|
83
|
+
<div class="${cls('error')}" hidden><code></code></div>
|
|
84
|
+
<div class="${cls('note')}" hidden></div>
|
|
85
|
+
<div class="${cls('progress')}" hidden><div class="${cls('step')}"></div><div class="${cls('bar')}" hidden><span></span></div></div>
|
|
86
|
+
<pre class="${cls('log')}" hidden></pre>
|
|
87
|
+
<div class="${cls('actions')}">
|
|
88
|
+
<button class="${cls('install')}" type="button" hidden></button>
|
|
89
|
+
<a class="${cls('link')}" target="_blank" rel="noopener noreferrer"></a>
|
|
90
|
+
<button class="${cls('recheck')}" type="button"></button>
|
|
91
|
+
<button class="${cls('path')}" type="button" hidden></button>
|
|
92
|
+
<button class="${cls('dismiss')}" type="button" hidden></button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
`;
|
|
96
|
+
root.querySelector(`.${cls('install')}`).addEventListener('click', () => { startInstall(); });
|
|
97
|
+
root.querySelector(`.${cls('recheck')}`).addEventListener('click', () => { recheck(); });
|
|
98
|
+
root.querySelector(`.${cls('path')}`).addEventListener('click', () => { addPath(); });
|
|
99
|
+
root.querySelector(`.${cls('dismiss')}`).addEventListener('click', () => { clearDone(); });
|
|
100
|
+
root.querySelector(`.${cls('copy')}`).addEventListener('click', async (event) => {
|
|
101
|
+
const button = event.currentTarget;
|
|
102
|
+
const command = root.querySelector(`.${cls('command')} code`)?.textContent || '';
|
|
103
|
+
if (!command) return;
|
|
104
|
+
try { await navigator.clipboard.writeText(command); } catch { return; }
|
|
105
|
+
button.textContent = cliInstallText('copied');
|
|
106
|
+
setTimeout(() => { if (button.isConnected) button.textContent = cliInstallText('copy'); }, 1500);
|
|
107
|
+
});
|
|
108
|
+
return root;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function place() {
|
|
112
|
+
const before = anchor?.();
|
|
113
|
+
if (!before || !before.parentNode) return false;
|
|
114
|
+
if (!el) el = build();
|
|
115
|
+
if (el.parentNode !== before.parentNode || el.nextSibling !== before) before.parentNode.insertBefore(el, before);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function paint(v) {
|
|
120
|
+
el.className = `${p}-cli-banner ${p}-cli-banner--${v.kind}${nudgeTimer ? ` ${p}-cli-banner--nudge` : ''}`;
|
|
121
|
+
el.setAttribute('role', v.live === 'assertive' ? 'alert' : 'status');
|
|
122
|
+
el.setAttribute('aria-live', v.live);
|
|
123
|
+
el.querySelector(`.${cls('icon')}`).textContent = v.icon;
|
|
124
|
+
el.querySelector(`.${cls('title')}`).textContent = v.title;
|
|
125
|
+
const body = el.querySelector(`.${cls('body')}`);
|
|
126
|
+
body.textContent = v.text;
|
|
127
|
+
body.hidden = !v.text;
|
|
128
|
+
|
|
129
|
+
const command = el.querySelector(`.${cls('command')}`);
|
|
130
|
+
command.hidden = !v.command;
|
|
131
|
+
command.querySelector('code').textContent = v.command || '';
|
|
132
|
+
const copy = command.querySelector(`.${cls('copy')}`);
|
|
133
|
+
if (copy.textContent !== cliInstallText('copied')) copy.textContent = cliInstallText('copy');
|
|
134
|
+
|
|
135
|
+
const error = el.querySelector(`.${cls('error')}`);
|
|
136
|
+
error.hidden = !v.error;
|
|
137
|
+
error.querySelector('code').textContent = v.error || '';
|
|
138
|
+
|
|
139
|
+
const note = el.querySelector(`.${cls('note')}`);
|
|
140
|
+
note.hidden = !v.note;
|
|
141
|
+
note.textContent = v.note || '';
|
|
142
|
+
// The command to run by hand is announced after the note that introduces it. (Moved only when it is
|
|
143
|
+
// elsewhere: a node that moves loses the focus of what is in it.)
|
|
144
|
+
const before = v.kind === 'failed' && v.note && v.command ? note : body;
|
|
145
|
+
if (command.previousElementSibling !== before) before.after(command);
|
|
146
|
+
|
|
147
|
+
const progress = el.querySelector(`.${cls('progress')}`);
|
|
148
|
+
progress.hidden = !v.progress;
|
|
149
|
+
el.querySelector(`.${cls('step')}`).textContent = v.progress?.text || '';
|
|
150
|
+
const bar = el.querySelector(`.${cls('bar')}`);
|
|
151
|
+
const percent = v.progress?.percent;
|
|
152
|
+
bar.hidden = !(typeof percent === 'number');
|
|
153
|
+
if (typeof percent === 'number') {
|
|
154
|
+
bar.setAttribute('role', 'progressbar');
|
|
155
|
+
bar.setAttribute('aria-valuemin', '0');
|
|
156
|
+
bar.setAttribute('aria-valuemax', '100');
|
|
157
|
+
bar.setAttribute('aria-valuenow', String(percent));
|
|
158
|
+
bar.firstElementChild.style.width = `${percent}%`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const log = el.querySelector(`.${cls('log')}`);
|
|
162
|
+
log.hidden = !v.log.length;
|
|
163
|
+
log.setAttribute('aria-label', cliInstallText('logLabel'));
|
|
164
|
+
const logText = v.log.join('\n');
|
|
165
|
+
if (log.textContent !== logText) {
|
|
166
|
+
log.textContent = logText;
|
|
167
|
+
log.scrollTop = log.scrollHeight;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const install = el.querySelector(`.${cls('install')}`);
|
|
171
|
+
install.hidden = !v.install;
|
|
172
|
+
if (v.install) {
|
|
173
|
+
install.textContent = v.install.label;
|
|
174
|
+
install.disabled = !!v.install.disabled;
|
|
175
|
+
install.setAttribute('aria-busy', v.kind === 'installing' ? 'true' : 'false');
|
|
176
|
+
}
|
|
177
|
+
const link = el.querySelector(`.${cls('link')}`);
|
|
178
|
+
link.hidden = !v.guide;
|
|
179
|
+
if (v.guide) { link.href = v.guide; link.textContent = cliInstallText('guide'); }
|
|
180
|
+
else link.removeAttribute('href');
|
|
181
|
+
const recheckButton = el.querySelector(`.${cls('recheck')}`);
|
|
182
|
+
recheckButton.hidden = !v.recheck;
|
|
183
|
+
if (!recheckButton.disabled) recheckButton.textContent = cliInstallText('recheck');
|
|
184
|
+
const pathButton = el.querySelector(`.${cls('path')}`);
|
|
185
|
+
pathButton.hidden = !v.addPath;
|
|
186
|
+
pathButton.textContent = v.addPath?.label || '';
|
|
187
|
+
pathButton.disabled = !!v.addPath?.disabled;
|
|
188
|
+
const dismiss = el.querySelector(`.${cls('dismiss')}`);
|
|
189
|
+
dismiss.hidden = !v.dismiss;
|
|
190
|
+
dismiss.textContent = cliInstallText('dismiss');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function render() {
|
|
194
|
+
if (destroyed) return;
|
|
195
|
+
const v = view();
|
|
196
|
+
const panelEl = panel?.();
|
|
197
|
+
if (panelEl) panelEl.classList.toggle(`${p}-cli-blocked`, v.blocked);
|
|
198
|
+
if (v.show && place()) paint(v);
|
|
199
|
+
else if (!v.show && el) el.remove();
|
|
200
|
+
if (v.blocked !== lastBlocked) {
|
|
201
|
+
lastBlocked = v.blocked;
|
|
202
|
+
try { onBlockedChange(v.blocked, v); } catch (error) { console.error('[cli-banner] onBlockedChange:', error); }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function clearDone() {
|
|
207
|
+
clearTimeout(doneTimer);
|
|
208
|
+
doneTimer = null;
|
|
209
|
+
if (job?.phase === 'done') job = null;
|
|
210
|
+
render();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function apply(event) {
|
|
214
|
+
if (!job && event?.type !== 'start') job = reduceInstallJob(null, { type: 'start' });
|
|
215
|
+
job = reduceInstallJob(job, event);
|
|
216
|
+
if (event?.type === 'done' && event.ok && event.status && typeof event.status === 'object') {
|
|
217
|
+
info = event.status;
|
|
218
|
+
forced = null;
|
|
219
|
+
}
|
|
220
|
+
render();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function settle(final) {
|
|
224
|
+
if (job?.phase === 'running' && final) job = reduceInstallJob(job, final);
|
|
225
|
+
if (job?.phase === 'done') {
|
|
226
|
+
clearTimeout(doneTimer);
|
|
227
|
+
const where = job.path;
|
|
228
|
+
if (where && (where.onPath === false || where.added === true)) doneTimer = setTimeout(clearDone, doneNoticeMs);
|
|
229
|
+
else job = null;
|
|
230
|
+
}
|
|
231
|
+
render();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function startInstall() {
|
|
235
|
+
if (installing || !INSTALLABLE_CLIS.includes(toolKey)) return;
|
|
236
|
+
installing = true;
|
|
237
|
+
clearTimeout(doneTimer);
|
|
238
|
+
job = reduceInstallJob(null, { type: 'start' });
|
|
239
|
+
render();
|
|
240
|
+
let final = null;
|
|
241
|
+
try {
|
|
242
|
+
final = await installCli(toolKey, { onEvent: apply });
|
|
243
|
+
} catch (error) {
|
|
244
|
+
final = { type: 'done', ok: false, code: 'internal', error: error?.message || null };
|
|
245
|
+
} finally {
|
|
246
|
+
installing = false;
|
|
247
|
+
settle(final);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function addPath() {
|
|
252
|
+
if (addingPath) return;
|
|
253
|
+
addingPath = true; pathError = ''; render();
|
|
254
|
+
try {
|
|
255
|
+
const result = await addCliToPath(toolKey);
|
|
256
|
+
info = { ...info, pathStatus: { dir: result.dir, onPath: true, canAdd: false } };
|
|
257
|
+
job = reduceInstallJob(null, { type: 'done', ok: true, status: info, path: { ...result, added: true } });
|
|
258
|
+
settle();
|
|
259
|
+
} catch (error) { pathError = error.message; }
|
|
260
|
+
finally { addingPath = false; render(); }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// An install the server is already running (another window started it, or this page was reloaded during
|
|
264
|
+
// one): followed here as if this banner had started it.
|
|
265
|
+
async function followRunning() {
|
|
266
|
+
if (installing) return;
|
|
267
|
+
const jobs = await getCliInstallJobs();
|
|
268
|
+
if (destroyed || installing || jobs?.[toolKey]?.state !== 'running') return;
|
|
269
|
+
installing = true;
|
|
270
|
+
job = reduceInstallJob(null, { type: 'start' });
|
|
271
|
+
render();
|
|
272
|
+
let final = null;
|
|
273
|
+
try {
|
|
274
|
+
final = await followCliInstall(toolKey, { onEvent: apply });
|
|
275
|
+
} catch (error) {
|
|
276
|
+
final = { type: 'done', ok: false, code: 'connection', error: error?.message || null };
|
|
277
|
+
} finally {
|
|
278
|
+
installing = false;
|
|
279
|
+
settle(final);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function recheck() {
|
|
284
|
+
const button = el?.querySelector(`.${cls('recheck')}`);
|
|
285
|
+
if (button) { button.disabled = true; button.textContent = cliInstallText('checking'); }
|
|
286
|
+
// Asking again starts over: an earlier failure is not repeated over a fresh answer.
|
|
287
|
+
if (job?.phase === 'failed') job = null;
|
|
288
|
+
try {
|
|
289
|
+
const next = await recheckCliStatus(toolKey);
|
|
290
|
+
info = next || info;
|
|
291
|
+
if (cliStateOf(info) === 'installed' || cliStateOf(info) === 'broken') forced = null;
|
|
292
|
+
} catch { /* the banner stays as it is */ }
|
|
293
|
+
finally {
|
|
294
|
+
if (button?.isConnected) { button.disabled = false; button.textContent = cliInstallText('recheck'); }
|
|
295
|
+
render();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const unsubscribe = subscribeCliStatus(toolKey, (next) => {
|
|
300
|
+
info = next || null;
|
|
301
|
+
const state = cliStateOf(info);
|
|
302
|
+
if (state === 'installed') {
|
|
303
|
+
forced = null;
|
|
304
|
+
if (job?.phase === 'failed') job = null;
|
|
305
|
+
}
|
|
306
|
+
if (state === 'missing') {
|
|
307
|
+
if (!looked) { looked = true; followRunning().catch(() => {}); }
|
|
308
|
+
} else {
|
|
309
|
+
looked = false;
|
|
310
|
+
}
|
|
311
|
+
render();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
/** Draw again (the panel was shown, or rebuilt). */
|
|
316
|
+
refresh: render,
|
|
317
|
+
/** Sending is held back: the CLI is missing, broken, failed to install, or being installed. */
|
|
318
|
+
blocked: () => view().blocked,
|
|
319
|
+
/** What the banner shows now (cli-install-view.js cliBannerView). */
|
|
320
|
+
view,
|
|
321
|
+
/** A send was held back: draw attention to the banner for a moment. */
|
|
322
|
+
nudge() {
|
|
323
|
+
if (!view().show) return;
|
|
324
|
+
clearTimeout(nudgeTimer);
|
|
325
|
+
nudgeTimer = setTimeout(() => { nudgeTimer = null; render(); }, NUDGE_MS);
|
|
326
|
+
render();
|
|
327
|
+
},
|
|
328
|
+
/**
|
|
329
|
+
* A session could not start the CLI ("Claude CLI not found", a spawn error): show "failed to start" with its
|
|
330
|
+
* words at once, then ask the server again; its answer decides what stays.
|
|
331
|
+
*/
|
|
332
|
+
flagFailure(reason = null) {
|
|
333
|
+
forced = { reason: reason ? String(reason).slice(0, 300) : null };
|
|
334
|
+
render();
|
|
335
|
+
recheckCliStatus(toolKey)
|
|
336
|
+
.then((next) => {
|
|
337
|
+
info = next || info;
|
|
338
|
+
const state = cliStateOf(info);
|
|
339
|
+
if (state === 'installed' || state === 'broken') forced = null;
|
|
340
|
+
render();
|
|
341
|
+
})
|
|
342
|
+
.catch(() => {});
|
|
343
|
+
},
|
|
344
|
+
/** Install from the banner's own button (for a panel's own entry point). */
|
|
345
|
+
install: startInstall,
|
|
346
|
+
destroy() {
|
|
347
|
+
destroyed = true;
|
|
348
|
+
unsubscribe();
|
|
349
|
+
clearTimeout(doneTimer);
|
|
350
|
+
clearTimeout(nudgeTimer);
|
|
351
|
+
el?.remove();
|
|
352
|
+
el = null;
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — the Git Bash row of Settings
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The page side of git-bash-view.js: the row of Settings → AI connections →
|
|
6
|
+
// Executable paths that shows the Git Bash every Claude Code SynaBun starts
|
|
7
|
+
// gets on Windows, with Download / Try again and Re-check. It asks
|
|
8
|
+
// GET /api/cli/git-bash, follows a download while it runs (a few times a
|
|
9
|
+
// second, until the row has left the page), and posts what the buttons ask
|
|
10
|
+
// with the guards of SynaBun's other local routes (a JSON body, X-SynaBun-UI).
|
|
11
|
+
// A server that has no such route (an older one), and every macOS or Linux
|
|
12
|
+
// server, leaves the row hidden.
|
|
13
|
+
|
|
14
|
+
import { gitBashText, gitBashView } from './git-bash-view.js';
|
|
15
|
+
|
|
16
|
+
const POST = Object.freeze({ method: 'POST', headers: { 'Content-Type': 'application/json', 'X-SynaBun-UI': '1' }, body: '{}' });
|
|
17
|
+
|
|
18
|
+
const ICON = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 4h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zm1 2v12h16V6H4zm2.3 2.3 3.7 3.7-3.7 3.7-1.4-1.4L7.2 12 4.9 9.7l1.4-1.4zM11 15h6v2h-6v-2z"/></svg>';
|
|
19
|
+
|
|
20
|
+
const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char]));
|
|
21
|
+
|
|
22
|
+
/** The row's markup: hidden until the server says there is something to show (Windows). */
|
|
23
|
+
export function gitBashRowHtml() {
|
|
24
|
+
return `
|
|
25
|
+
<div class="cli-path-row" id="cli-git-bash" data-git-bash hidden>
|
|
26
|
+
<div class="cli-path-icon" style="color:var(--t-secondary)">${ICON}</div>
|
|
27
|
+
<div class="cli-path-field">
|
|
28
|
+
<span class="cli-path-label" id="cli-git-bash-label">${escapeHtml(gitBashText('label'))}</span>
|
|
29
|
+
<div class="cli-path-input-row">
|
|
30
|
+
<span class="stg-help" data-git-bash-line role="status" aria-live="polite" aria-labelledby="cli-git-bash-label" style="flex:1;min-width:0;margin:0"></span>
|
|
31
|
+
<button type="button" class="cli-detect-btn" data-git-bash-action hidden></button>
|
|
32
|
+
<button type="button" class="cli-detect-btn" data-git-bash-recheck hidden>${escapeHtml(gitBashText('recheck'))}</button>
|
|
33
|
+
<span class="cli-path-status" data-git-bash-badge></span>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="stg-help" data-git-bash-note hidden></div>
|
|
36
|
+
<div class="stg-help">${escapeHtml(gitBashText('help'))}</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Fill the row and keep it current. `fetchImpl`, `schedule` and `cancel` are the page's own unless a test gives
|
|
43
|
+
* others. Returns `{ refresh, stop }`; the row stops following a download by itself once it is off the page.
|
|
44
|
+
*/
|
|
45
|
+
export function wireGitBashRow(row, { fetchImpl = (...args) => fetch(...args), pollMs = 1500, schedule = (fn, ms) => setTimeout(fn, ms), cancel = (id) => clearTimeout(id) } = {}) {
|
|
46
|
+
if (!row) return { refresh: async () => null, stop: () => {} };
|
|
47
|
+
const badge = row.querySelector('[data-git-bash-badge]');
|
|
48
|
+
const line = row.querySelector('[data-git-bash-line]');
|
|
49
|
+
const note = row.querySelector('[data-git-bash-note]');
|
|
50
|
+
const action = row.querySelector('[data-git-bash-action]');
|
|
51
|
+
const recheck = row.querySelector('[data-git-bash-recheck]');
|
|
52
|
+
let timer = null;
|
|
53
|
+
let stopped = false;
|
|
54
|
+
|
|
55
|
+
const clearPoll = () => { if (timer !== null) { cancel(timer); timer = null; } };
|
|
56
|
+
const stop = () => { stopped = true; clearPoll(); };
|
|
57
|
+
|
|
58
|
+
function draw(status) {
|
|
59
|
+
const view = gitBashView(status);
|
|
60
|
+
row.hidden = !view.visible;
|
|
61
|
+
clearPoll();
|
|
62
|
+
if (!view.visible) return view;
|
|
63
|
+
if (badge) {
|
|
64
|
+
badge.textContent = view.badge?.text || '';
|
|
65
|
+
badge.className = `cli-path-status${view.badge?.tone ? ` ${view.badge.tone}` : ''}`;
|
|
66
|
+
}
|
|
67
|
+
if (line) line.textContent = view.line;
|
|
68
|
+
if (note) { note.textContent = view.note || ''; note.hidden = !view.note; }
|
|
69
|
+
if (action) {
|
|
70
|
+
action.hidden = !view.action;
|
|
71
|
+
action.disabled = false;
|
|
72
|
+
action.textContent = view.action ? gitBashText(view.action) : '';
|
|
73
|
+
}
|
|
74
|
+
if (recheck) {
|
|
75
|
+
recheck.hidden = !view.recheck;
|
|
76
|
+
recheck.disabled = false;
|
|
77
|
+
recheck.textContent = gitBashText('recheck');
|
|
78
|
+
}
|
|
79
|
+
if (view.busy && !stopped) {
|
|
80
|
+
timer = schedule(() => {
|
|
81
|
+
timer = null;
|
|
82
|
+
if (row.isConnected === false) { stop(); return; }
|
|
83
|
+
refresh();
|
|
84
|
+
}, pollMs);
|
|
85
|
+
}
|
|
86
|
+
return view;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function ask(path, init) {
|
|
90
|
+
const res = await fetchImpl(path, init);
|
|
91
|
+
let body = null;
|
|
92
|
+
try { body = await res.json(); } catch { body = null; }
|
|
93
|
+
// A refusal that still says how things are (422: nothing to fetch here) is drawn as it is.
|
|
94
|
+
if (!res.ok && !(body && typeof body.state === 'string')) throw new Error(body?.error || body?.code || `HTTP ${res.status}`);
|
|
95
|
+
return body;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const said = (error) => { if (line) line.textContent = gitBashText('errorRequest', { message: error?.message || String(error) }); };
|
|
99
|
+
|
|
100
|
+
async function refresh() {
|
|
101
|
+
try { return draw(await ask('/api/cli/git-bash')); } catch {
|
|
102
|
+
// An older server, or none: there is no row to show.
|
|
103
|
+
row.hidden = true;
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
action?.addEventListener('click', async () => {
|
|
109
|
+
action.disabled = true;
|
|
110
|
+
try { draw(await ask('/api/cli/git-bash/install', POST)); } catch (error) { said(error); action.disabled = false; }
|
|
111
|
+
});
|
|
112
|
+
recheck?.addEventListener('click', async () => {
|
|
113
|
+
recheck.disabled = true;
|
|
114
|
+
recheck.textContent = gitBashText('checking');
|
|
115
|
+
try { draw(await ask('/api/cli/git-bash/recheck', POST)); } catch (error) {
|
|
116
|
+
said(error);
|
|
117
|
+
recheck.disabled = false;
|
|
118
|
+
recheck.textContent = gitBashText('recheck');
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
refresh();
|
|
123
|
+
return { refresh, stop };
|
|
124
|
+
}
|
|
@@ -862,7 +862,7 @@ export function wireJudgmentsTab(overlay, { toast = () => {}, initial = null } =
|
|
|
862
862
|
const outcome = row.outcome && typeof row.outcome === 'object' ? row.outcome : null;
|
|
863
863
|
const summary = `${clock(row.created_at)} · <code>${escapeHtml(row.surface)}</code>${riders.map(x => ` <span style="${CHIP}">+${escapeHtml(x)}</span>`).join('')} ${kit.th('settings.redesign.judgments.qMs', { origin: escapeHtml(row.origin), model: escapeHtml(row.model || ''), num: num(row.question_count), num2: num(row.latency_ms), num3: num(row.input_tokens) })}${num(row.output_tokens)}${row.cached ? ' · <span style="color:var(--accent)">' + kit.te('settings.redesign.judgments.cached') + '</span>' : ''}${row.error ? ` · <span style="color:#f66">${escapeHtml(row.error)}</span>` : ''}${row.entity_id ? ` · ${memLink(row.entity_id)}` : ''}${row.session_id ? ` · <a href="#" class="jv-session-link" data-session="${attr(row.session_id)}" title="${attr(row.session_id)}${kit.te('settings.redesign.judgments.thisSessionSJudgments')}" style="${LINK};font-family:monospace">${kit.te('settings.redesign.judgments.session')} ${escapeHtml(shortId(row.session_id))}</a>` : ''}${row.project ? ` · ${escapeHtml(row.project)}` : ''}`;
|
|
864
864
|
return `<details class="jv-log-row" data-id="${attr(row.id)}"${state.openLogIds.has(String(row.id)) ? ' open' : ''} style="font-size:12px;border:1px solid var(--s-medium);border-radius:6px;padding:4px 8px;background:var(--s-darker)">
|
|
865
|
-
<summary style="cursor:pointer;list-style:none" title="${attr(row.created_at)}">${summary}</summary>
|
|
865
|
+
<summary style="cursor:pointer;list-style:none" title="${attr(row.created_at)}">${summary}${row.surface === 'budget-split' && budgetSplitLine(row, row.answers || {}).line ? ' · ' + budgetSplitLine(row, row.answers || {}).line : ''}</summary>
|
|
866
866
|
<div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.state')}</div><pre style="margin:0;white-space:pre-wrap;font-size:11px;max-height:160px;overflow:auto">${escapeHtml(row.state_preview || '')}</pre>
|
|
867
867
|
<div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.answers')}</div><pre style="margin:0;white-space:pre-wrap;font-size:11px;max-height:200px;overflow:auto">${escapeHtml(answers || '—')}</pre>
|
|
868
868
|
${outcome ? `<div class="settings-hint" style="margin:6px 0 2px">${kit.te('settings.redesign.judgments.outcome')}</div><pre style="${PRE}">${escapeHtml(JSON.stringify(outcome, null, 2))}</pre>` : ''}
|
|
@@ -1029,7 +1029,16 @@ export function wireJudgmentsTab(overlay, { toast = () => {}, initial = null } =
|
|
|
1029
1029
|
const p = prob(a.goal_met);
|
|
1030
1030
|
return { line: p == null ? '' : flagged(kit.tx('settings.redesign.judgments.pGoalMet', { pct: pct(p) }), p >= knob('loop-goal', 'minConfidence', 0.8)) };
|
|
1031
1031
|
}
|
|
1032
|
+
function budgetSplitLine(row, answers) {
|
|
1033
|
+
const names = { share_claude_code: 'Claude Code', share_codex: 'Codex', share_opencode: 'OpenCode' };
|
|
1034
|
+
const parts = Object.entries(names).filter(([key]) => answers[key]?.type === 'score').map(([key, label]) => {
|
|
1035
|
+
const a = answers[key];
|
|
1036
|
+
return escapeHtml(kit.tx('settings.redesign.judgments.budgetShareScore', { provider: label, score: num(a.score ?? a.value), confidence: pct(a.confidence) }));
|
|
1037
|
+
});
|
|
1038
|
+
return { line: parts.join(' · ') };
|
|
1039
|
+
}
|
|
1032
1040
|
const TIMELINE = {
|
|
1041
|
+
'budget-split': budgetSplitLine,
|
|
1033
1042
|
'prompt-urgency': urgencyLine, 'rerank': rankLine, 'brief-rank': rankLine,
|
|
1034
1043
|
'agent-message': stopLine, 'turn-worth': stopLine, 'claim-check': stopLine,
|
|
1035
1044
|
'edit-stale': staleLine, 'stale-check': staleLine, 'plan-conflict': planLine, 'compact-digest': digestLine, 'loop-goal': loopLine,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
// Imported with the app, not when it is needed: once the server is down
|
|
15
15
|
// nothing more can be fetched, and that is exactly when the overlay wants it.
|
|
16
16
|
import { createMascot } from './synabun-mascot.js';
|
|
17
|
+
import { isDesktopApp } from './desktop-bridge.js';
|
|
17
18
|
|
|
18
19
|
const $ = (id) => document.getElementById(id);
|
|
19
20
|
|
|
@@ -48,6 +49,22 @@ function stored(key) {
|
|
|
48
49
|
try { return localStorage.getItem(key); } catch { return null; }
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
// Inside the desktop app the app starts and restarts its server itself: no
|
|
53
|
+
// Start button, no command to type. The sentences of a start that point at
|
|
54
|
+
// them say what to do in the app instead (desktop.offline.* in i18n).
|
|
55
|
+
const DESKTOP_LINES = { stalled: 'stalled', restartStalled: 'stalled', gaveUp: 'stalled', failedHint: 'failedHint' };
|
|
56
|
+
|
|
57
|
+
function desktopLine(line) {
|
|
58
|
+
const key = isDesktopApp() ? DESKTOP_LINES[line.key] : null;
|
|
59
|
+
return key ? t(`desktop.offline.${key}`) : '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The command box under the overlay: a terminal command, which the desktop app has no use for. */
|
|
63
|
+
function showStartCommand(show) {
|
|
64
|
+
const $box = $('loading-server-cmd-box');
|
|
65
|
+
if ($box) $box.style.display = show ? '' : 'none';
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
// ═══════════════════════════════════════════
|
|
52
69
|
// PUBLIC API
|
|
53
70
|
// ═══════════════════════════════════════════
|
|
@@ -76,8 +93,10 @@ export function showLoadingError(title, sub, canStart, serverOffline) {
|
|
|
76
93
|
if (serverOffline) _offlineTitle = title;
|
|
77
94
|
|
|
78
95
|
// Offline, the button asks the OS to start the server (synabun://start); it
|
|
79
|
-
// is left out only when that launcher is known not to be registered
|
|
80
|
-
|
|
96
|
+
// is left out only when that launcher is known not to be registered, and in
|
|
97
|
+
// the desktop app, which starts its server itself.
|
|
98
|
+
const desktopApp = isDesktopApp();
|
|
99
|
+
const offlineStart = !!serverOffline && !desktopApp && startButtonUsable(stored(START_STORAGE.launcher));
|
|
81
100
|
const $action = $('loading-action');
|
|
82
101
|
const $btn = $('loading-action-btn');
|
|
83
102
|
if (canStart || offlineStart) {
|
|
@@ -94,7 +113,8 @@ export function showLoadingError(title, sub, canStart, serverOffline) {
|
|
|
94
113
|
|
|
95
114
|
if (serverOffline) {
|
|
96
115
|
const $hint = $('loading-server-cmd-hint');
|
|
97
|
-
if ($hint) $hint.textContent = offlineStart ? t('loading.runCommandOr') : t('loading.runCommandSetup');
|
|
116
|
+
if ($hint) $hint.textContent = desktopApp ? t('desktop.offline.hint') : offlineStart ? t('loading.runCommandOr') : t('loading.runCommandSetup');
|
|
117
|
+
if (desktopApp) showStartCommand(false);
|
|
98
118
|
watchForServer();
|
|
99
119
|
} else {
|
|
100
120
|
stopWatchingForServer();
|
|
@@ -275,7 +295,7 @@ function poseMascot(state) {
|
|
|
275
295
|
function renderStart(snap) {
|
|
276
296
|
const platform = isWindowsPlatform(navigator) ? 'windows' : isMacPlatform(navigator) ? 'mac' : 'linux';
|
|
277
297
|
const view = startStatusView(snap, { platform });
|
|
278
|
-
const say = (line) => (line ? t(`loading.start.${line.key}`, line.params) : '');
|
|
298
|
+
const say = (line) => (line ? desktopLine(line) || t(`loading.start.${line.key}`, line.params) : '');
|
|
279
299
|
// A restart this page asked for or was told of, while it runs.
|
|
280
300
|
const restarting = !!snap.restart && (view.busy || view.state === 'online');
|
|
281
301
|
|
|
@@ -315,7 +335,8 @@ function renderStart(snap) {
|
|
|
315
335
|
// No dead button: when a restart did not bring the server back, Start is
|
|
316
336
|
// offered only if its launcher is there (the command below always is).
|
|
317
337
|
const startOffered = view.button === 'start' || view.button === 'retry';
|
|
318
|
-
|
|
338
|
+
// The desktop app keeps only the way back from a refused restart.
|
|
339
|
+
btn.style.display = (isDesktopApp() ? view.button !== 'back' : _restartShown && startOffered && !startButtonUsable(stored(START_STORAGE.launcher))) ? 'none' : '';
|
|
319
340
|
}
|
|
320
341
|
if ($label) {
|
|
321
342
|
$label.textContent = view.button === 'starting' ? t('loading.starting')
|
|
@@ -465,7 +486,10 @@ function beginRestart({ request = null, announced = false, boot = '' } = {}) {
|
|
|
465
486
|
const $action = $('loading-action');
|
|
466
487
|
if ($action) $action.style.display = 'block';
|
|
467
488
|
const $hint = $('loading-server-cmd-hint');
|
|
468
|
-
if (
|
|
489
|
+
if (isDesktopApp()) {
|
|
490
|
+
if ($hint) $hint.textContent = t('desktop.offline.hint');
|
|
491
|
+
showStartCommand(false);
|
|
492
|
+
} else if ($hint) $hint.textContent = startButtonUsable(stored(START_STORAGE.launcher)) ? t('loading.runCommandOr') : t('loading.runCommandSetup');
|
|
469
493
|
const $retry = $('loading-retry-status');
|
|
470
494
|
if ($retry) $retry.textContent = '';
|
|
471
495
|
clearStartStatus();
|
|
@@ -14,8 +14,10 @@ import { registerAction } from './ui-keybinds.js';
|
|
|
14
14
|
import { isGuest, hasPermission } from './ui-sync.js';
|
|
15
15
|
import { sendToPanel } from './ui-claude-panel.js';
|
|
16
16
|
import { forceCheckToolUpdates, showUpdateToast, TOOL_LABELS } from './ui-tool-updates.js';
|
|
17
|
-
import { forceCheckSynabunUpdate, openSynabunUpdateModal } from './ui-update.js';
|
|
17
|
+
import { forceCheckSynabunUpdate, openSynabunUpdateModal, synabunUpdateSourceLabel } from './ui-update.js';
|
|
18
18
|
import { requestServerRestart } from './ui-loading.js';
|
|
19
|
+
import { desktopBridge, isDesktopApp } from './desktop-bridge.js';
|
|
20
|
+
import { t } from './i18n.js';
|
|
19
21
|
|
|
20
22
|
const $ = (id) => document.getElementById(id);
|
|
21
23
|
|
|
@@ -391,8 +393,7 @@ function wireTerminalMenu() {
|
|
|
391
393
|
}
|
|
392
394
|
}
|
|
393
395
|
if (syn?.updateAvailable) {
|
|
394
|
-
|
|
395
|
-
updates.push({ label: `SynaBun v${syn.current} → v${syn.latest}`, source: srcLabel });
|
|
396
|
+
updates.push({ label: `SynaBun v${syn.current} → v${syn.latest}`, source: synabunUpdateSourceLabel(syn) });
|
|
396
397
|
}
|
|
397
398
|
|
|
398
399
|
const errors = [];
|
|
@@ -426,6 +427,9 @@ function wireTerminalMenu() {
|
|
|
426
427
|
// when the server is back (ui-loading.js requestServerRestart).
|
|
427
428
|
'menu-restart-server': () => { requestServerRestart(); },
|
|
428
429
|
'menu-stop-server': async () => {
|
|
430
|
+
// Inside the desktop app the server is the app's: stopping it is quitting SynaBun.
|
|
431
|
+
const desktop = desktopBridge();
|
|
432
|
+
if (desktop) { await desktop.quit(); return; }
|
|
429
433
|
const previousTitle = document.title;
|
|
430
434
|
try {
|
|
431
435
|
document.title = 'Stopping...';
|
|
@@ -451,6 +455,12 @@ function wireTerminalMenu() {
|
|
|
451
455
|
registerAction('toggle-terminal', () => emit('terminal:toggle'));
|
|
452
456
|
registerAction('toggle-terminal-alt', () => emit('terminal:toggle'));
|
|
453
457
|
|
|
458
|
+
// The desktop app is SynaBun's own window: no browser installs it, and Stop Server is Quit SynaBun there.
|
|
459
|
+
if (isDesktopApp()) {
|
|
460
|
+
const quitLabel = [...($('menu-stop-server')?.querySelector('.menu-text')?.childNodes || [])].filter(node => node.nodeType === 3).pop();
|
|
461
|
+
if (quitLabel) quitLabel.textContent = ` ${t('desktop.menu.quit')}`;
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
454
464
|
initPwaInstall({ changed: value => {
|
|
455
465
|
const el = $('menu-install-app');
|
|
456
466
|
if (el) el.style.display = value === 'installed' ? 'none' : '';
|
|
@@ -24,6 +24,7 @@ import { toggleScheduleQueue } from './ui-schedule-queue.js';
|
|
|
24
24
|
import { initUpdate } from './ui-update.js';
|
|
25
25
|
import { initToolUpdates } from './ui-tool-updates.js';
|
|
26
26
|
import { initSidepanelTrayPlaceholders } from './ui-sidepanel-tray.js';
|
|
27
|
+
import { initDesktopTitleBar } from './desktop-titlebar.js';
|
|
27
28
|
|
|
28
29
|
const $ = (id) => document.getElementById(id);
|
|
29
30
|
|
|
@@ -304,7 +305,8 @@ export function initNavbar() {
|
|
|
304
305
|
|
|
305
306
|
// ── Window Controls Overlay (PWA standalone) ──
|
|
306
307
|
// Toggles body.wco-active when the overlay chevron is clicked (traffic lights move into content).
|
|
307
|
-
|
|
308
|
+
// Inside the SynaBun desktop app the bar is the window's title bar, and desktop-titlebar.js does this and more.
|
|
309
|
+
if (!initDesktopTitleBar() && 'windowControlsOverlay' in navigator) {
|
|
308
310
|
const wco = navigator.windowControlsOverlay;
|
|
309
311
|
const updateWco = () => document.body.classList.toggle('wco-active', wco.visible);
|
|
310
312
|
updateWco();
|