@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,271 @@
|
|
|
1
|
+
import { Effect, Match, pipe } from "effect"
|
|
2
|
+
|
|
3
|
+
import type { AppError } from "@effect-template/lib/usecases/errors"
|
|
4
|
+
import type { ProjectItem } from "@effect-template/lib/usecases/projects"
|
|
5
|
+
|
|
6
|
+
import { nextBufferValue } from "./menu-buffer-input.js"
|
|
7
|
+
import { handleMenuNumberInput, submitPromptStep } from "./menu-input-utils.js"
|
|
8
|
+
import {
|
|
9
|
+
type ProjectAuthMenuAction,
|
|
10
|
+
projectAuthMenuActionByIndex,
|
|
11
|
+
projectAuthMenuSize,
|
|
12
|
+
projectAuthViewSteps,
|
|
13
|
+
readProjectAuthSnapshot,
|
|
14
|
+
writeProjectAuthFlow
|
|
15
|
+
} from "./menu-project-auth-data.js"
|
|
16
|
+
import { resetToMenu } from "./menu-shared.js"
|
|
17
|
+
import type {
|
|
18
|
+
MenuEnv,
|
|
19
|
+
MenuKeyInput,
|
|
20
|
+
MenuRunner,
|
|
21
|
+
MenuViewContext,
|
|
22
|
+
ProjectAuthFlow,
|
|
23
|
+
ProjectAuthSnapshot,
|
|
24
|
+
ViewState
|
|
25
|
+
} from "./menu-types.js"
|
|
26
|
+
|
|
27
|
+
type ProjectAuthContext = Pick<MenuViewContext, "setView" | "setMessage" | "setActiveDir"> & {
|
|
28
|
+
readonly runner: MenuRunner
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ProjectAuthContextWithProject = ProjectAuthContext & {
|
|
32
|
+
readonly project: ProjectItem
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const startProjectAuthMenu = (
|
|
36
|
+
project: ProjectItem,
|
|
37
|
+
snapshot: ProjectAuthSnapshot,
|
|
38
|
+
context: Pick<MenuViewContext, "setView" | "setMessage">
|
|
39
|
+
) => {
|
|
40
|
+
context.setView({ _tag: "ProjectAuthMenu", selected: 0, project, snapshot })
|
|
41
|
+
context.setMessage(null)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const startProjectAuthPrompt = (
|
|
45
|
+
project: ProjectItem,
|
|
46
|
+
snapshot: ProjectAuthSnapshot,
|
|
47
|
+
flow: ProjectAuthFlow,
|
|
48
|
+
context: Pick<MenuViewContext, "setView" | "setMessage">
|
|
49
|
+
) => {
|
|
50
|
+
context.setView({
|
|
51
|
+
_tag: "ProjectAuthPrompt",
|
|
52
|
+
flow,
|
|
53
|
+
step: 0,
|
|
54
|
+
buffer: "",
|
|
55
|
+
values: {},
|
|
56
|
+
project,
|
|
57
|
+
snapshot
|
|
58
|
+
})
|
|
59
|
+
context.setMessage(null)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const loadProjectAuthMenuView = (
|
|
63
|
+
project: ProjectItem,
|
|
64
|
+
context: Pick<MenuViewContext, "setView" | "setMessage">
|
|
65
|
+
): Effect.Effect<void, AppError, MenuEnv> =>
|
|
66
|
+
pipe(
|
|
67
|
+
readProjectAuthSnapshot(project),
|
|
68
|
+
Effect.tap((snapshot) =>
|
|
69
|
+
Effect.sync(() => {
|
|
70
|
+
startProjectAuthMenu(project, snapshot, context)
|
|
71
|
+
})
|
|
72
|
+
),
|
|
73
|
+
Effect.asVoid
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const successMessage = (flow: ProjectAuthFlow, label: string): string =>
|
|
77
|
+
Match.value(flow).pipe(
|
|
78
|
+
Match.when("ProjectGithubConnect", () => `Connected GitHub label (${label}) to project.`),
|
|
79
|
+
Match.when("ProjectGithubDisconnect", () => "Disconnected GitHub from project."),
|
|
80
|
+
Match.when("ProjectGitConnect", () => `Connected Git label (${label}) to project.`),
|
|
81
|
+
Match.when("ProjectGitDisconnect", () => "Disconnected Git from project."),
|
|
82
|
+
Match.when("ProjectClaudeConnect", () => `Connected Claude label (${label}) to project.`),
|
|
83
|
+
Match.when("ProjectClaudeDisconnect", () => "Disconnected Claude from project."),
|
|
84
|
+
Match.exhaustive
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const runProjectAuthEffect = (
|
|
88
|
+
project: ProjectItem,
|
|
89
|
+
flow: ProjectAuthFlow,
|
|
90
|
+
values: Readonly<Record<string, string>>,
|
|
91
|
+
label: string,
|
|
92
|
+
context: ProjectAuthContext
|
|
93
|
+
) => {
|
|
94
|
+
context.runner.runEffect(
|
|
95
|
+
pipe(
|
|
96
|
+
writeProjectAuthFlow(project, flow, values),
|
|
97
|
+
Effect.zipRight(readProjectAuthSnapshot(project)),
|
|
98
|
+
Effect.tap((snapshot) =>
|
|
99
|
+
Effect.sync(() => {
|
|
100
|
+
startProjectAuthMenu(project, snapshot, context)
|
|
101
|
+
context.setMessage(successMessage(flow, label))
|
|
102
|
+
})
|
|
103
|
+
),
|
|
104
|
+
Effect.asVoid
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const submitProjectAuthPrompt = (
|
|
110
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthPrompt" }>,
|
|
111
|
+
context: ProjectAuthContext
|
|
112
|
+
) => {
|
|
113
|
+
const steps = projectAuthViewSteps(view.flow)
|
|
114
|
+
submitPromptStep(
|
|
115
|
+
view,
|
|
116
|
+
steps,
|
|
117
|
+
context,
|
|
118
|
+
() => {
|
|
119
|
+
startProjectAuthMenu(view.project, view.snapshot, context)
|
|
120
|
+
},
|
|
121
|
+
(nextValues) => {
|
|
122
|
+
const rawLabel = (nextValues["label"] ?? "").trim()
|
|
123
|
+
const label = rawLabel.length > 0 ? rawLabel : "default"
|
|
124
|
+
runProjectAuthEffect(view.project, view.flow, nextValues, label, context)
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const runProjectAuthAction = (
|
|
130
|
+
action: ProjectAuthMenuAction,
|
|
131
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthMenu" }>,
|
|
132
|
+
context: ProjectAuthContext
|
|
133
|
+
) => {
|
|
134
|
+
if (action === "Back") {
|
|
135
|
+
resetToMenu(context)
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
if (action === "Refresh") {
|
|
139
|
+
context.runner.runEffect(loadProjectAuthMenuView(view.project, context))
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
action === "ProjectGithubDisconnect" || action === "ProjectGitDisconnect" || action === "ProjectClaudeDisconnect"
|
|
145
|
+
) {
|
|
146
|
+
runProjectAuthEffect(view.project, action, {}, "default", context)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
startProjectAuthPrompt(view.project, view.snapshot, action, context)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const setProjectAuthMenuSelection = (
|
|
154
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthMenu" }>,
|
|
155
|
+
selected: number,
|
|
156
|
+
context: Pick<MenuViewContext, "setView">
|
|
157
|
+
) => {
|
|
158
|
+
context.setView({ ...view, selected })
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const shiftProjectAuthMenuSelection = (
|
|
162
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthMenu" }>,
|
|
163
|
+
delta: number,
|
|
164
|
+
context: Pick<MenuViewContext, "setView">
|
|
165
|
+
) => {
|
|
166
|
+
const menuSize = projectAuthMenuSize()
|
|
167
|
+
const selected = (view.selected + delta + menuSize) % menuSize
|
|
168
|
+
setProjectAuthMenuSelection(view, selected, context)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const runProjectAuthMenuSelection = (
|
|
172
|
+
selected: number,
|
|
173
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthMenu" }>,
|
|
174
|
+
context: ProjectAuthContext
|
|
175
|
+
) => {
|
|
176
|
+
const action = projectAuthMenuActionByIndex(selected)
|
|
177
|
+
if (action === null) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
runProjectAuthAction(action, view, context)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const handleProjectAuthMenuNumberInput = (
|
|
184
|
+
input: string,
|
|
185
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthMenu" }>,
|
|
186
|
+
context: ProjectAuthContext
|
|
187
|
+
) => {
|
|
188
|
+
handleMenuNumberInput(
|
|
189
|
+
input,
|
|
190
|
+
context,
|
|
191
|
+
projectAuthMenuActionByIndex,
|
|
192
|
+
(action) => {
|
|
193
|
+
runProjectAuthAction(action, view, context)
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const handleProjectAuthMenuInput = (
|
|
199
|
+
input: string,
|
|
200
|
+
key: MenuKeyInput,
|
|
201
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthMenu" }>,
|
|
202
|
+
context: ProjectAuthContext
|
|
203
|
+
) => {
|
|
204
|
+
if (key.escape) {
|
|
205
|
+
resetToMenu(context)
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
if (key.upArrow) {
|
|
209
|
+
shiftProjectAuthMenuSelection(view, -1, context)
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
if (key.downArrow) {
|
|
213
|
+
shiftProjectAuthMenuSelection(view, 1, context)
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
if (key.return) {
|
|
217
|
+
runProjectAuthMenuSelection(view.selected, view, context)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
handleProjectAuthMenuNumberInput(input, view, context)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
type SetPromptBufferArgs = {
|
|
224
|
+
readonly input: string
|
|
225
|
+
readonly key: MenuKeyInput
|
|
226
|
+
readonly view: Extract<ViewState, { readonly _tag: "ProjectAuthPrompt" }>
|
|
227
|
+
readonly context: Pick<MenuViewContext, "setView">
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const setProjectAuthPromptBuffer = (args: SetPromptBufferArgs) => {
|
|
231
|
+
const nextBuffer = nextBufferValue(args.input, args.key, args.view.buffer)
|
|
232
|
+
if (nextBuffer === null) {
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
args.context.setView({ ...args.view, buffer: nextBuffer })
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const handleProjectAuthPromptInput = (
|
|
239
|
+
input: string,
|
|
240
|
+
key: MenuKeyInput,
|
|
241
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthPrompt" }>,
|
|
242
|
+
context: ProjectAuthContext
|
|
243
|
+
) => {
|
|
244
|
+
if (key.escape) {
|
|
245
|
+
startProjectAuthMenu(view.project, view.snapshot, context)
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
if (key.return) {
|
|
249
|
+
submitProjectAuthPrompt(view, context)
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
setProjectAuthPromptBuffer({ input, key, view, context })
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const openProjectAuthMenu = (context: ProjectAuthContextWithProject): void => {
|
|
256
|
+
context.setMessage(`Loading project auth (${context.project.displayName})...`)
|
|
257
|
+
context.runner.runEffect(loadProjectAuthMenuView(context.project, context))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export const handleProjectAuthInput = (
|
|
261
|
+
input: string,
|
|
262
|
+
key: MenuKeyInput,
|
|
263
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthMenu" | "ProjectAuthPrompt" }>,
|
|
264
|
+
context: ProjectAuthContext
|
|
265
|
+
) => {
|
|
266
|
+
if (view._tag === "ProjectAuthMenu") {
|
|
267
|
+
handleProjectAuthMenuInput(input, key, view, context)
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
handleProjectAuthPromptInput(input, key, view, context)
|
|
271
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Box, Text } from "ink"
|
|
2
|
+
import React from "react"
|
|
3
|
+
|
|
4
|
+
import { authMenuLabels, authViewSteps, authViewTitle } from "./menu-auth-data.js"
|
|
5
|
+
import {
|
|
6
|
+
renderMenuHelp,
|
|
7
|
+
renderPromptLayout,
|
|
8
|
+
renderSelectableMenuList,
|
|
9
|
+
resolvePromptState
|
|
10
|
+
} from "./menu-render-common.js"
|
|
11
|
+
import { renderLayout } from "./menu-render-layout.js"
|
|
12
|
+
import type { AuthSnapshot, ViewState } from "./menu-types.js"
|
|
13
|
+
|
|
14
|
+
const renderCountLine = (title: string, count: number): string => `${title}: ${count}`
|
|
15
|
+
|
|
16
|
+
export const renderAuthMenu = (
|
|
17
|
+
snapshot: AuthSnapshot,
|
|
18
|
+
selected: number,
|
|
19
|
+
message: string | null
|
|
20
|
+
): React.ReactElement => {
|
|
21
|
+
const el = React.createElement
|
|
22
|
+
const list = renderSelectableMenuList(authMenuLabels(), selected)
|
|
23
|
+
return renderLayout(
|
|
24
|
+
"docker-git / Auth profiles",
|
|
25
|
+
[
|
|
26
|
+
el(Text, null, `Global env: ${snapshot.globalEnvPath}`),
|
|
27
|
+
el(Text, null, `Claude auth: ${snapshot.claudeAuthPath}`),
|
|
28
|
+
el(Text, { color: "gray" }, renderCountLine("Entries", snapshot.totalEntries)),
|
|
29
|
+
el(Text, { color: "gray" }, renderCountLine("GitHub tokens", snapshot.githubTokenEntries)),
|
|
30
|
+
el(Text, { color: "gray" }, renderCountLine("Git tokens", snapshot.gitTokenEntries)),
|
|
31
|
+
el(Text, { color: "gray" }, renderCountLine("Git users", snapshot.gitUserEntries)),
|
|
32
|
+
el(Text, { color: "gray" }, renderCountLine("Claude logins", snapshot.claudeAuthEntries)),
|
|
33
|
+
el(Box, { flexDirection: "column", marginTop: 1 }, ...list),
|
|
34
|
+
renderMenuHelp("Use arrows + Enter, or type a number.")
|
|
35
|
+
],
|
|
36
|
+
message
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const renderAuthPrompt = (
|
|
41
|
+
view: Extract<ViewState, { readonly _tag: "AuthPrompt" }>,
|
|
42
|
+
message: string | null
|
|
43
|
+
): React.ReactElement => {
|
|
44
|
+
const el = React.createElement
|
|
45
|
+
const { prompt, visibleBuffer } = resolvePromptState(authViewSteps(view.flow), view.step, view.buffer)
|
|
46
|
+
let helpLine = "Enter = next, Esc = cancel."
|
|
47
|
+
if (view.flow === "GithubOauth" || view.flow === "ClaudeOauth") {
|
|
48
|
+
helpLine = "Enter = start OAuth, Esc = cancel."
|
|
49
|
+
} else if (view.flow === "ClaudeLogout") {
|
|
50
|
+
helpLine = "Enter = logout, Esc = cancel."
|
|
51
|
+
}
|
|
52
|
+
return renderPromptLayout({
|
|
53
|
+
title: `docker-git / Auth / ${authViewTitle(view.flow)}`,
|
|
54
|
+
header: [
|
|
55
|
+
el(Text, { color: "gray" }, `Global env: ${view.snapshot.globalEnvPath}`),
|
|
56
|
+
...(view.flow === "ClaudeOauth" || view.flow === "ClaudeLogout"
|
|
57
|
+
? [el(Text, { color: "gray" }, `Claude auth: ${view.snapshot.claudeAuthPath}`)]
|
|
58
|
+
: [])
|
|
59
|
+
],
|
|
60
|
+
prompt,
|
|
61
|
+
visibleBuffer,
|
|
62
|
+
helpLine,
|
|
63
|
+
message
|
|
64
|
+
})
|
|
65
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Box, Text } from "ink"
|
|
2
|
+
import React from "react"
|
|
3
|
+
|
|
4
|
+
import { renderLayout } from "./menu-render-layout.js"
|
|
5
|
+
|
|
6
|
+
export const renderSelectableMenuList = (
|
|
7
|
+
labels: ReadonlyArray<string>,
|
|
8
|
+
selected: number
|
|
9
|
+
): ReadonlyArray<React.ReactElement> => {
|
|
10
|
+
const el = React.createElement
|
|
11
|
+
return labels.map((label, index) =>
|
|
12
|
+
el(
|
|
13
|
+
Text,
|
|
14
|
+
{ key: `${index}-${label}`, color: index === selected ? "green" : "white" },
|
|
15
|
+
`${index === selected ? ">" : " "} ${index + 1}) ${label}`
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const renderMenuHelp = (primaryLine: string): React.ReactElement => {
|
|
21
|
+
const el = React.createElement
|
|
22
|
+
return el(
|
|
23
|
+
Box,
|
|
24
|
+
{ marginTop: 1, flexDirection: "column" },
|
|
25
|
+
el(Text, { color: "gray" }, primaryLine),
|
|
26
|
+
el(Text, { color: "gray" }, "Esc returns to the main menu.")
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type PromptStepLike = {
|
|
31
|
+
readonly label: string
|
|
32
|
+
readonly secret: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const resolvePromptState = (
|
|
36
|
+
steps: ReadonlyArray<PromptStepLike>,
|
|
37
|
+
step: number,
|
|
38
|
+
buffer: string
|
|
39
|
+
): { readonly prompt: string; readonly visibleBuffer: string } => {
|
|
40
|
+
const current = steps[step]
|
|
41
|
+
const prompt = current?.label ?? "Value"
|
|
42
|
+
const isSecret = current?.secret === true
|
|
43
|
+
const visibleBuffer = isSecret ? "*".repeat(buffer.length) : buffer
|
|
44
|
+
return { prompt, visibleBuffer }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type RenderPromptArgs = {
|
|
48
|
+
readonly title: string
|
|
49
|
+
readonly header: ReadonlyArray<React.ReactElement>
|
|
50
|
+
readonly prompt: string
|
|
51
|
+
readonly visibleBuffer: string
|
|
52
|
+
readonly helpLine: string
|
|
53
|
+
readonly message: string | null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const renderPromptLayout = (args: RenderPromptArgs): React.ReactElement => {
|
|
57
|
+
const el = React.createElement
|
|
58
|
+
return renderLayout(
|
|
59
|
+
args.title,
|
|
60
|
+
[
|
|
61
|
+
...args.header,
|
|
62
|
+
el(Box, { marginTop: 1 }, el(Text, null, `${args.prompt}: `), el(Text, { color: "green" }, args.visibleBuffer)),
|
|
63
|
+
el(Box, { marginTop: 1, flexDirection: "column" }, el(Text, { color: "gray" }, args.helpLine))
|
|
64
|
+
],
|
|
65
|
+
args.message
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Box, Text } from "ink"
|
|
2
|
+
import React from "react"
|
|
3
|
+
|
|
4
|
+
const renderMessage = (message: string | null): React.ReactElement | null => {
|
|
5
|
+
if (!message) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
return React.createElement(
|
|
9
|
+
Box,
|
|
10
|
+
{ marginTop: 1 },
|
|
11
|
+
React.createElement(Text, { color: "magenta" }, message)
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const renderLayout = (
|
|
16
|
+
title: string,
|
|
17
|
+
body: ReadonlyArray<React.ReactElement>,
|
|
18
|
+
message: string | null
|
|
19
|
+
): React.ReactElement => {
|
|
20
|
+
const el = React.createElement
|
|
21
|
+
const messageView = renderMessage(message)
|
|
22
|
+
const tail = messageView ? [messageView] : []
|
|
23
|
+
return el(
|
|
24
|
+
Box,
|
|
25
|
+
{ flexDirection: "column", padding: 1, borderStyle: "round" },
|
|
26
|
+
el(Text, { color: "cyan", bold: true }, title),
|
|
27
|
+
...body,
|
|
28
|
+
...tail
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Box, Text } from "ink"
|
|
2
|
+
import React from "react"
|
|
3
|
+
|
|
4
|
+
import { projectAuthMenuLabels, projectAuthViewSteps } from "./menu-project-auth-data.js"
|
|
5
|
+
import {
|
|
6
|
+
renderMenuHelp,
|
|
7
|
+
renderPromptLayout,
|
|
8
|
+
renderSelectableMenuList,
|
|
9
|
+
resolvePromptState
|
|
10
|
+
} from "./menu-render-common.js"
|
|
11
|
+
import { renderLayout } from "./menu-render-layout.js"
|
|
12
|
+
import type { ProjectAuthSnapshot, ViewState } from "./menu-types.js"
|
|
13
|
+
|
|
14
|
+
const renderActiveLabel = (value: string | null): string => value ?? "(not set)"
|
|
15
|
+
|
|
16
|
+
const renderCountLine = (title: string, count: number): string => `${title}: ${count}`
|
|
17
|
+
|
|
18
|
+
export const renderProjectAuthMenu = (
|
|
19
|
+
snapshot: ProjectAuthSnapshot,
|
|
20
|
+
selected: number,
|
|
21
|
+
message: string | null
|
|
22
|
+
): React.ReactElement => {
|
|
23
|
+
const el = React.createElement
|
|
24
|
+
const list = renderSelectableMenuList(projectAuthMenuLabels(), selected)
|
|
25
|
+
|
|
26
|
+
return renderLayout(
|
|
27
|
+
"docker-git / Project auth",
|
|
28
|
+
[
|
|
29
|
+
el(Text, null, `Project: ${snapshot.projectName}`),
|
|
30
|
+
el(Text, { color: "gray" }, `Dir: ${snapshot.projectDir}`),
|
|
31
|
+
el(Text, { color: "gray" }, `Project env: ${snapshot.envProjectPath}`),
|
|
32
|
+
el(Text, { color: "gray" }, `Global env: ${snapshot.envGlobalPath}`),
|
|
33
|
+
el(Text, { color: "gray" }, `Claude auth: ${snapshot.claudeAuthPath}`),
|
|
34
|
+
el(
|
|
35
|
+
Box,
|
|
36
|
+
{ marginTop: 1, flexDirection: "column" },
|
|
37
|
+
el(Text, { color: "gray" }, `GitHub label: ${renderActiveLabel(snapshot.activeGithubLabel)}`),
|
|
38
|
+
el(Text, { color: "gray" }, renderCountLine("Available GitHub tokens", snapshot.githubTokenEntries)),
|
|
39
|
+
el(Text, { color: "gray" }, `Git label: ${renderActiveLabel(snapshot.activeGitLabel)}`),
|
|
40
|
+
el(Text, { color: "gray" }, renderCountLine("Available Git tokens", snapshot.gitTokenEntries)),
|
|
41
|
+
el(Text, { color: "gray" }, `Claude label: ${renderActiveLabel(snapshot.activeClaudeLabel)}`),
|
|
42
|
+
el(Text, { color: "gray" }, renderCountLine("Available Claude logins", snapshot.claudeAuthEntries))
|
|
43
|
+
),
|
|
44
|
+
el(Box, { flexDirection: "column", marginTop: 1 }, ...list),
|
|
45
|
+
renderMenuHelp("Use arrows + Enter, or type a number from the list.")
|
|
46
|
+
],
|
|
47
|
+
message
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const renderProjectAuthPrompt = (
|
|
52
|
+
view: Extract<ViewState, { readonly _tag: "ProjectAuthPrompt" }>,
|
|
53
|
+
message: string | null
|
|
54
|
+
): React.ReactElement => {
|
|
55
|
+
const el = React.createElement
|
|
56
|
+
const { prompt, visibleBuffer } = resolvePromptState(projectAuthViewSteps(view.flow), view.step, view.buffer)
|
|
57
|
+
|
|
58
|
+
return renderPromptLayout({
|
|
59
|
+
title: "docker-git / Project auth / Set label",
|
|
60
|
+
header: [
|
|
61
|
+
el(Text, { color: "gray" }, `Project: ${view.snapshot.projectName}`),
|
|
62
|
+
el(Text, { color: "gray" }, `Project env: ${view.snapshot.envProjectPath}`),
|
|
63
|
+
el(Text, { color: "gray" }, `Global env: ${view.snapshot.envGlobalPath}`)
|
|
64
|
+
],
|
|
65
|
+
prompt,
|
|
66
|
+
visibleBuffer,
|
|
67
|
+
helpLine: "Enter = apply, Esc = cancel.",
|
|
68
|
+
message
|
|
69
|
+
})
|
|
70
|
+
}
|
|
@@ -5,7 +5,7 @@ import type React from "react"
|
|
|
5
5
|
import type { ProjectItem } from "@effect-template/lib/usecases/projects"
|
|
6
6
|
import type { SelectProjectRuntime } from "./menu-types.js"
|
|
7
7
|
|
|
8
|
-
export type SelectPurpose = "Connect" | "Down" | "Info" | "Delete"
|
|
8
|
+
export type SelectPurpose = "Connect" | "Down" | "Info" | "Delete" | "Auth"
|
|
9
9
|
|
|
10
10
|
const formatRepoRef = (repoRef: string): string => {
|
|
11
11
|
const trimmed = repoRef.trim()
|
|
@@ -58,6 +58,7 @@ const renderRuntimeLabel = (runtime: SelectProjectRuntime): string =>
|
|
|
58
58
|
export const selectTitle = (purpose: SelectPurpose): string =>
|
|
59
59
|
Match.value(purpose).pipe(
|
|
60
60
|
Match.when("Connect", () => "docker-git / Select project"),
|
|
61
|
+
Match.when("Auth", () => "docker-git / Project auth"),
|
|
61
62
|
Match.when("Down", () => "docker-git / Stop container"),
|
|
62
63
|
Match.when("Info", () => "docker-git / Show connection info"),
|
|
63
64
|
Match.when("Delete", () => "docker-git / Delete project"),
|
|
@@ -73,6 +74,7 @@ export const selectHint = (
|
|
|
73
74
|
"Connect",
|
|
74
75
|
() => `Enter = select + SSH, P = toggle Playwright MCP (${connectEnableMcpPlaywright ? "on" : "off"}), Esc = back`
|
|
75
76
|
),
|
|
77
|
+
Match.when("Auth", () => "Enter = open project auth menu, Esc = back"),
|
|
76
78
|
Match.when("Down", () => "Enter = stop container, Esc = back"),
|
|
77
79
|
Match.when("Info", () => "Use arrows to browse details, Enter = set active, Esc = back"),
|
|
78
80
|
Match.when("Delete", () => "Enter = ask/confirm delete, Esc = cancel"),
|
|
@@ -196,6 +198,14 @@ export const renderSelectDetails = (
|
|
|
196
198
|
|
|
197
199
|
return Match.value(purpose).pipe(
|
|
198
200
|
Match.when("Connect", () => renderConnectDetails(el, context, common, connectEnableMcpPlaywright)),
|
|
201
|
+
Match.when("Auth", () => [
|
|
202
|
+
titleRow(el, "Project auth"),
|
|
203
|
+
...common,
|
|
204
|
+
el(Text, { wrap: "wrap" }, `Repo: ${context.item.repoUrl} (${context.refLabel})`),
|
|
205
|
+
el(Text, { wrap: "wrap" }, `Env global: ${context.item.envGlobalPath}`),
|
|
206
|
+
el(Text, { wrap: "wrap" }, `Env project: ${context.item.envProjectPath}`),
|
|
207
|
+
el(Text, { color: "gray", wrap: "wrap" }, "Press Enter to manage labels for this project.")
|
|
208
|
+
]),
|
|
199
209
|
Match.when("Info", () => renderInfoDetails(el, context, common)),
|
|
200
210
|
Match.when("Down", () => [
|
|
201
211
|
titleRow(el, "Stop container"),
|
|
@@ -3,6 +3,7 @@ import { Box, Text } from "ink"
|
|
|
3
3
|
import React from "react"
|
|
4
4
|
|
|
5
5
|
import type { ProjectItem } from "@effect-template/lib/usecases/projects"
|
|
6
|
+
import { renderLayout } from "./menu-render-layout.js"
|
|
6
7
|
import {
|
|
7
8
|
buildSelectLabels,
|
|
8
9
|
renderSelectDetails,
|
|
@@ -41,34 +42,6 @@ export const renderStepLabel = (step: CreateStep, defaults: CreateInputs): strin
|
|
|
41
42
|
Match.exhaustive
|
|
42
43
|
)
|
|
43
44
|
|
|
44
|
-
const renderMessage = (message: string | null): React.ReactElement | null => {
|
|
45
|
-
if (!message) {
|
|
46
|
-
return null
|
|
47
|
-
}
|
|
48
|
-
return React.createElement(
|
|
49
|
-
Box,
|
|
50
|
-
{ marginTop: 1 },
|
|
51
|
-
React.createElement(Text, { color: "magenta" }, message)
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const renderLayout = (
|
|
56
|
-
title: string,
|
|
57
|
-
body: ReadonlyArray<React.ReactElement>,
|
|
58
|
-
message: string | null
|
|
59
|
-
): React.ReactElement => {
|
|
60
|
-
const el = React.createElement
|
|
61
|
-
const messageView = renderMessage(message)
|
|
62
|
-
const tail = messageView ? [messageView] : []
|
|
63
|
-
return el(
|
|
64
|
-
Box,
|
|
65
|
-
{ flexDirection: "column", padding: 1, borderStyle: "round" },
|
|
66
|
-
el(Text, { color: "cyan", bold: true }, title),
|
|
67
|
-
...body,
|
|
68
|
-
...tail
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
45
|
const compactElements = (
|
|
73
46
|
items: ReadonlyArray<React.ReactElement | null>
|
|
74
47
|
): ReadonlyArray<React.ReactElement> => items.filter((item): item is React.ReactElement => item !== null)
|
|
@@ -82,7 +55,7 @@ const renderMenuHints = (el: typeof React.createElement): React.ReactElement =>
|
|
|
82
55
|
el(
|
|
83
56
|
Text,
|
|
84
57
|
{ color: "gray" },
|
|
85
|
-
" - Aliases: create/c, select/s, info/i, status/ps, logs/l, down/d, down-all/da, delete/del, quit/q"
|
|
58
|
+
" - 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"
|
|
86
59
|
),
|
|
87
60
|
el(Text, { color: "gray" }, " - Use arrows and Enter to run.")
|
|
88
61
|
)
|
|
@@ -181,6 +154,9 @@ export const renderCreate = (
|
|
|
181
154
|
)
|
|
182
155
|
}
|
|
183
156
|
|
|
157
|
+
export { renderAuthMenu, renderAuthPrompt } from "./menu-render-auth.js"
|
|
158
|
+
export { renderProjectAuthMenu, renderProjectAuthPrompt } from "./menu-render-project-auth.js"
|
|
159
|
+
|
|
184
160
|
const computeListWidth = (labels: ReadonlyArray<string>): number => {
|
|
185
161
|
const maxLabelWidth = labels.length > 0 ? Math.max(...labels.map((label) => label.length)) : 24
|
|
186
162
|
return Math.min(Math.max(maxLabelWidth + 2, 28), 54)
|