TypeScript SDK
workhub-bot-sdk는 Node.js 환경에서 Workhub 봇을 개발하기 위한 공식 SDK입니다.
- npm: workhub-bot-sdk
- 최신 버전: 0.2.0
- 라이선스: MIT
설치
bash
npm install workhub-bot-sdkNode.js 18 이상이 필요합니다.
클라이언트 초기화
typescript
import { WorkhubBot } from "workhub-bot-sdk";
const bot = new WorkhubBot({
baseUrl: "https://workhub.example.com", // Workhub 서버 URL
apiKey: "whb_xxxxxxxx_live_xxxxxxxxxxxxxxxx", // 봇 API 키
timeout: 30000, // 요청 타임아웃 (ms, 기본: 30000)
});옵션
| 옵션 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
baseUrl | string | O | - | Workhub 서버 URL |
apiKey | string | O | - | 봇 API 키 |
timeout | number | - | 30000 | 요청 타임아웃 (ms) |
연결 확인
typescript
// API 키 검증 및 봇 정보 조회
const info = await bot.authenticate();
console.log(`봇 ID: ${info.bot_id}`);
console.log(`조직 ID: ${info.org_id}`);
console.log(`권한: ${info.scopes.join(", ")}`);
// MCP 세션 초기화
await bot.initialize();
// 연결 상태 확인
await bot.ping();메시지
typescript
// 채널에 메시지 전송
await bot.sendMessage({
channel_id: "채널-UUID",
content: "안녕하세요! **마크다운**도 지원합니다.",
});
// 토픽에 메시지 전송
await bot.sendMessage({
topic_id: "토픽-UUID",
content: "토픽 메시지입니다.",
});
// DM 전송
await bot.sendMessage({
dm_room_id: "DM-UUID",
content: "1:1 메시지입니다.",
});
// 메시지 조회
const messages = await bot.readMessages({
channel_id: "채널-UUID",
limit: 10,
});채널 & 프로젝트
typescript
// 전체 목록
const all = await bot.listChannels({ type: "all" });
// 채널만
const channels = await bot.listChannels({ type: "channels" });
// 프로젝트만
const projects = await bot.listChannels({ type: "projects" });
// 특정 프로젝트의 토픽
const topics = await bot.listChannels({
type: "topics",
project_id: "프로젝트-UUID",
});태스크
typescript
// 태스크 생성
const result = await bot.createTask({
topic_id: "토픽-UUID",
title: "API 문서 작성",
priority: "high",
assignee_id: "담당자-UUID",
due_date: "2026-04-15",
description: "MCP 및 SDK 문서를 작성합니다.",
});
// 태스크 수정
await bot.updateTask({
task_id: "태스크-UUID",
status: "in_progress",
priority: "urgent",
});
// 태스크 목록 조회
const tasks = await bot.listTasks({
topic_id: "토픽-UUID",
status: "todo",
});검색
typescript
// 통합 검색
const result = await bot.search({
query: "배포 일정",
type: "all",
limit: 20,
});
console.log(`총 ${result.total}건`);
// 태스크만 검색
const taskResult = await bot.search({
query: "버그",
type: "tasks",
});사용자
typescript
// ID로 사용자 조회
const user = await bot.getUser({ user_id: "사용자-UUID" });
// 이메일로 사용자 조회
const user2 = await bot.getUser({ email: "hong@example.com" });
// 사용자 목록 (부서 필터)
const devTeam = await bot.listUsers({
department: "개발",
limit: 100,
});파일
typescript
// 파일 업로드
const fileBuffer = fs.readFileSync("./report.pdf");
await bot.uploadFile(fileBuffer, "report.pdf", {
message_id: "메시지-UUID", // 선택
});
// 파일 다운로드
const data = await bot.downloadFile("파일-UUID");봇 커스텀 명령 (슬래시 커맨드)
봇이 자신의 슬래시 명령을 자가 등록/조회/삭제할 수 있습니다. 등록된 명령은 Workhub UI의 / 자동완성 팔레트에 즉시 반영됩니다.
필요한 scope
bots:commands:read— 조회bots:commands:write— 등록·삭제
제한
- 봇은 자신의 명령만 관리할 수 있습니다. 다른 봇의
bot_id로 호출하면403 Forbidden반환. bot_id대신"self"를 전달하면 SDK가 자동으로 인증된 봇의 UUID를 조회하여 치환합니다.
typescript
// 커스텀 명령 등록 (self 사용 권장)
const cmd = await bot.createBotCommand("self", {
command: "deploy",
description: "프로덕션 배포를 실행합니다",
usage_hint: "/deploy [branch]",
});
// 등록된 명령 목록
const commands = await bot.listBotCommands("self");
// 명령 삭제
await bot.deleteBotCommand("self", "deploy");일괄 등록 예시 (부팅 시)
typescript
const COMMANDS: CreateBotCommandParams[] = [
{ command: "weekly-preview", description: "주간업무 취합 보고서", usage_hint: "/weekly-preview" },
{ command: "weekly-reset", description: "이번 주 주간업무 초기화", usage_hint: "/weekly-reset [YYYY-Www]" },
{ command: "my-tasks", description: "내 배정 업무", usage_hint: "/my-tasks" },
];
for (const params of COMMANDS) {
try {
await bot.createBotCommand("self", params);
} catch (e) {
if ((e as WorkhubBotError).status !== 409) {
console.warn("command register failed:", params.command, e);
}
}
}채널 북마크
typescript
// 메시지 북마크
await bot.createBookmark("채널-UUID", "메시지-UUID");
// 북마크 목록 조회 (필터 지원)
const bookmarks = await bot.listBookmarks("채널-UUID", {
sort: "desc",
limit: 20,
});
// 북마크 삭제
await bot.deleteBookmark("채널-UUID", "메시지-UUID");채널 파일
typescript
// 채널 파일 목록 (검색, 필터, 정렬)
const files = await bot.listChannelFiles("채널-UUID", {
q: "보고서",
type: "document",
sort: "created_at",
order: "desc",
limit: 50,
});
console.log(`총 ${files.total}개 파일`);멘션 검색
typescript
// 멘션 대상 검색 (사용자, 부서, 봇, @all, @here)
const targets = await bot.searchMentions({
q: "김",
channel_id: "채널-UUID", // 채널 멤버 우선 표시
});
// 결과는 type별로 구분: "special", "user", "department", "bot"해시태그
typescript
// 해시태그 검색
const tags = await bot.searchHashtags("배포");
// 해시태그 프리뷰 (채널/태스크/프로젝트)
const preview = await bot.getHashtagPreview("project", "프로젝트-UUID");슬래시 명령
typescript
// 사용 가능한 슬래시 명령 목록
const slashCmds = await bot.listSlashCommands();
// 슬래시 명령 실행
const result = await bot.executeSlashCommand({
text: "/status 회의 중",
channel_id: "채널-UUID",
});
console.log(result.text);v0.2.0 신규 API (36개)
메시지 고급 기능
typescript
// 메시지 편집 (자기 자신이 보낸 것만, messages:write 필요)
await bot.updateMessage(messageId, "수정된 내용");
await bot.updateMessage(messageId, "본문", { content_html: "<p>본문</p>" });
// 메시지 삭제 (soft delete — deleted_at 세팅, 이벤트 브로드캐스트)
await bot.deleteMessage(messageId);
// 반응 추가/제거/조회
await bot.addReaction(messageId, "👍");
await bot.removeReaction(messageId, "👍");
const reactions = await bot.getReactions(messageId);
// 메시지 고정
await bot.pinMessage(messageId);
await bot.unpinMessage(messageId);
// 스레드 답글 조회
const replies = await bot.listThread(messageId);메시지 편집/삭제 제한
봇은 자기가 보낸 메시지만 편집하거나 삭제할 수 있습니다. 다른 봇/사용자 메시지에 시도 시 403 Forbidden 반환. messages:write scope 필요.
태스크 고급 기능
typescript
// 상태 변경 (todo | in_progress | review | done)
await bot.updateTaskStatus(taskId, "done");
// 선행 태스크 (의존성)
await bot.setTaskDependencies(taskId, [prereqId1, prereqId2]);
// 담당자 관리
await bot.addTaskAssignee(taskId, userId);
await bot.removeTaskAssignee(taskId, userId);
// 태스크 삭제
await bot.deleteTask(taskId);작업일지 (Work Log)
typescript
// 작업일지 기록
await bot.createWorkLog({
task_id: taskId,
work_date: "2026-04-18",
start_time: "09:00",
end_time: "18:00",
memo: "구현 완료",
});
// 날짜 범위로 조회
const logs = await bot.listWorkLogs({
from: "2026-04-01",
to: "2026-04-30",
});
// 삭제
await bot.deleteWorkLog(workLogId);채널 관리
typescript
// 채널 생성
const ch = await bot.createChannel({
name: "프로젝트-A",
description: "A 프로젝트 전용",
channel_type: "general",
});
// 수정 / 삭제
await bot.updateChannel(ch.id, { name: "새이름" });
await bot.deleteChannel(channelId);
// 참여 / 나가기
await bot.joinChannel(channelId);
await bot.leaveChannel(channelId);
// 멤버 관리
await bot.addChannelMember(channelId, userId);
await bot.removeChannelMember(channelId, userId);DM 관리
typescript
// DM 방 생성 (1:1 또는 그룹)
const dm = await bot.createDMRoom({ user_ids: [userA, userB] });
// DM 방 목록
const rooms = await bot.listDMRooms();
// DM 메시지 전송 / 조회
await bot.sendDMMessage(dm.id, "안녕하세요");
const msgs = await bot.listDMMessages(dm.id, { limit: 20 });
// 방 나가기
await bot.leaveDMRoom(dm.id);Topic / Project
typescript
// 프로젝트 목록 / 상세
const projects = await bot.listProjects({ status: "active" });
const proj = await bot.getProject(projectId);
// 토픽 목록
const topics = await bot.listTopics(projectId);
// 토픽 상태 변경 (active | completed | archived)
await bot.updateTopicStatus(topicId, "completed");
// 토픽 삭제 (이름 확인 필수)
await bot.deleteTopic(topicId, "토픽이름");User / Organization
typescript
// 현재 봇의 인증 컨텍스트
const me = await bot.getMe();
// 부서 목록
const departments = await bot.listDepartments();알림
typescript
// 알림 목록 (읽지 않은 것만)
const notifs = await bot.listNotifications({ unread_only: true, limit: 20 });
// 읽음 처리
await bot.markNotificationRead(notifId);
// 읽지 않은 알림 수
const { count } = await bot.getUnreadCount();MCP 도구 직접 호출
SDK의 편의 메서드 외에 MCP 도구를 직접 호출할 수도 있습니다:
typescript
// 사용 가능한 도구 목록
const tools = await bot.listTools();
for (const tool of tools) {
console.log(`${tool.name}: ${tool.description}`);
}에러 처리
typescript
try {
await bot.sendMessage({
channel_id: "잘못된-UUID",
content: "테스트",
});
} catch (error) {
if (error instanceof Error) {
console.error(`오류: ${error.message}`);
}
}예제: Echo 봇
채널에 메시지가 올라오면 그대로 되돌려보내는 봇입니다. Webhook 수신 → 메시지 전송의 기본 흐름을 익힐 수 있습니다.
프로젝트 구성
bash
mkdir echo-bot && cd echo-bot
npm init -y
npm install workhub-bot-sdkecho-bot.ts
typescript
import { WorkhubBot, WebhookServer } from "workhub-bot-sdk";
import type { WebhookEvent } from "workhub-bot-sdk";
// 1. 봇 클라이언트 — Workhub API 호출용
const bot = new WorkhubBot({
baseUrl: process.env.WORKHUB_URL || "http://localhost:8080",
apiKey: process.env.BOT_API_KEY || "whb_xxxxxxxx_live_xxxxxxxxxxxxxxxx",
});
// 2. Webhook 서버 — 이벤트 수신용
const webhook = new WebhookServer({
port: Number(process.env.PORT) || 9000,
secret: process.env.WEBHOOK_SECRET || "your-webhook-secret",
});
// 3. 연결 확인
async function init() {
const info = await bot.authenticate();
console.log(`✅ Echo 봇 연결: ${info.bot_id}`);
}
// 4. 메시지 이벤트 핸들러
webhook.on("message.created", async (event: WebhookEvent) => {
const { content, channel_id, sender_type, sender_id } =
event.data as Record<string, string>;
// 봇 자신의 메시지 → 무한 루프 방지
if (sender_type === "bot") return;
// 빈 메시지 무시
if (!channel_id || !content?.trim()) return;
try {
await bot.sendMessage({
channel_id,
content: `🔁 **Echo**: ${content}`,
});
console.log(`↩️ [${sender_id}] ${content}`);
} catch (err) {
console.error("메시지 전송 실패:", err);
}
});
// 5. 시작
init().then(() => {
webhook.start();
console.log(`🚀 Webhook 서버 시작: http://localhost:${9000}/webhook`);
});실행
bash
# 환경 변수 설정 후 실행
export WORKHUB_URL=http://localhost:8080
export BOT_API_KEY=whb_xxxxxxxx_live_xxxxxxxxxxxxxxxx
export WEBHOOK_SECRET=your-webhook-secret
npx tsx echo-bot.ts동작 흐름
사용자: "안녕하세요"
↓ Workhub → Webhook POST
Echo 봇: Webhook 수신
↓ bot.sendMessage()
채널: "🔁 Echo: 안녕하세요"예제: 회의록 요약 봇
채널에서 @요약봇 [기간] 으로 호출하면, 최근 메시지를 읽어 요약 리포트를 작성하는 봇입니다. OpenAI API를 활용한 AI 요약과, AI 없이 통계 기반 요약 두 가지 모드를 지원합니다.
프로젝트 구성
bash
mkdir summary-bot && cd summary-bot
npm init -y
npm install workhub-bot-sdk openaisummary-bot.ts
typescript
import { WorkhubBot, WebhookServer } from "workhub-bot-sdk";
import type { WebhookEvent } from "workhub-bot-sdk";
import OpenAI from "openai";
// ─── 설정 ───
const bot = new WorkhubBot({
baseUrl: process.env.WORKHUB_URL || "http://localhost:8080",
apiKey: process.env.BOT_API_KEY!,
});
const webhook = new WebhookServer({
port: Number(process.env.PORT) || 9100,
secret: process.env.WEBHOOK_SECRET!,
});
// OpenAI (선택 — 없으면 통계 기반 요약)
const openai = process.env.OPENAI_API_KEY
? new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
: null;
const BOT_NAME = "요약봇";
// ─── 메시지 파싱 ───
interface SummaryRequest {
channelId: string;
limit: number;
}
function parseMention(content: string, channelId: string): SummaryRequest | null {
// "@요약봇" 또는 "@요약봇 50" 형식
const pattern = new RegExp(`@${BOT_NAME}\\s*(\\d+)?`, "i");
const match = content.match(pattern);
if (!match) return null;
return {
channelId,
limit: match[1] ? Math.min(parseInt(match[1]), 100) : 30,
};
}
// ─── AI 요약 (OpenAI) ───
async function summarizeWithAI(messages: string[]): Promise<string> {
if (!openai) throw new Error("OpenAI not configured");
const joined = messages.join("\n");
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content: `당신은 회의록 요약 전문가입니다. 채널 대화 내용을 분석하여 다음 형식으로 요약해주세요:
- **주요 논의 사항**: 핵심 주제 3~5개
- **결정 사항**: 합의된 내용
- **액션 아이템**: 후속 조치가 필요한 항목
- **참여자**: 대화에 참여한 사람 목록
간결하고 핵심만 포함하세요. 마크다운 형식을 사용하세요.`,
},
{
role: "user",
content: `다음 채널 대화를 요약해주세요:\n\n${joined}`,
},
],
max_tokens: 1000,
temperature: 0.3,
});
return response.choices[0]?.message?.content || "요약을 생성할 수 없습니다.";
}
// ─── 통계 기반 요약 (AI 없이) ───
function summarizeWithStats(
messages: Array<{ sender: string; content: string; timestamp: string }>
): string {
// 참여자별 메시지 수
const participants = new Map<string, number>();
for (const msg of messages) {
participants.set(msg.sender, (participants.get(msg.sender) || 0) + 1);
}
// 시간 범위
const first = messages[0]?.timestamp || "";
const last = messages[messages.length - 1]?.timestamp || "";
// 참여자 통계
const sorted = [...participants.entries()].sort((a, b) => b[1] - a[1]);
const participantList = sorted
.map(([name, count]) => ` - ${name}: ${count}건`)
.join("\n");
// 최근 주요 메시지 (길이 순 상위 5개 = 핵심 발언일 확률 높음)
const keyMessages = [...messages]
.filter((m) => m.content.length > 20)
.sort((a, b) => b.content.length - a.content.length)
.slice(0, 5)
.map((m) => ` - **${m.sender}**: ${m.content.slice(0, 80)}...`)
.join("\n");
return [
`📋 **채널 대화 요약 리포트**`,
``,
`📅 **기간**: ${first} ~ ${last}`,
`💬 **총 메시지**: ${messages.length}건`,
`👥 **참여자** (${participants.size}명):`,
participantList,
``,
`📌 **주요 발언**:`,
keyMessages || " - (주요 발언 없음)",
``,
`---`,
`> 💡 AI 요약을 활성화하려면 \`OPENAI_API_KEY\` 환경 변수를 설정하세요.`,
].join("\n");
}
// ─── 이벤트 핸들러 ───
webhook.on("message.created", async (event: WebhookEvent) => {
const data = event.data as Record<string, string>;
if (data.sender_type === "bot") return;
const request = parseMention(data.content, data.channel_id);
if (!request) return;
console.log(`📝 요약 요청: channel=${request.channelId}, limit=${request.limit}`);
try {
// 1. 최근 메시지 조회
const result = await bot.readMessages({
channel_id: request.channelId,
limit: request.limit,
});
const messages = result.messages || [];
if (messages.length === 0) {
await bot.sendMessage({
channel_id: request.channelId,
content: "⚠️ 요약할 메시지가 없습니다.",
});
return;
}
// 2. 요약 생성
let summary: string;
if (openai) {
// AI 요약
const textLines = messages.map(
(m: any) => `[${m.sender_name}] ${m.content}`
);
summary = await summarizeWithAI(textLines);
summary = `🤖 **AI 회의록 요약** (최근 ${messages.length}건)\n\n${summary}`;
} else {
// 통계 기반 요약
const parsed = messages.map((m: any) => ({
sender: m.sender_name || "알 수 없음",
content: m.content || "",
timestamp: m.created_at || "",
}));
summary = summarizeWithStats(parsed);
}
// 3. 요약 전송
await bot.sendMessage({
channel_id: request.channelId,
content: summary,
});
console.log(`✅ 요약 전송 완료`);
} catch (err) {
console.error("요약 실패:", err);
await bot.sendMessage({
channel_id: request.channelId,
content: "❌ 요약 생성 중 오류가 발생했습니다.",
});
}
});
// ─── 시작 ───
async function main() {
const info = await bot.authenticate();
console.log(`✅ 요약 봇 연결: ${info.bot_id}`);
console.log(`🧠 AI 모드: ${openai ? "OpenAI" : "통계 기반"}`);
webhook.start();
console.log(`🚀 Webhook 서버 시작: http://localhost:9100/webhook`);
}
main().catch(console.error);실행
bash
# 기본 (통계 기반 요약)
export WORKHUB_URL=http://localhost:8080
export BOT_API_KEY=whb_xxxxxxxx_live_xxxxxxxxxxxxxxxx
export WEBHOOK_SECRET=your-webhook-secret
npx tsx summary-bot.ts
# AI 요약 모드 (OpenAI 키 추가)
export OPENAI_API_KEY=sk-xxxxxxxxxxxx
npx tsx summary-bot.ts사용 방법
채널에서 멘션으로 호출합니다:
@요약봇 → 최근 30건 요약
@요약봇 50 → 최근 50건 요약
@요약봇 100 → 최근 100건 요약 (최대)동작 흐름
사용자: "@요약봇 50"
↓ Webhook 수신
요약 봇: 채널 메시지 50건 조회 (readMessages)
↓ OpenAI API 또는 통계 분석
↓ bot.sendMessage()
채널: "🤖 AI 회의록 요약 (최근 50건)
📌 주요 논의 사항: ..."출력 예시 (통계 기반)
📋 채널 대화 요약 리포트
📅 기간: 2026-04-05T09:00:00Z ~ 2026-04-05T11:30:00Z
💬 총 메시지: 30건
👥 참여자 (5명):
- 김개발: 12건
- 이기획: 8건
- 박디자인: 5건
- 최운영: 3건
- 정보안: 2건
📌 주요 발언:
- 김개발: API 응답 속도가 200ms 이하로 개선되어야 합니다. 현재 평균...
- 이기획: 다음 스프린트에 사용자 프로필 페이지 리뉴얼을 포함하겠...
- 박디자인: 모바일 반응형 디자인 시안을 금요일까지 공유드리겠습니...출력 예시 (AI 요약)
🤖 AI 회의록 요약 (최근 30건)
📌 주요 논의 사항:
- API 성능 최적화 (응답 속도 200ms 목표)
- 사용자 프로필 페이지 리뉴얼 범위
- 모바일 반응형 디자인 일정
✅ 결정 사항:
- API 캐시 레이어 도입 확정
- 프로필 리뉴얼은 다음 스프린트에 포함
📋 액션 아이템:
- [ ] 김개발: Redis 캐시 POC (4/8까지)
- [ ] 박디자인: 모바일 시안 공유 (4/7까지)
- [ ] 이기획: 스프린트 백로그 업데이트
👥 참여자: 김개발, 이기획, 박디자인, 최운영, 정보안예제: 태스크 관리 봇
MCP 도구를 활용한 간단한 스크립트 예제입니다.
typescript
import { WorkhubBot } from "workhub-bot-sdk";
const bot = new WorkhubBot({
baseUrl: "http://localhost:8080",
apiKey: "whb_xxxxxxxx_live_xxxxxxxxxxxxxxxx",
});
async function main() {
const info = await bot.authenticate();
console.log(`연결: ${info.bot_id} (org: ${info.org_id})`);
// 토픽 목록 조회
const topics = await bot.listChannels({ type: "topics" });
console.log(topics);
// 태스크 검색
const result = await bot.search({ query: "배포", type: "tasks" });
console.log(`검색 결과: ${result.total}건`);
}
main().catch(console.error);