@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,174 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { mkdir } from "node:fs/promises";
|
|
4
|
+
|
|
5
|
+
export interface ContactCacheEntry {
|
|
6
|
+
name: string;
|
|
7
|
+
email?: string;
|
|
8
|
+
user_id?: string; // Also store user_id for lookup
|
|
9
|
+
union_id?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FeishuConfig {
|
|
13
|
+
appId: string;
|
|
14
|
+
appSecret: string;
|
|
15
|
+
userAccessToken?: string;
|
|
16
|
+
refreshToken?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the path to the global configuration file.
|
|
21
|
+
*/
|
|
22
|
+
export function getConfigPath(): string {
|
|
23
|
+
return join(homedir(), ".feishu-agent", "config.json");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns the path to the contact cache file.
|
|
28
|
+
*/
|
|
29
|
+
export function getContactCachePath(): string {
|
|
30
|
+
return join(homedir(), ".feishu-agent", "contact-cache.json");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Loads configuration from global config file.
|
|
35
|
+
*/
|
|
36
|
+
export async function loadConfig(): Promise<FeishuConfig> {
|
|
37
|
+
const configPath = getConfigPath();
|
|
38
|
+
let config: Partial<FeishuConfig> = {};
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const file = Bun.file(configPath);
|
|
42
|
+
if (await file.exists()) {
|
|
43
|
+
config = await file.json();
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// Ignore errors
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
appId: config.appId || "",
|
|
51
|
+
appSecret: config.appSecret || "",
|
|
52
|
+
userAccessToken: config.userAccessToken,
|
|
53
|
+
refreshToken: config.refreshToken,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Saves the provided configuration to the global configuration file.
|
|
59
|
+
*/
|
|
60
|
+
export async function saveGlobalConfig(config: Partial<FeishuConfig>): Promise<void> {
|
|
61
|
+
const configPath = getConfigPath();
|
|
62
|
+
const configDir = join(homedir(), ".feishu-agent");
|
|
63
|
+
|
|
64
|
+
let currentConfig: Partial<FeishuConfig> = {};
|
|
65
|
+
try {
|
|
66
|
+
const file = Bun.file(configPath);
|
|
67
|
+
if (await file.exists()) {
|
|
68
|
+
currentConfig = await file.json();
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// Ignore errors
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const newConfig = { ...currentConfig, ...config };
|
|
75
|
+
|
|
76
|
+
await mkdir(configDir, { recursive: true });
|
|
77
|
+
await Bun.write(configPath, JSON.stringify(newConfig, null, 2));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Saves a contact entry to the contact cache.
|
|
82
|
+
* Key is union_id, value is name, email, and open_id.
|
|
83
|
+
*/
|
|
84
|
+
export async function saveContactToCache(unionId: string, entry: ContactCacheEntry): Promise<void> {
|
|
85
|
+
const cachePath = getContactCachePath();
|
|
86
|
+
const cacheDir = join(homedir(), ".feishu-agent");
|
|
87
|
+
|
|
88
|
+
let contactCache: Record<string, ContactCacheEntry> = {};
|
|
89
|
+
try {
|
|
90
|
+
const file = Bun.file(cachePath);
|
|
91
|
+
if (await file.exists()) {
|
|
92
|
+
contactCache = await file.json();
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// Ignore errors
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
contactCache[unionId] = entry;
|
|
99
|
+
|
|
100
|
+
await mkdir(cacheDir, { recursive: true });
|
|
101
|
+
await Bun.write(cachePath, JSON.stringify(contactCache, null, 2));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Loads all cached contacts.
|
|
106
|
+
*/
|
|
107
|
+
export async function loadContactCache(): Promise<Record<string, ContactCacheEntry>> {
|
|
108
|
+
const cachePath = getContactCachePath();
|
|
109
|
+
try {
|
|
110
|
+
const file = Bun.file(cachePath);
|
|
111
|
+
if (await file.exists()) {
|
|
112
|
+
return await file.json();
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// Ignore errors
|
|
116
|
+
}
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Searches cached contacts by name or email.
|
|
122
|
+
*/
|
|
123
|
+
export async function searchContactCache(query: string): Promise<{ union_id: string; user_id?: string; name: string; email?: string }[]> {
|
|
124
|
+
const cache = await loadContactCache();
|
|
125
|
+
|
|
126
|
+
if (!query) return [];
|
|
127
|
+
|
|
128
|
+
const lowerQuery = query.toLowerCase();
|
|
129
|
+
|
|
130
|
+
const results: { union_id: string; user_id?: string; name: string; email?: string }[] = [];
|
|
131
|
+
for (const [unionId, entry] of Object.entries(cache)) {
|
|
132
|
+
if (
|
|
133
|
+
entry.name.toLowerCase().includes(lowerQuery) ||
|
|
134
|
+
(entry.email && entry.email.toLowerCase() === lowerQuery)
|
|
135
|
+
) {
|
|
136
|
+
results.push({ union_id: unionId, user_id: entry.user_id, name: entry.name, email: entry.email });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Gets a contact from cache by union_id or user_id.
|
|
144
|
+
*/
|
|
145
|
+
export async function getContactFromCache(id: string): Promise<ContactCacheEntry | undefined> {
|
|
146
|
+
const cache = await loadContactCache();
|
|
147
|
+
// Direct lookup by union_id
|
|
148
|
+
if (cache[id]) return cache[id];
|
|
149
|
+
// Lookup by user_id
|
|
150
|
+
for (const [unionId, entry] of Object.entries(cache)) {
|
|
151
|
+
if (entry.user_id === id) return entry;
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get union_id by user_id or vice versa
|
|
158
|
+
*/
|
|
159
|
+
export async function resolveContactId(id: string): Promise<{ union_id: string; user_id?: string } | undefined> {
|
|
160
|
+
const cache = await loadContactCache();
|
|
161
|
+
// If id looks like union_id (starts with on_), return it directly
|
|
162
|
+
if (id.startsWith('on_')) {
|
|
163
|
+
const entry = cache[id];
|
|
164
|
+
if (entry) return { union_id: id, user_id: entry.user_id };
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
// Otherwise, search for user_id
|
|
168
|
+
for (const [unionId, entry] of Object.entries(cache)) {
|
|
169
|
+
if (entry.user_id === id) {
|
|
170
|
+
return { union_id: unionId, user_id: id };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { FeishuClient } from "./client";
|
|
2
|
+
import { ListUsersResponse, UserInfo } from "../types";
|
|
3
|
+
import { saveContactToCache, searchContactCache } from "./config";
|
|
4
|
+
|
|
5
|
+
export class ContactManager {
|
|
6
|
+
private client: FeishuClient;
|
|
7
|
+
|
|
8
|
+
constructor(client: FeishuClient) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Search users by name or email.
|
|
14
|
+
* First checks cache, then falls back to API if no matches found.
|
|
15
|
+
*/
|
|
16
|
+
async searchUser(query: string): Promise<UserInfo[]> {
|
|
17
|
+
if (!query) return [];
|
|
18
|
+
|
|
19
|
+
// 1. Try cache first
|
|
20
|
+
const cachedResults = await searchContactCache(query);
|
|
21
|
+
if (cachedResults.length > 0) {
|
|
22
|
+
console.log(`Found ${cachedResults.length} contact(s) in cache for "${query}"`);
|
|
23
|
+
return cachedResults.map(c => ({
|
|
24
|
+
user_id: c.user_id,
|
|
25
|
+
union_id: c.union_id,
|
|
26
|
+
name: c.name,
|
|
27
|
+
email: c.email,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Fall back to API
|
|
32
|
+
console.log(`No cache matches for "${query}", searching Feishu API...`);
|
|
33
|
+
|
|
34
|
+
// List users from root department with union_id
|
|
35
|
+
const users = await this.listUsers("0");
|
|
36
|
+
|
|
37
|
+
// Filter by name or email
|
|
38
|
+
const lowerQuery = query.toLowerCase();
|
|
39
|
+
const results = users.filter(u =>
|
|
40
|
+
u.name.toLowerCase().includes(lowerQuery) ||
|
|
41
|
+
(u.en_name && u.en_name.toLowerCase().includes(lowerQuery)) ||
|
|
42
|
+
(u.email && u.email.toLowerCase().includes(lowerQuery))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Cache the results (key = union_id)
|
|
46
|
+
for (const user of results) {
|
|
47
|
+
if (user.union_id && user.name) {
|
|
48
|
+
await saveContactToCache(user.union_id, {
|
|
49
|
+
name: user.name,
|
|
50
|
+
email: user.email,
|
|
51
|
+
user_id: user.user_id,
|
|
52
|
+
open_id: user.open_id,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (results.length > 0) {
|
|
58
|
+
console.log(`Found ${results.length} contact(s), cached for future use.`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* List users in a specific department
|
|
66
|
+
*/
|
|
67
|
+
async listUsers(departmentId: string = "0", pageSize: number = 50, pageToken?: string): Promise<UserInfo[]> {
|
|
68
|
+
const params: Record<string, string> = {
|
|
69
|
+
department_id: departmentId,
|
|
70
|
+
page_size: pageSize.toString(),
|
|
71
|
+
user_id_type: "union_id", // Request union_id for calendar attendee compatibility
|
|
72
|
+
};
|
|
73
|
+
if (pageToken) params.page_token = pageToken;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const res = await this.client.get<ListUsersResponse["data"]>("/open-apis/contact/v3/users", params);
|
|
77
|
+
return res.items || [];
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.warn(`Failed to list users for department ${departmentId}:`, error);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { FeishuClient } from "./client";
|
|
2
|
+
import { ListTablesResponse, ListFieldsResponse, FeishuTable, FeishuField } from "../types";
|
|
3
|
+
|
|
4
|
+
export interface Schema {
|
|
5
|
+
baseToken: string;
|
|
6
|
+
tables: TableSchema[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TableSchema {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
fields: FeishuField[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class IntrospectionEngine {
|
|
16
|
+
private client: FeishuClient;
|
|
17
|
+
|
|
18
|
+
constructor(client: FeishuClient) {
|
|
19
|
+
this.client = client;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async introspect(baseToken: string, onProgress?: (msg: string) => void): Promise<Schema> {
|
|
23
|
+
const tables = await this.listTables(baseToken, onProgress);
|
|
24
|
+
|
|
25
|
+
const tableSchemas: TableSchema[] = await Promise.all(
|
|
26
|
+
tables.map(async (table) => {
|
|
27
|
+
onProgress?.(`Fetching fields for table ${table.name} (${table.table_id})...`);
|
|
28
|
+
const fields = await this.listFields(baseToken, table.table_id);
|
|
29
|
+
return {
|
|
30
|
+
id: table.table_id,
|
|
31
|
+
name: table.name,
|
|
32
|
+
fields,
|
|
33
|
+
};
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
baseToken,
|
|
39
|
+
tables: tableSchemas,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async listTables(baseToken: string, onProgress?: (msg: string) => void): Promise<FeishuTable[]> {
|
|
44
|
+
onProgress?.("Fetching tables list...");
|
|
45
|
+
let hasMore = true;
|
|
46
|
+
let pageToken = "";
|
|
47
|
+
const allTables: FeishuTable[] = [];
|
|
48
|
+
|
|
49
|
+
while (hasMore) {
|
|
50
|
+
const query: Record<string, string> = {};
|
|
51
|
+
if (pageToken) query.page_token = pageToken;
|
|
52
|
+
|
|
53
|
+
const data = await this.client.get<ListTablesResponse["data"]>(
|
|
54
|
+
`/open-apis/bitable/v1/apps/${baseToken}/tables`,
|
|
55
|
+
query
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (data.items) {
|
|
59
|
+
allTables.push(...data.items);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (hasMore && !data.page_token) {
|
|
63
|
+
break; // Safety break
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
hasMore = data.has_more;
|
|
67
|
+
pageToken = data.page_token;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onProgress?.(`Found ${allTables.length} tables.`);
|
|
71
|
+
return allTables;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async listFields(baseToken: string, tableId: string): Promise<FeishuField[]> {
|
|
75
|
+
let hasMore = true;
|
|
76
|
+
let pageToken = "";
|
|
77
|
+
const allFields: FeishuField[] = [];
|
|
78
|
+
|
|
79
|
+
while (hasMore) {
|
|
80
|
+
const query: Record<string, string> = {};
|
|
81
|
+
if (pageToken) query.page_token = pageToken;
|
|
82
|
+
|
|
83
|
+
const data = await this.client.get<ListFieldsResponse["data"]>(
|
|
84
|
+
`/open-apis/bitable/v1/apps/${baseToken}/tables/${tableId}/fields`,
|
|
85
|
+
query
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (data.items) {
|
|
89
|
+
allFields.push(...data.items);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (hasMore && !data.page_token) {
|
|
93
|
+
break; // Safety break
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
hasMore = data.has_more;
|
|
97
|
+
pageToken = data.page_token;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return allFields;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
package/src/core/todo.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { FeishuClient } from "./client";
|
|
2
|
+
|
|
3
|
+
export interface TodoItem {
|
|
4
|
+
record_id: string;
|
|
5
|
+
fields: {
|
|
6
|
+
Title: string;
|
|
7
|
+
Done: boolean;
|
|
8
|
+
Priority?: "High" | "Medium" | "Low";
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class TodoManager {
|
|
14
|
+
private client: FeishuClient;
|
|
15
|
+
private appToken: string;
|
|
16
|
+
private tableId?: string;
|
|
17
|
+
|
|
18
|
+
constructor(client: FeishuClient, appToken: string) {
|
|
19
|
+
this.client = client;
|
|
20
|
+
this.appToken = appToken;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Find a table by name, or return the first table if no name provided.
|
|
25
|
+
*/
|
|
26
|
+
async findTable(tableName: string = "Todo"): Promise<string> {
|
|
27
|
+
if (this.tableId) return this.tableId;
|
|
28
|
+
|
|
29
|
+
let pageToken = "";
|
|
30
|
+
let hasMore = true;
|
|
31
|
+
|
|
32
|
+
while (hasMore) {
|
|
33
|
+
const res: any = await this.client.get(`/open-apis/bitable/v1/apps/${this.appToken}/tables`, {
|
|
34
|
+
page_token: pageToken,
|
|
35
|
+
page_size: "100"
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const tables = res.data.items || [];
|
|
39
|
+
const target = tables.find((t: any) => t.name === tableName);
|
|
40
|
+
|
|
41
|
+
if (target) {
|
|
42
|
+
this.tableId = target.table_id;
|
|
43
|
+
return target.table_id;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
hasMore = res.data.has_more;
|
|
47
|
+
pageToken = res.data.page_token;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw new Error(`Table "${tableName}" not found in Base ${this.appToken}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async listTodos(viewId?: string): Promise<TodoItem[]> {
|
|
54
|
+
const tableId = await this.findTable();
|
|
55
|
+
const params: Record<string, string> = { page_size: "100" };
|
|
56
|
+
if (viewId) params.view_id = viewId;
|
|
57
|
+
|
|
58
|
+
// Filter by formula could be added here if needed
|
|
59
|
+
// params.filter = 'CurrentValue.[Done]=FALSE()';
|
|
60
|
+
|
|
61
|
+
const res: any = await this.client.get(
|
|
62
|
+
`/open-apis/bitable/v1/apps/${this.appToken}/tables/${tableId}/records`,
|
|
63
|
+
params
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return (res.data.items || []).map((item: any) => ({
|
|
67
|
+
record_id: item.record_id,
|
|
68
|
+
fields: item.fields
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async createTodo(title: string, priority: string = "Medium"): Promise<TodoItem> {
|
|
73
|
+
const tableId = await this.findTable();
|
|
74
|
+
const body = {
|
|
75
|
+
fields: {
|
|
76
|
+
"Title": title,
|
|
77
|
+
"Done": false,
|
|
78
|
+
"Priority": priority
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const res: any = await this.client.post(
|
|
83
|
+
`/open-apis/bitable/v1/apps/${this.appToken}/tables/${tableId}/records`,
|
|
84
|
+
body
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
record_id: res.data.record.record_id,
|
|
89
|
+
fields: res.data.record.fields
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async markDone(recordId: string): Promise<void> {
|
|
94
|
+
const tableId = await this.findTable();
|
|
95
|
+
const body = {
|
|
96
|
+
fields: {
|
|
97
|
+
"Done": true
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
await this.client.request(
|
|
102
|
+
`/open-apis/bitable/v1/apps/${this.appToken}/tables/${tableId}/records/${recordId}`,
|
|
103
|
+
{
|
|
104
|
+
method: "PUT",
|
|
105
|
+
body: JSON.stringify(body)
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { setupCommand } from "./cli/commands/setup";
|
|
4
|
+
import { authCommand } from "./cli/commands/auth";
|
|
5
|
+
import { whoamiCommand } from "./cli/commands/whoami";
|
|
6
|
+
import { loadConfig } from "./core/config";
|
|
7
|
+
import { createConfigCommands } from "./cli/commands/config";
|
|
8
|
+
import { createCalendarCommands } from "./cli/commands/calendar";
|
|
9
|
+
import { createTodoCommands } from "./cli/commands/todo";
|
|
10
|
+
import { createContactCommands } from "./cli/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
|
+
});
|
|
69
|
+
|
|
70
|
+
// Export modules for library usage
|
|
71
|
+
export * from "./core/client";
|
|
72
|
+
export * from "./core/auth-manager";
|
|
73
|
+
export * from "./core/calendar";
|
|
74
|
+
export * from "./core/contact";
|
|
75
|
+
export * from "./core/todo";
|
|
76
|
+
export * from "./types";
|