@tightknitai/tightknit 0.1.0-alpha.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/README.md +167 -0
- package/bin/tightknit-mcp.js +14 -0
- package/bin/tightknit.js +14 -0
- package/bin/tightknit.ts +12 -0
- package/package.json +47 -0
- package/src/cli/awards/assign.ts +28 -0
- package/src/cli/config/get.ts +27 -0
- package/src/cli/config/set.ts +23 -0
- package/src/cli/error-handler.ts +26 -0
- package/src/cli/events/create.ts +45 -0
- package/src/cli/events/delete.ts +21 -0
- package/src/cli/events/get.ts +17 -0
- package/src/cli/events/list.ts +29 -0
- package/src/cli/events/update.ts +27 -0
- package/src/cli/feeds/get.ts +17 -0
- package/src/cli/feeds/list.ts +25 -0
- package/src/cli/feeds/posts.ts +24 -0
- package/src/cli/groups/add-member.ts +24 -0
- package/src/cli/index.ts +99 -0
- package/src/cli/members/add.ts +28 -0
- package/src/cli/members/check.ts +17 -0
- package/src/cli/messages/send.ts +27 -0
- package/src/cli/posts/get.ts +17 -0
- package/src/cli/search/query.ts +25 -0
- package/src/core/client.ts +297 -0
- package/src/core/config.ts +85 -0
- package/src/core/output.ts +79 -0
- package/src/core/types.ts +79 -0
- package/src/mcp/server.ts +14 -0
- package/src/mcp/tools.ts +275 -0
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# @tightknitai/tightknit
|
|
2
|
+
|
|
3
|
+
Command-line interface and MCP server for the [Tightknit API](https://docs.tightknit.ai/api-reference/introduction).
|
|
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
|
|
33
|
+
|
|
34
|
+
# Or from the monorepo root
|
|
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).
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### CLI Commands
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Alias for convenience
|
|
47
|
+
alias tightknit="npx tsx packages/tightknit-cli/bin/tightknit.ts"
|
|
48
|
+
|
|
49
|
+
# Calendar Events
|
|
50
|
+
tightknit events list
|
|
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/..."
|
|
56
|
+
|
|
57
|
+
# Awards
|
|
58
|
+
tightknit awards assign <award-uuid> --recipient-email user@example.com
|
|
59
|
+
|
|
60
|
+
# Feeds & Posts
|
|
61
|
+
tightknit feeds list
|
|
62
|
+
tightknit feeds get <feed-id>
|
|
63
|
+
tightknit feeds posts <feed-id> --sort newest
|
|
64
|
+
tightknit posts get <post-id>
|
|
65
|
+
|
|
66
|
+
# Members
|
|
67
|
+
tightknit members add --email user@example.com --full-name "Jane Doe"
|
|
68
|
+
tightknit members check user@example.com
|
|
69
|
+
|
|
70
|
+
# Messages
|
|
71
|
+
tightknit messages send --channel C0123456 --text "Hello from CLI!"
|
|
72
|
+
|
|
73
|
+
# Groups
|
|
74
|
+
tightknit groups add-member <group-id> --email user@example.com
|
|
75
|
+
|
|
76
|
+
# Search (Beta)
|
|
77
|
+
tightknit search query "community meetup" --type post
|
|
78
|
+
|
|
79
|
+
# Config
|
|
80
|
+
tightknit config set api-key <key>
|
|
81
|
+
tightknit config get api-key
|
|
82
|
+
tightknit config set default-output json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Every command supports `--json` for structured JSON output and `--help` for usage info.
|
|
86
|
+
|
|
87
|
+
### MCP Server (for Claude Code)
|
|
88
|
+
|
|
89
|
+
The MCP server exposes all CLI commands as tools over STDIO.
|
|
90
|
+
|
|
91
|
+
Add to your Claude Code MCP config (`.claude/settings.json` or `~/.claude.json`):
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"tightknit": {
|
|
97
|
+
"command": "npx",
|
|
98
|
+
"args": ["-p", "@tightknitai/tightknit@alpha", "tightknit-mcp"],
|
|
99
|
+
"env": {
|
|
100
|
+
"TIGHTKNIT_API_KEY": "sk_your_key_here"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
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/src/mcp/server.ts"],
|
|
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
|
+
## Development
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Run tests
|
|
136
|
+
pnpm --filter @tightknitai/tightknit test
|
|
137
|
+
|
|
138
|
+
# Type check
|
|
139
|
+
pnpm --filter @tightknitai/tightknit typecheck
|
|
140
|
+
|
|
141
|
+
# Run CLI in dev mode
|
|
142
|
+
npx tsx packages/tightknit-cli/bin/tightknit.ts --help
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## API Coverage
|
|
146
|
+
|
|
147
|
+
All endpoints from the [Tightknit Admin API](https://api.tightknit.ai/doc) (`/admin/v0/`) are supported:
|
|
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).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const tsx = join(__dirname, "..", "node_modules", ".bin", "tsx");
|
|
8
|
+
const script = join(__dirname, "..", "src", "mcp", "server.ts");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
execFileSync(tsx, [script], { stdio: "inherit" });
|
|
12
|
+
} catch (err) {
|
|
13
|
+
process.exit(err.status ?? 1);
|
|
14
|
+
}
|
package/bin/tightknit.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const tsx = join(__dirname, "..", "node_modules", ".bin", "tsx");
|
|
8
|
+
const script = join(__dirname, "tightknit.ts");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
execFileSync(tsx, [script, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
12
|
+
} catch (err) {
|
|
13
|
+
process.exit(err.status ?? 1);
|
|
14
|
+
}
|
package/bin/tightknit.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { createProgram } from "../src/cli/index";
|
|
3
|
+
|
|
4
|
+
const program = createProgram();
|
|
5
|
+
|
|
6
|
+
// Set version from package.json dynamically
|
|
7
|
+
// Using a static version here to avoid JSON import complexity
|
|
8
|
+
program.version("0.1.0-alpha.0");
|
|
9
|
+
|
|
10
|
+
program.parseAsync(process.argv).catch(() => {
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tightknitai/tightknit",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "CLI and MCP server for the Tightknit API",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.0.0"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"tightknit": "bin/tightknit.js",
|
|
12
|
+
"tightknit-mcp": "bin/tightknit-mcp.js"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./src/cli/index.ts",
|
|
16
|
+
"./mcp": "./src/mcp/server.ts"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.build.json",
|
|
25
|
+
"dev": "tsx --watch bin/tightknit.ts",
|
|
26
|
+
"start": "tsx bin/tightknit.ts",
|
|
27
|
+
"start:mcp": "tsx src/mcp/server.ts",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:unit": "vitest run",
|
|
30
|
+
"test:unit-silent": "vitest run --reporter=dot",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
35
|
+
"chalk": "^5.4.1",
|
|
36
|
+
"cli-table3": "^0.6.5",
|
|
37
|
+
"commander": "^12.1.0",
|
|
38
|
+
"conf": "^13.0.1",
|
|
39
|
+
"tsx": "^4.19.4",
|
|
40
|
+
"zod": "^3.24.4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.15.2",
|
|
44
|
+
"typescript": "^5.8.3",
|
|
45
|
+
"vitest": "^3.1.2"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { assignAward } from "../../core/client";
|
|
3
|
+
import { printOutput, printSuccess } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const assignAwardCommand = new Command("assign")
|
|
7
|
+
.description("Assign an award to a user")
|
|
8
|
+
.argument("<award-id>", "Award UUID")
|
|
9
|
+
.requiredOption("--recipient-email <email>", "Recipient email address")
|
|
10
|
+
.option("--sender-email <email>", "Sender email address")
|
|
11
|
+
.option("--anonymous", "Send the award anonymously")
|
|
12
|
+
.option("--json", "Output as JSON")
|
|
13
|
+
.action(async (awardId: string, opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const result = await assignAward(awardId, {
|
|
16
|
+
recipient: { email: opts.recipientEmail },
|
|
17
|
+
sender: opts.senderEmail ? { email: opts.senderEmail } : undefined,
|
|
18
|
+
send_anonymously: opts.anonymous,
|
|
19
|
+
});
|
|
20
|
+
if (opts.json) {
|
|
21
|
+
printOutput(result, { json: true });
|
|
22
|
+
} else {
|
|
23
|
+
printSuccess("Award assigned successfully");
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
handleCommandError(error, opts.json);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getConfigPath, getConfigValue } from "../../core/config";
|
|
3
|
+
import { printOutput } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const getConfigCommand = new Command("get")
|
|
7
|
+
.description("Get a configuration value")
|
|
8
|
+
.argument("<key>", "Config key (api-key, community-id, default-output)")
|
|
9
|
+
.option("--json", "Output as JSON")
|
|
10
|
+
.option("--show-path", "Show the config file path")
|
|
11
|
+
.action(async (key: string, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
if (opts.showPath) {
|
|
14
|
+
console.log(getConfigPath());
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const value = getConfigValue(key);
|
|
18
|
+
const displayValue = key === "api-key" && value ? `${value.slice(0, 8)}...` : value;
|
|
19
|
+
if (opts.json) {
|
|
20
|
+
printOutput({ key, value: displayValue }, { json: true });
|
|
21
|
+
} else {
|
|
22
|
+
console.log(displayValue || "(not set)");
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
handleCommandError(error, opts.json);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { setConfigValue } from "../../core/config";
|
|
3
|
+
import { printSuccess } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const setConfigCommand = new Command("set")
|
|
7
|
+
.description("Set a configuration value")
|
|
8
|
+
.argument("<key>", "Config key (api-key, community-id, default-output)")
|
|
9
|
+
.argument("<value>", "Config value")
|
|
10
|
+
.option("--json", "Output as JSON")
|
|
11
|
+
.action(async (key: string, value: string, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
setConfigValue(key, value);
|
|
14
|
+
if (opts.json) {
|
|
15
|
+
console.log(JSON.stringify({ success: true, key, value: key === "api-key" ? "***" : value }, null, 2));
|
|
16
|
+
} else {
|
|
17
|
+
const displayValue = key === "api-key" ? "***" : value;
|
|
18
|
+
printSuccess(`Set ${key} = ${displayValue}`);
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
handleCommandError(error, opts.json);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TightknitApiError } from "../core/client";
|
|
2
|
+
import { printError } from "../core/output";
|
|
3
|
+
|
|
4
|
+
/** Handle errors in CLI commands with consistent formatting */
|
|
5
|
+
export function handleCommandError(error: unknown, json?: boolean): never {
|
|
6
|
+
if (error instanceof TightknitApiError) {
|
|
7
|
+
if (json) {
|
|
8
|
+
console.error(JSON.stringify(error.toJSON(), null, 2));
|
|
9
|
+
} else {
|
|
10
|
+
printError(error.message);
|
|
11
|
+
}
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (error instanceof Error) {
|
|
16
|
+
if (json) {
|
|
17
|
+
console.error(JSON.stringify({ error: true, message: error.message }, null, 2));
|
|
18
|
+
} else {
|
|
19
|
+
printError(error.message);
|
|
20
|
+
}
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
printError("An unknown error occurred");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { createEvent } from "../../core/client";
|
|
3
|
+
import { printOutput, printSuccess } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const createEventCommand = new Command("create")
|
|
7
|
+
.description("Create a new calendar event")
|
|
8
|
+
.requiredOption("--title <title>", "Event title (3-70 chars)")
|
|
9
|
+
.requiredOption("--description <text>", "Event description")
|
|
10
|
+
.requiredOption("--start-date <datetime>", "Start date/time (ISO 8601)")
|
|
11
|
+
.requiredOption("--end-date <datetime>", "End date/time (ISO 8601)")
|
|
12
|
+
.option("--location <location>", "Event location")
|
|
13
|
+
.option("--link <url>", "Event link/URL")
|
|
14
|
+
.option("--slug <slug>", "URL slug (3-70 chars)")
|
|
15
|
+
.option("--status <status>", "Status: needs_approval or published", "published")
|
|
16
|
+
.option("--publish-to-site", "Publish to companion site")
|
|
17
|
+
.option("--no-publish-to-site", "Do not publish to companion site")
|
|
18
|
+
.option("--enable-registration", "Enable registration button")
|
|
19
|
+
.option("--triggers-webhooks", "Trigger webhooks on creation")
|
|
20
|
+
.option("--json", "Output as JSON")
|
|
21
|
+
.action(async (opts) => {
|
|
22
|
+
try {
|
|
23
|
+
const result = await createEvent({
|
|
24
|
+
title: opts.title,
|
|
25
|
+
description: opts.description,
|
|
26
|
+
start_date: opts.startDate,
|
|
27
|
+
end_date: opts.endDate,
|
|
28
|
+
location: opts.location,
|
|
29
|
+
link: opts.link,
|
|
30
|
+
slug: opts.slug,
|
|
31
|
+
status: opts.status,
|
|
32
|
+
publish_to_site: opts.publishToSite,
|
|
33
|
+
enable_registration_button: opts.enableRegistration,
|
|
34
|
+
triggers_webhooks: opts.triggersWebhooks,
|
|
35
|
+
});
|
|
36
|
+
if (opts.json) {
|
|
37
|
+
printOutput(result, { json: true });
|
|
38
|
+
} else {
|
|
39
|
+
printSuccess(`Event created: ${result.data.calendar_event_id}`);
|
|
40
|
+
printOutput(result, {});
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
handleCommandError(error, opts.json);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { deleteEvent } from "../../core/client";
|
|
3
|
+
import { printSuccess } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const deleteEventCommand = new Command("delete")
|
|
7
|
+
.description("Delete a calendar event")
|
|
8
|
+
.argument("<id>", "Event ID")
|
|
9
|
+
.option("--json", "Output as JSON")
|
|
10
|
+
.action(async (id: string, opts) => {
|
|
11
|
+
try {
|
|
12
|
+
await deleteEvent(id);
|
|
13
|
+
if (opts.json) {
|
|
14
|
+
console.log(JSON.stringify({ success: true, message: "Event deleted" }, null, 2));
|
|
15
|
+
} else {
|
|
16
|
+
printSuccess("Event deleted successfully");
|
|
17
|
+
}
|
|
18
|
+
} catch (error) {
|
|
19
|
+
handleCommandError(error, opts.json);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getEvent } from "../../core/client";
|
|
3
|
+
import { printOutput } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const getEventCommand = new Command("get")
|
|
7
|
+
.description("Get a calendar event by ID")
|
|
8
|
+
.argument("<id>", "Event ID")
|
|
9
|
+
.option("--json", "Output as JSON")
|
|
10
|
+
.action(async (id: string, opts) => {
|
|
11
|
+
try {
|
|
12
|
+
const result = await getEvent(id);
|
|
13
|
+
printOutput(result, { json: opts.json });
|
|
14
|
+
} catch (error) {
|
|
15
|
+
handleCommandError(error, opts.json);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { listEvents } from "../../core/client";
|
|
3
|
+
import { printOutput } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const listEventsCommand = new Command("list")
|
|
7
|
+
.description("List calendar events")
|
|
8
|
+
.option("--page <number>", "Page number (0-indexed)", "0")
|
|
9
|
+
.option("--per-page <number>", "Records per page", "25")
|
|
10
|
+
.option("--time-filter <filter>", "Filter by time: upcoming or past")
|
|
11
|
+
.option("--status <status>", "Filter by status: draft, needs_approval, published")
|
|
12
|
+
.option("--feed-id <id>", "Filter by feed ID")
|
|
13
|
+
.option("--tag-ids <ids>", "Comma-separated tag UUIDs")
|
|
14
|
+
.option("--json", "Output as JSON")
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
try {
|
|
17
|
+
const result = await listEvents({
|
|
18
|
+
page: Number(opts.page),
|
|
19
|
+
per_page: Number(opts.perPage),
|
|
20
|
+
time_filter: opts.timeFilter,
|
|
21
|
+
status: opts.status,
|
|
22
|
+
feed_id: opts.feedId,
|
|
23
|
+
tag_ids: opts.tagIds,
|
|
24
|
+
});
|
|
25
|
+
printOutput(result.data, { json: opts.json });
|
|
26
|
+
} catch (error) {
|
|
27
|
+
handleCommandError(error, opts.json);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { updateAttendee } from "../../core/client";
|
|
3
|
+
import { printOutput, printSuccess } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const updateAttendeeCommand = new Command("update-attendee")
|
|
7
|
+
.description("Update a calendar event attendee")
|
|
8
|
+
.argument("<event-id>", "Calendar event ID")
|
|
9
|
+
.requiredOption("--email <email>", "Attendee email (user identifier)")
|
|
10
|
+
.option("--personal-join-link <url>", "Personal join link for the attendee")
|
|
11
|
+
.option("--json", "Output as JSON")
|
|
12
|
+
.action(async (eventId: string, opts) => {
|
|
13
|
+
try {
|
|
14
|
+
const result = await updateAttendee(eventId, {
|
|
15
|
+
user: { email: opts.email },
|
|
16
|
+
personal_join_link: opts.personalJoinLink,
|
|
17
|
+
});
|
|
18
|
+
if (opts.json) {
|
|
19
|
+
printOutput(result, { json: true });
|
|
20
|
+
} else {
|
|
21
|
+
printSuccess("Attendee updated successfully");
|
|
22
|
+
printOutput(result, {});
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
handleCommandError(error, opts.json);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getFeed } from "../../core/client";
|
|
3
|
+
import { printOutput } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const getFeedCommand = new Command("get")
|
|
7
|
+
.description("Retrieve a feed by ID")
|
|
8
|
+
.argument("<feed-id>", "Feed ID")
|
|
9
|
+
.option("--json", "Output as JSON")
|
|
10
|
+
.action(async (feedId: string, opts) => {
|
|
11
|
+
try {
|
|
12
|
+
const result = await getFeed(feedId);
|
|
13
|
+
printOutput(result, { json: opts.json });
|
|
14
|
+
} catch (error) {
|
|
15
|
+
handleCommandError(error, opts.json);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { listFeeds } from "../../core/client";
|
|
3
|
+
import { printOutput } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const listFeedsCommand = new Command("list")
|
|
7
|
+
.description("List feeds")
|
|
8
|
+
.option("--page <number>", "Page number (0-indexed)", "0")
|
|
9
|
+
.option("--per-page <number>", "Records per page", "25")
|
|
10
|
+
.option("--is-unlisted", "Filter to unlisted feeds only")
|
|
11
|
+
.option("--is-archived", "Filter to archived feeds only")
|
|
12
|
+
.option("--json", "Output as JSON")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const result = await listFeeds({
|
|
16
|
+
page: Number(opts.page),
|
|
17
|
+
per_page: Number(opts.perPage),
|
|
18
|
+
is_unlisted: opts.isUnlisted,
|
|
19
|
+
is_archived: opts.isArchived,
|
|
20
|
+
});
|
|
21
|
+
printOutput(result.data, { json: opts.json });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
handleCommandError(error, opts.json);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { listPostsInFeed } from "../../core/client";
|
|
3
|
+
import { printOutput } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const listPostsInFeedCommand = new Command("posts")
|
|
7
|
+
.description("List posts in a feed")
|
|
8
|
+
.argument("<feed-id>", 'Feed ID or "home" for the Home feed')
|
|
9
|
+
.option("--page <number>", "Page number (0-indexed)", "0")
|
|
10
|
+
.option("--per-page <number>", "Records per page", "25")
|
|
11
|
+
.option("--sort <method>", "Sort: oldest, newest, most-recent-activity")
|
|
12
|
+
.option("--json", "Output as JSON")
|
|
13
|
+
.action(async (feedId: string, opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const result = await listPostsInFeed(feedId, {
|
|
16
|
+
page: Number(opts.page),
|
|
17
|
+
per_page: Number(opts.perPage),
|
|
18
|
+
sort: opts.sort,
|
|
19
|
+
});
|
|
20
|
+
printOutput(result.data, { json: opts.json });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
handleCommandError(error, opts.json);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { addUserToGroup } from "../../core/client";
|
|
3
|
+
import { printOutput, printSuccess } from "../../core/output";
|
|
4
|
+
import { handleCommandError } from "../error-handler";
|
|
5
|
+
|
|
6
|
+
export const addGroupMemberCommand = new Command("add-member")
|
|
7
|
+
.description("Add a user to a group")
|
|
8
|
+
.argument("<group-id>", "Group ID")
|
|
9
|
+
.requiredOption("--email <email>", "User email address")
|
|
10
|
+
.option("--json", "Output as JSON")
|
|
11
|
+
.action(async (groupId: string, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
const result = await addUserToGroup(groupId, {
|
|
14
|
+
user: { email: opts.email },
|
|
15
|
+
});
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
printOutput(result, { json: true });
|
|
18
|
+
} else {
|
|
19
|
+
printSuccess("User added to group");
|
|
20
|
+
}
|
|
21
|
+
} catch (error) {
|
|
22
|
+
handleCommandError(error, opts.json);
|
|
23
|
+
}
|
|
24
|
+
});
|