@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,305 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { FeishuClient } from "../../core/client";
|
|
3
|
+
import { CalendarManager } from "../../core/calendar";
|
|
4
|
+
import { ContactManager } from "../../core/contact";
|
|
5
|
+
import { loadContactCache } from "../../core/config";
|
|
6
|
+
import { FeishuConfig } from "../../types";
|
|
7
|
+
|
|
8
|
+
interface CalendarOptions {
|
|
9
|
+
calendarId?: string;
|
|
10
|
+
summary?: string;
|
|
11
|
+
start?: string;
|
|
12
|
+
end?: string;
|
|
13
|
+
attendee?: string[];
|
|
14
|
+
attendeeName?: string[];
|
|
15
|
+
eventId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createCalendarCommands(program: Command, config: FeishuConfig) {
|
|
19
|
+
program
|
|
20
|
+
.command("list")
|
|
21
|
+
.description("List all calendars")
|
|
22
|
+
.action(async () => {
|
|
23
|
+
await handleListCalendars(config);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
program
|
|
27
|
+
.command("events")
|
|
28
|
+
.description("List events in a calendar")
|
|
29
|
+
.option("--calendar-id <string>", "Specify calendar ID")
|
|
30
|
+
.action(async (options: CalendarOptions) => {
|
|
31
|
+
await handleListEvents(config, options.calendarId);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.command("create")
|
|
36
|
+
.description("Create a new event")
|
|
37
|
+
.requiredOption("--summary <string>", "Event title")
|
|
38
|
+
.requiredOption("--start <string>", "Event start time")
|
|
39
|
+
.requiredOption("--end <string>", "Event end time")
|
|
40
|
+
.option("--attendee <ids...>", "User IDs (union_id) to invite")
|
|
41
|
+
.option("--attendee-name <names...>", "Contact names to invite")
|
|
42
|
+
.option("--calendar-id <string>", "Specify calendar ID")
|
|
43
|
+
.action(async (options: CalendarOptions) => {
|
|
44
|
+
await handleCreateEvent(config, options);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command("delete")
|
|
49
|
+
.description("Delete an event")
|
|
50
|
+
.requiredOption("--event-id <string>", "Event ID")
|
|
51
|
+
.option("--calendar-id <string>", "Specify calendar ID")
|
|
52
|
+
.action(async (options: CalendarOptions) => {
|
|
53
|
+
await handleDeleteEvent(config, options);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function handleListCalendars(config: FeishuConfig) {
|
|
58
|
+
if (!config.appId || !config.appSecret) {
|
|
59
|
+
console.error("Error: FEISHU_APP_ID and FEISHU_APP_SECRET must be set.");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
if (!config.userAccessToken) {
|
|
63
|
+
console.error("Error: User authorization required.");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const client = new FeishuClient(config);
|
|
68
|
+
const calendarManager = new CalendarManager(client);
|
|
69
|
+
|
|
70
|
+
console.log("\n📅 Your Calendars\n");
|
|
71
|
+
console.log("=".repeat(60));
|
|
72
|
+
|
|
73
|
+
const calendars = await calendarManager.listCalendars();
|
|
74
|
+
|
|
75
|
+
if (!calendars.calendar_list || calendars.calendar_list.length === 0) {
|
|
76
|
+
console.log("No calendars found.");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const primary = calendars.calendar_list.filter(c => c.type === "primary");
|
|
81
|
+
const subscribed = calendars.calendar_list.filter(c => c.type === "shared" || c.type === "exchange");
|
|
82
|
+
const other = calendars.calendar_list.filter(c => c.type !== "primary" && c.type !== "shared" && c.type !== "exchange");
|
|
83
|
+
|
|
84
|
+
if (primary.length > 0) {
|
|
85
|
+
console.log("\n【Primary】");
|
|
86
|
+
primary.forEach(c => {
|
|
87
|
+
console.log(` • ${c.summary}`);
|
|
88
|
+
console.log(` ID: ${c.calendar_id}`);
|
|
89
|
+
console.log(` Role: ${c.role}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (subscribed.length > 0) {
|
|
94
|
+
console.log("\n【Subscribed】");
|
|
95
|
+
subscribed.forEach(c => {
|
|
96
|
+
console.log(` • ${c.summary}`);
|
|
97
|
+
console.log(` ID: ${c.calendar_id}`);
|
|
98
|
+
console.log(` Role: ${c.role}`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (other.length > 0) {
|
|
103
|
+
console.log("\n【Other】");
|
|
104
|
+
other.forEach(c => {
|
|
105
|
+
console.log(` • ${c.summary} (${c.type})`);
|
|
106
|
+
console.log(` ID: ${c.calendar_id}`);
|
|
107
|
+
console.log(` Role: ${c.role}`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log("\n" + "=".repeat(60));
|
|
112
|
+
console.log(`Total: ${calendars.calendar_list.length} calendar(s)\n`);
|
|
113
|
+
|
|
114
|
+
if (primary.length > 0) {
|
|
115
|
+
console.log(`Tip: Use --calendar-id "${primary[0].calendar_id}" to list events.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function handleListEvents(config: FeishuConfig, calendarId?: string) {
|
|
120
|
+
if (!config.appId || !config.appSecret || !config.userAccessToken) {
|
|
121
|
+
console.error("Error: Authorization required. Run 'feishu-agent auth'.");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const client = new FeishuClient(config);
|
|
126
|
+
const calendarManager = new CalendarManager(client);
|
|
127
|
+
|
|
128
|
+
if (!calendarId) {
|
|
129
|
+
const calendars = await calendarManager.listCalendars();
|
|
130
|
+
const primary = calendars.calendar_list?.find(c => c.type === "primary");
|
|
131
|
+
if (primary) {
|
|
132
|
+
calendarId = primary.calendar_id;
|
|
133
|
+
console.log(`Using primary calendar: ${primary.summary}\n`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!calendarId) {
|
|
138
|
+
console.error("Error: No calendar available.");
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(`\n📅 Events\n`);
|
|
143
|
+
console.log("=".repeat(60));
|
|
144
|
+
|
|
145
|
+
const events = await calendarManager.listEvents(calendarId);
|
|
146
|
+
if (!events.items || events.items.length === 0) {
|
|
147
|
+
console.log("No events found.");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const activeEvents = events.items.filter(e => e.status !== "cancelled");
|
|
152
|
+
if (activeEvents.length === 0) {
|
|
153
|
+
console.log("No active events found.");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const contactCache = await loadContactCache();
|
|
158
|
+
|
|
159
|
+
for (const e of activeEvents) {
|
|
160
|
+
const start = e.start_time.timestamp
|
|
161
|
+
? new Date(parseInt(e.start_time.timestamp) * 1000).toLocaleString()
|
|
162
|
+
: e.start_time.date;
|
|
163
|
+
const end = e.end_time.timestamp
|
|
164
|
+
? new Date(parseInt(e.end_time.timestamp) * 1000).toLocaleString()
|
|
165
|
+
: e.end_time.date;
|
|
166
|
+
|
|
167
|
+
console.log(`\n📅 ${e.summary || "(No title)"}`);
|
|
168
|
+
console.log(` 🕐 ${start} - ${end}`);
|
|
169
|
+
console.log(` ID: ${e.event_id}`);
|
|
170
|
+
if (e.status && e.status !== "confirmed") {
|
|
171
|
+
console.log(` Status: ${e.status}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const attendees = await calendarManager.getEventAttendees(calendarId, e.event_id);
|
|
176
|
+
if (attendees && attendees.length > 0) {
|
|
177
|
+
const attendeeDisplay = attendees.map(a => {
|
|
178
|
+
if (a.type === "user" && a.user_id) {
|
|
179
|
+
// Lookup attendee name from cache by open_id or union_id
|
|
180
|
+
for (const [unionId, entry] of Object.entries(contactCache)) {
|
|
181
|
+
if (entry.open_id === a.user_id || unionId === a.user_id) {
|
|
182
|
+
return `${entry.name}${a.is_optional ? " (optional)" : ""}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return `${a.user_id}${a.is_optional ? " (optional)" : ""}`;
|
|
186
|
+
}
|
|
187
|
+
if (a.type === "chat") return `Chat: ${a.chat_id}`;
|
|
188
|
+
if (a.type === "third_party" && a.third_party_email) {
|
|
189
|
+
return `${a.third_party_email} (external)`;
|
|
190
|
+
}
|
|
191
|
+
return a.type;
|
|
192
|
+
});
|
|
193
|
+
console.log(` 👥 Attendees: ${attendeeDisplay.join(", ")}`);
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
// Ignore errors
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log("\n" + "=".repeat(60));
|
|
201
|
+
console.log(`Total: ${activeEvents.length} event(s)\n`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function handleCreateEvent(config: FeishuConfig, options: CalendarOptions) {
|
|
205
|
+
if (!config.appId || !config.appSecret || !config.userAccessToken) {
|
|
206
|
+
console.error("Error: Authorization required.");
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const client = new FeishuClient(config);
|
|
211
|
+
const calendarManager = new CalendarManager(client);
|
|
212
|
+
const contactManager = new ContactManager(client);
|
|
213
|
+
|
|
214
|
+
const { summary, start, end, attendee, attendeeName, calendarId } = options;
|
|
215
|
+
|
|
216
|
+
if (!summary || !start || !end) {
|
|
217
|
+
console.error("Error: --summary, --start, and --end are required.");
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Resolve attendee names to union_ids
|
|
222
|
+
let attendeeUserIds: string[] = attendee || [];
|
|
223
|
+
if (attendeeName && attendeeName.length > 0) {
|
|
224
|
+
console.log("\n🔍 Resolving attendee names...");
|
|
225
|
+
for (const name of attendeeName) {
|
|
226
|
+
const results = await contactManager.searchUser(name);
|
|
227
|
+
if (results.length === 0) {
|
|
228
|
+
console.error(`Error: No contact found for "${name}".`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
if (results.length > 1) {
|
|
232
|
+
console.log(` Multiple matches for "${name}":`);
|
|
233
|
+
results.forEach((r, i) => {
|
|
234
|
+
console.log(` ${i + 1}. ${r.name} (${r.email || r.union_id})`);
|
|
235
|
+
});
|
|
236
|
+
console.log(" Using the first match.");
|
|
237
|
+
}
|
|
238
|
+
attendeeUserIds.push(results[0].union_id);
|
|
239
|
+
console.log(` ✓ "${name}" -> ${results[0].name} (${results[0].union_id})`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Get calendar
|
|
244
|
+
let targetCalendarId = calendarId;
|
|
245
|
+
if (!targetCalendarId) {
|
|
246
|
+
const calendars = await calendarManager.listCalendars();
|
|
247
|
+
const primary = calendars.calendar_list?.find(c => c.type === "primary");
|
|
248
|
+
if (primary) targetCalendarId = primary.calendar_id;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!targetCalendarId) {
|
|
252
|
+
console.error("Error: No calendar available.");
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const startTimestamp = Math.floor(new Date(start).getTime() / 1000).toString();
|
|
257
|
+
const endTimestamp = Math.floor(new Date(end).getTime() / 1000).toString();
|
|
258
|
+
|
|
259
|
+
const event = await calendarManager.createEvent(targetCalendarId, {
|
|
260
|
+
summary,
|
|
261
|
+
startTime: { timestamp: startTimestamp },
|
|
262
|
+
endTime: { timestamp: endTimestamp },
|
|
263
|
+
attendeeUserIds: attendeeUserIds.length > 0 ? attendeeUserIds : undefined,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
console.log("\n✅ Event created!");
|
|
267
|
+
console.log(` Title: ${summary}`);
|
|
268
|
+
console.log(` Time: ${new Date(parseInt(startTimestamp) * 1000).toLocaleString()} - ${new Date(parseInt(endTimestamp) * 1000).toLocaleString()}`);
|
|
269
|
+
console.log(` Calendar ID: ${targetCalendarId}`);
|
|
270
|
+
if (attendeeUserIds.length > 0) {
|
|
271
|
+
console.log(` Attendees: ${attendeeUserIds.join(", ")}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function handleDeleteEvent(config: FeishuConfig, options: CalendarOptions) {
|
|
276
|
+
if (!config.appId || !config.appSecret || !config.userAccessToken) {
|
|
277
|
+
console.error("Error: Authorization required.");
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const client = new FeishuClient(config);
|
|
282
|
+
const calendarManager = new CalendarManager(client);
|
|
283
|
+
|
|
284
|
+
const { eventId, calendarId } = options;
|
|
285
|
+
let targetCalendarId = calendarId;
|
|
286
|
+
|
|
287
|
+
if (!targetCalendarId) {
|
|
288
|
+
const calendars = await calendarManager.listCalendars();
|
|
289
|
+
const primary = calendars.calendar_list?.find(c => c.type === "primary");
|
|
290
|
+
if (primary) targetCalendarId = primary.calendar_id;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!targetCalendarId) {
|
|
294
|
+
console.error("Error: No calendar available.");
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!eventId) {
|
|
299
|
+
console.error("Error: --event-id is required.");
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
await calendarManager.deleteEvent(targetCalendarId, eventId);
|
|
304
|
+
console.log(`\n✅ Event deleted: ${eventId}\n`);
|
|
305
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { saveGlobalConfig, getConfigPath } from "../../core/config";
|
|
3
|
+
import { FeishuConfig } from "../../types";
|
|
4
|
+
|
|
5
|
+
export function createConfigCommands(program: Command) {
|
|
6
|
+
program
|
|
7
|
+
.command("set")
|
|
8
|
+
.description("Set a config value")
|
|
9
|
+
.argument("<key>", "Config key (appId or appSecret)")
|
|
10
|
+
.argument("<value>", "Config value")
|
|
11
|
+
.action(async (key: string, value: string) => {
|
|
12
|
+
if (key !== "appId" && key !== "appSecret") {
|
|
13
|
+
console.error("Error: Key must be 'appId' or 'appSecret'.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
await saveGlobalConfig({ [key]: value });
|
|
18
|
+
console.log(`Updated global config: ${key} = ${value}`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command("get")
|
|
23
|
+
.description("Get a config value")
|
|
24
|
+
.argument("<key>", "Config key")
|
|
25
|
+
.action(async (key: string) => {
|
|
26
|
+
const file = Bun.file(getConfigPath());
|
|
27
|
+
if (await file.exists()) {
|
|
28
|
+
const config = await file.json();
|
|
29
|
+
console.log(config[key] || "(not set)");
|
|
30
|
+
} else {
|
|
31
|
+
console.log("(not set)");
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.command("list")
|
|
37
|
+
.description("List all config values")
|
|
38
|
+
.action(async () => {
|
|
39
|
+
const f = Bun.file(getConfigPath());
|
|
40
|
+
if (await f.exists()) {
|
|
41
|
+
const c = await f.json();
|
|
42
|
+
console.log("Global Configuration:");
|
|
43
|
+
console.log(JSON.stringify(c, null, 2));
|
|
44
|
+
} else {
|
|
45
|
+
console.log("Global Configuration: (empty)");
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { FeishuClient } from "../../core/client";
|
|
3
|
+
import { ContactManager } from "../../core/contact";
|
|
4
|
+
import { FeishuConfig } from "../../types";
|
|
5
|
+
|
|
6
|
+
interface ContactOptions {
|
|
7
|
+
dept?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createContactCommands(program: Command, config: FeishuConfig) {
|
|
11
|
+
program
|
|
12
|
+
.command("list")
|
|
13
|
+
.description("List users in a department")
|
|
14
|
+
.option("--dept <string>", "Department ID", "0")
|
|
15
|
+
.action(async (options: ContactOptions) => {
|
|
16
|
+
await handleList(config, options.dept);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command("search")
|
|
21
|
+
.description("Search users by name or email")
|
|
22
|
+
.argument("<query>", "Search query")
|
|
23
|
+
.action(async (query: string) => {
|
|
24
|
+
await handleSearch(config, query);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function handleList(config: FeishuConfig, dept: string = "0") {
|
|
29
|
+
if (!config.appId || !config.appSecret) {
|
|
30
|
+
console.error("Error: Credentials required.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const client = new FeishuClient(config);
|
|
35
|
+
const contactManager = new ContactManager(client);
|
|
36
|
+
|
|
37
|
+
console.log(`Fetching users for department: ${dept}`);
|
|
38
|
+
const users = await contactManager.listUsers(dept);
|
|
39
|
+
|
|
40
|
+
if (users.length > 0) {
|
|
41
|
+
users.forEach((u) => {
|
|
42
|
+
console.log(`- Name: ${u.name}${u.email ? ` (${u.email})` : ''}`);
|
|
43
|
+
if (u.user_id && u.union_id && u.user_id !== u.union_id) {
|
|
44
|
+
console.log(` ID: ${u.user_id} / UnionID: ${u.union_id}`);
|
|
45
|
+
} else if (u.user_id) {
|
|
46
|
+
console.log(` ID: ${u.user_id}`);
|
|
47
|
+
} else if (u.union_id) {
|
|
48
|
+
console.log(` UnionID: ${u.union_id}`);
|
|
49
|
+
}
|
|
50
|
+
console.log("");
|
|
51
|
+
});
|
|
52
|
+
console.log(`Total: ${users.length} users`);
|
|
53
|
+
} else {
|
|
54
|
+
console.log("No users found.");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function handleSearch(config: FeishuConfig, query: string) {
|
|
59
|
+
if (!config.appId || !config.appSecret) {
|
|
60
|
+
console.error("Error: Credentials required.");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!query) {
|
|
65
|
+
console.error("Error: search query is required.");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const client = new FeishuClient(config);
|
|
70
|
+
const contactManager = new ContactManager(client);
|
|
71
|
+
|
|
72
|
+
console.log(`Searching for users matching: '${query}'`);
|
|
73
|
+
const users = await contactManager.searchUser(query);
|
|
74
|
+
|
|
75
|
+
if (users.length > 0) {
|
|
76
|
+
users.forEach((u) => {
|
|
77
|
+
console.log(`- Name: ${u.name}${u.email ? ` (${u.email})` : ''}`);
|
|
78
|
+
if (u.user_id && u.union_id && u.user_id !== u.union_id) {
|
|
79
|
+
console.log(` ID: ${u.user_id} / UnionID: ${u.union_id}`);
|
|
80
|
+
} else if (u.user_id) {
|
|
81
|
+
console.log(` ID: ${u.user_id}`);
|
|
82
|
+
} else if (u.union_id) {
|
|
83
|
+
console.log(` UnionID: ${u.union_id}`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
console.log(`Total: ${users.length} matching users`);
|
|
87
|
+
} else {
|
|
88
|
+
console.log("No matching users found.");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { loadConfig, FeishuConfig } from "../../core/config";
|
|
2
|
+
import { FeishuClient } from "../../core/client";
|
|
3
|
+
import { IntrospectionEngine } from "../../core/introspection";
|
|
4
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
5
|
+
|
|
6
|
+
export async function initCommand(args: string[], cliOptions?: Partial<FeishuConfig>) {
|
|
7
|
+
const target = args[0];
|
|
8
|
+
|
|
9
|
+
// If no target provided, go straight to interactive mode
|
|
10
|
+
if (!target) {
|
|
11
|
+
await initInteractive(undefined, cliOptions);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Check if it's a Folder URL
|
|
16
|
+
if (target.includes("/drive/folder/")) {
|
|
17
|
+
console.warn("That looks like a Folder URL. Please provide a Base URL (多维表格).");
|
|
18
|
+
await initInteractive(undefined, cliOptions);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const baseToken = extractBaseToken(target);
|
|
23
|
+
if (baseToken) {
|
|
24
|
+
await initInteractive(target, cliOptions);
|
|
25
|
+
} else {
|
|
26
|
+
// Treat as local path (scaffolding) - for now just log
|
|
27
|
+
console.log(`Initializing in local path: ${target}`);
|
|
28
|
+
// TODO: Implement local scaffolding logic if needed
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function initInteractive(initialTarget?: string, cliOptions?: Partial<FeishuConfig>) {
|
|
33
|
+
let baseToken: string | null = null;
|
|
34
|
+
|
|
35
|
+
if (initialTarget) {
|
|
36
|
+
baseToken = extractBaseToken(initialTarget);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while (!baseToken) {
|
|
40
|
+
const input = prompt("Please enter the Feishu Base URL (or Base Token): ");
|
|
41
|
+
|
|
42
|
+
if (!input) {
|
|
43
|
+
console.error("Error: Base Token is required.");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
baseToken = extractBaseToken(input);
|
|
48
|
+
|
|
49
|
+
if (!baseToken) {
|
|
50
|
+
console.error("Invalid input. Could not extract Base Token.");
|
|
51
|
+
console.log("To find your Base URL: Open the Base (多维表格) in your browser and copy the link. It usually looks like: https://.../base/basexxxxxx");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`Detected Base Token: ${baseToken}`);
|
|
56
|
+
await runIntrospection(baseToken, cliOptions);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function runIntrospection(baseToken: string, cliOptions?: Partial<FeishuConfig>) {
|
|
60
|
+
// Load configuration
|
|
61
|
+
const config = await loadConfig(cliOptions);
|
|
62
|
+
|
|
63
|
+
// Get App Credentials
|
|
64
|
+
let appId = config.appId;
|
|
65
|
+
if (!appId) {
|
|
66
|
+
const input = prompt("Enter Feishu App ID:");
|
|
67
|
+
if (!input) {
|
|
68
|
+
console.error("Error: App ID is required.");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
appId = input;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let appSecret = config.appSecret;
|
|
75
|
+
if (!appSecret) {
|
|
76
|
+
const input = prompt("Enter Feishu App Secret:");
|
|
77
|
+
if (!input) {
|
|
78
|
+
console.error("Error: App Secret is required.");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
appSecret = input;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log("Fetching schema...");
|
|
85
|
+
|
|
86
|
+
const client = new FeishuClient({
|
|
87
|
+
appId,
|
|
88
|
+
appSecret,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const engine = new IntrospectionEngine(client);
|
|
93
|
+
const schema = await engine.introspect(baseToken, (msg) => console.log(msg));
|
|
94
|
+
|
|
95
|
+
await mkdir(".feishu_agent", { recursive: true });
|
|
96
|
+
await writeFile(".feishu_agent/schema.json", JSON.stringify(schema, null, 2));
|
|
97
|
+
console.log("Success! Schema saved to .feishu_agent/schema.json");
|
|
98
|
+
|
|
99
|
+
// Create .env if it doesn't exist
|
|
100
|
+
const envFile = Bun.file(".env");
|
|
101
|
+
if (!(await envFile.exists())) {
|
|
102
|
+
const envContent = `FEISHU_APP_ID=${appId}\nFEISHU_APP_SECRET=${appSecret}\n`;
|
|
103
|
+
await writeFile(".env", envContent);
|
|
104
|
+
console.log("Created .env file with credentials.");
|
|
105
|
+
} else {
|
|
106
|
+
console.log(".env file already exists, skipping creation.");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error("Failed to fetch schema:", error instanceof Error ? error.message : String(error));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function extractBaseToken(input: string): string | null {
|
|
116
|
+
// 1. Try to match URL pattern: .../base/<token>...
|
|
117
|
+
const urlMatch = input.match(/\/base\/([a-zA-Z0-9]+)/);
|
|
118
|
+
if (urlMatch) {
|
|
119
|
+
return urlMatch[1];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 2. check if input itself looks like a token (starts with 'base' or 'app')
|
|
123
|
+
if (/^(base|app)[a-zA-Z0-9]+$/.test(input)) {
|
|
124
|
+
return input;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
}
|