skills-script 1.1.2 → 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.md +3 -0
- package/package.json +1 -1
- package/src/commands/interactive.js +7 -1
- package/src/commands/switch-auth.js +154 -0
- package/src/commands/switch-setting.js +3 -5
- package/src/lib/claude-auth.js +265 -0
package/README.md
CHANGED
|
@@ -29,8 +29,10 @@ npx skills-script
|
|
|
29
29
|
1. 工具选择:
|
|
30
30
|
- `skills-sync`
|
|
31
31
|
- `claude-settings-switch`
|
|
32
|
+
- `claude-auth-switch`
|
|
32
33
|
2. 进入对应工具流程并执行。
|
|
33
34
|
3. `claude-settings-switch` 固定操作 `~/.claude`(例如 `/Users/youzi/.claude`)。
|
|
35
|
+
4. `claude-auth-switch` 管理官方登录授权账号的备份与切换(Keychain + `~/.claude.json`),账号档案隔离存放在 `~/.claude/auth-accounts/`。
|
|
34
36
|
|
|
35
37
|
## 非交互模式
|
|
36
38
|
|
|
@@ -44,6 +46,7 @@ npx skills-script sync claude,codex,.aaa
|
|
|
44
46
|
|
|
45
47
|
- `docs/tools/skills-sync.md`
|
|
46
48
|
- `docs/tools/claude-settings-switch.md`
|
|
49
|
+
- `docs/tools/claude-auth-switch.md`
|
|
47
50
|
|
|
48
51
|
## 发布
|
|
49
52
|
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { multiSelect, promptInput, selectOne } from '../lib/interactive.js';
|
|
3
3
|
import { resolveTargetDirs, runSyncByTargets } from './sync.js';
|
|
4
4
|
import { runClaudeSettingSwitch } from './switch-setting.js';
|
|
5
|
+
import { runClaudeAuthSwitch } from './switch-auth.js';
|
|
5
6
|
|
|
6
7
|
function parseCustomInput(input) {
|
|
7
8
|
if (!input) return [];
|
|
@@ -16,7 +17,8 @@ export async function runInteractive(cwd = process.cwd()) {
|
|
|
16
17
|
title: 'Select a tool',
|
|
17
18
|
options: [
|
|
18
19
|
{ label: 'skills-sync', value: 'skills-sync' },
|
|
19
|
-
{ label: 'claude-settings-switch', value: 'claude-settings-switch' }
|
|
20
|
+
{ label: 'claude-settings-switch', value: 'claude-settings-switch' },
|
|
21
|
+
{ label: 'claude-auth-switch', value: 'claude-auth-switch' }
|
|
20
22
|
]
|
|
21
23
|
});
|
|
22
24
|
|
|
@@ -24,6 +26,10 @@ export async function runInteractive(cwd = process.cwd()) {
|
|
|
24
26
|
return runClaudeSettingSwitch();
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
if (tool === 'claude-auth-switch') {
|
|
30
|
+
return runClaudeAuthSwitch();
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
if (tool !== 'skills-sync') {
|
|
28
34
|
console.error('[ERROR] Unsupported tool selected.');
|
|
29
35
|
return 1;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { selectOne } from '../lib/interactive.js';
|
|
2
|
+
import {
|
|
3
|
+
applyAccountState,
|
|
4
|
+
deleteProfile,
|
|
5
|
+
getCurrentAccount,
|
|
6
|
+
listProfiles,
|
|
7
|
+
loadProfile,
|
|
8
|
+
saveProfile,
|
|
9
|
+
writeClaudeJson,
|
|
10
|
+
writeCredentials
|
|
11
|
+
} from '../lib/claude-auth.js';
|
|
12
|
+
|
|
13
|
+
function formatProfileLabel(profile, currentSlug) {
|
|
14
|
+
const parts = [profile.email];
|
|
15
|
+
if (profile.slug === currentSlug) parts.push('(current)');
|
|
16
|
+
if (profile.corrupted) parts.push('(corrupted)');
|
|
17
|
+
if (profile.savedAt) parts.push(`saved ${profile.savedAt}`);
|
|
18
|
+
return parts.join(' ');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function backupCurrentAccount(current) {
|
|
22
|
+
const filePath = await saveProfile({
|
|
23
|
+
slug: current.slug,
|
|
24
|
+
email: current.email,
|
|
25
|
+
credentials: current.credentials,
|
|
26
|
+
claudeJson: current.claudeJson
|
|
27
|
+
});
|
|
28
|
+
console.log(`[OK] backup: ${filePath}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function runSaveCurrent(current) {
|
|
32
|
+
if (!current.loggedIn) {
|
|
33
|
+
console.error('[ERROR] No active login found (credentials or oauthAccount missing).');
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
await backupCurrentAccount(current);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function runSwitch(current) {
|
|
41
|
+
const profiles = (await listProfiles()).filter((p) => !p.corrupted);
|
|
42
|
+
const candidates = profiles.filter((p) => p.slug !== current.slug);
|
|
43
|
+
|
|
44
|
+
if (!candidates.length) {
|
|
45
|
+
console.error('[ERROR] No other saved account to switch to.');
|
|
46
|
+
console.error(' Save the current account first, then log in with another account and run this tool again.');
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const targetSlug = await selectOne({
|
|
51
|
+
title: `Switch Claude auth account (current: ${current.email || 'not logged in'})`,
|
|
52
|
+
options: candidates.map((p) => ({
|
|
53
|
+
label: formatProfileLabel(p, current.slug),
|
|
54
|
+
value: p.slug
|
|
55
|
+
}))
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const target = await loadProfile(targetSlug);
|
|
59
|
+
|
|
60
|
+
if (current.loggedIn) {
|
|
61
|
+
await backupCurrentAccount(current);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await writeCredentials(target.credentials);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`[ERROR] Failed to write credentials: ${error?.message || error}`);
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const nextClaudeJson = applyAccountState(current.claudeJson, target.accountState || {});
|
|
73
|
+
await writeClaudeJson(nextClaudeJson);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Roll credentials back so keychain and ~/.claude.json stay consistent.
|
|
76
|
+
if (current.credentials) {
|
|
77
|
+
try {
|
|
78
|
+
await writeCredentials(current.credentials);
|
|
79
|
+
} catch {
|
|
80
|
+
console.error('[ERROR] Rollback of credentials also failed. Re-login may be required.');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
console.error(`[ERROR] Failed to update ~/.claude.json: ${error?.message || error}`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(`[OK] active account: ${target.email || targetSlug}`);
|
|
88
|
+
console.log('[WARN] Restart running Claude Code sessions to pick up the new account.');
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function runDelete(current) {
|
|
93
|
+
const profiles = await listProfiles();
|
|
94
|
+
if (!profiles.length) {
|
|
95
|
+
console.error('[ERROR] No saved account profile found.');
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const targetSlug = await selectOne({
|
|
100
|
+
title: 'Delete a saved auth account profile',
|
|
101
|
+
options: profiles.map((p) => ({
|
|
102
|
+
label: formatProfileLabel(p, current.slug),
|
|
103
|
+
value: p.slug
|
|
104
|
+
}))
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const confirmed = await selectOne({
|
|
108
|
+
title: `Delete profile "${targetSlug}"? The saved tokens will be removed permanently.`,
|
|
109
|
+
options: [
|
|
110
|
+
{ label: 'No, keep it', value: false },
|
|
111
|
+
{ label: 'Yes, delete', value: true }
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!confirmed) {
|
|
116
|
+
console.log('[INFO] Canceled.');
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const filePath = await deleteProfile(targetSlug);
|
|
121
|
+
console.log(`[OK] deleted: ${filePath}`);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function runClaudeAuthSwitch() {
|
|
126
|
+
let current;
|
|
127
|
+
try {
|
|
128
|
+
current = await getCurrentAccount();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(`[ERROR] Failed to read current auth state: ${error?.message || error}`);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const action = await selectOne({
|
|
135
|
+
title: `Claude auth account (current: ${current.email || 'not logged in'})`,
|
|
136
|
+
options: [
|
|
137
|
+
{ label: 'Switch account', value: 'switch' },
|
|
138
|
+
{ label: 'Backup current account', value: 'save' },
|
|
139
|
+
{ label: 'Delete saved profile', value: 'delete' }
|
|
140
|
+
]
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
if (action === 'switch') return await runSwitch(current);
|
|
145
|
+
if (action === 'save') return await runSaveCurrent(current);
|
|
146
|
+
if (action === 'delete') return await runDelete(current);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error(`[ERROR] ${error?.message || error}`);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.error('[ERROR] Unsupported action.');
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
@@ -116,11 +116,9 @@ export async function runClaudeSettingSwitch() {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const baseUrlValue = findAnthropicBaseUrlValue(activeJson);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return 1;
|
|
123
|
-
}
|
|
119
|
+
// Without ANTHROPIC_BASE_URL the active settings use the official login,
|
|
120
|
+
// so back it up as the "auth" variant.
|
|
121
|
+
const baseUrlName = normalizeBaseUrlName(baseUrlValue) || 'auth';
|
|
124
122
|
|
|
125
123
|
const backupPath = await getUniqueBackupPath(claudeDir, `settings.${baseUrlName}.json`);
|
|
126
124
|
const selectedPath = path.join(claudeDir, selected);
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
const KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
8
|
+
|
|
9
|
+
// Fields in ~/.claude.json that belong to the logged-in account. They are
|
|
10
|
+
// swapped together with the credentials so quota/subscription/model-access
|
|
11
|
+
// state never leaks from one account into another.
|
|
12
|
+
export const ACCOUNT_SCOPED_KEYS = [
|
|
13
|
+
'oauthAccount',
|
|
14
|
+
'userID',
|
|
15
|
+
'hasAvailableSubscription',
|
|
16
|
+
'subscriptionNoticeCount',
|
|
17
|
+
'cachedExtraUsageDisabledReason',
|
|
18
|
+
'modelAccessCache',
|
|
19
|
+
's1mAccessCache',
|
|
20
|
+
'passesEligibilityCache',
|
|
21
|
+
'orgModelDefaultCache',
|
|
22
|
+
'additionalModelCostsCache',
|
|
23
|
+
'additionalModelOptionsCache',
|
|
24
|
+
'clientDataCacheSlots'
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export function getClaudeDir() {
|
|
28
|
+
return path.join(os.homedir(), '.claude');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getAuthStoreDir() {
|
|
32
|
+
return path.join(getClaudeDir(), 'auth-accounts');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getClaudeJsonPath() {
|
|
36
|
+
return path.join(os.homedir(), '.claude.json');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isDarwin() {
|
|
40
|
+
return process.platform === 'darwin';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getCredentialsFilePath() {
|
|
44
|
+
return path.join(getClaudeDir(), '.credentials.json');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function runSecurity(args, { stdinText } = {}) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
const child = spawn('security', args, {
|
|
50
|
+
stdio: [stdinText === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe']
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let stdout = '';
|
|
54
|
+
let stderr = '';
|
|
55
|
+
child.stdout.on('data', (chunk) => {
|
|
56
|
+
stdout += chunk;
|
|
57
|
+
});
|
|
58
|
+
child.stderr.on('data', (chunk) => {
|
|
59
|
+
stderr += chunk;
|
|
60
|
+
});
|
|
61
|
+
child.on('error', (error) => {
|
|
62
|
+
resolve({ code: -1, stdout, stderr: String(error?.message || error) });
|
|
63
|
+
});
|
|
64
|
+
child.on('close', (code) => {
|
|
65
|
+
resolve({ code: code ?? -1, stdout, stderr });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (stdinText !== undefined) {
|
|
69
|
+
child.stdin.end(stdinText);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function quoteForSecurityShell(value) {
|
|
75
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function readKeychainCredentials() {
|
|
79
|
+
const result = await runSecurity([
|
|
80
|
+
'find-generic-password',
|
|
81
|
+
'-s',
|
|
82
|
+
KEYCHAIN_SERVICE,
|
|
83
|
+
'-w'
|
|
84
|
+
]);
|
|
85
|
+
if (result.code !== 0) return null;
|
|
86
|
+
const raw = result.stdout.replace(/\n$/, '');
|
|
87
|
+
return raw || null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Writes through `security -i` (commands over stdin) so the token never
|
|
91
|
+
// appears in the process argument list.
|
|
92
|
+
async function writeKeychainCredentials(raw) {
|
|
93
|
+
const command = [
|
|
94
|
+
'add-generic-password',
|
|
95
|
+
'-U',
|
|
96
|
+
'-s',
|
|
97
|
+
quoteForSecurityShell(KEYCHAIN_SERVICE),
|
|
98
|
+
'-a',
|
|
99
|
+
quoteForSecurityShell(os.userInfo().username),
|
|
100
|
+
'-w',
|
|
101
|
+
quoteForSecurityShell(raw)
|
|
102
|
+
].join(' ');
|
|
103
|
+
|
|
104
|
+
const result = await runSecurity(['-i'], { stdinText: `${command}\n` });
|
|
105
|
+
if (result.code !== 0) {
|
|
106
|
+
throw new Error(`Keychain write failed: ${result.stderr.trim() || `exit ${result.code}`}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function readCredentials() {
|
|
111
|
+
if (isDarwin()) {
|
|
112
|
+
return readKeychainCredentials();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
return await fs.readFile(getCredentialsFilePath(), 'utf8');
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function writeCredentials(raw) {
|
|
123
|
+
if (isDarwin()) {
|
|
124
|
+
await writeKeychainCredentials(raw);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await writeFileAtomic(getCredentialsFilePath(), raw, 0o600);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function writeFileAtomic(filePath, content, mode) {
|
|
132
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
133
|
+
await fs.writeFile(tmpPath, content, { mode });
|
|
134
|
+
await fs.rename(tmpPath, filePath);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function readClaudeJson() {
|
|
138
|
+
const raw = await fs.readFile(getClaudeJsonPath(), 'utf8');
|
|
139
|
+
return JSON.parse(raw);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function writeClaudeJson(json) {
|
|
143
|
+
await writeFileAtomic(getClaudeJsonPath(), JSON.stringify(json, null, 2), 0o600);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function extractAccountState(claudeJson) {
|
|
147
|
+
const state = {};
|
|
148
|
+
for (const key of ACCOUNT_SCOPED_KEYS) {
|
|
149
|
+
if (Object.prototype.hasOwnProperty.call(claudeJson, key)) {
|
|
150
|
+
state[key] = claudeJson[key];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return state;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function applyAccountState(claudeJson, state) {
|
|
157
|
+
const next = { ...claudeJson };
|
|
158
|
+
for (const key of ACCOUNT_SCOPED_KEYS) {
|
|
159
|
+
if (Object.prototype.hasOwnProperty.call(state, key)) {
|
|
160
|
+
next[key] = state[key];
|
|
161
|
+
} else {
|
|
162
|
+
delete next[key];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return next;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function normalizeAccountSlug(email) {
|
|
169
|
+
if (typeof email !== 'string') return null;
|
|
170
|
+
const cleaned = email
|
|
171
|
+
.trim()
|
|
172
|
+
.toLowerCase()
|
|
173
|
+
.replace(/[^a-z0-9.-]+/g, '-')
|
|
174
|
+
.replace(/-+/g, '-')
|
|
175
|
+
.replace(/^-+|-+$/g, '');
|
|
176
|
+
return cleaned || null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function profileFileName(slug) {
|
|
180
|
+
return `auth.${slug}.json`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function ensureStoreDir() {
|
|
184
|
+
const dir = getAuthStoreDir();
|
|
185
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
186
|
+
// mkdir does not change the mode of a pre-existing directory.
|
|
187
|
+
await fs.chmod(dir, 0o700);
|
|
188
|
+
return dir;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export async function getCurrentAccount() {
|
|
192
|
+
const [credentials, claudeJson] = await Promise.all([
|
|
193
|
+
readCredentials(),
|
|
194
|
+
readClaudeJson()
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
const email = claudeJson?.oauthAccount?.emailAddress || null;
|
|
198
|
+
return {
|
|
199
|
+
credentials,
|
|
200
|
+
claudeJson,
|
|
201
|
+
email,
|
|
202
|
+
slug: normalizeAccountSlug(email),
|
|
203
|
+
loggedIn: Boolean(credentials && email)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function saveProfile({ slug, email, credentials, claudeJson }) {
|
|
208
|
+
const dir = await ensureStoreDir();
|
|
209
|
+
const filePath = path.join(dir, profileFileName(slug));
|
|
210
|
+
const profile = {
|
|
211
|
+
version: 1,
|
|
212
|
+
email,
|
|
213
|
+
savedAt: new Date().toISOString(),
|
|
214
|
+
credentials,
|
|
215
|
+
accountState: extractAccountState(claudeJson)
|
|
216
|
+
};
|
|
217
|
+
await writeFileAtomic(filePath, JSON.stringify(profile, null, 2), 0o600);
|
|
218
|
+
await fs.chmod(filePath, 0o600);
|
|
219
|
+
return filePath;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function listProfiles() {
|
|
223
|
+
let entries;
|
|
224
|
+
try {
|
|
225
|
+
entries = await fs.readdir(getAuthStoreDir(), { withFileTypes: true });
|
|
226
|
+
} catch {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const profiles = [];
|
|
231
|
+
for (const entry of entries) {
|
|
232
|
+
const match = entry.isFile() && entry.name.match(/^auth\.(.+)\.json$/);
|
|
233
|
+
if (!match) continue;
|
|
234
|
+
|
|
235
|
+
const filePath = path.join(getAuthStoreDir(), entry.name);
|
|
236
|
+
try {
|
|
237
|
+
const profile = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
238
|
+
profiles.push({
|
|
239
|
+
slug: match[1],
|
|
240
|
+
email: profile.email || match[1],
|
|
241
|
+
savedAt: profile.savedAt || null,
|
|
242
|
+
filePath
|
|
243
|
+
});
|
|
244
|
+
} catch {
|
|
245
|
+
profiles.push({ slug: match[1], email: match[1], savedAt: null, filePath, corrupted: true });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return profiles.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function loadProfile(slug) {
|
|
253
|
+
const filePath = path.join(getAuthStoreDir(), profileFileName(slug));
|
|
254
|
+
const profile = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
255
|
+
if (!profile || typeof profile !== 'object' || !profile.credentials) {
|
|
256
|
+
throw new Error(`Profile is missing credentials: ${filePath}`);
|
|
257
|
+
}
|
|
258
|
+
return profile;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export async function deleteProfile(slug) {
|
|
262
|
+
const filePath = path.join(getAuthStoreDir(), profileFileName(slug));
|
|
263
|
+
await fs.rm(filePath);
|
|
264
|
+
return filePath;
|
|
265
|
+
}
|