dsh-code 1.0.6 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +123 -26
- package/README.md +124 -27
- package/bin/deepseek.mjs +283 -35
- package/cordis.patch.yml +97 -0
- package/lib/index.mjs +5008 -881
- package/lib/session-query.mjs +150 -0
- package/lib/startup.mjs +4 -4
- package/lib/{theme-DCT8Y2xf.mjs → theme-7u5Qo3dF.mjs} +657 -20
- package/lib/types/app.d.ts +106 -62
- package/lib/types/authorization-panel.d.ts +3 -3
- package/lib/types/git-workflow.d.ts +91 -2
- package/lib/types/i18n.d.ts +39 -0
- package/lib/types/index.d.ts +100 -1
- package/lib/types/input-split.d.ts +1 -1
- package/lib/types/kernel-panels.d.ts +107 -29
- package/lib/types/language-panel.d.ts +12 -0
- package/lib/types/locales/en.d.ts +450 -0
- package/lib/types/locales/zh.d.ts +9 -0
- package/lib/types/mentions.d.ts +7 -3
- package/lib/types/models.d.ts +14 -0
- package/lib/types/panel-accent.d.ts +28 -0
- package/lib/types/rainbow.d.ts +69 -0
- package/lib/types/render/animations.d.ts +42 -0
- package/lib/types/render/editor.d.ts +4 -3
- package/lib/types/render/ime-cursor.d.ts +60 -0
- package/lib/types/render/inspector.d.ts +26 -0
- package/lib/types/render/lines.d.ts +21 -1
- package/lib/types/render/markdown.d.ts +1 -1
- package/lib/types/render/projection.d.ts +130 -4
- package/lib/types/render/status.d.ts +9 -9
- package/lib/types/render/text.d.ts +6 -0
- package/lib/types/render/usage.d.ts +113 -0
- package/lib/types/session-directory.d.ts +17 -0
- package/lib/types/session-query.d.ts +92 -0
- package/lib/types/startup.d.ts +1 -1
- package/lib/types/terminal-title.d.ts +66 -0
- package/lib/types/theme-panel.d.ts +2 -2
- package/lib/types/theme.d.ts +271 -52
- package/lib/types/update-panel.d.ts +49 -0
- package/lib/types/update.d.ts +75 -0
- package/lib/types/version.d.ts +4 -3
- package/package.json +246 -90
- package/src/app.ts +1369 -509
- package/src/approval.ts +166 -166
- package/src/authorization-panel.ts +19 -16
- package/src/editor-keys.ts +371 -371
- package/src/git-workflow.ts +229 -3
- package/src/i18n.ts +68 -0
- package/src/index.ts +534 -80
- package/src/input-split.ts +3 -3
- package/src/internals.ts +5 -0
- package/src/kernel-panels.ts +554 -86
- package/src/keyboard.ts +5 -4
- package/src/language-panel.ts +53 -0
- package/src/locales/en.ts +489 -0
- package/src/locales/zh.ts +488 -0
- package/src/mentions.ts +8 -4
- package/src/models.ts +264 -212
- package/src/panel-accent.ts +41 -0
- package/src/presets.ts +1 -1
- package/src/provider-settings.ts +1 -1
- package/src/rainbow.ts +208 -0
- package/src/render/animations.ts +104 -6
- package/src/render/editor.ts +25 -24
- package/src/render/export.ts +116 -95
- package/src/render/ime-cursor.ts +147 -0
- package/src/render/inspector.ts +42 -0
- package/src/render/lines.ts +628 -415
- package/src/render/markdown.ts +15 -3
- package/src/render/projection.ts +572 -21
- package/src/render/status.ts +59 -39
- package/src/render/text.ts +14 -0
- package/src/render/tool-preview.ts +77 -77
- package/src/render/usage.ts +430 -0
- package/src/render/width.ts +2 -2
- package/src/session-directory.ts +8 -6
- package/src/session-query.ts +239 -0
- package/src/startup.ts +3 -3
- package/src/subagents.ts +229 -229
- package/src/terminal-title.ts +190 -0
- package/src/theme-panel.ts +17 -21
- package/src/theme.ts +281 -33
- package/src/update-panel.ts +256 -0
- package/src/update.ts +126 -0
- package/src/version.ts +58 -20
- package/src/whale-glyph.ts +23 -23
package/src/approval.ts
CHANGED
|
@@ -1,166 +1,166 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The terminal approval answerer: one `approval/request` waterfall listener
|
|
3
|
-
* that renders the pending question as a y/n bar and resolves the decision
|
|
4
|
-
* back into the waterfall. Mirrors the web host's composer takeover — the
|
|
5
|
-
* service (audit pair, policy gate, fail-closed defaults) all live in
|
|
6
|
-
* dsh-base; this module only answers for agents this TUI owns.
|
|
7
|
-
*
|
|
8
|
-
* Vocabulary note: a client answerer may only ever resolve `'allowed-once'`
|
|
9
|
-
* or `'rejected'`; `'cancelled'` belongs to the request signal and
|
|
10
|
-
* `'unavailable'` to the fail-closed waterfall default.
|
|
11
|
-
*
|
|
12
|
-
* @module @deepseek-ai/dsh-code/approval
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { Context } from '@deepseek-ai/cordis'
|
|
16
|
-
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
17
|
-
import type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval'
|
|
18
|
-
|
|
19
|
-
/** The answer values a client answerer may resolve with. */
|
|
20
|
-
export type ApprovalAnswer = 'allowed-once' | 'rejected'
|
|
21
|
-
|
|
22
|
-
/** One pending approval question, derived from the request for rendering. */
|
|
23
|
-
export interface PendingApproval {
|
|
24
|
-
/** The asker's human-readable explanation, or a generic fallback. */
|
|
25
|
-
headline: string
|
|
26
|
-
/** The tool the question is about. */
|
|
27
|
-
toolName: string
|
|
28
|
-
/** Command-line preview resolved from the paired streaming tool call. */
|
|
29
|
-
command: string
|
|
30
|
-
/** Resolve the ask; calling twice is inert (one-shot latch). */
|
|
31
|
-
answer(outcome: ApprovalAnswer): void
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** The pending-question snapshot the renderer subscribes to. */
|
|
35
|
-
export interface ApprovalSnapshot {
|
|
36
|
-
/** The question on screen (queue head), or undefined when none is asked. */
|
|
37
|
-
pending: PendingApproval | undefined
|
|
38
|
-
/** Presentational: an answer was submitted, the ask has not settled yet. */
|
|
39
|
-
answered: boolean
|
|
40
|
-
/** Further asks waiting behind the on-screen one (FIFO, Codex-style). */
|
|
41
|
-
queued: number
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Store the pending question lands in; the renderer reads, the answerer writes. */
|
|
45
|
-
export interface ApprovalStore {
|
|
46
|
-
/** Subscribe to pending-state changes; returns the unsubscribe function. */
|
|
47
|
-
subscribe(listener: () => void): () => void
|
|
48
|
-
/** Read the current snapshot (identity-stable between changes). */
|
|
49
|
-
getSnapshot(): ApprovalSnapshot
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Create the approval store and mount the answerer listener on the context.
|
|
54
|
-
* The listener claims only requests for `owns`-owned agents and defers every
|
|
55
|
-
* other request back into the waterfall (`next()`), so sibling answerers stay
|
|
56
|
-
* usable. An aborted ask never reaches the human. Plugin teardown removes the
|
|
57
|
-
* listener; the service then fails its own question closed.
|
|
58
|
-
* @param ctx - plugin context whose event bus carries `approval/request`.
|
|
59
|
-
* @param owns - agents this terminal answers for.
|
|
60
|
-
* @param preview - resolves a tool-call preview for a pending request (the
|
|
61
|
-
* request contract carries no arguments; the UI self-serves from the
|
|
62
|
-
* transcript projection via `callId`).
|
|
63
|
-
* @returns the store the renderer subscribes to.
|
|
64
|
-
*/
|
|
65
|
-
export function mountApprovalAnswerer(
|
|
66
|
-
ctx: Context,
|
|
67
|
-
owns: (agent: Agent) => boolean,
|
|
68
|
-
preview: (request: ApprovalRequest) => string,
|
|
69
|
-
): ApprovalStore {
|
|
70
|
-
/** One live ask: its pending view plus the one-shot settle plumbing. */
|
|
71
|
-
interface Slot {
|
|
72
|
-
readonly pending: PendingApproval
|
|
73
|
-
answered: boolean
|
|
74
|
-
}
|
|
75
|
-
const queue: Slot[] = []
|
|
76
|
-
let snapshot: ApprovalSnapshot = { pending: undefined, answered: false, queued: 0 }
|
|
77
|
-
const listeners = new Set<() => void>()
|
|
78
|
-
const publish = (): void => {
|
|
79
|
-
const head = queue[0]
|
|
80
|
-
snapshot = {
|
|
81
|
-
pending: head === undefined ? undefined : head.pending,
|
|
82
|
-
answered: head !== undefined && head.answered,
|
|
83
|
-
queued: Math.max(0, queue.length - 1),
|
|
84
|
-
}
|
|
85
|
-
for (const listener of listeners) listener()
|
|
86
|
-
}
|
|
87
|
-
const removeSlot = (slot: Slot): void => {
|
|
88
|
-
const at = queue.indexOf(slot)
|
|
89
|
-
if (at !== -1) queue.splice(at, 1)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
ctx.on('approval/request', (request: ApprovalRequest, next: () => Promise<ApprovalOutcome>) => {
|
|
93
|
-
if (!owns(request.agent)) return next()
|
|
94
|
-
// An already-aborted ask never reaches the human (mirrors the host bridge).
|
|
95
|
-
if (request.signal?.aborted === true) return Promise.resolve<ApprovalOutcome>('cancelled')
|
|
96
|
-
|
|
97
|
-
let resolved = false
|
|
98
|
-
let settle!: (outcome: ApprovalOutcome) => void
|
|
99
|
-
// Established BEFORE the abort listener mounts: a synchronous throw
|
|
100
|
-
// between the listener registration and a later construction site would
|
|
101
|
-
// otherwise leave a subsequent abort invoking an unassigned settle from
|
|
102
|
-
// inside the AbortSignal listener (an uncaughtException).
|
|
103
|
-
const settled = new Promise<ApprovalOutcome>((resolve) => {
|
|
104
|
-
settle = resolve
|
|
105
|
-
})
|
|
106
|
-
const signal = request.signal
|
|
107
|
-
const onAbort = (): void => withdraw()
|
|
108
|
-
// Detach on every settle so an answered ask never retains a listener on
|
|
109
|
-
// the tool call's signal (long turns ask many times; each ask must let go).
|
|
110
|
-
const detachAbort = (): void => {
|
|
111
|
-
if (signal !== undefined) signal.removeEventListener('abort', onAbort)
|
|
112
|
-
}
|
|
113
|
-
const withdraw = (): void => {
|
|
114
|
-
if (resolved) return
|
|
115
|
-
resolved = true
|
|
116
|
-
detachAbort()
|
|
117
|
-
removeSlot(slot)
|
|
118
|
-
publish()
|
|
119
|
-
// The service's signal race would conclude 'cancelled' anyway; settle
|
|
120
|
-
// the same way so this listener never dangles a pending promise.
|
|
121
|
-
settle('cancelled')
|
|
122
|
-
}
|
|
123
|
-
if (signal !== undefined) {
|
|
124
|
-
signal.addEventListener('abort', onAbort, { once: true })
|
|
125
|
-
}
|
|
126
|
-
const slot: Slot = {
|
|
127
|
-
answered: false,
|
|
128
|
-
pending: {
|
|
129
|
-
headline: request.reason ?? `tool ${request.toolName} asks for your approval`,
|
|
130
|
-
toolName: request.toolName,
|
|
131
|
-
command: preview(request),
|
|
132
|
-
answer: (outcome: ApprovalAnswer): void => {
|
|
133
|
-
// One-shot latch: a second keypress after submission is inert.
|
|
134
|
-
if (resolved) return
|
|
135
|
-
resolved = true
|
|
136
|
-
detachAbort()
|
|
137
|
-
slot.answered = true
|
|
138
|
-
publish()
|
|
139
|
-
settle(outcome)
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
}
|
|
143
|
-
queue.push(slot)
|
|
144
|
-
publish()
|
|
145
|
-
|
|
146
|
-
return settled.then((outcome) => {
|
|
147
|
-
if (outcome !== 'cancelled') {
|
|
148
|
-
removeSlot(slot)
|
|
149
|
-
publish()
|
|
150
|
-
}
|
|
151
|
-
return outcome
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
subscribe(listener: () => void): () => void {
|
|
157
|
-
listeners.add(listener)
|
|
158
|
-
return () => {
|
|
159
|
-
listeners.delete(listener)
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
getSnapshot(): ApprovalSnapshot {
|
|
163
|
-
return snapshot
|
|
164
|
-
},
|
|
165
|
-
}
|
|
166
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The terminal approval answerer: one `approval/request` waterfall listener
|
|
3
|
+
* that renders the pending question as a y/n bar and resolves the decision
|
|
4
|
+
* back into the waterfall. Mirrors the web host's composer takeover — the
|
|
5
|
+
* service (audit pair, policy gate, fail-closed defaults) all live in
|
|
6
|
+
* dsh-base; this module only answers for agents this TUI owns.
|
|
7
|
+
*
|
|
8
|
+
* Vocabulary note: a client answerer may only ever resolve `'allowed-once'`
|
|
9
|
+
* or `'rejected'`; `'cancelled'` belongs to the request signal and
|
|
10
|
+
* `'unavailable'` to the fail-closed waterfall default.
|
|
11
|
+
*
|
|
12
|
+
* @module @deepseek-ai/dsh-code/approval
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
16
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
17
|
+
import type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval'
|
|
18
|
+
|
|
19
|
+
/** The answer values a client answerer may resolve with. */
|
|
20
|
+
export type ApprovalAnswer = 'allowed-once' | 'rejected'
|
|
21
|
+
|
|
22
|
+
/** One pending approval question, derived from the request for rendering. */
|
|
23
|
+
export interface PendingApproval {
|
|
24
|
+
/** The asker's human-readable explanation, or a generic fallback. */
|
|
25
|
+
headline: string
|
|
26
|
+
/** The tool the question is about. */
|
|
27
|
+
toolName: string
|
|
28
|
+
/** Command-line preview resolved from the paired streaming tool call. */
|
|
29
|
+
command: string
|
|
30
|
+
/** Resolve the ask; calling twice is inert (one-shot latch). */
|
|
31
|
+
answer(outcome: ApprovalAnswer): void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The pending-question snapshot the renderer subscribes to. */
|
|
35
|
+
export interface ApprovalSnapshot {
|
|
36
|
+
/** The question on screen (queue head), or undefined when none is asked. */
|
|
37
|
+
pending: PendingApproval | undefined
|
|
38
|
+
/** Presentational: an answer was submitted, the ask has not settled yet. */
|
|
39
|
+
answered: boolean
|
|
40
|
+
/** Further asks waiting behind the on-screen one (FIFO, Codex-style). */
|
|
41
|
+
queued: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Store the pending question lands in; the renderer reads, the answerer writes. */
|
|
45
|
+
export interface ApprovalStore {
|
|
46
|
+
/** Subscribe to pending-state changes; returns the unsubscribe function. */
|
|
47
|
+
subscribe(listener: () => void): () => void
|
|
48
|
+
/** Read the current snapshot (identity-stable between changes). */
|
|
49
|
+
getSnapshot(): ApprovalSnapshot
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create the approval store and mount the answerer listener on the context.
|
|
54
|
+
* The listener claims only requests for `owns`-owned agents and defers every
|
|
55
|
+
* other request back into the waterfall (`next()`), so sibling answerers stay
|
|
56
|
+
* usable. An aborted ask never reaches the human. Plugin teardown removes the
|
|
57
|
+
* listener; the service then fails its own question closed.
|
|
58
|
+
* @param ctx - plugin context whose event bus carries `approval/request`.
|
|
59
|
+
* @param owns - agents this terminal answers for.
|
|
60
|
+
* @param preview - resolves a tool-call preview for a pending request (the
|
|
61
|
+
* request contract carries no arguments; the UI self-serves from the
|
|
62
|
+
* transcript projection via `callId`).
|
|
63
|
+
* @returns the store the renderer subscribes to.
|
|
64
|
+
*/
|
|
65
|
+
export function mountApprovalAnswerer(
|
|
66
|
+
ctx: Context,
|
|
67
|
+
owns: (agent: Agent) => boolean,
|
|
68
|
+
preview: (request: ApprovalRequest) => string,
|
|
69
|
+
): ApprovalStore {
|
|
70
|
+
/** One live ask: its pending view plus the one-shot settle plumbing. */
|
|
71
|
+
interface Slot {
|
|
72
|
+
readonly pending: PendingApproval
|
|
73
|
+
answered: boolean
|
|
74
|
+
}
|
|
75
|
+
const queue: Slot[] = []
|
|
76
|
+
let snapshot: ApprovalSnapshot = { pending: undefined, answered: false, queued: 0 }
|
|
77
|
+
const listeners = new Set<() => void>()
|
|
78
|
+
const publish = (): void => {
|
|
79
|
+
const head = queue[0]
|
|
80
|
+
snapshot = {
|
|
81
|
+
pending: head === undefined ? undefined : head.pending,
|
|
82
|
+
answered: head !== undefined && head.answered,
|
|
83
|
+
queued: Math.max(0, queue.length - 1),
|
|
84
|
+
}
|
|
85
|
+
for (const listener of listeners) listener()
|
|
86
|
+
}
|
|
87
|
+
const removeSlot = (slot: Slot): void => {
|
|
88
|
+
const at = queue.indexOf(slot)
|
|
89
|
+
if (at !== -1) queue.splice(at, 1)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
ctx.on('approval/request', (request: ApprovalRequest, next: () => Promise<ApprovalOutcome>) => {
|
|
93
|
+
if (!owns(request.agent)) return next()
|
|
94
|
+
// An already-aborted ask never reaches the human (mirrors the host bridge).
|
|
95
|
+
if (request.signal?.aborted === true) return Promise.resolve<ApprovalOutcome>('cancelled')
|
|
96
|
+
|
|
97
|
+
let resolved = false
|
|
98
|
+
let settle!: (outcome: ApprovalOutcome) => void
|
|
99
|
+
// Established BEFORE the abort listener mounts: a synchronous throw
|
|
100
|
+
// between the listener registration and a later construction site would
|
|
101
|
+
// otherwise leave a subsequent abort invoking an unassigned settle from
|
|
102
|
+
// inside the AbortSignal listener (an uncaughtException).
|
|
103
|
+
const settled = new Promise<ApprovalOutcome>((resolve) => {
|
|
104
|
+
settle = resolve
|
|
105
|
+
})
|
|
106
|
+
const signal = request.signal
|
|
107
|
+
const onAbort = (): void => withdraw()
|
|
108
|
+
// Detach on every settle so an answered ask never retains a listener on
|
|
109
|
+
// the tool call's signal (long turns ask many times; each ask must let go).
|
|
110
|
+
const detachAbort = (): void => {
|
|
111
|
+
if (signal !== undefined) signal.removeEventListener('abort', onAbort)
|
|
112
|
+
}
|
|
113
|
+
const withdraw = (): void => {
|
|
114
|
+
if (resolved) return
|
|
115
|
+
resolved = true
|
|
116
|
+
detachAbort()
|
|
117
|
+
removeSlot(slot)
|
|
118
|
+
publish()
|
|
119
|
+
// The service's signal race would conclude 'cancelled' anyway; settle
|
|
120
|
+
// the same way so this listener never dangles a pending promise.
|
|
121
|
+
settle('cancelled')
|
|
122
|
+
}
|
|
123
|
+
if (signal !== undefined) {
|
|
124
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
125
|
+
}
|
|
126
|
+
const slot: Slot = {
|
|
127
|
+
answered: false,
|
|
128
|
+
pending: {
|
|
129
|
+
headline: request.reason ?? `tool ${request.toolName} asks for your approval`,
|
|
130
|
+
toolName: request.toolName,
|
|
131
|
+
command: preview(request),
|
|
132
|
+
answer: (outcome: ApprovalAnswer): void => {
|
|
133
|
+
// One-shot latch: a second keypress after submission is inert.
|
|
134
|
+
if (resolved) return
|
|
135
|
+
resolved = true
|
|
136
|
+
detachAbort()
|
|
137
|
+
slot.answered = true
|
|
138
|
+
publish()
|
|
139
|
+
settle(outcome)
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
queue.push(slot)
|
|
144
|
+
publish()
|
|
145
|
+
|
|
146
|
+
return settled.then((outcome) => {
|
|
147
|
+
if (outcome !== 'cancelled') {
|
|
148
|
+
removeSlot(slot)
|
|
149
|
+
publish()
|
|
150
|
+
}
|
|
151
|
+
return outcome
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
subscribe(listener: () => void): () => void {
|
|
157
|
+
listeners.add(listener)
|
|
158
|
+
return () => {
|
|
159
|
+
listeners.delete(listener)
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
getSnapshot(): ApprovalSnapshot {
|
|
163
|
+
return snapshot
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -13,7 +13,9 @@ import type { CredentialKey } from '@deepseek-ai/dsh-credentials'
|
|
|
13
13
|
import type { ProviderAuthorizationRow } from './authorization.ts'
|
|
14
14
|
import { panelViewport } from './render/inspector.ts'
|
|
15
15
|
import { displayText, singleLineText, truncateColumns } from './render/text.ts'
|
|
16
|
+
import { panelAccent } from './panel-accent.ts'
|
|
16
17
|
import { getPalette, inkColor } from './theme.ts'
|
|
18
|
+
import { t } from './i18n.ts'
|
|
17
19
|
|
|
18
20
|
interface PromptReply {
|
|
19
21
|
readonly resolve: (value: string) => void
|
|
@@ -154,7 +156,7 @@ export function ProviderAuthorizationPanel(props: ProviderAuthorizationPanelProp
|
|
|
154
156
|
setCursor(current => (current + 1) % props.row.methods.length)
|
|
155
157
|
return
|
|
156
158
|
}
|
|
157
|
-
if (key.return) start(props.row.methods[cursor]?.id ?? props.row.methods[0]
|
|
159
|
+
if (key.return) start(props.row.methods[cursor]?.id ?? props.row.methods[0].id)
|
|
158
160
|
return
|
|
159
161
|
}
|
|
160
162
|
|
|
@@ -182,7 +184,7 @@ export function ProviderAuthorizationPanel(props: ProviderAuthorizationPanelProp
|
|
|
182
184
|
setPromptCursor(current => (current + 1) % prompt.options.length)
|
|
183
185
|
return
|
|
184
186
|
}
|
|
185
|
-
if (key.return) answer(prompt.options[promptCursor]?.id ?? prompt.options[0]
|
|
187
|
+
if (key.return) answer(prompt.options[promptCursor]?.id ?? prompt.options[0].id)
|
|
186
188
|
return
|
|
187
189
|
}
|
|
188
190
|
if (key.backspace || key.delete) {
|
|
@@ -197,7 +199,7 @@ export function ProviderAuthorizationPanel(props: ProviderAuthorizationPanelProp
|
|
|
197
199
|
})
|
|
198
200
|
|
|
199
201
|
if (viewport.maxHeight === 0 || viewport.compact) {
|
|
200
|
-
return createElement(Text, { wrap: 'truncate-end' }, truncateColumns('
|
|
202
|
+
return createElement(Text, { wrap: 'truncate-end' }, truncateColumns(t('panel.auth.compact'), viewport.contentColumns))
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
const rows: Array<{ key: string; text: string; color?: string; bold?: boolean }> = []
|
|
@@ -229,18 +231,19 @@ export function ProviderAuthorizationPanel(props: ProviderAuthorizationPanelProp
|
|
|
229
231
|
rows.push({ key: 'draft', text: ` ${shown}▏`, color: inkColor(getPalette().text) })
|
|
230
232
|
}
|
|
231
233
|
} else {
|
|
232
|
-
rows.push({ key: 'waiting', text: '
|
|
234
|
+
rows.push({ key: 'waiting', text: ` ${t('panel.auth.waiting')}`, color: inkColor(getPalette().dim) })
|
|
233
235
|
}
|
|
234
236
|
if (copyState !== undefined) rows.push({ key: 'copy', text: ` ${singleLineText(copyState)}`, color: inkColor(copyState === 'copied' ? getPalette().success : getPalette().error) })
|
|
235
237
|
}
|
|
236
238
|
const visible = rows.slice(Math.max(0, rows.length - viewport.bodyRows))
|
|
237
239
|
const footer = phase === 'methods'
|
|
238
|
-
? '
|
|
239
|
-
: '
|
|
240
|
+
? t('panel.auth.methodsFooter')
|
|
241
|
+
: t('panel.auth.answerFooter')
|
|
242
|
+
const accent = panelAccent('auth-login', getPalette().brand)
|
|
240
243
|
return createElement(
|
|
241
244
|
Box,
|
|
242
|
-
{ flexDirection: 'column', width: viewport.outerColumns, paddingX: 1, borderStyle: 'round', borderColor: inkColor(
|
|
243
|
-
createElement(Text, { color: inkColor(
|
|
245
|
+
{ flexDirection: 'column', width: viewport.outerColumns, paddingX: 1, borderStyle: 'round', borderColor: inkColor(accent.border) },
|
|
246
|
+
createElement(Text, { color: inkColor(accent.title), bold: true, wrap: 'truncate-end' }, truncateColumns(t('panel.auth.title', { provider: displayText(props.row.label) }), viewport.contentColumns)),
|
|
244
247
|
...visible.map(row => createElement(Text, { key: row.key, color: row.color, bold: row.bold, wrap: 'truncate-end' }, truncateColumns(row.text, viewport.contentColumns))),
|
|
245
248
|
createElement(Text, { color: inkColor(getPalette().dim), wrap: 'truncate-end' }, truncateColumns(footer, viewport.contentColumns)),
|
|
246
249
|
)
|
|
@@ -248,9 +251,9 @@ export function ProviderAuthorizationPanel(props: ProviderAuthorizationPanelProp
|
|
|
248
251
|
|
|
249
252
|
export function ProviderAuthorizationLogoutPanel({ row, confirm, done, back }: {
|
|
250
253
|
row: ProviderAuthorizationRow
|
|
251
|
-
confirm(row: ProviderAuthorizationRow)
|
|
252
|
-
done()
|
|
253
|
-
back()
|
|
254
|
+
confirm: (row: ProviderAuthorizationRow) => Promise<void>
|
|
255
|
+
done: () => void
|
|
256
|
+
back: () => void
|
|
254
257
|
}): ReactElement {
|
|
255
258
|
const stdout = useStdout().stdout
|
|
256
259
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30)
|
|
@@ -270,14 +273,14 @@ export function ProviderAuthorizationLogoutPanel({ row, confirm, done, back }: {
|
|
|
270
273
|
setError(reason instanceof Error ? reason.message : String(reason))
|
|
271
274
|
})
|
|
272
275
|
})
|
|
273
|
-
if (viewport.maxHeight === 0 || viewport.compact) return createElement(Text, { wrap: 'truncate-end' }, truncateColumns('
|
|
276
|
+
if (viewport.maxHeight === 0 || viewport.compact) return createElement(Text, { wrap: 'truncate-end' }, truncateColumns(t('panel.auth.logoutCompact'), viewport.contentColumns))
|
|
274
277
|
return createElement(
|
|
275
278
|
Box,
|
|
276
279
|
{ flexDirection: 'column', width: viewport.outerColumns, paddingX: 1, borderStyle: 'round', borderColor: inkColor(getPalette().warn) },
|
|
277
|
-
createElement(Text, { color: inkColor(getPalette().warn), bold: true, wrap: 'truncate-end' }, truncateColumns('
|
|
278
|
-
createElement(Text, { wrap: 'truncate-end' }, truncateColumns(`
|
|
279
|
-
createElement(Text, { color: inkColor(getPalette().dim), wrap: 'truncate-end' }, truncateColumns('
|
|
280
|
+
createElement(Text, { color: inkColor(getPalette().warn), bold: true, wrap: 'truncate-end' }, truncateColumns(t('panel.auth.logoutTitle'), viewport.contentColumns)),
|
|
281
|
+
createElement(Text, { wrap: 'truncate-end' }, truncateColumns(` ${t('panel.auth.removeRecord', { provider: displayText(row.label) })}`, viewport.contentColumns)),
|
|
282
|
+
createElement(Text, { color: inkColor(getPalette().dim), wrap: 'truncate-end' }, truncateColumns(` ${t('panel.auth.unchanged')}`, viewport.contentColumns)),
|
|
280
283
|
error === undefined ? undefined : createElement(Text, { color: inkColor(getPalette().error), wrap: 'truncate-end' }, truncateColumns(` ${singleLineText(error)}`, viewport.contentColumns)),
|
|
281
|
-
createElement(Text, { color: inkColor(getPalette().dim), wrap: 'truncate-end' }, truncateColumns(busy ? 'working
|
|
284
|
+
createElement(Text, { color: inkColor(getPalette().dim), wrap: 'truncate-end' }, truncateColumns(busy ? t('panel.auth.working') : t('panel.auth.logoutFooter'), viewport.contentColumns)),
|
|
282
285
|
)
|
|
283
286
|
}
|