Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/chat-adapter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
node_modules
coverage
70 changes: 70 additions & 0 deletions packages/chat-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Beeper Desktop API Chat Adapter

Chat SDK adapter for Beeper Desktop API built on top of `@beeper/desktop-api`.

## Install

```bash
yarn add @beeper/chat-adapter-desktop-api
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The quickstart omits packages required by the sample.

The snippet imports chat and @chat-adapter/state-memory, but the install section only adds @beeper/chat-adapter-desktop-api. @chat-adapter/state-memory is only a devDependency in this package, so the README cannot work as written for consumers. Either list the extra installs here or rewrite the sample around packages the reader is explicitly told to add.

📦 Suggested README fix
-yarn add `@beeper/chat-adapter-desktop-api`
+yarn add `@beeper/chat-adapter-desktop-api` chat `@chat-adapter/state-memory`

Also applies to: 14-16

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/README.md` at line 8, The quickstart install
instruction only adds `@beeper/chat-adapter-desktop-api` but the sample imports
"chat" and "@chat-adapter/state-memory"; update the README to either include the
missing install(s) (e.g., add yarn add for "chat" and
"@chat-adapter/state-memory") or change the example imports to only use packages
you explicitly instruct the reader to install; reference the sample import names
("chat", "@chat-adapter/state-memory") and the existing install line ("yarn add
`@beeper/chat-adapter-desktop-api`") so the consumer instructions and sample code
match.

```

## Usage

```ts
import { Chat } from "chat";
import { createMemoryState } from "@chat-adapter/state-memory";
import { createBeeperDesktopAPIAdapter } from "@beeper/chat-adapter-desktop-api";

const adapter = createBeeperDesktopAPIAdapter({
accessToken: process.env.BEEPER_DESKTOP_API_ACCESS_TOKEN!,
botUserID: process.env.BEEPER_DESKTOP_API_BOT_USER_ID,
defaultAccountID: process.env.BEEPER_DESKTOP_API_DEFAULT_ACCOUNT_ID,
});

const bot = new Chat({
userName: "bot",
adapters: { beeperDesktopAPI: adapter },
state: createMemoryState(),
});

await bot.initialize();
console.log(adapter.getDesktopAPIServerInfo());
await adapter.startEventsListener(["*"]);
```

## Implemented easy-win features

- `openDM(userID)` with either:
- scoped input `<accountID>:<participantID>`, or
- `defaultAccountID` in adapter config
- `fetchMessage(threadID, messageID)`
- `fetchChannelInfo(channelId)`
- `listThreads(channelId, { cursor, limit })`
- auto reconnect for WS listener (`wsReconnectDelayMs`, default `3000`)
- auto server version detection (`/v1/info` first, then response headers fallback)

## Logger Example

A runnable logger example is included in `examples/logger`.

Run it:

```bash
BEEPER_DESKTOP_API_ACCESS_TOKEN=... yarn example:logger
```

If you run the example file directly:

```bash
yarn build
cd examples/logger
BEEPER_DESKTOP_API_ACCESS_TOKEN=... node index.mjs
```

Optional env vars:

- `BEEPER_DESKTOP_API_BASE_URL` (default `http://localhost:23373`)
- `BEEPER_DESKTOP_API_BOT_USER_ID`
- `BEEPER_DESKTOP_API_BOT_NAME`
- `BEEPER_DESKTOP_API_CHAT_IDS` (comma-separated, default `*`)
- `BEEPER_DESKTOP_API_DEFAULT_ACCOUNT_ID`
84 changes: 84 additions & 0 deletions packages/chat-adapter/examples/logger/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Chat } from "chat";

Check failure on line 1 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"chat"` with `'chat'`
import { createMemoryState } from "@chat-adapter/state-memory";

Check failure on line 2 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"@chat-adapter/state-memory"` with `'@chat-adapter/state-memory'`
import { createBeeperDesktopAPIAdapter } from "../../dist/index.js";

Check failure on line 3 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"../../dist/index.js"` with `'../../dist/index.js'`

const accessToken = process.env.BEEPER_DESKTOP_API_ACCESS_TOKEN;
if (!accessToken) {
throw new Error("Missing BEEPER_DESKTOP_API_ACCESS_TOKEN");

Check failure on line 7 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN"` with `'Missing·BEEPER_DESKTOP_API_ACCESS_TOKEN'`
}

