@butlr/butlr-mcp-server 0.1.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/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/cache/occupancy-cache.d.ts +72 -0
- package/dist/cache/occupancy-cache.d.ts.map +1 -0
- package/dist/cache/occupancy-cache.js +166 -0
- package/dist/cache/occupancy-cache.js.map +1 -0
- package/dist/cache/topology-cache.d.ts +36 -0
- package/dist/cache/topology-cache.d.ts.map +1 -0
- package/dist/cache/topology-cache.js +74 -0
- package/dist/cache/topology-cache.js.map +1 -0
- package/dist/clients/auth-client.d.ts +22 -0
- package/dist/clients/auth-client.d.ts.map +1 -0
- package/dist/clients/auth-client.js +82 -0
- package/dist/clients/auth-client.js.map +1 -0
- package/dist/clients/graphql-client.d.ts +6 -0
- package/dist/clients/graphql-client.d.ts.map +1 -0
- package/dist/clients/graphql-client.js +106 -0
- package/dist/clients/graphql-client.js.map +1 -0
- package/dist/clients/queries/topology.d.ts +36 -0
- package/dist/clients/queries/topology.d.ts.map +1 -0
- package/dist/clients/queries/topology.js +252 -0
- package/dist/clients/queries/topology.js.map +1 -0
- package/dist/clients/reporting-client.d.ts +191 -0
- package/dist/clients/reporting-client.d.ts.map +1 -0
- package/dist/clients/reporting-client.js +353 -0
- package/dist/clients/reporting-client.js.map +1 -0
- package/dist/clients/stats-client.d.ts +119 -0
- package/dist/clients/stats-client.d.ts.map +1 -0
- package/dist/clients/stats-client.js +238 -0
- package/dist/clients/stats-client.js.map +1 -0
- package/dist/clients/types.d.ts +215 -0
- package/dist/clients/types.d.ts.map +1 -0
- package/dist/clients/types.js +6 -0
- package/dist/clients/types.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors/mcp-errors.d.ts +63 -0
- package/dist/errors/mcp-errors.d.ts.map +1 -0
- package/dist/errors/mcp-errors.js +144 -0
- package/dist/errors/mcp-errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/butlr-available-rooms.d.ts +49 -0
- package/dist/tools/butlr-available-rooms.d.ts.map +1 -0
- package/dist/tools/butlr-available-rooms.js +492 -0
- package/dist/tools/butlr-available-rooms.js.map +1 -0
- package/dist/tools/butlr-fetch-entity-details.d.ts +42 -0
- package/dist/tools/butlr-fetch-entity-details.d.ts.map +1 -0
- package/dist/tools/butlr-fetch-entity-details.js +276 -0
- package/dist/tools/butlr-fetch-entity-details.js.map +1 -0
- package/dist/tools/butlr-get-asset-details.d.ts +51 -0
- package/dist/tools/butlr-get-asset-details.d.ts.map +1 -0
- package/dist/tools/butlr-get-asset-details.js +391 -0
- package/dist/tools/butlr-get-asset-details.js.map +1 -0
- package/dist/tools/butlr-get-current-occupancy.d.ts +21 -0
- package/dist/tools/butlr-get-current-occupancy.d.ts.map +1 -0
- package/dist/tools/butlr-get-current-occupancy.js +126 -0
- package/dist/tools/butlr-get-current-occupancy.js.map +1 -0
- package/dist/tools/butlr-get-occupancy-timeseries.d.ts +31 -0
- package/dist/tools/butlr-get-occupancy-timeseries.d.ts.map +1 -0
- package/dist/tools/butlr-get-occupancy-timeseries.js +145 -0
- package/dist/tools/butlr-get-occupancy-timeseries.js.map +1 -0
- package/dist/tools/butlr-hardware-snapshot.d.ts +55 -0
- package/dist/tools/butlr-hardware-snapshot.d.ts.map +1 -0
- package/dist/tools/butlr-hardware-snapshot.js +556 -0
- package/dist/tools/butlr-hardware-snapshot.js.map +1 -0
- package/dist/tools/butlr-list-topology.d.ts +27 -0
- package/dist/tools/butlr-list-topology.d.ts.map +1 -0
- package/dist/tools/butlr-list-topology.js +241 -0
- package/dist/tools/butlr-list-topology.js.map +1 -0
- package/dist/tools/butlr-search-assets.d.ts +53 -0
- package/dist/tools/butlr-search-assets.d.ts.map +1 -0
- package/dist/tools/butlr-search-assets.js +206 -0
- package/dist/tools/butlr-search-assets.js.map +1 -0
- package/dist/tools/butlr-space-busyness.d.ts +23 -0
- package/dist/tools/butlr-space-busyness.d.ts.map +1 -0
- package/dist/tools/butlr-space-busyness.js +304 -0
- package/dist/tools/butlr-space-busyness.js.map +1 -0
- package/dist/tools/butlr-traffic-flow.d.ts +39 -0
- package/dist/tools/butlr-traffic-flow.d.ts.map +1 -0
- package/dist/tools/butlr-traffic-flow.js +369 -0
- package/dist/tools/butlr-traffic-flow.js.map +1 -0
- package/dist/types/responses.d.ts +253 -0
- package/dist/types/responses.d.ts.map +1 -0
- package/dist/types/responses.js +8 -0
- package/dist/types/responses.js.map +1 -0
- package/dist/utils/asset-flattener.d.ts +50 -0
- package/dist/utils/asset-flattener.d.ts.map +1 -0
- package/dist/utils/asset-flattener.js +131 -0
- package/dist/utils/asset-flattener.js.map +1 -0
- package/dist/utils/asset-helpers.d.ts +8 -0
- package/dist/utils/asset-helpers.d.ts.map +1 -0
- package/dist/utils/asset-helpers.js +24 -0
- package/dist/utils/asset-helpers.js.map +1 -0
- package/dist/utils/field-validator.d.ts +29 -0
- package/dist/utils/field-validator.d.ts.map +1 -0
- package/dist/utils/field-validator.js +197 -0
- package/dist/utils/field-validator.js.map +1 -0
- package/dist/utils/fuzzy-match.d.ts +29 -0
- package/dist/utils/fuzzy-match.d.ts.map +1 -0
- package/dist/utils/fuzzy-match.js +70 -0
- package/dist/utils/fuzzy-match.js.map +1 -0
- package/dist/utils/graphql-helpers.d.ts +29 -0
- package/dist/utils/graphql-helpers.d.ts.map +1 -0
- package/dist/utils/graphql-helpers.js +43 -0
- package/dist/utils/graphql-helpers.js.map +1 -0
- package/dist/utils/natural-language.d.ts +73 -0
- package/dist/utils/natural-language.d.ts.map +1 -0
- package/dist/utils/natural-language.js +172 -0
- package/dist/utils/natural-language.js.map +1 -0
- package/dist/utils/occupancy-helpers.d.ts +63 -0
- package/dist/utils/occupancy-helpers.d.ts.map +1 -0
- package/dist/utils/occupancy-helpers.js +203 -0
- package/dist/utils/occupancy-helpers.js.map +1 -0
- package/dist/utils/path-builder.d.ts +10 -0
- package/dist/utils/path-builder.d.ts.map +1 -0
- package/dist/utils/path-builder.js +34 -0
- package/dist/utils/path-builder.js.map +1 -0
- package/dist/utils/time-range-validator.d.ts +13 -0
- package/dist/utils/time-range-validator.d.ts.map +1 -0
- package/dist/utils/time-range-validator.js +65 -0
- package/dist/utils/time-range-validator.js.map +1 -0
- package/dist/utils/timezone-helpers.d.ts +49 -0
- package/dist/utils/timezone-helpers.d.ts.map +1 -0
- package/dist/utils/timezone-helpers.js +209 -0
- package/dist/utils/timezone-helpers.js.map +1 -0
- package/dist/utils/tree-formatter.d.ts +23 -0
- package/dist/utils/tree-formatter.d.ts.map +1 -0
- package/dist/utils/tree-formatter.js +258 -0
- package/dist/utils/tree-formatter.js.map +1 -0
- package/package.json +93 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Butlr Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Butlr MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@butlr/butlr-mcp-server)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
|
|
7
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that connects AI assistants to [Butlr's](https://www.butlr.com) occupancy sensing platform. Query real-time space utilization, search facility assets, and analyze occupancy patterns through natural language.
|
|
8
|
+
|
|
9
|
+
### What you can do
|
|
10
|
+
|
|
11
|
+
- **Find available spaces** — "Are there any free conference rooms right now with capacity for 8?"
|
|
12
|
+
- **Monitor occupancy** — "How busy is the cafe? Should I head there now?"
|
|
13
|
+
- **Analyze trends** — "Show me occupancy patterns for Floor 3 over the past week"
|
|
14
|
+
- **Search your portfolio** — "Find all rooms named 'huddle' across Building 2"
|
|
15
|
+
- **Check sensor health** — "Which sensors are offline or need battery replacement?"
|
|
16
|
+
- **Track foot traffic** — "How many people entered the main lobby today?"
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- [Node.js](https://nodejs.org/) 20 or higher
|
|
21
|
+
- An MCP-compatible client ([Claude Desktop](https://claude.ai/download), [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [VS Code](https://code.visualstudio.com/), [Cursor](https://cursor.com/), etc.)
|
|
22
|
+
- Butlr API credentials (OAuth2 client ID, client secret, and organization ID) — see [Getting API Credentials](#getting-api-credentials)
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
<details open>
|
|
27
|
+
<summary><strong>Claude Desktop</strong></summary>
|
|
28
|
+
|
|
29
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"butlr": {
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": ["-y", "@butlr/butlr-mcp-server@latest"],
|
|
37
|
+
"env": {
|
|
38
|
+
"BUTLR_CLIENT_ID": "your_client_id",
|
|
39
|
+
"BUTLR_CLIENT_SECRET": "your_client_secret",
|
|
40
|
+
"BUTLR_ORG_ID": "your_org_id"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary><strong>Claude Code</strong></summary>
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
claude mcp add butlr -- npx -y @butlr/butlr-mcp-server@latest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then set environment variables in your shell or `.env` file:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
export BUTLR_CLIENT_ID=your_client_id
|
|
60
|
+
export BUTLR_CLIENT_SECRET=your_client_secret
|
|
61
|
+
export BUTLR_ORG_ID=your_org_id
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
</details>
|
|
65
|
+
|
|
66
|
+
<details>
|
|
67
|
+
<summary><strong>VS Code (Copilot)</strong></summary>
|
|
68
|
+
|
|
69
|
+
Add to `.vscode/mcp.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"servers": {
|
|
74
|
+
"butlr": {
|
|
75
|
+
"type": "stdio",
|
|
76
|
+
"command": "npx",
|
|
77
|
+
"args": ["-y", "@butlr/butlr-mcp-server@latest"],
|
|
78
|
+
"env": {
|
|
79
|
+
"BUTLR_CLIENT_ID": "your_client_id",
|
|
80
|
+
"BUTLR_CLIENT_SECRET": "your_client_secret",
|
|
81
|
+
"BUTLR_ORG_ID": "your_org_id"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
</details>
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary><strong>Cursor</strong></summary>
|
|
92
|
+
|
|
93
|
+
Add to `.cursor/mcp.json`:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"mcpServers": {
|
|
98
|
+
"butlr": {
|
|
99
|
+
"command": "npx",
|
|
100
|
+
"args": ["-y", "@butlr/butlr-mcp-server@latest"],
|
|
101
|
+
"env": {
|
|
102
|
+
"BUTLR_CLIENT_ID": "your_client_id",
|
|
103
|
+
"BUTLR_CLIENT_SECRET": "your_client_secret",
|
|
104
|
+
"BUTLR_ORG_ID": "your_org_id"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
</details>
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary><strong>Other MCP clients</strong></summary>
|
|
115
|
+
|
|
116
|
+
For any MCP client that supports stdio transport, use this command:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
npx -y @butlr/butlr-mcp-server@latest
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Pass the required environment variables (`BUTLR_CLIENT_ID`, `BUTLR_CLIENT_SECRET`, `BUTLR_ORG_ID`) through your client's configuration.
|
|
123
|
+
|
|
124
|
+
</details>
|
|
125
|
+
|
|
126
|
+
## Available Tools
|
|
127
|
+
|
|
128
|
+
| Tool | Description | Try asking... |
|
|
129
|
+
|------|-------------|---------------|
|
|
130
|
+
| `butlr_search_assets` | Search for assets (sites, buildings, floors, rooms, sensors) by name with fuzzy matching | "Find the main lobby" |
|
|
131
|
+
| `butlr_get_asset_details` | Get comprehensive details for specific assets by ID with batch support | "Show me details for Conference Room 401" |
|
|
132
|
+
| `butlr_hardware_snapshot` | Device health check: online/offline status and battery levels across your portfolio | "Which sensors need battery replacement?" |
|
|
133
|
+
| `butlr_available_rooms` | Find currently unoccupied rooms, filterable by capacity and tags | "Are there any free conference rooms right now?" |
|
|
134
|
+
| `butlr_space_busyness` | Current occupancy with qualitative labels (quiet/moderate/busy) and trend comparison | "How busy is the cafe right now?" |
|
|
135
|
+
| `butlr_traffic_flow` | Entry/exit counts with hourly breakdown for traffic-mode sensors | "How many people entered the lobby today?" |
|
|
136
|
+
| `butlr_list_topology` | Display org hierarchy tree with flexible depth control | "Show me all floors in Building 2" |
|
|
137
|
+
| `butlr_fetch_entity_details` | Retrieve specific fields for entities by ID (minimal token usage) | "What's the timezone for this site?" |
|
|
138
|
+
| `butlr_get_occupancy_timeseries` | Historical occupancy data with configurable time ranges | "Show occupancy trends for Floor 3 this week" |
|
|
139
|
+
| `butlr_get_current_occupancy` | Real-time occupancy snapshot (last 5 minutes median) | "How many people are on Floor 2 right now?" |
|
|
140
|
+
|
|
141
|
+
All tools are **read-only** — the server cannot modify any data in your Butlr account.
|
|
142
|
+
|
|
143
|
+
## Configuration
|
|
144
|
+
|
|
145
|
+
| Variable | Required | Default | Description |
|
|
146
|
+
|----------|----------|---------|-------------|
|
|
147
|
+
| `BUTLR_CLIENT_ID` | **Yes** | - | OAuth2 client ID |
|
|
148
|
+
| `BUTLR_CLIENT_SECRET` | **Yes** | - | OAuth2 client secret |
|
|
149
|
+
| `BUTLR_ORG_ID` | **Yes** | - | Organization ID |
|
|
150
|
+
| `BUTLR_BASE_URL` | No | `https://api.butlr.io` | API base URL |
|
|
151
|
+
| `BUTLR_TIMEZONE` | No | `UTC` | Default timezone |
|
|
152
|
+
| `MCP_CACHE_TOPO_TTL` | No | `600` | Topology cache TTL (seconds) |
|
|
153
|
+
| `DEBUG` | No | - | Set to `butlr-mcp` for verbose logging |
|
|
154
|
+
|
|
155
|
+
## Getting API Credentials
|
|
156
|
+
|
|
157
|
+
To use this MCP server, you need OAuth2 API credentials from Butlr:
|
|
158
|
+
|
|
159
|
+
1. **Contact your Butlr account representative** or visit [butlr.com](https://www.butlr.com) to request API access
|
|
160
|
+
2. You will receive a **Client ID**, **Client Secret**, and **Organization ID**
|
|
161
|
+
3. These credentials provide read-only access scoped to your organization's data
|
|
162
|
+
|
|
163
|
+
## Troubleshooting
|
|
164
|
+
|
|
165
|
+
**Authentication errors** — Verify your `BUTLR_CLIENT_ID`, `BUTLR_CLIENT_SECRET`, and `BUTLR_ORG_ID` are correct. Tokens are refreshed automatically.
|
|
166
|
+
|
|
167
|
+
**Rate limiting** — The server handles rate limits automatically with retry logic. If you see persistent rate limit errors, reduce the frequency of requests.
|
|
168
|
+
|
|
169
|
+
**No data returned** — Ensure your organization has active sensors deployed. Use `butlr_search_assets` to verify your org has discoverable assets.
|
|
170
|
+
|
|
171
|
+
**Debug logging** — Set `DEBUG=butlr-mcp` in your environment to see detailed request/response logs on stderr.
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for full development workflow and standards.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm install # Install dependencies
|
|
179
|
+
npm run build # Build TypeScript
|
|
180
|
+
npm test # Run tests (442 tests)
|
|
181
|
+
npm run typecheck # Type checking
|
|
182
|
+
npm run lint # ESLint
|
|
183
|
+
npm run dev # Dev with hot-reload
|
|
184
|
+
npm run dev:debug # Dev with debug logging
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Security
|
|
188
|
+
|
|
189
|
+
- All tools enforce read-only API access
|
|
190
|
+
- Never commit credentials to version control
|
|
191
|
+
- See [SECURITY.md](SECURITY.md) for vulnerability disclosure
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT - see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { LRUCache } from "lru-cache";
|
|
2
|
+
interface OccupancyCacheEntry {
|
|
3
|
+
occupancy: number;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
asset_id: string;
|
|
6
|
+
asset_type: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* LRU cache for current occupancy data with short TTL
|
|
10
|
+
* Reduces API calls for frequently accessed current occupancy queries
|
|
11
|
+
*
|
|
12
|
+
* Cache key format: occupancy:{asset_id}:{minute_bucket}
|
|
13
|
+
* Example: occupancy:room_123:202501131400 (truncated to minute)
|
|
14
|
+
*/
|
|
15
|
+
export declare const occupancyCache: LRUCache<string, OccupancyCacheEntry, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Generate cache key for occupancy queries
|
|
18
|
+
* Includes minute bucket to group queries within same minute
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateOccupancyCacheKey(assetId: string, timestamp?: Date): string;
|
|
21
|
+
/**
|
|
22
|
+
* Get cached occupancy data
|
|
23
|
+
*/
|
|
24
|
+
export declare function getCachedOccupancy(assetId: string, timestamp?: Date): OccupancyCacheEntry | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Get cached occupancy for multiple assets
|
|
27
|
+
* Returns { hits: {}, misses: [] }
|
|
28
|
+
*/
|
|
29
|
+
export declare function getBulkCachedOccupancy(assetIds: string[], timestamp?: Date): {
|
|
30
|
+
hits: Record<string, OccupancyCacheEntry>;
|
|
31
|
+
misses: string[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Store occupancy data in cache
|
|
35
|
+
*/
|
|
36
|
+
export declare function setCachedOccupancy(assetId: string, occupancy: number, assetType: string, timestamp?: Date): void;
|
|
37
|
+
/**
|
|
38
|
+
* Store multiple occupancy values
|
|
39
|
+
*/
|
|
40
|
+
export declare function setBulkCachedOccupancy(entries: Array<{
|
|
41
|
+
assetId: string;
|
|
42
|
+
occupancy: number;
|
|
43
|
+
assetType: string;
|
|
44
|
+
timestamp?: Date;
|
|
45
|
+
}>): void;
|
|
46
|
+
/**
|
|
47
|
+
* Clear all cached occupancy data
|
|
48
|
+
*/
|
|
49
|
+
export declare function clearOccupancyCache(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Invalidate cache for specific asset
|
|
52
|
+
*/
|
|
53
|
+
export declare function invalidateAssetOccupancy(assetId: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Get cache statistics
|
|
56
|
+
*/
|
|
57
|
+
export declare function getOccupancyCacheStats(): {
|
|
58
|
+
size: number;
|
|
59
|
+
maxSize: number;
|
|
60
|
+
ttl: number;
|
|
61
|
+
utilizationPercent: number;
|
|
62
|
+
};
|
|
63
|
+
export declare function recordCacheHit(): void;
|
|
64
|
+
export declare function recordCacheMiss(): void;
|
|
65
|
+
export declare function getCacheHitRate(): {
|
|
66
|
+
hits: number;
|
|
67
|
+
misses: number;
|
|
68
|
+
hitRate: number;
|
|
69
|
+
};
|
|
70
|
+
export declare function resetCacheMetrics(): void;
|
|
71
|
+
export {};
|
|
72
|
+
//# sourceMappingURL=occupancy-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"occupancy-cache.d.ts","sourceRoot":"","sources":["../../src/cache/occupancy-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AASrC,UAAU,mBAAmB;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,gDAKzB,CAAC;AAEH;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAanF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,IAAI,GACf,mBAAmB,GAAG,SAAS,CAkBjC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAAE,EAClB,SAAS,CAAC,EAAE,IAAI,GACf;IACD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC1C,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAoBA;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,IAAI,GACf,IAAI,CAkBN;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC,GACD,IAAI,CAQN;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAM1C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAa9D;AAED;;GAEG;AACH,wBAAgB,sBAAsB;;;;;EAOrC;AASD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED,wBAAgB,eAAe,IAAI;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAOA;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { LRUCache } from "lru-cache";
|
|
2
|
+
/**
|
|
3
|
+
* Occupancy cache configuration
|
|
4
|
+
* TTL: 60 seconds (fast-changing data)
|
|
5
|
+
*/
|
|
6
|
+
const CACHE_TTL_SECONDS = parseInt(process.env.MCP_CACHE_OCCUPANCY_TTL || "60", 10) * 1000; // Convert to milliseconds
|
|
7
|
+
const MAX_CACHE_ENTRIES = 500; // More entries than topology (many rooms)
|
|
8
|
+
/**
|
|
9
|
+
* LRU cache for current occupancy data with short TTL
|
|
10
|
+
* Reduces API calls for frequently accessed current occupancy queries
|
|
11
|
+
*
|
|
12
|
+
* Cache key format: occupancy:{asset_id}:{minute_bucket}
|
|
13
|
+
* Example: occupancy:room_123:202501131400 (truncated to minute)
|
|
14
|
+
*/
|
|
15
|
+
export const occupancyCache = new LRUCache({
|
|
16
|
+
max: MAX_CACHE_ENTRIES,
|
|
17
|
+
ttl: CACHE_TTL_SECONDS,
|
|
18
|
+
updateAgeOnGet: true, // Reset TTL on cache hit
|
|
19
|
+
updateAgeOnHas: false,
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Generate cache key for occupancy queries
|
|
23
|
+
* Includes minute bucket to group queries within same minute
|
|
24
|
+
*/
|
|
25
|
+
export function generateOccupancyCacheKey(assetId, timestamp) {
|
|
26
|
+
const now = timestamp || new Date();
|
|
27
|
+
// Truncate to minute (ignore seconds)
|
|
28
|
+
const minuteBucket = new Date(now);
|
|
29
|
+
minuteBucket.setSeconds(0, 0);
|
|
30
|
+
const bucketStr = minuteBucket
|
|
31
|
+
.toISOString()
|
|
32
|
+
.replace(/[-:T]/g, "") // Remove dashes, colons, and T
|
|
33
|
+
.substring(0, 12); // YYYYMMDDHHmm
|
|
34
|
+
return `occupancy:${assetId}:${bucketStr}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get cached occupancy data
|
|
38
|
+
*/
|
|
39
|
+
export function getCachedOccupancy(assetId, timestamp) {
|
|
40
|
+
const key = generateOccupancyCacheKey(assetId, timestamp);
|
|
41
|
+
const cached = occupancyCache.get(key);
|
|
42
|
+
// Track cache hit/miss metrics
|
|
43
|
+
if (cached) {
|
|
44
|
+
recordCacheHit();
|
|
45
|
+
if (process.env.DEBUG) {
|
|
46
|
+
console.error(`[occupancy-cache] Cache HIT for ${assetId}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
recordCacheMiss();
|
|
51
|
+
if (process.env.DEBUG) {
|
|
52
|
+
console.error(`[occupancy-cache] Cache MISS for ${assetId}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get cached occupancy for multiple assets
|
|
59
|
+
* Returns { hits: {}, misses: [] }
|
|
60
|
+
*/
|
|
61
|
+
export function getBulkCachedOccupancy(assetIds, timestamp) {
|
|
62
|
+
const hits = {};
|
|
63
|
+
const misses = [];
|
|
64
|
+
for (const assetId of assetIds) {
|
|
65
|
+
const cached = getCachedOccupancy(assetId, timestamp);
|
|
66
|
+
if (cached) {
|
|
67
|
+
hits[assetId] = cached;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
misses.push(assetId);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (process.env.DEBUG) {
|
|
74
|
+
console.error(`[occupancy-cache] Bulk query: ${Object.keys(hits).length} hits, ${misses.length} misses`);
|
|
75
|
+
}
|
|
76
|
+
return { hits, misses };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Store occupancy data in cache
|
|
80
|
+
*/
|
|
81
|
+
export function setCachedOccupancy(assetId, occupancy, assetType, timestamp) {
|
|
82
|
+
const now = timestamp || new Date();
|
|
83
|
+
const key = generateOccupancyCacheKey(assetId, now);
|
|
84
|
+
const entry = {
|
|
85
|
+
occupancy,
|
|
86
|
+
timestamp: now.toISOString(),
|
|
87
|
+
asset_id: assetId,
|
|
88
|
+
asset_type: assetType,
|
|
89
|
+
};
|
|
90
|
+
occupancyCache.set(key, entry);
|
|
91
|
+
if (process.env.DEBUG) {
|
|
92
|
+
console.error(`[occupancy-cache] Cached occupancy for ${assetId}: ${occupancy} (TTL: ${CACHE_TTL_SECONDS / 1000}s)`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Store multiple occupancy values
|
|
97
|
+
*/
|
|
98
|
+
export function setBulkCachedOccupancy(entries) {
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
setCachedOccupancy(entry.assetId, entry.occupancy, entry.assetType, entry.timestamp);
|
|
101
|
+
}
|
|
102
|
+
if (process.env.DEBUG) {
|
|
103
|
+
console.error(`[occupancy-cache] Bulk cached ${entries.length} occupancy values`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Clear all cached occupancy data
|
|
108
|
+
*/
|
|
109
|
+
export function clearOccupancyCache() {
|
|
110
|
+
occupancyCache.clear();
|
|
111
|
+
if (process.env.DEBUG) {
|
|
112
|
+
console.error("[occupancy-cache] Cache cleared");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Invalidate cache for specific asset
|
|
117
|
+
*/
|
|
118
|
+
export function invalidateAssetOccupancy(assetId) {
|
|
119
|
+
// Delete all keys for this asset (all time buckets)
|
|
120
|
+
let deleted = 0;
|
|
121
|
+
for (const key of occupancyCache.keys()) {
|
|
122
|
+
if (key.startsWith(`occupancy:${assetId}:`)) {
|
|
123
|
+
occupancyCache.delete(key);
|
|
124
|
+
deleted++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (process.env.DEBUG && deleted > 0) {
|
|
128
|
+
console.error(`[occupancy-cache] Invalidated ${deleted} cache entries for ${assetId}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get cache statistics
|
|
133
|
+
*/
|
|
134
|
+
export function getOccupancyCacheStats() {
|
|
135
|
+
return {
|
|
136
|
+
size: occupancyCache.size,
|
|
137
|
+
maxSize: MAX_CACHE_ENTRIES,
|
|
138
|
+
ttl: CACHE_TTL_SECONDS / 1000, // Convert back to seconds for display
|
|
139
|
+
utilizationPercent: (occupancyCache.size / MAX_CACHE_ENTRIES) * 100,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Calculate cache hit rate over time
|
|
144
|
+
* (For monitoring/debugging)
|
|
145
|
+
*/
|
|
146
|
+
let cacheHits = 0;
|
|
147
|
+
let cacheMisses = 0;
|
|
148
|
+
export function recordCacheHit() {
|
|
149
|
+
cacheHits++;
|
|
150
|
+
}
|
|
151
|
+
export function recordCacheMiss() {
|
|
152
|
+
cacheMisses++;
|
|
153
|
+
}
|
|
154
|
+
export function getCacheHitRate() {
|
|
155
|
+
const total = cacheHits + cacheMisses;
|
|
156
|
+
return {
|
|
157
|
+
hits: cacheHits,
|
|
158
|
+
misses: cacheMisses,
|
|
159
|
+
hitRate: total > 0 ? (cacheHits / total) * 100 : 0,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export function resetCacheMetrics() {
|
|
163
|
+
cacheHits = 0;
|
|
164
|
+
cacheMisses = 0;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=occupancy-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"occupancy-cache.js","sourceRoot":"","sources":["../../src/cache/occupancy-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { LRUCache } from "lru-cache";
|
|
2
|
+
interface CacheEntry {
|
|
3
|
+
data: Record<string, unknown>;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* LRU cache for topology data with TTL
|
|
8
|
+
* Reduces API calls for frequently accessed topology structures
|
|
9
|
+
*/
|
|
10
|
+
export declare const topologyCache: LRUCache<string, CacheEntry, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Generate cache key for topology queries
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateTopologyCacheKey(orgId: string, includeDevices: boolean, includeZones: boolean, siteIds?: string[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Get cached topology data
|
|
17
|
+
*/
|
|
18
|
+
export declare function getCachedTopology(key: string): CacheEntry | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Store topology data in cache
|
|
21
|
+
*/
|
|
22
|
+
export declare function setCachedTopology(key: string, data: Record<string, unknown>): void;
|
|
23
|
+
/**
|
|
24
|
+
* Clear all cached topology data
|
|
25
|
+
*/
|
|
26
|
+
export declare function clearTopologyCache(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Get cache statistics
|
|
29
|
+
*/
|
|
30
|
+
export declare function getCacheStats(): {
|
|
31
|
+
size: number;
|
|
32
|
+
maxSize: number;
|
|
33
|
+
ttl: number;
|
|
34
|
+
};
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=topology-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topology-cache.d.ts","sourceRoot":"","sources":["../../src/cache/topology-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAQrC,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,uCAKxB,CAAC;AAEH;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,OAAO,EACvB,YAAY,EAAE,OAAO,EACrB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,MAAM,CAWR;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAUrE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAalF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC;AAED;;GAEG;AACH,wBAAgB,aAAa;;;;EAM5B"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { LRUCache } from "lru-cache";
|
|
2
|
+
/**
|
|
3
|
+
* Topology cache configuration
|
|
4
|
+
*/
|
|
5
|
+
const CACHE_TTL_SECONDS = parseInt(process.env.MCP_CACHE_TOPO_TTL || "600", 10) * 1000; // Convert to milliseconds
|
|
6
|
+
const MAX_CACHE_ENTRIES = 100;
|
|
7
|
+
/**
|
|
8
|
+
* LRU cache for topology data with TTL
|
|
9
|
+
* Reduces API calls for frequently accessed topology structures
|
|
10
|
+
*/
|
|
11
|
+
export const topologyCache = new LRUCache({
|
|
12
|
+
max: MAX_CACHE_ENTRIES,
|
|
13
|
+
ttl: CACHE_TTL_SECONDS,
|
|
14
|
+
updateAgeOnGet: true, // Reset TTL on cache hit
|
|
15
|
+
updateAgeOnHas: false,
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Generate cache key for topology queries
|
|
19
|
+
*/
|
|
20
|
+
export function generateTopologyCacheKey(orgId, includeDevices, includeZones, siteIds) {
|
|
21
|
+
const parts = ["topo", orgId];
|
|
22
|
+
if (siteIds && siteIds.length > 0) {
|
|
23
|
+
parts.push(`sites:${siteIds.sort().join(",")}`);
|
|
24
|
+
}
|
|
25
|
+
parts.push(`devices:${includeDevices}`);
|
|
26
|
+
parts.push(`zones:${includeZones}`);
|
|
27
|
+
return parts.join(":");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get cached topology data
|
|
31
|
+
*/
|
|
32
|
+
export function getCachedTopology(key) {
|
|
33
|
+
const cached = topologyCache.get(key);
|
|
34
|
+
if (cached && process.env.DEBUG) {
|
|
35
|
+
console.error(`[topology-cache] Cache HIT for key: ${key}`);
|
|
36
|
+
}
|
|
37
|
+
else if (process.env.DEBUG) {
|
|
38
|
+
console.error(`[topology-cache] Cache MISS for key: ${key}`);
|
|
39
|
+
}
|
|
40
|
+
return cached;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Store topology data in cache
|
|
44
|
+
*/
|
|
45
|
+
export function setCachedTopology(key, data) {
|
|
46
|
+
const entry = {
|
|
47
|
+
data,
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
};
|
|
50
|
+
topologyCache.set(key, entry);
|
|
51
|
+
if (process.env.DEBUG) {
|
|
52
|
+
console.error(`[topology-cache] Cached data for key: ${key} (TTL: ${CACHE_TTL_SECONDS / 1000}s)`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Clear all cached topology data
|
|
57
|
+
*/
|
|
58
|
+
export function clearTopologyCache() {
|
|
59
|
+
topologyCache.clear();
|
|
60
|
+
if (process.env.DEBUG) {
|
|
61
|
+
console.error("[topology-cache] Cache cleared");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get cache statistics
|
|
66
|
+
*/
|
|
67
|
+
export function getCacheStats() {
|
|
68
|
+
return {
|
|
69
|
+
size: topologyCache.size,
|
|
70
|
+
maxSize: MAX_CACHE_ENTRIES,
|
|
71
|
+
ttl: CACHE_TTL_SECONDS / 1000, // Convert back to seconds for display
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=topology-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topology-cache.js","sourceRoot":"","sources":["../../src/cache/topology-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC;;GAEG;AACH,MAAM,iBAAiB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,KAAK,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,0BAA0B;AAClH,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAO9B;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,QAAQ,CAAqB;IAC5D,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,iBAAiB;IACtB,cAAc,EAAE,IAAI,EAAE,yBAAyB;IAC/C,cAAc,EAAE,KAAK;CACtB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAAa,EACb,cAAuB,EACvB,YAAqB,EACrB,OAAkB;IAElB,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAE9B,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,WAAW,cAAc,EAAE,CAAC,CAAC;IACxC,KAAK,CAAC,IAAI,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;IAEpC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAEtC,IAAI,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,uCAAuC,GAAG,EAAE,CAAC,CAAC;IAC9D,CAAC;SAAM,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAA6B;IAC1E,MAAM,KAAK,GAAe;QACxB,IAAI;QACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IAEF,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAE9B,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CACX,yCAAyC,GAAG,UAAU,iBAAiB,GAAG,IAAI,IAAI,CACnF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,aAAa,CAAC,KAAK,EAAE,CAAC;IAEtB,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,IAAI,EAAE,aAAa,CAAC,IAAI;QACxB,OAAO,EAAE,iBAAiB;QAC1B,GAAG,EAAE,iBAAiB,GAAG,IAAI,EAAE,sCAAsC;KACtE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Butlr Authentication Client
|
|
3
|
+
* Manages OAuth2 client credentials flow for API authentication
|
|
4
|
+
*/
|
|
5
|
+
declare class ButlrAuthClient {
|
|
6
|
+
private token;
|
|
7
|
+
private tokenExpiry;
|
|
8
|
+
private readonly clientId;
|
|
9
|
+
private readonly clientSecret;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* Get a valid access token, fetching a new one if needed
|
|
13
|
+
*/
|
|
14
|
+
getToken(): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Clear cached token (useful for testing)
|
|
17
|
+
*/
|
|
18
|
+
clearToken(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const authClient: ButlrAuthClient;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=auth-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../../src/clients/auth-client.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,cAAM,eAAe;IACnB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;;IAOtC;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAgEjC;;OAEG;IACH,UAAU,IAAI,IAAI;CAInB;AAGD,eAAO,MAAM,UAAU,iBAAwB,CAAC"}
|