@dench.com/cli 0.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.md +151 -0
- package/agentKind.ts +58 -0
- package/crm.ts +1402 -0
- package/dench +10 -0
- package/dench.mjs +10 -0
- package/dench.ts +3305 -0
- package/fs-daemon +10 -0
- package/fs-daemon.ts +682 -0
- package/host.ts +44 -0
- package/lib/cli-args.ts +52 -0
- package/openUrl.ts +120 -0
- package/package.json +35 -0
- package/session.ts +504 -0
package/session.ts
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
type Env = Record<string, string | undefined>;
|
|
5
|
+
|
|
6
|
+
export type StoredSessionLike = {
|
|
7
|
+
host: string;
|
|
8
|
+
organization?: { id?: string; name?: string; slug?: string };
|
|
9
|
+
agent?: { id?: string; name?: string; kind?: string };
|
|
10
|
+
sessionScope?: string;
|
|
11
|
+
sessionScopeLabel?: string;
|
|
12
|
+
savedAt?: number;
|
|
13
|
+
sessionExpiresAt?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type ConfigWithSessions<TSession extends StoredSessionLike> = {
|
|
17
|
+
currentHost?: string;
|
|
18
|
+
currentSessionKey?: string;
|
|
19
|
+
currentHosts?: Record<string, string>;
|
|
20
|
+
currentSessionKeys?: Record<string, string>;
|
|
21
|
+
sessions?: Record<string, TSession>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type StoredSessionEntry<TSession extends StoredSessionLike> = {
|
|
25
|
+
key: string;
|
|
26
|
+
host: string;
|
|
27
|
+
session: TSession;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type SessionScope = {
|
|
31
|
+
key: string;
|
|
32
|
+
label: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const SESSION_KEY_OPTION_NAMES = ["--session-key", "--session"];
|
|
36
|
+
const EXPLICIT_SESSION_ENV_NAMES = ["DENCH_SESSION_KEY", "DENCH_AGENT_SESSION"];
|
|
37
|
+
const AGENT_SESSION_ENV_NAMES = [
|
|
38
|
+
"CURSOR_AGENT_ID",
|
|
39
|
+
"CURSOR_SESSION_ID",
|
|
40
|
+
"CURSOR_WORKSPACE_ID",
|
|
41
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
42
|
+
"CLAUDECODE_SESSION_ID",
|
|
43
|
+
"CODEX_SESSION_ID",
|
|
44
|
+
"OPENAI_CODEX_SESSION_ID",
|
|
45
|
+
"AGENT_SESSION_ID",
|
|
46
|
+
"TERM_SESSION_ID",
|
|
47
|
+
"TMUX_PANE",
|
|
48
|
+
"STY",
|
|
49
|
+
"VSCODE_PID",
|
|
50
|
+
"VSCODE_IPC_HOOK_CLI",
|
|
51
|
+
"VSCODE_GIT_IPC_HANDLE",
|
|
52
|
+
];
|
|
53
|
+
const AGENT_PROCESS_PATTERNS = [
|
|
54
|
+
"cursor",
|
|
55
|
+
"claude",
|
|
56
|
+
"codex",
|
|
57
|
+
"openclaw",
|
|
58
|
+
"hermes",
|
|
59
|
+
"opencode",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
function optionFromArgs(args: string[], name: string) {
|
|
63
|
+
const index = args.indexOf(name);
|
|
64
|
+
if (index === -1) return undefined;
|
|
65
|
+
return args[index + 1];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function firstOptionFromArgs(args: string[], names: string[]) {
|
|
69
|
+
for (const name of names) {
|
|
70
|
+
const value = optionFromArgs(args, name);
|
|
71
|
+
if (value?.trim()) return value.trim();
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function firstPositionalArg(args: string[]) {
|
|
77
|
+
for (let i = 0; i < args.length; i++) {
|
|
78
|
+
if (args[i].startsWith("--")) {
|
|
79
|
+
if (args[i + 1] && !args[i + 1].startsWith("--")) i++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
return args[i];
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function firstEnvValue(env: Env, names: string[]) {
|
|
88
|
+
for (const name of names) {
|
|
89
|
+
const value = env[name]?.trim();
|
|
90
|
+
if (value) return { name, value };
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateStableSessionKeyInput(value: string, source: string) {
|
|
96
|
+
if (!/^(auto|explicit):/i.test(value)) return;
|
|
97
|
+
throw new Error(
|
|
98
|
+
`${source} must be a stable human-readable id, not internal session scope "${value}". Run dench use ${value} to select that existing session, or choose a stable id like billing-repo-agent before dench login.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function explicitSessionKeyInput({
|
|
103
|
+
args,
|
|
104
|
+
env = process.env,
|
|
105
|
+
}: {
|
|
106
|
+
args: string[];
|
|
107
|
+
env?: Env;
|
|
108
|
+
}) {
|
|
109
|
+
const optionNames =
|
|
110
|
+
firstPositionalArg(args) === "logout"
|
|
111
|
+
? SESSION_KEY_OPTION_NAMES.filter((name) => name !== "--session")
|
|
112
|
+
: SESSION_KEY_OPTION_NAMES;
|
|
113
|
+
const fromOption = firstOptionFromArgs(args, optionNames);
|
|
114
|
+
if (fromOption) {
|
|
115
|
+
validateStableSessionKeyInput(fromOption, "--session-key");
|
|
116
|
+
return { source: "--session-key", value: fromOption };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const fromEnv = firstEnvValue(env, EXPLICIT_SESSION_ENV_NAMES);
|
|
120
|
+
if (fromEnv) {
|
|
121
|
+
validateStableSessionKeyInput(fromEnv.value, fromEnv.name);
|
|
122
|
+
return { source: fromEnv.name, value: fromEnv.value };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function hashValue(value: string) {
|
|
129
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 24);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function normalizeHostValue(host: string) {
|
|
133
|
+
const withProtocol = /^https?:\/\//.test(host) ? host : `https://${host}`;
|
|
134
|
+
return new URL(withProtocol).origin;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function processAncestrySignature() {
|
|
138
|
+
if (process.platform === "win32") return undefined;
|
|
139
|
+
|
|
140
|
+
let pid = process.ppid;
|
|
141
|
+
const parts: string[] = [];
|
|
142
|
+
for (let depth = 0; pid > 1 && depth < 8; depth++) {
|
|
143
|
+
const result = spawnSync(
|
|
144
|
+
"ps",
|
|
145
|
+
["-p", String(pid), "-o", "ppid=", "-o", "command="],
|
|
146
|
+
{
|
|
147
|
+
encoding: "utf8",
|
|
148
|
+
timeout: 200,
|
|
149
|
+
windowsHide: true,
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
if (result.status !== 0 || !result.stdout.trim()) break;
|
|
153
|
+
|
|
154
|
+
const line = result.stdout.trim();
|
|
155
|
+
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
156
|
+
if (!match) break;
|
|
157
|
+
|
|
158
|
+
const command = match[2].toLowerCase();
|
|
159
|
+
const agentName = AGENT_PROCESS_PATTERNS.find((name) =>
|
|
160
|
+
command.includes(name),
|
|
161
|
+
);
|
|
162
|
+
if (agentName) {
|
|
163
|
+
parts.push(`${agentName}:${pid}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
pid = Number(match[1]);
|
|
167
|
+
if (!Number.isFinite(pid)) break;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return parts.length ? parts.join("|") : undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function resolveSessionScope({
|
|
174
|
+
args,
|
|
175
|
+
env = process.env,
|
|
176
|
+
cwd = process.cwd(),
|
|
177
|
+
ancestrySignature,
|
|
178
|
+
}: {
|
|
179
|
+
args: string[];
|
|
180
|
+
env?: Env;
|
|
181
|
+
cwd?: string;
|
|
182
|
+
ancestrySignature?: string | null;
|
|
183
|
+
}): SessionScope {
|
|
184
|
+
const resolvedAncestrySignature =
|
|
185
|
+
ancestrySignature === undefined
|
|
186
|
+
? processAncestrySignature()
|
|
187
|
+
: ancestrySignature;
|
|
188
|
+
const explicit = explicitSessionKeyInput({ args, env })?.value;
|
|
189
|
+
if (explicit) {
|
|
190
|
+
return {
|
|
191
|
+
key: `explicit:${hashValue(explicit)}`,
|
|
192
|
+
label: "explicit",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const contextParts = AGENT_SESSION_ENV_NAMES.flatMap((name) => {
|
|
197
|
+
const value = env[name]?.trim();
|
|
198
|
+
return value ? [`env:${name}:${value}`] : [];
|
|
199
|
+
});
|
|
200
|
+
if (resolvedAncestrySignature) {
|
|
201
|
+
contextParts.push(`process:${resolvedAncestrySignature}`);
|
|
202
|
+
}
|
|
203
|
+
contextParts.push(`cwd:${cwd}`);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
key: `auto:${hashValue(contextParts.join("\0"))}`,
|
|
207
|
+
label: contextParts.length > 1 ? "local-agent-context" : "cwd",
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function sessionConfigKey(host: string, scope: SessionScope) {
|
|
212
|
+
return `${normalizeHostValue(host)}#${scope.key}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isExplicitScope(scope: SessionScope) {
|
|
216
|
+
return scope.label === "explicit" || scope.key.startsWith("explicit:");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function sessionFromSelectedKey<TSession extends StoredSessionLike>(
|
|
220
|
+
config: ConfigWithSessions<TSession>,
|
|
221
|
+
selectedKey: string | undefined,
|
|
222
|
+
normalizedHost: string,
|
|
223
|
+
) {
|
|
224
|
+
if (!selectedKey) return undefined;
|
|
225
|
+
const selected = config.sessions?.[selectedKey];
|
|
226
|
+
if (selected && normalizeHostValue(selected.host) === normalizedHost) {
|
|
227
|
+
return { key: selectedKey, session: selected };
|
|
228
|
+
}
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function withCurrentSessionSelection<TSession extends StoredSessionLike>(
|
|
233
|
+
config: ConfigWithSessions<TSession>,
|
|
234
|
+
{
|
|
235
|
+
host,
|
|
236
|
+
scope,
|
|
237
|
+
sessionKey,
|
|
238
|
+
}: { host: string; scope: SessionScope; sessionKey: string },
|
|
239
|
+
): ConfigWithSessions<TSession> {
|
|
240
|
+
const normalizedHost = normalizeHostValue(host);
|
|
241
|
+
return {
|
|
242
|
+
...config,
|
|
243
|
+
currentHost: normalizedHost,
|
|
244
|
+
currentSessionKey: sessionKey,
|
|
245
|
+
currentHosts: {
|
|
246
|
+
...(config.currentHosts ?? {}),
|
|
247
|
+
[scope.key]: normalizedHost,
|
|
248
|
+
},
|
|
249
|
+
currentSessionKeys: {
|
|
250
|
+
...(config.currentSessionKeys ?? {}),
|
|
251
|
+
[scope.key]: sessionKey,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function withSavedSession<TSession extends StoredSessionLike>(
|
|
257
|
+
config: ConfigWithSessions<TSession>,
|
|
258
|
+
session: TSession,
|
|
259
|
+
scope: SessionScope,
|
|
260
|
+
) {
|
|
261
|
+
const host = normalizeHostValue(session.host);
|
|
262
|
+
const key = sessionConfigKey(host, scope);
|
|
263
|
+
const nextConfig = withCurrentSessionSelection(
|
|
264
|
+
{
|
|
265
|
+
...config,
|
|
266
|
+
sessions: {
|
|
267
|
+
...(config.sessions ?? {}),
|
|
268
|
+
[key]: {
|
|
269
|
+
...session,
|
|
270
|
+
host,
|
|
271
|
+
sessionScope: scope.key,
|
|
272
|
+
sessionScopeLabel: scope.label,
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{ host, scope, sessionKey: key },
|
|
277
|
+
);
|
|
278
|
+
return { key, config: nextConfig };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function listStoredSessionEntries<TSession extends StoredSessionLike>(
|
|
282
|
+
config: ConfigWithSessions<TSession>,
|
|
283
|
+
host?: string,
|
|
284
|
+
): Array<StoredSessionEntry<TSession>> {
|
|
285
|
+
const normalizedHost = host ? normalizeHostValue(host) : undefined;
|
|
286
|
+
return Object.entries(config.sessions ?? {})
|
|
287
|
+
.map(([key, session]) => ({
|
|
288
|
+
key,
|
|
289
|
+
host: normalizeHostValue(session.host),
|
|
290
|
+
session,
|
|
291
|
+
}))
|
|
292
|
+
.filter((entry) => !normalizedHost || entry.host === normalizedHost)
|
|
293
|
+
.sort((a, b) => {
|
|
294
|
+
if (a.host !== b.host) return a.host.localeCompare(b.host);
|
|
295
|
+
const aSaved = a.session.savedAt ?? 0;
|
|
296
|
+
const bSaved = b.session.savedAt ?? 0;
|
|
297
|
+
if (aSaved !== bSaved) return bSaved - aSaved;
|
|
298
|
+
return a.key.localeCompare(b.key);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function matchesSelector<TSession extends StoredSessionLike>(
|
|
303
|
+
entry: StoredSessionEntry<TSession>,
|
|
304
|
+
selector: string,
|
|
305
|
+
) {
|
|
306
|
+
const normalizedSelector = selector.trim().toLowerCase();
|
|
307
|
+
if (!normalizedSelector) return false;
|
|
308
|
+
|
|
309
|
+
const candidates = [
|
|
310
|
+
entry.key,
|
|
311
|
+
entry.session.sessionScope,
|
|
312
|
+
entry.session.organization?.id,
|
|
313
|
+
entry.session.organization?.name,
|
|
314
|
+
entry.session.organization?.slug,
|
|
315
|
+
entry.session.agent?.id,
|
|
316
|
+
entry.session.agent?.name,
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
return candidates.some(
|
|
320
|
+
(candidate) => candidate?.toLowerCase() === normalizedSelector,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function findStoredSessionEntries<TSession extends StoredSessionLike>(
|
|
325
|
+
config: ConfigWithSessions<TSession>,
|
|
326
|
+
selector: string,
|
|
327
|
+
host?: string,
|
|
328
|
+
) {
|
|
329
|
+
return listStoredSessionEntries(config, host).filter((entry) =>
|
|
330
|
+
matchesSelector(entry, selector),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function resolveConfigHost<TSession extends StoredSessionLike>(
|
|
335
|
+
config: ConfigWithSessions<TSession> | undefined,
|
|
336
|
+
scope: SessionScope,
|
|
337
|
+
) {
|
|
338
|
+
return config?.currentHosts?.[scope.key] ?? config?.currentHost;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function selectStoredSession<TSession extends StoredSessionLike>(
|
|
342
|
+
config: ConfigWithSessions<TSession>,
|
|
343
|
+
host: string,
|
|
344
|
+
scope: SessionScope,
|
|
345
|
+
):
|
|
346
|
+
| { status: "found"; session: TSession }
|
|
347
|
+
| { status: "missing" }
|
|
348
|
+
| { status: "ambiguous"; count: number } {
|
|
349
|
+
const selected = selectStoredSessionEntry(config, host, scope);
|
|
350
|
+
if (selected.status === "found") {
|
|
351
|
+
return { status: "found", session: selected.session };
|
|
352
|
+
}
|
|
353
|
+
return selected;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function selectStoredSessionEntry<TSession extends StoredSessionLike>(
|
|
357
|
+
config: ConfigWithSessions<TSession>,
|
|
358
|
+
host: string,
|
|
359
|
+
scope: SessionScope,
|
|
360
|
+
):
|
|
361
|
+
| { status: "found"; key: string; session: TSession }
|
|
362
|
+
| { status: "missing" }
|
|
363
|
+
| { status: "ambiguous"; count: number } {
|
|
364
|
+
const normalizedHost = normalizeHostValue(host);
|
|
365
|
+
const scopedSelected = sessionFromSelectedKey(
|
|
366
|
+
config,
|
|
367
|
+
config.currentSessionKeys?.[scope.key],
|
|
368
|
+
normalizedHost,
|
|
369
|
+
);
|
|
370
|
+
if (scopedSelected) {
|
|
371
|
+
return {
|
|
372
|
+
status: "found",
|
|
373
|
+
key: scopedSelected.key,
|
|
374
|
+
session: scopedSelected.session,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (!isExplicitScope(scope)) {
|
|
379
|
+
const defaultSelected = sessionFromSelectedKey(
|
|
380
|
+
config,
|
|
381
|
+
config.currentSessionKey,
|
|
382
|
+
normalizedHost,
|
|
383
|
+
);
|
|
384
|
+
if (defaultSelected) {
|
|
385
|
+
return {
|
|
386
|
+
status: "found",
|
|
387
|
+
key: defaultSelected.key,
|
|
388
|
+
session: defaultSelected.session,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const exactKey = sessionConfigKey(normalizedHost, scope);
|
|
394
|
+
const exact = config.sessions?.[exactKey];
|
|
395
|
+
if (exact) return { status: "found", key: exactKey, session: exact };
|
|
396
|
+
|
|
397
|
+
const hostSessions = Object.entries(config.sessions ?? {}).filter(
|
|
398
|
+
([, session]) => normalizeHostValue(session.host) === normalizedHost,
|
|
399
|
+
);
|
|
400
|
+
const scopedHostSessions = hostSessions.filter(
|
|
401
|
+
([key]) => key !== normalizedHost,
|
|
402
|
+
);
|
|
403
|
+
const legacy = config.sessions?.[normalizedHost];
|
|
404
|
+
if (legacy && scopedHostSessions.length === 0) {
|
|
405
|
+
return { status: "found", key: normalizedHost, session: legacy };
|
|
406
|
+
}
|
|
407
|
+
if (hostSessions.length > 1) {
|
|
408
|
+
return { status: "ambiguous", count: hostSessions.length };
|
|
409
|
+
}
|
|
410
|
+
return { status: "missing" };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function removeStoredSession<TSession extends StoredSessionLike>(
|
|
414
|
+
config: ConfigWithSessions<TSession>,
|
|
415
|
+
host: string,
|
|
416
|
+
scope: SessionScope,
|
|
417
|
+
):
|
|
418
|
+
| {
|
|
419
|
+
status: "removed";
|
|
420
|
+
config: ConfigWithSessions<TSession>;
|
|
421
|
+
removedKey: string;
|
|
422
|
+
session: TSession;
|
|
423
|
+
}
|
|
424
|
+
| { status: "missing"; config: ConfigWithSessions<TSession> }
|
|
425
|
+
| {
|
|
426
|
+
status: "ambiguous";
|
|
427
|
+
count: number;
|
|
428
|
+
config: ConfigWithSessions<TSession>;
|
|
429
|
+
} {
|
|
430
|
+
const selected = selectStoredSessionEntry(config, host, scope);
|
|
431
|
+
if (selected.status !== "found") {
|
|
432
|
+
return { ...selected, config };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const normalizedHost = normalizeHostValue(host);
|
|
436
|
+
const sessions = { ...(config.sessions ?? {}) };
|
|
437
|
+
delete sessions[selected.key];
|
|
438
|
+
|
|
439
|
+
const currentHosts = { ...(config.currentHosts ?? {}) };
|
|
440
|
+
const currentSessionKeys = { ...(config.currentSessionKeys ?? {}) };
|
|
441
|
+
if (
|
|
442
|
+
currentHosts[scope.key] &&
|
|
443
|
+
normalizeHostValue(currentHosts[scope.key]) === normalizedHost
|
|
444
|
+
) {
|
|
445
|
+
delete currentHosts[scope.key];
|
|
446
|
+
}
|
|
447
|
+
for (const [scopeKey, sessionKey] of Object.entries(currentSessionKeys)) {
|
|
448
|
+
if (sessionKey === selected.key) {
|
|
449
|
+
delete currentSessionKeys[scopeKey];
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const nextConfig: ConfigWithSessions<TSession> = { ...config };
|
|
454
|
+
if (nextConfig.currentSessionKey === selected.key) {
|
|
455
|
+
delete nextConfig.currentSessionKey;
|
|
456
|
+
}
|
|
457
|
+
if (Object.keys(sessions).length > 0) {
|
|
458
|
+
nextConfig.sessions = sessions;
|
|
459
|
+
} else {
|
|
460
|
+
delete nextConfig.sessions;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (Object.keys(currentHosts).length > 0) {
|
|
464
|
+
nextConfig.currentHosts = currentHosts;
|
|
465
|
+
} else {
|
|
466
|
+
delete nextConfig.currentHosts;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (Object.keys(currentSessionKeys).length > 0) {
|
|
470
|
+
nextConfig.currentSessionKeys = currentSessionKeys;
|
|
471
|
+
} else {
|
|
472
|
+
delete nextConfig.currentSessionKeys;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const hasRemainingSessionForHost = Object.values(sessions).some(
|
|
476
|
+
(session) => normalizeHostValue(session.host) === normalizedHost,
|
|
477
|
+
);
|
|
478
|
+
if (
|
|
479
|
+
nextConfig.currentHost &&
|
|
480
|
+
normalizeHostValue(nextConfig.currentHost) === normalizedHost &&
|
|
481
|
+
!hasRemainingSessionForHost
|
|
482
|
+
) {
|
|
483
|
+
delete nextConfig.currentHost;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
status: "removed",
|
|
488
|
+
config: nextConfig,
|
|
489
|
+
removedKey: selected.key,
|
|
490
|
+
session: selected.session,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function removeAllStoredSessions<TSession extends StoredSessionLike>(
|
|
495
|
+
config: ConfigWithSessions<TSession>,
|
|
496
|
+
) {
|
|
497
|
+
const nextConfig: ConfigWithSessions<TSession> = { ...config };
|
|
498
|
+
delete nextConfig.currentHost;
|
|
499
|
+
delete nextConfig.currentSessionKey;
|
|
500
|
+
delete nextConfig.currentHosts;
|
|
501
|
+
delete nextConfig.currentSessionKeys;
|
|
502
|
+
delete nextConfig.sessions;
|
|
503
|
+
return nextConfig;
|
|
504
|
+
}
|