@prover-coder-ai/docker-git 1.0.20 → 1.0.22
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/.package.json.release.bak +4 -3
- package/CHANGELOG.md +12 -0
- package/README.md +28 -1
- package/dist/src/docker-git/main.js +10256 -12
- package/dist/src/docker-git/main.js.map +1 -0
- package/package.json +3 -4
- package/src/docker-git/cli/parser-apply.ts +28 -0
- package/src/docker-git/cli/parser-clone.ts +3 -9
- package/src/docker-git/cli/parser-options.ts +71 -22
- package/src/docker-git/cli/parser.ts +2 -0
- package/src/docker-git/cli/usage.ts +11 -3
- package/src/docker-git/menu-actions.ts +5 -2
- package/src/docker-git/menu-create.ts +9 -13
- package/src/docker-git/menu-render.ts +1 -1
- package/src/docker-git/program.ts +2 -0
- package/tests/docker-git/entrypoint-auth.test.ts +14 -3
- package/tests/docker-git/parser-network-options.test.ts +47 -0
- package/tests/docker-git/parser.test.ts +105 -18
- package/vite.docker-git.config.ts +34 -0
- package/dist/main.js +0 -905
- package/dist/main.js.map +0 -1
- package/dist/src/app/main.js +0 -15
- package/dist/src/app/program.js +0 -61
- package/dist/src/docker-git/cli/input.js +0 -21
- package/dist/src/docker-git/cli/parser-attach.js +0 -19
- package/dist/src/docker-git/cli/parser-auth.js +0 -90
- package/dist/src/docker-git/cli/parser-clone.js +0 -41
- package/dist/src/docker-git/cli/parser-create.js +0 -1
- package/dist/src/docker-git/cli/parser-mcp-playwright.js +0 -18
- package/dist/src/docker-git/cli/parser-options.js +0 -109
- package/dist/src/docker-git/cli/parser-panes.js +0 -19
- package/dist/src/docker-git/cli/parser-scrap.js +0 -74
- package/dist/src/docker-git/cli/parser-sessions.js +0 -69
- package/dist/src/docker-git/cli/parser-shared.js +0 -26
- package/dist/src/docker-git/cli/parser-state.js +0 -62
- package/dist/src/docker-git/cli/parser.js +0 -46
- package/dist/src/docker-git/cli/read-command.js +0 -17
- package/dist/src/docker-git/cli/usage.js +0 -108
- package/dist/src/docker-git/menu-actions.js +0 -135
- package/dist/src/docker-git/menu-auth-data.js +0 -90
- package/dist/src/docker-git/menu-auth-helpers.js +0 -20
- package/dist/src/docker-git/menu-auth.js +0 -159
- package/dist/src/docker-git/menu-buffer-input.js +0 -9
- package/dist/src/docker-git/menu-create.js +0 -199
- package/dist/src/docker-git/menu-input-handler.js +0 -109
- package/dist/src/docker-git/menu-input-utils.js +0 -47
- package/dist/src/docker-git/menu-input.js +0 -2
- package/dist/src/docker-git/menu-labeled-env.js +0 -33
- package/dist/src/docker-git/menu-menu.js +0 -46
- package/dist/src/docker-git/menu-project-auth-claude.js +0 -43
- package/dist/src/docker-git/menu-project-auth-data.js +0 -165
- package/dist/src/docker-git/menu-project-auth.js +0 -124
- package/dist/src/docker-git/menu-render-auth.js +0 -45
- package/dist/src/docker-git/menu-render-common.js +0 -26
- package/dist/src/docker-git/menu-render-layout.js +0 -14
- package/dist/src/docker-git/menu-render-project-auth.js +0 -37
- package/dist/src/docker-git/menu-render-select.js +0 -129
- package/dist/src/docker-git/menu-render.js +0 -137
- package/dist/src/docker-git/menu-select-actions.js +0 -66
- package/dist/src/docker-git/menu-select-connect.js +0 -6
- package/dist/src/docker-git/menu-select-load.js +0 -12
- package/dist/src/docker-git/menu-select-order.js +0 -21
- package/dist/src/docker-git/menu-select-runtime.js +0 -82
- package/dist/src/docker-git/menu-select-view.js +0 -15
- package/dist/src/docker-git/menu-select.js +0 -98
- package/dist/src/docker-git/menu-shared.js +0 -180
- package/dist/src/docker-git/menu-startup.js +0 -57
- package/dist/src/docker-git/menu-types.js +0 -21
- package/dist/src/docker-git/menu.js +0 -226
- package/dist/src/docker-git/program.js +0 -42
- package/dist/src/docker-git/tmux.js +0 -176
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Box, Text } from "ink";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { projectAuthMenuLabels, projectAuthViewSteps } from "./menu-project-auth-data.js";
|
|
4
|
-
import { renderMenuHelp, renderPromptLayout, renderSelectableMenuList, resolvePromptState } from "./menu-render-common.js";
|
|
5
|
-
import { renderLayout } from "./menu-render-layout.js";
|
|
6
|
-
const renderActiveLabel = (value) => value ?? "(not set)";
|
|
7
|
-
const renderCountLine = (title, count) => `${title}: ${count}`;
|
|
8
|
-
export const renderProjectAuthMenu = (snapshot, selected, message) => {
|
|
9
|
-
const el = React.createElement;
|
|
10
|
-
const list = renderSelectableMenuList(projectAuthMenuLabels(), selected);
|
|
11
|
-
return renderLayout("docker-git / Project auth", [
|
|
12
|
-
el(Text, null, `Project: ${snapshot.projectName}`),
|
|
13
|
-
el(Text, { color: "gray" }, `Dir: ${snapshot.projectDir}`),
|
|
14
|
-
el(Text, { color: "gray" }, `Project env: ${snapshot.envProjectPath}`),
|
|
15
|
-
el(Text, { color: "gray" }, `Global env: ${snapshot.envGlobalPath}`),
|
|
16
|
-
el(Text, { color: "gray" }, `Claude auth: ${snapshot.claudeAuthPath}`),
|
|
17
|
-
el(Box, { marginTop: 1, flexDirection: "column" }, el(Text, { color: "gray" }, `GitHub label: ${renderActiveLabel(snapshot.activeGithubLabel)}`), el(Text, { color: "gray" }, renderCountLine("Available GitHub tokens", snapshot.githubTokenEntries)), el(Text, { color: "gray" }, `Git label: ${renderActiveLabel(snapshot.activeGitLabel)}`), el(Text, { color: "gray" }, renderCountLine("Available Git tokens", snapshot.gitTokenEntries)), el(Text, { color: "gray" }, `Claude label: ${renderActiveLabel(snapshot.activeClaudeLabel)}`), el(Text, { color: "gray" }, renderCountLine("Available Claude logins", snapshot.claudeAuthEntries))),
|
|
18
|
-
el(Box, { flexDirection: "column", marginTop: 1 }, ...list),
|
|
19
|
-
renderMenuHelp("Use arrows + Enter, or type a number from the list.")
|
|
20
|
-
], message);
|
|
21
|
-
};
|
|
22
|
-
export const renderProjectAuthPrompt = (view, message) => {
|
|
23
|
-
const el = React.createElement;
|
|
24
|
-
const { prompt, visibleBuffer } = resolvePromptState(projectAuthViewSteps(view.flow), view.step, view.buffer);
|
|
25
|
-
return renderPromptLayout({
|
|
26
|
-
title: "docker-git / Project auth / Set label",
|
|
27
|
-
header: [
|
|
28
|
-
el(Text, { color: "gray" }, `Project: ${view.snapshot.projectName}`),
|
|
29
|
-
el(Text, { color: "gray" }, `Project env: ${view.snapshot.envProjectPath}`),
|
|
30
|
-
el(Text, { color: "gray" }, `Global env: ${view.snapshot.envGlobalPath}`)
|
|
31
|
-
],
|
|
32
|
-
prompt,
|
|
33
|
-
visibleBuffer,
|
|
34
|
-
helpLine: "Enter = apply, Esc = cancel.",
|
|
35
|
-
message
|
|
36
|
-
});
|
|
37
|
-
};
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { Match } from "effect";
|
|
2
|
-
import { Text } from "ink";
|
|
3
|
-
const formatRepoRef = (repoRef) => {
|
|
4
|
-
const trimmed = repoRef.trim();
|
|
5
|
-
const prPrefix = "refs/pull/";
|
|
6
|
-
if (trimmed.startsWith(prPrefix)) {
|
|
7
|
-
const rest = trimmed.slice(prPrefix.length);
|
|
8
|
-
const number = rest.split("/")[0] ?? rest;
|
|
9
|
-
return `PR#${number}`;
|
|
10
|
-
}
|
|
11
|
-
return trimmed.length > 0 ? trimmed : "main";
|
|
12
|
-
};
|
|
13
|
-
const stoppedRuntime = () => ({
|
|
14
|
-
running: false,
|
|
15
|
-
sshSessions: 0,
|
|
16
|
-
startedAtIso: null,
|
|
17
|
-
startedAtEpochMs: null
|
|
18
|
-
});
|
|
19
|
-
const pad2 = (value) => value.toString().padStart(2, "0");
|
|
20
|
-
const formatUtcTimestamp = (epochMs, withSeconds) => {
|
|
21
|
-
const date = new Date(epochMs);
|
|
22
|
-
const seconds = withSeconds ? `:${pad2(date.getUTCSeconds())}` : "";
|
|
23
|
-
return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}${seconds} UTC`;
|
|
24
|
-
};
|
|
25
|
-
const renderStartedAtCompact = (runtime) => runtime.startedAtEpochMs === null ? "-" : formatUtcTimestamp(runtime.startedAtEpochMs, false);
|
|
26
|
-
const renderStartedAtDetailed = (runtime) => runtime.startedAtEpochMs === null ? "not available" : formatUtcTimestamp(runtime.startedAtEpochMs, true);
|
|
27
|
-
const runtimeForProject = (runtimeByProject, item) => runtimeByProject[item.projectDir] ?? stoppedRuntime();
|
|
28
|
-
const renderRuntimeLabel = (runtime) => `${runtime.running ? "running" : "stopped"}, ssh=${runtime.sshSessions}, started=${renderStartedAtCompact(runtime)}`;
|
|
29
|
-
export const selectTitle = (purpose) => Match.value(purpose).pipe(Match.when("Connect", () => "docker-git / Select project"), Match.when("Auth", () => "docker-git / Project auth"), Match.when("Down", () => "docker-git / Stop container"), Match.when("Info", () => "docker-git / Show connection info"), Match.when("Delete", () => "docker-git / Delete project"), Match.exhaustive);
|
|
30
|
-
export const selectHint = (purpose, connectEnableMcpPlaywright) => Match.value(purpose).pipe(Match.when("Connect", () => `Enter = select + SSH, P = toggle Playwright MCP (${connectEnableMcpPlaywright ? "on" : "off"}), Esc = back`), Match.when("Auth", () => "Enter = open project auth menu, Esc = back"), Match.when("Down", () => "Enter = stop container, Esc = back"), Match.when("Info", () => "Use arrows to browse details, Enter = set active, Esc = back"), Match.when("Delete", () => "Enter = ask/confirm delete, Esc = cancel"), Match.exhaustive);
|
|
31
|
-
export const buildSelectLabels = (items, selected, purpose, runtimeByProject) => items.map((item, index) => {
|
|
32
|
-
const prefix = index === selected ? ">" : " ";
|
|
33
|
-
const refLabel = formatRepoRef(item.repoRef);
|
|
34
|
-
const runtime = runtimeForProject(runtimeByProject, item);
|
|
35
|
-
const runtimeSuffix = purpose === "Down" || purpose === "Delete"
|
|
36
|
-
? ` [${renderRuntimeLabel(runtime)}]`
|
|
37
|
-
: ` [started=${renderStartedAtCompact(runtime)}]`;
|
|
38
|
-
return `${prefix} ${index + 1}. ${item.displayName} (${refLabel})${runtimeSuffix}`;
|
|
39
|
-
});
|
|
40
|
-
export const buildSelectListWindow = (total, selected, maxVisible) => {
|
|
41
|
-
if (total <= 0) {
|
|
42
|
-
return { start: 0, end: 0 };
|
|
43
|
-
}
|
|
44
|
-
const visible = Math.max(1, maxVisible);
|
|
45
|
-
if (total <= visible) {
|
|
46
|
-
return { start: 0, end: total };
|
|
47
|
-
}
|
|
48
|
-
const boundedSelected = Math.min(Math.max(selected, 0), total - 1);
|
|
49
|
-
const half = Math.floor(visible / 2);
|
|
50
|
-
const maxStart = total - visible;
|
|
51
|
-
const start = Math.min(Math.max(boundedSelected - half, 0), maxStart);
|
|
52
|
-
return { start, end: start + visible };
|
|
53
|
-
};
|
|
54
|
-
const buildDetailsContext = (item, runtimeByProject) => {
|
|
55
|
-
const runtime = runtimeForProject(runtimeByProject, item);
|
|
56
|
-
return {
|
|
57
|
-
item,
|
|
58
|
-
refLabel: formatRepoRef(item.repoRef),
|
|
59
|
-
authSuffix: item.authorizedKeysExists ? "" : " (missing)",
|
|
60
|
-
runtime,
|
|
61
|
-
sshSessionsLabel: runtime.sshSessions === 1
|
|
62
|
-
? "1 active SSH session"
|
|
63
|
-
: `${runtime.sshSessions} active SSH sessions`
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
const titleRow = (el, value) => el(Text, { color: "cyan", bold: true, wrap: "truncate" }, value);
|
|
67
|
-
const commonRows = (el, context) => [
|
|
68
|
-
el(Text, { wrap: "wrap" }, `Project directory: ${context.item.projectDir}`),
|
|
69
|
-
el(Text, { wrap: "wrap" }, `Container: ${context.item.containerName}`),
|
|
70
|
-
el(Text, { wrap: "wrap" }, `State: ${context.runtime.running ? "running" : "stopped"}`),
|
|
71
|
-
el(Text, { wrap: "wrap" }, `Started at: ${renderStartedAtDetailed(context.runtime)}`),
|
|
72
|
-
el(Text, { wrap: "wrap" }, `SSH sessions now: ${context.sshSessionsLabel}`)
|
|
73
|
-
];
|
|
74
|
-
const renderInfoDetails = (el, context, common) => [
|
|
75
|
-
titleRow(el, "Connection info"),
|
|
76
|
-
...common,
|
|
77
|
-
el(Text, { wrap: "wrap" }, `Service: ${context.item.serviceName}`),
|
|
78
|
-
el(Text, { wrap: "wrap" }, `SSH command: ${context.item.sshCommand}`),
|
|
79
|
-
el(Text, { wrap: "wrap" }, `Repo: ${context.item.repoUrl} (${context.refLabel})`),
|
|
80
|
-
el(Text, { wrap: "wrap" }, `Workspace: ${context.item.targetDir}`),
|
|
81
|
-
el(Text, { wrap: "wrap" }, `Authorized keys: ${context.item.authorizedKeysPath}${context.authSuffix}`),
|
|
82
|
-
el(Text, { wrap: "wrap" }, `Env global: ${context.item.envGlobalPath}`),
|
|
83
|
-
el(Text, { wrap: "wrap" }, `Env project: ${context.item.envProjectPath}`),
|
|
84
|
-
el(Text, { wrap: "wrap" }, `Codex auth: ${context.item.codexAuthPath} -> ${context.item.codexHome}`)
|
|
85
|
-
];
|
|
86
|
-
const renderDefaultDetails = (el, context) => [
|
|
87
|
-
titleRow(el, "Details"),
|
|
88
|
-
el(Text, { wrap: "truncate" }, `Repo: ${context.item.repoUrl}`),
|
|
89
|
-
el(Text, { wrap: "truncate" }, `Ref: ${context.item.repoRef}`),
|
|
90
|
-
el(Text, { wrap: "truncate" }, `Project dir: ${context.item.projectDir}`),
|
|
91
|
-
el(Text, { wrap: "truncate" }, `Workspace: ${context.item.targetDir}`),
|
|
92
|
-
el(Text, { wrap: "truncate" }, `SSH: ${context.item.sshCommand}`)
|
|
93
|
-
];
|
|
94
|
-
const renderConnectDetails = (el, context, common, connectEnableMcpPlaywright) => [
|
|
95
|
-
titleRow(el, "Connect + SSH"),
|
|
96
|
-
...common,
|
|
97
|
-
el(Text, { color: connectEnableMcpPlaywright ? "green" : "gray", wrap: "wrap" }, connectEnableMcpPlaywright
|
|
98
|
-
? "Playwright MCP: will be enabled before SSH (P to disable)."
|
|
99
|
-
: "Playwright MCP: keep current project setting (P to enable before SSH)."),
|
|
100
|
-
el(Text, { wrap: "wrap" }, `Repo: ${context.item.repoUrl} (${context.refLabel})`),
|
|
101
|
-
el(Text, { wrap: "wrap" }, `SSH command: ${context.item.sshCommand}`)
|
|
102
|
-
];
|
|
103
|
-
export const renderSelectDetails = (el, purpose, item, runtimeByProject, connectEnableMcpPlaywright) => {
|
|
104
|
-
if (!item) {
|
|
105
|
-
return [el(Text, { color: "gray", wrap: "truncate" }, "No project selected.")];
|
|
106
|
-
}
|
|
107
|
-
const context = buildDetailsContext(item, runtimeByProject);
|
|
108
|
-
const common = commonRows(el, context);
|
|
109
|
-
return Match.value(purpose).pipe(Match.when("Connect", () => renderConnectDetails(el, context, common, connectEnableMcpPlaywright)), Match.when("Auth", () => [
|
|
110
|
-
titleRow(el, "Project auth"),
|
|
111
|
-
...common,
|
|
112
|
-
el(Text, { wrap: "wrap" }, `Repo: ${context.item.repoUrl} (${context.refLabel})`),
|
|
113
|
-
el(Text, { wrap: "wrap" }, `Env global: ${context.item.envGlobalPath}`),
|
|
114
|
-
el(Text, { wrap: "wrap" }, `Env project: ${context.item.envProjectPath}`),
|
|
115
|
-
el(Text, { color: "gray", wrap: "wrap" }, "Press Enter to manage labels for this project.")
|
|
116
|
-
]), Match.when("Info", () => renderInfoDetails(el, context, common)), Match.when("Down", () => [
|
|
117
|
-
titleRow(el, "Stop container"),
|
|
118
|
-
...common,
|
|
119
|
-
el(Text, { wrap: "wrap" }, `Repo: ${context.item.repoUrl} (${context.refLabel})`)
|
|
120
|
-
]), Match.when("Delete", () => [
|
|
121
|
-
titleRow(el, "Delete project"),
|
|
122
|
-
...common,
|
|
123
|
-
context.runtime.sshSessions > 0
|
|
124
|
-
? el(Text, { color: "yellow", wrap: "wrap" }, "Warning: project has active SSH sessions.")
|
|
125
|
-
: el(Text, { color: "gray", wrap: "wrap" }, "No active SSH sessions detected."),
|
|
126
|
-
el(Text, { wrap: "wrap" }, `Repo: ${context.item.repoUrl} (${context.refLabel})`),
|
|
127
|
-
el(Text, { wrap: "wrap" }, "Removes project folder and runs docker compose down -v.")
|
|
128
|
-
]), Match.orElse(() => renderDefaultDetails(el, context)));
|
|
129
|
-
};
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { Match } from "effect";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { renderLayout } from "./menu-render-layout.js";
|
|
5
|
-
import { buildSelectLabels, buildSelectListWindow, renderSelectDetails, selectHint, selectTitle } from "./menu-render-select.js";
|
|
6
|
-
import { createSteps, menuItems } from "./menu-types.js";
|
|
7
|
-
// CHANGE: render menu views with Ink without JSX
|
|
8
|
-
// WHY: keep UI logic separate from input/state reducers
|
|
9
|
-
// QUOTE(ТЗ): "TUI? Красивый, удобный"
|
|
10
|
-
// REF: user-request-2026-02-01-tui
|
|
11
|
-
// SOURCE: n/a
|
|
12
|
-
// FORMAT THEOREM: forall v: view(v) -> render(v)
|
|
13
|
-
// PURITY: SHELL
|
|
14
|
-
// EFFECT: n/a
|
|
15
|
-
// INVARIANT: menu renders all items once
|
|
16
|
-
// COMPLEXITY: O(n)
|
|
17
|
-
export const renderStepLabel = (step, defaults) => Match.value(step).pipe(Match.when("repoUrl", () => "Repo URL"), Match.when("repoRef", () => `Repo ref [${defaults.repoRef}]`), Match.when("outDir", () => `Output dir [${defaults.outDir}]`), Match.when("runUp", () => `Run docker compose up now? [${defaults.runUp ? "Y" : "n"}]`), Match.when("mcpPlaywright", () => `Enable Playwright MCP (Chromium sidecar)? [${defaults.enableMcpPlaywright ? "y" : "N"}]`), Match.when("force", () => `Force recreate (overwrite files + wipe volumes)? [${defaults.force ? "y" : "N"}]`), Match.exhaustive);
|
|
18
|
-
const compactElements = (items) => items.filter((item) => item !== null);
|
|
19
|
-
const renderMenuHints = (el) => el(Box, { marginTop: 1, flexDirection: "column" }, el(Text, { color: "gray" }, "Hints:"), el(Text, { color: "gray" }, " - Paste repo URL to create directly."), el(Text, { color: "gray" }, " - Aliases: create/c, select/s, auth/a, project-auth/pa, info/i, status/ps, logs/l, down/d, down-all/da, delete/del, quit/q"), el(Text, { color: "gray" }, " - Use arrows and Enter to run."));
|
|
20
|
-
const renderMenuMessage = (el, message) => {
|
|
21
|
-
if (!message || message.length === 0) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return el(Box, { marginTop: 1, flexDirection: "column" }, ...message
|
|
25
|
-
.split("\n")
|
|
26
|
-
.map((line, index) => el(Text, { key: `${index}-${line}`, color: "magenta" }, line)));
|
|
27
|
-
};
|
|
28
|
-
export const renderMenu = (input) => {
|
|
29
|
-
const { activeDir, busy, cwd, message, runningDockerGitContainers, selected } = input;
|
|
30
|
-
const el = React.createElement;
|
|
31
|
-
const activeLabel = `Active: ${activeDir ?? "(none)"}`;
|
|
32
|
-
const runningLabel = `Running docker-git containers: ${runningDockerGitContainers}`;
|
|
33
|
-
const cwdLabel = `CWD: ${cwd}`;
|
|
34
|
-
const items = menuItems.map((item, index) => {
|
|
35
|
-
const indexLabel = `${index + 1})`;
|
|
36
|
-
const prefix = index === selected ? ">" : " ";
|
|
37
|
-
return el(Text, { key: item.label, color: index === selected ? "green" : "white" }, `${prefix} ${indexLabel} ${item.label}`);
|
|
38
|
-
});
|
|
39
|
-
const busyView = busy
|
|
40
|
-
? el(Box, { marginTop: 1 }, el(Text, { color: "yellow" }, "Running..."))
|
|
41
|
-
: null;
|
|
42
|
-
const messageView = renderMenuMessage(el, message);
|
|
43
|
-
const hints = renderMenuHints(el);
|
|
44
|
-
return renderLayout("docker-git", compactElements([
|
|
45
|
-
el(Text, null, activeLabel),
|
|
46
|
-
el(Text, null, runningLabel),
|
|
47
|
-
el(Text, null, cwdLabel),
|
|
48
|
-
el(Box, { flexDirection: "column", marginTop: 1 }, ...items),
|
|
49
|
-
hints,
|
|
50
|
-
busyView,
|
|
51
|
-
messageView
|
|
52
|
-
]), null);
|
|
53
|
-
};
|
|
54
|
-
export const renderCreate = (label, buffer, message, stepIndex, defaults) => {
|
|
55
|
-
const el = React.createElement;
|
|
56
|
-
const steps = createSteps.map((step, index) => el(Text, { key: step, color: index === stepIndex ? "green" : "gray" }, `${index === stepIndex ? ">" : " "} ${renderStepLabel(step, defaults)}`));
|
|
57
|
-
return renderLayout("docker-git / Create", [
|
|
58
|
-
el(Box, { flexDirection: "column", marginTop: 1 }, ...steps),
|
|
59
|
-
el(Box, { marginTop: 1 }, el(Text, null, `${label}: `), el(Text, { color: "green" }, buffer)),
|
|
60
|
-
el(Box, { marginTop: 1 }, el(Text, { color: "gray" }, "Enter = next, Esc = cancel."))
|
|
61
|
-
], message);
|
|
62
|
-
};
|
|
63
|
-
export { renderAuthMenu, renderAuthPrompt } from "./menu-render-auth.js";
|
|
64
|
-
export { renderProjectAuthMenu, renderProjectAuthPrompt } from "./menu-render-project-auth.js";
|
|
65
|
-
const computeListWidth = (labels) => {
|
|
66
|
-
const maxLabelWidth = labels.length > 0 ? Math.max(...labels.map((label) => label.length)) : 24;
|
|
67
|
-
return Math.min(Math.max(maxLabelWidth + 2, 28), 54);
|
|
68
|
-
};
|
|
69
|
-
const readStdoutRows = () => {
|
|
70
|
-
const rows = process.stdout.rows;
|
|
71
|
-
if (typeof rows !== "number" || !Number.isFinite(rows) || rows <= 0) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
return rows;
|
|
75
|
-
};
|
|
76
|
-
const computeSelectListMaxRows = () => {
|
|
77
|
-
const rows = readStdoutRows();
|
|
78
|
-
if (rows === null) {
|
|
79
|
-
return 12;
|
|
80
|
-
}
|
|
81
|
-
return Math.max(6, rows - 14);
|
|
82
|
-
};
|
|
83
|
-
const renderSelectListBox = (el, items, selected, labels, width) => {
|
|
84
|
-
const window = buildSelectListWindow(labels.length, selected, computeSelectListMaxRows());
|
|
85
|
-
const hiddenAbove = window.start;
|
|
86
|
-
const hiddenBelow = labels.length - window.end;
|
|
87
|
-
const visibleLabels = labels.slice(window.start, window.end);
|
|
88
|
-
const list = visibleLabels.map((label, offset) => {
|
|
89
|
-
const index = window.start + offset;
|
|
90
|
-
return el(Text, {
|
|
91
|
-
key: items[index]?.projectDir ?? String(index),
|
|
92
|
-
color: index === selected ? "green" : "white",
|
|
93
|
-
wrap: "truncate"
|
|
94
|
-
}, label);
|
|
95
|
-
});
|
|
96
|
-
const before = hiddenAbove > 0
|
|
97
|
-
? [el(Text, { color: "gray", wrap: "truncate" }, `[scroll] ${hiddenAbove} more above`)]
|
|
98
|
-
: [];
|
|
99
|
-
const after = hiddenBelow > 0
|
|
100
|
-
? [el(Text, { color: "gray", wrap: "truncate" }, `[scroll] ${hiddenBelow} more below`)]
|
|
101
|
-
: [];
|
|
102
|
-
const listBody = list.length > 0 ? list : [el(Text, { color: "gray" }, "No projects found.")];
|
|
103
|
-
return el(Box, { flexDirection: "column", width }, ...before, ...listBody, ...after);
|
|
104
|
-
};
|
|
105
|
-
const renderSelectDetailsBox = (el, input) => {
|
|
106
|
-
const details = renderSelectDetails(el, input.purpose, input.items[input.selected], input.runtimeByProject, input.connectEnableMcpPlaywright);
|
|
107
|
-
return el(Box, { flexDirection: "column", marginLeft: 2, flexGrow: 1 }, ...details);
|
|
108
|
-
};
|
|
109
|
-
export const renderSelect = (input) => {
|
|
110
|
-
const { confirmDelete, connectEnableMcpPlaywright, items, message, purpose, runtimeByProject, selected } = input;
|
|
111
|
-
const el = React.createElement;
|
|
112
|
-
const listLabels = buildSelectLabels(items, selected, purpose, runtimeByProject);
|
|
113
|
-
const listWidth = computeListWidth(listLabels);
|
|
114
|
-
const listBox = renderSelectListBox(el, items, selected, listLabels, listWidth);
|
|
115
|
-
const detailsBox = renderSelectDetailsBox(el, {
|
|
116
|
-
purpose,
|
|
117
|
-
items,
|
|
118
|
-
selected,
|
|
119
|
-
runtimeByProject,
|
|
120
|
-
connectEnableMcpPlaywright
|
|
121
|
-
});
|
|
122
|
-
const baseHint = selectHint(purpose, connectEnableMcpPlaywright);
|
|
123
|
-
const confirmHint = (() => {
|
|
124
|
-
if (purpose === "Delete" && confirmDelete) {
|
|
125
|
-
return "Confirm mode: Enter = delete now, Esc = cancel";
|
|
126
|
-
}
|
|
127
|
-
if (purpose === "Down" && confirmDelete) {
|
|
128
|
-
return "Confirm mode: Enter = stop now, Esc = cancel";
|
|
129
|
-
}
|
|
130
|
-
return baseHint;
|
|
131
|
-
})();
|
|
132
|
-
const hints = el(Box, { marginTop: 1 }, el(Text, { color: "gray" }, confirmHint));
|
|
133
|
-
return renderLayout(selectTitle(purpose), [
|
|
134
|
-
el(Box, { flexDirection: "row", marginTop: 1 }, listBox, detailsBox),
|
|
135
|
-
hints
|
|
136
|
-
], message);
|
|
137
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { runDockerComposeDown } from "@effect-template/lib/shell/docker";
|
|
2
|
-
import { renderError } from "@effect-template/lib/usecases/errors";
|
|
3
|
-
import { mcpPlaywrightUp } from "@effect-template/lib/usecases/mcp-playwright";
|
|
4
|
-
import { connectProjectSshWithUp, deleteDockerGitProject, listRunningProjectItems } from "@effect-template/lib/usecases/projects";
|
|
5
|
-
import { Effect, pipe } from "effect";
|
|
6
|
-
import { openProjectAuthMenu } from "./menu-project-auth.js";
|
|
7
|
-
import { buildConnectEffect } from "./menu-select-connect.js";
|
|
8
|
-
import { loadRuntimeByProject } from "./menu-select-runtime.js";
|
|
9
|
-
import { startSelectView } from "./menu-select-view.js";
|
|
10
|
-
import { pauseOnError, resetToMenu, resumeSshWithSkipInputs, resumeWithSkipInputs, withSuspendedTui } from "./menu-shared.js";
|
|
11
|
-
export const runConnectSelection = (selected, context, enableMcpPlaywright) => {
|
|
12
|
-
context.setMessage(enableMcpPlaywright
|
|
13
|
-
? `Enabling Playwright MCP for ${selected.displayName}, then connecting...`
|
|
14
|
-
: `Connecting to ${selected.displayName}...`);
|
|
15
|
-
context.setSshActive(true);
|
|
16
|
-
context.runner.runEffect(pipe(withSuspendedTui(buildConnectEffect(selected, enableMcpPlaywright, {
|
|
17
|
-
connectWithUp: (item) => connectProjectSshWithUp(item).pipe(Effect.mapError((error) => error)),
|
|
18
|
-
enableMcpPlaywright: (projectDir) => mcpPlaywrightUp({ _tag: "McpPlaywrightUp", projectDir, runUp: false }).pipe(Effect.asVoid, Effect.mapError((error) => error))
|
|
19
|
-
}), {
|
|
20
|
-
onError: pauseOnError(renderError),
|
|
21
|
-
onResume: resumeSshWithSkipInputs(context)
|
|
22
|
-
}), Effect.tap(() => Effect.sync(() => {
|
|
23
|
-
context.setMessage("SSH session ended. Press Esc to return to the menu.");
|
|
24
|
-
})), Effect.asVoid));
|
|
25
|
-
};
|
|
26
|
-
export const runDownSelection = (selected, context) => {
|
|
27
|
-
context.setMessage(`Stopping ${selected.displayName}...`);
|
|
28
|
-
context.runner.runEffect(withSuspendedTui(pipe(runDockerComposeDown(selected.projectDir), Effect.zipRight(listRunningProjectItems), Effect.flatMap((items) => pipe(loadRuntimeByProject(items), Effect.map((runtimeByProject) => ({ items, runtimeByProject })))), Effect.tap(({ items, runtimeByProject }) => Effect.sync(() => {
|
|
29
|
-
if (items.length === 0) {
|
|
30
|
-
resetToMenu(context);
|
|
31
|
-
context.setMessage("No running docker-git containers.");
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
startSelectView(items, "Down", context, runtimeByProject);
|
|
35
|
-
context.setMessage("Container stopped. Select another to stop, or Esc to return.");
|
|
36
|
-
})), Effect.asVoid), {
|
|
37
|
-
onError: pauseOnError(renderError),
|
|
38
|
-
onResume: resumeWithSkipInputs(context)
|
|
39
|
-
}));
|
|
40
|
-
};
|
|
41
|
-
export const runInfoSelection = (selected, context) => {
|
|
42
|
-
context.setMessage(`Details for ${selected.displayName} are shown on the right. Press Esc to return to the menu.`);
|
|
43
|
-
};
|
|
44
|
-
export const runAuthSelection = (selected, context) => {
|
|
45
|
-
openProjectAuthMenu({
|
|
46
|
-
project: selected,
|
|
47
|
-
runner: context.runner,
|
|
48
|
-
setView: context.setView,
|
|
49
|
-
setMessage: context.setMessage,
|
|
50
|
-
setActiveDir: context.setActiveDir
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
export const runDeleteSelection = (selected, context) => {
|
|
54
|
-
context.setMessage(`Deleting ${selected.displayName}...`);
|
|
55
|
-
context.runner.runEffect(pipe(withSuspendedTui(deleteDockerGitProject(selected).pipe(Effect.tap(() => Effect.sync(() => {
|
|
56
|
-
if (context.activeDir === selected.projectDir) {
|
|
57
|
-
context.setActiveDir(null);
|
|
58
|
-
}
|
|
59
|
-
context.setView({ _tag: "Menu" });
|
|
60
|
-
})), Effect.asVoid), {
|
|
61
|
-
onError: pauseOnError(renderError),
|
|
62
|
-
onResume: resumeWithSkipInputs(context)
|
|
63
|
-
}), Effect.tap(() => Effect.sync(() => {
|
|
64
|
-
context.setMessage("Project deleted.");
|
|
65
|
-
})), Effect.asVoid));
|
|
66
|
-
};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
const normalizedInput = (input) => input.trim().toLowerCase();
|
|
3
|
-
export const isConnectMcpToggleInput = (input) => normalizedInput(input) === "p";
|
|
4
|
-
export const buildConnectEffect = (selected, enableMcpPlaywright, deps) => enableMcpPlaywright
|
|
5
|
-
? deps.enableMcpPlaywright(selected.projectDir).pipe(Effect.zipRight(deps.connectWithUp(selected)))
|
|
6
|
-
: deps.connectWithUp(selected);
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Effect, pipe } from "effect";
|
|
2
|
-
import { loadRuntimeByProject } from "./menu-select-runtime.js";
|
|
3
|
-
import { startSelectView } from "./menu-select.js";
|
|
4
|
-
export const loadSelectView = (effect, purpose, context) => pipe(effect, Effect.flatMap((items) => pipe(loadRuntimeByProject(items), Effect.flatMap((runtimeByProject) => Effect.sync(() => {
|
|
5
|
-
if (items.length === 0) {
|
|
6
|
-
context.setMessage(purpose === "Down"
|
|
7
|
-
? "No running docker-git containers."
|
|
8
|
-
: "No docker-git projects found.");
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
startSelectView(items, purpose, context, runtimeByProject);
|
|
12
|
-
})))));
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const defaultRuntime = () => ({
|
|
2
|
-
running: false,
|
|
3
|
-
sshSessions: 0,
|
|
4
|
-
startedAtIso: null,
|
|
5
|
-
startedAtEpochMs: null
|
|
6
|
-
});
|
|
7
|
-
const runtimeForSort = (runtimeByProject, item) => runtimeByProject[item.projectDir] ?? defaultRuntime();
|
|
8
|
-
const startedAtEpochForSort = (runtime) => runtime.startedAtEpochMs ?? Number.NEGATIVE_INFINITY;
|
|
9
|
-
export const sortItemsByLaunchTime = (items, runtimeByProject) => items.toSorted((left, right) => {
|
|
10
|
-
const leftRuntime = runtimeForSort(runtimeByProject, left);
|
|
11
|
-
const rightRuntime = runtimeForSort(runtimeByProject, right);
|
|
12
|
-
const leftStartedAt = startedAtEpochForSort(leftRuntime);
|
|
13
|
-
const rightStartedAt = startedAtEpochForSort(rightRuntime);
|
|
14
|
-
if (leftStartedAt !== rightStartedAt) {
|
|
15
|
-
return rightStartedAt - leftStartedAt;
|
|
16
|
-
}
|
|
17
|
-
if (leftRuntime.running !== rightRuntime.running) {
|
|
18
|
-
return leftRuntime.running ? -1 : 1;
|
|
19
|
-
}
|
|
20
|
-
return left.displayName.localeCompare(right.displayName);
|
|
21
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { runCommandCapture } from "@effect-template/lib/shell/command-runner";
|
|
2
|
-
import { runDockerPsNames } from "@effect-template/lib/shell/docker";
|
|
3
|
-
import { Effect, pipe } from "effect";
|
|
4
|
-
const emptyRuntimeByProject = () => ({});
|
|
5
|
-
const stoppedRuntime = () => ({
|
|
6
|
-
running: false,
|
|
7
|
-
sshSessions: 0,
|
|
8
|
-
startedAtIso: null,
|
|
9
|
-
startedAtEpochMs: null
|
|
10
|
-
});
|
|
11
|
-
const countSshSessionsScript = "who -u 2>/dev/null | wc -l | tr -d '[:space:]'";
|
|
12
|
-
const dockerZeroStartedAt = "0001-01-01T00:00:00Z";
|
|
13
|
-
const parseSshSessionCount = (raw) => {
|
|
14
|
-
const parsed = Number.parseInt(raw.trim(), 10);
|
|
15
|
-
if (Number.isNaN(parsed) || parsed < 0) {
|
|
16
|
-
return 0;
|
|
17
|
-
}
|
|
18
|
-
return parsed;
|
|
19
|
-
};
|
|
20
|
-
const parseContainerStartedAt = (raw) => {
|
|
21
|
-
const trimmed = raw.trim();
|
|
22
|
-
if (trimmed.length === 0 || trimmed === dockerZeroStartedAt) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
const startedAtEpochMs = Date.parse(trimmed);
|
|
26
|
-
if (Number.isNaN(startedAtEpochMs)) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
startedAtIso: trimmed,
|
|
31
|
-
startedAtEpochMs
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
const toRuntimeMap = (entries) => {
|
|
35
|
-
const runtimeByProject = {};
|
|
36
|
-
for (const [projectDir, runtime] of entries) {
|
|
37
|
-
runtimeByProject[projectDir] = runtime;
|
|
38
|
-
}
|
|
39
|
-
return runtimeByProject;
|
|
40
|
-
};
|
|
41
|
-
const countContainerSshSessions = (containerName) => pipe(runCommandCapture({
|
|
42
|
-
cwd: process.cwd(),
|
|
43
|
-
command: "docker",
|
|
44
|
-
args: ["exec", containerName, "bash", "-lc", countSshSessionsScript]
|
|
45
|
-
}, [0], (exitCode) => ({ _tag: "CommandFailedError", command: "docker exec who -u", exitCode })), Effect.match({
|
|
46
|
-
onFailure: () => 0,
|
|
47
|
-
onSuccess: (raw) => parseSshSessionCount(raw)
|
|
48
|
-
}));
|
|
49
|
-
const inspectContainerStartedAt = (containerName) => pipe(runCommandCapture({
|
|
50
|
-
cwd: process.cwd(),
|
|
51
|
-
command: "docker",
|
|
52
|
-
args: ["inspect", "--format", "{{.State.StartedAt}}", containerName]
|
|
53
|
-
}, [0], (exitCode) => ({ _tag: "CommandFailedError", command: "docker inspect .State.StartedAt", exitCode })), Effect.match({
|
|
54
|
-
onFailure: () => null,
|
|
55
|
-
onSuccess: (raw) => parseContainerStartedAt(raw)
|
|
56
|
-
}));
|
|
57
|
-
// CHANGE: enrich select items with runtime state and SSH session counts
|
|
58
|
-
// WHY: prevent stopping/deleting containers that are currently used via SSH
|
|
59
|
-
// QUOTE(ТЗ): "писать скок SSH подключений к контейнеру сейчас"
|
|
60
|
-
// REF: issue-47
|
|
61
|
-
// SOURCE: n/a
|
|
62
|
-
// FORMAT THEOREM: forall p: runtime(p) -> {running(p), ssh_sessions(p), started_at(p)}
|
|
63
|
-
// PURITY: SHELL
|
|
64
|
-
// EFFECT: Effect<Record<string, SelectProjectRuntime>, never, MenuEnv>
|
|
65
|
-
// INVARIANT: projects without a known container start have startedAt = null
|
|
66
|
-
// COMPLEXITY: O(n + docker_ps + docker_exec + docker_inspect)
|
|
67
|
-
export const loadRuntimeByProject = (items) => pipe(runDockerPsNames(process.cwd()), Effect.flatMap((runningNames) => Effect.forEach(items, (item) => {
|
|
68
|
-
const running = runningNames.includes(item.containerName);
|
|
69
|
-
const sshSessionsEffect = running
|
|
70
|
-
? countContainerSshSessions(item.containerName)
|
|
71
|
-
: Effect.succeed(0);
|
|
72
|
-
return pipe(Effect.all([sshSessionsEffect, inspectContainerStartedAt(item.containerName)]), Effect.map(([sshSessions, startedAt]) => ({
|
|
73
|
-
running,
|
|
74
|
-
sshSessions,
|
|
75
|
-
startedAtIso: startedAt?.startedAtIso ?? null,
|
|
76
|
-
startedAtEpochMs: startedAt?.startedAtEpochMs ?? null
|
|
77
|
-
})), Effect.map((runtime) => [item.projectDir, runtime]));
|
|
78
|
-
}, { concurrency: 4 })), Effect.map((entries) => toRuntimeMap(entries)), Effect.match({
|
|
79
|
-
onFailure: () => emptyRuntimeByProject(),
|
|
80
|
-
onSuccess: (runtimeByProject) => runtimeByProject
|
|
81
|
-
}));
|
|
82
|
-
export const runtimeForSelection = (view, selected) => view.runtimeByProject[selected.projectDir] ?? stoppedRuntime();
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { sortItemsByLaunchTime } from "./menu-select-order.js";
|
|
2
|
-
const emptyRuntimeByProject = () => ({});
|
|
3
|
-
export const startSelectView = (items, purpose, context, runtimeByProject = emptyRuntimeByProject()) => {
|
|
4
|
-
const sortedItems = sortItemsByLaunchTime(items, runtimeByProject);
|
|
5
|
-
context.setMessage(null);
|
|
6
|
-
context.setView({
|
|
7
|
-
_tag: "SelectProject",
|
|
8
|
-
purpose,
|
|
9
|
-
items: sortedItems,
|
|
10
|
-
runtimeByProject,
|
|
11
|
-
selected: 0,
|
|
12
|
-
confirmDelete: false,
|
|
13
|
-
connectEnableMcpPlaywright: false
|
|
14
|
-
});
|
|
15
|
-
};
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { Match } from "effect";
|
|
2
|
-
import { runAuthSelection, runConnectSelection, runDeleteSelection, runDownSelection, runInfoSelection } from "./menu-select-actions.js";
|
|
3
|
-
import { isConnectMcpToggleInput } from "./menu-select-connect.js";
|
|
4
|
-
import { runtimeForSelection } from "./menu-select-runtime.js";
|
|
5
|
-
import { resetToMenu } from "./menu-shared.js";
|
|
6
|
-
export { startSelectView } from "./menu-select-view.js";
|
|
7
|
-
const clampIndex = (value, size) => {
|
|
8
|
-
if (size <= 0) {
|
|
9
|
-
return 0;
|
|
10
|
-
}
|
|
11
|
-
if (value < 0) {
|
|
12
|
-
return 0;
|
|
13
|
-
}
|
|
14
|
-
if (value >= size) {
|
|
15
|
-
return size - 1;
|
|
16
|
-
}
|
|
17
|
-
return value;
|
|
18
|
-
};
|
|
19
|
-
export const handleSelectInput = (input, key, view, context) => {
|
|
20
|
-
if (key.escape) {
|
|
21
|
-
resetToMenu(context);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
if (handleConnectOptionToggle(input, view, context)) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (handleSelectNavigation(key, view, context)) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (key.return) {
|
|
31
|
-
handleSelectReturn(view, context);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (input.trim().length > 0) {
|
|
35
|
-
context.setMessage("Use arrows + Enter to select a project, Esc to cancel.");
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
const handleConnectOptionToggle = (input, view, context) => {
|
|
39
|
-
if (view.purpose !== "Connect" || !isConnectMcpToggleInput(input)) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
const nextValue = !view.connectEnableMcpPlaywright;
|
|
43
|
-
context.setView({ ...view, connectEnableMcpPlaywright: nextValue, confirmDelete: false });
|
|
44
|
-
context.setMessage(nextValue
|
|
45
|
-
? "Playwright MCP will be enabled before SSH (press Enter to connect)."
|
|
46
|
-
: "Playwright MCP toggle is OFF (press Enter to connect without changes).");
|
|
47
|
-
return true;
|
|
48
|
-
};
|
|
49
|
-
const handleSelectNavigation = (key, view, context) => {
|
|
50
|
-
if (key.upArrow) {
|
|
51
|
-
const next = clampIndex(view.selected - 1, view.items.length);
|
|
52
|
-
context.setView({ ...view, selected: next, confirmDelete: false });
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
if (key.downArrow) {
|
|
56
|
-
const next = clampIndex(view.selected + 1, view.items.length);
|
|
57
|
-
context.setView({ ...view, selected: next, confirmDelete: false });
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
};
|
|
62
|
-
const formatSshSessionsLabel = (sshSessions) => sshSessions === 1 ? "1 active SSH session" : `${sshSessions} active SSH sessions`;
|
|
63
|
-
const handleSelectReturn = (view, context) => {
|
|
64
|
-
const selected = view.items[view.selected];
|
|
65
|
-
if (!selected) {
|
|
66
|
-
context.setMessage("No project selected.");
|
|
67
|
-
resetToMenu(context);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const selectedRuntime = runtimeForSelection(view, selected);
|
|
71
|
-
const sshSessionsLabel = formatSshSessionsLabel(selectedRuntime.sshSessions);
|
|
72
|
-
Match.value(view.purpose).pipe(Match.when("Connect", () => {
|
|
73
|
-
context.setActiveDir(selected.projectDir);
|
|
74
|
-
runConnectSelection(selected, context, view.connectEnableMcpPlaywright);
|
|
75
|
-
}), Match.when("Auth", () => {
|
|
76
|
-
context.setActiveDir(selected.projectDir);
|
|
77
|
-
runAuthSelection(selected, context);
|
|
78
|
-
}), Match.when("Down", () => {
|
|
79
|
-
if (selectedRuntime.sshSessions > 0 && !view.confirmDelete) {
|
|
80
|
-
context.setMessage(`${selected.containerName} has ${sshSessionsLabel}. Press Enter again to stop, Esc to cancel.`);
|
|
81
|
-
context.setView({ ...view, confirmDelete: true });
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
context.setActiveDir(selected.projectDir);
|
|
85
|
-
runDownSelection(selected, context);
|
|
86
|
-
}), Match.when("Info", () => {
|
|
87
|
-
context.setActiveDir(selected.projectDir);
|
|
88
|
-
runInfoSelection(selected, context);
|
|
89
|
-
}), Match.when("Delete", () => {
|
|
90
|
-
if (!view.confirmDelete) {
|
|
91
|
-
const activeSshWarning = selectedRuntime.sshSessions > 0 ? ` ${sshSessionsLabel}.` : "";
|
|
92
|
-
context.setMessage(`Really delete ${selected.displayName}?${activeSshWarning} Press Enter again to confirm, Esc to cancel.`);
|
|
93
|
-
context.setView({ ...view, confirmDelete: true });
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
runDeleteSelection(selected, context);
|
|
97
|
-
}), Match.exhaustive);
|
|
98
|
-
};
|