@dsh-cc/tui 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +144 -0
- package/lib/bash-history.d.ts +31 -0
- package/lib/bash-history.d.ts.map +1 -0
- package/lib/bash-history.js +94 -0
- package/lib/bash-history.js.map +1 -0
- package/lib/compact-fold.d.ts +35 -0
- package/lib/compact-fold.d.ts.map +1 -0
- package/lib/compact-fold.js +60 -0
- package/lib/compact-fold.js.map +1 -0
- package/lib/components/arg-completers.d.ts +22 -0
- package/lib/components/arg-completers.d.ts.map +1 -0
- package/lib/components/arg-completers.js +75 -0
- package/lib/components/arg-completers.js.map +1 -0
- package/lib/components/code-theme.d.ts +25 -0
- package/lib/components/code-theme.d.ts.map +1 -0
- package/lib/components/code-theme.js +137 -0
- package/lib/components/code-theme.js.map +1 -0
- package/lib/components/completion.d.ts +50 -0
- package/lib/components/completion.d.ts.map +1 -0
- package/lib/components/completion.js +279 -0
- package/lib/components/completion.js.map +1 -0
- package/lib/components/diff-card.d.ts +29 -0
- package/lib/components/diff-card.d.ts.map +1 -0
- package/lib/components/diff-card.js +175 -0
- package/lib/components/diff-card.js.map +1 -0
- package/lib/components/diff-hunks.d.ts +44 -0
- package/lib/components/diff-hunks.d.ts.map +1 -0
- package/lib/components/diff-hunks.js +188 -0
- package/lib/components/diff-hunks.js.map +1 -0
- package/lib/components/editor-history.d.ts +23 -0
- package/lib/components/editor-history.d.ts.map +1 -0
- package/lib/components/editor-history.js +29 -0
- package/lib/components/editor-history.js.map +1 -0
- package/lib/components/markdown-theme.d.ts +11 -0
- package/lib/components/markdown-theme.d.ts.map +1 -0
- package/lib/components/markdown-theme.js +29 -0
- package/lib/components/markdown-theme.js.map +1 -0
- package/lib/components/masked-input.d.ts +24 -0
- package/lib/components/masked-input.d.ts.map +1 -0
- package/lib/components/masked-input.js +168 -0
- package/lib/components/masked-input.js.map +1 -0
- package/lib/components/overlay-host.d.ts +31 -0
- package/lib/components/overlay-host.d.ts.map +1 -0
- package/lib/components/overlay-host.js +128 -0
- package/lib/components/overlay-host.js.map +1 -0
- package/lib/components/overlays.d.ts +94 -0
- package/lib/components/overlays.d.ts.map +1 -0
- package/lib/components/overlays.js +450 -0
- package/lib/components/overlays.js.map +1 -0
- package/lib/components/provider-box.d.ts +23 -0
- package/lib/components/provider-box.d.ts.map +1 -0
- package/lib/components/provider-box.js +164 -0
- package/lib/components/provider-box.js.map +1 -0
- package/lib/components/read-group.d.ts +51 -0
- package/lib/components/read-group.d.ts.map +1 -0
- package/lib/components/read-group.js +105 -0
- package/lib/components/read-group.js.map +1 -0
- package/lib/components/root-bash.d.ts +36 -0
- package/lib/components/root-bash.d.ts.map +1 -0
- package/lib/components/root-bash.js +112 -0
- package/lib/components/root-bash.js.map +1 -0
- package/lib/components/root-types.d.ts +42 -0
- package/lib/components/root-types.d.ts.map +1 -0
- package/lib/components/root-types.js +8 -0
- package/lib/components/root-types.js.map +1 -0
- package/lib/components/root-utils.d.ts +22 -0
- package/lib/components/root-utils.d.ts.map +1 -0
- package/lib/components/root-utils.js +45 -0
- package/lib/components/root-utils.js.map +1 -0
- package/lib/components/root.d.ts +25 -0
- package/lib/components/root.d.ts.map +1 -0
- package/lib/components/root.js +357 -0
- package/lib/components/root.js.map +1 -0
- package/lib/components/theme.d.ts +61 -0
- package/lib/components/theme.d.ts.map +1 -0
- package/lib/components/theme.js +99 -0
- package/lib/components/theme.js.map +1 -0
- package/lib/components/transcript.d.ts +43 -0
- package/lib/components/transcript.d.ts.map +1 -0
- package/lib/components/transcript.js +203 -0
- package/lib/components/transcript.js.map +1 -0
- package/lib/components/working-line.d.ts +35 -0
- package/lib/components/working-line.d.ts.map +1 -0
- package/lib/components/working-line.js +66 -0
- package/lib/components/working-line.js.map +1 -0
- package/lib/effort-catalog.d.ts +27 -0
- package/lib/effort-catalog.d.ts.map +1 -0
- package/lib/effort-catalog.js +36 -0
- package/lib/effort-catalog.js.map +1 -0
- package/lib/export-markdown.d.ts +18 -0
- package/lib/export-markdown.d.ts.map +1 -0
- package/lib/export-markdown.js +124 -0
- package/lib/export-markdown.js.map +1 -0
- package/lib/harness/approval-preview.d.ts +33 -0
- package/lib/harness/approval-preview.d.ts.map +1 -0
- package/lib/harness/approval-preview.js +179 -0
- package/lib/harness/approval-preview.js.map +1 -0
- package/lib/harness/boot-banner.d.ts +14 -0
- package/lib/harness/boot-banner.d.ts.map +1 -0
- package/lib/harness/boot-banner.js +89 -0
- package/lib/harness/boot-banner.js.map +1 -0
- package/lib/harness/driver-agent.d.ts +78 -0
- package/lib/harness/driver-agent.d.ts.map +1 -0
- package/lib/harness/driver-agent.js +314 -0
- package/lib/harness/driver-agent.js.map +1 -0
- package/lib/harness/driver-agents.d.ts +20 -0
- package/lib/harness/driver-agents.d.ts.map +1 -0
- package/lib/harness/driver-agents.js +123 -0
- package/lib/harness/driver-agents.js.map +1 -0
- package/lib/harness/driver-approvals.d.ts +38 -0
- package/lib/harness/driver-approvals.d.ts.map +1 -0
- package/lib/harness/driver-approvals.js +277 -0
- package/lib/harness/driver-approvals.js.map +1 -0
- package/lib/harness/driver-bash.d.ts +15 -0
- package/lib/harness/driver-bash.d.ts.map +1 -0
- package/lib/harness/driver-bash.js +73 -0
- package/lib/harness/driver-bash.js.map +1 -0
- package/lib/harness/driver-catalog.d.ts +84 -0
- package/lib/harness/driver-catalog.d.ts.map +1 -0
- package/lib/harness/driver-catalog.js +256 -0
- package/lib/harness/driver-catalog.js.map +1 -0
- package/lib/harness/driver-ctx.d.ts +462 -0
- package/lib/harness/driver-ctx.d.ts.map +1 -0
- package/lib/harness/driver-ctx.js +11 -0
- package/lib/harness/driver-ctx.js.map +1 -0
- package/lib/harness/driver-hud.d.ts +31 -0
- package/lib/harness/driver-hud.d.ts.map +1 -0
- package/lib/harness/driver-hud.js +204 -0
- package/lib/harness/driver-hud.js.map +1 -0
- package/lib/harness/driver-live.d.ts +36 -0
- package/lib/harness/driver-live.d.ts.map +1 -0
- package/lib/harness/driver-live.js +44 -0
- package/lib/harness/driver-live.js.map +1 -0
- package/lib/harness/driver-modal.d.ts +52 -0
- package/lib/harness/driver-modal.d.ts.map +1 -0
- package/lib/harness/driver-modal.js +78 -0
- package/lib/harness/driver-modal.js.map +1 -0
- package/lib/harness/driver-mode.d.ts +35 -0
- package/lib/harness/driver-mode.d.ts.map +1 -0
- package/lib/harness/driver-mode.js +84 -0
- package/lib/harness/driver-mode.js.map +1 -0
- package/lib/harness/driver-pickers.d.ts +48 -0
- package/lib/harness/driver-pickers.d.ts.map +1 -0
- package/lib/harness/driver-pickers.js +249 -0
- package/lib/harness/driver-pickers.js.map +1 -0
- package/lib/harness/driver-promote.d.ts +29 -0
- package/lib/harness/driver-promote.d.ts.map +1 -0
- package/lib/harness/driver-promote.js +36 -0
- package/lib/harness/driver-promote.js.map +1 -0
- package/lib/harness/driver-queue.d.ts +19 -0
- package/lib/harness/driver-queue.d.ts.map +1 -0
- package/lib/harness/driver-queue.js +281 -0
- package/lib/harness/driver-queue.js.map +1 -0
- package/lib/harness/driver-run-local.d.ts +32 -0
- package/lib/harness/driver-run-local.d.ts.map +1 -0
- package/lib/harness/driver-run-local.js +366 -0
- package/lib/harness/driver-run-local.js.map +1 -0
- package/lib/harness/driver-sessions.d.ts +28 -0
- package/lib/harness/driver-sessions.d.ts.map +1 -0
- package/lib/harness/driver-sessions.js +386 -0
- package/lib/harness/driver-sessions.js.map +1 -0
- package/lib/harness/driver.d.ts +22 -0
- package/lib/harness/driver.d.ts.map +1 -0
- package/lib/harness/driver.js +459 -0
- package/lib/harness/driver.js.map +1 -0
- package/lib/harness/preset.d.ts +32 -0
- package/lib/harness/preset.d.ts.map +1 -0
- package/lib/harness/preset.js +34 -0
- package/lib/harness/preset.js.map +1 -0
- package/lib/harness/session-list.d.ts +65 -0
- package/lib/harness/session-list.d.ts.map +1 -0
- package/lib/harness/session-list.js +91 -0
- package/lib/harness/session-list.js.map +1 -0
- package/lib/harness/shell-output.d.ts +44 -0
- package/lib/harness/shell-output.d.ts.map +1 -0
- package/lib/harness/shell-output.js +90 -0
- package/lib/harness/shell-output.js.map +1 -0
- package/lib/harness/statusline-cc-transcript.d.ts +51 -0
- package/lib/harness/statusline-cc-transcript.d.ts.map +1 -0
- package/lib/harness/statusline-cc-transcript.js +191 -0
- package/lib/harness/statusline-cc-transcript.js.map +1 -0
- package/lib/harness/statusline-command.d.ts +54 -0
- package/lib/harness/statusline-command.d.ts.map +1 -0
- package/lib/harness/statusline-command.js +161 -0
- package/lib/harness/statusline-command.js.map +1 -0
- package/lib/harness/statusline-payload.d.ts +79 -0
- package/lib/harness/statusline-payload.d.ts.map +1 -0
- package/lib/harness/statusline-payload.js +93 -0
- package/lib/harness/statusline-payload.js.map +1 -0
- package/lib/harness/statusline-settings.d.ts +47 -0
- package/lib/harness/statusline-settings.d.ts.map +1 -0
- package/lib/harness/statusline-settings.js +57 -0
- package/lib/harness/statusline-settings.js.map +1 -0
- package/lib/harness/statusline-wiring.d.ts +45 -0
- package/lib/harness/statusline-wiring.d.ts.map +1 -0
- package/lib/harness/statusline-wiring.js +247 -0
- package/lib/harness/statusline-wiring.js.map +1 -0
- package/lib/harness/usage-view.d.ts +84 -0
- package/lib/harness/usage-view.d.ts.map +1 -0
- package/lib/harness/usage-view.js +199 -0
- package/lib/harness/usage-view.js.map +1 -0
- package/lib/harness/worktree-exit.d.ts +105 -0
- package/lib/harness/worktree-exit.d.ts.map +1 -0
- package/lib/harness/worktree-exit.js +168 -0
- package/lib/harness/worktree-exit.js.map +1 -0
- package/lib/history.d.ts +50 -0
- package/lib/history.d.ts.map +1 -0
- package/lib/history.js +139 -0
- package/lib/history.js.map +1 -0
- package/lib/index.d.ts +63 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +54 -0
- package/lib/index.js.map +1 -0
- package/lib/input.d.ts +158 -0
- package/lib/input.d.ts.map +1 -0
- package/lib/input.js +400 -0
- package/lib/input.js.map +1 -0
- package/lib/invariant.d.ts +14 -0
- package/lib/invariant.d.ts.map +1 -0
- package/lib/invariant.js +20 -0
- package/lib/invariant.js.map +1 -0
- package/lib/mode-cycle.d.ts +22 -0
- package/lib/mode-cycle.d.ts.map +1 -0
- package/lib/mode-cycle.js +32 -0
- package/lib/mode-cycle.js.map +1 -0
- package/lib/model-catalog.d.ts +22 -0
- package/lib/model-catalog.d.ts.map +1 -0
- package/lib/model-catalog.js +45 -0
- package/lib/model-catalog.js.map +1 -0
- package/lib/packaged-preset.d.ts +32 -0
- package/lib/packaged-preset.d.ts.map +1 -0
- package/lib/packaged-preset.js +94 -0
- package/lib/packaged-preset.js.map +1 -0
- package/lib/plugin.d.ts +11 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +88 -0
- package/lib/plugin.js.map +1 -0
- package/lib/profile.d.ts +4 -0
- package/lib/profile.d.ts.map +1 -0
- package/lib/profile.js +7 -0
- package/lib/profile.js.map +1 -0
- package/lib/project-sessions.d.ts +29 -0
- package/lib/project-sessions.d.ts.map +1 -0
- package/lib/project-sessions.js +60 -0
- package/lib/project-sessions.js.map +1 -0
- package/lib/project.d.ts +75 -0
- package/lib/project.d.ts.map +1 -0
- package/lib/project.js +196 -0
- package/lib/project.js.map +1 -0
- package/lib/provider-actions.d.ts +37 -0
- package/lib/provider-actions.d.ts.map +1 -0
- package/lib/provider-actions.js +417 -0
- package/lib/provider-actions.js.map +1 -0
- package/lib/provider-command.d.ts +109 -0
- package/lib/provider-command.d.ts.map +1 -0
- package/lib/provider-command.js +220 -0
- package/lib/provider-command.js.map +1 -0
- package/lib/provider-detail.d.ts +15 -0
- package/lib/provider-detail.d.ts.map +1 -0
- package/lib/provider-detail.js +201 -0
- package/lib/provider-detail.js.map +1 -0
- package/lib/provider-flow.d.ts +191 -0
- package/lib/provider-flow.d.ts.map +1 -0
- package/lib/provider-flow.js +192 -0
- package/lib/provider-flow.js.map +1 -0
- package/lib/provider-presets.d.ts +32 -0
- package/lib/provider-presets.d.ts.map +1 -0
- package/lib/provider-presets.js +105 -0
- package/lib/provider-presets.js.map +1 -0
- package/lib/provider-read.d.ts +63 -0
- package/lib/provider-read.d.ts.map +1 -0
- package/lib/provider-read.js +115 -0
- package/lib/provider-read.js.map +1 -0
- package/lib/provider-settings.d.ts +30 -0
- package/lib/provider-settings.d.ts.map +1 -0
- package/lib/provider-settings.js +65 -0
- package/lib/provider-settings.js.map +1 -0
- package/lib/resume-target.d.ts +59 -0
- package/lib/resume-target.d.ts.map +1 -0
- package/lib/resume-target.js +143 -0
- package/lib/resume-target.js.map +1 -0
- package/lib/slash.d.ts +44 -0
- package/lib/slash.d.ts.map +1 -0
- package/lib/slash.js +81 -0
- package/lib/slash.js.map +1 -0
- package/lib/state/driver-types.d.ts +505 -0
- package/lib/state/driver-types.d.ts.map +1 -0
- package/lib/state/driver-types.js +8 -0
- package/lib/state/driver-types.js.map +1 -0
- package/lib/state/driver-usage-types.d.ts +59 -0
- package/lib/state/driver-usage-types.d.ts.map +1 -0
- package/lib/state/driver-usage-types.js +8 -0
- package/lib/state/driver-usage-types.js.map +1 -0
- package/lib/statusline.d.ts +63 -0
- package/lib/statusline.d.ts.map +1 -0
- package/lib/statusline.js +97 -0
- package/lib/statusline.js.map +1 -0
- package/lib/store/pickers.d.ts +34 -0
- package/lib/store/pickers.d.ts.map +1 -0
- package/lib/store/pickers.js +111 -0
- package/lib/store/pickers.js.map +1 -0
- package/lib/store/provider-panel.d.ts +107 -0
- package/lib/store/provider-panel.d.ts.map +1 -0
- package/lib/store/provider-panel.js +151 -0
- package/lib/store/provider-panel.js.map +1 -0
- package/lib/store/question.d.ts +21 -0
- package/lib/store/question.d.ts.map +1 -0
- package/lib/store/question.js +59 -0
- package/lib/store/question.js.map +1 -0
- package/lib/store/queue.d.ts +27 -0
- package/lib/store/queue.d.ts.map +1 -0
- package/lib/store/queue.js +34 -0
- package/lib/store/queue.js.map +1 -0
- package/lib/store/session.d.ts +73 -0
- package/lib/store/session.d.ts.map +1 -0
- package/lib/store/session.js +172 -0
- package/lib/store/session.js.map +1 -0
- package/lib/store/subagents.d.ts +16 -0
- package/lib/store/subagents.d.ts.map +1 -0
- package/lib/store/subagents.js +44 -0
- package/lib/store/subagents.js.map +1 -0
- package/lib/store/todos.d.ts +35 -0
- package/lib/store/todos.d.ts.map +1 -0
- package/lib/store/todos.js +68 -0
- package/lib/store/todos.js.map +1 -0
- package/lib/store/usage.d.ts +15 -0
- package/lib/store/usage.d.ts.map +1 -0
- package/lib/store/usage.js +27 -0
- package/lib/store/usage.js.map +1 -0
- package/lib/store/views.d.ts +419 -0
- package/lib/store/views.d.ts.map +1 -0
- package/lib/store/views.js +3 -0
- package/lib/store/views.js.map +1 -0
- package/lib/store.d.ts +19 -0
- package/lib/store.d.ts.map +1 -0
- package/lib/store.js +10 -0
- package/lib/store.js.map +1 -0
- package/lib/terminal/lease.d.ts +33 -0
- package/lib/terminal/lease.d.ts.map +1 -0
- package/lib/terminal/lease.js +99 -0
- package/lib/terminal/lease.js.map +1 -0
- package/lib/tool-card.d.ts +75 -0
- package/lib/tool-card.d.ts.map +1 -0
- package/lib/tool-card.js +109 -0
- package/lib/tool-card.js.map +1 -0
- package/lib/tool-verbs.d.ts +12 -0
- package/lib/tool-verbs.d.ts.map +1 -0
- package/lib/tool-verbs.js +29 -0
- package/lib/tool-verbs.js.map +1 -0
- package/lib/transcript.d.ts +40 -0
- package/lib/transcript.d.ts.map +1 -0
- package/lib/transcript.js +413 -0
- package/lib/transcript.js.map +1 -0
- package/lib/working-line.d.ts +44 -0
- package/lib/working-line.d.ts.map +1 -0
- package/lib/working-line.js +56 -0
- package/lib/working-line.js.map +1 -0
- package/package.json +72 -0
- package/presets/cc/.dsh-cc-managed.json +5 -0
- package/presets/cc/agent.cordis.yml +524 -0
- package/presets/cc/preset.yml +3 -0
- package/scripts/stage-preset.mjs +26 -0
package/lib/project.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project (working-directory identity) resolution for session management.
|
|
3
|
+
*
|
|
4
|
+
* A "project" is the namespace sessions, resume markers, and input history
|
|
5
|
+
* share. For a cwd inside a git repository the project is pinned to the
|
|
6
|
+
* *main repo root*, so an isolation worktree (`--worktree`,
|
|
7
|
+
* `.claude/worktrees/<slug>`) keeps the same project as its main checkout;
|
|
8
|
+
* for a cwd outside git the project is the directory itself. Every member
|
|
9
|
+
* path is `resolve()`d to a canonical absolute form and keyed by a short
|
|
10
|
+
* sha256 of the project root (deliberately not a lossy string encoding like
|
|
11
|
+
* Claude Code's `-` separator scheme — see design).
|
|
12
|
+
*
|
|
13
|
+
* All git probing runs through an injectable synchronous exec so tests never
|
|
14
|
+
* spawn git; any failure or timeout degrades to the directory identity.
|
|
15
|
+
*
|
|
16
|
+
* @module @dsh-cc/tui/project
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
import { realpathSync } from 'node:fs';
|
|
21
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
22
|
+
/**
|
|
23
|
+
* Git invocation timeout. A hung git (network FS, broken env) must not block
|
|
24
|
+
* session boot; expiry degrades to the directory identity. Generous for
|
|
25
|
+
* slow repos while still bounding the worst case.
|
|
26
|
+
*/
|
|
27
|
+
export const GIT_PROBE_TIMEOUT_MS = 2000;
|
|
28
|
+
/** The default exec: real `git` via spawnSync with a bounded timeout. */
|
|
29
|
+
export const gitExecSync = (argv, cwd) => {
|
|
30
|
+
const result = spawnSync('git', [...argv], {
|
|
31
|
+
cwd,
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
timeout: GIT_PROBE_TIMEOUT_MS,
|
|
34
|
+
});
|
|
35
|
+
if (result.error !== undefined || result.status !== 0)
|
|
36
|
+
return undefined;
|
|
37
|
+
return { stdout: result.stdout };
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* True when `commonDir` marks a *linked* worktree whose project root is its
|
|
41
|
+
* directory's parent. The common dir of a standard layout is `<root>/.git`;
|
|
42
|
+
* a linked worktree reports its main checkout's `/.git`, which differs from
|
|
43
|
+
* its own `top/.git`. Anything else (main checkout, a submodule's
|
|
44
|
+
* `<super>/.git/modules/<name>`, bare, exotic GIT_DIR) is not a linked
|
|
45
|
+
* worktree and keeps `resolve(top)` as its project root — so submodules do
|
|
46
|
+
* not collapse onto their superproject.
|
|
47
|
+
*
|
|
48
|
+
* Keep in sync with `isLinkedWorktree` in
|
|
49
|
+
* `packages/settings/settings-cascade/src/local-root.ts`, which replicates
|
|
50
|
+
* this detector for local-settings hoisting (see its module doc).
|
|
51
|
+
*/
|
|
52
|
+
function isLinkedWorktree(commonDir, top) {
|
|
53
|
+
if (!commonDir.endsWith(`${sep}.git`))
|
|
54
|
+
return false;
|
|
55
|
+
return resolve(commonDir) !== resolve(join(top, '.git'));
|
|
56
|
+
}
|
|
57
|
+
/** Resolve one `worktree <path>` porcelain line, handling git's C-style quoting. */
|
|
58
|
+
function unquoteWorktreePath(line) {
|
|
59
|
+
if (line.length === 0 || line[0] !== '"')
|
|
60
|
+
return line;
|
|
61
|
+
// strip surrounding quotes and decode git's C-style escapes (`\n` `\t`
|
|
62
|
+
// `\"` `\\`); unknown escapes keep the backslash verbatim.
|
|
63
|
+
const inner = line.slice(1, -1);
|
|
64
|
+
let out = '';
|
|
65
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
66
|
+
const ch = inner[i];
|
|
67
|
+
if (ch === '\\' && i + 1 < inner.length) {
|
|
68
|
+
const next = inner[i + 1];
|
|
69
|
+
if (next === 'n') {
|
|
70
|
+
out += '\n';
|
|
71
|
+
i += 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (next === 't') {
|
|
75
|
+
out += '\t';
|
|
76
|
+
i += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (next === '\\' || next === '"') {
|
|
80
|
+
out += next;
|
|
81
|
+
i += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
out += ch;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the project identity for `cwd`. Git is probed via the injectable
|
|
91
|
+
* exec (defaults to real `git`); any failure or timeout degrades to a
|
|
92
|
+
* directory identity (`projectRoot = resolve(cwd)`, no worktrees).
|
|
93
|
+
*
|
|
94
|
+
* Results are memoised per resolved `cwd` so every process probes git at
|
|
95
|
+
* most once per working directory (driver clients call this in hot spots —
|
|
96
|
+
* boot, picker open, history rebind — and reuse the same identity). Only
|
|
97
|
+
* default-exec calls participate: an injected `exec` bypasses the cache
|
|
98
|
+
* entirely, so scripted-exec tests and external probing stay deterministic.
|
|
99
|
+
* {@link __clearProjectCache} resets the memo (used by tests).
|
|
100
|
+
*/
|
|
101
|
+
export function resolveProject(cwd, exec = gitExecSync) {
|
|
102
|
+
const key = resolve(cwd);
|
|
103
|
+
const memoised = exec === gitExecSync ? projectCache.get(key) : undefined;
|
|
104
|
+
if (memoised !== undefined)
|
|
105
|
+
return memoised;
|
|
106
|
+
const info = probeProject(cwd, exec);
|
|
107
|
+
if (exec === gitExecSync)
|
|
108
|
+
projectCache.set(key, info);
|
|
109
|
+
return info;
|
|
110
|
+
}
|
|
111
|
+
/** Module-level memo keyed by `resolve(cwd)` (see {@link resolveProject}). */
|
|
112
|
+
const projectCache = new Map();
|
|
113
|
+
/** Test hook: drop the module-level memo (also safe as a no-op cleanup). */
|
|
114
|
+
export function __clearProjectCache() {
|
|
115
|
+
projectCache.clear();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Canonicalise a path: `resolve()` then dereference symlinks via realpath so
|
|
119
|
+
* a repo reached through a symlinked ancestor (e.g. macOS `/var` →
|
|
120
|
+
* `/private/var`) yields the SAME string whether it came from `resolve(cwd)`,
|
|
121
|
+
* git's toplevel, or git's common-dir — git may mix realpath'd and non-
|
|
122
|
+
* realpath'd output for the same directory, which would otherwise split one
|
|
123
|
+
* project into two keys. Falls back to `resolve()` if realpath fails (e.g.
|
|
124
|
+
* the path no longer exists).
|
|
125
|
+
*/
|
|
126
|
+
function canonical(path) {
|
|
127
|
+
try {
|
|
128
|
+
return realpathSync(path);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return resolve(path);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** The uncached git probe (shared by {@link resolveProject} and tests). */
|
|
135
|
+
function probeProject(cwd, exec) {
|
|
136
|
+
const top = exec(['rev-parse', '--show-toplevel'], cwd)?.stdout.trim();
|
|
137
|
+
if (top === undefined || top.length === 0)
|
|
138
|
+
return fallbackProject(cwd);
|
|
139
|
+
const commonRaw = exec(['rev-parse', '--git-common-dir'], cwd)?.stdout.trim();
|
|
140
|
+
if (commonRaw === undefined || commonRaw.length === 0)
|
|
141
|
+
return fallbackProject(cwd);
|
|
142
|
+
const topAbs = canonical(top);
|
|
143
|
+
const commonDir = canonical(resolve(cwd, commonRaw));
|
|
144
|
+
const projectRoot = isLinkedWorktree(commonDir, topAbs) ? dirname(commonDir) : topAbs;
|
|
145
|
+
// Best-effort worktree enumeration; a failure just yields an empty set —
|
|
146
|
+
// the caller's prefix heuristic still covers the main checkout.
|
|
147
|
+
let worktrees = [];
|
|
148
|
+
const porcelain = exec(['worktree', 'list', '--porcelain'], cwd);
|
|
149
|
+
if (porcelain !== undefined) {
|
|
150
|
+
for (const line of porcelain.stdout.split('\n')) {
|
|
151
|
+
if (!line.startsWith('worktree '))
|
|
152
|
+
continue;
|
|
153
|
+
const path = unquoteWorktreePath(line.slice('worktree '.length).trim());
|
|
154
|
+
if (path.length > 0)
|
|
155
|
+
worktrees.push(canonical(path));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { projectRoot, projectKey: projectKeyFor(projectRoot), worktrees };
|
|
159
|
+
}
|
|
160
|
+
/** Degraded identity for a non-git (or un-probeable) cwd. */
|
|
161
|
+
function fallbackProject(cwd) {
|
|
162
|
+
const projectRoot = resolve(cwd);
|
|
163
|
+
return { projectRoot, projectKey: projectKeyFor(projectRoot), worktrees: [] };
|
|
164
|
+
}
|
|
165
|
+
/** `sha256(root)` first 16 hex chars — the per-project bucket key. */
|
|
166
|
+
function projectKeyFor(projectRoot) {
|
|
167
|
+
return createHash('sha256').update(projectRoot).digest('hex').slice(0, 16);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Whether `entryCwd` belongs to the project: it equals the project root or
|
|
171
|
+
* any worktree path, or sits beneath one at a path-separator boundary
|
|
172
|
+
* (`/repo` matches `/repo` and `/repo/x`, never `/repo2`). Both `/` and `\`
|
|
173
|
+
* count as boundary separators so Windows-style recorded cwds are handled
|
|
174
|
+
* regardless of the host separator.
|
|
175
|
+
*/
|
|
176
|
+
export function isProjectMember(entryCwd, project) {
|
|
177
|
+
const entry = resolve(entryCwd);
|
|
178
|
+
const candidates = [project.projectRoot, ...project.worktrees];
|
|
179
|
+
for (const candidate of candidates) {
|
|
180
|
+
if (isWithin(candidate, entry))
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
/** True when `child` equals `parent` or sits beneath it at a separator boundary. */
|
|
186
|
+
function isWithin(parent, child) {
|
|
187
|
+
const p = resolve(parent);
|
|
188
|
+
const c = resolve(child);
|
|
189
|
+
if (c === p)
|
|
190
|
+
return true;
|
|
191
|
+
if (!c.startsWith(p))
|
|
192
|
+
return false;
|
|
193
|
+
const rest = c.slice(p.length);
|
|
194
|
+
return rest.startsWith('/') || rest.startsWith('\\');
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAA;AAcxC,yEAAyE;AACzE,MAAM,CAAC,MAAM,WAAW,GAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IACpD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;QACzC,GAAG;QACH,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,oBAAoB;KAC9B,CAAC,CAAA;IACF,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IACvE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;AAClC,CAAC,CAAA;AAmBD;;;;;;;;;;;;GAYG;AACH,SAAS,gBAAgB,CAAC,SAAiB,EAAE,GAAW;IACtD,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,GAAG,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACnD,OAAO,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;AAC1D,CAAC;AAED,oFAAoF;AACpF,SAAS,mBAAmB,CAAC,IAAY;IACvC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IACrD,uEAAuE;IACvE,2DAA2D;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QACnB,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;YACzB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAAC,GAAG,IAAI,IAAI,CAAC;gBAAC,CAAC,IAAI,CAAC,CAAC;gBAAC,SAAQ;YAAC,CAAC;YACnD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAAC,GAAG,IAAI,IAAI,CAAC;gBAAC,CAAC,IAAI,CAAC,CAAC;gBAAC,SAAQ;YAAC,CAAC;YACnD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAAC,GAAG,IAAI,IAAI,CAAC;gBAAC,CAAC,IAAI,CAAC,CAAC;gBAAC,SAAQ;YAAC,CAAC;QACtE,CAAC;QACD,GAAG,IAAI,EAAE,CAAA;IACX,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,OAAoB,WAAW;IACzE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACxB,MAAM,QAAQ,GAAG,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACzE,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAA;IAE3C,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAEpC,IAAI,IAAI,KAAK,WAAW;QAAE,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACrD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,8EAA8E;AAC9E,MAAM,YAAY,GAAG,IAAI,GAAG,EAAuB,CAAA;AAEnD,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB;IACjC,YAAY,CAAC,KAAK,EAAE,CAAA;AACtB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,SAAS,YAAY,CAAC,GAAW,EAAE,IAAiB;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,CAAA;IACtE,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,eAAe,CAAC,GAAG,CAAC,CAAA;IACtE,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,CAAA;IAC7E,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,eAAe,CAAC,GAAG,CAAC,CAAA;IAElF,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IAC7B,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAA;IACpD,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAErF,yEAAyE;IACzE,gEAAgE;IAChE,IAAI,SAAS,GAAa,EAAE,CAAA;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,CAAA;IAChE,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;gBAAE,SAAQ;YAC3C,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;YACvE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;QACtD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,CAAA;AAC3E,CAAC;AAED,6DAA6D;AAC7D,SAAS,eAAe,CAAC,GAAW;IAClC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;AAC/E,CAAC;AAED,sEAAsE;AACtE,SAAS,aAAa,CAAC,WAAmB;IACxC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAC5E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,OAAuD;IAEvD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/B,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAC9D,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;IAC7C,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,oFAAoF;AACpF,SAAS,QAAQ,CAAC,MAAc,EAAE,KAAa;IAC7C,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACzB,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IACxB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACxB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAClC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;IAC9B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AACtD,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ProviderPanelState } from './store/provider-panel.ts';
|
|
2
|
+
import type { ProviderCore } from './provider-command.ts';
|
|
3
|
+
/** Ordered wizard steps for the add-preset flow (§4.3). */
|
|
4
|
+
export declare const PRESET_STEPS: readonly ["credential", "verify", "done"];
|
|
5
|
+
/** Ordered wizard steps for the custom-provider flow (§4.6). */
|
|
6
|
+
export declare const CUSTOM_STEPS: readonly ["route", "displayName", "baseURL", "protocol", "ref", "key", "models", "done"];
|
|
7
|
+
/** Rotate/replace-key flow (§4.4): one masked key step. */
|
|
8
|
+
export declare const ROTATE_STEPS: readonly ["key"];
|
|
9
|
+
/** §4.6 protocol single-select options (all three wire protocols). */
|
|
10
|
+
export declare const CUSTOM_PROTOCOLS: readonly ["openai-completions", "openai-responses", "anthropic-messages"];
|
|
11
|
+
/** Wizard steps that render a live text `Input` (masked for secrets). */
|
|
12
|
+
export declare const TEXT_STEPS: Record<string, {
|
|
13
|
+
masked: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
/** Line shown when the environment shadows a credential ref (§7, C4). */
|
|
16
|
+
export declare function envShadowLine(ref: string): string;
|
|
17
|
+
export declare function credentialRefFor(route: string): string;
|
|
18
|
+
/** The live wizard step, if a wizard is open. */
|
|
19
|
+
export declare const currentStep: (panel: ProviderPanelState | undefined) => string | undefined;
|
|
20
|
+
/** Clear the wizard text field and the transient key buffer (§6). */
|
|
21
|
+
export declare const clearField: (core: ProviderCore) => void;
|
|
22
|
+
/** Open a fresh text field for the wizard's active step (prefilled). */
|
|
23
|
+
export declare const openField: (core: ProviderCore, panel: ProviderPanelState | undefined) => void;
|
|
24
|
+
/** Drop and reopen the text field for the (advanced) wizard step. */
|
|
25
|
+
export declare const reopenField: (core: ProviderCore, panel: ProviderPanelState | undefined) => void;
|
|
26
|
+
/**
|
|
27
|
+
* The wizard action flows (§4.3 add, §4.6 custom): returns the runtime
|
|
28
|
+
* members that drive the wizard, closing over the field/secret buffers and
|
|
29
|
+
* the shared helpers on `core`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createActions(core: ProviderCore): {
|
|
32
|
+
startPresetWizard(route: string): Promise<void>;
|
|
33
|
+
wizardSubmit(panel: ProviderPanelState): void;
|
|
34
|
+
fetchModels(panel: ProviderPanelState): void;
|
|
35
|
+
removeJustAdded(panel: ProviderPanelState): void;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=provider-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-actions.d.ts","sourceRoot":"","sources":["../src/provider-actions.ts"],"names":[],"mappings":"AAUA,OAAO,EASL,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAA;AAelC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEzD,2DAA2D;AAC3D,eAAO,MAAM,YAAY,2CAA4C,CAAA;AAErE,gEAAgE;AAChE,eAAO,MAAM,YAAY,0FAA2F,CAAA;AAEpH,2DAA2D;AAC3D,eAAO,MAAM,YAAY,kBAAmB,CAAA;AAE5C,sEAAsE;AACtE,eAAO,MAAM,gBAAgB,2EAA4E,CAAA;AAEzG,yEAAyE;AACzE,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAQ1D,CAAA;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,iDAAiD;AACjD,eAAO,MAAM,WAAW,GAAI,OAAO,kBAAkB,GAAG,SAAS,KAAG,MAAM,GAAG,SAG5E,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,UAAU,GAAI,MAAM,YAAY,KAAG,IAI/C,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,SAAS,GAAI,MAAM,YAAY,EAAE,OAAO,kBAAkB,GAAG,SAAS,KAAG,IAYrF,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,WAAW,GAAI,MAAM,YAAY,EAAE,OAAO,kBAAkB,GAAG,SAAS,KAAG,IAGvF,CAAA;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG;IACjD,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC7C,WAAW,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAC5C,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAA;CACjD,CAqUA"}
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/provider` action wizard engines (design doc docs/plans/2026-09-05-provider-
|
|
3
|
+
* management.md §4.3 add-preset, §4.4 rotate, §4.6 custom, §8-S2 verify probe):
|
|
4
|
+
* the wizard text field, step submissions, persistence, and the settings
|
|
5
|
+
* path-op write. Split out of provider-command.ts purely for line budget.
|
|
6
|
+
* @module @dsh-cc/tui/provider-command-actions
|
|
7
|
+
*/
|
|
8
|
+
import { Input } from '@dsh-cc/pi-tui';
|
|
9
|
+
import { MaskedInput } from "./components/masked-input.js";
|
|
10
|
+
import { setProviderOverlay } from "./store.js";
|
|
11
|
+
import { backFromWizard, setMessage, startWizardFor, wizardNext, wizardSetAnswer, wizardSetModelErrors, wizardSetNote, wizardSetVerify, } from "./store/provider-panel.js";
|
|
12
|
+
import { materializeCustomProfile, materializeProfile, normalizeRouteId, routeCollision, parseModelList, verifyProbeFor, } from "./provider-flow.js";
|
|
13
|
+
import { PRESETS, deriveCredentialRef, presetByRoute } from "./provider-presets.js";
|
|
14
|
+
import { PROVIDER_SETTINGS_NAMESPACE, readConfiguredProviders, loadDirectory } from "./provider-read.js";
|
|
15
|
+
import { setAsDefault, settingsWriteOf, writeRoute } from "./provider-settings.js";
|
|
16
|
+
/** Ordered wizard steps for the add-preset flow (§4.3). */
|
|
17
|
+
export const PRESET_STEPS = ['credential', 'verify', 'done'];
|
|
18
|
+
/** Ordered wizard steps for the custom-provider flow (§4.6). */
|
|
19
|
+
export const CUSTOM_STEPS = ['route', 'displayName', 'baseURL', 'protocol', 'ref', 'key', 'models', 'done'];
|
|
20
|
+
/** Rotate/replace-key flow (§4.4): one masked key step. */
|
|
21
|
+
export const ROTATE_STEPS = ['key'];
|
|
22
|
+
/** §4.6 protocol single-select options (all three wire protocols). */
|
|
23
|
+
export const CUSTOM_PROTOCOLS = ['openai-completions', 'openai-responses', 'anthropic-messages'];
|
|
24
|
+
/** Wizard steps that render a live text `Input` (masked for secrets). */
|
|
25
|
+
export const TEXT_STEPS = {
|
|
26
|
+
credential: { masked: true },
|
|
27
|
+
key: { masked: true },
|
|
28
|
+
route: { masked: false },
|
|
29
|
+
displayName: { masked: false },
|
|
30
|
+
baseURL: { masked: false },
|
|
31
|
+
ref: { masked: false },
|
|
32
|
+
models: { masked: false },
|
|
33
|
+
};
|
|
34
|
+
/** Line shown when the environment shadows a credential ref (§7, C4). */
|
|
35
|
+
export function envShadowLine(ref) {
|
|
36
|
+
return `${ref} is supplied by your environment and read-only in this UI — unset the environment variable to manage it here.`;
|
|
37
|
+
}
|
|
38
|
+
export function credentialRefFor(route) {
|
|
39
|
+
return presetByRoute(route)?.credentialRef ?? deriveCredentialRef(route);
|
|
40
|
+
}
|
|
41
|
+
/** The live wizard step, if a wizard is open. */
|
|
42
|
+
export const currentStep = (panel) => {
|
|
43
|
+
const wizard = panel?.wizard;
|
|
44
|
+
return wizard === undefined ? undefined : wizard.steps[wizard.stepIndex];
|
|
45
|
+
};
|
|
46
|
+
/** Clear the wizard text field and the transient key buffer (§6). */
|
|
47
|
+
export const clearField = (core) => {
|
|
48
|
+
if (core.buf.field !== undefined)
|
|
49
|
+
core.buf.field.setValue('');
|
|
50
|
+
core.buf.field = undefined;
|
|
51
|
+
core.buf.secret = undefined;
|
|
52
|
+
};
|
|
53
|
+
/** Open a fresh text field for the wizard's active step (prefilled). */
|
|
54
|
+
export const openField = (core, panel) => {
|
|
55
|
+
const step = currentStep(panel);
|
|
56
|
+
const spec = step === undefined ? undefined : TEXT_STEPS[step];
|
|
57
|
+
if (spec === undefined || panel?.wizard === undefined || step === undefined)
|
|
58
|
+
return;
|
|
59
|
+
const input = spec.masked ? new MaskedInput() : new Input();
|
|
60
|
+
const wizard = panel.wizard;
|
|
61
|
+
if (!spec.masked && step !== 'models') {
|
|
62
|
+
const answer = wizard.answers[step];
|
|
63
|
+
const prefill = step === 'route' && answer === undefined ? wizard.answers['route'] : answer;
|
|
64
|
+
if (prefill !== undefined && prefill !== '')
|
|
65
|
+
input.setValue(prefill);
|
|
66
|
+
}
|
|
67
|
+
core.buf.field = input;
|
|
68
|
+
};
|
|
69
|
+
/** Drop and reopen the text field for the (advanced) wizard step. */
|
|
70
|
+
export const reopenField = (core, panel) => {
|
|
71
|
+
core.buf.field = undefined;
|
|
72
|
+
openField(core, panel);
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The wizard action flows (§4.3 add, §4.6 custom): returns the runtime
|
|
76
|
+
* members that drive the wizard, closing over the field/secret buffers and
|
|
77
|
+
* the shared helpers on `core`.
|
|
78
|
+
*/
|
|
79
|
+
export function createActions(core) {
|
|
80
|
+
const { rt, buf } = core;
|
|
81
|
+
const submitKey = (panel, step, value) => {
|
|
82
|
+
const wizard = panel.wizard;
|
|
83
|
+
if (value === '') {
|
|
84
|
+
// Empty is absent (C4): never write a blank key.
|
|
85
|
+
clearField(core);
|
|
86
|
+
rt.emit(setProviderOverlay(rt.state(), setMessage(backFromWizard(panel), 'No key entered — nothing was written.')));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
buf.secret = value;
|
|
90
|
+
if (step === 'key' && wizard.kind === 'rotate') {
|
|
91
|
+
core.begin(rotateKey(wizard.route, value));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (step === 'key' && wizard.kind === 'custom') {
|
|
95
|
+
// The custom flow persists at the models step; keep the key buffered.
|
|
96
|
+
buf.field?.setValue('');
|
|
97
|
+
rt.emit(setProviderOverlay(rt.state(), wizardNext(panel)));
|
|
98
|
+
reopenField(core, rt.state().providerPanel);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Preset credential step: persist key then profile, then verify (§4.3.2).
|
|
102
|
+
core.begin(persistPreset(wizard.route, value));
|
|
103
|
+
};
|
|
104
|
+
const submitText = (panel, step, value) => {
|
|
105
|
+
const wizard = panel.wizard;
|
|
106
|
+
if (step === 'route') {
|
|
107
|
+
const normalized = normalizeRouteId(value);
|
|
108
|
+
if (normalized === null) {
|
|
109
|
+
buf.field?.setValue('');
|
|
110
|
+
core.patch(p => wizardSetNote(p, 'Enter a route id using lowercase letters, numbers, and dashes.'));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const collision = routeCollision(normalized, {
|
|
114
|
+
configured: readConfiguredProviders(settingsWriteOf(core)),
|
|
115
|
+
presets: PRESETS,
|
|
116
|
+
directory: loadDirectory(rt.ctx.get('llm')),
|
|
117
|
+
});
|
|
118
|
+
if (collision !== null) {
|
|
119
|
+
const what = collision === 'configured' ? 'already configured' : collision === 'preset' ? 'offered as a built-in preset' : 'known to the provider directory';
|
|
120
|
+
buf.field?.setValue('');
|
|
121
|
+
core.patch(p => wizardSetNote(p, `Route "${normalized}" is ${what} — pick another id.`));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
core.patch(p => wizardSetAnswer(wizardNext(wizardSetAnswer(p, 'ref', deriveCredentialRef(normalized))), 'route', normalized));
|
|
125
|
+
reopenField(core, rt.state().providerPanel);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (step === 'displayName') {
|
|
129
|
+
if (value.trim() === '') {
|
|
130
|
+
buf.field?.setValue('');
|
|
131
|
+
core.patch(p => wizardSetNote(p, 'Enter a display name.'));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
core.patch(p => wizardSetAnswer(wizardNext(p), 'displayName', value.trim()));
|
|
135
|
+
reopenField(core, rt.state().providerPanel);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (step === 'baseURL') {
|
|
139
|
+
let parsed;
|
|
140
|
+
try {
|
|
141
|
+
parsed = new URL(value.trim());
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
buf.field?.setValue('');
|
|
145
|
+
core.patch(p => wizardSetNote(p, 'baseURL must be an absolute http(s) URL — e.g. https://gw.example.com/v1'));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
149
|
+
buf.field?.setValue('');
|
|
150
|
+
core.patch(p => wizardSetNote(p, 'baseURL must be an absolute http(s) URL — e.g. https://gw.example.com/v1'));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
core.patch(p => wizardSetAnswer(wizardNext(p), 'baseURL', value.trim()));
|
|
154
|
+
reopenField(core, rt.state().providerPanel);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (step === 'ref') {
|
|
158
|
+
const ref = value.trim() === '' ? deriveCredentialRef(wizard.answers['route'] ?? wizard.route) : value.trim();
|
|
159
|
+
core.patch(p => wizardSetAnswer(wizardNext(p), 'ref', ref));
|
|
160
|
+
reopenField(core, rt.state().providerPanel);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (step === 'models') {
|
|
164
|
+
const { models, errors } = parseModelList(buf.field?.getValue() ?? '');
|
|
165
|
+
if (errors.length > 0) {
|
|
166
|
+
// Per-line errors; the field keeps its text so the user can fix it.
|
|
167
|
+
core.patch(p => wizardSetModelErrors(p, errors));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
core.patch(p => wizardSetAnswer(p, 'models', JSON.stringify(models)));
|
|
171
|
+
core.begin(persistCustom());
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const rotateKey = async (route, key) => {
|
|
175
|
+
const ref = credentialRefFor(route);
|
|
176
|
+
try {
|
|
177
|
+
await rt.ctx.get('credentials')?.set(ref, key);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
clearField(core);
|
|
181
|
+
core.patch(p => setMessage(p, envShadowLine(ref)));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
clearField(core);
|
|
185
|
+
core.patch(p => {
|
|
186
|
+
const { wizard: _dropped, ...rest } = p;
|
|
187
|
+
return setMessage({ ...rest, phase: 'detail' }, `Key for ${ref} updated — credentials resolve per request, so the next message uses it.`);
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const persistPreset = async (route, key) => {
|
|
191
|
+
const preset = presetByRoute(route);
|
|
192
|
+
if (preset === undefined)
|
|
193
|
+
return;
|
|
194
|
+
const ref = preset.credentialRef;
|
|
195
|
+
try {
|
|
196
|
+
await rt.ctx.get('credentials')?.set(ref, key);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Env-shadow rejection: explain, keep the wizard alive on the key step.
|
|
200
|
+
clearField(core);
|
|
201
|
+
core.patch(p => wizardSetNote(p, envShadowLine(ref)));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const profile = { ...materializeProfile(preset), apiKeyEnv: ref };
|
|
205
|
+
const error = await writeRoute(core, { op: 'set', path: ['providers', route], value: profile });
|
|
206
|
+
clearField(core);
|
|
207
|
+
if (error !== undefined) {
|
|
208
|
+
core.patch(p => wizardSetNote(p, error));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
core.patch(p => wizardNext(p));
|
|
212
|
+
await runVerify(preset, key);
|
|
213
|
+
};
|
|
214
|
+
const runVerify = async (preset, key) => {
|
|
215
|
+
// Draft-form probe (C5, review Blocker B1): NEVER the provider form.
|
|
216
|
+
const probe = verifyProbeFor({ baseURL: preset.probe?.baseURL, api: preset.probe?.api, apiKey: key });
|
|
217
|
+
if (probe === null) {
|
|
218
|
+
buf.secret = undefined;
|
|
219
|
+
core.patch(p => wizardSetVerify(wizardNext(p), {
|
|
220
|
+
status: 'skipped',
|
|
221
|
+
message: "This endpoint can't be listed programmatically — the first message is the test.",
|
|
222
|
+
}));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const llm = rt.ctx.get('llm');
|
|
226
|
+
if (llm === undefined || typeof llm.discoverModels !== 'function') {
|
|
227
|
+
buf.secret = undefined;
|
|
228
|
+
core.patch(p => wizardSetVerify(wizardNext(p), {
|
|
229
|
+
status: 'skipped',
|
|
230
|
+
message: 'Model discovery is unavailable in this profile — the probe was skipped; the first message is the test.',
|
|
231
|
+
}));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const controller = new AbortController();
|
|
235
|
+
const timer = setTimeout(() => controller.abort(), 15000);
|
|
236
|
+
try {
|
|
237
|
+
const models = await llm.discoverModels(PROVIDER_SETTINGS_NAMESPACE, { ...probe, signal: controller.signal });
|
|
238
|
+
core.patch(p => wizardSetVerify(wizardNext(p), { status: 'ok', message: `${models.length} models reachable` }));
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
242
|
+
core.patch(p => wizardSetVerify(p, {
|
|
243
|
+
status: 'failed',
|
|
244
|
+
message: `Verification failed: ${reason} — the configuration was kept; enter keeps it, x removes it.`,
|
|
245
|
+
}));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
buf.secret = undefined;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const persistCustom = async () => {
|
|
254
|
+
const wizard = rt.state().providerPanel?.wizard;
|
|
255
|
+
if (wizard === undefined)
|
|
256
|
+
return;
|
|
257
|
+
const route = wizard.answers['route'] ?? '';
|
|
258
|
+
const ref = wizard.answers['ref'] ?? deriveCredentialRef(route);
|
|
259
|
+
let models = [];
|
|
260
|
+
try {
|
|
261
|
+
models = JSON.parse(wizard.answers['models'] ?? '[]');
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
models = [];
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
await rt.ctx.get('credentials')?.set(ref, buf.secret ?? '');
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
clearField(core);
|
|
271
|
+
core.patch(p => wizardSetNote(p, envShadowLine(ref)));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
let profile;
|
|
275
|
+
try {
|
|
276
|
+
profile = {
|
|
277
|
+
apiKeyEnv: ref,
|
|
278
|
+
...materializeCustomProfile({
|
|
279
|
+
routeId: route,
|
|
280
|
+
displayName: wizard.answers['displayName'] ?? route,
|
|
281
|
+
baseURL: wizard.answers['baseURL'] ?? '',
|
|
282
|
+
api: wizard.answers['api'] ?? '',
|
|
283
|
+
...(models.length > 0 ? { models } : {}),
|
|
284
|
+
}),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
clearField(core);
|
|
289
|
+
core.patch(p => wizardSetNote(p, error instanceof Error ? error.message : String(error)));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const error = await writeRoute(core, { op: 'set', path: ['providers', route], value: profile });
|
|
293
|
+
clearField(core);
|
|
294
|
+
if (error !== undefined) {
|
|
295
|
+
core.patch(p => wizardSetNote(p, error));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
core.patch(p => setMessage(wizardNext(p), undefined));
|
|
299
|
+
};
|
|
300
|
+
return {
|
|
301
|
+
/** §4.3 add flow for a preset (custom wizard seeded for unknown ids). */
|
|
302
|
+
async startPresetWizard(route) {
|
|
303
|
+
const preset = presetByRoute(route);
|
|
304
|
+
const panel = rt.state().providerPanel;
|
|
305
|
+
if (panel === undefined)
|
|
306
|
+
return;
|
|
307
|
+
if (preset === undefined) {
|
|
308
|
+
// Unknown id: run the custom wizard with the id seeded (§4.6 shape).
|
|
309
|
+
const normalized = normalizeRouteId(route) ?? '';
|
|
310
|
+
rt.emit(setProviderOverlay(rt.state(), startWizardFor(panel, '', [...CUSTOM_STEPS], {
|
|
311
|
+
kind: 'custom',
|
|
312
|
+
...(normalized === '' ? {} : { answers: { route: normalized, ref: deriveCredentialRef(normalized) } }),
|
|
313
|
+
})));
|
|
314
|
+
openField(core, rt.state().providerPanel);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
void (async () => {
|
|
318
|
+
const ref = preset.credentialRef;
|
|
319
|
+
const state = await core.describeRef(ref);
|
|
320
|
+
const fresh = rt.state().providerPanel;
|
|
321
|
+
if (fresh === undefined || fresh.phase !== 'list')
|
|
322
|
+
return;
|
|
323
|
+
if (state?.configured === true && state.source === 'env') {
|
|
324
|
+
// Env-shadowed: skip the key step with the one-line explanation (§4.3.1).
|
|
325
|
+
rt.emit(setProviderOverlay(rt.state(), wizardSetNote(wizardNext(startWizardFor(fresh, route, [...PRESET_STEPS], {
|
|
326
|
+
kind: 'preset',
|
|
327
|
+
answers: { ref, credential: 'env' },
|
|
328
|
+
})), `${ref} is already supplied by your environment — the key step was skipped.`)));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
rt.emit(setProviderOverlay(rt.state(), startWizardFor(fresh, route, [...PRESET_STEPS], { kind: 'preset', answers: { ref } })));
|
|
332
|
+
openField(core, rt.state().providerPanel);
|
|
333
|
+
})();
|
|
334
|
+
},
|
|
335
|
+
/** Advance the wizard from its current step. */
|
|
336
|
+
wizardSubmit(panel) {
|
|
337
|
+
const wizard = panel.wizard;
|
|
338
|
+
if (wizard === undefined)
|
|
339
|
+
return;
|
|
340
|
+
const step = wizard.steps[wizard.stepIndex];
|
|
341
|
+
if (step === undefined)
|
|
342
|
+
return;
|
|
343
|
+
if (step === 'protocol') {
|
|
344
|
+
const api = CUSTOM_PROTOCOLS[wizard.selectIndex ?? 0] ?? CUSTOM_PROTOCOLS[0];
|
|
345
|
+
core.patch(p => wizardSetAnswer(wizardNext(p), 'api', api));
|
|
346
|
+
reopenField(core, rt.state().providerPanel);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (step === 'verify') {
|
|
350
|
+
// Enter = keep (§4.3.2): the config stays even when the probe failed.
|
|
351
|
+
if (wizard.verify?.status === 'failed')
|
|
352
|
+
core.patch(p => wizardSetNote(wizardNext(p), undefined));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (step === 'done') {
|
|
356
|
+
core.begin(setAsDefault(core, wizard.kind === 'custom' ? wizard.answers['route'] ?? wizard.route : wizard.route));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const spec = TEXT_STEPS[step];
|
|
360
|
+
if (spec === undefined)
|
|
361
|
+
return;
|
|
362
|
+
const value = buf.field?.getValue() ?? '';
|
|
363
|
+
if (spec.masked)
|
|
364
|
+
submitKey(panel, step, value);
|
|
365
|
+
else
|
|
366
|
+
submitText(panel, step, value);
|
|
367
|
+
},
|
|
368
|
+
/** Models-step fetch (tab): draft-form probe → prefill. */
|
|
369
|
+
fetchModels(panel) {
|
|
370
|
+
const wizard = panel.wizard;
|
|
371
|
+
if (wizard === undefined || wizard.steps[wizard.stepIndex] !== 'models')
|
|
372
|
+
return;
|
|
373
|
+
const probe = verifyProbeFor({ baseURL: wizard.answers['baseURL'], api: wizard.answers['api'], apiKey: buf.secret });
|
|
374
|
+
if (probe === null) {
|
|
375
|
+
core.patch(p => wizardSetNote(p, "This endpoint can't be listed programmatically — enter the model ids manually."));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const llm = rt.ctx.get('llm');
|
|
379
|
+
if (llm === undefined || typeof llm.discoverModels !== 'function') {
|
|
380
|
+
core.patch(p => wizardSetNote(p, 'Model discovery is unavailable in this profile — enter the model ids manually.'));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
core.begin((async () => {
|
|
384
|
+
try {
|
|
385
|
+
const models = await llm.discoverModels(PROVIDER_SETTINGS_NAMESPACE, probe);
|
|
386
|
+
buf.field?.setValue(models.map(m => m.id).join(', '));
|
|
387
|
+
core.patch(p => wizardSetNote(p, undefined));
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
391
|
+
core.patch(p => wizardSetNote(p, `Model fetch failed: ${reason} — enter the ids manually.`));
|
|
392
|
+
}
|
|
393
|
+
})());
|
|
394
|
+
},
|
|
395
|
+
/** keep/remove choice on a failed verify — remove unsets the just-written route. */
|
|
396
|
+
removeJustAdded(panel) {
|
|
397
|
+
const route = panel.wizard?.route;
|
|
398
|
+
if (route === undefined || route === '')
|
|
399
|
+
return;
|
|
400
|
+
core.begin((async () => {
|
|
401
|
+
const error = await writeRoute(core, { op: 'unset', path: ['providers', route] });
|
|
402
|
+
if (error !== undefined) {
|
|
403
|
+
core.patch(p => setMessage(p, `Removal failed: ${error}`));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
await rt.ctx.get('credentials')?.unset(credentialRefFor(route));
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
// Best-effort: the route removal stands regardless.
|
|
411
|
+
}
|
|
412
|
+
core.patch(p => setMessage(backFromWizard(p), `Removed ${route}.`));
|
|
413
|
+
})());
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
//# sourceMappingURL=provider-actions.js.map
|