feishu-user-plugin 1.3.5 → 1.3.7
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/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +22 -0
- package/README.md +66 -40
- package/package.json +10 -3
- package/scripts/check-tool-count.js +15 -0
- package/scripts/check-version.js +40 -0
- package/scripts/smoke.js +224 -0
- package/scripts/sync-claude-md.sh +12 -0
- package/scripts/sync-team-skills.sh +22 -0
- package/scripts/test-all-tools.js +158 -0
- package/skills/feishu-user-plugin/SKILL.md +5 -5
- package/skills/feishu-user-plugin/references/CLAUDE.md +152 -96
- package/skills/feishu-user-plugin/references/table.md +18 -9
- package/src/auth/credentials.js +350 -0
- package/src/cli.js +42 -13
- package/src/clients/official/base.js +424 -0
- package/src/clients/official/bitable.js +269 -0
- package/src/clients/official/calendar.js +176 -0
- package/src/clients/official/contacts.js +54 -0
- package/src/clients/official/docs.js +301 -0
- package/src/clients/official/drive.js +77 -0
- package/src/clients/official/groups.js +68 -0
- package/src/clients/official/im.js +414 -0
- package/src/clients/official/index.js +30 -0
- package/src/clients/official/okr.js +127 -0
- package/src/clients/official/tasks.js +142 -0
- package/src/clients/official/uploads.js +260 -0
- package/src/clients/official/wiki.js +207 -0
- package/src/{client.js → clients/user.js} +23 -17
- package/src/doc-blocks.js +20 -5
- package/src/index.js +4 -1744
- package/src/logger.js +20 -0
- package/src/oauth.js +8 -1
- package/src/official.js +5 -1734
- package/src/prompts/_registry.js +69 -0
- package/src/prompts/index.js +54 -0
- package/src/server.js +242 -0
- package/src/test-all.js +2 -2
- package/src/test-comprehensive.js +3 -3
- package/src/test-send.js +1 -1
- package/src/tools/_registry.js +30 -0
- package/src/tools/bitable.js +246 -0
- package/src/tools/calendar.js +207 -0
- package/src/tools/contacts.js +66 -0
- package/src/tools/diagnostics.js +172 -0
- package/src/tools/docs.js +158 -0
- package/src/tools/drive.js +111 -0
- package/src/tools/groups.js +81 -0
- package/src/tools/im-read.js +259 -0
- package/src/tools/messaging-bot.js +151 -0
- package/src/tools/messaging-user.js +292 -0
- package/src/tools/okr.js +159 -0
- package/src/tools/profile.js +43 -0
- package/src/tools/tasks.js +168 -0
- package/src/tools/uploads.js +63 -0
- package/src/tools/wiki.js +191 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// src/auth/credentials.js — single-source-of-truth credentials API.
|
|
2
|
+
//
|
|
3
|
+
// Reads from `~/.feishu-user-plugin/credentials.json` (created by the `migrate`
|
|
4
|
+
// CLI subcommand, schema documented at docs/CREDENTIALS-FORMAT.md). Falls back
|
|
5
|
+
// to legacy MCP-config discovery (src/config) when the file is absent so v1.3.6
|
|
6
|
+
// users have zero behaviour change until they opt in.
|
|
7
|
+
//
|
|
8
|
+
// What this owns:
|
|
9
|
+
// - credentials.json read / write (atomic, 0600 perms)
|
|
10
|
+
// - profile lookup for the running MCP server
|
|
11
|
+
// - persistence target for cookie heartbeat + UAT refresh
|
|
12
|
+
//
|
|
13
|
+
// What it does NOT own:
|
|
14
|
+
// - Profile switching mechanics (lives in src/server.js — this module just
|
|
15
|
+
// exposes `setActiveProfile` for the handler to call).
|
|
16
|
+
// - Cookie heartbeat (still lives in src/clients/user.js, calls
|
|
17
|
+
// `persistToConfig` here).
|
|
18
|
+
// - UAT refresh + cross-process file lock (still lives in
|
|
19
|
+
// src/clients/official/base.js, calls `readCredentials` + `persistToConfig`
|
|
20
|
+
// here). Plan to extract into src/auth/{cookie,uat}.js once stable.
|
|
21
|
+
//
|
|
22
|
+
// Public API (stable for callers):
|
|
23
|
+
// - readCredentials() → flat env block of the active profile (back-compat
|
|
24
|
+
// drop-in for src/config::readCredentials)
|
|
25
|
+
// - persistToConfig(updates) → writes the updates onto the active profile's
|
|
26
|
+
// env block; falls back to legacy mcpServers persistence when no
|
|
27
|
+
// credentials.json exists (back-compat drop-in)
|
|
28
|
+
// - readCanonical() → full {version, active, profiles, profileHints} object,
|
|
29
|
+
// or null if no credentials.json yet
|
|
30
|
+
// - getActiveProfileEnv(name?) → env block for a named profile (defaults to
|
|
31
|
+
// the active one), with legacy LARK_PROFILES_JSON / process.env fallback
|
|
32
|
+
// - getActiveProfileName() → string
|
|
33
|
+
// - listProfileNames() → string[] (always includes "default")
|
|
34
|
+
// - setActiveProfile(name) → atomic write of the `active` field
|
|
35
|
+
// - migrate({ dryRun }) → CLI helper; reads legacy config and writes
|
|
36
|
+
// credentials.json
|
|
37
|
+
//
|
|
38
|
+
// Re-exports for callers still on the legacy-only paths:
|
|
39
|
+
// - findMcpConfig, writeNewConfig, SERVER_NAMES (from src/config)
|
|
40
|
+
|
|
41
|
+
const fs = require('fs');
|
|
42
|
+
const os = require('os');
|
|
43
|
+
const path = require('path');
|
|
44
|
+
|
|
45
|
+
const legacy = require('../config');
|
|
46
|
+
|
|
47
|
+
// --- Constants ---
|
|
48
|
+
|
|
49
|
+
const SCHEMA_VERSION = 1;
|
|
50
|
+
const ENV_KEYS = [
|
|
51
|
+
'LARK_COOKIE',
|
|
52
|
+
'LARK_APP_ID',
|
|
53
|
+
'LARK_APP_SECRET',
|
|
54
|
+
'LARK_USER_ACCESS_TOKEN',
|
|
55
|
+
'LARK_USER_REFRESH_TOKEN',
|
|
56
|
+
'LARK_UAT_EXPIRES',
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// --- Path resolution ---
|
|
60
|
+
|
|
61
|
+
function _credentialsDir() {
|
|
62
|
+
return path.join(os.homedir(), '.feishu-user-plugin');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function _credentialsPath() {
|
|
66
|
+
return path.join(_credentialsDir(), 'credentials.json');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// --- Atomic file IO ---
|
|
70
|
+
|
|
71
|
+
function _atomicWriteJson(filePath, obj) {
|
|
72
|
+
const dir = path.dirname(filePath);
|
|
73
|
+
try { fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); } catch (_) {}
|
|
74
|
+
// chmod the dir if it pre-existed with looser perms
|
|
75
|
+
try { fs.chmodSync(dir, 0o700); } catch (_) {}
|
|
76
|
+
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
77
|
+
fs.writeFileSync(tmpPath, JSON.stringify(obj, null, 2) + '\n', { mode: 0o600 });
|
|
78
|
+
fs.renameSync(tmpPath, filePath);
|
|
79
|
+
// chmod again post-rename in case of umask interference
|
|
80
|
+
try { fs.chmodSync(filePath, 0o600); } catch (_) {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function _readFile() {
|
|
84
|
+
try {
|
|
85
|
+
const raw = fs.readFileSync(_credentialsPath(), 'utf8');
|
|
86
|
+
const parsed = JSON.parse(raw);
|
|
87
|
+
if (typeof parsed !== 'object' || parsed === null) return null;
|
|
88
|
+
if (parsed.version !== SCHEMA_VERSION) {
|
|
89
|
+
console.error(`[feishu-user-plugin] credentials.json schema version ${parsed.version} unsupported (expected ${SCHEMA_VERSION}). Ignoring file, falling back to legacy config.`);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (!parsed.profiles || typeof parsed.profiles !== 'object') return null;
|
|
93
|
+
if (typeof parsed.active !== 'string' || !parsed.profiles[parsed.active]) {
|
|
94
|
+
console.error(`[feishu-user-plugin] credentials.json has invalid active profile "${parsed.active}". Ignoring.`);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (!parsed.profileHints) parsed.profileHints = {};
|
|
98
|
+
return parsed;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
if (e.code !== 'ENOENT') {
|
|
101
|
+
console.error(`[feishu-user-plugin] credentials.json read failed: ${e.message}. Falling back to legacy config.`);
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --- Public API ---
|
|
108
|
+
|
|
109
|
+
function readCanonical() {
|
|
110
|
+
return _readFile();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getActiveProfileName() {
|
|
114
|
+
const f = _readFile();
|
|
115
|
+
return f ? f.active : 'default';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function listProfileNames() {
|
|
119
|
+
const f = _readFile();
|
|
120
|
+
if (f) return Object.keys(f.profiles);
|
|
121
|
+
// Legacy: default + LARK_PROFILES_JSON keys
|
|
122
|
+
let extras = [];
|
|
123
|
+
try {
|
|
124
|
+
const raw = process.env.LARK_PROFILES_JSON;
|
|
125
|
+
if (raw) extras = Object.keys(JSON.parse(raw) || {});
|
|
126
|
+
} catch (_) {}
|
|
127
|
+
return ['default', ...extras];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getActiveProfileEnv(name) {
|
|
131
|
+
const f = _readFile();
|
|
132
|
+
const target = name || (f ? f.active : 'default');
|
|
133
|
+
|
|
134
|
+
if (f) {
|
|
135
|
+
const profile = f.profiles[target];
|
|
136
|
+
if (!profile) {
|
|
137
|
+
throw new Error(`Profile "${target}" not found in credentials.json. Available: ${Object.keys(f.profiles).join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
return _normalizeEnv(profile);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Legacy paths: default reads process.env directly; named profiles come from LARK_PROFILES_JSON.
|
|
143
|
+
if (target === 'default') {
|
|
144
|
+
const env = {};
|
|
145
|
+
for (const k of ENV_KEYS) if (process.env[k] !== undefined) env[k] = process.env[k];
|
|
146
|
+
return env;
|
|
147
|
+
}
|
|
148
|
+
let map = {};
|
|
149
|
+
try {
|
|
150
|
+
const raw = process.env.LARK_PROFILES_JSON;
|
|
151
|
+
if (raw) map = JSON.parse(raw) || {};
|
|
152
|
+
} catch (e) {
|
|
153
|
+
throw new Error(`LARK_PROFILES_JSON parse failed: ${e.message}`);
|
|
154
|
+
}
|
|
155
|
+
const profile = map[target];
|
|
156
|
+
if (!profile) {
|
|
157
|
+
throw new Error(`Profile "${target}" not found. Available: ${['default', ...Object.keys(map)].join(', ')}`);
|
|
158
|
+
}
|
|
159
|
+
return _normalizeEnv(profile);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Coerce numeric LARK_UAT_EXPIRES → string so it round-trips through env-var
|
|
163
|
+
// callers (process.env always returns strings).
|
|
164
|
+
function _normalizeEnv(profile) {
|
|
165
|
+
const out = {};
|
|
166
|
+
for (const k of ENV_KEYS) {
|
|
167
|
+
if (profile[k] === undefined || profile[k] === null) continue;
|
|
168
|
+
out[k] = typeof profile[k] === 'number' ? String(profile[k]) : profile[k];
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function setActiveProfile(name) {
|
|
174
|
+
const f = _readFile();
|
|
175
|
+
if (!f) {
|
|
176
|
+
throw new Error('No credentials.json — run `npx feishu-user-plugin migrate --confirm` to create one.');
|
|
177
|
+
}
|
|
178
|
+
if (!f.profiles[name]) {
|
|
179
|
+
throw new Error(`Profile "${name}" not found in credentials.json. Available: ${Object.keys(f.profiles).join(', ')}`);
|
|
180
|
+
}
|
|
181
|
+
f.active = name;
|
|
182
|
+
_atomicWriteJson(_credentialsPath(), f);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function persistProfileUpdate(profileName, updates) {
|
|
186
|
+
const f = _readFile();
|
|
187
|
+
if (!f) return false;
|
|
188
|
+
if (!f.profiles[profileName]) {
|
|
189
|
+
console.error(`[feishu-user-plugin] persistProfileUpdate: profile "${profileName}" not found in credentials.json`);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
// LARK_UAT_EXPIRES sometimes comes through as string; preserve number when possible.
|
|
193
|
+
const normalized = {};
|
|
194
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
195
|
+
if (v === undefined || v === null) continue;
|
|
196
|
+
if (k === 'LARK_UAT_EXPIRES' && typeof v === 'string') {
|
|
197
|
+
const n = parseInt(v, 10);
|
|
198
|
+
normalized[k] = Number.isFinite(n) ? n : v;
|
|
199
|
+
} else {
|
|
200
|
+
normalized[k] = v;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
Object.assign(f.profiles[profileName], normalized);
|
|
204
|
+
_atomicWriteJson(_credentialsPath(), f);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Back-compat drop-in for src/config::readCredentials. Resolution order:
|
|
209
|
+
// 1. credentials.json (active profile)
|
|
210
|
+
// 2. process.env.LARK_* (MCP-server context — harness injects env at spawn)
|
|
211
|
+
// 3. legacy mcpServers discovery via src/config (CLI context where the
|
|
212
|
+
// caller process did not get the env block)
|
|
213
|
+
// The order matters: smoke.js + the MCP server want the in-process env to
|
|
214
|
+
// win over disk discovery (so the diff baseline matches the spawn env).
|
|
215
|
+
// CLI commands like `status` and `keepalive` have no env, so they fall
|
|
216
|
+
// through to the legacy reader.
|
|
217
|
+
function readCredentials() {
|
|
218
|
+
const f = _readFile();
|
|
219
|
+
if (f) {
|
|
220
|
+
return _normalizeEnv(f.profiles[f.active]);
|
|
221
|
+
}
|
|
222
|
+
const env = {};
|
|
223
|
+
for (const k of ENV_KEYS) if (process.env[k] !== undefined) env[k] = process.env[k];
|
|
224
|
+
if (Object.keys(env).length > 0) return env;
|
|
225
|
+
return legacy.readCredentials();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Back-compat drop-in for src/config::persistToConfig. Routes writes to:
|
|
229
|
+
// - credentials.json (active profile) when the file exists
|
|
230
|
+
// - legacy mcpServers env block otherwise
|
|
231
|
+
function persistToConfig(updates) {
|
|
232
|
+
const f = _readFile();
|
|
233
|
+
if (f) {
|
|
234
|
+
return persistProfileUpdate(f.active, updates);
|
|
235
|
+
}
|
|
236
|
+
return legacy.persistToConfig(updates);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// --- Migration (called by `npx feishu-user-plugin migrate`) ---
|
|
240
|
+
|
|
241
|
+
function migrate({ dryRun = true } = {}) {
|
|
242
|
+
const filePath = _credentialsPath();
|
|
243
|
+
const existing = _readFile();
|
|
244
|
+
if (existing) {
|
|
245
|
+
console.log(`credentials.json already exists at ${filePath}`);
|
|
246
|
+
console.log(`active profile: ${existing.active}`);
|
|
247
|
+
console.log(`profiles: ${Object.keys(existing.profiles).join(', ')}`);
|
|
248
|
+
console.log('');
|
|
249
|
+
console.log('No migration needed. To re-create from harness configs, delete the file first:');
|
|
250
|
+
console.log(` rm ${filePath}`);
|
|
251
|
+
return { ok: true, alreadyMigrated: true };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Discover legacy creds
|
|
255
|
+
const found = legacy.findMcpConfig();
|
|
256
|
+
if (!found) {
|
|
257
|
+
console.error('No MCP config found. Run `npx feishu-user-plugin setup` first.');
|
|
258
|
+
return { ok: false, reason: 'no-config' };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const defaultProfile = {};
|
|
262
|
+
for (const k of ENV_KEYS) {
|
|
263
|
+
if (found.serverEnv[k] !== undefined && found.serverEnv[k] !== null) {
|
|
264
|
+
defaultProfile[k] = k === 'LARK_UAT_EXPIRES' ? parseInt(found.serverEnv[k], 10) || 0 : found.serverEnv[k];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Merge LARK_PROFILES_JSON if present
|
|
269
|
+
const profiles = { default: defaultProfile };
|
|
270
|
+
const rawExtras = found.serverEnv.LARK_PROFILES_JSON;
|
|
271
|
+
if (rawExtras) {
|
|
272
|
+
try {
|
|
273
|
+
const parsed = JSON.parse(rawExtras);
|
|
274
|
+
for (const [name, env] of Object.entries(parsed)) {
|
|
275
|
+
if (name === 'default') {
|
|
276
|
+
console.error(`[migrate] Skipping LARK_PROFILES_JSON entry "default" (collision with primary profile).`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const cleaned = {};
|
|
280
|
+
for (const k of ENV_KEYS) {
|
|
281
|
+
if (env[k] !== undefined && env[k] !== null) {
|
|
282
|
+
cleaned[k] = k === 'LARK_UAT_EXPIRES' ? parseInt(env[k], 10) || 0 : env[k];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
profiles[name] = cleaned;
|
|
286
|
+
}
|
|
287
|
+
} catch (e) {
|
|
288
|
+
console.error(`[migrate] LARK_PROFILES_JSON parse failed: ${e.message}. Skipping extra profiles.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const credentials = {
|
|
293
|
+
version: SCHEMA_VERSION,
|
|
294
|
+
active: 'default',
|
|
295
|
+
profiles,
|
|
296
|
+
profileHints: {},
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
console.log(`Source: ${found.configPath}${found.projectPath ? ` (project: ${found.projectPath})` : ''}`);
|
|
300
|
+
console.log(`Target: ${filePath}`);
|
|
301
|
+
console.log(`Profiles found: ${Object.keys(profiles).join(', ')}`);
|
|
302
|
+
console.log('');
|
|
303
|
+
for (const [name, env] of Object.entries(profiles)) {
|
|
304
|
+
console.log(` [${name}]`);
|
|
305
|
+
for (const k of ENV_KEYS) {
|
|
306
|
+
if (env[k] === undefined) continue;
|
|
307
|
+
const display = k.includes('SECRET') || k.includes('TOKEN') || k.includes('COOKIE')
|
|
308
|
+
? `${String(env[k]).slice(0, 12)}…(${String(env[k]).length} chars)`
|
|
309
|
+
: env[k];
|
|
310
|
+
console.log(` ${k}: ${display}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
console.log('');
|
|
314
|
+
|
|
315
|
+
if (dryRun) {
|
|
316
|
+
console.log('Dry run — no file written. Re-run with `--confirm` to persist.');
|
|
317
|
+
return { ok: true, dryRun: true, credentials };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
_atomicWriteJson(filePath, credentials);
|
|
321
|
+
console.log(`✓ Wrote ${filePath} (mode 0600)`);
|
|
322
|
+
console.log('');
|
|
323
|
+
console.log('Next steps:');
|
|
324
|
+
console.log(' 1. Restart Claude Code / Codex so the MCP server adopts the new credentials source.');
|
|
325
|
+
console.log(' 2. Existing harness env blocks remain untouched as a fallback.');
|
|
326
|
+
console.log(' 3. To start fresh: delete the file and re-run migrate.');
|
|
327
|
+
return { ok: true, credentials };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// --- Re-exports for back-compat ---
|
|
331
|
+
|
|
332
|
+
module.exports = {
|
|
333
|
+
// canonical API
|
|
334
|
+
readCanonical,
|
|
335
|
+
getActiveProfileName,
|
|
336
|
+
listProfileNames,
|
|
337
|
+
getActiveProfileEnv,
|
|
338
|
+
setActiveProfile,
|
|
339
|
+
persistProfileUpdate,
|
|
340
|
+
migrate,
|
|
341
|
+
// back-compat with src/config
|
|
342
|
+
readCredentials,
|
|
343
|
+
persistToConfig,
|
|
344
|
+
findMcpConfig: legacy.findMcpConfig,
|
|
345
|
+
writeNewConfig: legacy.writeNewConfig,
|
|
346
|
+
SERVER_NAMES: legacy.SERVER_NAMES,
|
|
347
|
+
// constants
|
|
348
|
+
SCHEMA_VERSION,
|
|
349
|
+
ENV_KEYS,
|
|
350
|
+
};
|
package/src/cli.js
CHANGED
|
@@ -25,6 +25,17 @@ switch (cmd) {
|
|
|
25
25
|
case 'keepalive':
|
|
26
26
|
keepalive();
|
|
27
27
|
break;
|
|
28
|
+
case 'list-prompts': {
|
|
29
|
+
const { listPrompts } = require('./prompts');
|
|
30
|
+
for (const p of listPrompts()) {
|
|
31
|
+
console.log(`/${p.name} — ${p.description}`);
|
|
32
|
+
for (const a of (p.arguments || [])) console.log(` - ${a.name}${a.required ? ' (required)' : ''}: ${a.description}`);
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case 'migrate':
|
|
37
|
+
migrate();
|
|
38
|
+
break;
|
|
28
39
|
case 'help':
|
|
29
40
|
case '--help':
|
|
30
41
|
case '-h':
|
|
@@ -46,6 +57,9 @@ Commands:
|
|
|
46
57
|
oauth Run OAuth flow to obtain user_access_token
|
|
47
58
|
status Check authentication status
|
|
48
59
|
keepalive Refresh cookie + UAT to prevent expiration (for cron jobs)
|
|
60
|
+
migrate One-time consolidation: copy creds from harness configs into
|
|
61
|
+
~/.feishu-user-plugin/credentials.json (single source of truth).
|
|
62
|
+
Dry-run by default. Add --confirm to actually write.
|
|
49
63
|
help Show this help
|
|
50
64
|
|
|
51
65
|
Setup options:
|
|
@@ -70,17 +84,23 @@ Auto-renewal (optional):
|
|
|
70
84
|
`);
|
|
71
85
|
}
|
|
72
86
|
|
|
87
|
+
function migrate() {
|
|
88
|
+
const { migrate: runMigrate } = require('./auth/credentials');
|
|
89
|
+
const confirm = process.argv.includes('--confirm');
|
|
90
|
+
const result = runMigrate({ dryRun: !confirm });
|
|
91
|
+
process.exit(result.ok ? 0 : 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
async function keepalive() {
|
|
74
|
-
const { LarkUserClient } = require('./
|
|
75
|
-
const { LarkOfficialClient } = require('./official');
|
|
76
|
-
const {
|
|
95
|
+
const { LarkUserClient } = require('./clients/user');
|
|
96
|
+
const { LarkOfficialClient } = require('./clients/official');
|
|
97
|
+
const { readCredentials, persistToConfig } = require('./auth/credentials');
|
|
77
98
|
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
80
|
-
console.error('[keepalive] No
|
|
99
|
+
const creds = readCredentials();
|
|
100
|
+
if (!creds.LARK_COOKIE && !creds.LARK_APP_ID) {
|
|
101
|
+
console.error('[keepalive] No credentials found. Run: npx feishu-user-plugin setup');
|
|
81
102
|
process.exit(1);
|
|
82
103
|
}
|
|
83
|
-
const creds = found.serverEnv;
|
|
84
104
|
let ok = true;
|
|
85
105
|
|
|
86
106
|
// 1. Refresh Cookie
|
|
@@ -124,18 +144,27 @@ async function keepalive() {
|
|
|
124
144
|
}
|
|
125
145
|
|
|
126
146
|
async function checkStatus() {
|
|
127
|
-
const { LarkUserClient } = require('./
|
|
128
|
-
const { LarkOfficialClient } = require('./official');
|
|
147
|
+
const { LarkUserClient } = require('./clients/user');
|
|
148
|
+
const { LarkOfficialClient } = require('./clients/official');
|
|
129
149
|
const { findMcpConfig } = require('./config');
|
|
150
|
+
const { readCanonical, getActiveProfileName, listProfileNames, readCredentials } = require('./auth/credentials');
|
|
130
151
|
|
|
152
|
+
const canonical = readCanonical();
|
|
131
153
|
const found = findMcpConfig();
|
|
132
|
-
const creds =
|
|
154
|
+
const creds = readCredentials();
|
|
133
155
|
|
|
134
156
|
console.log('=== feishu-user-plugin Auth Status ===\n');
|
|
135
|
-
if (
|
|
136
|
-
|
|
157
|
+
if (canonical) {
|
|
158
|
+
const path = require('path');
|
|
159
|
+
const os = require('os');
|
|
160
|
+
console.log(`Source: ${path.join(os.homedir(), '.feishu-user-plugin', 'credentials.json')} (canonical)`);
|
|
161
|
+
console.log(`Active profile: ${getActiveProfileName()}`);
|
|
162
|
+
console.log(`Available profiles: ${listProfileNames().join(', ')}`);
|
|
163
|
+
} else if (found) {
|
|
164
|
+
console.log(`Source: ${found.configPath}${found.projectPath ? ` (project: ${found.projectPath})` : ''} (legacy)`);
|
|
165
|
+
console.log('Tip: run `npx feishu-user-plugin migrate --confirm` to consolidate creds into ~/.feishu-user-plugin/credentials.json.');
|
|
137
166
|
} else {
|
|
138
|
-
console.log('
|
|
167
|
+
console.log('Source: NOT FOUND (run: npx feishu-user-plugin setup)');
|
|
139
168
|
}
|
|
140
169
|
console.log('');
|
|
141
170
|
|