@tightknitai/tightknit 0.1.0-alpha.7 → 0.1.0-alpha.9
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 +46 -122
- package/bin/tightknit.js +2 -1
- package/dist/{chunk-PRA5JZ27.js → chunk-WSRTDKVW.js} +55 -17
- package/dist/chunk-WSRTDKVW.js.map +1 -0
- package/dist/cli/index.js +252 -103
- package/dist/cli/index.js.map +1 -1
- package/dist/mcp/server.js +144 -171
- package/dist/mcp/server.js.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-PRA5JZ27.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,94 +1,57 @@
|
|
|
1
1
|
# @tightknitai/tightknit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Setup
|
|
6
|
-
|
|
7
|
-
### Prerequisites
|
|
8
|
-
|
|
9
|
-
- Node.js 18+ (for native `fetch`)
|
|
10
|
-
- A Tightknit API key
|
|
11
|
-
|
|
12
|
-
### Create an API Key
|
|
13
|
-
|
|
14
|
-
1. Go to **Studio > Settings > Integrations > API Keys**
|
|
15
|
-
2. Click **Create API key**
|
|
16
|
-
3. Give it a descriptive name (e.g. "CLI")
|
|
17
|
-
4. Select the permissions for the APIs the key needs access to. Enable all permissions the CLI commands you plan to use require:
|
|
18
|
-
- **Calendar Events** — `events list`, `events get`, `events create`, `events delete`, `events update-attendee`
|
|
19
|
-
- **Feeds** — `feeds list`, `feeds get`, `feeds posts`
|
|
20
|
-
- **Posts** — `posts get`
|
|
21
|
-
- **Members** — `members add` (Enterprise), `members check`
|
|
22
|
-
- **Messages** — `messages send`
|
|
23
|
-
- **Groups** — `groups add-member`
|
|
24
|
-
- **Awards** — `awards assign`
|
|
25
|
-
- **Search** — `search query` (Beta)
|
|
26
|
-
5. Copy the key (starts with `sk_`) — it is only shown once
|
|
27
|
-
|
|
28
|
-
### Install and Configure
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
# Via npm
|
|
32
|
-
npx -p @tightknitai/tightknit@alpha tightknit config set api-key sk_your_key_here
|
|
3
|
+
> **Alpha** — APIs and commands may change between releases.
|
|
33
4
|
|
|
34
|
-
|
|
35
|
-
pnpm install
|
|
36
|
-
npx tsx packages/tightknit-cli/bin/tightknit.ts config set api-key sk_your_key_here
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
The API key is stored locally at `~/.config/tightknit/config.json`. Alternatively, set the `TIGHTKNIT_API_KEY` environment variable (takes precedence over stored config).
|
|
5
|
+
Command-line interface and MCP server for the [Tightknit API](https://docs.tightknit.ai/api-reference/introduction).
|
|
40
6
|
|
|
41
|
-
|
|
7
|
+
**Full documentation:** [docs.tightknit.ai/integrations/cli](https://docs.tightknit.ai/integrations/cli)
|
|
42
8
|
|
|
43
|
-
|
|
9
|
+
## Quick Start
|
|
44
10
|
|
|
45
11
|
```bash
|
|
46
|
-
#
|
|
47
|
-
|
|
12
|
+
# Install globally
|
|
13
|
+
npm install -g @tightknitai/tightknit@alpha
|
|
48
14
|
|
|
49
|
-
#
|
|
50
|
-
tightknit
|
|
51
|
-
tightknit events list --time-filter upcoming --status published --json
|
|
52
|
-
tightknit events get <event-id>
|
|
53
|
-
tightknit events create --title "Meetup" --description "Join us" --start-date "2025-06-01T18:00:00Z" --end-date "2025-06-01T20:00:00Z"
|
|
54
|
-
tightknit events delete <event-id>
|
|
55
|
-
tightknit events update-attendee <event-id> --email user@example.com --personal-join-link "https://zoom.us/..."
|
|
15
|
+
# Or run without installing
|
|
16
|
+
npx @tightknitai/tightknit@alpha --help
|
|
56
17
|
|
|
57
|
-
#
|
|
58
|
-
tightknit
|
|
18
|
+
# Set your API key
|
|
19
|
+
tightknit config set api-key sk_your_key_here
|
|
59
20
|
|
|
60
|
-
#
|
|
21
|
+
# Try it out
|
|
61
22
|
tightknit feeds list
|
|
62
|
-
tightknit
|
|
63
|
-
|
|
64
|
-
tightknit posts get <post-id>
|
|
23
|
+
tightknit events list --time-filter upcoming --json
|
|
24
|
+
```
|
|
65
25
|
|
|
66
|
-
|
|
67
|
-
tightknit members add --email user@example.com --full-name "Jane Doe"
|
|
68
|
-
tightknit members check user@example.com
|
|
26
|
+
## Authentication
|
|
69
27
|
|
|
70
|
-
|
|
71
|
-
tightknit messages send --channel C0123456 --text "Hello from CLI!"
|
|
28
|
+
The CLI resolves your API key in this order:
|
|
72
29
|
|
|
73
|
-
|
|
74
|
-
|
|
30
|
+
1. `--api-key <key>` flag (highest precedence)
|
|
31
|
+
2. `TIGHTKNIT_API_KEY` environment variable
|
|
32
|
+
3. Stored config (`~/.config/tightknit/config.json`)
|
|
75
33
|
|
|
76
|
-
|
|
77
|
-
tightknit search query "community meetup" --type post
|
|
34
|
+
## Commands
|
|
78
35
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
36
|
+
| Command Group | Commands |
|
|
37
|
+
|---------------|----------|
|
|
38
|
+
| `events` | `list`, `get`, `create`, `delete`, `update-attendee` |
|
|
39
|
+
| `feeds` | `list`, `get`, `posts` |
|
|
40
|
+
| `posts` | `get` |
|
|
41
|
+
| `members` | `add`, `check` |
|
|
42
|
+
| `messages` | `send` |
|
|
43
|
+
| `groups` | `add-member` |
|
|
44
|
+
| `awards` | `assign` |
|
|
45
|
+
| `search` | `query` (Beta) |
|
|
46
|
+
| `config` | `set`, `get` |
|
|
47
|
+
| `completion` | `bash`, `zsh`, `fish` |
|
|
48
|
+
| `mcp` | Start the MCP server |
|
|
84
49
|
|
|
85
|
-
Every command supports `--json` for structured
|
|
50
|
+
Every command supports `--json` for structured output and `--help` for usage details.
|
|
86
51
|
|
|
87
|
-
|
|
52
|
+
## MCP Server
|
|
88
53
|
|
|
89
|
-
The MCP server exposes all CLI commands as tools
|
|
90
|
-
|
|
91
|
-
Add to your Claude Code MCP config (`.claude/settings.json` or `~/.claude.json`):
|
|
54
|
+
The built-in MCP server exposes all CLI commands as tools for AI agents.
|
|
92
55
|
|
|
93
56
|
```json
|
|
94
57
|
{
|
|
@@ -104,31 +67,6 @@ Add to your Claude Code MCP config (`.claude/settings.json` or `~/.claude.json`)
|
|
|
104
67
|
}
|
|
105
68
|
```
|
|
106
69
|
|
|
107
|
-
Or if installed locally in the monorepo:
|
|
108
|
-
|
|
109
|
-
```json
|
|
110
|
-
{
|
|
111
|
-
"mcpServers": {
|
|
112
|
-
"tightknit": {
|
|
113
|
-
"command": "npx",
|
|
114
|
-
"args": ["tsx", "packages/tightknit-cli/bin/tightknit.ts", "mcp"],
|
|
115
|
-
"cwd": "/path/to/colombo",
|
|
116
|
-
"env": {
|
|
117
|
-
"TIGHTKNIT_API_KEY": "sk_your_key_here"
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
This gives Claude access to 17 tools: `tightknit_events_list`, `tightknit_events_create`, `tightknit_feeds_list`, `tightknit_messages_send`, etc.
|
|
125
|
-
|
|
126
|
-
**Authentication:** The API key can be provided two ways:
|
|
127
|
-
- **`TIGHTKNIT_API_KEY` env var** (recommended for MCP) — pass it in the MCP config as shown above
|
|
128
|
-
- **`tightknit config set api-key <key>`** — persists to `~/.config/tightknit/config.json`, good for CLI usage
|
|
129
|
-
|
|
130
|
-
The env var takes precedence if both are set.
|
|
131
|
-
|
|
132
70
|
## Development
|
|
133
71
|
|
|
134
72
|
```bash
|
|
@@ -138,30 +76,16 @@ pnpm --filter @tightknitai/tightknit test
|
|
|
138
76
|
# Type check
|
|
139
77
|
pnpm --filter @tightknitai/tightknit typecheck
|
|
140
78
|
|
|
141
|
-
#
|
|
142
|
-
|
|
79
|
+
# Build
|
|
80
|
+
pnpm --filter @tightknitai/tightknit build
|
|
81
|
+
|
|
82
|
+
# Run in dev mode (uses tsx, no build needed)
|
|
83
|
+
pnpm --filter @tightknitai/tightknit dev
|
|
84
|
+
|
|
85
|
+
# Test MCP server with Inspector
|
|
86
|
+
pnpm --filter @tightknitai/tightknit test:mcp
|
|
143
87
|
```
|
|
144
88
|
|
|
145
|
-
##
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
| Method | Path | CLI Command |
|
|
150
|
-
|--------|------|-------------|
|
|
151
|
-
| GET | `/calendar_events` | `events list` |
|
|
152
|
-
| POST | `/calendar_events` | `events create` |
|
|
153
|
-
| GET | `/calendar_events/:id` | `events get` |
|
|
154
|
-
| DELETE | `/calendar_events/:id` | `events delete` |
|
|
155
|
-
| PATCH | `/calendar_events/:id/attendees` | `events update-attendee` |
|
|
156
|
-
| POST | `/awards/:id/assign` | `awards assign` |
|
|
157
|
-
| GET | `/feeds` | `feeds list` |
|
|
158
|
-
| GET | `/feeds/:id` | `feeds get` |
|
|
159
|
-
| GET | `/feeds/:id/posts` | `feeds posts` |
|
|
160
|
-
| GET | `/posts/:id` | `posts get` |
|
|
161
|
-
| POST | `/members` | `members add` |
|
|
162
|
-
| POST | `/members/check` | `members check` |
|
|
163
|
-
| POST | `/messages` | `messages send` |
|
|
164
|
-
| POST | `/groups/:id/members` | `groups add-member` |
|
|
165
|
-
| GET | `/search` | `search query` |
|
|
166
|
-
|
|
167
|
-
Not yet implemented: `POST /files` (file upload — requires multipart form handling).
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
Apache-2.0
|
package/bin/tightknit.js
CHANGED
|
@@ -24,8 +24,18 @@ function getDefaultOutput() {
|
|
|
24
24
|
function setDefaultOutput(format) {
|
|
25
25
|
config.set("defaultOutput", format);
|
|
26
26
|
}
|
|
27
|
+
var VALID_ENVIRONMENTS = /* @__PURE__ */ new Set(["production", "staging"]);
|
|
27
28
|
function getEnvironment() {
|
|
28
|
-
|
|
29
|
+
const envVar = process.env.TIGHTKNIT_ENVIRONMENT;
|
|
30
|
+
if (envVar) {
|
|
31
|
+
if (!VALID_ENVIRONMENTS.has(envVar)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Invalid TIGHTKNIT_ENVIRONMENT: "${envVar}". Must be "production" or "staging"`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return envVar;
|
|
37
|
+
}
|
|
38
|
+
return config.get("environment");
|
|
29
39
|
}
|
|
30
40
|
function setEnvironment(env) {
|
|
31
41
|
config.set("environment", env);
|
|
@@ -40,7 +50,9 @@ function getConfigValue(key) {
|
|
|
40
50
|
case "environment":
|
|
41
51
|
return getEnvironment();
|
|
42
52
|
default:
|
|
43
|
-
throw new Error(
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Unknown config key: ${key}. Valid keys: ${VALID_CONFIG_KEYS}`
|
|
55
|
+
);
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
function setConfigValue(key, value) {
|
|
@@ -50,18 +62,24 @@ function setConfigValue(key, value) {
|
|
|
50
62
|
break;
|
|
51
63
|
case "default-output":
|
|
52
64
|
if (value !== "json" && value !== "table") {
|
|
53
|
-
throw new Error(
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Invalid output format: ${value}. Must be "json" or "table"`
|
|
67
|
+
);
|
|
54
68
|
}
|
|
55
69
|
setDefaultOutput(value);
|
|
56
70
|
break;
|
|
57
71
|
case "environment":
|
|
58
72
|
if (value !== "production" && value !== "staging") {
|
|
59
|
-
throw new Error(
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Invalid environment: ${value}. Must be "production" or "staging"`
|
|
75
|
+
);
|
|
60
76
|
}
|
|
61
77
|
setEnvironment(value);
|
|
62
78
|
break;
|
|
63
79
|
default:
|
|
64
|
-
throw new Error(
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Unknown config key: ${key}. Valid keys: ${VALID_CONFIG_KEYS}`
|
|
82
|
+
);
|
|
65
83
|
}
|
|
66
84
|
}
|
|
67
85
|
function getConfigPath() {
|
|
@@ -72,6 +90,8 @@ function getConfigPath() {
|
|
|
72
90
|
import { readFileSync } from "fs";
|
|
73
91
|
import { fileURLToPath } from "url";
|
|
74
92
|
import { dirname, join } from "path";
|
|
93
|
+
var MissingVersionError = class extends Error {
|
|
94
|
+
};
|
|
75
95
|
function readVersion() {
|
|
76
96
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
77
97
|
for (let i = 0; i < 5; i++) {
|
|
@@ -79,13 +99,23 @@ function readVersion() {
|
|
|
79
99
|
const pkgPath = join(dir, "package.json");
|
|
80
100
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
81
101
|
if (pkg.name === "@tightknitai/tightknit") {
|
|
82
|
-
|
|
102
|
+
if (!pkg.version) {
|
|
103
|
+
throw new MissingVersionError(
|
|
104
|
+
`Found @tightknitai/tightknit package.json at ${pkgPath} but "version" field is missing`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return pkg.version;
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (err instanceof MissingVersionError) {
|
|
111
|
+
throw err;
|
|
83
112
|
}
|
|
84
|
-
} catch {
|
|
85
113
|
}
|
|
86
114
|
dir = dirname(dir);
|
|
87
115
|
}
|
|
88
|
-
|
|
116
|
+
throw new Error(
|
|
117
|
+
"Could not find @tightknitai/tightknit package.json within 5 parent directories"
|
|
118
|
+
);
|
|
89
119
|
}
|
|
90
120
|
var VERSION = readVersion();
|
|
91
121
|
|
|
@@ -96,7 +126,7 @@ var BASE_URLS = {
|
|
|
96
126
|
};
|
|
97
127
|
var API_PREFIX = "/admin/v0";
|
|
98
128
|
function getBaseUrl() {
|
|
99
|
-
return BASE_URLS[getEnvironment()];
|
|
129
|
+
return process.env.TIGHTKNIT_API_URL || BASE_URLS[getEnvironment()];
|
|
100
130
|
}
|
|
101
131
|
function buildUrl(path, params) {
|
|
102
132
|
const url = new URL(`${API_PREFIX}${path}`, getBaseUrl());
|
|
@@ -113,7 +143,7 @@ async function parseErrorResponse(response) {
|
|
|
113
143
|
let message;
|
|
114
144
|
try {
|
|
115
145
|
const body = await response.json();
|
|
116
|
-
message =
|
|
146
|
+
message = JSON.stringify(body);
|
|
117
147
|
} catch {
|
|
118
148
|
message = response.statusText;
|
|
119
149
|
}
|
|
@@ -176,11 +206,15 @@ async function listEvents(params) {
|
|
|
176
206
|
);
|
|
177
207
|
}
|
|
178
208
|
async function createEvent(input) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
209
|
+
const { description, ...rest } = input;
|
|
210
|
+
return apiRequest(
|
|
211
|
+
"/calendar_events",
|
|
212
|
+
{
|
|
213
|
+
method: "POST",
|
|
214
|
+
body: { ...rest, description: { text: description } },
|
|
215
|
+
idempotencyKey: crypto.randomUUID()
|
|
216
|
+
}
|
|
217
|
+
);
|
|
184
218
|
}
|
|
185
219
|
async function getEvent(id) {
|
|
186
220
|
return apiRequest(`/calendar_events/${id}`);
|
|
@@ -250,12 +284,16 @@ async function sendMessage(input) {
|
|
|
250
284
|
async function search(params) {
|
|
251
285
|
return apiRequest(
|
|
252
286
|
"/search",
|
|
253
|
-
{
|
|
287
|
+
{
|
|
288
|
+
params
|
|
289
|
+
}
|
|
254
290
|
);
|
|
255
291
|
}
|
|
256
292
|
|
|
257
293
|
export {
|
|
258
294
|
setRuntimeApiKey,
|
|
295
|
+
getApiKey,
|
|
296
|
+
getDefaultOutput,
|
|
259
297
|
getConfigValue,
|
|
260
298
|
setConfigValue,
|
|
261
299
|
getConfigPath,
|
|
@@ -277,4 +315,4 @@ export {
|
|
|
277
315
|
sendMessage,
|
|
278
316
|
search
|
|
279
317
|
};
|
|
280
|
-
//# sourceMappingURL=chunk-
|
|
318
|
+
//# sourceMappingURL=chunk-WSRTDKVW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/core/version.ts","../src/core/client.ts"],"sourcesContent":["import Conf from 'conf';\n\nexport type Environment = 'production' | 'staging';\n\ninterface TightknitConfig {\n apiKey: string;\n defaultOutput: 'json' | 'table';\n environment: Environment;\n}\n\nconst config = new Conf<TightknitConfig>({\n projectName: 'tightknit',\n defaults: {\n apiKey: '',\n defaultOutput: 'table',\n environment: 'production'\n }\n});\n\n/** Runtime API key set via --api-key flag (highest precedence) */\nlet runtimeApiKey: string | undefined;\n\n/** Set a runtime API key (from --api-key flag). Takes highest precedence. */\nexport function setRuntimeApiKey(key: string | undefined): void {\n runtimeApiKey = key;\n}\n\n/** Get the API key — --api-key flag > env var > stored config */\nexport function getApiKey(): string {\n return runtimeApiKey || process.env.TIGHTKNIT_API_KEY || config.get('apiKey');\n}\n\n/** Set the API key */\nexport function setApiKey(key: string): void {\n config.set('apiKey', key);\n}\n\n/** Get the default output format */\nexport function getDefaultOutput(): 'json' | 'table' {\n return config.get('defaultOutput');\n}\n\n/** Set the default output format */\nexport function setDefaultOutput(format: 'json' | 'table'): void {\n config.set('defaultOutput', format);\n}\n\nconst VALID_ENVIRONMENTS = new Set<string>(['production', 'staging']);\n\n/** Get the configured environment */\nexport function getEnvironment(): Environment {\n const envVar = process.env.TIGHTKNIT_ENVIRONMENT;\n if (envVar) {\n if (!VALID_ENVIRONMENTS.has(envVar)) {\n throw new Error(\n `Invalid TIGHTKNIT_ENVIRONMENT: \"${envVar}\". Must be \"production\" or \"staging\"`\n );\n }\n return envVar as Environment;\n }\n return config.get('environment');\n}\n\n/** Set the environment */\nexport function setEnvironment(env: Environment): void {\n config.set('environment', env);\n}\n\nconst VALID_CONFIG_KEYS = 'api-key, default-output, environment';\n\n/** Get a config value by key name */\nexport function getConfigValue(key: string): string {\n switch (key) {\n case 'api-key':\n return getApiKey();\n case 'default-output':\n return getDefaultOutput();\n case 'environment':\n return getEnvironment();\n default:\n throw new Error(\n `Unknown config key: ${key}. Valid keys: ${VALID_CONFIG_KEYS}`\n );\n }\n}\n\n/** Set a config value by key name */\nexport function setConfigValue(key: string, value: string): void {\n switch (key) {\n case 'api-key':\n setApiKey(value);\n break;\n case 'default-output':\n if (value !== 'json' && value !== 'table') {\n throw new Error(\n `Invalid output format: ${value}. Must be \"json\" or \"table\"`\n );\n }\n setDefaultOutput(value);\n break;\n case 'environment':\n if (value !== 'production' && value !== 'staging') {\n throw new Error(\n `Invalid environment: ${value}. Must be \"production\" or \"staging\"`\n );\n }\n setEnvironment(value);\n break;\n default:\n throw new Error(\n `Unknown config key: ${key}. Valid keys: ${VALID_CONFIG_KEYS}`\n );\n }\n}\n\n/** Get the config file path (useful for debugging) */\nexport function getConfigPath(): string {\n return config.path;\n}\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nclass MissingVersionError extends Error {}\n\n/** Walks up from the current module to find the CLI's package.json version */\nfunction readVersion(): string {\n let dir = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 5; i++) {\n try {\n const pkgPath = join(dir, 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as {\n name?: string;\n version?: string;\n };\n if (pkg.name === '@tightknitai/tightknit') {\n if (!pkg.version) {\n throw new MissingVersionError(\n `Found @tightknitai/tightknit package.json at ${pkgPath} but \"version\" field is missing`\n );\n }\n return pkg.version;\n }\n } catch (err) {\n if (err instanceof MissingVersionError) {\n throw err;\n }\n // Expected when traversing directories without package.json — keep walking\n }\n dir = dirname(dir);\n }\n throw new Error(\n 'Could not find @tightknitai/tightknit package.json within 5 parent directories'\n );\n}\n\nexport const VERSION = readVersion();\n","import { getApiKey, getEnvironment } from './config';\nimport type { ApiError } from './types';\n\nconst BASE_URLS = {\n production: 'https://api.tightknit.ai',\n staging: 'https://staging-api.tightknit.ai'\n} as const;\n\nconst API_PREFIX = '/admin/v0';\n\n/** TIGHTKNIT_API_URL env var takes highest precedence for custom deployments and testing */\nfunction getBaseUrl(): string {\n return process.env.TIGHTKNIT_API_URL || BASE_URLS[getEnvironment()];\n}\n\ninterface RequestOptions {\n method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';\n body?: Record<string, unknown>;\n params?: Record<string, string | number | boolean | undefined>;\n idempotencyKey?: string;\n}\n\n/** Build a URL with query parameters */\nfunction buildUrl(\n path: string,\n params?: Record<string, string | number | boolean | undefined>\n): string {\n const url = new URL(`${API_PREFIX}${path}`, getBaseUrl());\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n return url.toString();\n}\n\n/** Parse an API error response into a structured error */\nasync function parseErrorResponse(response: Response): Promise<ApiError> {\n let message: string;\n try {\n const body = (await response.json()) as Record<string, unknown>;\n message = JSON.stringify(body);\n } catch {\n message = response.statusText;\n }\n\n return {\n error: true,\n code: response.status,\n message: `${message} (${response.status})`\n };\n}\n\n/** Make an authenticated request to the Tightknit API */\nexport async function apiRequest<T>(\n path: string,\n options: RequestOptions = {}\n): Promise<T> {\n const { method = 'GET', body, params, idempotencyKey } = options;\n\n const apiKey = getApiKey();\n if (!apiKey) {\n throw new TightknitApiError(\n 'No API key configured. Run: tightknit config set api-key <YOUR_KEY>',\n 401\n );\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json'\n };\n\n if (idempotencyKey && (method === 'POST' || method === 'PATCH')) {\n headers['Idempotency-Key'] = idempotencyKey;\n }\n\n const url = buildUrl(path, params);\n\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined\n });\n\n if (!response.ok) {\n const apiError = await parseErrorResponse(response);\n throw new TightknitApiError(apiError.message, apiError.code);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n return (await response.json()) as T;\n}\n\n/** Custom error class for Tightknit API errors */\nexport class TightknitApiError extends Error {\n code: number;\n\n constructor(message: string, code: number) {\n super(message);\n this.name = 'TightknitApiError';\n this.code = code;\n }\n\n toJSON(): ApiError {\n return {\n error: true,\n code: this.code,\n message: this.message\n };\n }\n}\n\n// --- Calendar Events ---\n// Paths use underscores: /calendar_events\n\nexport interface ListEventsParams {\n page?: number;\n per_page?: number;\n time_filter?: 'upcoming' | 'past';\n date_filter?: string;\n status?: 'draft' | 'needs_approval' | 'published';\n published_to_site?: boolean;\n is_unlisted?: boolean;\n feed_id?: string;\n tag_ids?: string;\n}\n\n/**\n * List calendar events with optional filtering and pagination.\n * @param params - Optional filters for time, status, feed, pagination, etc.\n * @returns Paginated list of calendar events.\n */\nexport async function listEvents(params?: ListEventsParams) {\n return apiRequest<{ data: unknown[]; page: number; per_page: number }>(\n '/calendar_events',\n { params: params as Record<string, string | number | boolean | undefined> }\n );\n}\n\nexport interface CreateEventInput {\n title: string;\n description: string;\n start_date: string;\n end_date: string;\n location?: string;\n link?: string;\n status?: 'needs_approval' | 'published';\n slug?: string;\n publish_to_site?: boolean;\n is_unlisted?: boolean;\n allow_public_guest_list?: boolean;\n enable_registration_button?: boolean;\n triggers_webhooks?: boolean;\n external_speakers?: string;\n cover_image_file_id?: string;\n reminders_config?: number[];\n publish_to_slack_channels?: string[];\n}\n\n/**\n * Create a new calendar event.\n * @param input - Event details including title, dates, and optional settings.\n * @returns The created event's ID and success status.\n */\nexport async function createEvent(input: CreateEventInput) {\n const { description, ...rest } = input;\n return apiRequest<{ success: boolean; data: { calendar_event_id: string } }>(\n '/calendar_events',\n {\n method: 'POST',\n body: { ...rest, description: { text: description } },\n idempotencyKey: crypto.randomUUID()\n }\n );\n}\n\n/**\n * Get a single calendar event by ID.\n * @param id - The calendar event ID.\n * @returns The calendar event details.\n */\nexport async function getEvent(id: string) {\n return apiRequest<unknown>(`/calendar_events/${id}`);\n}\n\n/**\n * Delete a calendar event by ID.\n * @param id - The calendar event ID.\n * @returns void on success (204 response).\n */\nexport async function deleteEvent(id: string) {\n return apiRequest<void>(`/calendar_events/${id}`, { method: 'DELETE' });\n}\n\nexport interface UpdateAttendeeInput {\n user: { slack_user_id: string } | { email: string } | { profile_id: string };\n personal_join_link?: string;\n}\n\n/**\n * Update or add an attendee for a calendar event.\n * @param eventId - The calendar event ID.\n * @param input - Attendee user identifier and optional join link.\n * @returns The updated attendee details.\n */\nexport async function updateAttendee(\n eventId: string,\n input: UpdateAttendeeInput\n) {\n return apiRequest<unknown>(`/calendar_events/${eventId}/attendees`, {\n method: 'PATCH',\n body: input as unknown as Record<string, unknown>,\n idempotencyKey: crypto.randomUUID()\n });\n}\n\n// --- Awards ---\n\nexport interface AssignAwardInput {\n recipient:\n | { slack_user_id: string }\n | { email: string }\n | { profile_id: string };\n sender?:\n | { slack_user_id: string }\n | { email: string }\n | { profile_id: string }\n | null;\n send_anonymously?: boolean;\n}\n\n/**\n * Assign an award to a recipient.\n * @param awardId - The award ID to assign.\n * @param input - Recipient identifier and optional sender/anonymity settings.\n * @returns Success status.\n */\nexport async function assignAward(awardId: string, input: AssignAwardInput) {\n return apiRequest<{ success: boolean }>(`/awards/${awardId}/assign`, {\n method: 'POST',\n body: input as unknown as Record<string, unknown>,\n idempotencyKey: crypto.randomUUID()\n });\n}\n\n// --- Feeds ---\n\nexport interface ListFeedsParams {\n page?: number;\n per_page?: number;\n is_unlisted?: boolean;\n is_archived?: boolean;\n}\n\n/**\n * List feeds with optional filtering and pagination.\n * @param params - Optional filters for unlisted/archived status and pagination.\n * @returns Paginated list of feeds.\n */\nexport async function listFeeds(params?: ListFeedsParams) {\n return apiRequest<{ data: unknown[]; page: number; per_page: number }>(\n '/feeds',\n { params: params as Record<string, string | number | boolean | undefined> }\n );\n}\n\n/**\n * Get a single feed by ID.\n * @param feedId - The feed ID.\n * @returns The feed details.\n */\nexport async function getFeed(feedId: string) {\n return apiRequest<unknown>(`/feeds/${feedId}`);\n}\n\nexport interface ListPostsInFeedParams {\n page?: number;\n per_page?: number;\n sort?: 'oldest' | 'newest' | 'most-recent-activity';\n}\n\n/**\n * List posts within a feed with optional sorting and pagination.\n * @param feedId - The feed ID to list posts from.\n * @param params - Optional sort order and pagination.\n * @returns Paginated list of posts.\n */\nexport async function listPostsInFeed(\n feedId: string,\n params?: ListPostsInFeedParams\n) {\n return apiRequest<{ data: unknown[]; page: number; per_page: number }>(\n `/feeds/${feedId}/posts`,\n { params: params as Record<string, string | number | boolean | undefined> }\n );\n}\n\n// --- Posts ---\n\n/**\n * Get a single post by ID.\n * @param postId - The post ID.\n * @returns The post details.\n */\nexport async function getPost(postId: string) {\n return apiRequest<unknown>(`/posts/${postId}`);\n}\n\n// --- Members ---\n\nexport interface AddMemberInput {\n email: string;\n full_name: string;\n avatar_url_original?: string;\n}\n\n/**\n * Add a new member to the community (Enterprise feature).\n * @param input - Member details including email, name, and optional avatar.\n * @returns The created member details.\n */\nexport async function addMember(input: AddMemberInput) {\n return apiRequest<unknown>('/members', {\n method: 'POST',\n body: input as unknown as Record<string, unknown>,\n idempotencyKey: crypto.randomUUID()\n });\n}\n\n/**\n * Check if a user is a member of the community by email.\n * @param email - The email address to check.\n * @returns Membership status and details.\n */\nexport async function checkMembership(email: string) {\n return apiRequest<unknown>('/members/check', {\n method: 'POST',\n body: { email }\n });\n}\n\n// --- Groups ---\n\nexport interface AddUserToGroupInput {\n user: { slack_user_id: string } | { email: string } | { profile_id: string };\n}\n\n/**\n * Add a user to a group.\n * @param groupId - The group ID.\n * @param input - User identifier to add to the group.\n * @returns The updated group membership details.\n */\nexport async function addUserToGroup(\n groupId: string,\n input: AddUserToGroupInput\n) {\n return apiRequest<unknown>(`/groups/${groupId}/members`, {\n method: 'POST',\n body: input as unknown as Record<string, unknown>,\n idempotencyKey: crypto.randomUUID()\n });\n}\n\n// --- Messages ---\n\nexport interface SendMessageInput {\n channel: string;\n text: string;\n thread_ts?: string;\n}\n\n/**\n * Send a message to a Slack channel.\n * @param input - Channel, message text, and optional thread timestamp.\n * @returns The sent message details.\n */\nexport async function sendMessage(input: SendMessageInput) {\n return apiRequest<unknown>('/messages', {\n method: 'POST',\n body: input as unknown as Record<string, unknown>,\n idempotencyKey: crypto.randomUUID()\n });\n}\n\n// --- Search ---\n\nexport interface SearchParams {\n q: string;\n type: 'post' | 'comment' | 'content_resource';\n page?: number;\n per_page?: number;\n}\n\n/**\n * Search across community content (Beta).\n * @param params - Search query, content type, and optional pagination.\n * @returns Paginated search results.\n */\nexport async function search(params: SearchParams) {\n return apiRequest<{ data: unknown[]; page: number; per_page: number }>(\n '/search',\n {\n params: params as unknown as Record<\n string,\n string | number | boolean | undefined\n >\n }\n );\n}\n"],"mappings":"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","names":[]}
|