@agi-fans/dsh-tui 0.20.0 → 0.21.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/lib/CHANGELOG.md +92 -3
- package/lib/chrome/main-screen-renderer.d.ts +2 -0
- package/lib/chrome/main-screen-renderer.js +5 -0
- package/lib/chrome/main-screen-renderer.js.map +1 -1
- package/lib/chrome/renderer.d.ts +9 -0
- package/lib/chrome/renderer.js +1 -1
- package/lib/chrome/renderer.js.map +1 -1
- package/lib/chrome/theme.js +32 -33
- package/lib/chrome/theme.js.map +1 -1
- package/lib/chrome/tool-args.d.ts +1 -1
- package/lib/chrome/tool-args.js +3 -1
- package/lib/chrome/tool-args.js.map +1 -1
- package/lib/chrome/tool-renderers.d.ts +2 -0
- package/lib/chrome/tool-renderers.js +10 -0
- package/lib/chrome/tool-renderers.js.map +1 -1
- package/lib/chrome/welcome-tips.d.ts +1 -0
- package/lib/chrome/welcome-tips.js +3 -2
- package/lib/chrome/welcome-tips.js.map +1 -1
- package/lib/commands/advisor.d.ts +15 -0
- package/lib/commands/advisor.js +200 -0
- package/lib/commands/advisor.js.map +1 -0
- package/lib/commands/auth.d.ts +4 -2
- package/lib/commands/auth.js +317 -83
- package/lib/commands/auth.js.map +1 -1
- package/lib/commands/btw.d.ts +15 -0
- package/lib/commands/btw.js +126 -0
- package/lib/commands/btw.js.map +1 -0
- package/lib/commands/diff.js +125 -5
- package/lib/commands/diff.js.map +1 -1
- package/lib/commands/export.d.ts +1 -1
- package/lib/commands/export.js +31 -5
- package/lib/commands/export.js.map +1 -1
- package/lib/commands/files.js +55 -7
- package/lib/commands/files.js.map +1 -1
- package/lib/commands/integrations.js +18 -6
- package/lib/commands/integrations.js.map +1 -1
- package/lib/commands/model.js +1 -0
- package/lib/commands/model.js.map +1 -1
- package/lib/commands/permission.js +35 -1
- package/lib/commands/permission.js.map +1 -1
- package/lib/commands/plugins.js +76 -39
- package/lib/commands/plugins.js.map +1 -1
- package/lib/commands/rate.d.ts +33 -0
- package/lib/commands/rate.js +212 -0
- package/lib/commands/rate.js.map +1 -0
- package/lib/commands/schedule.d.ts +19 -0
- package/lib/commands/schedule.js +197 -0
- package/lib/commands/schedule.js.map +1 -0
- package/lib/commands/session-configuration.d.ts +1 -1
- package/lib/commands/session-configuration.js +37 -28
- package/lib/commands/session-configuration.js.map +1 -1
- package/lib/commands/session.js +67 -48
- package/lib/commands/session.js.map +1 -1
- package/lib/commands/terminal.d.ts +15 -1
- package/lib/commands/terminal.js +119 -16
- package/lib/commands/terminal.js.map +1 -1
- package/lib/commands/worktree.d.ts +5 -0
- package/lib/commands/worktree.js +103 -0
- package/lib/commands/worktree.js.map +1 -0
- package/lib/definition.d.ts +45 -6
- package/lib/definition.js.map +1 -1
- package/lib/input/editor-discovery.js +2 -2
- package/lib/input/editor-discovery.js.map +1 -1
- package/lib/input/keybindings-config.d.ts +6 -2
- package/lib/input/keybindings-config.js +55 -6
- package/lib/input/keybindings-config.js.map +1 -1
- package/lib/input/keys.d.ts +2 -0
- package/lib/input/keys.js +10 -0
- package/lib/input/keys.js.map +1 -1
- package/lib/runtime/agent-profile.d.ts +5 -0
- package/lib/runtime/agent-profile.js +12 -0
- package/lib/runtime/agent-profile.js.map +1 -1
- package/lib/runtime/approval-rules.d.ts +59 -0
- package/lib/runtime/approval-rules.js +179 -0
- package/lib/runtime/approval-rules.js.map +1 -0
- package/lib/runtime/btw-policy.d.ts +7 -0
- package/lib/runtime/btw-policy.js +18 -0
- package/lib/runtime/btw-policy.js.map +1 -0
- package/lib/runtime/btw.d.ts +47 -0
- package/lib/runtime/btw.js +200 -0
- package/lib/runtime/btw.js.map +1 -0
- package/lib/runtime/composer-shell.d.ts +15 -0
- package/lib/runtime/composer-shell.js +84 -0
- package/lib/runtime/composer-shell.js.map +1 -0
- package/lib/runtime/file-recovery-local.d.ts +73 -0
- package/lib/runtime/file-recovery-local.js +351 -0
- package/lib/runtime/file-recovery-local.js.map +1 -0
- package/lib/runtime/file-recovery-recording.d.ts +23 -0
- package/lib/runtime/file-recovery-recording.js +131 -0
- package/lib/runtime/file-recovery-recording.js.map +1 -0
- package/lib/runtime/file-recovery-review.d.ts +4 -0
- package/lib/runtime/file-recovery-review.js +131 -0
- package/lib/runtime/file-recovery-review.js.map +1 -0
- package/lib/runtime/file-recovery-store.d.ts +83 -0
- package/lib/runtime/file-recovery-store.js +325 -0
- package/lib/runtime/file-recovery-store.js.map +1 -0
- package/lib/runtime/file-recovery-worker.d.ts +22 -0
- package/lib/runtime/file-recovery-worker.js +153 -0
- package/lib/runtime/file-recovery-worker.js.map +1 -0
- package/lib/runtime/file-review.d.ts +6 -3
- package/lib/runtime/file-review.js +43 -6
- package/lib/runtime/file-review.js.map +1 -1
- package/lib/runtime/herdr-agent.d.ts +4 -13
- package/lib/runtime/herdr-agent.js +19 -63
- package/lib/runtime/herdr-agent.js.map +1 -1
- package/lib/runtime/hook-settings.d.ts +2 -2
- package/lib/runtime/plugin-settings.d.ts +25 -2
- package/lib/runtime/plugin-settings.js +327 -31
- package/lib/runtime/plugin-settings.js.map +1 -1
- package/lib/runtime/provider-local.d.ts +24 -9
- package/lib/runtime/provider-local.js +714 -156
- package/lib/runtime/provider-local.js.map +1 -1
- package/lib/runtime/runner.js +48 -2
- package/lib/runtime/runner.js.map +1 -1
- package/lib/runtime/session-archive.d.ts +6 -0
- package/lib/runtime/session-archive.js +129 -3
- package/lib/runtime/session-archive.js.map +1 -1
- package/lib/runtime/session-runtime.js +1 -1
- package/lib/runtime/session-runtime.js.map +1 -1
- package/lib/runtime/settings-preferences.d.ts +5 -0
- package/lib/runtime/settings-preferences.js +34 -0
- package/lib/runtime/settings-preferences.js.map +1 -0
- package/lib/runtime/terminal-state.d.ts +29 -0
- package/lib/runtime/terminal-state.js +62 -0
- package/lib/runtime/terminal-state.js.map +1 -0
- package/lib/runtime/tool-presentation.js +50 -14
- package/lib/runtime/tool-presentation.js.map +1 -1
- package/lib/session/external-agents.d.ts +5 -0
- package/lib/session/external-agents.js +27 -0
- package/lib/session/external-agents.js.map +1 -0
- package/lib/session/feature-toggles.d.ts +1 -1
- package/lib/session/feature-toggles.js +1 -1
- package/lib/session/interaction-adapter.d.ts +1 -6
- package/lib/session/interaction-adapter.js +92 -14
- package/lib/session/interaction-adapter.js.map +1 -1
- package/lib/session/model-readiness.d.ts +16 -0
- package/lib/session/model-readiness.js +43 -0
- package/lib/session/model-readiness.js.map +1 -0
- package/lib/session/project-context.d.ts +5 -0
- package/lib/session/project-context.js +36 -0
- package/lib/session/project-context.js.map +1 -1
- package/lib/session/session-controller.d.ts +0 -2
- package/lib/session/session-controller.js +92 -89
- package/lib/session/session-controller.js.map +1 -1
- package/lib/session/session-library.js +2 -0
- package/lib/session/session-library.js.map +1 -1
- package/lib/session/session-tree.d.ts +1 -0
- package/lib/session/session-tree.js +1 -1
- package/lib/session/session-tree.js.map +1 -1
- package/lib/session/subagent-roster.d.ts +2 -1
- package/lib/session/subagent-roster.js +24 -3
- package/lib/session/subagent-roster.js.map +1 -1
- package/lib/session/subagent-tracker.d.ts +6 -2
- package/lib/session/subagent-tracker.js +49 -2
- package/lib/session/subagent-tracker.js.map +1 -1
- package/lib/session/tui-settings.d.ts +7 -2
- package/lib/session/tui-settings.js +3 -0
- package/lib/session/tui-settings.js.map +1 -1
- package/lib/session/working-directory.d.ts +4 -0
- package/lib/session/working-directory.js +4 -0
- package/lib/session/working-directory.js.map +1 -0
- package/lib/session/worktrees.d.ts +13 -0
- package/lib/session/worktrees.js +49 -0
- package/lib/session/worktrees.js.map +1 -0
- package/lib/views/agent-hub.js +3 -0
- package/lib/views/agent-hub.js.map +1 -1
- package/lib/views/approval-rule.d.ts +3 -0
- package/lib/views/approval-rule.js +4 -0
- package/lib/views/approval-rule.js.map +1 -0
- package/lib/views/at-complete.d.ts +8 -0
- package/lib/views/at-complete.js +64 -2
- package/lib/views/at-complete.js.map +1 -1
- package/lib/views/autocomplete.d.ts +4 -0
- package/lib/views/autocomplete.js +28 -11
- package/lib/views/autocomplete.js.map +1 -1
- package/lib/views/copy-selector.d.ts +3 -0
- package/lib/views/copy-selector.js +46 -15
- package/lib/views/copy-selector.js.map +1 -1
- package/lib/views/copy-targets.d.ts +2 -2
- package/lib/views/copy-targets.js +31 -13
- package/lib/views/copy-targets.js.map +1 -1
- package/lib/views/event-views.d.ts +1 -1
- package/lib/views/event-views.js +18 -0
- package/lib/views/event-views.js.map +1 -1
- package/lib/views/file-actions.d.ts +12 -0
- package/lib/views/file-actions.js +72 -0
- package/lib/views/file-actions.js.map +1 -0
- package/lib/views/hotkeys.d.ts +1 -0
- package/lib/views/hotkeys.js +6 -3
- package/lib/views/hotkeys.js.map +1 -1
- package/lib/views/prompt-selector.d.ts +3 -1
- package/lib/views/prompt-selector.js +33 -21
- package/lib/views/prompt-selector.js.map +1 -1
- package/lib/views/settings-list.d.ts +41 -2
- package/lib/views/settings-list.js +176 -57
- package/lib/views/settings-list.js.map +1 -1
- package/lib/views/transcript-export.js +9 -0
- package/lib/views/transcript-export.js.map +1 -1
- package/lib/views/transcript-render.d.ts +2 -0
- package/lib/views/transcript-render.js +46 -8
- package/lib/views/transcript-render.js.map +1 -1
- package/package.json +97 -48
package/lib/commands/auth.js
CHANGED
|
@@ -4,17 +4,18 @@ import process from 'node:process';
|
|
|
4
4
|
import { AuthorizationError, } from '@deepseek-ai/dsh-authorization';
|
|
5
5
|
import { credentialKey, credentialKeyId, credentialKeyScope, credentialRef, isCredentialKeySegment, parseCredentialKey, } from '@deepseek-ai/dsh-credentials';
|
|
6
6
|
import { createAuthorizationInteraction } from "../session/authorization-interaction.js";
|
|
7
|
+
import { APP_VERSION } from "../session/package-metadata.js";
|
|
7
8
|
import { registerCommands } from "./registration.js";
|
|
8
9
|
export const name = 'omdsh-command-auth';
|
|
9
10
|
export const inject = ['commands', 'credentials', 'settings', 'tui', 'llm'];
|
|
10
11
|
export const DEEPSEEK_PROVIDER = 'deepseek-official';
|
|
11
12
|
export const DEEPSEEK_API_KEY = credentialRef('DEEPSEEK_API_KEY');
|
|
12
|
-
export const OMDSH_DEEPSEEK_API_KEY = credentialRef('OMDSH_DEEPSEEK_API_KEY');
|
|
13
13
|
export const DEEPSEEK_SETTINGS = 'llm-deepseek';
|
|
14
14
|
export const PI_AI_SETTINGS = 'llm-pi-ai';
|
|
15
15
|
export const DEEPSEEK_API_KEYS_URL = 'https://platform.deepseek.com/api_keys';
|
|
16
16
|
export const DEEPSEEK_MODELS_URL = 'https://api.deepseek.com/v1/models';
|
|
17
17
|
export const CUSTOM_PROVIDER_VALUE = '__omdsh_custom__';
|
|
18
|
+
export const DEEPSEEK_ACCOUNT_CHOICE = '__omdsh_deepseek_account__';
|
|
18
19
|
export const FLOW_VALUE_PREFIX = '__omdsh_flow__:';
|
|
19
20
|
const PI_AI_RECORD_SCOPE = 'llm-pi-ai';
|
|
20
21
|
const FULLSCREEN_CHOICE_THRESHOLD = 8;
|
|
@@ -144,8 +145,13 @@ function authorizationFlows(ctx) {
|
|
|
144
145
|
function isPiAiDeepSeekFlow(flow) {
|
|
145
146
|
return credentialKeyScope(flow.key) === PI_AI_RECORD_SCOPE && credentialKeyId(flow.key) === 'deepseek';
|
|
146
147
|
}
|
|
148
|
+
function isAccountFlow(flow) {
|
|
149
|
+
return credentialKeyScope(flow.key) === 'deepseek-account-platform';
|
|
150
|
+
}
|
|
147
151
|
function loginAuthorizationFlows(ctx) {
|
|
148
152
|
return authorizationFlows(ctx).flatMap(flow => {
|
|
153
|
+
if (isAccountFlow(flow))
|
|
154
|
+
return [];
|
|
149
155
|
if (!isPiAiDeepSeekFlow(flow))
|
|
150
156
|
return [flow];
|
|
151
157
|
const profile = walkPath(sectionValue(ctx, PI_AI_SETTINGS), ['providers', 'deepseek']);
|
|
@@ -176,8 +182,9 @@ function parseFlowChoice(raw) {
|
|
|
176
182
|
return undefined;
|
|
177
183
|
}
|
|
178
184
|
}
|
|
179
|
-
function loginChoiceValues(entries, flows, includeCustom) {
|
|
185
|
+
function loginChoiceValues(entries, flows, includeCustom, includeAccount = false) {
|
|
180
186
|
return [
|
|
187
|
+
...(includeAccount ? [DEEPSEEK_ACCOUNT_CHOICE] : []),
|
|
181
188
|
...entries.map(entry => entry.provider),
|
|
182
189
|
...flows.map(flow => flowChoiceValue(flow.key)),
|
|
183
190
|
...(includeCustom ? [CUSTOM_PROVIDER_VALUE] : []),
|
|
@@ -225,13 +232,15 @@ function catalogForRecord(ctx, key) {
|
|
|
225
232
|
return loginableProviders(ctx).find(entry => entry.provider === id && entry.settingsNs === scope);
|
|
226
233
|
}
|
|
227
234
|
function isFlowRecordLogout(choice) {
|
|
228
|
-
return 'key' in choice && 'writable' in choice && !('provider' in choice);
|
|
235
|
+
return typeof choice !== 'string' && 'key' in choice && 'writable' in choice && !('provider' in choice);
|
|
229
236
|
}
|
|
230
237
|
async function configuredFlowRecords(ctx) {
|
|
231
238
|
const credentials = ctx.credentials;
|
|
232
239
|
const rows = [];
|
|
233
240
|
const claimed = new Set();
|
|
234
241
|
for (const flow of authorizationFlows(ctx)) {
|
|
242
|
+
if (isAccountFlow(flow))
|
|
243
|
+
continue;
|
|
235
244
|
const info = await credentials.describeRecord(flow.key);
|
|
236
245
|
if (!info.configured)
|
|
237
246
|
continue;
|
|
@@ -258,6 +267,9 @@ async function configuredFlowRecords(ctx) {
|
|
|
258
267
|
async function logoutableChoices(ctx) {
|
|
259
268
|
const providers = logoutableProviders(ctx);
|
|
260
269
|
const records = await configuredFlowRecords(ctx);
|
|
270
|
+
const account = accountClient(ctx);
|
|
271
|
+
const accountState = account === undefined ? undefined : await account.getState().catch(() => undefined);
|
|
272
|
+
const accountChoice = accountState?.status === 'credential-stored' ? [DEEPSEEK_ACCOUNT_CHOICE] : [];
|
|
261
273
|
const actionable = [];
|
|
262
274
|
let inactiveDeepSeek;
|
|
263
275
|
for (const entry of providers) {
|
|
@@ -266,19 +278,18 @@ async function logoutableChoices(ctx) {
|
|
|
266
278
|
continue;
|
|
267
279
|
}
|
|
268
280
|
const activeRef = profileApiKeyEnv(ctx, entry) ?? String(DEEPSEEK_API_KEY);
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
if (managed || (activeRef === String(DEEPSEEK_API_KEY) && current.source === 'file')) {
|
|
281
|
+
const current = await ctx.credentials.describe(credentialRef(activeRef));
|
|
282
|
+
if (current.configured && current.source === 'file')
|
|
272
283
|
actionable.push(entry);
|
|
273
|
-
|
|
274
|
-
else {
|
|
284
|
+
else
|
|
275
285
|
inactiveDeepSeek = entry;
|
|
276
|
-
}
|
|
277
286
|
}
|
|
278
|
-
const choices = [...actionable, ...records];
|
|
287
|
+
const choices = [...accountChoice, ...actionable, ...records];
|
|
279
288
|
// Preserve the direct explanatory response when the environment-owned or
|
|
280
289
|
// already-logged-out official route is the only thing the command can see.
|
|
281
|
-
|
|
290
|
+
if (choices.length === 0 && inactiveDeepSeek !== undefined)
|
|
291
|
+
return [inactiveDeepSeek];
|
|
292
|
+
return choices;
|
|
282
293
|
}
|
|
283
294
|
function selected(raw, values) {
|
|
284
295
|
const index = /^\d+$/u.test(raw) ? Number(raw) - 1 : -1;
|
|
@@ -316,7 +327,10 @@ async function pickLoginChoice(ctx, invocation) {
|
|
|
316
327
|
return configured !== 0 ? configured : left.flow.label.localeCompare(right.flow.label);
|
|
317
328
|
});
|
|
318
329
|
const orderedFlows = flowRows.map(row => row.flow);
|
|
319
|
-
const
|
|
330
|
+
const account = ctx.get('deepseekAccount');
|
|
331
|
+
const accountState = account?.getState === undefined ? undefined : await account.getState().catch(() => undefined);
|
|
332
|
+
const includeAccount = account?.getState !== undefined;
|
|
333
|
+
const values = loginChoiceValues(entries, orderedFlows, true, includeAccount);
|
|
320
334
|
const fullscreen = values.length > FULLSCREEN_CHOICE_THRESHOLD;
|
|
321
335
|
const raw = await ctx.tui.prompt({
|
|
322
336
|
...(fullscreen ? { presentation: 'fullscreen-list' } : {}),
|
|
@@ -325,6 +339,16 @@ async function pickLoginChoice(ctx, invocation) {
|
|
|
325
339
|
title: 'Provider',
|
|
326
340
|
question: 'Choose a provider to sign in or update',
|
|
327
341
|
options: [
|
|
342
|
+
...(includeAccount ? [{
|
|
343
|
+
label: 'DeepSeek account',
|
|
344
|
+
value: DEEPSEEK_ACCOUNT_CHOICE,
|
|
345
|
+
description: accountState?.status === 'credential-stored'
|
|
346
|
+
? 'signed in · shared with DSH; choose deepseek-account in /model'
|
|
347
|
+
: 'signed out · opens the DeepSeek browser sign-in',
|
|
348
|
+
...(accountState?.status === 'credential-stored'
|
|
349
|
+
? { badge: { label: 'signed in', tone: 'success' } }
|
|
350
|
+
: {}),
|
|
351
|
+
}] : []),
|
|
328
352
|
...entryRows.map(({ entry, badge }) => ({
|
|
329
353
|
label: providerListId(entry),
|
|
330
354
|
value: entry.provider,
|
|
@@ -350,6 +374,7 @@ async function pickLoginChoice(ctx, invocation) {
|
|
|
350
374
|
},
|
|
351
375
|
],
|
|
352
376
|
allowCustom: false,
|
|
377
|
+
notify: false,
|
|
353
378
|
signal: invocation.signal,
|
|
354
379
|
});
|
|
355
380
|
if (raw === null)
|
|
@@ -360,6 +385,8 @@ async function pickLoginChoice(ctx, invocation) {
|
|
|
360
385
|
const chosen = selected(raw, values);
|
|
361
386
|
if (chosen === undefined)
|
|
362
387
|
return undefined;
|
|
388
|
+
if (chosen === DEEPSEEK_ACCOUNT_CHOICE)
|
|
389
|
+
return DEEPSEEK_ACCOUNT_CHOICE;
|
|
363
390
|
const flowKey = parseFlowChoice(chosen);
|
|
364
391
|
if (flowKey !== undefined)
|
|
365
392
|
return orderedFlows.find(flow => flow.key === flowKey);
|
|
@@ -380,6 +407,7 @@ async function pickAuthorizationMethod(ctx, invocation, flow) {
|
|
|
380
407
|
value: method.id,
|
|
381
408
|
})),
|
|
382
409
|
allowCustom: false,
|
|
410
|
+
notify: false,
|
|
383
411
|
signal: invocation.signal,
|
|
384
412
|
});
|
|
385
413
|
if (raw === null)
|
|
@@ -401,7 +429,10 @@ async function loginAuthorization(ctx, invocation, flow) {
|
|
|
401
429
|
const outcome = await authorization.begin({
|
|
402
430
|
key: flow.key,
|
|
403
431
|
method,
|
|
404
|
-
interaction: createAuthorizationInteraction(
|
|
432
|
+
interaction: createAuthorizationInteraction({
|
|
433
|
+
notice: message => { ctx.tui.notice(message); },
|
|
434
|
+
prompt: request => ctx.tui.prompt({ ...request, notify: false }),
|
|
435
|
+
}, `Login to ${flow.label}`),
|
|
405
436
|
signal: invocation.signal,
|
|
406
437
|
});
|
|
407
438
|
if (outcome.status === 'cancelled')
|
|
@@ -423,45 +454,40 @@ async function loginAuthorization(ctx, invocation, flow) {
|
|
|
423
454
|
async function resetDeepSeekCredentialRoute(ctx) {
|
|
424
455
|
await ctx.settings.mutate(DEEPSEEK_SETTINGS, [{ op: 'unset', path: ['apiKeyEnv'] }]);
|
|
425
456
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (fallback.configured) {
|
|
429
|
-
return {
|
|
430
|
-
kind: 'success',
|
|
431
|
-
text: `${managed ? 'Logged out from the omdsh-managed DeepSeek credential.' : 'Logged out from DeepSeek.'} Falling back to DEEPSEEK_API_KEY from ${credentialSource(fallback)}.`,
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
return { kind: 'success', text: 'Logged out from DeepSeek.' };
|
|
435
|
-
}
|
|
436
|
-
/** Best-effort browser launch; the prompt always retains the full URL as a fallback. */
|
|
437
|
-
export function openDeepSeekDashboard() {
|
|
457
|
+
/** Best-effort browser launch; callers always retain the full URL as a fallback. */
|
|
458
|
+
function openExternal(url) {
|
|
438
459
|
const command = process.platform === 'darwin'
|
|
439
|
-
? { file: 'open', args: [
|
|
460
|
+
? { file: 'open', args: [url] }
|
|
440
461
|
: process.platform === 'win32'
|
|
441
|
-
? { file: 'cmd.exe', args: ['/d', '/s', '/c', 'start', '',
|
|
442
|
-
: { file: 'xdg-open', args: [
|
|
462
|
+
? { file: 'cmd.exe', args: ['/d', '/s', '/c', 'start', '', url] }
|
|
463
|
+
: { file: 'xdg-open', args: [url] };
|
|
443
464
|
try {
|
|
444
465
|
const child = spawn(command.file, command.args, { detached: true, stdio: 'ignore' });
|
|
445
466
|
child.once('error', () => undefined);
|
|
446
467
|
child.unref();
|
|
447
468
|
}
|
|
448
469
|
catch {
|
|
449
|
-
// Headless and remote terminals still receive the visible
|
|
470
|
+
// Headless and remote terminals still receive the visible URL.
|
|
450
471
|
}
|
|
451
472
|
}
|
|
473
|
+
/** Best-effort browser launch for the DeepSeek key dashboard. */
|
|
474
|
+
export function openDeepSeekDashboard() {
|
|
475
|
+
openExternal(DEEPSEEK_API_KEYS_URL);
|
|
476
|
+
}
|
|
452
477
|
async function loginDeepSeek(ctx, invocation, config) {
|
|
453
|
-
const current = await ctx.credentials.describe(
|
|
478
|
+
const current = await ctx.credentials.describe(DEEPSEEK_API_KEY);
|
|
454
479
|
if (current.configured && !current.writable) {
|
|
455
|
-
return { kind: 'error', text: unmanagedCredentialMessage(
|
|
480
|
+
return { kind: 'error', text: unmanagedCredentialMessage(DEEPSEEK_API_KEY, current, 'replace') };
|
|
456
481
|
}
|
|
457
482
|
if (config.openDashboard !== false)
|
|
458
483
|
openDeepSeekDashboard();
|
|
459
484
|
const raw = await ctx.tui.prompt({
|
|
460
485
|
title: 'Login to DeepSeek',
|
|
461
486
|
question: current.configured ? 'Paste a new DeepSeek API key' : 'Paste your DeepSeek API key',
|
|
462
|
-
detail: `Create or copy a key at ${DEEPSEEK_API_KEYS_URL}. It will be validated
|
|
487
|
+
detail: `Create or copy a key at ${DEEPSEEK_API_KEYS_URL}. It will be validated and stored locally as ${DEEPSEEK_API_KEY}.`,
|
|
463
488
|
allowCustom: true,
|
|
464
489
|
secret: true,
|
|
490
|
+
notify: false,
|
|
465
491
|
submitLabel: 'validate',
|
|
466
492
|
signal: invocation.signal,
|
|
467
493
|
});
|
|
@@ -478,23 +504,27 @@ async function loginDeepSeek(ctx, invocation, config) {
|
|
|
478
504
|
return { kind: 'error', text: error instanceof Error ? error.message : 'DeepSeek login failed.' };
|
|
479
505
|
}
|
|
480
506
|
try {
|
|
481
|
-
await ctx.credentials.set(
|
|
507
|
+
await ctx.credentials.set(DEEPSEEK_API_KEY, apiKey);
|
|
482
508
|
}
|
|
483
509
|
catch {
|
|
484
510
|
return { kind: 'error', text: 'The API key is valid, but it could not be saved to the Harness credential store.' };
|
|
485
511
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
512
|
+
// A profile may still pin the old product-owned reference. Reset it so the
|
|
513
|
+
// upstream default resolves the key that was just stored.
|
|
514
|
+
if (profileApiKeyEnv(ctx, fallbackDeepSeekEntry()) !== String(DEEPSEEK_API_KEY)) {
|
|
515
|
+
try {
|
|
516
|
+
await resetDeepSeekCredentialRoute(ctx);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return {
|
|
520
|
+
kind: 'error',
|
|
521
|
+
text: 'The API key is valid and saved, but the DeepSeek credential reference could not be reset. Run /login again after checking your settings file.',
|
|
522
|
+
};
|
|
523
|
+
}
|
|
494
524
|
}
|
|
495
525
|
return {
|
|
496
526
|
kind: 'success',
|
|
497
|
-
text: 'Logged in to DeepSeek.
|
|
527
|
+
text: 'Logged in to DeepSeek. The stored key is used by the deepseek-official route on the next model request.',
|
|
498
528
|
};
|
|
499
529
|
}
|
|
500
530
|
async function loginCatalog(ctx, invocation, entry) {
|
|
@@ -510,6 +540,7 @@ async function loginCatalog(ctx, invocation, entry) {
|
|
|
510
540
|
detail: `The key is stored locally and activates the ${entry.provider} route for the next model request. Use /model to select it.`,
|
|
511
541
|
allowCustom: true,
|
|
512
542
|
secret: true,
|
|
543
|
+
notify: false,
|
|
513
544
|
submitLabel: 'save',
|
|
514
545
|
signal: invocation.signal,
|
|
515
546
|
});
|
|
@@ -563,6 +594,7 @@ async function collectCustomModels(ctx, invocation, draft) {
|
|
|
563
594
|
],
|
|
564
595
|
initialValue: 'enter',
|
|
565
596
|
allowCustom: false,
|
|
597
|
+
notify: false,
|
|
566
598
|
signal: invocation.signal,
|
|
567
599
|
});
|
|
568
600
|
if (source === null)
|
|
@@ -587,6 +619,7 @@ async function collectCustomModels(ctx, invocation, draft) {
|
|
|
587
619
|
})),
|
|
588
620
|
multiSelect: true,
|
|
589
621
|
allowCustom: false,
|
|
622
|
+
notify: false,
|
|
590
623
|
filterable: discovered.length > FULLSCREEN_CHOICE_THRESHOLD,
|
|
591
624
|
...(discovered.length > FULLSCREEN_CHOICE_THRESHOLD ? { presentation: 'fullscreen-list' } : {}),
|
|
592
625
|
signal: invocation.signal,
|
|
@@ -605,6 +638,7 @@ async function collectCustomModels(ctx, invocation, draft) {
|
|
|
605
638
|
question: 'Enter model ids',
|
|
606
639
|
detail: `Could not fetch models (${detail}). Type one or more ids, separated by commas.`,
|
|
607
640
|
allowCustom: true,
|
|
641
|
+
notify: false,
|
|
608
642
|
signal: invocation.signal,
|
|
609
643
|
});
|
|
610
644
|
if (fallback === null)
|
|
@@ -617,6 +651,7 @@ async function collectCustomModels(ctx, invocation, draft) {
|
|
|
617
651
|
question: 'Enter model ids',
|
|
618
652
|
detail: 'Separate multiple ids with commas. Example: llama3.1, qwen2.5-coder',
|
|
619
653
|
allowCustom: true,
|
|
654
|
+
notify: false,
|
|
620
655
|
signal: invocation.signal,
|
|
621
656
|
});
|
|
622
657
|
if (typed === null)
|
|
@@ -629,6 +664,7 @@ async function loginCustom(ctx, invocation) {
|
|
|
629
664
|
question: 'Provider id',
|
|
630
665
|
detail: 'Lowercase letters, digits, and hyphens. This id is permanent.',
|
|
631
666
|
allowCustom: true,
|
|
667
|
+
notify: false,
|
|
632
668
|
signal: invocation.signal,
|
|
633
669
|
});
|
|
634
670
|
if (idRaw === null)
|
|
@@ -648,6 +684,7 @@ async function loginCustom(ctx, invocation) {
|
|
|
648
684
|
question: 'Base URL',
|
|
649
685
|
detail: 'Absolute http(s) prefix, for example http://127.0.0.1:11434/v1',
|
|
650
686
|
allowCustom: true,
|
|
687
|
+
notify: false,
|
|
651
688
|
signal: invocation.signal,
|
|
652
689
|
});
|
|
653
690
|
if (urlRaw === null)
|
|
@@ -671,6 +708,7 @@ async function loginCustom(ctx, invocation) {
|
|
|
671
708
|
})),
|
|
672
709
|
initialValue: 'openai-completions',
|
|
673
710
|
allowCustom: false,
|
|
711
|
+
notify: false,
|
|
674
712
|
signal: invocation.signal,
|
|
675
713
|
});
|
|
676
714
|
if (api === null)
|
|
@@ -684,6 +722,7 @@ async function loginCustom(ctx, invocation) {
|
|
|
684
722
|
detail: 'Leave empty if the endpoint does not need a key.',
|
|
685
723
|
allowCustom: true,
|
|
686
724
|
secret: true,
|
|
725
|
+
notify: false,
|
|
687
726
|
submitLabel: 'continue',
|
|
688
727
|
signal: invocation.signal,
|
|
689
728
|
});
|
|
@@ -752,60 +791,88 @@ async function loginCustom(ctx, invocation) {
|
|
|
752
791
|
}
|
|
753
792
|
async function logoutDeepSeek(ctx, invocation) {
|
|
754
793
|
const activeRef = profileApiKeyEnv(ctx, fallbackDeepSeekEntry()) ?? String(DEEPSEEK_API_KEY);
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
text: `No omdsh-managed DeepSeek login is active. DeepSeek is configured by ${credentialSource(current)}.`,
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
if (usesOmdshCredential && !current.configured) {
|
|
768
|
-
try {
|
|
769
|
-
await resetDeepSeekCredentialRoute(ctx);
|
|
770
|
-
}
|
|
771
|
-
catch {
|
|
772
|
-
return { kind: 'error', text: 'Could not reset the DeepSeek credential setting.' };
|
|
794
|
+
const current = await ctx.credentials.describe(credentialRef(activeRef));
|
|
795
|
+
if (!current.configured) {
|
|
796
|
+
if (activeRef !== String(DEEPSEEK_API_KEY)) {
|
|
797
|
+
try {
|
|
798
|
+
await resetDeepSeekCredentialRoute(ctx);
|
|
799
|
+
}
|
|
800
|
+
catch {
|
|
801
|
+
return { kind: 'error', text: 'Could not reset the DeepSeek credential setting.' };
|
|
802
|
+
}
|
|
773
803
|
}
|
|
774
|
-
return
|
|
804
|
+
return { kind: 'success', text: 'DeepSeek is already logged out.' };
|
|
805
|
+
}
|
|
806
|
+
if (current.source !== 'file') {
|
|
807
|
+
return { kind: 'success', text: `DeepSeek is configured by ${credentialSource(current)}; the credential store cannot remove it.` };
|
|
775
808
|
}
|
|
776
|
-
const removesStoredKey = current.source === 'file';
|
|
777
809
|
const answer = await ctx.tui.prompt({
|
|
778
810
|
title: 'Logout from DeepSeek',
|
|
779
|
-
question:
|
|
780
|
-
detail: 'The next model request will
|
|
811
|
+
question: 'Remove the stored DeepSeek API key?',
|
|
812
|
+
detail: 'The next model request will use DEEPSEEK_API_KEY from the launch environment when one is available.',
|
|
781
813
|
options: [
|
|
782
814
|
{ label: 'Cancel', value: 'cancel', description: 'Keep the current credential.' },
|
|
783
815
|
{
|
|
784
816
|
label: 'Log out',
|
|
785
817
|
value: 'logout',
|
|
786
|
-
description:
|
|
787
|
-
? 'Remove the key from the local Harness credential store.'
|
|
788
|
-
: 'Return the provider to its default credential source.',
|
|
818
|
+
description: 'Remove the key from the local Harness credential store.',
|
|
789
819
|
badge: { label: 'removes credential', tone: 'warning' },
|
|
790
820
|
},
|
|
791
821
|
],
|
|
792
822
|
initialValue: 'cancel',
|
|
793
823
|
allowCustom: false,
|
|
824
|
+
notify: false,
|
|
794
825
|
submitLabel: 'apply',
|
|
795
826
|
signal: invocation.signal,
|
|
796
827
|
});
|
|
797
828
|
if (answer !== 'logout')
|
|
798
829
|
return { kind: 'success' };
|
|
799
830
|
try {
|
|
800
|
-
|
|
831
|
+
await ctx.credentials.unset(credentialRef(activeRef));
|
|
832
|
+
if (activeRef !== String(DEEPSEEK_API_KEY))
|
|
801
833
|
await resetDeepSeekCredentialRoute(ctx);
|
|
802
|
-
if (removesStoredKey)
|
|
803
|
-
await ctx.credentials.unset(activeCredential);
|
|
804
834
|
}
|
|
805
835
|
catch {
|
|
806
836
|
return { kind: 'error', text: 'Could not remove the DeepSeek API key from the Harness credential store.' };
|
|
807
837
|
}
|
|
808
|
-
return
|
|
838
|
+
return { kind: 'success', text: 'Logged out from DeepSeek. The next model request falls back to the environment when available.' };
|
|
839
|
+
}
|
|
840
|
+
async function logoutDeepSeekAccount(ctx, invocation) {
|
|
841
|
+
const account = accountClient(ctx);
|
|
842
|
+
if (account === undefined)
|
|
843
|
+
return { kind: 'error', text: 'DeepSeek account integration is not mounted in this build.' };
|
|
844
|
+
let state;
|
|
845
|
+
try {
|
|
846
|
+
state = await account.getState();
|
|
847
|
+
}
|
|
848
|
+
catch {
|
|
849
|
+
return { kind: 'error', text: 'Could not read the shared DeepSeek account state.' };
|
|
850
|
+
}
|
|
851
|
+
if (state.status !== 'credential-stored') {
|
|
852
|
+
return { kind: 'success', text: 'No DeepSeek account grant is stored.' };
|
|
853
|
+
}
|
|
854
|
+
const answer = await ctx.tui.prompt({
|
|
855
|
+
title: 'Logout from DeepSeek account',
|
|
856
|
+
question: 'Sign out of the shared DeepSeek account?',
|
|
857
|
+
detail: 'Removes the local grant and revokes it in the background. The browser session is not signed out.',
|
|
858
|
+
options: [
|
|
859
|
+
{ label: 'Cancel', value: 'cancel', description: 'Keep the account grant.' },
|
|
860
|
+
{ label: 'Sign out', value: 'logout', description: 'Remove the local grant and revoke it in the background.' },
|
|
861
|
+
],
|
|
862
|
+
initialValue: 'cancel',
|
|
863
|
+
allowCustom: false,
|
|
864
|
+
notify: false,
|
|
865
|
+
signal: invocation.signal,
|
|
866
|
+
});
|
|
867
|
+
if (answer !== 'logout')
|
|
868
|
+
return { kind: 'success' };
|
|
869
|
+
try {
|
|
870
|
+
await account.signOut(accountClientMetadata());
|
|
871
|
+
}
|
|
872
|
+
catch {
|
|
873
|
+
return { kind: 'error', text: 'Could not sign out of the DeepSeek account. Try again from DSH Web/Desktop.' };
|
|
874
|
+
}
|
|
875
|
+
return { kind: 'success', text: 'Signed out of the DeepSeek account. The local grant was removed; remote revocation continues in the background.' };
|
|
809
876
|
}
|
|
810
877
|
async function pickLogoutChoice(ctx, invocation) {
|
|
811
878
|
const choices = await logoutableChoices(ctx);
|
|
@@ -813,7 +880,9 @@ async function pickLogoutChoice(ctx, invocation) {
|
|
|
813
880
|
return undefined;
|
|
814
881
|
if (choices.length === 1)
|
|
815
882
|
return choices[0];
|
|
816
|
-
const values = choices.map(choice =>
|
|
883
|
+
const values = choices.map(choice => choice === DEEPSEEK_ACCOUNT_CHOICE
|
|
884
|
+
? DEEPSEEK_ACCOUNT_CHOICE
|
|
885
|
+
: isFlowRecordLogout(choice) ? flowChoiceValue(choice.key) : choice.provider);
|
|
817
886
|
const fullscreen = values.length > FULLSCREEN_CHOICE_THRESHOLD;
|
|
818
887
|
const raw = await ctx.tui.prompt({
|
|
819
888
|
...(fullscreen ? { presentation: 'fullscreen-list' } : {}),
|
|
@@ -821,18 +890,25 @@ async function pickLogoutChoice(ctx, invocation) {
|
|
|
821
890
|
filterable: fullscreen,
|
|
822
891
|
title: 'Provider',
|
|
823
892
|
question: 'Choose a provider to log out from',
|
|
824
|
-
options: choices.map(choice =>
|
|
893
|
+
options: choices.map(choice => choice === DEEPSEEK_ACCOUNT_CHOICE
|
|
825
894
|
? {
|
|
826
|
-
label:
|
|
827
|
-
value:
|
|
828
|
-
description:
|
|
895
|
+
label: 'DeepSeek account',
|
|
896
|
+
value: DEEPSEEK_ACCOUNT_CHOICE,
|
|
897
|
+
description: 'shared with DSH · signed in',
|
|
829
898
|
}
|
|
830
|
-
:
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
899
|
+
: isFlowRecordLogout(choice)
|
|
900
|
+
? {
|
|
901
|
+
label: choice.label,
|
|
902
|
+
value: flowChoiceValue(choice.key),
|
|
903
|
+
description: choice.writable ? 'signed in' : 'not removable here',
|
|
904
|
+
}
|
|
905
|
+
: {
|
|
906
|
+
label: providerListId(choice),
|
|
907
|
+
value: choice.provider,
|
|
908
|
+
description: 'signed in',
|
|
909
|
+
}),
|
|
835
910
|
allowCustom: false,
|
|
911
|
+
notify: false,
|
|
836
912
|
signal: invocation.signal,
|
|
837
913
|
});
|
|
838
914
|
if (raw === null)
|
|
@@ -840,10 +916,12 @@ async function pickLogoutChoice(ctx, invocation) {
|
|
|
840
916
|
const chosen = selected(raw, values);
|
|
841
917
|
if (chosen === undefined)
|
|
842
918
|
return undefined;
|
|
919
|
+
if (chosen === DEEPSEEK_ACCOUNT_CHOICE)
|
|
920
|
+
return DEEPSEEK_ACCOUNT_CHOICE;
|
|
843
921
|
const flowKey = parseFlowChoice(chosen);
|
|
844
922
|
if (flowKey !== undefined)
|
|
845
923
|
return choices.find(choice => isFlowRecordLogout(choice) && choice.key === flowKey);
|
|
846
|
-
return choices.find(choice => !isFlowRecordLogout(choice) && choice.provider === chosen);
|
|
924
|
+
return choices.find(choice => typeof choice !== 'string' && !isFlowRecordLogout(choice) && choice.provider === chosen);
|
|
847
925
|
}
|
|
848
926
|
async function logoutRecord(ctx, invocation, record) {
|
|
849
927
|
if (!record.writable) {
|
|
@@ -867,6 +945,7 @@ async function logoutRecord(ctx, invocation, record) {
|
|
|
867
945
|
],
|
|
868
946
|
initialValue: 'cancel',
|
|
869
947
|
allowCustom: false,
|
|
948
|
+
notify: false,
|
|
870
949
|
submitLabel: 'apply',
|
|
871
950
|
signal: invocation.signal,
|
|
872
951
|
});
|
|
@@ -911,6 +990,7 @@ async function logoutCatalog(ctx, invocation, entry) {
|
|
|
911
990
|
],
|
|
912
991
|
initialValue: 'cancel',
|
|
913
992
|
allowCustom: false,
|
|
993
|
+
notify: false,
|
|
914
994
|
submitLabel: 'apply',
|
|
915
995
|
signal: invocation.signal,
|
|
916
996
|
});
|
|
@@ -928,6 +1008,156 @@ async function logoutCatalog(ctx, invocation, entry) {
|
|
|
928
1008
|
}
|
|
929
1009
|
return { kind: 'success', text: `Logged out from ${label}.` };
|
|
930
1010
|
}
|
|
1011
|
+
function accountClient(ctx) {
|
|
1012
|
+
return ctx.get('deepseekAccount');
|
|
1013
|
+
}
|
|
1014
|
+
function accountClientMetadata() {
|
|
1015
|
+
return {
|
|
1016
|
+
version: APP_VERSION,
|
|
1017
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
1018
|
+
timezoneOffsetSeconds: -new Date().getTimezoneOffset() * 60,
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function accountCallbackOrigin(ctx) {
|
|
1022
|
+
const server = ctx.get('webServer');
|
|
1023
|
+
if (server === undefined || typeof server.port !== 'number' || server.port === 0)
|
|
1024
|
+
return undefined;
|
|
1025
|
+
return `${server.protocol}//${server.host}:${server.port}`;
|
|
1026
|
+
}
|
|
1027
|
+
function accountFailure(attempt) {
|
|
1028
|
+
if (attempt?.phase === 'expired')
|
|
1029
|
+
return 'The DeepSeek sign-in expired. Run /login to try again.';
|
|
1030
|
+
if (attempt?.phase === 'failed')
|
|
1031
|
+
return `DeepSeek sign-in failed${attempt.errorCode === undefined ? '' : ` (${attempt.errorCode})`}. Run /login to try again.`;
|
|
1032
|
+
if (attempt?.phase === 'cancelled')
|
|
1033
|
+
return 'DeepSeek sign-in was cancelled.';
|
|
1034
|
+
return undefined;
|
|
1035
|
+
}
|
|
1036
|
+
async function waitForAccount(invocation, account, timeoutMs) {
|
|
1037
|
+
const deadline = Date.now() + timeoutMs;
|
|
1038
|
+
while (Date.now() < deadline) {
|
|
1039
|
+
if (invocation.signal.aborted) {
|
|
1040
|
+
const state = await account.getState().catch(() => undefined);
|
|
1041
|
+
const id = state?.attempt?.id;
|
|
1042
|
+
if (id !== undefined)
|
|
1043
|
+
await account.cancelSignIn(id).catch(() => undefined);
|
|
1044
|
+
return { kind: 'success' };
|
|
1045
|
+
}
|
|
1046
|
+
const state = await account.getState().catch(() => undefined);
|
|
1047
|
+
if (state?.status === 'credential-stored' && state.attempt?.phase === 'succeeded') {
|
|
1048
|
+
return { kind: 'success', text: 'Signed in to the DeepSeek account. Use /model to select the deepseek-account route; web search and fetch use the account token there.' };
|
|
1049
|
+
}
|
|
1050
|
+
const failure = accountFailure(state?.attempt);
|
|
1051
|
+
if (failure !== undefined)
|
|
1052
|
+
return { kind: 'error', text: failure };
|
|
1053
|
+
await new Promise(resolve => setTimeout(resolve, 400));
|
|
1054
|
+
}
|
|
1055
|
+
const state = await account.getState().catch(() => undefined);
|
|
1056
|
+
const id = state?.attempt?.id;
|
|
1057
|
+
if (id !== undefined)
|
|
1058
|
+
await account.cancelSignIn(id).catch(() => undefined);
|
|
1059
|
+
return { kind: 'error', text: 'DeepSeek sign-in timed out before the browser completed. Run /login to try again.' };
|
|
1060
|
+
}
|
|
1061
|
+
async function loginDeepSeekAccount(ctx, invocation, config) {
|
|
1062
|
+
const account = accountClient(ctx);
|
|
1063
|
+
if (account === undefined)
|
|
1064
|
+
return { kind: 'error', text: 'DeepSeek account integration is not mounted in this build.' };
|
|
1065
|
+
let state;
|
|
1066
|
+
try {
|
|
1067
|
+
state = await account.getState();
|
|
1068
|
+
}
|
|
1069
|
+
catch {
|
|
1070
|
+
return { kind: 'error', text: 'Could not read the shared DeepSeek account state.' };
|
|
1071
|
+
}
|
|
1072
|
+
if (state.status === 'credential-stored') {
|
|
1073
|
+
const choice = await ctx.tui.prompt({
|
|
1074
|
+
title: 'DeepSeek account',
|
|
1075
|
+
question: 'The account is signed in and shared with DSH.',
|
|
1076
|
+
detail: 'Use /model to select the deepseek-account route. Signing out removes the local grant; the DSH browser session is revoked in the background.',
|
|
1077
|
+
options: [
|
|
1078
|
+
{ label: 'Use account route', value: 'use', description: 'Close this prompt; select the route in /model.' },
|
|
1079
|
+
{ label: 'Sign out', value: 'out', description: 'Remove the local grant and revoke it in the background.' },
|
|
1080
|
+
{ label: 'Cancel', value: 'cancel' },
|
|
1081
|
+
],
|
|
1082
|
+
initialValue: 'use',
|
|
1083
|
+
allowCustom: false,
|
|
1084
|
+
notify: false,
|
|
1085
|
+
signal: invocation.signal,
|
|
1086
|
+
});
|
|
1087
|
+
if (choice === null || choice === 'cancel' || choice === 'use') {
|
|
1088
|
+
return { kind: 'success', text: 'Use /model to select the deepseek-account route.' };
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
await account.signOut(accountClientMetadata());
|
|
1092
|
+
}
|
|
1093
|
+
catch {
|
|
1094
|
+
return { kind: 'error', text: 'Could not sign out of the DeepSeek account. Try again from DSH Web/Desktop.' };
|
|
1095
|
+
}
|
|
1096
|
+
return { kind: 'success', text: 'Signed out of the DeepSeek account. The next account request requires sign-in again.' };
|
|
1097
|
+
}
|
|
1098
|
+
const server = ctx.get('webServer');
|
|
1099
|
+
if (server === undefined || ctx.tui.interactive === false) {
|
|
1100
|
+
return {
|
|
1101
|
+
kind: 'error',
|
|
1102
|
+
text: 'The loopback sign-in host is unavailable. Start omdsh with its normal terminal host, or sign in through DSH Web/Desktop and rerun /login.',
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
const origin = accountCallbackOrigin(ctx);
|
|
1106
|
+
if (origin === undefined)
|
|
1107
|
+
return { kind: 'error', text: 'The loopback sign-in host did not expose a port.' };
|
|
1108
|
+
let initial;
|
|
1109
|
+
try {
|
|
1110
|
+
initial = await account.startSignIn(accountClientMetadata(), origin, 'web');
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
return { kind: 'error', text: error instanceof Error ? error.message : 'Could not start DeepSeek sign-in.' };
|
|
1114
|
+
}
|
|
1115
|
+
const attempt = initial.attempt;
|
|
1116
|
+
const failure = accountFailure(attempt);
|
|
1117
|
+
if (failure !== undefined)
|
|
1118
|
+
return { kind: 'error', text: failure };
|
|
1119
|
+
let url = attempt?.authorizeUrl;
|
|
1120
|
+
for (let i = 0; i < 40 && url === undefined; i += 1) {
|
|
1121
|
+
if (invocation.signal.aborted)
|
|
1122
|
+
break;
|
|
1123
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1124
|
+
const next = await account.getState().catch(() => undefined);
|
|
1125
|
+
const nextFailure = accountFailure(next?.attempt);
|
|
1126
|
+
if (nextFailure !== undefined)
|
|
1127
|
+
return { kind: 'error', text: nextFailure };
|
|
1128
|
+
url = next?.attempt?.authorizeUrl;
|
|
1129
|
+
}
|
|
1130
|
+
if (url === undefined) {
|
|
1131
|
+
const id = attempt?.id ?? (await account.getState().catch(() => undefined))?.attempt?.id;
|
|
1132
|
+
if (id !== undefined)
|
|
1133
|
+
await account.cancelSignIn(id).catch(() => undefined);
|
|
1134
|
+
return { kind: 'error', text: 'DeepSeek did not return a browser URL for this sign-in attempt.' };
|
|
1135
|
+
}
|
|
1136
|
+
if (config.openBrowser !== false)
|
|
1137
|
+
openExternal(url);
|
|
1138
|
+
const answer = await ctx.tui.prompt({
|
|
1139
|
+
title: 'DeepSeek account',
|
|
1140
|
+
question: 'Complete sign-in in your browser, then return here.',
|
|
1141
|
+
detail: `${url}
|
|
1142
|
+
|
|
1143
|
+
This terminal is listening on ${origin}/oauth/callback until the attempt ends.`,
|
|
1144
|
+
options: [
|
|
1145
|
+
{ label: 'I completed sign-in', value: 'done' },
|
|
1146
|
+
{ label: 'Cancel', value: 'cancel', description: 'Withdraw this sign-in attempt.' },
|
|
1147
|
+
],
|
|
1148
|
+
initialValue: 'done',
|
|
1149
|
+
allowCustom: false,
|
|
1150
|
+
notify: false,
|
|
1151
|
+
signal: invocation.signal,
|
|
1152
|
+
});
|
|
1153
|
+
if (answer !== 'done') {
|
|
1154
|
+
const id = attempt?.id ?? (await account.getState().catch(() => undefined))?.attempt?.id;
|
|
1155
|
+
if (id !== undefined)
|
|
1156
|
+
await account.cancelSignIn(id).catch(() => undefined);
|
|
1157
|
+
return { kind: 'success', text: 'DeepSeek sign-in cancelled.' };
|
|
1158
|
+
}
|
|
1159
|
+
return waitForAccount(invocation, account, 120_000);
|
|
1160
|
+
}
|
|
931
1161
|
async function login(ctx, invocation, config) {
|
|
932
1162
|
if (invocation.rawInput.trim() !== '') {
|
|
933
1163
|
return { kind: 'error', text: 'Usage: /login (paste the key only into the protected prompt)' };
|
|
@@ -935,6 +1165,8 @@ async function login(ctx, invocation, config) {
|
|
|
935
1165
|
const choice = await pickLoginChoice(ctx, invocation);
|
|
936
1166
|
if (choice === undefined)
|
|
937
1167
|
return { kind: 'success' };
|
|
1168
|
+
if (choice === DEEPSEEK_ACCOUNT_CHOICE)
|
|
1169
|
+
return loginDeepSeekAccount(ctx, invocation, config);
|
|
938
1170
|
if (choice === CUSTOM_PROVIDER_VALUE)
|
|
939
1171
|
return loginCustom(ctx, invocation);
|
|
940
1172
|
if (isAuthorizationFlow(choice))
|
|
@@ -949,6 +1181,8 @@ async function logout(ctx, invocation) {
|
|
|
949
1181
|
const choice = await pickLogoutChoice(ctx, invocation);
|
|
950
1182
|
if (choice === undefined)
|
|
951
1183
|
return { kind: 'success' };
|
|
1184
|
+
if (choice === DEEPSEEK_ACCOUNT_CHOICE)
|
|
1185
|
+
return logoutDeepSeekAccount(ctx, invocation);
|
|
952
1186
|
if (isFlowRecordLogout(choice))
|
|
953
1187
|
return logoutRecord(ctx, invocation, choice);
|
|
954
1188
|
return isDeepSeek(choice)
|
|
@@ -958,7 +1192,7 @@ async function logout(ctx, invocation) {
|
|
|
958
1192
|
export function apply(ctx, config = {}) {
|
|
959
1193
|
registerCommands(ctx, [
|
|
960
1194
|
{ name: 'login', description: 'Sign in to a provider', handler: invocation => login(ctx, invocation, config) },
|
|
961
|
-
{ name: 'logout', description: 'Sign out of a stored provider', handler: invocation => logout(ctx, invocation) },
|
|
1195
|
+
{ name: 'logout', description: 'Sign out of a stored provider or account', handler: invocation => logout(ctx, invocation) },
|
|
962
1196
|
], 'omdsh authentication commands');
|
|
963
1197
|
}
|
|
964
1198
|
//# sourceMappingURL=auth.js.map
|