const baseUrl = process.env.BEEPER_DESKTOP_API_BASE_URL ?? "http://localhost:23373";

Check failure on line 10 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"http://localhost:23373"` with `'http://localhost:23373'`
const botUserID = process.env.BEEPER_DESKTOP_API_BOT_USER_ID;
const defaultAccountID = process.env.BEEPER_DESKTOP_API_DEFAULT_ACCOUNT_ID;
const chatIDs = (process.env.BEEPER_DESKTOP_API_CHAT_IDS ?? "*")

Check failure on line 13 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"*"` with `'*'`
.split(",")

Check failure on line 14 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `","` with `','`
.map((value) => value.trim())
.filter(Boolean);

const adapter = createBeeperDesktopAPIAdapter({
baseUrl,
accessToken,
botUserID,
defaultAccountID,
autoConnectEvents: false,
userName: process.env.BEEPER_DESKTOP_API_BOT_NAME ?? "bot",

Check failure on line 24 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"bot"` with `'bot'`
});

const bot = new Chat({
userName: process.env.BEEPER_DESKTOP_API_BOT_NAME ?? "bot",

Check failure on line 28 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"bot"` with `'bot'`
adapters: {
beeperDesktopAPI: adapter,
},
state: createMemoryState(),
logger: "info",

Check failure on line 33 in packages/chat-adapter/examples/logger/index.mjs

View workflow job for this annotation

GitHub Actions / lint

Replace `"info"` with `'info'`
});

bot.onNewMention(async (thread, message) => {
console.log("[mention]", {
threadId: thread.id,
messageId: message.id,
sender: message.author.userName,
text: message.text,
});
await thread.subscribe();
});

bot.onNewMessage(/[\s\S]*/u, async (thread, message) => {
console.log("[new-message]", {
threadId: thread.id,
messageId: message.id,
sender: message.author.userName,
isMention: message.isMention,
text: message.text,
});
});

bot.onSubscribedMessage(async (thread, message) => {
console.log("[subscribed-message]", {
threadId: thread.id,
messageId: message.id,
sender: message.author.userName,
text: message.text,
});
});

bot.onReaction(async (event) => {
console.log("[reaction]", {
threadId: event.threadId,
messageId: event.messageId,
added: event.added,
emoji: event.rawEmoji,
by: event.user.userName,
});
});

await bot.initialize();
console.log("BeeperDesktopAPI server info", adapter.getDesktopAPIServerInfo());
await adapter.startEventsListener(chatIDs);

console.log("BeeperDesktopAPI logger is running", {
baseUrl,
chatIDs,
});

await new Promise(() => {});
63 changes: 63 additions & 0 deletions packages/chat-adapter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"name": "@beeper/chat-adapter-desktop-api",
"version": "4.5.0",
"description": "Beeper Desktop API adapter for Chat SDK",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"dist"
],
"author": "Beeper Desktop <help@beeper.com>",
"packageManager": "yarn@1.22.22",
"private": false,
"scripts": {
"build": "yarn clean && tsup && node ./scripts/prepare-dist.mjs",
"dev": "tsup --watch",
"test": "vitest run --coverage",
"test:watch": "vitest",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist",
"example:logger": "yarn build && node examples/logger/index.mjs"
},
"dependencies": {
"@beeper/desktop-api": "^4.2.3",
"@chat-adapter/shared": "^4.13.4",
"chat": "^4.13.4",
"ws": "^8.18.3"
},
"devDependencies": {
"@vitest/coverage-v8": "^2.1.8",
"@chat-adapter/state-memory": "^4.13.4",
"@types/node": "^22.10.2",
"@types/ws": "^8.18.1",
"tsup": "^8.3.5",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
},
"repository": {
"type": "git",
"url": "git+https://github.com/beeper/desktop-api-js.git",
"directory": "packages/chat-adapter"
},
"homepage": "https://github.com/beeper/desktop-api-js/tree/main/packages/chat-adapter#readme",
"bugs": {
"url": "https://github.com/beeper/desktop-api-js/issues"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"chat",
"beeper-desktop-api",
"adapter"
],
"license": "MIT"
}
54 changes: 54 additions & 0 deletions packages/chat-adapter/scripts/prepare-dist.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packageDir = path.join(__dirname, '..');
const distDir = path.join(packageDir, 'dist');
const packageJsonPath = path.join(packageDir, 'package.json');

