feat: add Feishu (飞书/Lark) Desktop adapter via AppleScript (5 commands)
Feishu uses custom 'Lark Framework' (Chromium-based but NOT Electron). CDP port test failed — --remote-debugging-port has no effect. Uses AppleScript + clipboard approach (same as WeChat/ChatGPT). Commands: status, send, read, search (Cmd+K), new (Cmd+N) Includes adapter READMEs (EN+ZH).
This commit is contained in:
1 parent
fc9fc32d14
commit
832370f6e2
7 files changed
+235
No files matched your search
@@ -0,0 +1,20 @@
|
||||
# Feishu (飞书/Lark) Desktop Adapter
|
||||
|
||||
Control **Feishu/Lark Desktop** from the terminal via AppleScript.
|
||||
|
||||
> **Note:** Feishu uses a custom `Lark Framework` (Chromium-based but NOT Electron). CDP is not available, so this adapter uses AppleScript + clipboard.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Feishu/Lark must be running and logged in
|
||||
2. Terminal must have **Accessibility permission**
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `feishu status` | Check if Feishu/Lark is running |
|
||||
| `feishu send "msg"` | Send message in active chat (paste + Enter) |
|
||||
| `feishu read` | Read current chat (Cmd+A → Cmd+C) |
|
||||
| `feishu search "query"` | Global search (Cmd+K) |
|
||||
| `feishu new` | New message/document (Cmd+N) |
|
||||
@@ -0,0 +1,20 @@
|
||||
# 飞书 (Lark) 桌面端适配器
|
||||
|
||||
通过 AppleScript 在终端中控制**飞书桌面端**。
|
||||
|
||||
> **注意:** 飞书使用自研 `Lark Framework`(基于 Chromium 但非 Electron),不支持 CDP。
|
||||
|
||||
## 前置条件
|
||||
|
||||
1. 飞书必须正在运行且已登录
|
||||
2. Terminal 需要辅助功能权限
|
||||
|
||||
## 命令
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `feishu status` | 检查飞书是否在运行 |
|
||||
| `feishu send "消息"` | 发送消息(粘贴 + 回车) |
|
||||
| `feishu read` | 读取当前聊天(Cmd+A → Cmd+C) |
|
||||
| `feishu search "关键词"` | 全局搜索(Cmd+K) |
|
||||
| `feishu new` | 新建消息/文档(Cmd+N) |
|
||||
@@ -0,0 +1,32 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const newCommand = cli({
|
||||
site: 'feishu',
|
||||
name: 'new',
|
||||
description: 'Create a new message or document in Feishu',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.PUBLIC,
|
||||
browser: false,
|
||||
args: [],
|
||||
columns: ['Status'],
|
||||
func: async (page: IPage | null) => {
|
||||
try {
|
||||
execSync("osascript -e 'tell application \"Lark\" to activate'");
|
||||
execSync("osascript -e 'delay 0.3'");
|
||||
|
||||
// Cmd+N for new conversation/document
|
||||
execSync(
|
||||
"osascript " +
|
||||
"-e 'tell application \"System Events\"' " +
|
||||
"-e 'keystroke \"n\" using command down' " +
|
||||
"-e 'end tell'"
|
||||
);
|
||||
|
||||
return [{ Status: 'New item dialog opened (Cmd+N)' }];
|
||||
} catch (err: any) {
|
||||
return [{ Status: 'Error: ' + err.message }];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { execSync, spawnSync } from 'node:child_process';
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const readCommand = cli({
|
||||
site: 'feishu',
|
||||
name: 'read',
|
||||
description: 'Read the current chat content by selecting all and copying',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.PUBLIC,
|
||||
browser: false,
|
||||
args: [],
|
||||
columns: ['Content'],
|
||||
func: async (page: IPage | null) => {
|
||||
try {
|
||||
let clipBackup = '';
|
||||
try {
|
||||
clipBackup = execSync('pbpaste', { encoding: 'utf-8' });
|
||||
} catch { /* empty */ }
|
||||
|
||||
execSync("osascript -e 'tell application \"Lark\" to activate'");
|
||||
execSync("osascript -e 'delay 0.3'");
|
||||
|
||||
execSync(
|
||||
"osascript " +
|
||||
"-e 'tell application \"System Events\"' " +
|
||||
"-e 'keystroke \"a\" using command down' " +
|
||||
"-e 'delay 0.2' " +
|
||||
"-e 'keystroke \"c\" using command down' " +
|
||||
"-e 'delay 0.2' " +
|
||||
"-e 'end tell'"
|
||||
);
|
||||
|
||||
const content = execSync('pbpaste', { encoding: 'utf-8' }).trim();
|
||||
|
||||
if (clipBackup) {
|
||||
spawnSync('pbcopy', { input: clipBackup });
|
||||
}
|
||||
|
||||
// Deselect
|
||||
execSync("osascript -e 'tell application \"System Events\" to key code 53'");
|
||||
|
||||
return [{ Content: content || '(no content captured)' }];
|
||||
} catch (err: any) {
|
||||
return [{ Content: 'Error: ' + err.message }];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const searchCommand = cli({
|
||||
site: 'feishu',
|
||||
name: 'search',
|
||||
description: 'Open Feishu global search and type a query (Cmd+K)',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.PUBLIC,
|
||||
browser: false,
|
||||
args: [{ name: 'query', required: true, positional: true, help: 'Search query' }],
|
||||
columns: ['Status'],
|
||||
func: async (page: IPage | null, kwargs: any) => {
|
||||
const query = kwargs.query as string;
|
||||
try {
|
||||
execSync("osascript -e 'tell application \"Lark\" to activate'");
|
||||
execSync("osascript -e 'delay 0.3'");
|
||||
|
||||
// Feishu uses Cmd+K for global search (similar to Slack/Notion)
|
||||
execSync(
|
||||
"osascript " +
|
||||
"-e 'tell application \"System Events\"' " +
|
||||
"-e 'keystroke \"k\" using command down' " +
|
||||
"-e 'delay 0.5' " +
|
||||
`-e 'keystroke ${JSON.stringify(query)}' ` +
|
||||
"-e 'end tell'"
|
||||
);
|
||||
|
||||
return [{ Status: `Searching for: ${query}` }];
|
||||
} catch (err: any) {
|
||||
return [{ Status: 'Error: ' + err.message }];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { execSync, spawnSync } from 'node:child_process';
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const sendCommand = cli({
|
||||
site: 'feishu',
|
||||
name: 'send',
|
||||
description: 'Send a message in the active Feishu (Lark) conversation',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.PUBLIC,
|
||||
browser: false,
|
||||
args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
|
||||
columns: ['Status'],
|
||||
func: async (page: IPage | null, kwargs: any) => {
|
||||
const text = kwargs.text as string;
|
||||
try {
|
||||
// Backup clipboard
|
||||
let clipBackup = '';
|
||||
try {
|
||||
clipBackup = execSync('pbpaste', { encoding: 'utf-8' });
|
||||
} catch { /* empty */ }
|
||||
|
||||
spawnSync('pbcopy', { input: text });
|
||||
|
||||
execSync("osascript -e 'tell application \"Lark\" to activate'");
|
||||
execSync("osascript -e 'delay 0.5'");
|
||||
|
||||
execSync(
|
||||
"osascript " +
|
||||
"-e 'tell application \"System Events\"' " +
|
||||
"-e 'keystroke \"v\" using command down' " +
|
||||
"-e 'delay 0.2' " +
|
||||
"-e 'keystroke return' " +
|
||||
"-e 'end tell'"
|
||||
);
|
||||
|
||||
if (clipBackup) {
|
||||
spawnSync('pbcopy', { input: clipBackup });
|
||||
}
|
||||
|
||||
return [{ Status: 'Success' }];
|
||||
} catch (err: any) {
|
||||
return [{ Status: 'Error: ' + err.message }];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { cli, Strategy } from '../../registry.js';
|
||||
import type { IPage } from '../../types.js';
|
||||
|
||||
export const statusCommand = cli({
|
||||
site: 'feishu',
|
||||
name: 'status',
|
||||
description: 'Check if Feishu (Lark) Desktop is running on macOS',
|
||||
domain: 'localhost',
|
||||
strategy: Strategy.PUBLIC,
|
||||
browser: false,
|
||||
args: [],
|
||||
columns: ['Status', 'Detail'],
|
||||
func: async (page: IPage | null) => {
|
||||
try {
|
||||
const running = execSync("osascript -e 'application \"Lark\" is running'", { encoding: 'utf-8' }).trim();
|
||||
if (running !== 'true') {
|
||||
return [{ Status: 'Stopped', Detail: 'Feishu/Lark is not running' }];
|
||||
}
|
||||
|
||||
const windowCount = execSync(
|
||||
"osascript -e 'tell application \"System Events\" to count windows of application process \"Lark\"'",
|
||||
{ encoding: 'utf-8' }
|
||||
).trim();
|
||||
|
||||
return [{
|
||||
Status: 'Running',
|
||||
Detail: `${windowCount} window(s) open`,
|
||||
}];
|
||||
} catch (err: any) {
|
||||
return [{ Status: 'Error', Detail: err.message }];
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user