@prover-coder-ai/docker-git 1.0.16 → 1.0.17
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 +1 -1
- package/CHANGELOG.md +6 -0
- package/README.md +5 -6
- package/dist/main.js +24 -7
- package/dist/main.js.map +1 -1
- package/dist/src/docker-git/cli/parser-auth.js +32 -12
- package/dist/src/docker-git/cli/parser.js +1 -1
- package/dist/src/docker-git/cli/usage.js +4 -3
- package/dist/src/docker-git/menu-actions.js +23 -7
- package/dist/src/docker-git/menu-auth-data.js +90 -0
- package/dist/src/docker-git/menu-auth-helpers.js +20 -0
- package/dist/src/docker-git/menu-auth.js +159 -0
- package/dist/src/docker-git/menu-buffer-input.js +9 -0
- package/dist/src/docker-git/menu-create.js +5 -9
- package/dist/src/docker-git/menu-input-handler.js +70 -28
- package/dist/src/docker-git/menu-input-utils.js +47 -0
- package/dist/src/docker-git/menu-labeled-env.js +33 -0
- package/dist/src/docker-git/menu-project-auth-claude.js +43 -0
- package/dist/src/docker-git/menu-project-auth-data.js +165 -0
- package/dist/src/docker-git/menu-project-auth.js +124 -0
- package/dist/src/docker-git/menu-render-auth.js +45 -0
- package/dist/src/docker-git/menu-render-common.js +26 -0
- package/dist/src/docker-git/menu-render-layout.js +14 -0
- package/dist/src/docker-git/menu-render-project-auth.js +37 -0
- package/dist/src/docker-git/menu-render-select.js +10 -3
- package/dist/src/docker-git/menu-render.js +4 -13
- package/dist/src/docker-git/menu-select-actions.js +66 -0
- package/dist/src/docker-git/menu-select-view.js +15 -0
- package/dist/src/docker-git/menu-select.js +11 -75
- package/dist/src/docker-git/menu-shared.js +86 -17
- package/dist/src/docker-git/menu-types.js +2 -0
- package/dist/src/docker-git/menu.js +13 -1
- package/dist/src/docker-git/program.js +3 -3
- package/package.json +1 -1
- package/src/docker-git/cli/parser-auth.ts +46 -16
- package/src/docker-git/cli/parser-mcp-playwright.ts +0 -1
- package/src/docker-git/cli/parser.ts +1 -1
- package/src/docker-git/cli/usage.ts +4 -3
- package/src/docker-git/menu-actions.ts +31 -12
- package/src/docker-git/menu-auth-data.ts +184 -0
- package/src/docker-git/menu-auth-helpers.ts +30 -0
- package/src/docker-git/menu-auth.ts +311 -0
- package/src/docker-git/menu-buffer-input.ts +18 -0
- package/src/docker-git/menu-create.ts +5 -11
- package/src/docker-git/menu-input-handler.ts +104 -28
- package/src/docker-git/menu-input-utils.ts +85 -0
- package/src/docker-git/menu-labeled-env.ts +37 -0
- package/src/docker-git/menu-project-auth-claude.ts +70 -0
- package/src/docker-git/menu-project-auth-data.ts +292 -0
- package/src/docker-git/menu-project-auth.ts +271 -0
- package/src/docker-git/menu-render-auth.ts +65 -0
- package/src/docker-git/menu-render-common.ts +67 -0
- package/src/docker-git/menu-render-layout.ts +30 -0
- package/src/docker-git/menu-render-project-auth.ts +70 -0
- package/src/docker-git/menu-render-select.ts +11 -1
- package/src/docker-git/menu-render.ts +5 -29
- package/src/docker-git/menu-select-actions.ts +150 -0
- package/src/docker-git/menu-select-load.ts +1 -1
- package/src/docker-git/menu-select-view.ts +25 -0
- package/src/docker-git/menu-select.ts +21 -167
- package/src/docker-git/menu-shared.ts +135 -20
- package/src/docker-git/menu-types.ts +69 -2
- package/src/docker-git/menu.ts +26 -1
- package/src/docker-git/program.ts +10 -4
- package/tests/docker-git/entrypoint-auth.test.ts +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
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,25 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import { loadRuntimeByProject, runtimeForSelection } from "./menu-select-runtime.js";
|
|
8
|
-
import { resetToMenu, resumeTui, suspendTui } from "./menu-shared.js";
|
|
9
|
-
const emptyRuntimeByProject = () => ({});
|
|
10
|
-
export const startSelectView = (items, purpose, context, runtimeByProject = emptyRuntimeByProject()) => {
|
|
11
|
-
const sortedItems = sortItemsByLaunchTime(items, runtimeByProject);
|
|
12
|
-
context.setMessage(null);
|
|
13
|
-
context.setView({
|
|
14
|
-
_tag: "SelectProject",
|
|
15
|
-
purpose,
|
|
16
|
-
items: sortedItems,
|
|
17
|
-
runtimeByProject,
|
|
18
|
-
selected: 0,
|
|
19
|
-
confirmDelete: false,
|
|
20
|
-
connectEnableMcpPlaywright: false
|
|
21
|
-
});
|
|
22
|
-
};
|
|
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";
|
|
23
7
|
const clampIndex = (value, size) => {
|
|
24
8
|
if (size <= 0) {
|
|
25
9
|
return 0;
|
|
@@ -75,56 +59,7 @@ const handleSelectNavigation = (key, view, context) => {
|
|
|
75
59
|
}
|
|
76
60
|
return false;
|
|
77
61
|
};
|
|
78
|
-
const
|
|
79
|
-
context.runner.runEffect(pipe(Effect.sync(suspendTui), Effect.zipRight(effect), Effect.ensuring(Effect.sync(() => {
|
|
80
|
-
resumeTui();
|
|
81
|
-
onResume();
|
|
82
|
-
context.setSkipInputs(() => 2);
|
|
83
|
-
})), Effect.tap(() => Effect.sync(() => {
|
|
84
|
-
context.setMessage(doneMessage);
|
|
85
|
-
}))));
|
|
86
|
-
};
|
|
87
|
-
const runConnectSelection = (selected, context, enableMcpPlaywright) => {
|
|
88
|
-
context.setMessage(enableMcpPlaywright
|
|
89
|
-
? `Enabling Playwright MCP for ${selected.displayName}, then connecting...`
|
|
90
|
-
: `Connecting to ${selected.displayName}...`);
|
|
91
|
-
context.setSshActive(true);
|
|
92
|
-
runWithSuspendedTui(context, buildConnectEffect(selected, enableMcpPlaywright, {
|
|
93
|
-
connectWithUp: (item) => connectProjectSshWithUp(item).pipe(Effect.mapError((error) => error)),
|
|
94
|
-
enableMcpPlaywright: (projectDir) => mcpPlaywrightUp({ _tag: "McpPlaywrightUp", projectDir, runUp: false }).pipe(Effect.asVoid, Effect.mapError((error) => error))
|
|
95
|
-
}), () => {
|
|
96
|
-
context.setSshActive(false);
|
|
97
|
-
}, "SSH session ended. Press Esc to return to the menu.");
|
|
98
|
-
};
|
|
99
|
-
const runDownSelection = (selected, context) => {
|
|
100
|
-
context.setMessage(`Stopping ${selected.displayName}...`);
|
|
101
|
-
context.runner.runEffect(pipe(Effect.sync(suspendTui), Effect.zipRight(runDockerComposeDown(selected.projectDir)), Effect.zipRight(listRunningProjectItems), Effect.flatMap((items) => pipe(loadRuntimeByProject(items), Effect.map((runtimeByProject) => ({ items, runtimeByProject })))), Effect.tap(({ items, runtimeByProject }) => Effect.sync(() => {
|
|
102
|
-
if (items.length === 0) {
|
|
103
|
-
resetToMenu(context);
|
|
104
|
-
context.setMessage("No running docker-git containers.");
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
startSelectView(items, "Down", context, runtimeByProject);
|
|
108
|
-
context.setMessage("Container stopped. Select another to stop, or Esc to return.");
|
|
109
|
-
})), Effect.ensuring(Effect.sync(() => {
|
|
110
|
-
resumeTui();
|
|
111
|
-
context.setSkipInputs(() => 2);
|
|
112
|
-
})), Effect.asVoid));
|
|
113
|
-
};
|
|
114
|
-
const runInfoSelection = (selected, context) => {
|
|
115
|
-
context.setMessage(`Details for ${selected.displayName} are shown on the right. Press Esc to return to the menu.`);
|
|
116
|
-
};
|
|
117
|
-
const runDeleteSelection = (selected, context) => {
|
|
118
|
-
context.setMessage(`Deleting ${selected.displayName}...`);
|
|
119
|
-
runWithSuspendedTui(context, deleteDockerGitProject(selected).pipe(Effect.tap(() => Effect.sync(() => {
|
|
120
|
-
if (context.activeDir === selected.projectDir) {
|
|
121
|
-
context.setActiveDir(null);
|
|
122
|
-
}
|
|
123
|
-
context.setView({ _tag: "Menu" });
|
|
124
|
-
}))), () => {
|
|
125
|
-
// Only return to menu on success (see Effect.tap above).
|
|
126
|
-
}, "Project deleted.");
|
|
127
|
-
};
|
|
62
|
+
const formatSshSessionsLabel = (sshSessions) => sshSessions === 1 ? "1 active SSH session" : `${sshSessions} active SSH sessions`;
|
|
128
63
|
const handleSelectReturn = (view, context) => {
|
|
129
64
|
const selected = view.items[view.selected];
|
|
130
65
|
if (!selected) {
|
|
@@ -133,12 +68,13 @@ const handleSelectReturn = (view, context) => {
|
|
|
133
68
|
return;
|
|
134
69
|
}
|
|
135
70
|
const selectedRuntime = runtimeForSelection(view, selected);
|
|
136
|
-
const sshSessionsLabel = selectedRuntime.sshSessions
|
|
137
|
-
? "1 active SSH session"
|
|
138
|
-
: `${selectedRuntime.sshSessions} active SSH sessions`;
|
|
71
|
+
const sshSessionsLabel = formatSshSessionsLabel(selectedRuntime.sshSessions);
|
|
139
72
|
Match.value(view.purpose).pipe(Match.when("Connect", () => {
|
|
140
73
|
context.setActiveDir(selected.projectDir);
|
|
141
74
|
runConnectSelection(selected, context, view.connectEnableMcpPlaywright);
|
|
75
|
+
}), Match.when("Auth", () => {
|
|
76
|
+
context.setActiveDir(selected.projectDir);
|
|
77
|
+
runAuthSelection(selected, context);
|
|
142
78
|
}), Match.when("Down", () => {
|
|
143
79
|
if (selectedRuntime.sshSessions > 0 && !view.confirmDelete) {
|
|
144
80
|
context.setMessage(`${selected.containerName} has ${sshSessionsLabel}. Press Enter again to stop, Esc to cancel.`);
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
import { Effect, pipe } from "effect";
|
|
1
2
|
let stdoutPatched = false;
|
|
2
3
|
let stdoutMuted = false;
|
|
4
|
+
let baseStdoutWrite = null;
|
|
5
|
+
let baseStderrWrite = null;
|
|
6
|
+
const wrapWrite = (baseWrite) => (chunk, encoding, cb) => {
|
|
7
|
+
if (stdoutMuted) {
|
|
8
|
+
const callback = typeof encoding === "function" ? encoding : cb;
|
|
9
|
+
if (typeof callback === "function") {
|
|
10
|
+
callback();
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (typeof encoding === "function") {
|
|
15
|
+
return baseWrite(chunk, encoding);
|
|
16
|
+
}
|
|
17
|
+
return baseWrite(chunk, encoding, cb);
|
|
18
|
+
};
|
|
3
19
|
const disableMouseModes = () => {
|
|
4
20
|
// Disable xterm/urxvt mouse tracking and "alternate scroll" mode (wheel -> arrow keys).
|
|
5
21
|
process.stdout.write("\u001B[?1000l\u001B[?1002l\u001B[?1003l\u001B[?1005l\u001B[?1006l\u001B[?1015l\u001B[?1007l");
|
|
@@ -18,23 +34,72 @@ const ensureStdoutPatched = () => {
|
|
|
18
34
|
if (stdoutPatched) {
|
|
19
35
|
return;
|
|
20
36
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (typeof callback === "function") {
|
|
26
|
-
callback();
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
if (typeof encoding === "function") {
|
|
31
|
-
return baseWrite(chunk, encoding);
|
|
32
|
-
}
|
|
33
|
-
return baseWrite(chunk, encoding, cb);
|
|
34
|
-
};
|
|
35
|
-
process.stdout.write = mutedWrite;
|
|
37
|
+
baseStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
38
|
+
baseStderrWrite = process.stderr.write.bind(process.stderr);
|
|
39
|
+
process.stdout.write = wrapWrite(baseStdoutWrite);
|
|
40
|
+
process.stderr.write = wrapWrite(baseStderrWrite);
|
|
36
41
|
stdoutPatched = true;
|
|
37
42
|
};
|
|
43
|
+
// CHANGE: allow writing to the terminal even while stdout is muted
|
|
44
|
+
// WHY: we mute Ink renders during interactive commands, but still need to show prompts/errors
|
|
45
|
+
// REF: user-request-2026-02-18-tui-output-hidden
|
|
46
|
+
// SOURCE: n/a
|
|
47
|
+
// PURITY: SHELL
|
|
48
|
+
// EFFECT: n/a
|
|
49
|
+
// INVARIANT: bypasses the mute wrapper safely
|
|
50
|
+
export const writeToTerminal = (text) => {
|
|
51
|
+
ensureStdoutPatched();
|
|
52
|
+
const write = baseStdoutWrite ?? process.stdout.write.bind(process.stdout);
|
|
53
|
+
write(text);
|
|
54
|
+
};
|
|
55
|
+
// CHANGE: keep the user on the primary screen until they acknowledge
|
|
56
|
+
// WHY: otherwise output from failed docker/gh commands gets hidden again when TUI resumes
|
|
57
|
+
// REF: user-request-2026-02-18-tui-output-hidden
|
|
58
|
+
// SOURCE: n/a
|
|
59
|
+
// PURITY: SHELL
|
|
60
|
+
// EFFECT: Effect<void, never, never>
|
|
61
|
+
// INVARIANT: no-op when stdin/stdout aren't TTY (CI/e2e)
|
|
62
|
+
export const pauseForEnter = (prompt = "Press Enter to return to docker-git...") => {
|
|
63
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
64
|
+
return Effect.void;
|
|
65
|
+
}
|
|
66
|
+
return Effect.async((resume) => {
|
|
67
|
+
// Ensure the prompt isn't glued to the last command line.
|
|
68
|
+
writeToTerminal(`\n${prompt}\n`);
|
|
69
|
+
process.stdin.resume();
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
process.stdin.off("data", onData);
|
|
72
|
+
};
|
|
73
|
+
const onData = () => {
|
|
74
|
+
cleanup();
|
|
75
|
+
resume(Effect.void);
|
|
76
|
+
};
|
|
77
|
+
process.stdin.on("data", onData);
|
|
78
|
+
return Effect.sync(() => {
|
|
79
|
+
cleanup();
|
|
80
|
+
});
|
|
81
|
+
}).pipe(Effect.asVoid);
|
|
82
|
+
};
|
|
83
|
+
export const writeErrorAndPause = (renderedError) => pipe(Effect.sync(() => {
|
|
84
|
+
writeToTerminal(`\n[docker-git] ${renderedError}\n`);
|
|
85
|
+
}), Effect.zipRight(pauseForEnter()), Effect.asVoid);
|
|
86
|
+
export const withSuspendedTui = (effect, options) => {
|
|
87
|
+
const withError = options?.onError
|
|
88
|
+
? pipe(effect, Effect.tapError((error) => Effect.ignore(options.onError?.(error) ?? Effect.void)))
|
|
89
|
+
: effect;
|
|
90
|
+
return pipe(Effect.sync(suspendTui), Effect.zipRight(withError), Effect.ensuring(Effect.sync(() => {
|
|
91
|
+
resumeTui();
|
|
92
|
+
options?.onResume?.();
|
|
93
|
+
})));
|
|
94
|
+
};
|
|
95
|
+
export const resumeWithSkipInputs = (context, extra) => () => {
|
|
96
|
+
extra?.();
|
|
97
|
+
context.setSkipInputs(() => 2);
|
|
98
|
+
};
|
|
99
|
+
export const resumeSshWithSkipInputs = (context) => resumeWithSkipInputs(context, () => {
|
|
100
|
+
context.setSshActive(false);
|
|
101
|
+
});
|
|
102
|
+
export const pauseOnError = (render) => (error) => writeErrorAndPause(render(error));
|
|
38
103
|
// CHANGE: toggle stdout write muting for Ink rendering
|
|
39
104
|
// WHY: allow SSH sessions to own the terminal without TUI redraws
|
|
40
105
|
// QUOTE(ТЗ): "при изменении разершения он всё ломает?"
|
|
@@ -67,7 +132,9 @@ export const suspendTui = () => {
|
|
|
67
132
|
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
68
133
|
process.stdin.setRawMode(false);
|
|
69
134
|
}
|
|
70
|
-
|
|
135
|
+
// Switch back to the primary screen so interactive commands (ssh/gh/codex)
|
|
136
|
+
// can render normally. Do not clear it: users may need scrollback (OAuth codes/URLs).
|
|
137
|
+
process.stdout.write("\u001B[?1049l");
|
|
71
138
|
setStdoutMuted(true);
|
|
72
139
|
};
|
|
73
140
|
// CHANGE: restore TUI rendering after interactive commands
|
|
@@ -86,6 +153,7 @@ export const resumeTui = () => {
|
|
|
86
153
|
}
|
|
87
154
|
setStdoutMuted(false);
|
|
88
155
|
disableMouseModes();
|
|
156
|
+
// Return to the alternate screen for Ink rendering.
|
|
89
157
|
process.stdout.write("\u001B[?1049h\u001B[2J\u001B[H");
|
|
90
158
|
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
91
159
|
process.stdin.setRawMode(true);
|
|
@@ -99,7 +167,8 @@ export const leaveTui = () => {
|
|
|
99
167
|
// Ensure we don't leave the terminal in a broken "mouse reporting" mode.
|
|
100
168
|
setStdoutMuted(false);
|
|
101
169
|
disableMouseModes();
|
|
102
|
-
|
|
170
|
+
// Restore the primary screen on exit without clearing it (keeps useful scrollback).
|
|
171
|
+
process.stdout.write("\u001B[?1049l");
|
|
103
172
|
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
104
173
|
process.stdin.setRawMode(false);
|
|
105
174
|
}
|
|
@@ -9,6 +9,8 @@ export const createSteps = [
|
|
|
9
9
|
export const menuItems = [
|
|
10
10
|
{ id: { _tag: "Create" }, label: "Create project" },
|
|
11
11
|
{ id: { _tag: "Select" }, label: "Select project" },
|
|
12
|
+
{ id: { _tag: "Auth" }, label: "Auth profiles (keys)" },
|
|
13
|
+
{ id: { _tag: "ProjectAuth" }, label: "Project auth (bind labels)" },
|
|
12
14
|
{ id: { _tag: "Info" }, label: "Show connection info" },
|
|
13
15
|
{ id: { _tag: "Status" }, label: "docker compose ps" },
|
|
14
16
|
{ id: { _tag: "Logs" }, label: "docker compose logs --tail=200" },
|
|
@@ -8,7 +8,7 @@ import { render, useApp, useInput } from "ink";
|
|
|
8
8
|
import React, { useEffect, useMemo, useState } from "react";
|
|
9
9
|
import { resolveCreateInputs } from "./menu-create.js";
|
|
10
10
|
import { handleUserInput } from "./menu-input-handler.js";
|
|
11
|
-
import { renderCreate, renderMenu, renderSelect, renderStepLabel } from "./menu-render.js";
|
|
11
|
+
import { renderAuthMenu, renderAuthPrompt, renderCreate, renderMenu, renderProjectAuthMenu, renderProjectAuthPrompt, renderSelect, renderStepLabel } from "./menu-render.js";
|
|
12
12
|
import { leaveTui, resumeTui } from "./menu-shared.js";
|
|
13
13
|
import { defaultMenuStartupSnapshot, resolveMenuStartupSnapshot } from "./menu-startup.js";
|
|
14
14
|
import { createSteps } from "./menu-types.js";
|
|
@@ -54,6 +54,18 @@ const renderView = (context) => {
|
|
|
54
54
|
const label = renderStepLabel(step, currentDefaults);
|
|
55
55
|
return renderCreate(label, context.view.buffer, context.message, context.view.step, currentDefaults);
|
|
56
56
|
}
|
|
57
|
+
if (context.view._tag === "AuthMenu") {
|
|
58
|
+
return renderAuthMenu(context.view.snapshot, context.view.selected, context.message);
|
|
59
|
+
}
|
|
60
|
+
if (context.view._tag === "AuthPrompt") {
|
|
61
|
+
return renderAuthPrompt(context.view, context.message);
|
|
62
|
+
}
|
|
63
|
+
if (context.view._tag === "ProjectAuthMenu") {
|
|
64
|
+
return renderProjectAuthMenu(context.view.snapshot, context.view.selected, context.message);
|
|
65
|
+
}
|
|
66
|
+
if (context.view._tag === "ProjectAuthPrompt") {
|
|
67
|
+
return renderProjectAuthPrompt(context.view, context.message);
|
|
68
|
+
}
|
|
57
69
|
return renderSelect({
|
|
58
70
|
purpose: context.view.purpose,
|
|
59
71
|
items: context.view.items,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createProject } from "@effect-template/lib/usecases/actions";
|
|
2
|
-
import { authCodexLogin, authCodexLogout, authCodexStatus, authGithubLogin, authGithubLogout, authGithubStatus } from "@effect-template/lib/usecases/auth";
|
|
2
|
+
import { authClaudeLogin, authClaudeLogout, authClaudeStatus, authCodexLogin, authCodexLogout, authCodexStatus, authGithubLogin, authGithubLogout, authGithubStatus } from "@effect-template/lib/usecases/auth";
|
|
3
3
|
import { renderError } from "@effect-template/lib/usecases/errors";
|
|
4
4
|
import { mcpPlaywrightUp } from "@effect-template/lib/usecases/mcp-playwright";
|
|
5
5
|
import { downAllDockerGitProjects, listProjectStatus } from "@effect-template/lib/usecases/projects";
|
|
@@ -22,8 +22,8 @@ const setExitCode = (code) => Effect.sync(() => {
|
|
|
22
22
|
const logWarningAndExit = (error) => pipe(Effect.logWarning(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
|
|
23
23
|
const logErrorAndExit = (error) => pipe(Effect.logError(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
|
|
24
24
|
const handleNonBaseCommand = (command) => Match.value(command)
|
|
25
|
-
.pipe(Match.when({ _tag: "StatePath" }, () => statePath), Match.when({ _tag: "StateInit" }, (cmd) => stateInit(cmd)), Match.when({ _tag: "StateStatus" }, () => stateStatus), Match.when({ _tag: "StatePull" }, () => statePull), Match.when({ _tag: "StateCommit" }, (cmd) => stateCommit(cmd.message)), Match.when({ _tag: "StatePush" }, () => statePush), Match.when({ _tag: "StateSync" }, (cmd) => stateSync(cmd.message)), Match.when({ _tag: "AuthGithubLogin" }, (cmd) => authGithubLogin(cmd)), Match.when({ _tag: "AuthGithubStatus" }, (cmd) => authGithubStatus(cmd)), Match.when({ _tag: "AuthGithubLogout" }, (cmd) => authGithubLogout(cmd)), Match.when({ _tag: "AuthCodexLogin" }, (cmd) => authCodexLogin(cmd)), Match.when({ _tag: "AuthCodexStatus" }, (cmd) => authCodexStatus(cmd)), Match.when({ _tag: "AuthCodexLogout" }, (cmd) => authCodexLogout(cmd)), Match.when({ _tag: "
|
|
26
|
-
.pipe(Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)), Match.exhaustive);
|
|
25
|
+
.pipe(Match.when({ _tag: "StatePath" }, () => statePath), Match.when({ _tag: "StateInit" }, (cmd) => stateInit(cmd)), Match.when({ _tag: "StateStatus" }, () => stateStatus), Match.when({ _tag: "StatePull" }, () => statePull), Match.when({ _tag: "StateCommit" }, (cmd) => stateCommit(cmd.message)), Match.when({ _tag: "StatePush" }, () => statePush), Match.when({ _tag: "StateSync" }, (cmd) => stateSync(cmd.message)), Match.when({ _tag: "AuthGithubLogin" }, (cmd) => authGithubLogin(cmd)), Match.when({ _tag: "AuthGithubStatus" }, (cmd) => authGithubStatus(cmd)), Match.when({ _tag: "AuthGithubLogout" }, (cmd) => authGithubLogout(cmd)), Match.when({ _tag: "AuthCodexLogin" }, (cmd) => authCodexLogin(cmd)), Match.when({ _tag: "AuthCodexStatus" }, (cmd) => authCodexStatus(cmd)), Match.when({ _tag: "AuthCodexLogout" }, (cmd) => authCodexLogout(cmd)), Match.when({ _tag: "AuthClaudeLogin" }, (cmd) => authClaudeLogin(cmd)), Match.when({ _tag: "AuthClaudeStatus" }, (cmd) => authClaudeStatus(cmd)), Match.when({ _tag: "AuthClaudeLogout" }, (cmd) => authClaudeLogout(cmd)), Match.when({ _tag: "Attach" }, (cmd) => attachTmux(cmd)), Match.when({ _tag: "Panes" }, (cmd) => listTmuxPanes(cmd)), Match.when({ _tag: "SessionsList" }, (cmd) => listTerminalSessions(cmd)), Match.when({ _tag: "SessionsKill" }, (cmd) => killTerminalProcess(cmd)))
|
|
26
|
+
.pipe(Match.when({ _tag: "SessionsLogs" }, (cmd) => tailTerminalLogs(cmd)), Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)), Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd)), Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)), Match.exhaustive);
|
|
27
27
|
// CHANGE: compose CLI program with typed errors and shell effects
|
|
28
28
|
// WHY: keep a thin entry layer over pure parsing and template generation
|
|
29
29
|
// QUOTE(ТЗ): "CLI команду... создавать докер образы"
|
package/package.json
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { Either, Match } from "effect"
|
|
2
2
|
|
|
3
3
|
import type { RawOptions } from "@effect-template/lib/core/command-options"
|
|
4
|
-
import {
|
|
5
|
-
type AuthCommand,
|
|
6
|
-
type Command,
|
|
7
|
-
defaultTemplateConfig,
|
|
8
|
-
type ParseError
|
|
9
|
-
} from "@effect-template/lib/core/domain"
|
|
4
|
+
import { type AuthCommand, type Command, type ParseError } from "@effect-template/lib/core/domain"
|
|
10
5
|
|
|
11
6
|
import { parseRawOptions } from "./parser-options.js"
|
|
12
7
|
|
|
13
8
|
type AuthOptions = {
|
|
14
9
|
readonly envGlobalPath: string
|
|
15
10
|
readonly codexAuthPath: string
|
|
11
|
+
readonly claudeAuthPath: string
|
|
16
12
|
readonly label: string | null
|
|
17
13
|
readonly token: string | null
|
|
18
14
|
readonly scopes: string | null
|
|
15
|
+
readonly authWeb: boolean
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
const missingArgument = (name: string): ParseError => ({
|
|
@@ -34,24 +31,32 @@ const normalizeLabel = (value: string | undefined): string | null => {
|
|
|
34
31
|
return trimmed.length === 0 ? null : trimmed
|
|
35
32
|
}
|
|
36
33
|
|
|
34
|
+
const defaultEnvGlobalPath = ".docker-git/.orch/env/global.env"
|
|
35
|
+
const defaultCodexAuthPath = ".docker-git/.orch/auth/codex"
|
|
36
|
+
const defaultClaudeAuthPath = ".docker-git/.orch/auth/claude"
|
|
37
|
+
|
|
37
38
|
const resolveAuthOptions = (raw: RawOptions): AuthOptions => ({
|
|
38
|
-
envGlobalPath: raw.envGlobalPath ??
|
|
39
|
-
codexAuthPath: raw.codexAuthPath ??
|
|
39
|
+
envGlobalPath: raw.envGlobalPath ?? defaultEnvGlobalPath,
|
|
40
|
+
codexAuthPath: raw.codexAuthPath ?? defaultCodexAuthPath,
|
|
41
|
+
claudeAuthPath: defaultClaudeAuthPath,
|
|
40
42
|
label: normalizeLabel(raw.label),
|
|
41
43
|
token: normalizeLabel(raw.token),
|
|
42
|
-
scopes: normalizeLabel(raw.scopes)
|
|
44
|
+
scopes: normalizeLabel(raw.scopes),
|
|
45
|
+
authWeb: raw.authWeb === true
|
|
43
46
|
})
|
|
44
47
|
|
|
45
48
|
const buildGithubCommand = (action: string, options: AuthOptions): Either.Either<AuthCommand, ParseError> =>
|
|
46
49
|
Match.value(action).pipe(
|
|
47
50
|
Match.when("login", () =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
options.authWeb && options.token !== null
|
|
52
|
+
? Either.left(invalidArgument("--token", "cannot be combined with --web"))
|
|
53
|
+
: Either.right<AuthCommand>({
|
|
54
|
+
_tag: "AuthGithubLogin",
|
|
55
|
+
label: options.label,
|
|
56
|
+
token: options.authWeb ? null : options.token,
|
|
57
|
+
scopes: options.scopes,
|
|
58
|
+
envGlobalPath: options.envGlobalPath
|
|
59
|
+
})),
|
|
55
60
|
Match.when("status", () =>
|
|
56
61
|
Either.right<AuthCommand>({
|
|
57
62
|
_tag: "AuthGithubStatus",
|
|
@@ -89,6 +94,29 @@ const buildCodexCommand = (action: string, options: AuthOptions): Either.Either<
|
|
|
89
94
|
Match.orElse(() => Either.left(invalidArgument("auth action", `unknown action '${action}'`)))
|
|
90
95
|
)
|
|
91
96
|
|
|
97
|
+
const buildClaudeCommand = (action: string, options: AuthOptions): Either.Either<AuthCommand, ParseError> =>
|
|
98
|
+
Match.value(action).pipe(
|
|
99
|
+
Match.when("login", () =>
|
|
100
|
+
Either.right<AuthCommand>({
|
|
101
|
+
_tag: "AuthClaudeLogin",
|
|
102
|
+
label: options.label,
|
|
103
|
+
claudeAuthPath: options.claudeAuthPath
|
|
104
|
+
})),
|
|
105
|
+
Match.when("status", () =>
|
|
106
|
+
Either.right<AuthCommand>({
|
|
107
|
+
_tag: "AuthClaudeStatus",
|
|
108
|
+
label: options.label,
|
|
109
|
+
claudeAuthPath: options.claudeAuthPath
|
|
110
|
+
})),
|
|
111
|
+
Match.when("logout", () =>
|
|
112
|
+
Either.right<AuthCommand>({
|
|
113
|
+
_tag: "AuthClaudeLogout",
|
|
114
|
+
label: options.label,
|
|
115
|
+
claudeAuthPath: options.claudeAuthPath
|
|
116
|
+
})),
|
|
117
|
+
Match.orElse(() => Either.left(invalidArgument("auth action", `unknown action '${action}'`)))
|
|
118
|
+
)
|
|
119
|
+
|
|
92
120
|
const buildAuthCommand = (
|
|
93
121
|
provider: string,
|
|
94
122
|
action: string,
|
|
@@ -98,6 +126,8 @@ const buildAuthCommand = (
|
|
|
98
126
|
Match.when("github", () => buildGithubCommand(action, options)),
|
|
99
127
|
Match.when("gh", () => buildGithubCommand(action, options)),
|
|
100
128
|
Match.when("codex", () => buildCodexCommand(action, options)),
|
|
129
|
+
Match.when("claude", () => buildClaudeCommand(action, options)),
|
|
130
|
+
Match.when("cc", () => buildClaudeCommand(action, options)),
|
|
101
131
|
Match.orElse(() => Either.left(invalidArgument("auth provider", `unknown provider '${provider}'`)))
|
|
102
132
|
)
|
|
103
133
|
|
|
@@ -22,7 +22,7 @@ const statusCommand: Command = { _tag: "Status" }
|
|
|
22
22
|
const downAllCommand: Command = { _tag: "DownAll" }
|
|
23
23
|
|
|
24
24
|
const parseCreate = (args: ReadonlyArray<string>): Either.Either<Command, ParseError> =>
|
|
25
|
-
Either.flatMap(parseRawOptions(args), buildCreateCommand)
|
|
25
|
+
Either.flatMap(parseRawOptions(args), (raw) => buildCreateCommand(raw))
|
|
26
26
|
|
|
27
27
|
// CHANGE: parse CLI arguments into a typed command
|
|
28
28
|
// WHY: enforce deterministic, pure parsing before any effects run
|
|
@@ -28,7 +28,7 @@ Commands:
|
|
|
28
28
|
sessions List/kill/log container terminal processes
|
|
29
29
|
ps, status Show docker compose status for all docker-git projects
|
|
30
30
|
down-all Stop all docker-git containers (docker compose down)
|
|
31
|
-
auth Manage GitHub/Codex auth for docker-git
|
|
31
|
+
auth Manage GitHub/Codex/Claude Code auth for docker-git
|
|
32
32
|
state Manage docker-git state directory via git (sync across machines)
|
|
33
33
|
|
|
34
34
|
Options:
|
|
@@ -40,7 +40,6 @@ Options:
|
|
|
40
40
|
--container-name <name> Docker container name (default: dg-<repo>)
|
|
41
41
|
--service-name <name> Compose service name (default: dg-<repo>)
|
|
42
42
|
--volume-name <name> Docker volume name (default: dg-<repo>-home)
|
|
43
|
-
--secrets-root <path> Host root for shared secrets (default: n/a)
|
|
44
43
|
--authorized-keys <path> Host path to authorized_keys (default: <projectsRoot>/authorized_keys)
|
|
45
44
|
--env-global <path> Host path to shared env file (default: <projectsRoot>/.orch/env/global.env)
|
|
46
45
|
--env-project <path> Host path to project env file (default: ./.orch/env/project.env)
|
|
@@ -72,6 +71,7 @@ Container runtime env (set via .orch/env/project.env):
|
|
|
72
71
|
Auth providers:
|
|
73
72
|
github, gh GitHub CLI auth (tokens saved to env file)
|
|
74
73
|
codex Codex CLI auth (stored under .orch/auth/codex)
|
|
74
|
+
claude, cc Claude Code CLI auth (OAuth cache stored under .orch/auth/claude)
|
|
75
75
|
|
|
76
76
|
Auth actions:
|
|
77
77
|
login Run login flow and store credentials
|
|
@@ -80,7 +80,8 @@ Auth actions:
|
|
|
80
80
|
|
|
81
81
|
Auth options:
|
|
82
82
|
--label <label> Account label (default: default)
|
|
83
|
-
--token <token> GitHub token override (login only)
|
|
83
|
+
--token <token> GitHub token override (login only; useful for non-interactive/CI)
|
|
84
|
+
--web Force OAuth web flow (login only; ignores --token)
|
|
84
85
|
--scopes <scopes> GitHub scopes (login only, default: repo,workflow,read:org)
|
|
85
86
|
--env-global <path> Env file path for GitHub tokens (default: <projectsRoot>/.orch/env/global.env)
|
|
86
87
|
--codex-auth <path> Codex auth root path (default: <projectsRoot>/.orch/auth/codex)
|
|
@@ -12,10 +12,11 @@ import {
|
|
|
12
12
|
import { runDockerComposeUpWithPortCheck } from "@effect-template/lib/usecases/projects-up"
|
|
13
13
|
import { Effect, Match, pipe } from "effect"
|
|
14
14
|
|
|
15
|
+
import { openAuthMenu } from "./menu-auth.js"
|
|
15
16
|
import { startCreateView } from "./menu-create.js"
|
|
16
17
|
import { loadSelectView } from "./menu-select-load.js"
|
|
17
|
-
import {
|
|
18
|
-
import { type MenuEnv, type MenuRunner, type MenuState, type
|
|
18
|
+
import { withSuspendedTui, writeErrorAndPause } from "./menu-shared.js"
|
|
19
|
+
import { type MenuEnv, type MenuRunner, type MenuState, type MenuViewContext } from "./menu-types.js"
|
|
19
20
|
|
|
20
21
|
// CHANGE: keep menu actions and input parsing in a dedicated module
|
|
21
22
|
// WHY: reduce cognitive complexity in the TUI entry
|
|
@@ -39,9 +40,7 @@ export type MenuContext = {
|
|
|
39
40
|
readonly state: MenuState
|
|
40
41
|
readonly runner: MenuRunner
|
|
41
42
|
readonly exit: () => void
|
|
42
|
-
|
|
43
|
-
readonly setMessage: (message: string | null) => void
|
|
44
|
-
}
|
|
43
|
+
} & MenuViewContext
|
|
45
44
|
|
|
46
45
|
export type MenuSelectionContext = MenuContext & {
|
|
47
46
|
readonly selected: number
|
|
@@ -50,6 +49,8 @@ export type MenuSelectionContext = MenuContext & {
|
|
|
50
49
|
|
|
51
50
|
const actionLabel = (action: MenuAction): string =>
|
|
52
51
|
Match.value(action).pipe(
|
|
52
|
+
Match.when({ _tag: "Auth" }, () => "Auth profiles"),
|
|
53
|
+
Match.when({ _tag: "ProjectAuth" }, () => "Project auth"),
|
|
53
54
|
Match.when({ _tag: "Up" }, () => "docker compose up"),
|
|
54
55
|
Match.when({ _tag: "Status" }, () => "docker compose ps"),
|
|
55
56
|
Match.when({ _tag: "Logs" }, () => "docker compose logs"),
|
|
@@ -67,19 +68,13 @@ const runWithSuspendedTui = (
|
|
|
67
68
|
pipe(
|
|
68
69
|
Effect.sync(() => {
|
|
69
70
|
context.setMessage(`${label}...`)
|
|
70
|
-
suspendTui()
|
|
71
71
|
}),
|
|
72
|
-
Effect.zipRight(effect),
|
|
72
|
+
Effect.zipRight(withSuspendedTui(effect, { onError: (error) => writeErrorAndPause(renderError(error)) })),
|
|
73
73
|
Effect.tap(() =>
|
|
74
74
|
Effect.sync(() => {
|
|
75
75
|
context.setMessage(`${label} finished.`)
|
|
76
76
|
})
|
|
77
77
|
),
|
|
78
|
-
Effect.ensuring(
|
|
79
|
-
Effect.sync(() => {
|
|
80
|
-
resumeTui()
|
|
81
|
-
})
|
|
82
|
-
),
|
|
83
78
|
Effect.asVoid
|
|
84
79
|
)
|
|
85
80
|
)
|
|
@@ -140,6 +135,8 @@ const handleMenuAction = (
|
|
|
140
135
|
Match.when({ _tag: "Quit" }, () => Effect.succeed(quitOutcome)),
|
|
141
136
|
Match.when({ _tag: "Create" }, () => Effect.succeed(continueOutcome(state))),
|
|
142
137
|
Match.when({ _tag: "Select" }, () => Effect.succeed(continueOutcome(state))),
|
|
138
|
+
Match.when({ _tag: "Auth" }, () => Effect.succeed(continueOutcome(state))),
|
|
139
|
+
Match.when({ _tag: "ProjectAuth" }, () => Effect.succeed(continueOutcome(state))),
|
|
143
140
|
Match.when({ _tag: "Info" }, () => Effect.succeed(continueOutcome(state))),
|
|
144
141
|
Match.when({ _tag: "Delete" }, () => Effect.succeed(continueOutcome(state))),
|
|
145
142
|
Match.when({ _tag: "Up" }, () =>
|
|
@@ -171,6 +168,22 @@ const runSelectAction = (context: MenuContext) => {
|
|
|
171
168
|
context.runner.runEffect(loadSelectView(listProjectItems, "Connect", context))
|
|
172
169
|
}
|
|
173
170
|
|
|
171
|
+
const runAuthProfilesAction = (context: MenuContext) => {
|
|
172
|
+
context.setMessage(null)
|
|
173
|
+
openAuthMenu({
|
|
174
|
+
state: context.state,
|
|
175
|
+
runner: context.runner,
|
|
176
|
+
setView: context.setView,
|
|
177
|
+
setMessage: context.setMessage,
|
|
178
|
+
setActiveDir: context.setActiveDir
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const runProjectAuthAction = (context: MenuContext) => {
|
|
183
|
+
context.setMessage(null)
|
|
184
|
+
context.runner.runEffect(loadSelectView(listProjectItems, "Auth", context))
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
const runDownAllAction = (context: MenuContext) => {
|
|
175
188
|
context.setMessage(null)
|
|
176
189
|
runWithSuspendedTui(downAllDockerGitProjects, context, "Stopping all docker-git containers")
|
|
@@ -222,6 +235,12 @@ export const handleMenuActionSelection = (action: MenuAction, context: MenuConte
|
|
|
222
235
|
Match.when({ _tag: "Select" }, () => {
|
|
223
236
|
runSelectAction(context)
|
|
224
237
|
}),
|
|
238
|
+
Match.when({ _tag: "Auth" }, () => {
|
|
239
|
+
runAuthProfilesAction(context)
|
|
240
|
+
}),
|
|
241
|
+
Match.when({ _tag: "ProjectAuth" }, () => {
|
|
242
|
+
runProjectAuthAction(context)
|
|
243
|
+
}),
|
|
225
244
|
Match.when({ _tag: "Info" }, () => {
|
|
226
245
|
runInfoAction(context)
|
|
227
246
|
}),
|