if (!existsSync(distDir)) {
throw new Error(`Expected dist directory at ${distDir}`);
}

const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));

const mapPath = (value) => {
if (typeof value !== 'string') return value;
return value.replace(/^\.\/dist\//, './');
};

const mapExports = (value) => {
if (typeof value === 'string') return mapPath(value);
if (Array.isArray(value)) return value.map(mapExports);
if (value && typeof value === 'object') {
return Object.fromEntries(Object.entries(value).map(([key, inner]) => [key, mapExports(inner)]));
}
return value;
};

const publishPackage = {
name: packageJson.name,
version: packageJson.version,
description: packageJson.description,
type: packageJson.type,
main: mapPath(packageJson.main),
module: mapPath(packageJson.module),
types: mapPath(packageJson.types),
exports: mapExports(packageJson.exports),
author: packageJson.author,
repository: packageJson.repository,
homepage: packageJson.homepage,
bugs: packageJson.bugs,
publishConfig: packageJson.publishConfig,
keywords: packageJson.keywords,
license: packageJson.license,
dependencies: packageJson.dependencies,
};

writeFileSync(path.join(distDir, 'package.json'), JSON.stringify(publishPackage, null, 2) + '\n');

mkdirSync(path.join(distDir, 'examples', 'logger'), { recursive: true });
cpSync(path.join(packageDir, 'README.md'), path.join(distDir, 'README.md'));
cpSync(path.join(packageDir, 'examples', 'logger', 'index.mjs'), path.join(distDir, 'examples', 'logger', 'index.mjs'));
cpSync(path.join(packageDir, '..', '..', 'LICENSE'), path.join(distDir, 'LICENSE'));
14 changes: 14 additions & 0 deletions packages/chat-adapter/src/beeper-desktop-api.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare module '@beeper/desktop-api' {
export default class BeeperDesktop {
constructor(options: { accessToken: string; baseURL: string });

messages: any;
accounts: any;
chats: any;

get<T = unknown>(path: string, options?: unknown): any;
put(path: string, options?: unknown): Promise<unknown>;
post(path: string, options?: unknown): Promise<unknown>;
delete(path: string, options?: unknown): Promise<unknown>;
}
Comment on lines +1 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
import io, json, pathlib, tarfile, urllib.request

meta = json.load(urllib.request.urlopen('https://registry.npmjs.org/%40beeper%2Fdesktop-api'))
latest = meta['dist-tags']['latest']
version = meta['versions'][latest]
print('latest:', latest)
print('types field:', version.get('types') or version.get('typings'))

tarball = version['dist']['tarball']
data = urllib.request.urlopen(tarball).read()
with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as tar:
    dts = sorted(name for name in tar.getnames() if name.endswith('.d.ts'))
    print('published .d.ts sample:')
    for name in dts[:20]:
        print(name)

print('\nlocal any-typed members:')
for line in pathlib.Path('packages/chat-adapter/src/beeper-desktop-api.d.ts').read_text().splitlines():
    if ': any' in line or '): any' in line:
        print(line)
PY

Repository: beeper/desktop-api-js

Length of output: 2646


🏁 Script executed:

#!/bin/bash
# Check if the file exists and read its content
echo "=== Checking if the file exists ==="
if [ -f "packages/chat-adapter/src/beeper-desktop-api.d.ts" ]; then
  echo "File exists. Content:"
  cat -n "packages/chat-adapter/src/beeper-desktop-api.d.ts"
else
  echo "File not found. Listing packages/chat-adapter/src directory:"
  ls -la packages/chat-adapter/src/ 2>/dev/null || echo "Directory does not exist"
fi

echo -e "\n=== Checking for `@beeper/desktop-api` in package.json ==="
find packages -name "package.json" -type f | while read pkgjson; do
  if grep -q "@beeper/desktop-api" "$pkgjson"; then
    echo "Found in: $pkgjson"
    grep "@beeper/desktop-api" "$pkgjson"
  fi
done

Repository: beeper/desktop-api-js

Length of output: 1018


🏁 Script executed:

#!/bin/bash
# Search for imports/usage of `@beeper/desktop-api` in the codebase
echo "=== Searching for imports of `@beeper/desktop-api` ==="
rg "from\s+['\"]@beeper/desktop-api" --type ts --type tsx --type js --type jsx -A 2

Repository: beeper/desktop-api-js

Length of output: 162


🏁 Script executed:

#!/bin/bash
# Check if there are any other type declaration files or references
echo "=== Looking for references to BeeperDesktop class ==="
rg "BeeperDesktop" --type ts --type tsx --type js --type jsx -A 1 -B 1 | head -50

Repository: beeper/desktop-api-js

Length of output: 163


🏁 Script executed:

#!/bin/bash
# Try simpler search without file type specifiers
echo "=== Searching for imports of `@beeper/desktop-api` ==="
rg "from.*@beeper/desktop-api|import.*@beeper/desktop-api" -A 2

echo -e "\n=== Searching for BeeperDesktop usage ==="
rg "BeeperDesktop" -A 1 -B 1

Repository: beeper/desktop-api-js

Length of output: 47448


🏁 Script executed:

#!/bin/bash
# Check if there are other SDK type files in node_modules or dist
echo "=== Checking for actual SDK types in dist/node_modules ==="
find . -path ./node_modules -prune -o -name "@beeper" -type d -print 2>/dev/null | head -20

echo -e "\n=== Looking at package.json to understand the dev setup ==="
cat packages/chat-adapter/package.json | grep -A 5 -B 5 "@beeper/desktop-api"

Repository: beeper/desktop-api-js

Length of output: 486


🏁 Script executed:

#!/bin/bash
# Verify the actual SDK typing by checking src/client.ts constructor and exports
echo "=== Actual SDK constructor from src/client.ts ==="
grep -A 10 "constructor(" src/client.ts | head -20

echo -e "\n=== SDK exports typed resources ==="
grep -E "BeeperDesktop\.(Accounts|Chats|Messages|Assets)" src/client.ts | head -10

echo -e "\n=== Verify get method signature in SDK ==="
rg "^\s+get<" src/ -A 2 | head -15

Repository: beeper/desktop-api-js

Length of output: 979


🏁 Script executed:

#!/bin/bash
# Check how the shim actually impacts type usage in chat-adapter
echo "=== How chat-adapter uses BeeperDesktop from the shim ==="
grep -A 5 "new BeeperDesktop" packages/chat-adapter/src/index.ts

echo -e "\n=== Type annotations in chat-adapter that rely on SDK types ==="
grep "BeeperDesktopAPI" packages/chat-adapter/src/index.ts | head -5

Repository: beeper/desktop-api-js

Length of output: 680


Remove this ambient module declaration and use the SDK's published types.

The official @beeper/desktop-api SDK has fully typed APIs: get<Rsp>() returns APIPromise<Rsp>, not any; and provides typed resources like BeeperDesktop.Accounts, BeeperDesktop.Chats, etc. This shim collapses all of that to any and incorrectly requires baseURL in the constructor (the actual SDK makes it optional with environment variable fallback). This defeats TypeScript's type checking—for instance, client.get<BeeperDesktopAPIMessage>(...) returns any instead of APIPromise<BeeperDesktopAPIMessage>.

Either remove the shim and rely on the SDK's published declarations, or use module augmentation to extend only the missing pieces instead of redeclaring the whole module.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat-adapter/src/beeper-desktop-api.d.ts` around lines 1 - 13, The
ambient module declaration for '@beeper/desktop-api' collapses SDK types to any
and forces a required baseURL; remove this file so the project uses the SDK's
published types, or replace it with a minimal module augmentation that only
fixes what the SDK misses (e.g., make constructor option baseURL optional and
augment types for BeeperDesktop.messages/accounts/chats and get<T>() to return
APIPromise<T>), targeting the module name '@beeper/desktop-api' and the
BeeperDesktop class and get<T>() signature so TypeScript picks up the real
APIPromise<Rsp> and typed resources instead of any.

}
Loading