@teamclaw/feishu-agent 1.0.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 +178 -0
- package/package.json +49 -0
- package/src/cli/commands/auth.ts +309 -0
- package/src/cli/commands/calendar.ts +305 -0
- package/src/cli/commands/config.ts +48 -0
- package/src/cli/commands/contact.ts +90 -0
- package/src/cli/commands/init.ts +128 -0
- package/src/cli/commands/setup.ts +327 -0
- package/src/cli/commands/todo.ts +114 -0
- package/src/cli/commands/whoami.ts +63 -0
- package/src/cli/index.ts +68 -0
- package/src/core/auth-manager.ts +214 -0
- package/src/core/calendar.ts +276 -0
- package/src/core/client.ts +100 -0
- package/src/core/config.ts +174 -0
- package/src/core/contact.ts +83 -0
- package/src/core/introspection.ts +103 -0
- package/src/core/todo.ts +109 -0
- package/src/index.ts +76 -0
- package/src/types/index.ts +199 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
4
|
+
import { exec } from "node:child_process";
|
|
5
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
6
|
+
import { loadConfig, saveGlobalConfig } from "../../core/config";
|
|
7
|
+
import { FeishuClient } from "../../core/client";
|
|
8
|
+
import { IntrospectionEngine } from "../../core/introspection";
|
|
9
|
+
|
|
10
|
+
interface UserAccessTokenResponse {
|
|
11
|
+
code: number;
|
|
12
|
+
msg: string;
|
|
13
|
+
data?: {
|
|
14
|
+
access_token: string;
|
|
15
|
+
refresh_token: string;
|
|
16
|
+
token_type: string;
|
|
17
|
+
expires_in: number;
|
|
18
|
+
name: string;
|
|
19
|
+
en_name: string;
|
|
20
|
+
avatar: string;
|
|
21
|
+
user_id: string;
|
|
22
|
+
union_id: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function setupCommand() {
|
|
27
|
+
console.log("\n========================================");
|
|
28
|
+
console.log(" Feishu Agent Setup");
|
|
29
|
+
console.log("========================================\n");
|
|
30
|
+
|
|
31
|
+
// Step 1: Get App ID and Secret
|
|
32
|
+
console.log("Step 1: Feishu App Credentials");
|
|
33
|
+
console.log("-".repeat(40));
|
|
34
|
+
|
|
35
|
+
let config = await loadConfig();
|
|
36
|
+
let appId = config.appId;
|
|
37
|
+
let appSecret = config.appSecret;
|
|
38
|
+
|
|
39
|
+
if (!appId) {
|
|
40
|
+
const input = prompt("Enter Feishu App ID:");
|
|
41
|
+
if (!input) {
|
|
42
|
+
console.error("Error: App ID is required.");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
appId = input;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!appSecret) {
|
|
49
|
+
const input = prompt("Enter Feishu App Secret:");
|
|
50
|
+
if (!input) {
|
|
51
|
+
console.error("Error: App Secret is required.");
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
appSecret = input;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Save app credentials
|
|
58
|
+
await saveGlobalConfig({ appId, appSecret });
|
|
59
|
+
await ensureEnvFile(appId, appSecret);
|
|
60
|
+
console.log("App credentials saved.\n");
|
|
61
|
+
|
|
62
|
+
// Step 2: OAuth 2.0 Authorization
|
|
63
|
+
console.log("Step 2: OAuth 2.0 Authorization");
|
|
64
|
+
console.log("-".repeat(40));
|
|
65
|
+
console.log("This will open a browser to authorize with Feishu.");
|
|
66
|
+
console.log("The authorization will grant access to:");
|
|
67
|
+
console.log(" - Your calendar");
|
|
68
|
+
console.log(" - Your events");
|
|
69
|
+
console.log("");
|
|
70
|
+
|
|
71
|
+
const port = 3000;
|
|
72
|
+
const state = generateRandomString(32);
|
|
73
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
74
|
+
const encodedRedirectUri = encodeURIComponent(redirectUri);
|
|
75
|
+
const authUrl = `https://open.feishu.cn/open-apis/authen/v1/index?app_id=${appId}&redirect_uri=${encodedRedirectUri}&state=${state}`;
|
|
76
|
+
|
|
77
|
+
console.log("Redirect URI (must be configured in Feishu Console):");
|
|
78
|
+
console.log(` ${redirectUri}\n`);
|
|
79
|
+
console.log("Opening authorization URL...");
|
|
80
|
+
console.log("");
|
|
81
|
+
|
|
82
|
+
// Start local server and open browser
|
|
83
|
+
let authCode: string | null = null;
|
|
84
|
+
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
85
|
+
if (req.url?.startsWith("/callback")) {
|
|
86
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
87
|
+
const code = url.searchParams.get("code");
|
|
88
|
+
const receivedState = url.searchParams.get("state");
|
|
89
|
+
|
|
90
|
+
if (!code) {
|
|
91
|
+
res.writeHead(400);
|
|
92
|
+
res.end("No code received");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (receivedState !== state) {
|
|
97
|
+
res.writeHead(400);
|
|
98
|
+
res.end("State mismatch");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
authCode = code;
|
|
103
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
104
|
+
res.end(`
|
|
105
|
+
<html>
|
|
106
|
+
<head><title>Authorization Successful</title></head>
|
|
107
|
+
<body>
|
|
108
|
+
<h1>Authorization Successful!</h1>
|
|
109
|
+
<p>You can close this window and return to the terminal.</p>
|
|
110
|
+
<script>setTimeout(() => window.close(), 3000);</script>
|
|
111
|
+
</body>
|
|
112
|
+
</html>
|
|
113
|
+
`);
|
|
114
|
+
} else {
|
|
115
|
+
res.writeHead(404);
|
|
116
|
+
res.end("Not found");
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await new Promise<void>((resolve) => {
|
|
121
|
+
server.listen(port, () => {
|
|
122
|
+
openBrowser(authUrl);
|
|
123
|
+
resolve();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Wait for callback with timeout
|
|
128
|
+
const timeout = 5 * 60 * 1000;
|
|
129
|
+
const startTime = Date.now();
|
|
130
|
+
|
|
131
|
+
const waitForAuth = (): Promise<string> => {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
const interval = setInterval(() => {
|
|
134
|
+
if (authCode) {
|
|
135
|
+
clearInterval(interval);
|
|
136
|
+
resolve(authCode);
|
|
137
|
+
}
|
|
138
|
+
if (Date.now() - startTime > timeout) {
|
|
139
|
+
clearInterval(interval);
|
|
140
|
+
reject(new Error("Authorization timeout"));
|
|
141
|
+
}
|
|
142
|
+
}, 500);
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
await waitForAuth();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error("Authorization timeout. Please run setup again.");
|
|
150
|
+
server.close();
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log("Authorization code received, exchanging for access token...");
|
|
155
|
+
|
|
156
|
+
// Exchange code for token
|
|
157
|
+
const tokenResponse = await exchangeCodeForToken(appId, appSecret, authCode!, redirectUri);
|
|
158
|
+
|
|
159
|
+
if (tokenResponse.code !== 0 || !tokenResponse.data) {
|
|
160
|
+
console.error("Failed to get access token:", tokenResponse.msg);
|
|
161
|
+
server.close();
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { access_token, refresh_token, user_id, name } = tokenResponse.data;
|
|
166
|
+
|
|
167
|
+
console.log("Access token received!\n");
|
|
168
|
+
console.log(` User ID: ${user_id}`);
|
|
169
|
+
console.log(` Name: ${name}`);
|
|
170
|
+
console.log("");
|
|
171
|
+
|
|
172
|
+
// Save tokens to local config
|
|
173
|
+
await mkdir(".feishu_agent", { recursive: true });
|
|
174
|
+
await writeFile(".feishu_agent/config.json", JSON.stringify({
|
|
175
|
+
appId,
|
|
176
|
+
appSecret,
|
|
177
|
+
userAccessToken: access_token,
|
|
178
|
+
refreshToken: refresh_token,
|
|
179
|
+
userId: user_id,
|
|
180
|
+
}, null, 2));
|
|
181
|
+
|
|
182
|
+
console.log("Configuration saved to .feishu_agent/config.json\n");
|
|
183
|
+
|
|
184
|
+
// Step 3: Get Base Token for Bitable
|
|
185
|
+
console.log("Step 3: Feishu Bitable (多维表格)");
|
|
186
|
+
console.log("-".repeat(40));
|
|
187
|
+
|
|
188
|
+
let baseToken: string | null = null;
|
|
189
|
+
while (!baseToken) {
|
|
190
|
+
const input = prompt("Enter Feishu Base URL (or Base Token): ");
|
|
191
|
+
if (!input) {
|
|
192
|
+
console.error("Error: Base Token is required.");
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
baseToken = extractBaseToken(input);
|
|
196
|
+
if (!baseToken) {
|
|
197
|
+
console.error("Invalid input. Could not extract Base Token.");
|
|
198
|
+
console.log("Example: https://xxx.feishu.cn/base/basexxxxxx");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(`Detected Base Token: ${baseToken}`);
|
|
203
|
+
console.log("Fetching schema...");
|
|
204
|
+
|
|
205
|
+
const client = new FeishuClient({ appId, appSecret, userAccessToken: access_token });
|
|
206
|
+
const engine = new IntrospectionEngine(client);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const schema = await engine.introspect(baseToken, (msg) => console.log(msg));
|
|
210
|
+
await writeFile(".feishu_agent/schema.json", JSON.stringify(schema, null, 2));
|
|
211
|
+
console.log("Schema saved to .feishu_agent/schema.json\n");
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error("Failed to fetch schema:", error instanceof Error ? error.message : String(error));
|
|
214
|
+
// Continue anyway - schema is optional
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
server.close();
|
|
218
|
+
|
|
219
|
+
console.log("========================================");
|
|
220
|
+
console.log(" Setup Complete!");
|
|
221
|
+
console.log("========================================\n");
|
|
222
|
+
console.log("You can now use the following commands:");
|
|
223
|
+
console.log(" feishu-agent calendar list - List your calendars");
|
|
224
|
+
console.log(" feishu-agent calendar event list - List events in a calendar");
|
|
225
|
+
console.log(" feishu-agent todo list - List todos from Bitable");
|
|
226
|
+
console.log(" feishu-agent contact list - List contacts");
|
|
227
|
+
console.log("");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function generateRandomString(length: number): string {
|
|
231
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
232
|
+
let result = "";
|
|
233
|
+
for (let i = 0; i < length; i++) {
|
|
234
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function openBrowser(url: string): void {
|
|
240
|
+
const platform = process.platform;
|
|
241
|
+
let cmd: string;
|
|
242
|
+
|
|
243
|
+
switch (platform) {
|
|
244
|
+
case "win32":
|
|
245
|
+
cmd = `start ${url}`;
|
|
246
|
+
break;
|
|
247
|
+
case "darwin":
|
|
248
|
+
cmd = `open "${url}"`;
|
|
249
|
+
break;
|
|
250
|
+
default:
|
|
251
|
+
cmd = `xdg-open "${url}"`;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
exec(cmd, (error) => {
|
|
256
|
+
if (error) {
|
|
257
|
+
console.log("Could not auto-open browser. Please manually visit the URL:");
|
|
258
|
+
console.log(url);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function exchangeCodeForToken(
|
|
264
|
+
appId: string,
|
|
265
|
+
appSecret: string,
|
|
266
|
+
code: string,
|
|
267
|
+
redirectUri: string
|
|
268
|
+
): Promise<UserAccessTokenResponse> {
|
|
269
|
+
// 使用飞书身份认证 v1 API
|
|
270
|
+
// https://open.feishu.cn/document/ukTMukTMukTM/ukDMz4SMxQjL0MjN
|
|
271
|
+
const response = await fetch("https://open.feishu.cn/open-apis/authen/v1/access_token", {
|
|
272
|
+
method: "POST",
|
|
273
|
+
headers: { "Content-Type": "application/json" },
|
|
274
|
+
body: JSON.stringify({
|
|
275
|
+
grant_type: "authorization_code",
|
|
276
|
+
code: code,
|
|
277
|
+
app_id: appId,
|
|
278
|
+
app_secret: appSecret,
|
|
279
|
+
}),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const text = await response.text();
|
|
283
|
+
|
|
284
|
+
// 调试输出:查看原始响应
|
|
285
|
+
console.log("Token exchange response status:", response.status);
|
|
286
|
+
if (response.status !== 200 || text.includes("error")) {
|
|
287
|
+
console.log("Response body:", text);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 处理空响应
|
|
291
|
+
if (!text || text.trim() === "") {
|
|
292
|
+
throw new Error("Empty response from Feishu API");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
const data = JSON.parse(text);
|
|
297
|
+
// 如果返回错误,打印详细信息
|
|
298
|
+
if (data.code !== 0) {
|
|
299
|
+
throw new Error(`Feishu API error: ${data.msg || data.message || JSON.stringify(data)}`);
|
|
300
|
+
}
|
|
301
|
+
return data;
|
|
302
|
+
} catch (parseError) {
|
|
303
|
+
throw new Error(`Failed to parse JSON response: ${text.substring(0, 200)}. Error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function extractBaseToken(input: string): string | null {
|
|
308
|
+
const urlMatch = input.match(/\/base\/([a-zA-Z0-9]+)/);
|
|
309
|
+
if (urlMatch) {
|
|
310
|
+
return urlMatch[1];
|
|
311
|
+
}
|
|
312
|
+
if (/^(base|app)[a-zA-Z0-9]+$/.test(input)) {
|
|
313
|
+
return input;
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function ensureEnvFile(appId: string, appSecret: string): Promise<void> {
|
|
319
|
+
const envFile = Bun.file(".env");
|
|
320
|
+
if (!(await envFile.exists())) {
|
|
321
|
+
const envContent = `FEISHU_APP_ID=${appId}\nFEISHU_APP_SECRET=${appSecret}\n`;
|
|
322
|
+
await writeFile(".env", envContent);
|
|
323
|
+
console.log("Created .env file with credentials.");
|
|
324
|
+
} else {
|
|
325
|
+
console.log(".env file already exists.");
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { FeishuClient } from "../../core/client";
|
|
3
|
+
import { TodoManager } from "../../core/todo";
|
|
4
|
+
import { FeishuConfig } from "../../types";
|
|
5
|
+
|
|
6
|
+
interface TodoOptions {
|
|
7
|
+
title?: string;
|
|
8
|
+
priority?: string;
|
|
9
|
+
recordId?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createTodoCommands(program: Command, config: FeishuConfig) {
|
|
13
|
+
program
|
|
14
|
+
.command("list")
|
|
15
|
+
.description("List all todos from Bitable")
|
|
16
|
+
.action(async () => {
|
|
17
|
+
await handleList(config);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command("create")
|
|
22
|
+
.description("Create a new todo")
|
|
23
|
+
.requiredOption("--title <string>", "Todo title")
|
|
24
|
+
.option("--priority <string>", "Priority (High, Medium, Low)", "Medium")
|
|
25
|
+
.action(async (options: TodoOptions) => {
|
|
26
|
+
await handleCreate(config, options);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.command("done")
|
|
31
|
+
.description("Mark a todo as done")
|
|
32
|
+
.requiredOption("--record-id <string>", "Record ID")
|
|
33
|
+
.action(async (options: TodoOptions) => {
|
|
34
|
+
await handleDone(config, options);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function handleList(config: FeishuConfig) {
|
|
39
|
+
if (!config.appId || !config.appSecret) {
|
|
40
|
+
console.error("Error: Credentials required.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const baseToken = process.env.FEISHU_BASE_TOKEN;
|
|
45
|
+
if (!baseToken) {
|
|
46
|
+
console.error("Error: FEISHU_BASE_TOKEN required.");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const client = new FeishuClient(config);
|
|
51
|
+
const todoManager = new TodoManager(client, baseToken);
|
|
52
|
+
|
|
53
|
+
console.log(`Fetching todos from Base: ${baseToken}`);
|
|
54
|
+
const todos = await todoManager.listTodos();
|
|
55
|
+
|
|
56
|
+
if (todos.length > 0) {
|
|
57
|
+
console.table(todos.map((t: any) => ({
|
|
58
|
+
id: t.record_id,
|
|
59
|
+
title: t.fields.Title,
|
|
60
|
+
done: t.fields.Done,
|
|
61
|
+
priority: t.fields.Priority || "N/A"
|
|
62
|
+
})));
|
|
63
|
+
} else {
|
|
64
|
+
console.log("No todos found.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function handleCreate(config: FeishuConfig, options: TodoOptions) {
|
|
69
|
+
if (!config.appId || !config.appSecret) {
|
|
70
|
+
console.error("Error: Credentials required.");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const baseToken = process.env.FEISHU_BASE_TOKEN;
|
|
75
|
+
if (!baseToken) {
|
|
76
|
+
console.error("Error: FEISHU_BASE_TOKEN required.");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!options?.title) {
|
|
81
|
+
console.error("Error: --title is required.");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const client = new FeishuClient(config);
|
|
86
|
+
const todoManager = new TodoManager(client, baseToken);
|
|
87
|
+
|
|
88
|
+
const todo = await todoManager.createTodo(options.title, options.priority || "Medium");
|
|
89
|
+
console.log(`Todo created successfully! ID: ${todo.record_id}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function handleDone(config: FeishuConfig, options: TodoOptions) {
|
|
93
|
+
if (!config.appId || !config.appSecret) {
|
|
94
|
+
console.error("Error: Credentials required.");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const baseToken = process.env.FEISHU_BASE_TOKEN;
|
|
99
|
+
if (!baseToken) {
|
|
100
|
+
console.error("Error: FEISHU_BASE_TOKEN required.");
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!options?.recordId) {
|
|
105
|
+
console.error("Error: --record-id is required.");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const client = new FeishuClient(config);
|
|
110
|
+
const todoManager = new TodoManager(client, baseToken);
|
|
111
|
+
|
|
112
|
+
await todoManager.markDone(options.recordId);
|
|
113
|
+
console.log(`Todo ${options.recordId} marked as done.`);
|
|
114
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { FeishuClient } from "../../core/client";
|
|
3
|
+
import { loadConfig } from "../../core/config";
|
|
4
|
+
|
|
5
|
+
export async function whoamiCommand() {
|
|
6
|
+
console.log("\n=== Feishu Agent - Who Am I ===\n");
|
|
7
|
+
|
|
8
|
+
const config = await loadConfig();
|
|
9
|
+
|
|
10
|
+
if (!config.appId || !config.appSecret) {
|
|
11
|
+
console.error("Error: App credentials not configured.");
|
|
12
|
+
console.error("Run 'feishu-agent setup' to configure credentials.");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const client = new FeishuClient({
|
|
17
|
+
appId: config.appId,
|
|
18
|
+
appSecret: config.appSecret,
|
|
19
|
+
userAccessToken: config.userAccessToken,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Check if user has authorized
|
|
23
|
+
if (!client.hasUserToken()) {
|
|
24
|
+
console.log("App credentials:");
|
|
25
|
+
console.log(` App ID: ${config.appId}`);
|
|
26
|
+
console.log("");
|
|
27
|
+
console.log("User authorization: NOT CONFIGURED");
|
|
28
|
+
console.log("");
|
|
29
|
+
console.log("Run 'feishu-agent auth' to authorize with your Feishu account.");
|
|
30
|
+
console.log("This is required for calendar and other user-level operations.");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Get user info
|
|
35
|
+
try {
|
|
36
|
+
const user = await client.getCurrentUser();
|
|
37
|
+
|
|
38
|
+
if (user) {
|
|
39
|
+
console.log("App credentials:");
|
|
40
|
+
console.log(` App ID: ${config.appId}`);
|
|
41
|
+
console.log("");
|
|
42
|
+
console.log("User authorization: CONFIGURED");
|
|
43
|
+
console.log("");
|
|
44
|
+
console.log("Current user:");
|
|
45
|
+
console.log(` Name: ${user.name}`);
|
|
46
|
+
console.log(` User ID: ${user.user_id}`);
|
|
47
|
+
console.log("");
|
|
48
|
+
console.log("You can use calendar and other user-level commands.");
|
|
49
|
+
} else {
|
|
50
|
+
console.log("App credentials: CONFIGURED");
|
|
51
|
+
console.log("User authorization: CONFIGURED (but unable to fetch user info)");
|
|
52
|
+
console.log("");
|
|
53
|
+
console.log("Note: Token might be expired. Run 'feishu-agent auth' to re-authorize.");
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.log("App credentials: CONFIGURED");
|
|
57
|
+
console.log("User authorization: CONFIGURED (but verification failed)");
|
|
58
|
+
console.log("");
|
|
59
|
+
console.log(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log("Try running 'feishu-agent auth' to re-authorize.");
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { setupCommand } from "./commands/setup";
|
|
4
|
+
import { authCommand } from "./commands/auth";
|
|
5
|
+
import { whoamiCommand } from "./commands/whoami";
|
|
6
|
+
import { loadConfig } from "../core/config";
|
|
7
|
+
import { createConfigCommands } from "./commands/config";
|
|
8
|
+
import { createCalendarCommands } from "./commands/calendar";
|
|
9
|
+
import { createTodoCommands } from "./commands/todo";
|
|
10
|
+
import { createContactCommands } from "./commands/contact";
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name("feishu-agent")
|
|
17
|
+
.description("Feishu Agent CLI for AI assistants")
|
|
18
|
+
.version("1.0.0");
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command("setup")
|
|
22
|
+
.description("Initialize configuration")
|
|
23
|
+
.action(setupCommand);
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.command("auth")
|
|
27
|
+
.description("Authenticate with Feishu OAuth")
|
|
28
|
+
.action(authCommand);
|
|
29
|
+
|
|
30
|
+
program
|
|
31
|
+
.command("whoami")
|
|
32
|
+
.description("Show current user info")
|
|
33
|
+
.action(whoamiCommand);
|
|
34
|
+
|
|
35
|
+
// Load config for commands that need it
|
|
36
|
+
const config = await loadConfig();
|
|
37
|
+
|
|
38
|
+
// Config subcommand group
|
|
39
|
+
const configCmd = program
|
|
40
|
+
.command("config")
|
|
41
|
+
.description("Manage configuration");
|
|
42
|
+
createConfigCommands(configCmd);
|
|
43
|
+
|
|
44
|
+
// Calendar subcommand group
|
|
45
|
+
const calendarCmd = program
|
|
46
|
+
.command("calendar")
|
|
47
|
+
.description("Manage calendar events");
|
|
48
|
+
createCalendarCommands(calendarCmd, config);
|
|
49
|
+
|
|
50
|
+
// Todo subcommand group
|
|
51
|
+
const todoCmd = program
|
|
52
|
+
.command("todo")
|
|
53
|
+
.description("Manage todos");
|
|
54
|
+
createTodoCommands(todoCmd, config);
|
|
55
|
+
|
|
56
|
+
// Contact subcommand group
|
|
57
|
+
const contactCmd = program
|
|
58
|
+
.command("contact")
|
|
59
|
+
.description("Manage contacts");
|
|
60
|
+
createContactCommands(contactCmd, config);
|
|
61
|
+
|
|
62
|
+
await program.parseAsync();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main().catch((err) => {
|
|
66
|
+
console.error("Error:", err.message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
});
|